diff --git a/static/index.html b/static/index.html index 417ddf1..3f6a4bb 100644 --- a/static/index.html +++ b/static/index.html @@ -44,13 +44,6 @@ button.setAttribute('aria-label', button.dataset.originalLabel || '复制代码'); }); } - document.addEventListener('click', function(e) { - const target = e.target; - if (target && target.classList && target.classList.contains('copy-code-btn')) { - const blockId = target.getAttribute('data-code'); - if (blockId) copyCodeBlock(blockId); - } - }); diff --git a/static/src/app/methods/message.ts b/static/src/app/methods/message.ts index 0997227..742a95c 100644 --- a/static/src/app/methods/message.ts +++ b/static/src/app/methods/message.ts @@ -261,24 +261,40 @@ export const messageMethods = { this.stopRequested = true; this.dropToolEvents = shouldDropToolEvents; - // 使用 REST API 取消任务 try { const { useTaskStore } = await import('../../stores/task'); const taskStore = useTaskStore(); if (taskStore.currentTaskId) { await taskStore.cancelTask(); - debugLog('[Message] 任务已取消'); } + + // 等待后端确认 + await new Promise(resolve => setTimeout(resolve, 300)); + + // 清理前端状态 + this.clearPendingTools('user_stop'); + this.streamingMessage = false; + this.taskInProgress = false; + this.forceUnlockMonitor('user_stop'); } catch (error) { console.error('[Message] 取消任务失败:', error); - } - // 立即清理前端状态,避免出现"不可输入也不可停止"的卡死状态 - this.clearPendingTools('user_stop'); - this.streamingMessage = false; - this.taskInProgress = false; - this.forceUnlockMonitor('user_stop'); + // 即使失败也清理状态 + this.clearPendingTools('user_stop'); + this.streamingMessage = false; + this.taskInProgress = false; + this.forceUnlockMonitor('user_stop'); + + this.uiPushToast({ + title: '停止失败', + message: '任务可能仍在运行,请刷新页面', + type: 'warning' + }); + } finally { + // 确保清除 dropToolEvents 标志 + this.dropToolEvents = false; + } }, async clearChat() { diff --git a/static/src/app/methods/taskPolling.ts b/static/src/app/methods/taskPolling.ts index 97d40f1..51040c3 100644 --- a/static/src/app/methods/taskPolling.ts +++ b/static/src/app/methods/taskPolling.ts @@ -18,6 +18,26 @@ export const taskPollingMethods = { const eventData = event.data || {}; const eventIdx = event.idx; + // 事件去重检查 + if (typeof eventIdx === 'number') { + if (!this._processedEventIndices) { + this._processedEventIndices = new Set(); + } + + if (this._processedEventIndices.has(eventIdx)) { + debugLog(`[TaskPolling] 跳过重复事件 #${eventIdx}`); + return; + } + + this._processedEventIndices.add(eventIdx); + + // 限制 Set 大小(保留最近 1000 个) + if (this._processedEventIndices.size > 1000) { + const firstIdx = Math.min(...this._processedEventIndices); + this._processedEventIndices.delete(firstIdx); + } + } + // 检查事件的 conversation_id 是否匹配当前对话 // 如果不匹配,忽略该事件(避免切换对话后旧任务的事件显示到新对话中) if (eventData.conversation_id && this.currentConversationId) { @@ -529,31 +549,42 @@ export const taskPollingMethods = { } else { debugLog('[TaskPolling] 任务完成'); } + + // 同步处理状态更新 this.streamingMessage = false; this.stopRequested = false; + if (hasRunningSubAgents) { this.taskInProgress = true; this.waitingForSubAgent = true; } else { this.taskInProgress = false; this.waitingForSubAgent = false; + this.clearTaskState(); // 清理任务状态 } + this.$forceUpdate(); - // 停止轮询 + // 只更新统计,不重新加载历史 + if (this.currentConversationId) { + setTimeout(() => { + this.fetchConversationTokenStatistics(); + this.updateCurrentContextTokens(); + }, 500); + } + }, + + // 新增统一清理方法 + clearTaskState() { (async () => { const { useTaskStore } = await import('../../stores/task'); const taskStore = useTaskStore(); - taskStore.stopPolling(); + taskStore.clearTask(); // 使用 clearTask 而不是 stopPolling })(); - // 只更新统计,不重新加载历史(避免重复显示) - setTimeout(() => { - if (this.currentConversationId) { - this.fetchConversationTokenStatistics(); - this.updateCurrentContextTokens(); - } - }, 500); + if (typeof this.clearProcessedEvents === 'function') { + this.clearProcessedEvents(); + } }, handleUserMessage(data: any) { @@ -631,10 +662,22 @@ export const taskPollingMethods = { } }, + /** + * 清理已处理的事件索引 + */ + clearProcessedEvents() { + if (this._processedEventIndices) { + this._processedEventIndices.clear(); + } + }, + /** * 恢复任务状态(页面刷新后调用) */ async restoreTaskState() { + // 清理已处理的事件索引 + this.clearProcessedEvents(); + try { const { useTaskStore } = await import('../../stores/task'); const taskStore = useTaskStore(); diff --git a/static/src/stores/task.ts b/static/src/stores/task.ts index 59c8249..350b34d 100644 --- a/static/src/stores/task.ts +++ b/static/src/stores/task.ts @@ -180,6 +180,7 @@ export const useTaskStore = defineStore('task', { this.pollingInterval = null; } this.isPolling = false; + this.currentTaskId = null; // 清除任务 ID }, async cancelTask() {