agent-Specialization/static/src/stores/conversation.ts

269 lines
9.0 KiB
TypeScript

import { defineStore } from 'pinia';
export interface ConversationSummary {
id: string;
title: string;
updated_at: string | number;
total_messages?: number;
total_tools?: number;
}
export interface WorkspaceConversationGroup {
workspaceId: string;
conversations: ConversationSummary[];
loading: boolean;
hasMore: boolean;
loadingMore: boolean;
offset: number;
visibleOffset: number;
visibleLimit: number;
bufferLimit: number;
fetchLimit: number;
expanded: boolean;
}
interface ConversationState {
conversations: ConversationSummary[];
searchResults: ConversationSummary[];
conversationInsertAnimations: Record<string, 'create' | 'duplicate' | 'duplicateSource'>;
conversationListAnimationMode: 'idle' | 'create' | 'duplicate' | 'delete';
pendingDeletingConversationIds: string[];
conversationsLoading: boolean;
hasMoreConversations: boolean;
loadingMoreConversations: boolean;
currentConversationId: string | null;
currentConversationTitle: string;
searchQuery: string;
searchTimer: ReturnType<typeof setTimeout> | null;
searchActive: boolean;
searchInProgress: boolean;
searchMoreAvailable: boolean;
searchOffset: number;
searchTotal: number;
conversationsOffset: number;
conversationsLimit: number;
runningWorkspaceTasks: any[];
acknowledgedCompletedTaskIds: string[];
workspaceGroups: WorkspaceConversationGroup[];
multiAgentMode: boolean;
}
export const useConversationStore = defineStore('conversation', {
state: (): ConversationState => ({
conversations: [],
searchResults: [],
conversationInsertAnimations: {},
conversationListAnimationMode: 'idle',
pendingDeletingConversationIds: [],
conversationsLoading: false,
hasMoreConversations: false,
loadingMoreConversations: false,
currentConversationId: null,
currentConversationTitle: '',
searchQuery: '',
searchTimer: null,
searchActive: false,
searchInProgress: false,
searchMoreAvailable: false,
searchOffset: 0,
searchTotal: 0,
conversationsOffset: 0,
conversationsLimit: 20,
runningWorkspaceTasks: [],
acknowledgedCompletedTaskIds: [],
workspaceGroups: [],
multiAgentMode: false
}),
actions: {
resetConversations() {
this.conversations = [];
this.searchResults = [];
this.conversationInsertAnimations = {};
this.conversationListAnimationMode = 'idle';
this.pendingDeletingConversationIds = [];
this.searchActive = false;
this.searchInProgress = false;
this.searchMoreAvailable = false;
this.searchOffset = 0;
this.searchTotal = 0;
this.hasMoreConversations = false;
this.loadingMoreConversations = false;
this.conversationsOffset = 0;
this.runningWorkspaceTasks = [];
this.acknowledgedCompletedTaskIds = [];
},
cancelSearchTimer() {
if (this.searchTimer) {
clearTimeout(this.searchTimer);
this.searchTimer = null;
}
},
setWorkspaceGroups(groups: WorkspaceConversationGroup[]) {
this.workspaceGroups = groups;
},
setWorkspaceGroupExpanded(workspaceId: string, expanded: boolean) {
const group = this.workspaceGroups.find((g) => g.workspaceId === workspaceId);
if (group) {
group.expanded = expanded;
}
},
setWorkspaceGroupConversations(workspaceId: string, conversations: ConversationSummary[]) {
const group = this.workspaceGroups.find((g) => g.workspaceId === workspaceId);
if (group) {
group.conversations = conversations;
}
},
appendWorkspaceGroupConversations(workspaceId: string, conversations: ConversationSummary[]) {
const group = this.workspaceGroups.find((g) => g.workspaceId === workspaceId);
if (group) {
group.conversations.push(...conversations);
}
},
setWorkspaceGroupLoading(workspaceId: string, loading: boolean) {
const group = this.workspaceGroups.find((g) => g.workspaceId === workspaceId);
if (group) {
group.loading = loading;
}
},
setWorkspaceGroupLoadingMore(workspaceId: string, loadingMore: boolean) {
const group = this.workspaceGroups.find((g) => g.workspaceId === workspaceId);
if (group) {
group.loadingMore = loadingMore;
}
},
setWorkspaceGroupHasMore(workspaceId: string, hasMore: boolean) {
const group = this.workspaceGroups.find((g) => g.workspaceId === workspaceId);
if (group) {
group.hasMore = hasMore;
}
},
setWorkspaceGroupOffset(workspaceId: string, offset: number) {
const group = this.workspaceGroups.find((g) => g.workspaceId === workspaceId);
if (group) {
group.offset = offset;
}
},
setWorkspaceGroupVisibleOffset(workspaceId: string, visibleOffset: number) {
const group = this.workspaceGroups.find((g) => g.workspaceId === workspaceId);
if (group) {
group.visibleOffset = Math.max(0, visibleOffset);
}
},
ensureWorkspaceGroup(workspaceId: string) {
if (!workspaceId) return;
const exists = this.workspaceGroups.some((g) => g.workspaceId === workspaceId);
if (!exists) {
this.workspaceGroups.push({
workspaceId,
conversations: [],
loading: false,
hasMore: false,
loadingMore: false,
offset: 0,
visibleOffset: 0,
visibleLimit: 5,
bufferLimit: 20,
fetchLimit: 25,
expanded: true
});
}
},
async loadWorkspaceConversations(
workspaceId: string,
{ reset = false, refresh = false } = {}
) {
if (!workspaceId) return;
console.log('[sidebar-debug] loadWorkspaceConversations', {
workspaceId,
reset,
refresh
});
let index = this.workspaceGroups.findIndex((g) => g.workspaceId === workspaceId);
if (index === -1) {
this.ensureWorkspaceGroup(workspaceId);
index = this.workspaceGroups.length - 1;
}
const group = this.workspaceGroups[index];
if (reset) {
group.conversations = [];
group.offset = 0;
group.visibleOffset = 0;
group.hasMore = false;
}
if (group.loading || group.loadingMore) return;
const fetchOffset = refresh ? 0 : group.offset;
group.loading = true;
try {
const maParam = this.multiAgentMode ? '&multi_agent_mode=1' : '&multi_agent_mode=0';
const response = await fetch(
`/api/conversations?workspace_id=${encodeURIComponent(workspaceId)}&limit=${group.fetchLimit}&offset=${fetchOffset}${maParam}`
);
const data = await response.json();
if (data.success) {
const items = (data.data?.conversations || []).map((conv: any) => ({
id: conv.id,
title: conv.title,
updated_at: conv.updated_at,
total_messages: conv.total_messages,
total_tools: conv.total_tools
}));
if (refresh) {
const tail = group.conversations.slice(items.length);
group.conversations = [...items, ...tail];
} else if (fetchOffset === 0) {
group.conversations = items;
} else {
group.conversations.push(...items);
}
group.hasMore = !!data.data?.has_more;
if (!refresh) {
group.offset = fetchOffset + items.length;
}
} else {
console.error('加载工作区对话失败:', data.error);
}
} catch (error) {
console.error('加载工作区对话异常:', error);
} finally {
group.loading = false;
}
},
async loadMoreWorkspaceConversations(workspaceId: string) {
const index = this.workspaceGroups.findIndex((g) => g.workspaceId === workspaceId);
if (index === -1) return;
const group = this.workspaceGroups[index];
if (group.loadingMore) return;
const hasHidden = group.conversations.length > group.visibleLimit;
if (!hasHidden && !group.hasMore) return;
group.loadingMore = true;
// 先尝试从后端再加载 20 条作为新的缓冲
if (group.hasMore) {
try {
const fetchOffset = group.conversations.length;
const response = await fetch(
`/api/conversations?workspace_id=${encodeURIComponent(workspaceId)}&limit=${group.bufferLimit}&offset=${fetchOffset}`
);
const data = await response.json();
if (data.success) {
const items = (data.data?.conversations || []).map((conv: any) => ({
id: conv.id,
title: conv.title,
updated_at: conv.updated_at,
total_messages: conv.total_messages,
total_tools: conv.total_tools
}));
group.conversations.push(...items);
group.hasMore = !!data.data?.has_more;
}
} catch (error) {
console.error('加载更多工作区对话异常:', error);
}
}
// 把可见区扩大 20 条,让用户直接看到更多对话
group.visibleLimit += group.bufferLimit;
group.loadingMore = false;
}
}
});