From bc4ea277e9fdafb4b5e0cdcf495f6e31b413c986 Mon Sep 17 00:00:00 2001 From: ThearchyHelios Date: Wed, 14 Oct 2026 08:51:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=9B=AE=E5=BD=95=E9=97=AD?= =?UTF-8?q?=E7=8E=AF=EF=BC=88=E5=89=8D=E7=AB=AF=EF=BC=89=E5=A4=8D=E6=9F=A5?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9A=E9=83=A8=E5=88=86=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E7=9A=84=E5=AE=BD=E8=A1=A8=E4=B9=9F=E5=86=99=E3=80=8C=E5=B7=B2?= =?UTF-8?q?=E5=86=99=E8=BF=9B=E7=9B=AE=E5=BD=95=20N=20=E6=AE=B5=E3=80=8D?= =?UTF-8?q?=EF=BC=88=E6=AE=B5=E8=BF=9B=E5=BA=A6=E9=83=BD=E7=BB=93=E6=9D=9F?= =?UTF-8?q?=E4=BA=86=EF=BC=8C=E7=9C=8B=E4=B8=8D=E5=87=BA=E5=86=99=E8=BF=9B?= =?UTF-8?q?=E4=BA=86=E5=87=A0=E6=AE=B5=EF=BC=89=EF=BC=8C=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E7=AB=AF=E5=8E=9F=E5=9B=A0=E9=87=8C=E5=B7=B2=E7=BB=8F=E5=86=99?= =?UTF-8?q?=E4=BA=86=E7=9A=84=E4=B8=8D=E9=87=8D=E5=A4=8D=EF=BC=9B=E8=A1=A8?= =?UTF-8?q?=E5=8A=A9=E6=89=8B=E3=80=8C=E8=A1=A5=E4=B8=8A=E7=95=99=E7=A9=BA?= =?UTF-8?q?=E7=9A=84=E5=88=97=E3=80=8D=E8=B6=85=E8=BF=87=2020=20=E5=88=97?= =?UTF-8?q?=E6=97=B6=E5=86=99=E6=98=8E=E4=B8=80=E5=85=B1=E5=87=A0=E5=88=97?= =?UTF-8?q?=EF=BC=9B=E6=B5=81=E5=BC=8F=E5=9B=9E=E7=AD=94=E6=AF=8F=E6=9D=A5?= =?UTF-8?q?=E4=B8=80=E6=AE=B5=E5=8F=AA=E9=87=8D=E7=94=BB=E6=AD=A3=E5=9C=A8?= =?UTF-8?q?=E5=9B=9E=E7=AD=94=E7=9A=84=E9=82=A3=E4=B8=80=E8=BD=AE=EF=BC=9B?= =?UTF-8?q?=E7=AD=94=E5=AE=8C=E7=9A=84=E4=B8=80=E8=BD=AE=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E7=94=A8=E9=87=8F=E3=80=81=E6=B2=A1=E6=9C=89=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=97=B6=E4=B8=8D=E5=86=8D=E9=9C=B2=E5=87=BA=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=200=EF=BC=9Bcheck-catalog=20=E8=A1=A5=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=9A=84=E9=82=A3=E4=B8=80=E6=9D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/lib/terms.ts | 3 ++- frontend/src/pages/catalog/DraftDialog.tsx | 10 +++++----- frontend/src/pages/catalog/TableAssistant.tsx | 17 +++++++++-------- scripts/check-catalog.mjs | 7 +++++-- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/frontend/src/lib/terms.ts b/frontend/src/lib/terms.ts index 148bc7bb..f0930455 100644 --- a/frontend/src/lib/terms.ts +++ b/frontend/src/lib/terms.ts @@ -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: '看看哪些推断可以确认', diff --git a/frontend/src/pages/catalog/DraftDialog.tsx b/frontend/src/pages/catalog/DraftDialog.tsx index 06371021..d9970f06 100644 --- a/frontend/src/pages/catalog/DraftDialog.tsx +++ b/frontend/src/pages/catalog/DraftDialog.tsx @@ -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) ?? [] diff --git a/frontend/src/pages/catalog/TableAssistant.tsx b/frontend/src/pages/catalog/TableAssistant.tsx index db116014..caf55712 100644 --- a/frontend/src/pages/catalog/TableAssistant.tsx +++ b/frontend/src/pages/catalog/TableAssistant.tsx @@ -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, path: string) => { + // 两个回调保持不变:流式的正文每来一段只重画正在回答的那一轮 + const drop = useCallback((path: string) => setPending((cur) => dropPending(cur, path)), []) + const restore = useCallback((part: Extract, 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
{lastBlank > 0 && blankCols.length > 0 && ( send(T.suggestBlankText(blankCols.slice(0, 20)))} /> + onClick={() => send(T.suggestBlankText(blankCols.slice(0, 20), blankCols.length))} /> )} send(T.suggestFactsText)} /> {hasProposed && 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 @@ -366,14 +367,14 @@ function AssistantTurnView({ turn, pending, saved, onDrop, onRestore }: { {turn.state === 'stopped' && (

{T.stopped}

)} - {turn.state === 'done' && (usage || turn.queries) && ( + {turn.state === 'done' && (!!usage || (turn.queries ?? 0) > 0) && (

{[usage ? T.usage(usage.input_tokens, usage.output_tokens) : '', turn.queries ? T.queries(turn.queries) : ''].filter(Boolean).join(' · ')}

)}
) -} +}) /** 助手跑过的一条只读查询:摘要写行数(失败写原因),展开看 SQL 和前几行 */ function QueryBlock({ part }: { part: Extract }) { diff --git a/scripts/check-catalog.mjs b/scripts/check-catalog.mjs index e4209b40..3c9bf300 100644 --- a/scripts/check-catalog.mjs +++ b/scripts/check-catalog.mjs @@ -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()