mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 01:41:57 +00:00
数据目录阶段 4A 摘要提示:目录页顶部写明运行中查询过的表里还有几张有推断项未确认、几张没有目录,点击切到对应筛选并按使用次数排序
This commit is contained in:
@@ -2009,6 +2009,13 @@ export const CATALOG_TEXT = {
|
||||
countTitle: (label: string, n: number) => `${label} ${formatNumber(n)} 项`,
|
||||
noMatch: '没有符合条件的表',
|
||||
noMatchBody: '请换个关键词或筛选条件。',
|
||||
// ---- 用到但没确认(页面顶部的摘要)
|
||||
usageLead: (used: number) => `运行中查询过的 ${formatNumber(used)} 张表里,`,
|
||||
usagePending: (n: number) => `有 ${formatNumber(n)} 张还有推断项未确认`,
|
||||
usageNone: (n: number) => `${formatNumber(n)} 张还没有目录`,
|
||||
usageAllDone: (used: number) => `运行中查询过的 ${formatNumber(used)} 张表都已确认`,
|
||||
usageHintPending: '筛选有未确认项的表,按使用次数排序',
|
||||
usageHintNone: '筛选没有目录的表,按使用次数排序',
|
||||
// ---- 概览(没有选中表时)
|
||||
overviewTitle: '选择一张表开始审阅',
|
||||
overviewBody: '按使用次数从高到低逐表确认。推断的项只作提示,确认后才参与 SQL 检查。',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { ArrowLeft, BookMarked, Database, Sparkles } from 'lucide-react'
|
||||
import { ArrowLeft, BookMarked, Database, ListFilter as ListFilterIcon, Sparkles } from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
import { ApiError, api } from '../../api/client'
|
||||
import type { CatalogDetail, CatalogList, CatalogTableRow } from '../../types'
|
||||
@@ -12,7 +12,7 @@ import { StatusLegend, SystemNotesNotice } from './parts'
|
||||
import { DraftDialog } from './DraftDialog'
|
||||
import { TableDetail } from './TableDetail'
|
||||
import { TableIndex } from './TableIndex'
|
||||
import { filterCounts, progressOf, rowFromDetail, visibleRows } from './model'
|
||||
import { filterCounts, progressOf, rowFromDetail, usageSummary, visibleRows } from './model'
|
||||
import type { ListFilter, ListSort } from './model'
|
||||
|
||||
// ===========================================================================
|
||||
@@ -96,6 +96,18 @@ export function CatalogPage() {
|
||||
return [list[i - 1]?.table_name ?? null, list[i + 1]?.table_name ?? null]
|
||||
}, [table, visible, rows, sort])
|
||||
|
||||
const usage = useMemo(() => usageSummary(rows), [rows])
|
||||
/**
|
||||
* 顶部摘要点进来:清掉搜索词,切到对应的筛选,按使用次数排——用到最多的排在最前。窄屏一次只显示一栏,
|
||||
* 正在看表详情时回到清单才看得到筛选结果;宽屏两栏都在,正在看的表不动
|
||||
*/
|
||||
const focusUsed = useCallback((f: ListFilter) => {
|
||||
setQuery('')
|
||||
setFilter(f)
|
||||
setSort('usage')
|
||||
if (table && typeof matchMedia === 'function' && matchMedia('(max-width: 1023px)').matches) navigate(base)
|
||||
}, [table, navigate, base])
|
||||
|
||||
const filtered = !!query.trim() || filter !== 'all'
|
||||
const empty = rows.length > 0 && rows.every((r) => progressOf(r.counts) === 'none')
|
||||
const hasTables = rows.length > 0
|
||||
@@ -188,6 +200,7 @@ export function CatalogPage() {
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
{hasTables && usage.used > 0 && !empty && <UsageSummary {...usage} active={filter} onFocus={focusUsed} />}
|
||||
{body}
|
||||
{drafting && data && (
|
||||
<DraftDialog
|
||||
@@ -204,6 +217,45 @@ export function CatalogPage() {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 「用到但没确认」:运行中查询过的表里还有几张有推断项、几张没有目录。这些表的推断项会被助手当真用,最该先审。
|
||||
* 两段各是一个按钮,点了切到清单的对应筛选、按使用次数排;都审完了只写一句,不给按钮
|
||||
*/
|
||||
function UsageSummary({ used, pending, none, active, onFocus }: {
|
||||
used: number
|
||||
pending: number
|
||||
none: number
|
||||
active: ListFilter
|
||||
onFocus: (f: ListFilter) => void
|
||||
}) {
|
||||
const part = (f: 'pending' | 'none', text: string, hint: string) => (
|
||||
<button type="button" onClick={() => onFocus(f)} title={hint} aria-pressed={active === f} data-usage-focus={f}
|
||||
className={clsx('rounded px-1 font-medium underline decoration-dotted underline-offset-2 outline-none',
|
||||
'hover:bg-hover focus-visible:ring-2 focus-visible:ring-[var(--accent)]',
|
||||
active === f ? 'text-[var(--accent)]' : 'text-fg')}>
|
||||
{text}
|
||||
</button>
|
||||
)
|
||||
const todo = pending > 0 || none > 0
|
||||
return (
|
||||
<div className="flex shrink-0 items-start gap-2 border-b px-4 py-2 text-xs"
|
||||
style={todo ? { background: 'color-mix(in srgb, var(--st-waiting) 6%, var(--bg-panel))' } : undefined}
|
||||
data-catalog-usage-summary={`${used},${pending},${none}`}>
|
||||
<ListFilterIcon size={12} className="mt-0.5 shrink-0" style={{ color: todo ? 'var(--st-waiting)' : 'var(--st-done)' }} aria-hidden />
|
||||
{todo
|
||||
? (
|
||||
<p className="min-w-0 leading-relaxed text-dim">
|
||||
{CT.usageLead(used)}
|
||||
{pending > 0 && part('pending', CT.usagePending(pending), CT.usageHintPending)}
|
||||
{pending > 0 && none > 0 && ','}
|
||||
{none > 0 && part('none', CT.usageNone(none), CT.usageHintNone)}
|
||||
</p>
|
||||
)
|
||||
: <p className="min-w-0 text-dim">{CT.usageAllDone(used)}</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** 没有选中表时的右栏:空目录时引导起草;否则写审阅进度,给出下一张该审的表 */
|
||||
function Overview({ rows, empty, systemNotes, draft, onOpen }: {
|
||||
rows: CatalogTableRow[]
|
||||
|
||||
@@ -125,6 +125,22 @@ export function visibleRows(rows: CatalogTableRow[], q: string, filter: ListFilt
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 「用到但没确认」:运行中查询过的表(使用次数大于 0、还在表结构里)一共几张,其中几张还有推断项、几张还没有目录。
|
||||
* 页面顶部的摘要按它写,点进去复用清单的筛选(有未确认项 / 没有目录)和按使用次数排序,不另做一套
|
||||
*/
|
||||
export function usageSummary(rows: CatalogTableRow[]): { used: number; pending: number; none: number } {
|
||||
const out = { used: 0, pending: 0, none: 0 }
|
||||
for (const r of rows) {
|
||||
if (!r.in_schema || r.usage <= 0) continue
|
||||
out.used++
|
||||
const p = progressOf(r.counts)
|
||||
if (p === 'pending') out.pending++
|
||||
else if (p === 'none') out.none++
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/** 各筛选项下有几张表(筛选按钮上的数字,按搜索词算) */
|
||||
export function filterCounts(rows: CatalogTableRow[], q: string): Record<ListFilter, number> {
|
||||
const out: Record<ListFilter, number> = { all: 0, pending: 0, done: 0, none: 0 }
|
||||
|
||||
@@ -579,6 +579,43 @@ await section('数据目录 · 表清单的排序与筛选', async () => {
|
||||
await close()
|
||||
})
|
||||
|
||||
await section('数据目录 · 用到但没确认(顶部摘要)', async () => {
|
||||
const state = freshState()
|
||||
// 期望值照假数据现算:使用次数大于 0、还在表结构里的表,按推断项 / 没有目录分
|
||||
const used = state.tables[S1].filter((t) => !t.missing && t.usage > 0)
|
||||
const progress = (t) => { const c = countsOf(t.notes ?? {}); return c.proposed ? 'pending' : c.verified + c.confirmed + c.rejected ? 'done' : 'none' }
|
||||
const pending = used.filter((t) => progress(t) === 'pending').length
|
||||
const none = used.filter((t) => progress(t) === 'none').length
|
||||
const { page, sent, errors, close } = await open(`/data/catalog/${S1}`, { handlers: catalogHandlers(state) })
|
||||
const bar = page.locator('[data-catalog-usage-summary]')
|
||||
await bar.waitFor({ timeout: 3000 })
|
||||
check('数字照清单算:查询过的表、有推断项的、没有目录的', await bar.getAttribute('data-catalog-usage-summary') === `${used.length},${pending},${none}`,
|
||||
`${await bar.getAttribute('data-catalog-usage-summary')} ≠ ${used.length},${pending},${none}`)
|
||||
const text = await bar.innerText()
|
||||
check('写成一句话', text.includes(`运行中查询过的 ${used.length} 张表里,有 ${pending} 张还有推断项未确认,${none} 张还没有目录`), text)
|
||||
await bar.locator('[data-usage-focus="pending"]').click()
|
||||
check('点「还有推断项未确认」:切到「有未确认项」筛选',
|
||||
(await page.locator('[data-catalog-filter]').getAttribute('data-catalog-filter')) === 'pending')
|
||||
check('……排序是按使用次数', await page.locator('[data-catalog-sort]').inputValue() === 'usage')
|
||||
const shown = await rowNames(page)
|
||||
const expectTop = used.filter((t) => progress(t) === 'pending').sort((a, b) => b.usage - a.usage).map((t) => t.name)
|
||||
check('……查询过的、有推断项的表排在最前', shown.slice(0, expectTop.length).join(',') === expectTop.join(','),
|
||||
`${shown.slice(0, expectTop.length).join(',')} ≠ ${expectTop.join(',')}`)
|
||||
await bar.locator('[data-usage-focus="none"]').click()
|
||||
check('点「还没有目录」:切到「没有目录」筛选', (await page.locator('[data-catalog-filter]').getAttribute('data-catalog-filter')) === 'none'
|
||||
&& (await rowNames(page))[0] === used.filter((t) => progress(t) === 'none').sort((a, b) => b.usage - a.usage)[0].name)
|
||||
check('摘要只读、不发请求', writes(sent, /./).length === 0)
|
||||
// 都审完了:只写一句,不给按钮
|
||||
for (const t of state.tables[S1]) if (t.notes) t.notes = applyPut({}, { label: { value: t.notes.label?.value ?? t.name } })
|
||||
for (const t of state.tables[S1]) if (!t.notes && t.usage > 0) t.notes = { label: item(t.name, 'human') }
|
||||
await goto(page, `/data/catalog/${S1}`)
|
||||
await bar.waitFor({ timeout: 3000 })
|
||||
check('都确认了:写「都已确认」,没有按钮', (await bar.innerText()).includes(`运行中查询过的 ${used.length} 张表都已确认`)
|
||||
&& await bar.locator('[data-usage-focus]').count() === 0, await bar.innerText())
|
||||
check('没有运行时报错', errors.length === 0, errors.join(' | '))
|
||||
await close()
|
||||
})
|
||||
|
||||
await section('数据目录 · 起草', async () => {
|
||||
const state = freshState()
|
||||
const MODEL_ERR = '未配置模型接入,无法由助手起草数据目录。请到「设置 → 模型接入」添加'
|
||||
|
||||
Reference in New Issue
Block a user