@lobehub/chat 0.157.0 → 0.157.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 (57) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/package.json +30 -30
  3. package/src/app/api/chat/[provider]/route.test.ts +2 -2
  4. package/src/app/api/chat/[provider]/route.ts +1 -1
  5. package/src/app/api/chat/models/[provider]/route.ts +1 -1
  6. package/src/app/api/config.test.ts +1 -51
  7. package/src/app/api/openai/createBizOpenAI/auth.test.ts +52 -0
  8. package/src/app/api/openai/createBizOpenAI/index.ts +1 -1
  9. package/src/app/api/plugin/gateway/route.ts +1 -1
  10. package/src/app/api/text-to-image/[provider]/route.ts +61 -0
  11. package/src/components/GalleyGrid/index.tsx +2 -2
  12. package/src/database/client/schemas/message.ts +2 -0
  13. package/src/features/Conversation/Actions/Assistant.tsx +3 -2
  14. package/src/features/Conversation/Actions/Tool.tsx +23 -11
  15. package/src/features/Conversation/Messages/Assistant/ToolCalls/index.tsx +9 -14
  16. package/src/features/Conversation/Messages/Assistant/index.tsx +7 -3
  17. package/src/features/Conversation/Messages/Tool/Inspector/index.tsx +1 -1
  18. package/src/features/Conversation/Plugins/Render/index.tsx +11 -2
  19. package/src/hooks/useTokenCount.test.ts +38 -0
  20. package/src/hooks/useTokenCount.ts +1 -2
  21. package/src/libs/agent-runtime/AgentRuntime.ts +9 -1
  22. package/src/libs/agent-runtime/BaseAI.ts +3 -0
  23. package/src/libs/agent-runtime/types/index.ts +1 -0
  24. package/src/libs/agent-runtime/types/textToImage.ts +34 -0
  25. package/src/libs/agent-runtime/utils/createError.ts +1 -0
  26. package/src/libs/agent-runtime/utils/openaiCompatibleFactory/index.ts +51 -0
  27. package/src/locales/default/tool.ts +1 -0
  28. package/src/services/_url.ts +1 -1
  29. package/src/services/{imageGeneration.ts → textToImage.ts} +11 -2
  30. package/src/store/chat/initialState.ts +1 -1
  31. package/src/store/chat/selectors.ts +1 -1
  32. package/src/store/chat/slices/{tool → builtinTool}/action.test.ts +1 -1
  33. package/src/store/chat/slices/{tool → builtinTool}/action.ts +16 -4
  34. package/src/store/chat/slices/enchance/action.ts +10 -11
  35. package/src/store/chat/slices/message/action.ts +30 -92
  36. package/src/store/chat/slices/message/initialState.ts +5 -0
  37. package/src/store/chat/slices/message/selectors.ts +8 -0
  38. package/src/store/chat/slices/plugin/action.test.ts +1 -1
  39. package/src/store/chat/slices/plugin/action.ts +95 -80
  40. package/src/store/chat/store.ts +2 -2
  41. package/src/store/tool/slices/store/action.test.ts +6 -2
  42. package/src/store/tool/slices/store/action.ts +3 -1
  43. package/src/tools/dalle/Render/Item/Error.tsx +50 -0
  44. package/src/tools/dalle/Render/Item/Image.tsx +44 -0
  45. package/src/tools/dalle/Render/{Item.tsx → Item/index.tsx} +20 -29
  46. package/src/utils/fetch.test.ts +208 -3
  47. package/src/utils/fetch.ts +242 -19
  48. package/src/app/api/openai/images/createImageGeneration.ts +0 -26
  49. package/src/app/api/openai/images/route.ts +0 -16
  50. package/src/features/Conversation/Actions/Function.tsx +0 -17
  51. /package/src/app/api/{chat → middleware}/auth/index.test.ts +0 -0
  52. /package/src/app/api/{chat → middleware}/auth/index.ts +0 -0
  53. /package/src/app/api/{chat → middleware}/auth/utils.ts +0 -0
  54. /package/src/app/api/{auth.ts → openai/createBizOpenAI/auth.ts} +0 -0
  55. /package/src/store/chat/slices/{tool → builtinTool}/initialState.ts +0 -0
  56. /package/src/store/chat/slices/{tool → builtinTool}/selectors.ts +0 -0
  57. /package/src/tools/dalle/Render/{EditMode.tsx → Item/EditMode.tsx} +0 -0
