@lobehub/lobehub 2.0.0-next.26 → 2.0.0-next.28
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/changelog/v1.json +18 -0
- package/package.json +1 -1
- package/packages/types/src/discover/mcp.ts +6 -0
- package/packages/types/src/plugins/mcp.ts +4 -1
- package/packages/types/src/topic/topic.ts +14 -0
- package/renovate.json +4 -30
- package/src/features/MCP/utils.test.ts +91 -0
- package/src/features/MCP/utils.ts +20 -2
- package/src/features/PluginStore/Content.tsx +2 -3
- package/src/features/PluginStore/McpList/index.tsx +6 -2
- package/src/server/routers/lambda/market/index.ts +4 -2
- package/src/server/routers/lambda/topic.ts +7 -1
- package/src/services/aiModel/index.test.ts +3 -3
- package/src/services/aiModel/index.ts +56 -2
- package/src/services/aiProvider/index.test.ts +2 -2
- package/src/services/aiProvider/index.ts +48 -2
- package/src/services/chatGroup/index.ts +66 -2
- package/src/services/export/index.ts +10 -2
- package/src/services/file/index.ts +61 -2
- package/src/services/import/index.ts +133 -2
- package/src/services/mcp.ts +40 -6
- package/src/services/message/index.ts +176 -2
- package/src/services/message/{__tests__/server.test.ts → server.test.ts} +3 -3
- package/src/services/plugin/index.test.ts +8 -0
- package/src/services/plugin/index.ts +53 -2
- package/src/services/session/index.test.ts +8 -0
- package/src/services/session/index.ts +145 -2
- package/src/services/thread/index.test.ts +8 -0
- package/src/services/thread/index.ts +38 -2
- package/src/services/topic/index.test.ts +8 -0
- package/src/services/topic/index.ts +76 -2
- package/src/services/user/index.test.ts +8 -0
- package/src/services/user/index.ts +53 -2
- package/src/store/aiInfra/slices/aiModel/action.test.ts +17 -9
- package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +4 -2
- package/src/store/chat/slices/topic/action.test.ts +1 -1
- package/src/store/chat/slices/topic/action.ts +1 -2
- package/src/store/chat/slices/topic/reducer.ts +1 -2
- package/src/store/file/slices/chat/action.ts +1 -4
- package/src/store/file/slices/fileManager/action.ts +2 -3
- package/src/store/session/slices/sessionGroup/action.test.ts +5 -5
- package/src/store/tool/slices/mcpStore/action.test.ts +95 -3
- package/src/store/tool/slices/mcpStore/action.ts +177 -53
- package/src/store/tool/slices/oldStore/initialState.ts +1 -2
- package/src/store/user/slices/common/action.test.ts +1 -1
- package/src/services/aiModel/server.test.ts +0 -122
- package/src/services/aiModel/server.ts +0 -51
- package/src/services/aiModel/type.ts +0 -32
- package/src/services/aiProvider/server.ts +0 -43
- package/src/services/aiProvider/type.ts +0 -27
- package/src/services/chatGroup/server.ts +0 -67
- package/src/services/chatGroup/type.ts +0 -22
- package/src/services/export/server.ts +0 -9
- package/src/services/export/type.ts +0 -5
- package/src/services/file/server.ts +0 -53
- package/src/services/file/type.ts +0 -13
- package/src/services/import/server.ts +0 -133
- package/src/services/import/type.ts +0 -17
- package/src/services/message/server.ts +0 -151
- package/src/services/message/type.ts +0 -55
- package/src/services/plugin/server.ts +0 -42
- package/src/services/plugin/type.ts +0 -23
- package/src/services/session/server.test.ts +0 -260
- package/src/services/session/server.ts +0 -125
- package/src/services/session/type.ts +0 -82
- package/src/services/thread/server.ts +0 -32
- package/src/services/thread/type.ts +0 -21
- package/src/services/topic/server.ts +0 -57
- package/src/services/topic/type.ts +0 -40
- package/src/services/user/server.test.ts +0 -149
- package/src/services/user/server.ts +0 -47
- package/src/services/user/type.ts +0 -21
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
|
2
2
|
import { PluginItem, PluginListResponse } from '@lobehub/market-sdk';
|
|
3
3
|
import { TRPCClientError } from '@trpc/client';
|
|
4
|
+
import debug from 'debug';
|
|
4
5
|
import { produce } from 'immer';
|
|
5
6
|
import { uniqBy } from 'lodash-es';
|
|
6
7
|
import { gt, valid } from 'semver';
|
|
7
8
|
import useSWR, { SWRResponse } from 'swr';
|
|
8
9
|
import { StateCreator } from 'zustand/vanilla';
|
|
9
10
|
|
|
10
|
-
import { CURRENT_VERSION } from '
|
|
11
|
+
import { CURRENT_VERSION, isDesktop } from '@lobechat/const';
|
|
11
12
|
import { MCPErrorData } from '@/libs/mcp/types';
|
|
12
13
|
import { discoverService } from '@/services/discover';
|
|
13
14
|
import { mcpService } from '@/services/mcp';
|
|
14
15
|
import { pluginService } from '@/services/plugin';
|
|
15
16
|
import { globalHelpers } from '@/store/global/helpers';
|
|
16
17
|
import { mcpStoreSelectors } from '@/store/tool/selectors';
|
|
18
|
+
import { McpConnectionType } from '@/types/discover';
|
|
17
19
|
import {
|
|
18
20
|
CheckMcpInstallResult,
|
|
19
21
|
MCPErrorInfo,
|
|
@@ -28,8 +30,41 @@ import { setNamespace } from '@/utils/storeDebug';
|
|
|
28
30
|
import { ToolStore } from '../../store';
|
|
29
31
|
import { MCPStoreState } from './initialState';
|
|
30
32
|
|
|
33
|
+
const log = debug('lobe-mcp:store:action');
|
|
34
|
+
|
|
31
35
|
const n = setNamespace('mcpStore');
|
|
32
36
|
|
|
37
|
+
const doesConfigSchemaRequireInput = (configSchema?: any) => {
|
|
38
|
+
if (!configSchema) return false;
|
|
39
|
+
|
|
40
|
+
const hasRequiredArray =
|
|
41
|
+
Array.isArray(configSchema.required) && configSchema.required.some(Boolean);
|
|
42
|
+
|
|
43
|
+
const hasRequiredProperty =
|
|
44
|
+
!!configSchema.properties &&
|
|
45
|
+
Object.values(configSchema.properties).some(
|
|
46
|
+
(property: any) => property && property.required === true,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return hasRequiredArray || hasRequiredProperty;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const toNonEmptyStringRecord = (input?: Record<string, any>) => {
|
|
53
|
+
if (!input) return undefined;
|
|
54
|
+
|
|
55
|
+
const entries = Object.entries(input).filter(
|
|
56
|
+
([, value]) => value !== undefined && value !== null,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (entries.length === 0) return undefined;
|
|
60
|
+
|
|
61
|
+
return entries.reduce<Record<string, string>>((acc, [key, value]) => {
|
|
62
|
+
acc[key] = typeof value === 'string' ? value : String(value);
|
|
63
|
+
|
|
64
|
+
return acc;
|
|
65
|
+
}, {});
|
|
66
|
+
};
|
|
67
|
+
|
|
33
68
|
// 测试连接结果类型
|
|
34
69
|
export interface TestMcpConnectionResult {
|
|
35
70
|
error?: string;
|
|
@@ -101,6 +136,7 @@ export const createMCPPluginStoreSlice: StateCreator<
|
|
|
101
136
|
|
|
102
137
|
installMCPPlugin: async (identifier, options = {}) => {
|
|
103
138
|
const { resume = false, config, skipDepsCheck } = options;
|
|
139
|
+
const normalizedConfig = toNonEmptyStringRecord(config);
|
|
104
140
|
let plugin = mcpStoreSelectors.getPluginById(identifier)(get());
|
|
105
141
|
|
|
106
142
|
if (!plugin || !plugin.manifestUrl) {
|
|
@@ -149,12 +185,8 @@ export const createMCPPluginStoreSlice: StateCreator<
|
|
|
149
185
|
}
|
|
150
186
|
|
|
151
187
|
data = configInfo.manifest;
|
|
152
|
-
connection = {
|
|
153
|
-
...configInfo.connection,
|
|
154
|
-
config, // 合并用户提供的配置
|
|
155
|
-
};
|
|
188
|
+
connection = configInfo.connection ? { ...configInfo.connection } : undefined;
|
|
156
189
|
result = configInfo.checkResult;
|
|
157
|
-
connection = configInfo.connection;
|
|
158
190
|
} else {
|
|
159
191
|
// 正常模式:从头开始安装
|
|
160
192
|
|
|
@@ -175,59 +207,137 @@ export const createMCPPluginStoreSlice: StateCreator<
|
|
|
175
207
|
install: true,
|
|
176
208
|
});
|
|
177
209
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
210
|
+
const deploymentOptions: any[] = Array.isArray(data.deploymentOptions)
|
|
211
|
+
? data.deploymentOptions
|
|
212
|
+
: [];
|
|
213
|
+
|
|
214
|
+
const httpOption = deploymentOptions.find(
|
|
215
|
+
(option) => option?.connection?.url && option?.connection?.type === 'http',
|
|
216
|
+
) ||
|
|
217
|
+
deploymentOptions.find(
|
|
218
|
+
(option) => option?.connection?.url && !option?.connection?.type,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const hasNonHttpDeployment = deploymentOptions.some((option) => {
|
|
222
|
+
const type = option?.connection?.type;
|
|
223
|
+
if (!type && option?.connection?.url) return false;
|
|
224
|
+
|
|
225
|
+
return type && type !== 'http';
|
|
182
226
|
});
|
|
183
227
|
|
|
184
|
-
|
|
185
|
-
if (abortController.signal.aborted) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
228
|
+
const shouldUseHttpDeployment = !!httpOption && (!hasNonHttpDeployment || !isDesktop);
|
|
188
229
|
|
|
189
|
-
|
|
230
|
+
if (shouldUseHttpDeployment && httpOption) {
|
|
231
|
+
// ✅ HTTP 类型:跳过系统依赖检查,直接使用 URL
|
|
232
|
+
log('HTTP MCP detected, skipping system dependency check');
|
|
190
233
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
234
|
+
connection = {
|
|
235
|
+
auth: httpOption.connection?.auth || { type: 'none' },
|
|
236
|
+
headers: httpOption.connection?.headers,
|
|
237
|
+
type: 'http',
|
|
238
|
+
url: httpOption.connection?.url,
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
log('Using HTTP connection: %O', { type: connection.type, url: connection.url });
|
|
195
242
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
243
|
+
const configSchema = httpOption.connection?.configSchema;
|
|
244
|
+
const needsConfig = doesConfigSchemaRequireInput(configSchema);
|
|
245
|
+
|
|
246
|
+
if (needsConfig && !normalizedConfig) {
|
|
247
|
+
updateMCPInstallProgress(identifier, {
|
|
248
|
+
configSchema,
|
|
249
|
+
connection,
|
|
250
|
+
manifest: data,
|
|
251
|
+
needsConfig: true,
|
|
252
|
+
progress: 50,
|
|
253
|
+
step: MCPInstallStep.CONFIGURATION_REQUIRED,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
updateInstallLoadingState(identifier, undefined);
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
// ❌ stdio 类型:需要完整的系统依赖检查流程
|
|
261
|
+
|
|
262
|
+
// 步骤 2: 检查安装环境
|
|
199
263
|
updateMCPInstallProgress(identifier, {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
progress: 40,
|
|
203
|
-
step: MCPInstallStep.DEPENDENCIES_REQUIRED,
|
|
204
|
-
systemDependencies: result.systemDependencies,
|
|
264
|
+
progress: 30,
|
|
265
|
+
step: MCPInstallStep.CHECKING_INSTALLATION,
|
|
205
266
|
});
|
|
206
267
|
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
268
|
+
// 检查是否已被取消
|
|
269
|
+
if (abortController.signal.aborted) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
result = await mcpService.checkInstallation(data, abortController.signal);
|
|
274
|
+
|
|
275
|
+
if (!result.success) {
|
|
276
|
+
updateMCPInstallProgress(identifier, undefined);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 步骤 3: 检查系统依赖是否满足
|
|
281
|
+
if (!skipDepsCheck && !result.allDependenciesMet) {
|
|
282
|
+
// 依赖不满足,暂停安装流程并显示依赖安装引导
|
|
283
|
+
updateMCPInstallProgress(identifier, {
|
|
284
|
+
connection: result.connection,
|
|
285
|
+
manifest: data,
|
|
286
|
+
progress: 40,
|
|
287
|
+
step: MCPInstallStep.DEPENDENCIES_REQUIRED,
|
|
288
|
+
systemDependencies: result.systemDependencies,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// 暂停安装流程,等待用户安装依赖
|
|
292
|
+
updateInstallLoadingState(identifier, undefined);
|
|
293
|
+
return false; // 返回 false 表示需要安装依赖
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// 步骤 4: 检查是否需要配置
|
|
297
|
+
if (result.needsConfig) {
|
|
298
|
+
// 需要配置,暂停安装流程
|
|
299
|
+
updateMCPInstallProgress(identifier, {
|
|
300
|
+
checkResult: result,
|
|
301
|
+
configSchema: result.configSchema,
|
|
302
|
+
connection: result.connection,
|
|
303
|
+
manifest: data,
|
|
304
|
+
needsConfig: true,
|
|
305
|
+
progress: 50,
|
|
306
|
+
step: MCPInstallStep.CONFIGURATION_REQUIRED,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// 暂停安装流程,等待用户配置
|
|
310
|
+
updateInstallLoadingState(identifier, undefined);
|
|
311
|
+
return false; // 返回 false 表示需要配置
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
connection = result.connection;
|
|
210
315
|
}
|
|
316
|
+
}
|
|
211
317
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
configSchema: result.configSchema,
|
|
218
|
-
connection: result.connection,
|
|
219
|
-
manifest: data,
|
|
220
|
-
needsConfig: true,
|
|
221
|
-
progress: 50,
|
|
222
|
-
step: MCPInstallStep.CONFIGURATION_REQUIRED,
|
|
223
|
-
});
|
|
318
|
+
let mergedHttpHeaders: Record<string, string> | undefined;
|
|
319
|
+
let mergedStdioEnv: Record<string, string> | undefined;
|
|
320
|
+
|
|
321
|
+
if (connection?.type === 'http') {
|
|
322
|
+
const baseHeaders = toNonEmptyStringRecord(connection.headers);
|
|
224
323
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
324
|
+
if (baseHeaders || normalizedConfig) {
|
|
325
|
+
mergedHttpHeaders = {
|
|
326
|
+
...baseHeaders,
|
|
327
|
+
...normalizedConfig,
|
|
328
|
+
};
|
|
228
329
|
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (connection?.type === 'stdio') {
|
|
333
|
+
const baseEnv = toNonEmptyStringRecord(connection.env);
|
|
229
334
|
|
|
230
|
-
|
|
335
|
+
if (baseEnv || normalizedConfig) {
|
|
336
|
+
mergedStdioEnv = {
|
|
337
|
+
...baseEnv,
|
|
338
|
+
...normalizedConfig,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
231
341
|
}
|
|
232
342
|
|
|
233
343
|
// 获取服务器清单逻辑
|
|
@@ -251,7 +361,7 @@ export const createMCPPluginStoreSlice: StateCreator<
|
|
|
251
361
|
{
|
|
252
362
|
args: connection.args,
|
|
253
363
|
command: connection.command!,
|
|
254
|
-
env:
|
|
364
|
+
env: mergedStdioEnv,
|
|
255
365
|
name: identifier, // 将配置作为环境变量传递(resume 模式下)
|
|
256
366
|
},
|
|
257
367
|
{ avatar: plugin.icon, description: plugin.description, name: data.name },
|
|
@@ -261,6 +371,8 @@ export const createMCPPluginStoreSlice: StateCreator<
|
|
|
261
371
|
if (connection?.type === 'http') {
|
|
262
372
|
manifest = await mcpService.getStreamableMcpServerManifest(
|
|
263
373
|
{
|
|
374
|
+
auth: connection.auth,
|
|
375
|
+
headers: mergedHttpHeaders,
|
|
264
376
|
identifier,
|
|
265
377
|
metadata: {
|
|
266
378
|
avatar: plugin.icon,
|
|
@@ -318,7 +430,7 @@ export const createMCPPluginStoreSlice: StateCreator<
|
|
|
318
430
|
customParams: { mcp: connection },
|
|
319
431
|
identifier: plugin.identifier,
|
|
320
432
|
manifest: manifest,
|
|
321
|
-
settings:
|
|
433
|
+
settings: normalizedConfig,
|
|
322
434
|
type: 'plugin',
|
|
323
435
|
});
|
|
324
436
|
|
|
@@ -347,7 +459,7 @@ export const createMCPPluginStoreSlice: StateCreator<
|
|
|
347
459
|
resources: (manifest as any).resources,
|
|
348
460
|
tools: (manifest as any).tools,
|
|
349
461
|
},
|
|
350
|
-
platform: result
|
|
462
|
+
platform: result?.platform || process.platform,
|
|
351
463
|
success: true,
|
|
352
464
|
userAgent,
|
|
353
465
|
version: manifest.version || data.version,
|
|
@@ -423,7 +535,7 @@ export const createMCPPluginStoreSlice: StateCreator<
|
|
|
423
535
|
installDurationMs,
|
|
424
536
|
installParams: connection,
|
|
425
537
|
metadata: errorInfo.metadata,
|
|
426
|
-
platform: result
|
|
538
|
+
platform: result?.platform || process.platform,
|
|
427
539
|
success: false,
|
|
428
540
|
userAgent,
|
|
429
541
|
version: data?.version,
|
|
@@ -581,10 +693,22 @@ export const createMCPPluginStoreSlice: StateCreator<
|
|
|
581
693
|
|
|
582
694
|
useFetchMCPPluginList: (params) => {
|
|
583
695
|
const locale = globalHelpers.getCurrentLanguage();
|
|
696
|
+
const requestParams = isDesktop ? params : { ...params, connectionType: McpConnectionType.http };
|
|
697
|
+
const swrKeyParts = [
|
|
698
|
+
'useFetchMCPPluginList',
|
|
699
|
+
locale,
|
|
700
|
+
requestParams.page,
|
|
701
|
+
requestParams.pageSize,
|
|
702
|
+
requestParams.q,
|
|
703
|
+
requestParams.connectionType,
|
|
704
|
+
];
|
|
705
|
+
const swrKey = swrKeyParts.filter((part) => part !== undefined && part !== null && part !== '')
|
|
706
|
+
.join('-');
|
|
707
|
+
const page = requestParams.page ?? 1;
|
|
584
708
|
|
|
585
709
|
return useSWR<PluginListResponse>(
|
|
586
|
-
|
|
587
|
-
() => discoverService.getMCPPluginList(
|
|
710
|
+
swrKey,
|
|
711
|
+
() => discoverService.getMCPPluginList(requestParams),
|
|
588
712
|
{
|
|
589
713
|
onSuccess(data) {
|
|
590
714
|
set(
|
|
@@ -602,7 +726,7 @@ export const createMCPPluginStoreSlice: StateCreator<
|
|
|
602
726
|
}
|
|
603
727
|
|
|
604
728
|
// 累积数据逻辑
|
|
605
|
-
if (
|
|
729
|
+
if (page === 1) {
|
|
606
730
|
// 第一页,直接设置
|
|
607
731
|
draft.mcpPluginItems = uniqBy(data.items, 'identifier');
|
|
608
732
|
} else {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { isDesktop } from '@/const/version';
|
|
2
1
|
import { DiscoverPluginItem } from '@/types/discover';
|
|
3
2
|
|
|
4
3
|
export type PluginInstallLoadingMap = Record<string, boolean | undefined>;
|
|
@@ -47,7 +46,7 @@ export const initialPluginStoreState: PluginStoreState = {
|
|
|
47
46
|
// Plugin list state management initial values
|
|
48
47
|
currentPluginPage: 1,
|
|
49
48
|
displayMode: 'grid',
|
|
50
|
-
listType:
|
|
49
|
+
listType: PluginStoreTabs.MCP,
|
|
51
50
|
oldPluginItems: [],
|
|
52
51
|
pluginInstallLoading: {},
|
|
53
52
|
pluginInstallProgress: {},
|
|
@@ -30,7 +30,7 @@ describe('createCommonSlice', () => {
|
|
|
30
30
|
const avatar = 'data:image/png;base64,';
|
|
31
31
|
|
|
32
32
|
const spyOn = vi.spyOn(result.current, 'refreshUserState');
|
|
33
|
-
const updateAvatarSpy = vi.spyOn(userService, 'updateAvatar').mockResolvedValue(
|
|
33
|
+
const updateAvatarSpy = vi.spyOn(userService, 'updateAvatar').mockResolvedValue({} as any);
|
|
34
34
|
|
|
35
35
|
await act(async () => {
|
|
36
36
|
await result.current.updateAvatar(avatar);
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { AiProviderModelListItem } from 'model-bank';
|
|
2
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
-
|
|
4
|
-
import { lambdaClient } from '@/libs/trpc/client';
|
|
5
|
-
|
|
6
|
-
import { ServerService } from './server';
|
|
7
|
-
|
|
8
|
-
vi.mock('@/libs/trpc/client', () => ({
|
|
9
|
-
lambdaClient: {
|
|
10
|
-
aiModel: {
|
|
11
|
-
createAiModel: { mutate: vi.fn() },
|
|
12
|
-
getAiProviderModelList: { query: vi.fn() },
|
|
13
|
-
getAiModelById: { query: vi.fn() },
|
|
14
|
-
toggleModelEnabled: { mutate: vi.fn() },
|
|
15
|
-
updateAiModel: { mutate: vi.fn() },
|
|
16
|
-
batchUpdateAiModels: { mutate: vi.fn() },
|
|
17
|
-
batchToggleAiModels: { mutate: vi.fn() },
|
|
18
|
-
clearModelsByProvider: { mutate: vi.fn() },
|
|
19
|
-
clearRemoteModels: { mutate: vi.fn() },
|
|
20
|
-
updateAiModelOrder: { mutate: vi.fn() },
|
|
21
|
-
removeAiModel: { mutate: vi.fn() },
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
}));
|
|
25
|
-
|
|
26
|
-
describe('ServerService', () => {
|
|
27
|
-
const service = new ServerService();
|
|
28
|
-
|
|
29
|
-
it('should create AI model', async () => {
|
|
30
|
-
const params = {
|
|
31
|
-
id: 'test-id',
|
|
32
|
-
providerId: 'test-provider',
|
|
33
|
-
displayName: 'Test Model',
|
|
34
|
-
};
|
|
35
|
-
await service.createAiModel(params);
|
|
36
|
-
expect(vi.mocked(lambdaClient.aiModel.createAiModel.mutate)).toHaveBeenCalledWith(params);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('should get AI provider model list', async () => {
|
|
40
|
-
await service.getAiProviderModelList('123');
|
|
41
|
-
expect(vi.mocked(lambdaClient.aiModel.getAiProviderModelList.query)).toHaveBeenCalledWith({
|
|
42
|
-
id: '123',
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should get AI model by id', async () => {
|
|
47
|
-
await service.getAiModelById('123');
|
|
48
|
-
expect(vi.mocked(lambdaClient.aiModel.getAiModelById.query)).toHaveBeenCalledWith({
|
|
49
|
-
id: '123',
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should toggle model enabled', async () => {
|
|
54
|
-
const params = { id: '123', providerId: 'test', enabled: true };
|
|
55
|
-
await service.toggleModelEnabled(params);
|
|
56
|
-
expect(vi.mocked(lambdaClient.aiModel.toggleModelEnabled.mutate)).toHaveBeenCalledWith(params);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should update AI model', async () => {
|
|
60
|
-
const value = { contextWindowTokens: 4000, displayName: 'Updated Model' };
|
|
61
|
-
await service.updateAiModel('123', 'openai', value);
|
|
62
|
-
expect(vi.mocked(lambdaClient.aiModel.updateAiModel.mutate)).toHaveBeenCalledWith({
|
|
63
|
-
id: '123',
|
|
64
|
-
providerId: 'openai',
|
|
65
|
-
value,
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should batch update AI models', async () => {
|
|
70
|
-
const models: AiProviderModelListItem[] = [
|
|
71
|
-
{
|
|
72
|
-
id: '123',
|
|
73
|
-
enabled: true,
|
|
74
|
-
type: 'chat',
|
|
75
|
-
},
|
|
76
|
-
];
|
|
77
|
-
await service.batchUpdateAiModels('provider1', models);
|
|
78
|
-
expect(vi.mocked(lambdaClient.aiModel.batchUpdateAiModels.mutate)).toHaveBeenCalledWith({
|
|
79
|
-
id: 'provider1',
|
|
80
|
-
models,
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('should batch toggle AI models', async () => {
|
|
85
|
-
const models = ['123', '456'];
|
|
86
|
-
await service.batchToggleAiModels('provider1', models, true);
|
|
87
|
-
expect(vi.mocked(lambdaClient.aiModel.batchToggleAiModels.mutate)).toHaveBeenCalledWith({
|
|
88
|
-
id: 'provider1',
|
|
89
|
-
models,
|
|
90
|
-
enabled: true,
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('should clear models by provider', async () => {
|
|
95
|
-
await service.clearModelsByProvider('provider1');
|
|
96
|
-
expect(vi.mocked(lambdaClient.aiModel.clearModelsByProvider.mutate)).toHaveBeenCalledWith({
|
|
97
|
-
providerId: 'provider1',
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('should clear remote models', async () => {
|
|
102
|
-
await service.clearRemoteModels('provider1');
|
|
103
|
-
expect(vi.mocked(lambdaClient.aiModel.clearRemoteModels.mutate)).toHaveBeenCalledWith({
|
|
104
|
-
providerId: 'provider1',
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('should update AI model order', async () => {
|
|
109
|
-
const items = [{ id: '123', sort: 1 }];
|
|
110
|
-
await service.updateAiModelOrder('provider1', items);
|
|
111
|
-
expect(vi.mocked(lambdaClient.aiModel.updateAiModelOrder.mutate)).toHaveBeenCalledWith({
|
|
112
|
-
providerId: 'provider1',
|
|
113
|
-
sortMap: items,
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('should delete AI model', async () => {
|
|
118
|
-
const params = { id: '123', providerId: 'openai' };
|
|
119
|
-
await service.deleteAiModel(params);
|
|
120
|
-
expect(vi.mocked(lambdaClient.aiModel.removeAiModel.mutate)).toHaveBeenCalledWith(params);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { lambdaClient } from '@/libs/trpc/client';
|
|
2
|
-
import { IAiModelService } from '@/services/aiModel/type';
|
|
3
|
-
|
|
4
|
-
export class ServerService implements IAiModelService {
|
|
5
|
-
createAiModel: IAiModelService['createAiModel'] = async (params) => {
|
|
6
|
-
return lambdaClient.aiModel.createAiModel.mutate(params);
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
getAiProviderModelList: IAiModelService['getAiProviderModelList'] = async (id) => {
|
|
10
|
-
return lambdaClient.aiModel.getAiProviderModelList.query({ id });
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
getAiModelById: IAiModelService['getAiModelById'] = async (id) => {
|
|
14
|
-
return lambdaClient.aiModel.getAiModelById.query({ id });
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
toggleModelEnabled: IAiModelService['toggleModelEnabled'] = async (params) => {
|
|
18
|
-
return lambdaClient.aiModel.toggleModelEnabled.mutate(params);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
updateAiModel: IAiModelService['updateAiModel'] = async (id, providerId, value) => {
|
|
22
|
-
return lambdaClient.aiModel.updateAiModel.mutate({ id, providerId, value });
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
batchUpdateAiModels: IAiModelService['batchUpdateAiModels'] = async (id, models) => {
|
|
26
|
-
return lambdaClient.aiModel.batchUpdateAiModels.mutate({ id, models });
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
batchToggleAiModels: IAiModelService['batchToggleAiModels'] = async (id, models, enabled) => {
|
|
30
|
-
return lambdaClient.aiModel.batchToggleAiModels.mutate({ enabled, id, models });
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
clearModelsByProvider: IAiModelService['clearModelsByProvider'] = async (providerId) => {
|
|
34
|
-
return lambdaClient.aiModel.clearModelsByProvider.mutate({ providerId });
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
clearRemoteModels: IAiModelService['clearRemoteModels'] = async (providerId) => {
|
|
38
|
-
return lambdaClient.aiModel.clearRemoteModels.mutate({ providerId });
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
updateAiModelOrder: IAiModelService['updateAiModelOrder'] = async (providerId, items) => {
|
|
42
|
-
return lambdaClient.aiModel.updateAiModelOrder.mutate({ providerId, sortMap: items });
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
deleteAiModel: IAiModelService['deleteAiModel'] = async (params: {
|
|
46
|
-
id: string;
|
|
47
|
-
providerId: string;
|
|
48
|
-
}) => {
|
|
49
|
-
return lambdaClient.aiModel.removeAiModel.mutate(params);
|
|
50
|
-
};
|
|
51
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/* eslint-disable typescript-sort-keys/interface */
|
|
2
|
-
import {
|
|
3
|
-
AiModelSortMap,
|
|
4
|
-
AiProviderModelListItem,
|
|
5
|
-
CreateAiModelParams,
|
|
6
|
-
ToggleAiModelEnableParams,
|
|
7
|
-
UpdateAiModelParams,
|
|
8
|
-
} from 'model-bank';
|
|
9
|
-
|
|
10
|
-
export interface IAiModelService {
|
|
11
|
-
createAiModel: (params: CreateAiModelParams) => Promise<any>;
|
|
12
|
-
|
|
13
|
-
getAiProviderModelList: (id: string) => Promise<AiProviderModelListItem[]>;
|
|
14
|
-
|
|
15
|
-
getAiModelById: (id: string) => Promise<any>;
|
|
16
|
-
|
|
17
|
-
toggleModelEnabled: (params: ToggleAiModelEnableParams) => Promise<any>;
|
|
18
|
-
|
|
19
|
-
updateAiModel: (id: string, providerId: string, value: UpdateAiModelParams) => Promise<any>;
|
|
20
|
-
|
|
21
|
-
batchUpdateAiModels: (id: string, models: AiProviderModelListItem[]) => Promise<any>;
|
|
22
|
-
|
|
23
|
-
batchToggleAiModels: (id: string, models: string[], enabled: boolean) => Promise<any>;
|
|
24
|
-
|
|
25
|
-
clearRemoteModels: (providerId: string) => Promise<any>;
|
|
26
|
-
|
|
27
|
-
clearModelsByProvider: (providerId: string) => Promise<any>;
|
|
28
|
-
|
|
29
|
-
updateAiModelOrder: (providerId: string, items: AiModelSortMap[]) => Promise<any>;
|
|
30
|
-
|
|
31
|
-
deleteAiModel: (params: { id: string; providerId: string }) => Promise<any>;
|
|
32
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { lambdaClient } from '@/libs/trpc/client';
|
|
2
|
-
|
|
3
|
-
import { IAiProviderService } from './type';
|
|
4
|
-
|
|
5
|
-
export class ServerService implements IAiProviderService {
|
|
6
|
-
createAiProvider: IAiProviderService['createAiProvider'] = async (params) => {
|
|
7
|
-
return lambdaClient.aiProvider.createAiProvider.mutate(params);
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
getAiProviderList: IAiProviderService['getAiProviderList'] = async () => {
|
|
11
|
-
return lambdaClient.aiProvider.getAiProviderList.query();
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
getAiProviderById: IAiProviderService['getAiProviderById'] = async (id) => {
|
|
15
|
-
return lambdaClient.aiProvider.getAiProviderById.query({ id });
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
toggleProviderEnabled: IAiProviderService['toggleProviderEnabled'] = async (id, enabled) => {
|
|
19
|
-
return lambdaClient.aiProvider.toggleProviderEnabled.mutate({ enabled, id });
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
updateAiProvider: IAiProviderService['updateAiProvider'] = async (id, value) => {
|
|
23
|
-
return lambdaClient.aiProvider.updateAiProvider.mutate({ id, value });
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
updateAiProviderConfig: IAiProviderService['updateAiProviderConfig'] = async (id, value) => {
|
|
27
|
-
return lambdaClient.aiProvider.updateAiProviderConfig.mutate({ id, value });
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
updateAiProviderOrder: IAiProviderService['updateAiProviderOrder'] = async (items) => {
|
|
31
|
-
return lambdaClient.aiProvider.updateAiProviderOrder.mutate({ sortMap: items });
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
deleteAiProvider: IAiProviderService['deleteAiProvider'] = async (id) => {
|
|
35
|
-
return lambdaClient.aiProvider.removeAiProvider.mutate({ id });
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
getAiProviderRuntimeState: IAiProviderService['getAiProviderRuntimeState'] = async (
|
|
39
|
-
isLogin?: boolean,
|
|
40
|
-
) => {
|
|
41
|
-
return lambdaClient.aiProvider.getAiProviderRuntimeState.query({ isLogin });
|
|
42
|
-
};
|
|
43
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AiProviderDetailItem,
|
|
3
|
-
AiProviderRuntimeState,
|
|
4
|
-
AiProviderSortMap,
|
|
5
|
-
CreateAiProviderParams,
|
|
6
|
-
UpdateAiProviderConfigParams,
|
|
7
|
-
} from '@/types/aiProvider';
|
|
8
|
-
|
|
9
|
-
export interface IAiProviderService {
|
|
10
|
-
createAiProvider: (params: CreateAiProviderParams) => Promise<any>;
|
|
11
|
-
|
|
12
|
-
deleteAiProvider: (id: string) => Promise<any>;
|
|
13
|
-
|
|
14
|
-
getAiProviderById: (id: string) => Promise<AiProviderDetailItem | undefined>;
|
|
15
|
-
|
|
16
|
-
getAiProviderList: () => Promise<any>;
|
|
17
|
-
|
|
18
|
-
getAiProviderRuntimeState: (isLogin?: boolean) => Promise<AiProviderRuntimeState>;
|
|
19
|
-
|
|
20
|
-
toggleProviderEnabled: (id: string, enabled: boolean) => Promise<any>;
|
|
21
|
-
|
|
22
|
-
updateAiProvider: (id: string, value: any) => Promise<any>;
|
|
23
|
-
|
|
24
|
-
updateAiProviderConfig: (id: string, value: UpdateAiProviderConfigParams) => Promise<any>;
|
|
25
|
-
|
|
26
|
-
updateAiProviderOrder: (items: AiProviderSortMap[]) => Promise<any>;
|
|
27
|
-
}
|