@lobehub/chat 0.26.0 → 0.27.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 (40) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/package.json +1 -1
  3. package/src/const/message.ts +2 -0
  4. package/src/locales/default/common.ts +3 -0
  5. package/src/locales/default/plugin.ts +19 -1
  6. package/src/pages/api/openai.ts +7 -4
  7. package/src/pages/api/plugins.api.ts +16 -38
  8. package/src/pages/chat/[id]/Conversation/ChatList/MessageExtra.tsx +28 -26
  9. package/src/pages/chat/[id]/Conversation/ChatList/Plugins/FunctionCall.tsx +60 -0
  10. package/src/pages/chat/[id]/Conversation/ChatList/Plugins/PluginMessage.tsx +45 -0
  11. package/src/pages/chat/[id]/Conversation/ChatList/Plugins/PluginResultRender.tsx +51 -0
  12. package/src/pages/chat/[id]/Conversation/ChatList/Plugins/style.ts +25 -0
  13. package/src/pages/chat/[id]/Conversation/ChatList/index.tsx +34 -9
  14. package/src/pages/chat/[id]/edit/AgentPlugin/index.tsx +2 -2
  15. package/src/pages/setting/index.page.tsx +11 -28
  16. package/src/pages/setting/layout.tsx +35 -0
  17. package/src/plugins/Render.ts +11 -0
  18. package/src/plugins/index.ts +7 -3
  19. package/src/plugins/searchEngine/Render.tsx +85 -0
  20. package/src/plugins/searchEngine/index.ts +4 -1
  21. package/src/plugins/searchEngine/runner.ts +7 -73
  22. package/src/plugins/searchEngine/type.ts +82 -0
  23. package/src/plugins/type.ts +17 -0
  24. package/src/plugins/weather/Render.tsx +53 -0
  25. package/src/plugins/weather/index.ts +9 -1
  26. package/src/plugins/weather/runner.ts +12 -7
  27. package/src/plugins/weather/type.ts +36 -0
  28. package/src/plugins/webCrawler/index.ts +4 -1
  29. package/src/plugins/webCrawler/runner.ts +3 -27
  30. package/src/plugins/webCrawler/type.ts +32 -0
  31. package/src/services/chatModel.ts +1 -2
  32. package/src/services/plugin.ts +30 -0
  33. package/src/store/session/slices/chat/actions/message.ts +59 -40
  34. package/src/store/session/slices/chat/selectors/utils.test.ts +3 -2
  35. package/src/store/session/slices/chat/selectors/utils.ts +4 -3
  36. package/src/types/chatMessage.ts +5 -1
  37. package/src/types/openai.ts +3 -0
  38. package/src/types/plugin.ts +4 -0
  39. package/src/utils/message.ts +16 -0
  40. package/src/pages/chat/[id]/Conversation/ChatList/FunctionMessage.tsx +0 -31
@@ -1,7 +1,11 @@
1
+ import type { PluginItem } from '@/plugins/type';
2
+
1
3
  import searchEngine from './searchEngine';
2
4
  import getWeather from './weather';
3
5
  import webCrawler from './webCrawler';
4
6
 
