@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
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import { Flexbox, Text } from '@lobehub/ui';
|
|
4
4
|
import { createStaticStyles, keyframes } from 'antd-style';
|
|
5
|
-
import { memo } from 'react';
|
|
5
|
+
import { memo, useEffect, useState } from 'react';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
|
|
8
8
|
import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
|
|
9
9
|
import { shinyTextStyles } from '@/styles';
|
|
10
10
|
|
|
11
|
+
import { formatElapsedTime } from './utils';
|
|
12
|
+
|
|
11
13
|
const shimmer = keyframes`
|
|
12
14
|
0% {
|
|
13
15
|
transform: translateX(-100%);
|
|
@@ -48,6 +50,18 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
|
48
50
|
|
|
49
51
|
const InitializingState = memo(() => {
|
|
50
52
|
const { t } = useTranslation('chat');
|
|
53
|
+
const [elapsedTime, setElapsedTime] = useState(0);
|
|
54
|
+
|
|
55
|
+
// Timer for updating elapsed time every second
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const startTime = Date.now();
|
|
58
|
+
|
|
59
|
+
const timer = setInterval(() => {
|
|
60
|
+
setElapsedTime(Date.now() - startTime);
|
|
61
|
+
}, 1000);
|
|
62
|
+
|
|
63
|
+
return () => clearInterval(timer);
|
|
64
|
+
}, []);
|
|
51
65
|
|
|
52
66
|
return (
|
|
53
67
|
<Flexbox className={styles.container} gap={12}>
|
|
@@ -56,6 +70,7 @@ const InitializingState = memo(() => {
|
|
|
56
70
|
<Text className={shinyTextStyles.shinyText} weight={500}>
|
|
57
71
|
{t('task.status.initializing')}
|
|
58
72
|
</Text>
|
|
73
|
+
<Text type="secondary">({formatElapsedTime(elapsedTime)})</Text>
|
|
59
74
|
</Flexbox>
|
|
60
75
|
</Flexbox>
|
|
61
76
|
);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ThreadStatus } from '@lobechat/types';
|
|
4
|
+
import type { TaskDetail, UIChatMessage } from '@lobechat/types';
|
|
5
|
+
import { Flexbox, Text } from '@lobehub/ui';
|
|
6
|
+
import { memo } from 'react';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
8
|
+
|
|
9
|
+
import BubblesLoading from '@/components/BubblesLoading';
|
|
10
|
+
|
|
11
|
+
import ErrorState from './ErrorState';
|
|
12
|
+
import InitializingState from './InitializingState';
|
|
13
|
+
import TaskMessages from './TaskMessages';
|
|
14
|
+
import { useTaskPolling } from './useTaskPolling';
|
|
15
|
+
|
|
16
|
+
export interface TaskContentProps {
|
|
17
|
+
id: string;
|
|
18
|
+
isError: boolean;
|
|
19
|
+
messages: UIChatMessage[] | undefined;
|
|
20
|
+
status: ThreadStatus | undefined;
|
|
21
|
+
taskDetail: TaskDetail | undefined;
|
|
22
|
+
threadId: string | undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const TaskContent = memo<TaskContentProps>(
|
|
26
|
+
({ id, threadId, status, messages, taskDetail, isError }) => {
|
|
27
|
+
const { t } = useTranslation('chat');
|
|
28
|
+
const { isProcessing } = useTaskPolling({
|
|
29
|
+
messageId: id,
|
|
30
|
+
status,
|
|
31
|
+
threadId,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// No messages yet
|
|
35
|
+
if (!messages || messages.length === 0) {
|
|
36
|
+
// Still processing: show full initializing state
|
|
37
|
+
if (isProcessing) {
|
|
38
|
+
return <InitializingState />;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Already completed but loading messages: show simple loading
|
|
42
|
+
return (
|
|
43
|
+
<Flexbox align="center" gap={4} horizontal>
|
|
44
|
+
<BubblesLoading />
|
|
45
|
+
<Text type="secondary">{t('task.status.fetchingDetails')}</Text>
|
|
46
|
+
</Flexbox>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<>
|
|
52
|
+
<TaskMessages
|
|
53
|
+
duration={taskDetail?.duration}
|
|
54
|
+
isProcessing={isProcessing}
|
|
55
|
+
messages={messages}
|
|
56
|
+
startTime={taskDetail?.startedAt ? new Date(taskDetail.startedAt).getTime() : undefined}
|
|
57
|
+
totalCost={taskDetail?.totalCost}
|
|
58
|
+
/>
|
|
59
|
+
{/* Error states: Failed, Cancel */}
|
|
60
|
+
{isError && taskDetail && <ErrorState taskDetail={taskDetail} />}
|
|
61
|
+
</>
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
TaskContent.displayName = 'TaskContent';
|
|
67
|
+
|
|
68
|
+
export default TaskContent;
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { type AssistantContentBlock, type UIChatMessage } from '@lobechat/types';
|
|
4
|
+
import {
|
|
5
|
+
Accordion,
|
|
6
|
+
AccordionItem,
|
|
7
|
+
Block,
|
|
8
|
+
Flexbox,
|
|
9
|
+
Icon,
|
|
10
|
+
Markdown,
|
|
11
|
+
ScrollShadow,
|
|
12
|
+
Text,
|
|
13
|
+
} from '@lobehub/ui';
|
|
14
|
+
import { createStaticStyles, cssVar } from 'antd-style';
|
|
15
|
+
import { ScrollText, Workflow } from 'lucide-react';
|
|
16
|
+
import { type RefObject, memo, useEffect, useMemo, useState } from 'react';
|
|
17
|
+
import { useTranslation } from 'react-i18next';
|
|
18
|
+
|
|
19
|
+
import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
|
|
20
|
+
import { useAutoScroll } from '@/hooks/useAutoScroll';
|
|
21
|
+
|
|
22
|
+
import ContentBlock from '../../AssistantGroup/components/ContentBlock';
|
|
23
|
+
import Usage from '../../components/Extras/Usage';
|
|
24
|
+
import AnimatedNumber from '../../components/Extras/Usage/UsageDetail/AnimatedNumber';
|
|
25
|
+
import { accumulateUsage, formatDuration, formatElapsedTime } from './utils';
|
|
26
|
+
|
|
27
|
+
const styles = createStaticStyles(({ css }) => ({
|
|
28
|
+
contentScroll: css`
|
|
29
|
+
max-height: min(50vh, 300px);
|
|
30
|
+
`,
|
|
31
|
+
instructionContent: css`
|
|
32
|
+
overflow: auto;
|
|
33
|
+
max-height: 300px;
|
|
34
|
+
`,
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* InstructionAccordion - Shows the task instruction in a collapsible accordion
|
|
39
|
+
*/
|
|
40
|
+
const InstructionAccordion = memo<{ childrenCount: number; instruction: string }>(
|
|
41
|
+
({ instruction, childrenCount }) => {
|
|
42
|
+
const { t } = useTranslation('chat');
|
|
43
|
+
|
|
44
|
+
// Auto-collapse instruction when children count exceeds threshold
|
|
45
|
+
const [expandedKeys, setExpandedKeys] = useState<string[]>(['instruction']);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (childrenCount > 1) {
|
|
49
|
+
setExpandedKeys([]);
|
|
50
|
+
}
|
|
51
|
+
}, [childrenCount > 1]);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Accordion
|
|
55
|
+
expandedKeys={expandedKeys}
|
|
56
|
+
gap={8}
|
|
57
|
+
onExpandedChange={(keys) => setExpandedKeys(keys as string[])}
|
|
58
|
+
>
|
|
59
|
+
<AccordionItem
|
|
60
|
+
itemKey="instruction"
|
|
61
|
+
paddingBlock={4}
|
|
62
|
+
paddingInline={4}
|
|
63
|
+
title={
|
|
64
|
+
<Flexbox align="center" gap={8} horizontal>
|
|
65
|
+
<Block
|
|
66
|
+
align="center"
|
|
67
|
+
flex="none"
|
|
68
|
+
gap={4}
|
|
69
|
+
height={24}
|
|
70
|
+
horizontal
|
|
71
|
+
justify="center"
|
|
72
|
+
style={{ fontSize: 12 }}
|
|
73
|
+
variant="outlined"
|
|
74
|
+
width={24}
|
|
75
|
+
>
|
|
76
|
+
<Icon color={cssVar.colorTextSecondary} icon={ScrollText} />
|
|
77
|
+
</Block>
|
|
78
|
+
<Text as="span" type="secondary">
|
|
79
|
+
{t('task.instruction')}
|
|
80
|
+
</Text>
|
|
81
|
+
</Flexbox>
|
|
82
|
+
}
|
|
83
|
+
>
|
|
84
|
+
<Block
|
|
85
|
+
className={styles.instructionContent}
|
|
86
|
+
padding={12}
|
|
87
|
+
style={{ marginBlock: 8 }}
|
|
88
|
+
variant={'outlined'}
|
|
89
|
+
>
|
|
90
|
+
<Markdown variant={'chat'}>{instruction}</Markdown>
|
|
91
|
+
</Block>
|
|
92
|
+
</AccordionItem>
|
|
93
|
+
</Accordion>
|
|
94
|
+
);
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
InstructionAccordion.displayName = 'InstructionAccordion';
|
|
99
|
+
|
|
100
|
+
interface TaskMessagesProps {
|
|
101
|
+
/**
|
|
102
|
+
* Task duration in ms (for completed state)
|
|
103
|
+
*/
|
|
104
|
+
duration?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Whether the task is currently processing
|
|
107
|
+
*/
|
|
108
|
+
isProcessing?: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Messages from task execution (parsed by conversation-flow)
|
|
111
|
+
* Will extract assistantGroup.children as blocks for rendering
|
|
112
|
+
*/
|
|
113
|
+
messages: UIChatMessage[];
|
|
114
|
+
/**
|
|
115
|
+
* Model name for usage display
|
|
116
|
+
*/
|
|
117
|
+
model?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Provider name for usage display
|
|
120
|
+
*/
|
|
121
|
+
provider?: string;
|
|
122
|
+
/**
|
|
123
|
+
* Task start time for elapsed time calculation
|
|
124
|
+
*/
|
|
125
|
+
startTime?: number;
|
|
126
|
+
/**
|
|
127
|
+
* Total cost (for completed state)
|
|
128
|
+
*/
|
|
129
|
+
totalCost?: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Processing state - shows all blocks with loading indicator
|
|
134
|
+
*/
|
|
135
|
+
const ProcessingView = memo<{
|
|
136
|
+
accumulatedUsage: { cost?: number; totalTokens?: number };
|
|
137
|
+
assistantId: string;
|
|
138
|
+
blocks: AssistantContentBlock[];
|
|
139
|
+
model?: string;
|
|
140
|
+
provider?: string;
|
|
141
|
+
startTime?: number;
|
|
142
|
+
totalToolCalls: number;
|
|
143
|
+
}>(({ blocks, assistantId, startTime, model, provider, totalToolCalls, accumulatedUsage }) => {
|
|
144
|
+
const { t } = useTranslation('chat');
|
|
145
|
+
const [elapsedTime, setElapsedTime] = useState(0);
|
|
146
|
+
const { ref, handleScroll } = useAutoScroll<HTMLDivElement>({
|
|
147
|
+
deps: [blocks],
|
|
148
|
+
enabled: true,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Calculate initial elapsed time
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (startTime) {
|
|
154
|
+
setElapsedTime(Math.max(0, Date.now() - startTime));
|
|
155
|
+
}
|
|
156
|
+
}, [startTime]);
|
|
157
|
+
|
|
158
|
+
// Timer for updating elapsed time every second
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (!startTime) return;
|
|
161
|
+
|
|
162
|
+
const timer = setInterval(() => {
|
|
163
|
+
setElapsedTime(Math.max(0, Date.now() - startTime));
|
|
164
|
+
}, 1000);
|
|
165
|
+
|
|
166
|
+
return () => clearInterval(timer);
|
|
167
|
+
}, [startTime]);
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<Flexbox gap={8}>
|
|
171
|
+
<Flexbox align="center" gap={8} horizontal paddingInline={4}>
|
|
172
|
+
<Block
|
|
173
|
+
align="center"
|
|
174
|
+
flex="none"
|
|
175
|
+
gap={4}
|
|
176
|
+
height={24}
|
|
177
|
+
horizontal
|
|
178
|
+
justify="center"
|
|
179
|
+
style={{ fontSize: 12 }}
|
|
180
|
+
variant="outlined"
|
|
181
|
+
width={24}
|
|
182
|
+
>
|
|
183
|
+
<NeuralNetworkLoading size={16} />
|
|
184
|
+
</Block>
|
|
185
|
+
<Flexbox align="center" gap={4} horizontal>
|
|
186
|
+
<Text as="span" type="secondary" weight={500}>
|
|
187
|
+
<AnimatedNumber
|
|
188
|
+
duration={500}
|
|
189
|
+
formatter={(v) => Math.round(v).toString()}
|
|
190
|
+
value={totalToolCalls}
|
|
191
|
+
/>
|
|
192
|
+
</Text>
|
|
193
|
+
<Text as="span" type="secondary">
|
|
194
|
+
{t('task.metrics.toolCallsShort')}
|
|
195
|
+
</Text>
|
|
196
|
+
{startTime && (
|
|
197
|
+
<Text as="span" type="secondary">
|
|
198
|
+
({formatElapsedTime(elapsedTime)})
|
|
199
|
+
</Text>
|
|
200
|
+
)}
|
|
201
|
+
</Flexbox>
|
|
202
|
+
</Flexbox>
|
|
203
|
+
<ScrollShadow
|
|
204
|
+
className={styles.contentScroll}
|
|
205
|
+
offset={12}
|
|
206
|
+
onScroll={handleScroll}
|
|
207
|
+
ref={ref as RefObject<HTMLDivElement>}
|
|
208
|
+
size={8}
|
|
209
|
+
>
|
|
210
|
+
<Flexbox gap={8}>
|
|
211
|
+
{blocks.map((block) => (
|
|
212
|
+
<ContentBlock {...block} assistantId={assistantId} disableEditing key={block.id} />
|
|
213
|
+
))}
|
|
214
|
+
</Flexbox>
|
|
215
|
+
</ScrollShadow>
|
|
216
|
+
|
|
217
|
+
{/* Usage display */}
|
|
218
|
+
{model && provider && <Usage model={model} provider={provider} usage={accumulatedUsage} />}
|
|
219
|
+
</Flexbox>
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
ProcessingView.displayName = 'ProcessingView';
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Completed state - shows intermediate steps in accordion, final result visible
|
|
227
|
+
*/
|
|
228
|
+
const CompletedView = memo<{
|
|
229
|
+
assistantId: string;
|
|
230
|
+
blocks: AssistantContentBlock[];
|
|
231
|
+
duration?: number;
|
|
232
|
+
model?: string;
|
|
233
|
+
provider?: string;
|
|
234
|
+
totalCost?: number;
|
|
235
|
+
totalTokens?: number;
|
|
236
|
+
totalToolCalls: number;
|
|
237
|
+
}>(({ blocks, assistantId, duration, totalToolCalls, model, provider, totalTokens, totalCost }) => {
|
|
238
|
+
const { t } = useTranslation('chat');
|
|
239
|
+
|
|
240
|
+
// Split blocks: intermediate steps (all but last) and final result (last)
|
|
241
|
+
const { intermediateBlocks, finalBlock } = useMemo(() => {
|
|
242
|
+
if (blocks.length === 0) return { finalBlock: null, intermediateBlocks: [] };
|
|
243
|
+
if (blocks.length === 1) return { finalBlock: blocks[0], intermediateBlocks: [] };
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
finalBlock: blocks.at(-1)!,
|
|
247
|
+
intermediateBlocks: blocks.slice(0, -1),
|
|
248
|
+
};
|
|
249
|
+
}, [blocks]);
|
|
250
|
+
|
|
251
|
+
if (!finalBlock) return null;
|
|
252
|
+
|
|
253
|
+
const title = (
|
|
254
|
+
<Flexbox align="center" gap={8} horizontal>
|
|
255
|
+
<Block
|
|
256
|
+
align="center"
|
|
257
|
+
flex="none"
|
|
258
|
+
gap={4}
|
|
259
|
+
height={24}
|
|
260
|
+
horizontal
|
|
261
|
+
justify="center"
|
|
262
|
+
style={{ fontSize: 12 }}
|
|
263
|
+
variant="outlined"
|
|
264
|
+
width={24}
|
|
265
|
+
>
|
|
266
|
+
<Icon color={cssVar.colorTextSecondary} icon={Workflow} />
|
|
267
|
+
</Block>
|
|
268
|
+
<Flexbox align="center" gap={4} horizontal>
|
|
269
|
+
<Text as="span" type="secondary" weight={500}>
|
|
270
|
+
{totalToolCalls}
|
|
271
|
+
</Text>
|
|
272
|
+
<Text as="span" type="secondary">
|
|
273
|
+
{t('task.metrics.toolCallsShort')}
|
|
274
|
+
</Text>
|
|
275
|
+
{/* Duration display */}
|
|
276
|
+
{duration && (
|
|
277
|
+
<Text as="span" type="secondary">
|
|
278
|
+
{t('task.metrics.duration', { duration: formatDuration(duration) })}
|
|
279
|
+
</Text>
|
|
280
|
+
)}
|
|
281
|
+
</Flexbox>
|
|
282
|
+
</Flexbox>
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<Flexbox gap={8}>
|
|
287
|
+
{/* Intermediate steps - collapsed by default */}
|
|
288
|
+
{intermediateBlocks.length > 0 && (
|
|
289
|
+
<Accordion defaultExpandedKeys={[]} gap={8}>
|
|
290
|
+
<AccordionItem itemKey="intermediate" paddingBlock={4} paddingInline={4} title={title}>
|
|
291
|
+
<Flexbox gap={8} paddingInline={4} style={{ marginTop: 8 }}>
|
|
292
|
+
{intermediateBlocks.map((block) => (
|
|
293
|
+
<ContentBlock {...block} assistantId={assistantId} disableEditing key={block.id} />
|
|
294
|
+
))}
|
|
295
|
+
</Flexbox>
|
|
296
|
+
</AccordionItem>
|
|
297
|
+
</Accordion>
|
|
298
|
+
)}
|
|
299
|
+
|
|
300
|
+
{/* Final result - always visible */}
|
|
301
|
+
<ContentBlock {...finalBlock} assistantId={assistantId} disableEditing />
|
|
302
|
+
|
|
303
|
+
{/* Usage display */}
|
|
304
|
+
{model && provider && (
|
|
305
|
+
<Usage model={model} provider={provider} usage={{ cost: totalCost, totalTokens }} />
|
|
306
|
+
)}
|
|
307
|
+
</Flexbox>
|
|
308
|
+
);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
CompletedView.displayName = 'CompletedView';
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* TaskMessages - Renders task execution messages (blocks) for both processing and completed states
|
|
315
|
+
*
|
|
316
|
+
* Extracts assistantGroup.children (blocks) from messages and renders them:
|
|
317
|
+
* - Processing: Shows all blocks with loading indicator and real-time updates
|
|
318
|
+
* - Completed: Shows intermediate steps in accordion, final result always visible
|
|
319
|
+
*/
|
|
320
|
+
const TaskMessages = memo<TaskMessagesProps>(
|
|
321
|
+
({ messages, isProcessing = false, startTime, duration, model, provider, totalCost }) => {
|
|
322
|
+
// Extract blocks and instruction from messages
|
|
323
|
+
const { blocks, assistantId, instruction } = useMemo(() => {
|
|
324
|
+
if (!messages || messages.length === 0)
|
|
325
|
+
return { assistantId: '', blocks: [], instruction: undefined };
|
|
326
|
+
|
|
327
|
+
const assistantGroupMessage = messages.find((item) => item.role === 'assistantGroup');
|
|
328
|
+
const userMessage = messages.find((item) => item.role === 'user');
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
assistantId: assistantGroupMessage?.id ?? '',
|
|
332
|
+
blocks: assistantGroupMessage?.children ?? [],
|
|
333
|
+
instruction: userMessage?.content,
|
|
334
|
+
};
|
|
335
|
+
}, [messages]);
|
|
336
|
+
|
|
337
|
+
// Calculate total tool calls
|
|
338
|
+
const totalToolCalls = useMemo(
|
|
339
|
+
() => blocks.reduce((sum, block) => sum + (block.tools?.length || 0), 0),
|
|
340
|
+
[blocks],
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
// Accumulate usage from all blocks
|
|
344
|
+
const accumulatedUsage = useMemo(() => accumulateUsage(blocks), [blocks]);
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
<Flexbox gap={4}>
|
|
348
|
+
{/* Instruction accordion */}
|
|
349
|
+
{instruction && (
|
|
350
|
+
<InstructionAccordion childrenCount={blocks.length} instruction={instruction} />
|
|
351
|
+
)}
|
|
352
|
+
|
|
353
|
+
{/* Processing or Completed view */}
|
|
354
|
+
{isProcessing ? (
|
|
355
|
+
<ProcessingView
|
|
356
|
+
accumulatedUsage={accumulatedUsage}
|
|
357
|
+
assistantId={assistantId}
|
|
358
|
+
blocks={blocks}
|
|
359
|
+
model={model}
|
|
360
|
+
provider={provider}
|
|
361
|
+
startTime={startTime}
|
|
362
|
+
totalToolCalls={totalToolCalls}
|
|
363
|
+
/>
|
|
364
|
+
) : (
|
|
365
|
+
<CompletedView
|
|
366
|
+
assistantId={assistantId}
|
|
367
|
+
blocks={blocks}
|
|
368
|
+
duration={duration}
|
|
369
|
+
model={model}
|
|
370
|
+
provider={provider}
|
|
371
|
+
totalCost={totalCost}
|
|
372
|
+
totalTokens={accumulatedUsage.totalTokens}
|
|
373
|
+
totalToolCalls={totalToolCalls}
|
|
374
|
+
/>
|
|
375
|
+
)}
|
|
376
|
+
</Flexbox>
|
|
377
|
+
);
|
|
378
|
+
},
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
TaskMessages.displayName = 'TaskMessages';
|
|
382
|
+
|
|
383
|
+
export default TaskMessages;
|
|
@@ -5,4 +5,8 @@ export { default as ErrorState } from './ErrorState';
|
|
|
5
5
|
export { default as InitializingState } from './InitializingState';
|
|
6
6
|
export type { ProcessingStateVariant } from './ProcessingState';
|
|
7
7
|
export { default as ProcessingState } from './ProcessingState';
|
|
8
|
+
export type { TaskContentProps } from './TaskContent';
|
|
9
|
+
export { default as TaskContent } from './TaskContent';
|
|
10
|
+
export { default as TaskMessages } from './TaskMessages';
|
|
11
|
+
export * from './useTaskPolling';
|
|
8
12
|
export * from './utils';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ThreadStatus } from '@lobechat/types';
|
|
4
|
+
import { useEffect, useState } from 'react';
|
|
5
|
+
|
|
6
|
+
import { useChatStore } from '@/store/chat';
|
|
7
|
+
|
|
8
|
+
import { isProcessingStatus } from './utils';
|
|
9
|
+
|
|
10
|
+
interface UseTaskPollingParams {
|
|
11
|
+
messageId: string;
|
|
12
|
+
status: ThreadStatus | undefined;
|
|
13
|
+
threadId: string | undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const useTaskPolling = ({ messageId, threadId, status }: UseTaskPollingParams) => {
|
|
17
|
+
const isProcessing = isProcessingStatus(status);
|
|
18
|
+
const [hasFetched, setHasFetched] = useState(false);
|
|
19
|
+
|
|
20
|
+
const [useEnablePollingTaskStatus, operations] = useChatStore((s) => [
|
|
21
|
+
s.useEnablePollingTaskStatus,
|
|
22
|
+
s.operations,
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
// Check if exec_async_task is already polling for this message
|
|
26
|
+
const hasActiveOperationPolling = Object.values(operations).some(
|
|
27
|
+
(op) =>
|
|
28
|
+
op.status === 'running' &&
|
|
29
|
+
op.type === 'execAgentRuntime' &&
|
|
30
|
+
op.context?.messageId === messageId,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Enable polling when:
|
|
34
|
+
// 1. Has threadId
|
|
35
|
+
// 2. Not already being polled by an active operation
|
|
36
|
+
// 3. Either hasn't fetched yet (initial fetch) or is still processing (continuous polling)
|
|
37
|
+
const shouldPoll = !!threadId && !hasActiveOperationPolling && (!hasFetched || isProcessing);
|
|
38
|
+
const { data } = useEnablePollingTaskStatus(threadId, messageId, shouldPoll);
|
|
39
|
+
|
|
40
|
+
// Mark as fetched when we get data
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (data?.taskDetail && !hasFetched) {
|
|
43
|
+
setHasFetched(true);
|
|
44
|
+
}
|
|
45
|
+
}, [data?.taskDetail, hasFetched]);
|
|
46
|
+
|
|
47
|
+
return { isProcessing };
|
|
48
|
+
};
|
|
@@ -15,6 +15,7 @@ import AgentCouncilMessage from './AgentCouncil';
|
|
|
15
15
|
import AssistantMessage from './Assistant';
|
|
16
16
|
import AssistantGroupMessage from './AssistantGroup';
|
|
17
17
|
import CompressedGroupMessage from './CompressedGroup';
|
|
18
|
+
import GroupTasksMessage from './GroupTasks';
|
|
18
19
|
import SupervisorMessage from './Supervisor';
|
|
19
20
|
import TaskMessage from './Task';
|
|
20
21
|
import TasksMessage from './Tasks';
|
|
@@ -159,6 +160,10 @@ const MessageItem = memo<MessageItemProps>(
|
|
|
159
160
|
return <TasksMessage id={id} index={index} />;
|
|
160
161
|
}
|
|
161
162
|
|
|
163
|
+
case 'groupTasks': {
|
|
164
|
+
return <GroupTasksMessage id={id} index={index} />;
|
|
165
|
+
}
|
|
166
|
+
|
|
162
167
|
case 'agentCouncil': {
|
|
163
168
|
return <AgentCouncilMessage id={id} index={index} />;
|
|
164
169
|
}
|
|
@@ -369,6 +369,9 @@ export default {
|
|
|
369
369
|
'task.activity.toolCalling': 'Calling {{toolName}}...',
|
|
370
370
|
'task.activity.toolResult': '{{toolName}} result received',
|
|
371
371
|
'task.batchTasks': '{{count}} Batch Subtasks',
|
|
372
|
+
'task.groupTasks': '{{count}} Parallel Tasks',
|
|
373
|
+
'task.groupTasksTitle': '{{agents}} and {{count}} agents tasks',
|
|
374
|
+
'task.groupTasksTitleSimple': '{{agents}} {{count}} tasks',
|
|
372
375
|
'task.instruction': 'Task Instruction',
|
|
373
376
|
'task.intermediateSteps': '{{count}} intermediate steps',
|
|
374
377
|
'task.metrics.duration': '(took {{duration}})',
|
|
@@ -376,6 +379,7 @@ export default {
|
|
|
376
379
|
'task.metrics.toolCallsShort': 'skill uses',
|
|
377
380
|
'task.status.cancelled': 'Task Cancelled',
|
|
378
381
|
'task.status.failed': 'Task Failed',
|
|
382
|
+
'task.status.fetchingDetails': 'Fetching details...',
|
|
379
383
|
'task.status.initializing': 'Initializing task...',
|
|
380
384
|
'task.subtask': 'Subtask',
|
|
381
385
|
'thread.divider': 'Subtopic',
|
|
@@ -753,7 +753,11 @@ export const createRuntimeExecutors = (
|
|
|
753
753
|
const newState = structuredClone(state);
|
|
754
754
|
|
|
755
755
|
// Query latest messages from database
|
|
756
|
+
// Must pass agentId to ensure correct query scope, otherwise when topicId is undefined,
|
|
757
|
+
// the query will use isNull(topicId) condition which won't find messages with actual topicId
|
|
756
758
|
const latestMessages = await ctx.messageModel.query({
|
|
759
|
+
agentId: state.metadata?.agentId,
|
|
760
|
+
threadId: state.metadata?.threadId,
|
|
757
761
|
topicId: state.metadata?.topicId,
|
|
758
762
|
});
|
|
759
763
|
|