@lobehub/lobehub 2.0.0-next.45 → 2.0.0-next.47

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 (44) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/package.json +1 -1
  4. package/packages/database/src/models/file.ts +15 -1
  5. package/packages/database/src/repositories/aiInfra/index.test.ts +1 -1
  6. package/packages/database/src/repositories/dataExporter/index.test.ts +1 -1
  7. package/packages/database/src/repositories/tableViewer/index.test.ts +1 -1
  8. package/packages/types/src/aiProvider.ts +1 -1
  9. package/packages/types/src/document/index.ts +38 -38
  10. package/packages/types/src/exportConfig.ts +15 -15
  11. package/packages/types/src/generation/index.ts +5 -5
  12. package/packages/types/src/openai/chat.ts +15 -15
  13. package/packages/types/src/plugins/mcp.ts +29 -29
  14. package/packages/types/src/plugins/protocol.ts +43 -43
  15. package/packages/types/src/search.ts +4 -4
  16. package/packages/types/src/tool/plugin.ts +3 -3
  17. package/src/app/(backend)/f/[id]/route.ts +55 -0
  18. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/ChatItem/Thread.tsx +1 -1
  19. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/ChatItem/index.tsx +9 -16
  20. package/src/envs/app.ts +4 -3
  21. package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +3 -5
  22. package/src/features/Conversation/Messages/Assistant/Extra/index.test.tsx +3 -3
  23. package/src/features/Conversation/Messages/Assistant/Extra/index.tsx +8 -5
  24. package/src/features/Conversation/Messages/Assistant/index.tsx +29 -15
  25. package/src/features/Conversation/Messages/Group/Actions/WithContentId.tsx +3 -5
  26. package/src/features/Conversation/Messages/Group/index.tsx +12 -20
  27. package/src/features/Conversation/Messages/Supervisor/index.tsx +14 -5
  28. package/src/features/Conversation/Messages/User/index.tsx +14 -8
  29. package/src/features/Conversation/Messages/index.tsx +16 -26
  30. package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +7 -6
  31. package/src/features/Conversation/components/Extras/Usage/UsageDetail/tokens.ts +2 -5
  32. package/src/features/Conversation/components/Extras/Usage/index.tsx +13 -6
  33. package/src/features/Conversation/components/VirtualizedList/index.tsx +2 -1
  34. package/src/features/PluginsUI/Render/MCPType/index.tsx +26 -6
  35. package/src/server/modules/ContentChunk/index.test.ts +372 -0
  36. package/src/server/routers/desktop/mcp.ts +23 -8
  37. package/src/server/routers/tools/mcp.ts +24 -4
  38. package/src/server/services/file/impls/local.ts +4 -1
  39. package/src/server/services/file/index.ts +96 -1
  40. package/src/server/services/mcp/contentProcessor.ts +101 -0
  41. package/src/server/services/mcp/index.test.ts +52 -10
  42. package/src/server/services/mcp/index.ts +29 -26
  43. package/src/services/session/index.ts +0 -14
  44. package/src/utils/server/routeVariants.test.ts +340 -0
