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)}`,