@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.
- package/CHANGELOG.md +50 -0
- package/README.md +119 -38
- package/README.zh-CN.md +99 -44
- package/docs/Development-Guide/Data-Store/Selectors.zh-CN.md +68 -0
- package/package.json +3 -3
- package/src/app/api/openai/errorResponse.test.ts +55 -0
- package/src/app/chat/features/ChatHeader/PluginTag/PluginStatus.tsx +3 -2
- package/src/app/chat/features/ChatHeader/PluginTag/index.tsx +2 -1
- package/src/app/chat/features/Conversation/ChatList/Error/Plugin/PluginSettings.tsx +2 -1
- package/src/app/chat/features/Conversation/ChatList/Extras/Assistant.test.tsx +86 -0
- package/src/app/chat/features/Conversation/ChatList/Extras/Assistant.tsx +5 -1
- package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/Settings.tsx +2 -1
- package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/index.tsx +2 -1
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/Iframe.tsx +2 -1
- package/src/app/chat/features/Conversation/index.tsx +3 -3
- package/src/app/chat/features/Conversation/usePluginsInit.ts +14 -0
- package/src/app/settings/llm/LLM/Checker.tsx +2 -2
- package/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx +4 -3
- package/src/features/AgentSetting/AgentPlugin/MarketList.tsx +4 -3
- package/src/features/AgentSetting/AgentPlugin/PluginSettings.tsx +3 -2
- package/src/features/AgentSetting/store/action.ts +5 -5
- package/src/features/PluginDevModal/MetaForm.tsx +2 -1
- package/src/features/PluginSettingsModal/index.tsx +2 -1
- package/src/services/chat.ts +84 -0
- package/src/services/plugin.ts +15 -33
- package/src/store/plugin/index.ts +0 -1
- package/src/store/plugin/selectors.test.ts +185 -0
- package/src/store/plugin/selectors.ts +6 -3
- package/src/store/plugin/slices/customPlugin/action.ts +3 -3
- package/src/store/plugin/slices/plugin/action.ts +44 -15
- package/src/store/session/slices/chat/actions/message.ts +2 -2
- package/src/store/session/slices/chat/actions/plugin.ts +2 -2
- package/src/store/session/slices/chat/actions/topic.ts +2 -2
- package/src/store/session/slices/chat/actions/translate.ts +9 -7
- package/src/utils/fetch.ts +0 -3
- package/tests/setup.ts +6 -1
- package/src/app/chat/features/Conversation/ChatList/Extras/AudioPlayer.tsx +0 -139
- package/src/services/chatModel.ts +0 -48
- package/src/services/pluginMarket.ts +0 -13
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { ChatErrorType, ErrorResponse, ErrorType } from '@/types/fetch';
|
|
4
|
+
|
|
5
|
+
import { createErrorResponse } from './errorResponse';
|
|
6
|
+
|
|
7
|
+
describe('createErrorResponse', () => {
|
|
8
|
+
// 测试各种错误类型的状态码
|
|
9
|
+
it('returns a 401 status for NoAPIKey error type', () => {
|
|
10
|
+
const errorType = ChatErrorType.NoAPIKey;
|
|
11
|
+
const response = createErrorResponse(errorType);
|
|
12
|
+
expect(response.status).toBe(401);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('returns a 401 status for InvalidAccessCode error type', () => {
|
|
16
|
+
const errorType = ChatErrorType.InvalidAccessCode;
|
|
17
|
+
const response = createErrorResponse(errorType);
|
|
18
|
+
expect(response.status).toBe(401);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('returns a 577 status for OpenAIBizError error type', () => {
|
|
22
|
+
const errorType = ChatErrorType.OpenAIBizError;
|
|
23
|
+
const response = createErrorResponse(errorType);
|
|
24
|
+
expect(response.status).toBe(577);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// 测试默认情况
|
|
28
|
+
it('returns the same error type as status for unknown error types', () => {
|
|
29
|
+
const errorType = 500; // 假设500是一个未知的错误类型
|
|
30
|
+
const response = createErrorResponse(errorType);
|
|
31
|
+
expect(response.status).toBe(errorType);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// 测试返回的Response对象是否包含正确的body和errorType
|
|
35
|
+
it('returns a Response object with the correct body and errorType', () => {
|
|
36
|
+
const errorType = ChatErrorType.NoAPIKey;
|
|
37
|
+
const body = { message: 'No API key provided' };
|
|
38
|
+
const response = createErrorResponse(errorType, body);
|
|
39
|
+
return response.json().then((data) => {
|
|
40
|
+
expect(data).toEqual({
|
|
41
|
+
body,
|
|
42
|
+
errorType,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// 测试没有提供body时,返回的Response对象的body是否为undefined
|
|
48
|
+
it('returns a Response object with an undefined body when no body is provided', () => {
|
|
49
|
+
const errorType = ChatErrorType.NoAPIKey;
|
|
50
|
+
const response = createErrorResponse(errorType);
|
|
51
|
+
return response.json().then((data) => {
|
|
52
|
+
expect(data.body).toBeUndefined();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -6,7 +6,8 @@ import { useTranslation } from 'react-i18next';
|
|
|
6
6
|
import { Flexbox } from 'react-layout-kit';
|
|
7
7
|
|
|
8
8
|
import ManifestPreviewer from '@/components/ManifestPreviewer';
|
|
9
|
-
import {
|
|
9
|
+
import { usePluginStore } from '@/store/plugin';
|
|
10
|
+
import { pluginSelectors } from '@/store/plugin/selectors';
|
|
10
11
|
import { useSessionStore } from '@/store/session';
|
|
11
12
|
|
|
12
13
|
interface PluginStatusProps {
|
|
@@ -19,7 +20,7 @@ const PluginStatus = memo<PluginStatusProps>(({ title, id, deprecated }) => {
|
|
|
19
20
|
const [status, isCustom, fetchPluginManifest] = usePluginStore((s) => [
|
|
20
21
|
pluginSelectors.getPluginManifestLoadingStatus(id)(s),
|
|
21
22
|
pluginSelectors.isCustomPlugin(id)(s),
|
|
22
|
-
s.
|
|
23
|
+
s.installPlugin,
|
|
23
24
|
]);
|
|
24
25
|
|
|
25
26
|
const manifest = usePluginStore(pluginSelectors.getPluginManifestById(id));
|
|
@@ -5,7 +5,8 @@ import isEqual from 'fast-deep-equal';
|
|
|
5
5
|
import { LucideToyBrick } from 'lucide-react';
|
|
6
6
|
import { memo } from 'react';
|
|
7
7
|
|
|
8
|
-
import { pluginHelpers,
|
|
8
|
+
import { pluginHelpers, usePluginStore } from '@/store/plugin';
|
|
9
|
+
import { pluginSelectors } from '@/store/plugin/selectors';
|
|
9
10
|
|
|
10
11
|
import PluginStatus from './PluginStatus';
|
|
11
12
|
|
|
@@ -7,7 +7,8 @@ import { useTranslation } from 'react-i18next';
|
|
|
7
7
|
import { Center, Flexbox } from 'react-layout-kit';
|
|
8
8
|
|
|
9
9
|
import PluginSettingsConfig from '@/features/PluginSettings';
|
|
10
|
-
import { pluginHelpers,
|
|
10
|
+
import { pluginHelpers, usePluginStore } from '@/store/plugin';
|
|
11
|
+
import { pluginSelectors } from '@/store/plugin/selectors';
|
|
11
12
|
import { useSessionStore } from '@/store/session';
|
|
12
13
|
|
|
13
14
|
import { ErrorActionContainer, useStyles } from '../style';
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { Mock, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { useSessionStore } from '@/store/session';
|
|
5
|
+
import { agentSelectors } from '@/store/session/selectors';
|
|
6
|
+
import { ChatMessage } from '@/types/chatMessage';
|
|
7
|
+
|
|
8
|
+
import { AssistantMessageExtra } from './Assistant';
|
|
9
|
+
|
|
10
|
+
// Mock TTS and Translate components
|
|
11
|
+
vi.mock('./TTS', () => ({
|
|
12
|
+
default: vi.fn(() => <div>TTS Component</div>),
|
|
13
|
+
}));
|
|
14
|
+
vi.mock('./Translate', () => ({
|
|
15
|
+
default: vi.fn(() => <div>Translate Component</div>),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
// Mock dependencies
|
|
19
|
+
vi.mock('@/store/session', () => ({
|
|
20
|
+
useSessionStore: vi.fn(),
|
|
21
|
+
}));
|
|
22
|
+
vi.mock('@/store/session/selectors', () => ({
|
|
23
|
+
agentSelectors: {
|
|
24
|
+
currentAgentModel: vi.fn(),
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
const mockData: ChatMessage = {
|
|
29
|
+
content: 'test-content',
|
|
30
|
+
createAt: 0,
|
|
31
|
+
id: 'abc',
|
|
32
|
+
meta: { avatar: '', backgroundColor: '', description: '', tags: [], title: '' },
|
|
33
|
+
role: 'assistant',
|
|
34
|
+
updateAt: 0,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
describe('AssistantMessageExtra', () => {
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
// Set default mock return values
|
|
40
|
+
(useSessionStore as unknown as Mock).mockImplementation(() => ({
|
|
41
|
+
chatLoadingId: null,
|
|
42
|
+
}));
|
|
43
|
+
(agentSelectors.currentAgentModel as Mock).mockReturnValue('defaultModel');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should not render content if extra is undefined', async () => {
|
|
47
|
+
render(<AssistantMessageExtra {...mockData} />);
|
|
48
|
+
expect(screen.queryByText('defaultModel')).toBeNull();
|
|
49
|
+
expect(screen.queryByText('TTS Component')).toBeNull();
|
|
50
|
+
expect(screen.queryByText('Translate Component')).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should not render content if extra is defined but does not contain fromModel, tts, or translate', async () => {
|
|
54
|
+
render(<AssistantMessageExtra {...mockData} />);
|
|
55
|
+
expect(screen.queryByText('defaultModel')).toBeNull();
|
|
56
|
+
expect(screen.queryByText('TTS Component')).toBeNull();
|
|
57
|
+
expect(screen.queryByText('Translate Component')).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should render Tag component if extra.fromModel exists and does not match the current model', async () => {
|
|
61
|
+
render(<AssistantMessageExtra {...mockData} extra={{ fromModel: 'otherModel' }} />);
|
|
62
|
+
|
|
63
|
+
expect(screen.getByText('otherModel')).toBeInTheDocument();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should render TTS component if extra.fromModel and extra.tts coexist', async () => {
|
|
67
|
+
render(<AssistantMessageExtra {...mockData} extra={{ fromModel: 'otherModel', tts: {} }} />);
|
|
68
|
+
|
|
69
|
+
expect(screen.getByText('otherModel')).toBeInTheDocument();
|
|
70
|
+
expect(screen.getByText('TTS Component')).toBeInTheDocument();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should render Translate component if extra.translate exists', async () => {
|
|
74
|
+
render(<AssistantMessageExtra {...mockData} extra={{ translate: {} }} />);
|
|
75
|
+
expect(screen.getByText('Translate Component')).toBeInTheDocument();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should receive the correct loading attribute if loading is true for TTS and Translate components', async () => {
|
|
79
|
+
(useSessionStore as unknown as Mock).mockImplementation(() => ({
|
|
80
|
+
chatLoadingId: 'test-id',
|
|
81
|
+
}));
|
|
82
|
+
render(<AssistantMessageExtra {...mockData} extra={{ translate: {}, tts: {} }} />);
|
|
83
|
+
expect(screen.getByText('TTS Component')).toBeInTheDocument();
|
|
84
|
+
expect(screen.getByText('Translate Component')).toBeInTheDocument();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -17,7 +17,11 @@ export const AssistantMessageExtra: RenderMessageExtra = memo<ChatMessage>(
|
|
|
17
17
|
const loading = useSessionStore((s) => s.chatLoadingId === id);
|
|
18
18
|
|
|
19
19
|
const showModelTag = extra?.fromModel && model !== extra?.fromModel;
|
|
20
|
-
const
|
|
20
|
+
const showTranslate = !!extra?.translate;
|
|
21
|
+
const showTTS = !!extra?.tts;
|
|
22
|
+
|
|
23
|
+
const showExtra = showModelTag || showTranslate || showTTS;
|
|
24
|
+
|
|
21
25
|
if (!showExtra) return;
|
|
22
26
|
|
|
23
27
|
return (
|
|
@@ -4,7 +4,8 @@ import { memo, useState } from 'react';
|
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
|
|
6
6
|
import PluginSettingsModal from '@/features/PluginSettingsModal';
|
|
7
|
-
import {
|
|
7
|
+
import { usePluginStore } from '@/store/plugin';
|
|
8
|
+
import { pluginSelectors } from '@/store/plugin/selectors';
|
|
8
9
|
|
|
9
10
|
const Settings = memo<{ id: string }>(({ id }) => {
|
|
10
11
|
const item = usePluginStore(pluginSelectors.getPluginManifestById(id));
|
|
@@ -14,7 +14,8 @@ import { memo, useState } from 'react';
|
|
|
14
14
|
import { useTranslation } from 'react-i18next';
|
|
15
15
|
import { Flexbox } from 'react-layout-kit';
|
|
16
16
|
|
|
17
|
-
import { pluginHelpers,
|
|
17
|
+
import { pluginHelpers, usePluginStore } from '@/store/plugin';
|
|
18
|
+
import { pluginSelectors } from '@/store/plugin/selectors';
|
|
18
19
|
|
|
19
20
|
import PluginResult from './PluginResultJSON';
|
|
20
21
|
import Settings from './Settings';
|
package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/Iframe.tsx
CHANGED
|
@@ -2,7 +2,8 @@ import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
|
|
|
2
2
|
import { Skeleton } from 'antd';
|
|
3
3
|
import { memo, useRef, useState } from 'react';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { usePluginStore } from '@/store/plugin';
|
|
6
|
+
import { pluginSelectors } from '@/store/plugin/selectors';
|
|
6
7
|
import { useSessionStore } from '@/store/session';
|
|
7
8
|
import { chatSelectors } from '@/store/session/selectors';
|
|
8
9
|
|
|
@@ -5,10 +5,10 @@ import { useTranslation } from 'react-i18next';
|
|
|
5
5
|
import { Flexbox } from 'react-layout-kit';
|
|
6
6
|
|
|
7
7
|
import SafeSpacing from '@/components/SafeSpacing';
|
|
8
|
-
import { usePluginStore } from '@/store/plugin';
|
|
9
8
|
|
|
10
9
|
import ChatList from './ChatList';
|
|
11
10
|
import ChatScrollAnchor from './ScrollAnchor';
|
|
11
|
+
import { usePluginsInit } from './usePluginsInit';
|
|
12
12
|
|
|
13
13
|
const useStyles = createStyles(
|
|
14
14
|
({ css, responsive, stylish }) => css`
|
|
@@ -31,8 +31,8 @@ const Conversation = memo<ConversationProps>(({ mobile, chatInput }) => {
|
|
|
31
31
|
const { t } = useTranslation('chat');
|
|
32
32
|
const { styles } = useStyles();
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
// fetch plugin list and init plugins
|
|
35
|
+
usePluginsInit();
|
|
36
36
|
|
|
37
37
|
return (
|
|
38
38
|
<Flexbox flex={1} style={{ position: 'relative' }}>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { usePluginStore } from '@/store/plugin';
|
|
2
|
+
import { useSessionStore } from '@/store/session';
|
|
3
|
+
import { agentSelectors } from '@/store/session/slices/agentConfig';
|
|
4
|
+
|
|
5
|
+
export const usePluginsInit = () => {
|
|
6
|
+
const [plugins] = useSessionStore((s) => [agentSelectors.currentAgentPlugins(s)]);
|
|
7
|
+
|
|
8
|
+
const useFetchPluginStore = usePluginStore((s) => s.useFetchPluginStore);
|
|
9
|
+
useFetchPluginStore();
|
|
10
|
+
|
|
11
|
+
const checkPluginsIsInstalled = usePluginStore((s) => s.useCheckPluginsIsInstalled);
|
|
12
|
+
|
|
13
|
+
checkPluginsIsInstalled(plugins);
|
|
14
|
+
};
|
|
@@ -6,8 +6,8 @@ import { memo, useState } from 'react';
|
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
import { Flexbox } from 'react-layout-kit';
|
|
8
8
|
|
|
9
|
+
import { chatService } from '@/services/chat';
|
|
9
10
|
import { ChatMessageError } from '@/types/chatMessage';
|
|
10
|
-
import { fetchPresetTaskResult } from '@/utils/fetch';
|
|
11
11
|
|
|
12
12
|
import { getModelList } from './getModelList';
|
|
13
13
|
|
|
@@ -28,7 +28,7 @@ const Checker = memo<CheckerProps>(({ checkModel }) => {
|
|
|
28
28
|
getModelList();
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const data = await fetchPresetTaskResult({
|
|
31
|
+
const data = await chatService.fetchPresetTaskResult({
|
|
32
32
|
onError: (_, rawError) => {
|
|
33
33
|
setError(rawError);
|
|
34
34
|
},
|
|
@@ -5,7 +5,8 @@ import { useTranslation } from 'react-i18next';
|
|
|
5
5
|
import { Flexbox } from 'react-layout-kit';
|
|
6
6
|
|
|
7
7
|
import DevModal from '@/features/PluginDevModal';
|
|
8
|
-
import {
|
|
8
|
+
import { usePluginStore } from '@/store/plugin';
|
|
9
|
+
import { pluginSelectors } from '@/store/plugin/selectors';
|
|
9
10
|
|
|
10
11
|
import { useStore } from '../store';
|
|
11
12
|
|
|
@@ -19,8 +20,8 @@ const MarketList = memo<{ id: string }>(({ id }) => {
|
|
|
19
20
|
|
|
20
21
|
const [useFetchPluginList, fetchPluginManifest, deleteCustomPlugin, updateCustomPlugin] =
|
|
21
22
|
usePluginStore((s) => [
|
|
22
|
-
s.
|
|
23
|
-
s.
|
|
23
|
+
s.useFetchPluginStore,
|
|
24
|
+
s.installPlugin,
|
|
24
25
|
s.deleteCustomPlugin,
|
|
25
26
|
s.updateCustomPlugin,
|
|
26
27
|
]);
|
|
@@ -9,7 +9,8 @@ import { Flexbox } from 'react-layout-kit';
|
|
|
9
9
|
import DevModal from 'src/features/PluginDevModal';
|
|
10
10
|
|
|
11
11
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
|
12
|
-
import { pluginHelpers,
|
|
12
|
+
import { pluginHelpers, usePluginStore } from '@/store/plugin';
|
|
13
|
+
import { pluginSelectors } from '@/store/plugin/selectors';
|
|
13
14
|
|
|
14
15
|
import { useStore } from '../store';
|
|
15
16
|
import LocalPluginItem from './LocalPluginItem';
|
|
@@ -47,8 +48,8 @@ const MarketList = memo(() => {
|
|
|
47
48
|
|
|
48
49
|
const [useFetchPluginList, fetchPluginManifest, saveToDevList, updateNewDevPlugin] =
|
|
49
50
|
usePluginStore((s) => [
|
|
50
|
-
s.
|
|
51
|
-
s.
|
|
51
|
+
s.useFetchPluginStore,
|
|
52
|
+
s.installPlugin,
|
|
52
53
|
s.saveToCustomPluginList,
|
|
53
54
|
s.updateNewCustomPlugin,
|
|
54
55
|
]);
|
|
@@ -8,7 +8,8 @@ import { useTranslation } from 'react-i18next';
|
|
|
8
8
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
|
9
9
|
import { transformPluginSettings } from '@/features/PluginSettings';
|
|
10
10
|
import PluginSettingRender from '@/features/PluginSettings/PluginSettingRender';
|
|
11
|
-
import { pluginHelpers,
|
|
11
|
+
import { pluginHelpers, usePluginStore } from '@/store/plugin';
|
|
12
|
+
import { pluginSelectors } from '@/store/plugin/selectors';
|
|
12
13
|
|
|
13
14
|
import { useStore } from '../store';
|
|
14
15
|
|
|
@@ -30,7 +31,7 @@ const PluginSettings = memo(() => {
|
|
|
30
31
|
const [plugins] = useStore((s) => [s.config.plugins || [], !!s.config.plugins]);
|
|
31
32
|
|
|
32
33
|
const [useFetchPluginList, updatePluginSettings] = usePluginStore((s) => [
|
|
33
|
-
s.
|
|
34
|
+
s.useFetchPluginStore,
|
|
34
35
|
s.updatePluginSettings,
|
|
35
36
|
]);
|
|
36
37
|
const pluginList = usePluginStore(pluginSelectors.pluginList);
|
|
@@ -4,9 +4,9 @@ import { chainPickEmoji } from '@/chains/pickEmoji';
|
|
|
4
4
|
import { chainSummaryAgentName } from '@/chains/summaryAgentName';
|
|
5
5
|
import { chainSummaryDescription } from '@/chains/summaryDescription';
|
|
6
6
|
import { chainSummaryTags } from '@/chains/summaryTags';
|
|
7
|
+
import { chatService } from '@/services/chat';
|
|
7
8
|
import { MetaData } from '@/types/meta';
|
|
8
9
|
import { LobeAgentConfig } from '@/types/session';
|
|
9
|
-
import { fetchPresetTaskResult } from '@/utils/fetch';
|
|
10
10
|
import { setNamespace } from '@/utils/storeDebug';
|
|
11
11
|
|
|
12
12
|
import { SessionLoadingState } from '../store/initialState';
|
|
@@ -74,7 +74,7 @@ export const store: StateCreator<Store, [['zustand/devtools', never]]> = (set, g
|
|
|
74
74
|
|
|
75
75
|
const systemRole = config.systemRole;
|
|
76
76
|
|
|
77
|
-
const emoji = await fetchPresetTaskResult({
|
|
77
|
+
const emoji = await chatService.fetchPresetTaskResult({
|
|
78
78
|
onLoadingChange: (loading) => {
|
|
79
79
|
get().updateLoadingState('avatar', loading);
|
|
80
80
|
},
|
|
@@ -97,7 +97,7 @@ export const store: StateCreator<Store, [['zustand/devtools', never]]> = (set, g
|
|
|
97
97
|
// 替换为 ...
|
|
98
98
|
dispatchMeta({ type: 'update', value: { description: '...' } });
|
|
99
99
|
|
|
100
|
-
fetchPresetTaskResult({
|
|
100
|
+
chatService.fetchPresetTaskResult({
|
|
101
101
|
onError: () => {
|
|
102
102
|
dispatchMeta({ type: 'update', value: { description: preValue } });
|
|
103
103
|
},
|
|
@@ -120,7 +120,7 @@ export const store: StateCreator<Store, [['zustand/devtools', never]]> = (set, g
|
|
|
120
120
|
// 替换为 ...
|
|
121
121
|
dispatchMeta({ type: 'update', value: { tags: ['...'] } });
|
|
122
122
|
|
|
123
|
-
fetchPresetTaskResult({
|
|
123
|
+
chatService.fetchPresetTaskResult({
|
|
124
124
|
onError: () => {
|
|
125
125
|
dispatchMeta({ type: 'update', value: { tags: preValue } });
|
|
126
126
|
},
|
|
@@ -145,7 +145,7 @@ export const store: StateCreator<Store, [['zustand/devtools', never]]> = (set, g
|
|
|
145
145
|
// 替换为 ...
|
|
146
146
|
dispatchMeta({ type: 'update', value: { title: '...' } });
|
|
147
147
|
|
|
148
|
-
fetchPresetTaskResult({
|
|
148
|
+
chatService.fetchPresetTaskResult({
|
|
149
149
|
onError: () => {
|
|
150
150
|
dispatchMeta({ type: 'update', value: { title: previousTitle } });
|
|
151
151
|
},
|
|
@@ -5,7 +5,8 @@ import { memo } from 'react';
|
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
|
|
7
7
|
import { settingsSelectors, useGlobalStore } from '@/store/global';
|
|
8
|
-
import {
|
|
8
|
+
import { usePluginStore } from '@/store/plugin';
|
|
9
|
+
import { pluginSelectors } from '@/store/plugin/selectors';
|
|
9
10
|
|
|
10
11
|
const EmojiPicker = dynamic(() => import('@lobehub/ui/es/EmojiPicker'), { ssr: false });
|
|
11
12
|
|
|
@@ -7,7 +7,8 @@ import { useTranslation } from 'react-i18next';
|
|
|
7
7
|
import { Center, Flexbox } from 'react-layout-kit';
|
|
8
8
|
|
|
9
9
|
import PluginSettingsConfig from '@/features/PluginSettings';
|
|
10
|
-
import { pluginHelpers,
|
|
10
|
+
import { pluginHelpers, usePluginStore } from '@/store/plugin';
|
|
11
|
+
import { pluginSelectors } from '@/store/plugin/selectors';
|
|
11
12
|
|
|
12
13
|
interface PluginSettingsModalProps {
|
|
13
14
|
id: string;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { PluginRequestPayload, createHeadersWithPluginSettings } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
import { merge } from 'lodash-es';
|
|
3
|
+
|
|
4
|
+
import { VISION_MODEL_WHITE_LIST } from '@/const/llm';
|
|
5
|
+
import { usePluginStore } from '@/store/plugin';
|
|
6
|
+
import { pluginSelectors } from '@/store/plugin/selectors';
|
|
7
|
+
import { initialLobeAgentConfig } from '@/store/session/initialState';
|
|
8
|
+
import type { OpenAIChatStreamPayload } from '@/types/openai/chat';
|
|
9
|
+
import { fetchAIFactory, getMessageError } from '@/utils/fetch';
|
|
10
|
+
|
|
11
|
+
import { createHeaderWithOpenAI } from './_header';
|
|
12
|
+
import { OPENAI_URLS, URLS } from './_url';
|
|
13
|
+
|
|
14
|
+
interface FetchOptions {
|
|
15
|
+
signal?: AbortSignal | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class ChatService {
|
|
19
|
+
getChatCompletion = (
|
|
20
|
+
{ plugins: enabledPlugins, ...params }: Partial<OpenAIChatStreamPayload>,
|
|
21
|
+
options?: FetchOptions,
|
|
22
|
+
) => {
|
|
23
|
+
const payload = merge(
|
|
24
|
+
{
|
|
25
|
+
model: initialLobeAgentConfig.model,
|
|
26
|
+
stream: true,
|
|
27
|
+
...initialLobeAgentConfig.params,
|
|
28
|
+
},
|
|
29
|
+
params,
|
|
30
|
+
);
|
|
31
|
+
// ============ preprocess tools ============ //
|
|
32
|
+
|
|
33
|
+
const filterTools = pluginSelectors.enabledSchema(enabledPlugins)(usePluginStore.getState());
|
|
34
|
+
|
|
35
|
+
// the rule that model can use tools:
|
|
36
|
+
// 1. tools is not empty
|
|
37
|
+
// 2. model is not in vision white list, because vision model can't use tools
|
|
38
|
+
// TODO: we need to find some method to let vision model use tools
|
|
39
|
+
const shouldUseTools =
|
|
40
|
+
filterTools.length > 0 && !VISION_MODEL_WHITE_LIST.includes(payload.model);
|
|
41
|
+
|
|
42
|
+
const functions = shouldUseTools ? filterTools : undefined;
|
|
43
|
+
|
|
44
|
+
return fetch(OPENAI_URLS.chat, {
|
|
45
|
+
body: JSON.stringify({ ...payload, functions }),
|
|
46
|
+
headers: createHeaderWithOpenAI({ 'Content-Type': 'application/json' }),
|
|
47
|
+
method: 'POST',
|
|
48
|
+
signal: options?.signal,
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* run the plugin api to get result
|
|
54
|
+
* @param params
|
|
55
|
+
* @param options
|
|
56
|
+
*/
|
|
57
|
+
runPluginApi = async (params: PluginRequestPayload, options?: FetchOptions) => {
|
|
58
|
+
const { usePluginStore } = await import('@/store/plugin');
|
|
59
|
+
|
|
60
|
+
const s = usePluginStore.getState();
|
|
61
|
+
|
|
62
|
+
const settings = pluginSelectors.getPluginSettingsById(params.identifier)(s);
|
|
63
|
+
const manifest = pluginSelectors.getPluginManifestById(params.identifier)(s);
|
|
64
|
+
|
|
65
|
+
const gatewayURL = manifest?.gateway;
|
|
66
|
+
|
|
67
|
+
const res = await fetch(gatewayURL ?? URLS.plugins, {
|
|
68
|
+
body: JSON.stringify({ ...params, manifest }),
|
|
69
|
+
headers: createHeadersWithPluginSettings(settings),
|
|
70
|
+
method: 'POST',
|
|
71
|
+
signal: options?.signal,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (!res.ok) {
|
|
75
|
+
throw await getMessageError(res);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return await res.text();
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
fetchPresetTaskResult = fetchAIFactory(this.getChatCompletion);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const chatService = new ChatService();
|
package/src/services/plugin.ts
CHANGED
|
@@ -1,39 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LobeChatPluginsMarketIndex } from '@lobehub/chat-plugin-sdk';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { getPluginIndexJSON } from '@/const/url';
|
|
4
|
+
import { getCurrentLanguage } from '@/store/global/helpers';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
class PluginService {
|
|
7
|
+
/**
|
|
8
|
+
* get plugin list from store
|
|
9
|
+
*/
|
|
10
|
+
getPluginList = async () => {
|
|
11
|
+
const url = getPluginIndexJSON(getCurrentLanguage());
|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
signal?: AbortSignal | undefined;
|
|
10
|
-
withPlugin?: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* 请求插件结果´
|
|
15
|
-
*/
|
|
16
|
-
export const fetchPlugin = async (
|
|
17
|
-
params: PluginRequestPayload,
|
|
18
|
-
options?: FetchChatModelOptions,
|
|
19
|
-
) => {
|
|
20
|
-
const s = usePluginStore.getState();
|
|
21
|
-
|
|
22
|
-
const settings = pluginSelectors.getPluginSettingsById(params.identifier)(s);
|
|
23
|
-
const manifest = pluginSelectors.getPluginManifestById(params.identifier)(s);
|
|
13
|
+
const res = await fetch(url);
|
|
24
14
|
|
|
25
|
-
|
|
15
|
+
const data: LobeChatPluginsMarketIndex = await res.json();
|
|
26
16
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
method: 'POST',
|
|
31
|
-
signal: options?.signal,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
if (!res.ok) {
|
|
35
|
-
throw await getMessageError(res);
|
|
36
|
-
}
|
|
17
|
+
return data;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
37
20
|
|
|
38
|
-
|
|
39
|
-
};
|
|
21
|
+
export const pluginService = new PluginService();
|