@lobehub/chat 0.12.1 → 0.13.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 CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.13.1](https://github.com/lobehub/lobe-chat/compare/v0.13.0...v0.13.1)
6
+
7
+ <sup>Released on **2023-07-23**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: 修正搜索引擎插件的实现问题.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: 修正搜索引擎插件的实现问题 ([d19a805](https://github.com/lobehub/lobe-chat/commit/d19a805))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ## [Version 0.13.0](https://github.com/lobehub/lobe-chat/compare/v0.12.1...v0.13.0)
31
+
32
+ <sup>Released on **2023-07-23**</sup>
33
+
34
+ #### ✨ Features
35
+
36
+ - **misc**: 优化插件模式下的用户体验.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's improved
44
+
45
+ - **misc**: 优化插件模式下的用户体验, closes [#13](https://github.com/lobehub/lobe-chat/issues/13) ([4596f12](https://github.com/lobehub/lobe-chat/commit/4596f12))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ### [Version 0.12.1](https://github.com/lobehub/lobe-chat/compare/v0.12.0...v0.12.1)
6
56
 
7
57
  <sup>Released on **2023-07-23**</sup>
package/next.config.mjs CHANGED
@@ -13,7 +13,7 @@ const withPWA = nextPWA({
13
13
  const nextConfig = {
14
14
  reactStrictMode: true,
15
15
  pageExtensions: ['page.tsx', 'api.ts'],
16
- transpilePackages: ['@lobehub/ui', 'antd-style'],
16
+ transpilePackages: ['@lobehub/ui'],
17
17
 
18
18
  webpack(config) {
19
19
  config.experiments = {
@@ -31,8 +31,8 @@ const nextConfig = {
31
31
  destination: `${API_END_PORT_URL}/api/openai`,
32
32
  },
33
33
  {
34
- source: '/api/chain-dev',
35
- destination: `${API_END_PORT_URL}/api/chain`,
34
+ source: '/api/plugins-dev',
35
+ destination: `${API_END_PORT_URL}/api/plugins`,
36
36
  },
37
37
  ];
38
38
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.12.1",
3
+ "version": "0.13.1",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -0,0 +1,2 @@
1
+ // 只要 start with 这个,就可以判断为 function message
2
+ export const FUNCTION_MESSAGE_FLAG = '{"function';
@@ -38,6 +38,7 @@ export default {
38
38
  'plugin-realtimeWeather': '实时天气预报',
39
39
  'plugin-searchEngine': '搜索引擎',
40
40
  'pluginList': '插件列表',
41
+ 'pluginLoading': '插件运行中...',
41
42
  'profile': '助手身份',
42
43
  'reset': '重置',
43
44
  'searchAgentPlaceholder': '搜索助手和对话...',
@@ -1,74 +1,15 @@
1
- import { OpenAIStream, StreamingTextResponse } from 'ai';
2
- import { Configuration, OpenAIApi } from 'openai-edge';
3
- import { ChatCompletionFunctions, ChatCompletionRequestMessage } from 'openai-edge/types/api';
1
+ import { StreamingTextResponse } from 'ai';
4
2
 
5
3
  import { OpenAIStreamPayload } from '@/types/openai';
6
4
 
7
- import pluginList from '../../plugins';
5
+ import { createChatCompletion } from './openai';
8
6
 
9
7
  export const runtime = 'edge';
10
8
 
11
- const isDev = process.env.NODE_ENV === 'development';
12
- const OPENAI_PROXY_URL = process.env.OPENAI_PROXY_URL;
13
-
14
- // Create an OpenAI API client (that's edge friendly!)
15
- const config = new Configuration({
16
- apiKey: process.env.OPENAI_API_KEY,
17
- });
18
-
19
- const openai = new OpenAIApi(config, isDev && OPENAI_PROXY_URL ? OPENAI_PROXY_URL : undefined);
20
-
21
9
  export default async function handler(req: Request) {
22
- const {
23
- messages,
24
- plugins: enabledPlugins,
25
- ...params
26
- } = (await req.json()) as OpenAIStreamPayload;
27
-
28
- // ============ 1. 前置处理 functions ============ //
29
-
30
- const filterFunctions: ChatCompletionFunctions[] = pluginList
31
- .filter((p) => {
32
- // 如果不存在 enabledPlugins,那么全部不启用
33
- if (!enabledPlugins) return false;
34
-
35
- // 如果存在 enabledPlugins,那么只启用 enabledPlugins 中的插件
36
- return enabledPlugins.includes(p.name);
37
- })
38
- .map((f) => f.schema);
39
-
40
- const functions = filterFunctions.length === 0 ? undefined : filterFunctions;
41
-
42
- // ============ 2. 前置处理 messages ============ //
43
- const formatMessages = messages.map((m) => ({ content: m.content, role: m.role }));
44
-
45
- const response = await openai.createChatCompletion({
46
- functions,
47
- messages: formatMessages,
48
- stream: true,
49
- ...params,
50
- });
51
-
52
- const stream = OpenAIStream(response, {
53
- experimental_onFunctionCall: async ({ name, arguments: args }, createFunctionCallMessages) => {
54
- console.log(`执行 functionCall [${name}]`, 'args:', args);
55
-
56
- const func = pluginList.find((f) => f.name === name);
57
-
58
- if (func) {
59
- const result = await func.runner(args as any);
60
-
61
- const newMessages = createFunctionCallMessages(result) as ChatCompletionRequestMessage[];
10
+ const payload = (await req.json()) as OpenAIStreamPayload;
62
11
 
63
- return openai.createChatCompletion({
64
- functions,
65
- messages: [...formatMessages, ...newMessages],
66
- stream: true,
67
- ...params,
68
- });
69
- }
70
- },
71
- });
12
+ const stream = await createChatCompletion(payload);
72
13
 
73
14
  return new StreamingTextResponse(stream);
74
15
  }
@@ -0,0 +1,50 @@
1
+ import { OpenAIStream, OpenAIStreamCallbacks } from 'ai';
2
+ import { Configuration, OpenAIApi } from 'openai-edge';
3
+ import { ChatCompletionFunctions } from 'openai-edge/types/api';
4
+
5
+ import { OpenAIStreamPayload } from '@/types/openai';
6
+
7
+ import pluginList from '../../plugins';
8
+
9
+ const isDev = process.env.NODE_ENV === 'development';
10
+ const OPENAI_PROXY_URL = process.env.OPENAI_PROXY_URL;
11
+
12
+ // Create an OpenAI API client (that's edge friendly!)
13
+ const config = new Configuration({
14
+ apiKey: process.env.OPENAI_API_KEY,
15
+ });
16
+
17
+ export const openai = new OpenAIApi(
18
+ config,
19
+ isDev && OPENAI_PROXY_URL ? OPENAI_PROXY_URL : undefined,
20
+ );
21
+
22
+ export const createChatCompletion = async (
23
+ payload: OpenAIStreamPayload,
24
+ callbacks?: (payload: OpenAIStreamPayload) => OpenAIStreamCallbacks,
25
+ ) => {
26
+ const { messages, plugins: enabledPlugins, ...params } = payload;
27
+
28
+ // ============ 1. 前置处理 functions ============ //
29
+
30
+ const filterFunctions: ChatCompletionFunctions[] = pluginList
31
+ .filter((p) => {
32
+ // 如果不存在 enabledPlugins,那么全部不启用
33
+ if (!enabledPlugins) return false;
34
+
35
+ // 如果存在 enabledPlugins,那么只启用 enabledPlugins 中的插件
36
+ return enabledPlugins.includes(p.name);
37
+ })
38
+ .map((f) => f.schema);
39
+
40
+ const functions = filterFunctions.length === 0 ? undefined : filterFunctions;
41
+
42
+ // ============ 2. 前置处理 messages ============ //
43
+ const formatMessages = messages.map((m) => ({ content: m.content, role: m.role }));
44
+
45
+ const requestParams = { functions, messages: formatMessages, stream: true, ...params };
46
+
47
+ const response = await openai.createChatCompletion(requestParams);
48
+
49
+ return OpenAIStream(response, callbacks?.(requestParams));
50
+ };
@@ -0,0 +1,36 @@
1
+ import { StreamingTextResponse } from 'ai';
2
+ import { ChatCompletionRequestMessage } from 'openai-edge';
3
+
4
+ import { OpenAIStreamPayload } from '@/types/openai';
5
+
6
+ import pluginList from '../../plugins';
7
+ import { createChatCompletion, openai } from './openai';
8
+
9
+ export const runtime = 'edge';
10
+
11
+ export default async function handler(req: Request) {
12
+ const payload = (await req.json()) as OpenAIStreamPayload;
13
+
14
+ const stream = await createChatCompletion(payload, (payload) => ({
15
+ experimental_onFunctionCall: async ({ name, arguments: args }, createFunctionCallMessages) => {
16
+ console.log(`检测到 functionCall: ${name}`);
17
+
18
+ const func = pluginList.find((f) => f.name === name);
19
+
20
+ if (func) {
21
+ const result = await func.runner(args as any);
22
+
23
+ console.log(`[${name}]`, args, `result:`, JSON.stringify(result, null, 2));
24
+
25
+ const newMessages = createFunctionCallMessages(result) as ChatCompletionRequestMessage[];
26
+
27
+ return openai.createChatCompletion({
28
+ ...payload,
29
+ messages: [...payload.messages, ...newMessages],
30
+ });
31
+ }
32
+ },
33
+ }));
34
+
35
+ return new StreamingTextResponse(stream);
36
+ }
@@ -1,10 +1,12 @@
1
- import { ChatList } from '@lobehub/ui';
1
+ import { ChatList, ChatMessage } from '@lobehub/ui';
2
2
  import isEqual from 'fast-deep-equal';
3
- import { memo } from 'react';
3
+ import { ReactNode, memo } from 'react';
4
4
  import { shallow } from 'zustand/shallow';
5
5
 
6
6
  import { chatSelectors, useSessionStore } from '@/store/session';
7
+ import { isFunctionMessage } from '@/utils/message';
7
8
 
9
+ import FunctionMessage from './FunctionMessage';
8
10
  import MessageExtra from './MessageExtra';
9
11
 
10
12
  const List = () => {
@@ -14,6 +16,13 @@ const List = () => {
14
16
  shallow,
15
17
  );
16
18
 
19
+ const renderMessage = (content: ReactNode, message: ChatMessage) => {
20
+ if (message.role === 'function')
21
+ return isFunctionMessage(message.content) ? <FunctionMessage /> : content;
22
+
23
+ return content;
24
+ };
25
+
17
26
  return (
18
27
  <ChatList
19
28
  data={data}
@@ -33,6 +42,7 @@ const List = () => {
33
42
  onMessageChange={(id, content) => {
34
43
  dispatchMessage({ id, key: 'content', type: 'updateMessage', value: content });
35
44
  }}
45
+ renderMessage={renderMessage}
36
46
  renderMessageExtra={MessageExtra}
37
47
  style={{ marginTop: 24 }}
38
48
  />
@@ -0,0 +1,31 @@
1
+ import { LoadingOutlined } from '@ant-design/icons';
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
+ const useStyles = createStyles(({ css, token }) => ({
8
+ container: css`
9
+ padding: 4px 8px;
10
+
11
+ color: ${token.colorTextSecondary};
12
+
13
+ background: ${token.colorFillTertiary};
14
+ border: 1px solid ${token.colorBorder};
15
+ border-radius: 6px;
16
+ `,
17
+ }));
18
+ const FunctionMessage = memo(() => {
19
+ const { t } = useTranslation();
20
+ const { styles } = useStyles();
21
+ return (
22
+ <Flexbox className={styles.container} gap={8} horizontal>
23
+ <div>
24
+ <LoadingOutlined />
25
+ </div>
26
+ {t('pluginLoading')}
27
+ </Flexbox>
28
+ );
29
+ });
30
+
31
+ export default FunctionMessage;
@@ -1,9 +1,13 @@
1
+ import { Avatar, Icon, Tooltip } from '@lobehub/ui';
1
2
  import { Tag } from 'antd';
2
3
  import { createStyles } from 'antd-style';
4
+ import { LucideToyBrick } from 'lucide-react';
3
5
  import { ReactNode } from 'react';
6
+ import { useTranslation } from 'react-i18next';
4
7
  import { Flexbox } from 'react-layout-kit';
5
8
  import { shallow } from 'zustand/shallow';
6
9
 
10
+ import pluginList from '@/plugins';
7
11
  import { agentSelectors, useSessionStore } from '@/store/session';
8
12
  import { ChatMessage } from '@/types/chatMessage';
9
13
 
@@ -11,27 +15,70 @@ const useStyles = createStyles(({ css }) => ({
11
15
  container: css`
12
16
  margin-top: 8px;
13
17
  `,
18
+ plugin: css`
19
+ display: flex;
20
+ gap: 4px;
21
+ align-items: center;
22
+ width: fit-content;
23
+ `,
14
24
  }));
15
25
 
16
- const MessageExtra = ({ role, extra }: ChatMessage): ReactNode => {
26
+ const MessageExtra = ({ role, extra, function_call }: ChatMessage): ReactNode => {
17
27
  const { styles } = useStyles();
18
28
 
29
+ const { t } = useTranslation();
19
30
  const [model] = useSessionStore((s) => [agentSelectors.currentAgentModel(s)], shallow);
20
31
 
21
- // 1. 只有 ai 的 message
22
- // 2. 且存在 fromModel
23
- // 3. 且当前的 model 和 fromModel 不一致时
24
- if (role === 'assistant' && extra?.fromModel && model !== extra?.fromModel)
25
- // 才需要展示 model tag
26
- return (
27
- <Flexbox className={styles.container}>
28
- <div>
29
- <Tag bordered={false} style={{ borderRadius: 6 }}>
30
- {extra?.fromModel}
31
- </Tag>
32
- </div>
33
- </Flexbox>
34
- );
32
+ const plugin = pluginList.find((p) => p.name === function_call?.name);
33
+ const funcTag = (
34
+ <Tooltip title={function_call?.arguments}>
35
+ <Tag bordered={false} className={styles.plugin} style={{ borderRadius: 6 }}>
36
+ {plugin?.avatar ? (
37
+ <Avatar avatar={plugin?.avatar} size={18} />
38
+ ) : (
39
+ <Icon icon={LucideToyBrick} />
40
+ )}
41
+ {t(`plugin-${function_call?.name}` as any)}
42
+ </Tag>
43
+ </Tooltip>
44
+ );
45
+
46
+ const modelTag = (
47
+ <div>
48
+ <Tag bordered={false} style={{ borderRadius: 6 }}>
49
+ {extra?.fromModel}
50
+ </Tag>
51
+ </div>
52
+ );
53
+
54
+ // 1. 存在 fromModel
55
+ // 2. 且当前的 model 和 fromModel 不一致时
56
+ const hasModelTag = extra?.fromModel && model !== extra?.fromModel;
57
+
58
+ const hasFuncTag = !!function_call;
59
+
60
+ switch (role) {
61
+ case 'user':
62
+ case 'system': {
63
+ return;
64
+ }
65
+ case 'assistant': {
66
+ // 1. 只有 ai 的 message
67
+ // 2. 且存在 fromModel
68
+ // 3. 且当前的 model 和 fromModel 不一致时
69
+ if (!(hasModelTag || hasFuncTag)) return;
70
+
71
+ return (
72
+ <Flexbox className={styles.container}>
73
+ {hasFuncTag && funcTag}
74
+ {hasModelTag && modelTag}
75
+ </Flexbox>
76
+ );
77
+ }
78
+ case 'function': {
79
+ return <Flexbox className={styles.container}>{funcTag}</Flexbox>;
80
+ }
81
+ }
35
82
  };
36
83
 
37
84
  export default MessageExtra;
@@ -76,9 +76,10 @@ export interface DetectedExtensions {
76
76
  month_ago: number;
77
77
  }
78
78
 
79
- const fetchResult = async (keywords: string) => {
79
+ const fetchResult = async ({ keywords }: { keywords: string }) => {
80
80
  const params = {
81
81
  api_key: API_KEY,
82
+ engine: 'google',
82
83
  gl: 'cn',
83
84
  google_domain: 'google.com',
84
85
  hl: 'zh-cn',
@@ -9,7 +9,7 @@ import { URLS } from './url';
9
9
  */
10
10
  export const fetchChatModel = (
11
11
  params: Partial<OpenAIStreamPayload>,
12
- signal?: AbortSignal | undefined,
12
+ options?: { signal?: AbortSignal | undefined; withPlugin?: boolean },
13
13
  ) => {
14
14
  const payload = merge(
15
15
  {
@@ -23,12 +23,12 @@ export const fetchChatModel = (
23
23
  params,
24
24
  );
25
25
 
26
- return fetch(URLS.openai, {
26
+ return fetch(options?.withPlugin ? URLS.plugins : URLS.openai, {
27
27
  body: JSON.stringify(payload),
28
28
  headers: {
29
29
  'Content-Type': 'application/json',
30
30
  },
31
31
  method: 'POST',
32
- signal,
32
+ signal: options?.signal,
33
33
  });
34
34
  };
@@ -4,4 +4,5 @@ const prefix = isDev ? '-dev' : '';
4
4
 
5
5
  export const URLS = {
6
6
  openai: '/api/openai' + prefix,
7
+ plugins: '/api/plugins' + prefix,
7
8
  };
@@ -3,7 +3,8 @@ import { StateCreator } from 'zustand/vanilla';
3
3
  import { fetchChatModel } from '@/services/chatModel';
4
4
  import { SessionStore, agentSelectors, chatSelectors, sessionSelectors } from '@/store/session';
5
5
  import { ChatMessage } from '@/types/chatMessage';
6
- import { FetchSSEOptions, fetchSSE } from '@/utils/fetch';
6
+ import { fetchSSE } from '@/utils/fetch';
7
+ import { isFunctionMessage } from '@/utils/message';
7
8
  import { nanoid } from '@/utils/uuid';
8
9
 
9
10
  import { MessageDispatch, messagesReducer } from './messageReducer';
@@ -38,7 +39,11 @@ export interface ChatAction {
38
39
  * @param messages - 聊天消息数组
39
40
  * @param options - 获取 SSE 选项
40
41
  */
41
- generateMessage: (messages: ChatMessage[], options: FetchSSEOptions) => Promise<void>;
42
+ generateMessage: (
43
+ messages: ChatMessage[],
44
+ assistantMessageId: string,
45
+ withPlugin?: boolean,
46
+ ) => Promise<{ isFunctionCall: boolean; output: string }>;
42
47
  /**
43
48
  * 处理消息编辑
44
49
  * @param messageId - 消息 ID,可选
@@ -100,16 +105,57 @@ export const createChatSlice: StateCreator<
100
105
  get().dispatchSession({ chats, id: activeId, type: 'updateSessionChat' });
101
106
  },
102
107
 
103
- generateMessage: async (messages, options) => {
108
+ generateMessage: async (messages, assistantId, withPlugin) => {
109
+ const { dispatchMessage } = get();
104
110
  set({ chatLoading: true });
105
111
  const config = agentSelectors.currentAgentConfigSafe(get());
106
112
 
107
113
  const fetcher = () =>
108
- fetchChatModel({ messages, model: config.model, ...config.params, plugins: config.plugins });
114
+ fetchChatModel(
115
+ { messages, model: config.model, ...config.params, plugins: config.plugins },
116
+ { withPlugin },
117
+ );
109
118
 
110
- await fetchSSE(fetcher, options);
119
+ let output = '';
120
+ let isFunctionCall = false;
121
+
122
+ await fetchSSE(fetcher, {
123
+ onErrorHandle: (error) => {
124
+ dispatchMessage({ id: assistantId, key: 'error', type: 'updateMessage', value: error });
125
+ },
126
+ onMessageHandle: (text) => {
127
+ output += text;
128
+
129
+ dispatchMessage({
130
+ id: assistantId,
131
+ key: 'content',
132
+ type: 'updateMessage',
133
+ value: output,
134
+ });
135
+
136
+ // 如果是 function call
137
+ if (isFunctionMessage(output)) {
138
+ isFunctionCall = true;
139
+ // 设为 function
140
+ dispatchMessage({
141
+ id: assistantId,
142
+ key: 'role',
143
+ type: 'updateMessage',
144
+ value: 'function',
145
+ });
146
+ }
147
+
148
+ // 滚动到最后一条消息
149
+ const item = document.querySelector('#for-loading');
150
+ if (!item) return;
151
+
152
+ item.scrollIntoView({ behavior: 'smooth' });
153
+ },
154
+ });
111
155
 
112
156
  set({ chatLoading: false });
157
+
158
+ return { isFunctionCall, output };
113
159
  },
114
160
 
115
161
  handleMessageEditing: (messageId) => {
@@ -145,29 +191,36 @@ export const createChatSlice: StateCreator<
145
191
  value: model,
146
192
  });
147
193
 
148
- let output = '';
149
194
  // 生成 ai message
150
- await generateMessage(messages, {
151
- onErrorHandle: (error) => {
152
- dispatchMessage({ id: assistantId, key: 'error', type: 'updateMessage', value: error });
153
- },
154
- onMessageHandle: (text) => {
155
- output += text;
156
-
157
- dispatchMessage({
158
- id: assistantId,
159
- key: 'content',
160
- type: 'updateMessage',
161
- value: output,
162
- });
163
-
164
- // 滚动到最后一条消息
165
- const item = document.querySelector('#for-loading');
166
- if (!item) return;
167
-
168
- item.scrollIntoView({ behavior: 'smooth' });
169
- },
170
- });
195
+ const { output, isFunctionCall } = await generateMessage(messages, assistantId);
196
+
197
+ // 如果是 function,则发送函数调用方法
198
+ if (isFunctionCall) {
199
+ const { function_call } = JSON.parse(output);
200
+
201
+ dispatchMessage({
202
+ id: assistantId,
203
+ key: 'function_call',
204
+ type: 'updateMessage',
205
+ value: function_call,
206
+ });
207
+
208
+ await generateMessage(
209
+ [
210
+ ...messages,
211
+ { content: '', function_call, id: assistantId, role: 'assistant' } as ChatMessage,
212
+ ],
213
+ assistantId,
214
+ true,
215
+ );
216
+
217
+ dispatchMessage({
218
+ id: assistantId,
219
+ key: 'role',
220
+ type: 'updateMessage',
221
+ value: 'assistant',
222
+ });
223
+ }
171
224
  },
172
225
 
173
226
  resendMessage: async (messageId) => {
@@ -182,7 +235,7 @@ export const createChatSlice: StateCreator<
182
235
 
183
236
  const histories = chats
184
237
  .slice(0, currentIndex + 1)
185
- // 如果点击重新发送的 message 其 role 是 assistant,那么需要移除
238
+ // 如果点击重新发送的 message 其 role 是 assistant 或者 function,那么需要移除
186
239
  // 如果点击重新发送的 message 其 role 是 user,则不需要移除
187
240
  .filter((c) => !(c.role === 'assistant' && c.id === messageId));
188
241
 
@@ -1,3 +1,4 @@
1
+ import pluginList from '@/plugins';
1
2
  import { ChatMessage } from '@/types/chatMessage';
2
3
  import { LobeAgentSession } from '@/types/session';
3
4
 
@@ -5,6 +6,35 @@ export const organizeChats = (
5
6
  session: LobeAgentSession,
6
7
  avatar: { assistant: string; user: string },
7
8
  ) => {
9
+ const getMeta = (message: ChatMessage) => {
10
+ switch (message.role) {
11
+ case 'user': {
12
+ return {
13
+ avatar: avatar.user,
14
+ };
15
+ }
16
+
17
+ case 'system': {
18
+ return message.meta;
19
+ }
20
+
21
+ case 'assistant': {
22
+ return {
23
+ avatar: avatar.assistant,
24
+ title: session.meta.title,
25
+ };
26
+ }
27
+
28
+ case 'function': {
29
+ const plugin = pluginList.find((p) => p.name === message.function_call?.name);
30
+ return {
31
+ avatar: plugin?.avatar || '🧩',
32
+ title: plugin?.name || 'plugin-unknown',
33
+ };
34
+ }
35
+ }
36
+ };
37
+
8
38
  const basic = Object.values<ChatMessage>(session.chats)
9
39
  // 首先按照时间顺序排序,越早的在越前面
10
40
  .sort((pre, next) => pre.createAt - next.createAt)
@@ -14,17 +44,7 @@ export const organizeChats = (
14
44
  .map((m) => {
15
45
  return {
16
46
  ...m,
17
- meta:
18
- m.role === 'assistant'
19
- ? {
20
- avatar: avatar.assistant,
21
- title: session.meta.title,
22
- }
23
- : m.role === 'user'
24
- ? {
25
- avatar: avatar.user,
26
- }
27
- : m.meta,
47
+ meta: getMeta(m),
28
48
  };
29
49
  });
30
50
 
@@ -22,7 +22,6 @@ export interface ChatMessage extends BaseDataModel {
22
22
  */
23
23
  content: string;
24
24
  error?: any;
25
-
26
25
  // 扩展字段
27
26
  extra?: {
28
27
  fromModel?: string;
@@ -33,6 +32,9 @@ export interface ChatMessage extends BaseDataModel {
33
32
  };
34
33
  } & Record<string, any>;
35
34
 
35
+ function_call?: { arguments?: string; name: string };
36
+ name?: string;
37
+
36
38
  parentId?: string;
37
39
  // 引用
38
40
  quotaId?: string;
package/src/types/llm.ts CHANGED
@@ -39,7 +39,7 @@ export interface LLMParams {
39
39
  top_p?: number;
40
40
  }
41
41
 
42
- export type LLMRoleType = 'user' | 'system' | 'assistant';
42
+ export type LLMRoleType = 'user' | 'system' | 'assistant' | 'function';
43
43
 
44
44
  export interface LLMMessage {
45
45
  content: string;
@@ -91,7 +91,7 @@ interface FetchAITaskResultParams<T> {
91
91
  }
92
92
 
93
93
  export const fetchAIFactory =
94
- <T>(fetcher: (params: T, signal?: AbortSignal) => Promise<Response>) =>
94
+ <T>(fetcher: (params: T, options: { signal?: AbortSignal }) => Promise<Response>) =>
95
95
  async ({
96
96
  params,
97
97
  onMessageHandle,
@@ -117,7 +117,7 @@ export const fetchAIFactory =
117
117
 
118
118
  onLoadingChange?.(true);
119
119
 
120
- const data = await fetchSSE(() => fetcher(params, abortController?.signal), {
120
+ const data = await fetchSSE(() => fetcher(params, { signal: abortController?.signal }), {
121
121
  onErrorHandle: (error) => {
122
122
  errorHandle(new Error(error.message));
123
123
  },
@@ -0,0 +1,5 @@
1
+ import { FUNCTION_MESSAGE_FLAG } from '@/const/message';
2
+
3
+ export const isFunctionMessage = (content: string) => {
4
+ return content.startsWith(FUNCTION_MESSAGE_FLAG);
5
+ };