@lobehub/chat 0.26.0 → 0.26.1
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.
- package/CHANGELOG.md +25 -0
- package/package.json +1 -1
- package/src/const/message.ts +2 -0
- package/src/locales/default/common.ts +3 -0
- package/src/locales/default/plugin.ts +19 -1
- package/src/pages/api/openai.ts +7 -4
- package/src/pages/api/plugins.api.ts +16 -38
- package/src/pages/chat/[id]/Conversation/ChatList/MessageExtra.tsx +28 -26
- package/src/pages/chat/[id]/Conversation/ChatList/Plugins/FunctionCall.tsx +60 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Plugins/PluginMessage.tsx +45 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Plugins/PluginResultRender.tsx +51 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Plugins/style.ts +25 -0
- package/src/pages/chat/[id]/Conversation/ChatList/index.tsx +34 -9
- package/src/pages/chat/[id]/edit/AgentPlugin/index.tsx +2 -2
- package/src/pages/setting/index.page.tsx +11 -28
- package/src/pages/setting/layout.tsx +35 -0
- package/src/plugins/Render.ts +8 -0
- package/src/plugins/index.ts +7 -3
- package/src/plugins/searchEngine/index.ts +4 -1
- package/src/plugins/searchEngine/runner.ts +5 -73
- package/src/plugins/searchEngine/type.ts +80 -0
- package/src/plugins/type.ts +17 -0
- package/src/plugins/weather/Render.tsx +53 -0
- package/src/plugins/weather/index.ts +9 -1
- package/src/plugins/weather/runner.ts +12 -7
- package/src/plugins/weather/type.ts +36 -0
- package/src/plugins/webCrawler/index.ts +4 -1
- package/src/plugins/webCrawler/runner.ts +3 -27
- package/src/plugins/webCrawler/type.ts +32 -0
- package/src/services/chatModel.ts +1 -2
- package/src/services/plugin.ts +30 -0
- package/src/store/session/slices/chat/actions/message.ts +59 -40
- package/src/store/session/slices/chat/selectors/utils.test.ts +3 -2
- package/src/store/session/slices/chat/selectors/utils.ts +4 -3
- package/src/types/chatMessage.ts +5 -1
- package/src/types/openai.ts +3 -0
- package/src/types/plugin.ts +4 -0
- package/src/utils/message.ts +16 -0
- package/src/pages/chat/[id]/Conversation/ChatList/FunctionMessage.tsx +0 -31
|
@@ -1,82 +1,14 @@
|
|
|
1
1
|
import querystring from 'query-string';
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
35
|
-
description: string;
|
|
36
|
-
icon: string;
|
|
37
|
-
security?: string;
|
|
38
|
-
source_info_link?: string;
|
|
39
|
-
}
|
|
5
|
+
import { OrganicResults, Result } from './type';
|
|
40
6
|
|
|
41
|
-
|
|
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
|
-
|
|
76
|
-
month_ago: number;
|
|
77
|
-
}
|
|
9
|
+
const API_KEY = process.env.SERPAI_API_KEY;
|
|
78
10
|
|
|
79
|
-
const fetchResult
|
|
11
|
+
const fetchResult: PluginRunner<{ keywords: string }, Result> = async ({ keywords }) => {
|
|
80
12
|
const params = {
|
|
81
13
|
api_key: API_KEY,
|
|
82
14
|
engine: 'google',
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export type OrganicResults = OrganicResult[];
|
|
2
|
+
|
|
3
|
+
export interface SearchItem {
|
|
4
|
+
content?: string;
|
|
5
|
+
date?: string;
|
|
6
|
+
link?: string;
|
|
7
|
+
source?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
}
|
|
10
|
+
export type Result = SearchItem[];
|
|
11
|
+
|
|
12
|
+
interface OrganicResult {
|
|
13
|
+
about_page_link: string;
|
|
14
|
+
about_page_serpapi_link: string;
|
|
15
|
+
about_this_result: AboutThisResult;
|
|
16
|
+
cached_page_link?: string;
|
|
17
|
+
date?: string;
|
|
18
|
+
displayed_link: string;
|
|
19
|
+
favicon?: string;
|
|
20
|
+
link: string;
|
|
21
|
+
position: number;
|
|
22
|
+
related_results?: RelatedResult[];
|
|
23
|
+
rich_snippet?: RichSnippet;
|
|
24
|
+
snippet: string;
|
|
25
|
+
snippet_highlighted_words?: string[];
|
|
26
|
+
source: string;
|
|
27
|
+
thumbnail?: string;
|
|
28
|
+
title: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface AboutThisResult {
|
|
32
|
+
languages: string[];
|
|
33
|
+
regions: string[];
|
|
34
|
+
source: Source;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface Source {
|
|
38
|
+
description: string;
|
|
39
|
+
icon: string;
|
|
40
|
+
security?: string;
|
|
41
|
+
source_info_link?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface RelatedResult {
|
|
45
|
+
about_page_link: string;
|
|
46
|
+
about_page_serpapi_link: string;
|
|
47
|
+
about_this_result: AboutThisResult2;
|
|
48
|
+
cached_page_link: string;
|
|
49
|
+
date: string;
|
|
50
|
+
displayed_link: string;
|
|
51
|
+
link: string;
|
|
52
|
+
position: number;
|
|
53
|
+
snippet: string;
|
|
54
|
+
snippet_highlighted_words: string[];
|
|
55
|
+
title: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface AboutThisResult2 {
|
|
59
|
+
languages: string[];
|
|
60
|
+
regions: string[];
|
|
61
|
+
source: Source2;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface Source2 {
|
|
65
|
+
description: string;
|
|
66
|
+
icon: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface RichSnippet {
|
|
70
|
+
top: Top;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface Top {
|
|
74
|
+
detected_extensions: DetectedExtensions;
|
|
75
|
+
extensions: string[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface DetectedExtensions {
|
|
79
|
+
month_ago: number;
|
|
80
|
+
}
|
|
@@ -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 = {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1
|
+
import { PluginRunner } from '@/plugins/type';
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
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(
|
|
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
|
+
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { StateCreator } from 'zustand/vanilla';
|
|
2
2
|
|
|
3
3
|
import { fetchChatModel } from '@/services/chatModel';
|
|
4
|
+
import { fetchPlugin } from '@/services/plugin';
|
|
4
5
|
import { SessionStore, agentSelectors, chatSelectors, sessionSelectors } from '@/store/session';
|
|
5
|
-
import { ChatMessage } from '@/types/chatMessage';
|
|
6
|
+
import { ChatMessage, OpenAIFunctionCall } from '@/types/chatMessage';
|
|
6
7
|
import { fetchSSE } from '@/utils/fetch';
|
|
7
8
|
import { isFunctionMessage } from '@/utils/message';
|
|
8
9
|
import { nanoid } from '@/utils/uuid';
|
|
@@ -42,8 +43,7 @@ export interface ChatMessageAction {
|
|
|
42
43
|
generateMessage: (
|
|
43
44
|
messages: ChatMessage[],
|
|
44
45
|
assistantMessageId: string,
|
|
45
|
-
|
|
46
|
-
) => Promise<{ isFunctionCall: boolean; output: string }>;
|
|
46
|
+
) => Promise<{ isFunctionCall: boolean }>;
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* 实际获取 AI 响应
|
|
@@ -63,6 +63,7 @@ export interface ChatMessageAction {
|
|
|
63
63
|
* @param text - 消息文本
|
|
64
64
|
*/
|
|
65
65
|
sendMessage: (text: string) => Promise<void>;
|
|
66
|
+
triggerFunctionCall: (id: string) => Promise<void>;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
export const chatMessage: StateCreator<
|
|
@@ -108,16 +109,13 @@ export const chatMessage: StateCreator<
|
|
|
108
109
|
get().dispatchSession({ chats, id: activeId, type: 'updateSessionChat' });
|
|
109
110
|
},
|
|
110
111
|
|
|
111
|
-
generateMessage: async (messages, assistantId
|
|
112
|
+
generateMessage: async (messages, assistantId) => {
|
|
112
113
|
const { dispatchMessage } = get();
|
|
113
114
|
set({ chatLoadingId: assistantId });
|
|
114
115
|
const config = agentSelectors.currentAgentConfigSafe(get());
|
|
115
116
|
|
|
116
117
|
const fetcher = () =>
|
|
117
|
-
fetchChatModel(
|
|
118
|
-
{ messages, model: config.model, ...config.params, plugins: config.plugins },
|
|
119
|
-
{ withPlugin },
|
|
120
|
-
);
|
|
118
|
+
fetchChatModel({ messages, model: config.model, ...config.params, plugins: config.plugins });
|
|
121
119
|
|
|
122
120
|
let output = '';
|
|
123
121
|
let isFunctionCall = false;
|
|
@@ -139,13 +137,6 @@ export const chatMessage: StateCreator<
|
|
|
139
137
|
// 如果是 function call
|
|
140
138
|
if (isFunctionMessage(output)) {
|
|
141
139
|
isFunctionCall = true;
|
|
142
|
-
// 设为 function
|
|
143
|
-
dispatchMessage({
|
|
144
|
-
id: assistantId,
|
|
145
|
-
key: 'role',
|
|
146
|
-
type: 'updateMessage',
|
|
147
|
-
value: 'function',
|
|
148
|
-
});
|
|
149
140
|
}
|
|
150
141
|
|
|
151
142
|
// 滚动到最后一条消息
|
|
@@ -158,11 +149,11 @@ export const chatMessage: StateCreator<
|
|
|
158
149
|
|
|
159
150
|
set({ chatLoadingId: undefined });
|
|
160
151
|
|
|
161
|
-
return { isFunctionCall
|
|
152
|
+
return { isFunctionCall };
|
|
162
153
|
},
|
|
163
154
|
|
|
164
155
|
realFetchAIResponse: async (messages, userMessageId) => {
|
|
165
|
-
const { dispatchMessage, generateMessage, activeTopicId } = get();
|
|
156
|
+
const { dispatchMessage, generateMessage, triggerFunctionCall, activeTopicId } = get();
|
|
166
157
|
|
|
167
158
|
// 添加 systemRole
|
|
168
159
|
const { systemRole, model } = agentSelectors.currentAgentConfigSafe(get());
|
|
@@ -191,31 +182,11 @@ export const chatMessage: StateCreator<
|
|
|
191
182
|
dispatchMessage({ id: mid, key: 'fromModel', type: 'updateMessageExtra', value: model });
|
|
192
183
|
|
|
193
184
|
// 生成 ai message
|
|
194
|
-
const {
|
|
185
|
+
const { isFunctionCall } = await generateMessage(messages, mid);
|
|
195
186
|
|
|
196
187
|
// 如果是 function,则发送函数调用方法
|
|
197
188
|
if (isFunctionCall) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
dispatchMessage({
|
|
201
|
-
id: mid,
|
|
202
|
-
key: 'function_call',
|
|
203
|
-
type: 'updateMessage',
|
|
204
|
-
value: function_call,
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
await generateMessage(
|
|
208
|
-
[...messages, { content: '', function_call, id: mid, role: 'assistant' } as ChatMessage],
|
|
209
|
-
mid,
|
|
210
|
-
true,
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
dispatchMessage({
|
|
214
|
-
id: mid,
|
|
215
|
-
key: 'role',
|
|
216
|
-
type: 'updateMessage',
|
|
217
|
-
value: 'assistant',
|
|
218
|
-
});
|
|
189
|
+
triggerFunctionCall(mid);
|
|
219
190
|
}
|
|
220
191
|
},
|
|
221
192
|
|
|
@@ -245,7 +216,6 @@ export const chatMessage: StateCreator<
|
|
|
245
216
|
|
|
246
217
|
await realFetchAIResponse(histories, latestMsg.id);
|
|
247
218
|
},
|
|
248
|
-
|
|
249
219
|
sendMessage: async (message) => {
|
|
250
220
|
const { dispatchMessage, realFetchAIResponse, autocompleteSessionAgentMeta, activeTopicId } =
|
|
251
221
|
get();
|
|
@@ -270,4 +240,53 @@ export const chatMessage: StateCreator<
|
|
|
270
240
|
autocompleteSessionAgentMeta(session.id);
|
|
271
241
|
}
|
|
272
242
|
},
|
|
243
|
+
|
|
244
|
+
triggerFunctionCall: async (id) => {
|
|
245
|
+
const { dispatchMessage, generateMessage } = get();
|
|
246
|
+
const session = sessionSelectors.currentSession(get());
|
|
247
|
+
|
|
248
|
+
if (!session) return;
|
|
249
|
+
|
|
250
|
+
const message = session.chats[id];
|
|
251
|
+
if (!message) return;
|
|
252
|
+
|
|
253
|
+
let payload: OpenAIFunctionCall = { name: '' };
|
|
254
|
+
if (message.content) {
|
|
255
|
+
const { function_call } = JSON.parse(message.content);
|
|
256
|
+
dispatchMessage({ id, key: 'function_call', type: 'updateMessage', value: function_call });
|
|
257
|
+
dispatchMessage({ id, key: 'content', type: 'updateMessage', value: '' });
|
|
258
|
+
payload = function_call;
|
|
259
|
+
} else {
|
|
260
|
+
if (message.function_call) {
|
|
261
|
+
payload = message.function_call;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (!payload.name) return;
|
|
266
|
+
|
|
267
|
+
// const fid = nanoid();
|
|
268
|
+
dispatchMessage({ id, key: 'role', type: 'updateMessage', value: 'function' });
|
|
269
|
+
dispatchMessage({ id, key: 'name', type: 'updateMessage', value: payload.name });
|
|
270
|
+
dispatchMessage({ id, key: 'function_call', type: 'updateMessage', value: payload });
|
|
271
|
+
|
|
272
|
+
// dispatchMessage({
|
|
273
|
+
// id: id,
|
|
274
|
+
// message: FUNCTION_LOADING,
|
|
275
|
+
// parentId: message.,
|
|
276
|
+
// role: 'function',
|
|
277
|
+
// type: 'addMessage',
|
|
278
|
+
// });
|
|
279
|
+
|
|
280
|
+
const data = await fetchPlugin(payload);
|
|
281
|
+
|
|
282
|
+
dispatchMessage({ id, key: 'content', type: 'updateMessage', value: data });
|
|
283
|
+
|
|
284
|
+
const mid = nanoid();
|
|
285
|
+
|
|
286
|
+
dispatchMessage({ id: mid, message: LOADING_FLAT, role: 'assistant', type: 'addMessage' });
|
|
287
|
+
|
|
288
|
+
const chats = chatSelectors.currentChats(get());
|
|
289
|
+
|
|
290
|
+
await generateMessage(chats, mid);
|
|
291
|
+
},
|
|
273
292
|
});
|
|
@@ -295,7 +295,7 @@ describe('organizeChats', () => {
|
|
|
295
295
|
const result = organizeChats(session, avatar);
|
|
296
296
|
const meta = result[0].meta;
|
|
297
297
|
|
|
298
|
-
expect(meta.avatar).toBe(avatar
|
|
298
|
+
expect(meta.avatar).toBe('assistant-avatar');
|
|
299
299
|
expect(meta.title).toBeUndefined();
|
|
300
300
|
});
|
|
301
301
|
|
|
@@ -329,6 +329,7 @@ describe('organizeChats', () => {
|
|
|
329
329
|
function_call: {
|
|
330
330
|
name: 'realtimeWeather',
|
|
331
331
|
},
|
|
332
|
+
name: 'realtimeWeather',
|
|
332
333
|
} as ChatMessage;
|
|
333
334
|
|
|
334
335
|
session.chats[message.id] = message;
|
|
@@ -336,7 +337,7 @@ describe('organizeChats', () => {
|
|
|
336
337
|
const result = organizeChats(session, avatar);
|
|
337
338
|
const meta = result[3].meta;
|
|
338
339
|
|
|
339
|
-
expect(meta.avatar).toBe('
|
|
340
|
+
expect(meta.avatar).toBe('🧩');
|
|
340
341
|
expect(meta.title).toBe('realtimeWeather');
|
|
341
342
|
});
|
|
342
343
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { PluginsMap } from '@/plugins';
|
|
2
2
|
import { ChatMessage } from '@/types/chatMessage';
|
|
3
3
|
import { LobeAgentSession } from '@/types/session';
|
|
4
4
|
|
|
@@ -27,9 +27,10 @@ export const organizeChats = (
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
case 'function': {
|
|
30
|
-
const plugin =
|
|
30
|
+
const plugin = PluginsMap[message.name || ''];
|
|
31
|
+
|
|
31
32
|
return {
|
|
32
|
-
avatar:
|
|
33
|
+
avatar: '🧩',
|
|
33
34
|
title: plugin?.name || 'plugin-unknown',
|
|
34
35
|
};
|
|
35
36
|
}
|