agent-Specialization/static/src/stores/conversation.ts
JOJO 171833e4d1 feat(quickdock): 新增对话区右侧快捷窗口(待办/子智能体/后台指令/文件记录)
- 四窗口固定顺序占位布局,空时收起到 0 宽;动画区分「运行中新出现」与「切换/加载」
- 文件记录:edit/write 埋点写入对话 metadata.edited_files,预览走既有 /api/file/content
- 详情面板:lastDetail/lastStatus 快照、首次填充静态+瞬间到底、终态四色分类
- 数据通道全部走 REST 轮询(带 conversation_id 对话级隔离),含 demo 与设计文档
2026-07-22 00:52:36 +08:00

273 lines
9.2 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: {
/** 当前对话 id 同步(由 app watcher 在 this.currentConversationId 变化时写入) */
setCurrentConversationId(id: string | null) {
this.currentConversationId = id;
},
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;
}
}
});