From 72ad9b8cdb7820f226a524d3fce921d06d9f49ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Fri, 2 Oct 2026 17:42:14 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=9ADirectProject=20?= =?UTF-8?q?=E8=81=8A=E5=A4=A9=E6=8E=A7=E5=88=B6=E5=99=A8=E6=9A=B4=E9=9C=B2?= =?UTF-8?q?=E6=9B=B4=E6=97=A9=E5=8E=86=E5=8F=B2=E7=9A=84=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E6=80=81=E3=80=81=E5=A4=B1=E8=B4=A5=E6=80=81=E4=B8=8E=E9=87=8D?= =?UTF-8?q?=E8=AF=95=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 控制器新增 historyLoading / historyError 两个可渲染状态,与并发判据用的 ref 同步镜像。 loadEarlierHistory 失败时写入 historyError 并挂起自动加载,成功路径清掉失败记录。 新增 retryEarlierHistory:先解挂起再走同一条加载入口,供内联错误行的重试按钮调用。 加载失败不再写顶部状态行(状态行留给首屏读取失败等其它用途)。 Co-authored-by: Junie --- .../useDirectProjectChatController.ts | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts b/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts index 8f97e9b3e..7f9bc87d5 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts +++ b/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts @@ -137,7 +137,8 @@ export type DirectProjectChatControllerProps = { * * 状态变量归属:reducer 的回合字段、待发消息投影、收口计数与 `history` / `live` 只由 * `directThreadChat.ts` 写;本文件的 `commandInFlight`(唯一入口 `beginCommandInFlight` / - * `endCommandInFlight`,只覆盖 IPC 在飞)、`localMessages`、埋点句柄与分页 ref 只服务发送与展示; + * `endCommandInFlight`,只覆盖 IPC 在飞)、`localMessages`、分页 ref 与更早历史的 + * `historyLoading` / `historyError`(渲染层读的加载行与内联错误行判据)只服务发送与展示; * 界面上的「这一轮在跑吗」只有一个派生入口 `useDirectProjectTurnStatus()`,两态判据与真值表在 * `../conversation/directTurnPresentation.ts` 的 `DirectChatTurnState`,渲染时否定式读 * `state !== 'finished'`、肯定式读 `state === 'running'`(见 `DirectProjectTurn.tsx`)。 @@ -182,8 +183,17 @@ export function useDirectProjectChatController({ */ const pendingTurns = directThread.state.pendingTurns; const [historyHasMore, setHistoryHasMore] = useState(false); - const historyOldestItemIdRef = useRef(null); + /** + * 更早历史的加载态与失败态:渲染层要拿它决定加载行与内联错误行的显隐。 + * + * `historyLoadingRef` 是同一件事的同步副本:并发判据必须在同一次事件处理里立刻生效, + * 而 `setState` 要等下一次渲染,直接用 state 判会放过第二次触发。 + */ + const [historyLoading, setHistoryLoading] = useState(false); + const [historyError, setHistoryError] = useState(null); const historyLoadingRef = useRef(false); + const historyErrorRef = useRef(null); + const historyOldestItemIdRef = useRef(null); const projectPathRef = useRef(projectPath); projectPathRef.current = projectPath; const directTurnRunningRef = useRef(currentTurnRunning); @@ -196,6 +206,10 @@ export function useDirectProjectChatController({ setStatusNotice(''); setLocalMessages([]); setHistoryHasMore(false); + setHistoryLoading(false); + historyLoadingRef.current = false; + historyErrorRef.current = null; + setHistoryError(null); historyOldestItemIdRef.current = null; }, [projectPath]); @@ -649,18 +663,27 @@ export function useDirectProjectChatController({ } } + /** + * 自动加载更早历史(滚动触顶与填充视口共用这一条入口)。 + * + * 失败即挂起:`historyErrorRef` 非空期间这里直接返回,只有手动重试(先清掉错误)或切换 + * 项目才会重新开始。挂起态一定要能渲染出来——原来的常驻按钮删掉之后,内联错误行是用户 + * 唯一还能自己补救的入口。 + */ async function loadEarlierHistory() { const nextProjectPath = projectPath; if ( !enabled || !historyHasMore || !nextProjectPath || - historyLoadingRef.current + historyLoadingRef.current || + historyErrorRef.current ) return; const invoke = resolveTauriInvoke(); if (!invoke) return; historyLoadingRef.current = true; + setHistoryLoading(true); try { const pages = await readDirectHistoryPages({ existingEntries: directEntries, @@ -679,19 +702,27 @@ export function useDirectProjectChatController({ historyOldestItemIdRef.current = pages.firstItemId ?? historyOldestItemIdRef.current; if (pages.error) throw pages.error; + historyErrorRef.current = null; + setHistoryError(null); } catch (error) { // 与加载成功路径同样按项目守卫:切项目后在飞的读取失败不能算到新项目头上。 if (projectPathRef.current !== nextProjectPath) return; - setStatusNotice( - `读取更早的对话历史失败:${ - error instanceof Error ? error.message : String(error) - }`, - ); + const message = error instanceof Error ? error.message : String(error); + historyErrorRef.current = message; + setHistoryError(message); } finally { historyLoadingRef.current = false; + setHistoryLoading(false); } } + /** 手动重试更早历史:先解挂起,再走同一条加载入口。 */ + function retryEarlierHistory() { + historyErrorRef.current = null; + setHistoryError(null); + void loadEarlierHistory(); + } + return { appendLocalMessage, attachmentNotice, @@ -702,8 +733,11 @@ export function useDirectProjectChatController({ directEntries, directTurnRunning: currentTurnRunning, directTurnStartedAt: currentTurnStartedAt, + historyError, historyHasMore, + historyLoading, loadEarlierHistory, + retryEarlierHistory, commandInFlight, localMessages, pendingTurns,