@lobehub/lobehub 2.0.9 → 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 +50 -0
- package/Dockerfile +44 -52
- package/changelog/v2.json +18 -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/model-bank/src/aiModels/cerebras.ts +2 -22
- package/packages/model-bank/src/aiModels/google.ts +1 -44
- package/packages/model-bank/src/aiModels/nvidia.ts +12 -16
- package/packages/model-bank/src/aiModels/siliconcloud.ts +20 -0
- package/packages/model-bank/src/aiModels/volcengine.ts +69 -0
- package/packages/model-bank/src/aiModels/wenxin.ts +41 -38
- package/packages/model-bank/src/aiModels/zhipu.ts +58 -28
- package/packages/model-bank/src/types/aiModel.ts +29 -0
- package/packages/model-runtime/src/core/usageConverters/utils/computeChatCost.test.ts +2 -2
- package/packages/model-runtime/src/providers/google/createImage.test.ts +12 -12
- package/packages/model-runtime/src/providers/openrouter/index.test.ts +102 -0
- package/packages/model-runtime/src/providers/openrouter/index.ts +19 -7
- package/packages/model-runtime/src/providers/vercelaigateway/index.test.ts +47 -0
- package/packages/model-runtime/src/providers/vercelaigateway/index.ts +7 -1
- 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,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
|
|
|
@@ -655,8 +655,10 @@ describe('RuntimeExecutors', () => {
|
|
|
655
655
|
|
|
656
656
|
const result = await executors.call_tools_batch!(instruction, state);
|
|
657
657
|
|
|
658
|
-
// Should query messages from database
|
|
658
|
+
// Should query messages from database with agentId, threadId, and topicId
|
|
659
659
|
expect(mockMessageModel.query).toHaveBeenCalledWith({
|
|
660
|
+
agentId: 'agent-123',
|
|
661
|
+
threadId: 'thread-123',
|
|
660
662
|
topicId: 'topic-123',
|
|
661
663
|
});
|
|
662
664
|
|
|
@@ -952,6 +954,109 @@ describe('RuntimeExecutors', () => {
|
|
|
952
954
|
}),
|
|
953
955
|
);
|
|
954
956
|
});
|
|
957
|
+
|
|
958
|
+
it('should query messages with correct metadata fields when state.metadata is defined', async () => {
|
|
959
|
+
const executors = createRuntimeExecutors(ctx);
|
|
960
|
+
const state = createMockState({
|
|
961
|
+
metadata: {
|
|
962
|
+
agentId: 'agent-abc',
|
|
963
|
+
threadId: 'thread-xyz',
|
|
964
|
+
topicId: 'topic-abc-123',
|
|
965
|
+
},
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
const instruction = {
|
|
969
|
+
payload: {
|
|
970
|
+
parentMessageId: 'assistant-msg-123',
|
|
971
|
+
toolsCalling: [
|
|
972
|
+
{
|
|
973
|
+
apiName: 'search',
|
|
974
|
+
arguments: '{}',
|
|
975
|
+
id: 'tool-call-1',
|
|
976
|
+
identifier: 'web-search',
|
|
977
|
+
type: 'default' as const,
|
|
978
|
+
},
|
|
979
|
+
],
|
|
980
|
+
},
|
|
981
|
+
type: 'call_tools_batch' as const,
|
|
982
|
+
};
|
|
983
|
+
|
|
984
|
+
await executors.call_tools_batch!(instruction, state);
|
|
985
|
+
|
|
986
|
+
// Should query messages with agentId, threadId, and topicId from state.metadata
|
|
987
|
+
expect(mockMessageModel.query).toHaveBeenCalledWith({
|
|
988
|
+
agentId: 'agent-abc',
|
|
989
|
+
threadId: 'thread-xyz',
|
|
990
|
+
topicId: 'topic-abc-123',
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
it('should preserve messages in newState even when state.metadata.topicId is undefined', async () => {
|
|
995
|
+
// Regression test: When state.metadata.topicId is undefined, previously the query
|
|
996
|
+
// only passed topicId, which caused isNull(topicId) condition and returned 0 messages.
|
|
997
|
+
// This led to "messages: at least one message is required" error in the next call_llm step.
|
|
998
|
+
//
|
|
999
|
+
// Fix: Now we also pass agentId and threadId, so even when topicId is undefined,
|
|
1000
|
+
// the query can still find messages by agentId scope.
|
|
1001
|
+
|
|
1002
|
+
// Mock: query returns messages when agentId is provided (regardless of topicId)
|
|
1003
|
+
mockMessageModel.query = vi
|
|
1004
|
+
.fn()
|
|
1005
|
+
.mockImplementation((params: { agentId?: string; topicId?: string }) => {
|
|
1006
|
+
// With the fix, agentId is always passed, so we can find messages
|
|
1007
|
+
if (params.agentId) {
|
|
1008
|
+
return Promise.resolve([
|
|
1009
|
+
{ id: 'msg-1', content: 'Hello', role: 'user' },
|
|
1010
|
+
{ id: 'msg-2', content: 'Response', role: 'assistant', tool_calls: [] },
|
|
1011
|
+
]);
|
|
1012
|
+
}
|
|
1013
|
+
// Without agentId (old buggy behavior), return empty
|
|
1014
|
+
return Promise.resolve([]);
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
const executors = createRuntimeExecutors(ctx);
|
|
1018
|
+
// State with undefined topicId but has agentId
|
|
1019
|
+
const state = createMockState({
|
|
1020
|
+
messages: [
|
|
1021
|
+
{ content: 'Hello', role: 'user' },
|
|
1022
|
+
{ content: 'Response', role: 'assistant', tool_calls: [] },
|
|
1023
|
+
],
|
|
1024
|
+
metadata: {
|
|
1025
|
+
agentId: 'agent-123',
|
|
1026
|
+
threadId: 'thread-123',
|
|
1027
|
+
topicId: undefined, // topicId is undefined
|
|
1028
|
+
},
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
const instruction = {
|
|
1032
|
+
payload: {
|
|
1033
|
+
parentMessageId: 'assistant-msg-123',
|
|
1034
|
+
toolsCalling: [
|
|
1035
|
+
{
|
|
1036
|
+
apiName: 'search',
|
|
1037
|
+
arguments: '{}',
|
|
1038
|
+
id: 'tool-call-1',
|
|
1039
|
+
identifier: 'web-search',
|
|
1040
|
+
type: 'default' as const,
|
|
1041
|
+
},
|
|
1042
|
+
],
|
|
1043
|
+
},
|
|
1044
|
+
type: 'call_tools_batch' as const,
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
const result = await executors.call_tools_batch!(instruction, state);
|
|
1048
|
+
|
|
1049
|
+
// Verify agentId is passed in the query
|
|
1050
|
+
expect(mockMessageModel.query).toHaveBeenCalledWith({
|
|
1051
|
+
agentId: 'agent-123',
|
|
1052
|
+
threadId: 'thread-123',
|
|
1053
|
+
topicId: undefined,
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
// Expected: newState.messages should NOT be empty
|
|
1057
|
+
// The next call_llm step needs messages to work properly
|
|
1058
|
+
expect(result.newState.messages.length).toBeGreaterThan(0);
|
|
1059
|
+
});
|
|
955
1060
|
});
|
|
956
1061
|
|
|
957
1062
|
describe('resolve_aborted_tools executor', () => {
|
|
@@ -205,7 +205,7 @@ describe('AiAgentService.execAgent - threadId handling', () => {
|
|
|
205
205
|
|
|
206
206
|
expect(assistantMessageCall).toBeDefined();
|
|
207
207
|
expect(assistantMessageCall![0].threadId).toBeUndefined();
|
|
208
|
-
});
|
|
208
|
+
}, 10_000);
|
|
209
209
|
});
|
|
210
210
|
|
|
211
211
|
describe('when appContext is undefined', () => {
|
|
@@ -247,6 +247,6 @@ describe('AiAgentService.execAgent - threadId handling', () => {
|
|
|
247
247
|
|
|
248
248
|
// Verify groupId is passed to AgentRuntimeService (checked in appContext)
|
|
249
249
|
// This is handled by the createOperation call
|
|
250
|
-
});
|
|
250
|
+
}, 10_000);
|
|
251
251
|
});
|
|
252
252
|
});
|
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* Default maximum length for tool execution result content (in characters)
|
|
8
8
|
* This prevents context overflow when sending results back to LLM
|
|
9
|
-
* @default 6000 characters (~1,500 tokens for English, ~3,000 tokens for Chinese)
|
|
10
9
|
*/
|
|
11
|
-
export const DEFAULT_TOOL_RESULT_MAX_LENGTH =
|
|
10
|
+
export const DEFAULT_TOOL_RESULT_MAX_LENGTH = 25_000;
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* Truncate tool result content if it exceeds the maximum length
|
|
@@ -21,8 +20,6 @@ export const DEFAULT_TOOL_RESULT_MAX_LENGTH = 6000;
|
|
|
21
20
|
export function truncateToolResult(content: string, maxLength?: number): string {
|
|
22
21
|
const limit = maxLength ?? DEFAULT_TOOL_RESULT_MAX_LENGTH;
|
|
23
22
|
|
|
24
|
-
console.log('content-limit', content, limit);
|
|
25
|
-
|
|
26
23
|
if (!content || content.length <= limit) {
|
|
27
24
|
return content;
|
|
28
25
|
}
|