@lobehub/chat 1.1.16 → 1.1.17

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 (120) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/locales/ar/plugin.json +1 -0
  3. package/locales/ar/portal.json +4 -0
  4. package/locales/bg-BG/plugin.json +1 -0
  5. package/locales/bg-BG/portal.json +4 -0
  6. package/locales/de-DE/plugin.json +1 -0
  7. package/locales/de-DE/portal.json +4 -0
  8. package/locales/en-US/plugin.json +1 -0
  9. package/locales/en-US/portal.json +4 -0
  10. package/locales/es-ES/plugin.json +1 -0
  11. package/locales/es-ES/portal.json +4 -0
  12. package/locales/fr-FR/plugin.json +1 -0
  13. package/locales/fr-FR/portal.json +4 -0
  14. package/locales/it-IT/plugin.json +1 -0
  15. package/locales/it-IT/portal.json +4 -0
  16. package/locales/ja-JP/plugin.json +1 -0
  17. package/locales/ja-JP/portal.json +4 -0
  18. package/locales/ko-KR/plugin.json +1 -0
  19. package/locales/ko-KR/portal.json +4 -0
  20. package/locales/nl-NL/plugin.json +1 -0
  21. package/locales/nl-NL/portal.json +4 -0
  22. package/locales/pl-PL/plugin.json +1 -0
  23. package/locales/pl-PL/portal.json +4 -0
  24. package/locales/pt-BR/plugin.json +1 -0
  25. package/locales/pt-BR/portal.json +4 -0
  26. package/locales/ru-RU/plugin.json +1 -0
  27. package/locales/ru-RU/portal.json +4 -0
  28. package/locales/tr-TR/plugin.json +1 -0
  29. package/locales/tr-TR/portal.json +4 -0
  30. package/locales/vi-VN/plugin.json +1 -0
  31. package/locales/vi-VN/portal.json +4 -0
  32. package/locales/zh-CN/plugin.json +1 -0
  33. package/locales/zh-CN/portal.json +4 -0
  34. package/locales/zh-TW/plugin.json +1 -0
  35. package/locales/zh-TW/portal.json +4 -0
  36. package/package.json +1 -1
  37. package/src/app/(main)/chat/(workspace)/@portal/_layout/Desktop.tsx +17 -0
  38. package/src/app/(main)/chat/(workspace)/@portal/_layout/Mobile.tsx +18 -0
  39. package/src/app/(main)/chat/(workspace)/@portal/default.tsx +27 -0
  40. package/src/app/(main)/chat/(workspace)/@portal/features/Header.tsx +52 -0
  41. package/src/app/(main)/chat/(workspace)/@portal/features/Tools/ToolList/Item/index.tsx +74 -0
  42. package/src/app/(main)/chat/(workspace)/@portal/features/Tools/ToolList/Item/style.ts +46 -0
  43. package/src/app/(main)/chat/(workspace)/@portal/features/Tools/ToolList/index.tsx +39 -0
  44. package/src/app/(main)/chat/(workspace)/@portal/features/Tools/ToolUI/Footer.tsx +33 -0
  45. package/src/app/(main)/chat/(workspace)/@portal/features/Tools/ToolUI/ToolRender.tsx +50 -0
  46. package/src/app/(main)/chat/(workspace)/@portal/features/Tools/ToolUI/index.tsx +37 -0
  47. package/src/app/(main)/chat/(workspace)/@portal/index.tsx +18 -0
  48. package/src/app/(main)/chat/(workspace)/_layout/Desktop/Portal.tsx +79 -0
  49. package/src/app/(main)/chat/(workspace)/_layout/Desktop/TopicPanel.tsx +27 -22
  50. package/src/app/(main)/chat/(workspace)/_layout/Desktop/index.tsx +3 -1
  51. package/src/app/(main)/chat/(workspace)/_layout/type.ts +1 -0
  52. package/src/app/(main)/chat/loading.tsx +2 -20
  53. package/src/components/CircleLoading/index.tsx +21 -0
  54. package/src/const/layoutTokens.test.ts +11 -0
  55. package/src/const/layoutTokens.ts +4 -0
  56. package/src/const/message.ts +0 -14
  57. package/src/database/client/models/__tests__/message.test.ts +9 -12
  58. package/src/database/client/models/message.ts +6 -0
  59. package/src/database/server/models/__tests__/message.test.ts +70 -0
  60. package/src/database/server/models/message.ts +10 -0
  61. package/src/database/server/schemas/lobechat.ts +3 -1
  62. package/src/features/Conversation/Messages/Assistant/ToolCalls/index.tsx +3 -17
  63. package/src/features/Conversation/Messages/Tool/Inspector/index.tsx +26 -2
  64. package/src/features/Conversation/Messages/Tool/index.tsx +33 -6
  65. package/src/features/Conversation/Messages/components/Arguments.tsx +1 -1
  66. package/src/features/PluginAvatar/index.tsx +28 -0
  67. package/src/features/PluginsUI/Render/BuiltinType/index.tsx +44 -0
  68. package/src/features/{Conversation/Plugins → PluginsUI}/Render/StandaloneType/index.tsx +8 -1
  69. package/src/features/{Conversation/Plugins → PluginsUI}/Render/index.tsx +12 -1
  70. package/src/{features/Conversation/Messages/hooks → hooks}/useYamlArguments.ts +3 -1
  71. package/src/locales/default/index.ts +2 -0
  72. package/src/locales/default/plugin.ts +1 -0
  73. package/src/locales/default/portal.ts +4 -0
  74. package/src/server/routers/lambda/message.ts +12 -0
  75. package/src/services/message/client.test.ts +35 -0
  76. package/src/services/message/client.ts +6 -0
  77. package/src/services/message/server.ts +9 -0
  78. package/src/store/chat/initialState.ts +10 -1
  79. package/src/store/chat/selectors.ts +1 -0
  80. package/src/store/chat/slices/message/action.test.ts +1 -1
  81. package/src/store/chat/slices/message/action.ts +5 -5
  82. package/src/store/chat/slices/message/reducer.test.ts +230 -7
  83. package/src/store/chat/slices/message/reducer.ts +45 -22
  84. package/src/store/chat/slices/message/selectors.test.ts +133 -2
  85. package/src/store/chat/slices/message/selectors.ts +7 -0
  86. package/src/store/chat/slices/plugin/action.test.ts +309 -2
  87. package/src/store/chat/slices/plugin/action.ts +51 -1
  88. package/src/store/chat/slices/portal/action.test.ts +109 -0
  89. package/src/store/chat/slices/portal/action.ts +31 -0
  90. package/src/store/chat/slices/portal/initialState.ts +8 -0
  91. package/src/store/chat/slices/portal/selectors.test.ts +73 -0
  92. package/src/store/chat/slices/portal/selectors.ts +15 -0
  93. package/src/store/chat/store.ts +7 -1
  94. package/src/store/tool/selectors/tool.test.ts +14 -0
  95. package/src/store/tool/selectors/tool.ts +13 -0
  96. package/src/tools/docks.ts +3 -0
  97. package/src/types/tool/builtin.ts +11 -1
  98. package/src/utils/safeParseJSON.test.ts +71 -0
  99. package/src/utils/safeParseJSON.ts +12 -0
  100. package/src/const/message.test.ts +0 -55
  101. package/src/features/Conversation/Plugins/Render/BuiltinType/index.tsx +0 -30
  102. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/BuiltinType/index.test.tsx +0 -0
  103. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/DefaultType/IFrameRender/index.tsx +0 -0
  104. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/DefaultType/SystemJsRender/index.tsx +0 -0
  105. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/DefaultType/SystemJsRender/utils.ts +0 -0
  106. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/DefaultType/index.tsx +0 -0
  107. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/Loading.tsx +0 -0
  108. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/MarkdownType/index.tsx +0 -0
  109. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/StandaloneType/Iframe.tsx +0 -0
  110. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/useParseContent.ts +0 -0
  111. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/utils/iframeOnReady.test.ts +0 -0
  112. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/utils/iframeOnReady.ts +0 -0
  113. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/utils/listenToPlugin.test.ts +0 -0
  114. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/utils/listenToPlugin.ts +0 -0
  115. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/utils/pluginSettings.test.ts +0 -0
  116. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/utils/pluginSettings.ts +0 -0
  117. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/utils/pluginState.test.ts +0 -0
  118. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/utils/pluginState.ts +0 -0
  119. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/utils/postMessage.test.ts +0 -0
  120. /package/src/features/{Conversation/Plugins → PluginsUI}/Render/utils/postMessage.ts +0 -0
