接入DirectProject历史懒加载
进入项目先读取有限历史切片而非完整对话\n按最早item ID向前翻页并保留hasMore状态\n复用同一历史item投影构造聊天记录
This commit is contained in:
@@ -224,6 +224,8 @@ import { DeveloperProjectPanels } from './features/project-workspace/DeveloperPr
|
||||
import { DeveloperRuntimePanels } from './features/project-workspace/DeveloperRuntimePanels';
|
||||
import {
|
||||
type DirectThreadConsumeResult,
|
||||
directThreadHistoryItemsToMessages,
|
||||
type DirectThreadHistorySlice,
|
||||
type DirectThreadSubscriptionBootstrap,
|
||||
emptyDirectThreadReducerState,
|
||||
reduceDirectThreadEvents,
|
||||
@@ -1487,6 +1489,9 @@ export function App({
|
||||
const [conversationVisibleCount, setConversationVisibleCount] = useState(
|
||||
CONVERSATION_INITIAL_VISIBLE_COUNT,
|
||||
);
|
||||
const [directHistoryHasMore, setDirectHistoryHasMore] = useState(false);
|
||||
const directHistoryOldestItemIdRef = useRef<string | null>(null);
|
||||
const directHistoryLoadingRef = useRef(false);
|
||||
const [pendingCommand, setPendingCommand] = useState<PendingCommand | null>(
|
||||
null,
|
||||
);
|
||||
@@ -2725,7 +2730,7 @@ export function App({
|
||||
agentId: null,
|
||||
...(message.messageId ? { messageId: message.messageId } : {}),
|
||||
message: {
|
||||
role: message.role,
|
||||
role: message.role === 'user' ? 'user' : 'assistant',
|
||||
content: message.text,
|
||||
agentId: null,
|
||||
...(typeof message.updatedAt === 'number'
|
||||
@@ -3614,14 +3619,29 @@ export function App({
|
||||
? null
|
||||
: await readProjectSupervisorActiveSession(invoke, nextProjectPath);
|
||||
let runtimeError = '';
|
||||
const projectConversation = await invoke<LocalConversationResult>(
|
||||
directCodexProductRuntime
|
||||
? 'read_direct_project_conversation'
|
||||
: 'read_local_conversation',
|
||||
directCodexProductRuntime
|
||||
? { projectPath: nextProjectPath }
|
||||
: { projectPath: nextProjectPath, agentId: null },
|
||||
);
|
||||
let loadedDirectHistoryHasMore = false;
|
||||
const projectConversation = directCodexProductRuntime
|
||||
? (() => {
|
||||
return invoke<DirectThreadHistorySlice>(
|
||||
'read_direct_project_history_slice',
|
||||
{
|
||||
projectPath: nextProjectPath,
|
||||
limit: CONVERSATION_INITIAL_VISIBLE_COUNT,
|
||||
},
|
||||
).then((slice) => {
|
||||
loadedDirectHistoryHasMore = slice.hasMore;
|
||||
return {
|
||||
path: nextProjectPath,
|
||||
agentId: null,
|
||||
messages: directThreadHistoryItemsToMessages(slice.items),
|
||||
} satisfies LocalConversationResult;
|
||||
});
|
||||
})()
|
||||
: invoke<LocalConversationResult>('read_local_conversation', {
|
||||
projectPath: nextProjectPath,
|
||||
agentId: null,
|
||||
});
|
||||
const resolvedProjectConversation = await projectConversation;
|
||||
let supervisorConversation: LocalConversationResult | null = null;
|
||||
let runtime: AgentRuntimeState | null = null;
|
||||
let runtimeResponseStream: AgentRuntimeResponseStream | null = null;
|
||||
@@ -3658,7 +3678,7 @@ export function App({
|
||||
return;
|
||||
}
|
||||
const conversationMessages = mergeProjectSupervisorConversation(
|
||||
projectConversation.messages,
|
||||
resolvedProjectConversation.messages,
|
||||
supervisorConversation?.messages ?? [],
|
||||
);
|
||||
if (
|
||||
@@ -3679,6 +3699,12 @@ export function App({
|
||||
setProjectSupervisorResponseStream(null);
|
||||
}
|
||||
setProjectSupervisorRuntimeError(runtimeError || resumeError);
|
||||
if (directCodexProductRuntime) {
|
||||
setDirectHistoryHasMore(loadedDirectHistoryHasMore);
|
||||
directHistoryOldestItemIdRef.current =
|
||||
conversationMessages.find((message) => message.messageId)
|
||||
?.messageId ?? null;
|
||||
}
|
||||
setMessages((current) => {
|
||||
const nextConversationMessages = conversationMessages;
|
||||
const hasOnlyDefaultGreeting =
|
||||
@@ -11890,7 +11916,44 @@ export function App({
|
||||
)
|
||||
: null;
|
||||
|
||||
function showEarlierConversationMessages() {
|
||||
async function showEarlierConversationMessages() {
|
||||
if (directCodexProductRuntime && directHistoryHasMore) {
|
||||
const invoke = resolveTauriInvoke();
|
||||
const projectPath = localProject?.projectPath;
|
||||
if (invoke && projectPath && !directHistoryLoadingRef.current) {
|
||||
directHistoryLoadingRef.current = true;
|
||||
try {
|
||||
const slice = await invoke<DirectThreadHistorySlice>(
|
||||
'read_direct_project_history_slice',
|
||||
{
|
||||
projectPath,
|
||||
beforeItemId: directHistoryOldestItemIdRef.current,
|
||||
limit: CONVERSATION_VISIBLE_STEP,
|
||||
},
|
||||
);
|
||||
const older = directThreadHistoryItemsToMessages(slice.items).map(
|
||||
(message) => ({
|
||||
role:
|
||||
message.role === 'user'
|
||||
? ('user' as const)
|
||||
: ('assistant' as const),
|
||||
text: message.content,
|
||||
runtimeOwned: true,
|
||||
messageId: message.messageId,
|
||||
updatedAt: message.updatedAt,
|
||||
}),
|
||||
);
|
||||
setMessages((current) => [...older, ...current]);
|
||||
setDirectHistoryHasMore(slice.hasMore);
|
||||
directHistoryOldestItemIdRef.current =
|
||||
older.find((message) => message.messageId)?.messageId ??
|
||||
directHistoryOldestItemIdRef.current;
|
||||
} finally {
|
||||
directHistoryLoadingRef.current = false;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
setConversationVisibleCount((current) =>
|
||||
Math.min(messages.length, current + CONVERSATION_VISIBLE_STEP),
|
||||
);
|
||||
|
||||
@@ -16,6 +16,47 @@ export type DirectThreadConsumeResult = {
|
||||
events: DirectThreadRawEvent[];
|
||||
};
|
||||
|
||||
export type DirectThreadHistorySlice = {
|
||||
items: unknown[];
|
||||
hasMore: boolean;
|
||||
};
|
||||
|
||||
import type { LocalConversationMessageRecord } from '../../app/types';
|
||||
|
||||
export function directThreadHistoryItemsToMessages(
|
||||
items: unknown[],
|
||||
): LocalConversationMessageRecord[] {
|
||||
return items.flatMap((raw) => {
|
||||
if (!raw || typeof raw !== 'object') return [];
|
||||
const item = raw as Record<string, unknown>;
|
||||
const role = item.role;
|
||||
if (role !== 'user' && role !== 'assistant') return [];
|
||||
const messageRole = role as 'user' | 'assistant';
|
||||
const content = Array.isArray(item.content)
|
||||
? item.content
|
||||
.map((part) =>
|
||||
part && typeof part === 'object' && 'text' in part
|
||||
? (part as { text?: unknown }).text
|
||||
: null,
|
||||
)
|
||||
.filter((text): text is string => typeof text === 'string')
|
||||
.join('')
|
||||
: '';
|
||||
if (!content) return [];
|
||||
const messageId = typeof item.id === 'string' ? item.id : undefined;
|
||||
return [
|
||||
{
|
||||
schemaVersion: 'agc-direct-project-context.v1',
|
||||
role: messageRole,
|
||||
content,
|
||||
agentId: null,
|
||||
messageId,
|
||||
updatedAt: 0,
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
export type DirectThreadReducerState = {
|
||||
lastSeq: number;
|
||||
turnId: string | null;
|
||||
|
||||
Reference in New Issue
Block a user