mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-31 21:39:28 +00:00
fix: refactor chatStore from tasks dictionary to single task object (#627)
This commit is contained in:
parent
89a38f815e
commit
bf6cb685c5
25 changed files with 1059 additions and 1193 deletions
|
|
@ -60,8 +60,8 @@ export function AddWorker({
|
|||
return <div>Loading...</div>;
|
||||
}
|
||||
const activeProjectId = projectStore.activeProjectId;
|
||||
const activeTaskId = chatStore.activeTaskId;
|
||||
const tasks = chatStore.tasks;
|
||||
const activeTaskId = chatStore.taskId;
|
||||
const task = chatStore.task;
|
||||
const [showEnvConfig, setShowEnvConfig] = useState(false);
|
||||
const [activeMcp, setActiveMcp] = useState<McpItem | null>(null);
|
||||
const [envValues, setEnvValues] = useState<{ [key: string]: EnvValue }>({});
|
||||
|
|
@ -301,7 +301,7 @@ export function AddWorker({
|
|||
});
|
||||
setWorkerList(newWorkerList);
|
||||
} else if (
|
||||
activeTaskId && tasks[activeTaskId].messages.length === 0
|
||||
activeTaskId && task && task.messages.length === 0
|
||||
) {
|
||||
const worker: Agent = {
|
||||
tasks: [],
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export function NoticeCard() {
|
|||
}, 100);
|
||||
}
|
||||
}, [
|
||||
chatStore.tasks[chatStore.activeTaskId as string].cotList.length,
|
||||
chatStore.task?.cotList?.length,
|
||||
isExpanded,
|
||||
]);
|
||||
|
||||
|
|
@ -62,16 +62,16 @@ export function NoticeCard() {
|
|||
isExpanded ? "overflow-y-auto" : "overflow-y-auto max-h-[200px]"
|
||||
} transition-all duration-300 ease-in-out scrollbar-hide relative`}
|
||||
style={{
|
||||
maskImage: isExpanded
|
||||
? 'none'
|
||||
maskImage: isExpanded
|
||||
? 'none'
|
||||
: 'linear-gradient(to top, black 0%, black 40%, transparent 100%)',
|
||||
WebkitMaskImage: isExpanded
|
||||
? 'none'
|
||||
WebkitMaskImage: isExpanded
|
||||
? 'none'
|
||||
: 'linear-gradient(to top, black 0%, black 40%, transparent 100%)'
|
||||
}}
|
||||
>
|
||||
<div className="mt-sm flex flex-col px-2 gap-2">
|
||||
{chatStore.tasks[chatStore.activeTaskId as string].cotList.map(
|
||||
{(chatStore.task?.cotList || []).map(
|
||||
(cot: string, index: number) => {
|
||||
return (
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -47,31 +47,31 @@ export const ProjectChatContainer: React.FC<ProjectChatContainerProps> = ({
|
|||
useEffect(() => {
|
||||
if (!chatStore || !activeProjectId) return;
|
||||
|
||||
const activeTaskId = chatStore.activeTaskId;
|
||||
const activeTaskId = chatStore.taskId;
|
||||
if (!activeTaskId) return;
|
||||
|
||||
const task = chatStore.tasks[activeTaskId];
|
||||
const task = chatStore.task;
|
||||
if (!task) return;
|
||||
|
||||
const currentMessageCount = task.messages.length;
|
||||
|
||||
|
||||
// Check if a new user message was added
|
||||
if (currentMessageCount > lastMessageCount) {
|
||||
const lastMessage = task.messages[task.messages.length - 1];
|
||||
|
||||
|
||||
// If the last message is from user, scroll to bottom
|
||||
if (lastMessage && lastMessage.role === 'user') {
|
||||
scrollToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setLastMessageCount(currentMessageCount);
|
||||
}, [chatStore?.tasks[chatStore.activeTaskId as string]?.messages, lastMessageCount, scrollToBottom, activeProjectId]);
|
||||
}, [chatStore?.task?.messages, lastMessageCount, scrollToBottom, activeProjectId]);
|
||||
|
||||
// Reset message count when active task changes
|
||||
useEffect(() => {
|
||||
setLastMessageCount(0);
|
||||
}, [chatStore?.activeTaskId]);
|
||||
}, [chatStore?.taskId]);
|
||||
|
||||
// Intersection Observer for scroll-based animations
|
||||
useEffect(() => {
|
||||
|
|
@ -142,13 +142,13 @@ export const ProjectChatContainer: React.FC<ProjectChatContainerProps> = ({
|
|||
<AnimatePresence mode="popLayout">
|
||||
{chatStores.map(({ chatId, chatStore }) => {
|
||||
const chatState = chatStore.getState();
|
||||
const activeTaskId = chatState.activeTaskId;
|
||||
|
||||
if (!activeTaskId || !chatState.tasks[activeTaskId]) {
|
||||
const activeTaskId = chatState.taskId;
|
||||
|
||||
if (!activeTaskId || !chatState.task) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const task = chatState.tasks[activeTaskId];
|
||||
const task = chatState.task;
|
||||
const messages = task.messages || [];
|
||||
|
||||
// Only render if there are actual user messages (not just empty or system messages)
|
||||
|
|
|
|||
|
|
@ -56,13 +56,13 @@ export const ProjectSection = React.forwardRef<HTMLDivElement, ProjectSectionPro
|
|||
};
|
||||
}, [chatStore]);
|
||||
|
||||
const activeTaskId = chatState.activeTaskId;
|
||||
const activeTaskId = chatState.taskId;
|
||||
|
||||
if (!activeTaskId || !chatState.tasks[activeTaskId]) {
|
||||
if (!activeTaskId || !chatState.task) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const task = chatState.tasks[activeTaskId];
|
||||
const task = chatState.task;
|
||||
const messages = task.messages || [];
|
||||
|
||||
// Create a stable key based on messages content to prevent excessive re-renders
|
||||
|
|
|
|||
|
|
@ -101,20 +101,20 @@ export function TaskCard({
|
|||
|
||||
const isAllTaskFinished = useMemo(() => {
|
||||
return (
|
||||
chatStore.tasks[chatStore.activeTaskId as string].status === "finished"
|
||||
chatStore.task?.status === "finished"
|
||||
);
|
||||
}, [chatStore.tasks[chatStore.activeTaskId as string].status]);
|
||||
}, [chatStore.task?.status]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
chatStore.tasks[chatStore.activeTaskId as string].activeWorkSpace ===
|
||||
chatStore.task?.activeWorkSpace ===
|
||||
"workflow"
|
||||
) {
|
||||
setIsExpanded(false);
|
||||
} else {
|
||||
setIsExpanded(true);
|
||||
}
|
||||
}, [chatStore.tasks[chatStore.activeTaskId as string].activeWorkSpace]);
|
||||
}, [chatStore.task?.activeWorkSpace]);
|
||||
|
||||
// Improved height calculation logic
|
||||
useEffect(() => {
|
||||
|
|
@ -347,11 +347,9 @@ export function TaskCard({
|
|||
|
||||
// Set the active workspace and agent
|
||||
chatStore.setActiveWorkSpace(
|
||||
chatStore.activeTaskId as string,
|
||||
"workflow"
|
||||
);
|
||||
chatStore.setActiveAgent(
|
||||
chatStore.activeTaskId as string,
|
||||
task.agent?.agent_id
|
||||
);
|
||||
window.electronAPI.hideAllWebview();
|
||||
|
|
@ -386,9 +384,7 @@ export function TaskCard({
|
|||
<LoaderCircle
|
||||
size={16}
|
||||
className={`text-icon-information ${
|
||||
chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
].status === "running" && "animate-spin"
|
||||
chatStore.task?.status === "running" && "animate-spin"
|
||||
} `}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -37,16 +37,15 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
|
|||
const taskBoxRef = useRef<HTMLDivElement>(null);
|
||||
const [isTaskBoxSticky, setIsTaskBoxSticky] = useState(false);
|
||||
const chatState = chatStore.getState();
|
||||
const activeTaskId = chatState.activeTaskId;
|
||||
const activeTaskId = chatState.taskId;
|
||||
|
||||
// Subscribe to streaming decompose text separately for efficient updates
|
||||
const streamingDecomposeText = useSyncExternalStore(
|
||||
(callback) => chatStore.subscribe(callback),
|
||||
() => {
|
||||
const state = chatStore.getState();
|
||||
const taskId = state.activeTaskId;
|
||||
if (!taskId || !state.tasks[taskId]) return '';
|
||||
return state.tasks[taskId].streamingDecomposeText || '';
|
||||
if (!state.taskId || !state.task) return '';
|
||||
return state.task.streamingDecomposeText || '';
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -55,11 +54,11 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
|
|||
// Exclude human-reply scenarios (when user is replying to an activeAsk)
|
||||
const isHumanReply = queryGroup.userMessage &&
|
||||
activeTaskId &&
|
||||
chatState.tasks[activeTaskId] &&
|
||||
(chatState.tasks[activeTaskId].activeAsk ||
|
||||
chatState.task &&
|
||||
(chatState.task.activeAsk ||
|
||||
// Check if this user message follows an 'ask' message in the message sequence
|
||||
(() => {
|
||||
const messages = chatState.tasks[activeTaskId].messages;
|
||||
const messages = chatState.task.messages;
|
||||
const userMessageIndex = messages.findIndex((m: any) => m.id === queryGroup.userMessage.id);
|
||||
if (userMessageIndex > 0) {
|
||||
// Check the previous message - if it's an agent message with step 'ask', this is a human-reply
|
||||
|
|
@ -72,22 +71,22 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
|
|||
const isLastUserQuery = !queryGroup.taskMessage &&
|
||||
!isHumanReply &&
|
||||
activeTaskId &&
|
||||
chatState.tasks[activeTaskId] &&
|
||||
chatState.task &&
|
||||
queryGroup.userMessage &&
|
||||
queryGroup.userMessage.id === chatState.tasks[activeTaskId].messages.filter((m: any) => m.role === 'user').pop()?.id &&
|
||||
queryGroup.userMessage.id === chatState.task.messages.filter((m: any) => m.role === 'user').pop()?.id &&
|
||||
// Only show during active phases (not finished)
|
||||
chatState.tasks[activeTaskId].status !== 'finished';
|
||||
chatState.task.status !== 'finished';
|
||||
|
||||
// Only show the fallback task box for the newest query while the agent is still splitting work.
|
||||
// Simple Q&A sessions set hasWaitComfirm to true, so we should not render an empty task box there.
|
||||
// Also, do not show fallback task if we are currently decomposing (streaming text).
|
||||
const isDecomposing = streamingDecomposeText.length > 0;
|
||||
const shouldShowFallbackTask =
|
||||
isLastUserQuery && activeTaskId && !chatState.tasks[activeTaskId].hasWaitComfirm && !isDecomposing;
|
||||
isLastUserQuery && activeTaskId && !chatState.task?.hasWaitComfirm && !isDecomposing;
|
||||
|
||||
const task =
|
||||
(queryGroup.taskMessage || shouldShowFallbackTask) && activeTaskId
|
||||
? chatState.tasks[activeTaskId]
|
||||
? chatState.task
|
||||
: null;
|
||||
|
||||
// Set up intersection observer for this query group
|
||||
|
|
@ -230,15 +229,15 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
|
|||
progressValue={task?.progressValue || 0}
|
||||
summaryTask={task?.summaryTask || ""}
|
||||
onAddTask={() => {
|
||||
chatState.setIsTaskEdit(activeTaskId as string, true);
|
||||
chatState.setIsTaskEdit(true);
|
||||
chatState.addTaskInfo();
|
||||
}}
|
||||
onUpdateTask={(taskIndex, content) => {
|
||||
chatState.setIsTaskEdit(activeTaskId as string, true);
|
||||
chatState.setIsTaskEdit(true);
|
||||
chatState.updateTaskInfo(taskIndex, content);
|
||||
}}
|
||||
onDeleteTask={(taskIndex) => {
|
||||
chatState.setIsTaskEdit(activeTaskId as string, true);
|
||||
chatState.setIsTaskEdit(true);
|
||||
chatState.deleteTaskInfo(taskIndex);
|
||||
}}
|
||||
clickable={true}
|
||||
|
|
@ -279,8 +278,8 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
|
|||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
onClick={() => {
|
||||
chatState.setSelectedFile(activeTaskId as string, file);
|
||||
chatState.setActiveWorkSpace(activeTaskId as string, "documentWorkSpace");
|
||||
chatState.setSelectedFile(file);
|
||||
chatState.setActiveWorkSpace("documentWorkSpace");
|
||||
}}
|
||||
className="flex items-center gap-2 bg-message-fill-default rounded-sm px-2 py-1 w-[140px] cursor-pointer hover:bg-message-fill-hover transition-colors"
|
||||
>
|
||||
|
|
@ -356,8 +355,8 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
|
|||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
onClick={() => {
|
||||
chatState.setSelectedFile(activeTaskId as string, file);
|
||||
chatState.setActiveWorkSpace(activeTaskId as string, "documentWorkSpace");
|
||||
chatState.setSelectedFile(file);
|
||||
chatState.setActiveWorkSpace("documentWorkSpace");
|
||||
}}
|
||||
className="flex items-center gap-2 bg-message-fill-default rounded-2xl px-2 py-1 w-[120px] cursor-pointer hover:bg-message-fill-hover transition-colors"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -92,17 +92,17 @@ export default function ChatBox(): JSX.Element {
|
|||
const navigate = useNavigate();
|
||||
|
||||
const handleSend = async (messageStr?: string, taskId?: string) => {
|
||||
const _taskId = taskId || chatStore.activeTaskId;
|
||||
const _taskId = taskId || chatStore.taskId;
|
||||
if (message.trim() === "" && !messageStr) return;
|
||||
const tempMessageContent = messageStr || message;
|
||||
chatStore.setHasMessages(_taskId as string, true);
|
||||
chatStore.setHasMessages(true);
|
||||
if (!_taskId) return;
|
||||
|
||||
// Multi-turn support: Check if task is running or planning (splitting/confirm)
|
||||
const task = chatStore.tasks[_taskId];
|
||||
const task = chatStore.task;
|
||||
const requiresHumanReply = Boolean(task?.activeAsk);
|
||||
const isTaskInProgress = ["running", "pause"].includes(task?.status || "");
|
||||
const isTaskBusy = (
|
||||
const isTaskBusy = task ? (
|
||||
// running or paused counts as busy
|
||||
(task.status === 'running' && task.hasMessages) || task.status === 'pause' ||
|
||||
// splitting phase: has to_sub_tasks not confirmed OR skeleton computing
|
||||
|
|
@ -110,7 +110,7 @@ export default function ChatBox(): JSX.Element {
|
|||
((!task.messages.find(m => m.step === 'to_sub_tasks') && !task.hasWaitComfirm && task.messages.length > 0) || task.isTakeControl) ||
|
||||
// explicit confirm wait while task is pending but card not confirmed yet
|
||||
(!!task.messages.find(m => m.step === 'to_sub_tasks' && !m.isConfirm) && task.status === 'pending')
|
||||
);
|
||||
) : false;
|
||||
const isReplayChatStore = task?.type === "replay";
|
||||
if (!requiresHumanReply && isTaskBusy && !isReplayChatStore) {
|
||||
toast.error("Current task is in progress. Please wait for it to finish before sending a new request.", {
|
||||
|
|
@ -122,48 +122,48 @@ export default function ChatBox(): JSX.Element {
|
|||
if (textareaRef.current) textareaRef.current.style.height = "60px";
|
||||
try {
|
||||
if (requiresHumanReply) {
|
||||
chatStore.addMessages(_taskId, {
|
||||
chatStore.addMessages({
|
||||
id: generateUniqueId(),
|
||||
role: "user",
|
||||
content: tempMessageContent,
|
||||
attaches:
|
||||
JSON.parse(JSON.stringify(chatStore.tasks[_taskId]?.attaches)) || [],
|
||||
JSON.parse(JSON.stringify(chatStore.task?.attaches)) || [],
|
||||
});
|
||||
setMessage("");
|
||||
|
||||
|
||||
// Scroll to bottom after adding user message
|
||||
setTimeout(() => {
|
||||
scrollToBottom();
|
||||
}, 200);
|
||||
|
||||
chatStore.setIsPending(_taskId, true);
|
||||
chatStore.setIsPending(true);
|
||||
|
||||
await fetchPost(`/chat/${projectStore.activeProjectId}/human-reply`, {
|
||||
agent: chatStore.tasks[_taskId].activeAsk,
|
||||
agent: chatStore.task?.activeAsk,
|
||||
reply: tempMessageContent,
|
||||
});
|
||||
if (chatStore.tasks[_taskId].askList.length === 0) {
|
||||
chatStore.setActiveAsk(_taskId, "");
|
||||
if ((chatStore.task?.askList?.length ?? 0) === 0) {
|
||||
chatStore.setActiveAsk("");
|
||||
} else {
|
||||
let activeAskList = chatStore.tasks[_taskId].askList;
|
||||
let activeAskList = [...(chatStore.task?.askList || [])];
|
||||
console.log(
|
||||
"activeAskList",
|
||||
JSON.parse(JSON.stringify(activeAskList))
|
||||
);
|
||||
let message = activeAskList.shift();
|
||||
chatStore.setActiveAskList(_taskId, [...activeAskList]);
|
||||
chatStore.setActiveAsk(_taskId, message?.agent_name || "");
|
||||
chatStore.setIsPending(_taskId, false);
|
||||
chatStore.addMessages(_taskId, message!);
|
||||
chatStore.setActiveAskList([...activeAskList]);
|
||||
chatStore.setActiveAsk(message?.agent_name || "");
|
||||
chatStore.setIsPending(false);
|
||||
chatStore.addMessages(message!);
|
||||
}
|
||||
} else {
|
||||
// Check if we should continue the conversation or start a new task
|
||||
const hasMessages = chatStore.tasks[_taskId as string].messages.length > 0;
|
||||
const isFinished = chatStore.tasks[_taskId as string].status === "finished";
|
||||
const hasWaitComfirm = chatStore.tasks[_taskId as string]?.hasWaitComfirm;
|
||||
const hasMessages = (chatStore.task?.messages?.length ?? 0) > 0;
|
||||
const isFinished = chatStore.task?.status === "finished";
|
||||
const hasWaitComfirm = chatStore.task?.hasWaitComfirm;
|
||||
|
||||
// Check if this task was manually stopped (finished but without natural completion)
|
||||
const wasTaskStopped = isFinished && !chatStore.tasks[_taskId as string].messages.some(
|
||||
const wasTaskStopped = isFinished && !chatStore.task?.messages?.some(
|
||||
m => m.step === "end" // Natural completion has an "end" step message
|
||||
);
|
||||
|
||||
|
|
@ -171,29 +171,29 @@ export default function ChatBox(): JSX.Element {
|
|||
// 1. Has wait confirm (simple query response) - but not if task was stopped
|
||||
// 2. Task is naturally finished (complex task completed) - but not if task was stopped
|
||||
// 3. Has any messages but pending (ongoing conversation)
|
||||
const shouldContinueConversation = (hasWaitComfirm && !wasTaskStopped) || (isFinished && !wasTaskStopped) || (hasMessages && chatStore.tasks[_taskId as string].status === "pending");
|
||||
const shouldContinueConversation = (hasWaitComfirm && !wasTaskStopped) || (isFinished && !wasTaskStopped) || (hasMessages && chatStore.task?.status === "pending");
|
||||
|
||||
if (shouldContinueConversation) {
|
||||
// Check if this is the very first message and task hasn't started
|
||||
const hasSimpleResponse = chatStore.tasks[_taskId as string].messages.some(
|
||||
const hasSimpleResponse = chatStore.task?.messages?.some(
|
||||
m => m.step === "wait_confirm"
|
||||
);
|
||||
const hasComplexTask = chatStore.tasks[_taskId as string].messages.some(
|
||||
const hasComplexTask = chatStore.task?.messages?.some(
|
||||
m => m.step === "to_sub_tasks"
|
||||
);
|
||||
const hasErrorMessage = chatStore.tasks[_taskId as string].messages.some(
|
||||
const hasErrorMessage = chatStore.task?.messages?.some(
|
||||
m => m.role === "agent" && m.content.startsWith("❌ **Error**:")
|
||||
);
|
||||
|
||||
// Only start a new task if: pending, no messages processed yet
|
||||
// OR while or after replaying a project
|
||||
if ((chatStore.tasks[_taskId as string].status === "pending" && !hasSimpleResponse && !hasComplexTask && !isFinished)
|
||||
|| chatStore.tasks[_taskId].type === "replay" || hasErrorMessage) {
|
||||
if ((chatStore.task?.status === "pending" && !hasSimpleResponse && !hasComplexTask && !isFinished)
|
||||
|| chatStore.task?.type === "replay" || hasErrorMessage) {
|
||||
setMessage("");
|
||||
// Pass the message content to startTask instead of adding it to current chatStore
|
||||
const attachesToSend = JSON.parse(JSON.stringify(chatStore.tasks[_taskId]?.attaches)) || [];
|
||||
const attachesToSend = JSON.parse(JSON.stringify(chatStore.task?.attaches)) || [];
|
||||
try {
|
||||
await chatStore.startTask(_taskId, undefined, undefined, undefined, tempMessageContent, attachesToSend);
|
||||
await chatStore.startTask(undefined, undefined, undefined, tempMessageContent, attachesToSend);
|
||||
} catch (err: any) {
|
||||
console.error("Failed to start task:", err);
|
||||
toast.error(err?.message || "Failed to start task. Please check your model configuration.");
|
||||
|
|
@ -214,15 +214,15 @@ export default function ChatBox(): JSX.Element {
|
|||
question: tempMessageContent,
|
||||
task_id: nextTaskId
|
||||
});
|
||||
chatStore.setIsPending(_taskId, true);
|
||||
chatStore.setIsPending(true);
|
||||
// Add the user message to show it in UI
|
||||
chatStore.addMessages(_taskId, {
|
||||
chatStore.addMessages({
|
||||
id: generateUniqueId(),
|
||||
role: "user",
|
||||
content: tempMessageContent,
|
||||
attaches: JSON.parse(JSON.stringify(chatStore.tasks[_taskId]?.attaches)) || [],
|
||||
attaches: JSON.parse(JSON.stringify(chatStore.task?.attaches)) || [],
|
||||
});
|
||||
chatStore.setAttaches(_taskId, []);
|
||||
chatStore.setAttaches([]);
|
||||
setMessage("");
|
||||
}
|
||||
} else {
|
||||
|
|
@ -246,13 +246,13 @@ export default function ChatBox(): JSX.Element {
|
|||
setTimeout(() => {
|
||||
scrollToBottom();
|
||||
}, 200);
|
||||
|
||||
|
||||
// For the very first message, add it to the current chatStore first, then call startTask
|
||||
const attachesToSend = JSON.parse(JSON.stringify(chatStore.tasks[_taskId]?.attaches)) || [];
|
||||
const attachesToSend = JSON.parse(JSON.stringify(chatStore.task?.attaches)) || [];
|
||||
setMessage("");
|
||||
try {
|
||||
await chatStore.startTask(_taskId, undefined, undefined, undefined, tempMessageContent, attachesToSend);
|
||||
chatStore.setHasWaitComfirm(_taskId as string, true);
|
||||
await chatStore.startTask(undefined, undefined, undefined, tempMessageContent, attachesToSend);
|
||||
chatStore.setHasWaitComfirm(true);
|
||||
} catch (err: any) {
|
||||
console.error("Failed to start task:", err);
|
||||
toast.error(err?.message || "Failed to start task. Please check your model configuration.");
|
||||
|
|
@ -282,20 +282,19 @@ export default function ChatBox(): JSX.Element {
|
|||
return;
|
||||
}
|
||||
let _token: string = token.split("__")[0];
|
||||
let taskId: string = token.split("__")[1];
|
||||
chatStore.create(taskId, "share");
|
||||
chatStore.setHasMessages(taskId, true);
|
||||
let shareTaskId: string = token.split("__")[1];
|
||||
chatStore.create(shareTaskId, "share");
|
||||
chatStore.setHasMessages(true);
|
||||
const res = await proxyFetchGet(`/api/chat/share/info/${_token}`);
|
||||
if (res?.question) {
|
||||
chatStore.addMessages(taskId, {
|
||||
chatStore.addMessages({
|
||||
id: generateUniqueId(),
|
||||
role: "user",
|
||||
content: res.question.split("|")[0],
|
||||
});
|
||||
try {
|
||||
await chatStore.startTask(taskId, "share", _token, 0.1);
|
||||
chatStore.setActiveTaskId(taskId);
|
||||
chatStore.handleConfirmTask(projectStore.activeProjectId, taskId, "share");
|
||||
await chatStore.startTask("share", _token, 0.1);
|
||||
chatStore.handleConfirmTask(projectStore.activeProjectId, "share");
|
||||
} catch (err: any) {
|
||||
console.error("Failed to start shared task:", err);
|
||||
toast.error(err?.message || "Failed to start task. Please check your model configuration.");
|
||||
|
|
@ -352,13 +351,12 @@ export default function ChatBox(): JSX.Element {
|
|||
}, []);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const handleConfirmTask = async (taskId?: string) => {
|
||||
const _taskId = taskId || chatStore.activeTaskId;
|
||||
if (!_taskId || !projectStore.activeProjectId) {
|
||||
const handleConfirmTask = async () => {
|
||||
if (!chatStore.taskId || !projectStore.activeProjectId) {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
await chatStore.handleConfirmTask(projectStore.activeProjectId, _taskId);
|
||||
await chatStore.handleConfirmTask(projectStore.activeProjectId);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
|
|
@ -371,14 +369,13 @@ export default function ChatBox(): JSX.Element {
|
|||
});
|
||||
|
||||
if (result.success && result.files && result.files.length > 0) {
|
||||
const taskId = chatStore.activeTaskId as string;
|
||||
const files = [
|
||||
...chatStore.tasks[taskId].attaches.filter(
|
||||
...(chatStore.task?.attaches || []).filter(
|
||||
(f) => !result.files.find((r: File) => r.filePath === f.filePath)
|
||||
),
|
||||
...result.files,
|
||||
];
|
||||
chatStore.setAttaches(taskId, files);
|
||||
chatStore.setAttaches(files);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Select File Error:", error);
|
||||
|
|
@ -396,23 +393,23 @@ export default function ChatBox(): JSX.Element {
|
|||
// Pause/Resume handler
|
||||
const [isPauseResumeLoading, setIsPauseResumeLoading] = useState(false);
|
||||
const handlePauseResume = () => {
|
||||
const taskId = chatStore.activeTaskId as string;
|
||||
const task = chatStore.tasks[taskId];
|
||||
const task = chatStore.task;
|
||||
if (!task) return;
|
||||
const type = task.status === 'running' ? 'pause' : 'resume';
|
||||
|
||||
|
||||
setIsPauseResumeLoading(true);
|
||||
if (type === 'pause') {
|
||||
let { taskTime, elapsed } = task;
|
||||
const now = Date.now();
|
||||
elapsed += now - taskTime;
|
||||
chatStore.setElapsed(taskId, elapsed);
|
||||
chatStore.setTaskTime(taskId, 0);
|
||||
chatStore.setStatus(taskId, 'pause');
|
||||
chatStore.setElapsed(elapsed);
|
||||
chatStore.setTaskTime(0);
|
||||
chatStore.setStatus('pause');
|
||||
} else {
|
||||
chatStore.setTaskTime(taskId, Date.now());
|
||||
chatStore.setStatus(taskId, 'running');
|
||||
chatStore.setTaskTime(Date.now());
|
||||
chatStore.setStatus('running');
|
||||
}
|
||||
|
||||
|
||||
fetchPut(`/task/${projectStore.activeProjectId}/take-control`, {
|
||||
action: type,
|
||||
});
|
||||
|
|
@ -421,7 +418,7 @@ export default function ChatBox(): JSX.Element {
|
|||
|
||||
// Stop task handler - triggers Action.skip_task which preserves context
|
||||
const handleSkip = async () => {
|
||||
const taskId = chatStore.activeTaskId as string;
|
||||
const taskId = chatStore.taskId as string;
|
||||
console.log("=" .repeat(80));
|
||||
console.log("🛑 [STOP-BUTTON] handleSkip CALLED from frontend");
|
||||
console.log(`[STOP-BUTTON] taskId: ${taskId}, projectId: ${projectStore.activeProjectId}`);
|
||||
|
|
@ -443,7 +440,7 @@ export default function ChatBox(): JSX.Element {
|
|||
console.log("[STOP-BUTTON] ⚠️ SSE connection kept alive, waiting for backend 'end' event");
|
||||
|
||||
// Only set isPending to false so UI shows task is stopped
|
||||
chatStore.setIsPending(taskId, false);
|
||||
chatStore.setIsPending(false);
|
||||
console.log("[STOP-BUTTON] ✅ Task marked as not pending, SSE connection remains open");
|
||||
|
||||
toast.success("Task stopped successfully", {
|
||||
|
|
@ -455,8 +452,8 @@ export default function ChatBox(): JSX.Element {
|
|||
// If backend call failed, close SSE connection as fallback
|
||||
console.log("[STOP-BUTTON] Backend call failed, closing SSE connection as fallback");
|
||||
try {
|
||||
chatStore.stopTask(taskId);
|
||||
chatStore.setIsPending(taskId, false);
|
||||
chatStore.stopTask();
|
||||
chatStore.setIsPending(false);
|
||||
console.log("[STOP-BUTTON] ⚠️ SSE connection closed due to backend failure");
|
||||
toast.warning("Task stopped locally, but backend notification failed. Backend task may continue running.", {
|
||||
closeButton: true,
|
||||
|
|
@ -476,20 +473,20 @@ export default function ChatBox(): JSX.Element {
|
|||
|
||||
// Edit query handler
|
||||
const handleEditQuery = async () => {
|
||||
const taskId = chatStore.activeTaskId as string;
|
||||
const taskId = chatStore.taskId as string;
|
||||
const projectId = projectStore.activeProjectId;
|
||||
|
||||
// Early validation
|
||||
if (!projectId) {
|
||||
if (!projectId || !chatStore.task) {
|
||||
console.error("No active project ID found for edit operation");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get question and attachments before any deletions
|
||||
const messageIndex = chatStore.tasks[taskId].messages.findLastIndex(
|
||||
const messageIndex = chatStore.task.messages.findLastIndex(
|
||||
(item) => item.step === "to_sub_tasks"
|
||||
);
|
||||
const questionMessage = chatStore.tasks[taskId].messages[messageIndex - 2];
|
||||
const questionMessage = chatStore.task.messages[messageIndex - 2];
|
||||
const question = questionMessage.content;
|
||||
// Get the file attachments from the original user message (not from task.attaches which gets cleared after sending)
|
||||
const attachments = questionMessage.attaches || [];
|
||||
|
|
@ -516,56 +513,56 @@ export default function ChatBox(): JSX.Element {
|
|||
|
||||
// Create new task and clean up locally
|
||||
let id = chatStore.create();
|
||||
chatStore.setHasMessages(id, true);
|
||||
chatStore.setHasMessages(true);
|
||||
// Copy the file attachments to the new task
|
||||
if (attachments.length > 0) {
|
||||
chatStore.setAttaches(id, attachments);
|
||||
chatStore.setAttaches(attachments);
|
||||
}
|
||||
chatStore.removeTask(taskId);
|
||||
chatStore.removeTask();
|
||||
setMessage(question);
|
||||
};
|
||||
|
||||
// Task time tracking
|
||||
const [taskTime, setTaskTime] = useState(
|
||||
chatStore.getFormattedTaskTime(chatStore.activeTaskId as string)
|
||||
chatStore.getFormattedTaskTime()
|
||||
);
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
if (chatStore.activeTaskId) {
|
||||
if (chatStore.taskId) {
|
||||
setTaskTime(
|
||||
chatStore.getFormattedTaskTime(chatStore.activeTaskId)
|
||||
chatStore.getFormattedTaskTime()
|
||||
);
|
||||
}
|
||||
}, 500);
|
||||
return () => clearInterval(interval);
|
||||
}, [chatStore.activeTaskId]);
|
||||
}, [chatStore.taskId]);
|
||||
|
||||
// Determine BottomBox state
|
||||
const getBottomBoxState = () => {
|
||||
if (!chatStore.activeTaskId) return "input";
|
||||
const task = chatStore.tasks[chatStore.activeTaskId];
|
||||
if (!chatStore.taskId) return "input";
|
||||
const task = chatStore.task;
|
||||
|
||||
// Queued messages no longer change BottomBox state; QueuedBox renders independently
|
||||
|
||||
// Check for any to_sub_tasks message (confirmed or not)
|
||||
const anyToSubTasksMessage = task.messages.find((m) => m.step === "to_sub_tasks");
|
||||
const toSubTasksMessage = task.messages.find((m) => (m.step === "to_sub_tasks" && !m.isConfirm));
|
||||
|
||||
const anyToSubTasksMessage = task?.messages.find((m: Message) => m.step === "to_sub_tasks");
|
||||
const toSubTasksMessage = task?.messages.find((m: Message) => (m.step === "to_sub_tasks" && !m.isConfirm));
|
||||
|
||||
// Determine if we're in the "splitting in progress" phase (skeleton visible)
|
||||
// Only show splitting if there's NO to_sub_tasks message yet (not even confirmed)
|
||||
const isSkeletonPhase = (
|
||||
task.status !== 'finished' &&
|
||||
!anyToSubTasksMessage &&
|
||||
!task.hasWaitComfirm &&
|
||||
task.messages.length > 0) ||
|
||||
(task.isTakeControl && !anyToSubTasksMessage);
|
||||
task?.status !== 'finished' &&
|
||||
!anyToSubTasksMessage &&
|
||||
!task?.hasWaitComfirm &&
|
||||
(task?.messages?.length ?? 0) > 0) ||
|
||||
(task?.isTakeControl && !anyToSubTasksMessage);
|
||||
if (isSkeletonPhase) {
|
||||
return "splitting";
|
||||
}
|
||||
|
||||
// After splitting completes and TaskCard is awaiting user confirmation,
|
||||
// the Task becomes 'pending' and we show the confirm state.
|
||||
if (toSubTasksMessage && !toSubTasksMessage.isConfirm && task.status === 'pending') {
|
||||
if (toSubTasksMessage && !toSubTasksMessage.isConfirm && task?.status === 'pending') {
|
||||
return "confirm";
|
||||
}
|
||||
|
||||
|
|
@ -575,11 +572,11 @@ export default function ChatBox(): JSX.Element {
|
|||
}
|
||||
|
||||
// Check task status
|
||||
if (task.status === 'running' || task.status === 'pause') {
|
||||
if (task?.status === 'running' || task?.status === 'pause') {
|
||||
return "running";
|
||||
}
|
||||
|
||||
if (task.status === 'finished' && task.type !== '') {
|
||||
if (task?.status === 'finished' && task?.type !== '') {
|
||||
return "finished";
|
||||
}
|
||||
|
||||
|
|
@ -589,22 +586,18 @@ export default function ChatBox(): JSX.Element {
|
|||
const [hasSubTask, setHasSubTask] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const _hasSubTask = chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
]?.messages?.find((message) => message.step === "to_sub_tasks")
|
||||
const _hasSubTask = chatStore.task?.messages?.find((message: Message) => message.step === "to_sub_tasks")
|
||||
? true
|
||||
: false;
|
||||
setHasSubTask(_hasSubTask);
|
||||
}, [chatStore?.tasks[chatStore.activeTaskId as string]?.messages]);
|
||||
}, [chatStore.task?.messages]);
|
||||
|
||||
useEffect(() => {
|
||||
const activeAsk =
|
||||
chatStore?.tasks[chatStore.activeTaskId as string]?.activeAsk;
|
||||
const activeAsk = chatStore.task?.activeAsk;
|
||||
let timer: NodeJS.Timeout;
|
||||
if (activeAsk && activeAsk !== "") {
|
||||
const _taskId = chatStore.activeTaskId as string;
|
||||
timer = setTimeout(() => {
|
||||
handleSend("skip", _taskId);
|
||||
handleSend("skip");
|
||||
}, 30000); // 30 seconds
|
||||
return () => clearTimeout(timer); // clear previous timer
|
||||
}
|
||||
|
|
@ -613,7 +606,7 @@ export default function ChatBox(): JSX.Element {
|
|||
clearTimeout(timer);
|
||||
};
|
||||
}, [
|
||||
chatStore?.tasks[chatStore.activeTaskId as string]?.activeAsk,
|
||||
chatStore.task?.activeAsk,
|
||||
message, // depend on message
|
||||
]);
|
||||
|
||||
|
|
@ -648,7 +641,7 @@ export default function ChatBox(): JSX.Element {
|
|||
// Always try to call the backend to remove the task
|
||||
// The backend will handle the error gracefully if workforce is not initialized
|
||||
// Note: Replay creates a new chatstore, so no conflicts
|
||||
const task = chatStore.tasks[chatStore.activeTaskId as string];
|
||||
const task = chatStore.task;
|
||||
// Only skip backend call if task is finished or hasn't started yet (no messages)
|
||||
if(task && task.messages.length > 0 && task.status !== 'finished') {
|
||||
try {
|
||||
|
|
@ -677,42 +670,42 @@ export default function ChatBox(): JSX.Element {
|
|||
// Check if any chat store in the project has messages
|
||||
const hasAnyMessages = useMemo(() => {
|
||||
// First check current active chat store
|
||||
if (chatStore.activeTaskId && chatStore.tasks[chatStore.activeTaskId]) {
|
||||
const activeTask = chatStore.tasks[chatStore.activeTaskId];
|
||||
if (chatStore.taskId && chatStore.task) {
|
||||
const activeTask = chatStore.task;
|
||||
if ((activeTask.messages && activeTask.messages.length > 0) || activeTask.hasMessages) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Then check all other chat stores in the project
|
||||
return getAllChatStoresMemoized.some(({chatStore: store}) => {
|
||||
return getAllChatStoresMemoized.some(({chatStore: store}: {chatStore: any}) => {
|
||||
const state = store.getState();
|
||||
return state.activeTaskId &&
|
||||
state.tasks[state.activeTaskId] &&
|
||||
(state.tasks[state.activeTaskId].messages.length > 0 ||
|
||||
state.tasks[state.activeTaskId].hasMessages);
|
||||
return state.taskId &&
|
||||
state.task &&
|
||||
(state.task.messages.length > 0 ||
|
||||
state.task.hasMessages);
|
||||
});
|
||||
}, [chatStore, getAllChatStoresMemoized]);
|
||||
|
||||
const isTaskBusy = useMemo(() => {
|
||||
if (!chatStore.activeTaskId || !chatStore.tasks[chatStore.activeTaskId]) return false;
|
||||
const task = chatStore.tasks[chatStore.activeTaskId];
|
||||
if (!chatStore.taskId || !chatStore.task) return false;
|
||||
const task = chatStore.task;
|
||||
return (
|
||||
// running or paused
|
||||
task.status === 'running' ||
|
||||
task.status === 'running' ||
|
||||
task.status === 'pause' ||
|
||||
// splitting phase
|
||||
task.messages.some(m => m.step === 'to_sub_tasks' && !m.isConfirm) ||
|
||||
task.messages.some((m: Message) => m.step === 'to_sub_tasks' && !m.isConfirm) ||
|
||||
// skeleton/computing phase
|
||||
((!task.messages.find(m => m.step === 'to_sub_tasks') && !task.hasWaitComfirm && task.messages.length > 0) || task.isTakeControl)
|
||||
((!task.messages.find((m: Message) => m.step === 'to_sub_tasks') && !task.hasWaitComfirm && task.messages.length > 0) || task.isTakeControl)
|
||||
);
|
||||
}, [chatStore.activeTaskId, chatStore.tasks]);
|
||||
}, [chatStore.taskId, chatStore.task]);
|
||||
|
||||
const isInputDisabled = useMemo(() => {
|
||||
if (!chatStore.activeTaskId || !chatStore.tasks[chatStore.activeTaskId]) return true;
|
||||
|
||||
const task = chatStore.tasks[chatStore.activeTaskId];
|
||||
|
||||
if (!chatStore.taskId || !chatStore.task) return true;
|
||||
|
||||
const task = chatStore.task;
|
||||
|
||||
// If ask human is active, allow input
|
||||
if (task.activeAsk) return false;
|
||||
|
||||
|
|
@ -725,8 +718,8 @@ export default function ChatBox(): JSX.Element {
|
|||
|
||||
return false;
|
||||
}, [
|
||||
chatStore.activeTaskId,
|
||||
chatStore.tasks,
|
||||
chatStore.taskId,
|
||||
chatStore.task,
|
||||
privacy,
|
||||
useCloudModelInDev,
|
||||
isTaskBusy
|
||||
|
|
@ -742,7 +735,7 @@ export default function ChatBox(): JSX.Element {
|
|||
onSkip={handleSkip}
|
||||
isPauseResumeLoading={isPauseResumeLoading}
|
||||
/>
|
||||
{chatStore.activeTaskId && (
|
||||
{chatStore.taskId && (
|
||||
<BottomBox
|
||||
state={getBottomBoxState()}
|
||||
queuedMessages={isTaskBusy ? [] : projectStore.getProjectById(projectStore.activeProjectId || '')?.queuedMessages?.map(m => ({
|
||||
|
|
@ -751,21 +744,21 @@ export default function ChatBox(): JSX.Element {
|
|||
timestamp: m.timestamp
|
||||
})) || []}
|
||||
onRemoveQueuedMessage={(id) => handleRemoveTaskQueue(id)}
|
||||
subtitle={getBottomBoxState() === 'confirm'
|
||||
subtitle={getBottomBoxState() === 'confirm'
|
||||
? (() => {
|
||||
// Find the last message where role is "user"
|
||||
const messages = chatStore.tasks[chatStore.activeTaskId]?.messages || [];
|
||||
const lastUserMessage = messages.slice().reverse().find(msg => msg.role === "user");
|
||||
return lastUserMessage?.content || chatStore.tasks[chatStore.activeTaskId]?.summaryTask;
|
||||
const messages = chatStore.task?.messages || [];
|
||||
const lastUserMessage = messages.slice().reverse().find((msg: Message) => msg.role === "user");
|
||||
return lastUserMessage?.content || chatStore.task?.summaryTask;
|
||||
})()
|
||||
: chatStore.tasks[chatStore.activeTaskId]?.summaryTask}
|
||||
: chatStore.task?.summaryTask}
|
||||
onStartTask={() => handleConfirmTask()}
|
||||
onEdit={handleEditQuery}
|
||||
tokens={chatStore.tasks[chatStore.activeTaskId]?.tokens || 0}
|
||||
tokens={chatStore.task?.tokens || 0}
|
||||
taskTime={taskTime}
|
||||
taskStatus={chatStore.tasks[chatStore.activeTaskId]?.status}
|
||||
taskStatus={chatStore.task?.status}
|
||||
onReplay={handleReplay}
|
||||
replayDisabled={chatStore.tasks[chatStore.activeTaskId]?.status !== 'finished'}
|
||||
replayDisabled={chatStore.task?.status !== 'finished'}
|
||||
replayLoading={isReplayLoading}
|
||||
onPauseResume={handlePauseResume}
|
||||
pauseResumeLoading={isPauseResumeLoading}
|
||||
|
|
@ -774,11 +767,11 @@ export default function ChatBox(): JSX.Element {
|
|||
value: message,
|
||||
onChange: setMessage,
|
||||
onSend: handleSend,
|
||||
files: chatStore.tasks[chatStore.activeTaskId]?.attaches?.map(f => ({
|
||||
files: chatStore.task?.attaches?.map((f: any) => ({
|
||||
fileName: f.fileName,
|
||||
filePath: f.filePath
|
||||
})) || [],
|
||||
onFilesChange: (files) => chatStore.setAttaches(chatStore.activeTaskId as string, files as any),
|
||||
onFilesChange: (files) => chatStore.setAttaches(files as any),
|
||||
onAddFile: handleFileSelect,
|
||||
placeholder: t("chat.ask-placeholder"),
|
||||
disabled: isInputDisabled,
|
||||
|
|
@ -804,18 +797,18 @@ export default function ChatBox(): JSX.Element {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{chatStore.activeTaskId && (
|
||||
{chatStore.taskId && (
|
||||
<BottomBox
|
||||
state="input"
|
||||
inputProps={{
|
||||
value: message,
|
||||
onChange: setMessage,
|
||||
onSend: handleSend,
|
||||
files: chatStore.tasks[chatStore.activeTaskId]?.attaches?.map(f => ({
|
||||
files: chatStore.task?.attaches?.map((f: any) => ({
|
||||
fileName: f.fileName,
|
||||
filePath: f.filePath
|
||||
})) || [],
|
||||
onFilesChange: (files) => chatStore.setAttaches(chatStore.activeTaskId as string, files as any),
|
||||
onFilesChange: (files) => chatStore.setAttaches(files as any),
|
||||
onAddFile: handleFileSelect,
|
||||
placeholder: t("chat.ask-placeholder"),
|
||||
disabled: isInputDisabled,
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ export default function Folder({ data }: { data?: Agent }) {
|
|||
.invoke("read-file-dataurl", file.path)
|
||||
.then((dataUrl: string) => {
|
||||
setSelectedFile({ ...file, content: dataUrl });
|
||||
chatStore.setSelectedFile(chatStore.activeTaskId as string, file);
|
||||
chatStore.setSelectedFile(file);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
@ -204,7 +204,7 @@ export default function Folder({ data }: { data?: Agent }) {
|
|||
.invoke("open-file", file.type, file.path, isShowSourceCode)
|
||||
.then((res) => {
|
||||
setSelectedFile({ ...file, content: res });
|
||||
chatStore.setSelectedFile(chatStore.activeTaskId as string, file);
|
||||
chatStore.setSelectedFile(file);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
@ -304,10 +304,10 @@ export default function Folder({ data }: { data?: Agent }) {
|
|||
|
||||
const hasFetchedRemote = useRef(false);
|
||||
|
||||
// Reset hasFetchedRemote when activeTaskId changes
|
||||
// Reset hasFetchedRemote when taskId changes
|
||||
useEffect(() => {
|
||||
hasFetchedRemote.current = false;
|
||||
}, [chatStore.activeTaskId]);
|
||||
}, [chatStore.taskId]);
|
||||
|
||||
useEffect(() => {
|
||||
const setFileList = async () => {
|
||||
|
|
@ -348,7 +348,7 @@ export default function Folder({ data }: { data?: Agent }) {
|
|||
// Keep the old structure for compatibility
|
||||
setFileGroups((prev) => {
|
||||
const chatStoreSelectedFile =
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.selectedFile;
|
||||
chatStore.task?.selectedFile;
|
||||
if (chatStoreSelectedFile) {
|
||||
console.log(res, chatStoreSelectedFile);
|
||||
const file = res.find(
|
||||
|
|
@ -368,11 +368,11 @@ export default function Folder({ data }: { data?: Agent }) {
|
|||
});
|
||||
};
|
||||
setFileList();
|
||||
}, [chatStore.tasks[chatStore.activeTaskId as string]?.taskAssigning]);
|
||||
}, [chatStore.task?.taskAssigning]);
|
||||
|
||||
useEffect(() => {
|
||||
const chatStoreSelectedFile =
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.selectedFile;
|
||||
chatStore.task?.selectedFile;
|
||||
if (chatStoreSelectedFile && fileGroups[0]?.files) {
|
||||
const file = fileGroups[0].files.find(
|
||||
(item: any) => item.path === chatStoreSelectedFile.path
|
||||
|
|
@ -382,14 +382,14 @@ export default function Folder({ data }: { data?: Agent }) {
|
|||
}
|
||||
}
|
||||
}, [
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.selectedFile?.path,
|
||||
chatStore.task?.selectedFile?.path,
|
||||
fileGroups,
|
||||
isShowSourceCode,
|
||||
chatStore.activeTaskId,
|
||||
chatStore.taskId,
|
||||
]);
|
||||
|
||||
const handleBack = () => {
|
||||
chatStore.setActiveWorkSpace(chatStore.activeTaskId as string, "workflow");
|
||||
chatStore.setActiveWorkSpace("workflow");
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -115,18 +115,17 @@ export default function ProjectGroup({
|
|||
// Calculate if project has issues (requiring human in the loop)
|
||||
// Find tasks in chatStore where task_id matches any task in the project
|
||||
const hasHumanInLoop = useMemo(() => {
|
||||
if (!chatStore?.tasks || !project.tasks?.length) return false;
|
||||
if (!chatStore?.task || !project.tasks?.length) return false;
|
||||
|
||||
// Get all task_ids from the project, filtering out undefined/null values
|
||||
const projectTaskIds = project.tasks
|
||||
.map(task => task.task_id)
|
||||
.filter((id): id is string => !!id);
|
||||
|
||||
// Check if any task in chatStore with matching task_id has pending status
|
||||
return Object.entries(chatStore.tasks).some(([taskId, task]) =>
|
||||
projectTaskIds.includes(taskId) && task.status === 'pending'
|
||||
);
|
||||
}, [chatStore?.tasks, project.tasks]);
|
||||
// Check if current task matches any project task and has pending status
|
||||
const currentTaskId = chatStore.taskId;
|
||||
return currentTaskId && projectTaskIds.includes(currentTaskId) && chatStore.task.status === 'pending';
|
||||
}, [chatStore?.task, chatStore?.taskId, project.tasks]);
|
||||
const hasIssue = hasHumanInLoop;
|
||||
|
||||
// Calculate agent count (placeholder - count unique agents from tasks if available)
|
||||
|
|
|
|||
|
|
@ -101,20 +101,18 @@ export default function HistorySidebar() {
|
|||
// Check all chat stores for ongoing tasks
|
||||
chatStores.forEach(({ chatStore: cs }) => {
|
||||
const csState = cs.getState();
|
||||
Object.keys(csState.tasks || {}).forEach((taskId) => {
|
||||
const task = csState.tasks[taskId];
|
||||
// Only include ongoing tasks
|
||||
if (task.status !== "finished" && !task.type) {
|
||||
hasOngoingTasks = true;
|
||||
taskCount++;
|
||||
if (task.tokens) {
|
||||
totalTokens += task.tokens;
|
||||
}
|
||||
if (!lastPrompt && task.messages?.[0]?.content) {
|
||||
lastPrompt = task.messages[0].content;
|
||||
}
|
||||
const task = csState.task;
|
||||
// Only include ongoing tasks
|
||||
if (task && task.status !== "finished" && !task.type) {
|
||||
hasOngoingTasks = true;
|
||||
taskCount++;
|
||||
if (task.tokens) {
|
||||
totalTokens += task.tokens;
|
||||
}
|
||||
});
|
||||
if (!lastPrompt && task.messages?.[0]?.content) {
|
||||
lastPrompt = task.messages[0].content;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Only add project if it has ongoing tasks
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ const Layout = () => {
|
|||
|
||||
useEffect(() => {
|
||||
const handleBeforeClose = () => {
|
||||
const currentStatus = chatStore.tasks[chatStore.activeTaskId as string]?.status;
|
||||
if(["running", "pause"].includes(currentStatus)) {
|
||||
const currentStatus = chatStore.task?.status;
|
||||
if(currentStatus && ["running", "pause"].includes(currentStatus)) {
|
||||
setNoticeOpen(true);
|
||||
} else {
|
||||
window.electronAPI.closeWindow(true);
|
||||
|
|
@ -53,7 +53,7 @@ const Layout = () => {
|
|||
return () => {
|
||||
window.ipcRenderer.removeAllListeners("before-close");
|
||||
};
|
||||
}, [chatStore.tasks, chatStore.activeTaskId]);
|
||||
}, [chatStore.task, chatStore.taskId]);
|
||||
|
||||
// Determine what to show based on states
|
||||
const shouldShowOnboarding = initState === "done" && isFirstLaunch && !isInstalling;
|
||||
|
|
|
|||
|
|
@ -78,12 +78,12 @@ export default function Home() {
|
|||
const [activeAgent, setActiveAgent] = useState<Agent | null>(null);
|
||||
useEffect(() => {
|
||||
const taskAssigning =
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.taskAssigning;
|
||||
chatStore.task?.taskAssigning;
|
||||
if (taskAssigning) {
|
||||
const activeAgent = taskAssigning.find(
|
||||
(item) =>
|
||||
item.agent_id ===
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.activeWorkSpace
|
||||
chatStore.task?.activeWorkSpace
|
||||
);
|
||||
setActiveAgent(() => {
|
||||
if (activeAgent) {
|
||||
|
|
@ -93,8 +93,8 @@ export default function Home() {
|
|||
});
|
||||
}
|
||||
}, [
|
||||
chatStore.tasks[chatStore.activeTaskId as string].taskAssigning,
|
||||
chatStore.tasks[chatStore.activeTaskId as string].activeWorkSpace,
|
||||
chatStore.task?.taskAssigning,
|
||||
chatStore.task?.activeWorkSpace,
|
||||
]);
|
||||
|
||||
const [isTakeControl, setIsTakeControl] = useState(false);
|
||||
|
|
@ -193,7 +193,6 @@ export default function Home() {
|
|||
variant="ghost"
|
||||
onClick={() => {
|
||||
chatStore.setActiveWorkSpace(
|
||||
chatStore.activeTaskId as string,
|
||||
"workflow"
|
||||
);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ export function SearchHistoryDialog() {
|
|||
onTaskSelect={handleSetActive}
|
||||
onTaskDelete={handleDelete}
|
||||
onTaskShare={handleShare}
|
||||
activeTaskId={chatStore.activeTaskId || undefined}
|
||||
activeTaskId={chatStore.taskId || undefined}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ export const TaskState = ({
|
|||
(isSelected("ongoing") || forceVisible) &&
|
||||
"!text-icon-information"
|
||||
} ${
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.status ===
|
||||
chatStore.task?.status ===
|
||||
"running" && "animate-spin"
|
||||
}`}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -305,7 +305,7 @@ export default function TerminalComponent({
|
|||
}
|
||||
|
||||
// if there is history data, re-write
|
||||
if (chatStore.activeTaskId) {
|
||||
if (chatStore.taskId) {
|
||||
const terminalData = content || [];
|
||||
if (terminalData.length > 0) {
|
||||
xtermRef.current.writeln("\x1b[90m--- Previous Output ---\x1b[0m");
|
||||
|
|
@ -332,7 +332,7 @@ export default function TerminalComponent({
|
|||
// show prompt
|
||||
xtermRef.current.write(promptText);
|
||||
}, 200);
|
||||
}, [chatStore.activeTaskId, showWelcome, instanceId]);
|
||||
}, [chatStore.taskId, showWelcome, instanceId]);
|
||||
|
||||
// render terminal component
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -80,12 +80,12 @@ export default function TerminalAgentWrokSpace() {
|
|||
const [activeAgent, setActiveAgent] = useState<Agent | null>(null);
|
||||
useEffect(() => {
|
||||
const taskAssigning =
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.taskAssigning;
|
||||
chatStore.task?.taskAssigning;
|
||||
if (taskAssigning) {
|
||||
const activeAgent = taskAssigning.find(
|
||||
(item) =>
|
||||
item.agent_id ===
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.activeWorkSpace
|
||||
chatStore.task?.activeWorkSpace
|
||||
);
|
||||
setActiveAgent(() => {
|
||||
if (activeAgent) {
|
||||
|
|
@ -95,8 +95,8 @@ export default function TerminalAgentWrokSpace() {
|
|||
});
|
||||
}
|
||||
}, [
|
||||
chatStore.tasks[chatStore.activeTaskId as string].taskAssigning,
|
||||
chatStore.tasks[chatStore.activeTaskId as string].activeWorkSpace,
|
||||
chatStore.task?.taskAssigning,
|
||||
chatStore.task?.activeWorkSpace,
|
||||
]);
|
||||
|
||||
const [isTakeControl, setIsTakeControl] = useState(false);
|
||||
|
|
@ -145,7 +145,6 @@ export default function TerminalAgentWrokSpace() {
|
|||
variant="ghost"
|
||||
onClick={() => {
|
||||
chatStore.setActiveWorkSpace(
|
||||
chatStore.activeTaskId as string,
|
||||
"workflow"
|
||||
);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -116,17 +116,15 @@ function HeaderWin() {
|
|||
|
||||
const activeTaskTitle = useMemo(() => {
|
||||
if (
|
||||
chatStore.activeTaskId &&
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.summaryTask
|
||||
chatStore.taskId &&
|
||||
chatStore.task?.summaryTask
|
||||
) {
|
||||
return chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
].summaryTask.split("|")[0];
|
||||
return chatStore.task.summaryTask.split("|")[0];
|
||||
}
|
||||
return t("layout.new-project");
|
||||
}, [
|
||||
chatStore.activeTaskId,
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.summaryTask,
|
||||
chatStore.taskId,
|
||||
chatStore.task?.summaryTask,
|
||||
]);
|
||||
|
||||
const getReferFriendsLink = async () => {
|
||||
|
|
@ -147,7 +145,7 @@ function HeaderWin() {
|
|||
|
||||
//TODO: Mark ChatStore details as completed
|
||||
const handleEndProject = async () => {
|
||||
const taskId = chatStore.activeTaskId;
|
||||
const taskId = chatStore.taskId;
|
||||
const projectId = projectStore.activeProjectId;
|
||||
|
||||
if (!taskId) {
|
||||
|
|
@ -158,7 +156,7 @@ function HeaderWin() {
|
|||
const historyId = projectId ? projectStore.getHistoryId(projectId) : null;
|
||||
|
||||
try {
|
||||
const task = chatStore.tasks[taskId];
|
||||
const task = chatStore.task;
|
||||
|
||||
// Stop the task if it's running
|
||||
if (task && task.status === 'running') {
|
||||
|
|
@ -175,11 +173,11 @@ function HeaderWin() {
|
|||
}
|
||||
|
||||
// Delete from history using historyId
|
||||
if (historyId && task.status !== "finished") {
|
||||
if (historyId && task?.status !== "finished") {
|
||||
try {
|
||||
await proxyFetchDelete(`/api/chat/history/${historyId}`);
|
||||
// Remove from local store
|
||||
chatStore.removeTask(taskId);
|
||||
chatStore.removeTask();
|
||||
} catch (error) {
|
||||
console.log("History may not exist:", error);
|
||||
}
|
||||
|
|
@ -312,12 +310,12 @@ function HeaderWin() {
|
|||
platform === "darwin" && "pr-2"
|
||||
} flex h-full items-center z-50 relative no-drag gap-1`}
|
||||
>
|
||||
{chatStore.activeTaskId &&
|
||||
chatStore.tasks[chatStore.activeTaskId as string] &&
|
||||
{chatStore.taskId &&
|
||||
chatStore.task &&
|
||||
(
|
||||
(chatStore.tasks[chatStore.activeTaskId as string]?.messages?.length || 0) > 0 ||
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.hasMessages ||
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.status !== 'pending'
|
||||
(chatStore.task?.messages?.length || 0) > 0 ||
|
||||
chatStore.task?.hasMessages ||
|
||||
chatStore.task?.status !== 'pending'
|
||||
) && (
|
||||
<TooltipSimple content={t("layout.end-project")} side="bottom" align="end">
|
||||
<Button
|
||||
|
|
@ -331,11 +329,11 @@ function HeaderWin() {
|
|||
</Button>
|
||||
</TooltipSimple>
|
||||
)}
|
||||
{chatStore.activeTaskId &&
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.status === 'finished' && (
|
||||
{chatStore.taskId &&
|
||||
chatStore.task?.status === 'finished' && (
|
||||
<TooltipSimple content={t("layout.share")} side="bottom" align="end">
|
||||
<Button
|
||||
onClick={() => handleShare(chatStore.activeTaskId as string)}
|
||||
onClick={() => handleShare(chatStore.taskId as string)}
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
className="no-drag !text-button-fill-information-foreground bg-button-fill-information"
|
||||
|
|
@ -355,7 +353,7 @@ function HeaderWin() {
|
|||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-36">
|
||||
{chatStore.activeTaskId && chatStore.tasks[chatStore.activeTaskId as string] && (
|
||||
{chatStore.taskId && chatStore.task && (
|
||||
<DropdownMenuItem onClick={exportLog} className="cursor-pointer">
|
||||
<FileDown className="w-4 h-4" />
|
||||
{t("layout.report-bug")}
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ export function Node({ id, data }: NodeProps) {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (chatStore.tasks[chatStore.activeTaskId as string]?.activeAgent === id) {
|
||||
if (chatStore.task?.activeAgent === id) {
|
||||
const node = getNode(id);
|
||||
if (node) {
|
||||
setTimeout(() => {
|
||||
|
|
@ -164,7 +164,7 @@ export function Node({ id, data }: NodeProps) {
|
|||
}
|
||||
}
|
||||
}, [
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.activeAgent,
|
||||
chatStore.task?.activeAgent,
|
||||
id,
|
||||
setCenter,
|
||||
getNode,
|
||||
|
|
@ -328,7 +328,7 @@ export function Node({ id, data }: NodeProps) {
|
|||
} ${
|
||||
data.isEditMode ? "h-full" : "max-h-[calc(100vh-200px)]"
|
||||
} border-worker-border-default flex border border-solid rounded-xl overflow-hidden bg-worker-surface-primary ${
|
||||
chatStore.tasks[chatStore.activeTaskId as string].activeAgent === id
|
||||
chatStore.task?.activeAgent === id
|
||||
? `${agentMap[data.type]?.borderColor} z-50`
|
||||
: "border-worker-border-default z-10"
|
||||
} transition-all duration-300 ease-in-out ${
|
||||
|
|
@ -355,8 +355,7 @@ export function Node({ id, data }: NodeProps) {
|
|||
{isExpanded ? <SquareChevronLeft /> : <SquareCode />}
|
||||
</Button>
|
||||
{!Object.keys(agentMap).find((key) => key === data.type) &&
|
||||
chatStore.tasks[chatStore.activeTaskId as string].messages
|
||||
.length === 0 && (
|
||||
(chatStore.task?.messages?.length ?? 0) === 0 && (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
|
|
@ -419,7 +418,6 @@ export function Node({ id, data }: NodeProps) {
|
|||
className="max-h-[180px]"
|
||||
onClick={() => {
|
||||
chatStore.setActiveWorkSpace(
|
||||
chatStore.activeTaskId as string,
|
||||
data.agent?.agent_id as string
|
||||
);
|
||||
|
||||
|
|
@ -564,11 +562,9 @@ export function Node({ id, data }: NodeProps) {
|
|||
data.onExpandChange(id, true);
|
||||
if (task.agent) {
|
||||
chatStore.setActiveWorkSpace(
|
||||
chatStore.activeTaskId as string,
|
||||
"workflow"
|
||||
);
|
||||
chatStore.setActiveAgent(
|
||||
chatStore.activeTaskId as string,
|
||||
task.agent?.agent_id
|
||||
);
|
||||
window.electronAPI.hideAllWebview();
|
||||
|
|
@ -622,9 +618,7 @@ export function Node({ id, data }: NodeProps) {
|
|||
<LoaderCircle
|
||||
size={16}
|
||||
className={`text-icon-information ${
|
||||
chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
].status === "running" && "animate-spin"
|
||||
chatStore.task?.status === "running" && "animate-spin"
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -711,9 +705,7 @@ export function Node({ id, data }: NodeProps) {
|
|||
)}
|
||||
<div
|
||||
className={`${
|
||||
chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
].activeWorkSpace
|
||||
chatStore.task?.activeWorkSpace
|
||||
? "!w-[100px]"
|
||||
: "!w-[500px]"
|
||||
} pt-1 flex-shrink-0 flex-grow-0 min-w-0 text-text-primary text-xs leading-17 overflow-hidden text-ellipsis whitespace-nowrap`}
|
||||
|
|
@ -773,9 +765,7 @@ export function Node({ id, data }: NodeProps) {
|
|||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (toolkit.toolkitMethods === "write to file") {
|
||||
chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
].activeWorkSpace = "documentWorkSpace";
|
||||
chatStore.setActiveWorkSpace("documentWorkSpace");
|
||||
} else if (
|
||||
toolkit.toolkitMethods === "visit page"
|
||||
) {
|
||||
|
|
@ -794,9 +784,7 @@ export function Node({ id, data }: NodeProps) {
|
|||
<LoaderCircle
|
||||
size={16}
|
||||
className={`${
|
||||
chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
].status === "running" && "animate-spin"
|
||||
chatStore.task?.status === "running" && "animate-spin"
|
||||
}`}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -68,25 +68,21 @@ export function WorkSpaceMenu() {
|
|||
];
|
||||
const [agentList, setAgentList] = useState<Agent[]>([]);
|
||||
useEffect(() => {
|
||||
const taskAssigning =
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.taskAssigning;
|
||||
const taskAssigning = chatStore.task?.taskAssigning;
|
||||
const base = [...baseWorker, ...workerList].filter(
|
||||
(worker) => !taskAssigning.find((agent) => agent.type === worker.type)
|
||||
(worker) => !taskAssigning?.find((agent) => agent.type === worker.type)
|
||||
);
|
||||
setAgentList([...base, ...taskAssigning]);
|
||||
setAgentList([...base, ...(taskAssigning || [])]);
|
||||
}, [
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.taskAssigning,
|
||||
chatStore.task?.taskAssigning,
|
||||
workerList,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const cleanup = window.electronAPI.onWebviewNavigated((id: string, url: string) => {
|
||||
let webViewUrls = [
|
||||
...chatStore.tasks[chatStore.activeTaskId as string].webViewUrls,
|
||||
];
|
||||
let taskAssigning = [
|
||||
...chatStore.tasks[chatStore.activeTaskId as string].taskAssigning,
|
||||
];
|
||||
if (!chatStore.task) return;
|
||||
let webViewUrls = [...chatStore.task.webViewUrls];
|
||||
let taskAssigning = [...chatStore.task.taskAssigning];
|
||||
const hasId = taskAssigning.find((item) =>
|
||||
item.activeWebviewIds?.find((webview) => webview.id === id)
|
||||
);
|
||||
|
|
@ -109,10 +105,7 @@ export function WorkSpaceMenu() {
|
|||
img: "",
|
||||
processTaskId: hasUrl?.processTaskId || "",
|
||||
});
|
||||
chatStore.setTaskAssigning(
|
||||
chatStore.activeTaskId as string,
|
||||
taskAssigning
|
||||
);
|
||||
chatStore.setTaskAssigning(taskAssigning);
|
||||
}
|
||||
} else {
|
||||
taskAssigning[activeAgentIndex].activeWebviewIds?.push({
|
||||
|
|
@ -121,18 +114,13 @@ export function WorkSpaceMenu() {
|
|||
img: "",
|
||||
processTaskId: hasUrl?.processTaskId || "",
|
||||
});
|
||||
chatStore.setTaskAssigning(
|
||||
chatStore.activeTaskId as string,
|
||||
taskAssigning
|
||||
);
|
||||
chatStore.setTaskAssigning(taskAssigning);
|
||||
}
|
||||
const urlIndex = webViewUrls.findIndex((item) => item.url === url);
|
||||
if (urlIndex !== -1) {
|
||||
webViewUrls.splice(urlIndex, 1);
|
||||
}
|
||||
chatStore.setWebViewUrls(chatStore.activeTaskId as string, [
|
||||
...webViewUrls,
|
||||
]);
|
||||
chatStore.setWebViewUrls([...webViewUrls]);
|
||||
} else {
|
||||
// If no URL match found, also try to add to search_agent
|
||||
const searchAgentIndex = taskAssigning.findIndex((item) => item.type === 'search_agent');
|
||||
|
|
@ -143,10 +131,7 @@ export function WorkSpaceMenu() {
|
|||
img: "",
|
||||
processTaskId: webViewUrls[0]?.processTaskId || "",
|
||||
});
|
||||
chatStore.setTaskAssigning(
|
||||
chatStore.activeTaskId as string,
|
||||
taskAssigning
|
||||
);
|
||||
chatStore.setTaskAssigning(taskAssigning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -171,10 +156,8 @@ export function WorkSpaceMenu() {
|
|||
window.ipcRenderer
|
||||
.invoke("capture-webview", webview.id)
|
||||
.then((base64: string) => {
|
||||
let taskAssigning = [
|
||||
...chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.taskAssigning,
|
||||
];
|
||||
if (!chatStore.task) return;
|
||||
let taskAssigning = [...chatStore.task.taskAssigning];
|
||||
const searchAgentIndex = taskAssigning.findIndex(
|
||||
(agent) => agent.agent_id === webview.agent_id
|
||||
);
|
||||
|
|
@ -188,10 +171,7 @@ export function WorkSpaceMenu() {
|
|||
webview.index
|
||||
].img = base64;
|
||||
|
||||
chatStore.setTaskAssigning(
|
||||
chatStore.activeTaskId as string,
|
||||
taskAssigning
|
||||
);
|
||||
chatStore.setTaskAssigning(taskAssigning);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
@ -207,9 +187,9 @@ export function WorkSpaceMenu() {
|
|||
// Cleanup function to remove listener when component unmounts or dependencies change
|
||||
return cleanup;
|
||||
}, [
|
||||
chatStore.activeTaskId,
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.webViewUrls,
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.taskAssigning,
|
||||
chatStore.taskId,
|
||||
chatStore.task?.webViewUrls,
|
||||
chatStore.task?.taskAssigning,
|
||||
]);
|
||||
|
||||
const agentMap = {
|
||||
|
|
@ -288,15 +268,15 @@ export function WorkSpaceMenu() {
|
|||
};
|
||||
|
||||
const onValueChange = (val: string) => {
|
||||
if (!chatStore.activeTaskId) return;
|
||||
if (!chatStore.taskId) return;
|
||||
if (val === "") {
|
||||
chatStore.setActiveWorkSpace(chatStore.activeTaskId, "workflow");
|
||||
chatStore.setActiveWorkSpace("workflow");
|
||||
return;
|
||||
}
|
||||
if (val === "documentWorkSpace") {
|
||||
chatStore.setNuwFileNum(chatStore.activeTaskId, 0);
|
||||
chatStore.setNuwFileNum(0);
|
||||
}
|
||||
chatStore.setActiveWorkSpace(chatStore.activeTaskId, val);
|
||||
chatStore.setActiveWorkSpace(val);
|
||||
|
||||
window.electronAPI.hideAllWebview();
|
||||
};
|
||||
|
|
@ -305,13 +285,12 @@ export function WorkSpaceMenu() {
|
|||
<div className="h-full">
|
||||
<div className="h-full flex items-center flex-start">
|
||||
<div className="flex items-center flex-start gap-1 mr-3">
|
||||
{chatStore.activeTaskId && (
|
||||
{chatStore.taskId && (
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
size="sm"
|
||||
value={
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.activeWorkSpace as string
|
||||
chatStore.task?.activeWorkSpace as string
|
||||
}
|
||||
onValueChange={onValueChange}
|
||||
className="flex items-center gap-2"
|
||||
|
|
@ -323,16 +302,13 @@ export function WorkSpaceMenu() {
|
|||
value="documentWorkSpace"
|
||||
className="!w-10 !h-10 p-2 relative"
|
||||
>
|
||||
{chatStore.tasks[chatStore.activeTaskId as string].nuwFileNum >
|
||||
{(chatStore.task?.nuwFileNum ?? 0) >
|
||||
0 && (
|
||||
<Badge
|
||||
className="absolute top-0.5 right-0.5 h-4 min-w-4 rounded-full px-1 font-mono tabular-nums bg-icon-cuation text-white-100%"
|
||||
variant="destructive"
|
||||
>
|
||||
{
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.nuwFileNum
|
||||
}
|
||||
{chatStore.task?.nuwFileNum}
|
||||
</Badge>
|
||||
)}
|
||||
<Inbox className="!h-6 !w-6" />
|
||||
|
|
@ -353,8 +329,7 @@ export function WorkSpaceMenu() {
|
|||
<ToggleGroup
|
||||
type="single"
|
||||
value={
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.activeWorkSpace as string
|
||||
chatStore.task?.activeWorkSpace as string
|
||||
}
|
||||
onValueChange={onValueChange}
|
||||
className="flex items-center gap-2 max-w-[500px] overflow-x-auto scrollbar-horizontal"
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export const replayActiveTask = async (
|
|||
projectStore: ProjectStore,
|
||||
navigate: NavigateFunction
|
||||
) => {
|
||||
const taskId = chatStore.activeTaskId as string;
|
||||
const taskId = chatStore.taskId as string;
|
||||
const projectId = projectStore.activeProjectId as string;
|
||||
|
||||
if (!taskId || !projectId) {
|
||||
|
|
@ -75,8 +75,8 @@ export const replayActiveTask = async (
|
|||
}
|
||||
|
||||
// Fallback to current task's first message if no question found
|
||||
if (!question && chatStore.tasks[taskId] && chatStore.tasks[taskId].messages[0]) {
|
||||
question = chatStore.tasks[taskId].messages[0].content;
|
||||
if (!question && chatStore.task && chatStore.task.messages[0]) {
|
||||
question = chatStore.task.messages[0].content;
|
||||
console.log("[REPLAY] question fall back to ", question);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -139,9 +139,8 @@ export default function Project() {
|
|||
};
|
||||
|
||||
const handleClickAgent = (taskId: string, agent_id: string) => {
|
||||
chatStore.setActiveTaskId(taskId);
|
||||
chatStore.setActiveWorkSpace(taskId, "workflow");
|
||||
chatStore.setActiveAgent(taskId, agent_id);
|
||||
chatStore.setActiveWorkSpace("workflow");
|
||||
chatStore.setActiveAgent(agent_id);
|
||||
navigate(`/`);
|
||||
};
|
||||
|
||||
|
|
@ -157,8 +156,8 @@ export default function Project() {
|
|||
try {
|
||||
await proxyFetchDelete(`/api/chat/history/${id}`);
|
||||
setHistoryTasks((list) => list.filter((item) => item.id !== id));
|
||||
if (chatStore.tasks[id]) {
|
||||
chatStore.removeTask(id);
|
||||
if (chatStore.taskId === id) {
|
||||
chatStore.removeTask();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete history task:", error);
|
||||
|
|
@ -196,15 +195,14 @@ export default function Project() {
|
|||
};
|
||||
|
||||
const handleReplay = async (taskId: string, question: string) => {
|
||||
chatStore.replay(taskId, question, 0);
|
||||
chatStore.replay(question, 0);
|
||||
navigate({ pathname: "/" });
|
||||
};
|
||||
|
||||
const handleSetActive = (taskId: string, question: string) => {
|
||||
const task = chatStore.tasks[taskId];
|
||||
if (task) {
|
||||
const hasTask = chatStore.taskId === taskId && chatStore.task;
|
||||
if (hasTask) {
|
||||
// if there is a record, display the result
|
||||
chatStore.setActiveTaskId(taskId);
|
||||
navigate(`/`);
|
||||
} else {
|
||||
// if there is no record, execute replay
|
||||
|
|
@ -213,23 +211,24 @@ export default function Project() {
|
|||
};
|
||||
|
||||
const handleTakeControl = (type: "pause" | "resume", taskId: string) => {
|
||||
if (!chatStore.task || chatStore.taskId !== taskId) return;
|
||||
if (type === "pause") {
|
||||
let { taskTime, elapsed } = chatStore.tasks[taskId];
|
||||
let { taskTime, elapsed } = chatStore.task;
|
||||
|
||||
const now = Date.now();
|
||||
elapsed += now - taskTime;
|
||||
chatStore.setElapsed(taskId, elapsed);
|
||||
chatStore.setTaskTime(taskId, 0);
|
||||
chatStore.setElapsed(elapsed);
|
||||
chatStore.setTaskTime(0);
|
||||
} else {
|
||||
chatStore.setTaskTime(taskId, Date.now());
|
||||
chatStore.setTaskTime(Date.now());
|
||||
}
|
||||
fetchPut(`/task/${taskId}/take-control`, {
|
||||
action: type,
|
||||
});
|
||||
if (type === "pause") {
|
||||
chatStore.setStatus(taskId, "pause");
|
||||
chatStore.setStatus("pause");
|
||||
} else {
|
||||
chatStore.setStatus(taskId, "running");
|
||||
chatStore.setStatus("running");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -282,10 +281,9 @@ export default function Project() {
|
|||
onTaskSelect={handleSetActive}
|
||||
onTaskDelete={handleDelete}
|
||||
onTaskShare={handleShare}
|
||||
activeTaskId={chatStore.activeTaskId || undefined}
|
||||
ongoingTasks={chatStore.tasks}
|
||||
activeTaskId={chatStore.taskId || undefined}
|
||||
ongoingTasks={chatStore.task && chatStore.taskId ? { [chatStore.taskId]: chatStore.task } : {}}
|
||||
onOngoingTaskClick={(taskId) => {
|
||||
chatStore.setActiveTaskId(taskId);
|
||||
navigate(`/`);
|
||||
}}
|
||||
onOngoingTaskPause={(taskId) => handleTakeControl("pause", taskId)}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export default function Home() {
|
|||
|
||||
useEffect(() => {
|
||||
let taskAssigning = [
|
||||
...(chatStore.tasks[chatStore.activeTaskId as string]?.taskAssigning ||
|
||||
...(chatStore.task?.taskAssigning ||
|
||||
[]),
|
||||
];
|
||||
let webviews: { id: string; agent_id: string; index: number }[] = [];
|
||||
|
|
@ -75,7 +75,7 @@ export default function Home() {
|
|||
|
||||
// capture webview
|
||||
const captureWebview = async () => {
|
||||
const activeTask = chatStore.tasks[chatStore.activeTaskId as string];
|
||||
const activeTask = chatStore.task;
|
||||
if (!activeTask || activeTask.status === "finished") {
|
||||
return;
|
||||
}
|
||||
|
|
@ -83,7 +83,7 @@ export default function Home() {
|
|||
window.ipcRenderer
|
||||
.invoke("capture-webview", webview.id)
|
||||
.then((base64: string) => {
|
||||
const currentTask = chatStore.tasks[chatStore.activeTaskId as string];
|
||||
const currentTask = chatStore.task;
|
||||
if (!currentTask || currentTask.type) return;
|
||||
let taskAssigning = [
|
||||
...currentTask.taskAssigning,
|
||||
|
|
@ -100,15 +100,14 @@ export default function Home() {
|
|||
webview.index
|
||||
].img = base64;
|
||||
chatStore.setTaskAssigning(
|
||||
chatStore.activeTaskId as string,
|
||||
taskAssigning
|
||||
);
|
||||
const { processTaskId, url } =
|
||||
taskAssigning[searchAgentIndex].activeWebviewIds![
|
||||
webview.index
|
||||
];
|
||||
chatStore.setSnapshotsTemp(chatStore.activeTaskId as string, {
|
||||
api_task_id: chatStore.activeTaskId,
|
||||
chatStore.setSnapshotsTemp({
|
||||
api_task_id: chatStore.taskId,
|
||||
camel_task_id: processTaskId,
|
||||
browser_url: url,
|
||||
image_base64: base64,
|
||||
|
|
@ -150,10 +149,10 @@ export default function Home() {
|
|||
clearInterval(intervalTimer);
|
||||
}
|
||||
};
|
||||
}, [chatStore.tasks[chatStore.activeTaskId as string]?.taskAssigning]);
|
||||
}, [chatStore.task?.taskAssigning]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!chatStore.activeTaskId) {
|
||||
if (!chatStore.taskId) {
|
||||
projectStore.createProject("new project");
|
||||
}
|
||||
|
||||
|
|
@ -194,22 +193,19 @@ export default function Home() {
|
|||
</ResizablePanel>
|
||||
<ResizableHandle withHandle={true} className="custom-resizable-handle" />
|
||||
<ResizablePanel>
|
||||
{chatStore.tasks[chatStore.activeTaskId as string]
|
||||
{chatStore.task
|
||||
?.activeWorkSpace && (
|
||||
<div className="w-full h-full flex-1 flex flex-col animate-in fade-in-0 pr-2 slide-in-from-right-2 duration-300">
|
||||
{chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
]?.taskAssigning?.find(
|
||||
{chatStore.task?.taskAssigning?.find(
|
||||
(agent) =>
|
||||
agent.agent_id ===
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.activeWorkSpace
|
||||
chatStore.task?.activeWorkSpace
|
||||
)?.type === "search_agent" && (
|
||||
<div className="w-full h-[calc(100vh-104px)] flex-1 flex animate-in fade-in-0 slide-in-from-right-2 duration-300">
|
||||
<SearchAgentWrokSpace />
|
||||
</div>
|
||||
)}
|
||||
{chatStore.tasks[chatStore.activeTaskId as string]
|
||||
{chatStore.task
|
||||
?.activeWorkSpace === "workflow" && (
|
||||
<div className="w-full h-full flex-1 flex items-center justify-center animate-in fade-in-0 slide-in-from-right-2 duration-300">
|
||||
<div className="w-full h-full flex flex-col rounded-2xl border border-transparent border-solid p-2 relative">
|
||||
|
|
@ -218,7 +214,7 @@ export default function Home() {
|
|||
<div className="w-full h-full relative z-10">
|
||||
<Workflow
|
||||
taskAssigning={
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
chatStore.task
|
||||
?.taskAssigning || []
|
||||
}
|
||||
/>
|
||||
|
|
@ -226,21 +222,17 @@ export default function Home() {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
{chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
]?.taskAssigning?.find(
|
||||
{chatStore.task?.taskAssigning?.find(
|
||||
(agent) =>
|
||||
agent.agent_id ===
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.activeWorkSpace
|
||||
chatStore.task?.activeWorkSpace
|
||||
)?.type === "developer_agent" && (
|
||||
<div className="w-full h-[calc(100vh-104px)] flex-1 flex animate-in fade-in-0 slide-in-from-right-2 duration-300">
|
||||
<TerminalAgentWrokSpace></TerminalAgentWrokSpace>
|
||||
{/* <Terminal content={[]} /> */}
|
||||
</div>
|
||||
)}
|
||||
{chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.activeWorkSpace === "documentWorkSpace" && (
|
||||
{chatStore.task?.activeWorkSpace === "documentWorkSpace" && (
|
||||
<div className="w-full h-[calc(100vh-104px)] flex-1 flex items-center justify-center animate-in fade-in-0 slide-in-from-right-2 duration-300">
|
||||
<div className="w-full h-[calc(100vh-104px)] flex flex-col rounded-2xl border border-zinc-300 border-solid relative">
|
||||
{/*filter blur */}
|
||||
|
|
@ -251,13 +243,10 @@ export default function Home() {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
{chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
]?.taskAssigning?.find(
|
||||
{chatStore.task?.taskAssigning?.find(
|
||||
(agent) =>
|
||||
agent.agent_id ===
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.activeWorkSpace
|
||||
chatStore.task?.activeWorkSpace
|
||||
)?.type === "document_agent" && (
|
||||
<div className="w-full h-[calc(100vh-104px)] flex-1 flex items-center justify-center animate-in fade-in-0 slide-in-from-right-2 duration-300">
|
||||
<div className="w-full h-[calc(100vh-104px)] flex flex-col rounded-2xl border border-zinc-300 border-solid relative">
|
||||
|
|
@ -265,13 +254,10 @@ export default function Home() {
|
|||
<div className="absolute inset-0 blur-bg pointer-events-none bg-white-50 rounded-xl"></div>
|
||||
<div className="w-full h-full relative z-10">
|
||||
<Folder
|
||||
data={chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
]?.taskAssigning?.find(
|
||||
data={chatStore.task?.taskAssigning?.find(
|
||||
(agent) =>
|
||||
agent.agent_id ===
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.activeWorkSpace
|
||||
chatStore.task?.activeWorkSpace
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ export default function SettingGeneral() {
|
|||
variant="outline"
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
chatStore.clearTasks();
|
||||
chatStore.clearTask();
|
||||
|
||||
resetInstallation(); // Reset installation state for new account
|
||||
setNeedsBackendRestart(true); // Mark that backend is restarting
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -93,14 +93,9 @@ const isEmptyProject = (project: Project): boolean => {
|
|||
}
|
||||
|
||||
const chatState = chatStore.getState();
|
||||
const taskIds = Object.keys(chatState.tasks);
|
||||
|
||||
// Check if chat store has only one task
|
||||
if (taskIds.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const task = chatState.tasks[taskIds[0]];
|
||||
|
||||
// Check if chat store has a task
|
||||
const task = chatState.task;
|
||||
if (!task) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -309,9 +304,6 @@ const projectStore = create<ProjectStore>()((set, get) => ({
|
|||
// Create a new task in the new chat store with the queued content
|
||||
const newTaskId = newChatStore.getState().create(customTaskId);
|
||||
|
||||
//Set the initTask as the active taskId
|
||||
newChatStore.getState().setActiveTaskId(newTaskId);
|
||||
|
||||
return {taskId: newTaskId, chatStore: newChatStore};
|
||||
},
|
||||
|
||||
|
|
@ -483,9 +475,9 @@ const projectStore = create<ProjectStore>()((set, get) => ({
|
|||
const chatStore = project.chatStores[chatId];
|
||||
|
||||
if (chatStore) {
|
||||
// Call replay on the chat store with the taskId, question, and 0 delay
|
||||
// Call replay on the chat store with the question and delay
|
||||
try {
|
||||
await chatStore.getState().replay(taskId, question, 0.2);
|
||||
await chatStore.getState().replay(question, 0.2);
|
||||
console.log(`[ProjectStore] Started replay for task ${taskId}`);
|
||||
} catch (error) {
|
||||
console.error(`[ProjectStore] Failed to replay task ${taskId}:`, error);
|
||||
|
|
@ -771,12 +763,10 @@ const projectStore = create<ProjectStore>()((set, get) => ({
|
|||
Object.values(project.chatStores).forEach(chatStore => {
|
||||
if (chatStore && chatStore.getState) {
|
||||
const chatState = chatStore.getState();
|
||||
// Iterate through all tasks in the chat store
|
||||
Object.values(chatState.tasks).forEach(task => {
|
||||
if (task && typeof task.tokens === 'number') {
|
||||
totalTokens += task.tokens;
|
||||
}
|
||||
});
|
||||
// Get tokens from the task
|
||||
if (chatState.task && typeof chatState.task.tokens === 'number') {
|
||||
totalTokens += chatState.task.tokens;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue