详细设计解析)
Onyx 移动端输入栏控件ActionsPopover Deep Research 开关详细设计解析【免费下载链接】danswerOpen Source AI Platform - AI Chat with advanced features that works with every LLM项目地址: https://gitcode.com/GitHub_Trending/da/danswer本文是 Onyx原 Danswer移动端聊天输入栏控件改造的详细设计Detailed Design文档解读。它聚焦于一个明确的工程目标把 Web 端聊天输入栏的工具栏控件移植到 React Native 移动端——包括深度研究Deep Research开关胶囊与锚定式 ActionsPopover 弹层强制使用某个工具、启用/禁用工具、以及知识源选择子视图。读者读完本文后将掌握移动端如何在零后端改动的前提下复用/persona已下发的工具与知识源数据、通过四个新增请求字段打通发送链路以及如何以Web 优先Web-Parity-First为原则用Popover、SelectButton、Switch三个新原语复刻 Web 交互细节并理解其中平台驱动的差异点与实现风险。该设计文档属于 docs/mobile-chat/input-bar-controls/ 系列00-index → 01-research → 02-high-level-design → 03-detailed-design → 04-implementation-plan → 05-pr-roadmap的第三篇处于详细设计阶段对应仓库路径 03-detailed-design.md。一、设计总览零后端改动纯前端移植本设计的核心前提是**Web 端是移植的事实来源source of truth移动端在观感与结构上对齐 Web并记录平台驱动的差异**即文档中定义的 Approach B — Web-Parity-First。最关键的负载性事实load-bearing facts在设计文档与源码中均可得到验证/persona接口返回的 Agent 数据已经携带tools与knowledge_sources字段移动端无需新增目录获取请求后端SendMessageRequest已经接受deep_research、allowed_tool_ids、forced_tool_id、internal_search_filters四个发送字段每个 Agent 的disabled_tool_ids已经有对应的数据表与GET/PATCH接口。因此结论非常明确没有后端工作没有数据库迁移全部工作集中在移动端mobile/目录React Native Expo。二、数据库设计复用既有表不做任何 Schema 变更设计文档明确给出N/A — no schema changes。唯一的持久化状态是每个 Agent 的disabled_tool_ids而该数据通路已完整存在层位置说明表backend/onyx/db/models.py 中Assistant__UserSpecificConfig__tablename__ assistant__user_specific_config复合主键(assistant_id, user_id)disabled_tool_ids: ARRAY(Integer) NOT NULL两个外键均带ondeleteCASCADE迁移backend/alembic/versions/b329d00a9ea6_adding_assistant_specific_user_.py该表已存在并被迁移DB 层backend/onyx/db/user_preferences.pyget_all_user_assistant_specific_configs按 user_id 查询全部配置update_assistant_preferencesupsert已存在则更新disabled_tool_ids否则新建行后db_session.commit()接口backend/onyx/server/manage/users.py两个接口均要求Permission.BASIC_ACCESSGET /user/assistant/preferences返回UserSpecificAssistantPreferences即dict[int, {disabled_tool_ids: list[int]}]PATCH /user/assistant/{assistant_id}/preferences请求体为UserSpecificAssistantPreference {disabled_tool_ids: list[int]}返回 200 空 body源码佐证update_assistant_preferences的 upsert 逻辑backend/onyx/db/user_preferences.py与两个路由的完整实现backend/onyx/server/manage/users.py与设计文档描述完全一致。四个发送字段已存在于后端请求模型设计文档指出SendMessageRequest上已有的四个发送字段源码确认如下backend/onyx/server/query_and_chat/models.pyallowed_tool_ids: list[int] | None None forced_tool_id: int | None None file_descriptors: list[FileDescriptor] [] internal_search_filters: BaseFilters | None None deep_research: bool False其中internal_search_filters的类型BaseFilters定义在 backend/onyx/context/search/models.py其source_type: list[DocumentSource] | None None等其余字段默认为None——这正是设计文档最小化传输形状只发送{ source_type: [...] }其余字段后端默认 null的依据。三、类 / 接口设计移动端新契约3.1 聊天层新契约mobile/src/chat/设计文档给出了三组核心 TypeScript 契约全部镜像Web 端工具快照与工具判定tools.ts对应 Web 的web/src/lib/tools/interfaces.tsTier-2 子集export interface ToolSnapshot { id: number; name: string; display_name: string; description: string; in_code_tool_id: string | null; // 匹配 SEARCH_TOOL_ID 等 mcp_server_id: number | null; // MCP 工具在 Tier-2 从列表排除 chat_selectable: boolean; // 可见性过滤 }内置工具标识常量取自 Web 的web/src/app/app/components/tools/constants.tsexport const SEARCH_TOOL_ID SearchTool; export const WEB_SEARCH_TOOL_ID WebSearchTool; export const IMAGE_GENERATION_TOOL_ID ImageGenerationTool; export const FILE_READER_TOOL_ID FileReaderTool; // 始终从列表隐藏 // (PYTHON_TOOL_ID, OPEN_URL_TOOL_ID, CODING_AGENT_TOOL_ID 用于图标映射)工具判定函数族export function isSearchTool(t: ToolSnapshot): boolean; // in_code_tool_id SEARCH_TOOL_ID export function hasSearchToolsAvailable(tools: ToolSnapshot[]): boolean; // Search 或 WebSearch 存在 export function displayableTools(tools: ToolSnapshot[]): ToolSnapshot[]; // chat_selectable、排除 MCP 与 FileReader export function computeAllowedToolIds(tools, disabledToolIds): number[] | null; // agent 工具 − 被禁用的工具无禁用时返回 null后端视 null 允许全部 export const getIconForToolId: (inCodeToolId: string | null) IconFunctionComponent;知识源与搜索过滤sources.ts对应 Web 的ValidSources 源元数据 Tier-2 子集export type DocumentSource string; // snake_case 线格式值 (web,google_drive,…) export interface SourceMeta { icon: IconFunctionComponent; displayName: string; } export const SOURCE_META: RecordDocumentSource, SourceMeta; // 兜底 → 通用 globe/file export function getSourceMeta(s: DocumentSource): SourceMeta; export function buildInternalSearchFilters(selectedSources): InternalSearchFilters | null; // → { source_type } | null线格式新增类型mobile/src/api/chat/stream.tsexport interface InternalSearchFilters { source_type: DocumentSource[] | null; }最终解析后的发送载荷提交给submit()的聚合对象export interface ChatToolOptions { deepResearch: boolean; allowedToolIds: number[] | null; forcedToolId: number | null; internalSearchFilters: InternalSearchFilters | null; }3.2 新 UI 原语mobile/src/components/ui/select-button.tsx—— 有状态胶囊按钮镜像 Opal 的 SelectButton状态驱动、无 hovertype SelectState empty | selected; type SelectVariant select-light; // Tier-2 唯一需要的变体 interface SelectButtonProps { icon?: IconFunctionComponent; children?: string; // 标签 state?: SelectState; // 默认 empty variant?: SelectVariant; // 默认 select-light foldable?: boolean; // 折叠时隐藏标签仅图标 disabled?: boolean; onPress?: () void; accessibilityLabel?: string; }配套的select-button.styles.ts提供SELECT_COLORS: RecordSelectVariant, RecordSelectState, Recordrest|active|disabled, {bg;fg;icon}颜色矩阵以及resolveSelectState(disabled, pressed)状态解析函数——镜像button.styles.ts的resolveButtonState但去掉 hover移动端没有 hover。switch.tsx—— 开关镜像 Opal Switchinterface SwitchProps { checked: boolean; onCheckedChange: (checked: boolean) void; disabled?: boolean; accessibilityLabel?: string; }轨道 32×18rounded-full滑块 14×14选中时轨道背景为action-link-05滑块位移用 reanimated 动画。popover.tsx—— 锚定浮动面板Portal reanimated measureInWindowinterface PopoverProps { open: boolean; onClose: () void; anchorRef: RefObjectView; // 触发组件打开时测量 width?: number; // 默认 240对应 Web lg w-60 children: ReactNode; }从底部停靠的触发组件向上打开钳制在屏幕内打开时调用Keyboard.dismiss()。3.3 状态 Hookmobile/src/hooks/与状态提供器mobile/src/state/Hook职责持久性useDeepResearchToggle({ chatSessionId, agentId })深度研究开关完全复刻 Web 的 reset 语义ref 守卫仅当previousId ! null previousId ! chatSessionId时重置agentId变化时总是重置。移植自web/src/hooks/useDeepResearchToggle.ts55 行临时ephemeraluseForcedTools({ agentId })单元素强制语义forcedToolIdtoggleForcedTool(id)clear()切换 agent 时重置临时useAgentPreferences()disabledToolIdsFor(agentId)/setDisabledToolIds(agentId, ids)乐观更新 PATCH invalidateTanStack Query 以serverUrl为 key 请求GET /user/assistant/preferences服务端持久化useConnectorSources()GET /manage/connector-status→BasicCCPairInfo[].map(c c.source)去重federated/federated为 EE 专属Tier-2 暂缓可缓存useSourceSelection({ agentId, availableSources, hasSearchTool })每个 agent 的临时源选择isEnabled(s)/toggle(s)/enableAll()/disableAll()/initializedavailableSources 首次非空时自动初始化为全部临时状态聚合器ComposerToolsProvider.tsxcontext hubuseComposerTools(): { ...triggers/state for InputBar ActionsPopover... resolveToolOptions(): ChatToolOptions; // submit() 消费的对象 }它挂载上述四个 Hook以${sessionId}:${projectId}agentId为 key并对外暴露resolveToolOptions(){ deepResearch, allowedToolIds: computeAllowedToolIds(tools, disabled), forcedToolId, internalSearchFilters: buildInternalSearchFilters(selectedSources) }四、新增文件与目录结构设计文档给出了完整的新增文件清单新文件按职责文件职责mobile/src/components/ui/popover.tsx锚定浮动面板原语测量触发组件、Portal、向上打开、键盘处理mobile/src/components/ui/select-button.tsx有状态胶囊原语empty/selected、可折叠支撑深度研究 强制工具胶囊mobile/src/components/ui/select-button.styles.tsSELECT_COLORS矩阵 resolveSelectState镜像button.styles.tsmobile/src/components/ui/switch.tsx轨道滑块开关reanimated源行使用mobile/src/components/chat/ActionsPopover.tsx工具菜单组合Popover 主列表 源子视图持有open/subViewmobile/src/components/chat/ActionLineItem.tsx单工具行点击强制、尾部启用/禁用 下钻箭头mobile/src/components/chat/SourceSwitchList.tsx二级视图返回 全部启用/全部禁用 Switch行mobile/src/components/chat/SourceIcon.tsxDocumentSource→ logo/字形使用SOURCE_METAmobile/src/components/chat/ToolbarControls.tsx在InputBar渲染深度研究胶囊 强制工具胶囊 Actions 触发按钮mobile/src/chat/tools.tsToolSnapshot类型、工具 id 常量、判定函数、getIconForToolIdmobile/src/chat/sources.tsDocumentSource、SOURCE_META、buildInternalSearchFiltersmobile/src/hooks/useDeepResearchToggle.ts临时深度研究状态Web hook 移植mobile/src/hooks/useForcedTools.ts单强制状态agent 变化时重置mobile/src/hooks/useAgentPreferences.ts每 agentdisabled_tool_ids的 GET/PATCHmobile/src/hooks/useConnectorSources.tsGET/manage/connector-status→ 源列表mobile/src/hooks/useSourceSelection.ts每 agent 临时源选择 搜索耦合mobile/src/api/chat/agentPreferences.tsgetAgentPreferences()/patchAgentPreferences(agentId, ids)mobile/src/api/chat/connectors.tsgetConnectorSources()connector-status 获取mobile/src/state/ComposerToolsProvider.tsxContext 中枢聚合四个字段resolveToolOptions()mobile/src/icons/{hourglass,globe,cpu,link,server,plug,unplug,slash}.tsx8 个新 SVG 图标精确的 Web path 数据见 04-implementation-plan被修改的文件文件变更mobile/src/chat/agents.ts扩展MinimalAgent增加tools: ToolSnapshot[]、knowledge_sources: DocumentSource[]mobile/src/api/settings.ts给WorkspaceSettings增加deep_research_enabled?: booleanmobile/src/api/chat/stream.ts给SendMessageBody增加allowed_tool_ids?、forced_tool_id?、internal_search_filters?新增InternalSearchFilters类型mobile/src/hooks/useChatController.tssubmit(text, files?, onAccepted?, toolOptions?)替换硬编码的deep_research: false:296并填充三个新字段:291mobile/src/components/chat/InputBar.tsx在左侧簇:119-127渲染ToolbarControls接收 agent toolbar propsmobile/src/components/chat/ChatSurface.tsx包裹ComposerToolsProvider传入liveAgent.tools将resolveToolOptions()贯穿到sendWithAttachments→submitmobile/src/api/query-keys.ts新增agentPreferences、connectorSourceskey以serverUrl为 key完整目录树新增部分mobile/src/ ├── components/ │ ├── ui/ │ │ ├── popover.tsx (new) │ │ ├── select-button.tsx (new) │ │ ├── select-button.styles.ts (new) │ │ ├── switch.tsx (new) │ │ ├── button.tsx / button.styles.ts (SELECT_COLORS 的参照) │ │ └── line-item-button.tsx (复用 — rightChildren 插槽已存在) │ └── chat/ │ ├── ActionsPopover.tsx (new) │ ├── ActionLineItem.tsx (new) │ ├── SourceSwitchList.tsx (new) │ ├── SourceIcon.tsx (new) │ ├── ToolbarControls.tsx (new) │ ├── InputBar.tsx (modified: 左侧簇渲染 ToolbarControls) │ ├── ChatSurface.tsx (modified: ComposerToolsProvider 贯穿选项) │ └── FilePickerSheet.tsx (unchanged — 独立的回形针底表) ├── chat/ │ ├── tools.ts (new) │ ├── sources.ts (new) │ └── agents.ts (modified: 扩展 MinimalAgent) ├── hooks/ │ ├── useDeepResearchToggle.ts (new) │ ├── useForcedTools.ts (new) │ ├── useAgentPreferences.ts (new) │ ├── useConnectorSources.ts (new) │ ├── useSourceSelection.ts (new) │ └── useChatController.ts (modified: submit toolOptions body 构建) ├── api/ │ ├── chat/ │ │ ├── agentPreferences.ts (new) │ │ ├── connectors.ts (new) │ │ └── stream.ts (modified: SendMessageBody InternalSearchFilters) │ ├── settings.ts (modified: deep_research_enabled) │ └── query-keys.ts (modified) ├── state/ │ └── ComposerToolsProvider.tsx (new) └── icons/ ├── hourglass.tsx globe.tsx cpu.tsx link.tsx (new) └── server.tsx plug.tsx unplug.tsx slash.tsx (new)五、每个文件的实现要点popover.tsx打开时调用anchorRef.current.measureInWindow((x,y,w,h)…)记录触发组件矩形渲染一个全屏Portal nameactions-popover内含透明的外部点击Pressable点击关闭与Animated.View面板。定位公式bottom windowHeight - anchorY GAPleft clamp(anchorX, GUTTER, screenW - width - GUTTER)maxHeight anchorY - insets.top - GAP内容放入ScrollView打开时Keyboard.dismiss()reanimated 实现FadeIn 从底部原点的轻微translateY/scale。镜像 Opal 的Popover.Content sidebottom alignstart widthlgweb/lib/opal/.../popover/components.tsx但翻转为向上打开——这是文档记录的平台差异divergence。select-button.tsx.styles.ts按Button的方式构建但使用有状态矩阵SELECT_COLORS[variant][state][colorState]单元格为{bg,fg,icon}resolveSelectState(disabled, pressed)无 hover。select-light变体所有状态透明背景empty状态 fgtext-04/icontext-03selected状态 fg/iconaction-link-05取自stateful/styles.css:228-316。foldable折叠为仅图标——由于移动端没有:hover实现为条件标签渲染由state/按压驱动Web 的foldable{!enabled}语义 关闭时仅图标、开启时显示标签深度研究场景恰好需要这种表现无需 hover 展开。图标 16pxiconWrapperlg 1rem。switch.tsxroleswitch的Pressable轨道32×18rounded-fullreanimated 滑块14×14在2px↔17px间平移。轨道背景background-tint-03→ 选中action-link-05禁用变体遵循switch/styles.css。受控于checked/onCheckedChange。ActionsPopover.tsx组合Popover本地subView: {type:sources} | null镜像 Web 的secondaryView。主视图displayableTools(agent.tools).map(t ActionLineItem/)。二级视图SourceSwitchList/。读写useComposerTools。面板内原地替换内容并带滑动动画reanimatedLinearTransition保持同一面板与锚点。ActionLineItem.tsx单行LineItemButtonselected{forcedToolIdtool.id}onPress强制切换搜索工具尚未被强制时 → 打开源子视图镜像 WebActionLineItem.tsx:98-108。rightChildren尾部启用/禁用控件常显Switch或带SvgSlash的图标Button——移动端无 hover无法悬停显示 部分搜索源时的EnabledCount文本 搜索下钻的SvgChevronRight。禁用工具渲染为暗淡样式移动端LineItemButton无删除线——用colormuted 删除线样式记录为平台差异。SourceSwitchList.tsx返回箭头头部SvgChevronLeftButton 全部启用/全部禁用LineItemButtonSvgPlug/SvgUnplug 每源行leadingSourceIcon、标签、rightChildrenSwitch。镜像SwitchList.tsx:61-119。搜索框省略——Tier-2 范围。ToolbarControls.tsx按顺序渲染Actions 触发按钮SvgSlidersButtonref作为 popover 锚点——当displayableTools(agent.tools).length0时深度研究SelectButtonSvgHourglassstateon?selected:emptyfoldable{!on}——当deep_research_enabled hasSearchToolsAvailable(agent.tools)时强制工具胶囊SelectButtonstateselected工具图标名称点击移除。挂载ActionsPopover。API 薄封装agentPreferences.ts/connectors.ts是薄apiFetch包装路径为裸路径——getBaseUrl()已追加/apiapiFetch(/user/assistant/preferences) apiFetch(/user/assistant/${id}/preferences, {method:PATCH, body}) apiFetch(/manage/connector-status)8 个新图标react-native-svg移植 Web 的 path 数据viewBox0 0 16 16link除外 0 0 17 9rotate(315deg)精确数据在04-implementation-plan.md中逐字捕获。六、集成点Integration Points设计文档列出了所有与现有代码的衔接点InputBar.tsx:119-148— 左侧簇flex-row items-center gap-8当前仅回形针在回形针后新增ToolbarControls agent{…} tools{…}/。右侧簇发送/停止不变。ChatSurface.tsx— 子树包裹ComposerToolsProvider sessionId agentId agent{liveAgent}sendWithAttachments调用submit(text, descriptors, onAccepted, resolveToolOptions())。useChatController.ts:228,291-298—submit增加第 4 个参数toolOptions?: ChatToolOptionsbody 字面量设置deep_research: toolOptions?.deepResearch ?? false、allowed_tool_ids、forced_tool_id、internal_search_filters。runChatStream原样转发body。stream.ts—SendMessageBody扩展JSON 原样序列化。useAgents()/useLiveAgent— 无需改动MinimalAgent扩展后liveAgent.tools/knowledge_sources自动解析数据已在/persona线上见 backend/onyx/server/features/persona/models.py。useWorkspaceSettings()— 从现有/settingsGET 读取deep_research_enabledbackend/onyx/server/settings/models.py。app/_layout.tsx:71— 现有PortalHost/承载 popover无需改动。onyx-ai/shared—本次功能不涉及。契约原生存在于mobile/src/chat/符合聊天层原生而非共享的既定决策参见mobile/src/chat/contracts/projects.ts先例。共享抽取推迟到未来有成熟复用需求时。端到端流程验证对应高层面设计02-high-level-design.md的调用链GET /persona ──► agent.tools[], agent.knowledge_sources[]已在线仅需扩展类型 ChatSurface: useLiveAgent useWorkspaceSettings └─ ComposerToolsProvider以 sessionagent 为 key 的状态中枢 ├─ useDeepResearchToggle → deepResearchEnabled临时 ├─ useForcedTools → forcedToolId临时 ├─ useAgentPreferences → disabledToolIds ◄──► GET/PATCH /…/agent-preferences └─ useSourceSelection → selectedSources临时 └─ resolveToolOptions() → { deep_research, allowed_tool_ids, forced_tool_id, internal_search_filters } └─ InputBar: [paperclip] [Actions ▸] [DeepResearch pill] [forced-tool pills…] [send] └─ ActionsPopover ──► PortalHost锚定 ├─ ActionLineItem 行强制 启用/禁用 箭头 └─ SourceSwitchList返回 全部启用 Switch 行 发送useChatController.submit(text, files, onAccepted, toolOptions) └─ 构建 SendMessageBody4 字段→ runChatStream → POST /chat/send-chat-message七、实现前必读的重要注意事项设计文档用整节篇幅列出实现前的关键风险与约束这些是移植成功与否的分水岭7.1 键盘邻近底部栏上的锚定弹层是 #1 风险锚定弹层位于键盘邻近的底部停靠栏是最大风险点。要求向上打开bottom锚定数学打开时Keyboard.dismiss()left/width钳制到屏幕内maxHeight上限 滚动在keyboardWillShow/Hide与屏幕旋转时重新测量。这是设备端关卡on-device gate——Agent 无法验证负责人必须运行 dev build。兜底方案若锚定在设备上不稳定将相同的ActionsPopover内容通过FilePickerSheet底表外壳渲染仅替换容器——内容与渲染器无关。7.2SELECT_COLORS必须是完全类型化的字面量矩阵像BUTTON_COLORSbutton.styles.ts:30-189一样不允许计算键且只用 NativeWind 语义 token 类bg-*、text-*。移动端无 hover因此折叠 Web 的 hover 单元格保持icon与fg分离Web 分别设置。7.3foldable 状态驱动而非 hover 驱动Web 的Interactive.Foldable是 CSS:hover触发的网格动画。移动端仅在胶囊展开时渲染标签深度研究场景为stateselected不要尝试 hover 展开。7.4 无 hover 的启用/禁用交互Web 在行 hover 时显示SvgSlash。移动端需要决定一个固定交互工具行尾部的常显Switch或常显SvgSlash图标Button并一致应用。移植 Web 的守卫禁用当前被强制工具会清除强制ActionLineItem.tsx:98-108。推荐尾部Switch与源行保持一致。7.5 搜索工具 ↔ 知识源耦合必须精确移植守卫否则渲染死循环移植自ActionsPopover/index.tsxreconcile effect 由if (searchToolIdnull || !sourcesInitialized) return;:742-759门控且仅在不一致时切换。强制工具是单元素集合:299-307。启用某源会自动固定搜索禁用最后一个源会解除固定:365-396previouslyEnabledSourcesRef普通 ref在搜索重新启用时恢复源:790-810。必须镜像sourcesInitialized等价守卫并保持仅在不同时切换的幂等性setSearchToolEnabled:762-770。先交付源→过滤选择再把耦合作为独立的、jest 测试的步骤加入。7.6useDeepResearchToggle的 null→新会话保留语义精确复刻 ref 守卫仅在previousId ! null previousId ! chatSessionId时重置为 falseagentId变化时总是重置。naive 的[chatSessionId]重置会在发送中途的 null→新会话转换时丢失标志位。web/src/hooks/useDeepResearchToggle.ts:31-44。7.7allowed_tool_ids发送计算后的启用列表绝不发送裸[]computeAllowedToolIds在无禁用时返回null后端视null 允许全部发送[]会禁用一切。必须匹配 Web 的enabledToolIds语义。7.8 默认 Agentid 0的知识源knowledge_sources对 id 0 为空Web 使用连接器列表将其视为全部可访问。移动端useConnectorSourcesGET /manage/connector-status任何可聊天访问的用户提供该列表非默认 agent 使用agent.knowledge_sources为空但有搜索工具时回退为全部ActionsPopover/index.tsx:199-210。Federated connectorsGET /federatedEE推迟——这是 Tier-2 针对 federated-only 源的有文档记录的缺口。7.9disabled_tool_ids与 Web 共享移动端禁用的工具 PATCH 到与 Web 读取的同一个每用户每 agent 记录——有意的跨客户端同步。PATCH 发送完整数组后端字段为必填。乐观更新后 invalidate守卫快速连续切换的竞态。7.10 PII / 缓存connectorSources与agentPreferences非聊天内容可安全持久化到 MMKV Query 缓存。不要持久化selectedSources/forcedToolId/deepResearch临时状态存在于 provider 状态绝不进入 Query。7.11internal_search_filters最小化形状仅发送{ source_type: [...] }BaseFilters其余字段后端默认 nullbackend/onyx/context/search/models.py。值为 snake_caseDocumentSource字符串。7.12FILE_READER_TOOL_ID与 MCP 工具始终排除displayableTools从 Actions 列表排除它们MCP/OAuth 行、action 搜索框、管理端 More Actions 链接均不在范围内。7.13 测试策略tools.ts/sources.ts是纯函数 → jest 单元测试computeAllowedToolIds的 null vs 列表、hasSearchToolsAvailable、displayableTools过滤、buildInternalSearchFilters。useDeepResearchTogglereset 矩阵与源耦合 reducer 是其他高价值单测目标。测试中直接导入叶子组件reanimated barrels 会令 jest 崩溃——参见mobile/CLAUDE.md。八、与系列文档的关系本详细设计承接 01-research.md需求、Tier-2 范围决策、两端代码扫描、popover 行业分析、三种方案与选型与 02-high-level-design.md端到端流程、组件交互图、端到端场景、关键决策并向下输出给 04-implementation-plan.mdCLAUDE.md 格式计划 plan-challenge六项检查全过 8 个新图标精确 SVG path与 05-pr-roadmap.md4 个评审级 PR基础 → 深度研究骨架 → ActionsPopover 工具 → 源 耦合含范围、文件、测试与漂移检查点。九、总结03-detailed-design.md展示了 Onyx 移动端一次典型的零后端改动、纯前端移植功能设计通过复用已存在的/persona数据、四个已接受的后端请求字段以及每 agentdisabled_tool_ids的既有表与接口将 Web 端的深度研究开关与 Actions 工具/源选择控件完整复刻到 React Native 端。设计的精髓在于Web-Parity-First的严格对齐三个新 UI 原语Popover、SelectButton、Switch在观感与交互上精确镜像 Web/Opal 实现同时诚实记录移动端平台驱动的差异向上打开、无 hover、状态驱动折叠而实现注意事项部分则把移植中最容易踩坑的边界条件锚定弹层的键盘处理、搜索-源耦合的守卫、allowed_tool_ids的 null 语义、deep-research 的 reset 矩阵全部显式化为后续实现与评审提供了可验证的执行清单。【免费下载链接】danswerOpen Source AI Platform - AI Chat with advanced features that works with every LLM项目地址: https://gitcode.com/GitHub_Trending/da/danswer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考