@lobehub/lobehub 2.0.10 → 2.0.12

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 (101) 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/database/src/repositories/agentGroup/index.ts +4 -0
  61. package/packages/types/src/message/ui/chat.ts +2 -0
  62. package/packages/types/src/tool/builtin.ts +5 -5
  63. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/ActionButton/ForkGroupAndChat.tsx +2 -1
  64. package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/index.tsx +2 -2
  65. package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/useMarketGroupPublish.ts +11 -12
  66. package/src/features/Conversation/ChatItem/components/Title.tsx +1 -1
  67. package/src/features/Conversation/ChatList/index.tsx +0 -1
  68. package/src/features/Conversation/Messages/GroupTasks/TaskItem/ClientTaskItem.tsx +183 -0
  69. package/src/features/Conversation/Messages/GroupTasks/TaskItem/ServerTaskItem.tsx +94 -0
  70. package/src/features/Conversation/Messages/GroupTasks/TaskItem/TaskTitle.tsx +177 -0
  71. package/src/features/Conversation/Messages/GroupTasks/TaskItem/index.tsx +26 -0
  72. package/src/features/Conversation/Messages/GroupTasks/TaskItem/useClientTaskStats.ts +93 -0
  73. package/src/features/Conversation/Messages/GroupTasks/index.tsx +151 -0
  74. package/src/features/Conversation/Messages/Supervisor/index.tsx +7 -1
  75. package/src/features/Conversation/Messages/Task/ClientTaskDetail/index.tsx +72 -91
  76. package/src/features/Conversation/Messages/Task/TaskDetailPanel/StatusContent.tsx +46 -17
  77. package/src/features/Conversation/Messages/Tasks/TaskItem/ClientTaskItem.tsx +9 -24
  78. package/src/features/Conversation/Messages/Tasks/TaskItem/ServerTaskItem.tsx +18 -38
  79. package/src/features/Conversation/Messages/Tasks/shared/ErrorState.tsx +45 -2
  80. package/src/features/Conversation/Messages/Tasks/shared/InitializingState.tsx +16 -1
  81. package/src/features/Conversation/Messages/Tasks/shared/TaskContent.tsx +68 -0
  82. package/src/features/Conversation/Messages/Tasks/shared/TaskMessages.tsx +383 -0
  83. package/src/features/Conversation/Messages/Tasks/shared/index.ts +4 -0
  84. package/src/features/Conversation/Messages/Tasks/shared/useTaskPolling.ts +48 -0
  85. package/src/features/Conversation/Messages/index.tsx +5 -0
  86. package/src/locales/default/chat.ts +4 -0
  87. package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +4 -0
  88. package/src/server/modules/AgentRuntime/__tests__/RuntimeExecutors.test.ts +106 -1
  89. package/src/server/routers/lambda/agentGroup.ts +2 -0
  90. package/src/server/routers/lambda/market/agent.ts +17 -45
  91. package/src/server/routers/lambda/market/agentGroup.ts +13 -25
  92. package/src/server/services/aiAgent/__tests__/execAgent.threadId.test.ts +2 -2
  93. package/src/server/utils/truncateToolResult.ts +1 -4
  94. package/src/store/chat/agents/GroupOrchestration/__tests__/batch-exec-async-tasks.test.ts +15 -15
  95. package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +22 -15
  96. package/src/store/chat/agents/__tests__/createAgentExecutors/exec-tasks.test.ts +21 -10
  97. package/src/store/chat/agents/createAgentExecutors.ts +2 -0
  98. package/src/store/chat/slices/aiAgent/actions/groupOrchestration.ts +10 -7
  99. package/src/features/Conversation/Messages/Task/ClientTaskDetail/CompletedState.tsx +0 -108
  100. package/src/features/Conversation/Messages/Task/ClientTaskDetail/InstructionAccordion.tsx +0 -63
  101. package/src/features/Conversation/Messages/Task/ClientTaskDetail/ProcessingState.tsx +0 -123
@@ -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;
@@ -2,13 +2,13 @@
2
2
 
3
3
  import { memo } from 'react';
4
4
 
5
+ import { useChatStore } from '@/store/chat';
5
6
  import { type TaskDetail, ThreadStatus } from '@/types/index';
6
7
 
7
8
  import {
8
- CompletedState,
9
9
  ErrorState,
10
10
  InitializingState,
11
- ProcessingState,
11
+ TaskMessages,
12
12
  isProcessingStatus,
13
13
  } from '../../Tasks/shared';
14
14
 
@@ -18,30 +18,59 @@ interface StatusContentProps {
18
18
  taskDetail?: TaskDetail;
19
19
  }
20
20
 
21
- const StatusContent = memo<StatusContentProps>(({ taskDetail, content, messageId }) => {
21
+ const StatusContent = memo<StatusContentProps>(({ taskDetail, messageId }) => {
22
22
  const status = taskDetail?.status;
23
+ const threadId = taskDetail?.threadId;
24
+ const isProcessing = isProcessingStatus(status);
25
+
26
+ // Get polling hook - poll for task status to get messages
27
+ const [useEnablePollingTaskStatus, operations] = useChatStore((s) => [
28
+ s.useEnablePollingTaskStatus,
29
+ s.operations,
30
+ ]);
31
+
32
+ // Check if exec_async_task is already polling for this message
33
+ const hasActiveOperationPolling = Object.values(operations).some(
34
+ (op) =>
35
+ op.status === 'running' &&
36
+ op.type === 'execAgentRuntime' &&
37
+ op.context?.messageId === messageId,
38
+ );
39
+
40
+ // Enable polling when task has threadId and no active operation is polling
41
+ // For completed tasks, this will fetch messages once (no refreshInterval needed)
42
+ const shouldPoll = !!threadId && !hasActiveOperationPolling;
43
+ const { data } = useEnablePollingTaskStatus(threadId, messageId, shouldPoll);
44
+
45
+ const messages = data?.messages;
23
46
 
24
47
  // Initializing state: no status yet (task just created, waiting for backend)
25
48
  if (!status) {
26
49
  return <InitializingState />;
27
50
  }
28
51
 
29
- // Processing states: Processing, InReview, Pending, Active, Todo
30
- if (isProcessingStatus(status)) {
31
- return <ProcessingState messageId={messageId} taskDetail={taskDetail!} variant="detail" />;
32
- }
33
-
34
- // Completed state
35
- if (status === ThreadStatus.Completed) {
36
- return <CompletedState content={content} taskDetail={taskDetail!} variant="detail" />;
37
- }
38
-
39
- // Error states: Failed, Cancel
40
- if (status === ThreadStatus.Failed || status === ThreadStatus.Cancel) {
41
- return <ErrorState taskDetail={taskDetail!} />;
52
+ // Processing or Completed state with messages
53
+ if (messages && messages.length > 0) {
54
+ return (
55
+ <>
56
+ <TaskMessages
57
+ duration={taskDetail?.duration}
58
+ isProcessing={isProcessing}
59
+ messages={messages}
60
+ startTime={taskDetail?.startedAt ? new Date(taskDetail.startedAt).getTime() : undefined}
61
+ totalCost={taskDetail?.totalCost}
62
+ />
63
+ {
64
+ // Error states: Failed, Cancel
65
+ (status === ThreadStatus.Failed || status === ThreadStatus.Cancel) && (
66
+ <ErrorState taskDetail={taskDetail!} />
67
+ )
68
+ }
69
+ </>
70
+ );
42
71
  }
43
72
 
44
- // Fallback to initializing state for unknown status
73
+ // Still loading messages
45
74
  return <InitializingState />;
46
75
  });
47
76
 
@@ -9,9 +9,7 @@ import { messageMapKey } from '@/store/chat/utils/messageMapKey';
9
9
  import { ThreadStatus } from '@/types/index';
10
10
  import type { UIChatMessage } from '@/types/index';
11
11
 
12
- import ClientTaskDetailCompletedState from '../../Task/ClientTaskDetail/CompletedState';
13
- import ClientTaskDetailProcessingState from '../../Task/ClientTaskDetail/ProcessingState';
14
- import { ErrorState, InitializingState, isProcessingStatus } from '../shared';
12
+ import { ErrorState, InitializingState, TaskMessages, isProcessingStatus } from '../shared';
15
13
  import TaskTitle, { type TaskMetrics } from './TaskTitle';
16
14
 
17
15
  interface ClientTaskItemProps {
@@ -43,8 +41,7 @@ const ClientTaskItem = memo<ClientTaskItemProps>(({ item }) => {
43
41
 
44
42
  // Use task message's agentId (skip 'supervisor' as it's not a valid agent ID for queries)
45
43
  // Fall back to activeAgentId if not available
46
- const agentId =
47
- itemAgentId && itemAgentId !== 'supervisor' ? itemAgentId : activeAgentId;
44
+ const agentId = itemAgentId && itemAgentId !== 'supervisor' ? itemAgentId : activeAgentId;
48
45
 
49
46
  const threadContext = useMemo(
50
47
  () => ({
@@ -135,33 +132,21 @@ const ClientTaskItem = memo<ClientTaskItemProps>(({ item }) => {
135
132
  {/* Initializing State - no taskDetail yet or no blocks */}
136
133
  {(isInitializing || (isProcessing && !hasBlocks)) && <InitializingState />}
137
134
 
138
- {/* Processing State - show streaming blocks */}
139
- {!isInitializing && isProcessing && hasBlocks && (
140
- <ClientTaskDetailProcessingState
141
- assistantId={assistantGroupMessage!.id}
142
- blocks={blocks!}
135
+ {/* Processing or Completed State - show blocks via TaskMessages */}
136
+ {!isInitializing && (isProcessing || isCompleted) && hasBlocks && threadMessages && (
137
+ <TaskMessages
138
+ duration={taskDetail?.duration}
139
+ isProcessing={isProcessing}
140
+ messages={threadMessages}
143
141
  model={model ?? undefined}
144
142
  provider={provider ?? undefined}
145
143
  startTime={assistantGroupMessage?.createdAt}
144
+ totalCost={taskDetail?.totalCost}
146
145
  />
147
146
  )}
148
147
 
149
148
  {/* Error State */}
150
149
  {!isInitializing && isError && taskDetail && <ErrorState taskDetail={taskDetail} />}
151
-
152
- {/* Completed State - show blocks with final result */}
153
- {!isInitializing && isCompleted && taskDetail && hasBlocks && (
154
- <ClientTaskDetailCompletedState
155
- assistantId={assistantGroupMessage!.id}
156
- blocks={blocks!}
157
- duration={taskDetail.duration}
158
- model={model ?? undefined}
159
- provider={provider ?? undefined}
160
- totalCost={taskDetail.totalCost}
161
- totalTokens={taskDetail.totalTokens}
162
- totalToolCalls={taskDetail.totalToolCalls}
163
- />
164
- )}
165
150
  </Block>
166
151
  </AccordionItem>
167
152
  );
@@ -1,18 +1,12 @@
1
1
  'use client';
2
2
 
3
- import { AccordionItem, Block, Text } from '@lobehub/ui';
3
+ import { AccordionItem, Block } from '@lobehub/ui';
4
4
  import { memo, useMemo, useState } from 'react';
5
5
 
6
6
  import { ThreadStatus } from '@/types/index';
7
7
  import type { UIChatMessage } from '@/types/index';
8
8
 
9
- import {
10
- CompletedState,
11
- ErrorState,
12
- InitializingState,
13
- ProcessingState,
14
- isProcessingStatus,
15
- } from '../shared';
9
+ import { TaskContent } from '../shared';
16
10
  import TaskTitle, { type TaskMetrics } from './TaskTitle';
17
11
 
18
12
  interface ServerTaskItemProps {
@@ -20,17 +14,15 @@ interface ServerTaskItemProps {
20
14
  }
21
15
 
22
16
  const ServerTaskItem = memo<ServerTaskItemProps>(({ item }) => {
23
- const { id, content, metadata, taskDetail } = item;
17
+ const { id, metadata, taskDetail, tasks } = item;
24
18
  const [expanded, setExpanded] = useState(false);
25
19
 
26
20
  const title = taskDetail?.title || metadata?.taskTitle;
27
- const instruction = metadata?.instruction;
28
21
  const status = taskDetail?.status;
22
+ const threadId = taskDetail?.threadId;
29
23
 
30
- const isProcessing = isProcessingStatus(status);
31
24
  const isCompleted = status === ThreadStatus.Completed;
32
25
  const isError = status === ThreadStatus.Failed || status === ThreadStatus.Cancel;
33
- const isInitializing = !taskDetail || !status;
34
26
 
35
27
  // Build metrics for TaskTitle (only for completed/error states)
36
28
  const metrics: TaskMetrics | undefined = useMemo(() => {
@@ -42,7 +34,13 @@ const ServerTaskItem = memo<ServerTaskItemProps>(({ item }) => {
42
34
  };
43
35
  }
44
36
  return undefined;
45
- }, [isCompleted, isError, taskDetail?.duration, taskDetail?.totalSteps, taskDetail?.totalToolCalls]);
37
+ }, [
38
+ isCompleted,
39
+ isError,
40
+ taskDetail?.duration,
41
+ taskDetail?.totalSteps,
42
+ taskDetail?.totalToolCalls,
43
+ ]);
46
44
 
47
45
  return (
48
46
  <AccordionItem
@@ -54,32 +52,14 @@ const ServerTaskItem = memo<ServerTaskItemProps>(({ item }) => {
54
52
  title={<TaskTitle metrics={metrics} status={status} title={title} />}
55
53
  >
56
54
  <Block gap={16} padding={12} style={{ marginBlock: 8 }} variant={'outlined'}>
57
- {instruction && (
58
- <Block padding={12}>
59
- <Text fontSize={13} type={'secondary'}>
60
- {instruction}
61
- </Text>
62
- </Block>
63
- )}
64
-
65
- {/* Initializing State - no taskDetail yet */}
66
- {isInitializing && <InitializingState />}
67
-
68
- {/* Processing State */}
69
- {!isInitializing && isProcessing && taskDetail && (
70
- <ProcessingState messageId={id} taskDetail={taskDetail} variant="compact" />
71
- )}
72
-
73
- {/* Error State */}
74
- {!isInitializing && isError && taskDetail && <ErrorState taskDetail={taskDetail} />}
75
-
76
- {/* Completed State */}
77
- {!isInitializing && isCompleted && taskDetail && (
78
- <CompletedState
79
- content={content}
80
- expanded={expanded}
55
+ {expanded && (
56
+ <TaskContent
57
+ id={id}
58
+ isError={isError}
59
+ messages={tasks}
60
+ status={status}
81
61
  taskDetail={taskDetail}
82
- variant="compact"
62
+ threadId={threadId}
83
63
  />
84
64
  )}
85
65
  </Block>
@@ -38,6 +38,46 @@ interface ErrorStateProps {
38
38
  taskDetail: TaskDetail;
39
39
  }
40
40
 
41
+ /**
42
+ * Extract displayable error content from various error structures
43
+ */
44
+ const getErrorContent = (error: Record<string, any> | undefined): string | null => {
45
+ if (!error) return null;
46
+
47
+ // Try common error structures
48
+ // 1. error.error.body (TRPC style)
49
+ if (error.error?.body) {
50
+ return JSON.stringify(error.error.body, null, 2);
51
+ }
52
+
53
+ // 2. error.body (direct body)
54
+ if (error.body && typeof error.body === 'object') {
55
+ return JSON.stringify(error.body, null, 2);
56
+ }
57
+
58
+ // 3. error.message (if it's not "[object Object]")
59
+ if (error.message && typeof error.message === 'string' && error.message !== '[object Object]') {
60
+ return error.message;
61
+ }
62
+
63
+ // 4. If error itself is an object with meaningful content, stringify it
64
+ // Skip if it only has a useless message field
65
+ const keys = Object.keys(error);
66
+ if (keys.length > 0) {
67
+ // Filter out useless "[object Object]" values
68
+ const meaningfulEntries = Object.entries(error).filter(([, value]) => {
69
+ if (typeof value === 'string' && value === '[object Object]') return false;
70
+ return true;
71
+ });
72
+
73
+ if (meaningfulEntries.length > 0) {
74
+ return JSON.stringify(Object.fromEntries(meaningfulEntries), null, 2);
75
+ }
76
+ }
77
+
78
+ return null;
79
+ };
80
+
41
81
  const ErrorState = memo<ErrorStateProps>(({ taskDetail }) => {
42
82
  const { t } = useTranslation('chat');
43
83
 
@@ -49,6 +89,9 @@ const ErrorState = memo<ErrorStateProps>(({ taskDetail }) => {
49
89
  const formattedDuration = useMemo(() => formatDuration(duration), [duration]);
50
90
  const formattedCost = useMemo(() => formatCost(totalCost), [totalCost]);
51
91
 
92
+ // Extract error content
93
+ const errorContent = useMemo(() => getErrorContent(error), [error]);
94
+
52
95
  const hasMetrics = !!(formattedDuration || totalToolCalls || totalMessages || formattedCost);
53
96
 
54
97
  return (
@@ -56,14 +99,14 @@ const ErrorState = memo<ErrorStateProps>(({ taskDetail }) => {
56
99
  {/* Error Content */}
57
100
  <Alert
58
101
  extra={
59
- error?.error?.body && (
102
+ errorContent && (
60
103
  <Highlighter
61
104
  actionIconSize={'small'}
62
105
  language={'json'}
63
106
  padding={8}
64
107
  variant={'borderless'}
65
108
  >
66
- {JSON.stringify(error?.error?.body, null, 2)}
109
+ {errorContent}
67
110
  </Highlighter>
68
111
  )
69
112
  }