@lobehub/lobehub 2.0.0-next.64 → 2.0.0-next.66

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 (75) hide show
  1. package/.github/workflows/claude-translator.yml +1 -0
  2. package/CHANGELOG.md +50 -0
  3. package/changelog/v1.json +18 -0
  4. package/locales/ar/chat.json +3 -0
  5. package/locales/ar/plugin.json +5 -0
  6. package/locales/bg-BG/chat.json +3 -0
  7. package/locales/bg-BG/plugin.json +5 -0
  8. package/locales/de-DE/chat.json +3 -0
  9. package/locales/de-DE/plugin.json +5 -0
  10. package/locales/en-US/chat.json +3 -0
  11. package/locales/es-ES/chat.json +3 -0
  12. package/locales/es-ES/plugin.json +5 -0
  13. package/locales/fa-IR/chat.json +3 -0
  14. package/locales/fa-IR/plugin.json +5 -0
  15. package/locales/fr-FR/chat.json +3 -0
  16. package/locales/fr-FR/plugin.json +5 -0
  17. package/locales/it-IT/chat.json +3 -0
  18. package/locales/it-IT/plugin.json +5 -0
  19. package/locales/ja-JP/chat.json +3 -0
  20. package/locales/ja-JP/plugin.json +5 -0
  21. package/locales/ko-KR/chat.json +3 -0
  22. package/locales/ko-KR/plugin.json +5 -0
  23. package/locales/nl-NL/chat.json +3 -0
  24. package/locales/nl-NL/plugin.json +5 -0
  25. package/locales/pl-PL/chat.json +3 -0
  26. package/locales/pl-PL/plugin.json +5 -0
  27. package/locales/pt-BR/chat.json +3 -0
  28. package/locales/pt-BR/plugin.json +5 -0
  29. package/locales/ru-RU/chat.json +3 -0
  30. package/locales/ru-RU/plugin.json +5 -0
  31. package/locales/tr-TR/chat.json +3 -0
  32. package/locales/tr-TR/plugin.json +5 -0
  33. package/locales/vi-VN/chat.json +3 -0
  34. package/locales/vi-VN/plugin.json +5 -0
  35. package/locales/zh-CN/chat.json +3 -0
  36. package/locales/zh-CN/plugin.json +2 -2
  37. package/locales/zh-TW/chat.json +3 -0
  38. package/locales/zh-TW/plugin.json +5 -0
  39. package/package.json +5 -5
  40. package/packages/conversation-flow/src/__tests__/fixtures/index.ts +4 -8
  41. package/packages/conversation-flow/src/__tests__/fixtures/inputs/{assistant-with-tools.json → assistantGroup/assistant-with-tools.json} +2 -1
  42. package/packages/conversation-flow/src/__tests__/fixtures/inputs/assistantGroup/index.ts +8 -0
  43. package/packages/conversation-flow/src/__tests__/fixtures/inputs/index.ts +2 -4
  44. package/packages/conversation-flow/src/__tests__/fixtures/outputs/{assistant-with-tools.json → assistantGroup/assistant-with-tools.json} +8 -8
  45. package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistantGroup/index.ts +8 -0
  46. package/packages/conversation-flow/src/__tests__/parse.test.ts +6 -6
  47. package/packages/conversation-flow/src/parse.ts +45 -1
  48. package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +64 -0
  49. package/packages/database/package.json +2 -2
  50. package/packages/obervability-otel/package.json +1 -1
  51. package/packages/types/src/message/common/metadata.ts +8 -1
  52. package/packages/types/src/message/ui/chat.ts +1 -0
  53. package/src/app/(backend)/market/agent/[[...segments]]/route.ts +1 -1
  54. package/src/app/(backend)/market/oidc/[[...segments]]/route.ts +1 -1
  55. package/src/app/market-auth-callback/layout.tsx +27 -3
  56. package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +2 -2
  57. package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +15 -1
  58. package/src/features/Conversation/Messages/Assistant/CollapsedMessage.tsx +37 -0
  59. package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +16 -9
  60. package/src/features/Conversation/Messages/Group/Actions/WithContentId.tsx +28 -6
  61. package/src/features/Conversation/Messages/Group/CollapsedMessage.tsx +37 -0
  62. package/src/features/Conversation/Messages/Group/{GroupChildren.tsx → Group.tsx} +18 -4
  63. package/src/features/Conversation/Messages/Group/index.tsx +4 -6
  64. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +14 -0
  65. package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +1 -1
  66. package/src/libs/mcp/__tests__/index.test.ts +6 -6
  67. package/src/locales/default/chat.ts +3 -0
  68. package/src/store/chat/slices/message/actions/publicApi.ts +17 -0
  69. package/src/store/chat/slices/message/selectors/displayMessage.ts +1 -1
  70. package/src/store/chat/slices/message/selectors/messageState.ts +7 -0
  71. package/src/store/chat/slices/translate/action.test.ts +26 -32
  72. package/src/store/chat/slices/translate/action.ts +3 -3
  73. /package/packages/conversation-flow/src/__tests__/fixtures/inputs/{complex-scenario.json → assistantGroup/tools-with-branches.json} +0 -0
  74. /package/packages/conversation-flow/src/__tests__/fixtures/outputs/{complex-scenario.json → assistantGroup/tools-with-branches.json} +0 -0
  75. /package/src/features/Conversation/Messages/Group/{GroupContext.tsx → GroupContext.ts} +0 -0
