feat(coding): add conversation archive and first-message titles
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
} from '../../shared/coding-conversation-reducer';
|
||||
import { AppError } from '@/lib/error-model';
|
||||
import { queueCodingConversationSessionSync } from '@/lib/agent-session-sync';
|
||||
import { codingWorkspaceStore } from './coding-workspace';
|
||||
import {
|
||||
getCodingConversationSnapshot,
|
||||
openCodingConversationEvents,
|
||||
@@ -660,8 +661,20 @@ export function createCodingConversationStore(
|
||||
set({ connectionState: 'error', globalError: 'Conversation 事件更新无法读取。' });
|
||||
}
|
||||
});
|
||||
source.addEventListener('conversation.metadata-changed', (event) => {
|
||||
if (eventSource !== source) return;
|
||||
try {
|
||||
const metadata = parse<{ projectId: string }>(event);
|
||||
if (typeof metadata.projectId === 'string') {
|
||||
void codingWorkspaceStore.getState().refreshConversations(metadata.projectId).catch(() => undefined);
|
||||
}
|
||||
} catch { /* Reconnection refreshes authoritative metadata. */ }
|
||||
});
|
||||
source.onopen = () => {
|
||||
if (eventSource === source) set({ connectionState: 'live', globalError: null });
|
||||
if (eventSource !== source) return;
|
||||
set({ connectionState: 'live', globalError: null });
|
||||
const workspace = codingWorkspaceStore.getState();
|
||||
if (workspace.activeProjectId) void workspace.refreshConversations(workspace.activeProjectId).catch(() => undefined);
|
||||
};
|
||||
source.onerror = () => {
|
||||
if (eventSource === source) {
|
||||
|
||||
@@ -61,6 +61,7 @@ export interface CodingWorkspaceState {
|
||||
conversationErrorsByProjectId: Record<string, Record<string, string>>;
|
||||
creatingAgentIds: Record<string, true>;
|
||||
load(): Promise<void>;
|
||||
refreshConversations(projectId: string): Promise<void>;
|
||||
openProject(projectPath: string): Promise<CodingProjectSummary>;
|
||||
createProject(input: {
|
||||
projectPath?: string;
|
||||
@@ -139,6 +140,9 @@ export function createCodingWorkspaceStore(
|
||||
const conversationFlights = new Map<string, Promise<CodingConversationMetadata>>();
|
||||
let loadFlight: Promise<void> | null = null;
|
||||
let loadGeneration = 0;
|
||||
let metadataRevision = 0;
|
||||
let metadataTail = Promise.resolve();
|
||||
const refreshFlights = new Map<string, { dirty: boolean; promise: Promise<void> }>();
|
||||
|
||||
return createStore<CodingWorkspaceState>((set, get) => ({
|
||||
projects: [],
|
||||
@@ -152,6 +156,30 @@ export function createCodingWorkspaceStore(
|
||||
conversationErrorsByProjectId: {},
|
||||
creatingAgentIds: {},
|
||||
|
||||
async refreshConversations(projectId) {
|
||||
if (get().activeProjectId !== projectId) return;
|
||||
const existing = refreshFlights.get(projectId);
|
||||
if (existing) {
|
||||
existing.dirty = true;
|
||||
return await existing.promise;
|
||||
}
|
||||
const refresh = { dirty: true, promise: Promise.resolve() };
|
||||
refresh.promise = (async () => {
|
||||
while (refresh.dirty && get().activeProjectId === projectId) {
|
||||
refresh.dirty = false;
|
||||
await metadataTail;
|
||||
const revision = metadataRevision;
|
||||
const generation = loadGeneration;
|
||||
const conversations = await deps.listConversations(projectId);
|
||||
if (get().activeProjectId !== projectId || generation !== loadGeneration) return;
|
||||
if (revision !== metadataRevision) { refresh.dirty = true; continue; }
|
||||
if (!refresh.dirty) set({ conversations });
|
||||
}
|
||||
})().finally(() => { refreshFlights.delete(projectId); });
|
||||
refreshFlights.set(projectId, refresh);
|
||||
return await refresh.promise;
|
||||
},
|
||||
|
||||
async load() {
|
||||
if (loadFlight) return await loadFlight;
|
||||
const generation = ++loadGeneration;
|
||||
@@ -175,6 +203,8 @@ export function createCodingWorkspaceStore(
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (get().activeProjectId === activeProject.id) await metadataTail;
|
||||
const revision = metadataRevision;
|
||||
const [snapshot, conversations] = await Promise.all([
|
||||
deps.getConfig(activeProject.id),
|
||||
deps.listConversations(activeProject.id),
|
||||
@@ -186,11 +216,13 @@ export function createCodingWorkspaceStore(
|
||||
activeProjectId: activeProject.id,
|
||||
activeProject: snapshot.project,
|
||||
config: snapshot.config,
|
||||
conversations,
|
||||
conversations: revision === metadataRevision || get().activeProjectId !== activeProject.id
|
||||
? conversations : get().conversations,
|
||||
selectedAgentId: currentAgentId ?? firstEnabledAgent(snapshot.config)?.id ?? null,
|
||||
loadState: 'ready',
|
||||
error: null,
|
||||
});
|
||||
if (revision !== metadataRevision) void get().refreshConversations(activeProject.id).catch(() => undefined);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (generation !== loadGeneration) return;
|
||||
@@ -259,6 +291,7 @@ export function createCodingWorkspaceStore(
|
||||
agentId,
|
||||
title: '新对话',
|
||||
}).then((conversation) => {
|
||||
metadataRevision += 1;
|
||||
if (get().activeProjectId !== state.activeProject?.id) return conversation;
|
||||
set((current) => ({
|
||||
conversations: [
|
||||
@@ -285,6 +318,9 @@ export function createCodingWorkspaceStore(
|
||||
async patchConversation(conversationId, patch) {
|
||||
const sourceProjectId = get().activeProjectId;
|
||||
if (!sourceProjectId) throw new Error('当前没有可用的项目。');
|
||||
metadataRevision += 1;
|
||||
const flight = metadataTail.then(() => deps.patchConversation(conversationId, patch));
|
||||
metadataTail = flight.then(() => undefined, () => undefined);
|
||||
set((current) => ({
|
||||
conversationErrorsByProjectId: withConversationError(
|
||||
current.conversationErrorsByProjectId,
|
||||
@@ -294,10 +330,11 @@ export function createCodingWorkspaceStore(
|
||||
),
|
||||
}));
|
||||
try {
|
||||
const conversation = await deps.patchConversation(conversationId, patch);
|
||||
const conversation = await flight;
|
||||
get().upsertConversation(sourceProjectId, conversation);
|
||||
return conversation;
|
||||
} catch (error) {
|
||||
metadataRevision += 1;
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
set((current) => ({
|
||||
conversationErrorsByProjectId: withConversationError(
|
||||
@@ -312,6 +349,7 @@ export function createCodingWorkspaceStore(
|
||||
},
|
||||
|
||||
upsertConversation(projectId, conversation) {
|
||||
metadataRevision += 1;
|
||||
if (get().activeProjectId !== projectId) return;
|
||||
set((current) => ({
|
||||
conversations: [
|
||||
|
||||
Reference in New Issue
Block a user