@lobehub/chat 1.66.5 → 1.67.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 (102) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/changelog/v1.json +18 -0
  5. package/locales/ar/models.json +9 -3
  6. package/locales/ar/plugin.json +12 -0
  7. package/locales/bg-BG/models.json +9 -3
  8. package/locales/bg-BG/plugin.json +12 -0
  9. package/locales/de-DE/models.json +9 -3
  10. package/locales/de-DE/plugin.json +12 -0
  11. package/locales/en-US/models.json +9 -3
  12. package/locales/en-US/plugin.json +12 -0
  13. package/locales/es-ES/models.json +9 -3
  14. package/locales/es-ES/plugin.json +12 -0
  15. package/locales/fa-IR/models.json +9 -3
  16. package/locales/fa-IR/plugin.json +12 -0
  17. package/locales/fr-FR/models.json +9 -3
  18. package/locales/fr-FR/plugin.json +12 -0
  19. package/locales/it-IT/models.json +9 -3
  20. package/locales/it-IT/plugin.json +12 -0
  21. package/locales/ja-JP/models.json +9 -3
  22. package/locales/ja-JP/plugin.json +12 -0
  23. package/locales/ko-KR/models.json +9 -3
  24. package/locales/ko-KR/plugin.json +12 -0
  25. package/locales/nl-NL/models.json +9 -3
  26. package/locales/nl-NL/plugin.json +12 -0
  27. package/locales/pl-PL/models.json +9 -3
  28. package/locales/pl-PL/plugin.json +12 -0
  29. package/locales/pt-BR/models.json +9 -3
  30. package/locales/pt-BR/plugin.json +12 -0
  31. package/locales/ru-RU/models.json +9 -3
  32. package/locales/ru-RU/plugin.json +12 -0
  33. package/locales/tr-TR/models.json +9 -3
  34. package/locales/tr-TR/plugin.json +12 -0
  35. package/locales/vi-VN/models.json +9 -3
  36. package/locales/vi-VN/plugin.json +12 -0
  37. package/locales/zh-CN/models.json +9 -3
  38. package/locales/zh-CN/plugin.json +12 -0
  39. package/locales/zh-TW/models.json +9 -3
  40. package/locales/zh-TW/plugin.json +12 -0
  41. package/package.json +10 -6
  42. package/packages/web-crawler/README.md +34 -0
  43. package/packages/web-crawler/package.json +13 -0
  44. package/packages/web-crawler/src/crawImpl/browserless.ts +62 -0
  45. package/packages/web-crawler/src/crawImpl/index.ts +11 -0
  46. package/packages/web-crawler/src/crawImpl/jina.ts +37 -0
  47. package/packages/web-crawler/src/crawImpl/naive.ts +84 -0
  48. package/packages/web-crawler/src/crawler.ts +66 -0
  49. package/packages/web-crawler/src/index.ts +2 -0
  50. package/packages/web-crawler/src/type.ts +42 -0
  51. package/packages/web-crawler/src/urlRules.ts +34 -0
  52. package/packages/web-crawler/src/utils/__snapshots__/htmlToMarkdown.test.ts.snap +638 -0
  53. package/packages/web-crawler/src/utils/appUrlRules.test.ts +26 -0
  54. package/packages/web-crawler/src/utils/appUrlRules.ts +40 -0
  55. package/packages/web-crawler/src/utils/errorType.ts +12 -0
  56. package/packages/web-crawler/src/utils/html/terms.html +1222 -0
  57. package/packages/web-crawler/src/utils/html/yingchao.html +1001 -0
  58. package/packages/web-crawler/src/utils/htmlToMarkdown.test.ts +35 -0
  59. package/packages/web-crawler/src/utils/htmlToMarkdown.ts +45 -0
  60. package/packages/web-crawler/tsconfig.json +20 -0
  61. package/pnpm-workspace.yaml +3 -0
  62. package/src/config/aiModels/openai.ts +29 -5
  63. package/src/database/server/models/__tests__/message.test.ts +2 -2
  64. package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +4 -35
  65. package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +1 -1
  66. package/src/features/PluginsUI/Render/BuiltinType/index.tsx +3 -0
  67. package/src/features/PluginsUI/Render/index.tsx +1 -0
  68. package/src/features/Portal/Plugins/Body/ToolRender.tsx +1 -0
  69. package/src/locales/default/plugin.ts +12 -0
  70. package/src/server/routers/tools/search.ts +23 -0
  71. package/src/services/search.ts +8 -0
  72. package/src/store/chat/slices/builtinTool/actions/searXNG.ts +50 -0
  73. package/src/store/chat/slices/builtinTool/initialState.ts +1 -0
  74. package/src/tools/web-browsing/Portal/PageContent/index.tsx +190 -0
  75. package/src/tools/web-browsing/Portal/PageContents/index.tsx +23 -0
  76. package/src/tools/web-browsing/Portal/{ResultList → Search/ResultList}/SearchItem/Video.tsx +1 -1
  77. package/src/tools/web-browsing/Portal/Search/index.tsx +69 -0
  78. package/src/tools/web-browsing/Portal/index.tsx +28 -64
  79. package/src/tools/web-browsing/Render/PageContent/Loading.tsx +57 -0
  80. package/src/tools/web-browsing/Render/PageContent/Result.tsx +142 -0
  81. package/src/tools/web-browsing/Render/PageContent/index.tsx +41 -0
  82. package/src/tools/web-browsing/Render/{SearchQuery → Search/SearchQuery}/SearchView.tsx +1 -1
  83. package/src/tools/web-browsing/Render/{SearchQuery → Search/SearchQuery}/index.tsx +1 -1
  84. package/src/tools/web-browsing/Render/{SearchResult → Search/SearchResult}/ShowMore.tsx +1 -1
  85. package/src/tools/web-browsing/Render/Search/index.tsx +62 -0
  86. package/src/tools/web-browsing/Render/index.tsx +35 -44
  87. package/src/tools/web-browsing/index.ts +43 -47
  88. package/src/tools/web-browsing/systemRole.ts +109 -0
  89. package/src/types/tool/builtin.ts +2 -0
  90. package/src/types/tool/crawler.ts +19 -0
  91. package/src/types/tool/search.ts +1 -0
  92. /package/src/tools/web-browsing/Portal/{Footer.tsx → Search/Footer.tsx} +0 -0
  93. /package/src/tools/web-browsing/Portal/{ResultList → Search/ResultList}/SearchItem/CategoryAvatar.tsx +0 -0
  94. /package/src/tools/web-browsing/Portal/{ResultList → Search/ResultList}/SearchItem/TitleExtra.tsx +0 -0
  95. /package/src/tools/web-browsing/Portal/{ResultList → Search/ResultList}/SearchItem/index.tsx +0 -0
  96. /package/src/tools/web-browsing/Portal/{ResultList → Search/ResultList}/index.tsx +0 -0
  97. /package/src/tools/web-browsing/Render/{ConfigForm → Search/ConfigForm}/Form.tsx +0 -0
  98. /package/src/tools/web-browsing/Render/{ConfigForm → Search/ConfigForm}/SearchXNGIcon.tsx +0 -0
  99. /package/src/tools/web-browsing/Render/{ConfigForm → Search/ConfigForm}/index.tsx +0 -0
  100. /package/src/tools/web-browsing/Render/{ConfigForm → Search/ConfigForm}/style.tsx +0 -0
  101. /package/src/tools/web-browsing/Render/{SearchResult → Search/SearchResult}/SearchResultItem.tsx +0 -0
  102. /package/src/tools/web-browsing/Render/{SearchResult → Search/SearchResult}/index.tsx +0 -0