@@ -24,15 +24,22 @@ export interface ChatPluginAction {
24
24
  content: string,
25
25
  triggerAiMessage?: boolean,
26
26
  ) => Promise<void>;
27
+
28
+ internal_callPluginApi: (id: string, payload: ChatToolPayload) => Promise<string | undefined>;
29
+ internal_invokeDifferentTypePlugin: (id: string, payload: ChatToolPayload) => Promise<any>;
27
30
  internal_transformToolCalls: (toolCalls: MessageToolCall[]) => ChatToolPayload[];
31
+ internal_updatePluginError: (id: string, error: any) => Promise<void>;
32
+
28
33
  invokeBuiltinTool: (id: string, payload: ChatToolPayload) => Promise<void>;
29
34
  invokeDefaultTypePlugin: (id: string, payload: any) => Promise<string | undefined>;
30
35
  invokeMarkdownTypePlugin: (id: string, payload: ChatToolPayload) => Promise<void>;
36
+
31
37
  invokeStandaloneTypePlugin: (id: string, payload: ChatToolPayload) => Promise<void>;
32
- runPluginApi: (id: string, payload: ChatToolPayload) => Promise<string | undefined>;
38
+
39
+ reInvokeToolMessage: (id: string) => Promise<void>;
33
40
  triggerAIMessage: (params: { parentId?: string; traceId?: string }) => Promise<void>;
34
- triggerToolCalls: (id: string) => Promise<void>;
35
41
 
42
+ triggerToolCalls: (id: string) => Promise<void>;
36
43
  updatePluginState: (id: string, key: string, value: any) => Promise<void>;
37
44
  }
38
45
 
@@ -62,6 +69,70 @@ export const chatPlugin: StateCreator<
62
69
 
63
70
  if (triggerAiMessage) await triggerAIMessage({ parentId: id });
64
71
  },
72
+ internal_callPluginApi: async (id, payload) => {
73
+ const { internal_updateMessageContent, refreshMessages, internal_toggleChatLoading } = get();
74
+ let data: string;
75
+
76
+ try {
77
+ const abortController = internal_toggleChatLoading(
78
+ true,
79
+ id,
80
+ n('fetchPlugin/start') as string,
81
+ );
82
+
83
+ const message = chatSelectors.getMessageById(id)(get());
84
+
85
+ const res = await chatService.runPluginApi(payload, {
86
+ signal: abortController?.signal,
87
+ trace: { observationId: message?.observationId, traceId: message?.traceId },
88
+ });
89
+ data = res.text;
90
+
91
+ // save traceId
92
+ if (res.traceId) {
93
+ await messageService.updateMessage(id, { traceId: res.traceId });
94
+ }
95
+ } catch (error) {
96
+ console.log(error);
97
+ const err = error as Error;
98
+
99
+ // ignore the aborted request error
100
+ if (!err.message.includes('The user aborted a request.')) {
101
+ await messageService.updateMessageError(id, error as any);
102
+ await refreshMessages();
103
+ }
104
+
105
+ data = '';
106
+ }
107
+
108
+ internal_toggleChatLoading(false, id, n('fetchPlugin/end') as string);
109
+ // 如果报错则结束了
110
+ if (!data) return;
111
+
112
+ await internal_updateMessageContent(id, data);
113
+
114
+ return data;
115
+ },
116
+
117
+ internal_invokeDifferentTypePlugin: async (id, payload) => {
118
+ switch (payload.type) {
119
+ case 'standalone': {
120
+ return await get().invokeStandaloneTypePlugin(id, payload);
121
+ }
122
+
123
+ case 'markdown': {
124
+ return await get().invokeMarkdownTypePlugin(id, payload);
125
+ }
126
+
127
+ case 'builtin': {
128
+ return await get().invokeBuiltinTool(id, payload);
129
+ }
130
+
131
+ default: {
132
+ return await get().invokeDefaultTypePlugin(id, payload);
133
+ }
134
+ }
135
+ },
65
136
 
66
137
  internal_transformToolCalls: (toolCalls) => {
67
138
  return toolCalls
@@ -98,6 +169,13 @@ export const chatPlugin: StateCreator<
98
169
  .filter(Boolean) as ChatToolPayload[];
99
170
  },
100
171
 
172
+ internal_updatePluginError: async (id, error) => {
173
+ const { refreshMessages } = get();
174
+
175
+ await messageService.updateMessage(id, { pluginError: error });
176
+ await refreshMessages();
177
+ },
178
+
101
179
  invokeBuiltinTool: async (id, payload) => {
102
180
  const { internal_toggleChatLoading, internal_updateMessageContent } = get();
103
181
  const params = JSON.parse(payload.arguments);
@@ -131,9 +209,9 @@ export const chatPlugin: StateCreator<
131
209
  },
132
210
 
133
211
  invokeDefaultTypePlugin: async (id, payload) => {
134
- const { runPluginApi } = get();
212
+ const { internal_callPluginApi } = get();
135
213
 
136
- const data = await runPluginApi(id, payload);
214
+ const data = await internal_callPluginApi(id, payload);
137
215
 
138
216
  if (!data) return;
139
217
 
@@ -141,9 +219,9 @@ export const chatPlugin: StateCreator<
141
219
  },
142
220
 
143
221
  invokeMarkdownTypePlugin: async (id, payload) => {
144
- const { runPluginApi } = get();
222
+ const { internal_callPluginApi } = get();
145
223
 
146
- await runPluginApi(id, payload);
224
+ await internal_callPluginApi(id, payload);
147
225
  },
148
226
 
149
227
  invokeStandaloneTypePlugin: async (id, payload) => {
@@ -166,49 +244,13 @@ export const chatPlugin: StateCreator<
166
244
  }
167
245
  },
168
246
 
