Onyx 移动端 Agentic Reasoning Timeline 移植设计:从 Web 到 React Native 的对等实现研究
2026/9/11 20:26:10 网站建设 项目流程

Onyx 移动端 Agentic Reasoning Timeline 移植设计:从 Web 到 React Native 的对等实现研究

【免费下载链接】danswerOpen Source AI Platform - AI Chat with advanced features that works with every LLM项目地址: https://gitcode.com/GitHub_Trending/da/danswer

本篇技术指南基于 Onyx(danswer)仓库中docs/mobile-chat/9b-timeline/01-research.md的设计研究文档,系统讲解如何将 Web 端聊天界面中的Agent 推理时间线(AgentTimeline,即 reasoning / search / tool 子步骤的容器化展示)1:1 移植到 Onyx React Native + Expo 移动应用(mobile/)中。读者将掌握:Web 端时间线的数据流与渲染契约、移动端现有的可复用接缝、三种实现方案的取舍,以及最终选定的"完整外壳优先"(Full-Parity)方案在源码中的具体落地形态,并理解react-hooks/refs等平台约束如何驱动实现层面的必要分叉。

需求背景:为什么要把 Web 推理时间线搬进移动端

Web 端的 AI 助手回答中,Agent 在给出最终答案前会经历一段"推理过程"——包括内部推理(reasoning)、搜索、工具调用等多个子步骤。Web 端已经通过AgentTimeline组件将这些子步骤以步骤容器(step container)+ 头部(header)+ 图标(icon)+ 连接线(connector)+ 折叠/展开(collapse/expand)的形式呈现给用户。

9b 阶段的目标非常明确(需求原文):

  • 将这套时间线移植到 Onyx 的移动端(React Native + Expo,即mobile/目录),达到Web 对等(web parity)——不仅要"看起来一样",结构也要一样;
  • 在 9a(引用来源CitedSources)的基础上扩展,并注册进 PR-3 渲染器分发(renderers/registry.ts)体系中;
  • 后端不做任何改动——推理数据包(reasoning packets)后端早已发出;
  • 9b 将构建AgentTimeline组合层(composition layer),后续所有阶段(search/fetch/tool 渲染器、9c–9e)都将接入这一层。

范围澄清:9b 只做"最细的行走骨架"

按 2026-07-16 所有者的范围锁定,9b 采用FOUNDATION-ONLY(仅基础)策略,只交付三件事:

  1. turn/tab 分组引擎(grouping engine)
  2. AgentTimeline组合层(真实步骤 + 折叠/展开);
  3. 唯一的 reasoning 步骤渲染器(处理reasoning_start / reasoning_delta / reasoning_done三种包)。

其余一切均为独立后续阶段:

