@lobehub/lobehub 2.0.0-next.65 → 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.
- package/.github/workflows/claude-translator.yml +1 -0
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -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 +5 -5
- 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/market-auth-callback/layout.tsx +27 -3
- package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +15 -1
- 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/Group/Actions/WithContentId.tsx +28 -6
- 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/index.tsx +4 -6
- package/src/features/Conversation/hooks/useChatListActionsBar.tsx +14 -0
- 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/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
|
@@ -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[]) => {
|
|
@@ -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
|
|
|
@@ -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
|
+
});
|
|
@@ -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} />
|
|
@@ -24,19 +24,30 @@ interface GroupActionsProps {
|
|
|
24
24
|
|
|
25
25
|
const WithContentId = memo<GroupActionsProps>(({ id, data, index, contentBlock }) => {
|
|
26
26
|
const { tools } = data;
|
|
27
|
-
const [isThreadMode, hasThread, isRegenerating] = useChatStore((s) => [
|
|
27
|
+
const [isThreadMode, hasThread, isRegenerating, isCollapsed] = useChatStore((s) => [
|
|
28
28
|
!!s.activeThreadId,
|
|
29
29
|
threadSelectors.hasThreadBySourceMsgId(id)(s),
|
|
30
30
|
messageStateSelectors.isMessageRegenerating(id)(s),
|
|
31
|
+
messageStateSelectors.isMessageCollapsed(id)(s),
|
|
31
32
|
]);
|
|
32
33
|
const isGroupSession = useSessionStore(sessionSelectors.isCurrentSessionGroupSession);
|
|
33
34
|
const [showShareModal, setShareModal] = useState(false);
|
|
34
35
|
|
|
35
|
-
const {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
const {
|
|
37
|
+
edit,
|
|
38
|
+
delAndRegenerate,
|
|
39
|
+
regenerate,
|
|
40
|
+
copy,
|
|
41
|
+
divider,
|
|
42
|
+
del,
|
|
43
|
+
branching,
|
|
44
|
+
share,
|
|
45
|
+
expand,
|
|
46
|
+
collapse,
|
|
47
|
+
} = useChatListActionsBar({
|
|
48
|
+
hasThread,
|
|
49
|
+
isRegenerating,
|
|
50
|
+
});
|
|
40
51
|
|
|
41
52
|
const hasTools = !!tools;
|
|
42
53
|
|
|
@@ -64,6 +75,7 @@ const WithContentId = memo<GroupActionsProps>(({ id, data, index, contentBlock }
|
|
|
64
75
|
resendThreadMessage,
|
|
65
76
|
delAndResendThreadMessage,
|
|
66
77
|
toggleMessageEditing,
|
|
78
|
+
toggleMessageCollapsed,
|
|
67
79
|
] = useChatStore((s) => [
|
|
68
80
|
s.deleteMessage,
|
|
69
81
|
s.regenerateAssistantMessage,
|
|
@@ -74,6 +86,7 @@ const WithContentId = memo<GroupActionsProps>(({ id, data, index, contentBlock }
|
|
|
74
86
|
s.resendThreadMessage,
|
|
75
87
|
s.delAndResendThreadMessage,
|
|
76
88
|
s.toggleMessageEditing,
|
|
89
|
+
s.toggleMessageCollapsed,
|
|
77
90
|
]);
|
|
78
91
|
const { message } = App.useApp();
|
|
79
92
|
const virtuosoRef = use(VirtuosoContext);
|
|
@@ -133,6 +146,12 @@ const WithContentId = memo<GroupActionsProps>(({ id, data, index, contentBlock }
|
|
|
133
146
|
setShareModal(true);
|
|
134
147
|
break;
|
|
135
148
|
}
|
|
149
|
+
|
|
150
|
+
case 'collapse':
|
|
151
|
+
case 'expand': {
|
|
152
|
+
toggleMessageCollapsed(id);
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
136
155
|
}
|
|
137
156
|
|
|
138
157
|
if (action.keyPath.at(-1) === 'translate') {
|
|
@@ -146,6 +165,8 @@ const WithContentId = memo<GroupActionsProps>(({ id, data, index, contentBlock }
|
|
|
146
165
|
[data, topic],
|
|
147
166
|
);
|
|
148
167
|
|
|
168
|
+
const collapseAction = isCollapsed ? expand : collapse;
|
|
169
|
+
|
|
149
170
|
return (
|
|
150
171
|
<>
|
|
151
172
|
<ActionIconGroup
|
|
@@ -154,6 +175,7 @@ const WithContentId = memo<GroupActionsProps>(({ id, data, index, contentBlock }
|
|
|
154
175
|
items: [
|
|
155
176
|
edit,
|
|
156
177
|
copy,
|
|
178
|
+
collapseAction,
|
|
157
179
|
divider,
|
|
158
180
|
share,
|
|
159
181
|
divider,
|
|
@@ -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, 300)}</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
|
+
});
|
|
@@ -4,6 +4,10 @@ import isEqual from 'fast-deep-equal';
|
|
|
4
4
|
import { memo, useMemo } from 'react';
|
|
5
5
|
import { Flexbox } from 'react-layout-kit';
|
|
6
6
|
|
|
7
|
+
import { CollapsedMessage } from '@/features/Conversation/Messages/Group/CollapsedMessage';
|
|
8
|
+
import { useChatStore } from '@/store/chat';
|
|
9
|
+
import { messageStateSelectors } from '@/store/chat/slices/message/selectors';
|
|
10
|
+
|
|
7
11
|
import { GroupMessageContext } from './GroupContext';
|
|
8
12
|
import GroupItem from './GroupItem';
|
|
9
13
|
|
|
@@ -19,18 +23,28 @@ const useStyles = createStyles(({ css }) => {
|
|
|
19
23
|
|
|
20
24
|
interface GroupChildrenProps {
|
|
21
25
|
blocks: AssistantContentBlock[];
|
|
26
|
+
content?: string;
|
|
22
27
|
contentId?: string;
|
|
23
28
|
disableEditing?: boolean;
|
|
24
29
|
id: string;
|
|
25
30
|
messageIndex: number;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
|
-
const
|
|
29
|
-
({ blocks, contentId, disableEditing, messageIndex, id }) => {
|
|
33
|
+
const Group = memo<GroupChildrenProps>(
|
|
34
|
+
({ blocks, contentId, disableEditing, messageIndex, id, content }) => {
|
|
30
35
|
const { styles } = useStyles();
|
|
31
|
-
|
|
36
|
+
const [isCollapsed] = useChatStore((s) => [messageStateSelectors.isMessageCollapsed(id)(s)]);
|
|
32
37
|
const contextValue = useMemo(() => ({ assistantGroupId: id }), [id]);
|
|
33
38
|
|
|
39
|
+
if (isCollapsed) {
|
|
40
|
+
return (
|
|
41
|
+
content && (
|
|
42
|
+
<Flexbox>
|
|
43
|
+
<CollapsedMessage content={content} id={id} />
|
|
44
|
+
</Flexbox>
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
34
48
|
return (
|
|
35
49
|
<GroupMessageContext value={contextValue}>
|
|
36
50
|
<Flexbox className={styles.container} gap={8}>
|
|
@@ -53,4 +67,4 @@ const GroupChildren = memo<GroupChildrenProps>(
|
|
|
53
67
|
isEqual,
|
|
54
68
|
);
|
|
55
69
|
|
|
56
|
-
export default
|
|
70
|
+
export default Group;
|
|
@@ -10,22 +10,19 @@ import Avatar from '@/features/ChatItem/components/Avatar';
|
|
|
10
10
|
import BorderSpacing from '@/features/ChatItem/components/BorderSpacing';
|
|
11
11
|
import Title from '@/features/ChatItem/components/Title';
|
|
12
12
|
import { useStyles } from '@/features/ChatItem/style';
|
|
13
|
-
import GroupChildren from '@/features/Conversation/Messages/Group/GroupChildren';
|
|
14
13
|
import Usage from '@/features/Conversation/components/Extras/Usage';
|
|
15
14
|
import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
|
|
16
15
|
import { useAgentStore } from '@/store/agent';
|
|
17
16
|
import { agentChatConfigSelectors } from '@/store/agent/selectors';
|
|
18
17
|
import { useChatStore } from '@/store/chat';
|
|
19
|
-
import {
|
|
20
|
-
displayMessageSelectors,
|
|
21
|
-
messageStateSelectors,
|
|
22
|
-
} from '@/store/chat/slices/message/selectors';
|
|
18
|
+
import { displayMessageSelectors, messageStateSelectors } from '@/store/chat/selectors';
|
|
23
19
|
import { useGlobalStore } from '@/store/global';
|
|
24
20
|
import { useSessionStore } from '@/store/session';
|
|
25
21
|
import { sessionSelectors } from '@/store/session/selectors';
|
|
26
22
|
|
|
27
23
|
import { GroupActionsBar } from './Actions';
|
|
28
24
|
import EditState from './EditState';
|
|
25
|
+
import Group from './Group';
|
|
29
26
|
|
|
30
27
|
const MOBILE_AVATAR_SIZE = 32;
|
|
31
28
|
|
|
@@ -111,8 +108,9 @@ const GroupMessage = memo<GroupMessageProps>(({ id, index, disableEditing }) =>
|
|
|
111
108
|
width={'100%'}
|
|
112
109
|
>
|
|
113
110
|
{children && children.length > 0 && (
|
|
114
|
-
<
|
|
111
|
+
<Group
|
|
115
112
|
blocks={children}
|
|
113
|
+
content={lastAssistantMsg?.content}
|
|
116
114
|
contentId={contentId}
|
|
117
115
|
disableEditing={disableEditing}
|
|
118
116
|
id={id}
|