开发者指南 · 05
前端架构
5.1 目录结构
text
frontend/src/
├── main.tsx # React 19 入口(Admin SPA)
├── App.tsx # ConfigProvider + AuthProvider + ThemeProvider + StreamProvider + Router
├── router/index.tsx # BrowserRouter + 三层 ErrorBoundary
├── layouts/AppLayout.tsx # 主布局(侧栏 + 内容区 + 文件面板触发)
│
├── stores/
│ ├── authContext.tsx # 认证状态
│ ├── streamContext.tsx # SSE 流状态(admin 用)
│ ├── fileWorkspaceContext.tsx # 文件面板状态
│ └── themeContext.tsx # 多主题切换 + Antd ThemeConfig
│
├── services/api.ts # 统一 API 客户端
│
├── types/index.ts # 共享类型(Message / MessageBlock / Subagent / etc.)
│
├── styles/
│ ├── global.css # 全局样式 / 滚动条 / markdown
│ ├── themes.css # 多主题 CSS 变量定义
│ └── theme.ts # 后备 JS 常量(实际以 CSS 变量为准)
│
├── utils/
│ ├── csvParse.ts # CSV/TSV 状态机解析
│ ├── fileKind.ts # 扩展名 → kind 分类
│ └── timezone.ts # 时区工具
│
├── pages/
│ ├── Login.tsx # 品牌分栏登录/注册
│ ├── AdminServices/index.tsx # Service 管理(4 Tab)
│ ├── Scheduler/index.tsx # 定时任务(Admin / Service 双 Tab)
│ ├── WeChat/index.tsx # Admin WeChat 接入
│ ├── Settings/
│ │ ├── index.tsx # SettingsLayout(侧栏菜单)
│ │ ├── PromptPage.tsx # System Prompt + Memory & Soul + 能力提示词
│ │ ├── SubagentPage.tsx
│ │ ├── PackagesPage.tsx # per-user venv
│ │ ├── InboxPage.tsx
│ │ └── GeneralPage.tsx # API Keys + 时区 + 主题 + Advanced 开关 + BatchRunner 内嵌
│ └── Chat/
│ ├── index.tsx # 主聊天页
│ ├── chat.module.css # CSS Modules(--jf-* 变量)
│ ├── markdown.ts # 唯一渲染管线(含 <<FILE:>> 处理)
│ ├── useSmartScroll.ts # 智能滚动 hook
│ ├── types.ts # StreamBlock 联合类型
│ └── components/
│ ├── ThinkingBlock.tsx
│ ├── ToolIndicator.tsx
│ ├── SubagentCard.tsx
│ ├── StreamingMessage.tsx
│ ├── MessageBubble.tsx
│ ├── ApprovalCard.tsx
│ ├── ImageAttachment.tsx
│ ├── VoiceInput.tsx
│ └── PlanTracker.tsx
│
├── components/
│ ├── FilePanel.tsx
│ ├── FilePreview.tsx # 多类型文件查看器
│ ├── FileTreePicker.tsx # 可访问文件/脚本图形选择器
│ ├── HeaderControls.tsx
│ ├── LogoLoading.tsx
│ ├── SplitToggle.tsx
│ ├── ApiKeyWarning.tsx # 无 LLM Key 时引导 Modal
│ ├── ErrorBoundary.tsx # 全仓唯一 class 组件
│ └── modals/
│ ├── BatchRunner.tsx # 内嵌于 GeneralPage
│ ├── SoulSettings.tsx
│ ├── SubagentManager.tsx
│ ├── SystemPromptEditor.tsx
│ └── UserProfileEditor.tsx
│
└── service-chat/ # Vite 第二入口(Consumer 端)
├── main.tsx
├── ServiceChatApp.tsx
├── ServiceToolBadge.tsx # 友好工具状态条(替代 admin 的 ToolIndicator)
├── streamHandler.ts # 轻量 SSE handler(无 HITL/subagent)
├── serviceApi.ts # Consumer 端 API(与 services/api.ts 解耦)
└── serviceChat.module.css5.2 路由系统
tsx
<BrowserRouter>
<Routes>
<Route path="/login" element={<PublicRoute><Login/></PublicRoute>} />
<Route element={
<ErrorBoundary scope="app-layout">
<ProtectedRoute><AppLayout/></ProtectedRoute>
</ErrorBoundary>
}>
<Route path="/" element={
<ErrorBoundary scope="chat"><ChatPage/></ErrorBoundary>
} />
<Route path="/settings" element={
<ErrorBoundary scope="settings"><SettingsLayout/></ErrorBoundary>
}>
<Route index element={<Navigate to="/settings/prompt" replace/>} />
<Route path="prompt" element={<PromptPage/>} />
<Route path="subagents" element={<SubagentPage/>} />
<Route path="packages" element={<PackagesPage/>} />
<Route path="batch" element={<Navigate to="/settings/general" replace/>} />
<Route path="services" element={<AdminServicesPage/>} />
<Route path="scheduler" element={<SchedulerPage/>} />
<Route path="wechat" element={<WeChatPage/>} />
<Route path="inbox" element={<InboxPage/>} />
<Route path="general" element={<GeneralPage/>} />
</Route>
</Route>
<Route path="*" element={<Navigate to="/" replace/>} />
</Routes>
</BrowserRouter>ProtectedRoute:未登录重定向/loginPublicRoute:已登录重定向/AppLayout提供侧栏导航 +<Outlet/>+ Portal 节点#sider-slotChatPage/SettingsLayout用createPortal把侧栏内容注入#sider-slot/settings/batch重定向到/settings/general(兼容旧链接)
5.3 状态管理
不使用全局状态库,采用 React Context + 组件局部状态。
| Context | 文件 | 用途 |
|---|---|---|
authContext | stores/authContext.tsx | user / loading / login / register / logout,Token 存 localStorage |
streamContext | stores/streamContext.tsx | SSE 流状态、blocks 缓冲、HITL 中断 |
fileWorkspaceContext | stores/fileWorkspaceContext.tsx | 文件面板状态、当前编辑文件 |
themeContext | stores/themeContext.tsx | 多主题切换 + Antd ThemeConfig |
5.4 API 客户端
src/services/api.ts 提供 typed API 客户端。
typescript
async function request<T>(method: string, path: string, body?: unknown): Promise<T>自动处理:
- Bearer Token 注入(从
localStorage) - Content-Type(JSON / FormData)
- 401 自动清除 Token 并刷新
- 错误提取
detail字段
模块划分:
| 模块 | 函数集 |
|---|---|
| Auth | login / register / getMe |
| Conversations | list/create/get/delete + attachmentUrl |
| Chat | streamChat / resumeChat / stopChat / abortStream / checkServerStreaming |
| Files | list/read/write/edit/delete/move + uploadFiles / downloadFile / mediaUrl |
| System Prompt | CRUD + 版本管理 |
| User Profile | CRUD + 版本管理 |
| Capability Prompts | getCapabilityPrompts / updateCapabilityPrompt / resetCapabilityPrompt |
| Soul Config | getSoulConfig / updateSoulConfig |
| Subagents | list/get/add/update/delete |
| Scripts | runScript |
| Audio | transcribeAudio |
| Models | getModels |
| Batch | uploadBatchExcel / startBatchRun / listBatchTasks / getBatchTask / cancelBatchTask / batchDownloadUrl |
| Scheduler | Admin + Service 任务 CRUD + runNow |
| Services | CRUD + Keys + WeChat channel |
| Inbox | list/get/updateStatus/delete + getUnreadCount |
| Packages | venv 状态 + install/uninstall |
| API Keys | getApiKeys / updateApiKeys / testApiKeys / getApiKeysStatus |
| WeChat (Admin) | adminWechatQrcode / adminWechatStatus / adminWechatSession / adminWechatMessages |
| WeChat (Service) | serviceWcQrcode / serviceWcSessions / serviceWcSessionMessages |
5.5 SSE 流式处理
5.5.1 事件类型
typescript
type SSEEvent =
| { type: 'token'; content: string }
| { type: 'thinking'; content: string }
| { type: 'tool_call'; name: string; args: string }
| { type: 'tool_call_chunk'; args_delta: string }
| { type: 'tool_result'; name: string; content: string }
| { type: 'subagent_call'; name: string; task: string }
| { type: 'subagent_call_chunk'; args_delta: string }
| { type: 'subagent_start'; name: string }
| { type: 'subagent_token'; content: string; agent: string }
| { type: 'subagent_thinking'; content: string; agent: string }
| { type: 'subagent_tool_call'; name: string; args: string; agent: string }
| { type: 'subagent_tool_chunk'; args_delta: string }
| { type: 'subagent_tool_result'; name: string; content: string; agent: string }
| { type: 'subagent_end'; name: string; result: string }
| { type: 'interrupt'; actions: unknown[]; configs: unknown[] }
| { type: 'done' }
| { type: 'error'; content: string }5.5.2 性能优化
text
SSE 回调 → useRef 直接修改 blocks 数组(避免每 token setState)
→ requestAnimationFrame 节流刷新(~60fps)
→ 批量更新到 React state
→ StreamingMessage (React.memo) 避免不必要重渲染5.5.3 断流恢复
- 后端
_stream_agent/_stream_consumer在finally块检测未保存的部分回复,追加⚠️ [连接中断 — 已保存已生成内容]后持久化(_saved标志位防止重复保存) _active_streams字典追踪{thread_id → {user_id, conv_id}}GET /api/chat/streaming-status返回当前用户活跃 streaming 对话列表- 前端 Chat 页加载时
checkServerStreaming(),如发现仍在后台 streaming,显示黄色横幅 + 「终止并保存」/「刷新状态」按钮
5.5.4 Stop 按钮
- 流式输出时发送按钮变为红色 Stop(Phosphor
Stop) POST /api/chat/stop设置asyncio.Event取消标志,_stream_agent每次迭代检查
5.6 Chat 组件层次与共享渲染
5.6.1 组件树
text
ChatPage (pages/Chat/index.tsx)
├── 对话列表(createPortal → #sider-slot)
├── 消息区域
│ ├── MessageBubble(历史消息)
│ │ └── BlocksRenderer(msg.blocks 优先 → fallback 旧逻辑)
│ ├── StreamingMessage(流式消息容器,React.memo)
│ │ ├── ThinkingBlock(折叠/展开)
│ │ ├── ToolIndicator(工具调用,可展开 args/result)
│ │ └── SubagentCard(子代理 timeline 渲染)
│ └── ApprovalCard(HITL 审批:文件 diff / Plan 编辑)
├── 输入区
│ ├── ImageAttachment(粘贴/拖拽/文件选择)
│ ├── VoiceInput(toggle 模式:单击开始 → 单击停止 → 自动转写)
│ ├── 能力开关 / Plan Mode / 模型选择器
│ └── 发送/Stop 按钮
└── useSmartScroll(用户上滚暂停吸底,回底部恢复)5.6.2 Admin / Service 共享渲染
跨 admin / service 共享的组件(改一次两边都生效):
pages/Chat/markdown.ts— 唯一 markdown 渲染管线,含<<FILE:>>处理 / DOMPurify / hljspages/Chat/components/StreamingMessage.tsx— 共享渲染组件,接受toolRenderer/hideSubagents/avatarSrcpropspages/Chat/types.ts的StreamBlock数据结构
渲染差异:
| 维度 | Admin | Service |
|---|---|---|
| 工具块 | 默认 ToolIndicator(真实工具名 + args/result) | 传 toolRenderer={ServiceToolBadge}(友好文案,未在白名单的统一"思考中…") |
| Subagent | 显示完整 SubagentCard | 传 hideSubagents 隐藏 |
| 媒体 URL | adminMediaUrl(path) | setMediaUrlBuilder(buildConsumerMediaUrl) 覆盖为 query 参数携带 service API key |
5.6.3 消息 Blocks 持久化
流式输出和历史消息使用统一的交错渲染:
- 后端:
save_message(blocks=...),blocks是有序数组text:{"type": "text", "content": "..."}thinking:{"type": "thinking", "content": "..."}tool:{"type": "tool", "name": "...", "args": "...", "result": "...", "done": true}subagent:{"type": "subagent", "name": "...", "task": "...", "status": "done", "content": "...", "tools": [...], "timeline": [...], "done": true}
- 前端:
MessageBubble检测msg.blocks时使用BlocksRenderer,否则 fallback 旧逻辑
5.7 文件预览面板
- kind 分类:
utils/fileKind.ts按扩展名 →image|audio|video|pdf|markdown|html|csv|json|text|binary - openFile 优化:媒体/binary 跳过
api.readFile,直接setEditingFile(path) - 渲染策略:
| kind | 渲染方式 |
|---|---|
| image / audio / video / pdf | 原生 <img>/<audio>/<video>/<iframe> 走 mediaUrl(path),工具栏隐藏「保存」 |
| markdown | 复用 pages/Chat/markdown.ts::renderMarkdown,样式 .jf-file-md-preview |
| html | <iframe sandbox="allow-scripts">(无 same-origin — 允许 Plotly/ECharts 但禁访问父页) |
| csv / tsv | utils/csvParse.ts 状态机解析 → antd Table(最多 2000 行) |
| json / jsonl / ndjson | JSON.parse + hljs 高亮 |
| text / code | textarea 编辑 |
| binary | Empty 占位 + 下载按钮 |
- 工具栏切换:toggle 类(md/html/csv/json)头部加 antd
Segmented「预览/源码」 - 下载按钮:所有 kind 工具栏统一新增
5.8 错误边界
- 组件:
components/ErrorBoundary.tsx(全仓唯一 class 组件,React 19 仍要求 class 形式,刻意豁免 "avoid classes" 规则) - 三层部署(
router/index.tsx):scope="app-layout"包裹<AppLayout/>— 兜底整个受保护区域scope="chat"包裹<ChatPage/>— 聊天页崩溃不影响侧栏scope="settings"包裹<SettingsLayout/>— 设置页崩溃不影响聊天
- 交互:友好提示 + 可展开错误详情 + 复制错误信息按钮(含 scope/时间/URL/UA/stack/componentStack)
- 样式:Antd
Result+Collapse,背景用--jf-bg-deep
5.9 设计系统与多主题
5.9.1 多主题(themes.css + themeContext.tsx)
[data-theme]属性在<html>上,由 ThemeProvider 控制- 持久化:
localStorage.jf-theme - 三套已有主题:
| 主题 | 风格 | 主色 | 特殊规则 |
|---|---|---|---|
dark(默认) | 暖粉紫深色 | #E89FD9 Primary | — |
cyber-ocean | 青蓝浅色 | — | — |
terminal | 磷绿 CRT 终端 | #33ff00 | 全局 monospace 字体、border-radius: 0、磷光 text-shadow、CRT 扫描线、按钮 hover 反转、text-transform: uppercase |
- 添加新主题:
themes.css复制一个[data-theme]块并调整值themeContext.tsx添加THEMES项 + Antd ThemeConfig
- CSS 变量命名:
- 品牌色:
--jf-primary/secondary/accent/highlight/legacy - RGB 三元组:
--jf-primary-rgb(用于rgba(var(--jf-primary-rgb), 0.12)) - 渐变:
--jf-gradient-from/to、--jf-user-bubble-bg/shadow - 背景:
--jf-bg-deep/panel/raised/code/inset - 文字:
--jf-text/text-muted/text-dim/text-quaternary - 边框:
--jf-border/border-rgb/border-strong - 语义色:
--jf-success/warning/error/info - 阴影:
--jf-shadow-float/hover/brand - Diff:
--jf-diff-add-bg/del-bg/eq-text - Antd:
--jf-menu-selected-bg/select-option-bg
- 品牌色:
5.9.2 圆角规范
| 档位 | CSS 变量 | 用途 |
|---|---|---|
| sm | var(--jf-radius-sm) | 4px,内嵌圆角 |
| md | var(--jf-radius-md) | 8px,按钮、面板 |
| lg | var(--jf-radius-lg) | 12px,卡片、模态 |
| bubble | var(--jf-radius-bubble) | 16px,消息气泡 |
- 圆形元素仍用
'50%' - 内联
borderRadius必须使用变量字符串:'var(--jf-radius-md)',不要硬编码
5.9.3 其他规范
- 图标统一使用
@phosphor-icons/react(替代@ant-design/icons) - 样式优先级:antd 组件 > inline style(含 CSS 变量)> CSS Modules
- 类型定义集中在
src/types/index.ts - API 调用统一通过
src/services/api.ts - Logo:
/media_resources/jellyfishlogo.png - 字体:正文 Segoe UI,代码 JetBrains Mono(Google Fonts CDN)
5.9.4 Advanced Tab 可见性
GeneralPage.tsx「高级功能」卡片:两个 Switch 控制 Prompt 页的 Advanced Tab 可见性localStoragekey:show_advanced_system(操作规则)/show_advanced_soul(Memory & Soul)- 默认关闭,通过自定义事件
advanced-settings-changed实时响应