后续阶段内容
内部/Web 搜索、fetch/open_url独立渲染器阶段
python/code、coding-agent + bash独立渲染器阶段
custom-tool(自定义工具)独立渲染器阶段
deep-research + 嵌套 Agent(sub_turn_index独立渲染器阶段
memory(记忆)独立渲染器阶段
图片生成独立阶段(9e)
多模型(model_index移动端单模型,明确不做,但分组必须容忍非零model_index
并行工具标签页(tab_index并行)后续阶段,但分组键与数据模型不得排除其可能性

同时有三个硬性约束:Web 对等是硬要求(不发明新的移动端时间线);聊天层保持原生(位于mobile/src/chat/,而非@onyx-ai/shared,这是 PR-2 的既定决策);后端不变

现有代码基础:9b 的接缝早已预留

研究文档通过代码库扫描(并逐一实际读取验证)确认,9b 需要依赖的所有"接缝"(seam)在移动端已经存在且被显式预留:

1. 扁平化包处理器 ——mobile/src/chat/messageProcessor.ts

这是一个扁平、游标增量式nextPacketIndex)的 packet→state 归约器:

ProcessedMessageState = { nodeId: number; nextPacketIndex: number; // 游标:只处理游标之后的包 citationMap: CitationMap; citations: StreamingCitation[]; seenCitationDocIds: Set<string>; documentMap: Map<string, SearchDoc>; isComplete: boolean; stopReason: StopReason | undefined; // ... }

源码头部注释明确写道:"9b extends it with turn/tab grouping + timeline steps, so the shape here stays deliberately flat (grouping-free)."—— 即 9b 将在其上扩展 turn/tab 分组与时间线步骤,因此当前形态刻意保持扁平(无分组)。

从当前仓库源码看,messageProcessor.ts已实现了getGroupKey${turn_index}-${tab_index ?? 0})、injectSectionEnd(合成SECTION_END让步骤读起来完整)、handleTurnTransition(新的turn_index关闭所有先前打开的分组)等核心机制——这正是文档中"分组引擎"的雏形。

2. 时间线组件桩 ——mobile/src/components/chat/AgentTimeline.tsx

研究文档描述它当时是一个stub:36px 导轨(w-36)、24pxAgentAvatar、reanimated 微光ThinkingLabelTimelineStep列表原语(TimelineStepData = {key, label, status: "running"|"done"|"error", icon?}),连接线h-8 w-[1px] bg-border-01,首尾opacity-0stepsprop 恒为[],无人填充它。它被渲染在MessageRow.AssistantMessage中、答案上方

而从当前仓库看,AgentTimeline.tsx已经是完整的 Web 移植版本:接收turnGroups: TurnGroup[]chatStatestopReason等,内部调用useTimelineHeaderuseTimelineMetricsuseTimelineExpansionuseTimelineUIState,并根据 7 种 UI 状态分发StreamingHeader / ParallelStreamingHeader / StoppedHeader / CompletedHeader——即文档中"Approach C"的设计已经在仓库中落地,这也印证了研究结论的可行性。

3. 渲染器注册表 ——mobile/src/components/chat/renderers/registry.ts

移动端渲染器契约比 Web 更简单

MessageRenderer = { matches(packets), Component } MessageRendererProps = { packets, processed }

采用first-match-winsRENDERERS数组 +findRenderer,目前只注册了MessageTextRenderer。相比 Web 的 render-prop 形式MessageRenderer<T,S>,这是文档明确点出的简化。

4. 每 flush 全量重算 ——mobile/src/hooks/usePacketDisplay.ts

processed = useMemo(() => processPackets(createInitialState(nodeId), packets), [nodeId, packets])

每次 flush 全量重算(数组身份每次变化)。文档特别强调:这刻意不是渲染期可变的 ref——移动端的react-hooks/refslint禁止在渲染期间访问ref.current,因此 Web 端用 ref 持有的usePacketProcessor模式在移动端非法。这一点成为后续所有方案共同的硬约束。

5. 消息行组装 ——mobile/src/components/chat/MessageRow.tsx

AssistantMessage的组装顺序为:usePacketDisplay(node)<Renderer packets processed/>(位于<View className="px-12">内)→AgentTimeline在其上 → 9a 的CitedSources在其下。hasContent = Renderer != null && packets.length > 0

6. 流式模型 ——mobile/src/chat/streamingModels.ts

PacketType当前包含:MESSAGE_START/DELTA/ENDSTOPSECTION_ENDERRORCITATION_INFOSEARCH_TOOL_DOCUMENTS_DELTAOPEN_URL_DOCUMENTSPlacement已携带全部 4 个字段(turn_indextab_indexsub_turn_indexmodel_index)。文档要求新增REASONING_START/DELTA/DONE(以及用于分组容忍的TOP_LEVEL_BRANCHING)——当前仓库中这些枚举值已存在(streamingModels.ts第 16–17 行、第 46–48 行)。

7. 9a 复用资产

mobile/src/chat/contracts/documents.ts提供完整的SearchDoc/StreamingCitation/CitationMap(9a 产物),供延后的 search/fetch 渲染器复用;9a 的源 UI(SourceRow/SourceIcon/openSource/CitedSources)同样可被延后阶段直接复用。

后端现状:推理包已存在,无需改动

后端已在backend/onyx/chat/llm_step.py中发出推理数据包(源码证据):

包类型字段
ReasoningStarttype="reasoning_start"无字段
ReasoningDeltatype="reasoning_delta"{reasoning: str}(增量文本)
ReasoningDonetype="reasoning_done"无字段

数据包携带Placement.turn_index/tab_index。文档强调两个关键事实:

  • 线上没有message_end——整个 turn 只能通过OverallStoptype="stop")完成;
  • SECTION_END通常是客户端合成的——Web 端在新的turn_index出现时把它注入先前的分组、在 STOP 时注入所有仍打开的分组——这正是步骤被标记为"完成"的方式。

当前移动端messageProcessor.tsinjectSectionEnd与此完全对应:它幂等地向分组追加合成包,并在 STOP 时关闭所有仍打开的分组(见handleStopPacket,源码注释:"the backend never sends one")。

此外TopLevelBranching {num_parallel_branches}是并行前的元数据(预并行信息),移动端用它维护expectedBranches映射(handleTopLevelBranching)。

Web 端真相源:对等的目标架构

Web 端实现全部位于web/src/app/app/message/messageComponents/下,是 9b 的对等目标,分四层:

1. 分组层

timeline/hooks/packetProcessor.ts:按${turn_index}-${tab_index ?? 0}分组、合成SECTION_END、归类 tool/display 分组;随后timeline/transformers.ts完成GroupedPacket[]TransformedStep[]TurnGroup[]的变换,其中isParallel = 同一 turn_index 下有多个步骤

移动端的对应实现位于mobile/src/chat/timeline/transformers.tstransformPacketGroupsGroupedPacket映射为TransformedStep(含key/turnIndex/tabIndex/packets),groupStepsByTurnturnIndex聚合、按tabIndex排序并计算isParallel

2. 组合层

AgentMessage.tsx运行usePacketProcessor+usePacedTurnGroups200ms 错峰揭晓),上方渲染<AgentTimeline>,下方渲染最终答案的RendererComponent

移动端对应:mobile/src/hooks/timeline/usePacketProcessor.tsprocessPacketsuseMemo全量重算方式托管(注释明确说明这是从 Web 的渲染期 ref 变更结构而来);mobile/src/hooks/timeline/usePacedTurnGroups.ts实现了 200ms 错峰(PACING_DELAY_MS = 200),并处理了历史回放旁路(shouldBypassPacing)、STOP 时立即冲刷全部待揭晓步骤、tool-after-message 隐藏答案等细节。

3. 外壳层

AgentTimeline.tsx+useTimelineUIState7 种状态EMPTY / DISPLAY_CONTENT_ONLY / STREAMING_SEQUENTIAL / STREAMING_PARALLEL / STOPPED / COMPLETED_COLLAPSED / COMPLETED_EXPANDED)+useTimelineExpansion(默认折叠;用户未手动切换时,停止/答案开始后自动折叠)+useTimelineHeader(微光头部文字)+useTimelineMetrics

移动端对应文件为mobile/src/hooks/timeline/下的useTimelineUIState.tsuseTimelineExpansion.tsuseTimelineHeader.tsuseTimelineMetrics.tsuseStreamingDuration.tsuseTimelineStepState.ts,以及mobile/src/components/chat/timeline/headers/下的四个头部组件(StreamingHeaderParallelStreamingHeaderStoppedHeaderCompletedHeader)。

4. 单步层

TimelineRendererComponent(每步独立的isExpandedrenderType = override ?? (isExpanded ? FULL : COMPACT))+StepContainerTimelineIconColumn导轨 +TimelineSurface色调 +TimelineStepContent头/折叠/主体);结尾追加 Done/Stopped 终止步骤。

移动端对应mobile/src/components/chat/timeline/下的StepContainer.tsxTimelineRendererComponent.tsxTimelineStep.tsxCollapsedStreamingContent.tsxExpandedTimelineContent.tsx,以及primitives/中的TimelineRootTimelineHeaderRowTimelineIconColumnTimelineSurfaceTimelineStepContentTimelineRowTimelineTopSpacertimelineTokens