169
- runPluginApi: async (id, payload) => {
170
- const { internal_updateMessageContent, refreshMessages, internal_toggleChatLoading } = get();
171
- let data: string;
247
+ reInvokeToolMessage: async (id) => {
248
+ const message = chatSelectors.getMessageById(id)(get());
249
+ if (!message || message.role !== 'tool' || !message.plugin) return;
172
250
 
173
- try {
174
- const abortController = internal_toggleChatLoading(
175
- true,
176
- id,
177
- n('fetchPlugin/start') as string,
178
- );
251
+ const payload: ChatToolPayload = { ...message.plugin, id: message.tool_call_id! };
179
252
 
180
- const message = chatSelectors.getMessageById(id)(get());
181
-
182
- const res = await chatService.runPluginApi(payload, {
183
- signal: abortController?.signal,
184
- trace: { observationId: message?.observationId, traceId: message?.traceId },
185
- });
186
- data = res.text;
187
-
188
- // save traceId
189
- if (res.traceId) {
190
- await messageService.updateMessage(id, { traceId: res.traceId });
191
- }
192
- } catch (error) {
193
- console.log(error);
194
- const err = error as Error;
195
-
196
- // ignore the aborted request error
197
- if (!err.message.includes('The user aborted a request.')) {
198
- await messageService.updateMessageError(id, error as any);
199
- await refreshMessages();
200
- }
201
-
202
- data = '';
203
- }
204
-
205
- internal_toggleChatLoading(false, id, n('fetchPlugin/end') as string);
206
- // 如果报错则结束了
207
- if (!data) return;
208
-
209
- await internal_updateMessageContent(id, data);
210
-
211
- return data;
253
+ await get().internal_invokeDifferentTypePlugin(id, payload);
212
254
  },
213
255
 
214
256
  triggerAIMessage: async ({ parentId, traceId }) => {
@@ -216,19 +258,10 @@ export const chatPlugin: StateCreator<
216
258
  const chats = chatSelectors.currentChats(get());
217
259
  await internal_coreProcessMessage(chats, parentId ?? chats.at(-1)!.id, { traceId });
218
260
  },
219
-
220
261
  triggerToolCalls: async (assistantId) => {
221
262
  const message = chatSelectors.getMessageById(assistantId)(get());
222
263
  if (!message || !message.tools) return;
223
264
 
224
- const {
225
- invokeDefaultTypePlugin,
226
- invokeMarkdownTypePlugin,
227
- invokeStandaloneTypePlugin,
228
- invokeBuiltinTool,
229
- triggerAIMessage,
230
- } = get();
231
-
232
265
  let shouldCreateMessage = false;
233
266
  let latestToolId = '';
234
267
  const messagePools = message.tools.map(async (payload) => {
@@ -244,29 +277,12 @@ export const chatPlugin: StateCreator<
244
277
 
245
278
  const id = await get().internal_createMessage(toolMessage);
246
279
 
247
- switch (payload.type) {
248
- case 'standalone': {
249
- await invokeStandaloneTypePlugin(id, payload);
250
- break;
251
- }
252
-
253
- case 'markdown': {
254
- await invokeMarkdownTypePlugin(id, payload);
255
- break;
256
- }
257
-
258
- case 'builtin': {
259
- await invokeBuiltinTool(id, payload);
260
- break;
261
- }
280
+ // trigger the plugin call
281
+ const data = await get().internal_invokeDifferentTypePlugin(id, payload);
262
282
 
263
- default: {
264
- const data = await invokeDefaultTypePlugin(id, payload);
265
- if (data) {
266
- shouldCreateMessage = true;
267
- latestToolId = id;
268
- }
269
- }
283
+ if (payload.type === 'default' && data) {
284
+ shouldCreateMessage = true;
285
+ latestToolId = id;
270
286
  }
271
287
  });
272
288
 
@@ -277,9 +293,8 @@ export const chatPlugin: StateCreator<
277
293
 
278
294
  const traceId = chatSelectors.getTraceIdByMessageId(latestToolId)(get());
279
295
 
280
- await triggerAIMessage({ traceId });
296
+ await get().triggerAIMessage({ traceId });
281
297
  },
282
-
283
298
  updatePluginState: async (id, key, value) => {
284
299
  const { refreshMessages } = get();
285
300
 
@@ -6,11 +6,11 @@ import { StateCreator } from 'zustand/vanilla';
6
6
  import { isDev } from '@/utils/env';
7
7
 
8
8
  import { ChatStoreState, initialState } from './initialState';
9
+ import { ChatBuiltinToolAction, chatToolSlice } from './slices/builtinTool/action';
9
10
  import { ChatEnhanceAction, chatEnhance } from './slices/enchance/action';
10
11
  import { ChatMessageAction, chatMessage } from './slices/message/action';
11
12
  import { ChatPluginAction, chatPlugin } from './slices/plugin/action';
12
13
  import { ShareAction, chatShare } from './slices/share/action';
13
- import { ChatToolAction, chatToolSlice } from './slices/tool/action';
14
14
  import { ChatTopicAction, chatTopic } from './slices/topic/action';
15
15
 
16
16
  export interface ChatStoreAction
@@ -19,7 +19,7 @@ export interface ChatStoreAction
19
19
  ShareAction,
20
20
  ChatEnhanceAction,
21
21
  ChatPluginAction,
22
- ChatToolAction {}
22
+ ChatBuiltinToolAction {}
23
23
 
24
24
  export type ChatStore = ChatStoreAction & ChatStoreState;
25
25
 
@@ -156,7 +156,9 @@ describe('useToolStore:pluginStore', () => {
156
156
  const { result } = renderHook(() => useToolStore.getState().useFetchPluginStore());
157
157
 
158
158
  // Then
159
- expect(useSWR).toHaveBeenCalledWith('loadPluginStore', expect.any(Function));
159
+ expect(useSWR).toHaveBeenCalledWith('loadPluginStore', expect.any(Function), {
160
+ revalidateOnFocus: false,
161
+ });
160
162
  expect(result.current.data).toEqual(pluginListMock);
161
163
  expect(result.current.error).toBeNull();
162
164
  expect(result.current.isValidating).toBe(false);
@@ -175,7 +177,9 @@ describe('useToolStore:pluginStore', () => {
175
177
  const { result } = renderHook(() => useToolStore.getState().useFetchPluginStore());
176
178
 
177
179
  // Then
178
- expect(useSWR).toHaveBeenCalledWith('loadPluginStore', expect.any(Function));
180
+ expect(useSWR).toHaveBeenCalledWith('loadPluginStore', expect.any(Function), {
181
+ revalidateOnFocus: false,
182
+ });
179
183
  expect(result.current.data).toBeNull();
180
184
  expect(result.current.error).toEqual(error);
181
185
  expect(result.current.isValidating).toBe(false);
@@ -101,5 +101,7 @@ export const createPluginStoreSlice: StateCreator<
101
101
  revalidateOnFocus: false,
102
102
  }),
103
103
  useFetchPluginStore: () =>
104
- useSWR<LobeChatPluginsMarketIndex>('loadPluginStore', get().loadPluginStore),
104
+ useSWR<LobeChatPluginsMarketIndex>('loadPluginStore', get().loadPluginStore, {
105
+ revalidateOnFocus: false,
106
+ }),
105
107
  });
@@ -0,0 +1,50 @@
1
+ import { Alert, Highlighter, Icon } from '@lobehub/ui';
2
+ import { Button } from 'antd';
3
+ import { LucideRefreshCw } from 'lucide-react';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+
8
+ import { useChatStore } from '@/store/chat';
9
+ import { chatSelectors } from '@/store/chat/selectors';
10
+
11
+ interface ErrorProps {
12
+ index: number;
13
+ messageId: string;
14
+ }
15
+
16
+ const Error = memo<ErrorProps>(({ messageId, index }) => {
17
+ const { t } = useTranslation('error');
18
+ const { t: ct } = useTranslation('common');
19
+
20
+ const error = useChatStore(
21
+ (s) => chatSelectors.getMessageById(messageId)(s)?.pluginState?.['error']?.[index],
22
+ );
23
+ const [reInvokeToolMessage] = useChatStore((s) => [s.reInvokeToolMessage]);
24
+
25
+ return (
26
+ error && (
27
+ <Flexbox gap={12}>
28
+ <Alert
29
+ extra={
30
+ <Highlighter copyButtonSize={'small'} language={'json'}>
31
+ {JSON.stringify(error.body, null, 2)}
32
+ </Highlighter>
33
+ }
34
+ extraDefaultExpand
35
+ message={t(`response.${error.errorType}` as any)}
36
+ type={'error'}
37
+ />
38
+ <Button
39
+ icon={<Icon icon={LucideRefreshCw} />}
40
+ onClick={() => reInvokeToolMessage(messageId)}
41
+ type={'primary'}
42
+ >
43
+ {ct('retry')}
44
+ </Button>
45
+ </Flexbox>
46
+ )
47
+ );
48
+ });
49
+
50
+ export default Error;
@@ -0,0 +1,44 @@
1
+ import { Icon, Image, Tooltip } from '@lobehub/ui';
2
+ import { Loader2 } from 'lucide-react';
3
+ import { memo } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import ImageFileItem from '@/components/FileList/ImageFileItem';
8
+
9
+ interface ImagePreviewProps {
10
+ imageId?: string;
11
+ previewUrl?: string;
12
+ prompt: string;
13
+ }
14
+
15
+ const ImagePreview = memo<ImagePreviewProps>(({ imageId, previewUrl, prompt }) => {
16
+ const { t } = useTranslation('tool');
17
+
18
+ return imageId ? (
19
+ // <Flexbox className={styles.action}>
20
+ // <ActionIconGroup
21
+ // items={[{ icon: LucideEdit, key: 'edit', label: t('edit', { ns: 'common' }) }]}
22
+ // onActionClick={(e) => {
23
+ // if (e.key === 'edit') {
24
+ // setEdit(true);
25
+ // }
26
+ // }}
27
+ // />
28
+ // </Flexbox>
29
+ <ImageFileItem id={imageId} />
30
+ ) : (
31
+ previewUrl && (
32
+ <Flexbox style={{ position: 'relative' }}>
33
+ <div style={{ position: 'absolute', right: 8, top: 8, zIndex: 10 }}>
34
+ <Tooltip title={t('dalle.downloading')}>
35
+ <Icon icon={Loader2} size={'large'} spin />
36
+ </Tooltip>
37
+ </div>
38
+ <Image alt={prompt} size={'100%'} src={previewUrl} />
39
+ </Flexbox>
40
+ )
41
+ );
42
+ });
43
+
44
+ export default ImagePreview;
@@ -1,4 +1,4 @@
1
- import { Icon, Image, Tooltip } from '@lobehub/ui';
1
+ import { Highlighter, Icon } from '@lobehub/ui';
2
2
  import { Spin } from 'antd';
3
3
  import { createStyles } from 'antd-style';
4
4
  import { Loader2 } from 'lucide-react';
@@ -6,12 +6,13 @@ import { memo, useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
 
9
- import ImageFileItem from '@/components/FileList/ImageFileItem';
10
9
  import { useChatStore } from '@/store/chat';
11
10
  import { chatToolSelectors } from '@/store/chat/selectors';
12
11
  import { DallEImageItem } from '@/types/tool/dalle';
13
12
 
14
13
  import EditMode from './EditMode';
14
+ import Error from './Error';
15
+ import ImagePreview from './Image';
15
16
 
16
17
  const useStyles = createStyles(({ css, token, prefixCls }) => ({
17
18
  action: css`
@@ -32,8 +33,8 @@ const useStyles = createStyles(({ css, token, prefixCls }) => ({
32
33
  `,
33
34
  }));
34
35
 
35
- const ImageItem = memo<DallEImageItem & { messageId: string }>(
36
- ({ prompt, messageId, imageId, previewUrl, style, size, quality }) => {
36
+ const ImageItem = memo<DallEImageItem & { index: number; messageId: string }>(
37
+ ({ prompt, messageId, imageId, previewUrl, index, style, size, quality }) => {
37
38
  const { t } = useTranslation('tool');
38
39
  const { styles } = useStyles();
39
40
 
@@ -55,30 +56,7 @@ const ImageItem = memo<DallEImageItem & { messageId: string }>(
55
56
  );
56
57
 
57
58
  if (imageId || previewUrl)
58
- return imageId ? (
59
- // <Flexbox className={styles.action}>
60
- // <ActionIconGroup
61
- // items={[{ icon: LucideEdit, key: 'edit', label: t('edit', { ns: 'common' }) }]}
62
- // onActionClick={(e) => {
63
- // if (e.key === 'edit') {
64
- // setEdit(true);
65
- // }
66
- // }}
67
- // />
68
- // </Flexbox>
69
- <ImageFileItem id={imageId} />
70
- ) : (
71
- previewUrl && (
72
- <Flexbox style={{ position: 'relative' }}>
73
- <div style={{ position: 'absolute', right: 8, top: 8, zIndex: 10 }}>
74
- <Tooltip title={t('dalle.downloading')}>
75
- <Icon icon={Loader2} size={'large'} spin />
76
- </Tooltip>
77
- </div>
78
- <Image alt={prompt} size={'100%'} src={previewUrl} />
79
- </Flexbox>
80
- )
81
- );
59
+ return <ImagePreview imageId={imageId} previewUrl={previewUrl} prompt={prompt} />;
82
60
 
83
61
  return (
84
62
  <Flexbox className={styles.container} padding={8}>
@@ -87,7 +65,20 @@ const ImageItem = memo<DallEImageItem & { messageId: string }>(
87
65
  {prompt}
88
66
  </Spin>
89
67
  ) : (
90
- prompt
68
+ <Flexbox gap={12}>
69
+ <Flexbox>
70
+ <Highlighter
71
+ copyButtonSize={'small'}
72
+ fileName={t('dalle.prompt')}
73
+ fullFeatured
74
+ language={'prompt'}
75
+ showLanguage
76
+ >
77
+ {prompt}
78
+ </Highlighter>
79
+ </Flexbox>
80
+ <Error index={index} messageId={messageId} />
81
+ </Flexbox>
91
82
  )}
92
83
  </Flexbox>
93
84
  );