渲染进程设计
渲染进程负责所有 UI 渲染和用户交互,使用 React 18 + TypeScript 构建。入口文件为 packages/renderer/src/app/App.tsx。
Context Provider 层级
App 组件定义了严格的 Provider 嵌套顺序。外层 Provider 可被内层 Provider 和组件访问:
graph TB
LP[LocaleProvider — i18n 国际化]
TP[ThemeProvider — 主题/壁纸]
CDP[ConfirmDialogProvider — 全局确认弹窗]
AP[AuthProvider — 认证状态]
SDP[SettingsDataProvider — 设置数据]
EP[ElfiProvider — Elfi 助手]
WSP[WebSearchProvider — 搜索]
ChatDP[ChatDataProvider — 聊天数据缓存]
CBP[ChatBackendProvider — WebSocket 后端]
UCP[UnifiedChatProvider — 统一聊天 API]
HPP[HtmlPreviewProvider — HTML 预览]
DPP[DevPreviewProvider — 开发预览]
PR[ProtectedRoute — 路由守卫]
HR[HashRouter — 前端路由]
CTP[ChatTabsProvider — 多标签管理]
AR[AppRoutes — 路由定义]
AC[AppContent — 主内容]
LP --> TP --> CDP --> AP --> SDP --> EP --> WSP --> ChatDP --> CBP --> UCP --> HPP --> DPP --> PR --> HR --> CTP --> AR --> AC
{/* App.tsx 中的实际嵌套结构 */}
function App() {
return (
<LocaleProvider>
<ThemeProvider>
<ConfirmDialogProvider>
<AuthProvider>
<SettingsDataProvider>
<ElfiProvider>
<WebSearchProvider>
<ChatDataProvider>
<ChatBackendProvider>
<UnifiedChatProvider>
<HtmlPreviewProvider>
<DevPreviewProvider>
<ProtectedRoute>
<HashRouter>
<ChatTabsProvider>
<AppRoutes>
<AppContent />
</AppRoutes>
</ChatTabsProvider>
</HashRouter>
</ProtectedRoute>
</DevPreviewProvider>
</HtmlPreviewProvider>
</UnifiedChatProvider>
</ChatBackendProvider>
</ChatDataProvider>
</WebSearchProvider>
</ElfiProvider>
</SettingsDataProvider>
</AuthProvider>
</ConfirmDialogProvider>
</ThemeProvider>
</LocaleProvider>
);
}
Zustand Store
高频更新的聊天状态使用 Zustand 管理,避免 Context 引起的不必要重渲染。
chatStore
核心聊天状态存储,使用 subscribeWithSelector 中间件实现精确订阅:
{/* 简化的 chatStore 状态接口 */}
interface ChatState {
// 会话
sessions: Session[];
sessionsLoading: boolean;
sessionsError: Error | null;
// 助手
assistants: ChatAssistant[];
assistantsLoading: boolean;
// 提供商
providers: LLMProvider[];
providersLoading: boolean;
// 消息缓存 (按 sessionId 索引)
messageCache: Map<string, Message[]>;
// 流式状态
streamingState: StreamingState;
// 分支信息
branchInfo: Map<string, BranchInfo>;
// Actions
fetchSessions(): Promise<void>;
fetchAssistants(): Promise<void>;
fetchProviders(): Promise<void>;
loadMessages(sessionId: string): Promise<void>;
setStreamingState(state: Partial<StreamingState>): void;
switchBranch(messageId: string, index: number): void;
}
{/* 使用 selector 精确订阅,避免无关重渲染 */}
const sessions = useChatStore(state => state.sessions);
const isStreaming = useChatStore(state => state.streamingState.isStreaming);
| Store | 文件 | 职责 |
|---|---|---|
chatStore | shared/state/chatStore.ts | 会话、消息、流式状态、分支 |
settingsStore | shared/state/settingsStore.ts | 设置状态 |
useChatStoreSync | shared/state/useChatStoreSync.ts | ChatDataContext → chatStore 同步 |
React Context 详解
23+ 个 Context,每个承担独立职责:
| Context | 文件 | 职责 | 核心状态/方法 |
|---|---|---|---|
LocaleContext | shared/state/LocaleContext.tsx | i18n 国际化 (en/zh/ja) | useTranslation(), setLocale() |
themeStore | shared/state/themeStore.ts(宿主 app/ThemeHost.tsx) | 主题模式、壁纸、自定义 CSS | mode, resolvedMode, userTheme, customCss |
ConfirmDialogHost | shared/state/ConfirmDialogHost.tsx | 全局确认弹窗 | confirm(options) |
authStore | shared/state/authStore.ts | 认证状态 | user, isAuthenticated, login(), logout() |
SettingsDataHost | app/SettingsDataHost.tsx | 设置数据读写 | settings, updateSetting() |
ElfiContext | features/elfi/state/ElfiContext.tsx | Elfi 智能助手 | isOpen, messages, sendMessage() |
webSearchStore | shared/state/webSearchStore.ts(宿主 app/WebSearchHost.tsx) | 网络搜索 | search(), results |
chatStore | shared/state/chatStore.ts | 聊天数据缓存 + 统一聊天 API(原 ChatDataContext / UnifiedChatContext 已迁入此处) | sessions, messages, sendMessage(), regenerate(), editMessage() |
htmlPreviewStore | shared/state/htmlPreviewStore.ts(宿主 app/HtmlPreviewHost.tsx) | HTML 内容预览 | showPreview(), previewContent |
devPreviewStore | shared/state/devPreviewStore.ts(宿主 app/DevPreviewHost.tsx) | 开发模式预览 | isDevMode, devData |
ChatTabsContext | features/chat/state/ChatTabsContext.tsx | 多标签聊天管理 | tabs, activeTabId, createTab(), closeTab() |
AgentContext | features/agents/state/AgentContext.tsx | Agent 状态 | agents, selectedAgent |
subagentStore | shared/state/subagentStore.ts(宿主 shared/state/SubagentHost.tsx) | 子智能体状态 | subagents, status |
MessageSelectionContext | features/chat/state/MessageSelectionContext.tsx | 消息多选 | selectedIds, toggleSelect() |
sessionOrganizerStore | shared/state/sessionOrganizerStore.ts(宿主 shared/state/sessionOrganizerStore/SessionOrganizerHost.tsx) | 会话整理/分类 | folders, moveSession() |
worksStore | shared/state/worksStore.ts | 作品管理 | works, createWork() |
musicWorksStore | shared/state/musicWorksStore.ts | 音乐作品 | tracks, playTrack() |
musicTemplateStore | shared/state/musicTemplateStore.ts | 音乐模板 | templates |
VideoWorksContext | features/media/state/VideoWorksContext.tsx | 视频作品 | videos |
videoTemplateStore | shared/state/videoTemplateStore.ts | 视频模板 | templates |
TemplateContext | features/marketplace/state/TemplateContext.tsx | 图片模板 | templates |
useWorldInfoHighlight | shared/hooks/characters/useWorldInfoHighlight.ts | WI 关键词高亮 | highlightedKeywords |
Custom Hooks
60+ 个自定义 Hook,按功能领域组织:
聊天相关
| Hook | 文件 | 用途 |
|---|---|---|
useAppState | shared/hooks/useAppState.ts | 集中管理视图标志、活动标签、用户偏好 |
useRouteSync | shared/hooks/useRouteSync.ts | URL 路径到视图状态同步 |
useSessionProtection | features/chat/hooks/useSessionProtection.ts | 活动会话保护 (防止刷新清除消息) |
useDraft | features/chat/hooks/useDraft.ts | 输入框草稿保存/恢复 |
useAutoUpdate | shared/hooks/useAutoUpdate.ts | 应用自动更新 |
useAudioRecorder | features/media/hooks/useAudioRecorder.ts | 语音录制 |
功能 Hook 目录
| 目录 | 用途 | 代表 Hook |
|---|---|---|
features/chat/hooks/ | 聊天操作 | useMessageStream, useBranchNavigation |
features/marketplace/hooks/channel/ | 渠道插件 | useChannelPlugins |
shared/hooks/characters/character/ | 角色卡 | useCharacterCards |
shared/hooks/characters/worldinfo/ | 世界信息 | useWorldInfo |
shared/hooks/characters/ | 世界信息高亮 / 标签 | useWorldInfoHighlight, useTagsData |
features/characters/hooks/sprites/ | 表情立绘 | useSprites |
features/settings/components/tabs/tools-tab/hooks/ | MCP 服务器 | useMcpManagement |
features/kb/hooks/ | 笔记系统 | useNotesData |
features/todo/hooks/ | 待办事项 | useTodoData |
features/tasks/hooks/ | 任务管理 | useTasksData |
features/cron/hooks/ | 定时任务 | useCronJobs |
features/marketplace/hooks/skills/ | Skill 管理 | useSkillHub |
组件架构
布局结构
graph TB
Root["div.fixed.inset-0.flex.bg-background"]
WS[WorkspaceShell]
WR[WorkspaceRail — 左侧导航栏]
TB[TitleBar — 标签栏 + 窗口控制]
MC[Main Content — 路由内容区]
Root --> WS
WS --> WR
WS --> TB
WS --> MC
MC --> ChatPage
MC --> AgentPage
MC --> SettingsPage
MC --> RoleplayPage
MC --> NotesPage
MC --> CoworkPage
- WorkspaceRail: 左侧固定导航栏,包含首页、Agent、设置等入口
- WorkspaceShell: 主布局容器,管理 Rail + 标签栏 + 内容区
- TitleBar: 多标签栏 + 窗口控制按钮(最大化/最小化/关闭)
- Main Content: 根据路由动态渲染对应页面组件
工作区扩展(Workspace Registry)
Chat / Agent 页面中间的「工作区面板」(文件树右侧的 editor / git diff / HTML preview / canvas / design studio 等区域)通过 WorkspaceDefinition 注册表扩展。WorkspaceArea.tsx 作为 host shell 只做 layout + 注册表 dispatch — 不包含任何 activeView === 'X' 业务分支。
graph TB
WA["WorkspaceArea<br/>(layout + dispatch)"]
Reg["registry.ts<br/>WORKSPACE_REGISTRY[]"]
Slot["WorkspaceToolbarProvider<br/>(slot context)"]
Aff["host-affordances<br/>(cross-view coordination)"]
Defs["definitions/"]
WA -->|findActiveWorkspace| Reg
WA -->|wraps| Slot
WA -->|consumes| Aff
Reg -->|imports| Defs
Defs --> EW[EditorWorkspace]
Defs --> GW[GitWorkspace]
Defs --> HW[HtmlPreviewWorkspace]
Defs --> AW[A2UIWorkspace]
Defs --> DW[DevPreviewWorkspace]
Defs --> DS[DesignStudioWorkspace]
Defs --> CW[CanvasWorkspace]
每个 WorkspaceDefinition 自治管理:
- 自己的
view标识符 +isAvailable(ctx)谓词 - 自己的 view-mode 状态(split/code/preview 等内部 useState — 不外溢到 host)
- 通过
useWorkspaceToolbar(config)把 toolbar 配置发布到 slot;host 的<WorkspaceToolbar>通过useWorkspaceToolbarConfig()读取 - 可选
onClose(ctx, cb)钩子(关闭按钮触发,run outside React tree,所以 close-fn 通道走WorkspaceCallbacks)
加新 workspace 类型 = 改 2 个文件:在 registry.ts 数组里加一项 + 创建 definitions/<Name>Workspace.tsx。WorkspaceArea.tsx 不需要改。详细配方见 packages/renderer/CLAUDE.md 「🧩 添加新的 Workspace 类型」节,契约 spec 见 openspec/specs/workspace-registry/spec.md。
跨 view 协调(如 editor 里的「Preview」按钮跳到 htmlPreview view、HTML preview 最大化时整个 workspace pane 隐藏)集中在 host-affordances.ts —— 这是唯一允许出现 activeView === 'X' 判断的位置。
| 文件 | 职责 |
|---|---|
features/chat/components/content/WorkspaceArea.tsx | Host shell(layout + slot reader + 注册表 dispatch,无业务分支) |
features/chat/components/content/workspaces/types.ts | WorkspaceDefinition / WorkspaceHostContext / WorkspaceCallbacks 契约 |
features/chat/components/content/workspaces/registry.ts | WORKSPACE_REGISTRY 单一注册点 + findActiveWorkspace 查询 |
features/chat/components/content/workspaces/host-affordances.ts | 跨 view 协调(cross-view toggle + 最大化隐藏) |
features/chat/components/content/workspaces/workspace-toolbar-slot{,-internals,-hooks}.{tsx,ts} | Toolbar slot 三件套(Provider / contexts / hooks) |
features/chat/components/content/workspaces/definitions/<Name>Workspace.tsx | 7 个 workspace definition(editor / git / htmlPreview / a2ui / devPreview / designStudio / canvas) |
features/chat/components/content/workspaces/inline-file-viewers.tsx | InlineMarkdownViewer + InlineBinaryViewer(image/audio/video/pdf + "cannot be previewed" fallback) |
features/chat/components/content/workspaces/inline-file-viewers-types.ts | getBinaryFileType / isMarkdownFile / isNonTextFile 文件类型检测 |
features/chat/components/content/unified-page/hooks/useWorkspaceState.ts | 工作区状态 hook(handleFileSelect 内做 .html → openHtmlPreview() 路由 + isNonTextFile 二进制路由) |
路由系统
使用 HashRouter(Electron 不支持 BrowserRouter)配合 useRouteSync Hook 实现 URL 到视图状态的同步:
{/* 路由到视图状态同步 */}
function useRouteSync(pathname: string, state: AppState) {
useEffect(() => {
if (pathname.startsWith('/chat')) {
state.setIsChatView(true);
state.setIsHomeView(false);
} else if (pathname.startsWith('/agents')) {
state.setIsAgentView(true);
} else if (pathname.startsWith('/settings')) {
state.setIsSettingsView(true);
}
}, [pathname]);
}
i18n 国际化
支持 3 种语言,使用 useTranslation() Hook:
| 语言 | 目录 | 文件数 |
|---|---|---|
| English | locales/en/ | 15+ JSON 文件 |
| 中文 | locales/zh/ | 15+ JSON 文件 |
| 日本語 | locales/ja/ | 15+ JSON 文件 |
翻译文件按功能域拆分:common.json, chat.json, settings.json, characters.json, elfi.json 等。
AppState Hook
useAppState 集中管理全局 UI 状态:
{/* 简化的 AppState 接口 */}
interface AppState {
// 视图标志
isHomeView: boolean;
isChatView: boolean;
isAgentView: boolean;
isSettingsView: boolean;
// 活动会话
selectedSession: string | null;
activeSessions: Set<string>;
processingSessions: Set<string>;
// 标签
activeTab: string;
settingsInitialTab: string;
// 用户偏好
autoExpandTools: boolean;
showRawParameters: boolean;
showThinking: boolean;
autoScrollToBottom: boolean;
sendByCtrlEnter: boolean;
// 模态框
showVersionModal: boolean;
}
构建配置
| 配置项 | 值 |
|---|---|
| 构建工具 | Vite 7.0 |
| 样式 | Tailwind CSS 3.4 + CSS 变量 (语义化 Token) |
| 代码分割 | 路由级 lazy loading(lazy/ 目录,按用途分 6 文件 + 1 barrel) |
| Tree Shaking | 具名导入 |
| 主 bundle 上限 | 500 KB |
相关文件
| 文件 | 说明 |
|---|---|
packages/renderer/src/app/App.tsx | 应用入口,Provider 层级和布局 |
packages/renderer/src/shared/state/chatStore.ts | Zustand 聊天状态 Store |
packages/renderer/src/shared/state/settingsStore.ts | Zustand 设置状态 Store |
packages/renderer/src/shared/state/chatStore.ts | 统一聊天 API(原 UnifiedChatContext 已迁入 chatStore) |
packages/renderer/src/shared/state/themeStore.ts | 主题 Store(原 ThemeContext) |
packages/renderer/src/shared/hooks/useAppState.ts | 集中 UI 状态管理 |
packages/renderer/src/shared/hooks/useRouteSync.ts | 路由同步 |
packages/renderer/src/app/layout/WorkspaceShell.tsx | 主布局容器 |
packages/renderer/src/app/layout/WorkspaceRail.tsx | 左侧导航栏 |
packages/renderer/src/app/title-bar/ | 标签栏组件 |
packages/renderer/src/app/lazy/ | 懒加载组件目录(pages.tsx / workspaces.tsx / inline.tsx / shell.tsx / skeletons.tsx / with-suspense.tsx,barrel 在 lazy/index.ts) |
packages/renderer/src/features/chat/components/content/WorkspaceArea.tsx | 中间工作区 host shell(layout + 注册表 dispatch,不含业务分支) |
packages/renderer/src/features/chat/components/content/workspaces/ | 工作区注册表(WorkspaceDefinition 契约 + 7 个 definition 文件 + toolbar slot + host-affordances) |
packages/renderer/src/app/railItems.ts | 导航栏配置 |
packages/renderer/src/app/mainContentRouter.tsx | 主内容路由解析 |
packages/renderer/CLAUDE.md | 前端 UI 开发规范 |