ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

RikkaHub web-ui 前端架构指南:基于 React Router 7 的嵌入式 Web 界面深度解析

RikkaHub web-ui 前端架构指南:基于 React Router 7 的嵌入式 Web 界面深度解析 人工智能大模型AI 应用移动开发交互助手【免费下载链接】rikkahubRikkaHub is an Android APP that supports for multiple LLM providers.项目地址https://gitcode.com/gh_mirrors/ri/rikkahub点击查看免费下载RikkaHub 是一个支持多家 LLM 提供商的多模态 Android 应用其web-ui模块是内嵌在应用中的 Web 前端SPA。本指南以仓库内 web-ui/AGENTS.md 为核心结合 package.json、copy.ts、app/services/api.ts 等源码系统讲解该前端的技术栈、目录架构、类型同步机制、实时通信流程、构建部署链路与开发规范。读完本文你将掌握如何在 RikkaHub 中开发、构建和调试这套嵌入式 Web 界面并理解它与 Kotlin 后端之间完整的数据流与构建集成方式。1. 项目定位嵌入 Android 应用的 Web 前端web-ui是 RikkaHub 项目的嵌入式 Web 前端基于 React Router 7 构建为单页应用SPA。它的构建产物通过 copy.ts 脚本复制到web/src/main/resources/static目录由 Kotlin 后端的 Ktor 服务器提供静态文件服务。也就是说用户访问http://localhost:8080/时浏览器拿到的正是这套web-ui的构建产物桌面 Web 端与 Android 端共享同一套对话与设置数据。从 react-router.config.ts 可以看到ssr: false即纯 SPA 模式——虽然 React Router 7 默认具备 SSR 能力但本项目显式关闭了服务端渲染所有页面渲染都发生在浏览器端构建时build/server/目录的 SSR 服务器代码在 SPA 模式下并不会被使用。2. 技术栈全景根据 package.json 与 AGENTS.mdweb-ui的核心依赖如下类别技术选型版本用途路由框架React Router 77.13.0文件路由 Vite 构建SPA 模式UI 框架React19.2.4组件渲染语言TypeScript5.9.2严格类型检查与 Kotlin 后端类型完全对齐样式Tailwind CSS v44.1.13原子化 CSS CSS 变量主题系统组件库shadcn/uiNew York 风格—基于 Radix UI 的无头组件状态管理Zustand5.0.11组合 slices 模式HTTP 客户端ky1.14.3基于 fetch 的 REST SSE国际化i18next react-i18next25.x / 16.xzh-CN / en-US 双语言Markdownreact-markdown remark/rehype 插件—GFM、LaTeX、Shiki 高亮代码高亮shiki3.22.0带复制按钮的代码块数据请求tanstack/react-query5.90.20服务端状态缓存包管理pnpm—依赖与脚本管理配套工具链还包括oxlintlint、oxfmt格式化、tsx运行 TypeScript 脚本、vite-plugin-svgrSVG 组件化与vite-tsconfig-paths~路径别名。3. 开发命令一览web-ui的全部脚本定义在 package.json 的scripts字段中与 AGENTS.md 中的命令一一对应# 开发服务器HMR /api 代理到 localhost:8080 pnpm run dev # 生产构建react-router build tsx copy.ts构建并复制到后端 pnpm run build # 类型检查react-router typegen 生成路由类型 tsc 严格检查 pnpm run typecheck # 代码格式化 pnpm run fmt # 检查格式 pnpm run fmt:check注意pnpm run build是一个组合命令先执行react-router build产出静态资源再执行tsx copy.ts把构建产物同步到 Kotlin 后端的静态资源目录。因此日常开发中改完前端代码后运行pnpm run build即可让 Android/Ktor 服务直接提供最新页面。4. 目录结构与架构分层4.1 顶层目录职责web-ui的源码全部位于app/下各目录职责清晰目录职责app/routes/React Router 7 文件路由app/components/组件库ui / message / markdown / input / workbench / extendedapp/stores/Zustand 状态管理组合 slicesapp/hooks/自定义 React Hooksapp/services/API 服务层ky 客户端 SSE 实现app/types/TypeScript 类型与 Kotlin 对齐app/lib/工具函数cn / display / files / errorapp/locales/国际化语言文件zh-CN / en-USapp/assets/静态资源4.2 路由结构app/routes.ts 中只有两条路由规则export default [index(routes/home.tsx), route(c/:id, routes/c.$id.tsx)] satisfies RouteConfig;index(routes/home.tsx)/根路由route(c/:id, routes/c.$id.tsx)/c/:id对话详情路由。home.tsx与c.$id.tsx都是薄壳文件实际 UI 全部实现在routes/conversations.tsx650 行中两个路由文件通过 re-export 复用它。React Router 7 的文件路由会在.react-router/types/下自动生成类型这也是pnpm run typecheck第一步react-router typegen的作用来源。根布局 app/root.tsx 承担了全局初始化职责useSettingsSubscription()订阅设置流ThemeProvider提供亮/暗主题WebAuthGate处理 Web 访问鉴权Toaster全局消息提示QueryClientProvider提供 react-query 上下文同时定义了HydrateFallback加载占位符与ErrorBoundary错误边界区分 404 与运行时错误。5. 核心设计概念5.1 类型系统与 Kotlin 后端完全对齐app/types/下的所有类型与 Kotlin 后端严格一一对应这是整个前后端协作的基石。AGENTS.md 给出了完整的映射表TypeScript 类型Kotlin 类型后端位置MessageRoleMessageRoleai/src/main/java/me/rerere/ai/core/MessageRole.ktTokenUsageUsageai/src/main/java/me/rerere/ai/core/Usage.ktUIMessagePartUIMessagePartai/src/main/java/me/rerere/ai/ui/Message.ktUIMessageUIMessageai/src/main/java/me/rerere/ai/ui/Message.ktMessageNodeMessageNodeapp/src/main/java/me/rerere/rikkahub/data/model/Conversation.ktConversationConversationapp/src/main/java/me/rerere/rikkahub/data/model/Conversation.ktConversationDtoConversationDtoapp/src/main/java/me/rerere/rikkahub/web/dto/WebDto.ktSettingsSettingsapp/src/main/java/me/rerere/rikkahub/data/datastore/PreferencesStore.kt这条铁律是修改任何 TypeScript 类型时必须同步修改 Kotlin 后端对应类型否则序列化/反序列化会因字段缺失或多余而失败。AGENTS.md 为此专门设计了类型同步检查清单见下文 §9.3。5.2 Message Parts可组合的多模态消息模型消息由多个UIMessagePart通过联合类型组合而成一条消息可以同时包含文本、图片、视频、音频、文档、推理内容和工具调用。完整的类型定义见 app/types/parts.tstype UIMessagePart | TextPart // { type: text, text: string } | ImagePart // { type: image, url: string } | VideoPart // { type: video, url: string } | AudioPart // { type: audio, url: string } | DocumentPart // { type: document, url, fileName, mime } | ReasoningPart // { type: reasoning, reasoning, steps[] } | ToolPart // { type: tool, toolCallId, input, output, approvalState }从源码看ToolPart还携带toolName字段并内嵌一个完整的ToolApprovalState联合类型export type ToolApprovalState | { type: auto } | { type: pending } | { type: approved } | { type: denied; reason: string } | { type: answered; answer: string };这对应 AI 前端常见的工具调用需人工批准交互pending状态展示审批 UI批准后变为approved拒绝可附reason或直接以自然语言answered回填答案。每个 part 由app/components/message/parts/下的独立组件渲染见 §8.2 的分发器模式。5.3 Message Branching多分支对话树MessageNode支撑对话的分支能力每个节点可以持有多个可选分支消息interface MessageNode { id: string; messages: UIMessage[]; // 多个可选分支 selectIndex: number; // 当前选中的分支 }用户可以重新生成响应创建新分支在分支间切换对应selectIndex编辑消息创建分叉。后端在 app/src/main/java/me/rerere/rikkahub/data/model/Conversation.kt 维护同一棵消息树前端通过 SSE 的ConversationNodeUpdateEventDto增量事件保持分支状态同步。5.4 状态管理Zustand 组合 Slicesapp/stores/app-store.ts 演示了组合 slices 模式——多个 slice 通过展开合并进同一个 storeexport const useAppStore createAppStoreState()((...args) ({ ...createSettingsSlice(...args), // 全局设置 ...createChatInputSlice(...args), // 聊天输入 ...createClockSlice(...args), // 时钟/时间 })); export const useSettingsStore useAppStore; export const useChatInputStore useAppStore; export const useClockStore useAppStore;Settings Slice从后端 SSE 流/api/settings/stream实时更新包含助手、模型、提供商、显示设置等全局配置在 app/root.tsx 中通过useSettingsSubscription()一次性订阅。Chat Input Slice为每个对话维护独立的输入草稿支持文本 多媒体附件图片、视频、音频、文档编辑消息时保存源部分用于对比。Clock Slice维护时钟状态典型用途是消息时间显示。useSettingsSubscription的实现app/stores/hooks/use-settings-subscription.ts非常简洁——它从共享事件总线subscribeToEvent订阅EVENT_SETTINGS事件拿到最新Settings后调用setSettings写入 store所有依赖选择器的组件随之响应式更新。5.5 API 客户端ky 手写 SSEapp/services/api.ts 封装了基于ky的 HTTP 客户端同时提供 REST 与 SSE 两套能力// REST API await api.getT(url, options) await api.postT(url, data, options) await api.postMultipartT(url, formData) // 文件上传 await api.putT(url, data) await api.patchT(url, data) await api.deleteT(url, options) // SSE 流手动实现支持事件类型和多行 data sseT(url, { onMessage: ({ data, event, id }) { ... }, onError: (error) { ... }, onOpen: () { ... }, onClose: () { ... }, }, { signal: abortController.signal })客户端默认配置前缀/api开发时代理到http://localhost:8080、超时 30 秒、错误统一转换为ApiError类携带message与code字段。从源码可以看到两个 AGENTS.md 之外的重要细节Web 鉴权beforeRequesthook 会从localStorage读取rikkahub:web-auth令牌若未过期则自动附加Authorization: Bearer token401 响应会触发rikkahub:web-auth-required自定义事件并清空令牌由WebAuthGate组件弹窗要求输入密码换取新令牌POST /api/auth/token。对于img、video等无法带请求头的资源appendWebAuthQuery会把令牌作为?access_token查询参数拼接。SSE 手写解析由于需要支持鉴权头与 abort项目没有使用原生EventSource而是基于kyInstance.getReadableStream逐行解析event:/data:/id:字段空行表示一个事件结束支持多行 data 拼接。开发时代理配置在 vite.config.tsserver: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, }, }, }6. 国际化i18next 命名空间体系web-ui支持 zh-CN默认与 en-US 两种语言。翻译文件按功能模块拆分为 4 个命名空间避免单一 JSON 过大common通用 UI侧边栏、主题、快捷跳转等input输入相关聊天输入、模型选择、文件选择器等markdownMarkdown 渲染代码块、复制按钮等message消息显示消息部分、工具调用、推理步骤等。语言检测优先级见 app/i18n.tslocalStorage中的lang 浏览器语言 默认中文const fromStorage window.localStorage.getItem(lang); const browserLanguage window.navigator.language; const initialLanguage fromStorage || (browserLanguage.startsWith(zh) ? zh-CN : en-US);三种访问方式import { useTranslation } from react-i18next; function MyComponent() { // 默认命名空间 (common) const { t } useTranslation(); return div{t(chat.send_hint_enter)}/div; // 指定命名空间 const { t: tInput } useTranslation(input); return div{tInput(model_list.title)}/div; // 使用命名空间前缀 (推荐) return div{t(input:model_list.title)}/div; }添加新翻译键的标准流程先确定归属命名空间 → 在对应的app/locales/zh-CN/*.json与app/locales/en-US/*.json中成对添加键值 → 用t(namespace:key)访问。7. 构建与部署链路7.1 两阶段构建pnpm run build拆成两个阶段阶段一React Router Buildreact-router build # 输出: # - build/client/ → 静态资源 (HTML JS CSS) # - build/server/ → SSR 服务器代码 (SPA 模式下不使用)阶段二Copy to Backendpnpm run copy.ts # 将 build/client/ 复制到 ../web/src/main/resources/static/copy.ts 的实现要点先校验./build/client存在不存在则报错退出并提示先 build然后递归清空../web/src/main/resources/static目录再逐文件复制保证目标目录始终是干净的全量产物。7.2 静态文件服务链路web-ui/build/client/ ├── index.html ├── assets/*.js └── assets/*.css ↓ (copy.ts) ../web/src/main/resources/static/ ↓ (Ktor 静态文件路由) 用户访问 http://localhost:8080/也就是说一次pnpm run build之后Ktor 后端即可从web/src/main/resources/static直接对外提供整套 Web 界面无需额外的静态资源服务器。8. 开发规范与实现模式8.1 组件开发规范shadcn/ui 组件从~/components/ui/导入~路径别名指向app/样式为 New York 风格components.json: style: new-york图标统一从lucide-react导入。自定义组件的推荐模式是继承ComponentPropsT透传原生 propsimport type { ComponentProps } from react; interface MyComponentProps extends ComponentPropsdiv { value: string; onChange: (value: string) void; } export function MyComponent({ value, onChange, ...props }: MyComponentProps) { return div {...props}{value}/div; }状态读取模式全局状态一律通过 Zustand 选择器读取避免不必要的重渲染const settings useSettingsStore((state) state.settings); const currentModelId useSettingsStore((state) state.settings?.currentModelId); const setSettings useSettingsStore((state) state.setSettings); const setText useChatInputStore((state) state.setText); // 用选择器派生计算避免全量订阅 const currentAssistant useSettingsStore( (state) state.settings?.assistants.find(a a.id state.settings?.currentAssistantId) );UI 局部状态弹窗开关、选中索引等则使用useState。8.2 消息渲染分发器模式消息渲染采用容器 → 遍历 → 分发三层结构ChatMessage (容器) └── MessageParts (遍历 parts 数组) └── MessagePart (分发器 - 根据 part.type) ├── TextPart ├── ImagePart ├── VideoPart ├── AudioPart ├── DocumentPart ├── ReasoningPart (支持展开推理步骤) └── ToolPart (支持工具调用展示和批准)添加新 Part 类型的五步流程AGENTS.md 明确给出在 app/types/parts.ts 添加类型定义在 Kotlin 后端同步添加ai/src/main/java/me/rerere/ai/ui/Message.kt在app/components/message/parts/创建渲染组件在app/components/message/message-part.tsx添加分发逻辑在app/types/helpers.ts添加类型守卫。8.3 Markdown 渲染增强app/components/markdown/markdown.tsx提供增强 Markdown 渲染特性包括LaTeX 数学公式内联\(...\)与块\[...\]会被预处理为$...$与$$...$$交给 KaTeXrehype-katexGFM 支持表格、删除线、任务列表remark-gfm代码高亮基于 Shiki带复制按钮think标签转换为引用块样式对应推理内容的折叠展示引用链接citation,domain格式处理主题适配亮色/暗色自动切换。预处理流程先定位所有代码块位置避免在代码块内部误替换再替换 LaTeX 语法与think标签最后交给react-markdown及 rehype 插件链。8.4 文件 URL 解析app/lib/files.ts的resolveFileUrl(url)按以下规则解析消息附件data:URLbase64→ 直接返回http/https→ 直接返回file://Android 本地文件→ 映射为/api/files/path/...相对路径 → 映射为/api/files/path/...。该函数在ImagePart、VideoPart、AudioPart、DocumentPart组件中统一使用保证移动端本地文件在 Web 端也能通过 Ktor 文件接口访问。9. 与 Kotlin 后端的实时数据流9.1 应用启动流程root.tsx 渲染 ↓ useSettingsSubscription() 订阅 /api/settings/stream (SSE) ↓ 后端推送 Settings 对象 ↓ useSettingsStore.setSettings() 更新全局状态 ↓ 所有组件响应式更新 (助手、模型、提供商等)9.2 对话加载与消息发送流程对话加载采用快照 SSE 增量双通道用户选择/创建对话 ↓ GET /api/conversations/:id (获取初始快照) ↓ 返回 ConversationDto (完整消息树) ↓ 建立 SSE 连接: GET /api/conversations/:id/stream ↓ 后端推送事件: - ConversationSnapshotEventDto (大更新 - 完整快照) - ConversationNodeUpdateEventDto (增量更新 - 节点变化) ↓ UI 实时渲染消息和生成进度消息发送流程用户点击发送 ↓ useChatInputStore.getSubmitParts(conversationId) // 构建消息部分 ↓ POST /api/conversations/:id/send body: { parts: UIMessagePart[] } ↓ 后端处理并启动生成 ↓ SSE 流推送: node_update 事件 (每个 token 或部分) ↓ conversation.isGenerating true → false ↓ UI 实时显示流式响应 ↓ 生成完成后 useChatInputStore.clearDraft(conversationId)9.3 全部 API 端点后端端点定义在web模块的 Kotlin 代码中方法端点用途GET/api/settings/stream设置 SSE 流GET/api/conversations对话列表GET/api/conversations/:id获取对话快照GET/api/conversations/:id/stream对话 SSE 流POST/api/conversations/:id/send发送消息POST/api/files/upload文件上传GET/api/files/path/*文件访问9.4 类型同步检查清单修改任何跨端类型时AGENTS.md 要求逐项确认更新 TypeScript 类型app/types/更新对应的 Kotlin 类型参考 §5.1 类型映射表运行pnpm run typecheck确保前端类型正确在 Kotlin 端运行类型检查测试前后端数据序列化/反序列化更新相关组件的类型守卫app/types/helpers.ts10. 自定义 Hooks 模式10.1 useConversationList对话列表管理 hookapp/hooks/use-conversation-list.ts支持分页与实时更新const { conversations, // 对话列表 activeId, // 当前选中的对话 ID setActiveId, // 设置当前对话 loading, // 加载状态 error, // 错误信息 hasMore, // 是否有更多 loadMore, // 加载更多 refreshList, // 刷新列表 updateConversationSummary, // 增量更新 } useConversationList({ currentAssistantId, routeId, // 当前路由的对话 ID autoSelectFirst: true, pageSize: 30, });特性自动监听助手切换并刷新无限滚动分页基于react-infinite-scroll-componentSSE 事件驱动的增量更新排序规则为固定对话在前其余按更新时间降序。10.2 useCurrentAssistant / useCurrentModel从 Settings store 中提取当前助手与模型信息const { currentAssistant, currentAssistantId } useCurrentAssistant(); const { currentModel, currentProvider } useCurrentModel();11. 性能优化策略AGENTS.md 列出的四条优化手段与源码相互印证代码分割React Router 7 自动按路由分割首页与对话页分成独立 chunkTree ShakingTailwind v4 Vite 自动移除未使用的样式与代码选择性订阅Zustand store 全部通过选择器读取避免组件级联重渲染SSE 流式更新用服务端推送取代 API 轮询减少无效请求并实时渲染 token虚拟滚动对话列表使用react-infinite-scroll-component按需加载。12. 常见问题排查AGENTS.md 给出了四类高频问题的处理路径1. 开发服务器启动失败多为 5173 端口被占用lsof -ti:5173 | xargs kill -9 # 杀掉占用进程 pnpm run dev2. API 请求失败开发环境确保 Kotlin 后端在 8080 端口运行Vite 将/api代理到该地址# 在 Kotlin 项目目录 ./gradlew :web:run3. 类型错误运行类型生成与检查并核对.react-router/types/下的生成结果pnpm run typecheck4. 构建失败清理缓存与依赖后重装重建rm -rf node_modules .react-router build pnpm install pnpm run build注仓库为只读环境以上清理命令请在本地开发环境中执行。13. 小结RikkaHub 的web-ui是一个技术选型克制而完整的前端工程React Router 7 的 SPA 模式配合 Vite 提供极简的路由与构建体验Zustand 组合 slices 让全局设置与聊天输入状态边界清晰ky 客户端 手写 SSE 覆盖了 REST、文件上传与流式更新的全部通信需求而TypeScript 与 Kotlin 类型严格对齐这一设计则从根本上保证了嵌入式 Web 端与 Android 端共享同一套对话数据模型。理解 web-ui/AGENTS.md 与上述源码你就掌握了在这个仓库中为 RikkaHub 开发 Web 界面、排查问题与扩展新消息类型的完整方法论。赞分享人工智能大模型AI 应用移动开发交互助手【免费下载链接】rikkahubRikkaHub is an Android APP that supports for multiple LLM providers.项目地址https://gitcode.com/gh_mirrors/ri/rikkahub点击查看免费下载相关推荐RikkaHub web-ui 前端架构深度解析React Router 7 SPA、Zustand 状态流与 Kotlin 后端的实时协同RikkaHub web ui 前端架构深度解析React Router 7 SPA、Zustand 状态流与 Kotlin 后端的实时协同 RikkaHub人工智能大模型AI 应用移动开发交互助手Nasiko 控制平面 UI 技术指南基于原生 Web Components 的嵌入式管理界面架构解析Nasiko 控制平面 UI 技术指南基于原生 Web Components 的嵌入式管理界面架构解析 本文基于仓库文档 docs/CONTROL_PLANEgit-bug Web UI 前端架构深度解析从 Vite React 到嵌入式 SPA 的完整工程实践git bug Web UI 前端架构深度解析从 Vite React 到嵌入式 SPA 的完整工程实践 git bug 是一个内嵌于 Git 仓库的分布开发工具研发协作上一篇assistant-ui 移动端接入指南使用 assistant-ui/metro 在 Expo / React Native 中启用 use generative 工具编译下一篇Jupyter Notebook依赖管理终极指南5个pipreqs高级技巧提升数据科学工作流创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表