@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.
Files changed (39) hide show
  1. package/CHANGELOG.md +25 -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 +8 -0
  18. package/src/plugins/index.ts +7 -3
  19. package/src/plugins/searchEngine/index.ts +4 -1
  20. package/src/plugins/searchEngine/runner.ts +5 -73
  21. package/src/plugins/searchEngine/type.ts +80 -0
  22. package/src/plugins/type.ts +17 -0
  23. package/src/plugins/weather/Render.tsx +53 -0
  24. package/src/plugins/weather/index.ts +9 -1
  25. package/src/plugins/weather/runner.ts +12 -7
  26. package/src/plugins/weather/type.ts +36 -0
  27. package/src/plugins/webCrawler/index.ts +4 -1
  28. package/src/plugins/webCrawler/runner.ts +3 -27
  29. package/src/plugins/webCrawler/type.ts +32 -0
  30. package/src/services/chatModel.ts +1 -2
  31. package/src/services/plugin.ts +30 -0
  32. package/src/store/session/slices/chat/actions/message.ts +59 -40
  33. package/src/store/session/slices/chat/selectors/utils.test.ts +3 -2
  34. package/src/store/session/slices/chat/selectors/utils.ts +4 -3
  35. package/src/types/chatMessage.ts +5 -1
  36. package/src/types/openai.ts +3 -0
  37. package/src/types/plugin.ts +4 -0
  38. package/src/utils/message.ts +16 -0
  39. 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
- 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',
@@ -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 = { 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
+ };
@@ -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
- withPlugin?: boolean,
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, withPlugin) => {
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, output };
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 { output, isFunctionCall } = await generateMessage(messages, mid);
185
+ const { isFunctionCall } = await generateMessage(messages, mid);
195
186
 
196
187
  // 如果是 function,则发送函数调用方法
197
188
  if (isFunctionCall) {
198
- const { function_call } = JSON.parse(output);
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.assistant);
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 pluginList from '@/plugins';
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 = pluginList.find((p) => p.name === message.function_call?.name);
30
+ const plugin = PluginsMap[message.name || ''];
31
+
31
32
  return {
32
- avatar: plugin?.avatar || '🧩',
33
+ avatar: '🧩',
33
34
  title: plugin?.name || 'plugin-unknown',
34
35
  };
35
36
  }