架构概览
AISCouncil 是一个完全在浏览器中运行的零托管机器人管理平台。生产输出是一个由 80 个模块化源文件组装而成的单个 index.html 文件(约 980 KB)。没有外部运行时依赖 —— 只有 JavaScript 和 WASM 交付到浏览器。
单文件架构
应用程序是一个单一的自包含 HTML 文件。在开发期间,它被拆分为 src/ 部分,以便更小的上下文窗口和专注编辑。build.sh 脚本按严格顺序将所有 80 个部分串联回 index.html。
src/shell-head.html \
src/shell-style.html |
src/shell-body.html |
src/core-boot.js |
src/core-auth-main.js | build.sh
... | ---------> index.html (~980 KB)
src/settings-main.js |
src/miniprograms.js |
src/pwa.js |
src/shell-bottom.js /
永远不要直接编辑 index.html。始终编辑 src/ 中的相应文件,然后运行 ./build.sh 重新组装。
全局命名空间
所有模块在 window.AIS 全局命名空间上注册。命名空间在 src/core-boot.js 中初始化:
window.AIS = { version: "2.0.0", type: "aiscouncil" };
AIS.PLATFORM_VERSION = "1.0.0";
AIS.ABI_VERSION = 1;
跨模块通信使用轻量级事件总线:
AIS.on("event", handler); // 订阅
AIS.off("event", handler); // 取消订阅
AIS.emit("event", data); // 发布
模块系统:AIS.lazy()
模块使用 Qwik 风格的延迟水化模式。工厂函数被推迟,直到通过 AIS 上的属性 getter 首次访问模块:
if (!AIS.Council)
AIS.lazy("Council", function () {
"use strict";
// 模块代码在这里
return { run, renderCouncilMessage, estimateCost };
});
首次访问时(例如 AIS.Council.run()),getter 触发,执行工厂,用返回的模块对象替换自身,并返回它。后续访问直接访问普通值,没有开销。
WASM 内核模块仍然可以在首次访问之前通过设置 AIS.Council = wasmModule 来覆盖 JS 模块,因为该属性定义为 configurable: true。
模块类别
核心模块(始终加载)
核心模块在单个 <script> 块内定义(core-boot.js 到 core-end.js)。它们在页面加载时急切执行,形成应用程序的基础。
| 模块 | 文件 | 用途 |
|---|---|---|
AIS.Auth | core-auth-main.js, core-auth-local.js, core-auth-init.js | WebAuthn/通行密钥 + OAuth 登录、会话管理、身份验证 cookie |
AIS.Billing | core-billing.js | 订阅级别、试用、托管计划 |
AIS.Codec | core-codec.js | Base80 编码、VLQ 版本控制、deflate 压缩 |
AIS.Storage | core-storage.js | IndexedDB + 可选 SQLite WASM、离线优先 |
AIS.Providers | core-providers.js, core-providers-builtin.js | LLM 提供商注册表、SSE 流式工厂 |
AIS.UI | core-ui.js | DOM 工具、markdown 渲染器、toast 通知 |
AIS.Session | core-session.js | 机器人会话 CRUD(IndexedDB 支持) |
AIS.Chat | core-chat.js | 聊天历史、流式消息发送/接收 |
AIS.Config | core-config.js | 机器人配置面板绑定、URL 同步 |
AIS.App | core-app-botlist.js, core-app-switch.js, core-app-events.js, core-app-search.js, core-app-init.js | 应用程序控制器、初始化、路由、机器人管理 |
WASM 可替换模块(延迟加载)
每个 WASM 可替换模块位于自己的 <script> 块中,使用 AIS.lazy() 模式。它们只在首次访问时执行。
| 模块 | 文件 | 用途 |
|---|---|---|
AIS.Registry | registry.js | 社区模型注册表、24 小时缓存、GitHub 回退 |
AIS.Grid | grid.js | 模型表格/卡片渲染器(从 TypeScript 编译) |
AIS.Council | council.js | 多模型审议引擎(7 种风格) |
AIS.Wizard | wizard.js | 双模式首次运行设置向导 |
AIS.Vision | vision.js | 视觉模型的图像输入(粘贴/上传) |
AIS.Memory | memory.js | 持久化每机器人键值记忆 |
AIS.ImageGen | imagegen.js | 图像生成(DALL-E、Grok Imagine、OpenRouter) |
AIS.Tools | tools.js | 工具/函数调用格式标准化 |
AIS.Reminders | reminders.js | 通过 /remind 命令的定时消息 |
AIS.Themes | themes.js | 视觉主题系统 |
AIS.Templates | templates-registry.js | 系统提示词模板、欢迎屏幕 |
AIS.ModelPicker | model-picker.js | 可排序的模型浏览器 |
基础设施模块(延迟加载)
| 模块 | 文件 | 用途 |
|---|---|---|
AIS.ModuleLoader | moduleloader.js | 热交换模块生命周期、OPFS 缓存 |
AIS.Plugins | plugins.js | 插件系统:清单验证、钩子 |
AIS.MCP | mcp.js | 模型上下文协议(工具 + 资源) |
AIS.Channels | channels-core.js, channels-whatsapp.js, channels-adapters.js, channels-stubs.js | 渠道适配器(Telegram、Discord、Matrix、Slack、WhatsApp) |
AIS.Sandbox | sandbox.js | WASM 工具沙箱(Pyodide、QuickJS、SQLite) |
AIS.Publish | publish.js | SEO + 静态 HTML 发布 |
AIS.Perf | perf.js | 性能监控 |
AIS.P2P | p2p.js | 通过 WebRTC + CRDT 的 P2P 协作 |
平台模块
| 模块 | 文件 | 用途 |
|---|---|---|
AIS.Settings | settings-main.js | 全局设置对话框(所有部分) |
AIS.I18n | i18n.js | 国际化 |
AIS.MiniPrograms | miniprograms.js | 小程序运行时(沙箱 iframe) |
AIS.Docs | docs.js | 内联文档查看器 |
AIS.Profiles | profiles.js | 多模型配置文件/委员会模板 |
AIS.Cron | cron.js | 基于浏览器的调度器 |
CSS 架构
应用程序使用无类 CSS 方法。样式针对语义 HTML 元素和 ID,而不是类。
| 选择器类型 | 示例 | 用例 |
|---|---|---|
| ID | #header, #sidebar-left, #config-body | 唯一布局容器 |
| 语义元素 | #messages > article, article menu button | 消息、操作、导航 |
| 数据属性 | [data-from="user"], [data-variant="primary"] | 变体(消息角色、按钮类型) |
| 状态类 | .active, .collapsed, .mobile-open | JS 切换状态 |
| 组件类 | .council-member-row, .status-dot | 动态 JS 创建的组件 |
| 实用类 | .f1, .sc, .dim | 一次性内联样式替换 |
使用的语义 HTML 元素:<header>、<main>、<aside>、<section>、<footer>、<nav>、<article>、<menu>、<details>、<dialog>、<output>。
类仅用于三种情况:状态切换(.active、.collapsed)、动态 JS 组件(.council-member-row)和实用简写(.f1、.sc)。如果您可以用语义选择器或数据属性定位元素,请这样做而不是添加类。
存储架构
存储分为同步和异步存储:
| 存储 | 键 | 原因 |
|---|---|---|
| localStorage(同步) | ais-theme, ais-apikey-*, ais-user, ais-ollama-endpoint | 启动时需要同步读取 |
| IndexedDB(异步,无限制) | ais-bots, ais-chat-*, ais-addon-manifests | 大数据,无 5 MB 限制 |
| SQLite WASM(可选,OPFS) | 二进制 blob | 延迟加载以进行并发访问 |
首次启动时,AIS.Storage.init() 自动将数据从 localStorage 迁移到 IndexedDB。
提供商架构
提供商通过 AIS.Providers.register() 注册。大多数提供商使用共享的 openaiCompatible() SSE 流式工厂,它处理:
- 服务器发送事件解析
- 逐 token 流式回调
- AbortController 信号支持
- 从响应头计算 token 数
六个内置提供商在 core-providers-builtin.js 中注册:
| 提供商 | API 风格 | 身份验证 | 说明 |
|---|---|---|---|
| Anthropic | 原生(Messages API) | x-api-key 头 | 自定义流式格式 |
| OpenAI | OpenAI 兼容 | Bearer token | 标准 SSE |
| xAI | OpenAI 兼容 | Bearer token | Grok 模型 |
| Google Gemini | 原生(Gemini API) | ?key= 查询参数 | 避免 CORS 预检 |
| OpenRouter | OpenAI 兼容 | Bearer token | 300+ 模型,免费额度 |
| Ollama | OpenAI 兼容 | 无 | 本地 LLM,自动检测模型 |
所有 API 密钥本地存储(localStorage['ais-apikey-{provider}'])。密钥直接从浏览器发送到提供商 —— 永远不通过任何服务器代理。
设计原则
代码库遵循每个更改必须满足的严格设计原则:
- HTML5 原生优先 —— 在 JS 等效之前使用
<dialog>、<output>、hidden属性、CSS 动画。 - 零轮询 —— 没有
requestAnimationFrame循环、没有setInterval、没有setTimeout用于动画。仅事件驱动。 - 被动监听器 —— 所有不可取消事件使用
{ passive: true }。 - 事件委托 —— 容器上的单个监听器,而不是动态列表中每项的监听器。
- 14px 最小字体 —— 所有文本必须可被视觉 LLM 读取。没有低于 14px 的文本。
- VLM 友好布局 —— 48px+ 点击目标、大切换开关、高对比度。设置和菜单在 1920x1080 上无需滚动即可显示所有主要项目。
- 减少动画 ——
@media (prefers-reduced-motion: reduce)禁用所有动画。 - CSS 包含 —— 侧边栏使用
contain: strict,可滚动列表使用content-visibility: auto。
在添加任何功能之前,问四个决策门问题:
- 浏览器能否原生执行此操作?
- 这需要服务器吗?(如果是,使其可选。)
- 这能否扩展到每设备 100 万个机器人?
- 内存和 CPU 成本是多少?
WASM 内核
可选的 WASM 内核(kernel/)用 Zig 编写,编译后约 5.5 KB。它为模块系统提供低级原语:槽管理、钩子分发、环形缓冲区 I/O 和预写日志。
内核在 64 MB SharedArrayBuffer 上运行,定义了模块段、环形缓冲区、WAL 和暂存区的内存区域。JS 模块可以在运行时被 WASM 等效模块替换,而不会破坏延迟加载契约。
API 架构
平台针对不同关注点使用分离的 Worker:
| Worker | 域 | 用途 |
|---|---|---|
aiscouncil-api | api.aiscouncil.net | 计费、使用量、密钥分发、地理位置 |
aiscouncil-auth | auth.aiscouncil.net | OAuth 回调、令牌验证 |
API 基础 URL 在启动时从域检测设置,可以通过 localStorage['ais-api-base'] 为本地开发覆盖。