@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,190 @@
1
+ import { Alert, CopyButton, Icon, Markdown } from '@lobehub/ui';
2
+ import { Descriptions, Segmented, Typography } from 'antd';
3
+ import { createStyles } from 'antd-style';
4
+ import { ExternalLink } from 'lucide-react';
5
+ import Link from 'next/link';
6
+ import { memo, useState } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { Flexbox } from 'react-layout-kit';
9
+
10
+ import { CrawlResult } from '@/types/tool/crawler';
11
+
12
+ const useStyles = createStyles(({ token, css }) => {
13
+ return {
14
+ cardBody: css`
15
+ padding-block: 12px 8px;
16
+ padding-inline: 16px;
17
+ `,
18
+ container: css`
19
+ cursor: pointer;
20
+
21
+ overflow: hidden;
22
+
23
+ max-width: 360px;
24
+ border: 1px solid ${token.colorBorderSecondary};
25
+ border-radius: 12px;
26
+
27
+ transition: border-color 0.2s;
28
+
29
+ :hover {
30
+ border-color: ${token.colorPrimary};
31
+ }
32
+ `,
33
+ description: css`
34
+ margin-block: 0 4px !important;
35
+ color: ${token.colorTextSecondary};
36
+ `,
37
+ detailsSection: css`
38
+ padding-block: ${token.paddingSM}px;
39
+ `,
40
+ externalLink: css`
41
+ color: ${token.colorPrimary};
42
+ `,
43
+ footer: css`
44
+ padding: ${token.paddingXS}px;
45
+ border-radius: 6px;
46
+ text-align: center;
47
+ background-color: ${token.colorFillQuaternary};
48
+ `,
49
+ footerText: css`
50
+ font-size: ${token.fontSizeSM}px;
51
+ color: ${token.colorTextTertiary} !important;
52
+ `,
53
+ metaInfo: css`
54
+ display: flex;
55
+ align-items: center;
56
+ color: ${token.colorTextSecondary};
57
+ `,
58
+ sliced: css`
59
+ color: ${token.colorTextQuaternary};
60
+ `,
61
+ title: css`
62
+ overflow: hidden;
63
+ display: -webkit-box;
64
+ -webkit-box-orient: vertical;
65
+ -webkit-line-clamp: 2;
66
+
67
+ margin-block-end: 0;
68
+
69
+ font-size: 16px;
70
+ font-weight: bold;
71
+ `,
72
+ titleRow: css`
73
+ color: ${token.colorText};
74
+ `,
75
+
76
+ url: css`
77
+ color: ${token.colorTextTertiary};
78
+ `,
79
+ };
80
+ });
81
+
82
+ interface PageContentProps {
83
+ messageId: string;
84
+ result?: CrawlResult;
85
+ }
86
+
87
+ const SLICED_LIMITED = 10_000;
88
+
89
+ const PageContent = memo<PageContentProps>(({ result }) => {
90
+ const { t } = useTranslation('plugin');
91
+ const { styles } = useStyles();
92
+ const [display, setDisplay] = useState('render');
93
+
94
+ if (!result) return undefined;
95
+
96
+ const { url, title, description, content } = result.data;
97
+ return (
98
+ <Flexbox gap={24}>
99
+ <Flexbox gap={8}>
100
+ <Flexbox
101
+ align={'center'}
102
+ className={styles.titleRow}
103
+ gap={24}
104
+ horizontal
105
+ justify={'space-between'}
106
+ >
107
+ <Flexbox>
108
+ <div className={styles.title}>{title || result.originalUrl}</div>
109
+ </Flexbox>
110
+ </Flexbox>
111
+ {description && (
112
+ <Typography.Paragraph
113
+ className={styles.description}
114
+ ellipsis={{ expandable: false, rows: 4 }}
115
+ >
116
+ {description}
117
+ </Typography.Paragraph>
118
+ )}
119
+ <Flexbox align={'center'} className={styles.url} gap={4} horizontal>
120
+ {result.data.siteName && <div>{result.data.siteName} · </div>}
121
+ <Link
122
+ className={styles.url}
123
+ href={url}
124
+ onClick={(e) => e.stopPropagation()}
125
+ rel={'nofollow'}
126
+ style={{ display: 'flex', gap: 4 }}
127
+ target={'_blank'}
128
+ >
129
+ {result.originalUrl}
130
+ <Icon icon={ExternalLink} />
131
+ </Link>
132
+ </Flexbox>
133
+
134
+ <div className={styles.footer}>
135
+ <Descriptions
136
+ classNames={{
137
+ content: styles.footerText,
138
+ }}
139
+ column={2}
140
+ items={[
141
+ {
142
+ children: result.data.content?.length,
143
+ label: t('search.crawPages.meta.words'),
144
+ },
145
+ {
146
+ children: result.crawler,
147
+ label: t('search.crawPages.meta.crawler'),
148
+ },
149
+ ]}
150
+ size="small"
151
+ />
152
+ </div>
153
+ </Flexbox>
154
+ {content && (
155
+ <Flexbox gap={12} paddingBlock={'0 12px'}>
156
+ <Flexbox horizontal justify={'space-between'}>
157
+ <Segmented
158
+ onChange={(value) => setDisplay(value)}
159
+ options={[
160
+ { label: t('search.crawPages.detail.preview'), value: 'render' },
161
+ { label: t('search.crawPages.detail.raw'), value: 'raw' },
162
+ ]}
163
+ value={display}
164
+ />
165
+ <CopyButton content={content} />
166
+ </Flexbox>
167
+ {content.length > SLICED_LIMITED && (
168
+ <Alert message={t('search.crawPages.detail.tooLong')} variant={'pure'} />
169
+ )}
170
+ {display === 'render' ? (
171
+ <Markdown variant={'chat'}>{content}</Markdown>
172
+ ) : (
173
+ <div style={{ paddingBlock: '0 12px' }}>
174
+ {content.length < SLICED_LIMITED ? (
175
+ content
176
+ ) : (
177
+ <>
178
+ <span>{content.slice(0, SLICED_LIMITED)}</span>
179
+ <span className={styles.sliced}>{content.slice(SLICED_LIMITED, -1)}</span>
180
+ </>
181
+ )}
182
+ </div>
183
+ )}
184
+ </Flexbox>
185
+ )}
186
+ </Flexbox>
187
+ );
188
+ });
189
+
190
+ export default PageContent;
@@ -0,0 +1,23 @@
1
+ import { memo } from 'react';
2
+
3
+ import { useChatStore } from '@/store/chat';
4
+ import { CrawlResult } from '@/types/tool/crawler';
5
+
6
+ import PageContent from '../PageContent';
7
+
8
+ interface PageContentProps {
9
+ messageId: string;
10
+ results: CrawlResult[];
11
+ urls: string[];
12
+ }
13
+
14
+ const PageContents = memo<PageContentProps>(({ urls, messageId, results }) => {
15
+ const activePageContentUrl = useChatStore((s) => s.activePageContentUrl);
16
+
17
+ const url = urls.find((u) => u === activePageContentUrl);
18
+ const result = results.find((result) => result.originalUrl === url);
19
+
20
+ return <PageContent messageId={messageId} result={result} />;
21
+ });
22
+
23
+ export default PageContents;
@@ -5,7 +5,7 @@ import { Flexbox } from 'react-layout-kit';
5
5
 
