@lobehub/chat 0.114.9 → 0.115.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/locales/de-DE/tool.json +8 -0
- package/locales/en-US/tool.json +8 -0
- package/locales/es-ES/tool.json +8 -0
- package/locales/fr-FR/tool.json +8 -0
- package/locales/it-IT/tool.json +8 -0
- package/locales/ja-JP/tool.json +8 -0
- package/locales/ko-KR/tool.json +8 -0
- package/locales/nl-NL/tool.json +8 -0
- package/locales/pl-PL/tool.json +8 -0
- package/locales/pt-BR/tool.json +8 -0
- package/locales/ru-RU/tool.json +8 -0
- package/locales/tr-TR/tool.json +8 -0
- package/locales/zh-CN/plugin.json +2 -2
- package/locales/zh-CN/setting.json +1 -1
- package/locales/zh-CN/tool.json +8 -0
- package/locales/zh-TW/tool.json +8 -0
- package/package.json +1 -1
- package/src/app/api/openai/images/createImageGeneration.ts +26 -0
- package/src/app/api/openai/images/route.ts +16 -0
- package/src/app/api/proxy/route.ts +3 -1
- package/src/app/chat/(desktop)/features/ChatHeader.tsx +56 -56
- package/src/app/chat/features/ChatHeader/PluginTag/PluginStatus.tsx +7 -4
- package/src/app/chat/features/ChatHeader/PluginTag/index.tsx +3 -3
- package/src/app/chat/features/SystemRole/index.tsx +0 -1
- package/src/components/GalleyGrid/Grid.tsx +36 -0
- package/src/components/GalleyGrid/index.tsx +58 -0
- package/src/components/GalleyGrid/style.ts +24 -0
- package/src/const/settings.ts +7 -0
- package/src/database/schemas/files.ts +1 -0
- package/src/database/schemas/message.ts +1 -1
- package/src/features/AgentSetting/AgentPlugin/index.tsx +2 -2
- package/src/features/ChatInput/ActionBar/ModelSwitch.tsx +2 -1
- package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +3 -3
- package/src/features/ChatInput/ActionBar/Tools/index.tsx +26 -14
- package/src/features/Conversation/ChatList/Messages/Function.tsx +1 -1
- package/src/features/Conversation/ChatList/Plugins/Inspector/index.tsx +4 -2
- package/src/features/Conversation/ChatList/Plugins/Render/BuiltinType/index.tsx +36 -0
- package/src/features/Conversation/ChatList/Plugins/Render/DefaultType/index.tsx +6 -66
- package/src/features/Conversation/ChatList/Plugins/Render/Loading.tsx +61 -0
- package/src/features/Conversation/ChatList/Plugins/Render/index.tsx +21 -12
- package/src/features/Conversation/ChatList/Plugins/Render/useParseContent.ts +15 -0
- package/src/locales/default/index.ts +2 -0
- package/src/locales/default/plugin.ts +2 -2
- package/src/locales/default/setting.ts +1 -1
- package/src/locales/default/tool.ts +9 -0
- package/src/services/__tests__/chat.test.ts +132 -2
- package/src/services/_url.ts +1 -0
- package/src/services/chat.ts +3 -5
- package/src/services/file.ts +20 -5
- package/src/services/imageGeneration.ts +26 -0
- package/src/store/chat/actions/enhance.ts +65 -1
- package/src/store/chat/actions/plugin.test.ts +9 -9
- package/src/store/chat/actions/plugin.ts +38 -6
- package/src/store/chat/initialState.ts +3 -0
- package/src/store/chat/selectors/enchance.ts +11 -0
- package/src/store/chat/selectors/index.ts +1 -0
- package/src/store/global/selectors/__snapshots__/settings.test.ts.snap +5 -0
- package/src/store/global/selectors/settings.ts +5 -0
- package/src/store/tool/helpers.ts +6 -5
- package/src/store/tool/initialState.ts +3 -1
- package/src/store/tool/selectors/index.ts +5 -0
- package/src/store/tool/selectors/tool.test.ts +128 -0
- package/src/store/tool/selectors/tool.ts +125 -0
- package/src/store/tool/slices/builtin/action.ts +54 -0
- package/src/store/tool/slices/builtin/index.ts +2 -0
- package/src/store/tool/slices/builtin/initialState.ts +13 -0
- package/src/store/tool/slices/builtin/selectors.test.ts +70 -0
- package/src/store/tool/slices/builtin/selectors.ts +20 -0
- package/src/store/tool/slices/customPlugin/action.ts +0 -3
- package/src/store/tool/slices/plugin/selectors.test.ts +0 -69
- package/src/store/tool/slices/plugin/selectors.ts +1 -84
- package/src/store/tool/store.ts +8 -2
- package/src/tools/dalle/Render/EditMode.tsx +66 -0
- package/src/tools/dalle/Render/Item.tsx +97 -0
- package/src/tools/dalle/Render/ToolBar.tsx +54 -0
- package/src/tools/dalle/Render/index.tsx +21 -0
- package/src/tools/dalle/index.ts +81 -0
- package/src/tools/index.ts +11 -0
- package/src/tools/renders.ts +8 -0
- package/src/types/chatMessage.ts +1 -1
- package/src/types/openai/image.ts +34 -0
- package/src/types/settings.ts +6 -0
- package/src/types/tool/builtin.ts +36 -0
- package/src/types/tool/dalle.ts +12 -0
- package/src/types/tool/index.ts +5 -1
- package/src/types/tool/tool.ts +9 -0
- package/src/store/tool/selectors.ts +0 -3
package/src/services/file.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FileModel } from '@/database/models/file';
|
|
2
2
|
import { DB_File } from '@/database/schemas/files';
|
|
3
|
+
import { PROXY_URL } from '@/services/_url';
|
|
3
4
|
import { FilePreview } from '@/types/files';
|
|
4
5
|
import compressImage from '@/utils/compressImage';
|
|
5
6
|
|
|
@@ -14,6 +15,7 @@ class FileService {
|
|
|
14
15
|
if (this.isImage(file.fileType) && !isTestData) {
|
|
15
16
|
return this.uploadImageFile(file);
|
|
16
17
|
}
|
|
18
|
+
|
|
17
19
|
// save to local storage
|
|
18
20
|
// we may want to save to a remote server later
|
|
19
21
|
return FileModel.create(file);
|
|
@@ -22,6 +24,7 @@ class FileService {
|
|
|
22
24
|
async uploadImageFile(file: DB_File) {
|
|
23
25
|
// 加载图片
|
|
24
26
|
const url = file.url || URL.createObjectURL(new Blob([file.data]));
|
|
27
|
+
|
|
25
28
|
const img = new Image();
|
|
26
29
|
img.src = url;
|
|
27
30
|
await (() =>
|
|
@@ -31,10 +34,7 @@ class FileService {
|
|
|
31
34
|
|
|
32
35
|
// 压缩图片
|
|
33
36
|
const fileType = 'image/webp';
|
|
34
|
-
const base64String = compressImage({
|
|
35
|
-
img,
|
|
36
|
-
type: fileType,
|
|
37
|
-
});
|
|
37
|
+
const base64String = compressImage({ img, type: fileType });
|
|
38
38
|
const binaryString = atob(base64String.split('base64,')[1]);
|
|
39
39
|
const uint8Array = Uint8Array.from(binaryString, (char) => char.charCodeAt(0));
|
|
40
40
|
file.data = uint8Array.buffer;
|
|
@@ -42,6 +42,21 @@ class FileService {
|
|
|
42
42
|
return FileModel.create(file);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
async uploadImageByUrl(url: string, file: Pick<DB_File, 'name' | 'metadata'>) {
|
|
46
|
+
const res = await fetch(PROXY_URL, { body: url, method: 'POST' });
|
|
47
|
+
const data = await res.arrayBuffer();
|
|
48
|
+
const fileType = res.headers.get('content-type') || 'image/webp';
|
|
49
|
+
|
|
50
|
+
return this.uploadFile({
|
|
51
|
+
data,
|
|
52
|
+
fileType,
|
|
53
|
+
metadata: file.metadata,
|
|
54
|
+
name: file.name,
|
|
55
|
+
saveMode: 'local',
|
|
56
|
+
size: data.byteLength,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
45
60
|
async removeFile(id: string) {
|
|
46
61
|
return FileModel.delete(id);
|
|
47
62
|
}
|
|
@@ -57,7 +72,7 @@ class FileService {
|
|
|
57
72
|
}
|
|
58
73
|
|
|
59
74
|
// arrayBuffer to url
|
|
60
|
-
const url = URL.createObjectURL(new Blob([item.data]));
|
|
75
|
+
const url = URL.createObjectURL(new Blob([item.data], { type: item.fileType }));
|
|
61
76
|
const base64 = Buffer.from(item.data).toString('base64');
|
|
62
77
|
|
|
63
78
|
return {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createHeaderWithOpenAI } from '@/services/_header';
|
|
2
|
+
import { OPENAI_URLS } from '@/services/_url';
|
|
3
|
+
import { OpenAIImagePayload } from '@/types/openai/image';
|
|
4
|
+
|
|
5
|
+
interface FetchOptions {
|
|
6
|
+
signal?: AbortSignal | undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class ImageGenerationService {
|
|
10
|
+
async generateImage(params: Omit<OpenAIImagePayload, 'model' | 'n'>, options?: FetchOptions) {
|
|
11
|
+
const payload: OpenAIImagePayload = { ...params, model: 'dall-e-3', n: 1 };
|
|
12
|
+
|
|
13
|
+
const res = await fetch(OPENAI_URLS.images, {
|
|
14
|
+
body: JSON.stringify(payload),
|
|
15
|
+
headers: createHeaderWithOpenAI({ 'Content-Type': 'application/json' }),
|
|
16
|
+
method: 'POST',
|
|
17
|
+
signal: options?.signal,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const urls = await res.json();
|
|
21
|
+
|
|
22
|
+
return urls[0] as string;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const imageGenerationService = new ImageGenerationService();
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { produce } from 'immer';
|
|
2
|
+
import pMap from 'p-map';
|
|
2
3
|
import { StateCreator } from 'zustand/vanilla';
|
|
3
4
|
|
|
4
5
|
import { chainLangDetect } from '@/chains/langDetect';
|
|
5
6
|
import { chainTranslate } from '@/chains/translate';
|
|
6
7
|
import { supportLocales } from '@/locales/options';
|
|
7
8
|
import { chatService } from '@/services/chat';
|
|
9
|
+
import { fileService } from '@/services/file';
|
|
10
|
+
import { imageGenerationService } from '@/services/imageGeneration';
|
|
8
11
|
import { messageService } from '@/services/message';
|
|
9
12
|
import { chatSelectors } from '@/store/chat/selectors';
|
|
10
13
|
import { ChatStore } from '@/store/chat/store';
|
|
14
|
+
import { DallEImageItem } from '@/types/tool/dalle';
|
|
11
15
|
import { setNamespace } from '@/utils/storeDebug';
|
|
12
16
|
|
|
13
17
|
const n = setNamespace('enhance');
|
|
@@ -17,13 +21,16 @@ const n = setNamespace('enhance');
|
|
|
17
21
|
*/
|
|
18
22
|
export interface ChatEnhanceAction {
|
|
19
23
|
clearTTS: (id: string) => Promise<void>;
|
|
20
|
-
|
|
21
24
|
clearTranslate: (id: string) => Promise<void>;
|
|
25
|
+
generateImageFromPrompts: (items: DallEImageItem[], id: string) => Promise<void>;
|
|
26
|
+
text2image: (id: string, data: DallEImageItem[]) => Promise<void>;
|
|
27
|
+
toggleDallEImageLoading: (key: string, value: boolean) => void;
|
|
22
28
|
translateMessage: (id: string, targetLang: string) => Promise<void>;
|
|
23
29
|
ttsMessage: (
|
|
24
30
|
id: string,
|
|
25
31
|
state?: { contentMd5?: string; file?: string; voice?: string },
|
|
26
32
|
) => Promise<void>;
|
|
33
|
+
updateImageItem: (id: string, updater: (data: DallEImageItem[]) => void) => Promise<void>;
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
export const chatEnhance: StateCreator<
|
|
@@ -42,6 +49,52 @@ export const chatEnhance: StateCreator<
|
|
|
42
49
|
await get().refreshMessages();
|
|
43
50
|
},
|
|
44
51
|
|
|
52
|
+
generateImageFromPrompts: async (items, messageId) => {
|
|
53
|
+
const { toggleDallEImageLoading, updateImageItem } = get();
|
|
54
|
+
// eslint-disable-next-line unicorn/consistent-function-scoping
|
|
55
|
+
const getMessageById = (id: string) => chatSelectors.getMessageById(id)(get());
|
|
56
|
+
|
|
57
|
+
const message = getMessageById(messageId);
|
|
58
|
+
const parent = getMessageById(message!.parentId!);
|
|
59
|
+
const originPrompt = parent?.content;
|
|
60
|
+
|
|
61
|
+
await pMap(items, async (params, index) => {
|
|
62
|
+
toggleDallEImageLoading(messageId + params.prompt, true);
|
|
63
|
+
const url = await imageGenerationService.generateImage(params);
|
|
64
|
+
|
|
65
|
+
await updateImageItem(messageId, (draft) => {
|
|
66
|
+
draft[index].previewUrl = url;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
toggleDallEImageLoading(messageId + params.prompt, false);
|
|
70
|
+
|
|
71
|
+
fileService
|
|
72
|
+
.uploadImageByUrl(url, {
|
|
73
|
+
metadata: { ...params, originPrompt: originPrompt },
|
|
74
|
+
name: `${originPrompt || params.prompt}_${index}.png`,
|
|
75
|
+
})
|
|
76
|
+
.then(({ id }) => {
|
|
77
|
+
updateImageItem(messageId, (draft) => {
|
|
78
|
+
draft[index].imageId = id;
|
|
79
|
+
draft[index].previewUrl = undefined;
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
text2image: async (id, data) => {
|
|
85
|
+
// const isAutoGen = settingsSelectors.isDalleAutoGenerating(useGlobalStore.getState());
|
|
86
|
+
// if (!isAutoGen) return;
|
|
87
|
+
|
|
88
|
+
await get().generateImageFromPrompts(data, id);
|
|
89
|
+
},
|
|
90
|
+
toggleDallEImageLoading: (key, value) => {
|
|
91
|
+
set(
|
|
92
|
+
{ dalleImageLoading: { ...get().dalleImageLoading, [key]: value } },
|
|
93
|
+
false,
|
|
94
|
+
n('toggleDallEImageLoading'),
|
|
95
|
+
);
|
|
96
|
+
},
|
|
97
|
+
|
|
45
98
|
translateMessage: async (id, targetLang) => {
|
|
46
99
|
const { toggleChatLoading, dispatchMessage, refreshMessages } = get();
|
|
47
100
|
|
|
@@ -95,4 +148,15 @@ export const chatEnhance: StateCreator<
|
|
|
95
148
|
await messageService.updateMessageTTS(id, state);
|
|
96
149
|
await get().refreshMessages();
|
|
97
150
|
},
|
|
151
|
+
|
|
152
|
+
updateImageItem: async (id, updater) => {
|
|
153
|
+
const message = chatSelectors.getMessageById(id)(get());
|
|
154
|
+
if (!message) return;
|
|
155
|
+
|
|
156
|
+
const data: DallEImageItem[] = JSON.parse(message.content);
|
|
157
|
+
|
|
158
|
+
const nextContent = produce(data, updater);
|
|
159
|
+
await messageService.updateMessageContent(id, JSON.stringify(nextContent));
|
|
160
|
+
await get().refreshMessages();
|
|
161
|
+
},
|
|
98
162
|
});
|
|
@@ -86,7 +86,7 @@ describe('ChatPluginAction', () => {
|
|
|
86
86
|
const { result } = renderHook(() => useChatStore());
|
|
87
87
|
|
|
88
88
|
await act(async () => {
|
|
89
|
-
await result.current.
|
|
89
|
+
await result.current.invokeDefaultTypePlugin(messageId, pluginPayload);
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
expect(initialState.toggleChatLoading).toHaveBeenCalledWith(
|
|
@@ -121,7 +121,7 @@ describe('ChatPluginAction', () => {
|
|
|
121
121
|
const { result } = renderHook(() => useChatStore());
|
|
122
122
|
|
|
123
123
|
await act(async () => {
|
|
124
|
-
await result.current.
|
|
124
|
+
await result.current.invokeDefaultTypePlugin(messageId, pluginPayload);
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
expect(initialState.toggleChatLoading).toHaveBeenCalledWith(
|
|
@@ -150,7 +150,7 @@ describe('ChatPluginAction', () => {
|
|
|
150
150
|
|
|
151
151
|
const initialState = {
|
|
152
152
|
refreshMessages: vi.fn(),
|
|
153
|
-
|
|
153
|
+
invokeDefaultTypePlugin: vi.fn(),
|
|
154
154
|
};
|
|
155
155
|
useChatStore.setState(initialState);
|
|
156
156
|
|
|
@@ -172,7 +172,7 @@ describe('ChatPluginAction', () => {
|
|
|
172
172
|
role: 'function',
|
|
173
173
|
});
|
|
174
174
|
expect(initialState.refreshMessages).toHaveBeenCalled();
|
|
175
|
-
expect(initialState.
|
|
175
|
+
expect(initialState.invokeDefaultTypePlugin).toHaveBeenCalledWith(
|
|
176
176
|
messageId,
|
|
177
177
|
messagePluginPayload,
|
|
178
178
|
);
|
|
@@ -191,7 +191,7 @@ describe('ChatPluginAction', () => {
|
|
|
191
191
|
|
|
192
192
|
useToolStore.setState({ installedPlugins: [plugin] });
|
|
193
193
|
|
|
194
|
-
useChatStore.setState({ refreshMessages: vi.fn(),
|
|
194
|
+
useChatStore.setState({ refreshMessages: vi.fn(), invokeDefaultTypePlugin: vi.fn() });
|
|
195
195
|
|
|
196
196
|
(chatSelectors.getMessageById as Mock).mockImplementation(() => () => ({
|
|
197
197
|
id: messageId,
|
|
@@ -212,7 +212,7 @@ describe('ChatPluginAction', () => {
|
|
|
212
212
|
plugin: expect.objectContaining({ apiName }),
|
|
213
213
|
}),
|
|
214
214
|
);
|
|
215
|
-
expect(result.current.
|
|
215
|
+
expect(result.current.invokeDefaultTypePlugin).toHaveBeenCalledWith(
|
|
216
216
|
messageId,
|
|
217
217
|
expect.objectContaining({
|
|
218
218
|
apiName: apiName,
|
|
@@ -231,7 +231,7 @@ describe('ChatPluginAction', () => {
|
|
|
231
231
|
|
|
232
232
|
useChatStore.setState({
|
|
233
233
|
refreshMessages: vi.fn(),
|
|
234
|
-
|
|
234
|
+
invokeDefaultTypePlugin: vi.fn(),
|
|
235
235
|
});
|
|
236
236
|
|
|
237
237
|
(chatSelectors.getMessageById as Mock).mockImplementation(() => () => ({
|
|
@@ -248,8 +248,8 @@ describe('ChatPluginAction', () => {
|
|
|
248
248
|
// 验证 refreshMessages 是否被调用
|
|
249
249
|
expect(result.current.refreshMessages).toHaveBeenCalled();
|
|
250
250
|
|
|
251
|
-
// 验证
|
|
252
|
-
expect(result.current.
|
|
251
|
+
// 验证 invokeDefaultTypePlugin 是否没有被调用,因为类型是 standalone
|
|
252
|
+
expect(result.current.invokeDefaultTypePlugin).not.toHaveBeenCalled();
|
|
253
253
|
});
|
|
254
254
|
});
|
|
255
255
|
|
|
@@ -17,7 +17,8 @@ const n = setNamespace('plugin');
|
|
|
17
17
|
|
|
18
18
|
export interface ChatPluginAction {
|
|
19
19
|
fillPluginMessageContent: (id: string, content: string) => Promise<void>;
|
|
20
|
-
|
|
20
|
+
invokeBuiltinTool: (id: string, payload: ChatPluginPayload) => Promise<void>;
|
|
21
|
+
invokeDefaultTypePlugin: (id: string, payload: any) => Promise<void>;
|
|
21
22
|
triggerFunctionCall: (id: string) => Promise<void>;
|
|
22
23
|
updatePluginState: (id: string, key: string, value: any) => Promise<void>;
|
|
23
24
|
}
|
|
@@ -37,7 +38,26 @@ export const chatPlugin: StateCreator<
|
|
|
37
38
|
const chats = chatSelectors.currentChats(get());
|
|
38
39
|
await coreProcessMessage(chats, id);
|
|
39
40
|
},
|
|
40
|
-
|
|
41
|
+
invokeBuiltinTool: async (id, payload) => {
|
|
42
|
+
const { toggleChatLoading, refreshMessages } = get();
|
|
43
|
+
const params = JSON.parse(payload.arguments);
|
|
44
|
+
toggleChatLoading(true, id, n('invokeBuiltinTool') as string);
|
|
45
|
+
const data = await useToolStore.getState().invokeBuiltinTool(payload.apiName, params);
|
|
46
|
+
toggleChatLoading(false);
|
|
47
|
+
|
|
48
|
+
if (data) {
|
|
49
|
+
await messageService.updateMessageContent(id, data);
|
|
50
|
+
await refreshMessages();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// postToolCalling
|
|
54
|
+
// @ts-ignore
|
|
55
|
+
const { [payload.apiName]: action } = get();
|
|
56
|
+
if (!action || !data) return;
|
|
57
|
+
|
|
58
|
+
await action(id, JSON.parse(data));
|
|
59
|
+
},
|
|
60
|
+
invokeDefaultTypePlugin: async (id, payload) => {
|
|
41
61
|
const { refreshMessages, coreProcessMessage, toggleChatLoading } = get();
|
|
42
62
|
let data: string;
|
|
43
63
|
|
|
@@ -65,8 +85,9 @@ export const chatPlugin: StateCreator<
|
|
|
65
85
|
const chats = chatSelectors.currentChats(get());
|
|
66
86
|
await coreProcessMessage(chats, id);
|
|
67
87
|
},
|
|
88
|
+
|
|
68
89
|
triggerFunctionCall: async (id) => {
|
|
69
|
-
const {
|
|
90
|
+
const { invokeDefaultTypePlugin, invokeBuiltinTool, refreshMessages } = get();
|
|
70
91
|
|
|
71
92
|
const message = chatSelectors.getMessageById(id)(get());
|
|
72
93
|
if (!message) return;
|
|
@@ -111,10 +132,21 @@ export const chatPlugin: StateCreator<
|
|
|
111
132
|
});
|
|
112
133
|
await refreshMessages();
|
|
113
134
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
135
|
+
switch (payload.type) {
|
|
136
|
+
case 'standalone': {
|
|
137
|
+
// TODO: need to auth user's settings
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case 'builtin': {
|
|
141
|
+
await invokeBuiltinTool(id, payload);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
default: {
|
|
145
|
+
await invokeDefaultTypePlugin(id, payload);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
117
148
|
},
|
|
149
|
+
|
|
118
150
|
updatePluginState: async (id, key, value) => {
|
|
119
151
|
const { refreshMessages } = get();
|
|
120
152
|
|
|
@@ -13,6 +13,8 @@ export interface ChatStoreState {
|
|
|
13
13
|
* @deprecated
|
|
14
14
|
*/
|
|
15
15
|
chatLoadingId?: string;
|
|
16
|
+
dalleImageLoading: Record<string, boolean>;
|
|
17
|
+
|
|
16
18
|
inputMessage: string;
|
|
17
19
|
isSearchingTopic: boolean;
|
|
18
20
|
messageLoadingIds: [];
|
|
@@ -36,6 +38,7 @@ export interface ChatStoreState {
|
|
|
36
38
|
|
|
37
39
|
export const initialState: ChatStoreState = {
|
|
38
40
|
activeId: 'inbox',
|
|
41
|
+
dalleImageLoading: {},
|
|
39
42
|
inputMessage: '',
|
|
40
43
|
isSearchingTopic: false,
|
|
41
44
|
messageLoadingIds: [],
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ChatStoreState } from '@/store/chat';
|
|
2
|
+
|
|
3
|
+
const isDallEImageGenerating = (id: string) => (s: ChatStoreState) => s.dalleImageLoading[id];
|
|
4
|
+
|
|
5
|
+
const isGeneratingDallEImage = (s: ChatStoreState) =>
|
|
6
|
+
Object.values(s.dalleImageLoading).some(Boolean);
|
|
7
|
+
|
|
8
|
+
export const chatEnhanceSelectors = {
|
|
9
|
+
isDallEImageGenerating,
|
|
10
|
+
isGeneratingDallEImage,
|
|
11
|
+
};
|
|
@@ -147,6 +147,11 @@ exports[`settingsSelectors > currentSettings > should merge DEFAULT_SETTINGS and
|
|
|
147
147
|
"password": "password123",
|
|
148
148
|
"primaryColor": "blue",
|
|
149
149
|
"themeMode": "light",
|
|
150
|
+
"tool": {
|
|
151
|
+
"dalle": {
|
|
152
|
+
"autoGenerate": false,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
150
155
|
"tts": {
|
|
151
156
|
"openAI": {
|
|
152
157
|
"sttModel": "whisper-1",
|
|
@@ -84,14 +84,19 @@ const currentLanguage = (s: GlobalStore) => {
|
|
|
84
84
|
return locale;
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
+
const dalleConfig = (s: GlobalStore) => s.settings.tool?.dalle || {};
|
|
88
|
+
const isDalleAutoGenerating = (s: GlobalStore) => s.settings.tool?.dalle?.autoGenerate;
|
|
89
|
+
|
|
87
90
|
export const settingsSelectors = {
|
|
88
91
|
currentLanguage,
|
|
89
92
|
currentSettings,
|
|
90
93
|
currentTTS,
|
|
94
|
+
dalleConfig,
|
|
91
95
|
defaultAgent,
|
|
92
96
|
defaultAgentConfig,
|
|
93
97
|
defaultAgentMeta,
|
|
94
98
|
exportSettings,
|
|
99
|
+
isDalleAutoGenerating,
|
|
95
100
|
modelList: modelListSelectors,
|
|
96
101
|
openAIAPI: openAIAPIKeySelectors,
|
|
97
102
|
openAIProxyUrl: openAIProxyUrlSelectors,
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PluginSchema } from '@lobehub/chat-plugin-sdk';
|
|
2
2
|
|
|
3
|
+
import { MetaData } from '@/types/meta';
|
|
3
4
|
import { LobeTool } from '@/types/tool';
|
|
4
5
|
|
|
5
6
|
const getPluginFormList = (list: LobeTool[], id: string) => list?.find((p) => p.identifier === id);
|
|
6
7
|
|
|
7
|
-
const getPluginTitle = (meta?:
|
|
8
|
-
const getPluginDesc = (meta?:
|
|
8
|
+
const getPluginTitle = (meta?: MetaData) => meta?.title;
|
|
9
|
+
const getPluginDesc = (meta?: MetaData) => meta?.description;
|
|
9
10
|
|
|
10
|
-
const getPluginTags = (meta?:
|
|
11
|
-
const getPluginAvatar = (meta?:
|
|
11
|
+
const getPluginTags = (meta?: MetaData) => meta?.tags;
|
|
12
|
+
const getPluginAvatar = (meta?: MetaData) => meta?.avatar || '🧩';
|
|
12
13
|
|
|
13
14
|
const isCustomPlugin = (id: string, pluginList: LobeTool[]) =>
|
|
14
15
|
pluginList.some((i) => i.identifier === id && i.type === 'customPlugin');
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { BuiltinToolState, initialBuiltinToolState } from './slices/builtin';
|
|
1
2
|
import { CustomPluginState, initialCustomPluginState } from './slices/customPlugin';
|
|
2
3
|
import { PluginState, initialPluginState } from './slices/plugin';
|
|
3
4
|
import { PluginStoreState, initialPluginStoreState } from './slices/store';
|
|
4
5
|
|
|
5
|
-
export type ToolStoreState = PluginState & CustomPluginState & PluginStoreState;
|
|
6
|
+
export type ToolStoreState = PluginState & CustomPluginState & PluginStoreState & BuiltinToolState;
|
|
6
7
|
|
|
7
8
|
export const initialState: ToolStoreState = {
|
|
8
9
|
...initialPluginState,
|
|
9
10
|
...initialCustomPluginState,
|
|
10
11
|
...initialPluginStoreState,
|
|
12
|
+
...initialBuiltinToolState,
|
|
11
13
|
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { builtinToolSelectors } from '../slices/builtin/selectors';
|
|
2
|
+
export { customPluginSelectors } from '../slices/customPlugin/selectors';
|
|
3
|
+
export { pluginSelectors } from '../slices/plugin/selectors';
|
|
4
|
+
export { pluginStoreSelectors } from '../slices/store/selectors';
|
|
5
|
+
export { toolSelectors } from './tool';
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { LobeChatPluginManifest, LobeChatPluginMeta } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { initialState } from '../initialState';
|
|
5
|
+
import { ToolStoreState } from '../initialState';
|
|
6
|
+
import { toolSelectors } from './tool';
|
|
7
|
+
|
|
8
|
+
const mockState = {
|
|
9
|
+
...initialState,
|
|
10
|
+
pluginInstallLoading: {
|
|
11
|
+
'plugin-1': false,
|
|
12
|
+
'plugin-2': true,
|
|
13
|
+
},
|
|
14
|
+
installedPlugins: [
|
|
15
|
+
{
|
|
16
|
+
identifier: 'plugin-1',
|
|
17
|
+
type: 'plugin',
|
|
18
|
+
manifest: {
|
|
19
|
+
identifier: 'plugin-1',
|
|
20
|
+
api: [{ name: 'api-1' }],
|
|
21
|
+
type: 'default',
|
|
22
|
+
} as LobeChatPluginManifest,
|
|
23
|
+
settings: { setting1: 'value1' },
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
identifier: 'plugin-2',
|
|
27
|
+
manifest: {
|
|
28
|
+
identifier: 'plugin-2',
|
|
29
|
+
api: [{ name: 'api-2' }],
|
|
30
|
+
},
|
|
31
|
+
type: 'plugin',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
identifier: 'plugin-3',
|
|
35
|
+
manifest: {
|
|
36
|
+
identifier: 'plugin-3',
|
|
37
|
+
api: [{ name: 'api-3' }],
|
|
38
|
+
},
|
|
39
|
+
type: 'customPlugin',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
pluginStoreList: [
|
|
43
|
+
{
|
|
44
|
+
identifier: 'plugin-1',
|
|
45
|
+
author: 'Author 1',
|
|
46
|
+
createdAt: '2021-01-01',
|
|
47
|
+
meta: { avatar: 'avatar-url-1', title: 'Plugin 1' },
|
|
48
|
+
homepage: 'http://homepage-1.com',
|
|
49
|
+
} as LobeChatPluginMeta,
|
|
50
|
+
{
|
|
51
|
+
identifier: 'plugin-2',
|
|
52
|
+
author: 'Author 2',
|
|
53
|
+
createdAt: '2022-02-02',
|
|
54
|
+
meta: { avatar: 'avatar-url-2', title: 'Plugin 2' },
|
|
55
|
+
homepage: 'http://homepage-2.com',
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
} as ToolStoreState;
|
|
59
|
+
|
|
60
|
+
describe('toolSelectors', () => {
|
|
61
|
+
describe('enabledSchema', () => {
|
|
62
|
+
it('enabledSchema should return correct ChatCompletionFunctions array', () => {
|
|
63
|
+
const result = toolSelectors.enabledSchema(['plugin-1', 'plugin-2'])(mockState);
|
|
64
|
+
expect(result).toEqual([{ name: 'plugin-1____api-1' }, { name: 'plugin-2____api-2' }]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('enabledSchema should return with standalone plugin', () => {
|
|
68
|
+
const result = toolSelectors.enabledSchema(['plugin-4'])({
|
|
69
|
+
...mockState,
|
|
70
|
+
installedPlugins: [
|
|
71
|
+
...mockState.installedPlugins,
|
|
72
|
+
{
|
|
73
|
+
identifier: 'plugin-4',
|
|
74
|
+
manifest: {
|
|
75
|
+
identifier: 'plugin-4',
|
|
76
|
+
api: [{ name: 'api-4' }],
|
|
77
|
+
type: 'standalone',
|
|
78
|
+
},
|
|
79
|
+
type: 'plugin',
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
} as ToolStoreState);
|
|
83
|
+
expect(result).toEqual([{ name: 'plugin-4____api-4____standalone' }]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('enabledSchema should return md5 hash apiName', () => {
|
|
87
|
+
const result = toolSelectors.enabledSchema(['long-long-plugin-with-id'])({
|
|
88
|
+
...mockState,
|
|
89
|
+
installedPlugins: [
|
|
90
|
+
...mockState.installedPlugins,
|
|
91
|
+
{
|
|
92
|
+
identifier: 'long-long-plugin-with-id',
|
|
93
|
+
manifest: {
|
|
94
|
+
identifier: 'long-long-plugin-with-id',
|
|
95
|
+
api: [{ name: 'long-long-manifest-long-long-apiName' }],
|
|
96
|
+
},
|
|
97
|
+
type: 'plugin',
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
} as ToolStoreState);
|
|
101
|
+
expect(result).toEqual([
|
|
102
|
+
{ name: 'long-long-plugin-with-id____MD5HASH_396eae4c671da3fb642c49ad2b9e8790' },
|
|
103
|
+
]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('enabledSchema should return empty', () => {
|
|
107
|
+
const result = toolSelectors.enabledSchema([])(mockState);
|
|
108
|
+
expect(result).toEqual([]);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('getPluginManifestLoadingStatus', () => {
|
|
113
|
+
it('should return "loading" if the plugin manifest is being loaded', () => {
|
|
114
|
+
const result = toolSelectors.getManifestLoadingStatus('plugin-2')(mockState);
|
|
115
|
+
expect(result).toBe('loading');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should return "error" if the plugin manifest is not found', () => {
|
|
119
|
+
const result = toolSelectors.getManifestLoadingStatus('non-existing-plugin')(mockState);
|
|
120
|
+
expect(result).toBe('error');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should return "success" if the plugin manifest is loaded', () => {
|
|
124
|
+
const result = toolSelectors.getManifestLoadingStatus('plugin-1')(mockState);
|
|
125
|
+
expect(result).toBe('success');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|