@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.
Files changed (108) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/Dockerfile +44 -52
  3. package/changelog/v2.json +18 -0
  4. package/locales/ar/chat.json +4 -0
  5. package/locales/ar/models.json +65 -0
  6. package/locales/bg-BG/chat.json +4 -0
  7. package/locales/bg-BG/models.json +10 -0
  8. package/locales/de-DE/chat.json +4 -0
  9. package/locales/de-DE/models.json +41 -0
  10. package/locales/en-US/chat.json +4 -0
  11. package/locales/es-ES/chat.json +4 -0
  12. package/locales/es-ES/models.json +50 -0
  13. package/locales/fa-IR/chat.json +4 -0
  14. package/locales/fa-IR/models.json +39 -0
  15. package/locales/fr-FR/chat.json +4 -0
  16. package/locales/fr-FR/models.json +9 -0
  17. package/locales/it-IT/chat.json +4 -0
  18. package/locales/it-IT/models.json +62 -0
  19. package/locales/ja-JP/chat.json +4 -0
  20. package/locales/ja-JP/models.json +40 -0
  21. package/locales/ko-KR/chat.json +4 -0
  22. package/locales/ko-KR/models.json +31 -0
  23. package/locales/nl-NL/chat.json +4 -0
  24. package/locales/nl-NL/models.json +52 -0
  25. package/locales/pl-PL/chat.json +4 -0
  26. package/locales/pl-PL/models.json +43 -0
  27. package/locales/pt-BR/chat.json +4 -0
  28. package/locales/pt-BR/models.json +92 -0
  29. package/locales/ru-RU/chat.json +4 -0
  30. package/locales/ru-RU/models.json +34 -0
  31. package/locales/tr-TR/chat.json +4 -0
  32. package/locales/tr-TR/models.json +55 -0
  33. package/locales/vi-VN/chat.json +4 -0
  34. package/locales/vi-VN/models.json +31 -0
  35. package/locales/zh-CN/chat.json +4 -0
  36. package/locales/zh-TW/chat.json +4 -0
  37. package/package.json +1 -1
  38. package/packages/agent-runtime/src/groupOrchestration/GroupOrchestrationSupervisor.ts +18 -1
  39. package/packages/agent-runtime/src/groupOrchestration/__tests__/GroupOrchestrationSupervisor.test.ts +76 -5
  40. package/packages/agent-runtime/src/groupOrchestration/types.ts +3 -3
  41. package/packages/builtin-tool-group-management/src/client/Intervention/ExecuteTask.tsx +11 -11
  42. package/packages/builtin-tool-group-management/src/client/Intervention/ExecuteTasks.tsx +78 -79
  43. package/packages/builtin-tool-group-management/src/client/Render/ExecuteTask/index.tsx +3 -3
  44. package/packages/builtin-tool-group-management/src/client/Render/ExecuteTasks/index.tsx +61 -63
  45. package/packages/builtin-tool-group-management/src/client/Streaming/ExecuteTask/index.tsx +3 -3
  46. package/packages/builtin-tool-group-management/src/executor.test.ts +7 -9
  47. package/packages/builtin-tool-group-management/src/executor.ts +3 -3
  48. package/packages/builtin-tool-group-management/src/manifest.ts +49 -50
  49. package/packages/builtin-tool-group-management/src/systemRole.ts +153 -5
  50. package/packages/builtin-tool-group-management/src/types.ts +3 -2
  51. package/packages/builtin-tool-gtd/src/systemRole.ts +4 -4
  52. package/packages/context-engine/src/processors/TasksFlatten.ts +7 -5
  53. package/packages/context-engine/src/processors/__tests__/TasksFlatten.test.ts +164 -0
  54. package/packages/conversation-flow/src/__tests__/fixtures/inputs/agentGroup/index.ts +4 -0
  55. package/packages/conversation-flow/src/__tests__/fixtures/inputs/agentGroup/supervisor-after-multi-tasks.json +91 -0
  56. package/packages/conversation-flow/src/__tests__/fixtures/inputs/agentGroup/supervisor-content-only.json +74 -0
  57. package/packages/conversation-flow/src/__tests__/parse.test.ts +37 -0
  58. package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +70 -4
  59. package/packages/conversation-flow/src/transformation/__tests__/FlatListBuilder.test.ts +147 -0
  60. package/packages/model-bank/src/aiModels/cerebras.ts +2 -22
  61. package/packages/model-bank/src/aiModels/google.ts +1 -44
  62. package/packages/model-bank/src/aiModels/nvidia.ts +12 -16
  63. package/packages/model-bank/src/aiModels/siliconcloud.ts +20 -0
  64. package/packages/model-bank/src/aiModels/volcengine.ts +69 -0
  65. package/packages/model-bank/src/aiModels/wenxin.ts +41 -38
  66. package/packages/model-bank/src/aiModels/zhipu.ts +58 -28
  67. package/packages/model-bank/src/types/aiModel.ts +29 -0
  68. package/packages/model-runtime/src/core/usageConverters/utils/computeChatCost.test.ts +2 -2
  69. package/packages/model-runtime/src/providers/google/createImage.test.ts +12 -12
  70. package/packages/model-runtime/src/providers/openrouter/index.test.ts +102 -0
  71. package/packages/model-runtime/src/providers/openrouter/index.ts +19 -7
  72. package/packages/model-runtime/src/providers/vercelaigateway/index.test.ts +47 -0
  73. package/packages/model-runtime/src/providers/vercelaigateway/index.ts +7 -1
  74. package/packages/types/src/message/ui/chat.ts +2 -0
  75. package/packages/types/src/tool/builtin.ts +5 -5
  76. package/src/features/Conversation/ChatItem/components/Title.tsx +1 -1
  77. package/src/features/Conversation/ChatList/index.tsx +0 -1
  78. package/src/features/Conversation/Messages/GroupTasks/TaskItem/ClientTaskItem.tsx +183 -0
  79. package/src/features/Conversation/Messages/GroupTasks/TaskItem/ServerTaskItem.tsx +94 -0
  80. package/src/features/Conversation/Messages/GroupTasks/TaskItem/TaskTitle.tsx +177 -0
  81. package/src/features/Conversation/Messages/GroupTasks/TaskItem/index.tsx +26 -0
  82. package/src/features/Conversation/Messages/GroupTasks/TaskItem/useClientTaskStats.ts +93 -0
  83. package/src/features/Conversation/Messages/GroupTasks/index.tsx +151 -0
  84. package/src/features/Conversation/Messages/Supervisor/index.tsx +7 -1
  85. package/src/features/Conversation/Messages/Task/ClientTaskDetail/index.tsx +72 -91
  86. package/src/features/Conversation/Messages/Task/TaskDetailPanel/StatusContent.tsx +46 -17
  87. package/src/features/Conversation/Messages/Tasks/TaskItem/ClientTaskItem.tsx +9 -24
  88. package/src/features/Conversation/Messages/Tasks/TaskItem/ServerTaskItem.tsx +18 -38
  89. package/src/features/Conversation/Messages/Tasks/shared/ErrorState.tsx +45 -2
  90. package/src/features/Conversation/Messages/Tasks/shared/InitializingState.tsx +16 -1
  91. package/src/features/Conversation/Messages/Tasks/shared/TaskContent.tsx +68 -0
  92. package/src/features/Conversation/Messages/Tasks/shared/TaskMessages.tsx +383 -0
  93. package/src/features/Conversation/Messages/Tasks/shared/index.ts +4 -0
  94. package/src/features/Conversation/Messages/Tasks/shared/useTaskPolling.ts +48 -0
  95. package/src/features/Conversation/Messages/index.tsx +5 -0
  96. package/src/locales/default/chat.ts +4 -0
  97. package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +4 -0
  98. package/src/server/modules/AgentRuntime/__tests__/RuntimeExecutors.test.ts +106 -1
  99. package/src/server/services/aiAgent/__tests__/execAgent.threadId.test.ts +2 -2
  100. package/src/server/utils/truncateToolResult.ts +1 -4
  101. package/src/store/chat/agents/GroupOrchestration/__tests__/batch-exec-async-tasks.test.ts +15 -15
  102. package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +22 -15
  103. package/src/store/chat/agents/__tests__/createAgentExecutors/exec-tasks.test.ts +21 -10
  104. package/src/store/chat/agents/createAgentExecutors.ts +2 -0
  105. package/src/store/chat/slices/aiAgent/actions/groupOrchestration.ts +10 -7
  106. package/src/features/Conversation/Messages/Task/ClientTaskDetail/CompletedState.tsx +0 -108
  107. package/src/features/Conversation/Messages/Task/ClientTaskDetail/InstructionAccordion.tsx +0 -63
  108. package/src/features/Conversation/Messages/Task/ClientTaskDetail/ProcessingState.tsx +0 -123