@@ -253,6 +253,7 @@
253
253
  },
254
254
  "localSystem": {
255
255
  "apiName": {
256
+ "editLocalFile": "编辑文件",
256
257
  "getCommandOutput": "获取代码输出",
257
258
  "globLocalFiles": "匹配搜索文件",
258
259
  "grepContent": "搜索内容",
@@ -263,8 +264,7 @@
263
264
  "renameLocalFile": "重命名",
264
265
  "runCommand": "执行代码",
265
266
  "searchLocalFiles": "搜索文件",
266
- "writeLocalFile": "写入文件",
267
- "editLocalFile": "编辑文件"
267
+ "writeLocalFile": "写入文件"
268
268
  },
269
269
  "title": "本地系统"
270
270
  },
@@ -17,6 +17,7 @@
17
17
  "availableAgents": "可用助理",
18
18
  "backToBottom": "返回底部",
19
19
  "chatList": {
20
+ "expandMessage": "展開訊息",
20
21
  "longMessageDetail": "查看詳情"
21
22
  },
22
23
  "clearCurrentMessages": "清空當前對話",
@@ -173,9 +174,11 @@
173
174
  "title": "提及成員"
174
175
  },
175
176
  "messageAction": {
177
+ "collapse": "收起訊息",
176
178
  "continueGeneration": "繼續生成",
177
179
  "delAndRegenerate": "刪除並重新生成",
178
180
  "deleteDisabledByThreads": "存在子話題,無法刪除",
181
+ "expand": "展開訊息",
179
182
  "regenerate": "重新生成"
180
183
  },
181
184
  "messages": {
@@ -253,6 +253,11 @@
253
253
  },