6
6
  import { SearchResult } from '@/types/tool/search';
7
7
 
8
- import { ENGINE_ICON_MAP } from '../../../const';
8
+ import { ENGINE_ICON_MAP } from '../../../../const';
9
9
  import TitleExtra from './TitleExtra';
10
10
 
11
11
  const useStyles = createStyles(({ css, token }) => {
@@ -0,0 +1,69 @@
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 { SearchQuery, 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 InspectorUIProps {
15
+ messageId: string;
16
+ query: SearchQuery;
17
+ response: SearchResponse;
18
+ }
19
+
20
+ const Inspector = memo<InspectorUIProps>(({ query: args, messageId, response }) => {
21
+ const engines = uniq((response.results || []).map((result) => result.engine));
22
+ const defaultEngines = engines.length > 0 ? engines : args.searchEngines || [];
23
+ const loading = useChatStore(chatToolSelectors.isSearXNGSearching(messageId));
24
+
25
+ if (loading) {
26
+ return (
27
+ <Flexbox gap={12} height={'100%'}>
28
+ <SearchBar
29
+ aiSummary={false}
30
+ defaultEngines={defaultEngines}
31
+ defaultQuery={args.query}
32
+ messageId={messageId}
33
+ tooltip={false}
34
+ />
35
+
36
+ <Flexbox gap={16} paddingBlock={16} paddingInline={12}>
37
+ {[1, 2, 3, 4, 6].map((id) => (
38
+ <Skeleton
39
+ active
40
+ key={id}
41
+ paragraph={{ rows: 3, width: `${(id % 4) + 5}0%` }}
42
+ title={false}
43
+ />
44
+ ))}
45
+ </Flexbox>
46
+ </Flexbox>
47
+ );
48
+ }
49
+
50
+ return (
51
+ <Flexbox gap={0} height={'100%'}>
52
+ <Flexbox gap={12} height={'100%'}>
53
+ <SearchBar
54
+ aiSummary={false}
55
+ defaultEngines={defaultEngines}
56
+ defaultQuery={args.query}
57
+ messageId={messageId}
58
+ tooltip={false}
59
+ />
60
+ <Flexbox height={'100%'} width={'100%'}>
61
+ <ResultList dataSources={response.results} />
62
+ </Flexbox>
63
+ </Flexbox>
64
+ <Footer />
65
+ </Flexbox>
66
+ );
67
+ });
68
+
69
+ export default Inspector;
@@ -1,77 +1,41 @@
1
- import { Skeleton } from 'antd';
2
- import { uniq } from 'lodash-es';
3
1
  import { memo } from 'react';
4
- import { Flexbox } from 'react-layout-kit';
5
2
 
6
- import { useChatStore } from '@/store/chat';
7
- import { chatToolSelectors } from '@/store/chat/selectors';
8
- import { SearchResponse } from '@/types/tool/search';
3
+ import { WebBrowsingApiName } from '@/tools/web-browsing';
4
+ import { BuiltinPortalProps } from '@/types/tool';
5
+ import { CrawlPluginState } from '@/types/tool/crawler';
6
+ import { SearchQuery } from '@/types/tool/search';
9
7
 
10
- import SearchBar from '../components/SearchBar';
11
- import Footer from './Footer';
12
- import ResultList from './ResultList';
8
+ import PageContent from './PageContent';
9
+ import PageContents from './PageContents';
10
+ import Search from './Search';
13
11
 
14
- interface SearchArguments {
15
- query: string;
16
- searchEngine?: string[];
17
- }
12
+ const Inspector = memo<BuiltinPortalProps>(({ arguments: args, messageId, state, apiName }) => {
13
+ switch (apiName) {
14
+ case WebBrowsingApiName.searchWithSearXNG: {
15
+ return <Search messageId={messageId} query={args as SearchQuery} response={state} />;
16
+ }
18
17
 
19
- interface InspectorUIProps<T = Record<string, any>, S = any> {
20
- arguments: T;
21
- identifier: string;
22
- messageId: string;
23
- state: S;
24
- }
18
+ case WebBrowsingApiName.crawlSinglePage: {
19
+ const url = args.url;
20
+ const result = (state as CrawlPluginState).results.find(
21
+ (result) => result.originalUrl === url,
22
+ );
25
23
 
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));
24
+ return <PageContent messageId={messageId} result={result} />;
25
+ }
31
26
 
32
- if (loading) {
27
+ case WebBrowsingApiName.crawlMultiPages: {
33
28
  return (
34
- <Flexbox gap={12} height={'100%'}>
35
- <SearchBar
36
- aiSummary={false}
37
- defaultEngines={defaultEngines}
38
- defaultQuery={args.query}
39
- messageId={messageId}
40
- tooltip={false}
41
- />
42
-
43
- <Flexbox gap={16} paddingBlock={16} paddingInline={12}>
44
- {[1, 2, 3, 4, 6].map((id) => (
45
- <Skeleton
46
- active
47
- key={id}
48
- paragraph={{ rows: 3, width: `${(id % 4) + 5}0%` }}
49
- title={false}
50
- />
51
- ))}
52
- </Flexbox>
53
- </Flexbox>
29
+ <PageContents
30
+ messageId={messageId}
31
+ results={(state as CrawlPluginState).results}
32
+ urls={args.urls}
33
+ />
54
34
  );
55
35
  }
36
+ }
56
37
 
57
- return (
58
- <Flexbox gap={0} height={'100%'}>
59
- <Flexbox gap={12} height={'100%'}>
60
- <SearchBar
61
- aiSummary={false}
62
- defaultEngines={defaultEngines}
63
- defaultQuery={args.query}
64
- messageId={messageId}
65
- tooltip={false}
66
- />
67
- <Flexbox height={'100%'} width={'100%'}>
68
- <ResultList dataSources={state.results} />
69
- </Flexbox>
70
- </Flexbox>
71
- <Footer />
72
- </Flexbox>
73
- );
74
- },
75
- );
38
+ return null;
39
+ });
76
40
 
