mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 00:21:58 +00:00
数据目录起草(前端):表结构里已经没有的表(只剩目录)不再给「重新起草这张表」「补充起草要求」——服务端只会把它记成已跳过。表详情顶部照写上次起草的结果(没写全的原因、留空了几列),不再建议补充起草要求后重新起草,写明「表结构中已没有这张表,不能重新起草」;清单上留空列的轻提示照旧,悬停里不再建议重新起草。起草设置收到的预先选好的范围(表详情的「补充起草要求」、结果里的「重新起草这几张表」)里的表已经不在表结构里时不再退回别的范围:那一项照旧选着,写明「表结构中已没有这张表,不能起草」(只少了几张时写其中几张不会起草),不能开始,也不默认勾用模型、不把焦点放进起草要求——以前退到「使用次数最多的 20 张表」还勾着用模型,以为只起草这一张,实际按别的范围起草了;选的那一项不在了时,单选框照实际起草的那一项勾。check-catalog 新加一节:只剩目录的表的清单提示与详情;打开详情以后表才没了(清单重取了、详情没重新载入)时「补充起草要求」打开的设置;重新打开后不再给两个按钮。check-all 里数据目录的耗时按实测改成 140 秒
This commit is contained in:
@@ -2434,6 +2434,13 @@ export const CATALOG_DRAFT_TEXT = {
|
||||
// ---- 从结果、表详情回到设置:范围就是那几张表
|
||||
scopeBlank: (n: number) => `有留空列的 ${formatNumber(n)} 张表`,
|
||||
scopeTable: (name: string) => `这张表:${name}`,
|
||||
/**
|
||||
* 预先选好的范围里有表已经不在表结构里(打开表详情以后别处重新探查了结构):写在那一项下面。一张都不剩时那一项
|
||||
* 照旧选着、不能开始,不退回别的范围
|
||||
*/
|
||||
presetMissing: (gone: number, total: number) => (gone < total
|
||||
? `其中 ${formatNumber(gone)} 张在表结构中已没有,不会起草`
|
||||
: total === 1 ? '表结构中已没有这张表,不能起草' : `表结构中已没有这 ${formatNumber(total)} 张表,不能起草`),
|
||||
// ---- 进行中
|
||||
queued: '排队中,等待开始…',
|
||||
running: '正在起草…',
|
||||
@@ -2538,6 +2545,8 @@ export const CATALOG_DRAFT_TEXT = {
|
||||
redraftHint: '用模型重新起草这张表(不带补充说明,带上数据源的起草要求);人工确认、驳回过的项不会被改动',
|
||||
redraftBusy: '已有起草任务在进行,结束后再重新起草这张表',
|
||||
redraftEditing: '正在编辑这张表,保存或取消后再重新起草',
|
||||
/** 表结构里已经没有的表(只剩目录):上次起草的结果照写,不给重新起草(服务端只会把它记成已跳过) */
|
||||
redraftMissing: '表结构中已没有这张表,不能重新起草',
|
||||
}
|
||||
|
||||
/** 设置页「偏好设置」里「起草模型」一节(服务端 api/settings.py 的 catalog_draft 一组) */
|
||||
|
||||
@@ -25,7 +25,8 @@ import type { DraftPreset, DraftStartBody } from './draftJob'
|
||||
//
|
||||
// 留空的列(模型列出了、中文名和含义都没给)不算没写全,表照样是已完成;结果里写合计和办法——在起草要求里补充缩写和
|
||||
// 术语的含义,再重新起草这几张表。「重新起草这几张表」回到设置:范围就是那几张、勾着用模型、焦点在起草要求,
|
||||
// 补完再开始。表详情里的「补充起草要求」也是这样打开(范围是那一张,见 preset)。
|
||||
// 补完再开始。表详情里的「补充起草要求」也是这样打开(范围是那一张,见 preset)。预先选好的范围里的表已经不在表结构
|
||||
// 里时不退回别的范围:那一项照旧选着、写明原因,不能开始。
|
||||
// ===========================================================================
|
||||
|
||||
type Scope = 'preset' | 'selected' | 'filtered' | 'top' | 'all'
|
||||
@@ -66,8 +67,9 @@ export function DraftDialog({
|
||||
/** 打开时显示的任务是撞上已有任务(409)转过来的:写明 */
|
||||
initialBusy?: boolean
|
||||
/**
|
||||
* 打开设置时预先选好的范围(表详情里「补充起草要求」:只有那张表)。给了就勾上用模型、焦点放在起草要求上:
|
||||
* 打开它就是为了先补起草要求再起草
|
||||
* 打开设置时预先选好的范围(表详情里「补充起草要求」:只有那张表)。给了就选它;里面有可起草的表时勾上用模型、
|
||||
* 焦点放在起草要求上(打开它就是为了先补起草要求再起草)。一张可起草的都没有(已不在表结构里)时照旧选着它、
|
||||
* 写明原因、不能开始,也不勾用模型——不退回别的范围
|
||||
*/
|
||||
preset?: DraftPreset | null
|
||||
/** 点了停止、服务端还没停下来 */
|
||||
@@ -88,9 +90,13 @@ export function DraftDialog({
|
||||
/** 预先选好的范围:打开时给的,或者结果里点了「重新起草这几张表」 */
|
||||
const [preset, setPreset] = useState<DraftPreset | null>(initialPreset)
|
||||
const scopes = useMemo(() => {
|
||||
const out: { key: Scope; label: string; tables: string[] }[] = []
|
||||
const pre = preset?.tables.filter((t) => liveSet.has(t)) ?? []
|
||||
if (preset && pre.length) out.push({ key: 'preset', label: preset.label, tables: pre })
|
||||
const out: { key: Scope; label: string; tables: string[]; gone?: number }[] = []
|
||||
// 预先选好的范围总在第一项:里面的表已经不在表结构里的(打开表详情以后别处重新探查了结构)不起草、写明有几张;
|
||||
// 一张都不剩也留着这一项,不退回别的范围——以为只起草那一张,实际按别的范围用模型起草了
|
||||
if (preset) {
|
||||
const pre = preset.tables.filter((t) => liveSet.has(t))
|
||||
out.push({ key: 'preset', label: preset.label, tables: pre, gone: preset.tables.length - pre.length })
|
||||
}
|
||||
const sel = selected.filter((t) => liveSet.has(t))
|
||||
if (sel.length) out.push({ key: 'selected', label: CT.scopeSelected(sel.length), tables: sel })
|
||||
const vis = visible.filter((r) => r.in_schema).map((r) => r.table_name)
|
||||
@@ -100,11 +106,15 @@ export function DraftDialog({
|
||||
return out
|
||||
}, [preset, selected, visible, filtered, live, liveSet])
|
||||
const [scope, setScope] = useState<Scope>(() => scopes[0].key)
|
||||
const tables = scopes.find((s) => s.key === scope)?.tables ?? scopes[0].tables
|
||||
/** 起草的就是这一项:选的那一项不在了(所选、筛选变了)就是第一项,单选框也照它勾,看得见起草的是哪些 */
|
||||
const current = scopes.find((s) => s.key === scope) ?? scopes[0]
|
||||
const tables = current.tables
|
||||
const tooMany = tables.length > DRAFT_MAX_TABLES
|
||||
const [useModel, setUseModel] = useState(!!initialPreset)
|
||||
/** 打开时给的范围里有可起草的表:勾上用模型、焦点放进起草要求(打开它就是为了先补起草要求);一张都没有就都不做 */
|
||||
const presetLive = !!initialPreset && initialPreset.tables.some((t) => liveSet.has(t))
|
||||
const [useModel, setUseModel] = useState(presetLive)
|
||||
/** 回到设置以后把焦点放进起草要求(预先选好范围的两种入口都是为了先补起草要求) */
|
||||
const [focusGuidance, setFocusGuidance] = useState(!!initialPreset)
|
||||
const [focusGuidance, setFocusGuidance] = useState(presetLive)
|
||||
const [guidanceText, setGuidanceText] = useState(guidance)
|
||||
const [note, setNote] = useState('')
|
||||
const guidanceLen = charCount(guidanceText)
|
||||
@@ -284,9 +294,16 @@ export function DraftDialog({
|
||||
<div className="space-y-1.5" role="radiogroup" aria-label={CT.draftScope}>
|
||||
{scopes.map((s) => (
|
||||
<label key={s.key} className={clsx('flex cursor-pointer items-center gap-2 rounded-md border px-3 py-2 text-xs',
|
||||
scope === s.key ? 'border-[var(--accent)] bg-accent-soft' : 'hover:bg-hover')} data-draft-scope={s.key}>
|
||||
<input type="radio" name="catalog-draft-scope" checked={scope === s.key} onChange={() => setScope(s.key)} />
|
||||
<span>{s.label}</span>
|
||||
current.key === s.key ? 'border-[var(--accent)] bg-accent-soft' : 'hover:bg-hover')} data-draft-scope={s.key}>
|
||||
<input type="radio" name="catalog-draft-scope" checked={current.key === s.key} onChange={() => setScope(s.key)} />
|
||||
<span className="min-w-0 break-words">
|
||||
{s.label}
|
||||
{!!s.gone && (
|
||||
<span className={clsx('block text-2xs', s.tables.length ? 'text-faint' : 'text-[var(--warn)]')} data-draft-preset-missing="">
|
||||
{DT.presetMissing(s.gone, s.gone + s.tables.length)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -77,9 +77,12 @@ export function TableDetail({
|
||||
onDetail: (d: CatalogDetail) => void
|
||||
/** 这个数据源有排队、进行中的起草任务:「重新起草这张表」等它结束 */
|
||||
draftBusy?: boolean
|
||||
/** 上次起草失败、部分完成或有留空列时的「重新起草这张表」:建只含这张表的起草任务(用模型、不带补充说明) */
|
||||
/**
|
||||
* 上次起草失败、部分完成或有留空列时的「重新起草这张表」:建只含这张表的起草任务(用模型、不带补充说明)。
|
||||
* 表结构里已经没有的表不给
|
||||
*/
|
||||
onRedraft?: (table: string) => Promise<void>
|
||||
/** 上次起草有留空列时的「补充起草要求」:打开起草设置,范围只有这张表,先补起草要求再开始 */
|
||||
/** 上次起草有留空列时的「补充起草要求」:打开起草设置,范围只有这张表,先补起草要求再开始。表结构里已经没有的表不给 */
|
||||
onDraftGuidance?: (table: string) => void
|
||||
}) {
|
||||
const [detail, setDetail] = useState<CatalogDetail | null>(null)
|
||||
@@ -465,7 +468,8 @@ export function TableDetail({
|
||||
// 上次起草没写全:写明原因;有留空的列:写几列、怎么办(在起草要求里补充缩写和术语的含义)。只有留空的列时是
|
||||
// 提示(表照样写完了),不是失败。给「重新起草这张表」(只含这张表、用模型);有留空的列再给「补充起草要求」
|
||||
// (打开起草设置,范围是这张表)。有起草任务在进行、正在编辑这张表时先不给点:起草会写进这张表,编辑中的保存
|
||||
// 就会撞上版本冲突
|
||||
// 就会撞上版本冲突。表结构里已经没有这张表(只剩目录)时上次的结果照写,不给这两个按钮、不建议重新起草,
|
||||
// 写明为什么:服务端只会把它记成「已跳过」
|
||||
<Notice tone={lastBad ? (lastBad.status === 'failed' ? 'err' : 'warn') : 'info'} attr={{ 'data-catalog-last-draft': lastDraft.status }}>
|
||||
{lastBad && <p className="font-medium">{lastBad.status === 'failed' ? DT.lastFailed : DT.lastPartial}</p>}
|
||||
{lastBad && taskReason(lastBad) && (
|
||||
@@ -474,10 +478,11 @@ export function TableDetail({
|
||||
{lastBlank > 0 && (
|
||||
<>
|
||||
<p className={lastBad ? 'mt-0.5 text-dim' : 'font-medium'} data-catalog-last-draft-blank={lastBlank}>{DT.lastBlank(lastBlank)}</p>
|
||||
<p className="mt-0.5 text-dim">{DT.lastBlankAdvice}</p>
|
||||
{detail.in_schema && <p className="mt-0.5 text-dim">{DT.lastBlankAdvice}</p>}
|
||||
</>
|
||||
)}
|
||||
{onRedraft && (
|
||||
{!detail.in_schema && <p className="mt-1 text-2xs text-faint" data-catalog-redraft-missing="">{DT.redraftMissing}</p>}
|
||||
{detail.in_schema && onRedraft && (
|
||||
<>
|
||||
<div className="mt-2 flex flex-wrap items-center gap-1.5">
|
||||
<button type="button" className="btn btn-sm" disabled={draftBusy || redrafting || !!edit} title={DT.redraftHint}
|
||||
|
||||
@@ -213,8 +213,10 @@ const Row = memo(function Row({ row: r, index, active, checked, onToggle, onOpen
|
||||
const last = r.last_draft && (r.last_draft.status === 'failed' || r.last_draft.status === 'partial') ? r.last_draft : null
|
||||
const lastLabel = last ? (last.status === 'failed' ? DT.lastFailed : DT.lastPartial) : ''
|
||||
const lastText = last ? (taskReason(last) ? DT.lastWith(lastLabel, taskReason(last)) : lastLabel) : ''
|
||||
// 上次起草有留空的列:表照样写完了,只是模型对这几列拿不准。淡色的轻提示,和失败标记分开
|
||||
// 上次起草有留空的列:表照样写完了,只是模型对这几列拿不准。淡色的轻提示,和失败标记分开。表结构里已经没有的表
|
||||
// 起草不了:悬停里不建议重新起草
|
||||
const blank = lastBlankOf(r.last_draft)
|
||||
const blankTitle = r.in_schema ? `${DT.lastBlank(blank)}。${DT.lastBlankAdvice}` : DT.lastBlank(blank)
|
||||
return (
|
||||
<li
|
||||
className={clsx('relative flex items-stretch border-b border-hairline', active ? 'bg-accent-soft' : 'hover:bg-hover')}
|
||||
@@ -265,7 +267,7 @@ const Row = memo(function Row({ row: r, index, active, checked, onToggle, onOpen
|
||||
</span>
|
||||
)}
|
||||
{blank > 0 && (
|
||||
<span className="mt-0.5 flex min-w-0 items-center gap-1 text-2xs text-faint" title={`${DT.lastBlank(blank)}。${DT.lastBlankAdvice}`}
|
||||
<span className="mt-0.5 flex min-w-0 items-center gap-1 text-2xs text-faint" title={blankTitle}
|
||||
data-row-blank={blank}>
|
||||
<Info size={10} className="shrink-0" aria-hidden />
|
||||
<span className="min-w-0 truncate">{DT.lastBlank(blank)}</span>
|
||||
|
||||
@@ -97,7 +97,7 @@ const ORDER = [
|
||||
const ESTIMATE = {
|
||||
challenge: 30,
|
||||
stream: 173, chat: 160, studio: 149, manage: 145, runs: 112, publish: 92, shell: 87,
|
||||
evidence: 91, versions: 60, catalog: 130, 'ui-kit': 70, ui: 61, guards: 8, 'canvas-layout': 2, copy: 2, tokens: 1, trace: 1, decode: 1,
|
||||
evidence: 91, versions: 60, catalog: 140, 'ui-kit': 70, ui: 61, guards: 8, 'canvas-layout': 2, copy: 2, tokens: 1, trace: 1, decode: 1,
|
||||
// 占位,换成真检查时按实测改
|
||||
schedules: 60, compare: 45, verify: 71, inquiry: 60, bundle: 16, whatif: 33, 'report-export': 30,
|
||||
}
|
||||
|
||||
@@ -1544,6 +1544,90 @@ await section('数据目录 · 起草:模型拿不准、留空的列', async (
|
||||
await close()
|
||||
})
|
||||
|
||||
await section('数据目录 · 起草:表结构里已经没有的表', async () => {
|
||||
const state = freshState()
|
||||
const tOf = (n) => state.tables[S1].find((t) => t.name === n)
|
||||
// 起草过(部分完成、留了几列空)的表后来重新探查结构时没了,只剩目录;另一张有留空列的这时还在表结构里
|
||||
tOf('legacy_coupons').lastDraft = {
|
||||
status: 'partial', job_id: 'check-job-old', finished_at: T0, error: null, model_error: CALL_ERR, blank_columns: 3,
|
||||
}
|
||||
tOf('channels').lastDraft = { status: 'ok', job_id: 'check-job-old', finished_at: T0, error: null, model_error: null, blank_columns: 4 }
|
||||
const { page, sent, errors, natives, close } = await open(`/data/catalog/${S1}`, { handlers: catalogHandlers(state) })
|
||||
await page.locator('[data-catalog-row]').first().waitFor()
|
||||
const tip = page.locator('[data-catalog-last-draft]')
|
||||
const tipText = async () => (await tip.innerText({ timeout: 500 }).catch(() => '')).replace(/\s+/g, ' ').trim()
|
||||
const buttons = () => tip.locator('[data-catalog-redraft], [data-catalog-draft-guidance]').count()
|
||||
|
||||
// 清单:只剩目录的表照样写上次起草留空了几列(是历史),悬停里不再建议重新起草(起草不了)
|
||||
const goneBlank = page.locator('[data-catalog-row="legacy_coupons"] [data-row-blank]')
|
||||
const goneTitle = (await goneBlank.getAttribute('title', { timeout: 1000 }).catch(() => '')) ?? ''
|
||||
check('清单:只剩目录的表照样提示上次起草留空的列,悬停里不建议重新起草',
|
||||
(await goneBlank.innerText({ timeout: 500 }).catch(() => '')).includes('上次起草有 3 列模型拿不准、留空了')
|
||||
&& !!goneTitle && !goneTitle.includes('重新起草'), goneTitle)
|
||||
|
||||
// 表详情:写明上次起草的结果,不给「重新起草这张表」「补充起草要求」(服务端只会把它记成已跳过),写明为什么
|
||||
await page.locator('[data-catalog-row="legacy_coupons"] [data-catalog-open-row]').click()
|
||||
check('只剩目录的表:详情顶部照样写上次起草部分完成、原因和留空的列',
|
||||
await until(async () => (await tipText()).includes('上次起草部分完成'))
|
||||
&& (await tipText()).includes(CALL_ERR) && (await tipText()).includes('3 列模型拿不准、留空了'), await tipText())
|
||||
check('……不给「重新起草这张表」「补充起草要求」,写明表结构中已没有这张表、不能重新起草',
|
||||
await buttons() === 0
|
||||
&& (await tip.locator('[data-catalog-redraft-missing]').innerText({ timeout: 500 }).catch(() => '')).includes('表结构中已没有这张表,不能重新起草'),
|
||||
await tipText())
|
||||
check('……也不建议补充起草要求再重新起草', !(await tipText()).includes('再重新起草'), await tipText())
|
||||
await shot(page, 'catalog-missing-last-draft')
|
||||
|
||||
// 打开详情以后表才没了(别处重新探查了结构):清单重取了、详情还没有重新载入。这时点「补充起草要求」,起草设置
|
||||
// 不能退回别的范围(以前退到「使用次数最多的 20 张表」还勾着用模型,以为只起草这一张,实际按别的范围起草了)
|
||||
await page.locator('[data-catalog-row="channels"] [data-catalog-open-row]').click()
|
||||
check('还在表结构里的表:详情给「重新起草这张表」和「补充起草要求」',
|
||||
await until(async () => (await tip.locator('[data-catalog-draft-guidance]').count()) === 1)
|
||||
&& await tip.locator('[data-catalog-redraft]').count() === 1, await tipText())
|
||||
tOf('channels').missing = true
|
||||
// 起草另一张表:每张表起草完页面就重取清单;正在看的这张不在任务里,详情不重新载入
|
||||
check('起草另一张表(不用模型)', await startDraft(page, { select: ['gates'] }))
|
||||
const job = state.jobs.at(-1)
|
||||
settle(state, job, 'gates', { added: 1 })
|
||||
finishJob(job)
|
||||
await until(async () => (await draftPhase(page)) === 'done')
|
||||
await page.locator('[data-draft-close]').click()
|
||||
check('……清单重取回来:这张表标成表结构中已没有,详情还是打开时的那份',
|
||||
await until(async () => (await page.locator('[data-catalog-row="channels"] [data-row-missing]').count()) === 1)
|
||||
&& await tip.locator('[data-catalog-draft-guidance]').count() === 1)
|
||||
const jobsBefore = count(sent, /^POST .*draft-jobs$/)
|
||||
await tip.locator('[data-catalog-draft-guidance]').click()
|
||||
const presetInput = draftBox(page).locator('[data-draft-scope="preset"] input')
|
||||
check('「补充起草要求」:范围仍是这张表、选着它,写明表结构中已没有这张表、不能起草',
|
||||
await until(async () => (await draftPhase(page)) === 'setup')
|
||||
&& await presetInput.isChecked({ timeout: 1000 }).catch(() => false)
|
||||
&& (await draftText(page, '[data-draft-scope="preset"]')).includes('channels')
|
||||
&& (await draftText(page, '[data-draft-preset-missing]')).includes('表结构中已没有这张表,不能起草'),
|
||||
`${await draftText(page, '[data-draft-scope="preset"]')} · 选中:${await draftBox(page).locator('[data-draft-scope]:has(input:checked)').getAttribute('data-draft-scope', { timeout: 500 }).catch(() => '')}`)
|
||||
check('……不能开始,也不默认勾用模型,焦点不放进起草要求', await page.locator('[data-draft-start]').isDisabled()
|
||||
&& !(await draftBox(page).locator('[data-draft-model] input').isChecked())
|
||||
&& await page.evaluate(() => document.activeElement?.id !== 'catalog-draft-guidance'),
|
||||
`${await page.locator('[data-draft-start]').innerText()} · 用模型:${await draftBox(page).locator('[data-draft-model] input').isChecked()}`)
|
||||
// 截图等弹窗的打开动效播完
|
||||
if (SHOTS) await sleep(400)
|
||||
await shot(page, 'catalog-missing-preset')
|
||||
await page.keyboard.press('Escape')
|
||||
check('……关掉:没有建任务', await until(async () => (await draftBox(page).count()) === 0) && count(sent, /^POST .*draft-jobs$/) === jobsBefore)
|
||||
|
||||
// 换一张再回来:详情重新载入,也认得表结构中已没有,不再给两个按钮
|
||||
await page.locator('[data-catalog-row="gates"] [data-catalog-open-row]').click()
|
||||
await until(async () => (await page.locator('[data-catalog-detail="gates"][data-version]').count()) === 1)
|
||||
await page.locator('[data-catalog-row="channels"] [data-catalog-open-row]').click()
|
||||
check('重新打开这张表:详情也写明表结构中已没有,不再给两个按钮',
|
||||
await until(async () => (await page.locator('[data-catalog-detail="channels"] [data-catalog-missing]').count()) === 1)
|
||||
&& await buttons() === 0 && (await tipText()).includes('不能重新起草'), await tipText())
|
||||
await cleanCopy(page, '表结构里已经没有的表')
|
||||
check('只发过起草任务相关的写请求', writes(sent, /./).every((s) => /catalog\/(draft-jobs|guidance)/.test(s.key)),
|
||||
JSON.stringify(writes(sent, /./).map((s) => s.key)))
|
||||
check('没有原生对话框', natives.length === 0, natives.join(' | '))
|
||||
check('没有运行时报错', errors.length === 0, errors[0] ?? '')
|
||||
await close()
|
||||
})
|
||||
|
||||
await section('数据目录 · 表详情与单项审阅', async () => {
|
||||
const state = freshState()
|
||||
const { page, sent, errors, natives, close } = await open(`/data/catalog/${S1}`, { handlers: catalogHandlers(state) })
|
||||
|
||||
Reference in New Issue
Block a user