记录一次AI底座的设计开发尝试
文章目录
引言
之前每次做一个新的AI应用,都要重新包装claude agent sdk,有可能还想用的别的agent sdk。甚至有时候需要加入mqtt。那么能不能做一个基座应用呢,类似于微信小程序,小程序能力是基座,开发者关注自己的应用,而不是关注如何实现和手机的交互。所以AI底座的能力也是类似,底座上可以注册应用,只关注应用的开发而非AI能力的接入。应用可以调用底座的各种能力。下面是详细记录。
思考,如果一个平台,如微信,飞书,等提供基础AI能力,开发者调用公共能力,专注业务开发,再搞个应用市场。
技术栈
electron+shadcn/ui+react。
架构

底座负责提供公共能力
通过注册子应用(两种模式,远程link,本地html)
打开应用的时候装载preload.js提供底座能力。(后面需要区分子应用自己的,现在是全部能力,有些子应用是不需要的)。


暴露的方法
可以通过ipc或者mqtt来调用,不过mqtt模式还需要配置mqtt的连接信息。
Preload / eventCenter API 使用说明(渲染进程可调用)
本项目通过 Electron preload 在所有窗口注入 window.eventCenter,用于从渲染进程安全地调用主进程能力(IPC)。本节是“使用说明”风格的文档:你可以直接照着示例写业务代码。
1)在哪里能用?
- 主窗口(React/Vite 渲染页)可用
- 通过
apps.open / openAppPopup / replaceWithAppPopup打开的应用窗口也可用(同样注入 preload)
2)错误与登录失效处理
- 所有方法失败都会
throw Error,建议统一try/catch处理 - 如果主进程返回
authInvalid,preload 会触发浏览器事件eventCenter:authInvalid(用于自动退出到登录页)
window.addEventListener("eventCenter:authInvalid", async () => {
// 这里按你的路由/状态管理做“强制退出到登录页”
})
async function safeCall<T>(fn: () => Promise<T>): Promise<T | null> {
try {
return await fn()
} catch (e) {
console.error(e)
return null
}
}
3)两种调用方式:推荐直接方法 / 通用 invoke
3.1 推荐:直接方法(更像 SDK)
直接调用 window.eventCenter.xxx(),不需要关心 service/method 的名字。
3.2 通用:invoke(service, method, args?)
当你需要调用未封装的能力,使用:
await window.eventCenter.invoke("apps", "list", [])
await window.eventCenter.invoke("modules", "listModels", [])
4)API 速查(preload 直接暴露)
说明:入参写成
{ ... }表示对象;pathSuffix | path两者任选其一即可。
| 方法 | 用途 | 入参 | 返回 |
|---|---|---|---|
getClientConfig() | 获取本机配置(登录态、环境模式、设备码等) | - | { mode, userKey, userNickname, deviceCode, mqtt, setupCompleted, mqttEnabled, mqttDisabledReason } |
getDeviceCode() | 获取/生成 deviceCode | - | string |
getUserKey() | 获取当前 userKey | - | string | null |
setUserKey(userKey) | 设置当前 userKey(影响本地数据隔离:应用列表、模型列表、KV 等) | string | { userKey: string | null } |
getEventMode() | 获取事件模式 | - | ipc | mqtt | both |
setEventMode({ mode, mqtt? }) | 设置事件模式(mqtt/both 时需要 mqtt 配置) | { mode: "ipc" | "mqtt" | "both", mqtt?: { url, username?, password? } } | { eventMode: string } |
getMqttConfig() | 读取 MQTT 配置(当前 userKey 作用域) | - | { url, username?, password? } | null |
setMqttConfig(mqtt) | 保存 MQTT 配置(当前 userKey 作用域) | { url, username?, password? } | { url, username?, password?, ... } |
pickDirectory() | 打开目录选择器(用于应用管理/应用内选择路径等) | - | { directory: string | null } |
storageGet({ appId, key }) | 应用 KV:读取 | { appId, key } | { exists: boolean, value: any } |
storageSet({ appId, key, value }) | 应用 KV:写入 | { appId, key, value } | { ok: true } |
storageRemove({ appId, key }) | 应用 KV:删除 | { appId, key } | { ok: true } |
storageList({ appId, prefix? }) | 应用 KV:列出(可按前缀筛选) | { appId, prefix? } | { items: Array<{ key, value }> } |
getAppDetail({ appId }) | 根据 appId 获取应用详情(alias、目录、hsq.config.json 信息等) | { appId } | { app } |
openAppPopup({ appId, pathSuffix }) | 打开新的弹窗(注入 preload),URL=应用入口+pathSuffix | { appId, pathSuffix | path } | { ok: true, winId: string } |
replaceWithAppPopup({ appId, pathSuffix }) | 关闭当前窗口并打开新的弹窗(同上逻辑) | { appId, pathSuffix | path } | { ok: true, winId: string } |
closeCurrentWindow() | 关闭当前窗口(调用方所在窗口) | - | { ok: true, closed: boolean } |
writeTextFile({ appId, path, content }) | 保存/修改文本文件(相对应用目录) | { appId, path, content, encoding?, overwrite?, mkdirp? } | { ok: true, path: string } |
readTextFile({ appId, path }) | 读取文本文件(相对应用目录) | { appId, path, encoding? } | { ok: true, path: string, content: string } |
deletePath({ appId, path }) | 删除文件/目录(相对应用目录) | { appId, path, recursive? } | { ok: true, deleted: boolean } |
copyPath({ appId, from, to }) | 复制文件(相对应用目录) | { appId, from, to, overwrite?, mkdirp? } | { ok: true, from: string, to: string } |
movePath({ appId, from, to }) | 移动/重命名文件(相对应用目录) | { appId, from, to, overwrite?, mkdirp? } | { ok: true, from: string, to: string } |
createCaptcha({ scene }) | 获取图形验证码(base64) | { scene: string } | { captchaBase64: string } |
refreshCaptcha({ scene }) | 刷新图形验证码 | { scene: string } | { captchaBase64: string } |
login({ username, password }) | 登录 | { username, password } | { userKey, token, tokenExpired } |
register({ username, password, captcha, nickname? }) | 注册(含验证码) | { username, password, captcha, nickname? } | { userKey, token | null, tokenExpired } |
getAccountInfo() | 获取账户信息(昵称) | - | { nickname: string | null } |
updatePassword({ oldPassword, newPassword }) | 修改密码 | { oldPassword, newPassword } | { ok: true } |
logout() | 退出登录(清理本地登录信息) | - | { ok: true } |
getSelectedModel() | 获取当前选中的默认模型 | - | ManagedModelItem | null |
setSelectedModel(id) | 设置默认模型 | string | null | { selectedModelId: string | null } |
listModels() | 列出所有模型配置 | - | ManagedModelItem[] |
pickFiles(input?) | 打开文件选择器(支持多选) | { filters?: Array<{ name, extensions }>, multi?: boolean } | { files: string[] } |
pickWorkingDirectory() | 打开目录选择器(用于绑定 agent 运行目录) | - | { directory: string | null } |
readAbsoluteText(input) | 读取任意路径的文本文件(绝对路径) | { path: string, encoding?: string } | { ok: true, path, content, size, name, ext } |
readImageAsBase64(input) | 读取本地图片并转为 base64 data URL(支持 PNG/JPG/WebP/GIF/SVG/BMP,最大 20MB,可选缩放) | { path, maxWidth?, maxHeight?, quality? } | { ok, path, base64, mimeType, width, height, sizeBytes } |
previewFile(input) | 自动识别文件类型并返回预览内容(图片→base64,文本→content+language,二进制→type=‘binary’) | { path, maxTextBytes?, maxImageDim? } | FilePreviewResult |
previewFileDialog(input) | 打开独立窗口预览文件(自动识别类型渲染) | { filePath, title? } | { ok: true } |
focusOrOpenPopup(input) | 打开应用弹窗(如已打开则聚焦切换) | { appId, pathSuffix? } | { ok, winId, existing } |
showItemInFolder(input) | 在系统文件管理器(Finder/资源管理器)中定位并高亮文件 | { path } | { ok: true } |
openExternalApp(input) | 用系统默认程序打开文件/目录 | { path } | { ok: true } |
openExternalAppByName(input) | 按应用名称启动应用(macOS/Windows) | { name } | { ok: true, path? } |
openInBrowser(input) | 在系统默认浏览器中打开 URL | { url } | { ok: true } |
writeImageBase64(input) | 将 base64 图片写入应用目录 | { appId?, path, base64, overwrite?, mkdirp? } | { ok, path, sizeBytes } |
getAppId() | 获取当前窗口的 appId(子应用窗口返回 appId,主窗口返回 null) | - | string | null |
getAppWorkdir(appId?) | 获取应用工作目录(子应用默认 {userData}/appData/{appId}) | string? | { workdir, isDefault } |
setAppWorkdir({ appId?, directory }) | 设置应用工作目录(空字符串重置为默认) | { appId?, directory } | { workdir } |
runAgentWithModel(input) | 调用 agent(自动注入模型信息 + 子应用自动注入 cwd) | { agentName, prompt, sessionId?, modelId?, cwd?, allowTools?, collectMessages?, permissionMode? } | AgentRunResult |
5)常见使用场景(直接复制即可)
5.1 登录 / 注册(含验证码)
// 1) 获取验证码
const { captchaBase64 } = await window.eventCenter.createCaptcha({ scene: "register" })
// 2) 注册(captcha 为用户输入的验证码文本)
await window.eventCenter.register({
username: "test_user",
password: "123456",
captcha: "a1b2",
nickname: "张三"
})
// 3) 登录
await window.eventCenter.login({ username: "test_user", password: "123456" })
5.2 应用管理:启动、查详情
// 启动应用(同一个 appId 只会存在一个窗口;重复启动会切换到已打开窗口)
await window.eventCenter.invoke("apps", "open", [{ id: "<managedAppId>" }])
// 通过 appId 获取该应用的管理信息
const { app } = await window.eventCenter.getAppDetail({ appId: "demo_app" })
console.log(app.alias, app.hsq.appName, app.hsq.appVersion)
5.3 打开弹窗(把后缀直接拼在应用入口后面)
// 推荐用 hash / query 作为后缀(对远端/本地都通用)
await window.eventCenter.openAppPopup({ appId: "demo_app", pathSuffix: "#/settings" })
await window.eventCenter.openAppPopup({ appId: "demo_app", pathSuffix: "?tab=logs" })
// pathSuffix 也支持传 path
await window.eventCenter.openAppPopup({ appId: "demo_app", path: "#/profile" })
// 关闭当前窗口并打开新弹窗(例如:强制跳登录页)
await window.eventCenter.replaceWithAppPopup({ appId: "demo_app", pathSuffix: "#/login" })
// 仅关闭当前窗口(例如:弹窗点“关闭”)
await window.eventCenter.closeCurrentWindow()
5.4 文件操作(限定在应用目录内)
所有文件路径均为“相对应用目录”的相对路径,不允许传绝对路径,也不允许 ../ 越界到应用目录之外。
// 保存/覆盖(会自动创建父目录)
await window.eventCenter.writeTextFile({
appId: "demo_app",
path: "data/settings.json",
content: JSON.stringify({ theme: "dark" }, null, 2),
mkdirp: true
})
// 读取
const file = await window.eventCenter.readTextFile({ appId: "demo_app", path: "data/settings.json" })
console.log(file.content)
// 复制
await window.eventCenter.copyPath({ appId: "demo_app", from: "data/settings.json", to: "data/settings.bak.json" })
// 移动/重命名
await window.eventCenter.movePath({ appId: "demo_app", from: "data/settings.bak.json", to: "data/archive/settings.bak.json" })
// 删除文件
await window.eventCenter.deletePath({ appId: "demo_app", path: "data/settings.json" })
// 删除目录(需要 recursive=true)
await window.eventCenter.deletePath({ appId: "demo_app", path: "data/archive", recursive: true })
拼接规则:
pathSuffix以?或#开头:直接拼接- 其它情况:自动补
/再拼接(例如sub/page)
5.5 应用 KV 存储(按 userKey + appId 隔离)
await window.eventCenter.storageSet({ appId: "demo_app", key: "profile", value: { theme: "dark" } })
const res = await window.eventCenter.storageGet({ appId: "demo_app", key: "profile" })
if (res.exists) console.log(res.value)
await window.eventCenter.storageRemove({ appId: "demo_app", key: "profile" })
5.6 模型管理
推荐使用 preload 直接暴露的便捷方法:
// 列出所有模型
const models = await window.eventCenter.listModels()
// 获取/设置默认模型
const selected = await window.eventCenter.getSelectedModel()
await window.eventCenter.setSelectedModel(models[0].id)
// 新增(仍通过 invoke)
await window.eventCenter.invoke("modules", "createModel", [
{ alias: "Claude", name: "claude-3-7-sonnet-latest", api_url: "https://api.anthropic.com", api_token: "..." }
])
// 编辑
await window.eventCenter.invoke("modules", "updateModel", [
modelId,
{ alias: "Claude Pro", name: "claude-opus-4-8", api_url: "https://api.anthropic.com", api_token: "..." }
])
// 删除
await window.eventCenter.invoke("modules", "deleteModel", [modelId])
// 连接测试(只测试可达性/状态码)
const test = await window.eventCenter.invoke("modules", "testModelConnection", [
{ api_url: "https://api.anthropic.com", api_token: "...", timeoutMs: 6000 }
])
console.log(test.ok, test.latencyMs, test.statusCode, test.error)
5.7 调用 Agent(带模型信息)
使用 runAgentWithModel 自动注入模型配置,无需手动传 api_token:
// 基础调用:自动使用默认模型的 api_url / api_token / model 名
const result = await window.eventCenter.runAgentWithModel({
agentName: 'claude',
prompt: '帮我分析这段代码的性能问题...'
})
console.log(result.sessionId, result.result, result.totalCostUsd)
// 指定模型 + 指定运行目录 + 会话续接
const result2 = await window.eventCenter.runAgentWithModel({
agentName: 'claude',
prompt: '继续上一个问题...',
modelId: 'some-model-id', // 可选:指定模型,不传则用默认模型
sessionId: result.sessionId, // 可选:续接会话
cwd: '/path/to/project', // 可选:agent 运行目录(文件读写等操作的根目录)
allowTools: true, // 可选:是否允许 agent 使用工具(默认 false)
collectMessages: true // 可选:是否收集中间消息流
})
// 返回类型 AgentRunResult:
// { sessionId, messageId, ok, result, stopReason, usage, totalCostUsd, messages }
注意: runAgentWithModel 会优先使用 modelId 指定的模型;若 modelId 为空则回退到默认模型(getSelectedModel)。模型信息(model.name、api_token、api_url)会自动合并到调用参数中,无需手动传入。
5.8 文件选择与绝对路径读取
新增的文件选择与绝对路径读取能力,不限定在应用目录内:
// 打开文件选择器(支持多选)
const { files } = await window.eventCenter.pickFiles({
multi: true, // 是否多选,默认 true
filters: [ // 可选:文件类型过滤
{ name: 'Text Files', extensions: ['txt', 'md', 'json'] },
{ name: 'All Files', extensions: ['*'] }
]
})
console.log(files) // string[] — 所选文件的绝对路径列表
// 读取任意绝对路径的文件内容
const file = await window.eventCenter.readAbsoluteText({
path: files[0],
encoding: 'utf8' // 可选,默认 'utf8'
})
console.log(file.name, file.ext, file.size, file.content)
// file: { ok: true, path, content, size, name, ext }
// 打开目录选择器(用于绑定 agent 工作目录)
const { directory } = await window.eventCenter.pickWorkingDirectory()
console.log(directory) // string | null
提示: 文件选择 + readAbsoluteText + runAgentWithModel({ cwd }) 组合使用,可实现"选择文件发给 agent 分析,并指定工作目录"的完整工作流。详见 5.9 节完整示例。
5.9 模型调试工作流完整示例
// 1. 选择工作目录
const { directory } = await window.eventCenter.pickWorkingDirectory()
// 2. 选择文件作为上下文
const { files } = await window.eventCenter.pickFiles({ multi: true })
// 3. 读取文件内容
const fileContents = await Promise.all(
files.map(path => window.eventCenter.readAbsoluteText({ path }))
)
// 4. 构造 prompt(将文件内容注入上下文)
const fileContext = fileContents
.map(f => `--- FILE: ${f.name} ---\n${f.content}\n--- END ---`)
.join('\n')
const prompt = `[CONTEXT FILES]\n${fileContext}\n\n[USER MESSAGE]\n请分析这些文件的代码结构`
// 5. 调用 agent
const result = await window.eventCenter.runAgentWithModel({
agentName: 'claude',
prompt,
cwd: directory,
allowTools: true
})
console.log(result.result)
5.10 图片读取 + Vision 工作流
// 1. 读取本地设计图(自动缩放至 2048px,0.85 质量)
const img = await window.eventCenter.readImageAsBase64({
path: '/path/to/design.png',
maxWidth: 2048,
maxHeight: 2048,
quality: 0.85
})
console.log(img.mimeType, img.width, img.height, img.sizeBytes)
// img.base64 → "data:image/png;base64,iVBORw0KGgo..."
// 2. 将图片传给 Agent 做视觉分析 / 代码生成
const result = await window.eventCenter.runAgentWithModel({
agentName: 'claude',
prompt: '请根据这张设计图生成对应的 React 组件代码',
images: [
{ base64: img.base64, detail: 'high' } // detail: 'auto' | 'low' | 'high'
],
allowTools: true,
permissionMode: 'bypassPermissions'
})
// 3. Agent 生成代码后,将关联图片资源保存到应用目录
await window.eventCenter.writeImageBase64({
path: 'assets/generated-icon.png',
base64: generatedImageBase64,
mkdirp: true
})
Vision 要点:
readImageAsBase64自动检测格式,支持 PNG/JPG/WebP/GIF/SVG/BMP- 文件大小上限 20MB,超限会抛错
maxWidth/maxHeight等比缩放,避免大图导致 token 爆炸quality仅对 JPG/WebP 生效(0-1)images支持同时传入多张图片(多页设计稿)detail: 'auto'让模型自动选择分辨率;'low'低分辨率省钱;'high'高清writeImageBase64路径限定在应用目录内(安全约束,与writeTextFile一致)
5.11 文件预览(自动识别类型)
previewFile 自动检测文件类型并返回适合前端渲染的预览数据:
// 任意文件路径,自动识别类型
const preview = await window.eventCenter.previewFile({
path: '/path/to/any-file',
maxTextBytes: 256 * 1024, // 文本最大字节数(默认 512KB,上限 2MB)
maxImageDim: 1400 // 图片最大尺寸(超限等比缩放)
})
switch (preview.type) {
case 'image':
// { type:'image', name, ext, sizeBytes, base64, mimeType, width, height }
console.log(preview.base64) // data:image/png;base64,...
break
case 'text':
// { type:'text', name, ext, sizeBytes, content, language, truncated }
console.log(preview.content) // 文本内容
console.log(preview.language) // typescript | json | markdown | python | ...
console.log(preview.truncated) // 内容是否被截断
break
case 'binary':
// { type:'binary', name, ext, sizeBytes } — 无法预览的二进制文件
break
}
类型识别规则:
| 类别 | 扩展名 | 返回 |
|---|---|---|
| 图片 | .png .jpg .webp .gif .bmp .svg | type:'image' + base64 + 尺寸 |
| 文本/代码 | .js .ts .json .md .py .go .rs 等 40+ 种 | type:'text' + content + language |
| 二进制 | 含 null 字节的文件 | type:'binary' |
5.12 弹窗预览 + 文件定位
previewFileDialog 打开独立 Electron 窗口预览文件,showItemInFolder 在系统文件管理器中定位:
// 打开独立窗口预览文件(自动识别图片/文本/代码,带完整 UI)
await window.eventCenter.previewFileDialog({
filePath: '/path/to/report.pdf',
title: '可选标题' // 默认取文件名
})
// 在 Finder / 资源管理器中定位并高亮文件
await window.eventCenter.showItemInFolder({ path: '/path/to/report.pdf' })
预览弹窗行为:
| 文件类型 | 展示方式 |
|---|---|
| 图片 | 全窗口铺满,显示尺寸和 MIME 类型 |
| 文本/代码 | 等宽字体渲染,标注语言类型 |
| 二进制/不支持 | 显示文件名 + 完整路径 +「打开文件目录」按钮 |
previewFile vs previewFileDialog:
previewFile | previewFileDialog | |
|---|---|---|
| 类型 | 纯数据 API,无 UI | 打开独立 Electron 窗口 |
| 适用场景 | 子应用拿数据自己渲染 | 快速预览,无需写 UI 代码 |
| 二进制处理 | 返回 type:'binary',调用方自行处理 | 弹窗内显示路径 + 打开目录按钮 |
5.13 打开外部应用与浏览器
openExternalApp 用系统默认程序打开文件或目录,openExternalAppByName 按应用名启动,openInBrowser 打开 URL:
// 用系统默认程序打开文件(PDF → 预览,图片 → 预览,目录 → Finder)
await window.eventCenter.openExternalApp({ path: '/path/to/report.pdf' })
await window.eventCenter.openExternalApp({ path: '/path/to/project-folder' })
// 按应用名称打开(跨平台:macOS/Windows)
await window.eventCenter.openExternalAppByName({ name: '微信' })
await window.eventCenter.openExternalAppByName({ name: 'WeChat' })
await window.eventCenter.openExternalAppByName({ name: 'Calculator' })
// 在默认浏览器中打开 URL
await window.eventCenter.openInBrowser({ url: 'https://github.com' })
openExternalAppByName 平台行为:
| 平台 | 查找策略 | 示例 |
|---|---|---|
| macOS | 先搜 /Applications/<name>.app(精确+模糊匹配),失败则 open -a "name" | '微信' → /Applications/WeChat.app |
| Windows | 调用 start "" "name"(需在 PATH 或已注册) | '微信' → 启动微信 |
| Linux | 不保证可用 | — |
安全约束:
openInBrowser仅接受http://和https://协议openExternalApp要求目标路径必须存在openExternalAppByName仅允许指定应用名,不做命令注入(输入会自动转义双引号)
6)可通过 invoke() 调用的服务(更完整的入口)
你可以用 eventCenter.listServices 查看当前注册的所有服务与方法:
const services = await window.eventCenter.invoke("eventCenter", "listServices", [])
console.log(services)
以下是常用服务:
eventCenter(配置/环境/连接测试)
invoke("eventCenter","getClientConfig",[])invoke("eventCenter","getUserKey",[])/invoke("eventCenter","setUserKey",[userKey])invoke("eventCenter","getEventMode",[])/invoke("eventCenter","setEventMode",[input])invoke("eventCenter","getMqttConfig",[])/invoke("eventCenter","setMqttConfig",[mqtt])invoke("eventCenter","testMqttConnection",[input])
apps(应用 CRUD / 启动 / 弹窗)
invoke("apps","list",[])invoke("apps","create",[ { alias, directory } ])invoke("apps","update",[ { id, alias?, directory? } ])invoke("apps","remove",[ { id } ])invoke("apps","open",[ { id } ])(同一个 appId 单实例窗口)invoke("apps","getByAppId",[ { appId } ])invoke("apps","openPopup",[ { appId, pathSuffix } ])invoke("apps","replaceWithPopup",[ { appId, pathSuffix } ])invoke("apps","getAppWorkdir",[ appId ])— 获取应用工作目录invoke("apps","setAppWorkdir",[ { appId, directory } ])— 设置应用工作目录invoke("app","previewFileDialog",[ { filePath, title? } ])— 打开独立预览窗口invoke("app","openInBrowser",[ { url } ])— 在默认浏览器打开 URLinvoke("app","openExternalApp",[ { path } ])— 以默认程序打开文件/目录invoke("app","openExternalAppByName",[ { name } ])— 按应用名称启动应用
appStorage(应用 KV)
invoke("appStorage","get",[ { appId, key } ])invoke("appStorage","set",[ { appId, key, value } ])invoke("appStorage","remove",[ { appId, key } ])invoke("appStorage","list",[ { appId, prefix? } ])
modules(模型管理)
invoke("modules","listModels",[])invoke("modules","getModel",[id])invoke("modules","createModel",[ { alias, name, api_url, api_token } ])invoke("modules","updateModel",[ id, { alias, name, api_url, api_token } ])invoke("modules","deleteModel",[ id ])invoke("modules","testModelConnection",[ { api_url, api_token, timeoutMs? } ])invoke("modules","setSelectedModel",[ id ])— 设置默认模型invoke("modules","getSelectedModelId",[])— 获取默认模型 IDinvoke("modules","getSelectedModel",[])— 获取默认模型完整信息
agent(调用 AI Agent)
invoke("agent","listAgents",[])— 列出可用 agent(目前仅 claude)invoke("agent","runAgent",[ { agentName, prompt, sessionId?, model?, api_token?, api_url?, cwd?, allowTools?, collectMessages?, permissionMode?, images? } ])— 直接调用 agent(支持 vision)- 推荐使用 preload 便捷方法
runAgentWithModel(input)自动注入模型信息
files(文件操作:任意路径读取 + 选择器)
invoke("files","pickFiles",[ { filters?, multi? } ])— 打开文件选择器invoke("files","pickWorkingDirectory",[])— 打开目录选择器invoke("files","readAbsoluteText",[ { path, encoding? } ])— 读取任意绝对路径文件invoke("files","readImageAsBase64",[ { path, maxWidth?, maxHeight?, quality? } ])— 读取图片转 base64invoke("files","previewFile",[ { path, maxTextBytes?, maxImageDim? } ])— 自动检测类型预览文件invoke("files","showItemInFolder",[ { path } ])— 系统文件管理器中定位文件invoke("files","writeImageBase64",[ { appId, path, base64, overwrite?, mkdirp? } ])— 写入 base64 图片invoke("files","writeText",[ { appId, path, content, encoding?, overwrite?, mkdirp? } ])— 写入文件(限定应用目录)invoke("files","readText",[ { appId, path, encoding? } ])— 读取文件(限定应用目录)invoke("files","delete",[ { appId, path, recursive? } ])— 删除文件/目录(限定应用目录)invoke("files","copy",[ { appId, from, to, overwrite?, mkdirp? } ])— 复制文件(限定应用目录)invoke("files","move",[ { appId, from, to, overwrite?, mkdirp? } ])— 移动/重命名(限定应用目录)
7)appId 自动注入(子应用窗口)
通过 apps.open / openAppPopup / replaceWithAppPopup 打开的子应用窗口,会携带 --hsqAppId=<appId>。
所有 preload 暴露的方法(除 auth 和 eventCenter 服务外),在子应用窗口调用时都会自动注入 appId。你无需在子应用代码中显式传递 appId:
// 子应用窗口中,以下调用无需手动传 appId(自动注入)
// KV 存储
await window.eventCenter.storageSet({ key: 'theme', value: 'dark' })
// 等价于: invoke('appStorage', 'set', [{ appId: 'auto', key: 'theme', value: 'dark' }])
// 文件操作
await window.eventCenter.writeTextFile({ path: 'data.json', content: '...' })
await window.eventCenter.readTextFile({ path: 'data.json' })
// Agent 调用
await window.eventCenter.runAgentWithModel({ agentName: 'claude', prompt: '...' })
如果显式传入 appId,则以传入的为准。
7.1)子应用工作目录
每个子应用有独立的工作目录,默认为 {userData}/appData/{appId}(自动创建):
// 获取当前工作目录
const { workdir, isDefault } = await window.eventCenter.getAppWorkdir()
console.log(workdir) // e.g. /Users/xxx/Library/.../appData/demo_app
console.log(isDefault) // true = 使用默认目录
// 修改工作目录(持久化)
await window.eventCenter.setAppWorkdir({ directory: '/path/to/custom/dir' })
// 重置为默认目录
await window.eventCenter.setAppWorkdir({ directory: '' })
Agent 自动使用工作目录: 在子应用窗口中调用 runAgentWithModel 时,若未显式传 cwd,会自动使用该应用的工作目录作为 Agent 运行根目录。主应用窗口无效(需手动传 cwd)。
8)窗口行为说明(避免踩坑)
apps.open:同一个appId只允许一个主窗口;重复调用会切换到已存在窗口(不会新开)openAppPopup:每次都会新开一个弹窗(允许同一 appId 多个弹窗)openAppPopup默认会把“当前窗口”作为 parent(弹窗跟随当前窗口层级显示);如果你希望弹窗独立存在,可改用invoke("apps","openPopup",[ { appId, pathSuffix, attachParent: false } ])replaceWithAppPopup:会关闭调用方所在窗口,再打开新弹窗(常用于“当前页面需要被替换”这类场景)
注册子应用

hsq.config.json
两种配置模式
本地模式(本地的index.html,包括就行,不限制技术栈,只要打包的产物就行)
远程模式(输入一个URL)
这是一个测试子应用的目录和配置
{
"appId": "demo_local_app",
"appName": "示例本地应用",
"appVersion": "1.0.0",
"appDescription": "这是一个本地应用示例:mode=local 时目录下必须存在 index.html。",
"gitUrl": "xxxxx",
"mode": "local"
}
项目只有一个index.html,里面通过window.eventCenter来通信
如


启动看下。这是独立的子应用,只是一个index.html文件,里面通过eventCenter来调用基座能力。

获取模型列表,调用agent

理想
有了基座,就可以不断的完善基座,给应用提供各种生态。
这样的话,只需要关注应用的开发,而非能力的接入。关注业务逻辑的实现。
降低开发难度。会开发前端就行或者甚至不需要前端。ai就可以开发。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)