mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-20 01:09:26 +00:00
update
This commit is contained in:
parent
5055069f59
commit
aa1916fa82
4 changed files with 450 additions and 274 deletions
|
|
@ -23,7 +23,39 @@ export const ProjectSection = React.forwardRef<HTMLDivElement, ProjectSectionPro
|
|||
onSkip,
|
||||
isPauseResumeLoading
|
||||
}, ref) => {
|
||||
const chatState = chatStore.getState();
|
||||
// Subscribe to store changes with throttling to prevent excessive re-renders
|
||||
const [chatState, setChatState] = React.useState(() => chatStore.getState());
|
||||
|
||||
React.useEffect(() => {
|
||||
let timeoutId: NodeJS.Timeout | null = null;
|
||||
let latestState: any = null;
|
||||
|
||||
const unsubscribe = chatStore.subscribe((state) => {
|
||||
latestState = state;
|
||||
|
||||
// Throttle updates to max once per 100ms
|
||||
if (!timeoutId) {
|
||||
timeoutId = setTimeout(() => {
|
||||
if (latestState) {
|
||||
setChatState(latestState);
|
||||
}
|
||||
timeoutId = null;
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
// Apply final state on cleanup
|
||||
if (latestState) {
|
||||
setChatState(latestState);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [chatStore]);
|
||||
|
||||
const activeTaskId = chatState.activeTaskId;
|
||||
|
||||
if (!activeTaskId || !chatState.tasks[activeTaskId]) {
|
||||
|
|
@ -33,8 +65,17 @@ export const ProjectSection = React.forwardRef<HTMLDivElement, ProjectSectionPro
|
|||
const task = chatState.tasks[activeTaskId];
|
||||
const messages = task.messages || [];
|
||||
|
||||
// Group messages by query cycles and show in chronological order (oldest first)
|
||||
const queryGroups = groupMessagesByQuery(messages);
|
||||
// Create a stable key based on messages content to prevent excessive re-renders
|
||||
const messagesKey = React.useMemo(() => {
|
||||
// Only re-compute when message count or last message changes
|
||||
const lastMsg = messages[messages.length - 1];
|
||||
return `${messages.length}-${lastMsg?.id || ''}-${lastMsg?.content?.length || 0}`;
|
||||
}, [messages.length, messages[messages.length - 1]?.id, messages[messages.length - 1]?.content?.length]);
|
||||
|
||||
// Memoize grouping to prevent re-creating objects on every render
|
||||
const queryGroups = React.useMemo(() => {
|
||||
return groupMessagesByQuery(messages);
|
||||
}, [messagesKey, messages]);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
|
|
@ -152,7 +193,7 @@ function groupMessagesByQuery(messages: any[]) {
|
|||
otherMessages: []
|
||||
};
|
||||
}
|
||||
} else {
|
||||
} else {
|
||||
// Other messages (assistant responses, errors, etc.)
|
||||
if (currentGroup) {
|
||||
currentGroup.otherMessages.push(message);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useRef, useEffect, useState } from 'react';
|
||||
import React, { useRef, useEffect, useState, useSyncExternalStore } from 'react';
|
||||
import { motion, useMotionValue, useTransform } from 'framer-motion';
|
||||
import { UserMessageCard } from './MessageItem/UserMessageCard';
|
||||
import { AgentMessageCard } from './MessageItem/AgentMessageCard';
|
||||
|
|
@ -38,24 +38,35 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
|
|||
const chatState = chatStore.getState();
|
||||
const activeTaskId = chatState.activeTaskId;
|
||||
|
||||
// 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 || '';
|
||||
}
|
||||
);
|
||||
|
||||
// Show task if this query group has a task message OR if it's the most recent user query during splitting
|
||||
// During splitting phase (no to_sub_tasks yet), show task for the most recent query only
|
||||
// Exclude human-reply scenarios (when user is replying to an activeAsk)
|
||||
const isHumanReply = queryGroup.userMessage &&
|
||||
const isHumanReply = queryGroup.userMessage &&
|
||||
activeTaskId &&
|
||||
chatState.tasks[activeTaskId] &&
|
||||
(chatState.tasks[activeTaskId].activeAsk ||
|
||||
// Check if this user message follows an 'ask' message in the message sequence
|
||||
(() => {
|
||||
const messages = chatState.tasks[activeTaskId].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
|
||||
const prevMessage = messages[userMessageIndex - 1];
|
||||
return prevMessage?.role === 'agent' && prevMessage?.step === 'ask';
|
||||
}
|
||||
return false;
|
||||
})());
|
||||
(chatState.tasks[activeTaskId].activeAsk ||
|
||||
// Check if this user message follows an 'ask' message in the message sequence
|
||||
(() => {
|
||||
const messages = chatState.tasks[activeTaskId].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
|
||||
const prevMessage = messages[userMessageIndex - 1];
|
||||
return prevMessage?.role === 'agent' && prevMessage?.step === 'ask';
|
||||
}
|
||||
return false;
|
||||
})());
|
||||
|
||||
const isLastUserQuery = !queryGroup.taskMessage &&
|
||||
!isHumanReply &&
|
||||
|
|
@ -68,8 +79,10 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
|
|||
|
||||
// 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;
|
||||
isLastUserQuery && activeTaskId && !chatState.tasks[activeTaskId].hasWaitComfirm && !isDecomposing;
|
||||
|
||||
const task =
|
||||
(queryGroup.taskMessage || shouldShowFallbackTask) && activeTaskId
|
||||
|
|
@ -114,7 +127,7 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
|
|||
sentinel.style.height = '1px';
|
||||
sentinel.style.pointerEvents = 'none';
|
||||
sentinel.style.zIndex = '-1';
|
||||
|
||||
|
||||
// Insert sentinel before the sticky element
|
||||
taskBoxRef.current.parentNode?.insertBefore(sentinel, taskBoxRef.current);
|
||||
|
||||
|
|
@ -144,9 +157,9 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
|
|||
const anyToSubTasksMessage = task?.messages.find((m: any) => m.step === "to_sub_tasks");
|
||||
const isSkeletonPhase = task && (
|
||||
(task.status !== 'finished' &&
|
||||
!anyToSubTasksMessage &&
|
||||
!task.hasWaitComfirm &&
|
||||
task.messages.length > 0) ||
|
||||
!anyToSubTasksMessage &&
|
||||
!task.hasWaitComfirm &&
|
||||
task.messages.length > 0) ||
|
||||
(task.isTakeControl && !anyToSubTasksMessage)
|
||||
);
|
||||
|
||||
|
|
@ -156,7 +169,7 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
|
|||
data-query-id={queryGroup.queryId}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{
|
||||
transition={{
|
||||
duration: 0.3,
|
||||
delay: index * 0.1 // Stagger animation for multiple groups
|
||||
}}
|
||||
|
|
@ -191,16 +204,16 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
|
|||
>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
animate={{
|
||||
opacity: 1,
|
||||
y: 0
|
||||
}}
|
||||
transition={{
|
||||
transition={{
|
||||
duration: 0.3,
|
||||
delay: 0.1 // Slight delay for sequencing
|
||||
}}
|
||||
>
|
||||
<div
|
||||
<div
|
||||
style={{
|
||||
transition: 'all 0.3s ease-in-out',
|
||||
transformOrigin: 'top'
|
||||
|
|
@ -236,105 +249,28 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
|
|||
|
||||
{/* Other Messages */}
|
||||
{queryGroup.otherMessages.map((message) => {
|
||||
if (message.content.length > 0) {
|
||||
if (message.step === "end") {
|
||||
return (
|
||||
<motion.div
|
||||
key={`end-${message.id}`}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="flex flex-col pl-3 gap-4"
|
||||
>
|
||||
<AgentMessageCard
|
||||
typewriter={
|
||||
task?.type !== "replay" ||
|
||||
(task?.type === "replay" && task?.delayTime !== 0)
|
||||
}
|
||||
id={message.id}
|
||||
content={message.content}
|
||||
onTyping={() => {}}
|
||||
/>
|
||||
{/* File List */}
|
||||
{message.fileList && (
|
||||
<div className="flex pl-3 gap-2 flex-wrap">
|
||||
{message.fileList.map((file: any, fileIndex: number) => (
|
||||
<motion.div
|
||||
key={`file-${message.id}-${file.name}-${fileIndex}`}
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
onClick={() => {
|
||||
chatState.setSelectedFile(activeTaskId as string, file);
|
||||
chatState.setActiveWorkSpace(activeTaskId as string, "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"
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<div className="max-w-[100px] font-bold text-sm text-body text-text-body overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{file.name.split(".")[0]}
|
||||
</div>
|
||||
<div className="font-medium leading-29 text-xs text-text-body">
|
||||
{file.type}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
} else if (message.content === "skip") {
|
||||
return (
|
||||
<motion.div
|
||||
key={`skip-${message.id}`}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="flex flex-col pl-3 gap-4"
|
||||
>
|
||||
if (message.content.length > 0) {
|
||||
if (message.step === "end") {
|
||||
return (
|
||||
<motion.div
|
||||
key={`end-${message.id}`}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="flex flex-col pl-3 gap-4"
|
||||
>
|
||||
<AgentMessageCard
|
||||
key={message.id}
|
||||
id={message.id}
|
||||
content="No reply received, task continues..."
|
||||
onTyping={() => {}}
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<motion.div
|
||||
key={`message-${message.id}`}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="flex flex-col pl-3 gap-4"
|
||||
>
|
||||
<AgentMessageCard
|
||||
key={message.id}
|
||||
typewriter={
|
||||
task?.type !== "replay" ||
|
||||
(task?.type === "replay" && task?.delayTime !== 0)
|
||||
}
|
||||
id={message.id}
|
||||
content={message.content}
|
||||
onTyping={() => {}}
|
||||
attaches={message.attaches}
|
||||
onTyping={() => { }}
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
} else if (message.step === "end" && message.content === "") {
|
||||
return (
|
||||
<motion.div
|
||||
key={`end-empty-${message.id}`}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="flex flex-col pl-3 gap-4"
|
||||
>
|
||||
{/* File List */}
|
||||
{message.fileList && (
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<div className="flex pl-3 gap-2 flex-wrap">
|
||||
{message.fileList.map((file: any, fileIndex: number) => (
|
||||
<motion.div
|
||||
key={`file-${message.id}-${file.name}-${fileIndex}`}
|
||||
|
|
@ -345,11 +281,10 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
|
|||
chatState.setSelectedFile(activeTaskId as string, file);
|
||||
chatState.setActiveWorkSpace(activeTaskId as string, "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"
|
||||
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"
|
||||
>
|
||||
<FileText size={16} className="text-icon-primary flex-shrink-0" />
|
||||
<div className="flex flex-col">
|
||||
<div className="max-w-48 font-bold text-sm text-body text-text-body overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
<div className="max-w-[100px] font-bold text-sm text-body text-text-body overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{file.name.split(".")[0]}
|
||||
</div>
|
||||
<div className="font-medium leading-29 text-xs text-text-body">
|
||||
|
|
@ -362,30 +297,122 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
|
|||
)}
|
||||
</motion.div>
|
||||
);
|
||||
} else if (message.content === "skip") {
|
||||
return (
|
||||
<motion.div
|
||||
key={`skip-${message.id}`}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="flex flex-col pl-3 gap-4"
|
||||
>
|
||||
<AgentMessageCard
|
||||
key={message.id}
|
||||
id={message.id}
|
||||
content="No reply received, task continues..."
|
||||
onTyping={() => { }}
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<motion.div
|
||||
key={`message-${message.id}`}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="flex flex-col pl-3 gap-4"
|
||||
>
|
||||
<AgentMessageCard
|
||||
key={message.id}
|
||||
typewriter={
|
||||
task?.type !== "replay" ||
|
||||
(task?.type === "replay" && task?.delayTime !== 0)
|
||||
}
|
||||
id={message.id}
|
||||
content={message.content}
|
||||
onTyping={() => { }}
|
||||
attaches={message.attaches}
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
} else if (message.step === "end" && message.content === "") {
|
||||
return (
|
||||
<motion.div
|
||||
key={`end-empty-${message.id}`}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="flex flex-col pl-3 gap-4"
|
||||
>
|
||||
{message.fileList && (
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{message.fileList.map((file: any, fileIndex: number) => (
|
||||
<motion.div
|
||||
key={`file-${message.id}-${file.name}-${fileIndex}`}
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
onClick={() => {
|
||||
chatState.setSelectedFile(activeTaskId as string, file);
|
||||
chatState.setActiveWorkSpace(activeTaskId as string, "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"
|
||||
>
|
||||
<FileText size={16} className="text-icon-primary flex-shrink-0" />
|
||||
<div className="flex flex-col">
|
||||
<div className="max-w-48 font-bold text-sm text-body text-text-body overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{file.name.split(".")[0]}
|
||||
</div>
|
||||
<div className="font-medium leading-29 text-xs text-text-body">
|
||||
{file.type}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
// Notice Card
|
||||
if (
|
||||
message.step === "notice_card" &&
|
||||
!task?.isTakeControl &&
|
||||
task?.cotList && task.cotList.length > 0
|
||||
) {
|
||||
return <NoticeCard key={`notice-${message.id}`} />;
|
||||
}
|
||||
// Notice Card
|
||||
if (
|
||||
message.step === "notice_card" &&
|
||||
!task?.isTakeControl &&
|
||||
task?.cotList && task.cotList.length > 0
|
||||
) {
|
||||
return <NoticeCard key={`notice-${message.id}`} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
})}
|
||||
return null;
|
||||
})}
|
||||
|
||||
{/* Skeleton for loading state */}
|
||||
{isSkeletonPhase && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.4 }}
|
||||
>
|
||||
<TypeCardSkeleton isTakeControl={task?.isTakeControl || false} />
|
||||
</motion.div>
|
||||
)}
|
||||
{/* Streaming Decompose Text - rendered separately to avoid flickering */}
|
||||
{isLastUserQuery && streamingDecomposeText && (
|
||||
<div className="flex flex-col pl-3 gap-4">
|
||||
<div className="flex items-center gap-2 text-text-label text-sm font-medium">
|
||||
<span className="animate-pulse">Splitting Tasks</span>
|
||||
</div>
|
||||
<div className="bg-message-fill-default/50 rounded-lg p-3 overflow-hidden">
|
||||
<pre className="whitespace-pre-wrap break-all font-mono text-xs text-text-secondary leading-relaxed">
|
||||
{streamingDecomposeText}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Skeleton for loading state */}
|
||||
{isSkeletonPhase && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.4 }}
|
||||
>
|
||||
<TypeCardSkeleton isTakeControl={task?.isTakeControl || false} />
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ interface Task {
|
|||
isTakeControl: boolean;
|
||||
isTaskEdit: boolean;
|
||||
isContextExceeded?: boolean;
|
||||
// Streaming decompose text - stored separately to avoid frequent re-renders
|
||||
streamingDecomposeText: string;
|
||||
}
|
||||
|
||||
export interface ChatStore {
|
||||
|
|
@ -56,9 +58,10 @@ export interface ChatStore {
|
|||
setActiveTaskId: (taskId: string) => void;
|
||||
replay: (taskId: string, question: string, time: number) => Promise<void>;
|
||||
startTask: (taskId: string, type?: string, shareToken?: string, delayTime?: number, messageContent?: string, messageAttaches?: File[]) => Promise<void>;
|
||||
handleConfirmTask: (project_id:string, taskId: string, type?: string) => void;
|
||||
handleConfirmTask: (project_id: string, taskId: string, type?: string) => void;
|
||||
addMessages: (taskId: string, messages: Message) => void;
|
||||
setMessages: (taskId: string, messages: Message[]) => void;
|
||||
updateMessage: (taskId: string, messageId: string, message: Message) => void;
|
||||
removeMessage: (taskId: string, messageId: string) => void;
|
||||
setAttaches: (taskId: string, attaches: File[]) => void;
|
||||
setSummaryTask: (taskId: string, summaryTask: string) => void;
|
||||
|
|
@ -102,6 +105,8 @@ export interface ChatStore {
|
|||
clearTasks: () => void,
|
||||
setIsContextExceeded: (taskId: string, isContextExceeded: boolean) => void;
|
||||
setNextTaskId: (taskId: string | null) => void;
|
||||
setStreamingDecomposeText: (taskId: string, text: string) => void;
|
||||
clearStreamingDecomposeText: (taskId: string) => void;
|
||||
}
|
||||
|
||||
export type VanillaChatStore = {
|
||||
|
|
@ -118,6 +123,10 @@ const autoConfirmTimers: Record<string, ReturnType<typeof setTimeout>> = {};
|
|||
// Track active SSE connections for proper cleanup
|
||||
const activeSSEControllers: Record<string, AbortController> = {};
|
||||
|
||||
// Throttle streaming decompose text updates to prevent excessive re-renders
|
||||
const streamingDecomposeTextBuffer: Record<string, string> = {};
|
||||
const streamingDecomposeTextTimers: Record<string, ReturnType<typeof setTimeout>> = {};
|
||||
|
||||
const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
||||
(set, get) => ({
|
||||
activeTaskId: null,
|
||||
|
|
@ -162,6 +171,7 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
snapshotsTemp: [],
|
||||
isTakeControl: false,
|
||||
isTaskEdit: false,
|
||||
streamingDecomposeText: '',
|
||||
},
|
||||
}
|
||||
}))
|
||||
|
|
@ -212,6 +222,27 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
})
|
||||
})
|
||||
},
|
||||
updateMessage(taskId: string, messageId: string, message: Message) {
|
||||
set((state) => {
|
||||
const task = state.tasks[taskId];
|
||||
if (!task) return state;
|
||||
const messages = task.messages.map((m) => {
|
||||
if (m.id === messageId) {
|
||||
return message;
|
||||
}
|
||||
return m;
|
||||
});
|
||||
return {
|
||||
tasks: {
|
||||
...state.tasks,
|
||||
[taskId]: {
|
||||
...task,
|
||||
messages,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
stopTask(taskId: string) {
|
||||
// Abort the SSE connection for this task
|
||||
try {
|
||||
|
|
@ -305,7 +336,7 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
/**
|
||||
* Replay creates its own chatStore for each task with replayProject
|
||||
*/
|
||||
if(project_id && type !== "replay") {
|
||||
if (project_id && type !== "replay") {
|
||||
console.log("Creating a new Chat Instance for current project on end")
|
||||
const newChatResult = projectStore.appendInitChatStore(project_id);
|
||||
|
||||
|
|
@ -313,7 +344,7 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
newTaskId = newChatResult.taskId;
|
||||
targetChatStore = newChatResult.chatStore;
|
||||
targetChatStore.getState().setIsPending(newTaskId, true);
|
||||
|
||||
|
||||
//From handleSend if message is given
|
||||
// Add the message to the new chatStore if provided
|
||||
if (messageContent) {
|
||||
|
|
@ -329,11 +360,11 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
}
|
||||
|
||||
const base_Url = import.meta.env.DEV ? import.meta.env.VITE_PROXY_URL : import.meta.env.VITE_BASE_URL
|
||||
const api = type == 'share' ?
|
||||
`${base_Url}/api/chat/share/playback/${shareToken}?delay_time=${delayTime}`
|
||||
: type == 'replay' ?
|
||||
`${base_Url}/api/chat/steps/playback/${newTaskId}?delay_time=${delayTime}`
|
||||
: `${baseURL}/chat`
|
||||
const api = type == 'share' ?
|
||||
`${base_Url}/api/chat/share/playback/${shareToken}?delay_time=${delayTime}`
|
||||
: type == 'replay' ?
|
||||
`${base_Url}/api/chat/steps/playback/${newTaskId}?delay_time=${delayTime}`
|
||||
: `${baseURL}/chat`
|
||||
|
||||
const { tasks } = get()
|
||||
let historyId: string | null = projectStore.getHistoryId(project_id);
|
||||
|
|
@ -387,9 +418,9 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
apiModel = {
|
||||
api_key: res.value,
|
||||
model_type: cloud_model_type,
|
||||
model_platform: cloud_model_type.includes('gpt') ? 'openai' :
|
||||
cloud_model_type.includes('claude') ? 'anthropic' :
|
||||
cloud_model_type.includes('gemini') ? 'gemini' : 'openai-compatible-model',
|
||||
model_platform: cloud_model_type.includes('gpt') ? 'openai' :
|
||||
cloud_model_type.includes('claude') ? 'anthropic' :
|
||||
cloud_model_type.includes('gemini') ? 'gemini' : 'openai-compatible-model',
|
||||
api_url: res.api_url,
|
||||
extra_params: {}
|
||||
}
|
||||
|
|
@ -451,7 +482,7 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
} catch (error) {
|
||||
console.log('get-env-path error', error)
|
||||
}
|
||||
|
||||
|
||||
// create history
|
||||
if (!type) {
|
||||
const authStore = getAuthStore();
|
||||
|
|
@ -478,11 +509,11 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
* TODO(history): Remove historyId handling to support per projectId
|
||||
* instead in history api
|
||||
*/
|
||||
if(project_id && historyId) projectStore.setHistoryId(project_id, historyId);
|
||||
if (project_id && historyId) projectStore.setHistoryId(project_id, historyId);
|
||||
})
|
||||
}
|
||||
const browser_port = await window.ipcRenderer.invoke('get-browser-port');
|
||||
|
||||
|
||||
// Lock the chatStore reference at the start of SSE session to prevent focus changes
|
||||
// during active message processing
|
||||
let lockedChatStore = targetChatStore;
|
||||
|
|
@ -577,8 +608,8 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
// - Task switching: confirmed, new_task_state, end
|
||||
// - Multi-turn simple answer: wait_confirm
|
||||
const isTaskSwitchingEvent = agentMessages.step === "confirmed" ||
|
||||
agentMessages.step === "new_task_state" ||
|
||||
agentMessages.step === "end";
|
||||
agentMessages.step === "new_task_state" ||
|
||||
agentMessages.step === "end";
|
||||
|
||||
const isMultiTurnSimpleAnswer = agentMessages.step === "wait_confirm";
|
||||
|
||||
|
|
@ -612,78 +643,78 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
*/
|
||||
let currentTaskId = getCurrentTaskId();
|
||||
const previousChatStore = getCurrentChatStore()
|
||||
if(agentMessages.step === "confirmed") {
|
||||
if (agentMessages.step === "confirmed") {
|
||||
const { question } = agentMessages.data;
|
||||
const shouldCreateNewChat = project_id && (question || messageContent);
|
||||
|
||||
|
||||
//All except first confirmed event to reuse the existing chatStore
|
||||
if(shouldCreateNewChat && !skipFirstConfirm) {
|
||||
/**
|
||||
* For Tasks where appended to existing project by
|
||||
* reusing same projectId. Need to create new chatStore
|
||||
* as it has been skipped earlier in startTask.
|
||||
*/
|
||||
const nextTaskId = previousChatStore.nextTaskId || undefined;
|
||||
const newChatResult = projectStore.appendInitChatStore(project_id || projectStore.activeProjectId!, nextTaskId);
|
||||
|
||||
if (newChatResult) {
|
||||
const { taskId: newTaskId, chatStore: newChatStore } = newChatResult;
|
||||
|
||||
// Update references for both scenarios
|
||||
updateLockedReferences(newChatStore, newTaskId);
|
||||
newChatStore.getState().setIsPending(newTaskId, false);
|
||||
|
||||
if(type === "replay") {
|
||||
newChatStore.getState().setDelayTime(newTaskId, delayTime as number);
|
||||
newChatStore.getState().setType(newTaskId, "replay");
|
||||
}
|
||||
if (shouldCreateNewChat && !skipFirstConfirm) {
|
||||
/**
|
||||
* For Tasks where appended to existing project by
|
||||
* reusing same projectId. Need to create new chatStore
|
||||
* as it has been skipped earlier in startTask.
|
||||
*/
|
||||
const nextTaskId = previousChatStore.nextTaskId || undefined;
|
||||
const newChatResult = projectStore.appendInitChatStore(project_id || projectStore.activeProjectId!, nextTaskId);
|
||||
|
||||
const lastMessage = previousChatStore.tasks[currentTaskId]?.messages.at(-1);
|
||||
if(lastMessage?.role === "user" && lastMessage?.id) {
|
||||
previousChatStore.removeMessage(currentTaskId, lastMessage.id);
|
||||
}
|
||||
|
||||
//Trick: by the time the question is retrieved from event,
|
||||
//the last message from previous chatStore is at display
|
||||
newChatStore.getState().addMessages(newTaskId, {
|
||||
id: generateUniqueId(),
|
||||
role: "user",
|
||||
content: question || messageContent as string,
|
||||
//TODO: The attaches that reach here (when Improve API is called) doesn't reach the backend
|
||||
attaches: [...(previousChatStore.tasks[currentTaskId]?.attaches || []), ...(messageAttaches || [])],
|
||||
});
|
||||
console.log("[NEW CHATSTORE] Created for ", project_id);
|
||||
if (newChatResult) {
|
||||
const { taskId: newTaskId, chatStore: newChatStore } = newChatResult;
|
||||
|
||||
//Create a new history point
|
||||
if (!type) {
|
||||
const authStore = getAuthStore();
|
||||
// Update references for both scenarios
|
||||
updateLockedReferences(newChatStore, newTaskId);
|
||||
newChatStore.getState().setIsPending(newTaskId, false);
|
||||
|
||||
const obj = {
|
||||
"project_id": project_id,
|
||||
"task_id": newTaskId,
|
||||
"user_id": authStore.user_id,
|
||||
"question": question || messageContent || (targetChatStore.getState().tasks[newTaskId]?.messages[0]?.content ?? ''),
|
||||
"language": systemLanguage,
|
||||
"model_platform": apiModel.model_platform,
|
||||
"model_type": apiModel.model_type,
|
||||
"api_url": modelType === 'cloud' ? "cloud" : apiModel.api_url,
|
||||
"max_retries": 3,
|
||||
"file_save_path": "string",
|
||||
"installed_mcp": "string",
|
||||
"status": 1,
|
||||
"tokens": 0
|
||||
}
|
||||
await proxyFetchPost(`/api/chat/history`, obj).then(res => {
|
||||
historyId = res.id;
|
||||
|
||||
/**Save history id for replay reuse purposes.
|
||||
* TODO(history): Remove historyId handling to support per projectId
|
||||
* instead in history api
|
||||
*/
|
||||
if(project_id && historyId) projectStore.setHistoryId(project_id, historyId);
|
||||
})
|
||||
}
|
||||
if (type === "replay") {
|
||||
newChatStore.getState().setDelayTime(newTaskId, delayTime as number);
|
||||
newChatStore.getState().setType(newTaskId, "replay");
|
||||
}
|
||||
|
||||
const lastMessage = previousChatStore.tasks[currentTaskId]?.messages.at(-1);
|
||||
if (lastMessage?.role === "user" && lastMessage?.id) {
|
||||
previousChatStore.removeMessage(currentTaskId, lastMessage.id);
|
||||
}
|
||||
|
||||
//Trick: by the time the question is retrieved from event,
|
||||
//the last message from previous chatStore is at display
|
||||
newChatStore.getState().addMessages(newTaskId, {
|
||||
id: generateUniqueId(),
|
||||
role: "user",
|
||||
content: question || messageContent as string,
|
||||
//TODO: The attaches that reach here (when Improve API is called) doesn't reach the backend
|
||||
attaches: [...(previousChatStore.tasks[currentTaskId]?.attaches || []), ...(messageAttaches || [])],
|
||||
});
|
||||
console.log("[NEW CHATSTORE] Created for ", project_id);
|
||||
|
||||
//Create a new history point
|
||||
if (!type) {
|
||||
const authStore = getAuthStore();
|
||||
|
||||
const obj = {
|
||||
"project_id": project_id,
|
||||
"task_id": newTaskId,
|
||||
"user_id": authStore.user_id,
|
||||
"question": question || messageContent || (targetChatStore.getState().tasks[newTaskId]?.messages[0]?.content ?? ''),
|
||||
"language": systemLanguage,
|
||||
"model_platform": apiModel.model_platform,
|
||||
"model_type": apiModel.model_type,
|
||||
"api_url": modelType === 'cloud' ? "cloud" : apiModel.api_url,
|
||||
"max_retries": 3,
|
||||
"file_save_path": "string",
|
||||
"installed_mcp": "string",
|
||||
"status": 1,
|
||||
"tokens": 0
|
||||
}
|
||||
await proxyFetchPost(`/api/chat/history`, obj).then(res => {
|
||||
historyId = res.id;
|
||||
|
||||
/**Save history id for replay reuse purposes.
|
||||
* TODO(history): Remove historyId handling to support per projectId
|
||||
* instead in history api
|
||||
*/
|
||||
if (project_id && historyId) projectStore.setHistoryId(project_id, historyId);
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//NOTE: Triggered only with first "confirmed" in the project
|
||||
//Handle Original cases - with old chatStore
|
||||
|
|
@ -696,18 +727,18 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
return
|
||||
}
|
||||
|
||||
const {
|
||||
setNuwFileNum,
|
||||
setCotList,
|
||||
getTokens,
|
||||
setUpdateCount,
|
||||
addTokens,
|
||||
setStatus,
|
||||
addWebViewUrl,
|
||||
setIsPending,
|
||||
addMessages,
|
||||
setHasWaitComfirm,
|
||||
setSummaryTask,
|
||||
const {
|
||||
setNuwFileNum,
|
||||
setCotList,
|
||||
getTokens,
|
||||
setUpdateCount,
|
||||
addTokens,
|
||||
setStatus,
|
||||
addWebViewUrl,
|
||||
setIsPending,
|
||||
addMessages,
|
||||
setHasWaitComfirm,
|
||||
setSummaryTask,
|
||||
setTaskAssigning,
|
||||
setTaskInfo,
|
||||
setTaskRunning,
|
||||
|
|
@ -721,11 +752,50 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
setElapsed,
|
||||
setActiveTaskId,
|
||||
setIsContextExceeded,
|
||||
setIsTaskEdit} = getCurrentChatStore()
|
||||
setIsTaskEdit } = getCurrentChatStore()
|
||||
|
||||
currentTaskId = getCurrentTaskId();
|
||||
// if (tasks[currentTaskId].status === 'finished') return
|
||||
if (agentMessages.step === "decompose_text") {
|
||||
const { content } = agentMessages.data;
|
||||
const text = content;
|
||||
const currentId = getCurrentTaskId();
|
||||
|
||||
// Get current buffer or task state
|
||||
const currentContent = streamingDecomposeTextBuffer[currentId] ||
|
||||
getCurrentChatStore().tasks[currentId]?.streamingDecomposeText || "";
|
||||
const newContent = text || "";
|
||||
let updatedContent = newContent;
|
||||
|
||||
if (newContent.startsWith(currentContent)) {
|
||||
// Accumulated format: new content contains old content -> Replace
|
||||
updatedContent = newContent;
|
||||
} else {
|
||||
// Delta format: new content is a chunk -> Append
|
||||
updatedContent = currentContent + newContent;
|
||||
}
|
||||
|
||||
// Store in buffer immediately
|
||||
streamingDecomposeTextBuffer[currentId] = updatedContent;
|
||||
|
||||
// Throttle store updates to every 50ms for smoother streaming display
|
||||
if (!streamingDecomposeTextTimers[currentId]) {
|
||||
streamingDecomposeTextTimers[currentId] = setTimeout(() => {
|
||||
const bufferedText = streamingDecomposeTextBuffer[currentId];
|
||||
if (bufferedText !== undefined) {
|
||||
getCurrentChatStore().setStreamingDecomposeText(currentId, bufferedText);
|
||||
}
|
||||
delete streamingDecomposeTextTimers[currentId];
|
||||
}, 16);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (agentMessages.step === "to_sub_tasks") {
|
||||
// Clear streaming decompose text when task splitting is done
|
||||
const currentStore = getCurrentChatStore();
|
||||
const currentId = getCurrentTaskId();
|
||||
currentStore.clearStreamingDecomposeText(currentId);
|
||||
// Check if this is a multi-turn scenario after task completion
|
||||
const isMultiTurnAfterCompletion = tasks[currentTaskId].status === 'finished';
|
||||
|
||||
|
|
@ -863,18 +933,18 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
return;
|
||||
}
|
||||
if (agentMessages.step === "wait_confirm") {
|
||||
const {content, question} = agentMessages.data;
|
||||
const { content, question } = agentMessages.data;
|
||||
setHasWaitComfirm(currentTaskId, true)
|
||||
setIsPending(currentTaskId, false)
|
||||
|
||||
const currentChatStore = getCurrentChatStore();
|
||||
//Make sure to add user Message on replay and avoid duplication of first msg
|
||||
if(question && !(currentChatStore.tasks[currentTaskId].messages.length === 1)) {
|
||||
if (question && !(currentChatStore.tasks[currentTaskId].messages.length === 1)) {
|
||||
//Replace the optimistic update if existent.
|
||||
const lastMessage = currentChatStore.tasks[currentTaskId]?.messages.at(-1);
|
||||
if(lastMessage?.role === "user" && lastMessage.id && lastMessage.content === question) {
|
||||
if (lastMessage?.role === "user" && lastMessage.id && lastMessage.content === question) {
|
||||
currentChatStore.removeMessage(currentTaskId, lastMessage.id)
|
||||
}
|
||||
}
|
||||
addMessages(currentTaskId, {
|
||||
id: generateUniqueId(),
|
||||
role: "user",
|
||||
|
|
@ -1075,7 +1145,7 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
if (target) {
|
||||
const { agentIndex, taskIndex } = target
|
||||
const agentName = taskAssigning.find((agent: Agent) => agent.agent_id === assignee_id)?.name
|
||||
if(agentName!==taskAssigning[agentIndex].name){
|
||||
if (agentName !== taskAssigning[agentIndex].name) {
|
||||
taskAssigning[agentIndex].tasks[taskIndex].reAssignTo = agentName
|
||||
}
|
||||
}
|
||||
|
|
@ -1126,7 +1196,7 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
// Only update or add to taskRunning, never duplicate
|
||||
if (taskRunningIndex === -1) {
|
||||
// Task not in taskRunning, add it
|
||||
if(task){
|
||||
if (task) {
|
||||
task.status = taskState === "waiting" ? "waiting" : "running";
|
||||
}
|
||||
taskRunning!.push(
|
||||
|
|
@ -1322,27 +1392,27 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
}
|
||||
|
||||
|
||||
if (agentMessages.step === "context_too_long") {
|
||||
console.error('Context too long:', agentMessages.data)
|
||||
const currentLength = agentMessages.data.current_length || 0;
|
||||
const maxLength = agentMessages.data.max_length || 100000;
|
||||
|
||||
// Show toast notification
|
||||
toast.dismiss();
|
||||
toast.error(
|
||||
`⚠️ Context Limit Exceeded\n\nThe conversation history is too long (${currentLength.toLocaleString()} / ${maxLength.toLocaleString()} characters).\n\nPlease create a new project to continue your work.`,
|
||||
{
|
||||
duration: Infinity,
|
||||
closeButton: true,
|
||||
}
|
||||
);
|
||||
if (agentMessages.step === "context_too_long") {
|
||||
console.error('Context too long:', agentMessages.data)
|
||||
const currentLength = agentMessages.data.current_length || 0;
|
||||
const maxLength = agentMessages.data.max_length || 100000;
|
||||
|
||||
// Set flag to block input and set status to pause
|
||||
setIsContextExceeded(currentTaskId, true);
|
||||
setStatus(currentTaskId, "pause");
|
||||
uploadLog(currentTaskId, type);
|
||||
return
|
||||
}
|
||||
// Show toast notification
|
||||
toast.dismiss();
|
||||
toast.error(
|
||||
`⚠️ Context Limit Exceeded\n\nThe conversation history is too long (${currentLength.toLocaleString()} / ${maxLength.toLocaleString()} characters).\n\nPlease create a new project to continue your work.`,
|
||||
{
|
||||
duration: Infinity,
|
||||
closeButton: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Set flag to block input and set status to pause
|
||||
setIsContextExceeded(currentTaskId, true);
|
||||
setStatus(currentTaskId, "pause");
|
||||
uploadLog(currentTaskId, type);
|
||||
return
|
||||
}
|
||||
|
||||
if (agentMessages.step === "error") {
|
||||
try {
|
||||
|
|
@ -1355,8 +1425,8 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
|
||||
// Safely extract error message with fallback chain
|
||||
const errorMessage = agentMessages.data?.message ||
|
||||
(typeof agentMessages.data === 'string' ? agentMessages.data : null) ||
|
||||
'An error occurred while processing your request';
|
||||
(typeof agentMessages.data === 'string' ? agentMessages.data : null) ||
|
||||
'An error occurred while processing your request';
|
||||
|
||||
// Mark all incomplete tasks as failed
|
||||
let taskRunning = [...tasks[currentTaskId].taskRunning];
|
||||
|
|
@ -1438,10 +1508,10 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
const taskIdToRemove = agentMessages.data.task_id as string;
|
||||
const projectStore = useProjectStore.getState();
|
||||
//Remove the task from the queue on error
|
||||
if(project_id) {
|
||||
if (project_id) {
|
||||
const project = projectStore.getProjectById(project_id);
|
||||
if (project && project.queuedMessages) {
|
||||
const messageToRemove = project.queuedMessages.find(msg =>
|
||||
const messageToRemove = project.queuedMessages.find(msg =>
|
||||
msg.task_id === taskIdToRemove || msg.content.includes(taskIdToRemove)
|
||||
);
|
||||
if (messageToRemove) {
|
||||
|
|
@ -1467,7 +1537,7 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
// Find and remove the message with matching task ID
|
||||
const project = projectStore.getProjectById(project_id);
|
||||
if (project && project.queuedMessages) {
|
||||
const messageToRemove = project.queuedMessages.find(msg =>
|
||||
const messageToRemove = project.queuedMessages.find(msg =>
|
||||
msg.task_id === taskIdToRemove || msg.content.includes(taskIdToRemove)
|
||||
);
|
||||
if (messageToRemove) {
|
||||
|
|
@ -1773,7 +1843,7 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
const { create, setHasMessages, addMessages, startTask, setActiveTaskId, handleConfirmTask } = get();
|
||||
//get project id
|
||||
const project_id = useProjectStore.getState().activeProjectId
|
||||
if(!project_id) {
|
||||
if (!project_id) {
|
||||
console.error("Can't replay task because no project id provided")
|
||||
return;
|
||||
}
|
||||
|
|
@ -1996,7 +2066,7 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
},
|
||||
}))
|
||||
},
|
||||
handleConfirmTask: async (project_id:string, taskId: string, type?: string) => {
|
||||
handleConfirmTask: async (project_id: string, taskId: string, type?: string) => {
|
||||
const { tasks, setMessages, setActiveWorkSpace, setStatus, setTaskTime, setTaskInfo, setTaskRunning, setIsTaskEdit } = get();
|
||||
if (!taskId) return;
|
||||
|
||||
|
|
@ -2022,7 +2092,7 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
task: taskInfo,
|
||||
});
|
||||
await fetchPost(`/task/${project_id}/start`, {});
|
||||
|
||||
|
||||
setActiveWorkSpace(taskId, 'workflow')
|
||||
setStatus(taskId, 'running')
|
||||
}
|
||||
|
|
@ -2435,6 +2505,43 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
...state,
|
||||
nextTaskId: taskId,
|
||||
}))
|
||||
},
|
||||
setStreamingDecomposeText: (taskId, text) => {
|
||||
set((state) => {
|
||||
if (!state.tasks[taskId]) return state;
|
||||
return {
|
||||
...state,
|
||||
tasks: {
|
||||
...state.tasks,
|
||||
[taskId]: {
|
||||
...state.tasks[taskId],
|
||||
streamingDecomposeText: text,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
clearStreamingDecomposeText: (taskId) => {
|
||||
// Clear buffer and any pending timer
|
||||
delete streamingDecomposeTextBuffer[taskId];
|
||||
if (streamingDecomposeTextTimers[taskId]) {
|
||||
clearTimeout(streamingDecomposeTextTimers[taskId]);
|
||||
delete streamingDecomposeTextTimers[taskId];
|
||||
}
|
||||
|
||||
set((state) => {
|
||||
if (!state.tasks[taskId]) return state;
|
||||
return {
|
||||
...state,
|
||||
tasks: {
|
||||
...state.tasks,
|
||||
[taskId]: {
|
||||
...state.tasks[taskId],
|
||||
streamingDecomposeText: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
|
|
|
|||
11
src/types/chatbox.d.ts
vendored
11
src/types/chatbox.d.ts
vendored
|
|
@ -38,7 +38,7 @@ declare global {
|
|||
toolkitStatus?: AgentStatus;
|
||||
}[];
|
||||
failure_count?: number;
|
||||
reAssignTo?:string;
|
||||
reAssignTo?: string;
|
||||
}
|
||||
|
||||
interface File {
|
||||
|
|
@ -66,7 +66,7 @@ declare global {
|
|||
log: AgentMessage[];
|
||||
img?: string[];
|
||||
activeWebviewIds?: ActiveWebView[];
|
||||
tools?:string[];
|
||||
tools?: string[];
|
||||
workerInfo?: {
|
||||
name: string;
|
||||
description: string;
|
||||
|
|
@ -94,13 +94,13 @@ declare global {
|
|||
task_id?: string;
|
||||
summary?: string;
|
||||
agent_name?: string;
|
||||
attaches?:File[]
|
||||
attaches?: File[]
|
||||
}
|
||||
|
||||
interface AgentMessage {
|
||||
step: string;
|
||||
data: {
|
||||
project_id?:string;
|
||||
project_id?: string;
|
||||
failure_count?: number;
|
||||
tokens?: number;
|
||||
sub_tasks?: TaskInfo[];
|
||||
|
|
@ -125,7 +125,8 @@ declare global {
|
|||
tools?: string[];
|
||||
//Context Length
|
||||
current_length?: number;
|
||||
max_length?: number
|
||||
max_length?: number;
|
||||
text?: string;
|
||||
};
|
||||
status?: 'running' | 'filled' | 'completed';
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue