Skip to content

GAL 视窗对话框只显示「(编写代码中……)」 #9

Description

@helium2006

Issue:GAL 视窗对话框只显示「(编写代码中……)」,正文永远不渲染

标题

GAL 视窗对话框只显示「(编写代码中……)」——nodes/partial 从 useSession 读取,应为 useChat


环境

  • gal-view 0.3.3,commit d002fe0(github:Ayase34/gal-view#main)
  • 宿主:npm 安装的 @deepseek-ai/dsh,Web GUI(dsh web,http://127.0.0.1:3080)
  • 平台:Windows,Node v24.16.0
  • 安装方式:dsh plugin --profile web add github:Ayase34/gal-view#main

复现

  1. 装好插件、重启 web,打开会话页的「GAL视窗」标签。
  2. 在视窗内的输入框发一句话,等模型跑完。

现象

对话框从头到尾只显示一句状态行「(编写代码中……)」,正文始终为空:

  • 用户说的话不显示;
  • 模型的回复定稿后也不显示(不是"流式期间不渲染"那么简单,是彻底不渲染);
  • 历史面板(透明按钮「历史」)同样永远显示"还没有对话记录";
  • 「思考中」也从不出现,无论模型是在思考还是在调工具,状态恒为「编写代码中」。

预期:按 README —— 对话正文渲染进「台词」元素,打字机逐字显示,超出容量可点击翻页。

根因

.dsh-plugin/client/GalView.jsx(0.3.3 约 L157–L169)从会话 hook 里取 chat 投影的字段:

export function GalView({ useSession, inputActions, useScene, useHistory, useAssets, useFonts, useStore, actions, api }) {
  ...
  const nodes = useSession(s => s.nodes)          // ← 恒为 undefined
  const partial = useSession(s => s.partial)      // ← 恒为 undefined
  const running = useSession(s => s.running)      // ← 这个是对的
  const blank = useSession(s => s.blank)
  const runningCalls = useSession(s => s.runningCalls)  // ← 恒为 undefined
  const pending = useSession(s => s.pending)            // ← 恒为 undefined
  const promptError = useSession(s => s.promptError)

但 conversation.view 槽位的 useSession 只暴露控制器快照 SessionSnapshot,字段集是固定且公开的:

export interface SessionSnapshot {
  readonly sessionId: SessionId
  readonly queue: readonly QueuedMessage[]
  readonly pendingSubmissions: readonly PendingSubmission[]
  readonly running: boolean
  readonly subagent: { ... } | null
  readonly removed: boolean
  readonly openState: OpenState
  readonly openError: RemoteFailure | null
  readonly hasMore: boolean
  readonly loadingOlder: boolean
  readonly promptError: PromptError | null
  readonly blank: boolean
  readonly lastAgentError: string | null
  readonly promptAttempted: boolean
  readonly awaitingFirstTurn: boolean
}

没有 nodes / partial / runningCalls。

已定稿的对话节点与流式 partial 属于 chat 投影,通过同槽位的另一个标准 hook useChat 提供,且它的 legacy 切片正好就是插件想要的形状:

// dsh-client-ui-chat:LegacySliceBuilder.snapshot()
{ nodes, turnTimings, turnEnds, partial, runningCalls }

两条链路可以对照证实:

  • dsh-client-ui-session 的 BUILTIN_SOURCE:hooks: { session: binding.session } → 这就是 useSession
  • dsh-client-ui-chat 的 ctx.uiSession.provide({ hooks: ['chat'], resolve: binding => ({ hooks: { chat: chatSource(binding) } }) }) → 这就是 useChat

两者的标准 props 名称与来源(dsh-client-ui-renderer 的 standardKit / standardProps):

conversation.view standardProps:
  ... useChat: UseChat, useConversation: UseConversation, useInput, inputActions,
      useSession: SessionSnapshotSelector, sessionId, useProjection, useTrajectory

顺带一提,官方自带的 ChatView 正是这么用的:

function ChatView({ useSession, useChat, ... }) {
  const order = useChat((s) => s.order)
  const running = useSession((s) => s.running)   // 控制器字段走 useSession

因果链(为什么恰好卡在「编写代码中」)

  1. nodes === undefined → nodesToLines(undefined) → [] → lines 恒为空
  2. lastLine === null、pendingPlayer === null
  3. showStatusPage = running && (liveText !== '' || statusHold || pendingPlayer === null) → 只要 running 就恒为真
  4. currentLine 被换成 { key: 'live', kind: 'assistant', text: '' },正文为空
  5. partial === undefined → deriveStatus 里 hasText 为假、partialStatus(undefined) 返回 null → 兜底 '编写代码中'
  6. 于是整轮对话期间,对话框 = 空正文 + 一句「(编写代码中…)」,历史面板恒为空

如果 partial 能读到,工具调用阶段仍会显示「编写代码中」(这是设计意图),但正文会正常渲染 —— 所以问题不是文案,而是数据源接错。

为什么现有测试没抓到

scripts/smoke.mjs 的夹具是照着假设造宿主,而不是照着宿主契约:

const sessionState = {
  current: {
    sessionId: 'session-1',
    nodes: [ ... ],      // ← 把 chat 投影塞进了 session
    partial: null,
    running: false,
    blank: false,
  },
}
...
useSession: bindHook(sessionSource),

useChat 从头到尾没传,组件里也不读。测试和实现共享同一个错误假设,所以 100% 绿。

建议修复

从 useChat 取投影数据,useSession 只留控制器字段:

export function GalView({ useSession, useChat, inputActions, useScene, useHistory, useAssets, useFonts, useStore, actions, api }) {
  ...
  // 会话节点与流式 partial 属于 chat 投影(标准 hook useChat)——
  // useSession 只提供 running/blank/promptError 等控制器字段。
  const chatFallback = useSession(s => s)
  const chat = useChat !== null && useChat !== undefined
    ? useChat(s => s.legacy ?? s)
    : chatFallback                                   // 兼容把两者合一的旧夹具
  const nodes = chat.nodes
  const partial = chat.partial
  const runningCalls = chat.runningCalls

  const running = useSession(s => s.running)
  const blank = useSession(s => s.blank)
  const promptError = useSession(s => s.promptError)
  const pending = useSession(s => s.pending)          // 注意:见下

pending(待批准/待回答)也不在 SessionSnapshot 上,它走的是另一个标准 hook useSessionPendingInteraction。如果 deriveStatus 的「等待回应」要生效,这里需要一并改成:

const pendingInteraction = useSessionPendingInteraction(s => s)
const pending = pendingInteraction === undefined || pendingInteraction === null ? [] : [pendingInteraction]

(我按上面这版打了补丁,本地已验证正文能正常渲染;pending 那处我没有实测,仅从契约推断。)

验证方式(无需 Playwright)

宿主契约可以直接对照宿主产物核实,不依赖 gal-view 自己的夹具:

# useSession 的真实字段集
grep -r "interface SessionSnapshot" node_modules/@deepseek-ai/dsh-client-ui-chat/lib/client.js

# useChat 的 legacy 切片
grep -nE "nodes: this\.finalized|partial: this\.partial|runningCalls: this\.runningCalls" \
  node_modules/@deepseek-ai/dsh-client-ui-chat/lib/client.js

# conversation.view 是否声明了 useChat
grep -n '"useChat: UseChat"' node_modules/@deepseek-ai/dsh-cordis-client-runner/lib/client.js

另外我在本地用真实构建产物做了一次渲染核对(假 React + 假宿主,把节点放进 chat 投影):

产物 打字机目标
修复前 [""] —— 对话框无正文
修复后 ["<chat 投影里的定稿文本>"]

建议同时修测试

scripts/smoke.mjs 的夹具应拆成两个源,让 useChat 成为必需项,否则这个 bug 回归时测试依然全绿:

// session 源只有控制器字段
sessionState.current = { sessionId, running, blank, promptError }
// chat 源承载投影
chatState.current = { nodes, partial, runningCalls }
...
useSession: bindHook(sessionSource),
useChat: bindHook(chatSource),

并补一条断言:非空 nodes 时对话框正文必须出现该文本、且不得出现「(编写代码中…)」。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions