@lobehub/chat 0.102.1 → 0.102.3

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 +50 -0
  2. package/README.md +119 -38
  3. package/README.zh-CN.md +99 -44
  4. package/docs/Development-Guide/Data-Store/Selectors.zh-CN.md +68 -0
  5. package/package.json +3 -3
  6. package/src/app/api/openai/errorResponse.test.ts +55 -0
  7. package/src/app/chat/features/ChatHeader/PluginTag/PluginStatus.tsx +3 -2
  8. package/src/app/chat/features/ChatHeader/PluginTag/index.tsx +2 -1
  9. package/src/app/chat/features/Conversation/ChatList/Error/Plugin/PluginSettings.tsx +2 -1
  10. package/src/app/chat/features/Conversation/ChatList/Extras/Assistant.test.tsx +86 -0
  11. package/src/app/chat/features/Conversation/ChatList/Extras/Assistant.tsx +5 -1
  12. package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/Settings.tsx +2 -1
  13. package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/index.tsx +2 -1
  14. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/Iframe.tsx +2 -1
  15. package/src/app/chat/features/Conversation/index.tsx +3 -3
  16. package/src/app/chat/features/Conversation/usePluginsInit.ts +14 -0
  17. package/src/app/settings/llm/LLM/Checker.tsx +2 -2
  18. package/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx +4 -3
  19. package/src/features/AgentSetting/AgentPlugin/MarketList.tsx +4 -3
  20. package/src/features/AgentSetting/AgentPlugin/PluginSettings.tsx +3 -2
  21. package/src/features/AgentSetting/store/action.ts +5 -5
  22. package/src/features/PluginDevModal/MetaForm.tsx +2 -1
  23. package/src/features/PluginSettingsModal/index.tsx +2 -1
  24. package/src/services/chat.ts +84 -0
  25. package/src/services/plugin.ts +15 -33
  26. package/src/store/plugin/index.ts +0 -1
  27. package/src/store/plugin/selectors.test.ts +185 -0
  28. package/src/store/plugin/selectors.ts +6 -3
  29. package/src/store/plugin/slices/customPlugin/action.ts +3 -3
  30. package/src/store/plugin/slices/plugin/action.ts +44 -15
  31. package/src/store/session/slices/chat/actions/message.ts +2 -2
  32. package/src/store/session/slices/chat/actions/plugin.ts +2 -2
  33. package/src/store/session/slices/chat/actions/topic.ts +2 -2
  34. package/src/store/session/slices/chat/actions/translate.ts +9 -7
  35. package/src/utils/fetch.ts +0 -3
  36. package/tests/setup.ts +6 -1
  37. package/src/app/chat/features/Conversation/ChatList/Extras/AudioPlayer.tsx +0 -139
  38. package/src/services/chatModel.ts +0 -48
  39. package/src/services/pluginMarket.ts +0 -13