@@ -21,7 +21,7 @@ export enum MCPInstallStep {
21
21
  /* eslint-enable */
22
22
  export interface CheckMcpInstallParams {
23
23
  /**
24
- * 安装详情
24
+ * Installation details
25
25
  */
26
26
  installationDetails: {
27
27
  packageName?: string;
@@ -29,11 +29,11 @@ export interface CheckMcpInstallParams {
29
29
  setupSteps?: string[];
30
30
  };
31
31
  /**
32
- * 安装方法
32
+ * Installation method
33
33
  */
34
34
  installationMethod: string;
35
35
  /**
36
- * 系统依赖项
36
+ * System dependencies
37
37
  */
38
38
  systemDependencies?: SystemDependency[];
39
39
  }
@@ -41,12 +41,12 @@ export interface CheckMcpInstallParams {
41
41
  export interface CheckMcpInstallResult {
42
42
  allDependenciesMet?: boolean;
43
43
  /**
44
- * 所有部署选项的检查结果
44
+ * Check results for all deployment options
45
45
  */
46
46
  allOptions?: Array<{
47
47
  allDependenciesMet?: boolean;
48
48
  /**
49
- * 连接信息,用于后续连接使用
49
+ * Connection information for subsequent connection use
50
50
  */
51
51
  connection?: {
52
52
  args?: string[];
@@ -66,11 +66,11 @@ export interface CheckMcpInstallResult {
66
66
  }>;
67
67
  }>;
68
68
  /**
69
- * 配置模式,提到顶层方便访问
69
+ * Configuration schema, elevated to top level for easy access
70
70
  */
71
71
  configSchema?: any;
72
72
  /**
73
- * 连接信息,用于后续连接使用
73
+ * Connection information for subsequent connection use
74
74
  */
75
75
  connection?: {
76
76
  args?: string[];
@@ -79,28 +79,28 @@ export interface CheckMcpInstallResult {
79
79
  url?: string;
80
80
  };
81
81
  /**
82
- * 如果检测失败,提供错误信息
82
+ * Error information if detection fails
83
83
  */
84
84
  error?: string;
85
85
  /**
86
- * 是否为推荐选项
86
+ * Whether this is the recommended option
87
87
  */
88
88
  isRecommended?: boolean;
89
89
  /**
90
- * 是否需要配置(如 API key 等)
90
+ * Whether configuration is needed (e.g. API key, etc.)
91
91
  */
92
92
  needsConfig?: boolean;
93
93
  /**
94
- * 插件安装检测结果
94
+ * Plugin installation detection result
95
95
  */
96
96
  packageInstalled?: boolean;
97
97
  platform: string;
98
98
  /**
99
- * 检测结果是否成功
99
+ * Whether the detection result is successful
100
100
  */
101
101
  success: boolean;
102
102
  /**
103
- * 系统依赖检测结果
103
+ * System dependency detection results
104
104
  */
105
105
  systemDependencies?: Array<{
106
106
  error?: string;
@@ -119,12 +119,12 @@ export interface MCPErrorInfoMetadata {
119
119
  errorLog?: string;
120
120
 
121
121
  /**
122
- * 原始错误信息
122
+ * Original error message
123
123
  */
124
124
  originalError?: string;
125
125
 
126
126
  /**
127
- * MCP 连接参数
127
+ * MCP connection parameters
128
128
  */
129
129
  params?: {
130
130
  args?: string[];
@@ -132,7 +132,7 @@ export interface MCPErrorInfoMetadata {
132
132
  type?: string;
133
133
  };
134
134
  /**
135
- * 进程相关信息
135
+ * Process-related information
136
136
  */
137
137
  process?: {
138
138
  exitCode?: number;
@@ -140,31 +140,31 @@ export interface MCPErrorInfoMetadata {
140
140
  };
141
141
 
142
142
  /**
143
- * 错误发生的步骤
143
+ * Step where the error occurred
144
144
  */
145
145
  step?: string;
146
146
 
147
147
  /**
148
- * 时间戳
148
+ * Timestamp
149
149
  */
150
150
  timestamp?: number;
151
151
  }
152
152
  /**
153
- * 结构化的错误信息
153
+ * Structured error information
154
154
  */
155
155
  export interface MCPErrorInfo {
156
156
  /**
157
- * 核心错误信息(用户友好的简短描述)
157
+ * Core error message (user-friendly brief description)
158
158
  */
159
159
  message: string;
160
160
 
161
161
  /**
162
- * 结构化的错误元数据
162
+ * Structured error metadata
163
163
  */
164
164
  metadata?: MCPErrorInfoMetadata;
165
165
 
166
166
  /**
167
- * 错误类型
167
+ * Error type
168
168
  */
169
169
  type: MCPErrorType;
170
170
  }
@@ -174,7 +174,7 @@ export interface MCPInstallProgress {
174
174
  configSchema?: any;
175
175
  // connection info from checkInstallation
176
176
  connection?: any;
177
- // 结构化的错误信息,当安装失败时显示
177
+ // Structured error information, displayed when installation fails
178
178
  errorInfo?: MCPErrorInfo;
179
179
  manifest?: any;
180
180
  // LobeChatPluginManifest
@@ -182,12 +182,12 @@ export interface MCPInstallProgress {
182
182
  // 0-100
183
183
  progress: number;
184
184
  step: MCPInstallStep;
185
- // 系统依赖检测结果,当需要安装依赖时显示
185
+ // System dependency detection results, displayed when dependencies need to be installed
186
186
  systemDependencies?: Array<{
187
187
  error?: string;
188
188
  installInstructions?: {
189
- current?: string; // 当前系统的安装指令
190
- manual?: string; // 手动安装指令
189
+ current?: string; // Installation command for the current system
190
+ manual?: string; // Manual installation command
191
191
  };
192
192
  installed: boolean;
193
193
  meetRequirement: boolean;
@@ -206,16 +206,16 @@ export interface McpConnection {
206
206
  token?: string;
207
207
  type: 'none' | 'bearer' | 'oauth2';
208
208
  };
209
- // STDIO 连接参数
209
+ // STDIO connection parameters
210
210
  command?: string;
211
211
  env?: Record<string, string>;
212
212
  headers?: Record<string, string>;
213
213
  type: 'http' | 'stdio';
214
- // HTTP 连接参数
214
+ // HTTP connection parameters
215
215
  url?: string;
216
216
  }
217
217
 
218
- // 测试连接参数类型
218
+ // Test connection parameter type
219
219
  export interface McpConnectionParams {
220
220
  connection: McpConnection;
221
221
  identifier: string;
@@ -1,21 +1,21 @@
1
1
  /**
2
- * 协议来源类型
2
+ * Protocol source type
3
3
  */
4
4
  export enum ProtocolSource {
5
- /** 社区贡献 */
5
+ /** Community contribution */
6
6
  COMMUNITY = 'community',
7
- /** 开发者自定义 */
7
+ /** Developer custom */
8
8
  DEVELOPER = 'developer',
9
- /** GitHub 官方 */
9
+ /** GitHub official */
10
10
  GITHUB_OFFICIAL = 'github_official',
11
- /** 官方LobeHub市场 */
11
+ /** Official LobeHub marketplace */
12
12
  OFFICIAL = 'official',
13
- /** 第三方市场 */
13
+ /** Third-party marketplace */
14
14
  THIRD_PARTY = 'third_party',
15
15
  }
16
16
 
17
17
  /**
18
- * MCP Schema - stdio 配置类型
18
+ * MCP Schema - stdio configuration type
19
19
  */
20
20
  export interface McpStdioConfig {
21
21
  args?: string[];
@@ -25,7 +25,7 @@ export interface McpStdioConfig {
25
25
  }
26
26
 
27
27
  /**
28
- * MCP Schema - http 配置类型
28
+ * MCP Schema - http configuration type
29
29
  */
30
30
  export interface McpHttpConfig {
31
31
  headers?: Record<string, string>;
@@ -34,74 +34,74 @@ export interface McpHttpConfig {
34
34
  }
35
35
 
36
36
  /**
37
- * MCP Schema 配置类型
37
+ * MCP Schema configuration type
38
38
  */
39
39
  export type McpConfig = McpStdioConfig | McpHttpConfig;
40
40
 
41
41
  /**
42
- * MCP Schema 对象
43
- * 符合 RFC 0001 定义
42
+ * MCP Schema object
43
+ * Conforms to RFC 0001 definition
44
44
  */
45
45
  export interface McpSchema {
46
- /** 插件作者 */
46
+ /** Plugin author */
47
47
  author: string;
48
- /** 插件配置 */
48
+ /** Plugin configuration */
49
49
  config: McpConfig;
50
- /** 插件描述 */
50
+ /** Plugin description */
51
51
  description: string;
52
- /** 插件主页 */
52
+ /** Plugin homepage */
53
53
  homepage?: string;
54
- /** 插件图标 */
54
+ /** Plugin icon */
55
55
  icon?: string;
56
- /** 插件唯一标识符,必须与URL中的id参数匹配 */
56
+ /** Plugin unique identifier, must match the id parameter in the URL */
57
57
  identifier: string;
58
- /** 插件名称 */
58
+ /** Plugin name */
59
59
  name: string;
60
- /** 插件版本 (semver) */
60
+ /** Plugin version (semver) */
61
61
  version: string;
62
62
  }
63
63
 
64
64
  /**
65
- * RFC 0001 协议参数
65
+ * RFC 0001 protocol parameters
66
66
  * lobehub://plugin/install?id=xxx&schema=xxx&marketId=xxx&meta_*=xxx
67
67
  */
68
68
  export interface McpInstallProtocolParamsRFC {
69
- /** 可选的 UI 显示元数据,以 meta_ 为前缀 */
69
+ /** Optional UI display metadata, prefixed with meta_ */
70
70
  [key: `meta_${string}`]: string | undefined;
71
- /** 插件的唯一标识符 */
71
+ /** Unique identifier of the plugin */
72
72
  id: string;
73
- /** 提供该插件的 Marketplace 的唯一标识符 */
73
+ /** Unique identifier of the Marketplace providing this plugin */
74
74
  marketId?: string;
75
- /** Base64URL 编码的 MCP Schema 对象 */
75
+ /** Base64URL encoded MCP Schema object */
76
76
  schema: string;
77
- /** 插件类型,对于 MCP 固定为 'mcp' */
77
+ /** Plugin type, fixed as 'mcp' for MCP */
78
78
  type: 'mcp';
79
79
  }
80
80
 
81
81
  /**
82
- * 协议URL解析结果
82
+ * Protocol URL parsing result
83
83
  */
84
84
  export interface ProtocolUrlParsed {
85
- /** 操作类型 (如: 'install') */
85
+ /** Action type (e.g.: 'install') */
86
86
  action: 'install' | 'configure' | 'update';
87
- /** 解析后的参数 */
87
+ /** Parsed parameters */
88
88
  params: {
89
89
  id: string;
90
90
  marketId?: string;
91
91
  type: string;
92
92
  };
93
- /** MCP Schema 对象 */
93
+ /** MCP Schema object */
94
94
  schema: McpSchema;
95
- /** 协议来源 */
95
+ /** Protocol source */
96
96
  source: ProtocolSource;
97
- /** 插件类型 (如: 'mcp') */
97
+ /** Plugin type (e.g.: 'mcp') */
98
98
  type: 'mcp' | 'plugin';
99
- /** URL类型 (如: 'plugin') */
99
+ /** URL type (e.g.: 'plugin') */
100
100
  urlType: string;
101
101
  }
102
102
 
103
103
  /**
104
- * 安装确认弹窗信息
104
+ * Installation confirmation dialog information
105
105
  */
106
106
  export interface InstallConfirmationInfo {
107
107
  dependencies?: string[];
@@ -125,42 +125,42 @@ export interface InstallConfirmationInfo {
125
125
  url?: string;
126
126
  };
127
127
  type: ProtocolSource;
128
- verified: boolean; // 是否为验证来源
128
+ verified: boolean; // Whether it's a verified source
129
129
  };
130
130
  }
131
131
 
132
132
  /**
133
- * 协议处理器接口
133
+ * Protocol handler interface
134
134
  */
135
135
  export interface ProtocolHandler {
136
136
  /**
137
- * 处理协议URL
137
+ * Handle protocol URL
138
138
  */
139
139
  handle(
140
140
  parsed: ProtocolUrlParsed,
141
141
  ): Promise<{ error?: string; success: boolean; targetWindow?: string }>;
142
142
 
143
143
  /**
144
- * 支持的操作
144
+ * Supported actions
145
145
  */
146
146
  readonly supportedActions: string[];
147
147
 
148
148
  /**
149
- * 协议类型
149
+ * Protocol type
150
150
  */
151
151
  readonly type: string;
152
152
  }
153
153
 
154
154
  /**
155
- * 协议路由配置
155
+ * Protocol routing configuration
156
156
  */
157
157
  export interface ProtocolRouteConfig {
158
- /** 操作类型 */
158
+ /** Action type */
159
159
  action: string;
160
- /** 目标路径(相对于窗口base路径) */
160
+ /** Target path (relative to window base path) */
161
161
  targetPath?: string;
162
- /** 目标窗口 */
162
+ /** Target window */
163
163
  targetWindow: 'chat' | 'settings';
164
- /** 协议类型 */
164
+ /** Protocol type */
165
165
  type: string;
166
166
  }
@@ -4,16 +4,16 @@ export type SearchMode = 'off' | 'auto' | 'on';
4
4
 
5
5
  export enum ModelSearchImplement {
6
6
  /**
7
- * 模型内置了搜索功能
8
- * 类似 Jina PPLX 等模型的搜索模式,让调用方无感知
7
+ * Model has built-in search functionality
8
+ * Similar to search modes of models like Jina, PPLX, transparent to the caller
9
9
  */
10
10
  Internal = 'internal',
11
11
  /**
12
- * 使用参数开关的方式,例如 QwenGoogleOpenRouter,搜索结果在
12
+ * Uses parameter toggle approach, e.g. Qwen, Google, OpenRouter, search results in
13
13
  */
14
14
  Params = 'params',
15
15
  /**
16
- * 使用工具调用的方式
16
+ * Uses tool calling approach
17
17
  */
18
18
  Tool = 'tool',
19
19
  }
@@ -19,7 +19,7 @@ export interface CustomPluginParams {
19
19
 
20
20
  /* eslint-disable sort-keys-fix/sort-keys-fix , typescript-sort-keys/interface */
21
21
  /**
22
- * TODO: 临时方案,后续需要做一次大重构
22
+ * TODO: Temporary solution, needs major refactoring in the future
23
23
  */
24
24
  mcp?: {
25
25
  args?: string[];
@@ -27,13 +27,13 @@ export interface CustomPluginParams {
27
27
  command?: string;
28
28
  type: 'http' | 'stdio';
29
29
  url?: string;
30
- // 新增认证配置支持
30
+ // Added authentication configuration support
31
31
  auth?: {
32
32
  type: 'none' | 'bearer' | 'oauth2';
33
33
  token?: string; // Bearer Token
34
34
  accessToken?: string; // OAuth2 Access Token
35
35
  };
36
- // 新增 headers 配置支持
36
+ // Added headers configuration support
37
37
  headers?: Record<string, string>;
38
38
  };
39
39
  avatar?: string;
@@ -0,0 +1,55 @@
1
+ import debug from 'debug';
2
+
3
+ import { FileModel } from '@/database/models/file';
4
+ import { getServerDB } from '@/database/server';
5
+ import { FileService } from '@/server/services/file';
6
+
7
+ const log = debug('lobe-file:proxy');
8
+
9
+ type Params = Promise<{ id: string }>;
10
+
11
+ /**
12
+ * File proxy service
13
+ * GET /f/:id
14
+ *
15
+ * Features:
16
+ * - Query database to get file record (without userId filter for public access)
17
+ * - Generate access URL based on platform (desktop → local file, web → S3 presigned URL)
18
+ * - Return 302 redirect
19
+ */
20
+ export const GET = async (_req: Request, segmentData: { params: Params }) => {
21
+ try {
22
+ const params = await segmentData.params;
23
+ const { id } = params;
24
+
25
+ log('File proxy request: %s', id);
26
+
27
+ // Get database connection
28
+ const db = await getServerDB();
29
+
30
+ // Query file record without userId filter (public access)
31
+ const file = await FileModel.getFileById(db, id);
32
+
33
+ if (!file) {
34
+ log('File not found: %s', id);
35
+ return new Response('File not found', {
36
+ status: 404,
37
+ });
38
+ }
39
+
40
+ // Create file service with file owner's userId
41
+ const fileService = new FileService(db, file.userId);
42
+
43
+ // Web: Generate S3 presigned URL (5 minutes expiry)
44
+ const redirectUrl = await fileService.createPreSignedUrlForPreview(file.url, 300);
45
+ log('Web S3 presigned URL generated (expires in 5 min)');
46
+
47
+ // Return 302 redirect
48
+ return Response.redirect(redirectUrl, 302);
49
+ } catch (error) {
50
+ console.error('File proxy error:', error);
51
+ return new Response('Internal server error', {
52
+ status: 500,
53
+ });
54
+ }
55
+ };
@@ -39,7 +39,7 @@ const Thread = memo<ThreadProps>(({ id, placement, style }) => {
39
39
  direction={placement === 'end' ? 'horizontal-reverse' : 'horizontal'}
40
40
  gap={12}
41
41
  paddingInline={16}
42
- style={{ paddingBottom: 16, ...style }}
42
+ style={{ marginTop: -12, paddingBottom: 16, ...style }}
43
43
  >
44
44
  <div style={{ width: 40 }} />
45
45
  <Flexbox className={styles.container} gap={4} padding={4} style={{ width: 'fit-content' }}>
@@ -1,13 +1,13 @@
1
1
  import { createStyles } from 'antd-style';
2
2
  import React, { memo } from 'react';
3
3
 
4
- import SupervisorThinkingTag from '@/app/[variants]/(main)/chat/components/conversation/features/ChatList/ChatItem/OrchestratorThinking';
5
4
  import { ChatItem } from '@/features/Conversation';
6
5
  import { useAgentStore } from '@/store/agent';
7
6
  import { agentChatConfigSelectors } from '@/store/agent/selectors';
8
7
  import { useChatStore } from '@/store/chat';
9
- import { chatSelectors, threadSelectors } from '@/store/chat/selectors';
8
+ import { displayMessageSelectors, threadSelectors } from '@/store/chat/selectors';
10
9
 
10
+ import SupervisorThinkingTag from './OrchestratorThinking';
11
11
  import Thread from './Thread';
12
12
 
13
13
  const useStyles = createStyles(({ css, token, isDarkMode }) => {
@@ -26,15 +26,16 @@ const useStyles = createStyles(({ css, token, isDarkMode }) => {
26
26
  content: '';
27
27
 
28
28
  position: absolute;
29
- inset-block: 56px 50px;
29
+ inset-block-end: 60px;
30
30
 
31
- width: 32px;
31
+ width: 38px;
32
+ height: 53px;
32
33
  border-block-end: 2px solid ${borderColor};
33
34
  }
34
35
  `,
35
36
  start: css`
36
37
  &::after {
37
- inset-inline-start: 36px;
38
+ inset-inline-start: 30px;
38
39
  border-inline-start: 2px solid ${borderColor};
39
40
  border-end-start-radius: 8px;
40
41
  }
@@ -52,7 +53,7 @@ const MainChatItem = memo<ThreadChatItemProps>(({ id, index }) => {
52
53
 
53
54
  const [showThread, historyLength] = useChatStore((s) => [
54
55
  threadSelectors.hasThreadBySourceMsgId(id)(s),
55
- chatSelectors.mainDisplayChatIDs(s).length,
56
+ displayMessageSelectors.mainDisplayChatIDs(s).length,
56
57
  ]);
57
58
 
58
59
  const [displayMode, enableHistoryDivider] = useAgentStore((s) => [
@@ -60,7 +61,7 @@ const MainChatItem = memo<ThreadChatItemProps>(({ id, index }) => {
60
61
  agentChatConfigSelectors.enableHistoryDivider(historyLength, index)(s),
61
62
  ]);
62
63
 
63
- const userRole = useChatStore((s) => chatSelectors.getMessageById(id)(s)?.role);
64
+ const userRole = useChatStore((s) => displayMessageSelectors.getDisplayMessageById(id)(s)?.role);
64
65
 
65
66
  const placement = displayMode === 'chat' && userRole === 'user' ? 'end' : 'start';
66
67
 
@@ -71,15 +72,7 @@ const MainChatItem = memo<ThreadChatItemProps>(({ id, index }) => {
71
72
  <ChatItem
72
73
  className={showThread ? cx(styles.line, styles[placement]) : ''}
73
74
  enableHistoryDivider={enableHistoryDivider}
74
- endRender={
75
- showThread && (
76
- <Thread
77
- id={id}
78
- placement={placement}
79
- style={{ marginTop: displayMode === 'docs' ? 12 : undefined }}
80
- />
81
- )
82
- }
75
+ endRender={showThread && <Thread id={id} placement={placement} />}
83
76
  id={id}
84
77
  index={index}
85
78
  />
package/src/envs/app.ts CHANGED
@@ -18,7 +18,9 @@ const APP_URL = process.env.APP_URL
18
18
  ? process.env.APP_URL
19
19
  : isInVercel
20
20
  ? vercelUrl
21
- : 'http://localhost:3010';
21
+ : process.env.NODE_ENV === 'development'
22
+ ? 'http://localhost:3010'
23
+ : 'http://localhost:3210';
22
24
 
23
25
  // INTERNAL_APP_URL is used for server-to-server calls to bypass CDN/proxy
24
26
  // Falls back to APP_URL if not set
@@ -37,7 +39,6 @@ export const getAppConfig = () => {
37
39
  },
38
40
  server: {
39
41
  ACCESS_CODES: z.any(z.string()).optional(),
40
-
41
42
  AGENTS_INDEX_URL: z.string().url(),
42
43
 
43
44
  DEFAULT_AGENT_CONFIG: z.string(),
@@ -46,7 +47,7 @@ export const getAppConfig = () => {
46
47
  PLUGINS_INDEX_URL: z.string().url(),
47
48
  PLUGIN_SETTINGS: z.string().optional(),
48
49
 
49
- APP_URL: z.string().optional(),
50
+ APP_URL: z.string(),
50
51
  INTERNAL_APP_URL: z.string().optional(),
51
52
  VERCEL_EDGE_CONFIG: z.string().optional(),
52
53
  MIDDLEWARE_REWRITE_THROUGH_LOCAL: z.boolean().optional(),
@@ -53,11 +53,9 @@ export const AssistantActionsBar = memo<AssistantActionsProps>(({ id, data, inde
53
53
  const items = useMemo(() => {
54
54
  if (hasTools) return [delAndRegenerate, copy];
55
55
 
56
- return [
57
- edit,
58
- copy,
59
- // inThread || isGroupSession ? null : branching
60
- ].filter(Boolean) as ActionIconGroupItemType[];
56
+ return [edit, copy, inThread || isGroupSession ? null : branching].filter(
57
+ Boolean,
58
+ ) as ActionIconGroupItemType[];
61
59
  }, [inThread, hasTools, isGroupSession, delAndRegenerate, copy, edit, branching]);
62
60
 
63
61
  const { t } = useTranslation('common');
@@ -24,7 +24,7 @@ vi.mock('@/store/chat', () => ({
24
24
  useChatStore: vi.fn(),
25
25
  }));
26
26
 
27
- const mockData: UIChatMessage = {
27
+ const mockData = {
28
28
  content: 'test-content',
29
29
  createdAt: 0,
30
30
  id: 'abc',
@@ -53,8 +53,8 @@ describe('AssistantMessageExtra', () => {
53
53
  expect(screen.queryByText('Translate Component')).toBeNull();
54
54
  });
55
55
 
56
- it('should render Usage component if extra.model exists', async () => {
57
- render(<AssistantMessageExtra {...mockData} extra={{ model: 'gpt-4', provider: 'openai' }} />);
56
+ it('should render Usage component if model prop exists', async () => {
57
+ render(<AssistantMessageExtra {...mockData} model="gpt-4" provider="openai" />);
58
58
 
59
59
  expect(screen.getByText('Usage Component')).toBeInTheDocument();
60
60
  });
@@ -1,4 +1,4 @@
1
- import { type MessageMetadata } from '@lobechat/types';
1
+ import { ModelPerformance, ModelUsage } from '@lobechat/types';
2
2
  import { memo } from 'react';
3
3
  import { Flexbox } from 'react-layout-kit';
4
4
 
@@ -14,18 +14,21 @@ interface AssistantMessageExtraProps {
14
14
  content: string;
15
15
  extra?: any;
16
16
  id: string;
17
- metadata?: MessageMetadata | null;
17
+ model?: string;
18
+ performance?: ModelPerformance;
19
+ provider?: string;
18
20
  tools?: any[];
21
+ usage?: ModelUsage;
19
22
  }
20
23
 
21
24
  export const AssistantMessageExtra = memo<AssistantMessageExtraProps>(
22
- ({ extra, id, content, metadata, tools }) => {
25
+ ({ extra, id, content, performance, usage, tools, provider, model }) => {
23
26
  const loading = useChatStore(messageStateSelectors.isMessageGenerating(id));
24
27
 
25
28
  return (
26
29
  <Flexbox gap={8} style={{ marginTop: !!tools?.length ? 8 : 4 }}>
27
- {content !== LOADING_FLAT && extra?.model && (
28
- <Usage metadata={metadata || {}} model={extra?.model} provider={extra.provider!} />
30
+ {content !== LOADING_FLAT && model && (
31
+ <Usage model={model} performance={performance} provider={provider!} usage={usage} />
29
32
  )}
30
33
  <>
31
34
  {!!extra?.tts && (