@lobehub/chat 1.63.2 → 1.64.0

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 (151) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/ar/models.json +25 -16
  4. package/locales/ar/plugin.json +16 -0
  5. package/locales/ar/portal.json +0 -5
  6. package/locales/ar/tool.json +18 -0
  7. package/locales/bg-BG/models.json +25 -16
  8. package/locales/bg-BG/plugin.json +16 -0
  9. package/locales/bg-BG/portal.json +0 -5
  10. package/locales/bg-BG/tool.json +18 -0
  11. package/locales/de-DE/models.json +25 -16
  12. package/locales/de-DE/plugin.json +16 -0
  13. package/locales/de-DE/portal.json +0 -5
  14. package/locales/de-DE/tool.json +18 -0
  15. package/locales/en-US/models.json +24 -15
  16. package/locales/en-US/plugin.json +16 -0
  17. package/locales/en-US/portal.json +0 -5
  18. package/locales/en-US/tool.json +18 -0
  19. package/locales/es-ES/models.json +25 -16
  20. package/locales/es-ES/plugin.json +16 -0
  21. package/locales/es-ES/portal.json +0 -5
  22. package/locales/es-ES/tool.json +18 -0
  23. package/locales/fa-IR/models.json +25 -16
  24. package/locales/fa-IR/plugin.json +16 -0
  25. package/locales/fa-IR/portal.json +0 -5
  26. package/locales/fa-IR/tool.json +18 -0
  27. package/locales/fr-FR/models.json +25 -16
  28. package/locales/fr-FR/plugin.json +16 -0
  29. package/locales/fr-FR/portal.json +0 -5
  30. package/locales/fr-FR/tool.json +18 -0
  31. package/locales/it-IT/models.json +25 -16
  32. package/locales/it-IT/plugin.json +16 -0
  33. package/locales/it-IT/portal.json +0 -5
  34. package/locales/it-IT/tool.json +18 -0
  35. package/locales/ja-JP/models.json +24 -15
  36. package/locales/ja-JP/plugin.json +16 -0
  37. package/locales/ja-JP/portal.json +0 -5
  38. package/locales/ja-JP/tool.json +18 -0
  39. package/locales/ko-KR/models.json +25 -16
  40. package/locales/ko-KR/plugin.json +16 -0
  41. package/locales/ko-KR/portal.json +0 -5
  42. package/locales/ko-KR/tool.json +18 -0
  43. package/locales/nl-NL/models.json +25 -16
  44. package/locales/nl-NL/plugin.json +16 -0
  45. package/locales/nl-NL/portal.json +0 -5
  46. package/locales/nl-NL/tool.json +18 -0
  47. package/locales/pl-PL/models.json +25 -16
  48. package/locales/pl-PL/plugin.json +16 -0
  49. package/locales/pl-PL/portal.json +0 -5
  50. package/locales/pl-PL/tool.json +18 -0
  51. package/locales/pt-BR/models.json +24 -15
  52. package/locales/pt-BR/plugin.json +16 -0
  53. package/locales/pt-BR/portal.json +0 -5
  54. package/locales/pt-BR/tool.json +18 -0
  55. package/locales/ru-RU/models.json +25 -16
  56. package/locales/ru-RU/plugin.json +16 -0
  57. package/locales/ru-RU/portal.json +0 -5
  58. package/locales/ru-RU/tool.json +18 -0
  59. package/locales/tr-TR/models.json +25 -16
  60. package/locales/tr-TR/plugin.json +16 -0
  61. package/locales/tr-TR/portal.json +0 -5
  62. package/locales/tr-TR/tool.json +18 -0
  63. package/locales/vi-VN/models.json +24 -15
  64. package/locales/vi-VN/plugin.json +16 -0
  65. package/locales/vi-VN/portal.json +0 -5
  66. package/locales/vi-VN/tool.json +18 -0
  67. package/locales/zh-CN/models.json +30 -21
  68. package/locales/zh-CN/plugin.json +16 -0
  69. package/locales/zh-CN/portal.json +1 -6
  70. package/locales/zh-CN/tool.json +19 -1
  71. package/locales/zh-TW/models.json +23 -14
  72. package/locales/zh-TW/plugin.json +16 -0
  73. package/locales/zh-TW/portal.json +0 -5
  74. package/locales/zh-TW/tool.json +18 -0
  75. package/package.json +1 -1
  76. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/index.tsx +1 -0
  77. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/SearchTags.tsx +17 -0
  78. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Tags.tsx +8 -2
  79. package/src/config/tools.ts +16 -0
  80. package/src/database/repositories/aiInfra/index.test.ts +29 -0
  81. package/src/features/ChatInput/ActionBar/Search/index.tsx +6 -15
  82. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/ToolTitle.tsx +76 -0
  83. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/index.tsx +8 -21
  84. package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +62 -50
  85. package/src/features/PluginsUI/Render/BuiltinType/index.tsx +11 -1
  86. package/src/features/PluginsUI/Render/index.tsx +3 -0
  87. package/src/features/Portal/Plugins/Body/index.tsx +3 -7
  88. package/src/features/Portal/Plugins/Header.tsx +14 -2
  89. package/src/hooks/useAgentEnableSearch.ts +27 -0
  90. package/src/libs/agent-runtime/perplexity/index.test.ts +26 -0
  91. package/src/libs/agent-runtime/utils/streams/openai.ts +1 -1
  92. package/src/libs/trpc/client/index.ts +1 -0
  93. package/src/libs/trpc/client/tools.ts +20 -0
  94. package/src/locales/default/plugin.ts +16 -0
  95. package/src/locales/default/portal.ts +0 -5
  96. package/src/locales/default/tool.ts +18 -0
  97. package/src/server/modules/SearXNG.ts +33 -0
  98. package/src/server/routers/lambda/message.ts +11 -0
  99. package/src/server/routers/tools/__tests__/fixtures/searXNG.ts +668 -0
  100. package/src/server/routers/tools/__tests__/search.test.ts +47 -0
  101. package/src/server/routers/tools/index.ts +3 -0
  102. package/src/server/routers/tools/search.ts +38 -0
  103. package/src/services/__tests__/__snapshots__/chat.test.ts.snap +1 -0
  104. package/src/services/_auth.ts +4 -4
  105. package/src/services/chat.ts +31 -10
  106. package/src/services/message/_deprecated.ts +4 -0
  107. package/src/services/message/client.ts +4 -0
  108. package/src/services/message/server.ts +5 -5
  109. package/src/services/message/type.ts +2 -0
  110. package/src/services/search.ts +9 -0
  111. package/src/store/aiInfra/slices/aiModel/selectors.ts +12 -5
  112. package/src/store/chat/slices/builtinTool/action.ts +121 -0
  113. package/src/store/chat/slices/builtinTool/initialState.ts +2 -0
  114. package/src/store/chat/slices/builtinTool/selectors.ts +3 -0
  115. package/src/store/chat/slices/message/action.ts +11 -0
  116. package/src/store/chat/slices/plugin/action.test.ts +2 -2
  117. package/src/store/chat/slices/plugin/action.ts +2 -2
  118. package/src/store/tool/selectors/tool.ts +5 -12
  119. package/src/store/tool/slices/builtin/selectors.ts +1 -1
  120. package/src/store/user/slices/modelList/action.ts +6 -0
  121. package/src/store/user/slices/modelList/selectors/keyVaults.ts +1 -0
  122. package/src/tools/index.ts +7 -0
  123. package/src/tools/portals.ts +6 -1
  124. package/src/tools/renders.ts +3 -0
  125. package/src/{features/Portal/Plugins → tools/web-browsing/Portal}/Footer.tsx +13 -10
  126. package/src/tools/web-browsing/Portal/ResultList/SearchItem/CategoryAvatar.tsx +70 -0
  127. package/src/tools/web-browsing/Portal/ResultList/SearchItem/TitleExtra.tsx +38 -0
  128. package/src/tools/web-browsing/Portal/ResultList/SearchItem/Video.tsx +135 -0
  129. package/src/tools/web-browsing/Portal/ResultList/SearchItem/index.tsx +91 -0
  130. package/src/tools/web-browsing/Portal/ResultList/index.tsx +21 -0
  131. package/src/tools/web-browsing/Portal/index.tsx +65 -0
  132. package/src/tools/web-browsing/Render/ConfigForm/Form.tsx +110 -0
  133. package/src/tools/web-browsing/Render/ConfigForm/SearchXNGIcon.tsx +20 -0
  134. package/src/tools/web-browsing/Render/ConfigForm/index.tsx +67 -0
  135. package/src/tools/web-browsing/Render/ConfigForm/style.tsx +63 -0
  136. package/src/tools/web-browsing/Render/SearchQuery/SearchView.tsx +88 -0
  137. package/src/tools/web-browsing/Render/SearchQuery/index.tsx +61 -0
  138. package/src/tools/web-browsing/Render/SearchResult/SearchResultItem.tsx +72 -0
  139. package/src/tools/web-browsing/Render/SearchResult/ShowMore.tsx +68 -0
  140. package/src/tools/web-browsing/Render/SearchResult/index.tsx +105 -0
  141. package/src/tools/web-browsing/Render/index.tsx +57 -0
  142. package/src/tools/web-browsing/components/EngineAvatar.tsx +32 -0
  143. package/src/tools/web-browsing/components/SearchBar.tsx +134 -0
  144. package/src/tools/web-browsing/const.ts +11 -0
  145. package/src/tools/web-browsing/index.ts +102 -0
  146. package/src/types/message/chat.ts +1 -0
  147. package/src/types/message/tools.ts +10 -0
  148. package/src/types/tool/builtin.ts +2 -0
  149. package/src/types/tool/search.ts +38 -0
  150. package/src/types/user/settings/keyVaults.ts +8 -1
  151. package/src/utils/toolManifest.ts +20 -0