@@ -0,0 +1,185 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { PluginStoreState } from './initialState';
4
+ import { pluginSelectors } from './selectors';
5
+
6
+ const mockState = {
7
+ pluginManifestMap: {
8
+ 'plugin-1': {
9
+ identifier: 'plugin-1',
10
+ api: [{ name: 'api-1' }],
11
+ type: 'default',
12
+ },
13
+ 'plugin-2': {
14
+ identifier: 'plugin-2',
15
+ api: [{ name: 'api-2' }],
16
+ type: 'default',
17
+ },
18
+ },
19
+ pluginManifestLoading: {
20
+ 'plugin-1': false,
21
+ 'plugin-2': true,
22
+ },
23
+ pluginList: [
24
+ {
25
+ identifier: 'plugin-1',
26
+ author: 'Author 1',
27
+ createAt: '2021-01-01',
28
+ meta: { avatar: 'avatar-url-1', title: 'Plugin 1' },
29
+ homepage: 'http://homepage-1.com',
30
+ },
31
+ {
32
+ identifier: 'plugin-2',
33
+ author: 'Author 2',
34
+ createAt: '2022-02-02',
35
+ meta: { avatar: 'avatar-url-2', title: 'Plugin 2' },
36
+ homepage: 'http://homepage-2.com',
37
+ },
38
+ ],
39
+ customPluginList: [],
40
+ pluginsSettings: {},
41
+ } as unknown as PluginStoreState;
42
+
43
+ describe('pluginSelectors', () => {
44
+ describe('enabledSchema', () => {
45
+ it('enabledSchema should return correct ChatCompletionFunctions array', () => {
46
+ const result = pluginSelectors.enabledSchema(['plugin-1'])(mockState);
47
+ expect(result).toEqual([{ name: 'plugin-1____api-1____default' }]);
48
+ });
49
+ it('enabledSchema should return empty', () => {
50
+ const result = pluginSelectors.enabledSchema([])(mockState);
51
+ expect(result).toEqual([]);
52
+ });
53
+ });
54
+
55
+ describe('getPluginManifestById', () => {
56
+ it('getPluginManifestById should return the correct manifest', () => {
57
+ const result = pluginSelectors.getPluginManifestById('plugin-1')(mockState);
58
+ expect(result).toEqual(mockState.pluginManifestMap['plugin-1']);
59
+ });
60
+ });
61
+ describe('getPluginManifestLoadingStatus', () => {
62
+ it('success', () => {
63
+ const result = pluginSelectors.getPluginManifestLoadingStatus('plugin-1')(mockState);
64
+ expect(result).toBe('success');
65
+ });
66
+ it('loading', () => {
67
+ let result = pluginSelectors.getPluginManifestLoadingStatus('plugin-2')(mockState);
68
+ expect(result).toBe('loading');
69
+ });
70
+ it('error', () => {
71
+ let result = pluginSelectors.getPluginManifestLoadingStatus('plugin-3')(mockState);
72
+ expect(result).toBe('error');
73
+ });
74
+ });
75
+
76
+ describe('onlinePluginStore', () => {
77
+ it('should return the online plugin list', () => {
78
+ const result = pluginSelectors.onlinePluginStore(mockState);
79
+ expect(result).toEqual(mockState.pluginList);
80
+ });
81
+ });
82
+
83
+ describe('pluginList', () => {
84
+ it('should return the combined list of pluginList and customPluginList', () => {
85
+ const result = pluginSelectors.pluginList(mockState);
86
+ expect(result).toEqual([...mockState.pluginList, ...mockState.customPluginList]);
87
+ });
88
+ });
89
+
90
+ describe('getPluginMetaById', () => {
91
+ it('should return the plugin metadata by id', () => {
92
+ const result = pluginSelectors.getPluginMetaById('plugin-1')(mockState);
93
+ expect(result).toEqual(mockState.pluginList[0]);
94
+ });
95
+ });
96
+
97
+ describe('getDevPluginById', () => {
98
+ it('should return undefined for non-existing custom plugin', () => {
99
+ const result = pluginSelectors.getDevPluginById('non-existing')(mockState);
100
+ expect(result).toBeUndefined();
101
+ });
102
+
103
+ it('should return custom plugin by id if exists', () => {
104
+ const customPlugin = {
105
+ identifier: 'custom-plugin',
106
+ author: 'Custom Author',
107
+ createAt: '2023-03-03',
108
+ meta: { avatar: 'avatar-url-custom', title: 'Custom Plugin' },
109
+ homepage: 'http://homepage-custom.com',
110
+ };
111
+ const stateWithCustomPlugin = {
112
+ ...mockState,
113
+ customPluginList: [customPlugin],
114
+ } as PluginStoreState;
115
+ const result = pluginSelectors.getDevPluginById('custom-plugin')(stateWithCustomPlugin);
116
+ expect(result).toEqual(customPlugin);
117
+ });
118
+ });
119
+
120
+ describe('getPluginSettingsById', () => {
121
+ it('should return the plugin settings by id', () => {
122
+ const mockSettings = { setting1: 'value1' };
123
+ const stateWithSettings = {
124
+ ...mockState,
125
+ pluginsSettings: { 'plugin-1': mockSettings },
126
+ };
127
+ const result = pluginSelectors.getPluginSettingsById('plugin-1')(stateWithSettings);
128
+ expect(result).toEqual(mockSettings);
129
+ });
130
+ });
131
+
132
+ describe('isCustomPlugin', () => {
133
+ it('should return false for a non-custom plugin', () => {
134
+ const result = pluginSelectors.isCustomPlugin('plugin-1')(mockState);
135
+ expect(result).toBe(false);
136
+ });
137
+
138
+ it('should return true for a custom plugin', () => {
139
+ const stateWithCustomPlugin = {
140
+ ...mockState,
141
+ customPluginList: [{ identifier: 'custom-plugin' }],
142
+ } as PluginStoreState;
143
+ const result = pluginSelectors.isCustomPlugin('custom-plugin')(stateWithCustomPlugin);
144
+ expect(result).toBe(true);
145
+ });
146
+ });
147
+
148
+ describe('displayPluginList', () => {
149
+ it('should return a list of plugins with display information', () => {
150
+ const result = pluginSelectors.displayPluginList(mockState);
151
+ const expected = mockState.pluginList.map((p) => ({
152
+ author: p.author,
153
+ avatar: p.meta?.avatar,
154
+ createAt: p.createAt,
155
+ desc: p.meta.description,
156
+ homepage: p.homepage,
157
+ identifier: p.identifier,
158
+ title: p.meta.title,
159
+ }));
160
+ expect(result).toEqual(expected);
161
+ });
162
+ });
163
+
164
+ describe('hasPluginUI', () => {
165
+ it('should return false when the plugin does not have a UI component', () => {
166
+ const result = pluginSelectors.hasPluginUI('non-ui-plugin')(mockState);
167
+ expect(result).toBe(false);
168
+ });
169
+
170
+ it('should return true when the plugin has a UI component', () => {
171
+ const stateWithUIPlugin = {
172
+ ...mockState,
173
+ pluginManifestMap: {
174
+ ...mockState.pluginManifestMap,
175
+ 'ui-plugin': {
176
+ ...mockState.pluginManifestMap['plugin-1'],
177
+ ui: true,
178
+ },
179
+ },
180
+ } as unknown as PluginStoreState;
181
+ const result = pluginSelectors.hasPluginUI('ui-plugin')(stateWithUIPlugin);
182
+ expect(result).toBe(true);
183
+ });
184
+ });
185
+ });
@@ -1,10 +1,10 @@
1
1
  import { uniqBy } from 'lodash-es';
2
2
 
3
3
  import { PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
4
- import { pluginHelpers } from '@/store/plugin/helpers';
5
4
  import { ChatCompletionFunctions } from '@/types/openai/chat';
6
5
 
7
- import { PluginStoreState } from './initialState';
6
+ import { pluginHelpers } from './helpers';
7
+ import type { PluginStoreState } from './initialState';
8
8
 
9
9
  const enabledSchema =
10
10
  (enabledPlugins: string[] = []) =>
@@ -34,6 +34,8 @@ const enabledSchema =
34
34
  return uniqBy(list, 'name');
35
35
  };
36
36
 
37
+ const onlinePluginStore = (s: PluginStoreState) => s.pluginList;
38
+
37
39
  const pluginList = (s: PluginStoreState) => [...s.pluginList, ...s.customPluginList];
38
40
 
39
41
  const getPluginMetaById = (id: string) => (s: PluginStoreState) =>
@@ -47,7 +49,7 @@ const getPluginSettingsById = (id: string) => (s: PluginStoreState) => s.plugins
47
49
 
48
50
  // 获取插件 manifest 加载状态
49
51
  const getPluginManifestLoadingStatus = (id: string) => (s: PluginStoreState) => {
50
- const manifest = getPluginManifestById(id);
52
+ const manifest = getPluginManifestById(id)(s);
51
53
 
52
54
  if (s.pluginManifestLoading[id]) return 'loading';
53
55
 
@@ -86,5 +88,6 @@ export const pluginSelectors = {
86
88
  getPluginSettingsById,
87
89
  hasPluginUI,
88
90
  isCustomPlugin,
91
+ onlinePluginStore,
89
92
  pluginList,
90
93
  };
@@ -47,11 +47,11 @@ export const createCustomPluginSlice: StateCreator<
47
47
  set({ newCustomPlugin: defaultCustomPlugin }, false, t('saveToCustomPluginList'));
48
48
  },
49
49
  updateCustomPlugin: (id, value) => {
50
- const { dispatchCustomPluginList, fetchPluginManifest } = get();
50
+ const { dispatchCustomPluginList, installPlugin } = get();
51
51
  // 1. 更新 list 项信息
52
52
  dispatchCustomPluginList({ id, plugin: value, type: 'updateItem' });
53
- // 2. 更新 重新拉取 manifest
54
- fetchPluginManifest(id);
53
+ // 2. 重新安装插件
54
+ installPlugin(id);
55
55
  },
56
56
 
57
57
  updateNewCustomPlugin: (newCustomPlugin) => {
@@ -9,7 +9,7 @@ import { merge, uniq } from 'lodash-es';
9
9
  import useSWR, { SWRResponse } from 'swr';
10
10
  import { StateCreator } from 'zustand/vanilla';
11
11
 
12
- import { getPluginList } from '@/services/pluginMarket';
12
+ import { pluginService } from '@/services/plugin';
13
13
  import { pluginSelectors } from '@/store/plugin/selectors';
14
14
  import { LobeSessions } from '@/types/session';
15
15
  import { setNamespace } from '@/utils/storeDebug';
@@ -24,13 +24,17 @@ const t = setNamespace('plugin');
24
24
  */
25
25
  export interface PluginAction {
26
26
  checkLocalEnabledPlugins: (sessions: LobeSessions) => void;
27
+ checkPluginsIsInstalled: (plugins: string[]) => void;
27
28
  deletePluginSettings: (id: string) => void;
28
29
  dispatchPluginManifest: (payload: PluginDispatch) => void;
29
- fetchPluginManifest: (identifier: string) => Promise<void>;
30
+ installPlugin: (identifier: string) => Promise<void>;
31
+ installPlugins: (plugins: string[]) => Promise<void>;
32
+ loadPluginStore: () => Promise<LobeChatPluginsMarketIndex>;
30
33
  resetPluginSettings: () => void;
31
34
  updateManifestLoadingState: (key: string, value: boolean | undefined) => void;
32
35
  updatePluginSettings: <T>(id: string, settings: Partial<T>) => void;
33
- useFetchPluginList: () => SWRResponse<LobeChatPluginsMarketIndex>;
36
+ useCheckPluginsIsInstalled: (plugins: string[]) => SWRResponse;
37
+ useFetchPluginStore: () => SWRResponse<LobeChatPluginsMarketIndex>;
34
38
  }
35
39
 
36
40
  export const createPluginSlice: StateCreator<
@@ -40,7 +44,7 @@ export const createPluginSlice: StateCreator<
40
44
  PluginAction
41
45
  > = (set, get) => ({
42
46
  checkLocalEnabledPlugins: async (sessions) => {
43
- const { fetchPluginManifest } = get();
47
+ const { checkPluginsIsInstalled } = get();
44
48
 
45
49
  let enabledPlugins: string[] = [];
46
50
 
@@ -53,9 +57,23 @@ export const createPluginSlice: StateCreator<
53
57
 
54
58
  const plugins = uniq(enabledPlugins);
55
59
 
56
- await Promise.all(plugins.map((identifier) => fetchPluginManifest(identifier)));
60
+ await checkPluginsIsInstalled(plugins);
61
+ },
62
+ checkPluginsIsInstalled: async (plugins) => {
63
+ // if there is no plugins, just skip.
64
+ if (plugins.length === 0) return;
65
+
66
+ const { loadPluginStore, installPlugins } = get();
67
+
68
+ // check if the store is empty
69
+ // if it is, we need to load the plugin store
70
+ if (pluginSelectors.onlinePluginStore(get()).length === 0) {
71
+ await loadPluginStore();
72
+ }
73
+
74
+ await installPlugins(plugins);
57
75
 
58
- set({ manifestPrepared: true }, false, t('checkLocalEnabledPlugins'));
76
+ set({ manifestPrepared: true }, false, t('checkPluginsIsInstalled'));
59
77
  },
60
78
  deletePluginSettings: (id) => {
61
79
  set(
@@ -72,9 +90,10 @@ export const createPluginSlice: StateCreator<
72
90
 
73
91
  set({ pluginManifestMap: nextManifest }, false, t('dispatchPluginManifest', payload));
74
92
  },
75
- fetchPluginManifest: async (name) => {
93
+ installPlugin: async (name) => {
76
94
  const plugin = pluginSelectors.getPluginMetaById(name)(get());
77
- // 1. 校验文件
95
+
96
+ // 1. valid plugin
78
97
 
79
98
  if (!plugin) return;
80
99
 
@@ -98,7 +117,7 @@ export const createPluginSlice: StateCreator<
98
117
 
99
118
  get().updateManifestLoadingState(name, undefined);
100
119
  if (!data) {
101
- message.error('插件描述文件请求失败');
120
+ message.error(`插件 ${plugin.meta.title} 描述文件请求失败`);
102
121
  return;
103
122
  }
104
123
 
@@ -113,6 +132,18 @@ export const createPluginSlice: StateCreator<
113
132
  // 4. 存储 manifest 信息
114
133
  get().dispatchPluginManifest({ id: plugin.identifier, plugin: data, type: 'addManifest' });
115
134
  },
135
+ installPlugins: async (plugins) => {
136
+ const { installPlugin } = get();
137
+
138
+ await Promise.all(plugins.map((identifier) => installPlugin(identifier)));
139
+ },
140
+ loadPluginStore: async () => {
141
+ const pluginMarketIndex = await pluginService.getPluginList();
142
+
143
+ set({ pluginList: pluginMarketIndex.plugins }, false, t('loadPluginList'));
144
+
145
+ return pluginMarketIndex;
146
+ },
116
147
  resetPluginSettings: () => {
117
148
  set({ pluginsSettings: {} }, false, t('resetPluginSettings'));
118
149
  },
@@ -135,10 +166,8 @@ export const createPluginSlice: StateCreator<
135
166
  );
136
167
  },
137
168
 
138
- useFetchPluginList: () =>
139
- useSWR<LobeChatPluginsMarketIndex>('fetchPluginList', getPluginList, {
140
- onSuccess: (pluginMarketIndex) => {
141
- set({ pluginList: pluginMarketIndex.plugins }, false, t('useFetchPluginList'));
142
- },
143
- }),
169
+ useCheckPluginsIsInstalled: (plugins) => useSWR(plugins, get().checkPluginsIsInstalled),
170
+
171
+ useFetchPluginStore: () =>
172
+ useSWR<LobeChatPluginsMarketIndex>('loadPluginStore', get().loadPluginStore),
144
173
  });
@@ -4,7 +4,7 @@ import { StateCreator } from 'zustand/vanilla';
4
4
  import { VISION_MODEL_WHITE_LIST } from '@/const/llm';
5
5
  import { LOADING_FLAT } from '@/const/message';
6
6
  import { VISION_MODEL_DEFAULT_MAX_TOKENS } from '@/const/settings';
7
- import { fetchChatModel } from '@/services/chatModel';
7
+ import { chatService } from '@/services/chat';
8
8
  import { filesSelectors, useFileStore } from '@/store/files';
9
9
  import { SessionStore } from '@/store/session';
10
10
  import { ChatMessage } from '@/types/chatMessage';
@@ -263,7 +263,7 @@ export const chatMessage: StateCreator<
263
263
  }
