feat: give the curator continuous context and durable idea history

This commit is contained in:
wuyang
2026-07-28 17:31:37 +08:00
parent 5c483536b4
commit af7bb68268
10 changed files with 967 additions and 114 deletions
+73 -4
View File
@@ -363,12 +363,41 @@ function IdeaDetail({
</ul>
</section>
{idea.snapshots && idea.snapshots.length > 1 && (
<details className="idea-history">
<summary>这个想法走过的路</summary>
<div>
{[...idea.snapshots].reverse().map((snapshot, index) => (
<article key={`${snapshot.created_at}-${index}`}>
<time>
{new Intl.DateTimeFormat("zh-CN", {
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
}).format(new Date(snapshot.created_at))}
</time>
<strong>
{snapshot.change_kind === "manual_calibration"
? "你重新放置了它"
: snapshot.motion}
</strong>
<p>{snapshot.trajectory}</p>
</article>
))}
</div>
</details>
)}
{idea.fragments && idea.fragments.length > 0 && (
<details className="evidence">
<summary>构成这个想法的片段</summary>
<summary>原始脉络</summary>
<div>
{idea.fragments.map((fragment) => (
<blockquote key={fragment.id}>{fragment.content}</blockquote>
<article key={fragment.id}>
<time>{dayLabel(fragment.created_at)}</time>
<blockquote>{fragment.content}</blockquote>
</article>
))}
</div>
</details>
@@ -454,6 +483,8 @@ const eventNames: Record<string, string> = {
tool_search_ideas: "搜索已有想法",
tool_inspect_idea: "查看想法轨迹",
model_attempt_completed: "模型返回",
model_round_completed: "完成一轮判断",
convergence_repair_started: "转入收敛判断",
validation_failed: "结构校验未通过",
decision_committed: "提交分析结果",
run_failed: "运行失败",
@@ -466,6 +497,7 @@ const auditNames: Record<string, string> = {
logout: "退出登录",
fragment_created: "保存新片段",
idea_position_calibrated: "人工校准位势",
agent_run_requeued: "重新分析",
debug_sharing_changed: "调整调试共享",
};
@@ -510,6 +542,18 @@ function EventPayload({ event }: { event: AgentEvent }) {
</p>
);
}
if (event.event_type === "model_round_completed") {
return (
<p>
输入 {compactNumber(Number(payload.input_tokens ?? 0))} · 输出{" "}
{compactNumber(Number(payload.output_tokens ?? 0))} · 推理{" "}
{compactNumber(Number(payload.reasoning_tokens ?? 0))}
</p>
);
}
if (event.event_type === "convergence_repair_started") {
return <p>工具探索没有及时收束,系统改用已有上下文直接完成判断。</p>;
}
if (event.event_type === "validation_failed") {
return (
<p>
@@ -561,11 +605,24 @@ function RunDetail({
onClose: () => void;
}) {
const [detail, setDetail] = useState<AgentRunDetail | null>(null);
const [retrying, setRetrying] = useState(false);
const [requeued, setRequeued] = useState(false);
useEffect(() => {
api.adminRun(runId).then(setDetail);
}, [runId]);
async function retry() {
if (retrying || requeued) return;
setRetrying(true);
try {
await api.retryAdminRun(runId);
setRequeued(true);
} finally {
setRetrying(false);
}
}
return (
<div className="sheet-backdrop" role="presentation" onMouseDown={onClose}>
<article
@@ -614,8 +671,7 @@ function RunDetail({
<blockquote>{detail.fragment.content}</blockquote>
) : (
<div className="redacted-content">
内容已隔离 · {detail.fragment.content_length} 字 · 指纹{" "}
{detail.fragment.content_sha256}
内容已隔离 · {detail.fragment.content_length} 字
</div>
)}
<small>{detail.privacy.reason}</small>
@@ -636,6 +692,19 @@ function RunDetail({
</article>
))}
</section>
{detail.run.status === "error" && (
<button
className="retry-run"
onClick={() => void retry()}
disabled={retrying || requeued}
>
{requeued
? "已经重新交给策展者"
: retrying
? "正在重新交付…"
: "重新整理这条记录"}
</button>
)}
<footer className="reasoning-boundary">
不保存模型隐藏思维链;这里展示的是输入、工具行为、用量、校验与最终结构化产物。
</footer>
+9
View File
@@ -12,12 +12,17 @@ export type User = {
};
export type Snapshot = {
title: string | null;
summary: string | null;
maturity_ai: number;
maturity_override: number | null;
confidence: number | null;
motion: string;
position: string;
tension: string;
trajectory: string;
possible_moves: string[];
change_kind: "analysis" | "manual_calibration" | "legacy_partial";
created_at: string;
};
@@ -226,6 +231,10 @@ export const api = {
request<{ items: AgentRun[] }>(`/api/admin/runs?limit=${limit}`),
adminRun: (id: string) =>
request<AgentRunDetail>(`/api/admin/runs/${id}`),
retryAdminRun: (id: string) =>
request<{ queued: boolean }>(`/api/admin/runs/${id}/retry`, {
method: "POST",
}),
adminAudit: (limit = 80) =>
request<{ items: AuditEvent[] }>(`/api/admin/audit?limit=${limit}`),
};
+57 -3
View File
@@ -885,12 +885,18 @@ summary:focus-visible {
color: var(--warm);
}
.idea-history,
.evidence {
margin-top: 50px;
border-top: 1px solid var(--line);
color: var(--muted);
}
.idea-history + .evidence {
margin-top: 0;
}
.idea-history summary,
.evidence summary {
padding: 18px 0;
cursor: pointer;
@@ -898,12 +904,44 @@ summary:focus-visible {
letter-spacing: 0.05em;
}
.idea-history article {
display: grid;
grid-template-columns: 92px 1fr;
gap: 4px 14px;
padding: 14px 0;
border-bottom: 1px solid rgba(23, 37, 31, 0.07);
}
.idea-history time,
.evidence time {
color: #92958f;
font-size: 9px;
}
.idea-history strong {
color: var(--ink);
font-size: 12px;
font-weight: 500;
}
.idea-history p {
grid-column: 2;
margin: 2px 0 0;
color: #626b66;
font-size: 11px;
line-height: 1.7;
}
.evidence article {
padding: 13px 0;
border-bottom: 1px solid rgba(23, 37, 31, 0.07);
}
.evidence blockquote {
margin: 0;
padding: 15px 0;
margin: 5px 0 0;
padding: 0;
color: #4e5752;
font: 400 14px/1.85 "Songti SC", "STSong", serif;
border-bottom: 1px solid rgba(23, 37, 31, 0.07);
}
.admin-view {
@@ -1411,6 +1449,22 @@ summary:focus-visible {
margin: 0 !important;
}
.retry-run {
margin-top: 28px;
padding: 9px 13px;
border: 1px solid rgba(153, 104, 72, 0.28);
border-radius: 4px;
background: transparent;
color: var(--warm);
cursor: pointer;
font-size: 10px;
}
.retry-run:disabled {
cursor: default;
opacity: 0.6;
}
.reasoning-boundary {
margin-top: 45px;
padding-top: 16px;