@@ -2,6 +2,7 @@ import { LobeBuiltinTool } from '@/types/tool';
2
2
 
3
3
  import { ArtifactsManifest } from './artifacts';
4
4
  import { DalleManifest } from './dalle';
5
+ import { WebBrowsingManifest } from './web-browsing';
5
6
 
6
7
  export const builtinTools: LobeBuiltinTool[] = [
7
8
  {
@@ -14,4 +15,10 @@ export const builtinTools: LobeBuiltinTool[] = [
14
15
  manifest: DalleManifest,
15
16
  type: 'builtin',
16
17
  },
18
+ {
19
+ hidden: true,
20
+ identifier: WebBrowsingManifest.identifier,
21
+ manifest: WebBrowsingManifest,
22
+ type: 'builtin',
23
+ },
17
24
  ];
@@ -1,3 +1,8 @@
1
1
  import { BuiltinPortal } from '@/types/tool';
2
2
 
3
- export const BuiltinToolsPortals: Record<string, BuiltinPortal> = {};
3
+ import { WebBrowsingManifest } from './web-browsing';
4
+ import WebBrowsing from './web-browsing/Portal';
5
+
6
+ export const BuiltinToolsPortals: Record<string, BuiltinPortal> = {
7
+ [WebBrowsingManifest.identifier]: WebBrowsing as BuiltinPortal,
8
+ };
@@ -2,7 +2,10 @@ import { BuiltinRender } from '@/types/tool';
2
2
 
3
3
  import { DalleManifest } from './dalle';
4
4
  import DalleRender from './dalle/Render';
5
+ import { WebBrowsingManifest } from './web-browsing';
6
+ import WebBrowsing from './web-browsing/Render';
5
7
 
6
8
  export const BuiltinToolsRenders: Record<string, BuiltinRender> = {
7
9
  [DalleManifest.identifier]: DalleRender as BuiltinRender,
10
+ [WebBrowsingManifest.identifier]: WebBrowsing as BuiltinRender,
8
11
  };
@@ -1,6 +1,6 @@
1
1
  import { ActionIcon, Icon } from '@lobehub/ui';
2
2
  import { Button } from 'antd';
3
- import { LucideBotMessageSquare, LucideNotepadText } from 'lucide-react';
3
+ import { LucideNotepadText, PlusSquareIcon } from 'lucide-react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { Flexbox } from 'react-layout-kit';
6
6
 
@@ -8,34 +8,37 @@ import { useChatStore } from '@/store/chat';
8
8
  import { chatPortalSelectors, chatSelectors } from '@/store/chat/selectors';
9
9
 
10
10
  const Footer = () => {
11
- const [messageId, isAIGenerating, triggerAIMessage, summaryPluginContent] = useChatStore((s) => [
11
+ const [messageId, isAIGenerating, triggerAIMessage, saveSearchResult] = useChatStore((s) => [
12
12
  chatPortalSelectors.toolMessageId(s),
13
13
  chatSelectors.isAIGenerating(s),
14
14
  s.triggerAIMessage,
15
- s.summaryPluginContent,
15
+ s.saveSearXNGSearchResult,
16
16
  ]);
17
- const { t } = useTranslation('portal');
17
+
18
+ const { t } = useTranslation('tool');
18
19
 
19
20
  return (
20
21
  <Flexbox gap={8} horizontal paddingBlock={12} paddingInline={12}>
21
22
  <Button
22
- icon={<Icon icon={LucideBotMessageSquare} />}
23
+ icon={<Icon icon={LucideNotepadText} />}
23
24
  loading={isAIGenerating}
24
25
  onClick={() => {
25
- triggerAIMessage({ parentId: messageId });
26
+ if (!messageId) return;
27
+
28
+ triggerAIMessage({});
26
29
  }}
27
30
  >
28
- {t('actions.genAiMessage')}
31
+ {t('search.summaryTooltip')}
29
32
  </Button>
30
33
  <ActionIcon
31
- icon={LucideNotepadText}
34
+ icon={PlusSquareIcon}
32
35
  loading={isAIGenerating}
33
36
  onClick={() => {
34
37
  if (!messageId) return;
35
38
 
36
- summaryPluginContent(messageId);
39
+ saveSearchResult(messageId);
37
40
  }}
38
- title={t('actions.summaryTooltip')}
41
+ title={t('search.createNewSearch')}
39
42
  />
40
43
  </Flexbox>
41
44
  );
@@ -0,0 +1,70 @@
1
+ import { Avatar, Icon } from '@lobehub/ui';
2
+ import { useTheme } from 'antd-style';
3
+ import {
4
+ LucideAtom,
5
+ LucideClapperboard,
6
+ LucideFiles,
7
+ LucideImages,
8
+ LucideLaptop,
9
+ LucideMusic,
10
+ LucideNewspaper,
11
+ LucideShoppingBag,
12
+ LucideTextSearch,
13
+ LucideUserRound,
14
+ } from 'lucide-react';
15
+ import { memo, useMemo } from 'react';
16
+
17
+ interface CategoryAvatarProps {
18
+ category: string;
19
+ size?: number;
20
+ }
21
+
22
+ const CategoryAvatar = memo<CategoryAvatarProps>(({ category, size = 24 }) => {
23
+ const theme = useTheme();
24
+
25
+ const categoryIcon = useMemo(() => {
26
+ switch (category) {
27
+ default:
28
+ case 'general': {
29
+ return LucideTextSearch;
30
+ }
31
+ case 'videos': {
32
+ return LucideClapperboard;
33
+ }
34
+ case 'images': {
35
+ return LucideImages;
36
+ }
37
+ case 'files': {
38
+ return LucideFiles;
39
+ }
40
+ case 'music': {
41
+ return LucideMusic;
42
+ }
43
+ case 'shopping': {
44
+ return LucideShoppingBag;
45
+ }
46
+ case 'social': {
47
+ return LucideUserRound;
48
+ }
49
+ case 'it': {
50
+ return LucideLaptop;
51
+ }
52
+ case 'news': {
53
+ return LucideNewspaper;
54
+ }
55
+ case 'science': {
56
+ return LucideAtom;
57
+ }
58
+ }
59
+ }, [category]);
60
+
61
+ return (
62
+ <Avatar
63
+ avatar={<Icon icon={categoryIcon} style={{ color: theme.colorTextSecondary }} />}
64
+ background={theme.colorFillTertiary}
65
+ size={size}
66
+ />
67
+ );
68
+ });
69
+
70
+ export default CategoryAvatar;
@@ -0,0 +1,38 @@
1
+ import { Tooltip } from '@lobehub/ui';
2
+ import { Tag, Typography } from 'antd';
3
+ import { memo } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import CategoryAvatar from './CategoryAvatar';
8
+
9
+ interface TitleExtraProps {
10
+ category: string;
11
+ highlight?: boolean;
12
+ score: number;
13
+ }
14
+
15
+ const TitleExtra = memo<TitleExtraProps>(({ category, score, highlight }) => {
16
+ const { t } = useTranslation('tool');
17
+
18
+ return (
19
+ <Flexbox align={'center'} gap={4} horizontal>
20
+ <Tooltip title={t(highlight ? 'search.includedTooltip' : 'search.scoreTooltip')}>
21
+ {highlight ? (
22
+ <Tag bordered={false} color={'blue'} style={{ marginInlineEnd: 0 }}>
23
+ {score.toFixed(1)}
24
+ </Tag>
25
+ ) : (
26
+ <Typography.Text
27
+ style={{ textAlign: 'center', width: 32, wordBreak: 'keep-all' }}
28
+ type={'secondary'}
29
+ >
30
+ {score.toFixed(1)}
31
+ </Typography.Text>
32
+ )}
33
+ </Tooltip>
34
+ <CategoryAvatar category={category} />
35
+ </Flexbox>
36
+ );
37
+ });
38
+ export default TitleExtra;
@@ -0,0 +1,135 @@
1
+ import { Avatar as AntAvatar, Typography } from 'antd';
2
+ import { createStyles } from 'antd-style';
3
+ import { memo, useState } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { SearchResult } from '@/types/tool/search';
7
+
8
+ import { ENGINE_ICON_MAP } from '../../../const';
9
+ import TitleExtra from './TitleExtra';
10
+
11
+ const useStyles = createStyles(({ css, token }) => {
12
+ return {
13
+ container: css`
14
+ display: flex;
15
+ flex: 1;
16
+
17
+ padding: 8px;
18
+ border-radius: 8px;
19
+
20
+ color: initial;
21
+
22
+ &:hover {
23
+ background: ${token.colorFillTertiary};
24
+ }
25
+ `,
26
+ desc: css`
27
+ overflow: hidden;
28
+ display: -webkit-box;
29
+ -webkit-box-orient: vertical;
30
+ -webkit-line-clamp: 2;
31
+
32
+ color: ${token.colorTextTertiary};
33
+ text-overflow: ellipsis;
34
+ `,
35
+ displayLink: css`
36
+ color: ${token.colorTextQuaternary};
37
+ `,
38
+ iframe: css`
39
+ border: 1px solid ${token.colorBorder};
40
+ border-radius: 8px;
41
+ `,
42
+ title: css`
43
+ overflow: hidden;
44
+ display: -webkit-box;
45
+ -webkit-box-orient: vertical;
46
+ -webkit-line-clamp: 1;
47
+
48
+ font-size: 16px;
49
+ color: ${token.colorLink};
50
+ text-overflow: ellipsis;
51
+ `,
52
+ url: css`
53
+ overflow: hidden;
54
+ display: -webkit-box;
55
+ -webkit-box-orient: vertical;
56
+ -webkit-line-clamp: 1;
57
+
58
+ color: ${token.colorTextDescription};
59
+ text-overflow: ellipsis;
60
+ `,
61
+ };
62
+ });
63
+
64
+ interface SearchResultProps extends SearchResult {
65
+ highlight?: boolean;
66
+ }
67
+ const VideoItem = memo<SearchResultProps>(
68
+ ({ content, url, iframe_src, highlight, score, engines, title, category }) => {
69
+ const { styles, theme } = useStyles();
70
+
71
+ const [expand, setExpand] = useState(false);
72
+ return (
73
+ <Flexbox gap={12}>
74
+ <Flexbox className={styles.container} onClick={() => setExpand(!expand)}>
75
+ <Flexbox flex={1} gap={8} horizontal padding={12}>
76
+ {iframe_src && (
77
+ <Flexbox>
78
+ <iframe
79
+ // alt={title}
80
+ className={styles.iframe}
81
+ height={100}
82
+ onClick={(e) => {
83
+ e.preventDefault();
84
+ e.stopPropagation();
85
+ }}
86
+ onPlay={(e) => {
87
+ e.preventDefault();
88
+ }}
89
+ src={iframe_src}
90
+ style={{
91
+ pointerEvents: 'none',
92
+ }}
93
+ width={200}
94
+ />
95
+ </Flexbox>
96
+ )}
97
+ <Flexbox flex={1} gap={8}>
98
+ <Flexbox align={'center'} distribution={'space-between'} gap={12} horizontal>
99
+ <Flexbox align={'center'} gap={8} horizontal>
100
+ <AntAvatar.Group>
101
+ {engines.map((engine) => (
102
+ <AntAvatar
103
+ key={engine}
104
+ src={ENGINE_ICON_MAP[engine]}
105
+ style={{
106
+ background: theme.colorBgLayout,
107
+ height: 20,
108
+ padding: 3,
109
+ width: 20,
110
+ }}
111
+ />
112
+ ))}
113
+ </AntAvatar.Group>
114
+ <Flexbox className={styles.title}>{title}</Flexbox>
115
+ </Flexbox>
116
+ <TitleExtra category={category} highlight={highlight} score={score} />
117
+ </Flexbox>
118
+ <Typography.Text className={styles.url} type={'secondary'}>
119
+ {url}
120
+ </Typography.Text>
121
+ <Flexbox className={styles.desc}>{content}</Flexbox>
122
+ </Flexbox>
123
+ </Flexbox>
124
+ </Flexbox>
125
+ {expand && iframe_src && (
126
+ <Flexbox>
127
+ <iframe className={styles.iframe} height={440} src={iframe_src} width={'100%'} />
128
+ </Flexbox>
129
+ )}
130
+ </Flexbox>
131
+ );
132
+ },
133
+ );
134
+
135
+ export default VideoItem;
@@ -0,0 +1,91 @@
1
+ import { Typography } from 'antd';
2
+ import { createStyles } from 'antd-style';
3
+ import { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { SearchResult } from '@/types/tool/search';
7
+
8
+ import { EngineAvatarGroup } from '../../../components/EngineAvatar';
9
+ import TitleExtra from './TitleExtra';
10
+ import Video from './Video';
11
+
12
+ const useStyles = createStyles(({ css, token }) => {
13
+ return {
14
+ container: css`
15
+ display: flex;
16
+ flex: 1;
17
+
18
+ padding: 8px;
19
+
20
+ color: initial;
21
+
22
+ border-radius: 8px;
23
+
24
+ &:hover {
25
+ background: ${token.colorFillTertiary};
26
+ }
27
+ `,
28
+ desc: css`
29
+ overflow: hidden;
30
+
31
+ display: -webkit-box;
32
+ -webkit-box-orient: vertical;
33
+
34
+ color: ${token.colorTextTertiary};
35
+ text-overflow: ellipsis;
36
+
37
+ -webkit-line-clamp: 2;
38
+ `,
39
+ displayLink: css`
40
+ color: ${token.colorTextQuaternary};
41
+ `,
42
+ title: css`
43
+ font-size: 16px;
44
+ color: ${token.colorLink};
45
+ `,
46
+ url: css`
47
+ overflow: hidden;
48
+ { /* stylelint-disable-line */ }
49
+ display: -webkit-box;
50
+ -webkit-box-orient: vertical;
51
+
52
+ color: ${token.colorTextDescription};
53
+ text-overflow: ellipsis;
54
+
55
+ -webkit-line-clamp: 1;
56
+ `,
57
+ };
58
+ });
59
+
60
+ interface SearchResultProps extends SearchResult {
61
+ highlight?: boolean;
62
+ }
63
+
64
+ const SearchItem = memo<SearchResultProps>((props) => {
65
+ const { content, url, score, engines, title, category } = props;
66
+ const { styles } = useStyles();
67
+
68
+ if (category === 'videos') return <Video {...props} />;
69
+
70
+ return (
71
+ <a className={styles.container} href={url!} rel="noreferrer" target={'_blank'}>
72
+ <Flexbox distribution={'space-between'} flex={1} gap={8} padding={12}>
73
+ <Flexbox gap={8}>
74
+ <Flexbox align={'center'} distribution={'space-between'} horizontal>
75
+ <Flexbox align={'center'} gap={8} horizontal>
76
+ <EngineAvatarGroup engines={engines} />
77
+ <Flexbox className={styles.title}>{title}</Flexbox>
78
+ </Flexbox>
79
+ <TitleExtra category={category} highlight={props.highlight} score={score} />
80
+ </Flexbox>
81
+ <Typography.Text className={styles.url} type={'secondary'}>
82
+ {url}
83
+ </Typography.Text>
84
+ <Flexbox className={styles.desc}>{content}</Flexbox>
85
+ </Flexbox>
86
+ </Flexbox>
87
+ </a>
88
+ );
89
+ });
90
+
91
+ export default SearchItem;
@@ -0,0 +1,21 @@
1
+ import React, { memo, useCallback } from 'react';
2
+ import { Virtuoso } from 'react-virtuoso';
3
+
4
+ import { SearchResult } from '@/types/tool/search';
5
+
6
+ import Item from './SearchItem';
7
+
8
+ interface ResultListProps {
9
+ dataSources: SearchResult[];
10
+ }
11
+
12
+ const ResultList = memo<ResultListProps>(({ dataSources }) => {
13
+ const itemContent = useCallback(
14
+ (index: number, result: SearchResult) => <Item {...result} highlight={index < 5} />,
15
+ [],
16
+ );
17
+
18
+ return <Virtuoso data={dataSources} height={'100%'} itemContent={itemContent} />;
19
+ });
20
+
21
+ export default ResultList;
@@ -0,0 +1,65 @@
1
+ import { Skeleton } from 'antd';
2
+ import { uniq } from 'lodash-es';
3
+ import { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { useChatStore } from '@/store/chat';
7
+ import { chatToolSelectors } from '@/store/chat/selectors';
8
+ import { SearchResponse } from '@/types/tool/search';
9
+
10
+ import SearchBar from '../components/SearchBar';
11
+ import Footer from './Footer';
12
+ import ResultList from './ResultList';
13
+
14
+ interface SearchArguments {
15
+ query: string;
16
+ searchEngine?: string[];
17
+ }
18
+
19
+ interface InspectorUIProps<T = Record<string, any>, S = any> {
20
+ arguments: T;
21
+ identifier: string;
22
+ messageId: string;
23
+ state: S;
24
+ }
25
+
26
+ const Inspector = memo<InspectorUIProps<SearchArguments, SearchResponse>>(
27
+ ({ arguments: args, messageId, state }) => {
28
+ const engines = uniq((state.results || []).map((result) => result.engine));
29
+ const defaultEngines = engines.length > 0 ? engines : args.searchEngine || [];
30
+ const loading = useChatStore(chatToolSelectors.isSearXNGSearching(messageId));
31
+
32
+ return (
33
+ <Flexbox gap={12} height={'100%'}>
34
+ <SearchBar
35
+ aiSummary={false}
36
+ defaultEngines={defaultEngines}
37
+ defaultQuery={args.query}
38
+ messageId={messageId}
39
+ tooltip={false}
40
+ />
41
+ {loading ? (
42
+ <Flexbox gap={16} paddingBlock={16} paddingInline={12}>
43
+ {[1, 2, 3, 4, 6].map((id) => (
44
+ <Skeleton
45
+ active
46
+ key={id}
47
+ paragraph={{ rows: 3, width: `${(id % 4) + 5}0%` }}
48
+ title={false}
49
+ />
50
+ ))}
51
+ </Flexbox>
52
+ ) : (
53
+ <>
54
+ <Flexbox height={'100%'} width={'100%'}>
55
+ <ResultList dataSources={state.results} />
56
+ </Flexbox>
57
+ <Footer />
58
+ </>
59
+ )}
60
+ </Flexbox>
61
+ );
62
+ },
63
+ );
64
+
65
+ export default Inspector;
@@ -0,0 +1,110 @@
1
+ import { Icon } from '@lobehub/ui';
2
+ import { Button } from 'antd';
3
+ import { KeyRoundIcon, Loader2Icon } from 'lucide-react';
4
+ import { memo, useMemo, useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Center, Flexbox } from 'react-layout-kit';
7
+
8
+ import { FormInput, FormPassword } from '@/components/FormInput';
9
+ import { useChatStore } from '@/store/chat';
10
+ import { useUserStore } from '@/store/user';
11
+ import { keyVaultsConfigSelectors } from '@/store/user/selectors';
12
+
13
+ import SearchXNGIcon from './SearchXNGIcon';
14
+ import { FormAction } from './style';
15
+
16
+ interface ProviderApiKeyFormProps {
17
+ id: string;
18
+ provider: string;
19
+ }
20
+
21
+ const Form = memo<ProviderApiKeyFormProps>(({ provider, id }) => {
22
+ const { t } = useTranslation('plugin');
23
+
24
+ const [apiKey, baseURL, setConfig] = useUserStore((s) => [
25
+ keyVaultsConfigSelectors.getVaultByProvider(provider as any)(s)?.apiKey,
26
+ keyVaultsConfigSelectors.getVaultByProvider(provider as any)(s)?.baseURL,
27
+ s.updateKeyVaultSettings,
28
+ ]);
29
+
30
+ const [showKey, setShow] = useState(false);
31
+
32
+ const [resend, deleteMessage] = useChatStore((s) => [s.reInvokeToolMessage, s.deleteMessage]);
33
+
34
+ const [loading, setLoading] = useState(false);
35
+
36
+ const avatar = useMemo(() => {
37
+ switch (provider) {
38
+ default: {
39
+ return <SearchXNGIcon />;
40
+ }
41
+ }
42
+ }, [provider]);
43
+
44
+ return (
45
+ <Center gap={16} style={{ width: 400 }}>
46
+ <FormAction
47
+ avatar={avatar}
48
+ description={t('search.searchxng.description')}
49
+ title={t('search.searchxng.title')}
50
+ >
51
+ <FormInput
52
+ onChange={(value) => {
53
+ setConfig(provider, { baseURL: value });
54
+ }}
55
+ placeholder={'https://searxng.xxx'}
56
+ suffix={<div>{loading && <Icon icon={Loader2Icon} spin />}</div>}
57
+ value={baseURL}
58
+ />
59
+ {showKey ? (
60
+ <FormPassword
61
+ autoComplete={'new-password'}
62
+ onChange={(value) => {
63
+ setConfig(provider, { apiKey: value });
64
+ }}
65
+ placeholder={t('search.searchxng.keyPlaceholder')}
66
+ suffix={<div>{loading && <Icon icon={Loader2Icon} spin />}</div>}
67
+ value={apiKey}
68
+ />
69
+ ) : (
70
+ <Button
71
+ block
72
+ icon={<Icon icon={KeyRoundIcon} />}
73
+ onClick={() => {
74
+ setShow(true);
75
+ }}
76
+ type={'text'}
77
+ >
78
+ {t('search.config.addKey')}
79
+ </Button>
80
+ )}
81
+ <Flexbox gap={12} width={'100%'}>
82
+ <Button
83
+ block
84
+ disabled={loading}
85
+ onClick={async () => {
86
+ setLoading(true);
87
+ resend(id).then(() => {
88
+ setLoading(false);
89
+ });
90
+ // deleteMessage(id);
91
+ }}
92
+ style={{ marginTop: 8 }}
93
+ type={'primary'}
94
+ >
95
+ {t('search.config.confirm')}
96
+ </Button>
97
+ <Button
98
+ onClick={() => {
99
+ deleteMessage(id);
100
+ }}
101
+ >
102
+ {t('search.config.close')}
103
+ </Button>
104
+ </Flexbox>
105
+ </FormAction>
106
+ </Center>
107
+ );
108
+ });
109
+
110
+ export default Form;
@@ -0,0 +1,20 @@
1
+ const SearchXNGIcon = () => {
2
+ return (
3
+ <svg height={48} viewBox="0 0 300 300" width={48} xmlns="http://www.w3.org/2000/svg">
4
+ <path
5
+ d="M121.929 66.386a50.717 50.717 0 0 0-31.18 5.052l-7.514-14.523a67.076 67.076 0 0 1 78.427 12.336 67.022 67.022 0 0 1 11.688 78.505l-14.464-7.634a50.677 50.677 0 0 0-8.837-59.357 50.71 50.71 0 0 0-28.12-14.38Z"
6
+ fill="#3050FF"
7
+ />
8
+ <path
9
+ d="M114.491 34.702c-45.165 0-81.78 36.603-81.78 81.755 0 45.153 36.615 81.756 81.78 81.756 45.166 0 81.78-36.603 81.78-81.756 0-45.152-36.614-81.755-81.78-81.755ZM0 116.457C0 53.244 51.26 2 114.491 2c63.232 0 114.492 51.244 114.492 114.457 0 63.214-51.26 114.458-114.492 114.458C51.26 230.915 0 179.671 0 116.457Z"
10
+ fill="#3050FF"
11
+ />
12
+ <path
13
+ d="m205.592 163.093-42.644 44.509L257.357 298 300 253.491l-94.408-90.398Z"
14
+ fill="#3050FF"
15
+ />
16
+ </svg>
17
+ );
18
+ };
19
+
20
+ export default SearchXNGIcon;