渲染器契约(interfaces.ts

Web 采用render-prop形式:MessageRenderer<T,S>计算RendererResult[]并调用children(results)

RendererResult = { icon, status, content, expandedText?, // 展开态文本 supportsCollapsible?, // 是否支持折叠 alwaysCollapsible?, timelineLayout?, noPaddingRight?, surfaceBackground?, // 表面背景色 }

外层StepContainer拥有包裹层,渲染器自身从不绘制自己的外壳。

Reasoning 渲染器

timeline/renderers/reasoning/ReasoningRenderer.tsxSvgCircle图标、状态文案"Thinking"(或提取的 markdown 标题)、流式推理 markdown 的ExpandableTextDisplay500ms 最小思考门控

移动端将推理文本解析逻辑独立为纯模块mobile/src/chat/timeline/reasoningState.ts(保持 reanimated-free 以便单测):constructCurrentReasoningState判断hasStart/hasEndSECTION_END/ERROR/REASONING_DONE任一即结束)、拼接REASONING_DELTA文本;extractFirstParagraph在推理以短 markdown 标题开头时提取title(超过 60 字符视为散文而非标题,MAX_TITLE_LENGTH = 60)。UI 侧对应ReasoningTextSheet.tsx/ReasoningTextWindow.tsx

移动端原语与平台约束(关键注意事项)

研究文档从渲染基础设施扫描中总结了移动端的可用原语与"坑":

  • 可用原语ViewText(font+color 枚举)、IconSeparatorButtonCardContent/ContentActionSpinner(RNAnimated,jest 安全)、StreamingMarkdown(富 markdown,但颜色必须通过varsLight/varsDark+textPresets@onyx-ai/shared/native解析为具体 hex——markdown 库内部NativeWind 类不生效)、reanimated 4.3.1(当前微光效果;ChatSurface使用LinearTransition/FadeIn/FadeOut);
  • 没有Collapsible/Accordion原语——折叠/展开是全新工作(需向所有者确认:移植 Web UX 还是用 reanimated 组合);
  • 没有 chevron-up(需旋转chevron-down);没有 brain/推理图标没有纯圆圈字形(Web 用SvgCircle)——加图标是小规模的所有者决策;
  • NativeWind 间距是像素值(类名数字 == px);
  • RN 没有 hover——删除 Web 的isHover分支,桌面端 hover 展示必须变成常显或显式点击的触达(且要真实的命中目标);
  • reanimated jest 注意事项:把纯逻辑(分组/步骤推导/状态机)放在无 reanimated 的模块中,叶子组件直接导入;
  • 流式重渲染性能:行组件 memoize、合并更新;自动折叠的高度动画在流式过程中可能卡顿(优先固定高度摘要 + 点击展开);
  • 无障碍:触发器accessibilityRole="button"+accessibilityState={{expanded}};折叠后子元素必须从无障碍/焦点树中移除,而非仅视觉隐藏。

行业最佳实践

研究文档汇总了六条业界共识(外部资料仅作背景,不构成仓库事实):

  1. 渐进式披露是主流——默认折叠;流式中显示紧凑的 "Thinking… (Ns)" 标签(带动画字形 + 耗时计时);完成后自动折叠为一行摘要;不要默认倾倒完整思维链(DeepSeek 式的流水输出被点名批评为令人淹没);
  2. Agent-UX "活动时间线"——可折叠的冗余度、固定的"当前步骤"、分层透明度;滚动的聊天线程不是好的工作流追踪器,结构化的时间线(而非内联文本)才是正确容器;
  3. 触屏披露无障碍——触发器必须暴露展开/折叠状态;折叠面板的子元素必须从 AX/焦点树移除;
  4. RN 流式重渲染陷阱——React.memo行组件 +useCallbackrenderItem;不要每个 tick 都给每行新的对象/函数身份;合并/节流流式setState(不要每个 token 都 setState);
  5. Hover→触屏陷阱——每个桌面 hover 揭示都必须变成常显或显式点击的触达,且要有真实命中目标(RN 完全没有 hover);
  6. RN 时间线/步骤指示器库又薄又没人维护——预期从原语自己构建导轨/圆点/可折叠节点;现有库是线性表单向导式的 stepper,不是流式/嵌套时间线。

三种实现方案

方案 A —— 极简优先:"Lean Steps"(分组进处理器 + reasoning 叶子接入现有导轨)

扩展恰好两个现有接缝、新增一个叶子:

  • 分组逻辑内置于已经是纯函数的messageProcessor(在ProcessedMessageState上新增steps: TimelineStep[]),因此usePacketDisplay的每次 flush 全量重算路径完全不动、jest 安全;
  • 给现有AgentTimelinestub 喂真实 reasoning 步骤,新增一个ReasoningStep叶子(流式StreamingMarkdown主体 + 点击切换折叠 + 叶子本地计时);
  • 扁平{packets, processed}渲染器契约原样保留——reasoning 是时间线驻留的,不是注册表渲染器,因此findRenderer/RENDERERS和 9a 引用路径都不受影响;
  • 不移植任何 Web 机制(无 render-prop 契约、无 7 状态机、无错峰)。

改动文件streamingModels.ts(+4 枚举、+4 接口)、messageProcessor.ts(分组 →steps/stepByKey)、AgentTimeline.tsx(渲染TimelineStep[]switch(kind))、MessageRow.tsx(传 steps、细化isLoading);新增ReasoningStep.tsx及分组单元测试。

工作量:约 360–420 LOC,1 个 PR代价:最小改动面、对 9a 零风险、纯分组可完全单测;但没有错峰、没有 7 状态头部、没有逐步 render-prop 契约;第二个渲染器要接入需新增kind+case(而非findRenderer),即当真正的工具需要色调表面/逐步折叠时,AgentTimeline要做一次有界的重构。后续契合度:每个延后阶段 = 加kind+ reducer 分支 + 叶子 + 一个case;9a 的 SourceRow 可落入 search/fetch 叶子;更重的机制(错峰、render-propRendererResult)等真正有工具需要时再作为该接缝的有界演进引入。

方案 B —— 灵活优先:"可扩展步骤接缝"

一个纯 turn/tab分组模块chat/timeline/grouping.ts)与现有扁平processPackets并列运行(都通过useMemo托管,遵守 refs-lint 禁令),产出TurnGroup[]TransformedStep[]。每个步骤由优先级排序的步骤注册表(镜像 Web 的findRenderer)解析到一个步骤渲染器,渲染器返回 WebRendererResult数据契约的移动端模拟普通对象返回,非 JSX,比 Web 的 render-prop 简单但同样可扩展)。单个StepContainer(导轨 + 色调表面 + 头部 + 折叠主体)拥有全部 chrome;渲染器从不绘制自己的包裹层。Reasoning 是 1 号渲染器(也是链条最后的兜底)。messageProcessor保持扁平(分组是兄弟模块,遵循其头部注释)。新增精简版useTimelineExpansion+useTimelineUIState,以及全新Collapsible原语 + 推理图标(均为所有者确认项)。