@@ -0,0 +1,50 @@
1
+ import isEqual from 'fast-deep-equal';
2
+
3
+ import PluginRender from '@/features/PluginsUI/Render';
4
+ import { useChatStore } from '@/store/chat';
5
+ import { chatPortalSelectors, chatSelectors } from '@/store/chat/selectors';
6
+ import { BuiltinToolsDocks } from '@/tools/docks';
7
+ import { safeParseJSON } from '@/utils/safeParseJSON';
8
+
9
+ const ToolRender = () => {
10
+ const messageId = useChatStore(chatPortalSelectors.toolUIMessageId);
11
+ const message = useChatStore(chatSelectors.getMessageById(messageId || ''), isEqual);
12
+
13
+ // make sure the message and id is valid
14
+ if (!messageId || !message) return;
15
+
16
+ const { plugin, pluginState } = message;
17
+
18
+ // make sure the plugin and identifier is valid
19
+ if (!plugin || !plugin.identifier) return;
20
+
21
+ const args = safeParseJSON(plugin.arguments);
22
+
23
+ if (!args) return;
24
+
25
+ const Render = BuiltinToolsDocks[plugin.identifier];
26
+
27
+ if (!Render)
28
+ return (
29
+ <PluginRender
30
+ arguments={plugin.arguments}
31
+ content={message.content}
32
+ id={messageId}
33
+ identifier={plugin.identifier}
34
+ payload={plugin}
35
+ pluginState={pluginState}
36
+ type={plugin?.type}
37
+ />
38
+ );
39
+
40
+ return (
41
+ <Render
42
+ arguments={args}
43
+ identifier={plugin.identifier}
44
+ messageId={messageId}
45
+ state={pluginState}
46
+ />
47
+ );
48
+ };
49
+
50
+ export default ToolRender;
@@ -0,0 +1,37 @@
1
+ import isEqual from 'fast-deep-equal';
2
+ import { Flexbox } from 'react-layout-kit';
3
+
4
+ import { useChatStore } from '@/store/chat';
5
+ import { chatPortalSelectors, chatSelectors } from '@/store/chat/selectors';
6
+ import { safeParseJSON } from '@/utils/safeParseJSON';
7
+
8
+ import Footer from './Footer';
9
+ import ToolRender from './ToolRender';
10
+
11
+ const ToolUI = () => {
12
+ const messageId = useChatStore(chatPortalSelectors.toolUIMessageId);
13
+ const message = useChatStore(chatSelectors.getMessageById(messageId || ''), isEqual);
14
+
15
+ // make sure the message and id is valid
16
+ if (!messageId || !message) return;
17
+
18
+ const { plugin } = message;
19
+
20
+ // make sure the plugin and identifier is valid
21
+ if (!plugin || !plugin.identifier) return;
22
+
23
+ const args = safeParseJSON(plugin.arguments);
24
+
25
+ if (!args) return;
26
+
27
+ return (
28
+ <>
29
+ <Flexbox height={'100%'} paddingInline={12}>
30
+ <ToolRender />
31
+ </Flexbox>
32
+ <Footer />
33
+ </>
34
+ );
35
+ };
36
+
37
+ export default ToolUI;
@@ -0,0 +1,18 @@
1
+ 'use client';
2
+
3
+ import { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { useChatStore } from '@/store/chat';
7
+ import { chatPortalSelectors } from '@/store/chat/selectors';
8
+
9
+ import ToolList from './features/Tools/ToolList';
10
+ import ToolUI from './features/Tools/ToolUI';
11
+
12
+ const Inspector = memo(() => {
13
+ const showToolUI = useChatStore(chatPortalSelectors.showToolUI);
14
+
15
+ return <Flexbox height={'100%'}>{showToolUI ? <ToolUI /> : <ToolList />}</Flexbox>;
16
+ });
17
+
18
+ export default Inspector;
@@ -0,0 +1,79 @@
1
+ 'use client';
2
+
3
+ import { DraggablePanel, DraggablePanelContainer } from '@lobehub/ui';
4
+ import { createStyles, useResponsive } from 'antd-style';
5
+ import { PropsWithChildren, memo } from 'react';
6
+ import { Flexbox } from 'react-layout-kit';
7
+
8
+ import SafeSpacing from '@/components/SafeSpacing';
9
+ import { CHAT_DOCK_TOOL_UI_WIDTH, CHAT_DOCK_WIDTH, MAX_WIDTH } from '@/const/layoutTokens';
10
+ import { useChatStore } from '@/store/chat';
11
+ import { chatPortalSelectors } from '@/store/chat/slices/portal/selectors';
12
+
13
+ const useStyles = createStyles(({ css, token }) => ({
14
+ content: css`
15
+ display: flex;
16
+ flex-direction: column;
17
+ height: 100% !important;
18
+ `,
19
+ drawer: css`
20
+ z-index: 10;
21
+ background: ${token.colorBgLayout};
22
+ `,
23
+ header: css`
24
+ border-bottom: 1px solid ${token.colorBorder};
25
+ `,
26
+ panel: css`
27
+ overflow: hidden;
28
+
29
+ height: 100%;
30
+ margin: 12px;
31
+
32
+ background: ${token.colorBgContainer};
33
+ border: 1px solid ${token.colorPrimaryActive};
34
+ border-radius: 8px;
35
+ `,
36
+ }));
37
+
38
+ const PortalPanel = memo(({ children }: PropsWithChildren) => {
39
+ const { styles } = useStyles();
40
+ const { md = true } = useResponsive();
41
+
42
+ const [showInspector, showToolUI] = useChatStore((s) => [
43
+ chatPortalSelectors.showDock(s),
44
+ chatPortalSelectors.showToolUI(s),
45
+ ]);
46
+
47
+ return (
48
+ showInspector && (
49
+ <DraggablePanel
50
+ className={styles.drawer}
51
+ classNames={{
52
+ content: styles.content,
53
+ }}
54
+ expand
55
+ hanlderStyle={{ display: 'none' }}
56
+ maxWidth={MAX_WIDTH}
57
+ minWidth={showToolUI ? CHAT_DOCK_TOOL_UI_WIDTH : CHAT_DOCK_WIDTH}
58
+ mode={md ? 'fixed' : 'float'}
59
+ placement={'right'}
60
+ showHandlerWhenUnexpand={false}
61
+ showHandlerWideArea={false}
62
+ >
63
+ <DraggablePanelContainer
64
+ style={{
65
+ flex: 'none',
66
+ height: '100%',
67
+ maxHeight: '100vh',
68
+ minWidth: CHAT_DOCK_WIDTH,
69
+ }}
70
+ >
71
+ <SafeSpacing />
72
+ <Flexbox className={styles.panel}>{children}</Flexbox>
73
+ </DraggablePanelContainer>
74
+ </DraggablePanel>
75
+ )
76
+ );
77
+ });
78
+
79
+ export default PortalPanel;
@@ -7,6 +7,8 @@ import { PropsWithChildren, memo, useEffect, useState } from 'react';
7
7
 
8
8
  import SafeSpacing from '@/components/SafeSpacing';
9
9
  import { CHAT_SIDEBAR_WIDTH } from '@/const/layoutTokens';
10
+ import { useChatStore } from '@/store/chat';
11
+ import { chatPortalSelectors } from '@/store/chat/slices/portal/selectors';
10
12
  import { useGlobalStore } from '@/store/global';
11
13
  import { systemStatusSelectors } from '@/store/global/selectors';
12
14
 
@@ -32,6 +34,7 @@ const TopicPanel = memo(({ children }: PropsWithChildren) => {
32
34
  systemStatusSelectors.showChatSideBar(s),
33
35
  s.toggleChatSideBar,
34
36
  ]);
37
+ const showInspector = useChatStore(chatPortalSelectors.showDock);
35
38
 
36
39
  const [cacheExpand, setCacheExpand] = useState<boolean>(Boolean(showAgentSettings));
37
40
 
@@ -47,30 +50,32 @@ const TopicPanel = memo(({ children }: PropsWithChildren) => {
47
50
  }, [lg, cacheExpand]);
48
51
 
49
52
  return (
50
- <DraggablePanel
51
- className={styles.drawer}
52
- classNames={{
53
- content: styles.content,
54
- }}
55
- expand={showAgentSettings}
56
- minWidth={CHAT_SIDEBAR_WIDTH}
57
- mode={md ? 'fixed' : 'float'}
58
- onExpandChange={handleExpand}
59
- placement={'right'}
60
- showHandlerWideArea={false}
61
- >
62
- <DraggablePanelContainer
63
- style={{
64
- flex: 'none',
65
- height: '100%',
66
- maxHeight: '100vh',
67
- minWidth: CHAT_SIDEBAR_WIDTH,
53
+ !showInspector && (
54
+ <DraggablePanel
55
+ className={styles.drawer}
56
+ classNames={{
57
+ content: styles.content,
68
58
  }}
59
+ expand={showAgentSettings}
60
+ minWidth={CHAT_SIDEBAR_WIDTH}
61
+ mode={md ? 'fixed' : 'float'}
62
+ onExpandChange={handleExpand}
63
+ placement={'right'}
64
+ showHandlerWideArea={false}
69
65
  >
70
- <SafeSpacing />
71
- {children}
72
- </DraggablePanelContainer>
73
- </DraggablePanel>
66
+ <DraggablePanelContainer
67
+ style={{
68
+ flex: 'none',
69
+ height: '100%',
70
+ maxHeight: '100vh',
71
+ minWidth: CHAT_SIDEBAR_WIDTH,
72
+ }}
73
+ >
74
+ <SafeSpacing />
75
+ {children}
76
+ </DraggablePanelContainer>
77
+ </DraggablePanel>
78
+ )
74
79
  );
75
80
  });
76
81
 
@@ -2,10 +2,11 @@ import { Flexbox } from 'react-layout-kit';
2
2
 
3
3
  import { LayoutProps } from '../type';
4
4
  import ChatHeader from './ChatHeader';
5
+ import Inspector from './Portal';
5
6
  import HotKeys from './HotKeys';
6
7
  import TopicPanel from './TopicPanel';
7
8
 
8
- const Layout = ({ children, topic, conversation }: LayoutProps) => {
9
+ const Layout = ({ children, topic, conversation, portal }: LayoutProps) => {
9
10
  return (
10
11
  <>
11
12
  <ChatHeader />
@@ -23,6 +24,7 @@ const Layout = ({ children, topic, conversation }: LayoutProps) => {
23
24
  {conversation}
24
25
  </Flexbox>
25
26
  {children}
27
+ <Inspector>{portal}</Inspector>
26
28
  <TopicPanel>{topic}</TopicPanel>
27
29
  </Flexbox>
28
30
  <HotKeys />
@@ -3,5 +3,6 @@ import { ReactNode } from 'react';
3
3
  export interface LayoutProps {
4
4
  children: ReactNode;
5
5
  conversation: ReactNode;
6
+ portal: ReactNode;
6
7
  topic: ReactNode;
7
8
  }
@@ -1,21 +1,3 @@
1
- 'use client';
1
+ import CircleLoading from '@/components/CircleLoading';
2
2
 
3
- import { Icon } from '@lobehub/ui';
4
- import { Typography } from 'antd';
5
- import { LoaderCircle } from 'lucide-react';
6
- import { useTranslation } from 'react-i18next';
7
- import { Center, Flexbox } from 'react-layout-kit';
8
-
9
- export default () => {
10
- const { t } = useTranslation('common');
11
- return (
12
- <Center height={'100%'} width={'100%'}>
13
- <Flexbox align={'center'} gap={8}>
14
- <div>
15
- <Icon icon={LoaderCircle} size={'large'} spin />
16
- </div>
17
- <Typography.Text type={'secondary'}>{t('loading')}</Typography.Text>
18
- </Flexbox>
19
- </Center>
20
- );
21
- };
3
+ export default () => <CircleLoading />;
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+
3
+ import { Icon } from '@lobehub/ui';
4
+ import { Typography } from 'antd';
5
+ import { LoaderCircle } from 'lucide-react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Center, Flexbox } from 'react-layout-kit';
8
+
9
+ export default () => {
10
+ const { t } = useTranslation('common');
11
+ return (
12
+ <Center height={'100%'} width={'100%'}>
13
+ <Flexbox align={'center'} gap={8}>
14
+ <div>
15
+ <Icon icon={LoaderCircle} size={'large'} spin />
16
+ </div>
17
+ <Typography.Text type={'secondary'}>{t('loading')}</Typography.Text>
18
+ </Flexbox>
19
+ </Center>
20
+ );
21
+ };
@@ -0,0 +1,11 @@
1
+ import { HEADER_ICON_SIZE } from './layoutTokens';
2
+
3
+ describe('HEADER_ICON_SIZE', () => {
4
+ it('mobile', () => {
5
+ expect(HEADER_ICON_SIZE(true)).toEqual({ blockSize: 36, fontSize: 22 });
6
+ });
7
+
8
+ it('desktop', () => {
9
+ expect(HEADER_ICON_SIZE(false)).toEqual({ fontSize: 24 });
10
+ });
11
+ });
@@ -7,6 +7,10 @@ export const CHAT_TEXTAREA_MAX_HEIGHT = 800;
7
7
  export const CHAT_TEXTAREA_HEIGHT = 230;
8
8
  export const CHAT_TEXTAREA_HEIGHT_MOBILE = 108;
9
9
  export const CHAT_SIDEBAR_WIDTH = 280;
10
+
11
+ export const CHAT_DOCK_WIDTH = 400;
12
+ export const CHAT_DOCK_TOOL_UI_WIDTH = 800;
13
+
10
14
  export const MARKET_SIDEBAR_WIDTH = 400;
11
15
  export const FOLDER_WIDTH = 270;
12
16
  export const MAX_WIDTH = 1024;
@@ -1,15 +1 @@
1
1
  export const LOADING_FLAT = '...';
2
-
3
- // start with this,it should be a function message
4
- export const FUNCTION_MESSAGE_FLAG = '{"tool_calls"';
5
-
6
- export const isFunctionMessageAtStart = (content: string) => {
7
- return content.startsWith(FUNCTION_MESSAGE_FLAG);
8
- };
9
-
10
- export const testFunctionMessageAtEnd = (content: string) => {
11
- const regExp = /{"tool_calls":.*?]}$/;
12
- const match = content?.trim().match(regExp);
13
-
14
- return { content: match ? match[0] : '', valid: !!match };
15
- };
@@ -390,21 +390,18 @@ describe('MessageModel', () => {
390
390
  expect(updatedMessage.pluginState).toHaveProperty('testKey', 'testValue');
391
391
  });
392
392
  });
393
- describe('clearTable', () => {
394
- it('should clear the table', async () => {
395
- await MessageModel.create(messageData);
396
- await MessageModel.clearTable();
397
- const messages = await MessageModel.queryAll();
398
- expect(messages).toHaveLength(0);
399
- });
400
- });
401
393
 
402
- describe('updatePluginState', () => {
403
- it('should update plugin state', async () => {
394
+ describe('updatePlugin', () => {
395
+ it('should update plugin', async () => {
396
+ const value = {
397
+ identifier: 'testValue',
398
+ arguments: 'abc',
399
+ apiName: 'abc',
400
+ };
404
401
  const createdMessage = await MessageModel.create(messageData);
405
- await MessageModel.updatePluginState(createdMessage.id, { testKey: 'testValue' });
402
+ await MessageModel.updatePlugin(createdMessage.id, value);
406
403
  const updatedMessage = await MessageModel.findById(createdMessage.id);
407
- expect(updatedMessage.pluginState).toHaveProperty('testKey', 'testValue');
404
+ expect(updatedMessage.plugin).toEqual(value);
408
405
  });
409
406
  });
410
407
 
@@ -190,6 +190,12 @@ class _MessageModel extends BaseModel {
190
190
  return this.update(id, { pluginState: { ...item.pluginState, ...value } });
191
191
  }
192
192
 
193
+ async updatePlugin(id: string, value: any) {
194
+ const item = await this.findById(id);
195
+
196
+ return this.update(id, { plugin: { ...item.plugin, ...value } });
197
+ }
198
+
193
199
  /**
194
200
  * Batch updates multiple fields of the specified messages.
195
201
  *
@@ -544,6 +544,47 @@ describe('MessageModel', () => {
544
544
  const result = await serverDB.select().from(messages).where(eq(messages.id, '1')).execute();
545
545
  expect(result[0].content).toBe('message 1');
546
546
  });
547
+
548
+ it('should update message tools', async () => {
549
+ // 创建测试数据
550
+ await serverDB.insert(messages).values([
551
+ {
552
+ id: '1',
553
+ userId,
554
+ role: 'user',
555
+ content: 'message 1',
556
+ tools: [
557
+ {
558
+ id: 'call_Z8UU8LedZcoJHFGkfqYecjmT',
559
+ type: 'builtin',
560
+ apiName: 'searchWithSearXNG',
561
+ arguments:
562
+ '{"query":"杭州洪水 2023","searchEngines":["google","bing","baidu","duckduckgo","brave"]}',
563
+ identifier: 'lobe-web-browsing',
564
+ },
565
+ ],
566
+ },
567
+ ]);
568
+
569
+ // 调用 updateMessage 方法
570
+ await messageModel.update('1', {
571
+ tools: [
572
+ {
573
+ id: 'call_Z8UU8LedZcoJHFGkfqYecjmT',
574
+ type: 'builtin',
575
+ apiName: 'searchWithSearXNG',
576
+ arguments: '{"query":"2024 杭州暴雨","searchEngines":["duckduckgo","google","brave"]}',
577
+ identifier: 'lobe-web-browsing',
578
+ },
579
+ ],
580
+ });
581
+
582
+ // 断言结果
583
+ const result = await serverDB.select().from(messages).where(eq(messages.id, '1')).execute();
584
+ expect(result[0].tools[0].arguments).toBe(
585
+ '{"query":"2024 杭州暴雨","searchEngines":["duckduckgo","google","brave"]}',
586
+ );
587
+ });
547
588
  });
548
589
 
549
590
  describe('deleteMessage', () => {
@@ -662,6 +703,35 @@ describe('MessageModel', () => {
662
703
  );
663
704
  });
664
705
  });
706
+ describe('updateMessagePlugin', () => {
707
+ it('should update the state field in messagePlugins table', async () => {
708
+ // 创建测试数据
709
+ await serverDB.insert(messages).values({ id: '1', content: 'abc', role: 'user', userId });
710
+ await serverDB
711
+ .insert(messagePlugins)
712
+ .values([
713
+ { id: '1', toolCallId: 'tool1', identifier: 'plugin1', state: { key1: 'value1' } },
714
+ ]);
715
+
716
+ // 调用 updatePluginState 方法
717
+ await messageModel.updateMessagePlugin('1', { identifier: 'plugin2' });
718
+
719
+ // 断言结果
720
+ const result = await serverDB
721
+ .select()
722
+ .from(messagePlugins)
723
+ .where(eq(messagePlugins.id, '1'))
724
+ .execute();
725
+ expect(result[0].identifier).toEqual('plugin2');
726
+ });
727
+
728
+ it('should throw an error if plugin does not exist', async () => {
729
+ // 调用 updatePluginState 方法
730
+ await expect(messageModel.updatePluginState('1', { key: 'value' })).rejects.toThrowError(
731
+ 'Plugin not found',
732
+ );
733
+ });
734
+ });
665
735
 
666
736
  describe('updateTranslate', () => {
667
737
  it('should insert a new record if message does not exist in messageTranslates table', async () => {
@@ -10,6 +10,7 @@ import { merge } from '@/utils/merge';
10
10
 
11
11
  import {
12
12
  MessageItem,
13
+ MessagePluginItem,
13
14
  filesToMessages,
14
15
  messagePlugins,
15
16
  messageTTS,
@@ -271,6 +272,15 @@ export class MessageModel {
271
272
  .where(eq(messagePlugins.id, id));
272
273
  }
273
274
 
275
+ async updateMessagePlugin(id: string, value: Partial<MessagePluginItem>) {
276
+ const item = await serverDB.query.messagePlugins.findFirst({
277
+ where: eq(messagePlugins.id, id),
278
+ });
279
+ if (!item) throw new Error('Plugin not found');
280
+
281
+ return serverDB.update(messagePlugins).set(value).where(eq(messagePlugins.id, id));
282
+ }
283
+
274
284
  async updateTranslate(id: string, translate: Partial<MessageItem>) {
275
285
  const result = await serverDB.query.messageTranslates.findFirst({
276
286
  where: and(eq(messageTranslates.id, id)),
@@ -15,7 +15,7 @@ import {
15
15
  uniqueIndex,
16
16
  varchar,
17
17
  } from 'drizzle-orm/pg-core';
18
- import { createInsertSchema } from 'drizzle-zod';
18
+ import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
19
19
 
20
20
  import { DEFAULT_PREFERENCE } from '@/const/user';
21
21
  import { LobeAgentChatConfig, LobeAgentTTSConfig } from '@/types/agent';
@@ -451,6 +451,8 @@ export const messagePlugins = pgTable('message_plugins', {
451
451
  state: jsonb('state'),
452
452
  error: jsonb('error'),
453
453
  });
454
+ export type MessagePluginItem = typeof messagePlugins.$inferSelect;
455
+ export const updateMessagePluginSchema = createSelectSchema(messagePlugins);
454
456
 
455
457
  export const messageTTS = pgTable('message_tts', {
456
458
  id: text('id')
@@ -1,9 +1,9 @@
1
- import { Avatar, Icon } from '@lobehub/ui';
1
+ import { Icon } from '@lobehub/ui';
2
2
  import isEqual from 'fast-deep-equal';
3
3
  import { Loader2, LucideChevronDown, LucideChevronRight, LucideToyBrick } from 'lucide-react';
4
4
  import { CSSProperties, memo, useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
- import { Center, Flexbox } from 'react-layout-kit';
6
+ import { Flexbox } from 'react-layout-kit';
7
7
 
8
8
  import { useChatStore } from '@/store/chat';
9
9
  import { chatSelectors } from '@/store/chat/selectors';
@@ -30,16 +30,8 @@ const CallItem = memo<InspectorProps>(
30
30
 
31
31
  const pluginMeta = useToolStore(toolSelectors.getMetaById(identifier), isEqual);
32
32
 
33
- const pluginAvatar = pluginHelpers.getPluginAvatar(pluginMeta);
34
-
35
33
  const pluginTitle = pluginHelpers.getPluginTitle(pluginMeta) ?? t('unknownPlugin');
36
34
 
37
- const avatar = pluginAvatar ? (
38
- <Avatar alt={pluginTitle} avatar={pluginAvatar} size={32} />
39
- ) : (
40
- <Icon icon={LucideToyBrick} />
41
- );
42
-
43
35
  return (
44
36
  <Flexbox gap={8} style={style}>
45
37
  <Flexbox
@@ -54,13 +46,7 @@ const CallItem = memo<InspectorProps>(
54
46
  }}
55
47
  >
56
48
  <Flexbox align={'center'} gap={8} horizontal>
57
- {loading ? (
58
- <Center height={30} width={24}>
59
- <Icon icon={Loader2} spin />
60
- </Center>
61
- ) : (
62
- avatar
63
- )}
49
+ {loading ? <Icon icon={Loader2} spin /> : <Icon icon={LucideToyBrick} />}
64
50
  {pluginTitle}
65
51
  </Flexbox>
66
52
  <Icon icon={open ? LucideChevronDown : LucideChevronRight} />
@@ -3,6 +3,7 @@ import { ActionIcon, Avatar, Highlighter, Icon, Tag } from '@lobehub/ui';
3
3
  import { Tabs } from 'antd';
4
4
  import isEqual from 'fast-deep-equal';
5
5
  import {
6
+ InspectionPanel,
6
7
  LucideBug,
7
8
  LucideBugOff,
8
9
  LucideChevronDown,
@@ -13,8 +14,11 @@ import { memo, useState } from 'react';
13
14
  import { useTranslation } from 'react-i18next';
14
15
  import { Flexbox } from 'react-layout-kit';
15
16
 
17
+ import { DESKTOP_HEADER_ICON_SIZE } from '@/const/layoutTokens';
18
+ import { useChatStore } from '@/store/chat';
19
+ import { chatPortalSelectors } from '@/store/chat/slices/portal/selectors';
16
20
  import { pluginHelpers, useToolStore } from '@/store/tool';
17
- import { pluginSelectors, toolSelectors } from '@/store/tool/selectors';
21
+ import { toolSelectors } from '@/store/tool/selectors';
18
22
  import { ChatPluginPayload } from '@/types/message';
19
23
 
20
24
  import PluginResult from './PluginResultJSON';
@@ -24,6 +28,7 @@ import { useStyles } from './style';
24
28
  export interface InspectorProps {
25
29
  arguments?: string;
26
30
  content: string;
31
+ id: string;
27
32
  identifier?: string;
28
33
  loading?: boolean;
29
34
  payload?: ChatPluginPayload;
@@ -40,14 +45,20 @@ const Inspector = memo<InspectorProps>(
40
45
  setShow,
41
46
  content,
42
47
  identifier = 'unknown',
48
+ id,
43
49
  }) => {
44
50
  const { t } = useTranslation('plugin');
45
51
  const { styles } = useStyles();
46
52
  const [open, setOpen] = useState(false);
53
+ const [isMessageToolUIOpen, openToolUI, toggleInspector] = useChatStore((s) => [
54
+ chatPortalSelectors.isMessageToolUIOpen(id)(s),
55
+ s.openToolUI,
56
+ s.toggleDock,
57
+ ]);
47
58
 
48
59
  const pluginMeta = useToolStore(toolSelectors.getMetaById(identifier), isEqual);
49
60
 
50
- const showRightAction = useToolStore(pluginSelectors.isPluginHasUI(identifier));
61
+ const showRightAction = useToolStore(toolSelectors.isToolHasUI(identifier));
51
62
  const pluginAvatar = pluginHelpers.getPluginAvatar(pluginMeta);
52
63
 
53
64
  const pluginTitle = pluginHelpers.getPluginTitle(pluginMeta) ?? t('unknownPlugin');
@@ -94,6 +105,19 @@ const Inspector = memo<InspectorProps>(
94
105
  </Flexbox>
95
106
 
96
107
  <Flexbox horizontal>
108
+ {showRightAction && false && (
109
+ <ActionIcon
110
+ icon={InspectionPanel}
111
+ onClick={() => {
112
+ if (!isMessageToolUIOpen) openToolUI(id, identifier);
113
+ else {
114
+ toggleInspector(false);
115
+ }
116
+ }}
117
+ size={DESKTOP_HEADER_ICON_SIZE}
118
+ title={'inspector'}
119
+ />
120
+ )}
97
121
  <ActionIcon
98
122
  icon={open ? LucideBugOff : LucideBug}
99
123
  onClick={() => {