@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.
- 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/database/src/repositories/agentGroup/index.ts +4 -0
- package/packages/types/src/message/ui/chat.ts +2 -0
- package/packages/types/src/tool/builtin.ts +5 -5
- package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/ActionButton/ForkGroupAndChat.tsx +2 -1
- package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/index.tsx +2 -2
- package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/useMarketGroupPublish.ts +11 -12
- 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/routers/lambda/agentGroup.ts +2 -0
- package/src/server/routers/lambda/market/agent.ts +17 -45
- package/src/server/routers/lambda/market/agentGroup.ts +13 -25
- 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,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
|
|
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
|
|
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>(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
30
|
-
if (
|
|
31
|
-
return
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
|
139
|
-
{!isInitializing && isProcessing && hasBlocks && (
|
|
140
|
-
<
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
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,
|
|
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
|
-
}, [
|
|
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
|
-
{
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
102
|
+
errorContent && (
|
|
60
103
|
<Highlighter
|
|
61
104
|
actionIconSize={'small'}
|
|
62
105
|
language={'json'}
|
|
63
106
|
padding={8}
|
|
64
107
|
variant={'borderless'}
|
|
65
108
|
>
|
|
66
|
-
{
|
|
109
|
+
{errorContent}
|
|
67
110
|
</Highlighter>
|
|
68
111
|
)
|
|
69
112
|
}
|