改动文件:新增chat/timeline/{grouping,stepContract,stepRegistry}.tschat/timeline/renderers/reasoning/ReasoningRenderer.tsxcomponents/chat/timeline/StepContainer.tsxcomponents/chat/timeline/{useTimelineExpansion,useTimelineUIState}.tscomponents/ui/collapsible.tsx(待确认)、icons/reasoning-circle.tsx(待确认);修改streamingModels.tsusePacketDisplay.ts(+turnGroups)、AgentTimeline.tsxMessageRow.tsx

工作量:约 1,810 LOC(生产 + 测试),2 个 PR——9b-1 纯引擎(分组 + 契约 + 注册表 + reasoning 推导 + 测试,无 UI)、9b-2 UI 与接线(Collapsible、StepContainer、expansion/uiState、视图层、AgentTimeline 重写)。代价:每个延后阶段 = 一个新文件(谓词 + 返回RendererResult的渲染器),分组/容器/折叠/MessageRow零改动,改造代价摊薄到约 0;但 LOC 约为 A 的 2 倍,且引入了 reasoning 几乎用不到的契约字段(alwaysCollapsibletimelineLayout,部分要到阶段 2 才非投机),外加 2 个所有者把关的产物带来决策延迟。后续契合度:近乎完美——这就是接缝本身;search/fetch 直接复用 9a 的 SourceRow;并行标签页已在键中区分;model_index被容忍;只有嵌套是可能扩展groupStepsByTurn的阶段。

方案 C —— 全量对等:"忠实外壳优先"

