@agentkai/browser
Version:
AgentKai的浏览器环境特定实现
323 lines (322 loc) • 10.5 kB
JavaScript
import { PlatformType, } from '@agentkai/core';
/**
* 浏览器环境的文件系统实现,使用IndexedDB
*/
export class IndexedDBFileSystem {
constructor(dbName, storeName) {
Object.defineProperty(this, "db", {
enumerable: true,
configurable: true,
writable: true,
value: null
});
Object.defineProperty(this, "indexedDB", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "dbName", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "storeName", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.indexedDB = window.indexedDB || window.mozIndexedDB ||
window.webkitIndexedDB || window.msIndexedDB;
this.dbName = dbName;
this.storeName = storeName;
}
async getDB() {
if (this.db) {
return this.db;
}
return new Promise((resolve, reject) => {
const request = this.indexedDB.open(this.dbName, 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName, { keyPath: 'path' });
}
};
request.onsuccess = (event) => {
this.db = event.target.result;
resolve(this.db);
};
request.onerror = (event) => {
reject(new Error(`无法打开数据库: ${event.target.errorCode}`));
};
});
}
async getStore(mode = 'readonly') {
const db = await this.getDB();
const transaction = db.transaction([this.storeName], mode);
return transaction.objectStore(this.storeName);
}
async readFile(path) {
const store = await this.getStore();
return new Promise((resolve, reject) => {
const request = store.get(path);
request.onsuccess = () => {
const result = request.result;
if (!result) {
reject(new Error(`文件不存在: ${path}`));
return;
}
resolve(result.content);
};
request.onerror = () => {
reject(new Error(`读取文件失败: ${path}`));
};
});
}
async writeFile(path, data) {
const store = await this.getStore('readwrite');
return new Promise((resolve, reject) => {
const request = store.put({ path, content: data, timestamp: Date.now() });
request.onsuccess = () => {
resolve();
};
request.onerror = () => {
reject(new Error(`写入文件失败: ${path}`));
};
});
}
async exists(path) {
const store = await this.getStore();
return new Promise((resolve) => {
const request = store.get(path);
request.onsuccess = () => {
resolve(!!request.result);
};
request.onerror = () => {
resolve(false);
};
});
}
async mkdir(path, _options) {
// 在浏览器环境中,我们只需要确保文件目录的路径存在
// 由于IndexedDB没有目录结构,我们实际上不需要创建目录
// 但为了兼容性,我们创建一个特殊的标记文件以表示目录存在
const dirMarker = `${path}/.dir`;
await this.writeFile(dirMarker, '');
}
async readdir(path) {
const store = await this.getStore();
return new Promise((resolve, reject) => {
const request = store.openCursor();
const files = [];
// 确保dirPath以/结尾以便正确匹配
const normalizedDirPath = path.endsWith('/') ? path : `${path}/`;
request.onerror = () => {
reject(new Error(`读取目录失败: ${path}`));
};
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
const filePath = cursor.value.path;
// 检查是否是该目录下的文件/子目录
if (filePath.startsWith(normalizedDirPath) && filePath !== `${path}/.dir`) {
// 提取文件/目录名
const relativePath = filePath.slice(normalizedDirPath.length);
// 只包含一级子项
if (!relativePath.includes('/')) {
files.push(relativePath);
}
}
cursor.continue();
}
else {
resolve(files);
}
};
});
}
async unlink(path) {
const store = await this.getStore('readwrite');
return new Promise((resolve, reject) => {
const request = store.delete(path);
request.onsuccess = () => {
resolve();
};
request.onerror = () => {
reject(new Error(`删除文件失败: ${path}`));
};
});
}
async stat(path) {
const isDir = await this.exists(`${path}/.dir`);
return {
isDirectory: () => {
return isDir || path === '/';
}
};
}
}
/**
* 浏览器环境的环境变量提供者实现,使用localStorage
*/
export class BrowserEnvProvider {
constructor() {
Object.defineProperty(this, "STORAGE_KEY_PREFIX", {
enumerable: true,
configurable: true,
writable: true,
value: 'agentkai_env_'
});
}
get(key, defaultValue) {
const value = localStorage.getItem(this.STORAGE_KEY_PREFIX + key);
return value !== null ? value : defaultValue;
}
set(key, value) {
localStorage.setItem(this.STORAGE_KEY_PREFIX + key, value);
}
getAll() {
const env = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith(this.STORAGE_KEY_PREFIX)) {
const envKey = key.slice(this.STORAGE_KEY_PREFIX.length);
const value = localStorage.getItem(key);
if (value !== null) {
env[envKey] = value;
}
}
}
return env;
}
}
/**
* 浏览器环境的路径工具实现
*/
export class BrowserPathUtils {
home() {
// 在浏览器中,没有真正的主目录概念
return '/home/browser-user';
}
join(...paths) {
// 简单的路径拼接实现
return paths
.filter(Boolean)
.join('/')
.replace(/\/+/g, '/'); // 替换多个连续的/为单个/
}
resolve(...paths) {
// 浏览器中的路径解析简化实现
const segments = [];
for (const path of paths) {
if (path.startsWith('/')) {
// 绝对路径,重置segments
segments.length = 0;
}
const parts = path.split('/');
for (const part of parts) {
if (part === '.' || part === '')
continue;
if (part === '..') {
segments.pop();
}
else {
segments.push(part);
}
}
}
return '/' + segments.join('/');
}
dirname(pathString) {
if (!pathString.includes('/'))
return '.';
// 移除末尾的/
pathString = pathString.replace(/\/+$/, '');
return pathString.substring(0, pathString.lastIndexOf('/')) || '/';
}
basename(pathString) {
// 移除末尾的/
pathString = pathString.replace(/\/+$/, '');
return pathString.substring(pathString.lastIndexOf('/') + 1) || '';
}
extname(pathString) {
const basename = this.basename(pathString);
const dotIndex = basename.lastIndexOf('.');
return dotIndex > 0 ? basename.substring(dotIndex) : '';
}
}
/**
* 浏览器环境的平台信息实现
*/
export class BrowserPlatformInfo {
homeDir() {
// 在浏览器中,没有真正的主目录概念
return '/home/browser-user';
}
platform() {
return 'browser';
}
isNode() {
return false;
}
isBrowser() {
return true;
}
tmpdir() {
// 浏览器中没有临时目录的概念
return '/tmp';
}
cwd() {
// 浏览器中没有当前工作目录的概念
return '/';
}
}
/**
* 浏览器环境的平台服务实现
*/
export class BrowserPlatformServices {
constructor() {
Object.defineProperty(this, "type", {
enumerable: true,
configurable: true,
writable: true,
value: PlatformType.BROWSER
});
Object.defineProperty(this, "fs", {
enumerable: true,
configurable: true,
writable: true,
value: new IndexedDBFileSystem('agentkai-fs', 'files')
});
Object.defineProperty(this, "env", {
enumerable: true,
configurable: true,
writable: true,
value: new BrowserEnvProvider()
});
Object.defineProperty(this, "path", {
enumerable: true,
configurable: true,
writable: true,
value: new BrowserPathUtils()
});
Object.defineProperty(this, "platformInfo", {
enumerable: true,
configurable: true,
writable: true,
value: new BrowserPlatformInfo()
});
}
}
/**
* 浏览器环境的平台服务工厂
*/
export class BrowserPlatformServiceFactory {
create() {
return new BrowserPlatformServices();
}
}
export const platform = new BrowserPlatformServiceFactory().create();