@lobehub/lobehub 2.0.0-next.135 → 2.0.0-next.137
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/apps/desktop/package.json +4 -4
- package/changelog/v1.json +18 -0
- package/docs/development/database-schema.dbml +5 -0
- package/e2e/package.json +2 -2
- package/package.json +44 -44
- package/packages/database/migrations/0052_topic_and_messages.sql +9 -0
- package/packages/database/migrations/meta/0052_snapshot.json +8850 -0
- package/packages/database/migrations/meta/_journal.json +7 -0
- package/packages/database/src/core/migrations.json +15 -0
- package/packages/database/src/models/__tests__/topic.test.ts +12 -6
- package/packages/database/src/schemas/message.ts +5 -2
- package/packages/database/src/schemas/topic.ts +5 -0
- package/packages/model-runtime/package.json +2 -2
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +33 -12
- package/packages/python-interpreter/src/worker.ts +18 -18
- package/packages/web-crawler/package.json +1 -1
- package/src/app/[variants]/(main)/settings/provider/(list)/ProviderGrid/index.tsx +19 -0
- package/src/app/[variants]/(main)/settings/provider/ProviderMenu/List.tsx +200 -73
- package/src/app/[variants]/(main)/settings/provider/detail/default/ClientMode.tsx +1 -1
- package/src/locales/default/modelProvider.ts +2 -0
- package/src/services/chat/chat.test.ts +2 -1
- package/src/services/chat/index.ts +7 -4
- package/src/store/aiInfra/slices/aiProvider/__tests__/selectors.test.ts +15 -7
- package/src/store/aiInfra/slices/aiProvider/selectors.ts +6 -2
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { ActionIcon, Dropdown, Icon, ScrollShadow
|
|
3
|
+
import { ActionIcon, Dropdown, Icon, ScrollShadow } from '@lobehub/ui';
|
|
4
|
+
import { Collapse } from 'antd';
|
|
5
|
+
import { createStyles } from 'antd-style';
|
|
4
6
|
import type { ItemType } from 'antd/es/menu/interface';
|
|
5
7
|
import isEqual from 'fast-deep-equal';
|
|
6
|
-
import { ArrowDownUpIcon, LucideCheck } from 'lucide-react';
|
|
7
|
-
import { useCallback, useMemo, useState } from 'react';
|
|
8
|
+
import { ArrowDownUpIcon, ChevronDownIcon, LucideCheck } from 'lucide-react';
|
|
9
|
+
import { type ReactNode, useCallback, useMemo, useState } from 'react';
|
|
8
10
|
import { useTranslation } from 'react-i18next';
|
|
9
11
|
import { Flexbox } from 'react-layout-kit';
|
|
10
12
|
|
|
@@ -24,14 +26,68 @@ enum SortType {
|
|
|
24
26
|
Default = 'default',
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
const useStyles = createStyles(({ css, token }) => ({
|
|
30
|
+
collapse: css`
|
|
31
|
+
&.ant-collapse {
|
|
32
|
+
border: none;
|
|
33
|
+
border-radius: 0;
|
|
34
|
+
background: transparent;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.ant-collapse-item {
|
|
38
|
+
border: none !important;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.ant-collapse-header {
|
|
42
|
+
padding: 0 !important;
|
|
43
|
+
padding-block: 8px !important;
|
|
44
|
+
|
|
45
|
+
font-size: 12px !important;
|
|
46
|
+
color: ${token.colorTextSecondary} !important;
|
|
47
|
+
|
|
48
|
+
background: transparent !important;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ant-collapse-content {
|
|
52
|
+
border: none !important;
|
|
53
|
+
background: transparent !important;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.ant-collapse-content-box {
|
|
57
|
+
padding: 0 !important;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.ant-collapse-expand-icon {
|
|
61
|
+
padding-inline-end: 4px !important;
|
|
62
|
+
}
|
|
63
|
+
`,
|
|
64
|
+
sectionHeader: css`
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
justify-content: space-between;
|
|
70
|
+
|
|
71
|
+
margin-block-start: 8px;
|
|
72
|
+
padding-block: 4px;
|
|
73
|
+
|
|
74
|
+
font-size: 12px;
|
|
75
|
+
color: ${token.colorTextSecondary};
|
|
76
|
+
`,
|
|
77
|
+
}));
|
|
78
|
+
|
|
27
79
|
const ProviderList = (props: {
|
|
28
80
|
mobile?: boolean;
|
|
29
81
|
onProviderSelect: (providerKey: string) => void;
|
|
30
82
|
}) => {
|
|
31
83
|
const { onProviderSelect, mobile } = props;
|
|
32
84
|
const { t } = useTranslation('modelProvider');
|
|
85
|
+
const { styles } = useStyles();
|
|
33
86
|
const [open, setOpen] = useState(false);
|
|
34
87
|
|
|
88
|
+
// Collapse states - using array of active keys
|
|
89
|
+
const [activeKeys, setActiveKeys] = useState<string[]>(['enabled', 'custom', 'disabled']);
|
|
90
|
+
|
|
35
91
|
const [sortType, updateSystemStatus] = useGlobalStore((s) => [
|
|
36
92
|
systemStatusSelectors.disabledModelProvidersSortType(s),
|
|
37
93
|
s.updateSystemStatus,
|
|
@@ -54,6 +110,11 @@ const ProviderList = (props: {
|
|
|
54
110
|
isEqual,
|
|
55
111
|
);
|
|
56
112
|
|
|
113
|
+
const disabledCustomProviderList = useAiInfraStore(
|
|
114
|
+
aiProviderSelectors.disabledCustomAiProviderList,
|
|
115
|
+
isEqual,
|
|
116
|
+
);
|
|
117
|
+
|
|
57
118
|
// Sort model providers based on sort type
|
|
58
119
|
const sortedDisabledProviders = useMemo(() => {
|
|
59
120
|
const providers = [...disabledModelProviderList];
|
|
@@ -81,84 +142,150 @@ const ProviderList = (props: {
|
|
|
81
142
|
}
|
|
82
143
|
}, [disabledModelProviderList, sortType]);
|
|
83
144
|
|
|
145
|
+
const collapseItems = useMemo(() => {
|
|
146
|
+
const items: {
|
|
147
|
+
children: ReactNode;
|
|
148
|
+
extra?: ReactNode;
|
|
149
|
+
key: string;
|
|
150
|
+
label: string;
|
|
151
|
+
}[] = [
|
|
152
|
+
{
|
|
153
|
+
children: (
|
|
154
|
+
<Flexbox gap={0}>
|
|
155
|
+
{enabledModelProviderList.map((item) => (
|
|
156
|
+
<ProviderItem {...item} key={item.id} onClick={onProviderSelect} />
|
|
157
|
+
))}
|
|
158
|
+
</Flexbox>
|
|
159
|
+
),
|
|
160
|
+
extra: (
|
|
161
|
+
<div onClick={(e) => e.stopPropagation()}>
|
|
162
|
+
<ActionIcon
|
|
163
|
+
icon={ArrowDownUpIcon}
|
|
164
|
+
onClick={() => setOpen(true)}
|
|
165
|
+
size={'small'}
|
|
166
|
+
title={t('menu.sort')}
|
|
167
|
+
/>
|
|
168
|
+
</div>
|
|
169
|
+
),
|
|
170
|
+
key: 'enabled',
|
|
171
|
+
label: t('menu.list.enabled'),
|
|
172
|
+
},
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
// Add custom providers section if there are any
|
|
176
|
+
if (disabledCustomProviderList.length > 0) {
|
|
177
|
+
items.push({
|
|
178
|
+
children: (
|
|
179
|
+
<Flexbox gap={0}>
|
|
180
|
+
{disabledCustomProviderList.map((item) => (
|
|
181
|
+
<ProviderItem {...item} key={item.id} onClick={onProviderSelect} />
|
|
182
|
+
))}
|
|
183
|
+
</Flexbox>
|
|
184
|
+
),
|
|
185
|
+
key: 'custom',
|
|
186
|
+
label: t('menu.list.custom'),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Add disabled providers section
|
|
191
|
+
items.push({
|
|
192
|
+
children: (
|
|
193
|
+
<Flexbox gap={0}>
|
|
194
|
+
{sortedDisabledProviders.map((item) => (
|
|
195
|
+
<ProviderItem {...item} key={item.id} onClick={onProviderSelect} />
|
|
196
|
+
))}
|
|
197
|
+
</Flexbox>
|
|
198
|
+
),
|
|
199
|
+
extra:
|
|
200
|
+
disabledModelProviderList.length > 1 ? (
|
|
201
|
+
<div onClick={(e) => e.stopPropagation()}>
|
|
202
|
+
<Dropdown
|
|
203
|
+
menu={{
|
|
204
|
+
items: [
|
|
205
|
+
{
|
|
206
|
+
icon: sortType === SortType.Default ? <Icon icon={LucideCheck} /> : <div />,
|
|
207
|
+
key: 'default',
|
|
208
|
+
label: t('menu.list.disabledActions.sortDefault'),
|
|
209
|
+
onClick: () => updateSortType(SortType.Default),
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
type: 'divider',
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
icon:
|
|
216
|
+
sortType === SortType.Alphabetical ? <Icon icon={LucideCheck} /> : <div />,
|
|
217
|
+
key: 'alphabetical',
|
|
218
|
+
label: t('menu.list.disabledActions.sortAlphabetical'),
|
|
219
|
+
onClick: () => updateSortType(SortType.Alphabetical),
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
icon:
|
|
223
|
+
sortType === SortType.AlphabeticalDesc ? (
|
|
224
|
+
<Icon icon={LucideCheck} />
|
|
225
|
+
) : (
|
|
226
|
+
<div />
|
|
227
|
+
),
|
|
228
|
+
key: 'alphabeticalDesc',
|
|
229
|
+
label: t('menu.list.disabledActions.sortAlphabeticalDesc'),
|
|
230
|
+
onClick: () => updateSortType(SortType.AlphabeticalDesc),
|
|
231
|
+
},
|
|
232
|
+
] as ItemType[],
|
|
233
|
+
}}
|
|
234
|
+
trigger={['click']}
|
|
235
|
+
>
|
|
236
|
+
<ActionIcon
|
|
237
|
+
icon={ArrowDownUpIcon}
|
|
238
|
+
size={'small'}
|
|
239
|
+
title={t('menu.list.disabledActions.sort')}
|
|
240
|
+
/>
|
|
241
|
+
</Dropdown>
|
|
242
|
+
</div>
|
|
243
|
+
) : undefined,
|
|
244
|
+
key: 'disabled',
|
|
245
|
+
label: t('menu.list.disabled'),
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
return items;
|
|
249
|
+
}, [
|
|
250
|
+
enabledModelProviderList,
|
|
251
|
+
disabledCustomProviderList,
|
|
252
|
+
sortedDisabledProviders,
|
|
253
|
+
disabledModelProviderList.length,
|
|
254
|
+
sortType,
|
|
255
|
+
t,
|
|
256
|
+
onProviderSelect,
|
|
257
|
+
updateSortType,
|
|
258
|
+
]);
|
|
259
|
+
|
|
84
260
|
return (
|
|
85
261
|
<ScrollShadow gap={4} height={'100%'} paddingInline={12} size={4} style={{ paddingBottom: 32 }}>
|
|
86
262
|
{!mobile && <All onClick={onProviderSelect} />}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
>
|
|
93
|
-
<Text style={{ fontSize: 12 }} type={'secondary'}>
|
|
94
|
-
{t('menu.list.enabled')}
|
|
95
|
-
</Text>
|
|
96
|
-
<ActionIcon
|
|
97
|
-
icon={ArrowDownUpIcon}
|
|
98
|
-
onClick={() => {
|
|
99
|
-
setOpen(true);
|
|
263
|
+
{open && (
|
|
264
|
+
<SortProviderModal
|
|
265
|
+
defaultItems={enabledModelProviderList}
|
|
266
|
+
onCancel={() => {
|
|
267
|
+
setOpen(false);
|
|
100
268
|
}}
|
|
101
|
-
|
|
102
|
-
title={t('menu.sort')}
|
|
269
|
+
open={open}
|
|
103
270
|
/>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
271
|
+
)}
|
|
272
|
+
<Collapse
|
|
273
|
+
activeKey={activeKeys}
|
|
274
|
+
className={styles.collapse}
|
|
275
|
+
expandIcon={({ isActive }) => (
|
|
276
|
+
<Icon
|
|
277
|
+
icon={ChevronDownIcon}
|
|
278
|
+
size={'small'}
|
|
279
|
+
style={{
|
|
280
|
+
transform: isActive ? 'rotate(0deg)' : 'rotate(-90deg)',
|
|
281
|
+
transition: 'transform 0.2s ease',
|
|
109
282
|
}}
|
|
110
|
-
open={open}
|
|
111
283
|
/>
|
|
112
284
|
)}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
<Flexbox align={'center'} horizontal justify={'space-between'}>
|
|
118
|
-
<Text style={{ fontSize: 12, marginTop: 8 }} type={'secondary'}>
|
|
119
|
-
{t('menu.list.disabled')}
|
|
120
|
-
</Text>
|
|
121
|
-
{disabledModelProviderList.length > 1 && (
|
|
122
|
-
<Dropdown
|
|
123
|
-
menu={{
|
|
124
|
-
items: [
|
|
125
|
-
{
|
|
126
|
-
icon: sortType === SortType.Default ? <Icon icon={LucideCheck} /> : <div />,
|
|
127
|
-
key: 'default',
|
|
128
|
-
label: t('menu.list.disabledActions.sortDefault'),
|
|
129
|
-
onClick: () => updateSortType(SortType.Default),
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
type: 'divider',
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
icon: sortType === SortType.Alphabetical ? <Icon icon={LucideCheck} /> : <div />,
|
|
136
|
-
key: 'alphabetical',
|
|
137
|
-
label: t('menu.list.disabledActions.sortAlphabetical'),
|
|
138
|
-
onClick: () => updateSortType(SortType.Alphabetical),
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
icon:
|
|
142
|
-
sortType === SortType.AlphabeticalDesc ? <Icon icon={LucideCheck} /> : <div />,
|
|
143
|
-
key: 'alphabeticalDesc',
|
|
144
|
-
label: t('menu.list.disabledActions.sortAlphabeticalDesc'),
|
|
145
|
-
onClick: () => updateSortType(SortType.AlphabeticalDesc),
|
|
146
|
-
},
|
|
147
|
-
] as ItemType[],
|
|
148
|
-
}}
|
|
149
|
-
trigger={['click']}
|
|
150
|
-
>
|
|
151
|
-
<ActionIcon
|
|
152
|
-
icon={ArrowDownUpIcon}
|
|
153
|
-
size={'small'}
|
|
154
|
-
title={t('menu.list.disabledActions.sort')}
|
|
155
|
-
/>
|
|
156
|
-
</Dropdown>
|
|
157
|
-
)}
|
|
158
|
-
</Flexbox>
|
|
159
|
-
{sortedDisabledProviders.map((item) => (
|
|
160
|
-
<ProviderItem {...item} key={item.id} onClick={onProviderSelect} />
|
|
161
|
-
))}
|
|
285
|
+
ghost
|
|
286
|
+
items={collapseItems}
|
|
287
|
+
onChange={(keys) => setActiveKeys(keys as string[])}
|
|
288
|
+
/>
|
|
162
289
|
</ScrollShadow>
|
|
163
290
|
);
|
|
164
291
|
};
|
|
@@ -15,7 +15,7 @@ const ClientMode = memo<{ id: string }>(({ id }) => {
|
|
|
15
15
|
const useFetchAiProviderItem = useAiInfraStore((s) => s.useFetchAiProviderItem);
|
|
16
16
|
useFetchAiProviderItem(id);
|
|
17
17
|
|
|
18
|
-
const { data, isLoading } = useClientDataSWR(
|
|
18
|
+
const { data, isLoading } = useClientDataSWR(`get-client-provider-${id}`, () =>
|
|
19
19
|
aiProviderService.getAiProviderById(id),
|
|
20
20
|
);
|
|
21
21
|
|
|
@@ -193,6 +193,7 @@ export default {
|
|
|
193
193
|
},
|
|
194
194
|
list: {
|
|
195
195
|
title: {
|
|
196
|
+
custom: '未启用自定义服务商',
|
|
196
197
|
disabled: '未启用服务商',
|
|
197
198
|
enabled: '已启用服务商',
|
|
198
199
|
},
|
|
@@ -201,6 +202,7 @@ export default {
|
|
|
201
202
|
addCustomProvider: '添加自定义服务商',
|
|
202
203
|
all: '全部',
|
|
203
204
|
list: {
|
|
205
|
+
custom: '自定义未启用',
|
|
204
206
|
disabled: '未启用',
|
|
205
207
|
disabledActions: {
|
|
206
208
|
sort: '排序方式',
|
|
@@ -1062,7 +1062,7 @@ describe('ChatService', () => {
|
|
|
1062
1062
|
);
|
|
1063
1063
|
});
|
|
1064
1064
|
|
|
1065
|
-
it('should make a POST request
|
|
1065
|
+
it('should make a POST request with chatCompletion apiMode in non-openai provider payload', async () => {
|
|
1066
1066
|
const params: Partial<ChatStreamPayload> = {
|
|
1067
1067
|
model: 'deepseek-reasoner',
|
|
1068
1068
|
provider: 'deepseek',
|
|
@@ -1076,6 +1076,7 @@ describe('ChatService', () => {
|
|
|
1076
1076
|
stream: true,
|
|
1077
1077
|
...DEFAULT_AGENT_CONFIG.params,
|
|
1078
1078
|
messages: [],
|
|
1079
|
+
apiMode: 'chatCompletion',
|
|
1079
1080
|
provider: undefined,
|
|
1080
1081
|
};
|
|
1081
1082
|
|
|
@@ -267,11 +267,14 @@ class ChatService {
|
|
|
267
267
|
model = findDeploymentName(model, provider);
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
270
|
+
// When user explicitly disables Responses API, set apiMode to 'chatCompletion'
|
|
271
|
+
// This ensures the user's preference takes priority over provider's useResponseModels config
|
|
272
|
+
// When user enables Responses API, set to 'responses' to force use Responses API
|
|
273
|
+
const apiMode: 'responses' | 'chatCompletion' = aiProviderSelectors.isProviderEnableResponseApi(
|
|
274
|
+
provider,
|
|
275
|
+
)(getAiInfraStoreState())
|
|
273
276
|
? 'responses'
|
|
274
|
-
:
|
|
277
|
+
: 'chatCompletion';
|
|
275
278
|
|
|
276
279
|
// Get the chat config to check streaming preference
|
|
277
280
|
const chatConfig = agentChatConfigSelectors.currentChatConfig(getAgentStoreState());
|
|
@@ -5,9 +5,10 @@ import { aiProviderSelectors } from '../selectors';
|
|
|
5
5
|
describe('aiProviderSelectors', () => {
|
|
6
6
|
const mockState: any = {
|
|
7
7
|
aiProviderList: [
|
|
8
|
-
{ id: 'provider1', enabled: true, sort: 1 },
|
|
9
|
-
{ id: 'provider2', enabled: false, sort: 2 },
|
|
10
|
-
{ id: 'provider3', enabled: true, sort: 0 },
|
|
8
|
+
{ id: 'provider1', enabled: true, sort: 1, source: 'builtin' },
|
|
9
|
+
{ id: 'provider2', enabled: false, sort: 2, source: 'builtin' },
|
|
10
|
+
{ id: 'provider3', enabled: true, sort: 0, source: 'builtin' },
|
|
11
|
+
{ id: 'custom1', enabled: false, sort: 3, source: 'custom' },
|
|
11
12
|
],
|
|
12
13
|
aiProviderDetail: {
|
|
13
14
|
id: 'provider1',
|
|
@@ -56,16 +57,23 @@ describe('aiProviderSelectors', () => {
|
|
|
56
57
|
it('should return enabled providers sorted by sort', () => {
|
|
57
58
|
const result = aiProviderSelectors.enabledAiProviderList(mockState);
|
|
58
59
|
expect(result).toEqual([
|
|
59
|
-
{ id: 'provider3', enabled: true, sort: 0 },
|
|
60
|
-
{ id: 'provider1', enabled: true, sort: 1 },
|
|
60
|
+
{ id: 'provider3', enabled: true, sort: 0, source: 'builtin' },
|
|
61
|
+
{ id: 'provider1', enabled: true, sort: 1, source: 'builtin' },
|
|
61
62
|
]);
|
|
62
63
|
});
|
|
63
64
|
});
|
|
64
65
|
|
|
65
66
|
describe('disabledAiProviderList', () => {
|
|
66
|
-
it('should return disabled providers', () => {
|
|
67
|
+
it('should return disabled builtin providers', () => {
|
|
67
68
|
const result = aiProviderSelectors.disabledAiProviderList(mockState);
|
|
68
|
-
expect(result).toEqual([{ id: 'provider2', enabled: false, sort: 2 }]);
|
|
69
|
+
expect(result).toEqual([{ id: 'provider2', enabled: false, sort: 2, source: 'builtin' }]);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('disabledCustomAiProviderList', () => {
|
|
74
|
+
it('should return disabled custom providers', () => {
|
|
75
|
+
const result = aiProviderSelectors.disabledCustomAiProviderList(mockState);
|
|
76
|
+
expect(result).toEqual([{ id: 'custom1', enabled: false, sort: 3, source: 'custom' }]);
|
|
69
77
|
});
|
|
70
78
|
});
|
|
71
79
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isProviderDisableBrowserRequest } from '@/config/modelProviders';
|
|
2
2
|
import { AIProviderStoreState } from '@/store/aiInfra/initialState';
|
|
3
|
-
import { AiProviderRuntimeConfig } from '@/types/aiProvider';
|
|
3
|
+
import { AiProviderRuntimeConfig, AiProviderSourceEnum } from '@/types/aiProvider';
|
|
4
4
|
import { GlobalLLMProviderKey } from '@/types/user/settings';
|
|
5
5
|
|
|
6
6
|
// List
|
|
@@ -8,7 +8,10 @@ const enabledAiProviderList = (s: AIProviderStoreState) =>
|
|
|
8
8
|
s.aiProviderList.filter((item) => item.enabled).sort((a, b) => a.sort! - b.sort!);
|
|
9
9
|
|
|
10
10
|
const disabledAiProviderList = (s: AIProviderStoreState) =>
|
|
11
|
-
s.aiProviderList.filter((item) => !item.enabled);
|
|
11
|
+
s.aiProviderList.filter((item) => !item.enabled && item.source !== AiProviderSourceEnum.Custom);
|
|
12
|
+
|
|
13
|
+
const disabledCustomAiProviderList = (s: AIProviderStoreState) =>
|
|
14
|
+
s.aiProviderList.filter((item) => !item.enabled && item.source === AiProviderSourceEnum.Custom);
|
|
12
15
|
|
|
13
16
|
const enabledImageModelList = (s: AIProviderStoreState) => s.enabledImageModelList || [];
|
|
14
17
|
|
|
@@ -116,6 +119,7 @@ const isInitAiProviderRuntimeState = (s: AIProviderStoreState) => !!s.isInitAiPr
|
|
|
116
119
|
export const aiProviderSelectors = {
|
|
117
120
|
activeProviderConfig,
|
|
118
121
|
disabledAiProviderList,
|
|
122
|
+
disabledCustomAiProviderList,
|
|
119
123
|
enabledAiProviderList,
|
|
120
124
|
enabledImageModelList,
|
|
121
125
|
isActiveProviderApiKeyNotEmpty,
|