现在就把 Web 的整个时间线外壳 1:1 移植:分组 +transformers+usePacedTurnGroups(200ms 错峰)+ 完整useTimelineUIState(7 状态)+useTimelineExpansion+useTimelineHeader+useStreamingDuration(实时 "Ns"/"Thought for {duration}")+useTimelineMetrics+ render-prop 渲染器契约 +StepContainer+TimelineRendererComponent+ Streaming/Stopped/Completed 头部 + Done/Stopped 终止步骤——只接线 reasoning 渲染器主体。保持移动端每 flush 全量重算模型(Web 的 ref 持有usePacketProcessor在移动端非法)的方式是:把分组做成纯函数;usePacedTurnGroups必须重构掉 Web 的渲染期 ref 读取以符合 refs lint。该外壳在只有 reasoning 内容时与 Web行为上无法区分,因此延后阶段只需添加渲染器主体。

改动文件:约 22 个新增 + 6 个修改(分组、transformers、接口、错峰 hook、4 个状态/头部/指标 hook、时长 hook、3 个导轨/表面/内容原语、StepContainer + TimelineRendererComponent、3 个头部组件、ReasoningRenderer、Collapsible/ExpandableTextDisplay(待确认)、圆形图标、AgentTimeline 重写;修改streamingModelsusePacketDisplayMessageRowregistry)。

工作量:约 3,700–4,300 LOC,坦诚地说 5–6 个 PR(接线 → 错峰 → 状态 → 原语 → 渲染器 → 头部 + 组合)。代价:外壳第一天即与 Web 忠实一致(微光头部文字、实时计时、错峰揭示、答案开始自动折叠、Done/Stopped、色调表面);每个延后渲染器都是纯增量、外壳零返工、零漂移;但这是 reasoning-only 阶段最大的 LOC/审查面,7 状态机的大片区域在并行存在前以死路径形式上线,错峰 + 自动折叠高度动画带来真实的流式性能/卡顿风险,usePacedTurnGroups(重构掉渲染期 ref)是最容易出 bug、且存在行为分歧风险的文件。后续契合度:同类最佳——并行标签页数据模型 +sub_turn_index/model_index容忍内置且休眠;但这种最大扩展性正是提前支付外壳成本的唯一理由。

方案横评

维度A(Lean Steps)B(可扩展接缝)C(忠实外壳)
规模≈400 LOC,1 PR≈1,800 LOC,2 PR≈4,000 LOC,5–6 PR
共享基础三者都新增同样的streamingModels包类型和同样的分组键(${turn_index}-${tab_index ?? 0}model_index忽略),分歧仅在"现在建多少外壳/契约"
refs-lint 约束三者都被约束:都不能移植 Web 渲染期可变 ref 的usePacketProcessor;分组是纯useMemo;C 在此风险最大(错峰 hook 重构)
扩展性 vs YAGNI推迟 render-prop/错峰/状态机直到第二个渲染器证明形状(风险:之后AgentTimeline有界重构)前置数据契约 + 注册表(真正难改造的部分),但不错峰/不全状态前置一切(风险:死代码 + 卡顿 + 移植移动靶)
即时对等只有 C 让外壳第一天 Web 忠实(微光头部、实时计时、错峰揭示、答案自动折叠、Done/Stopped)。A、B 正确渲染 reasoning 但外壳更简单;路线图的对等要求是"外观 AND 结构"——仅 reasoning 时,A/B 仍可匹配步骤/导轨/折叠形状,同时推迟头部状态机
所有者确认项A 一个都不需要(无新原语/图标)B、C 在 UI 落地前都需要Collapsible原语 + 推理图标决策

最终选型:方案 C —— "忠实外壳优先"(Full Parity)

方案 C 在 GATE 1(所有者,2026-07-16)被选定。所有者指令(原文意图):

"I want something that matches web. I am going to build the renderers immediately — current PR goes in [first], then the renderers. I don't want any refactor later. I'm fine with any number of lines — I want everything, whatever way we need it. Just don't drift away from web."

("我要和 Web 一致的东西。我会立刻构建渲染器——当前 PR 先合入,然后渲染器。我之后不想做任何重构。行数多少我都无所谓——我要全部,用任何我们需要的方式。只是别偏离 Web。")

