@lobehub/lobehub 2.0.0-next.7 → 2.0.0-next.9
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/desktop-pr-build.yml +8 -8
- package/.github/workflows/docker.yml +17 -16
- package/.github/workflows/e2e.yml +3 -3
- package/.github/workflows/release-desktop-beta.yml +8 -8
- package/.github/workflows/release.yml +1 -1
- package/.github/workflows/test.yml +4 -4
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/locales/ar/models.json +6 -6
- package/locales/bg-BG/models.json +6 -6
- package/locales/de-DE/models.json +6 -6
- package/locales/en-US/models.json +6 -6
- package/locales/es-ES/models.json +6 -6
- package/locales/fa-IR/models.json +6 -6
- package/locales/fr-FR/models.json +6 -6
- package/locales/it-IT/models.json +6 -6
- package/locales/ja-JP/models.json +6 -6
- package/locales/ko-KR/models.json +6 -6
- package/locales/nl-NL/models.json +6 -6
- package/locales/pl-PL/models.json +6 -6
- package/locales/pt-BR/models.json +6 -6
- package/locales/ru-RU/models.json +6 -6
- package/locales/tr-TR/models.json +6 -6
- package/locales/vi-VN/models.json +6 -6
- package/locales/zh-CN/models.json +6 -6
- package/locales/zh-TW/models.json +6 -6
- package/package.json +1 -1
- package/packages/const/src/index.ts +0 -1
- package/packages/const/src/url.ts +1 -4
- package/packages/context-engine/src/index.ts +1 -6
- package/packages/context-engine/src/processors/GroupMessageFlatten.ts +12 -2
- package/packages/context-engine/src/processors/__tests__/GroupMessageFlatten.test.ts +73 -9
- package/packages/context-engine/src/providers/index.ts +0 -2
- package/packages/database/package.json +1 -1
- package/packages/database/src/models/__tests__/message.grouping.test.ts +812 -0
- package/packages/database/src/models/__tests__/message.test.ts +322 -170
- package/packages/database/src/models/message.ts +62 -24
- package/packages/database/src/utils/__tests__/groupMessages.test.ts +145 -2
- package/packages/database/src/utils/groupMessages.ts +7 -5
- package/packages/types/src/message/common/base.ts +13 -0
- package/packages/types/src/message/common/image.ts +8 -0
- package/packages/types/src/message/common/metadata.ts +39 -0
- package/packages/types/src/message/common/tools.ts +10 -0
- package/packages/types/src/message/db/params.ts +47 -1
- package/packages/types/src/message/ui/chat.ts +4 -1
- package/packages/types/src/search.ts +16 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/index.tsx +2 -2
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/useSend.ts +6 -4
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/useSend.ts +15 -10
- package/src/app/[variants]/(main)/chat/@session/features/SessionListContent/List/Item/index.tsx +4 -2
- package/src/components/Thinking/index.tsx +4 -3
- package/src/features/AgentSetting/AgentPlugin/index.tsx +2 -2
- package/src/features/ChatInput/ActionBar/STT/browser.tsx +2 -2
- package/src/features/ChatInput/ActionBar/STT/openai.tsx +2 -2
- package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +1 -3
- package/src/features/Conversation/Error/ErrorJsonViewer.tsx +4 -3
- package/src/features/Conversation/Error/OllamaBizError/index.tsx +7 -2
- package/src/features/Conversation/Error/index.tsx +15 -5
- package/src/features/Conversation/MarkdownElements/LobeArtifact/Render/index.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/Extra/index.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +5 -3
- package/src/features/Conversation/Messages/Assistant/Tool/Inspector/BuiltinPluginTitle.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/Tool/Inspector/ToolTitle.tsx +4 -2
- package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/Tool/index.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/index.tsx +4 -4
- package/src/features/Conversation/Messages/Default.tsx +2 -2
- package/src/features/Conversation/Messages/User/Extra.tsx +2 -2
- package/src/features/Conversation/Messages/User/index.tsx +4 -4
- package/src/features/Conversation/Messages/index.tsx +3 -3
- package/src/features/Conversation/components/AutoScroll.tsx +2 -2
- package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +9 -6
- package/src/features/PluginTag/index.tsx +1 -3
- package/src/features/PluginsUI/Render/BuiltinType/index.test.tsx +37 -28
- package/src/features/Portal/Artifacts/Body/index.tsx +2 -2
- package/src/server/modules/ModelRuntime/trace.ts +11 -4
- package/src/server/routers/lambda/message.ts +14 -3
- package/src/services/chat/chat.test.ts +1 -40
- package/src/services/chat/contextEngineering.test.ts +0 -30
- package/src/services/chat/contextEngineering.ts +1 -12
- package/src/services/chat/index.ts +2 -7
- package/src/services/chat/types.ts +1 -1
- package/src/services/message/_deprecated.ts +1 -1
- package/src/services/message/client.ts +8 -2
- package/src/services/message/server.ts +7 -2
- package/src/services/message/type.ts +6 -1
- package/src/store/chat/helpers.test.ts +99 -0
- package/src/store/chat/helpers.ts +21 -2
- package/src/store/chat/selectors.ts +1 -1
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +3 -3
- package/src/store/chat/slices/builtinTool/actions/index.ts +1 -4
- package/src/store/chat/slices/message/action.test.ts +5 -1
- package/src/store/chat/slices/message/action.ts +102 -14
- package/src/store/chat/slices/message/reducer.test.ts +363 -5
- package/src/store/chat/slices/message/reducer.ts +87 -3
- package/src/store/chat/slices/message/{selectors.test.ts → selectors/chat.test.ts} +266 -30
- package/src/store/chat/slices/message/{selectors.ts → selectors/chat.ts} +29 -79
- package/src/store/chat/slices/message/selectors/index.ts +2 -0
- package/src/store/chat/slices/message/selectors/messageState.test.ts +36 -0
- package/src/store/chat/slices/message/selectors/messageState.ts +80 -0
- package/src/store/chat/slices/plugin/action.test.ts +34 -132
- package/src/store/chat/slices/plugin/action.ts +1 -44
- package/src/store/tool/selectors/tool.test.ts +1 -1
- package/src/store/tool/selectors/tool.ts +6 -8
- package/src/store/tool/slices/builtin/action.test.ts +83 -35
- package/src/store/tool/slices/builtin/action.ts +0 -9
- package/src/store/tool/slices/builtin/selectors.test.ts +4 -30
- package/src/store/tool/slices/builtin/selectors.ts +15 -21
- package/src/tools/index.ts +0 -6
- package/src/tools/renders.ts +0 -3
- package/src/tools/web-browsing/Portal/Search/Footer.tsx +2 -2
- package/packages/const/src/guide.ts +0 -89
- package/packages/context-engine/src/providers/InboxGuide.ts +0 -102
- package/packages/context-engine/src/providers/__tests__/InboxGuideProvider.test.ts +0 -121
- package/src/services/chat/__snapshots__/chat.test.ts.snap +0 -110
- package/src/store/chat/slices/builtinTool/actions/__tests__/dalle.test.ts +0 -121
- package/src/store/chat/slices/builtinTool/actions/dalle.ts +0 -124
- package/src/tools/dalle/Render/GalleyGrid.tsx +0 -60
- package/src/tools/dalle/Render/Item/EditMode.tsx +0 -66
- package/src/tools/dalle/Render/Item/Error.tsx +0 -49
- package/src/tools/dalle/Render/Item/Image.tsx +0 -44
- package/src/tools/dalle/Render/Item/ImageFileItem.tsx +0 -57
- package/src/tools/dalle/Render/Item/index.tsx +0 -88
- package/src/tools/dalle/Render/ToolBar.tsx +0 -56
- package/src/tools/dalle/Render/index.tsx +0 -52
- package/src/tools/dalle/index.ts +0 -92
|
@@ -75,6 +75,29 @@ interface UpdateMessageExtra {
|
|
|
75
75
|
value: any;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
interface UpdateGroupBlockToolResult {
|
|
79
|
+
blockId: string;
|
|
80
|
+
groupMessageId: string;
|
|
81
|
+
toolId: string;
|
|
82
|
+
toolResult: {
|
|
83
|
+
content: string;
|
|
84
|
+
error?: any;
|
|
85
|
+
id: string;
|
|
86
|
+
state?: any;
|
|
87
|
+
};
|
|
88
|
+
type: 'updateGroupBlockToolResult';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface AddGroupBlock {
|
|
92
|
+
blockId: string;
|
|
93
|
+
groupMessageId: string;
|
|
94
|
+
type: 'addGroupBlock';
|
|
95
|
+
value: {
|
|
96
|
+
content: string;
|
|
97
|
+
id: string;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
78
101
|
export type MessageDispatch =
|
|
79
102
|
| CreateMessage
|
|
80
103
|
| UpdateMessage
|
|
@@ -86,7 +109,9 @@ export type MessageDispatch =
|
|
|
86
109
|
| UpdateMessageTools
|
|
87
110
|
| AddMessageTool
|
|
88
111
|
| DeleteMessageTool
|
|
89
|
-
| DeleteMessages
|
|
112
|
+
| DeleteMessages
|
|
113
|
+
| UpdateGroupBlockToolResult
|
|
114
|
+
| AddGroupBlock;
|
|
90
115
|
|
|
91
116
|
export const messagesReducer = (
|
|
92
117
|
state: UIChatMessage[],
|
|
@@ -96,10 +121,27 @@ export const messagesReducer = (
|
|
|
96
121
|
case 'updateMessage': {
|
|
97
122
|
return produce(state, (draftState) => {
|
|
98
123
|
const { id, value } = payload;
|
|
124
|
+
|
|
125
|
+
// First, try to find in top-level messages
|
|
99
126
|
const index = draftState.findIndex((i) => i.id === id);
|
|
100
|
-
if (index
|
|
127
|
+
if (index >= 0) {
|
|
128
|
+
draftState[index] = merge(draftState[index], { ...value, updatedAt: Date.now() });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
101
131
|
|
|
102
|
-
|
|
132
|
+
// If not found, search in group message children (blocks)
|
|
133
|
+
for (const message of draftState) {
|
|
134
|
+
if (message.role === 'group' && message.children) {
|
|
135
|
+
const blockIndex = message.children.findIndex((block) => block.id === id);
|
|
136
|
+
if (blockIndex >= 0) {
|
|
137
|
+
message.children[blockIndex] = merge(message.children[blockIndex], {
|
|
138
|
+
...value,
|
|
139
|
+
});
|
|
140
|
+
message.updatedAt = Date.now();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
103
145
|
});
|
|
104
146
|
}
|
|
105
147
|
|
|
@@ -226,6 +268,48 @@ export const messagesReducer = (
|
|
|
226
268
|
});
|
|
227
269
|
});
|
|
228
270
|
}
|
|
271
|
+
|
|
272
|
+
case 'updateGroupBlockToolResult': {
|
|
273
|
+
return produce(state, (draftState) => {
|
|
274
|
+
const { groupMessageId, blockId, toolId, toolResult } = payload;
|
|
275
|
+
|
|
276
|
+
// Find the group message
|
|
277
|
+
const msg = draftState.find((m) => m.id === groupMessageId);
|
|
278
|
+
if (!msg || msg.role !== 'group' || !msg.children) return;
|
|
279
|
+
|
|
280
|
+
// Find the block within children
|
|
281
|
+
const block = msg.children.find((b) => b.id === blockId);
|
|
282
|
+
if (!block || !block.tools) return;
|
|
283
|
+
|
|
284
|
+
// Find the tool and update its result
|
|
285
|
+
const tool = block.tools.find((t) => t.id === toolId);
|
|
286
|
+
if (!tool) return;
|
|
287
|
+
|
|
288
|
+
// Update tool result (optimistic update)
|
|
289
|
+
tool.result = toolResult;
|
|
290
|
+
|
|
291
|
+
msg.updatedAt = Date.now();
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
case 'addGroupBlock': {
|
|
296
|
+
return produce(state, (draftState) => {
|
|
297
|
+
const { groupMessageId, blockId, value } = payload;
|
|
298
|
+
|
|
299
|
+
// Find the group message
|
|
300
|
+
const msg = draftState.find((m) => m.id === groupMessageId);
|
|
301
|
+
if (!msg || msg.role !== 'group' || !msg.children) return;
|
|
302
|
+
|
|
303
|
+
// Add new block to children
|
|
304
|
+
msg.children.push({
|
|
305
|
+
content: value.content,
|
|
306
|
+
id: blockId,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
msg.updatedAt = Date.now();
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
229
313
|
default: {
|
|
230
314
|
throw new Error('暂未实现的 type,请检查 reducer');
|
|
231
315
|
}
|
|
@@ -12,7 +12,7 @@ import { messageMapKey } from '@/store/chat/utils/messageMapKey';
|
|
|
12
12
|
import { createServerConfigStore } from '@/store/serverConfig/store';
|
|
13
13
|
import { merge } from '@/utils/merge';
|
|
14
14
|
|
|
15
|
-
import { chatSelectors } from './
|
|
15
|
+
import { chatSelectors } from './chat';
|
|
16
16
|
|
|
17
17
|
vi.mock('i18next', () => ({
|
|
18
18
|
t: vi.fn((key) => key), // Simplified mock return value
|
|
@@ -416,35 +416,6 @@ describe('chatSelectors', () => {
|
|
|
416
416
|
});
|
|
417
417
|
});
|
|
418
418
|
|
|
419
|
-
describe('isToolCallStreaming', () => {
|
|
420
|
-
it('should return true when tool call is streaming for given message and index', () => {
|
|
421
|
-
const state: Partial<ChatStore> = {
|
|
422
|
-
toolCallingStreamIds: {
|
|
423
|
-
'msg-1': [true, false, true],
|
|
424
|
-
},
|
|
425
|
-
};
|
|
426
|
-
expect(chatSelectors.isToolCallStreaming('msg-1', 0)(state as ChatStore)).toBe(true);
|
|
427
|
-
expect(chatSelectors.isToolCallStreaming('msg-1', 2)(state as ChatStore)).toBe(true);
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
it('should return false when tool call is not streaming for given message and index', () => {
|
|
431
|
-
const state: Partial<ChatStore> = {
|
|
432
|
-
toolCallingStreamIds: {
|
|
433
|
-
'msg-1': [true, false, true],
|
|
434
|
-
},
|
|
435
|
-
};
|
|
436
|
-
expect(chatSelectors.isToolCallStreaming('msg-1', 1)(state as ChatStore)).toBe(false);
|
|
437
|
-
expect(chatSelectors.isToolCallStreaming('msg-2', 0)(state as ChatStore)).toBe(false);
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
it('should return false when no streaming data exists for the message', () => {
|
|
441
|
-
const state: Partial<ChatStore> = {
|
|
442
|
-
toolCallingStreamIds: {},
|
|
443
|
-
};
|
|
444
|
-
expect(chatSelectors.isToolCallStreaming('msg-1', 0)(state as ChatStore)).toBe(false);
|
|
445
|
-
});
|
|
446
|
-
});
|
|
447
|
-
|
|
448
419
|
describe('activeBaseChats with group chat messages', () => {
|
|
449
420
|
it('should retrieve agent meta for group chat messages with groupId and agentId', () => {
|
|
450
421
|
const groupChatMessages = [
|
|
@@ -470,4 +441,269 @@ describe('chatSelectors', () => {
|
|
|
470
441
|
expect(chats[0].meta).toBeDefined();
|
|
471
442
|
});
|
|
472
443
|
});
|
|
444
|
+
|
|
445
|
+
describe('getGroupLatestMessageWithoutTools', () => {
|
|
446
|
+
it('should return the last child without tools', () => {
|
|
447
|
+
const groupMessage = {
|
|
448
|
+
id: 'group-1',
|
|
449
|
+
role: 'group',
|
|
450
|
+
content: '',
|
|
451
|
+
children: [
|
|
452
|
+
{
|
|
453
|
+
id: 'child-1',
|
|
454
|
+
content: 'First response',
|
|
455
|
+
tools: [
|
|
456
|
+
{
|
|
457
|
+
id: 'tool-1',
|
|
458
|
+
identifier: 'test',
|
|
459
|
+
apiName: 'test',
|
|
460
|
+
arguments: '{}',
|
|
461
|
+
type: 'default',
|
|
462
|
+
},
|
|
463
|
+
],
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
id: 'child-2',
|
|
467
|
+
content: 'Second response',
|
|
468
|
+
tools: [],
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
id: 'child-3',
|
|
472
|
+
content: 'Final response',
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
} as UIChatMessage;
|
|
476
|
+
|
|
477
|
+
const state: Partial<ChatStore> = {
|
|
478
|
+
activeId: 'test-id',
|
|
479
|
+
messagesMap: {
|
|
480
|
+
[messageMapKey('test-id')]: [groupMessage],
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
const result = chatSelectors.getGroupLatestMessageWithoutTools('group-1')(state as ChatStore);
|
|
485
|
+
expect(result).toBeDefined();
|
|
486
|
+
expect(result?.id).toBe('child-3');
|
|
487
|
+
expect(result?.content).toBe('Final response');
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('should return null if the last child has tools', () => {
|
|
491
|
+
const groupMessage = {
|
|
492
|
+
id: 'group-2',
|
|
493
|
+
role: 'group',
|
|
494
|
+
content: '',
|
|
495
|
+
children: [
|
|
496
|
+
{
|
|
497
|
+
id: 'child-1',
|
|
498
|
+
content: 'First response',
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
id: 'child-2',
|
|
502
|
+
content: 'Second response with tools',
|
|
503
|
+
tools: [
|
|
504
|
+
{
|
|
505
|
+
id: 'tool-1',
|
|
506
|
+
identifier: 'test',
|
|
507
|
+
apiName: 'test',
|
|
508
|
+
arguments: '{}',
|
|
509
|
+
type: 'default',
|
|
510
|
+
},
|
|
511
|
+
],
|
|
512
|
+
},
|
|
513
|
+
],
|
|
514
|
+
} as UIChatMessage;
|
|
515
|
+
|
|
516
|
+
const state: Partial<ChatStore> = {
|
|
517
|
+
activeId: 'test-id',
|
|
518
|
+
messagesMap: {
|
|
519
|
+
[messageMapKey('test-id')]: [groupMessage],
|
|
520
|
+
},
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
const result = chatSelectors.getGroupLatestMessageWithoutTools('group-2')(state as ChatStore);
|
|
524
|
+
expect(result).toBeUndefined();
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it('should return the last child when it has empty tools array', () => {
|
|
528
|
+
const groupMessage = {
|
|
529
|
+
id: 'group-3',
|
|
530
|
+
role: 'group',
|
|
531
|
+
content: '',
|
|
532
|
+
children: [
|
|
533
|
+
{
|
|
534
|
+
id: 'child-1',
|
|
535
|
+
content: 'First response with tools',
|
|
536
|
+
tools: [
|
|
537
|
+
{
|
|
538
|
+
id: 'tool-1',
|
|
539
|
+
identifier: 'test',
|
|
540
|
+
apiName: 'test',
|
|
541
|
+
arguments: '{}',
|
|
542
|
+
type: 'default',
|
|
543
|
+
},
|
|
544
|
+
],
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
id: 'child-2',
|
|
548
|
+
content: 'Final response',
|
|
549
|
+
tools: [],
|
|
550
|
+
},
|
|
551
|
+
],
|
|
552
|
+
} as UIChatMessage;
|
|
553
|
+
|
|
554
|
+
const state: Partial<ChatStore> = {
|
|
555
|
+
activeId: 'test-id',
|
|
556
|
+
messagesMap: {
|
|
557
|
+
[messageMapKey('test-id')]: [groupMessage],
|
|
558
|
+
},
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
const result = chatSelectors.getGroupLatestMessageWithoutTools('group-3')(state as ChatStore);
|
|
562
|
+
expect(result).toBeDefined();
|
|
563
|
+
expect(result?.id).toBe('child-2');
|
|
564
|
+
expect(result?.content).toBe('Final response');
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it('should return null for non-group messages', () => {
|
|
568
|
+
const assistantMessage = {
|
|
569
|
+
id: 'msg-1',
|
|
570
|
+
role: 'assistant',
|
|
571
|
+
content: 'Regular message',
|
|
572
|
+
} as UIChatMessage;
|
|
573
|
+
|
|
574
|
+
const state: Partial<ChatStore> = {
|
|
575
|
+
activeId: 'test-id',
|
|
576
|
+
messagesMap: {
|
|
577
|
+
[messageMapKey('test-id')]: [assistantMessage],
|
|
578
|
+
},
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
const result = chatSelectors.getGroupLatestMessageWithoutTools('msg-1')(state as ChatStore);
|
|
582
|
+
expect(result).toBeUndefined();
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
it('should return null for group messages without children', () => {
|
|
586
|
+
const groupMessage = {
|
|
587
|
+
id: 'group-4',
|
|
588
|
+
role: 'group',
|
|
589
|
+
content: '',
|
|
590
|
+
children: undefined,
|
|
591
|
+
} as UIChatMessage;
|
|
592
|
+
|
|
593
|
+
const state: Partial<ChatStore> = {
|
|
594
|
+
activeId: 'test-id',
|
|
595
|
+
messagesMap: {
|
|
596
|
+
[messageMapKey('test-id')]: [groupMessage],
|
|
597
|
+
},
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
const result = chatSelectors.getGroupLatestMessageWithoutTools('group-4')(state as ChatStore);
|
|
601
|
+
expect(result).toBeUndefined();
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it('should return null for group messages with empty children array', () => {
|
|
605
|
+
const groupMessage = {
|
|
606
|
+
id: 'group-5',
|
|
607
|
+
role: 'group',
|
|
608
|
+
content: '',
|
|
609
|
+
children: [],
|
|
610
|
+
} as unknown as UIChatMessage;
|
|
611
|
+
|
|
612
|
+
const state: Partial<ChatStore> = {
|
|
613
|
+
activeId: 'test-id',
|
|
614
|
+
messagesMap: {
|
|
615
|
+
[messageMapKey('test-id')]: [groupMessage],
|
|
616
|
+
},
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
const result = chatSelectors.getGroupLatestMessageWithoutTools('group-5')(state as ChatStore);
|
|
620
|
+
expect(result).toBeUndefined();
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
it('should return null if all children have tools', () => {
|
|
624
|
+
const groupMessage = {
|
|
625
|
+
id: 'group-6',
|
|
626
|
+
role: 'group',
|
|
627
|
+
content: '',
|
|
628
|
+
children: [
|
|
629
|
+
{
|
|
630
|
+
id: 'child-1',
|
|
631
|
+
content: 'First response',
|
|
632
|
+
tools: [
|
|
633
|
+
{
|
|
634
|
+
id: 'tool-1',
|
|
635
|
+
identifier: 'test',
|
|
636
|
+
apiName: 'test',
|
|
637
|
+
arguments: '{}',
|
|
638
|
+
type: 'default',
|
|
639
|
+
},
|
|
640
|
+
],
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
id: 'child-2',
|
|
644
|
+
content: 'Second response',
|
|
645
|
+
tools: [
|
|
646
|
+
{
|
|
647
|
+
id: 'tool-2',
|
|
648
|
+
identifier: 'test2',
|
|
649
|
+
apiName: 'test2',
|
|
650
|
+
arguments: '{}',
|
|
651
|
+
type: 'default',
|
|
652
|
+
},
|
|
653
|
+
],
|
|
654
|
+
},
|
|
655
|
+
],
|
|
656
|
+
} as unknown as UIChatMessage;
|
|
657
|
+
|
|
658
|
+
const state: Partial<ChatStore> = {
|
|
659
|
+
activeId: 'test-id',
|
|
660
|
+
messagesMap: {
|
|
661
|
+
[messageMapKey('test-id')]: [groupMessage],
|
|
662
|
+
},
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
const result = chatSelectors.getGroupLatestMessageWithoutTools('group-6')(state as ChatStore);
|
|
666
|
+
expect(result).toBeUndefined();
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
it('should handle empty tools array as no tools', () => {
|
|
670
|
+
const groupMessage = {
|
|
671
|
+
id: 'group-7',
|
|
672
|
+
role: 'group',
|
|
673
|
+
content: '',
|
|
674
|
+
children: [
|
|
675
|
+
{
|
|
676
|
+
id: 'child-1',
|
|
677
|
+
content: 'Response with empty tools',
|
|
678
|
+
tools: [],
|
|
679
|
+
},
|
|
680
|
+
],
|
|
681
|
+
} as unknown as UIChatMessage;
|
|
682
|
+
|
|
683
|
+
const state: Partial<ChatStore> = {
|
|
684
|
+
activeId: 'test-id',
|
|
685
|
+
messagesMap: {
|
|
686
|
+
[messageMapKey('test-id')]: [groupMessage],
|
|
687
|
+
},
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
const result = chatSelectors.getGroupLatestMessageWithoutTools('group-7')(state as ChatStore);
|
|
691
|
+
expect(result).toBeDefined();
|
|
692
|
+
expect(result?.id).toBe('child-1');
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
it('should return null when message is not found', () => {
|
|
696
|
+
const state: Partial<ChatStore> = {
|
|
697
|
+
activeId: 'test-id',
|
|
698
|
+
messagesMap: {
|
|
699
|
+
[messageMapKey('test-id')]: [],
|
|
700
|
+
},
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
const result = chatSelectors.getGroupLatestMessageWithoutTools('non-existent')(
|
|
704
|
+
state as ChatStore,
|
|
705
|
+
);
|
|
706
|
+
expect(result).toBeUndefined();
|
|
707
|
+
});
|
|
708
|
+
});
|
|
473
709
|
});
|
|
@@ -10,8 +10,8 @@ import { sessionMetaSelectors } from '@/store/session/selectors';
|
|
|
10
10
|
import { useUserStore } from '@/store/user';
|
|
11
11
|
import { userProfileSelectors } from '@/store/user/selectors';
|
|
12
12
|
|
|
13
|
-
import { chatHelpers } from '
|
|
14
|
-
import type { ChatStoreState } from '
|
|
13
|
+
import { chatHelpers } from '../../../helpers';
|
|
14
|
+
import type { ChatStoreState } from '../../../initialState';
|
|
15
15
|
|
|
16
16
|
const getMeta = (message: UIChatMessage) => {
|
|
17
17
|
switch (message.role) {
|
|
@@ -47,7 +47,7 @@ const getBaseChatsByKey =
|
|
|
47
47
|
return messages.map((i) => ({ ...i, meta: getMeta(i) }));
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
const currentChatKey = (s: ChatStoreState) => messageMapKey(s.activeId, s.activeTopicId);
|
|
50
|
+
export const currentChatKey = (s: ChatStoreState) => messageMapKey(s.activeId, s.activeTopicId);
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* Current active raw message list, include thread messages
|
|
@@ -89,7 +89,7 @@ const mainDisplayChats = (s: ChatStoreState): UIChatMessage[] => {
|
|
|
89
89
|
return getChatsWithThread(s, displayChats);
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
-
const mainDisplayChatIDs = (s: ChatStoreState) => mainDisplayChats(s).map((s) => s.id);
|
|
92
|
+
export const mainDisplayChatIDs = (s: ChatStoreState) => mainDisplayChats(s).map((s) => s.id);
|
|
93
93
|
|
|
94
94
|
const mainAIChats = (s: ChatStoreState): UIChatMessage[] => {
|
|
95
95
|
const messages = activeBaseChats(s);
|
|
@@ -154,7 +154,7 @@ const countMessagesByThreadId = (id: string) => (s: ChatStoreState) => {
|
|
|
154
154
|
return messages.length;
|
|
155
155
|
};
|
|
156
156
|
|
|
157
|
-
const getMessageByToolCallId = (id: string) => (s: ChatStoreState) => {
|
|
157
|
+
export const getMessageByToolCallId = (id: string) => (s: ChatStoreState) => {
|
|
158
158
|
const messages = activeBaseChats(s);
|
|
159
159
|
return messages.find((m) => m.tool_call_id === id);
|
|
160
160
|
};
|
|
@@ -166,67 +166,6 @@ const currentChatLoadingState = (s: ChatStoreState) => !s.messagesInit;
|
|
|
166
166
|
|
|
167
167
|
const isCurrentChatLoaded = (s: ChatStoreState) => !!s.messagesMap[currentChatKey(s)];
|
|
168
168
|
|
|
169
|
-
const isMessageEditing = (id: string) => (s: ChatStoreState) => s.messageEditingIds.includes(id);
|
|
170
|
-
const isMessageLoading = (id: string) => (s: ChatStoreState) => s.messageLoadingIds.includes(id);
|
|
171
|
-
|
|
172
|
-
const isMessageGenerating = (id: string) => (s: ChatStoreState) => s.chatLoadingIds.includes(id);
|
|
173
|
-
const isMessageInRAGFlow = (id: string) => (s: ChatStoreState) =>
|
|
174
|
-
s.messageRAGLoadingIds.includes(id);
|
|
175
|
-
const isMessageInChatReasoning = (id: string) => (s: ChatStoreState) =>
|
|
176
|
-
s.reasoningLoadingIds.includes(id);
|
|
177
|
-
|
|
178
|
-
const isPluginApiInvoking = (id: string) => (s: ChatStoreState) =>
|
|
179
|
-
s.pluginApiLoadingIds.includes(id);
|
|
180
|
-
|
|
181
|
-
const isToolCallStreaming = (id: string, index: number) => (s: ChatStoreState) => {
|
|
182
|
-
const isLoading = s.toolCallingStreamIds[id];
|
|
183
|
-
|
|
184
|
-
if (!isLoading) return false;
|
|
185
|
-
|
|
186
|
-
return isLoading[index];
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const isInToolsCalling = (id: string, index: number) => (s: ChatStoreState) => {
|
|
190
|
-
const isStreamingToolsCalling = isToolCallStreaming(id, index)(s);
|
|
191
|
-
|
|
192
|
-
const isInvokingPluginApi = s.messageInToolsCallingIds.includes(id);
|
|
193
|
-
|
|
194
|
-
return isStreamingToolsCalling || isInvokingPluginApi;
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
const isToolApiNameShining =
|
|
198
|
-
(messageId: string, index: number, toolCallId: string) => (s: ChatStoreState) => {
|
|
199
|
-
const toolMessageId = getMessageByToolCallId(toolCallId)(s)?.id;
|
|
200
|
-
const isStreaming = isToolCallStreaming(messageId, index)(s);
|
|
201
|
-
const isPluginInvoking = !toolMessageId ? true : isPluginApiInvoking(toolMessageId)(s);
|
|
202
|
-
|
|
203
|
-
return isStreaming || isPluginInvoking;
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
const isAIGenerating = (s: ChatStoreState) =>
|
|
207
|
-
s.chatLoadingIds.some((id) => mainDisplayChatIDs(s).includes(id));
|
|
208
|
-
|
|
209
|
-
const isInRAGFlow = (s: ChatStoreState) =>
|
|
210
|
-
s.messageRAGLoadingIds.some((id) => mainDisplayChatIDs(s).includes(id));
|
|
211
|
-
|
|
212
|
-
const isCreatingMessage = (s: ChatStoreState) => s.isCreatingMessage;
|
|
213
|
-
|
|
214
|
-
const isHasMessageLoading = (s: ChatStoreState) =>
|
|
215
|
-
s.messageLoadingIds.some((id) => mainDisplayChatIDs(s).includes(id));
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* this function is used to determine whether the send button should be disabled
|
|
219
|
-
*/
|
|
220
|
-
const isSendButtonDisabledByMessage = (s: ChatStoreState) =>
|
|
221
|
-
// 1. when there is message loading
|
|
222
|
-
isHasMessageLoading(s) ||
|
|
223
|
-
// 2. when is creating the topic
|
|
224
|
-
s.creatingTopic ||
|
|
225
|
-
// 3. when is creating the message
|
|
226
|
-
isCreatingMessage(s) ||
|
|
227
|
-
// 4. when the message is in RAG flow
|
|
228
|
-
isInRAGFlow(s);
|
|
229
|
-
|
|
230
169
|
const inboxActiveTopicMessages = (state: ChatStoreState) => {
|
|
231
170
|
const activeTopicId = state.activeTopicId;
|
|
232
171
|
return state.messagesMap[messageMapKey(INBOX_SESSION_ID, activeTopicId)] || [];
|
|
@@ -280,6 +219,29 @@ const getSupervisorTodos = (groupId?: string, topicId?: string | null) => (s: Ch
|
|
|
280
219
|
return s.supervisorTodos[messageMapKey(groupId, topicId)] || [];
|
|
281
220
|
};
|
|
282
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Gets the latest message block from a group message that doesn't contain tools
|
|
224
|
+
* Returns null if the last block contains tools or if message is not a group message
|
|
225
|
+
*/
|
|
226
|
+
const getGroupLatestMessageWithoutTools = (id: string) => (s: ChatStoreState) => {
|
|
227
|
+
const message = getMessageById(id)(s);
|
|
228
|
+
|
|
229
|
+
if (!message || message.role !== 'group' || !message.children || message.children.length === 0)
|
|
230
|
+
return;
|
|
231
|
+
|
|
232
|
+
// Get the last child
|
|
233
|
+
const lastChild = message.children.at(-1);
|
|
234
|
+
|
|
235
|
+
if (!lastChild) return;
|
|
236
|
+
|
|
237
|
+
// Return the last child only if it doesn't have tools
|
|
238
|
+
if (!lastChild.tools || lastChild.tools.length === 0) {
|
|
239
|
+
return lastChild;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return;
|
|
243
|
+
};
|
|
244
|
+
|
|
283
245
|
export const chatSelectors = {
|
|
284
246
|
activeBaseChats,
|
|
285
247
|
activeBaseChatsWithoutTool,
|
|
@@ -289,6 +251,7 @@ export const chatSelectors = {
|
|
|
289
251
|
currentToolMessages,
|
|
290
252
|
currentUserFiles,
|
|
291
253
|
getBaseChatsByKey,
|
|
254
|
+
getGroupLatestMessageWithoutTools,
|
|
292
255
|
getMessageById,
|
|
293
256
|
getMessageByToolCallId,
|
|
294
257
|
getSupervisorTodos,
|
|
@@ -296,21 +259,8 @@ export const chatSelectors = {
|
|
|
296
259
|
getThreadMessages,
|
|
297
260
|
getTraceIdByMessageId,
|
|
298
261
|
inboxActiveTopicMessages,
|
|
299
|
-
isAIGenerating,
|
|
300
|
-
isCreatingMessage,
|
|
301
262
|
isCurrentChatLoaded,
|
|
302
|
-
isHasMessageLoading,
|
|
303
|
-
isInToolsCalling,
|
|
304
|
-
isMessageEditing,
|
|
305
|
-
isMessageGenerating,
|
|
306
|
-
isMessageInChatReasoning,
|
|
307
|
-
isMessageInRAGFlow,
|
|
308
|
-
isMessageLoading,
|
|
309
|
-
isPluginApiInvoking,
|
|
310
|
-
isSendButtonDisabledByMessage,
|
|
311
263
|
isSupervisorLoading,
|
|
312
|
-
isToolApiNameShining,
|
|
313
|
-
isToolCallStreaming,
|
|
314
264
|
latestMessage,
|
|
315
265
|
mainAIChats,
|
|
316
266
|
mainAIChatsMessageString,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { ChatStore } from '@/store/chat';
|
|
4
|
+
|
|
5
|
+
import { messageStateSelectors } from './messageState';
|
|
6
|
+
|
|
7
|
+
describe('messageStateSelectors', () => {
|
|
8
|
+
describe('isToolCallStreaming', () => {
|
|
9
|
+
it('should return true when tool call is streaming for given message and index', () => {
|
|
10
|
+
const state: Partial<ChatStore> = {
|
|
11
|
+
toolCallingStreamIds: {
|
|
12
|
+
'msg-1': [true, false, true],
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
expect(messageStateSelectors.isToolCallStreaming('msg-1', 0)(state as ChatStore)).toBe(true);
|
|
16
|
+
expect(messageStateSelectors.isToolCallStreaming('msg-1', 2)(state as ChatStore)).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should return false when tool call is not streaming for given message and index', () => {
|
|
20
|
+
const state: Partial<ChatStore> = {
|
|
21
|
+
toolCallingStreamIds: {
|
|
22
|
+
'msg-1': [true, false, true],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
expect(messageStateSelectors.isToolCallStreaming('msg-1', 1)(state as ChatStore)).toBe(false);
|
|
26
|
+
expect(messageStateSelectors.isToolCallStreaming('msg-2', 0)(state as ChatStore)).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should return false when no streaming data exists for the message', () => {
|
|
30
|
+
const state: Partial<ChatStore> = {
|
|
31
|
+
toolCallingStreamIds: {},
|
|
32
|
+
};
|
|
33
|
+
expect(messageStateSelectors.isToolCallStreaming('msg-1', 0)(state as ChatStore)).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
});
|