@lobehub/lobehub 2.0.0-next.65 → 2.0.0-next.67
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/.github/workflows/claude-translator.yml +1 -0
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/locales/ar/chat.json +3 -0
- package/locales/bg-BG/chat.json +3 -0
- package/locales/de-DE/chat.json +3 -0
- package/locales/en-US/chat.json +3 -0
- package/locales/es-ES/chat.json +3 -0
- package/locales/fa-IR/chat.json +3 -0
- package/locales/fr-FR/chat.json +3 -0
- package/locales/it-IT/chat.json +3 -0
- package/locales/ja-JP/chat.json +3 -0
- package/locales/ko-KR/chat.json +3 -0
- package/locales/nl-NL/chat.json +3 -0
- package/locales/pl-PL/chat.json +3 -0
- package/locales/pt-BR/chat.json +3 -0
- package/locales/ru-RU/chat.json +3 -0
- package/locales/tr-TR/chat.json +3 -0
- package/locales/vi-VN/chat.json +3 -0
- package/locales/zh-CN/chat.json +3 -0
- package/locales/zh-TW/chat.json +3 -0
- package/package.json +7 -6
- package/packages/conversation-flow/src/__tests__/fixtures/index.ts +4 -8
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/{assistant-with-tools.json → assistantGroup/assistant-with-tools.json} +2 -1
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/assistantGroup/index.ts +8 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/index.ts +2 -4
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/{assistant-with-tools.json → assistantGroup/assistant-with-tools.json} +8 -8
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistantGroup/index.ts +8 -0
- package/packages/conversation-flow/src/__tests__/parse.test.ts +6 -6
- package/packages/conversation-flow/src/parse.ts +45 -1
- package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +64 -0
- package/packages/database/package.json +2 -2
- package/packages/obervability-otel/package.json +1 -1
- package/packages/types/src/message/common/metadata.ts +8 -1
- package/packages/types/src/message/ui/chat.ts +1 -0
- package/src/app/(backend)/market/agent/[[...segments]]/route.ts +1 -1
- package/src/app/(backend)/market/oidc/[[...segments]]/route.ts +1 -1
- package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/ChatItem/index.tsx +1 -0
- package/src/app/[variants]/(main)/chat/components/conversation/features/ChatMinimap/index.tsx +21 -28
- package/src/app/market-auth-callback/layout.tsx +27 -3
- package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +2 -2
- package/src/features/ChatItem/style.ts +4 -0
- package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +18 -4
- package/src/features/Conversation/Messages/Assistant/CollapsedMessage.tsx +37 -0
- package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +16 -9
- package/src/features/Conversation/Messages/Assistant/index.tsx +329 -230
- package/src/features/Conversation/Messages/Group/Actions/WithContentId.tsx +31 -9
- package/src/features/Conversation/Messages/Group/CollapsedMessage.tsx +37 -0
- package/src/features/Conversation/Messages/Group/{GroupChildren.tsx → Group.tsx} +18 -4
- package/src/features/Conversation/Messages/Group/GroupItem.tsx +3 -5
- package/src/features/Conversation/Messages/Group/index.tsx +84 -19
- package/src/features/Conversation/Messages/User/Actions/ActionsBar.tsx +3 -3
- package/src/features/Conversation/Messages/index.tsx +24 -8
- package/src/features/Conversation/components/VirtualizedList/VirtuosoContext.ts +13 -13
- package/src/features/Conversation/components/VirtualizedList/index.tsx +92 -58
- package/src/features/Conversation/components/WideScreenContainer/index.tsx +10 -6
- package/src/features/Conversation/hooks/useChatListActionsBar.tsx +14 -0
- package/src/features/Conversation/hooks/useDoubleClickEdit.ts +3 -3
- package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +1 -1
- package/src/libs/mcp/__tests__/index.test.ts +6 -6
- package/src/locales/default/chat.ts +3 -0
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +9 -1
- package/src/store/chat/slices/message/actions/publicApi.ts +17 -0
- package/src/store/chat/slices/message/selectors/displayMessage.ts +1 -1
- package/src/store/chat/slices/message/selectors/messageState.ts +7 -0
- package/src/store/chat/slices/translate/action.test.ts +26 -32
- package/src/store/chat/slices/translate/action.ts +3 -3
- /package/packages/conversation-flow/src/__tests__/fixtures/inputs/{complex-scenario.json → assistantGroup/tools-with-branches.json} +0 -0
- /package/packages/conversation-flow/src/__tests__/fixtures/outputs/{complex-scenario.json → assistantGroup/tools-with-branches.json} +0 -0
- /package/src/features/Conversation/Messages/Group/{GroupContext.tsx → GroupContext.ts} +0 -0
|
@@ -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('
|
|
103
|
-
it('should handle
|
|
104
|
-
const result = parse(inputs.
|
|
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.
|
|
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
|
-
|
|
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
|
|
|
@@ -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 || '
|
|
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 || '
|
|
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[]) => {
|
package/src/app/[variants]/(main)/chat/components/conversation/features/ChatMinimap/index.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { Icon } from '@lobehub/ui';
|
|
|
4
4
|
import { Popover, Tooltip } from 'antd';
|
|
5
5
|
import { createStyles, useTheme } from 'antd-style';
|
|
6
6
|
import debug from 'debug';
|
|
7
|
+
import isEqual from 'fast-deep-equal';
|
|
7
8
|
import { ChevronDown, ChevronUp } from 'lucide-react';
|
|
8
9
|
import { markdownToTxt } from 'markdown-to-txt';
|
|
9
10
|
import { memo, useCallback, useMemo, useState, useSyncExternalStore } from 'react';
|
|
@@ -11,13 +12,13 @@ import { useTranslation } from 'react-i18next';
|
|
|
11
12
|
import { Flexbox } from 'react-layout-kit';
|
|
12
13
|
|
|
13
14
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
getVirtuaActiveIndex,
|
|
16
|
+
getVirtuaGlobalRef,
|
|
17
|
+
subscribeVirtuaActiveIndex,
|
|
18
|
+
subscribeVirtuaGlobalRef,
|
|
18
19
|
} from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
|
|
19
20
|
import { useChatStore } from '@/store/chat';
|
|
20
|
-
import {
|
|
21
|
+
import { displayMessageSelectors } from '@/store/chat/selectors';
|
|
21
22
|
|
|
22
23
|
const log = debug('lobe-react:chat-minimap');
|
|
23
24
|
|
|
@@ -194,21 +195,17 @@ interface MinimapIndicator {
|
|
|
194
195
|
width: number;
|
|
195
196
|
}
|
|
196
197
|
|
|
197
|
-
const ChatMinimap = () => {
|
|
198
|
+
const ChatMinimap = memo(() => {
|
|
198
199
|
const { t } = useTranslation('chat');
|
|
199
200
|
const { styles, cx } = useStyles();
|
|
200
201
|
const [isHovered, setIsHovered] = useState(false);
|
|
201
|
-
const
|
|
202
|
-
subscribeVirtuosoGlobalRef,
|
|
203
|
-
getVirtuosoGlobalRef,
|
|
204
|
-
() => null,
|
|
205
|
-
);
|
|
202
|
+
const virtuaRef = useSyncExternalStore(subscribeVirtuaGlobalRef, getVirtuaGlobalRef, () => null);
|
|
206
203
|
const activeIndex = useSyncExternalStore(
|
|
207
|
-
|
|
208
|
-
|
|
204
|
+
subscribeVirtuaActiveIndex,
|
|
205
|
+
getVirtuaActiveIndex,
|
|
209
206
|
() => null,
|
|
210
207
|
);
|
|
211
|
-
const messages = useChatStore(
|
|
208
|
+
const messages = useChatStore(displayMessageSelectors.mainAIChats, isEqual);
|
|
212
209
|
|
|
213
210
|
const theme = useTheme();
|
|
214
211
|
|
|
@@ -247,20 +244,19 @@ const ChatMinimap = () => {
|
|
|
247
244
|
|
|
248
245
|
const handleJump = useCallback(
|
|
249
246
|
(virtIndex: number) => {
|
|
250
|
-
|
|
247
|
+
virtuaRef?.current?.scrollToIndex(virtIndex, {
|
|
251
248
|
align: 'start',
|
|
252
|
-
behavior: 'smooth',
|
|
253
|
-
index: virtIndex,
|
|
254
249
|
// The current index detection will be off by 1, so we need to add 1 here
|
|
255
250
|
offset: 6,
|
|
251
|
+
smooth: true,
|
|
256
252
|
});
|
|
257
253
|
},
|
|
258
|
-
[
|
|
254
|
+
[virtuaRef],
|
|
259
255
|
);
|
|
260
256
|
|
|
261
257
|
const handleStep = useCallback(
|
|
262
258
|
(direction: 'prev' | 'next') => {
|
|
263
|
-
const ref =
|
|
259
|
+
const ref = virtuaRef?.current;
|
|
264
260
|
if (!ref || indicators.length === 0) return;
|
|
265
261
|
|
|
266
262
|
let targetPosition: number;
|
|
@@ -303,14 +299,13 @@ const ChatMinimap = () => {
|
|
|
303
299
|
|
|
304
300
|
if (!targetIndicator) return;
|
|
305
301
|
|
|
306
|
-
ref.scrollToIndex({
|
|
302
|
+
ref.scrollToIndex(targetIndicator.virtuosoIndex, {
|
|
307
303
|
align: 'start',
|
|
308
|
-
behavior: 'smooth',
|
|
309
|
-
index: targetIndicator.virtuosoIndex,
|
|
310
304
|
offset: 6,
|
|
305
|
+
smooth: true,
|
|
311
306
|
});
|
|
312
307
|
},
|
|
313
|
-
[activeIndex, activeIndicatorPosition, indicators,
|
|
308
|
+
[activeIndex, activeIndicatorPosition, indicators, virtuaRef],
|
|
314
309
|
);
|
|
315
310
|
|
|
316
311
|
if (indicators.length <= MIN_MESSAGES) return null;
|
|
@@ -353,9 +348,7 @@ const ChatMinimap = () => {
|
|
|
353
348
|
aria-label={t('minimap.jumpToMessage', { index: position + 1 })}
|
|
354
349
|
className={styles.indicator}
|
|
355
350
|
onClick={() => handleJump(virtuosoIndex)}
|
|
356
|
-
style={{
|
|
357
|
-
width,
|
|
358
|
-
}}
|
|
351
|
+
style={{ width }}
|
|
359
352
|
type={'button'}
|
|
360
353
|
>
|
|
361
354
|
<div
|
|
@@ -382,6 +375,6 @@ const ChatMinimap = () => {
|
|
|
382
375
|
</Flexbox>
|
|
383
376
|
</Flexbox>
|
|
384
377
|
);
|
|
385
|
-
};
|
|
378
|
+
});
|
|
386
379
|
|
|
387
|
-
export default
|
|
380
|
+
export default ChatMinimap;
|
|
@@ -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=
|
|
10
|
-
<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 {
|
|
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 =
|
|
80
|
+
const chats = dbMessageSelectors.activeDbMessages(useChatStore.getState());
|
|
81
81
|
return chats.map((chat) => chat.content).join('');
|
|
82
82
|
}, [messageString, historyCount, enableHistoryCount]);
|
|
83
83
|
|
|
@@ -6,7 +6,7 @@ import { memo, use, useCallback, useContext, useMemo, useState } from 'react';
|
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
|
|
8
8
|
import ShareMessageModal from '@/features/Conversation/components/ShareMessageModal';
|
|
9
|
-
import {
|
|
9
|
+
import { VirtuaContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
|
|
10
10
|
import { useChatStore } from '@/store/chat';
|
|
11
11
|
import { messageStateSelectors, threadSelectors } from '@/store/chat/selectors';
|
|
12
12
|
import { useSessionStore } from '@/store/session';
|
|
@@ -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,9 +87,10 @@ 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
|
-
const
|
|
93
|
+
const virtuaRef = use(VirtuaContext);
|
|
89
94
|
|
|
90
95
|
const onActionClick = useCallback(
|
|
91
96
|
async (action: ActionIconGroupEvent) => {
|
|
@@ -93,7 +98,7 @@ export const AssistantActionsBar = memo<AssistantActionsProps>(({ id, data, inde
|
|
|
93
98
|
case 'edit': {
|
|
94
99
|
toggleMessageEditing(id, true);
|
|
95
100
|
|
|
96
|
-
|
|
101
|
+
virtuaRef?.current?.scrollToIndex(index, { align: 'start' });
|
|
97
102
|
}
|
|
98
103
|
}
|
|
99
104
|
if (!data) return;
|
|
@@ -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
|
+
});
|
|
@@ -3,6 +3,7 @@ import { UIChatMessage } from '@lobechat/types';
|
|
|
3
3
|
import { ReactNode, memo } from 'react';
|
|
4
4
|
import { Flexbox } from 'react-layout-kit';
|
|
5
5
|
|
|
6
|
+
import { CollapsedMessage } from '@/features/Conversation/Messages/Assistant/CollapsedMessage';
|
|
6
7
|
import { useChatStore } from '@/store/chat';
|
|
7
8
|
import { aiChatSelectors, messageStateSelectors } from '@/store/chat/selectors';
|
|
8
9
|
|
|
@@ -18,9 +19,10 @@ export const AssistantMessageContent = memo<
|
|
|
18
19
|
editableContent: ReactNode;
|
|
19
20
|
}
|
|
20
21
|
>(({ id, tools, content, chunksList, search, imageList, ...props }) => {
|
|
21
|
-
const [editing, generating] = useChatStore((s) => [
|
|
22
|
+
const [editing, generating, isCollapsed] = useChatStore((s) => [
|
|
22
23
|
messageStateSelectors.isMessageEditing(id)(s),
|
|
23
24
|
messageStateSelectors.isMessageGenerating(id)(s),
|
|
25
|
+
messageStateSelectors.isMessageCollapsed(id)(s),
|
|
24
26
|
]);
|
|
25
27
|
|
|
26
28
|
const isToolCallGenerating = generating && (content === LOADING_FLAT || !content) && !!tools;
|
|
@@ -40,14 +42,19 @@ export const AssistantMessageContent = memo<
|
|
|
40
42
|
|
|
41
43
|
const showFileChunks = !!chunksList && chunksList.length > 0;
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
if (editing)
|
|
46
|
+
return (
|
|
47
|
+
<DefaultMessage
|
|
48
|
+
content={content}
|
|
49
|
+
id={id}
|
|
50
|
+
isToolCallGenerating={isToolCallGenerating}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
if (isCollapsed) return <CollapsedMessage content={content} id={id} />;
|
|
56
|
+
|
|
57
|
+
return (
|
|
51
58
|
<Flexbox gap={8} id={id}>
|
|
52
59
|
{showSearch && (
|
|
53
60
|
<SearchGrounding citations={search?.citations} searchQueries={search?.searchQueries} />
|