enhance: refine the task trigger flow in replay, and normal flow

This commit is contained in:
a7m-1st 2026-02-05 22:43:01 +03:00
parent 559615d4f0
commit b71d5decc4
4 changed files with 180 additions and 114 deletions

View file

@ -119,7 +119,11 @@ export default function ChatBox(): JSX.Element {
const navigate = useNavigate();
const handleSend = async (messageStr?: string, taskId?: string) => {
const handleSend = async (
messageStr?: string,
taskId?: string,
executionId?: string
) => {
const _taskId = taskId || chatStore.activeTaskId;
if (message.trim() === '' && !messageStr) return;
const tempMessageContent = messageStr || message;
@ -254,7 +258,8 @@ export default function ChatBox(): JSX.Element {
undefined,
undefined,
tempMessageContent,
attachesToSend
attachesToSend,
executionId
);
} catch (err: any) {
console.error('Failed to start task:', err);
@ -274,6 +279,7 @@ export default function ChatBox(): JSX.Element {
//Generate nextId in case new chatStore is created to sync with the backend beforehand
const nextTaskId = generateUniqueId();
chatStore.setNextTaskId(nextTaskId);
chatStore.setNextExecutionId(taskId as string, executionId);
// Use improve endpoint (POST /chat/{id}) - {id} is project_id
// This reuses the existing SSE connection and step_solve loop
@ -329,7 +335,8 @@ export default function ChatBox(): JSX.Element {
undefined,
undefined,
tempMessageContent,
attachesToSend
attachesToSend,
executionId
);
chatStore.setHasWaitComfirm(_taskId as string, true);
} catch (err: any) {
@ -784,7 +791,7 @@ export default function ChatBox(): JSX.Element {
// Dequeue the task from triggerTaskStore (marks as running)
taskToProcess = triggerTaskStoreState.dequeueTask();
if (!taskToProcess) return;
// Skip if we've already started processing this trigger task
if (processedTriggerTaskRef.current === taskToProcess.id) {
console.log(
@ -803,8 +810,7 @@ export default function ChatBox(): JSX.Element {
// Mark this trigger task as being processed to prevent duplicate sends
processedTriggerTaskRef.current = taskToProcess.id;
// Register execution mapping for this task
// The executionId from the queued task will be used for tracking
// Register execution mapping for this task (for external tracking)
if (taskToProcess.executionId && projectStore.activeProjectId) {
const targetTaskId = chatStore.nextTaskId || taskId;
@ -817,10 +823,10 @@ export default function ChatBox(): JSX.Element {
}
// Process the queued message via handleSend
// Use the formattedMessage from the trigger task
// Pass executionId so startTask can set it on the new task
const messageContent =
taskToProcess.formattedMessage || taskToProcess.taskPrompt;
handleSend(messageContent);
handleSend(messageContent, undefined, taskToProcess.executionId);
}, [
projectStore.activeProjectId,
triggerTaskStoreState.taskQueue,
@ -1088,7 +1094,7 @@ export default function ChatBox(): JSX.Element {
href="https://www.eigent.ai/terms-of-use"
target="_blank"
className="text-text-information underline"
onClick={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()} rel="noreferrer"
>
{t('layout.terms-of-use')}
</a>{' '}
@ -1097,7 +1103,7 @@ export default function ChatBox(): JSX.Element {
href="https://www.eigent.ai/privacy-policy"
target="_blank"
className="text-text-information underline"
onClick={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()} rel="noreferrer"
>
{t('layout.privacy-policy')}
</a>

View file

@ -1,5 +1,5 @@
import { proxyFetchGet } from '@/api/http';
import { useProjectStore } from '@/store/projectStore';
import { ProjectType, useProjectStore } from '@/store/projectStore';
import { useTriggerStore } from '@/store/triggerStore';
import {
TriggeredTask,
@ -87,7 +87,14 @@ export function useTriggerTaskExecutor() {
});
// Use replayProject to load the project from history
store.replayProject(taskIdsList, question, projectId, historyId);
// store.replayProject(taskIdsList, question, projectId, historyId);
store.createProject(
`Trigger Project ${question}`,
`No tasks to replay`,
projectId,
ProjectType.NORMAL,
historyId
);
return true;
} catch (error) {

View file

@ -26,6 +26,7 @@ import {
import { showCreditsToast } from '@/components/Toast/creditsToast';
import { showStorageToast } from '@/components/Toast/storageToast';
import { generateUniqueId, uploadLog } from '@/lib';
import { proxyUpdateTriggerExecution } from '@/service/triggerApi';
import { ExecutionStatus } from '@/types';
import {
AgentMessageStatus,
@ -77,6 +78,9 @@ interface Task {
isContextExceeded?: boolean;
// Streaming decompose text - stored separately to avoid frequent re-renders
streamingDecomposeText: string;
// Trigger execution ID for tracking trigger task completion
executionId?: string;
nextExecutionId?: string;
}
export interface ChatStore {
@ -96,7 +100,8 @@ export interface ChatStore {
shareToken?: string,
delayTime?: number,
messageContent?: string,
messageAttaches?: File[]
messageAttaches?: File[],
executionId?: string
) => Promise<void>;
handleConfirmTask: (
project_id: string,
@ -170,6 +175,11 @@ export interface ChatStore {
setNextTaskId: (taskId: string | null) => void;
setStreamingDecomposeText: (taskId: string, text: string) => void;
clearStreamingDecomposeText: (taskId: string) => void;
setExecutionId: (taskId: string, executionId: string | undefined) => void;
setNextExecutionId: (
taskId: string,
nextExecutionId: string | undefined
) => void;
}
export type VanillaChatStore = {
@ -228,23 +238,95 @@ const ttftTracking: Record<
{ confirmedAt: number; firstTokenLogged: boolean }
> = {};
// Helper function to update trigger execution status using triggerTaskStore
// Track which executionIds have already been reported to prevent duplicate updates
const reportedExecutionIds = new Set<string>();
// Helper function to update trigger execution status using executionId from task
const updateTriggerExecutionStatus = async (
_projectStore: any,
_project_id: string | null | undefined,
chatStoreState: ChatStore,
projectId: string | null | undefined,
currentTaskId: string,
status: import('@/types').ExecutionStatus,
tokens: number,
errorMessage?: string
) => {
// Use triggerTaskStore for reliable execution status tracking
const triggerTaskStore = useTriggerTaskStore.getState();
await triggerTaskStore.updateExecutionStatus(
console.log('[updateTriggerExecutionStatus] Called with:', {
projectId,
currentTaskId,
status,
tokens,
errorMessage
);
});
// Get executionId directly from the task
const executionId = chatStoreState.tasks[currentTaskId]?.executionId;
if (!executionId) {
// No executionId means this is not a trigger-initiated task, skip silently
console.log(
'[updateTriggerExecutionStatus] No executionId found for task:',
currentTaskId,
'- skipping (not a trigger-initiated task)'
);
return;
}
// Check if this execution has already been reported
if (reportedExecutionIds.has(executionId)) {
console.log(
'[updateTriggerExecutionStatus] Execution already reported:',
executionId
);
return;
}
try {
// Mark as reported to prevent duplicate updates
reportedExecutionIds.add(executionId);
// Call the API to update execution status
await proxyUpdateTriggerExecution(
executionId,
{
status,
completed_at: new Date().toISOString(),
...(errorMessage && { error_message: errorMessage }),
tokens_used: tokens,
},
{ projectId: projectId || undefined }
);
console.log(
'[updateTriggerExecutionStatus] Execution status updated:',
executionId,
'->',
status
);
// Complete or fail the current trigger task in triggerTaskStore
const triggerTaskStore = useTriggerTaskStore.getState();
const currentTask = triggerTaskStore.currentTask;
if (currentTask && currentTask.executionId === executionId) {
if (status === ExecutionStatus.Completed) {
triggerTaskStore.completeTask(currentTask.id);
} else if (
status === ExecutionStatus.Failed ||
status === ExecutionStatus.Cancelled
) {
triggerTaskStore.failTask(
currentTask.id,
errorMessage || 'Task failed'
);
}
}
} catch (err) {
console.warn(
`[updateTriggerExecutionStatus] Failed to update execution status to ${status}:`,
err
);
// Remove from reported set so it can be retried
reportedExecutionIds.delete(executionId);
}
};
const chatStore = (initial?: Partial<ChatStore>) =>
@ -292,6 +374,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
isTakeControl: false,
isTaskEdit: false,
streamingDecomposeText: '',
executionId: undefined,
},
},
}));
@ -426,7 +509,8 @@ const chatStore = (initial?: Partial<ChatStore>) =>
shareToken?: string,
delayTime?: number,
messageContent?: string,
messageAttaches?: File[]
messageAttaches?: File[],
executionId?: string
) => {
// ✅ Wait for backend to be ready before starting task (except for replay/share)
if (!type || type === 'normal') {
@ -483,6 +567,11 @@ const chatStore = (initial?: Partial<ChatStore>) =>
targetChatStore = newChatResult.chatStore;
targetChatStore.getState().setIsPending(newTaskId, true);
// Set executionId if this is a trigger-initiated task
if (executionId) {
targetChatStore.getState().setExecutionId(newTaskId, executionId);
}
//From handleSend if message is given
// Add the message to the new chatStore if provided
if (messageContent) {
@ -850,6 +939,16 @@ const chatStore = (initial?: Partial<ChatStore>) =>
updateLockedReferences(newChatStore, newTaskId);
newChatStore.getState().setIsPending(newTaskId, false);
// If nextExecutionId exists, pass it to new task
if (previousChatStore.tasks[currentTaskId]?.nextExecutionId) {
newChatStore
.getState()
.setExecutionId(
newTaskId,
previousChatStore.tasks[currentTaskId]?.nextExecutionId
);
}
if (type === 'replay') {
newChatStore
.getState()
@ -918,7 +1017,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
const currentTaskId = getCurrentTaskId();
// Update trigger execution status to Completed for connection closed by server
updateTriggerExecutionStatus(
projectStore,
getCurrentChatStore(),
project_id,
currentTaskId,
ExecutionStatus.Running,
@ -1268,6 +1367,17 @@ const chatStore = (initial?: Partial<ChatStore>) =>
step: AgentStep.WAIT_CONFIRM,
isConfirm: false,
});
// Update trigger execution status to Completed for simple question/answer flow
// This handles cases where the task ends with wait_confirm instead of the end step
updateTriggerExecutionStatus(
currentChatStore,
project_id,
currentTaskId,
ExecutionStatus.Completed,
currentChatStore.tasks[currentTaskId]?.tokens || 0
);
return;
}
// Task State
@ -1465,7 +1575,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
) {
// This is a quick reply - update trigger execution status to Completed
updateTriggerExecutionStatus(
projectStore,
getCurrentChatStore(),
project_id,
currentTaskId,
ExecutionStatus.Completed,
@ -1984,7 +2094,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
uploadLog(currentTaskId, type);
// Update trigger execution status to Failed on error
updateTriggerExecutionStatus(
projectStore,
getCurrentChatStore(),
project_id,
currentTaskId,
ExecutionStatus.Failed,
@ -2296,7 +2406,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
// Update trigger execution status to Completed
updateTriggerExecutionStatus(
projectStore,
getCurrentChatStore(),
project_id,
currentTaskId,
ExecutionStatus.Completed,
@ -2420,7 +2530,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
const currentTaskId = getCurrentTaskId();
// Update trigger execution status to Completed for connection closed by server
updateTriggerExecutionStatus(
projectStore,
getCurrentChatStore(),
project_id,
currentTaskId,
ExecutionStatus.Cancelled,
@ -3235,6 +3345,36 @@ const chatStore = (initial?: Partial<ChatStore>) =>
};
});
},
setExecutionId: (taskId, executionId) => {
set((state) => {
if (!state.tasks[taskId]) return state;
return {
...state,
tasks: {
...state.tasks,
[taskId]: {
...state.tasks[taskId],
executionId,
},
},
};
});
},
setNextExecutionId: (taskId, nextExecutionId) => {
set((state) => {
if (!state.tasks[taskId]) return state;
return {
...state,
tasks: {
...state.tasks,
[taskId]: {
...state.tasks[taskId],
nextExecutionId,
},
},
};
});
},
}));
const filterMessage = (message: AgentMessage) => {

View file

@ -12,7 +12,6 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
import { proxyUpdateTriggerExecution } from '@/service/triggerApi';
import { ExecutionStatus, TriggerType } from '@/types';
import { create } from 'zustand';
@ -224,15 +223,6 @@ interface TriggerTaskStore {
getExecutionMapping: (chatTaskId: string) => ExecutionMapping | undefined;
/** Remove execution mapping */
removeExecutionMapping: (chatTaskId: string) => void;
/** Update execution status on the backend */
updateExecutionStatus: (
chatTaskId: string,
status: ExecutionStatus,
tokens: number,
errorMessage?: string
) => Promise<void>;
/** Check if a task has been reported */
isExecutionReported: (chatTaskId: string) => boolean;
}
export const useTriggerTaskStore = create<TriggerTaskStore>((set, get) => ({
@ -443,81 +433,4 @@ export const useTriggerTaskStore = create<TriggerTaskStore>((set, get) => ({
return { executionMappings: newMappings };
});
},
updateExecutionStatus: async (
chatTaskId: string,
status: ExecutionStatus,
tokens: number,
errorMessage?: string
) => {
const { executionMappings, removeExecutionMapping, completeTask, failTask } = get();
const mapping = executionMappings.get(chatTaskId);
if (!mapping) {
console.warn(
'[TriggerTaskStore] No execution mapping found for chat task:',
chatTaskId
);
return;
}
if (mapping.reported) {
console.log(
'[TriggerTaskStore] Execution already reported for chat task:',
chatTaskId
);
return;
}
try {
await proxyUpdateTriggerExecution(
mapping.executionId,
{
status,
completed_at: new Date().toISOString(),
...(errorMessage && { error_message: errorMessage }),
tokens_used: tokens,
},
{ projectId: mapping.projectId }
);
// Mark as reported
set((state) => {
const newMappings = new Map(state.executionMappings);
const updatedMapping = newMappings.get(chatTaskId);
if (updatedMapping) {
updatedMapping.reported = true;
}
return { executionMappings: newMappings };
});
console.log(
'[TriggerTaskStore] Execution status updated:',
mapping.executionId,
'->',
status
);
// Complete or fail the trigger task based on status
// This moves the task from currentTask to taskHistory
if (status === ExecutionStatus.Completed) {
completeTask(mapping.triggerTaskId);
} else if (status === ExecutionStatus.Failed || status === ExecutionStatus.Cancelled) {
failTask(mapping.triggerTaskId, errorMessage || 'Task failed');
}
// Clean up mapping after successful update
removeExecutionMapping(chatTaskId);
} catch (err) {
console.warn(
`[TriggerTaskStore] Failed to update execution status to ${status}:`,
err
);
}
},
isExecutionReported: (chatTaskId: string) => {
const mapping = get().executionMappings.get(chatTaskId);
return mapping?.reported ?? false;
},
}));