mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-22 11:15:47 +00:00
adding task start countdown to indicate timeout auto start process (#1621)
This commit is contained in:
parent
29ecdb6267
commit
b2ac129cd8
15 changed files with 118 additions and 27 deletions
|
|
@ -15,6 +15,7 @@
|
|||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
/**
|
||||
|
|
@ -26,16 +27,37 @@ export interface BoxHeaderConfirmProps {
|
|||
onEdit?: () => void;
|
||||
className?: string;
|
||||
loading?: boolean;
|
||||
autoStartDeadline?: number | null;
|
||||
}
|
||||
|
||||
export const BoxHeaderConfirm = ({
|
||||
subtitle,
|
||||
subtitle: _subtitle,
|
||||
onStartTask,
|
||||
onEdit,
|
||||
className,
|
||||
loading = false,
|
||||
autoStartDeadline = null,
|
||||
}: BoxHeaderConfirmProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [remainingSeconds, setRemainingSeconds] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!autoStartDeadline) {
|
||||
setRemainingSeconds(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const updateRemainingSeconds = () => {
|
||||
setRemainingSeconds(
|
||||
Math.max(0, Math.ceil((autoStartDeadline - Date.now()) / 1000))
|
||||
);
|
||||
};
|
||||
|
||||
updateRemainingSeconds();
|
||||
const intervalId = window.setInterval(updateRemainingSeconds, 250);
|
||||
return () => window.clearInterval(intervalId);
|
||||
}, [autoStartDeadline]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
|
|
@ -56,15 +78,27 @@ export const BoxHeaderConfirm = ({
|
|||
<ChevronLeft />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="success"
|
||||
size="sm"
|
||||
className="rounded-full"
|
||||
onClick={onStartTask}
|
||||
disabled={loading}
|
||||
>
|
||||
{t('chat.start-task')}
|
||||
</Button>
|
||||
<div className="gap-2 flex items-center">
|
||||
{remainingSeconds !== null && (
|
||||
<span
|
||||
className="text-body-xs font-medium text-ds-text-success-default-default whitespace-nowrap tabular-nums"
|
||||
aria-label={t('chat.auto-start-in', {
|
||||
seconds: remainingSeconds,
|
||||
})}
|
||||
>
|
||||
{t('chat.auto-start-in', { seconds: remainingSeconds })}
|
||||
</span>
|
||||
)}
|
||||
<Button
|
||||
variant="success"
|
||||
size="sm"
|
||||
className="rounded-full"
|
||||
onClick={onStartTask}
|
||||
disabled={loading}
|
||||
>
|
||||
{t('chat.start-task')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -85,7 +119,7 @@ export interface BoxHeaderSaveProps {
|
|||
}
|
||||
|
||||
export const BoxHeaderSave = ({
|
||||
subtitle,
|
||||
subtitle: _subtitle,
|
||||
onSave,
|
||||
onEdit,
|
||||
className,
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ interface BottomBoxProps {
|
|||
|
||||
// Subtask-related props (confirm/save state)
|
||||
subtitle?: string;
|
||||
autoStartDeadline?: number | null;
|
||||
|
||||
// Action buttons
|
||||
onStartTask?: () => void;
|
||||
|
|
@ -67,6 +68,7 @@ export default function BottomBox({
|
|||
queuedMessages = [],
|
||||
onRemoveQueuedMessage,
|
||||
subtitle,
|
||||
autoStartDeadline,
|
||||
onStartTask,
|
||||
onSavePlan,
|
||||
onEdit,
|
||||
|
|
@ -105,6 +107,7 @@ export default function BottomBox({
|
|||
onStartTask={onStartTask}
|
||||
onEdit={onEdit}
|
||||
loading={loading}
|
||||
autoStartDeadline={autoStartDeadline}
|
||||
/>
|
||||
)}
|
||||
{state === 'save' && (
|
||||
|
|
|
|||
|
|
@ -1008,6 +1008,9 @@ export default function ChatBox(): JSX.Element {
|
|||
})()
|
||||
: chatStore.tasks[chatStore.activeTaskId]?.summaryTask
|
||||
}
|
||||
autoStartDeadline={
|
||||
chatStore.tasks[chatStore.activeTaskId]?.autoConfirmDeadline
|
||||
}
|
||||
onStartTask={() => handleConfirmTask()}
|
||||
onSavePlan={async () => {
|
||||
if (chatStore.activeTaskId) {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@
|
|||
"splitting-tasks": "تقسيم المهام",
|
||||
"working-on-tasks-for": "العمل على المهام لمدة <elapsed>{{time}}</elapsed>",
|
||||
"worked-for": "عُمل لمدة <elapsed>{{time}}</elapsed>",
|
||||
"start-task": "بدء المهمة",
|
||||
"start-task": "نفّذ الآن",
|
||||
"auto-start-in": "بدء تلقائي خلال {{seconds}}ث",
|
||||
"message-cannot-be-empty": "لا يمكن أن تكون الرسالة فارغة",
|
||||
"remove-file": "إزالة الملف",
|
||||
"drop-files-to-attach": "أسقط الملفات للإرفاق",
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@
|
|||
"splitting-tasks": "Aufgaben teilen",
|
||||
"working-on-tasks-for": "Arbeitet an Aufgaben seit <elapsed>{{time}}</elapsed>",
|
||||
"worked-for": "Gearbeitet für <elapsed>{{time}}</elapsed>",
|
||||
"start-task": "Aufgabe starten",
|
||||
"start-task": "Jetzt ausführen",
|
||||
"auto-start-in": "Auto-Start in {{seconds}}s",
|
||||
"message-cannot-be-empty": "Nachricht darf nicht leer sein",
|
||||
"remove-file": "Datei entfernen",
|
||||
"drop-files-to-attach": "Dateien zum Anhängen ablegen",
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@
|
|||
"splitting-tasks": "Splitting Tasks",
|
||||
"working-on-tasks-for": "Working on tasks for <elapsed>{{time}}</elapsed>",
|
||||
"worked-for": "Worked for <elapsed>{{time}}</elapsed>",
|
||||
"start-task": "Start Task",
|
||||
"start-task": "Execute Now",
|
||||
"auto-start-in": "Auto-start in {{seconds}}s",
|
||||
"message-cannot-be-empty": "Message cannot be empty",
|
||||
"remove-file": "Remove file",
|
||||
"drop-files-to-attach": "Drop files to attach",
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@
|
|||
"splitting-tasks": "Dividiendo tareas",
|
||||
"working-on-tasks-for": "Trabajando en tareas durante <elapsed>{{time}}</elapsed>",
|
||||
"worked-for": "Trabajó durante <elapsed>{{time}}</elapsed>",
|
||||
"start-task": "Iniciar tarea",
|
||||
"start-task": "Ejecutar ahora",
|
||||
"auto-start-in": "Inicio automático en {{seconds}}s",
|
||||
"message-cannot-be-empty": "El mensaje no puede estar vacío",
|
||||
"remove-file": "Eliminar archivo",
|
||||
"drop-files-to-attach": "Arrastra archivos para adjuntar",
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@
|
|||
"splitting-tasks": "Division des tâches",
|
||||
"working-on-tasks-for": "Travaille sur les tâches depuis <elapsed>{{time}}</elapsed>",
|
||||
"worked-for": "A travaillé pendant <elapsed>{{time}}</elapsed>",
|
||||
"start-task": "Démarrer la tâche",
|
||||
"start-task": "Exécuter maintenant",
|
||||
"auto-start-in": "Démarrage auto dans {{seconds}}s",
|
||||
"message-cannot-be-empty": "Le message ne peut pas être vide",
|
||||
"remove-file": "Supprimer le fichier",
|
||||
"drop-files-to-attach": "Déposez les fichiers à joindre",
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@
|
|||
"splitting-tasks": "Suddivisione dei compiti",
|
||||
"working-on-tasks-for": "Lavoro alle attività da <elapsed>{{time}}</elapsed>",
|
||||
"worked-for": "Ha lavorato per <elapsed>{{time}}</elapsed>",
|
||||
"start-task": "Avvia compito",
|
||||
"start-task": "Esegui ora",
|
||||
"auto-start-in": "Avvio automatico tra {{seconds}}s",
|
||||
"message-cannot-be-empty": "Il messaggio non può essere vuoto",
|
||||
"remove-file": "Rimuovi file",
|
||||
"drop-files-to-attach": "Trascina i file da allegare",
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@
|
|||
"splitting-tasks": "タスク分割",
|
||||
"working-on-tasks-for": "タスクを実行中 · <elapsed>{{time}}</elapsed>",
|
||||
"worked-for": "作業時間 <elapsed>{{time}}</elapsed>",
|
||||
"start-task": "タスク開始",
|
||||
"start-task": "今すぐ実行",
|
||||
"auto-start-in": "{{seconds}}秒後に自動開始",
|
||||
"message-cannot-be-empty": "メッセージは空にできません",
|
||||
"remove-file": "ファイルを削除",
|
||||
"drop-files-to-attach": "ファイルをドロップして添付",
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@
|
|||
"splitting-tasks": "작업 분할",
|
||||
"working-on-tasks-for": "작업 진행 중 · <elapsed>{{time}}</elapsed>",
|
||||
"worked-for": "작업 시간 <elapsed>{{time}}</elapsed>",
|
||||
"start-task": "작업 시작",
|
||||
"start-task": "지금 실행",
|
||||
"auto-start-in": "{{seconds}}초 후 자동 시작",
|
||||
"message-cannot-be-empty": "메시지는 비워둘 수 없습니다",
|
||||
"remove-file": "파일 제거",
|
||||
"drop-files-to-attach": "첨부할 파일을 여기에 놓으세요",
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@
|
|||
"splitting-tasks": "Разделение задач",
|
||||
"working-on-tasks-for": "Работа над задачами · <elapsed>{{time}}</elapsed>",
|
||||
"worked-for": "Работал <elapsed>{{time}}</elapsed>",
|
||||
"start-task": "Начать задачу",
|
||||
"start-task": "Выполнить сейчас",
|
||||
"auto-start-in": "Автозапуск через {{seconds}} с",
|
||||
"message-cannot-be-empty": "Сообщение не может быть пустым",
|
||||
"remove-file": "Удалить файл",
|
||||
"drop-files-to-attach": "Перетащите файлы для прикрепления",
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@
|
|||
"splitting-tasks": "拆分任务",
|
||||
"working-on-tasks-for": "正在处理任务 · <elapsed>{{time}}</elapsed>",
|
||||
"worked-for": "已工作 <elapsed>{{time}}</elapsed>",
|
||||
"start-task": "开始任务",
|
||||
"start-task": "立即执行",
|
||||
"auto-start-in": "{{seconds}}秒后自动开始",
|
||||
"message-cannot-be-empty": "消息不能为空",
|
||||
"remove-file": "移除文件",
|
||||
"drop-files-to-attach": "拖放文件以附加",
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@
|
|||
"splitting-tasks": "拆分任務",
|
||||
"working-on-tasks-for": "正在處理任務 · <elapsed>{{time}}</elapsed>",
|
||||
"worked-for": "已工作 <elapsed>{{time}}</elapsed>",
|
||||
"start-task": "開始任務",
|
||||
"start-task": "立即執行",
|
||||
"auto-start-in": "{{seconds}}秒後自動開始",
|
||||
"message-cannot-be-empty": "訊息不能為空",
|
||||
"remove-file": "移除文件",
|
||||
"drop-files-to-attach": "拖放文件以附加",
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ interface Task {
|
|||
snapshotsTemp: any[];
|
||||
isTakeControl: boolean;
|
||||
planDirty: boolean;
|
||||
autoConfirmDeadline: number | null;
|
||||
isContextExceeded?: boolean;
|
||||
// Streaming decompose text - stored separately to avoid frequent re-renders
|
||||
streamingDecomposeText: string;
|
||||
|
|
@ -479,6 +480,7 @@ export interface ChatStore {
|
|||
setIsTakeControl: (taskId: string, isTakeControl: boolean) => void;
|
||||
setSnapshotsTemp: (taskId: string, snapshot: any) => void;
|
||||
setPlanDirty: (taskId: string, dirty: boolean) => void;
|
||||
setAutoConfirmDeadline: (taskId: string, deadline: number | null) => void;
|
||||
savePlan: (taskId: string) => Promise<void>;
|
||||
clearTasks: () => void;
|
||||
setIsContextExceeded: (taskId: string, isContextExceeded: boolean) => void;
|
||||
|
|
@ -499,6 +501,7 @@ export type VanillaChatStore = {
|
|||
|
||||
// Track auto-confirm timers per task to avoid reusing stale timers across rounds
|
||||
const autoConfirmTimers: Record<string, ReturnType<typeof setTimeout>> = {};
|
||||
const AUTO_CONFIRM_TIMEOUT_MS = 30000;
|
||||
|
||||
// Track active SSE connections for proper cleanup
|
||||
const activeSSEControllers: Record<string, AbortController> = {};
|
||||
|
|
@ -849,6 +852,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
|
|||
snapshotsTemp: [],
|
||||
isTakeControl: false,
|
||||
planDirty: false,
|
||||
autoConfirmDeadline: null,
|
||||
streamingDecomposeText: '',
|
||||
executionId: undefined,
|
||||
},
|
||||
|
|
@ -877,6 +881,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
|
|||
clearTimeout(autoConfirmTimers[taskId]);
|
||||
delete autoConfirmTimers[taskId];
|
||||
}
|
||||
get().setAutoConfirmDeadline(taskId, null);
|
||||
} catch (error) {
|
||||
console.warn('Error clearing auto-confirm timer in removeTask:', error);
|
||||
}
|
||||
|
|
@ -948,6 +953,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
|
|||
clearTimeout(autoConfirmTimers[taskId]);
|
||||
delete autoConfirmTimers[taskId];
|
||||
}
|
||||
get().setAutoConfirmDeadline(taskId, null);
|
||||
} catch (error) {
|
||||
console.warn('Error clearing auto-confirm timer in stopTask:', error);
|
||||
}
|
||||
|
|
@ -1599,6 +1605,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
|
|||
setStreamingDecomposeText,
|
||||
clearStreamingDecomposeText,
|
||||
setPlanDirty,
|
||||
setAutoConfirmDeadline,
|
||||
} = getCurrentChatStore();
|
||||
|
||||
currentTaskId = getCurrentTaskId();
|
||||
|
|
@ -1694,18 +1701,27 @@ const chatStore = (initial?: Partial<ChatStore>) =>
|
|||
clearTimeout(autoConfirmTimers[currentTaskId]);
|
||||
delete autoConfirmTimers[currentTaskId];
|
||||
}
|
||||
setAutoConfirmDeadline(currentTaskId, null);
|
||||
} catch (error) {
|
||||
console.warn('Error clearing auto-confirm timer:', error);
|
||||
}
|
||||
|
||||
// 30 seconds auto confirm
|
||||
try {
|
||||
setAutoConfirmDeadline(
|
||||
currentTaskId,
|
||||
Date.now() + AUTO_CONFIRM_TIMEOUT_MS
|
||||
);
|
||||
autoConfirmTimers[currentTaskId] = setTimeout(() => {
|
||||
try {
|
||||
const currentStore = getCurrentChatStore();
|
||||
const currentId = getCurrentTaskId();
|
||||
const { tasks, handleConfirmTask, setPlanDirty } =
|
||||
currentStore;
|
||||
const {
|
||||
tasks,
|
||||
handleConfirmTask,
|
||||
setPlanDirty,
|
||||
setAutoConfirmDeadline,
|
||||
} = currentStore;
|
||||
const message = tasks[currentId].messages.findLast(
|
||||
(item) => item.step === AgentStep.TO_SUB_TASKS
|
||||
);
|
||||
|
|
@ -1721,6 +1737,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
|
|||
handleConfirmTask(project_id, currentId, type);
|
||||
}
|
||||
setPlanDirty(currentId, false);
|
||||
setAutoConfirmDeadline(currentId, null);
|
||||
delete autoConfirmTimers[currentId];
|
||||
} catch (error) {
|
||||
console.error(
|
||||
|
|
@ -1728,11 +1745,13 @@ const chatStore = (initial?: Partial<ChatStore>) =>
|
|||
error
|
||||
);
|
||||
// Clean up the timer reference even if there's an error
|
||||
setAutoConfirmDeadline(currentTaskId, null);
|
||||
delete autoConfirmTimers[currentTaskId];
|
||||
}
|
||||
}, 30000);
|
||||
}, AUTO_CONFIRM_TIMEOUT_MS);
|
||||
} catch (error) {
|
||||
console.error('Error setting auto-confirm timer:', error);
|
||||
setAutoConfirmDeadline(currentTaskId, null);
|
||||
}
|
||||
|
||||
const newNoticeMessage: Message = {
|
||||
|
|
@ -3437,6 +3456,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
|
|||
setTaskInfo,
|
||||
setTaskRunning,
|
||||
setPlanDirty,
|
||||
setAutoConfirmDeadline,
|
||||
} = get();
|
||||
if (!taskId) return;
|
||||
|
||||
|
|
@ -3446,6 +3466,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
|
|||
clearTimeout(autoConfirmTimers[taskId]);
|
||||
delete autoConfirmTimers[taskId];
|
||||
}
|
||||
setAutoConfirmDeadline(taskId, null);
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'Error clearing auto-confirm timer in handleConfirmTask:',
|
||||
|
|
@ -3841,8 +3862,23 @@ const chatStore = (initial?: Partial<ChatStore>) =>
|
|||
},
|
||||
}));
|
||||
},
|
||||
setAutoConfirmDeadline(taskId: string, deadline: number | null) {
|
||||
set((state) => {
|
||||
if (!state.tasks[taskId]) return state;
|
||||
return {
|
||||
...state,
|
||||
tasks: {
|
||||
...state.tasks,
|
||||
[taskId]: {
|
||||
...state.tasks[taskId],
|
||||
autoConfirmDeadline: deadline,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
async savePlan(taskId: string) {
|
||||
const { tasks, setPlanDirty } = get();
|
||||
const { tasks, setPlanDirty, setAutoConfirmDeadline } = get();
|
||||
const task = tasks[taskId];
|
||||
if (!task) return;
|
||||
try {
|
||||
|
|
@ -3872,10 +3908,12 @@ const chatStore = (initial?: Partial<ChatStore>) =>
|
|||
clearTimeout(autoConfirmTimers[taskId]);
|
||||
delete autoConfirmTimers[taskId];
|
||||
}
|
||||
setAutoConfirmDeadline(taskId, null);
|
||||
} catch (error) {
|
||||
console.warn('Error clearing auto-confirm timer in savePlan:', error);
|
||||
}
|
||||
|
||||
setAutoConfirmDeadline(taskId, Date.now() + AUTO_CONFIRM_TIMEOUT_MS);
|
||||
autoConfirmTimers[taskId] = setTimeout(() => {
|
||||
try {
|
||||
const latestState = get();
|
||||
|
|
@ -3894,12 +3932,14 @@ const chatStore = (initial?: Partial<ChatStore>) =>
|
|||
latestState.handleConfirmTask(projectId, taskId);
|
||||
}
|
||||
latestState.setPlanDirty(taskId, false);
|
||||
latestState.setAutoConfirmDeadline(taskId, null);
|
||||
delete autoConfirmTimers[taskId];
|
||||
} catch (error) {
|
||||
console.error('Error in savePlan auto-confirm handler:', error);
|
||||
get().setAutoConfirmDeadline(taskId, null);
|
||||
delete autoConfirmTimers[taskId];
|
||||
}
|
||||
}, 30000);
|
||||
}, AUTO_CONFIRM_TIMEOUT_MS);
|
||||
},
|
||||
clearTasks: () => {
|
||||
const { create } = get();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue