From 37ae7508b8f0582a2ac73d06b6062756a831614f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Thu, 17 Sep 2026 16:39:24 +0800 Subject: [PATCH] =?UTF-8?q?App=20=E8=81=8A=E5=A4=A9=E5=8E=86=E5=8F=B2?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E6=94=B9=E8=B5=B0=E8=BF=9E=E6=8B=89=E5=BE=AA?= =?UTF-8?q?=E7=8E=AF=20=E9=A6=96=E5=B1=8F=E4=B8=8E=E3=80=8C=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E6=9B=B4=E6=97=A9=E3=80=8D=E5=85=B1=E7=94=A8=20readDi?= =?UTF-8?q?rectHistoryPages=EF=BC=8C=E4=B8=8D=E5=86=8D=E5=90=84=E8=87=AA?= =?UTF-8?q?=E5=8D=95=E5=8F=91=E4=B8=80=E9=A1=B5=20=E9=A6=96=E5=B1=8F?= =?UTF-8?q?=E5=9F=BA=E7=BA=BF=E5=8F=96=E7=A9=BA=E5=B9=B6=E5=9C=A8=E5=AE=88?= =?UTF-8?q?=E5=8D=AB=E5=90=8E=E4=B8=80=E6=AC=A1=E6=80=A7=E5=B9=B6=E5=85=A5?= =?UTF-8?q?=E8=81=8A=E5=A4=A9=20reducer=EF=BC=8C=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E4=BB=8D=E6=8C=89=E5=8E=9F=E6=9C=89=E8=AF=AD=E4=B9=89=E6=8A=9B?= =?UTF-8?q?=E5=87=BA=20=E3=80=8C=E6=98=BE=E7=A4=BA=E6=9B=B4=E6=97=A9?= =?UTF-8?q?=E3=80=8D=E6=8C=89=E5=BD=93=E5=89=8D=E8=A7=86=E5=9B=BE=E6=9D=A1?= =?UTF-8?q?=E7=9B=AE=E5=88=A4=E6=96=AD=E6=96=B0=E5=9B=9E=E5=90=88=EF=BC=8C?= =?UTF-8?q?=E5=8F=96=E5=88=B0=E5=A4=9A=E9=A1=B5=E4=B8=80=E6=AC=A1=E6=80=A7?= =?UTF-8?q?=E5=B9=B6=E5=85=A5=E5=B9=B6=E6=8E=A8=E8=BF=9B=E9=94=9A=E7=82=B9?= =?UTF-8?q?=20=E8=BF=9E=E6=8B=89=E4=B8=AD=E9=80=94=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E4=BF=9D=E7=95=99=E5=B7=B2=E5=8F=96=E5=88=B0=E7=9A=84=E9=A1=B5?= =?UTF-8?q?=EF=BC=8C=E6=8A=A5=E9=94=99=E7=BB=9F=E4=B8=80=E8=B5=B0=E5=90=8C?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=E5=87=BA=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ai-game-creator-shell/src/App.tsx | 90 +++++++++++++++++--------- 1 file changed, 60 insertions(+), 30 deletions(-) diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index e2a652ee9..5a4ae6b75 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -232,17 +232,19 @@ import { } from './features/project-workspace/chatComposerQueue'; import { DeveloperProjectPanels } from './features/project-workspace/DeveloperProjectPanels'; import { DeveloperRuntimePanels } from './features/project-workspace/DeveloperRuntimePanels'; +import { readDirectHistoryPages } from './features/project-workspace/directHistoryPaging'; import { applyDirectThreadConsumeResult, type DirectThreadChatState, emptyDirectThreadChatState, - mergeDirectThreadHistorySlice, + mergeDirectHistoryItems, resolveDirectThreadBootstrap, selectDirectChatEntries, } from './features/project-workspace/directThreadChat'; import { type DirectThreadConsumeResult, type DirectThreadHistorySlice, + type DirectThreadItem, type DirectThreadSubscriptionBootstrap, } from './features/project-workspace/directThreadEvents'; import type { DirectCodexUserContentPart } from './features/project-workspace/generated'; @@ -3491,21 +3493,38 @@ export function App({ let runtimeError = ''; let loadedDirectHistoryHasMore = false; let loadedDirectHistoryFirstItemId: string | null = null; - // 首屏切片先暂存,待下方 staleness 守卫通过后再并入聊天 reducer: + // 首屏连拉到的条目先暂存,待下方 staleness 守卫通过后再并入聊天 reducer: // 迟到的切片属于已经切走的项目,不能在守卫之前就写进全局聊天状态。 - let loadedDirectHistorySlice: DirectThreadHistorySlice | null = null; + let loadedDirectHistoryItems: DirectThreadItem[] = []; const projectConversation = directCodexProductRuntime ? (() => { - return invoke( - 'read_direct_project_history_slice', - { - projectPath: nextProjectPath, - limit: CONVERSATION_INITIAL_VISIBLE_COUNT, - }, - ).then((slice) => { - loadedDirectHistoryHasMore = slice.hasMore; - loadedDirectHistoryFirstItemId = slice.firstItemId; - loadedDirectHistorySlice = slice; + // 首屏铺的就是这份视图的基线,"新回合"比较没有意义:判据退化为"这一页得有能渲染 + // 的条目",所以基线取空,免得拿上一份对话的残留状态去比。首屏失败仍按原有语义 + // 抛出,交给调用方的 catch 处理。 + return readDirectHistoryPages({ + existingEntries: [], + beforeItemId: null, + readSlice: (beforeItemId) => + invoke( + 'read_direct_project_history_slice', + beforeItemId + ? { + projectPath: nextProjectPath, + beforeItemId, + limit: CONVERSATION_INITIAL_VISIBLE_COUNT, + } + : { + projectPath: nextProjectPath, + limit: CONVERSATION_INITIAL_VISIBLE_COUNT, + }, + ), + }).then((pages) => { + if (pages.error) { + throw pages.error; + } + loadedDirectHistoryHasMore = pages.hasMore; + loadedDirectHistoryFirstItemId = pages.firstItemId; + loadedDirectHistoryItems = pages.items; return { path: nextProjectPath, agentId: null, @@ -3579,12 +3598,10 @@ export function App({ if (directCodexProductRuntime) { setDirectHistoryHasMore(loadedDirectHistoryHasMore); directHistoryOldestItemIdRef.current = loadedDirectHistoryFirstItemId; - if (loadedDirectHistorySlice) { - const slice = loadedDirectHistorySlice; + if (loadedDirectHistoryItems.length > 0) { + const items = loadedDirectHistoryItems; // 首屏历史进聊天 reducer;`messages` 在 DirectProject 下只剩运行期本地消息。 - setDirectThreadChat((state) => - mergeDirectThreadHistorySlice(state, slice), - ); + setDirectThreadChat((state) => mergeDirectHistoryItems(state, items)); } } setMessages((current) => { @@ -11807,24 +11824,37 @@ export function App({ if (invoke && projectPath && !directHistoryLoadingRef.current) { directHistoryLoadingRef.current = true; try { - const slice = await invoke( - 'read_direct_project_history_slice', - { - projectPath, - beforeItemId: directHistoryOldestItemIdRef.current, - limit: CONVERSATION_VISIBLE_STEP, - }, - ); + // 一次点击连拉,直到出现新回合或取不动为止:一屏全是工具卡片 / 思考文本时, + // 单发一页会让用户点了"显示更早"却看不到任何变化。 + const pages = await readDirectHistoryPages({ + existingEntries: selectDirectChatEntries(directThreadChat), + beforeItemId: directHistoryOldestItemIdRef.current, + readSlice: (beforeItemId) => + invoke( + 'read_direct_project_history_slice', + beforeItemId + ? { + projectPath, + beforeItemId, + limit: CONVERSATION_VISIBLE_STEP, + } + : { projectPath, limit: CONVERSATION_VISIBLE_STEP }, + ), + }); if (localProjectPathRef.current !== projectPath) { return; } - // 更早的一屏历史进聊天 reducer:与运行态同形、同身份,重复读取只补不重。 + // 连拉到的页一次性进聊天 reducer:与运行态同形、同身份,重复读取只补不重。 setDirectThreadChat((state) => - mergeDirectThreadHistorySlice(state, slice), + mergeDirectHistoryItems(state, pages.items), ); - setDirectHistoryHasMore(slice.hasMore); + setDirectHistoryHasMore(pages.hasMore); directHistoryOldestItemIdRef.current = - slice.firstItemId ?? directHistoryOldestItemIdRef.current; + pages.firstItemId ?? directHistoryOldestItemIdRef.current; + if (pages.error) { + // 已经取到的页照常进视图,失败只走下面同一个报错出口。 + throw pages.error; + } } catch (error) { setWorkspaceStatus( `读取更早的对话历史失败:${error instanceof Error ? error.message : String(error)}`,