对 9b 的具体含义

  1. 1:1 移植 Web 的整个时间线外壳:render-propMessageRenderer<T,S>契约 +RendererResult/RenderType(HIGHLIGHT/FULL/COMPACT/INLINE)、纯分组(packetProcessortransformers)、usePacedTurnGroups(200ms 错峰)、完整useTimelineUIState(7 状态)+useTimelineExpansion+useTimelineHeader+useStreamingDuration+useTimelineMetricsStepContainer+TimelineRendererComponent+ 导轨/表面/内容原语、Streaming/Stopped/Completed 头部、Done/Stopped 终止步骤。原样采用 Web 的 render-prop 渲染器契约(而非方案 B 的简化数据对象),使未来每个 Web 渲染器都能近乎机械化地移植。
  2. 9b 只接 reasoning 渲染器主体。休眠的并行标签页状态(isParallelSTREAMING_PARALLELshowParallelTabs)和sub_turn_index/model_index容忍随外壳一起上线(与 Web 一致),使并行/嵌套/多模型成为日后仅 UI 层的后续工作、接缝零改动。
  3. 9b 功能流范围(已确认):本规范只覆盖完整外壳 + reasoning 渲染器。每个工具渲染器(内部/Web 搜索、fetch/open_url、python/code、coding-agent+bash、custom-tool、deep-research + 嵌套 Agent、memory)都是该零重构接缝上的独立后续 PR,按阶段逐一规范(先 grill 后设计),由所有者构建时确定——不在本设计内。但设计必须完整规定 render-prop 契约 + 每种工具的渲染器清单/分发(inventory/dispatch),使这些后续工作机械化。

唯一被接受的分叉(平台强制、行为保持)

Web 的usePacketProcessorusePacedTurnGroups渲染期间读取stateRef.current(重置检测/旁路),而移动端react-hooks/refslint禁止这一点。这两者被重构为useMemo重算 + 仅 effect 写 ref的模型,保持相同的分组输出和相同的 200ms 揭晓时序。这是实现层面的必然不是外观/结构漂移——渲染出的时间线与 Web 忠实一致。细节记录在 03-detailed-design.md。

这一决策的落地可以从当前仓库源码中直接验证:mobile/src/hooks/timeline/usePacedTurnGroups.ts的头部注释明确写道,Web 版本在渲染期读取错峰 ref 被 lint 禁止,因此渲染相关字段(revealedStepKeystoolPacingComplete)改为由 effect/callback发布useState,内部记账(待办队列、定时器、标志)保留在仅在 effect/callback 中触碰的 ref 中,且明确"dropped web's prevPacedRef stabilization"。

对后续阶段的指导意义

9b 选型的分层价值在于:数据契约和分组键先行固化,UI 机制按需演进。任何延后阶段(9c–9e)在接入时只需做三件事:

  1. streamingModels.ts注册对应的PacketType
  2. 按 render-prop 契约实现一个返回RendererResult的渲染器(复用 9a 的 SourceRow 等资产);
  3. 在步骤注册表/分发中登记匹配谓词——外壳(StepContainer、头部状态机、错峰、折叠、Done/Stopped 终止步骤)零改动。

而移动端开发者需要始终牢记两条平台红线:渲染期间不得触碰ref.current(分组与推导一律纯useMemo);纯逻辑与 reanimated 分离(分组/步骤推导/状态机放在无 reanimated 模块中,保证 jest 可测)。这两条红线既是方案 C 相比 Web 的唯一实现分叉来源,也是移动端时间线长期可维护、可单测的根本保证。

结语

从研究文档到当前仓库源码,docs/mobile-chat/9b-timeline/系列文档(00-index.md、01-research.md、02-high-level-design.md、03-detailed-design.md、04-implementation-plan.md)与mobile/中的时间线实现(mobile/src/chat/timeline/mobile/src/components/chat/timeline/mobile/src/hooks/timeline/)构成了"设计先行、源码印证"的完整闭环。这套方法论的核心可复用于仓库内其他平台移植类任务:先扫描并确认既有接缝,再以"对等但合法"的方式重构 Web 机制,最后让延后功能在零返工的接缝上机械接入。

【免费下载链接】danswerOpen Source AI Platform - AI Chat with advanced features that works with every LLM项目地址: https://gitcode.com/GitHub_Trending/da/danswer

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询