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() {