|
|
@@ -74,16 +74,16 @@
|
|
|
/>
|
|
|
|
|
|
<!-- 下载确认条 -->
|
|
|
- <div v-if="downloadConfirm" class="download-confirm-bar">
|
|
|
+ <div v-if="downloadConfirm && downloadConfirmTaskId === currentTaskId" class="download-confirm-bar">
|
|
|
<span class="download-confirm-text">是否下载 {{ downloadConfirm.filename }} 到本地?</span>
|
|
|
<div class="download-confirm-actions">
|
|
|
<button class="download-confirm-btn download-btn" @click="doDownload">下载</button>
|
|
|
- <button class="download-confirm-btn cancel-btn" @click="downloadConfirm = null">取消</button>
|
|
|
+ <button class="download-confirm-btn cancel-btn" @click="clearDownloadConfirm">取消</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 任务进度条 -->
|
|
|
- <TodoListBar v-if="currentTodos.length > 0" :todos="currentTodos" />
|
|
|
+ <TodoListBar v-if="showTodoBar" :todos="currentTodos" />
|
|
|
|
|
|
<!-- 输入区域 -->
|
|
|
<ChatInputArea
|
|
|
@@ -214,9 +214,28 @@
|
|
|
const pendingApprovalSessionId = ref('');
|
|
|
const pendingApprovalThreadId = ref('');
|
|
|
const downloadConfirm = ref<{ url: string; filename: string } | null>(null);
|
|
|
+ const downloadConfirmTaskId = ref('');
|
|
|
+ let downloadConfirmTimer: ReturnType<typeof setTimeout> | null = null;
|
|
|
+
|
|
|
+ const clearDownloadConfirm = () => {
|
|
|
+ downloadConfirm.value = null;
|
|
|
+ downloadConfirmTaskId.value = '';
|
|
|
+ if (downloadConfirmTimer) {
|
|
|
+ clearTimeout(downloadConfirmTimer);
|
|
|
+ downloadConfirmTimer = null;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const startDownloadConfirmTimer = () => {
|
|
|
+ if (downloadConfirmTimer) clearTimeout(downloadConfirmTimer);
|
|
|
+ downloadConfirmTimer = setTimeout(() => {
|
|
|
+ clearDownloadConfirm();
|
|
|
+ }, 10000);
|
|
|
+ };
|
|
|
const contextBreakdown = ref({ messages: 0, mcp: 0, skills: 0, system_prompt: 0, other: 0 });
|
|
|
|
|
|
const currentTodos = ref<Array<{ content: string; status: string }>>([]);
|
|
|
+ const showTodoBar = ref(false);
|
|
|
|
|
|
const syncTodoBarFromMessages = () => {
|
|
|
for (let i = messages.value.length - 1; i >= 0; i--) {
|
|
|
@@ -226,11 +245,13 @@
|
|
|
const step = msg.thinkingSteps[j];
|
|
|
if (step.type === 'updated_todo_list' && step.todos) {
|
|
|
currentTodos.value = [...step.todos];
|
|
|
+ showTodoBar.value = true;
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ showTodoBar.value = false;
|
|
|
currentTodos.value = [];
|
|
|
};
|
|
|
|
|
|
@@ -445,29 +466,18 @@
|
|
|
const approveTask = taskList.value.find((t) => t.sessionId === sessionId);
|
|
|
if (!approveTask) return;
|
|
|
|
|
|
- // 清除中断消息的 pendingApproval 状态,获取中断前累计时长和思考内容
|
|
|
const taskMessages = approveTask.messages;
|
|
|
const lastMsg = taskMessages[taskMessages.length - 1];
|
|
|
- const prevElapsedMs = lastMsg?.pendingApprovalData?.elapsedMs || 0;
|
|
|
- const prevThinkingContent = lastMsg?.pendingApprovalData?.prevThinkingContent || lastMsg?.thinkingContent || '';
|
|
|
- if (lastMsg && lastMsg.isPendingApproval) {
|
|
|
- lastMsg.isPendingApproval = false;
|
|
|
- }
|
|
|
+ if (!lastMsg || !lastMsg.isPendingApproval) return;
|
|
|
|
|
|
- const now = dayjs();
|
|
|
- const aiMsgIndex = taskMessages.length;
|
|
|
- const aiMsg: Message = {
|
|
|
- type: 'ai',
|
|
|
- content: '',
|
|
|
- thinkingContent: prevThinkingContent,
|
|
|
- time: now.format('HH:mm'),
|
|
|
- createdAt: now.toISOString(),
|
|
|
- isLoading: true,
|
|
|
- thinkingSteps: [],
|
|
|
- generateStartTime: Date.now(),
|
|
|
- baseDurationMs: prevElapsedMs,
|
|
|
- };
|
|
|
- taskMessages.push(aiMsg);
|
|
|
+ // 复用同一条消息继续渲染:清除审批状态,恢复 loading,重置前端计时起点
|
|
|
+ lastMsg.isPendingApproval = false;
|
|
|
+ lastMsg.pendingApprovalData = undefined;
|
|
|
+ lastMsg.isLoading = true;
|
|
|
+ lastMsg.generateStartTime = Date.now();
|
|
|
+ // thinkingSteps、thinkingContent、baseDurationMs 保持不变,后续继续累积
|
|
|
+
|
|
|
+ const aiMsgIndex = taskMessages.length - 1;
|
|
|
approveTask.messages = [...taskMessages];
|
|
|
if (currentTaskId.value === approveTask.id) {
|
|
|
messages.value = approveTask.messages;
|
|
|
@@ -496,9 +506,9 @@
|
|
|
} catch (error) {
|
|
|
console.error('审批继续执行失败:', error);
|
|
|
message.error('审批失败,请重试');
|
|
|
- if (aiMsg.isLoading) {
|
|
|
- aiMsg.isLoading = false;
|
|
|
- aiMsg.content = '抱歉,审批请求失败,请稍后重试。';
|
|
|
+ lastMsg.isLoading = false;
|
|
|
+ if (!lastMsg.content) {
|
|
|
+ lastMsg.content = '抱歉,审批请求失败,请稍后重试。';
|
|
|
}
|
|
|
} finally {
|
|
|
loading.value = false;
|
|
|
@@ -518,12 +528,20 @@
|
|
|
const rejectTask = taskList.value.find((t) => t.sessionId === sessionId);
|
|
|
if (!rejectTask) return;
|
|
|
|
|
|
- // 清除中断消息的 pendingApproval 状态
|
|
|
const taskMessages = rejectTask.messages;
|
|
|
const lastMsg = taskMessages[taskMessages.length - 1];
|
|
|
- if (lastMsg && lastMsg.isPendingApproval) {
|
|
|
- lastMsg.isPendingApproval = false;
|
|
|
- }
|
|
|
+ if (!lastMsg || !lastMsg.isPendingApproval) return;
|
|
|
+
|
|
|
+ // 复用同一条消息:清除审批状态,标记为已拒绝
|
|
|
+ lastMsg.isPendingApproval = false;
|
|
|
+ lastMsg.pendingApprovalData = undefined;
|
|
|
+ lastMsg.isLoading = false;
|
|
|
+ lastMsg.content = (lastMsg.content ? lastMsg.content + '\n\n' : '') + '已拒绝执行计划。';
|
|
|
+
|
|
|
+ rejectTask.messages = [...taskMessages];
|
|
|
+
|
|
|
+ pendingApprovalSessionId.value = '';
|
|
|
+ pendingApprovalThreadId.value = '';
|
|
|
|
|
|
try {
|
|
|
await chatResumeStream(
|
|
|
@@ -538,19 +556,6 @@
|
|
|
console.error('拒绝操作失败:', error);
|
|
|
}
|
|
|
|
|
|
- const now = dayjs();
|
|
|
- const rejectMsg: Message = {
|
|
|
- type: 'ai',
|
|
|
- content: '已拒绝执行计划。',
|
|
|
- time: now.format('HH:mm'),
|
|
|
- createdAt: now.toISOString(),
|
|
|
- };
|
|
|
- taskMessages.push(rejectMsg);
|
|
|
- rejectTask.messages = [...taskMessages];
|
|
|
-
|
|
|
- pendingApprovalSessionId.value = '';
|
|
|
- pendingApprovalThreadId.value = '';
|
|
|
-
|
|
|
if (currentTaskId.value === rejectTask.id) {
|
|
|
messages.value = rejectTask.messages;
|
|
|
await scrollToBottom();
|
|
|
@@ -736,7 +741,7 @@
|
|
|
document.body.appendChild(a);
|
|
|
a.click();
|
|
|
document.body.removeChild(a);
|
|
|
- downloadConfirm.value = null;
|
|
|
+ clearDownloadConfirm();
|
|
|
};
|
|
|
|
|
|
const downloadWordFile = (url: string) => {
|
|
|
@@ -953,6 +958,7 @@
|
|
|
});
|
|
|
}
|
|
|
currentTodos.value = [...(data.todos || [])];
|
|
|
+ showTodoBar.value = true;
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
@@ -981,6 +987,8 @@
|
|
|
});
|
|
|
if (data.download_url) {
|
|
|
downloadConfirm.value = { url: data.download_url, filename: data.filename || '文件' };
|
|
|
+ downloadConfirmTaskId.value = taskId;
|
|
|
+ startDownloadConfirmTimer();
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
@@ -993,9 +1001,10 @@
|
|
|
message: `${(data.message || '审查完成').replace(/(\d+ms)/g, '')}${data.duration_ms ? `(${(data.duration_ms / 1000).toFixed(0)}s)` : ''}${data.progress ? ` [${data.progress}]` : ''}`,
|
|
|
timestamp: Date.now(),
|
|
|
});
|
|
|
- // 用后端返回的时间替换前端计时,作为中断前的累计基准
|
|
|
+ // 累加后端返回的各段耗时,并重置前端计时起点,避免重复计算已验证的时间段
|
|
|
if (data.duration_ms) {
|
|
|
- aiMsg.baseDurationMs = data.duration_ms;
|
|
|
+ aiMsg.baseDurationMs = (aiMsg.baseDurationMs || 0) + data.duration_ms;
|
|
|
+ aiMsg.generateStartTime = Date.now();
|
|
|
}
|
|
|
if (data.preview) {
|
|
|
aiMsg.content += data.preview;
|
|
|
@@ -1423,6 +1432,10 @@
|
|
|
onBeforeUnmount(() => {
|
|
|
document.removeEventListener('mousemove', onDragMove);
|
|
|
document.removeEventListener('mouseup', onDragEnd);
|
|
|
+ if (downloadConfirmTimer) {
|
|
|
+ clearTimeout(downloadConfirmTimer);
|
|
|
+ downloadConfirmTimer = null;
|
|
|
+ }
|
|
|
});
|
|
|
</script>
|
|
|
|