254
254
  "localSystem": {
255
255
  "apiName": {
256
+ "editLocalFile": "編輯檔案",
257
+ "getCommandOutput": "取得程式輸出",
258
+ "globLocalFiles": "匹配搜尋檔案",
259
+ "grepContent": "搜尋內容",
260
+ "killCommand": "終止程式執行",
256
261
  "listLocalFiles": "查看檔案列表",
257
262
  "moveLocalFiles": "移動檔案",
258
263
  "readLocalFile": "讀取檔案內容",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.64",
3
+ "version": "2.0.0-next.66",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -130,8 +130,8 @@
130
130
  "@ant-design/pro-components": "^2.8.10",
131
131
  "@anthropic-ai/sdk": "^0.67.1",
132
132
  "@auth/core": "^0.40.0",
133
- "@aws-sdk/client-s3": "~3.893.0",
134
- "@aws-sdk/s3-request-presigner": "~3.893.0",
133
+ "@aws-sdk/client-s3": "~3.932.0",
134
+ "@aws-sdk/s3-request-presigner": "~3.932.0",
135
135
  "@azure-rest/ai-inference": "1.0.0-beta.5",
136
136
  "@azure/core-auth": "^1.10.1",
137
137
  "@cfworker/json-schema": "^4.1.1",
@@ -225,7 +225,7 @@
225
225
  "langfuse": "^3.38.6",
226
226
  "langfuse-core": "^3.38.6",
227
227
  "lodash-es": "^4.17.21",
228
- "lucide-react": "^0.548.0",
228
+ "lucide-react": "^0.553.0",
229
229
  "mammoth": "^1.11.0",
230
230
  "markdown-to-txt": "^2.0.1",
231
231
  "marked": "^16.4.2",
@@ -242,7 +242,7 @@
242
242
  "nuqs": "^2.7.3",
243
243
  "officeparser": "5.1.1",
244
244
  "oidc-provider": "^9.5.2",
245
- "ollama": "^0.6.2",
245
+ "ollama": "^0.6.3",
246
246
  "openai": "^4.104.0",
247
247
  "openapi-fetch": "^0.14.1",
248
248
  "partial-json": "^0.1.7",
@@ -1,17 +1,15 @@
1
1
  import type { Message, ParseResult } from '../../types';
2
2
  // Input fixtures
3
3
  import assistantChainWithFollowupInput from './inputs/assistant-chain-with-followup.json';
4
- import assistantWithToolsInput from './inputs/assistant-with-tools.json';
4
+ import { assistantGroup as assistantGroupInputs } from './inputs/assistantGroup';
5
5
  import { branch as branchInputs } from './inputs/branch';
6
6
  import { compare as compareInputs } from './inputs/compare';
7
- import complexScenarioInput from './inputs/complex-scenario.json';
8
7
  import linearConversationInput from './inputs/linear-conversation.json';
9
8
  // Output fixtures
10
9
  import assistantChainWithFollowupOutput from './outputs/assistant-chain-with-followup.json';
11
- import assistantWithToolsOutput from './outputs/assistant-with-tools.json';
10
+ import { assistantGroup as assistantGroupOutputs } from './outputs/assistantGroup';
12
11
  import { branch as branchOutputs } from './outputs/branch';
13
12
  import { compare as compareOutputs } from './outputs/compare';
14
- import complexScenarioOutput from './outputs/complex-scenario.json';
15
13
  import linearConversationOutput from './outputs/linear-conversation.json';
16
14
 
17
15
  /**
@@ -28,10 +26,9 @@ export interface SerializedParseResult {
28
26
  */
29
27
  export const inputs = {
30
28
  assistantChainWithFollowup: assistantChainWithFollowupInput as Message[],
31
- assistantWithTools: assistantWithToolsInput as Message[],
29
+ assistantGroup: assistantGroupInputs,
32
30
  branch: branchInputs,
33
31
  compare: compareInputs,
34
- complexScenario: complexScenarioInput as Message[],
35
32
  linearConversation: linearConversationInput as Message[],
36
33
  };
37
34
 
@@ -40,9 +37,8 @@ export const inputs = {
40
37
  */
41
38
  export const outputs = {
42
39
  assistantChainWithFollowup: assistantChainWithFollowupOutput as unknown as SerializedParseResult,
43
- assistantWithTools: assistantWithToolsOutput as unknown as SerializedParseResult,
40
+ assistantGroup: assistantGroupOutputs,
44
41
  branch: branchOutputs,
45
42
  compare: compareOutputs,
46
- complexScenario: complexScenarioOutput as unknown as SerializedParseResult,
47
43
  linearConversation: linearConversationOutput as unknown as SerializedParseResult,
48
44
  };
@@ -97,7 +97,8 @@
97
97
  "tps": 48,
98
98
  "ttft": 178,
99
99
  "duration": 875,
100
- "latency": 1053
100
+ "latency": 1053,
101
+ "collapsed": true
101
102
  }
102
103
  },
103
104
  {
@@ -0,0 +1,8 @@
1
+ import type { Message } from '../../../../types';
2
+ import assistantWithTools from './assistant-with-tools.json';
3
+ import toolsWithBranches from './tools-with-branches.json';
4
+
5
+ export const assistantGroup = {
6
+ assistantWithTools: assistantWithTools as Message[],
7
+ toolsWithBranches: toolsWithBranches as Message[],
8
+ };
@@ -1,14 +1,12 @@
1
1
  import type { Message } from '../../../types';
2
- import assistantWithTools from './assistant-with-tools.json';
2
+ import { assistantGroup } from './assistantGroup';
3
3
  import { branch } from './branch';
4
4
  import { compare } from './compare';
5
- import complexScenario from './complex-scenario.json';
6
5
  import linearConversation from './linear-conversation.json';
7
6
 
8
7
  export const inputs = {
9
- assistantWithTools: assistantWithTools as Message[],
8
+ assistantGroup,
10
9
  branch,
11
10
  compare,
12
- complexScenario: complexScenario as Message[],
13
11
  linearConversation: linearConversation as Message[],
14
12
  };
@@ -9,18 +9,12 @@
9
9
  {
10
10
  "id": "msg-102",
11
11
  "type": "message",
12
- "tools": [
13
- "msg-103",
14
- "msg-104"
15
- ]
12
+ "tools": ["msg-103", "msg-104"]
16
13
  },
17
14
  {
18
15
  "id": "msg-105",
19
16
  "type": "message",
20
- "tools": [
21
- "msg-106",
22
- "msg-107"
23
- ]
17
+ "tools": ["msg-106", "msg-107"]
24
18
  },
25
19
  {
26
20
  "id": "msg-108",
@@ -98,6 +92,9 @@
98
92
  "totalInputTokens": 28,
99
93
  "totalOutputTokens": 45,
100
94
  "totalTokens": 73
95
+ },
96
+ "metadata": {
97
+ "collapsed": true
101
98
  }
102
99
  },
103
100
  {
@@ -178,6 +175,9 @@
178
175
  "totalOutputTokens": 199,
179
176
  "totalTokens": 628,
180
177
  "cost": 0.0018839999999999998
178
+ },
179
+ "metadata": {
180
+ "collapsed": true
181
181
  }
182
182
  }