77
41
  export default Inspector;
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+
3
+ import { CopyButton } from '@lobehub/ui';
4
+ import { createStyles } from 'antd-style';
5
+ import Link from 'next/link';
6
+ import { memo } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { Flexbox } from 'react-layout-kit';
9
+
10
+ import { shinyTextStylish } from '@/styles/loading';
11
+
12
+ const useStyles = createStyles(({ token, css }) => {
13
+ return {
14
+ cardBody: css`
15
+ padding-block: 12px 8px;
16
+ padding-inline: 12px;
17
+ `,
18
+ container: css`
19
+ overflow: hidden;
20
+ justify-content: space-between;
21
+
22
+ max-width: 360px;
23
+ border: 1px solid ${token.colorBorderSecondary};
24
+ border-radius: 12px;
25
+ `,
26
+
27
+ footer: css`
28
+ padding-block: 8px;
29
+ padding-inline: 12px;
30
+
31
+ font-size: ${token.fontSizeSM}px;
32
+ color: ${token.colorTextTertiary};
33
+
34
+ background-color: ${token.colorFillQuaternary};
35
+ `,
36
+ shining: shinyTextStylish(token),
37
+ };
38
+ });
39
+
40
+ const LoadingCard = memo<{ url: string }>(({ url }) => {
41
+ const { t } = useTranslation('plugin');
42
+ const { styles } = useStyles();
43
+
44
+ return (
45
+ <Flexbox className={styles.container}>
46
+ <Flexbox className={styles.cardBody} horizontal>
47
+ <Link href={url} rel={'nofollow'} target={'_blank'}>
48
+ <div className={styles.shining}>{url}</div>
49
+ </Link>
50
+ <CopyButton content={url} size={'small'} />
51
+ </Flexbox>
52
+ <div className={styles.footer}>{t('search.crawPages.crawling')}</div>
53
+ </Flexbox>
54
+ );
55
+ });
56
+
57
+ export default LoadingCard;
@@ -0,0 +1,142 @@
1
+ 'use client';
2
+
3
+ import { CrawlSuccessResult } from '@lobechat/web-crawler';
4
+ import { Icon } from '@lobehub/ui';
5
+ import { Descriptions, Typography } from 'antd';
6
+ import { createStyles } from 'antd-style';
7
+ import { ExternalLink } from 'lucide-react';
8
+ import Link from 'next/link';
9
+ import { memo } from 'react';
10
+ import { useTranslation } from 'react-i18next';
11
+ import { Center, Flexbox } from 'react-layout-kit';
12
+
13
+ import { useChatStore } from '@/store/chat';
14
+ import { WebBrowsingManifest } from '@/tools/web-browsing';
15
+
16
+ const { Paragraph } = Typography;
17
+
18
+ const useStyles = createStyles(({ token, css }) => {
19
+ return {
20
+ cardBody: css`
21
+ padding-block: 12px 8px;
22
+ padding-inline: 16px;
23
+ `,
24
+ container: css`
25
+ cursor: pointer;
26
+
27
+ overflow: hidden;
28
+
29
+ max-width: 360px;
30
+ border: 1px solid ${token.colorBorderSecondary};
31
+ border-radius: 12px;
32
+
33
+ transition: border-color 0.2s;
34
+
35
+ :hover {
36
+ border-color: ${token.colorPrimary};
37
+ }
38
+ `,
39
+ description: css`
40
+ margin-block: 0 4px !important;
41
+ color: ${token.colorTextTertiary};
42
+ `,
43
+ detailsSection: css`
44
+ padding-block: ${token.paddingSM}px;
45
+ `,
46
+ externalLink: css`
47
+ color: ${token.colorTextQuaternary};
48
+
49
+ :hover {
50
+ color: ${token.colorText};
51
+ }
52
+ `,
53
+ footer: css`
54
+ padding: ${token.paddingXS}px;
55
+ text-align: center;
56
+ background-color: ${token.colorFillQuaternary};
57
+ `,
58
+ footerText: css`
59
+ font-size: ${token.fontSizeSM}px;
60
+ color: ${token.colorTextTertiary} !important;
61
+ `,
62
+ metaInfo: css`
63
+ display: flex;
64
+ align-items: center;
65
+ color: ${token.colorTextSecondary};
66
+ `,
67
+ title: css`
68
+ overflow: hidden;
69
+ display: -webkit-box;
70
+ -webkit-box-orient: vertical;
71
+ -webkit-line-clamp: 1;
72
+
73
+ margin-block-end: 0;
74
+ `,
75
+ titleRow: css`
76
+ color: ${token.colorText};
77
+ `,
78
+ };
79
+ });
80
+
81
+ interface CrawlerData {
82
+ crawler: string;
83
+ messageId: string;
84
+ originalUrl: string;
85
+ result: CrawlSuccessResult;
86
+ }
87
+
88
+ const CrawlerResultCard = memo<CrawlerData>(({ result, messageId, crawler, originalUrl }) => {
89
+ const { t } = useTranslation('plugin');
90
+ const { styles } = useStyles();
91
+ const [openToolUI, togglePageContent] = useChatStore((s) => [s.openToolUI, s.togglePageContent]);
92
+
93
+ const { url, title, description } = result;
94
+
95
+ return (
96
+ <Flexbox
97
+ className={styles.container}
98
+ justify={'space-between'}
99
+ onClick={() => {
100
+ openToolUI(messageId, WebBrowsingManifest.identifier);
101
+ togglePageContent(originalUrl);
102
+ }}
103
+ >
104
+ <Flexbox className={styles.cardBody} gap={8}>
105
+ <Flexbox align={'center'} className={styles.titleRow} horizontal justify={'space-between'}>
106
+ <Flexbox>
107
+ <div className={styles.title}>{title || originalUrl}</div>
108
+ </Flexbox>
109
+ <Link href={url} onClick={(e) => e.stopPropagation()} target={'_blank'}>
110
+ <Center className={styles.externalLink}>
111
+ <Icon icon={ExternalLink} />
112
+ </Center>
113
+ </Link>
114
+ </Flexbox>
115
+ <Paragraph className={styles.description} ellipsis={{ expandable: false, rows: 2 }}>
116
+ {description || result.content?.slice(0, 40)}
117
+ </Paragraph>
118
+ </Flexbox>
119
+ <div className={styles.footer}>
120
+ <Descriptions
121
+ classNames={{
122
+ content: styles.footerText,
123
+ }}
124
+ column={2}
125
+ items={[
126
+ {
127
+ children: result.content?.length,
128
+ label: t('search.crawPages.meta.words'),
129
+ },
130
+ {
131
+ children: crawler,
132
+ label: t('search.crawPages.meta.crawler'),
133
+ },
134
+ ]}
135
+ size="small"
136
+ />
137
+ </div>
138
+ </Flexbox>
139
+ );
140
+ });
141
+
142
+ export default CrawlerResultCard;
@@ -0,0 +1,41 @@
1
+ import { memo } from 'react';
2
+ import { Flexbox } from 'react-layout-kit';
3
+
4
+ import { CrawlPluginState } from '@/types/tool/crawler';
5
+
6
+ import Loading from './Loading';
7
+ import Result from './Result';
8
+
9
+ interface PagesContentProps {
10
+ messageId: string;
11
+ results?: CrawlPluginState['results'];
12
+ urls: string[];
13
+ }
14
+
15
+ const PagesContent = memo<PagesContentProps>(({ results, messageId, urls }) => {
16
+ if (!results || results.length === 0) {
17
+ return (
18
+ <Flexbox gap={12} horizontal>
19
+ {urls.map((url) => (
20
+ <Loading key={url} url={url} />
21
+ ))}
22
+ </Flexbox>
23
+ );
24
+ }
25
+
26
+ return (
27
+ <Flexbox gap={12} horizontal>
28
+ {results.map((result) => (
29
+ <Result
30
+ crawler={result.crawler}
31
+ key={result.originalUrl}
32
+ messageId={messageId}
33
+ originalUrl={result.originalUrl}
34
+ result={result.data}
35
+ />
36
+ ))}
37
+ </Flexbox>
38
+ );
39
+ });
40
+
41
+ export default PagesContent;
@@ -8,7 +8,7 @@ import { Flexbox } from 'react-layout-kit';
8
8
 
