|
|
@@ -91,6 +91,7 @@
|
|
|
v-model="inputMessage"
|
|
|
:pendingFile="pendingFile"
|
|
|
:loading="loading || !!pendingApprovalSessionId"
|
|
|
+ :streaming="streaming"
|
|
|
:contextUsed="contextUsed"
|
|
|
:contextMax="contextMax"
|
|
|
:contextPercent="contextPercent"
|
|
|
@@ -100,6 +101,7 @@
|
|
|
:taskList="taskList"
|
|
|
:currentTaskId="currentTaskId"
|
|
|
@send="handleSendMessage"
|
|
|
+ @stop="stopStreaming"
|
|
|
@file-upload="triggerFileUpload"
|
|
|
@file-change="handleFileUpload"
|
|
|
@remove-pending-file="removePendingFile"
|
|
|
@@ -334,8 +336,14 @@
|
|
|
|
|
|
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 stopStreaming = () => {
|
|
|
+ activeAbortController.value?.abort();
|
|
|
+ };
|
|
|
+
|
|
|
const getCurrentSessionId = (): string => {
|
|
|
const currentTask = taskList.value.find((t) => t.id === currentTaskId.value);
|
|
|
return currentTask?.sessionId || '';
|
|
|
@@ -496,12 +504,17 @@
|
|
|
showTodoBar.value = false;
|
|
|
currentTodos.value = [];
|
|
|
|
|
|
+ const controller = new AbortController();
|
|
|
+ activeAbortController.value = controller;
|
|
|
+ streaming.value = true;
|
|
|
+
|
|
|
try {
|
|
|
const result = await chatResumeStream(
|
|
|
{
|
|
|
session_id: sessionId,
|
|
|
thread_id: pendingApprovalThreadId.value || undefined,
|
|
|
action: 'approve',
|
|
|
+ signal: controller.signal,
|
|
|
},
|
|
|
(chunk: string) => {
|
|
|
parseSSEData(chunk, aiMsgIndex, approveTask.id);
|
|
|
@@ -513,13 +526,27 @@
|
|
|
setCurrentSessionId(result.session_id);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- console.error('审批继续执行失败:', error);
|
|
|
- message.error('审批失败,请重试');
|
|
|
- lastMsg.isLoading = false;
|
|
|
- if (!lastMsg.content) {
|
|
|
- lastMsg.content = '抱歉,审批请求失败,请稍后重试。';
|
|
|
+ if ((error as Error)?.name === 'AbortError') {
|
|
|
+ // 用户主动停止生成
|
|
|
+ lastMsg.isLoading = false;
|
|
|
+ // 后端未返回耗时,冻结前端伪计时
|
|
|
+ lastMsg.durationMs = (lastMsg.baseDurationMs || 0) + (lastMsg.generateStartTime ? Date.now() - lastMsg.generateStartTime : 0);
|
|
|
+ if (!lastMsg.content) {
|
|
|
+ lastMsg.content = '已停止生成。';
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ console.error('审批继续执行失败:', error);
|
|
|
+ message.error('审批失败,请重试');
|
|
|
+ lastMsg.isLoading = false;
|
|
|
+ if (!lastMsg.content) {
|
|
|
+ lastMsg.content = '抱歉,审批请求失败,请稍后重试。';
|
|
|
+ }
|
|
|
}
|
|
|
} finally {
|
|
|
+ if (activeAbortController.value === controller) {
|
|
|
+ activeAbortController.value = null;
|
|
|
+ }
|
|
|
+ streaming.value = false;
|
|
|
loading.value = false;
|
|
|
approveTask.isStreaming = false;
|
|
|
pendingApprovalSessionId.value = '';
|
|
|
@@ -549,6 +576,7 @@
|
|
|
|
|
|
rejectTask.messages = [...taskMessages];
|
|
|
|
|
|
+ const threadId = pendingApprovalThreadId.value || undefined;
|
|
|
pendingApprovalSessionId.value = '';
|
|
|
pendingApprovalThreadId.value = '';
|
|
|
|
|
|
@@ -556,7 +584,7 @@
|
|
|
await chatResumeStream(
|
|
|
{
|
|
|
session_id: sessionId,
|
|
|
- thread_id: pendingApprovalThreadId.value || undefined,
|
|
|
+ thread_id: threadId,
|
|
|
action: 'reject',
|
|
|
},
|
|
|
() => {}
|
|
|
@@ -1058,6 +1086,8 @@
|
|
|
timestamp: Date.now(),
|
|
|
});
|
|
|
aiMsg.isLoading = false;
|
|
|
+ // 后端未返回耗时,冻结前端伪计时
|
|
|
+ aiMsg.durationMs = (aiMsg.baseDurationMs || 0) + (aiMsg.generateStartTime ? Date.now() - aiMsg.generateStartTime : 0);
|
|
|
break;
|
|
|
|
|
|
case 'word_download':
|
|
|
@@ -1072,9 +1102,9 @@
|
|
|
aiMsg.sessionId = data.session_id;
|
|
|
fetchContext(data.session_id);
|
|
|
}
|
|
|
- if (data.duration_ms) {
|
|
|
- aiMsg.durationMs = (aiMsg.baseDurationMs || 0) + data.duration_ms;
|
|
|
- }
|
|
|
+ aiMsg.durationMs = data.duration_ms
|
|
|
+ ? (aiMsg.baseDurationMs || 0) + data.duration_ms
|
|
|
+ : (aiMsg.baseDurationMs || 0) + (aiMsg.generateStartTime ? Date.now() - aiMsg.generateStartTime : 0);
|
|
|
aiMsg.isLoading = false;
|
|
|
break;
|
|
|
|
|
|
@@ -1085,8 +1115,9 @@
|
|
|
break;
|
|
|
|
|
|
case 'interrupt': {
|
|
|
- // 优先使用 agent_done 已设置的后端时间,否则用前端计时
|
|
|
- const elapsedMs = aiMsg.baseDurationMs || (aiMsg.generateStartTime ? Date.now() - aiMsg.generateStartTime : 0);
|
|
|
+ // 后端各段耗时 + 前端当前段计时,吸收进 baseDurationMs 保证审批继续后计时连续
|
|
|
+ const elapsedMs = (aiMsg.baseDurationMs || 0) + (aiMsg.generateStartTime ? Date.now() - aiMsg.generateStartTime : 0);
|
|
|
+ aiMsg.baseDurationMs = elapsedMs;
|
|
|
aiMsg.isLoading = false;
|
|
|
aiMsg.isPendingApproval = true;
|
|
|
const interruptSessionId = data.session_id || streamingTask.sessionId;
|
|
|
@@ -1174,12 +1205,17 @@
|
|
|
const originTask = taskList.value.find((t) => t.id === originTaskId);
|
|
|
if (originTask) originTask.isStreaming = true;
|
|
|
|
|
|
+ const controller = new AbortController();
|
|
|
+ activeAbortController.value = controller;
|
|
|
+ streaming.value = true;
|
|
|
+
|
|
|
try {
|
|
|
const result = await unifiedStream(
|
|
|
{
|
|
|
message: userInput,
|
|
|
session_id: getCurrentSessionId() || undefined,
|
|
|
mode: editMode.value,
|
|
|
+ signal: controller.signal,
|
|
|
},
|
|
|
(chunk: string) => {
|
|
|
parseSSEData(chunk, aiMsgIndex, originTaskId);
|
|
|
@@ -1202,7 +1238,31 @@
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ } catch (error) {
|
|
|
+ // 用户主动停止生成
|
|
|
+ if ((error as Error)?.name === 'AbortError') {
|
|
|
+ const originTask = taskList.value.find((t) => t.id === originTaskId);
|
|
|
+ const arr = currentTaskId.value === originTaskId ? messages.value : originTask?.messages;
|
|
|
+ const aiMsg = arr?.[aiMsgIndex];
|
|
|
+ if (aiMsg) {
|
|
|
+ aiMsg.isLoading = false;
|
|
|
+ // 后端未返回耗时,冻结前端伪计时
|
|
|
+ aiMsg.durationMs = (aiMsg.baseDurationMs || 0) + (aiMsg.generateStartTime ? Date.now() - aiMsg.generateStartTime : 0);
|
|
|
+ if (!aiMsg.content) {
|
|
|
+ aiMsg.content = '已停止生成。';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (originTask && currentTaskId.value !== originTaskId) {
|
|
|
+ originTask.messages = [...(arr || [])];
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ throw error;
|
|
|
} finally {
|
|
|
+ if (activeAbortController.value === controller) {
|
|
|
+ activeAbortController.value = null;
|
|
|
+ }
|
|
|
+ streaming.value = false;
|
|
|
const originTask = taskList.value.find((t) => t.id === originTaskId);
|
|
|
if (originTask) originTask.isStreaming = false;
|
|
|
}
|
|
|
@@ -1251,6 +1311,10 @@
|
|
|
const originTask = taskList.value.find((t) => t.id === originTaskId);
|
|
|
if (originTask) originTask.isStreaming = true;
|
|
|
|
|
|
+ const controller = new AbortController();
|
|
|
+ activeAbortController.value = controller;
|
|
|
+ streaming.value = true;
|
|
|
+
|
|
|
try {
|
|
|
const result = await reviewPdfStream(
|
|
|
{
|
|
|
@@ -1258,6 +1322,7 @@
|
|
|
session_id: getCurrentSessionId() || undefined,
|
|
|
message: userInput || undefined,
|
|
|
mode: editMode.value,
|
|
|
+ signal: controller.signal,
|
|
|
},
|
|
|
(chunk: string) => {
|
|
|
parseSSEData(chunk, aiMsgIndex, originTaskId);
|
|
|
@@ -1281,7 +1346,31 @@
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ } catch (error) {
|
|
|
+ // 用户主动停止生成
|
|
|
+ if ((error as Error)?.name === 'AbortError') {
|
|
|
+ const originTask = taskList.value.find((t) => t.id === originTaskId);
|
|
|
+ const arr = currentTaskId.value === originTaskId ? messages.value : originTask?.messages;
|
|
|
+ const aiMsg = arr?.[aiMsgIndex];
|
|
|
+ if (aiMsg) {
|
|
|
+ aiMsg.isLoading = false;
|
|
|
+ // 后端未返回耗时,冻结前端伪计时
|
|
|
+ aiMsg.durationMs = (aiMsg.baseDurationMs || 0) + (aiMsg.generateStartTime ? Date.now() - aiMsg.generateStartTime : 0);
|
|
|
+ if (!aiMsg.content) {
|
|
|
+ aiMsg.content = '已停止生成。';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (originTask && currentTaskId.value !== originTaskId) {
|
|
|
+ originTask.messages = [...(arr || [])];
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ throw error;
|
|
|
} finally {
|
|
|
+ if (activeAbortController.value === controller) {
|
|
|
+ activeAbortController.value = null;
|
|
|
+ }
|
|
|
+ streaming.value = false;
|
|
|
const originTask = taskList.value.find((t) => t.id === originTaskId);
|
|
|
if (originTask) originTask.isStreaming = false;
|
|
|
}
|
|
|
@@ -1461,6 +1550,8 @@
|
|
|
clearTimeout(downloadConfirmTimer);
|
|
|
downloadConfirmTimer = null;
|
|
|
}
|
|
|
+ // 组件卸载时主动断开流式连接
|
|
|
+ activeAbortController.value?.abort();
|
|
|
});
|
|
|
</script>
|
|
|
|