数据目录闭环(前端)复查修正:部分完成的宽表也写「已写进目录 N 段」(段进度都结束了,看不出写进了几段),服务端原因里已经写了的不重复;表助手「补上留空的列」超过 20 列时写明一共几列;流式回答每来一段只重画正在回答的那一轮;答完的一轮没有用量、没有查询时不再露出一个 0;check-catalog 补部分完成的那一条

This commit is contained in:
ThearchyHelios
2026-10-14 08:51:23 +08:00
parent a6e5b857fe
commit bc4ea277e9
4 changed files with 21 additions and 16 deletions
+2 -1
View File
@@ -3005,7 +3005,8 @@ export const CATALOG_ASSISTANT_TEXT = {
`上次起草有 ${formatNumber(n)} 列模型拿不准、留空了${cols.length ? `,例如 ${cols.join('、')}` : ''}。可以先从这几列开始。`,
suggestLabel: '可以这样开始',
suggestBlank: (n: number) => `补上留空的 ${formatNumber(n)} 列`,
suggestBlankText: (cols: string[]) => `请帮我补上这几列的中文名和含义:${cols.join('、')}。拿不准的地方先问我。`,
suggestBlankText: (cols: string[], total: number) =>
`请帮我补上这几列的中文名和含义:${cols.join('、')}${total > cols.length ? ` 等 ${formatNumber(total)} 列` : ''}。拿不准的地方先问我。`,
suggestFacts: '核对有效记录条件和去重规则',
suggestFactsText: '这张表的有效记录条件和去重规则应该怎么写?拿不准的地方先问我。',
suggestPending: '看看哪些推断可以确认',
+5 -5
View File
@@ -533,13 +533,13 @@ function TaskRow({ task: t, jobError }: { task: CatalogDraftTask; jobError: stri
const reason = jobError && t.error === jobError ? (t.model_error ?? '') : taskReason(t)
const started = t.status !== 'pending' && t.status !== 'skipped'
const parts = t.parts_total != null && t.parts_total > 1 && started
// 段被缩小、拆半后实际的块(比段多时才写);已经写进目录的段:起草中写着(停止也不丢),停下来时服务端的原因里
// 没说的才补一句(没写全的段为什么没写出来,服务端的原因里有),写完了的不写
// 段被缩小、拆半后实际的块(比段多时才写);已经写进目录的段:起草中、部分完成时写着(停止也不丢,部分完成的
// 段进度是「都结束了」,看不出写进了几段),停下来时补一句剩下几段没起草。服务端的原因里已经写了的不重复,写完了的不写
const chunks = started ? chunksOf(t) : null
const written = writtenOf(t)
const writtenText = written > 0 && t.status === 'running' ? DT.partsWritten(written)
: written > 0 && t.status === 'cancelled' && !reason.includes(DT.partsWritten(written))
? DT.partsWrittenStopped(written, Math.max(0, (t.parts_total ?? written) - written)) : ''
const writtenText = written <= 0 || reason.includes(DT.partsWritten(written)) ? ''
: t.status === 'running' || t.status === 'partial' ? DT.partsWritten(written)
: t.status === 'cancelled' ? DT.partsWrittenStopped(written, Math.max(0, (t.parts_total ?? written) - written)) : ''
const changes = DT.taskAdded(t.added, t.updated)
const blank = blankOf(t)
const sample = t.blank_sample?.filter(Boolean) ?? []
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'
import { memo, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'
import type { KeyboardEvent as ReactKeyboardEvent } from 'react'
import { ArrowUp, BookMarked, Database, Info, MessagesSquare, Minus, Plus, Square, Trash2, X } from 'lucide-react'
import clsx from 'clsx'
@@ -149,11 +149,12 @@ export function TableAssistant({ sourceId, table, tableLabel, detail, profileEna
if (!streaming) send(text)
}
const drop = (path: string) => setPending((cur) => dropPending(cur, path))
const restore = (part: Extract<TurnPart, { kind: 'proposal' }>, path: string) => {
// 两个回调保持不变:流式的正文每来一段只重画正在回答的那一轮
const drop = useCallback((path: string) => setPending((cur) => dropPending(cur, path)), [])
const restore = useCallback((part: Extract<TurnPart, { kind: 'proposal' }>, path: string) => {
const change = part.changes.find((c) => c.path === path)
if (change) setPending((cur) => mergeProposal(cur, [change], part.id, part.version))
}
}, [])
const clearAll = async () => {
if (!pendingRef.current.length) return
if (!(await confirmDialog({ title: T.clearTitle(pendingRef.current.length), confirmLabel: T.clearConfirm, danger: true }))) return
@@ -259,7 +260,7 @@ export function TableAssistant({ sourceId, table, tableLabel, detail, profileEna
<div className="flex flex-wrap gap-1.5">
{lastBlank > 0 && blankCols.length > 0 && (
<SuggestButton kind="blank" label={T.suggestBlank(lastBlank)}
onClick={() => send(T.suggestBlankText(blankCols.slice(0, 20)))} />
onClick={() => send(T.suggestBlankText(blankCols.slice(0, 20), blankCols.length))} />
)}
<SuggestButton kind="facts" label={T.suggestFacts} onClick={() => send(T.suggestFactsText)} />
{hasProposed && <SuggestButton kind="pending" label={T.suggestPending} onClick={() => send(T.suggestPendingText)} />}
@@ -333,7 +334,7 @@ function SuggestButton({ kind, label, onClick }: { kind: string; label: string;
// 一轮回答:正文、只读查询、修改提案按到来的先后排;末尾写这一轮的结局(出错、停止、用量)
// ---------------------------------------------------------------------------
function AssistantTurnView({ turn, pending, saved, onDrop, onRestore }: {
const AssistantTurnView = memo(function AssistantTurnView({ turn, pending, saved, onDrop, onRestore }: {
turn: AssistantTurn
pending: PendingChange[]
saved: ReadonlySet<string>
@@ -366,14 +367,14 @@ function AssistantTurnView({ turn, pending, saved, onDrop, onRestore }: {
{turn.state === 'stopped' && (
<p className="text-2xs text-faint" data-assistant-turn-status="stopped">{T.stopped}</p>
)}
{turn.state === 'done' && (usage || turn.queries) && (
{turn.state === 'done' && (!!usage || (turn.queries ?? 0) > 0) && (
<p className="tnum text-2xs text-faint" data-assistant-turn-status="done">
{[usage ? T.usage(usage.input_tokens, usage.output_tokens) : '', turn.queries ? T.queries(turn.queries) : ''].filter(Boolean).join(' · ')}
</p>
)}
</div>
)
}
})
/** 助手跑过的一条只读查询:摘要写行数(失败写原因),展开看 SQL 和前几行 */
function QueryBlock({ part }: { part: Extract<TurnPart, { kind: 'query' }> }) {
+5 -2
View File
@@ -1604,8 +1604,9 @@ await section('数据目录 · 起草:逐段写进目录、块进度与停止
check('……可以只重试这 2 张', (await page.locator('[data-draft-retry]').innerText().catch(() => '')).includes('(2 张)'))
await page.locator('[data-draft-close]').click()
// 老服务端停下来时原因里没写:界面自己补一句
const legacy = newJob(state, S1, { tables: ['store_sales'], use_model: true })
// 老服务端停下来时原因里没写:界面自己补一句。部分完成的表段进度是「都结束了」,看不出写进了几段:也写一句
const legacy = newJob(state, S1, { tables: ['store_sales', 'members'], use_model: true })
settle(state, legacy, 'members', { status: 'partial', parts_total: 2, parts_done: 2, parts_written: 1, model_error: CALL_ERR })
settle(state, legacy, 'store_sales', { status: 'running', parts_done: 1, parts_written: 1 })
stopJob(legacy)
legacy.tasks[0].model_error = null
@@ -1615,6 +1616,8 @@ await section('数据目录 · 起草:逐段写进目录、块进度与停止
await draftBox(page).locator('[data-draft-last-open]').click()
check('服务端的原因里没写已写进几段:界面补「已写进目录 1 段,剩下 2 段没起草」', await until(async () => (await text('store_sales', '[data-draft-task-written]')) === '已写进目录 1 段,剩下 2 段没起草'),
await text('store_sales', '[data-draft-task-written]'))
check('……部分完成的表写「已写进目录 1 段」,原因照旧', (await text('members', '[data-draft-task-written]')) === '已写进目录 1 段'
&& (await text('members', '[data-draft-task-reason]')).includes(CALL_ERR), await text('members', '[data-draft-task-written]'))
await page.locator('[data-draft-close]').click()
state.jobs.pop()