diff --git a/frontend-web/src/open-canvas/components/ai-writing/AIWritingPanel.tsx b/frontend-web/src/open-canvas/components/ai-writing/AIWritingPanel.tsx index 9439c133..e38984ed 100644 --- a/frontend-web/src/open-canvas/components/ai-writing/AIWritingPanel.tsx +++ b/frontend-web/src/open-canvas/components/ai-writing/AIWritingPanel.tsx @@ -91,7 +91,7 @@ export function AIWritingPanel({ const { state, startWriting, submitIntervention, pauseWriting, resumeWriting, reset } = useAIWriting(); const { historySession, isHistoryMode, openHistory, closeHistory } = useAIWritingHistory(); const writingForm = useWritingFormOptional(); - const { sessions, loading: sessionsLoading, refresh, rename, remove } = useAIWritingSessions(); + const { sessions, loading: sessionsLoading, refresh, rename, remove, markBackground, clearBackground } = useAIWritingSessions(); const { articleTypes, refresh: refreshArticleTypes } = useArticleTypes(); useEffect(() => { refreshArticleTypes(); }, [refreshArticleTypes]); @@ -207,24 +207,31 @@ export function AIWritingPanel({ }, [closeHistory, reset]); // ── 后台挂起:把当前写作转后台,后端自动驱动到定稿(离页/刷新/切号不打断) ── - const handleBackgroundSuspend = useCallback(async () => { + // 与「多智能体会商」一致:点击后**立刻**回到初始页面,挂起请求 fire-and-forget; + // 历史下拉靠乐观标记马上显示「挂起中」,不等后端确认。 + const handleBackgroundSuspend = useCallback(() => { const sid = state.sessionId; if (!sid) { toast.error('当前没有可挂起的写作会话'); return; } - try { - await suspendAIWritingToBackground(sid); - } catch (e) { - toast.error(e instanceof Error ? e.message : '后台挂起失败'); - return; - } - toast.success('已转入后台自动完成,稍后可在「历史记录」查看进度与结果'); - // reset() 会 stop 客户端 SSE 订阅并回到 idle —— 后端驱动器接管续跑,不冲突。 + // 1) 立刻:乐观标记 + 重置 UI 回到 idle(reset() 会 stop 客户端 SSE 订阅, + // 后端驱动器接管续跑,不冲突)。 + markBackground(sid); reset(); - // 刷新历史列表,让「挂起中」徽标立刻出现。 - refresh(); - }, [state.sessionId, reset, refresh]); + toast.success('已转入后台自动完成,稍后可在「历史记录」查看进度与结果'); + // 2) 后台:fire-and-forget 通知后端拉起驱动;完成后刷新真实状态并清除乐观标记。 + void (async () => { + try { + await suspendAIWritingToBackground(sid); + } catch (e) { + toast.error(e instanceof Error ? e.message : '后台挂起失败'); + } finally { + await refresh(); + clearBackground(sid); + } + })(); + }, [state.sessionId, reset, refresh, markBackground, clearBackground]); // 离页 / 刷新 / 关标签页时,若写作仍在进行中则自动转后台(与圆桌「自动挂起」一致)。 // 用 ref 持有最新的「是否在跑 + 会话 id」,让卸载/卸载前回调读到即时值。 diff --git a/frontend-web/src/open-canvas/hooks/useAIWritingSessions.ts b/frontend-web/src/open-canvas/hooks/useAIWritingSessions.ts index ffa7ae6d..ac98220f 100644 --- a/frontend-web/src/open-canvas/hooks/useAIWritingSessions.ts +++ b/frontend-web/src/open-canvas/hooks/useAIWritingSessions.ts @@ -1,4 +1,4 @@ -import { useCallback, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import { listAIWritingSessions, @@ -10,6 +10,27 @@ import type { AIWritingSessionSummary } from '../api/ai-writing-sessions'; export function useAIWritingSessions() { const [sessions, setSessions] = useState([]); const [loading, setLoading] = useState(false); + // 「后台挂起」乐观标记:点击挂起后立刻让历史下拉显示「挂起中」,不必等后端确认。 + // 后端状态(status='background')刷新到位后由调用方 clearBackground 清除。 + const [pendingBackground, setPendingBackground] = useState>(() => new Set()); + + const markBackground = useCallback((id: string) => { + setPendingBackground((prev) => { + if (prev.has(id)) return prev; + const next = new Set(prev); + next.add(id); + return next; + }); + }, []); + + const clearBackground = useCallback((id: string) => { + setPendingBackground((prev) => { + if (!prev.has(id)) return prev; + const next = new Set(prev); + next.delete(id); + return next; + }); + }, []); const refresh = useCallback(async () => { setLoading(true); @@ -41,5 +62,18 @@ export function useAIWritingSessions() { } }, []); - return { sessions, loading, refresh, rename, remove }; + // 把乐观「挂起中」标记叠加到真实列表上:已在列表里的会话立刻显示 background。 + const displaySessions = useMemo( + () => + pendingBackground.size === 0 + ? sessions + : sessions.map((s) => + pendingBackground.has(s.id) && s.status !== 'done' + ? { ...s, status: 'background' } + : s, + ), + [sessions, pendingBackground], + ); + + return { sessions: displaySessions, loading, refresh, rename, remove, markBackground, clearBackground }; }