@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
@@ -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
  }
@@ -2,12 +2,14 @@
2
2
 
3
3
  import { Flexbox, Text } from '@lobehub/ui';
4
4
  import { createStaticStyles, keyframes } from 'antd-style';
5
- import { memo } from 'react';
5
+ import { memo, useEffect, useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
8
  import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
9
9
  import { shinyTextStyles } from '@/styles';
10
10
 
11
+ import { formatElapsedTime } from './utils';
12
+
11
13
  const shimmer = keyframes`
12
14
  0% {
13
15
  transform: translateX(-100%);
@@ -48,6 +50,18 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
48
50
 
49
51
  const InitializingState = memo(() => {
50
52
  const { t } = useTranslation('chat');
53
+ const [elapsedTime, setElapsedTime] = useState(0);
54
+
55
+ // Timer for updating elapsed time every second
56
+ useEffect(() => {
57
+ const startTime = Date.now();
58
+
59
+ const timer = setInterval(() => {
60
+ setElapsedTime(Date.now() - startTime);
61
+ }, 1000);
62
+
63
+ return () => clearInterval(timer);
64
+ }, []);
51
65
 
52
66
  return (
53
67
  <Flexbox className={styles.container} gap={12}>
@@ -56,6 +70,7 @@ const InitializingState = memo(() => {
56
70
  <Text className={shinyTextStyles.shinyText} weight={500}>
57
71
  {t('task.status.initializing')}
58
72
  </Text>
73
+ <Text type="secondary">({formatElapsedTime(elapsedTime)})</Text>
59
74
  </Flexbox>
60
75
  </Flexbox>
61
76
  );
@@ -0,0 +1,68 @@
1
+ 'use client';
2
+
3
+ import { ThreadStatus } from '@lobechat/types';
4
+ import type { TaskDetail, UIChatMessage } from '@lobechat/types';
5
+ import { Flexbox, Text } from '@lobehub/ui';
6
+ import { memo } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+
9
+ import BubblesLoading from '@/components/BubblesLoading';
10
+
11
+ import ErrorState from './ErrorState';
12
+ import InitializingState from './InitializingState';
13
+ import TaskMessages from './TaskMessages';
14
+ import { useTaskPolling } from './useTaskPolling';
15
+
16
+ export interface TaskContentProps {
17
+ id: string;
18
+ isError: boolean;
19
+ messages: UIChatMessage[] | undefined;
20
+ status: ThreadStatus | undefined;
21
+ taskDetail: TaskDetail | undefined;
22
+ threadId: string | undefined;
23
+ }
24
+
25
+ const TaskContent = memo<TaskContentProps>(
26
+ ({ id, threadId, status, messages, taskDetail, isError }) => {
27
+ const { t } = useTranslation('chat');
28
+ const { isProcessing } = useTaskPolling({
29
+ messageId: id,
30
+ status,
31
+ threadId,
32
+ });
33
+
34
+ // No messages yet
35
+ if (!messages || messages.length === 0) {
36
+ // Still processing: show full initializing state
37
+ if (isProcessing) {
38
+ return <InitializingState />;
39
+ }
40
+
41
+ // Already completed but loading messages: show simple loading
42
+ return (
43
+ <Flexbox align="center" gap={4} horizontal>
44
+ <BubblesLoading />
45
+ <Text type="secondary">{t('task.status.fetchingDetails')}</Text>
46
+ </Flexbox>
47
+ );
48
+ }
49
+
50
+ return (
51
+ <>
52
+ <TaskMessages
53
+ duration={taskDetail?.duration}
54
+ isProcessing={isProcessing}
55
+ messages={messages}
56
+ startTime={taskDetail?.startedAt ? new Date(taskDetail.startedAt).getTime() : undefined}
57
+ totalCost={taskDetail?.totalCost}
58
+ />
59
+ {/* Error states: Failed, Cancel */}
60
+ {isError && taskDetail && <ErrorState taskDetail={taskDetail} />}
61
+ </>
62
+ );
63
+ },
64
+ );
65
+
66
+ TaskContent.displayName = 'TaskContent';
67
+
68
+ export default TaskContent;