|
@@ -94,7 +94,7 @@
|
|
|
ref="chatInputRef"
|
|
ref="chatInputRef"
|
|
|
v-model="inputMessage"
|
|
v-model="inputMessage"
|
|
|
:pendingFile="pendingFile"
|
|
:pendingFile="pendingFile"
|
|
|
- :loading="loading || !!pendingApprovalSessionId"
|
|
|
|
|
|
|
+ :loading="loading || !!currentPendingApproval"
|
|
|
:streaming="streaming"
|
|
:streaming="streaming"
|
|
|
:contextUsed="contextUsed"
|
|
:contextUsed="contextUsed"
|
|
|
:contextMax="contextMax"
|
|
:contextMax="contextMax"
|
|
@@ -297,8 +297,6 @@
|
|
|
const contextPercent = ref(0);
|
|
const contextPercent = ref(0);
|
|
|
const thinkLevel = ref('off');
|
|
const thinkLevel = ref('off');
|
|
|
const editMode = ref('full');
|
|
const editMode = ref('full');
|
|
|
- const pendingApprovalSessionId = ref('');
|
|
|
|
|
- const pendingApprovalThreadId = ref('');
|
|
|
|
|
const downloadConfirm = ref<{ url: string; filename: string } | null>(null);
|
|
const downloadConfirm = ref<{ url: string; filename: string } | null>(null);
|
|
|
const downloadConfirmTaskId = ref('');
|
|
const downloadConfirmTaskId = ref('');
|
|
|
let downloadConfirmTimer: ReturnType<typeof setTimeout> | null = null;
|
|
let downloadConfirmTimer: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -483,14 +481,62 @@
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const inputMessage = ref('');
|
|
const inputMessage = ref('');
|
|
|
- const loading = ref(false);
|
|
|
|
|
- const streaming = ref(false);
|
|
|
|
|
- const activeAbortController = ref<AbortController | null>(null);
|
|
|
|
|
const selectedSessions = ref<Array<{ id: string; name: string }>>([]);
|
|
const selectedSessions = ref<Array<{ id: string; name: string }>>([]);
|
|
|
const selectedSkill = ref<{ name: string; display_name: string } | null>(null);
|
|
const selectedSkill = ref<{ name: string; display_name: string } | null>(null);
|
|
|
|
|
|
|
|
|
|
+ // 按任务维度的状态存储
|
|
|
|
|
+ const taskAbortControllers = ref<Map<string, AbortController>>(new Map());
|
|
|
|
|
+ const taskPendingApprovals = ref<Map<string, { sessionId: string; threadId: string }>>(new Map());
|
|
|
|
|
+
|
|
|
|
|
+ // loading 和 streaming 改为计算属性,基于当前任务的 isStreaming 状态
|
|
|
|
|
+ const loading = computed(() => {
|
|
|
|
|
+ const currentTask = taskList.value.find((t) => t.id === currentTaskId.value);
|
|
|
|
|
+ return currentTask?.isStreaming ?? false;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const streaming = computed(() => {
|
|
|
|
|
+ const currentTask = taskList.value.find((t) => t.id === currentTaskId.value);
|
|
|
|
|
+ return currentTask?.isStreaming ?? false;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 获取当前任务的待审批状态
|
|
|
|
|
+ const currentPendingApproval = computed(() => {
|
|
|
|
|
+ return taskPendingApprovals.value.get(currentTaskId.value);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 设置任务的AbortController
|
|
|
|
|
+ const setTaskAbortController = (taskId: string, controller: AbortController | null) => {
|
|
|
|
|
+ const map = new Map(taskAbortControllers.value);
|
|
|
|
|
+ if (controller) {
|
|
|
|
|
+ map.set(taskId, controller);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ map.delete(taskId);
|
|
|
|
|
+ }
|
|
|
|
|
+ taskAbortControllers.value = map;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 设置任务的审批状态
|
|
|
|
|
+ const setTaskPendingApproval = (taskId: string, sessionId: string, threadId: string) => {
|
|
|
|
|
+ const map = new Map(taskPendingApprovals.value);
|
|
|
|
|
+ map.set(taskId, { sessionId, threadId });
|
|
|
|
|
+ taskPendingApprovals.value = map;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 清除任务的审批状态
|
|
|
|
|
+ const clearTaskPendingApproval = (taskId: string) => {
|
|
|
|
|
+ const map = new Map(taskPendingApprovals.value);
|
|
|
|
|
+ map.delete(taskId);
|
|
|
|
|
+ taskPendingApprovals.value = map;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 停止指定任务的流式传输
|
|
|
|
|
+ const stopTaskStreaming = (taskId: string) => {
|
|
|
|
|
+ const controller = taskAbortControllers.value.get(taskId);
|
|
|
|
|
+ controller?.abort();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
const stopStreaming = () => {
|
|
const stopStreaming = () => {
|
|
|
- activeAbortController.value?.abort();
|
|
|
|
|
|
|
+ stopTaskStreaming(currentTaskId.value);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const getCurrentSessionId = (): string => {
|
|
const getCurrentSessionId = (): string => {
|
|
@@ -528,8 +574,7 @@
|
|
|
showFileList.value = false;
|
|
showFileList.value = false;
|
|
|
|
|
|
|
|
if (newTask.sessionId && !historyLoadedTasks.value.has(taskId)) {
|
|
if (newTask.sessionId && !historyLoadedTasks.value.has(taskId)) {
|
|
|
- if (streaming.value) return;
|
|
|
|
|
- loading.value = true;
|
|
|
|
|
|
|
+ if (newTask.isStreaming) return; // 新任务正在流式中,无需加载历史
|
|
|
try {
|
|
try {
|
|
|
const res = await getDetail(newTask.sessionId);
|
|
const res = await getDetail(newTask.sessionId);
|
|
|
const messages = res?.messages || (Array.isArray(res) ? res : res?.data);
|
|
const messages = res?.messages || (Array.isArray(res) ? res : res?.data);
|
|
@@ -539,8 +584,6 @@
|
|
|
historyLoadedTasks.value.add(taskId);
|
|
historyLoadedTasks.value.add(taskId);
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('加载历史会话失败:', error);
|
|
console.error('加载历史会话失败:', error);
|
|
|
- } finally {
|
|
|
|
|
- loading.value = false;
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -642,7 +685,22 @@
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const handleApprove = async () => {
|
|
const handleApprove = async () => {
|
|
|
- const sessionId = pendingApprovalSessionId.value || getCurrentSessionId();
|
|
|
|
|
|
|
+ // 优先使用当前任务的审批状态,如果没有则查找任何有待审批状态的任务
|
|
|
|
|
+ let pendingApproval = currentPendingApproval.value;
|
|
|
|
|
+ if (!pendingApproval) {
|
|
|
|
|
+ // 查找任何有待审批状态的任务
|
|
|
|
|
+ for (const [taskId, approval] of taskPendingApprovals.value.entries()) {
|
|
|
|
|
+ pendingApproval = approval;
|
|
|
|
|
+ // 切换到该任务以便用户看到审批界面
|
|
|
|
|
+ if (currentTaskId.value !== taskId) {
|
|
|
|
|
+ await switchTask(taskId);
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!pendingApproval) return;
|
|
|
|
|
+
|
|
|
|
|
+ const sessionId = pendingApproval.sessionId;
|
|
|
if (!sessionId) return;
|
|
if (!sessionId) return;
|
|
|
|
|
|
|
|
const approveTask = taskList.value.find((t) => t.sessionId === sessionId);
|
|
const approveTask = taskList.value.find((t) => t.sessionId === sessionId);
|
|
@@ -665,20 +723,18 @@
|
|
|
messages.value = approveTask.messages;
|
|
messages.value = approveTask.messages;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- loading.value = true;
|
|
|
|
|
approveTask.isStreaming = true;
|
|
approveTask.isStreaming = true;
|
|
|
showTodoBar.value = false;
|
|
showTodoBar.value = false;
|
|
|
currentTodos.value = [];
|
|
currentTodos.value = [];
|
|
|
|
|
|
|
|
const controller = new AbortController();
|
|
const controller = new AbortController();
|
|
|
- activeAbortController.value = controller;
|
|
|
|
|
- streaming.value = true;
|
|
|
|
|
|
|
+ setTaskAbortController(approveTask.id, controller);
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
const result = await chatResumeStream(
|
|
const result = await chatResumeStream(
|
|
|
{
|
|
{
|
|
|
session_id: sessionId,
|
|
session_id: sessionId,
|
|
|
- thread_id: pendingApprovalThreadId.value || undefined,
|
|
|
|
|
|
|
+ thread_id: pendingApproval?.threadId || undefined,
|
|
|
action: 'approve',
|
|
action: 'approve',
|
|
|
signal: controller.signal,
|
|
signal: controller.signal,
|
|
|
},
|
|
},
|
|
@@ -709,14 +765,9 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
} finally {
|
|
} finally {
|
|
|
- if (activeAbortController.value === controller) {
|
|
|
|
|
- activeAbortController.value = null;
|
|
|
|
|
- }
|
|
|
|
|
- streaming.value = false;
|
|
|
|
|
- loading.value = false;
|
|
|
|
|
|
|
+ setTaskAbortController(approveTask.id, null);
|
|
|
approveTask.isStreaming = false;
|
|
approveTask.isStreaming = false;
|
|
|
- pendingApprovalSessionId.value = '';
|
|
|
|
|
- pendingApprovalThreadId.value = '';
|
|
|
|
|
|
|
+ clearTaskPendingApproval(approveTask.id);
|
|
|
if (currentTaskId.value === approveTask.id) {
|
|
if (currentTaskId.value === approveTask.id) {
|
|
|
await scrollToBottom();
|
|
await scrollToBottom();
|
|
|
}
|
|
}
|
|
@@ -724,7 +775,22 @@
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const handleReject = async () => {
|
|
const handleReject = async () => {
|
|
|
- const sessionId = pendingApprovalSessionId.value || getCurrentSessionId();
|
|
|
|
|
|
|
+ // 优先使用当前任务的审批状态,如果没有则查找任何有待审批状态的任务
|
|
|
|
|
+ let pendingApproval = currentPendingApproval.value;
|
|
|
|
|
+ if (!pendingApproval) {
|
|
|
|
|
+ // 查找任何有待审批状态的任务
|
|
|
|
|
+ for (const [taskId, approval] of taskPendingApprovals.value.entries()) {
|
|
|
|
|
+ pendingApproval = approval;
|
|
|
|
|
+ // 切换到该任务以便用户看到审批界面
|
|
|
|
|
+ if (currentTaskId.value !== taskId) {
|
|
|
|
|
+ await switchTask(taskId);
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!pendingApproval) return;
|
|
|
|
|
+
|
|
|
|
|
+ const sessionId = pendingApproval.sessionId;
|
|
|
if (!sessionId) return;
|
|
if (!sessionId) return;
|
|
|
|
|
|
|
|
const rejectTask = taskList.value.find((t) => t.sessionId === sessionId);
|
|
const rejectTask = taskList.value.find((t) => t.sessionId === sessionId);
|
|
@@ -742,9 +808,8 @@
|
|
|
|
|
|
|
|
rejectTask.messages = [...taskMessages];
|
|
rejectTask.messages = [...taskMessages];
|
|
|
|
|
|
|
|
- const threadId = pendingApprovalThreadId.value || undefined;
|
|
|
|
|
- pendingApprovalSessionId.value = '';
|
|
|
|
|
- pendingApprovalThreadId.value = '';
|
|
|
|
|
|
|
+ const threadId = pendingApproval?.threadId || undefined;
|
|
|
|
|
+ clearTaskPendingApproval(rejectTask.id);
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
await chatResumeStream(
|
|
await chatResumeStream(
|
|
@@ -987,7 +1052,7 @@
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
if (loading.value) return;
|
|
if (loading.value) return;
|
|
|
- if (pendingApprovalSessionId.value) {
|
|
|
|
|
|
|
+ if (currentPendingApproval.value) {
|
|
|
message.warning('请先审批执行计划');
|
|
message.warning('请先审批执行计划');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
@@ -1015,7 +1080,6 @@
|
|
|
selectedSkill.value = null;
|
|
selectedSkill.value = null;
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- loading.value = true;
|
|
|
|
|
await nextTick();
|
|
await nextTick();
|
|
|
chatMessagesRef.value?.forceScrollToBottom();
|
|
chatMessagesRef.value?.forceScrollToBottom();
|
|
|
|
|
|
|
@@ -1045,7 +1109,6 @@
|
|
|
currentTask.messages = [...messages.value];
|
|
currentTask.messages = [...messages.value];
|
|
|
}
|
|
}
|
|
|
} finally {
|
|
} finally {
|
|
|
- loading.value = false;
|
|
|
|
|
await scrollToBottom();
|
|
await scrollToBottom();
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
@@ -1321,8 +1384,7 @@
|
|
|
elapsedMs,
|
|
elapsedMs,
|
|
|
prevThinkingContent: aiMsg.thinkingContent,
|
|
prevThinkingContent: aiMsg.thinkingContent,
|
|
|
};
|
|
};
|
|
|
- pendingApprovalSessionId.value = interruptSessionId;
|
|
|
|
|
- pendingApprovalThreadId.value = data.thread_id || '';
|
|
|
|
|
|
|
+ setTaskPendingApproval(taskId, interruptSessionId, data.thread_id || '');
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -1397,8 +1459,7 @@
|
|
|
if (originTask) originTask.isStreaming = true;
|
|
if (originTask) originTask.isStreaming = true;
|
|
|
|
|
|
|
|
const controller = new AbortController();
|
|
const controller = new AbortController();
|
|
|
- activeAbortController.value = controller;
|
|
|
|
|
- streaming.value = true;
|
|
|
|
|
|
|
+ setTaskAbortController(originTaskId, controller);
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
const result = await unifiedStream(
|
|
const result = await unifiedStream(
|
|
@@ -1424,6 +1485,12 @@
|
|
|
}
|
|
}
|
|
|
originTask.pendingSessionId = undefined;
|
|
originTask.pendingSessionId = undefined;
|
|
|
if (originTask.id !== result.session_id) {
|
|
if (originTask.id !== result.session_id) {
|
|
|
|
|
+ // 将 AbortController 从旧 ID 迁移到新 ID
|
|
|
|
|
+ const controller = taskAbortControllers.value.get(originTaskId);
|
|
|
|
|
+ if (controller) {
|
|
|
|
|
+ setTaskAbortController(originTaskId, null);
|
|
|
|
|
+ setTaskAbortController(result.session_id, controller);
|
|
|
|
|
+ }
|
|
|
taskList.value = taskList.value.filter((t) => t.id !== result.session_id);
|
|
taskList.value = taskList.value.filter((t) => t.id !== result.session_id);
|
|
|
originTask.id = result.session_id;
|
|
originTask.id = result.session_id;
|
|
|
historyLoadedTasks.value.add(result.session_id);
|
|
historyLoadedTasks.value.add(result.session_id);
|
|
@@ -1454,11 +1521,9 @@
|
|
|
}
|
|
}
|
|
|
throw error;
|
|
throw error;
|
|
|
} finally {
|
|
} finally {
|
|
|
- if (activeAbortController.value === controller) {
|
|
|
|
|
- activeAbortController.value = null;
|
|
|
|
|
- }
|
|
|
|
|
- streaming.value = false;
|
|
|
|
|
- // 任务在流结束时可能已被改名为后端 session_id,用发送时捕获的对象引用重置状态
|
|
|
|
|
|
|
+ // 任务在流结束时可能已被改名为后端 session_id,需要使用最终的 ID 清理状态
|
|
|
|
|
+ const finalTaskId = originTask?.id || originTaskId;
|
|
|
|
|
+ setTaskAbortController(finalTaskId, null);
|
|
|
if (originTask) originTask.isStreaming = false;
|
|
if (originTask) originTask.isStreaming = false;
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
@@ -1506,8 +1571,7 @@
|
|
|
if (originTask) originTask.isStreaming = true;
|
|
if (originTask) originTask.isStreaming = true;
|
|
|
|
|
|
|
|
const controller = new AbortController();
|
|
const controller = new AbortController();
|
|
|
- activeAbortController.value = controller;
|
|
|
|
|
- streaming.value = true;
|
|
|
|
|
|
|
+ setTaskAbortController(originTaskId, controller);
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
const result = await reviewPdfStream(
|
|
const result = await reviewPdfStream(
|
|
@@ -1534,6 +1598,12 @@
|
|
|
}
|
|
}
|
|
|
originTask.pendingSessionId = undefined;
|
|
originTask.pendingSessionId = undefined;
|
|
|
if (originTask.id !== result.session_id) {
|
|
if (originTask.id !== result.session_id) {
|
|
|
|
|
+ // 将 AbortController 从旧 ID 迁移到新 ID
|
|
|
|
|
+ const controller = taskAbortControllers.value.get(originTaskId);
|
|
|
|
|
+ if (controller) {
|
|
|
|
|
+ setTaskAbortController(originTaskId, null);
|
|
|
|
|
+ setTaskAbortController(result.session_id, controller);
|
|
|
|
|
+ }
|
|
|
taskList.value = taskList.value.filter((t) => t.id !== result.session_id);
|
|
taskList.value = taskList.value.filter((t) => t.id !== result.session_id);
|
|
|
originTask.id = result.session_id;
|
|
originTask.id = result.session_id;
|
|
|
historyLoadedTasks.value.add(result.session_id);
|
|
historyLoadedTasks.value.add(result.session_id);
|
|
@@ -1564,11 +1634,9 @@
|
|
|
}
|
|
}
|
|
|
throw error;
|
|
throw error;
|
|
|
} finally {
|
|
} finally {
|
|
|
- if (activeAbortController.value === controller) {
|
|
|
|
|
- activeAbortController.value = null;
|
|
|
|
|
- }
|
|
|
|
|
- streaming.value = false;
|
|
|
|
|
- // 任务在流结束时可能已被改名为后端 session_id,用发送时捕获的对象引用重置状态
|
|
|
|
|
|
|
+ // 任务在流结束时可能已被改名为后端 session_id,需要使用最终的 ID 清理状态
|
|
|
|
|
+ const finalTaskId = originTask?.id || originTaskId;
|
|
|
|
|
+ setTaskAbortController(finalTaskId, null);
|
|
|
if (originTask) originTask.isStreaming = false;
|
|
if (originTask) originTask.isStreaming = false;
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
@@ -1626,7 +1694,6 @@
|
|
|
|
|
|
|
|
// 如果是历史任务,加载其消息
|
|
// 如果是历史任务,加载其消息
|
|
|
if (targetTask.sessionId && !historyLoadedTasks.value.has(targetTask.id)) {
|
|
if (targetTask.sessionId && !historyLoadedTasks.value.has(targetTask.id)) {
|
|
|
- loading.value = true;
|
|
|
|
|
try {
|
|
try {
|
|
|
const res = await getDetail(targetTask.sessionId);
|
|
const res = await getDetail(targetTask.sessionId);
|
|
|
const messages = res?.messages || (Array.isArray(res) ? res : res?.data);
|
|
const messages = res?.messages || (Array.isArray(res) ? res : res?.data);
|
|
@@ -1636,8 +1703,6 @@
|
|
|
historyLoadedTasks.value.add(targetTask.id);
|
|
historyLoadedTasks.value.add(targetTask.id);
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('加载历史会话失败:', error);
|
|
console.error('加载历史会话失败:', error);
|
|
|
- } finally {
|
|
|
|
|
- loading.value = false;
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1751,8 +1816,8 @@
|
|
|
clearTimeout(downloadConfirmTimer);
|
|
clearTimeout(downloadConfirmTimer);
|
|
|
downloadConfirmTimer = null;
|
|
downloadConfirmTimer = null;
|
|
|
}
|
|
}
|
|
|
- // 组件卸载时主动断开流式连接
|
|
|
|
|
- activeAbortController.value?.abort();
|
|
|
|
|
|
|
+ // 组件卸载时主动断开所有任务的流式连接
|
|
|
|
|
+ taskAbortControllers.value.forEach((controller) => controller.abort());
|
|
|
});
|
|
});
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|