@lobehub/lobehub 2.0.10 → 2.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/Dockerfile +44 -52
- package/changelog/v2.json +9 -0
- package/locales/ar/chat.json +4 -0
- package/locales/ar/models.json +65 -0
- package/locales/bg-BG/chat.json +4 -0
- package/locales/bg-BG/models.json +10 -0
- package/locales/de-DE/chat.json +4 -0
- package/locales/de-DE/models.json +41 -0
- package/locales/en-US/chat.json +4 -0
- package/locales/es-ES/chat.json +4 -0
- package/locales/es-ES/models.json +50 -0
- package/locales/fa-IR/chat.json +4 -0
- package/locales/fa-IR/models.json +39 -0
- package/locales/fr-FR/chat.json +4 -0
- package/locales/fr-FR/models.json +9 -0
- package/locales/it-IT/chat.json +4 -0
- package/locales/it-IT/models.json +62 -0
- package/locales/ja-JP/chat.json +4 -0
- package/locales/ja-JP/models.json +40 -0
- package/locales/ko-KR/chat.json +4 -0
- package/locales/ko-KR/models.json +31 -0
- package/locales/nl-NL/chat.json +4 -0
- package/locales/nl-NL/models.json +52 -0
- package/locales/pl-PL/chat.json +4 -0
- package/locales/pl-PL/models.json +43 -0
- package/locales/pt-BR/chat.json +4 -0
- package/locales/pt-BR/models.json +92 -0
- package/locales/ru-RU/chat.json +4 -0
- package/locales/ru-RU/models.json +34 -0
- package/locales/tr-TR/chat.json +4 -0
- package/locales/tr-TR/models.json +55 -0
- package/locales/vi-VN/chat.json +4 -0
- package/locales/vi-VN/models.json +31 -0
- package/locales/zh-CN/chat.json +4 -0
- package/locales/zh-TW/chat.json +4 -0
- package/package.json +1 -1
- package/packages/agent-runtime/src/groupOrchestration/GroupOrchestrationSupervisor.ts +18 -1
- package/packages/agent-runtime/src/groupOrchestration/__tests__/GroupOrchestrationSupervisor.test.ts +76 -5
- package/packages/agent-runtime/src/groupOrchestration/types.ts +3 -3
- package/packages/builtin-tool-group-management/src/client/Intervention/ExecuteTask.tsx +11 -11
- package/packages/builtin-tool-group-management/src/client/Intervention/ExecuteTasks.tsx +78 -79
- package/packages/builtin-tool-group-management/src/client/Render/ExecuteTask/index.tsx +3 -3
- package/packages/builtin-tool-group-management/src/client/Render/ExecuteTasks/index.tsx +61 -63
- package/packages/builtin-tool-group-management/src/client/Streaming/ExecuteTask/index.tsx +3 -3
- package/packages/builtin-tool-group-management/src/executor.test.ts +7 -9
- package/packages/builtin-tool-group-management/src/executor.ts +3 -3
- package/packages/builtin-tool-group-management/src/manifest.ts +49 -50
- package/packages/builtin-tool-group-management/src/systemRole.ts +153 -5
- package/packages/builtin-tool-group-management/src/types.ts +3 -2
- package/packages/builtin-tool-gtd/src/systemRole.ts +4 -4
- package/packages/context-engine/src/processors/TasksFlatten.ts +7 -5
- package/packages/context-engine/src/processors/__tests__/TasksFlatten.test.ts +164 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/agentGroup/index.ts +4 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/agentGroup/supervisor-after-multi-tasks.json +91 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/agentGroup/supervisor-content-only.json +74 -0
- package/packages/conversation-flow/src/__tests__/parse.test.ts +37 -0
- package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +70 -4
- package/packages/conversation-flow/src/transformation/__tests__/FlatListBuilder.test.ts +147 -0
- package/packages/types/src/message/ui/chat.ts +2 -0
- package/packages/types/src/tool/builtin.ts +5 -5
- package/src/features/Conversation/ChatItem/components/Title.tsx +1 -1
- package/src/features/Conversation/ChatList/index.tsx +0 -1
- package/src/features/Conversation/Messages/GroupTasks/TaskItem/ClientTaskItem.tsx +183 -0
- package/src/features/Conversation/Messages/GroupTasks/TaskItem/ServerTaskItem.tsx +94 -0
- package/src/features/Conversation/Messages/GroupTasks/TaskItem/TaskTitle.tsx +177 -0
- package/src/features/Conversation/Messages/GroupTasks/TaskItem/index.tsx +26 -0
- package/src/features/Conversation/Messages/GroupTasks/TaskItem/useClientTaskStats.ts +93 -0
- package/src/features/Conversation/Messages/GroupTasks/index.tsx +151 -0
- package/src/features/Conversation/Messages/Supervisor/index.tsx +7 -1
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/index.tsx +72 -91
- package/src/features/Conversation/Messages/Task/TaskDetailPanel/StatusContent.tsx +46 -17
- package/src/features/Conversation/Messages/Tasks/TaskItem/ClientTaskItem.tsx +9 -24
- package/src/features/Conversation/Messages/Tasks/TaskItem/ServerTaskItem.tsx +18 -38
- package/src/features/Conversation/Messages/Tasks/shared/ErrorState.tsx +45 -2
- package/src/features/Conversation/Messages/Tasks/shared/InitializingState.tsx +16 -1
- package/src/features/Conversation/Messages/Tasks/shared/TaskContent.tsx +68 -0
- package/src/features/Conversation/Messages/Tasks/shared/TaskMessages.tsx +383 -0
- package/src/features/Conversation/Messages/Tasks/shared/index.ts +4 -0
- package/src/features/Conversation/Messages/Tasks/shared/useTaskPolling.ts +48 -0
- package/src/features/Conversation/Messages/index.tsx +5 -0
- package/src/locales/default/chat.ts +4 -0
- package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +4 -0
- package/src/server/modules/AgentRuntime/__tests__/RuntimeExecutors.test.ts +106 -1
- package/src/server/services/aiAgent/__tests__/execAgent.threadId.test.ts +2 -2
- package/src/server/utils/truncateToolResult.ts +1 -4
- package/src/store/chat/agents/GroupOrchestration/__tests__/batch-exec-async-tasks.test.ts +15 -15
- package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +22 -15
- package/src/store/chat/agents/__tests__/createAgentExecutors/exec-tasks.test.ts +21 -10
- package/src/store/chat/agents/createAgentExecutors.ts +2 -0
- package/src/store/chat/slices/aiAgent/actions/groupOrchestration.ts +10 -7
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/CompletedState.tsx +0 -108
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/InstructionAccordion.tsx +0 -63
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/ProcessingState.tsx +0 -123
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { AccordionItem, Block, Text } from '@lobehub/ui';
|
|
4
|
+
import { memo, useMemo, useState } from 'react';
|
|
5
|
+
|
|
6
|
+
import { useAgentGroupStore } from '@/store/agentGroup';
|
|
7
|
+
import { agentGroupSelectors } from '@/store/agentGroup/selectors';
|
|
8
|
+
import { useChatStore } from '@/store/chat';
|
|
9
|
+
import { displayMessageSelectors } from '@/store/chat/selectors';
|
|
10
|
+
import { messageMapKey } from '@/store/chat/utils/messageMapKey';
|
|
11
|
+
import { ThreadStatus } from '@/types/index';
|
|
12
|
+
import type { UIChatMessage } from '@/types/index';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
ErrorState,
|
|
16
|
+
InitializingState,
|
|
17
|
+
TaskMessages,
|
|
18
|
+
isProcessingStatus,
|
|
19
|
+
} from '../../Tasks/shared';
|
|
20
|
+
import TaskTitle, { type TaskMetrics } from './TaskTitle';
|
|
21
|
+
|
|
22
|
+
interface ClientTaskItemProps {
|
|
23
|
+
item: UIChatMessage;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ClientTaskItem = memo<ClientTaskItemProps>(({ item }) => {
|
|
27
|
+
const { id, agentId: itemAgentId, groupId: itemGroupId, metadata, taskDetail } = item;
|
|
28
|
+
const [expanded, setExpanded] = useState(false);
|
|
29
|
+
|
|
30
|
+
const title = taskDetail?.title || metadata?.taskTitle;
|
|
31
|
+
const instruction = metadata?.instruction;
|
|
32
|
+
const status = taskDetail?.status;
|
|
33
|
+
const threadId = taskDetail?.threadId;
|
|
34
|
+
|
|
35
|
+
const isProcessing = isProcessingStatus(status);
|
|
36
|
+
const isCompleted = status === ThreadStatus.Completed;
|
|
37
|
+
const isError = status === ThreadStatus.Failed || status === ThreadStatus.Cancel;
|
|
38
|
+
const isInitializing = !taskDetail || !status;
|
|
39
|
+
|
|
40
|
+
// Fetch thread messages for client mode
|
|
41
|
+
// Use item's agentId (from task message) to query with the correct SubAgent ID that created the thread
|
|
42
|
+
// Fall back to activeAgentId if task message doesn't have agentId (shouldn't happen normally)
|
|
43
|
+
const [activeAgentId, activeTopicId, useFetchMessages] = useChatStore((s) => [
|
|
44
|
+
s.activeAgentId,
|
|
45
|
+
s.activeTopicId,
|
|
46
|
+
s.useFetchMessages,
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
// Use task message's agentId (skip 'supervisor' as it's not a valid agent ID for queries)
|
|
50
|
+
// Fall back to activeAgentId if not available
|
|
51
|
+
const agentId = itemAgentId && itemAgentId !== 'supervisor' ? itemAgentId : activeAgentId;
|
|
52
|
+
|
|
53
|
+
// Get agent info from store for displaying
|
|
54
|
+
const activeGroupId = useAgentGroupStore(agentGroupSelectors.activeGroupId);
|
|
55
|
+
const agent = useAgentGroupStore((s) =>
|
|
56
|
+
activeGroupId && itemAgentId
|
|
57
|
+
? agentGroupSelectors.getAgentByIdFromGroup(activeGroupId, itemAgentId)(s)
|
|
58
|
+
: null,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const threadContext = useMemo(
|
|
62
|
+
() => ({
|
|
63
|
+
agentId,
|
|
64
|
+
groupId: itemGroupId,
|
|
65
|
+
scope: 'thread' as const,
|
|
66
|
+
threadId,
|
|
67
|
+
topicId: activeTopicId,
|
|
68
|
+
}),
|
|
69
|
+
[agentId, itemGroupId, activeTopicId, threadId],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const threadMessageKey = useMemo(
|
|
73
|
+
() => (threadId ? messageMapKey(threadContext) : null),
|
|
74
|
+
[threadId, threadContext],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Fetch thread messages (skip when executing - messages come from real-time updates)
|
|
78
|
+
useFetchMessages(threadContext, isProcessing);
|
|
79
|
+
|
|
80
|
+
// Get thread messages from store using selector
|
|
81
|
+
const threadMessages = useChatStore((s) =>
|
|
82
|
+
threadMessageKey
|
|
83
|
+
? displayMessageSelectors.getDisplayMessagesByKey(threadMessageKey)(s)
|
|
84
|
+
: undefined,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Find the assistantGroup message which contains the children blocks
|
|
88
|
+
const assistantGroupMessage = threadMessages?.find((item) => item.role === 'assistantGroup');
|
|
89
|
+
const blocks = assistantGroupMessage?.children;
|
|
90
|
+
const childrenCount = blocks?.length ?? 0;
|
|
91
|
+
|
|
92
|
+
// Get model/provider from assistantGroup message
|
|
93
|
+
const model = assistantGroupMessage?.model;
|
|
94
|
+
const provider = assistantGroupMessage?.provider;
|
|
95
|
+
|
|
96
|
+
// Build metrics for TaskTitle based on blocks data
|
|
97
|
+
const metrics: TaskMetrics | undefined = useMemo(() => {
|
|
98
|
+
if (isProcessing && blocks) {
|
|
99
|
+
const toolCalls = blocks.reduce((sum, block) => sum + (block.tools?.length || 0), 0);
|
|
100
|
+
return {
|
|
101
|
+
isLoading: false,
|
|
102
|
+
startTime: assistantGroupMessage?.createdAt,
|
|
103
|
+
steps: blocks.length,
|
|
104
|
+
toolCalls,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (isCompleted || isError) {
|
|
108
|
+
return {
|
|
109
|
+
duration: taskDetail?.duration,
|
|
110
|
+
steps: taskDetail?.totalSteps,
|
|
111
|
+
toolCalls: taskDetail?.totalToolCalls,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}, [
|
|
116
|
+
isProcessing,
|
|
117
|
+
isCompleted,
|
|
118
|
+
isError,
|
|
119
|
+
blocks,
|
|
120
|
+
assistantGroupMessage?.createdAt,
|
|
121
|
+
taskDetail?.duration,
|
|
122
|
+
taskDetail?.totalSteps,
|
|
123
|
+
taskDetail?.totalToolCalls,
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
// Check if we have blocks to show (for Processing and Completed states)
|
|
127
|
+
const hasBlocks = blocks && childrenCount > 0;
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<AccordionItem
|
|
131
|
+
expand={expanded}
|
|
132
|
+
itemKey={id}
|
|
133
|
+
onExpandChange={setExpanded}
|
|
134
|
+
paddingBlock={4}
|
|
135
|
+
paddingInline={4}
|
|
136
|
+
title={
|
|
137
|
+
<TaskTitle
|
|
138
|
+
agent={
|
|
139
|
+
agent
|
|
140
|
+
? { avatar: agent.avatar || undefined, backgroundColor: agent.backgroundColor }
|
|
141
|
+
: undefined
|
|
142
|
+
}
|
|
143
|
+
metrics={metrics}
|
|
144
|
+
status={status}
|
|
145
|
+
title={title}
|
|
146
|
+
/>
|
|
147
|
+
}
|
|
148
|
+
>
|
|
149
|
+
<Block gap={16} padding={12} style={{ marginBlock: 8 }} variant={'outlined'}>
|
|
150
|
+
{instruction && (
|
|
151
|
+
<Block padding={12}>
|
|
152
|
+
<Text fontSize={13} type={'secondary'}>
|
|
153
|
+
{instruction}
|
|
154
|
+
</Text>
|
|
155
|
+
</Block>
|
|
156
|
+
)}
|
|
157
|
+
|
|
158
|
+
{/* Initializing State - no taskDetail yet or no blocks */}
|
|
159
|
+
{(isInitializing || (isProcessing && !hasBlocks)) && <InitializingState />}
|
|
160
|
+
|
|
161
|
+
{/* Processing or Completed State - show blocks via TaskMessages */}
|
|
162
|
+
{!isInitializing && (isProcessing || isCompleted) && hasBlocks && threadMessages && (
|
|
163
|
+
<TaskMessages
|
|
164
|
+
duration={taskDetail?.duration}
|
|
165
|
+
isProcessing={isProcessing}
|
|
166
|
+
messages={threadMessages}
|
|
167
|
+
model={model ?? undefined}
|
|
168
|
+
provider={provider ?? undefined}
|
|
169
|
+
startTime={assistantGroupMessage?.createdAt}
|
|
170
|
+
totalCost={taskDetail?.totalCost}
|
|
171
|
+
/>
|
|
172
|
+
)}
|
|
173
|
+
|
|
174
|
+
{/* Error State */}
|
|
175
|
+
{!isInitializing && isError && taskDetail && <ErrorState taskDetail={taskDetail} />}
|
|
176
|
+
</Block>
|
|
177
|
+
</AccordionItem>
|
|
178
|
+
);
|
|
179
|
+
}, Object.is);
|
|
180
|
+
|
|
181
|
+
ClientTaskItem.displayName = 'ClientTaskItem';
|
|
182
|
+
|
|
183
|
+
export default ClientTaskItem;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ThreadStatus } from '@lobechat/types';
|
|
4
|
+
import type { UIChatMessage } from '@lobechat/types';
|
|
5
|
+
import { AccordionItem, Block } from '@lobehub/ui';
|
|
6
|
+
import isEqual from 'fast-deep-equal';
|
|
7
|
+
import { memo, useMemo, useState } from 'react';
|
|
8
|
+
|
|
9
|
+
import { useAgentGroupStore } from '@/store/agentGroup';
|
|
10
|
+
import { agentGroupSelectors } from '@/store/agentGroup/selectors';
|
|
11
|
+
|
|
12
|
+
import { TaskContent } from '../../Tasks/shared';
|
|
13
|
+
import TaskTitle, { type TaskMetrics } from './TaskTitle';
|
|
14
|
+
|
|
15
|
+
interface ServerTaskItemProps {
|
|
16
|
+
item: UIChatMessage;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ServerTaskItem = memo<ServerTaskItemProps>(({ item }) => {
|
|
20
|
+
const { id, agentId, metadata, taskDetail, tasks } = item;
|
|
21
|
+
const [expanded, setExpanded] = useState(false);
|
|
22
|
+
|
|
23
|
+
const title = taskDetail?.title || metadata?.taskTitle;
|
|
24
|
+
const status = taskDetail?.status;
|
|
25
|
+
const threadId = taskDetail?.threadId;
|
|
26
|
+
|
|
27
|
+
const isCompleted = status === ThreadStatus.Completed;
|
|
28
|
+
const isError = status === ThreadStatus.Failed || status === ThreadStatus.Cancel;
|
|
29
|
+
|
|
30
|
+
// Get agent info from store
|
|
31
|
+
const activeGroupId = useAgentGroupStore(agentGroupSelectors.activeGroupId);
|
|
32
|
+
const agent = useAgentGroupStore((s) =>
|
|
33
|
+
activeGroupId && agentId
|
|
34
|
+
? agentGroupSelectors.getAgentByIdFromGroup(activeGroupId, agentId)(s)
|
|
35
|
+
: null,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// Build metrics for TaskTitle (only for completed/error states)
|
|
39
|
+
const metrics: TaskMetrics | undefined = useMemo(() => {
|
|
40
|
+
if (isCompleted || isError) {
|
|
41
|
+
return {
|
|
42
|
+
duration: taskDetail?.duration,
|
|
43
|
+
steps: taskDetail?.totalSteps,
|
|
44
|
+
toolCalls: taskDetail?.totalToolCalls,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}, [
|
|
49
|
+
isCompleted,
|
|
50
|
+
isError,
|
|
51
|
+
taskDetail?.duration,
|
|
52
|
+
taskDetail?.totalSteps,
|
|
53
|
+
taskDetail?.totalToolCalls,
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<AccordionItem
|
|
58
|
+
expand={expanded}
|
|
59
|
+
itemKey={id}
|
|
60
|
+
onExpandChange={setExpanded}
|
|
61
|
+
paddingBlock={4}
|
|
62
|
+
paddingInline={4}
|
|
63
|
+
title={
|
|
64
|
+
<TaskTitle
|
|
65
|
+
agent={
|
|
66
|
+
agent
|
|
67
|
+
? { avatar: agent.avatar || undefined, backgroundColor: agent.backgroundColor }
|
|
68
|
+
: undefined
|
|
69
|
+
}
|
|
70
|
+
metrics={metrics}
|
|
71
|
+
status={status}
|
|
72
|
+
title={title}
|
|
73
|
+
/>
|
|
74
|
+
}
|
|
75
|
+
>
|
|
76
|
+
<Block gap={16} padding={12} style={{ marginBlock: 8 }} variant={'outlined'}>
|
|
77
|
+
{expanded && (
|
|
78
|
+
<TaskContent
|
|
79
|
+
id={id}
|
|
80
|
+
isError={isError}
|
|
81
|
+
messages={tasks}
|
|
82
|
+
status={status}
|
|
83
|
+
taskDetail={taskDetail}
|
|
84
|
+
threadId={threadId}
|
|
85
|
+
/>
|
|
86
|
+
)}
|
|
87
|
+
</Block>
|
|
88
|
+
</AccordionItem>
|
|
89
|
+
);
|
|
90
|
+
}, isEqual);
|
|
91
|
+
|
|
92
|
+
ServerTaskItem.displayName = 'ServerTaskItem';
|
|
93
|
+
|
|
94
|
+
export default ServerTaskItem;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ThreadStatus } from '@lobechat/types';
|
|
4
|
+
import { Avatar, Block, Flexbox, Icon, Text } from '@lobehub/ui';
|
|
5
|
+
import { cssVar } from 'antd-style';
|
|
6
|
+
import { Footprints, ListChecksIcon, Wrench, XIcon } from 'lucide-react';
|
|
7
|
+
import { memo, useEffect, useState } from 'react';
|
|
8
|
+
import { useTranslation } from 'react-i18next';
|
|
9
|
+
|
|
10
|
+
import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
|
|
11
|
+
import { DEFAULT_AVATAR } from '@/const/meta';
|
|
12
|
+
|
|
13
|
+
import { formatDuration, formatElapsedTime, isProcessingStatus } from '../../Tasks/shared';
|
|
14
|
+
|
|
15
|
+
export interface TaskMetrics {
|
|
16
|
+
/** Task duration in milliseconds (for completed tasks) */
|
|
17
|
+
duration?: number;
|
|
18
|
+
/** Whether metrics are still loading */
|
|
19
|
+
isLoading?: boolean;
|
|
20
|
+
/** Start time timestamp for elapsed time calculation */
|
|
21
|
+
startTime?: number;
|
|
22
|
+
/** Number of execution steps/blocks */
|
|
23
|
+
steps?: number;
|
|
24
|
+
/** Total tool calls count */
|
|
25
|
+
toolCalls?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface TaskTitleProps {
|
|
29
|
+
/** Agent info for avatar display */
|
|
30
|
+
agent?: {
|
|
31
|
+
avatar?: string;
|
|
32
|
+
backgroundColor?: string | null;
|
|
33
|
+
};
|
|
34
|
+
/** Metrics to display (steps, tool calls, elapsed time) */
|
|
35
|
+
metrics?: TaskMetrics;
|
|
36
|
+
status?: ThreadStatus;
|
|
37
|
+
title?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const TaskStatusIndicator = memo<{ status?: ThreadStatus }>(({ status }) => {
|
|
41
|
+
const isCompleted = status === ThreadStatus.Completed;
|
|
42
|
+
const isError = status === ThreadStatus.Failed || status === ThreadStatus.Cancel;
|
|
43
|
+
const isProcessing = status ? isProcessingStatus(status) : false;
|
|
44
|
+
const isInitializing = !status;
|
|
45
|
+
|
|
46
|
+
let icon;
|
|
47
|
+
|
|
48
|
+
if (isCompleted) {
|
|
49
|
+
icon = <Icon color={cssVar.colorSuccess} icon={ListChecksIcon} />;
|
|
50
|
+
} else if (isError) {
|
|
51
|
+
icon = <Icon color={cssVar.colorError} icon={XIcon} />;
|
|
52
|
+
} else if (isProcessing || isInitializing) {
|
|
53
|
+
icon = <NeuralNetworkLoading size={16} />;
|
|
54
|
+
} else {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<Block
|
|
60
|
+
align={'center'}
|
|
61
|
+
flex={'none'}
|
|
62
|
+
gap={4}
|
|
63
|
+
height={24}
|
|
64
|
+
horizontal
|
|
65
|
+
justify={'center'}
|
|
66
|
+
style={{
|
|
67
|
+
fontSize: 12,
|
|
68
|
+
}}
|
|
69
|
+
variant={'outlined'}
|
|
70
|
+
width={24}
|
|
71
|
+
>
|
|
72
|
+
{icon}
|
|
73
|
+
</Block>
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
TaskStatusIndicator.displayName = 'TaskStatusIndicator';
|
|
78
|
+
|
|
79
|
+
interface MetricsDisplayProps {
|
|
80
|
+
metrics: TaskMetrics;
|
|
81
|
+
status?: ThreadStatus;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const MetricsDisplay = memo<MetricsDisplayProps>(({ metrics, status }) => {
|
|
85
|
+
const { t } = useTranslation('chat');
|
|
86
|
+
const { steps, toolCalls, startTime, duration, isLoading } = metrics;
|
|
87
|
+
const [elapsedTime, setElapsedTime] = useState(0);
|
|
88
|
+
|
|
89
|
+
const isProcessing = status ? isProcessingStatus(status) : false;
|
|
90
|
+
|
|
91
|
+
// Calculate initial elapsed time
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (startTime && isProcessing) {
|
|
94
|
+
setElapsedTime(Math.max(0, Date.now() - startTime));
|
|
95
|
+
}
|
|
96
|
+
}, [startTime, isProcessing]);
|
|
97
|
+
|
|
98
|
+
// Timer for updating elapsed time every second (only when processing)
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (!startTime || !isProcessing) return;
|
|
101
|
+
|
|
102
|
+
const timer = setInterval(() => {
|
|
103
|
+
setElapsedTime(Math.max(0, Date.now() - startTime));
|
|
104
|
+
}, 1000);
|
|
105
|
+
|
|
106
|
+
return () => clearInterval(timer);
|
|
107
|
+
}, [startTime, isProcessing]);
|
|
108
|
+
|
|
109
|
+
// Don't show metrics if loading or no data
|
|
110
|
+
if (isLoading) return null;
|
|
111
|
+
|
|
112
|
+
const hasSteps = steps !== undefined && steps > 0;
|
|
113
|
+
const hasToolCalls = toolCalls !== undefined && toolCalls > 0;
|
|
114
|
+
const hasTime = isProcessing ? startTime !== undefined : duration !== undefined;
|
|
115
|
+
|
|
116
|
+
// Don't render if no metrics to show
|
|
117
|
+
if (!hasSteps && !hasToolCalls && !hasTime) return null;
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<Flexbox align="center" gap={8} horizontal>
|
|
121
|
+
{/* Steps */}
|
|
122
|
+
{hasSteps && (
|
|
123
|
+
<Flexbox align="center" gap={2} horizontal>
|
|
124
|
+
<Icon color={cssVar.colorTextTertiary} icon={Footprints} size={12} />
|
|
125
|
+
<Text fontSize={12} type="secondary">
|
|
126
|
+
{steps}
|
|
127
|
+
</Text>
|
|
128
|
+
</Flexbox>
|
|
129
|
+
)}
|
|
130
|
+
{/* Tool calls */}
|
|
131
|
+
{hasToolCalls && (
|
|
132
|
+
<Flexbox align="center" gap={2} horizontal>
|
|
133
|
+
<Icon color={cssVar.colorTextTertiary} icon={Wrench} size={12} />
|
|
134
|
+
<Text fontSize={12} type="secondary">
|
|
135
|
+
{toolCalls}
|
|
136
|
+
</Text>
|
|
137
|
+
</Flexbox>
|
|
138
|
+
)}
|
|
139
|
+
{/* Time */}
|
|
140
|
+
{hasTime && (
|
|
141
|
+
<Text fontSize={12} type="secondary">
|
|
142
|
+
{isProcessing
|
|
143
|
+
? formatElapsedTime(elapsedTime)
|
|
144
|
+
: duration
|
|
145
|
+
? t('task.metrics.duration', { duration: formatDuration(duration) })
|
|
146
|
+
: null}
|
|
147
|
+
</Text>
|
|
148
|
+
)}
|
|
149
|
+
</Flexbox>
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
MetricsDisplay.displayName = 'MetricsDisplay';
|
|
154
|
+
|
|
155
|
+
const TaskTitle = memo<TaskTitleProps>(({ title, status, metrics, agent }) => {
|
|
156
|
+
return (
|
|
157
|
+
<Flexbox align="center" gap={6} horizontal>
|
|
158
|
+
<TaskStatusIndicator status={status} />
|
|
159
|
+
{agent && (
|
|
160
|
+
<Avatar
|
|
161
|
+
avatar={agent.avatar || DEFAULT_AVATAR}
|
|
162
|
+
background={agent.backgroundColor || undefined}
|
|
163
|
+
shape={'circle'}
|
|
164
|
+
size={20}
|
|
165
|
+
/>
|
|
166
|
+
)}
|
|
167
|
+
<Text ellipsis fontSize={14}>
|
|
168
|
+
{title}
|
|
169
|
+
</Text>
|
|
170
|
+
{metrics && <MetricsDisplay metrics={metrics} status={status} />}
|
|
171
|
+
</Flexbox>
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
TaskTitle.displayName = 'TaskTitle';
|
|
176
|
+
|
|
177
|
+
export default TaskTitle;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { UIChatMessage } from '@lobechat/types';
|
|
4
|
+
import isEqual from 'fast-deep-equal';
|
|
5
|
+
import { memo } from 'react';
|
|
6
|
+
|
|
7
|
+
import ClientTaskItem from './ClientTaskItem';
|
|
8
|
+
import ServerTaskItem from './ServerTaskItem';
|
|
9
|
+
|
|
10
|
+
interface TaskItemProps {
|
|
11
|
+
item: UIChatMessage;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const TaskItem = memo<TaskItemProps>(({ item }) => {
|
|
15
|
+
const isClientMode = item.taskDetail?.clientMode;
|
|
16
|
+
|
|
17
|
+
if (isClientMode) {
|
|
18
|
+
return <ClientTaskItem item={item} />;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return <ServerTaskItem item={item} />;
|
|
22
|
+
}, isEqual);
|
|
23
|
+
|
|
24
|
+
TaskItem.displayName = 'TaskItem';
|
|
25
|
+
|
|
26
|
+
export default TaskItem;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
|
|
5
|
+
import { useChatStore } from '@/store/chat';
|
|
6
|
+
import { displayMessageSelectors } from '@/store/chat/selectors';
|
|
7
|
+
import { messageMapKey } from '@/store/chat/utils/messageMapKey';
|
|
8
|
+
|
|
9
|
+
export interface ClientTaskStats {
|
|
10
|
+
isLoading: boolean;
|
|
11
|
+
startTime?: number;
|
|
12
|
+
steps: number;
|
|
13
|
+
toolCalls: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface UseClientTaskStatsOptions {
|
|
17
|
+
/** Agent ID from the task message (use task's agentId, not activeAgentId) */
|
|
18
|
+
agentId?: string;
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
/** Group ID from the task message (use task's groupId, not activeGroupId) */
|
|
21
|
+
groupId?: string;
|
|
22
|
+
threadId?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Hook to fetch thread messages and compute task statistics for client mode tasks.
|
|
27
|
+
* Used in TaskItem to display progress metrics (steps, tool calls, elapsed time).
|
|
28
|
+
*/
|
|
29
|
+
export const useClientTaskStats = ({
|
|
30
|
+
agentId: propAgentId,
|
|
31
|
+
groupId,
|
|
32
|
+
threadId,
|
|
33
|
+
enabled = true,
|
|
34
|
+
}: UseClientTaskStatsOptions): ClientTaskStats => {
|
|
35
|
+
// Use task message's agentId to query with the correct SubAgent ID that created the thread
|
|
36
|
+
// Fall back to activeAgentId if not provided
|
|
37
|
+
const [activeAgentId, activeTopicId, useFetchMessages] = useChatStore((s) => [
|
|
38
|
+
s.activeAgentId,
|
|
39
|
+
s.activeTopicId,
|
|
40
|
+
s.useFetchMessages,
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const agentId = propAgentId || activeAgentId;
|
|
44
|
+
|
|
45
|
+
const threadContext = useMemo(
|
|
46
|
+
() => ({
|
|
47
|
+
agentId,
|
|
48
|
+
groupId,
|
|
49
|
+
scope: 'thread' as const,
|
|
50
|
+
threadId,
|
|
51
|
+
topicId: activeTopicId,
|
|
52
|
+
}),
|
|
53
|
+
[agentId, groupId, activeTopicId, threadId],
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const threadMessageKey = useMemo(
|
|
57
|
+
() => (threadId ? messageMapKey(threadContext) : null),
|
|
58
|
+
[threadId, threadContext],
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Fetch thread messages (skip when disabled or no threadId)
|
|
62
|
+
useFetchMessages(threadContext, !enabled || !threadId);
|
|
63
|
+
|
|
64
|
+
// Get thread messages from store using selector
|
|
65
|
+
const threadMessages = useChatStore((s) =>
|
|
66
|
+
threadMessageKey
|
|
67
|
+
? displayMessageSelectors.getDisplayMessagesByKey(threadMessageKey)(s)
|
|
68
|
+
: undefined,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Compute stats from thread messages
|
|
72
|
+
return useMemo(() => {
|
|
73
|
+
if (!threadMessages || !enabled) {
|
|
74
|
+
return { isLoading: true, steps: 0, toolCalls: 0 };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Find the assistantGroup message which contains the children blocks
|
|
78
|
+
const assistantGroupMessage = threadMessages.find((item) => item.role === 'assistantGroup');
|
|
79
|
+
const blocks = assistantGroupMessage?.children ?? [];
|
|
80
|
+
|
|
81
|
+
// Calculate stats
|
|
82
|
+
const steps = blocks.length;
|
|
83
|
+
const toolCalls = blocks.reduce((sum, block) => sum + (block.tools?.length || 0), 0);
|
|
84
|
+
const startTime = assistantGroupMessage?.createdAt;
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
isLoading: false,
|
|
88
|
+
startTime,
|
|
89
|
+
steps,
|
|
90
|
+
toolCalls,
|
|
91
|
+
};
|
|
92
|
+
}, [threadMessages, enabled]);
|
|
93
|
+
};
|