agent-Specialization/static/src/app/methods/conversation.ts

666 lines
26 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// @ts-nocheck
import { debugLog, traceLog } from './common';
export const conversationMethods = {
// 完整重置所有状态
resetAllStates(reason = 'unspecified', options: { preserveMonitorWindows?: boolean } = {}) {
console.log('[DEBUG_AWAITING] ===== resetAllStates =====', { reason });
// 如果正在等待子智能体完成,不重置任务状态
if (this.waitingForSubAgent) {
debugLog('跳过状态重置:正在等待子智能体完成', { reason });
return;
}
debugLog('重置所有前端状态', { reason, conversationId: this.currentConversationId });
this.logMessageState('resetAllStates:before-cleanup', { reason });
this.fileHideContextMenu();
this.monitorResetVisual({
preserveBubble: true,
preservePointer: true,
preserveWindows: !!options?.preserveMonitorWindows,
preserveQueue: !!options?.preserveMonitorWindows
});
// 重置消息和流状态
this.streamingMessage = false;
this.currentMessageIndex = -1;
this.stopRequested = false;
this.taskInProgress = false;
this.dropToolEvents = false;
// 清理工具状态
this.toolResetTracking();
// 新增将所有未完成的工具标记为已完成并清理awaitingFirstContent状态
const assistantMsgsBefore = this.messages.filter(m => m.role === 'assistant').map(m => ({
awaitingFirstContent: m.awaitingFirstContent,
generatingLabel: m.generatingLabel
}));
this.messages.forEach((msg) => {
if (msg.role === 'assistant') {
// 清理等待动画状态
if (msg.awaitingFirstContent) {
msg.awaitingFirstContent = false;
}
if (msg.generatingLabel) {
msg.generatingLabel = '';
}
// 清理工具状态
if (msg.actions) {
msg.actions.forEach(action => {
if (action.type === 'tool' &&
(action.tool.status === 'preparing' || action.tool.status === 'running')) {
action.tool.status = 'completed';
}
});
}
}
});
const assistantMsgsAfter = this.messages.filter(m => m.role === 'assistant').map(m => ({
awaitingFirstContent: m.awaitingFirstContent,
generatingLabel: m.generatingLabel
}));
console.log('[DEBUG_AWAITING] resetAllStates 清理完成', {
before: assistantMsgsBefore,
after: assistantMsgsAfter
});
// 清理Markdown缓存
if (this.markdownCache) {
this.markdownCache.clear();
}
this.chatClearThinkingLocks();
// 强制更新视图
this.$forceUpdate();
this.inputSetSettingsOpen(false);
this.inputSetToolMenuOpen(false);
this.inputSetQuickMenuOpen(false);
this.modeMenuOpen = false;
this.inputSetLineCount(1);
this.inputSetMultiline(false);
this.inputClearMessage();
this.inputClearSelectedImages();
this.inputSetImagePickerOpen(false);
this.imageEntries = [];
this.imageLoading = false;
this.conversationHasImages = false;
this.toolSetSettingsLoading(false);
this.toolSetSettings([]);
debugLog('前端状态重置完成');
this._scrollListenerReady = false;
this.$nextTick(() => {
this.ensureScrollListener();
});
// 重置已加载对话标记,便于后续重新加载新对话历史
this.lastHistoryLoadedConversationId = null;
this.logMessageState('resetAllStates:after-cleanup', { reason });
},
scheduleResetAfterTask(reason = 'unspecified', options: { preserveMonitorWindows?: boolean } = {}) {
const start = Date.now();
const maxWait = 4000;
const interval = 200;
const tryReset = () => {
if (!this.monitorIsLocked || Date.now() - start >= maxWait) {
this.resetAllStates(reason, options);
return;
}
setTimeout(tryReset, interval);
};
tryReset();
},
resetTokenStatistics() {
this.resourceResetTokenStatistics();
},
// ==========================================
// 对话管理核心功能
// ==========================================
async loadConversationsList() {
const queryOffset = this.conversationsOffset;
const queryLimit = this.conversationsLimit;
const refreshToken = queryOffset === 0 ? ++this.conversationListRefreshToken : this.conversationListRefreshToken;
const requestSeq = ++this.conversationListRequestSeq;
this.conversationsLoading = true;
try {
const response = await fetch(`/api/conversations?limit=${queryLimit}&offset=${queryOffset}`);
const data = await response.json();
if (data.success) {
if (refreshToken !== this.conversationListRefreshToken) {
debugLog('忽略已过期的对话列表响应', {
requestSeq,
responseOffset: queryOffset
});
return;
}
if (queryOffset === 0) {
this.conversations = data.data.conversations;
} else {
this.conversations.push(...data.data.conversations);
}
if (this.currentConversationId) {
this.promoteConversationToTop(this.currentConversationId);
}
this.hasMoreConversations = data.data.has_more;
debugLog(`已加载 ${this.conversations.length} 个对话`);
if (
this.conversationsOffset === 0 &&
!this.currentConversationId &&
this.conversations.length > 0 &&
!this.isExplicitNewConversationRoute()
) {
// 只有在初始化完成后,才自动加载第一个对话
// 避免与 bootstrapRoute 冲突
if (this.initialRouteResolved) {
const latestConversation = this.conversations[0];
if (latestConversation && latestConversation.id) {
await this.loadConversation(latestConversation.id);
}
}
}
} else {
console.error('加载对话列表失败:', data.error);
}
} catch (error) {
console.error('加载对话列表异常:', error);
} finally {
if (refreshToken === this.conversationListRefreshToken) {
this.conversationsLoading = false;
}
}
},
async loadMoreConversations() {
if (this.loadingMoreConversations || !this.hasMoreConversations) return;
this.loadingMoreConversations = true;
this.conversationsOffset += this.conversationsLimit;
await this.loadConversationsList();
this.loadingMoreConversations = false;
},
async loadConversation(conversationId, options = {}) {
const force = Boolean(options.force);
debugLog('加载对话:', conversationId);
traceLog('loadConversation:start', { conversationId, currentConversationId: this.currentConversationId, force });
this.logMessageState('loadConversation:start', { conversationId, force });
this.suppressTitleTyping = true;
this.titleReady = false;
this.currentConversationTitle = '';
this.titleTypingText = '';
if (!force && conversationId === this.currentConversationId) {
debugLog('已是当前对话,跳过加载');
traceLog('loadConversation:skip-same', { conversationId });
this.suppressTitleTyping = false;
this.titleReady = true;
return;
}
if ((this.compressionInProgress || this.compressing) && !force) {
const confirmed = await this.confirmAction({
title: '压缩进行中',
message: '对话正在压缩中,切换对话会导致压缩失败,确认要继续吗?',
confirmText: '确认',
cancelText: '取消'
});
if (!confirmed) {
this.suppressTitleTyping = false;
this.titleReady = true;
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;
}
}
// 应用个性化设置中的默认模型和思考模式
try {
const { usePersonalizationStore } = await import('../../stores/personalization');
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();
const hasActiveTask = taskStore.hasActiveTask || this.taskInProgress || this.waitingForSubAgent;
if (hasActiveTask) {
const confirmed = await this.confirmAction({
title: '切换对话',
message: this.waitingForSubAgent
? '后台子智能体正在运行,切换对话后将不会自动接收完成提示。确定要切换吗?'
: '当前有任务正在执行,切换对话后任务会停止。确定要切换吗?',
confirmText: '切换',
cancelText: '取消'
});
if (!confirmed) {
this.suppressTitleTyping = false;
this.titleReady = true;
return;
}
if (taskStore.hasActiveTask) {
await taskStore.cancelTask();
taskStore.clearTask();
if (typeof this.clearProcessedEvents === 'function') {
this.clearProcessedEvents();
}
}
if (this.waitingForSubAgent && !taskStore.hasActiveTask) {
this.waitingForSubAgent = false;
}
this.streamingMessage = false;
this.taskInProgress = false;
this.stopRequested = false;
}
} catch (error) {
console.error('[切换对话] 检查/停止任务失败:', error);
}
try {
// 1. 调用加载API
const response = await fetch(`/api/conversations/${conversationId}/load`, {
method: 'PUT'
});
const result = await response.json();
if (result.success) {
debugLog('对话加载API成功:', result);
traceLog('loadConversation:api-success', { conversationId, title: result.title });
// 2. 更新当前对话信息
this.skipConversationHistoryReload = true;
this.currentConversationId = conversationId;
this.currentConversationTitle = result.title;
this.titleReady = true;
this.suppressTitleTyping = false;
this.startTitleTyping(this.currentConversationTitle, { animate: false });
this.promoteConversationToTop(conversationId);
history.pushState({ conversationId }, '', `/${this.stripConversationPrefix(conversationId)}`);
this.skipConversationLoadedEvent = true;
// 3. 重置UI状态
this.resetAllStates(`loadConversation:${conversationId}`);
this.subAgentFetch();
this.fetchTodoList();
// 4. 立即加载历史和统计,确保列表切换后界面同步更新
await this.fetchAndDisplayHistory();
this.fetchConversationTokenStatistics();
this.updateCurrentContextTokens();
traceLog('loadConversation:after-history', {
conversationId,
messagesLen: Array.isArray(this.messages) ? this.messages.length : 'n/a'
});
} else {
console.error('对话加载失败:', result.message);
this.suppressTitleTyping = false;
this.titleReady = true;
this.uiPushToast({
title: '加载对话失败',
message: result.message || '服务器未返回成功状态',
type: 'error'
});
}
} catch (error) {
console.error('加载对话异常:', error);
traceLog('loadConversation:error', { conversationId, error: error?.message || String(error) });
this.suppressTitleTyping = false;
this.titleReady = true;
this.uiPushToast({
title: '加载对话异常',
message: error.message || String(error),
type: 'error'
});
}
},
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');
// 应用个性化设置中的默认模型和思考模式
try {
const { usePersonalizationStore } = await import('../../stores/personalization');
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);
}
// 检查是否有运行中的任务,如果有则提示用户
let hasActiveTask = false;
try {
const { useTaskStore } = await import('../../stores/task');
const taskStore = useTaskStore();
if (taskStore.hasActiveTask || this.taskInProgress || this.waitingForSubAgent) {
hasActiveTask = true;
// 显示提示
const confirmed = await this.confirmAction({
title: '创建新对话',
message: this.waitingForSubAgent
? '后台子智能体正在运行,创建新对话后将不会自动接收完成提示。确定要创建吗?'
: '当前有任务正在执行,创建新对话后任务会停止。确定要创建吗?',
confirmText: '创建',
cancelText: '取消'
});
if (!confirmed) {
// 用户取消创建
return;
}
// 用户确认,停止任务
debugLog('[创建新对话] 用户确认,正在停止任务...');
if (taskStore.hasActiveTask) {
await taskStore.cancelTask();
taskStore.clearTask();
if (typeof this.clearProcessedEvents === 'function') {
this.clearProcessedEvents();
}
}
// 重置任务相关状态
this.streamingMessage = false;
this.taskInProgress = false;
this.stopRequested = false;
if (this.waitingForSubAgent && !taskStore.hasActiveTask) {
this.waitingForSubAgent = false;
}
debugLog('[创建新对话] 任务已停止');
}
} catch (error) {
console.error('[创建新对话] 检查/停止任务失败:', error);
}
try {
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;
debugLog('新对话创建成功:', newConversationId);
traceLog('createNewConversation:created', { newConversationId });
// 在本地列表插入占位,避免等待刷新
const placeholder = {
id: newConversationId,
title: '新对话',
updated_at: new Date().toISOString(),
total_messages: 0,
total_tools: 0
};
this.conversations = [
placeholder,
...this.conversations.filter(conv => conv && conv.id !== newConversationId)
];
// 直接加载新对话,确保状态一致
// 如果 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'
});
// 如果停止了任务,显示提示
if (hasActiveTask) {
this.uiPushToast({
title: '任务已停止',
message: '创建新对话后,之前的任务已停止',
type: 'info',
duration: 3000
});
}
} 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(conversationId) {
const confirmed = await this.confirmAction({
title: '删除对话',
message: '确定要删除这个对话吗?删除后无法恢复。',
confirmText: '删除',
cancelText: '取消'
});
if (!confirmed) {
return;
}
debugLog('删除对话:', conversationId);
this.logMessageState('deleteConversation:start', { conversationId });
try {
const response = await fetch(`/api/conversations/${conversationId}`, {
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');
}
// 刷新对话列表
this.conversationsOffset = 0;
await this.loadConversationsList();
} 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 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;
if (newId) {
await this.loadConversation(newId, { force: true });
}
this.conversationsOffset = 0;
await this.loadConversationsList();
} 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'
});
}
}
};