// @ts-nocheck import { debugLog, traceLog } from '../common'; import { usePersonalizationStore } from '../../../stores/personalization'; import { } from './shared'; export const actionMethods = { promoteConversationToTop(conversationId) { if (!Array.isArray(this.conversations) || !conversationId) { return; } const index = this.conversations.findIndex((conv) => conv && conv.id === conversationId); if (index > 0) { const [selected] = this.conversations.splice(index, 1); this.conversations.unshift(selected); } }, async createNewConversation() { console.log('[DEBUG_AWAITING] ===== createNewConversation ====='); if (this.compressionInProgress || this.compressing) { const confirmed = await this.confirmAction({ title: '压缩进行中', message: '对话正在压缩中,切换对话会导致压缩失败,确认要继续吗?', confirmText: '确认', cancelText: '取消' }); if (!confirmed) { return; } try { if (this.currentConversationId) { await fetch(`/api/conversations/${this.currentConversationId}/compression_cancel`, { method: 'POST' }); } } catch (e) { console.warn('取消压缩失败:', e); } this.compressionInProgress = false; this.compressing = false; this.compressionMode = ''; this.compressionStage = ''; if (this.compressionToastId) { this.uiDismissToast(this.compressionToastId); this.compressionToastId = null; } } debugLog('创建新对话...'); traceLog('createNewConversation:start', { currentConversationId: this.currentConversationId, convCount: Array.isArray(this.conversations) ? this.conversations.length : 'n/a' }); this.logMessageState('createNewConversation:start'); await this.persistComposerDraftNow({ reason: 'create-new-conversation', force: true, keepalive: true }).catch(() => {}); // 应用个性化设置中的默认模型和思考模式 try { const personalizationStore = usePersonalizationStore(); if (personalizationStore.loaded) { const defaultRunMode = personalizationStore.form.default_run_mode; const defaultModel = personalizationStore.form.default_model; if (defaultRunMode) { this.runMode = defaultRunMode; debugLog('应用默认运行模式:', defaultRunMode); } if (defaultModel) { this.currentModelKey = defaultModel; debugLog('应用默认模型:', defaultModel); } // 根据默认运行模式设置思考模式 if (defaultRunMode === 'thinking') { this.thinkingMode = true; } else if (defaultRunMode === 'fast') { this.thinkingMode = false; } } } catch (error) { console.warn('应用个性化默认设置失败:', error); } // 创建新对话只切换视图,不再取消后台任务;停止当前轮询避免事件串写。 try { const { useTaskStore } = await import('../../../stores/task'); const taskStore = useTaskStore(); if (taskStore.hasActiveTask || this.taskInProgress) { taskStore.clearTask(); if (typeof this.clearProcessedEvents === 'function') { this.clearProcessedEvents(); } } this.clearLocalTaskUiState?.('create-new-conversation'); } catch (error) { console.error('[创建新对话] 停止本地轮询失败:', error); } let backupToastId = null; try { const personalizationStore = usePersonalizationStore(); const shouldShowBackupToast = this.versioningHostMode && personalizationStore.form.versioning_enabled_by_default && personalizationStore.form.versioning_backup_mode === 'full'; if (shouldShowBackupToast) { backupToastId = this.uiPushToast({ title: '正在初始化备份', message: '正在创建完整工作区快照,请稍候…', type: 'info', duration: null, closable: false }); this.versioningInitializingBackupToastId = backupToastId; } const response = await fetch('/api/conversations', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ thinking_mode: this.thinkingMode, mode: this.runMode }) }); const result = await response.json(); if (result.success) { const newConversationId = result.conversation_id; const waitForAnimation = (durationMs) => new Promise((resolve) => { window.setTimeout(resolve, durationMs); }); debugLog('新对话创建成功:', newConversationId); traceLog('createNewConversation:created', { newConversationId }); // 在本地列表插入占位,避免等待刷新 const placeholder = { id: newConversationId, title: '新对话', updated_at: new Date().toISOString(), total_messages: 0, total_tools: 0 }; this.conversationInsertAnimations = { ...this.conversationInsertAnimations, [newConversationId]: 'create' }; this.conversationListAnimationMode = 'create'; this.conversations = [ placeholder, ...this.conversations.filter((conv) => conv && conv.id !== newConversationId) ]; // 分组视图下同步在当前工作区分组顶部插入占位 try { const { useConversationStore } = await import('../../../stores/conversation'); const conversationStore = useConversationStore(); const currentWorkspaceId = this.currentHostWorkspaceId; if (currentWorkspaceId) { conversationStore.ensureWorkspaceGroup(currentWorkspaceId); const group = conversationStore.workspaceGroups.find( (g: any) => g.workspaceId === currentWorkspaceId ); if (group) { group.conversations = [ placeholder, ...group.conversations.filter((conv: any) => conv.id !== newConversationId) ]; group.expanded = true; group.visibleOffset = 0; } } } catch (_err) { // ignore } window.setTimeout(() => { const rest = { ...this.conversationInsertAnimations }; delete rest[newConversationId]; this.conversationInsertAnimations = rest; this.conversationListAnimationMode = 'idle'; }, 700); // 等待插入动画先完成一帧,避免立即加载对话导致列表闪烁/卡顿。 // 工作区旁新建不切换对话,所以不会触发此处的状态风暴;顶部新建需要切对话, // 但把 loadConversation 放到入场动画基本结束后再执行,动画就和工作区旁一致。 await waitForAnimation(540); // 直接加载新对话,确保状态一致 // 如果 socket 事件已把 currentConversationId 设置为新ID,则强制加载一次以同步状态 await this.loadConversation(newConversationId, { force: true }); traceLog('createNewConversation:after-load', { newConversationId, currentConversationId: this.currentConversationId }); // 刷新对话列表获取最新统计 this.conversationsOffset = 0; await this.loadConversationsList(); traceLog('createNewConversation:after-refresh', { newConversationId, conversationsLen: Array.isArray(this.conversations) ? this.conversations.length : 'n/a' }); // 分组视图下刷新当前工作区数据,用 refresh 模式避免清空已加载内容导致闪烁 try { const { useConversationStore } = await import('../../../stores/conversation'); const conversationStore = useConversationStore(); const currentWorkspaceId = this.currentHostWorkspaceId; if (currentWorkspaceId) { await conversationStore.loadWorkspaceConversations(currentWorkspaceId, { refresh: true }); } } catch (_err) { // ignore } await this.refreshRunningWorkspaceTasks?.(); } else { console.error('创建对话失败:', result.message); this.uiPushToast({ title: '创建对话失败', message: result.message || '服务器未返回成功状态', type: 'error' }); } } catch (error) { console.error('创建对话异常:', error); this.uiPushToast({ title: '创建对话异常', message: error.message || String(error), type: 'error' }); } finally { if (backupToastId) { this.uiDismissToast(backupToastId); this.versioningInitializingBackupToastId = null; } } }, async createWorkspaceConversation(workspaceId: string) { if (!workspaceId) return; debugLog('在指定工作区创建新对话:', workspaceId); try { const response = await fetch('/api/conversations', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ workspace_id: workspaceId }) }); const result = await response.json(); if (result.success) { const newConversationId = result.conversation_id; debugLog('工作区新对话创建成功:', newConversationId); const placeholder = { id: newConversationId, title: '新对话', updated_at: new Date().toISOString(), total_messages: 0, total_tools: 0 }; // 统一播放“整体下移 + 新项从左侧滑入”的动画 this.conversationInsertAnimations = { ...this.conversationInsertAnimations, [newConversationId]: 'create' }; this.conversationListAnimationMode = 'create'; window.setTimeout(() => { const rest = { ...this.conversationInsertAnimations }; delete rest[newConversationId]; this.conversationInsertAnimations = rest; this.conversationListAnimationMode = 'idle'; }, 700); // 立即在分组侧边栏插入占位,保证用户能立刻看到 try { const { useConversationStore } = await import('../../../stores/conversation'); const conversationStore = useConversationStore(); conversationStore.ensureWorkspaceGroup(workspaceId); const group = conversationStore.workspaceGroups.find((g: any) => g.workspaceId === workspaceId); if (group) { group.conversations = [ placeholder, ...group.conversations.filter((conv: any) => conv.id !== newConversationId) ]; group.expanded = true; group.visibleOffset = 0; } // 延迟刷新该工作区列表以获取真实数据,用 refresh 模式保留占位避免闪烁 window.setTimeout(() => { conversationStore.loadWorkspaceConversations(workspaceId, { refresh: true }).catch(() => {}); }, 300); } catch (_err) { // ignore } } else { console.error('创建对话失败:', result.message); this.uiPushToast({ title: '创建对话失败', message: result.message || '服务器未返回成功状态', type: 'error' }); } } catch (error) { console.error('创建工作区对话异常:', error); this.uiPushToast({ title: '创建工作区对话异常', message: error.message || String(error), type: 'error' }); } }, async deleteConversation(payload) { const conversationId = typeof payload === 'string' ? payload : payload?.id; const workspaceId = typeof payload === 'object' ? payload?.workspaceId : undefined; if (!conversationId) return; const confirmed = await this.confirmAction({ title: '删除对话', message: '确定要删除这个对话吗?删除后无法恢复。', confirmText: '删除', cancelText: '取消' }); if (!confirmed) { return; } const waitForAnimation = (durationMs) => new Promise((resolve) => { window.setTimeout(resolve, durationMs); }); // 确认弹窗自身有 250ms 退出动画;等弹窗完全消失后,再开始列表删除动画。 await waitForAnimation(270); debugLog('删除对话:', conversationId); this.logMessageState('deleteConversation:start', { conversationId }); try { const deleteUrl = workspaceId ? `/api/conversations/${conversationId}?workspace_id=${encodeURIComponent(workspaceId)}` : `/api/conversations/${conversationId}`; const response = await fetch(deleteUrl, { method: 'DELETE' }); const result = await response.json(); if (result.success) { debugLog('对话删除成功'); // 如果删除的是当前对话,清空界面 if (conversationId === this.currentConversationId) { this.logMessageState('deleteConversation:before-clear', { conversationId }); this.messages = []; this.logMessageState('deleteConversation:after-clear', { conversationId }); this.currentConversationId = null; this.currentConversationTitle = ''; this.resetAllStates(`deleteConversation:${conversationId}`); this.resetTokenStatistics(); history.replaceState({}, '', '/new'); } // 先给目标项加 deleting 类执行左滑动画,再从本地列表移除。 // 这比完全依赖 transition-group leave 更稳定:即使列表即将为空或分支切换,也能先播放离场动画。 this.conversationListAnimationMode = 'delete'; const deletingIds = Array.isArray(this.pendingDeletingConversationIds) ? this.pendingDeletingConversationIds : []; if (!deletingIds.includes(conversationId)) { this.pendingDeletingConversationIds = [...deletingIds, conversationId]; } await this.$nextTick(); // 分组侧边栏使用 transition-group 离场动画,立即从列表移除 try { const { useConversationStore } = await import('../../../stores/conversation'); const conversationStore = useConversationStore(); conversationStore.workspaceGroups.forEach((group: any) => { group.conversations = group.conversations.filter((conv: any) => conv.id !== conversationId); const maxVisibleOffset = Math.max(0, group.conversations.length - group.visibleLimit); if (group.visibleOffset > maxVisibleOffset) { group.visibleOffset = maxVisibleOffset; } }); } catch (_err) { // ignore } await waitForAnimation(430); this.conversations = this.conversations.filter( (conversation) => conversation.id !== conversationId ); this.searchResults = this.searchResults.filter( (conversation) => conversation.id !== conversationId ); this.pendingDeletingConversationIds = (Array.isArray(this.pendingDeletingConversationIds) ? this.pendingDeletingConversationIds : [] ).filter((id) => id !== conversationId); // 等待“左滑离场 + 0.1s 后集体上移”动画结束,避免刷新列表打断过渡。 await waitForAnimation(180); // 刷新对话列表 this.conversationsOffset = 0; await this.loadConversationsList(); this.conversationListAnimationMode = 'idle'; } else { console.error('删除对话失败:', result.message); this.uiPushToast({ title: '删除对话失败', message: result.message || '服务器未返回成功状态', type: 'error' }); } } catch (error) { this.pendingDeletingConversationIds = (Array.isArray(this.pendingDeletingConversationIds) ? this.pendingDeletingConversationIds : [] ).filter((id) => id !== conversationId); this.conversationListAnimationMode = 'idle'; console.error('删除对话异常:', error); this.uiPushToast({ title: '删除对话异常', message: error.message || String(error), type: 'error' }); } }, async duplicateConversation(conversationId) { debugLog('复制对话:', conversationId); try { const response = await fetch(`/api/conversations/${conversationId}/duplicate`, { method: 'POST' }); const result = await response.json(); if (response.ok && result.success) { const newId = result.duplicate_conversation_id; const waitForAnimation = (durationMs) => new Promise((resolve) => { window.setTimeout(resolve, durationMs); }); if (newId) { const sourceIndex = this.conversations.findIndex( (conv) => conv && conv.id === conversationId ); const sourceConversation = sourceIndex >= 0 ? this.conversations[sourceIndex] : null; const duplicateTitle = result.load_result?.title || sourceConversation?.title || '复制的对话'; const duplicatePlaceholder = { id: newId, title: duplicateTitle, updated_at: new Date().toISOString(), total_messages: sourceConversation?.total_messages || 0, total_tools: sourceConversation?.total_tools || 0 }; this.conversationInsertAnimations = { ...this.conversationInsertAnimations, [conversationId]: 'duplicateSource', [newId]: 'duplicate' }; this.conversationListAnimationMode = 'duplicate'; const withoutDuplicate = this.conversations.filter((conv) => conv && conv.id !== newId); const insertIndex = withoutDuplicate.findIndex( (conv) => conv && conv.id === conversationId ); if (insertIndex >= 0) { this.conversations = [ ...withoutDuplicate.slice(0, insertIndex + 1), duplicatePlaceholder, ...withoutDuplicate.slice(insertIndex + 1) ]; } else { this.conversations = [duplicatePlaceholder, ...withoutDuplicate]; } window.setTimeout(() => { const rest = { ...this.conversationInsertAnimations }; delete rest[conversationId]; delete rest[newId]; this.conversationInsertAnimations = rest; this.conversationListAnimationMode = 'idle'; }, 860); // 同步插入分组侧边栏对应工作区 try { const { useConversationStore } = await import('../../../stores/conversation'); const conversationStore = useConversationStore(); const group = conversationStore.workspaceGroups.find((g: any) => g.conversations.some((conv: any) => conv.id === conversationId) ); if (group) { const sourceGroupIndex = group.conversations.findIndex((conv: any) => conv.id === conversationId); const withoutDuplicate = group.conversations.filter((conv: any) => conv.id !== newId); if (sourceGroupIndex >= 0) { group.conversations = [ ...withoutDuplicate.slice(0, sourceGroupIndex + 1), duplicatePlaceholder, ...withoutDuplicate.slice(sourceGroupIndex + 1) ]; } else { group.conversations = [duplicatePlaceholder, ...withoutDuplicate]; } group.expanded = true; } } catch (_err) { // ignore } // 先让“目标下方钻出 + 下方整体下移”动画播完,再加载复制出的对话。 await waitForAnimation(860); await this.loadConversation(newId, { force: true, preserveListPosition: true }); } } else { const message = result.message || result.error || '复制失败'; this.uiPushToast({ title: '复制对话失败', message, type: 'error' }); } } catch (error) { console.error('复制对话异常:', error); this.uiPushToast({ title: '复制对话异常', message: error.message || String(error), type: 'error' }); } } };