5
- const pluginList = [getWeather, searchEngine, webCrawler];
6
-
7
- export default pluginList;
7
+ export const PluginsMap: Record<string, PluginItem> = {
8
+ [getWeather.name]: getWeather,
9
+ [searchEngine.name]: searchEngine,
10
+ [webCrawler.name]: webCrawler,
11
+ };
@@ -0,0 +1,85 @@
1
+ import { createStyles } from 'antd-style';
2
+ import Link from 'next/link';
3
+ import { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { PluginRenderProps } from '@/plugins/type';
7
+
8
+ import { Result } from './type';
9
+
10
+ const useStyles = createStyles(({ css, token, cx }) => {
11
+ const title = css`
12
+ margin-top: 4px;
13
+ font-size: 16px;
14
+ `;
15
+ return {
16
+ container: css`
17
+ :not(:last-child) {
18
+ border-bottom: 1px solid ${token.colorBorder};
19
+ }
20
+ `,
21
+ desc: css`
22
+ color: ${token.colorTextTertiary};
23
+ `,
24
+ displayLink: css`
25
+ color: ${token.colorTextQuaternary};
26
+ `,
27
+ favicon: css`
28
+ border-radius: 50%;
29
+ `,
30
+ link: css`
31
+ &:hover {
32
+ .${cx(title)} {
33
+ text-decoration: underline;
34
+ }
35
+ }
36
+ `,
37
+ title,
38
+ };
39
+ });
40
+
41
+ const Render = memo<PluginRenderProps<Result>>(({ content }) => {
42
+ const { styles } = useStyles();
43
+ return (
44
+ <Flexbox gap={8}>
45
+ <Flexbox>
46
+ {content.map((item) => (
47
+ <Flexbox
48
+ className={styles.container}
49
+ distribution={'space-between'}
50
+ horizontal
51
+ key={item.link}
52
+ padding={12}
53
+ >
54
+ <Flexbox>
55
+ <Link className={styles.link} href={item.link!} target={'_blank'}>
56
+ <Flexbox>
57
+ <Flexbox align={'center'} gap={12} horizontal>
58
+ {item.favicon && (
59
+ <img
60
+ alt={item.title || item.link}
61
+ className={styles.favicon}
62
+ height={24}
63
+ src={item.favicon}
64
+ width={24}
65
+ />
66
+ )}
67
+ <Flexbox>
68
+ <Flexbox className={styles.desc}>{item.source}</Flexbox>
69
+ <Flexbox className={styles.displayLink}>{item.displayed_link}</Flexbox>
70
+ </Flexbox>
71
+ </Flexbox>
72
+ <Flexbox className={styles.title}>{item.title}</Flexbox>
73
+ </Flexbox>
74
+ </Link>
75
+ <Flexbox className={styles.desc}>
76
+ {item.date ? `${item.date} - ${item.content}` : item.content}
77
+ </Flexbox>
78
+ </Flexbox>
79
+ </Flexbox>
80
+ ))}
81
+ </Flexbox>
82
+ </Flexbox>
83
+ );
84
+ });
85
+ export default Render;
@@ -1,6 +1,9 @@
1
1
  import { ChatCompletionFunctions } from 'openai-edge/types/api';
2
2
 
3
+ import { PluginItem } from '@/plugins/type';
4
+
3
5
  import runner from './runner';
6
+ import { Result } from './type';
4
7
 
5
8
  const schema: ChatCompletionFunctions = {
6
9
  description: '查询搜索引擎获取信息',
@@ -17,7 +20,7 @@ const schema: ChatCompletionFunctions = {
17
20
  },
18
21
  };
19
22
 
20
- const searchEngine = {
23
+ const searchEngine: PluginItem<Result> = {
21
24
  avatar: '🔍',
22
25
  name: 'searchEngine',
23
26
  runner,
@@ -1,82 +1,14 @@
1
1
  import querystring from 'query-string';
2
2
 
3
- const BASE_URL = 'https://serpapi.com/search';
4
-
5
- const API_KEY = process.env.SERPAI_API_KEY;
6
-
7
- export type OrganicResults = OrganicResult[];
8
-
9
- export interface OrganicResult {
10
- about_page_link: string;
11
- about_page_serpapi_link: string;
12
- about_this_result: AboutThisResult;
13
- cached_page_link?: string;
14
- date?: string;
15
- displayed_link: string;
16
- favicon?: string;
17
- link: string;
18
- position: number;
19
- related_results?: RelatedResult[];
20
- rich_snippet?: RichSnippet;
21
- snippet: string;
22
- snippet_highlighted_words?: string[];
23
- source: string;
24
- thumbnail?: string;
25
- title: string;
26
- }
27
-
28
- export interface AboutThisResult {
29
- languages: string[];
30
- regions: string[];
31
- source: Source;
32
- }
3
+ import { PluginRunner } from '@/plugins/type';
33
4
 
34
- export interface Source {
35
- description: string;
36
- icon: string;
37
- security?: string;
38
- source_info_link?: string;
39
- }
5
+ import { OrganicResults, Result } from './type';
40
6
 
41
- export interface RelatedResult {
42
- about_page_link: string;
43
- about_page_serpapi_link: string;
44
- about_this_result: AboutThisResult2;
45
- cached_page_link: string;
46
- date: string;
47
- displayed_link: string;
48
- link: string;
49
- position: number;
50
- snippet: string;
51
- snippet_highlighted_words: string[];
52
- title: string;
53
- }
54
-
55
- export interface AboutThisResult2 {
56
- languages: string[];
57
- regions: string[];
58
- source: Source2;
59
- }
60
-
61
- export interface Source2 {
62
- description: string;
63
- icon: string;
64
- }
65
-
66
- export interface RichSnippet {
67
- top: Top;
68
- }
69
-
70
- export interface Top {
71
- detected_extensions: DetectedExtensions;
72
- extensions: string[];
73
- }
7
+ const BASE_URL = 'https://serpapi.com/search';
74
8
 
75
- export interface DetectedExtensions {
76
- month_ago: number;
77
- }
9
+ const API_KEY = process.env.SERPAI_API_KEY;
78
10
 
79
- const fetchResult = async ({ keywords }: { keywords: string }) => {
11
+ const fetchResult: PluginRunner<{ keywords: string }, Result> = async ({ keywords }) => {
80
12
  const params = {
81
13
  api_key: API_KEY,
82
14
  engine: 'google',
@@ -98,6 +30,8 @@ const fetchResult = async ({ keywords }: { keywords: string }) => {
98
30
  return results.map((r) => ({
99
31
  content: r.snippet,
100
32
  date: r.date,
33
+ displayed_link: r.displayed_link,
34
+ favicon: r.favicon,
101
35
  link: r.link,
102
36
  source: r.source,
103
37
  title: r.title,
@@ -0,0 +1,82 @@
1
+ export type OrganicResults = OrganicResult[];
2
+
3
+ export interface SearchItem {
4
+ content: string;
5
+ date?: string;
6
+ displayed_link?: string;
7
+ favicon?: string;
8
+ link: string;
9
+ source?: string;
10
+ title: string;
11
+ }
12
+ export type Result = SearchItem[];
13
+
14
+ interface OrganicResult {
15
+ about_page_link: string;
16
+ about_page_serpapi_link: string;
17
+ about_this_result: AboutThisResult;
18
+ cached_page_link?: string;
19
+ date?: string;
20
+ displayed_link: string;
21
+ favicon?: string;
22
+ link: string;
23
+ position: number;
24
+ related_results?: RelatedResult[];
25
+ rich_snippet?: RichSnippet;
26
+ snippet: string;
27
+ snippet_highlighted_words?: string[];
28
+ source: string;
29
+ thumbnail?: string;
30
+ title: string;
31
+ }
32
+
33
+ interface AboutThisResult {
34
+ languages: string[];
35
+ regions: string[];
36
+ source: Source;
37
+ }
38
+
39
+ interface Source {
40
+ description: string;
41
+ icon: string;
42
+ security?: string;
43
+ source_info_link?: string;
44
+ }
45
+
46
+ interface RelatedResult {
47
+ about_page_link: string;
48
+ about_page_serpapi_link: string;
49
+ about_this_result: AboutThisResult2;
50
+ cached_page_link: string;
51
+ date: string;
52
+ displayed_link: string;
53
+ link: string;
54
+ position: number;
55
+ snippet: string;
56
+ snippet_highlighted_words: string[];
57
+ title: string;
58
+ }
59
+
60
+ interface AboutThisResult2 {
61
+ languages: string[];
62
+ regions: string[];
63
+ source: Source2;
64
+ }
65
+
66
+ interface Source2 {
67
+ description: string;
68
+ icon: string;
69
+ }
70
+
71
+ interface RichSnippet {
72
+ top: Top;
73
+ }
74
+
75
+ interface Top {
76
+ detected_extensions: DetectedExtensions;
77
+ extensions: string[];
78
+ }
79
+
80
+ interface DetectedExtensions {
81
+ month_ago: number;
82
+ }
@@ -0,0 +1,17 @@
1
+ import { ChatCompletionFunctions } from 'openai-edge/types/api';
2
+ import { ReactNode } from 'react';
3
+
4
+ export interface PluginItem<Result = any, RunnerParams = any> {
5
+ avatar: string;
6
+ name: string;
7
+ runner: PluginRunner<RunnerParams, Result>;
8
+ schema: ChatCompletionFunctions;
9
+ }
10
+ export type PluginRender = (props: PluginRenderProps) => ReactNode;
11
+
12
+ export type PluginRunner<Params = object, Result = any> = (params: Params) => Promise<Result>;
13
+
14
+ export interface PluginRenderProps<Result = any> {
15
+ content: Result;
16
+ name: string;
17
+ }
@@ -0,0 +1,53 @@
1
+ import { Table } from 'antd';
2
+ import { createStyles } from 'antd-style';
3
+ import { memo } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import { PluginRenderProps } from '@/plugins/type';
8
+
9
+ import { WeatherResult } from './type';
10
+
11
+ const useStyles = createStyles(({ css, token }) => ({
12
+ time: css`
13
+ color: ${token.colorTextQuaternary};
14
+ `,
15
+ title: css`
16
+ font-weight: bold;
17
+ `,
18
+ }));
19
+
20
+ const Render = memo<PluginRenderProps<WeatherResult>>(({ content }) => {
21
+ const { t } = useTranslation('plugin');
22
+
23
+ const { styles } = useStyles();
24
+ return (
25
+ <div>
26
+ {content.map((item, index) => (
27
+ <Flexbox gap={8} key={`${item.city}-${index}`}>
28
+ <Flexbox align={'center'} distribution={'space-between'} horizontal>
29
+ <div className={styles.title}>{t('realtimeWeather.title', { city: item.city })}</div>
30
+ <div className={styles.time}>
31
+ {t('realtimeWeather.updateAt')}: {item.reporttime}
32
+ </div>
33
+ </Flexbox>
34
+ <Table
35
+ bordered
36
+ columns={[
37
+ { dataIndex: 'date', title: t('realtimeWeather.data.date') },
38
+ { dataIndex: 'week', title: t('realtimeWeather.data.week') },
39
+ { dataIndex: 'dayweather', title: t('realtimeWeather.data.dayweather') },
40
+ { dataIndex: 'daytemp_float', title: t('realtimeWeather.data.daytemp_float') },
41
+ { dataIndex: 'daywind', title: t('realtimeWeather.data.daywind') },
42
+ { dataIndex: 'nightweather', title: t('realtimeWeather.data.nightweather') },
43
+ { dataIndex: 'nighttemp_float', title: t('realtimeWeather.data.nighttemp_float') },
44
+ ]}
45
+ dataSource={item.casts}
46
+ pagination={false}
47
+ />
48
+ </Flexbox>
49
+ ))}
50
+ </div>
51
+ );
52
+ });
53
+ export default Render;
@@ -1,3 +1,6 @@
1
+ import { PluginItem } from '@/plugins/type';
2
+ import { WeatherResult } from '@/plugins/weather/type';
3
+
1
4
  import runner from './runner';
2
5
 
3
6
  const schema = {
@@ -15,6 +18,11 @@ const schema = {
15
18
  },
16
19
  };
17
20
 
18
- const getWeather = { avatar: '☂️', name: 'realtimeWeather', runner, schema };
21
+ const getWeather: PluginItem<WeatherResult> = {
22
+ avatar: '☂️',
23
+ name: 'realtimeWeather',
24
+ runner,
25
+ schema,
26
+ };
19
27
 
20
28
  export default getWeather;
@@ -1,30 +1,35 @@
1
+ import { PluginRunner } from '@/plugins/type';
2
+
3
+ import { Response, WeatherParams, WeatherResult } from './type';
4
+
1
5
  const weatherBaseURL = 'https://restapi.amap.com/v3/weather/weatherInfo';
2
6
 
3
7
  const citySearchURL = 'https://restapi.amap.com/v3/config/district';
4
8
 
5
9
  const KEY = process.env.GAODE_WEATHER_KEY;
6
10
 
7
- interface WeatherParams {
8
- city: string;
9
- extensions?: 'base' | 'all';
10
- }
11
-
12
11
  const fetchCityCode = async (keywords: string): Promise<string> => {
13
12
  const URL = `${citySearchURL}?keywords=${keywords}&subdistrict=0&extensions=base&key=${KEY}`;
14
13
  const res = await fetch(URL);
15
14
 
16
15
  const data = await res.json();
16
+ console.log(data);
17
17
 
18
18
  return data.districts[0].adcode;
19
19
  };
20
20
 
21
- const fetchWeather = async ({ city, extensions = 'all' }: WeatherParams) => {
21
+ const fetchWeather: PluginRunner<WeatherParams, WeatherResult> = async ({
22
+ city,
23
+ extensions = 'all',
24
+ }) => {
22
25
  const cityCode = await fetchCityCode(city);
23
26
 
24
27
  const URL = `${weatherBaseURL}?city=${cityCode}&extensions=${extensions}&key=${KEY}`;
25
28
  const res = await fetch(URL);
26
29
 
27
- return await res.json();
30
+ const data: Response = await res.json();
31
+
32
+ return data.forecasts;
28
33
  };
29
34
 
30
35
  export default fetchWeather;
@@ -0,0 +1,36 @@
1
+ export interface WeatherParams {
2
+ city: string;
3
+ extensions?: 'base' | 'all';
4
+ }
5
+ export type WeatherResult = Forecast[];
6
+
7
+ export interface Response {
8
+ count: string;
9
+ forecasts: Forecast[];
10
+ info: string;
11
+ infocode: string;
12
+ status: string;
13
+ }
14
+
15
+ export interface Forecast {
16
+ adcode: string;
17
+ casts: Cast[];
18
+ city: string;
19
+ province: string;
20
+ reporttime: string;
21
+ }
22
+
23
+ export interface Cast {
24
+ date: string;
25
+ daypower: string;
26
+ daytemp: string;
27
+ daytemp_float: string;
28
+ dayweather: string;
29
+ daywind: string;
30
+ nightpower: string;
31
+ nighttemp: string;
32
+ nighttemp_float: string;
33
+ nightweather: string;
34
+ nightwind: string;
35
+ week: string;
36
+ }
@@ -1,4 +1,7 @@
1
+ import { PluginItem } from '@/plugins/type';
2
+
1
3
  import runner from './runner';
4
+ import { Result } from './type';
2
5
 
3
6
  const schema = {
4
7
  description: '提取网页内容并总结',
@@ -15,6 +18,6 @@ const schema = {
15
18
  },
16
19
  };
17
20
 
18
- const getWeather = { avatar: '🕸', name: 'websiteCrawler', runner, schema };
21
+ const getWeather: PluginItem<Result> = { avatar: '🕸', name: 'websiteCrawler', runner, schema };
19
22
 
20
23
  export default getWeather;
@@ -1,36 +1,12 @@
1
- export type DataResults = DataItem[];
1
+ import { PluginRunner } from '@/plugins/type';
2
2
 
3
- export interface DataItem {
4
- crawl: Crawl;
5
- markdown: string;
6
- metadata: Metadata;
7
- screenshotUrl: any;
8
- text: string;
9
- url: string;
10
- }
11
-
12
- export interface Crawl {
13
- depth: number;
14
- httpStatusCode: number;
15
- loadedTime: string;
16
- loadedUrl: string;
17
- referrerUrl: string;
18
- }
19
-
20
- export interface Metadata {
21
- author: any;
22
- canonicalUrl: string;
23
- description: string;
24
- keywords: string;
25
- languageCode: string;
26
- title: string;
27
- }
3
+ import { DataResults, Result } from './type';
28
4
 
29
5
  const BASE_URL =
30
6
  'https://api.apify.com/v2/acts/apify~website-content-crawler/run-sync-get-dataset-items';
31
7
  const token = process.env.APIFY_API_KEY;
32
8
 
33
- const runner = async ({ url }: { url: string }) => {
9
+ const runner: PluginRunner<{ url: string }, Result> = async ({ url }) => {
34
10
  // Prepare Actor input
35
11
  const input = {
36
12
  aggressivePrune: false,
@@ -0,0 +1,32 @@
1
+ export type DataResults = DataItem[];
2
+
3
+ export type Result = {
4
+ content?: string;
5
+ title?: string;
6
+ url?: string;
7
+ };
8
+ export interface DataItem {
9
+ crawl: Crawl;
10
+ markdown: string;
11
+ metadata: Metadata;
12
+ screenshotUrl: any;
13
+ text: string;
14
+ url: string;
15
+ }
16
+
17
+ export interface Crawl {
18
+ depth: number;
19
+ httpStatusCode: number;
20
+ loadedTime: string;
21
+ loadedUrl: string;
22
+ referrerUrl: string;
23
+ }
24
+
25
+ export interface Metadata {
26
+ author: any;
27
+ canonicalUrl: string;
28
+ description: string;
29
+ keywords: string;
30
+ languageCode: string;
31
+ title: string;
32
+ }
@@ -9,7 +9,6 @@ import { URLS } from './url';
9
9
 
10
10
  interface FetchChatModelOptions {
11
11
  signal?: AbortSignal | undefined;
12
- withPlugin?: boolean;
13
12
  }
14
13
 
15
14
  /**
@@ -28,7 +27,7 @@ export const fetchChatModel = (
28
27
  params,
29
28
  );
30
29
 
31
- return fetch(options?.withPlugin ? URLS.plugins : URLS.openai, {
30
+ return fetch(URLS.openai, {
32
31
  body: JSON.stringify(payload),
33
32
  headers: {
34
33
  'Content-Type': 'application/json',
@@ -0,0 +1,30 @@
1
+ import { LOBE_CHAT_ACCESS_CODE } from '@/const/fetch';
2
+ import { useSettings } from '@/store/settings';
3
+ import { OpenAIPluginPayload } from '@/types/plugin';
4
+
5
+ import { URLS } from './url';
6
+
7
+ interface FetchChatModelOptions {
8
+ signal?: AbortSignal | undefined;
9
+ withPlugin?: boolean;
10
+ }
11
+
12
+ /**
13
+ * 专门用于对话的 fetch
14
+ */
15
+ export const fetchPlugin = async (
16
+ params: Partial<OpenAIPluginPayload>,
17
+ options?: FetchChatModelOptions,
18
+ ) => {
19
+ const res = await fetch(URLS.plugins, {
20
+ body: JSON.stringify(params),
21
+ headers: {
22
+ 'Content-Type': 'application/json',
23
+ [LOBE_CHAT_ACCESS_CODE]: useSettings.getState().settings.password || '',
24
+ },
25
+ method: 'POST',
26
+ signal: options?.signal,
27
+ });
28
+
29
+ return await res.text();
30
+ };