264
264
 
265
265
  const fetcher = () =>
266
- fetchChatModel(
266
+ chatService.getChatCompletion(
267
267
  {
268
268
  messages: postMessages,
269
269
  model: config.model,
@@ -2,7 +2,7 @@ import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
2
2
  import { StateCreator } from 'zustand/vanilla';
3
3
 
4
4
  import { PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
5
- import { fetchPlugin } from '@/services/plugin';
5
+ import { chatService } from '@/services/chat';
6
6
  import { SessionStore } from '@/store/session';
7
7
  import { OpenAIFunctionCall } from '@/types/chatMessage';
8
8
  import { setNamespace } from '@/utils/storeDebug';
@@ -41,7 +41,7 @@ export const chatPlugin: StateCreator<
41
41
  let data: string;
42
42
  try {
43
43
  const abortController = toggleChatLoading(true, id, t('fetchPlugin') as string);
44
- data = await fetchPlugin(payload, { signal: abortController?.signal });
44
+ data = await chatService.runPluginApi(payload, { signal: abortController?.signal });
45
45
  } catch (error) {
46
46
  dispatchMessage({ id, key: 'error', type: 'updateMessage', value: error });
47
47
 
@@ -2,8 +2,8 @@ import { StateCreator } from 'zustand/vanilla';
2
2
 
3
3
  import { chainSummaryTitle } from '@/chains/summaryTitle';
4
4
  import { LOADING_FLAT } from '@/const/message';
5
+ import { chatService } from '@/services/chat';
5
6
  import { SessionStore } from '@/store/session';
6
- import { fetchPresetTaskResult } from '@/utils/fetch';
7
7
  import { setNamespace } from '@/utils/storeDebug';
8
8
  import { nanoid } from '@/utils/uuid';
9
9
 
@@ -145,7 +145,7 @@ export const chatTopic: StateCreator<
145
145
  let output = '';
146
146
 
147
147
  // 自动总结话题标题
148
- fetchPresetTaskResult({
148
+ chatService.fetchPresetTaskResult({
149
149
  onError: () => {
150
150
  dispatchTopic({ id: topicId, key: 'title', type: 'updateChatTopic', value: defaultTitle });
151
151
  },
@@ -4,8 +4,8 @@ import { StateCreator } from 'zustand/vanilla';
4
4
  import { chainLangDetect } from '@/chains/langDetect';
5
5
  import { chainTranslate } from '@/chains/translate';
6
6
  import { supportLocales } from '@/locales/options';
7
+ import { chatService } from '@/services/chat';
7
8
  import { SessionStore } from '@/store/session';
8
- import { fetchPresetTaskResult } from '@/utils/fetch';
9
9
  import { setNamespace } from '@/utils/storeDebug';
10
10
 
11
11
  import { sessionSelectors } from '../../session/selectors';
@@ -72,14 +72,16 @@ export const chatTranslate: StateCreator<
72
72
  toggleChatLoading(true, id, t('translateMessage(start)', { id }) as string);
73
73
 
74
74
  // detect from language
75
- fetchPresetTaskResult({
76
- params: chainLangDetect(message.content),
77
- }).then((data) => {
78
- if (data && supportLocales.includes(data)) from = data;
79
- });
75
+ chatService
76
+ .fetchPresetTaskResult({
77
+ params: chainLangDetect(message.content),
78
+ })
79
+ .then((data) => {
80
+ if (data && supportLocales.includes(data)) from = data;
81
+ });
80
82
 
81
83
  // translate to target language
82
- await fetchPresetTaskResult({
84
+ await chatService.fetchPresetTaskResult({
83
85
  onMessageHandle: (text) => {
84
86
  dispatchMessage({
85
87
  id,
@@ -1,6 +1,5 @@
1
1
  import { t } from 'i18next';
2
2
 
3
- import { fetchChatModel } from '@/services/chatModel';
4
3
  import { ChatMessageError } from '@/types/chatMessage';
5
4
  import { ErrorResponse, ErrorType } from '@/types/fetch';
6
5
 
@@ -122,5 +121,3 @@ export const fetchAIFactory =
122
121
 
123
122
  return await data?.text();
124
123
  };
125
-
126
- export const fetchPresetTaskResult = fetchAIFactory(fetchChatModel);
package/tests/setup.ts CHANGED
@@ -1,8 +1,13 @@
1
+ /* eslint-disable import/newline-after-import,import/first */
1
2
  import '@testing-library/jest-dom';
2
- // remove antd hash on test
3
3
  import { theme } from 'antd';
4
4
  // mock indexedDB to test with dexie
5
5
  // refs: https://github.com/dumbmatter/fakeIndexedDB#dexie-and-other-indexeddb-api-wrappers
6
6
  import 'fake-indexeddb/auto';
7
+ import React from 'react';
7
8
 
9
+ // remove antd hash on test
8
10
  theme.defaultConfig.hashed = false;
11
+
12
+ // 将 React 设置为全局变量,这样就不需要在每个测试文件中导入它了
13
+ global.React = React;
@@ -1,139 +0,0 @@
1
- import { ActionIcon, ActionIconProps, Icon, Tag } from '@lobehub/ui';
2
- import { Dropdown, Slider } from 'antd';
3
- import { Download, PauseCircle, Play, StopCircle } from 'lucide-react';
4
- import React, { memo, useCallback, useMemo } from 'react';
5
- import { Flexbox } from 'react-layout-kit';
6
-
7
- const secondsToMinutesAndSeconds = (num: number) => Math.floor(num);
8
- export interface AudioProps {
9
- currentTime: number;
10
- download: () => void;
11
- duration: number;
12
- isPlaying: boolean;
13
- pause: () => void;
14
- play: () => void;
15
- setTime: (time: number) => void;
16
- stop: () => void;
17
- }
18
-
19
- export interface AudioPlayerProps {
20
- allowPause?: boolean;
21
- audio: AudioProps;
22
- buttonSize?: ActionIconProps['size'];
23
- className?: string;
24
- isLoading?: boolean;
25
- onInitPlay?: () => void;
26
- onPause?: () => void;
27
- onPlay?: () => void;
28
- onStop?: () => void;
29
- showSlider?: boolean;
30
- style?: React.CSSProperties;
31
- timeRender?: 'tag' | 'text';
32
- timeStyle?: React.CSSProperties;
33
- timeType?: 'left' | 'current' | 'combine';
34
- }
35
-
36
- const AudioPlayer = memo<AudioPlayerProps>(
37
- ({
38
- isLoading,
39
- style,
40
- timeStyle,
41
- buttonSize,
42
- className,
43
- audio,
44
- allowPause = true,
45
- timeType = 'left',
46
- showSlider = true,
47
- timeRender = 'text',
48
- onInitPlay,
49
- onPause,
50
- onStop,
51
- onPlay,
52
- }) => {
53
- const { isPlaying, play, stop, pause, duration, setTime, currentTime, download } = audio;
54
-
55
- const formatedLeftTime = secondsToMinutesAndSeconds(duration - currentTime);
56
- const formatedCurrentTime = secondsToMinutesAndSeconds(currentTime);
57
- const formatedDuration = secondsToMinutesAndSeconds(duration);
58
-
59
- const Time = useMemo(
60
- () => (timeRender === 'tag' ? Tag : (props: any) => <div {...props} />),
61
- [timeRender],
62
- );
63
-
64
- const handlePlay = useCallback(() => {
65
- if ((!duration || duration === 0) && !isLoading) {
66
- onInitPlay?.();
67
- } else {
68
- play?.();
69
- onPlay?.();
70
- }
71
- }, [play, duration]);
72
-
73
- const handlePause = useCallback(() => {
74
- pause?.();
75
- onPause?.();
76
- }, [pause]);
77
-
78
- const handleStop = useCallback(() => {
79
- stop?.();
80
- onStop?.();
81
- }, [stop]);
82
-
83
- return (
84
- <Flexbox
85
- align={'center'}
86
- className={className}
87
- gap={8}
88
- horizontal
89
- style={{ paddingRight: 8, width: '100%', ...style }}
90
- >
91
- <ActionIcon
92
- icon={isPlaying ? (allowPause ? PauseCircle : StopCircle) : Play}
93
- loading={isLoading}
94
- onClick={isPlaying ? (allowPause ? handlePause : handleStop) : handlePlay}
95
- size={buttonSize || { blockSize: 32, fontSize: 16 }}
96
- style={{ flex: 'none' }}
97
- />
98
- {showSlider && (
99
- <Slider
100
- disabled={duration === 0}
101
- max={duration}
102
- min={0}
103
- onChange={(e) => setTime(e)}
104
- step={0.01}
105
- style={{ flex: 1 }}
106
- tooltip={{ formatter: secondsToMinutesAndSeconds as any }}
107
- value={currentTime}
108
- />
109
- )}
110
- <Dropdown
111
- disabled={duration === 0}
112
- menu={{
113
- items: [
114
- {
115
- key: 'download',
116
- label: <Icon icon={Download} size={{ fontSize: 16 }} />,
117
- onClick: download,
118
- },
119
- ],
120
- }}
121
- placement="top"
122
- >
123
- <Time style={{ cursor: 'pointer', flex: 'none', ...timeStyle }}>
124
- {timeType === 'left' && formatedLeftTime}
125
- {timeType === 'current' && formatedCurrentTime}
126
- {timeType === 'combine' && (
127
- <span>
128
- {formatedCurrentTime}
129
- <span style={{ opacity: 0.66 }}>{` / ${formatedDuration}`}</span>
130
- </span>
131
- )}
132
- </Time>
133
- </Dropdown>
134
- </Flexbox>
135
- );
136
- },
137
- );
138
-
139
- export default AudioPlayer;
@@ -1,48 +0,0 @@
1
- import { merge } from 'lodash-es';
2
-
3
- import { VISION_MODEL_WHITE_LIST } from '@/const/llm';
4
- import { pluginSelectors, usePluginStore } from '@/store/plugin';
5
- import { initialLobeAgentConfig } from '@/store/session/initialState';
6
- import type { OpenAIChatStreamPayload } from '@/types/openai/chat';
7
-
8
- import { createHeaderWithOpenAI } from './_header';
9
- import { OPENAI_URLS } from './_url';
10
-
11
- interface FetchChatModelOptions {
12
- signal?: AbortSignal | undefined;
13
- }
14
-
15
- /**
16
- * 专门用于对话的 fetch
17
- */
18
- export const fetchChatModel = (
19
- { plugins: enabledPlugins, ...params }: Partial<OpenAIChatStreamPayload>,
20
- options?: FetchChatModelOptions,
21
- ) => {
22
- const payload = merge(
23
- {
24
- model: initialLobeAgentConfig.model,
25
- stream: true,
26
- ...initialLobeAgentConfig.params,
27
- },
28
- params,
29
- );
30
- // ============ preprocess tools ============ //
31
-
32
- const filterTools = pluginSelectors.enabledSchema(enabledPlugins)(usePluginStore.getState());
33
-
34
- // the rule that model can use tools:
35
- // 1. tools is not empty
36
- // 2. model is not in vision white list, because vision model can't use tools
37
- // TODO: we need to find some method to let vision model use tools
38
- const shouldUseTools = filterTools.length > 0 && !VISION_MODEL_WHITE_LIST.includes(payload.model);
39
-
40
- const functions = shouldUseTools ? filterTools : undefined;
41
-
42
- return fetch(OPENAI_URLS.chat, {
43
- body: JSON.stringify({ ...payload, functions }),
44
- headers: createHeaderWithOpenAI({ 'Content-Type': 'application/json' }),
45
- method: 'POST',
46
- signal: options?.signal,
47
- });
48
- };
@@ -1,13 +0,0 @@
1
- import { getPluginIndexJSON } from '@/const/url';
2
- import { getCurrentLanguage } from '@/store/global/helpers';
3
-
4
- /**
5
- * fetch Plugin Market List
6
- */
7
- export const getPluginList = async () => {
8
- const url = getPluginIndexJSON(getCurrentLanguage());
9
-
10
- const res = await fetch(url);
11
-
12
- return res.json();
13
- };