183
183
  ],
@@ -0,0 +1,8 @@
1
+ import type { SerializedParseResult } from '../..';
2
+ import assistantWithTools from './assistant-with-tools.json';
3
+ import toolsWithBranches from './tools-with-branches.json';
4
+
5
+ export const assistantGroup = {
6
+ assistantWithTools: assistantWithTools as unknown as SerializedParseResult,
7
+ toolsWithBranches: toolsWithBranches as unknown as SerializedParseResult,
8
+ };
@@ -22,9 +22,9 @@ describe('parse', () => {
22
22
 
23
23
  describe('Tool Usage', () => {
24
24
  it('should parse assistant with tools correctly', () => {
25
- const result = parse(inputs.assistantWithTools);
25
+ const result = parse(inputs.assistantGroup.assistantWithTools);
26
26
 
27
- expect(serializeParseResult(result)).toEqual(outputs.assistantWithTools);
27
+ expect(serializeParseResult(result)).toEqual(outputs.assistantGroup.assistantWithTools);
28
28
  });
29
29
 
30
30
  it('should include follow-up messages after assistant chain', () => {
@@ -99,11 +99,11 @@ describe('parse', () => {
99
99
  });
100
100
  });
101
101
 
102
- describe('Complex Scenarios', () => {
103
- it('should handle complex mixed scenarios correctly', () => {
104
- const result = parse(inputs.complexScenario);
102
+ describe('Assistant Group Scenarios', () => {
103
+ it('should handle tools with assistant branches correctly', () => {
104
+ const result = parse(inputs.assistantGroup.toolsWithBranches);
105
105
 
106
- expect(serializeParseResult(result)).toEqual(outputs.complexScenario);
106
+ expect(serializeParseResult(result)).toEqual(outputs.assistantGroup.toolsWithBranches);
107
107
  });
108
108
  });
109
109
 
@@ -40,9 +40,53 @@ export function parse(messages: Message[], messageGroups?: MessageGroupMetadata[
40
40
  const flatList = transformer.flatten(messages);
41
41
 
42
42
  // Convert messageMap from Map to plain object for serialization
43
+ // Clean up metadata for assistant messages with tools
43
44
  const messageMapObj: Record<string, Message> = {};
45
+ const usagePerformanceFields = new Set([
46
+ 'acceptedPredictionTokens',
47
+ 'cost',
48
+ 'duration',
49
+ 'inputAudioTokens',
50
+ 'inputCacheMissTokens',
51
+ 'inputCachedTokens',
52
+ 'inputCitationTokens',
53
+ 'inputImageTokens',
54
+ 'inputTextTokens',
55
+ 'inputWriteCacheTokens',
56
+ 'latency',
57
+ 'outputAudioTokens',
58
+ 'outputImageTokens',
59
+ 'outputReasoningTokens',
60
+ 'outputTextTokens',
61
+ 'rejectedPredictionTokens',
62
+ 'totalInputTokens',
63
+ 'totalOutputTokens',
64
+ 'totalTokens',
65
+ 'tps',
66
+ 'ttft',
67
+ ]);
68
+
44
69
  helperMaps.messageMap.forEach((message, id) => {
45
- messageMapObj[id] = message;
70
+ // For assistant messages with tools, clean metadata to keep only usage/performance fields
71
+ if (
72
+ message.role === 'assistant' &&
73
+ message.tools &&
74
+ message.tools.length > 0 &&
75
+ message.metadata
76
+ ) {
77
+ const cleanedMetadata: Record<string, any> = {};
78
+ Object.entries(message.metadata).forEach(([key, value]) => {
79
+ if (usagePerformanceFields.has(key)) {
80
+ cleanedMetadata[key] = value;
81
+ }
82
+ });
83
+ messageMapObj[id] = {
84
+ ...message,
85
+ metadata: Object.keys(cleanedMetadata).length > 0 ? cleanedMetadata : undefined,
86
+ };
87
+ } else {
88
+ messageMapObj[id] = message;
89
+ }
46
90
  });
47
91
 
48
92
  return {
@@ -445,6 +445,40 @@ export class FlatListBuilder {
445
445
  const msgUsage = assistant.usage || metaUsage;
446
446
  const msgPerformance = assistant.performance || metaPerformance;
447
447
 
448
+ // Extract non-usage/performance metadata fields
449
+ const otherMetadata: Record<string, any> = {};
450
+ if (assistant.metadata) {
451
+ const usagePerformanceFields = new Set([
452
+ 'acceptedPredictionTokens',
453
+ 'cost',
454
+ 'duration',
455
+ 'inputAudioTokens',
456
+ 'inputCacheMissTokens',
457
+ 'inputCachedTokens',
458
+ 'inputCitationTokens',
459
+ 'inputImageTokens',
460
+ 'inputTextTokens',
461
+ 'inputWriteCacheTokens',
462
+ 'latency',
463
+ 'outputAudioTokens',
464
+ 'outputImageTokens',
465
+ 'outputReasoningTokens',
466
+ 'outputTextTokens',
467
+ 'rejectedPredictionTokens',
468
+ 'totalInputTokens',
469
+ 'totalOutputTokens',
470
+ 'totalTokens',
471
+ 'tps',
472
+ 'ttft',
473
+ ]);
474
+
475
+ Object.entries(assistant.metadata).forEach(([key, value]) => {
476
+ if (!usagePerformanceFields.has(key)) {
477
+ otherMetadata[key] = value;
478
+ }
479
+ });
480
+ }
481
+
448
482
  const childBlock: AssistantContentBlock = {
449
483
  content: assistant.content || '',
450
484
  id: assistant.id,
@@ -457,12 +491,37 @@ export class FlatListBuilder {
457
491
  if (assistant.reasoning) childBlock.reasoning = assistant.reasoning;
458
492
  if (toolsWithResults.length > 0) childBlock.tools = toolsWithResults;
459
493
  if (msgUsage) childBlock.usage = msgUsage;
494
+ if (Object.keys(otherMetadata).length > 0) {
495
+ childBlock.metadata = otherMetadata;
496
+ }
460
497
 
461
498
  children.push(childBlock);
462
499
  }
463
500
 
464
501
  const aggregated = this.messageTransformer.aggregateMetadata(children);
465
502
 
503
+ // Collect all non-usage/performance metadata from all children
504
+ const groupMetadata: Record<string, any> = {};
505
+ children.forEach((child) => {
506
+ if ((child as any).metadata) {
507
+ Object.assign(groupMetadata, (child as any).metadata);
508
+ }
509
+ });
510
+
511
+ // If there's group-level metadata, apply it to first child and remove from others
512
+ if (Object.keys(groupMetadata).length > 0 && children.length > 0) {
513
+ // Ensure first child has the group metadata
514
+ if (!(children[0] as any).metadata) {
515
+ (children[0] as any).metadata = {};
516
+ }
517
+ Object.assign((children[0] as any).metadata, groupMetadata);
518
+
519
+ // Remove metadata from subsequent children (keep only in first child)
520
+ for (let i = 1; i < children.length; i++) {
521
+ delete (children[i] as any).metadata;
522
+ }
523
+ }
524
+
466
525
  const result: Message = {
467
526
  ...firstAssistant,
468
527
  children,
@@ -480,6 +539,11 @@ export class FlatListBuilder {
480
539
  if (aggregated.performance) result.performance = aggregated.performance;
481
540
  if (aggregated.usage) result.usage = aggregated.usage;
482
541
 
542
+ // Add group-level metadata if it exists
543
+ if (Object.keys(groupMetadata).length > 0) {
544
+ result.metadata = groupMetadata;
545
+ }
546
+
483
547
  return result;
484
548
  }
485
549
 
@@ -23,9 +23,9 @@
23
23
  },
24
24
  "peerDependencies": {
25
25
  "@electric-sql/pglite": "^0.2.17",
26
- "dayjs": ">=1.11.18",
26
+ "dayjs": ">=1.11.19",
27
27
  "drizzle-orm": ">=0.44.7",
28
- "nanoid": ">=5.1.5",
28
+ "nanoid": ">=5.1.6",
29
29
  "pg": ">=8.16.3"
30
30
  }
31
31
  }
@@ -20,6 +20,6 @@
20
20
  "@opentelemetry/sdk-node": "^0.207.0",
21
21
  "@opentelemetry/sdk-trace-node": "^2.0.1",
22
22
  "@opentelemetry/semantic-conventions": "^1.36.0",
23
- "@vercel/otel": "^1.13.0"
23
+ "@vercel/otel": "^2.1.0"
24
24
  }
25
25
  }
@@ -75,7 +75,9 @@ export const ModelPerformanceSchema = z.object({
75
75
  latency: z.number().optional(),
76
76
  });
77
77
 
78
- export const MessageMetadataSchema = ModelUsageSchema.merge(ModelPerformanceSchema);
78
+ export const MessageMetadataSchema = ModelUsageSchema.merge(ModelPerformanceSchema).extend({
79
+ collapsed: z.boolean().optional(),
80
+ });
79
81
 
80
82
  export interface ModelUsage extends ModelTokensUsage {
81
83
  /**
@@ -106,5 +108,10 @@ export interface ModelPerformance {
106
108
  export interface MessageMetadata extends ModelUsage, ModelPerformance {
107
109
  activeBranchIndex?: number;
108
110
  activeColumn?: boolean;
111
+ /**
112
+ * 消息折叠状态
113
+ * true: 折叠, false/undefined: 展开
114
+ */
115
+ collapsed?: boolean;
109
116
  compare?: boolean;
110
117
  }
@@ -40,6 +40,7 @@ export interface AssistantContentBlock {
40
40
  error?: ChatMessageError | null;
41
41
  id: string;
42
42
  imageList?: ChatImageItem[];
43
+ metadata?: Record<string, any>;
43
44
  performance?: ModelPerformance;
44
45
  reasoning?: ModelReasoning;
45
46
  tools?: ChatToolPayloadWithResult[];
@@ -7,7 +7,7 @@ type RouteContext = {
7
7
  }>;
8
8
  };
9
9
 
10
- const MARKET_BASE_URL = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'http://127.0.0.1:8787';
10
+ const MARKET_BASE_URL = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'https://market.lobehub.com';
11
11
 
12
12
  const extractAccessToken = (req: NextRequest) => {
13
13
  const authorization = req.headers.get('authorization');
@@ -7,7 +7,7 @@ type RouteContext = {
7
7
  }>;
8
8
  };
9
9
 
10
- const MARKET_BASE_URL = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'http://127.0.0.1:8787';
10
+ const MARKET_BASE_URL = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'https://market.lobehub.com';
11
11
  const ALLOWED_ENDPOINTS = new Set(['handoff', 'token', 'userinfo']);
12
12
 
13
13
  const ensureEndpoint = (segments?: string[]) => {
@@ -1,13 +1,37 @@
1
+ import { cookies, headers } from 'next/headers';
1
2
  import { ReactNode } from 'react';
3
+ import { isRtlLang } from 'rtl-detect';
4
+ import { NuqsAdapter } from 'nuqs/adapters/next/app';
5
+
6
+
7
+ import { DEFAULT_LANG, LOBE_LOCALE_COOKIE } from '@/const/locale';
8
+ import GlobalLayout from '@/layout/GlobalProvider';
9
+ import { Locales } from '@/locales/resources';
10
+ import { parseBrowserLanguage } from '@/utils/locale';
2
11
 
3
12
  interface RootLayoutProps {
4
13
  children: ReactNode;
5
14
  }
6
15
 
7
- const RootLayout = ({ children }: RootLayoutProps) => {
16
+ const RootLayout = async ({ children }: RootLayoutProps) => {
17
+ // 获取 locale:优先级为 cookie > 浏览器语言 > 默认语言
18
+ const cookieStore = await cookies();
19
+ const headersList = await headers();
20
+ const cookieLocale = cookieStore.get(LOBE_LOCALE_COOKIE)?.value as Locales | undefined;
21
+ const browserLanguage = parseBrowserLanguage(headersList, DEFAULT_LANG);
22
+ const locale = (cookieLocale || browserLanguage || DEFAULT_LANG) as Locales;
23
+
24
+ const direction = isRtlLang(locale) ? 'rtl' : 'ltr';
25
+
8
26
  return (
9
- <html lang="en" suppressHydrationWarning>
10
- <body>{children}</body>
27
+ <html dir={direction} lang={locale} suppressHydrationWarning>
28
+ <body>
29
+ <NuqsAdapter>
30
+ <GlobalLayout appearance="auto" isMobile={false} locale={locale}>
31
+ {children}
32
+ </GlobalLayout>
33
+ </NuqsAdapter>
34
+ </body>
11
35
  </html>
12
36
  );
13
37
  };
@@ -13,7 +13,7 @@ import { useTokenCount } from '@/hooks/useTokenCount';
13
13
  import { useAgentStore } from '@/store/agent';
14
14
  import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
15
15
  import { useChatStore } from '@/store/chat';
16
- import { chatSelectors, topicSelectors } from '@/store/chat/selectors';
16
+ import { dbMessageSelectors, topicSelectors } from '@/store/chat/selectors';
17
17
  import { useToolStore } from '@/store/tool';
18
18
  import { toolSelectors } from '@/store/tool/selectors';
19
19
 
@@ -77,7 +77,7 @@ const Token = memo<TokenTagProps>(({ total: messageString }) => {
77
77
  const inputTokenCount = useTokenCount(input);
78
78
 
79
79
  const chatsString = useMemo(() => {
80
- const chats = chatSelectors.mainAIChatsWithHistoryConfig(useChatStore.getState());
80
+ const chats = dbMessageSelectors.activeDbMessages(useChatStore.getState());
81
81
  return chats.map((chat) => chat.content).join('');
82
82
  }, [messageString, historyCount, enableHistoryCount]);
83
83
 
@@ -23,10 +23,11 @@ interface AssistantActionsProps {
23
23
  }
24
24
  export const AssistantActionsBar = memo<AssistantActionsProps>(({ id, data, index }) => {
25
25
  const { error, tools } = data;
26
- const [isThreadMode, hasThread, isRegenerating] = useChatStore((s) => [
26
+ const [isThreadMode, hasThread, isRegenerating, isCollapsed] = useChatStore((s) => [
27
27
  !!s.activeThreadId,
28
28
  threadSelectors.hasThreadBySourceMsgId(id)(s),
29
29
  messageStateSelectors.isMessageRegenerating(id)(s),
30
+ messageStateSelectors.isMessageCollapsed(id)(s),
30
31
  ]);
31
32
  const isGroupSession = useSessionStore(sessionSelectors.isCurrentSessionGroupSession);
32
33
  const [showShareModal, setShareModal] = useState(false);
@@ -43,6 +44,8 @@ export const AssistantActionsBar = memo<AssistantActionsProps>(({ id, data, inde
43
44
  share,
44
45
  tts,
45
46
  translate,
47
+ collapse,
48
+ expand,
46
49
  } = useChatListActionsBar({ hasThread, isRegenerating });
47
50
 
48
51
  const hasTools = !!tools;
@@ -72,6 +75,7 @@ export const AssistantActionsBar = memo<AssistantActionsProps>(({ id, data, inde
72
75
  resendThreadMessage,
73
76
  delAndResendThreadMessage,
74
77
  toggleMessageEditing,
78
+ toggleMessageCollapsed,
75
79
  ] = useChatStore((s) => [
76
80
  s.deleteMessage,
77
81
  s.regenerateAssistantMessage,
@@ -83,6 +87,7 @@ export const AssistantActionsBar = memo<AssistantActionsProps>(({ id, data, inde
83
87
  s.resendThreadMessage,
84
88
  s.delAndResendThreadMessage,
85
89
  s.toggleMessageEditing,
90
+ s.toggleMessageCollapsed,
86
91
  ]);
87
92
  const { message } = App.useApp();
88
93
  const virtuosoRef = use(VirtuosoContext);
@@ -142,6 +147,12 @@ export const AssistantActionsBar = memo<AssistantActionsProps>(({ id, data, inde
142
147
  break;
143
148
  }
144
149
 
150
+ case 'collapse':
151
+ case 'expand': {
152
+ toggleMessageCollapsed(id);
153
+ break;
154
+ }
155
+
145
156
  // case 'export': {
146
157
  // setModal(true);
147
158
  // break;
@@ -166,6 +177,8 @@ export const AssistantActionsBar = memo<AssistantActionsProps>(({ id, data, inde
166
177
 
167
178
  if (error) return <ErrorActionsBar onActionClick={onActionClick} />;
168
179
 
180
+ const collapseAction = isCollapsed ? expand : collapse;
181
+
169
182
  return (
170
183
  <>
171
184
  <ActionIconGroup
@@ -174,6 +187,7 @@ export const AssistantActionsBar = memo<AssistantActionsProps>(({ id, data, inde
174
187
  items: [
175
188
  edit,
176
189
  copy,
190
+ collapseAction,
177
191
  divider,
178
192
  tts,
179
193
  translate,
@@ -0,0 +1,37 @@
1
+ import { Button, Markdown, MaskShadow } from '@lobehub/ui';
2
+ import { memo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { useChatStore } from '@/store/chat';
7
+
8
+ interface CollapsedMessageProps {
9
+ content: string;
10
+ id: string;
11
+ }
12
+
13
+ export const CollapsedMessage = memo<CollapsedMessageProps>(({ id, content }) => {
14
+ const { t } = useTranslation('chat');
15
+ const toggleMessageCollapsed = useChatStore((s) => s.toggleMessageCollapsed);
16
+
17
+ return (
18
+ <Flexbox>
19
+ <MaskShadow>
20
+ <Markdown variant={'chat'}>{content?.slice(0, 100)}</Markdown>
21
+ </MaskShadow>
22
+ <Flexbox padding={4}>
23
+ <Button
24
+ block
25
+ color={'default'}
26
+ onClick={() => {
27
+ toggleMessageCollapsed(id, false);
28
+ }}
29
+ size={'small'}
30
+ variant={'filled'}
31
+ >
32
+ {t('chatList.expandMessage')}
33
+ </Button>
34
+ </Flexbox>
35
+ </Flexbox>
36
+ );
37
+ });