@lobehub/chat 1.2.6 → 1.2.7

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 (32) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/package.json +1 -1
  3. package/src/app/(main)/chat/(workspace)/@portal/features/ArtifactUI/Footer.tsx +1 -1
  4. package/src/app/(main)/chat/(workspace)/@portal/features/Header.tsx +1 -1
  5. package/src/app/(main)/chat/(workspace)/_layout/Mobile/PortalModal.tsx +35 -0
  6. package/src/app/(main)/chat/(workspace)/_layout/Mobile/index.tsx +3 -1
  7. package/src/database/client/models/__tests__/message.test.ts +13 -0
  8. package/src/database/client/models/message.ts +4 -0
  9. package/src/database/server/models/__tests__/message.test.ts +103 -0
  10. package/src/database/server/models/message.ts +13 -6
  11. package/src/features/Conversation/Actions/Tool.tsx +12 -6
  12. package/src/features/Conversation/Messages/Tool/Inspector/index.tsx +25 -19
  13. package/src/features/Conversation/Messages/Tool/Inspector/style.ts +9 -0
  14. package/src/server/routers/lambda/message.ts +7 -1
  15. package/src/services/message/client.test.ts +16 -2
  16. package/src/services/message/client.ts +5 -1
  17. package/src/services/message/server.ts +7 -2
  18. package/src/services/message/type.ts +2 -1
  19. package/src/store/chat/slices/message/action.test.ts +144 -0
  20. package/src/store/chat/slices/message/action.ts +111 -64
  21. package/src/store/chat/slices/message/reducer.test.ts +200 -1
  22. package/src/store/chat/slices/message/reducer.ts +62 -2
  23. package/src/store/chat/slices/plugin/action.test.ts +42 -0
  24. package/src/store/chat/slices/plugin/action.ts +46 -0
  25. package/src/store/chat/slices/portal/action.test.ts +6 -6
  26. package/src/store/chat/slices/portal/action.ts +3 -3
  27. package/src/store/chat/slices/topic/action.test.ts +3 -2
  28. package/src/store/chat/slices/topic/action.ts +1 -1
  29. package/src/store/global/action.test.ts +13 -0
  30. package/src/store/global/action.ts +7 -0
  31. package/src/store/global/initialState.ts +1 -0
  32. package/src/store/global/selectors.ts +2 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.2.7](https://github.com/lobehub/lobe-chat/compare/v1.2.6...v1.2.7)
6
+
7
+ <sup>Released on **2024-07-03**</sup>
8
+
9
+ #### 💄 Styles
10
+
11
+ - **misc**: Improve delete assistant message with tools.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Styles
19
+
20
+ - **misc**: Improve delete assistant message with tools, closes [#3127](https://github.com/lobehub/lobe-chat/issues/3127) ([1230777](https://github.com/lobehub/lobe-chat/commit/1230777))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ### [Version 1.2.6](https://github.com/lobehub/lobe-chat/compare/v1.2.5...v1.2.6)
6
31
 
7
32
  <sup>Released on **2024-07-03**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot 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",
@@ -35,7 +35,7 @@ const Footer = () => {
35
35
 
36
36
  summaryPluginContent(messageId);
37
37
  }}
38
- title={'actions.summaryTooltip'}
38
+ title={t('actions.summaryTooltip')}
39
39
  />
40
40
  </Flexbox>
41
41
  );
@@ -18,7 +18,7 @@ import { toolSelectors } from '@/store/tool/selectors';
18
18
  const Header = memo(() => {
19
19
  const [showToolUI, toggleInspector, closeToolUI, toolUIIdentifier = ''] = useChatStore((s) => [
20
20
  chatPortalSelectors.showArtifactUI(s),
21
- s.toggleDock,
21
+ s.togglePortal,
22
22
  s.closeToolUI,
23
23
  chatPortalSelectors.toolUIIdentifier(s),
24
24
  ]);
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+
3
+ import { Modal } from '@lobehub/ui';
4
+ import { PropsWithChildren, memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import { useGlobalStore } from '@/store/global';
8
+ import { systemStatusSelectors } from '@/store/global/selectors';
9
+
10
+ import { useWorkspaceModal } from '../../features/useWorkspaceModal';
11
+
12
+ const PortalModal = memo(({ children }: PropsWithChildren) => {
13
+ const [showAgentSettings, toggleConfig] = useGlobalStore((s) => [
14
+ systemStatusSelectors.mobileShowPortal(s),
15
+ s.toggleMobilePortal,
16
+ ]);
17
+ const [open, setOpen] = useWorkspaceModal(showAgentSettings, toggleConfig);
18
+ const { t } = useTranslation('portal');
19
+
20
+ return (
21
+ <Modal
22
+ height={'95%'}
23
+ onCancel={() => setOpen(false)}
24
+ open={open}
25
+ styles={{
26
+ body: { padding: 0 },
27
+ }}
28
+ title={t('title')}
29
+ >
30
+ {children}
31
+ </Modal>
32
+ );
33
+ });
34
+
35
+ export default PortalModal;
@@ -2,9 +2,10 @@ import MobileContentLayout from '@/components/server/MobileNavLayout';
2
2
 
3
3
  import { LayoutProps } from '../type';
4
4
  import ChatHeader from './ChatHeader';
5
+ import PortalModal from './PortalModal';
5
6
  import TopicModal from './TopicModal';
6
7
 
7
- const Layout = ({ children, topic, conversation }: LayoutProps) => {
8
+ const Layout = ({ children, topic, conversation, portal }: LayoutProps) => {
8
9
  return (
9
10
  <>
10
11
  <MobileContentLayout header={<ChatHeader />} style={{ overflowY: 'hidden' }}>
@@ -12,6 +13,7 @@ const Layout = ({ children, topic, conversation }: LayoutProps) => {
12
13
  {children}
13
14
  </MobileContentLayout>
14
15
  <TopicModal>{topic}</TopicModal>
16
+ <PortalModal>{portal}</PortalModal>
15
17
  </>
16
18
  );
17
19
  };
@@ -250,6 +250,19 @@ describe('MessageModel', () => {
250
250
  });
251
251
  });
252
252
 
253
+ describe('bulkDelete', () => {
254
+ it('should delete many messages', async () => {
255
+ const createdMessage = await MessageModel.create(messageData);
256
+ const createdMessage2 = await MessageModel.create(messageData);
257
+ await MessageModel.bulkDelete([createdMessage.id, createdMessage2.id]);
258
+
259
+ const messageInDb1 = await MessageModel.findById(createdMessage.id);
260
+ const messageInDb2 = await MessageModel.findById(createdMessage2.id);
261
+ expect(messageInDb1).toBeUndefined();
262
+ expect(messageInDb2).toBeUndefined();
263
+ });
264
+ });
265
+
253
266
  describe('update', () => {
254
267
  it('should update a message', async () => {
255
268
  const createdMessage = await MessageModel.create(messageData);
@@ -129,6 +129,10 @@ class _MessageModel extends BaseModel {
129
129
  return super._deleteWithSync(id);
130
130
  }
131
131
 
132
+ async bulkDelete(ids: string[]) {
133
+ return super._bulkDeleteWithSync(ids);
134
+ }
135
+
132
136
  async clearTable() {
133
137
  return this._clearWithSync();
134
138
  }
@@ -491,6 +491,75 @@ describe('MessageModel', () => {
491
491
  expect(pluginResult).toHaveLength(1);
492
492
  expect(pluginResult[0].identifier).toBe('plugin1');
493
493
  });
494
+
495
+ it('should create tool message ', async () => {
496
+ // 调用 create 方法
497
+ const state = {
498
+ query: 'Composio',
499
+ answers: [],
500
+ results: [
501
+ {
502
+ url: 'https://www.composio.dev/',
503
+ score: 16,
504
+ title: 'Composio - Connect 90+ tools to your AI agents',
505
+ engine: 'bing',
506
+ content:
507
+ 'Faster DevelopmentHigher ReliabilityBetter Integrations. Get Started Now. Our platform lets you ditch the specs and seamlessly integrate any tool you need in less than 5 mins.',
508
+ engines: ['bing', 'qwant', 'brave', 'duckduckgo'],
509
+ category: 'general',
510
+ template: 'default.html',
511
+ positions: [1, 1, 1, 1],
512
+ thumbnail: '',
513
+ parsed_url: ['https', 'www.composio.dev', '/', '', '', ''],
514
+ publishedDate: null,
515
+ },
516
+ {
517
+ url: 'https://www.composio.co/',
518
+ score: 10.75,
519
+ title: 'Composio',
520
+ engine: 'bing',
521
+ content:
522
+ 'Composio was created to help streamline the entire book creation process! Writing. Take time out to write / Make a schedule to write consistently. We have writing software that optimizes your books for printing or ebook format. Figure out what you want to write. Collaborate and write with others. Professional editing is a necessity.',
523
+ engines: ['qwant', 'duckduckgo', 'google', 'bing', 'brave'],
524
+ category: 'general',
525
+ template: 'default.html',
526
+ positions: [5, 2, 1, 5, 4],
527
+ thumbnail: null,
528
+ parsed_url: ['https', 'www.composio.co', '/', '', '', ''],
529
+ publishedDate: null,
530
+ },
531
+ ],
532
+ unresponsive_engines: [],
533
+ };
534
+ const result = await messageModel.create({
535
+ content: '[{}]',
536
+ plugin: {
537
+ apiName: 'searchWithSearXNG',
538
+ arguments: '{\n "query": "Composio"\n}',
539
+ identifier: 'lobe-web-browsing',
540
+ type: 'builtin',
541
+ },
542
+ pluginState: state,
543
+ role: 'tool',
544
+ tool_call_id: 'tool_call_ymxXC2J0',
545
+ sessionId: '1',
546
+ });
547
+
548
+ // 断言结果
549
+ expect(result.id).toBeDefined();
550
+ expect(result.content).toBe('[{}]');
551
+ expect(result.role).toBe('tool');
552
+ expect(result.sessionId).toBe('1');
553
+
554
+ const pluginResult = await serverDB
555
+ .select()
556
+ .from(messagePlugins)
557
+ .where(eq(messagePlugins.id, result.id))
558
+ .execute();
559
+ expect(pluginResult).toHaveLength(1);
560
+ expect(pluginResult[0].identifier).toBe('lobe-web-browsing');
561
+ expect(pluginResult[0].state!).toMatchObject(state);
562
+ });
494
563
  });
495
564
 
496
565
  describe('batchCreateMessages', () => {
@@ -645,6 +714,40 @@ describe('MessageModel', () => {
645
714
  });
646
715
  });
647
716
 
717
+ describe('deleteMessages', () => {
718
+ it('should delete 2 messages', async () => {
719
+ // 创建测试数据
720
+ await serverDB.insert(messages).values([
721
+ { id: '1', userId, role: 'user', content: 'message 1' },
722
+ { id: '2', userId, role: 'user', content: 'message 2' },
723
+ ]);
724
+
725
+ // 调用 deleteMessage 方法
726
+ await messageModel.deleteMessages(['1', '2']);
727
+
728
+ // 断言结果
729
+ const result = await serverDB.select().from(messages).where(eq(messages.id, '1')).execute();
730
+ expect(result).toHaveLength(0);
731
+ const result2 = await serverDB.select().from(messages).where(eq(messages.id, '2')).execute();
732
+ expect(result2).toHaveLength(0);
733
+ });
734
+
735
+ it('should only delete messages belonging to the user', async () => {
736
+ // 创建测试数据
737
+ await serverDB.insert(messages).values([
738
+ { id: '1', userId: '456', role: 'user', content: 'message 1' },
739
+ { id: '2', userId: '456', role: 'user', content: 'message 1' },
740
+ ]);
741
+
742
+ // 调用 deleteMessage 方法
743
+ await messageModel.deleteMessages(['1', '2']);
744
+
745
+ // 断言结果
746
+ const result = await serverDB.select().from(messages).where(eq(messages.id, '1')).execute();
747
+ expect(result).toHaveLength(1);
748
+ });
749
+ });
750
+
648
751
  describe('deleteAllMessages', () => {
649
752
  it('should delete all messages belonging to the user', async () => {
650
753
  // 创建测试数据
@@ -206,7 +206,7 @@ export class MessageModel {
206
206
  // **************** Create *************** //
207
207
 
208
208
  async create(
209
- { fromModel, fromProvider, files, ...message }: CreateMessageParams,
209
+ { fromModel, fromProvider, files, plugin, pluginState, ...message }: CreateMessageParams,
210
210
  id: string = this.genId(),
211
211
  ): Promise<MessageItem> {
212
212
  return serverDB.transaction(async (trx) => {
@@ -224,12 +224,13 @@ export class MessageModel {
224
224
  // Insert the plugin data if the message is a tool
225
225
  if (message.role === 'tool') {
226
226
  await trx.insert(messagePlugins).values({
227
- apiName: message.plugin?.apiName,
228
- arguments: message.plugin?.arguments,
227
+ apiName: plugin?.apiName,
228
+ arguments: plugin?.arguments,
229
229
  id,
230
- identifier: message.plugin?.identifier,
230
+ identifier: plugin?.identifier,
231
+ state: pluginState,
231
232
  toolCallId: message.tool_call_id,
232
- type: message.plugin?.type,
233
+ type: plugin?.type,
233
234
  });
234
235
  }
235
236
 
@@ -352,6 +353,12 @@ export class MessageModel {
352
353
  });
353
354
  }
354
355
 
356
+ async deleteMessages(ids: string[]) {
357
+ return serverDB
358
+ .delete(messages)
359
+ .where(and(eq(messages.userId, this.userId), inArray(messages.id, ids)));
360
+ }
361
+
355
362
  async deleteMessageTranslate(id: string) {
356
363
  return serverDB.delete(messageTranslates).where(and(eq(messageTranslates.id, id)));
357
364
  }
@@ -360,7 +367,7 @@ export class MessageModel {
360
367
  return serverDB.delete(messageTTS).where(and(eq(messageTTS.id, id)));
361
368
  }
362
369
 
363
- async deleteMessages(sessionId?: string | null, topicId?: string | null) {
370
+ async deleteMessagesBySession(sessionId?: string | null, topicId?: string | null) {
364
371
  return serverDB
365
372
  .delete(messages)
366
373
  .where(
@@ -7,19 +7,25 @@ import { useChatListActionsBar } from '../hooks/useChatListActionsBar';
7
7
  import { RenderAction } from '../types';
8
8
 
9
9
  export const ToolActionsBar: RenderAction = memo(({ id }) => {
10
- const { regenerate } = useChatListActionsBar();
11
- const [reInvokeToolMessage] = useChatStore((s) => [s.reInvokeToolMessage]);
10
+ const { regenerate, del } = useChatListActionsBar();
11
+ const [reInvokeToolMessage, deleteToolMessage] = useChatStore((s) => [
12
+ s.reInvokeToolMessage,
13
+ s.deleteToolMessage,
14
+ ]);
12
15
 
13
16
  return (
14
17
  <ActionIconGroup
15
- // dropdownMenu={[regenerate]}
16
- items={[regenerate]}
17
- onActionClick={(event) => {
18
+ items={[regenerate, del]}
19
+ onActionClick={async (event) => {
18
20
  switch (event.key) {
19
21
  case 'regenerate': {
20
- reInvokeToolMessage(id);
22
+ await reInvokeToolMessage(id);
21
23
  break;
22
24
  }
25
+
26
+ case 'del': {
27
+ await deleteToolMessage(id);
28
+ }
23
29
  }
24
30
  }}
25
31
  type="ghost"
@@ -1,6 +1,6 @@
1
1
  import { Loading3QuartersOutlined } from '@ant-design/icons';
2
- import { ActionIcon, Avatar, Highlighter, Icon, Tag } from '@lobehub/ui';
3
- import { Tabs } from 'antd';
2
+ import { ActionIcon, Highlighter, Icon, Tag } from '@lobehub/ui';
3
+ import { Tabs, Typography } from 'antd';
4
4
  import isEqual from 'fast-deep-equal';
5
5
  import {
6
6
  InspectionPanel,
@@ -8,15 +8,16 @@ import {
8
8
  LucideBugOff,
9
9
  LucideChevronDown,
10
10
  LucideChevronRight,
11
- LucideToyBrick,
12
11
  } from 'lucide-react';
13
12
  import { memo, useState } from 'react';
14
13
  import { useTranslation } from 'react-i18next';
15
14
  import { Flexbox } from 'react-layout-kit';
16
15
 
17
16
  import { DESKTOP_HEADER_ICON_SIZE } from '@/const/layoutTokens';
17
+ import PluginAvatar from '@/features/PluginAvatar';
18
+ import { useIsMobile } from '@/hooks/useIsMobile';
18
19
  import { useChatStore } from '@/store/chat';
19
- import { chatPortalSelectors } from '@/store/chat/slices/portal/selectors';
20
+ import { chatPortalSelectors } from '@/store/chat/selectors';
20
21
  import { pluginHelpers, useToolStore } from '@/store/tool';
21
22
  import { toolSelectors } from '@/store/tool/selectors';
22
23
  import { ChatPluginPayload } from '@/types/message';
@@ -50,25 +51,19 @@ const Inspector = memo<InspectorProps>(
50
51
  const { t } = useTranslation(['plugin', 'portal']);
51
52
  const { styles } = useStyles();
52
53
  const [open, setOpen] = useState(false);
53
- const [isMessageToolUIOpen, openToolUI, toggleInspector] = useChatStore((s) => [
54
+ const [isMessageToolUIOpen, openToolUI, togglePortal] = useChatStore((s) => [
54
55
  chatPortalSelectors.isArtifactMessageUIOpen(id)(s),
55
56
  s.openToolUI,
56
- s.toggleDock,
57
+ s.togglePortal,
57
58
  ]);
58
59
 
60
+ const isMobile = useIsMobile();
59
61
  const pluginMeta = useToolStore(toolSelectors.getMetaById(identifier), isEqual);
60
62
 
61
63
  const showRightAction = useToolStore(toolSelectors.isToolHasUI(identifier));
62
- const pluginAvatar = pluginHelpers.getPluginAvatar(pluginMeta);
63
64
 
64
65
  const pluginTitle = pluginHelpers.getPluginTitle(pluginMeta) ?? t('unknownPlugin');
65
66
 
66
- const avatar = pluginAvatar ? (
67
- <Avatar alt={pluginTitle} avatar={pluginAvatar} size={32} />
68
- ) : (
69
- <Icon icon={LucideToyBrick} />
70
- );
71
-
72
67
  let args, params;
73
68
  try {
74
69
  args = JSON.stringify(payload, null, 2);
@@ -84,7 +79,7 @@ const Inspector = memo<InspectorProps>(
84
79
  <Flexbox
85
80
  align={'center'}
86
81
  className={styles.container}
87
- gap={8}
82
+ gap={isMobile ? 16 : 8}
88
83
  horizontal
89
84
  onClick={() => {
90
85
  setShow?.(!showRender);
@@ -96,22 +91,33 @@ const Inspector = memo<InspectorProps>(
96
91
  <Loading3QuartersOutlined spin />
97
92
  </div>
98
93
  ) : (
99
- avatar
94
+ <PluginAvatar identifier={identifier} size={isMobile ? 36 : undefined} />
95
+ )}
96
+ {isMobile ? (
97
+ <Flexbox>
98
+ <div>{pluginTitle}</div>
99
+ <Typography.Text className={styles.apiName} type={'secondary'}>
100
+ {payload?.apiName}
101
+ </Typography.Text>
102
+ </Flexbox>
103
+ ) : (
104
+ <>
105
+ <div>{pluginTitle}</div>
106
+ <Tag>{payload?.apiName}</Tag>
107
+ </>
100
108
  )}
101
- <div>{pluginTitle}</div>
102
- <Tag>{payload?.apiName}</Tag>
103
109
  </Flexbox>
104
110
  {showRightAction && <Icon icon={showRender ? LucideChevronDown : LucideChevronRight} />}
105
111
  </Flexbox>
106
112
 
107
113
  <Flexbox horizontal>
108
- {showRightAction && (
114
+ {!isMobile && showRightAction && (
109
115
  <ActionIcon
110
116
  icon={InspectionPanel}
111
117
  onClick={() => {
112
118
  if (!isMessageToolUIOpen) openToolUI(id, identifier);
113
119
  else {
114
- toggleInspector(false);
120
+ togglePortal(false);
115
121
  }
116
122
  }}
117
123
  size={DESKTOP_HEADER_ICON_SIZE}
@@ -1,6 +1,15 @@
1
1
  import { createStyles } from 'antd-style';
2
2
 
3
3
  export const useStyles = createStyles(({ css, token }) => ({
4
+ apiName: css`
5
+ overflow: hidden;
6
+ display: -webkit-box;
7
+ -webkit-box-orient: vertical;
8
+ -webkit-line-clamp: 1;
9
+
10
+ font-size: 12px;
11
+ text-overflow: ellipsis;
12
+ `,
4
13
  container: css`
5
14
  cursor: pointer;
6
15
 
@@ -82,6 +82,12 @@ export const messageRouter = router({
82
82
  }),
83
83
 
84
84
  removeMessages: messageProcedure
85
+ .input(z.object({ ids: z.array(z.string()) }))
86
+ .mutation(async ({ input, ctx }) => {
87
+ return ctx.messageModel.deleteMessages(input.ids);
88
+ }),
89
+
90
+ removeMessagesByAssistant: messageProcedure
85
91
  .input(
86
92
  z.object({
87
93
  sessionId: z.string().nullable().optional(),
@@ -89,7 +95,7 @@ export const messageRouter = router({
89
95
  }),
90
96
  )
91
97
  .mutation(async ({ input, ctx }) => {
92
- return ctx.messageModel.deleteMessages(input.sessionId, input.topicId);
98
+ return ctx.messageModel.deleteMessagesBySession(input.sessionId, input.topicId);
93
99
  }),
94
100
 
95
101
  searchMessages: messageProcedure
@@ -23,6 +23,7 @@ vi.mock('@/database/client/models/message', () => {
23
23
  count: vi.fn(),
24
24
  query: vi.fn(),
25
25
  delete: vi.fn(),
26
+ bulkDelete: vi.fn(),
26
27
  queryBySessionId: vi.fn(),
27
28
  update: vi.fn(),
28
29
  updatePlugin: vi.fn(),
@@ -100,6 +101,19 @@ describe('MessageClientService', () => {
100
101
  expect(result).toBe(true);
101
102
  });
102
103
  });
104
+ describe('removeMessages', () => {
105
+ it('should remove a message by id', async () => {
106
+ // Setup
107
+ (MessageModel.bulkDelete as Mock).mockResolvedValue(true);
108
+
109
+ // Execute
110
+ const result = await messageService.removeMessages([mockMessageId]);
111
+
112
+ // Assert
113
+ expect(MessageModel.bulkDelete).toHaveBeenCalledWith([mockMessageId]);
114
+ expect(result).toBe(true);
115
+ });
116
+ });
103
117
 
104
118
  describe('getMessages', () => {
105
119
  it('should retrieve messages by sessionId and topicId', async () => {
@@ -132,7 +146,7 @@ describe('MessageClientService', () => {
132
146
  });
133
147
  });
134
148
 
135
- describe('removeMessages', () => {
149
+ describe('removeMessagesByAssistant', () => {
136
150
  it('should batch remove messages by assistantId and topicId', async () => {
137
151
  // Setup
138
152
  const assistantId = 'assistant-id';
@@ -140,7 +154,7 @@ describe('MessageClientService', () => {
140
154
  (MessageModel.batchDelete as Mock).mockResolvedValue(true);
141
155
 
142
156
  // Execute
143
- const result = await messageService.removeMessages(assistantId, topicId);
157
+ const result = await messageService.removeMessagesByAssistant(assistantId, topicId);
144
158
 
145
159
  // Assert
146
160
  expect(MessageModel.batchDelete).toHaveBeenCalledWith(assistantId, topicId);
@@ -74,7 +74,11 @@ export class ClientService implements IMessageService {
74
74
  return MessageModel.delete(id);
75
75
  }
76
76
 
77
- async removeMessages(assistantId: string, topicId?: string) {
77
+ async removeMessages(ids: string[]) {
78
+ return MessageModel.bulkDelete(ids);
79
+ }
80
+
81
+ async removeMessagesByAssistant(assistantId: string, topicId?: string) {
78
82
  return MessageModel.batchDelete(assistantId, topicId);
79
83
  }
80
84
 
@@ -80,8 +80,13 @@ export class ServerService implements IMessageService {
80
80
  removeMessage(id: string): Promise<any> {
81
81
  return lambdaClient.message.removeMessage.mutate({ id });
82
82
  }
83
- removeMessages(sessionId: string, topicId?: string | undefined): Promise<any> {
84
- return lambdaClient.message.removeMessages.mutate({
83
+
84
+ removeMessages(ids: string[]): Promise<any> {
85
+ return lambdaClient.message.removeMessages.mutate({ ids });
86
+ }
87
+
88
+ removeMessagesByAssistant(sessionId: string, topicId?: string | undefined): Promise<any> {
89
+ return lambdaClient.message.removeMessagesByAssistant.mutate({
85
90
  sessionId: this.toDbSessionId(sessionId),
86
91
  topicId,
87
92
  });
@@ -39,7 +39,8 @@ export interface IMessageService {
39
39
  bindMessagesToTopic(topicId: string, messageIds: string[]): Promise<any>;
40
40
 
41
41
  removeMessage(id: string): Promise<any>;
42
- removeMessages(assistantId: string, topicId?: string): Promise<any>;
42
+ removeMessages(ids: string[]): Promise<any>;
43
+ removeMessagesByAssistant(assistantId: string, topicId?: string): Promise<any>;
43
44
  removeAllMessages(): Promise<any>;
44
45
 
45
46
  hasMessages(): Promise<boolean>;