9
9
  import { useIsMobile } from '@/hooks/useIsMobile';
10
10
 
11
- import { EngineAvatarGroup } from '../../components/EngineAvatar';
11
+ import { EngineAvatarGroup } from '../../../components/EngineAvatar';
12
12
 
13
13
  const useStyles = createStyles(({ css, token }) => ({
14
14
  font: css`
@@ -10,7 +10,7 @@ import { useChatStore } from '@/store/chat';
10
10
  import { chatToolSelectors } from '@/store/chat/selectors';
11
11
  import { SearchQuery, SearchResponse } from '@/types/tool/search';
12
12
 
13
- import SearchBar from '../../components/SearchBar';
13
+ import SearchBar from '../../../components/SearchBar';
14
14
  import SearchView from './SearchView';
15
15
 
16
16
  interface SearchQueryViewProps {
@@ -6,7 +6,7 @@ import { Flexbox } from 'react-layout-kit';
6
6
  import { useChatStore } from '@/store/chat';
7
7
  import { WebBrowsingManifest } from '@/tools/web-browsing';
8
8
 
9
- import { EngineAvatarGroup } from '../../components/EngineAvatar';
9
+ import { EngineAvatarGroup } from '../../../components/EngineAvatar';
10
10
 
11
11
  const useStyles = createStyles(({ css, token }) => ({
12
12
  container: css`