@@ -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
+ };
@@ -0,0 +1,151 @@
1
+ 'use client';
2
+
3
+ import { type UIChatMessage } from '@lobechat/types';
4
+ import { Block, Flexbox, GroupAvatar, Icon, Tag } from '@lobehub/ui';
5
+ import { cssVar } from 'antd-style';
6
+ import isEqual from 'fast-deep-equal';
7
+ import { ListTodo } from 'lucide-react';
8
+ import { memo, useMemo } from 'react';
9
+ import { useTranslation } from 'react-i18next';
10
+
11
+ import { DEFAULT_AVATAR } from '@/const/meta';
12
+ import { useAgentGroupStore } from '@/store/agentGroup';
13
+ import { agentGroupSelectors } from '@/store/agentGroup/selectors';
14
+
15
+ import { ChatItem } from '../../ChatItem';
16
+ import { dataSelectors, useConversationStore } from '../../store';
17
+ import { AssistantActionsBar } from '../Task/Actions';
18
+ import TaskItem from './TaskItem';
19
+
20
+ interface GroupTasksMessageProps {
21
+ id: string;
22
+ index: number;
23
+ }
24
+
25
+ /**
26
+ * Custom avatar component for GroupTasks
27
+ * Shows GroupAvatar (only task agents, no user) with a ListTodo badge
28
+ */
29
+ const GroupTasksAvatar = memo<{ avatars: { avatar?: string; background?: string }[] }>(
30
+ ({ avatars }) => {
31
+ return (
32
+ <Flexbox flex={'none'} height={28} style={{ position: 'relative' }} width={28}>
33
+ <GroupAvatar
34
+ avatarShape={'square'}
35
+ avatars={avatars.map((a) => ({
36
+ avatar: a.avatar || DEFAULT_AVATAR,
37
+ background: a.background,
38
+ }))}
39
+ cornerShape={'square'}
40
+ size={28}
41
+ />
42
+ <Block
43
+ align={'center'}
44
+ flex={'none'}
45
+ height={16}
46
+ justify={'center'}
47
+ style={{
48
+ borderRadius: 4,
49
+ position: 'absolute',
50
+ right: -4,
51
+ top: -4,
52
+ }}
53
+ variant={'outlined'}
54
+ width={16}
55
+ >
56
+ <Icon color={cssVar.colorTextDescription} icon={ListTodo} size={10} />
57
+ </Block>
58
+ </Flexbox>
59
+ );
60
+ },
61
+ );
62
+
63
+ GroupTasksAvatar.displayName = 'GroupTasksAvatar';
64
+
65
+ const GroupTasksMessage = memo<GroupTasksMessageProps>(({ id, index }) => {
66
+ const { t } = useTranslation('chat');
67
+ const item = useConversationStore(dataSelectors.getDisplayMessageById(id), isEqual)!;
68
+ const actionsConfig = useConversationStore((s) => s.actionsBar?.assistant);
69
+ const tasks = (item as UIChatMessage)?.tasks?.filter(Boolean) as UIChatMessage[] | undefined;
70
+
71
+ // Get unique agent IDs from tasks
72
+ const taskAgentIds = useMemo(() => {
73
+ if (!tasks) return [];
74
+ const ids = tasks.map((task) => task.agentId).filter(Boolean) as string[];
75
+ return [...new Set(ids)];
76
+ }, [tasks]);
77
+
78
+ // Get active group ID
79
+ const activeGroupId = useAgentGroupStore(agentGroupSelectors.activeGroupId);
80
+
81
+ // Get agent info (avatars and names) for all unique agents in tasks
82
+ const taskAgents = useAgentGroupStore((s) => {
83
+ if (!activeGroupId || taskAgentIds.length === 0) return [];
84
+ return taskAgentIds
85
+ .map((agentId) => {
86
+ const agent = agentGroupSelectors.getAgentByIdFromGroup(activeGroupId, agentId)(s);
87
+ return agent
88
+ ? { avatar: agent.avatar, background: agent.backgroundColor, title: agent.title }
89
+ : null;
90
+ })
91
+ .filter(Boolean) as { avatar?: string; background?: string; title?: string }[];
92
+ }, isEqual);
93
+
94
+ // Build title: "Agent1 / Agent2 等 N 个 agents tasks" (show max 2 agents)
95
+ const title = useMemo(() => {
96
+ const agentNames = taskAgents.map((a) => a.title).filter(Boolean);
97
+ if (agentNames.length === 0) return '';
98
+
99
+ const totalAgents = agentNames.length;
100
+ // Show at most 2 agent names
101
+ const displayedAgents = agentNames.slice(0, 2).join(' / ');
102
+
103
+ if (totalAgents <= 2) {
104
+ // Show all agent names when 2 or fewer
105
+ return t('task.groupTasksTitleSimple', {
106
+ agents: displayedAgents,
107
+ count: tasks?.length || 0,
108
+ });
109
+ }
110
+
111
+ // Show "Agent1 / Agent2 等 X 个 agents tasks" when more than 2
112
+ return t('task.groupTasksTitle', {
113
+ agents: displayedAgents,
114
+ count: totalAgents,
115
+ taskCount: tasks?.length || 0,
116
+ });
117
+ }, [taskAgents, tasks?.length, t]);
118
+
119
+ if (!tasks || tasks.length === 0) {
120
+ return null;
121
+ }
122
+
123
+ const { createdAt } = item;
124
+
125
+ return (
126
+ <ChatItem
127
+ aboveMessage={null}
128
+ actions={
129
+ <AssistantActionsBar actionsConfig={actionsConfig} data={item} id={id} index={index} />
130
+ }
131
+ avatar={{ title }}
132
+ customAvatarRender={() => <GroupTasksAvatar avatars={taskAgents} />}
133
+ id={id}
134
+ message=""
135
+ placement="left"
136
+ showTitle
137
+ time={createdAt}
138
+ titleAddon={<Tag>{t('task.groupTasks', { count: tasks.length })}</Tag>}
139
+ >
140
+ <Flexbox gap={8} width={'100%'}>
141
+ {tasks.map((task) => (
142
+ <TaskItem item={task} key={task.id} />
143
+ ))}
144
+ </Flexbox>
145
+ </ChatItem>
146
+ );
147
+ }, isEqual);
148
+
149
+ GroupTasksMessage.displayName = 'GroupTasksMessage';
150
+
151
+ export default GroupTasksMessage;
@@ -110,7 +110,13 @@ const GroupMessage = memo<GroupMessageProps>(({ id, index, disableEditing, isLat
110
110
  titleAddon={<Tag>{t('supervisor.label')}</Tag>}
111
111
  >
112
112
  {children && children.length > 0 && (
113
- <Group blocks={children} disableEditing={disableEditing} id={id} messageIndex={index} />
113
+ <Group
114
+ blocks={children}
115
+ content={item.content}
116
+ disableEditing={disableEditing}
117
+ id={id}
118
+ messageIndex={index}
119
+ />
114
120
  )}
115
121
  {model && (
116
122
  <Usage model={model} performance={performance} provider={provider!} usage={usage} />
@@ -1,7 +1,6 @@
1
1
  'use client';
2
2
 
3
3
  import { type TaskDetail, ThreadStatus } from '@lobechat/types';
4
- import { Flexbox } from '@lobehub/ui';
5
4
  import { memo, useMemo } from 'react';
6
5
 
7
6
  import BubblesLoading from '@/components/BubblesLoading';
@@ -9,10 +8,8 @@ import { useChatStore } from '@/store/chat';
9
8
  import { displayMessageSelectors } from '@/store/chat/selectors';
10
9
  import { messageMapKey } from '@/store/chat/utils/messageMapKey';
11
10
 
12
- import CompletedState from './CompletedState';
11
+ import { TaskMessages } from '../../Tasks/shared';
13
12
  import InitializingState from './InitializingState';
14
- import InstructionAccordion from './InstructionAccordion';
15
- import ProcessingState from './ProcessingState';
16
13
 
17
14
  interface ClientTaskDetailProps {
18
15
  /** Agent ID from the task message (use task's agentId, not activeAgentId) */
@@ -24,92 +21,76 @@ interface ClientTaskDetailProps {
24
21
  taskDetail?: TaskDetail;
25
22
  }
26
23
 
27
- const ClientTaskDetail = memo<ClientTaskDetailProps>(({ agentId: propAgentId, groupId, taskDetail }) => {
28
- const threadId = taskDetail?.threadId;
29
- const isExecuting = taskDetail?.status === ThreadStatus.Processing;
30
-
31
- // Use task message's agentId to query with the correct SubAgent ID that created the thread
32
- // Fall back to activeAgentId if task message doesn't have agentId (shouldn't happen normally)
33
- const [activeAgentId, activeTopicId, useFetchMessages] = useChatStore((s) => [
34
- s.activeAgentId,
35
- s.activeTopicId,
36
- s.useFetchMessages,
37
- ]);
38
-
39
- const agentId = propAgentId || activeAgentId;
40
-
41
- const threadContext = useMemo(
42
- () => ({
43
- agentId,
44
- groupId,
45
- scope: 'thread' as const,
46
- threadId,
47
- topicId: activeTopicId,
48
- }),
49
- [agentId, groupId, activeTopicId, threadId],
50
- );
51
-
52
- const threadMessageKey = useMemo(
53
- () => (threadId ? messageMapKey(threadContext) : null),
54
- [threadId],
55
- );
56
-
57
- // Fetch thread messages (skip when executing - messages come from real-time updates)
58
- useFetchMessages(threadContext, isExecuting);
59
-
60
- // Get thread messages from store using selector
61
- const threadMessages = useChatStore((s) =>
62
- threadMessageKey
63
- ? displayMessageSelectors.getDisplayMessagesByKey(threadMessageKey)(s)
64
- : undefined,
65
- );
66
-
67
- if (!threadMessages) return <BubblesLoading />;
68
-
69
- // Find the assistantGroup message which contains the children blocks
70
- const assistantGroupMessage = threadMessages.find((item) => item.role === 'assistantGroup');
71
- const instruction = threadMessages.find((item) => item.role === 'user')?.content;
72
- const childrenCount = assistantGroupMessage?.children?.length ?? 0;
73
-
74
- // Get model/provider from assistantGroup message
75
- const model = assistantGroupMessage?.model;
76
- const provider = assistantGroupMessage?.provider;
77
-
78
- // Initializing state: no status yet (task just created, waiting for client execution)
79
- if (threadMessages.length === 0 || !assistantGroupMessage?.children || childrenCount === 0) {
80
- return <InitializingState />;
81
- }
82
-
83
- return (
84
- <Flexbox gap={4}>
85
- {instruction && (
86
- <InstructionAccordion childrenCount={childrenCount} instruction={instruction} />
87
- )}
88
-
89
- {isExecuting ? (
90
- <ProcessingState
91
- assistantId={assistantGroupMessage.id}
92
- blocks={assistantGroupMessage.children}
93
- model={model ?? undefined}
94
- provider={provider ?? undefined}
95
- startTime={assistantGroupMessage.createdAt}
96
- />
97
- ) : (
98
- <CompletedState
99
- assistantId={assistantGroupMessage.id}
100
- blocks={assistantGroupMessage.children}
101
- duration={taskDetail?.duration}
102
- model={model ?? undefined}
103
- provider={provider ?? undefined}
104
- totalCost={taskDetail?.totalCost}
105
- totalTokens={taskDetail?.totalTokens}
106
- totalToolCalls={taskDetail?.totalToolCalls}
107
- />
108
- )}
109
- </Flexbox>
110
- );
111
- });
112
-
113
- ClientTaskDetail.displayName = 'ClientClientTaskDetail';
24
+ const ClientTaskDetail = memo<ClientTaskDetailProps>(
25
+ ({ agentId: propAgentId, groupId, taskDetail }) => {
26
+ const threadId = taskDetail?.threadId;
27
+ const isExecuting = taskDetail?.status === ThreadStatus.Processing;
28
+
29
+ // Use task message's agentId to query with the correct SubAgent ID that created the thread
30
+ // Fall back to activeAgentId if task message doesn't have agentId (shouldn't happen normally)
31
+ const [activeAgentId, activeTopicId, useFetchMessages] = useChatStore((s) => [
32
+ s.activeAgentId,
33
+ s.activeTopicId,
34
+ s.useFetchMessages,
35
+ ]);
36
+
37
+ const agentId = propAgentId || activeAgentId;
38
+
39
+ const threadContext = useMemo(
40
+ () => ({
41
+ agentId,
42
+ groupId,
43
+ scope: 'thread' as const,
44
+ threadId,
45
+ topicId: activeTopicId,
46
+ }),
47
+ [agentId, groupId, activeTopicId, threadId],
48
+ );
49
+
50
+ const threadMessageKey = useMemo(
51
+ () => (threadId ? messageMapKey(threadContext) : null),
52
+ [threadId],
53
+ );
54
+
55
+ // Fetch thread messages (skip when executing - messages come from real-time updates)
56
+ useFetchMessages(threadContext, isExecuting);
57
+
58
+ // Get thread messages from store using selector
59
+ const threadMessages = useChatStore((s) =>
60
+ threadMessageKey
61
+ ? displayMessageSelectors.getDisplayMessagesByKey(threadMessageKey)(s)
62
+ : undefined,
63
+ );
64
+
65
+ if (!threadMessages) return <BubblesLoading />;
66
+
67
+ // Find the assistantGroup message which contains the children blocks
68
+ const assistantGroupMessage = threadMessages.find((item) => item.role === 'assistantGroup');
69
+ const childrenCount = assistantGroupMessage?.children?.length ?? 0;
70
+
71
+ // Get model/provider from assistantGroup message
72
+ const model = assistantGroupMessage?.model;
73
+ const provider = assistantGroupMessage?.provider;
74
+
75
+ // Initializing state: no status yet (task just created, waiting for client execution)
76
+ if (threadMessages.length === 0 || !assistantGroupMessage?.children || childrenCount === 0) {
77
+ return <InitializingState />;
78
+ }
79
+
80
+ return (
81
+ <TaskMessages
82
+ duration={taskDetail?.duration}
83
+ isProcessing={isExecuting}
84
+ messages={threadMessages}
85
+ model={model ?? undefined}
86
+ provider={provider ?? undefined}
87
+ startTime={assistantGroupMessage.createdAt}
88
+ totalCost={taskDetail?.totalCost}
89
+ />
90
+ );
91
+ },
92
+ );
93
+
94
+ ClientTaskDetail.displayName = 'ClientTaskDetail';
114
95
 
115
96
  export default ClientTaskDetail;