修改
This commit is contained in:
@@ -68,6 +68,9 @@ type GateItem = {
|
||||
description: string;
|
||||
status: CardStatus;
|
||||
reason?: string | null;
|
||||
summary?: string | null;
|
||||
proposal?: string | null;
|
||||
ask?: string | null;
|
||||
};
|
||||
|
||||
type PipelineItem = RoundItem | GateItem;
|
||||
@@ -485,6 +488,95 @@ function CardArrow() {
|
||||
);
|
||||
}
|
||||
|
||||
function parseGateReason(reason: string | null | undefined): {
|
||||
summary: string | null;
|
||||
proposal: string | null;
|
||||
ask: string | null;
|
||||
} {
|
||||
const text = (reason ?? "").trim();
|
||||
if (!text) return { summary: null, proposal: null, ask: null };
|
||||
// Find the ask: greedy from any "是否/要不要/能否/还是/请..." question phrase
|
||||
// to end. Fallback: the trailing sentence containing ?/?.
|
||||
const askPatterns = [
|
||||
/(是否[^。]*$)/,
|
||||
/(要不要[^。]*$)/,
|
||||
/(能否[^。]*$)/,
|
||||
/(请[^。]{0,40}[??][^。]*$)/,
|
||||
];
|
||||
let ask: string | null = null;
|
||||
let body = text;
|
||||
for (const pat of askPatterns) {
|
||||
const m = text.match(pat);
|
||||
if (m && m.index !== undefined) {
|
||||
ask = m[0].trim().replace(/^[。;;,,\s]+/, "");
|
||||
body = text.slice(0, m.index).trim().replace(/[。;;,,\s]+$/, "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ask) {
|
||||
// Fallback: split off trailing sentence(s) containing ? or ?
|
||||
const qIdx = Math.max(text.lastIndexOf("?"), text.lastIndexOf("?"));
|
||||
if (qIdx >= 0) {
|
||||
// expand back to previous 。 or start of string
|
||||
const prevDot = Math.max(
|
||||
text.lastIndexOf("。", qIdx - 1),
|
||||
text.lastIndexOf(";", qIdx - 1),
|
||||
text.lastIndexOf(";", qIdx - 1),
|
||||
);
|
||||
ask = text.slice(prevDot + 1).trim().replace(/^[。;;,,\s]+/, "");
|
||||
body = text.slice(0, prevDot + 1).trim().replace(/[。;;,,\s]+$/, "");
|
||||
}
|
||||
}
|
||||
const sentences = body
|
||||
.split(/。/)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
if (sentences.length === 0) {
|
||||
return { summary: null, proposal: null, ask };
|
||||
}
|
||||
if (sentences.length === 1) {
|
||||
return { summary: sentences[0], proposal: null, ask };
|
||||
}
|
||||
const proposalKw =
|
||||
/(H\d|H[1-3]\b|拟|要做|计划|提议|方案|增强|仿写|修改|修\s*\d|fix|patch|候选|逐条审|relabel|sft|train)/i;
|
||||
let splitIdx = sentences.findIndex((s) => proposalKw.test(s));
|
||||
if (splitIdx <= 0) splitIdx = Math.max(1, Math.floor(sentences.length / 2));
|
||||
const summary = sentences.slice(0, splitIdx).join("。 ");
|
||||
const proposal = sentences.slice(splitIdx).join("。 ");
|
||||
return { summary, proposal, ask };
|
||||
}
|
||||
|
||||
function GateBodyRow({
|
||||
label,
|
||||
text,
|
||||
tone,
|
||||
}: {
|
||||
label: string;
|
||||
text: string;
|
||||
tone: "status" | "proposal" | "ask";
|
||||
}) {
|
||||
const labelTone =
|
||||
tone === "ask"
|
||||
? "bg-amber-500/25 text-amber-800 dark:bg-amber-400/30 dark:text-amber-100"
|
||||
: tone === "proposal"
|
||||
? "bg-sky-500/15 text-sky-700 dark:bg-sky-400/20 dark:text-sky-200"
|
||||
: "bg-zinc-500/15 text-zinc-700 dark:bg-zinc-400/20 dark:text-zinc-200";
|
||||
const textTone = tone === "ask" ? "text-foreground font-medium" : "text-foreground/85";
|
||||
return (
|
||||
<div className="flex items-start gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
"shrink-0 rounded px-1.5 py-0.5 font-mono text-[10px] font-semibold tracking-wider",
|
||||
labelTone,
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
<p className={cn("text-sm leading-relaxed", textTone)}>{text}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function GateCard({
|
||||
item,
|
||||
onClick,
|
||||
@@ -500,12 +592,18 @@ function GateCard({
|
||||
item.gate_kind === "human-review"
|
||||
? "在右侧对话里给出 OK / 调整后的指令,agent 才会继续。"
|
||||
: "在右侧对话里回 OK / 修改建议,确认后进入下一轮训练。";
|
||||
// Prefer structured fields; fall back to heuristic parsing of `reason`.
|
||||
const parsed = parseGateReason(item.reason);
|
||||
const summary = item.summary ?? parsed.summary;
|
||||
const proposal = item.proposal ?? parsed.proposal;
|
||||
const ask = item.ask ?? parsed.ask;
|
||||
const hasStructured = Boolean(summary || proposal || ask);
|
||||
return (
|
||||
<Tag
|
||||
type={onClick ? "button" : undefined}
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between gap-3 rounded-2xl border-2 border-dashed bg-amber-500/5 p-4 text-left transition-colors",
|
||||
"flex w-full items-center justify-between gap-4 rounded-2xl border-2 border-dashed bg-amber-500/5 p-5 text-left transition-colors",
|
||||
isWaiting
|
||||
? "border-amber-400 bg-amber-500/10 ring-2 ring-amber-300/40 dark:ring-amber-500/30 animate-pipeline-pill-glow"
|
||||
: isComplete
|
||||
@@ -516,32 +614,42 @@ function GateCard({
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<span className="flex size-9 shrink-0 items-center justify-center rounded-full bg-amber-500/15 font-mono text-amber-700 text-sm font-semibold dark:text-amber-300">
|
||||
<span className="flex size-10 shrink-0 items-center justify-center rounded-full bg-amber-500/15 font-mono text-amber-700 text-base font-semibold dark:text-amber-300">
|
||||
{numLabel}
|
||||
</span>
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-amber-500/20 text-amber-700 dark:text-amber-300">
|
||||
<UserCheckIcon className="size-5" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="font-semibold text-foreground text-sm">
|
||||
<UserCheckIcon className="size-5 shrink-0 text-amber-700 dark:text-amber-300" />
|
||||
<span className="font-semibold text-foreground text-base">
|
||||
{item.title}
|
||||
</span>
|
||||
<span className="rounded bg-amber-500/15 px-1.5 py-0.5 font-mono text-[10px] text-amber-700 dark:text-amber-300">
|
||||
<span className="rounded bg-amber-500/15 px-1.5 py-0.5 font-mono text-xs text-amber-700 dark:text-amber-300">
|
||||
{item.run_id}
|
||||
</span>
|
||||
</div>
|
||||
{item.reason ? (
|
||||
<p className="mt-1 text-[12px] text-foreground/80 leading-snug line-clamp-3">
|
||||
{hasStructured ? (
|
||||
<div className="mt-2 space-y-1.5">
|
||||
{summary ? (
|
||||
<GateBodyRow label="现状" text={summary} tone="status" />
|
||||
) : null}
|
||||
{proposal ? (
|
||||
<GateBodyRow label="提议" text={proposal} tone="proposal" />
|
||||
) : null}
|
||||
{ask ? (
|
||||
<GateBodyRow label="请选" text={ask} tone="ask" />
|
||||
) : null}
|
||||
</div>
|
||||
) : item.reason ? (
|
||||
<p className="mt-1.5 text-sm text-foreground/85 leading-relaxed">
|
||||
{item.reason}
|
||||
</p>
|
||||
) : (
|
||||
<p className="mt-1 text-[11px] text-muted-foreground leading-snug">
|
||||
<p className="mt-1.5 text-sm text-muted-foreground leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
)}
|
||||
{isWaiting ? (
|
||||
<p className="mt-1 text-[10px] uppercase tracking-wide text-amber-700/80 dark:text-amber-400/80">
|
||||
<p className="mt-2 text-xs tracking-wide text-amber-700/90 dark:text-amber-400/90">
|
||||
{hint}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user