@@ -0,0 +1,35 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { expect } from 'vitest';
4
+
5
+ import { FilterOptions } from '../type';
6
+ import { htmlToMarkdown } from './htmlToMarkdown';
7
+
8
+ interface TestItem {
9
+ file: string;
10
+ url: string;
11
+ filterOptions?: FilterOptions;
12
+ }
13
+ const list: TestItem[] = [
14
+ {
15
+ file: 'terms.html',
16
+ url: 'https://lobehub.com/terms',
17
+ },
18
+ {
19
+ file: 'yingchao.html',
20
+ url: 'https://www.qiumiwu.com/standings/yingchao',
21
+ filterOptions: { pureText: true, enableReadability: false },
22
+ },
23
+ ];
24
+
25
+ describe('htmlToMarkdown', () => {
26
+ list.forEach((item) => {
27
+ it(`should transform ${item.file} to markdown`, () => {
28
+ const html = readFileSync(path.join(__dirname, `./html/${item.file}`), { encoding: 'utf8' });
29
+
30
+ const data = htmlToMarkdown(html, { url: item.url, filterOptions: item.filterOptions || {} });
31
+
32
+ expect(data).toMatchSnapshot();
33
+ });
34
+ });
35
+ });
@@ -0,0 +1,45 @@
1
+ import { Readability } from '@mozilla/readability';
2
+ import { Window } from 'happy-dom';
3
+ import { NodeHtmlMarkdown, type TranslatorConfigObject } from 'node-html-markdown';
4
+
5
+ import { FilterOptions } from '../type';
6
+
7
+ export const htmlToMarkdown = (
8
+ html: string,
9
+ { url, filterOptions }: { filterOptions: FilterOptions; url: string },
10
+ ) => {
11
+ const window = new Window({ url });
12
+
13
+ const document = window.document;
14
+ document.body.innerHTML = html;
15
+
16
+ // @ts-expect-error reason: Readability expects a Document type
17
+ const parsedContent = new Readability(document).parse();
18
+
19
+ const useReadability = filterOptions.enableReadability ?? true;
20
+
21
+ let htmlNode = html;
22
+
23
+ if (useReadability && parsedContent?.content) {
24
+ htmlNode = parsedContent?.content;
25
+ }
26
+
27
+ const customTranslators = (
28
+ filterOptions.pureText
29
+ ? {
30
+ a: {
31
+ postprocess: (_: string, content: string) => content,
32
+ },
33
+ img: {
34
+ ignore: true,
35
+ },
36
+ }
37
+ : {}
38
+ ) as TranslatorConfigObject;
39
+
40
+ const nodeHtmlMarkdown = new NodeHtmlMarkdown({}, customTranslators);
41
+
42
+ const content = nodeHtmlMarkdown.translate(htmlNode);
43
+
44
+ return { ...parsedContent, content };
45
+ };
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "CommonJS",
4
+ "target": "ESNext",
5
+ "lib": ["dom", "dom.iterable", "esnext"],
6
+ "sourceMap": true,
7
+ "skipDefaultLibCheck": true,
8
+ "jsx": "react-jsx",
9
+ "baseUrl": ".",
10
+ "allowSyntheticDefaultImports": true,
11
+ "moduleResolution": "node",
12
+ "forceConsistentCasingInFileNames": true,
13
+ "noImplicitReturns": true,
14
+ "noUnusedLocals": true,
15
+ "resolveJsonModule": true,
16
+ "skipLibCheck": true,
17
+ "strict": true,
18
+ "types": ["vitest/globals"]
19
+ }
20
+ }
@@ -0,0 +1,3 @@
1
+ packages:
2
+ - 'packages/**'
3
+ - '.'
@@ -21,6 +21,7 @@ export const openaiChatModels: AIChatModelCard[] = [
21
21
  id: 'o3-mini',
22
22
  maxOutput: 100_000,
23
23
  pricing: {
24
+ cachedInput: 0.55,
24
25
  input: 1.1,
25
26
  output: 4.4,
26
27
  },
@@ -39,6 +40,7 @@ export const openaiChatModels: AIChatModelCard[] = [
39
40
  id: 'o1-mini',
40
41
  maxOutput: 65_536,
41
42
  pricing: {
43
+ cachedInput: 0.55,
42
44
  input: 1.1,
43
45
  output: 4.4,
44
46
  },
@@ -58,6 +60,7 @@ export const openaiChatModels: AIChatModelCard[] = [
58
60
  id: 'o1',
59
61
  maxOutput: 100_000,
60
62
  pricing: {
63
+ cachedInput: 7.5,
61
64
  input: 15,
62
65
  output: 60,
63
66
  },
@@ -82,6 +85,26 @@ export const openaiChatModels: AIChatModelCard[] = [
82
85
  releasedAt: '2024-09-12',
83
86
  type: 'chat',
84
87
  },
88
+ {
89
+ abilities: {
90
+ functionCall: true,
91
+ vision: true,
92
+ },
93
+ contextWindowTokens: 128_000,
94
+ description:
95
+ 'GPT-4.5 的研究预览版,它是我们迄今为止最大、最强大的 GPT 模型。它拥有广泛的世界知识,并能更好地理解用户意图,使其在创造性任务和自主规划方面表现出色。GPT-4.5 可接受文本和图像输入,并生成文本输出(包括结构化输出)。支持关键的开发者功能,如函数调用、批量 API 和流式输出。在需要创造性、开放式思考和对话的任务(如写作、学习或探索新想法)中,GPT-4.5 表现尤为出色。知识截止日期为 2023 年 10 月。',
96
+ displayName: 'GPT-4.5 Preview',
97
+ enabled: true,
98
+ id: 'gpt-4.5-preview',
99
+ maxOutput: 16_384,
100
+ pricing: {
101
+ cachedInput: 37.5,
102
+ input: 75,
103
+ output: 150,
104
+ },
105
+ releasedAt: '2025-02-27',
106
+ type: 'chat',
107
+ },
85
108
  {
86
109
  abilities: {
87
110
  functionCall: true,
@@ -93,8 +116,9 @@ export const openaiChatModels: AIChatModelCard[] = [
93
116
  displayName: 'GPT-4o mini',
94
117
  enabled: true,
95
118
  id: 'gpt-4o-mini',
96
- maxOutput: 16_385,
119
+ maxOutput: 16_384,
97
120
  pricing: {
121
+ cachedInput: 0.075,
98
122
  input: 0.15,
99
123
  output: 0.6,
100
124
  },
@@ -110,7 +134,6 @@ export const openaiChatModels: AIChatModelCard[] = [
110
134
  description:
111
135
  'ChatGPT-4o 是一款动态模型,实时更新以保持当前最新版本。它结合了强大的语言理解与生成能力,适合于大规模应用场景,包括客户服务、教育和技术支持。',
112
136
  displayName: 'GPT-4o 1120',
113
- enabled: true,
114
137
  id: 'gpt-4o-2024-11-20',
115
138
  pricing: {
116
139
  input: 2.5,
@@ -131,6 +154,7 @@ export const openaiChatModels: AIChatModelCard[] = [
131
154
  enabled: true,
132
155
  id: 'gpt-4o',
133
156
  pricing: {
157
+ cachedInput: 1.25,
134
158
  input: 2.5,
135
159
  output: 10,
136
160
  },
@@ -350,7 +374,7 @@ export const openaiChatModels: AIChatModelCard[] = [
350
374
  abilities: {
351
375
  functionCall: true,
352
376
  },
353
- contextWindowTokens: 16_385,
377
+ contextWindowTokens: 16_384,
354
378
  description:
355
379
  'GPT 3.5 Turbo,适用于各种文本生成和理解任务,Currently points to gpt-3.5-turbo-0125',
356
380
  displayName: 'GPT-3.5 Turbo',
@@ -365,7 +389,7 @@ export const openaiChatModels: AIChatModelCard[] = [
365
389
  abilities: {
366
390
  functionCall: true,
367
391
  },
368
- contextWindowTokens: 16_385,
392
+ contextWindowTokens: 16_384,
369
393
  description:
370
394
  'GPT 3.5 Turbo,适用于各种文本生成和理解任务,Currently points to gpt-3.5-turbo-0125',
371
395
  displayName: 'GPT-3.5 Turbo 0125',
@@ -381,7 +405,7 @@ export const openaiChatModels: AIChatModelCard[] = [
381
405
  abilities: {
382
406
  functionCall: true,
383
407
  },
384
- contextWindowTokens: 16_385,
408
+ contextWindowTokens: 16_384,
385
409
  description:
386
410
  'GPT 3.5 Turbo,适用于各种文本生成和理解任务,Currently points to gpt-3.5-turbo-0125',
387
411
  displayName: 'GPT-3.5 Turbo 1106',
@@ -1271,7 +1271,7 @@ describe('MessageModel', () => {
1271
1271
  const result = await messageModel.getHeatmaps();
1272
1272
 
1273
1273
  // 断言结果
1274
- expect(result.length).toBeGreaterThan(366);
1274
+ expect(result.length).toBeGreaterThanOrEqual(366);
1275
1275
  expect(result.length).toBeLessThan(368);
1276
1276
 
1277
1277
  // 检查两天前的数据
@@ -1389,7 +1389,7 @@ describe('MessageModel', () => {
1389
1389
  const result = await messageModel.getHeatmaps();
1390
1390
 
1391
1391
  // 断言结果
1392
- expect(result.length).toBeGreaterThan(366);
1392
+ expect(result.length).toBeGreaterThanOrEqual(366);
1393
1393
  expect(result.length).toBeLessThan(368);
1394
1394
 
1395
1395
  // 检查所有数据的 count 和 level 是否为 0
@@ -1,14 +1,9 @@
1
- import { Icon } from '@lobehub/ui';
2
- import { ConfigProvider, Empty } from 'antd';
3
- import { useTheme } from 'antd-style';
4
- import { LucideSquareArrowLeft, LucideSquareArrowRight } from 'lucide-react';
5
- import { memo, useContext, useEffect } from 'react';
6
- import { useTranslation } from 'react-i18next';
7
- import { Center, Flexbox } from 'react-layout-kit';
1
+ import { memo, useEffect } from 'react';
2
+ import { Flexbox } from 'react-layout-kit';
8
3
 
9
4
  import PluginRender from '@/features/PluginsUI/Render';
10
5
  import { useChatStore } from '@/store/chat';
11
- import { chatPortalSelectors, chatSelectors } from '@/store/chat/selectors';
6
+ import { chatSelectors } from '@/store/chat/selectors';
12
7
  import { ChatMessage } from '@/types/message';
13
8
 
14
9
  import Arguments from './Arguments';
@@ -30,14 +25,7 @@ const CustomRender = memo<CustomRenderProps>(
30
25
  setShowPluginRender,
31
26
  pluginError,
32
27
  }) => {
33
- const [loading, isMessageToolUIOpen] = useChatStore((s) => [
34
- chatSelectors.isPluginApiInvoking(id)(s),
35
- chatPortalSelectors.isPluginUIOpen(id)(s),
36
- ]);
37
- const { direction } = useContext(ConfigProvider.ConfigContext);
38
- const { t } = useTranslation('plugin');
39
-
40
- const theme = useTheme();
28
+ const [loading] = useChatStore((s) => [chatSelectors.isPluginApiInvoking(id)(s)]);
41
29
 
42
30
  useEffect(() => {
43
31
  if (!plugin?.type || loading) return;
@@ -45,25 +33,6 @@ const CustomRender = memo<CustomRenderProps>(
45
33
  setShowPluginRender(plugin?.type !== 'default');
46
34
  }, [plugin?.type, loading]);
47
35
 
48
- if (isMessageToolUIOpen)
49
- return (
50
- <Center paddingBlock={8} style={{ background: theme.colorFillQuaternary, borderRadius: 4 }}>
51
- <Empty
52
- description={t('showInPortal')}
53
- image={
54
- <Icon
55
- color={theme.colorTextQuaternary}
56
- icon={direction === 'rtl' ? LucideSquareArrowLeft : LucideSquareArrowRight}
57
- size={'large'}
58
- />
59
- }
60
- styles={{
61
- image: { height: 24 },
62
- }}
63
- />
64
- </Center>
65
- );
66
-
67
36
  if (loading) return <Arguments arguments={requestArgs} shine />;
68
37
 
69
38
  return (
@@ -1,12 +1,12 @@
1
1
  import { Suspense, memo } from 'react';
2
2
 
3
3
  import { LOADING_FLAT } from '@/const/message';
4
- import ErrorResponse from '@/features/Conversation/Messages/Assistant/Tool/Render/ErrorResponse';
5
4
  import { useChatStore } from '@/store/chat';
6
5
  import { chatSelectors } from '@/store/chat/selectors';
7
6
 
8
7
  import Arguments from './Arguments';
9
8
  import CustomRender from './CustomRender';
9
+ import ErrorResponse from './ErrorResponse';
10
10
 
11
11
  interface RenderProps {
12
12
  messageId: string;
@@ -7,6 +7,7 @@ import Loading from '../Loading';
7
7
  import { useParseContent } from '../useParseContent';
8
8
 
9
9
  export interface BuiltinTypeProps {
10
+ apiName?: string;
10
11
  arguments?: string;
11
12
  content: string;
12
13
  id: string;
@@ -25,6 +26,7 @@ const BuiltinType = memo<BuiltinTypeProps>(
25
26
  identifier,
26
27
  loading,
27
28
  pluginError,
29
+ apiName,
28
30
  }) => {
29
31
  const { isJSON, data } = useParseContent(content);
30
32
 
@@ -40,6 +42,7 @@ const BuiltinType = memo<BuiltinTypeProps>(
40
42
 
41
43
  return (
42
44
  <Render
45
+ apiName={apiName}
43
46
  args={args}
44
47
  content={data}
45
48
  identifier={identifier}
@@ -40,6 +40,7 @@ const PluginRender = memo<PluginRenderProps>(
40
40
  case 'builtin': {
41
41
  return (
42
42
  <BuiltinType
43
+ apiName={payload?.apiName}
43
44
  arguments={argumentsStr}
44
45
  content={content}
45
46
  id={id}
@@ -40,6 +40,7 @@ const ToolRender = memo(() => {
40
40
 
41
41
  return (
42
42
  <Render
43
+ apiName={plugin.apiName}
43
44
  arguments={args}
44
45
  identifier={plugin.identifier}
45
46
  messageId={messageId}
@@ -141,6 +141,18 @@ export default {
141
141
  close: '删除',
142
142
  confirm: '已完成配置并重试',
143
143
  },
144
+ crawPages: {
145
+ crawling: '链接识别中',
146
+ detail: {
147
+ preview: '预览',
148
+ raw: '原始文本',
149
+ tooLong: '文本内容过长,对话上下文仅保留前 10000 字符,超过部分不计入会话上下文',
150
+ },
151
+ meta: {
152
+ crawler: '抓取模式',
153
+ words: '字符数',
154
+ },
155
+ },
144
156
  searchxng: {
145
157
  baseURL: '请输入',
146
158
  description: '请输入 SearchXNG 的网址,即可开始联网搜索',
@@ -1,4 +1,6 @@
1
+ import { Crawler } from '@lobechat/web-crawler';
1
2
  import { TRPCError } from '@trpc/server';
3
+ import pMap from 'p-map';
2
4
  import { z } from 'zod';
3
5
 
4
6
  import { toolsEnv } from '@/config/tools';
@@ -10,6 +12,27 @@ import { SEARCH_SEARXNG_NOT_CONFIG } from '@/types/tool/search';
10
12
  const searchProcedure = isServerMode ? authedProcedure : passwordProcedure;
11
13
 
12
14
  export const searchRouter = router({
15
+ crawlPages: searchProcedure
16
+ .input(
17
+ z.object({
18
+ impls: z.string().array().optional(),
19
+ urls: z.string().array(),
20
+ }),
21
+ )
22
+ .mutation(async ({ input }) => {
23
+ const crawler = new Crawler();
24
+
25
+ const results = await pMap(
26
+ input.urls,
27
+ async (url) => {
28
+ return await crawler.crawl({ impls: input.impls, url });
29
+ },
30
+ { concurrency: 10 },
31
+ );
32
+
33
+ return { results };
34
+ }),
35
+
13
36
  query: searchProcedure
14
37
  .input(
15
38
  z.object({
@@ -4,6 +4,14 @@ class SearchService {
4
4
  search(query: string, searchEngine?: string[]) {
5
5
  return toolsClient.search.query.query({ query, searchEngine });
6
6
  }
7
+
8
+ crawlPage(url: string) {
9
+ return toolsClient.search.crawlPages.mutate({ urls: [url] });
10
+ }
11
+
12
+ crawlPages(urls: string[]) {
13
+ return toolsClient.search.crawlPages.mutate({ urls });
14
+ }
7
15
  }
8
16
 
9
17
  export const searchService = new SearchService();
@@ -13,6 +13,16 @@ import {
13
13
  import { nanoid } from '@/utils/uuid';
14
14
 
15
15
  export interface SearchAction {
16
+ crawlMultiPages: (
17
+ id: string,
18
+ params: { urls: string[] },
19
+ aiSummary?: boolean,
20
+ ) => Promise<boolean | undefined>;
21
+ crawlSinglePage: (
22
+ id: string,
23
+ params: { url: string },
24
+ aiSummary?: boolean,
25
+ ) => Promise<boolean | undefined>;
16
26
  /**
17
27
  * 重新发起搜索
18
28
  * @description 会更新插件的 arguments 参数,然后再次搜索
@@ -28,6 +38,7 @@ export interface SearchAction {
28
38
  data: SearchQuery,
29
39
  aiSummary?: boolean,
30
40
  ) => Promise<void | boolean>;
41
+ togglePageContent: (url: string) => void;
31
42
  toggleSearchLoading: (id: string, loading: boolean) => void;
32
43
  }
33
44
 
@@ -37,12 +48,47 @@ export const searchSlice: StateCreator<
37
48
  [],
38
49
  SearchAction
39
50
  > = (set, get) => ({
51
+ crawlMultiPages: async (id, params, aiSummary = true) => {
52
+ const { internal_updateMessageContent } = get();
53
+ get().toggleSearchLoading(id, true);
54
+ const response = await searchService.crawlPages(params.urls);
55
+
56
+ await get().updatePluginState(id, response);
57
+ get().toggleSearchLoading(id, false);
58
+ const { results } = response;
59
+
60
+ if (!results) return;
61
+
62
+ const content = results.map((item) =>
63
+ 'errorMessage' in item
64
+ ? item
65
+ : {
66
+ ...item.data,
67
+ // if crawl too many content
68
+ // slice the top 10000 char
69
+ content: item.data.content?.slice(0, 10_000),
70
+ },
71
+ );
72
+
73
+ await internal_updateMessageContent(id, JSON.stringify(content));
74
+
75
+ // if aiSummary is true, then trigger ai message
76
+ return aiSummary;
77
+ },
78
+
79
+ crawlSinglePage: async (id, params, aiSummary) => {
80
+ const { crawlMultiPages } = get();
81
+
82
+ return await crawlMultiPages(id, { urls: [params.url] }, aiSummary);
83
+ },
84
+
40
85
  reSearchWithSearXNG: async (id, data, options) => {
41
86
  get().toggleSearchLoading(id, true);
42
87
  await get().updatePluginArguments(id, data);
43
88
 
44
89
  await get().searchWithSearXNG(id, data, options?.aiSummary);
45
90
  },
91
+
46
92
  saveSearXNGSearchResult: async (id) => {
47
93
  const message = chatSelectors.getMessageById(id)(get());
48
94
  if (!message || !message.plugin) return;
@@ -133,6 +179,10 @@ export const searchSlice: StateCreator<
133
179
  return aiSummary;
134
180
  },
135
181
 
182
+ togglePageContent: (url) => {
183
+ set({ activePageContentUrl: url });
184
+ },
185
+
136
186
  toggleSearchLoading: (id, loading) => {
137
187
  set(
138
188
  { searchLoading: { ...get().searchLoading, [id]: loading } },
@@ -1,6 +1,7 @@
1
1
  import { FileItem } from '@/types/files';
2
2
 
3
3
  export interface ChatToolState {
4
+ activePageContentUrl?: string;
4
5
  dalleImageLoading: Record<string, boolean>;
5
6
  dalleImageMap: Record<string, FileItem>;
6
7
  searchLoading: Record<string, boolean>;