From c11307aa671798be0c6dc906bdbaeb7fee74a1a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Sat, 27 Jun 2026 13:12:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai=E5=86=99=E4=BD=9C):=20=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=E8=AE=B0=E5=BD=95=E5=8F=AF=E7=BB=A7=E7=BB=AD=E6=9C=AA?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=9A=84=E4=BC=9A=E8=AF=9D(=E4=BB=8Echeckpoi?= =?UTF-8?q?nt=E7=BB=AD=E8=B7=91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 历史列表里 in_progress/draft_ready/review_ready 记录加绿色▶️「继续未完成」按钮 (点标题亦可);done/error/background 不可继续。多 worker 安全——与4个确认卡同机制, 全基于共享 checkpoint(gateway GET /threads/{id}/state + /history 读,任意 worker)。 链路:continueWriting(sessionId) → closeHistory + 设活跃 sessionId → useStream fetchStateHistory 拉该 thread checkpoint 进 stream.values → host rehydrate effect (continueTargetRef 守卫,每 thread 一次)用 buildRehydrateSnapshot 重建已完成阶段; status=awaiting_* → 显示确认卡等用户操作,否则 submit({command:{}}) 从 checkpoint 续跑当前阶段。重新加回 buildRehydrateSnapshot + rehydrate action。 入口在侧栏 WritingHistoryItem。刷新=重置不受影响(这是显式点击,非自动重连)。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../components/ai-writing/ResearchSidebar.tsx | 42 ++++++++++--- .../open-canvas/contexts/AIWritingContext.tsx | 63 ++++++++++++++++++- .../open-canvas/hooks/useAIWritingStream.ts | 40 ++++++++++++ 3 files changed, 135 insertions(+), 10 deletions(-) diff --git a/frontend-web/src/open-canvas/components/ai-writing/ResearchSidebar.tsx b/frontend-web/src/open-canvas/components/ai-writing/ResearchSidebar.tsx index 73627792..f43106b7 100644 --- a/frontend-web/src/open-canvas/components/ai-writing/ResearchSidebar.tsx +++ b/frontend-web/src/open-canvas/components/ai-writing/ResearchSidebar.tsx @@ -12,6 +12,7 @@ import { Newspaper, PanelLeftClose, Pencil, + Play, Plus, RefreshCw, Search, @@ -109,12 +110,17 @@ const WRITING_STATUS_PILL: Record void; onShowProgress?: () => void; + /** 继续这条未完成的会话(从 checkpoint 续跑当前阶段 / 显示确认卡)。 */ + onContinue?: () => void; onDelete: () => void; onRename: (title: string) => void; }) { @@ -129,8 +137,10 @@ function WritingHistoryItem({ const [value, setValue] = useState(session.title); const inputRef = useRef(null); const isBackground = session.status === "background"; - // 「挂起中」点击看进度(流程图弹窗),其余打开历史回看。 - const open = isBackground && onShowProgress ? onShowProgress : onSelect; + const canContinue = !!onContinue && RESUMABLE_STATUSES.has(session.status); + // 行/标题主点击:未完成→继续续跑;挂起中→看进度弹窗;其余(完成/出错)→只读历史回看。 + const primary = isBackground && onShowProgress ? onShowProgress : (canContinue ? onContinue! : onSelect); + const primaryTitle = isBackground ? "查看进度" : canContinue ? "继续未完成" : "打开历史"; const pill = WRITING_STATUS_PILL[session.status] ?? { label: session.status, className: "text-slate-400" }; useEffect(() => { @@ -168,9 +178,9 @@ function WritingHistoryItem({ ) : ( + )}