画布助手没完成的那一轮把原因和诊断记进 meta.cause,之前的轮次也摊开「怎么办」和诊断

以前画布轮次失败只记 error 那一句,诊断(模型交回了什么)没落库:部署上那几轮事后查不到为什么失败,只能靠本地重放推断。现在 error 操作的 hint、detail、code、diagnostics 按问数据一轮同样的形状记进 meta.cause(kind=copilot),和查看记录 looks 并存;面板展开「之前的轮次」时报错带上怎么办和诊断原文,和当场看到的一样,老轮次照旧只有一句。

验证:tsc、check-copy;从这个分支另起一套服务跑 studio、stream、decode 三项浏览器检查全过。
This commit is contained in:
ThearchyHelios
2026-10-19 22:48:23 +08:00
parent 6b68961c65
commit 626a423625
2 changed files with 35 additions and 5 deletions
+17 -2
View File
@@ -16,7 +16,7 @@ import { formatCost, formatLapse, formatOffset, formatSpan, formatTokens, shortI
import { statusLabel } from '../lib/status'
import { CHAT_BLUEPRINT_TEXT, runClassLabel, SQL_CHECK_TEXT } from '../lib/terms'
import { sortSqlChecks } from '../lib/sqlcheck'
import type { RunUsage, ToolChange } from '../types'
import type { ConversationTurn, RunUsage, ToolChange } from '../types'
import { AssistantStream, type StreamTurn } from './AssistantStream'
import { Composer } from './Composer'
import { SqlCheckBody } from './SqlChecks'
@@ -220,6 +220,21 @@ function copilotTurn(
}
}
/**
* 之前那几轮的报错:落库时记了原因(meta.cause,助手本轮未完成的那种)就连「怎么办」和诊断一起摊开,
* 和这一轮当场看到的一样;老轮次只有一句话
*/
const pastError = (t: ConversationTurn): StreamTurn['error'] => {
const cause = t.meta?.cause
if (!t.error) return undefined
if (!cause || typeof cause !== 'object' || cause.kind !== 'copilot') return t.error
return {
error: t.error,
...(typeof cause.hint === 'string' && cause.hint ? { hint: cause.hint } : {}),
...(typeof cause.detail === 'string' && cause.detail ? { detail: cause.detail } : {}),
}
}
/** 这一轮变少了的工具,分「没让删」和「按要求」两组:和改图回执(toast)同一条规则 */
const toolLossOf = (out: CopilotOutcome) =>
splitShrunk(out.toolChanges, out.dropped.map((w) => ({ node_id: w.nodeId, field: w.field })))
@@ -254,7 +269,7 @@ function ChatView({ hasRun, onOpenRun }: { hasRun: boolean; onOpenRun: () => voi
status: t.status === 'error' ? '本轮未能完成' : t.graph ? '已修改画布' : '已回答',
steps: [],
output: t.answer ? { 回答: t.answer } : t.explanation ? { 说明: t.explanation } : null,
error: t.status === 'error' ? t.error || undefined : undefined,
error: t.status === 'error' ? pastError(t) : undefined,
})), [earlier])
const shown = showPast ? [...pastTurns, ...turns] : turns
const streamRef = useRef<HTMLDivElement>(null)
+18 -3
View File
@@ -484,13 +484,20 @@ const memoryOf = (past: ConversationTurn[]): StudioState['copilotMemory'] => ({
past, total: past.length, turns: Math.min(past.length, COPILOT_HISTORY_TURNS),
})
/**
* 画布这一轮没完成时记下的原因:同问数据一轮的 meta.cause(backend/app/chat/record.py 的 kind=copilot)。
* 以前只记 error 那一句,诊断(模型交回了什么)没落库,事后查不了为什么失败
*/
type CanvasCause = { kind: 'copilot'; message: string; hint?: string; detail?: string; code?: string
diagnostics?: Record<string, unknown> }
/** 把画布上这一轮记进会话,下一条指令才接得上 */
function recordCanvasTurn(
conversationId: string | null,
instruction: string,
patch: { graph?: any; explanation?: string; answer?: string; status?: 'done' | 'error'; error?: string
/** 这一轮的查看记录(look §2.5.8):服务端下一轮据此写「上一轮查看过」 */
meta?: { looks: LookEvent[] } },
/** 这一轮的查看记录(look §2.5.8):服务端下一轮据此写「上一轮查看过」;没完成的另记原因 */
meta?: { looks?: LookEvent[]; cause?: CanvasCause } },
set?: (fn: (s: StudioState) => Partial<StudioState>) => void,
): void {
if (!conversationId) return
@@ -2148,7 +2155,15 @@ export const useStudio = create<StudioState>((set, get) => ({
finish({ explanation: '', error: message,
errorHint: typeof op.hint === 'string' ? op.hint : '',
errorDetail: typeof op.detail === 'string' ? op.detail : '' })
recordCanvasTurn(get().copilotConversationId, instruction, { status: 'error', error: message, ...looksMeta() }, set)
const cause: CanvasCause = {
kind: 'copilot', message,
...(typeof op.hint === 'string' && op.hint ? { hint: op.hint } : {}),
...(typeof op.detail === 'string' && op.detail ? { detail: op.detail } : {}),
...(typeof op.code === 'string' && op.code ? { code: op.code } : {}),
...(op.diagnostics && typeof op.diagnostics === 'object' ? { diagnostics: op.diagnostics } : {}),
}
recordCanvasTurn(get().copilotConversationId, instruction,
{ status: 'error', error: message, meta: { ...looksMeta().meta, cause } }, set)
break
}
}