plugin-ai-api 1.1.0 → 1.1.2
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/README.md +51 -12
- package/dist/client/185.c47663fefaeb0e5b.js +10 -0
- package/dist/client/562.9012cfd1fa04303d.js +10 -0
- package/dist/client/685.b5b1e0a5b825d253.js +10 -0
- package/dist/client/97.1bc5103fd9d995a8.js +10 -0
- package/dist/client/index.js +1 -1
- package/dist/client-v2/185.b552dc91ec2371ba.js +10 -0
- package/dist/client-v2/562.db2984167250b1be.js +10 -0
- package/dist/client-v2/685.cf16e5b829e06f85.js +10 -0
- package/dist/client-v2/97.96da323832251796.js +10 -0
- package/dist/client-v2/index.js +1 -1
- package/dist/externalVersion.js +8 -8
- package/dist/locale/en-US.json +38 -1
- package/dist/locale/vi-VN.json +177 -138
- package/dist/locale/zh-CN.json +177 -138
- package/dist/server/billing.js +17 -9
- package/dist/server/collections/ai-api-model-metadata.js +26 -0
- package/dist/server/collections/ai-api-model-prices.js +8 -0
- package/dist/server/collections/ai-api-response-records.js +101 -0
- package/dist/server/collections/ai-api-usage-records.js +1 -0
- package/dist/server/collections/ai-api-virtual-models.js +68 -0
- package/dist/server/middleware/response-record-resource.js +66 -0
- package/dist/server/middleware/role-permission.js +43 -18
- package/dist/server/migrations/20260827000000-add-cache-input-price.js +66 -0
- package/dist/server/migrations/20260901000000-remove-default-group-members.js +60 -0
- package/dist/server/migrations/20260902000000-seed-default-role-permissions.js +55 -0
- package/dist/server/migrations/20260903000000-seed-sample-response-records.js +170 -0
- package/dist/server/plugin.js +66 -16
- package/dist/server/routes/auth.js +16 -0
- package/dist/server/routes/chat-completions.js +38 -6
- package/dist/server/routes/completions.js +16 -4
- package/dist/server/routes/embeddings.js +34 -5
- package/dist/server/routes/models.js +29 -0
- package/dist/server/routes/responses.js +530 -0
- package/dist/server/routes/router.js +65 -10
- package/dist/server/usage.js +26 -4
- package/dist/server/utils/direct-llm-context.js +10 -9
- package/dist/server/utils/resolve-service.js +24 -0
- package/dist/server/utils/response-store.js +138 -0
- package/dist/server/utils/responses-format.js +686 -0
- package/dist/server/utils/responses-stream.js +330 -0
- package/dist/server/utils/virtual-models.js +238 -0
- package/dist/server/validation.js +45 -2
- package/dist/swagger.js +137 -0
- package/package.json +34 -32
- package/src/__tests__/locale.test.ts +43 -0
- package/src/client/__tests__/settings-registration.test.tsx +1 -0
- package/src/client/index.tsx +10 -10
- package/src/client/models/index.ts +12 -12
- package/src/client/plugin.tsx +9 -1
- package/src/client-v2/__tests__/settings-registration.test.tsx +1 -0
- package/src/client-v2/pages/ModelMetadataPage.tsx +44 -0
- package/src/client-v2/pages/ModelPricingPage.tsx +15 -0
- package/src/client-v2/pages/ModelRoutingPage.tsx +238 -0
- package/src/client-v2/pages/UsageGroupsPage.tsx +75 -38
- package/src/client-v2/plugin.tsx +8 -0
- package/src/index.ts +11 -11
- package/src/locale/en-US.json +38 -1
- package/src/locale/vi-VN.json +177 -138
- package/src/locale/zh-CN.json +177 -138
- package/src/server/__tests__/billing-quota.test.ts +8 -2
- package/src/server/__tests__/billing.test.ts +13 -0
- package/src/server/__tests__/embeddings.test.ts +184 -0
- package/src/server/__tests__/models.test.ts +21 -1
- package/src/server/__tests__/response-record-resource.test.ts +50 -0
- package/src/server/__tests__/response-store-integration.test.ts +341 -0
- package/src/server/__tests__/response-store.test.ts +195 -0
- package/src/server/__tests__/responses-contract.test.ts +469 -0
- package/src/server/__tests__/responses-format.test.ts +299 -0
- package/src/server/__tests__/responses-router.test.ts +182 -0
- package/src/server/__tests__/responses-streaming.test.ts +368 -0
- package/src/server/__tests__/responses.test.ts +462 -0
- package/src/server/__tests__/role-permission.test.ts +139 -0
- package/src/server/__tests__/seed-role-permission.test.ts +88 -0
- package/src/server/__tests__/types/responses-sdk.types.test-d.ts +23 -0
- package/src/server/__tests__/usage-groups.test.ts +96 -0
- package/src/server/__tests__/usage-route.test.ts +1 -0
- package/src/server/__tests__/usage.test.ts +15 -0
- package/src/server/__tests__/validation.test.ts +66 -7
- package/src/server/__tests__/virtual-model-routing.test.ts +589 -0
- package/src/server/billing.ts +29 -6
- package/src/server/collections/ai-api-model-metadata.ts +26 -0
- package/src/server/collections/ai-api-model-prices.ts +8 -0
- package/src/server/collections/ai-api-response-records.ts +77 -0
- package/src/server/collections/ai-api-usage-records.ts +1 -0
- package/src/server/collections/ai-api-virtual-models.ts +58 -0
- package/src/server/middleware/response-record-resource.ts +44 -0
- package/src/server/middleware/role-permission.ts +69 -35
- package/src/server/migrations/20260827000000-add-cache-input-price.ts +49 -0
- package/src/server/migrations/20260901000000-remove-default-group-members.ts +56 -0
- package/src/server/migrations/20260902000000-seed-default-role-permissions.ts +46 -0
- package/src/server/migrations/20260903000000-seed-sample-response-records.ts +162 -0
- package/src/server/plugin.ts +84 -20
- package/src/server/resource/ai-api-config.ts +2 -1
- package/src/server/routes/agent-completions.ts +3 -0
- package/src/server/routes/auth.ts +21 -1
- package/src/server/routes/chat-completions.ts +34 -10
- package/src/server/routes/completions.ts +16 -4
- package/src/server/routes/embeddings.ts +42 -6
- package/src/server/routes/models.ts +34 -0
- package/src/server/routes/responses.ts +640 -0
- package/src/server/routes/router.ts +81 -12
- package/src/server/services/__tests__/file-processor.test.ts +1 -0
- package/src/server/usage.ts +30 -2
- package/src/server/utils/app-observability.ts +1 -1
- package/src/server/utils/direct-llm-context.ts +23 -12
- package/src/server/utils/openai-format.ts +1 -0
- package/src/server/utils/resolve-service.ts +39 -1
- package/src/server/utils/response-store.ts +148 -0
- package/src/server/utils/responses-format.ts +974 -0
- package/src/server/utils/responses-stream.ts +384 -0
- package/src/server/utils/virtual-models.ts +320 -0
- package/src/server/validation.ts +50 -0
- package/src/swagger.ts +139 -0
- package/dist/client/562.44b16aad4718b4c7.js +0 -10
- package/dist/client/685.ae483e17b6b49c98.js +0 -10
- package/dist/client/97.9b6b2d2b01a4c060.js +0 -10
- package/dist/client-v2/562.45d5c504433be38b.js +0 -10
- package/dist/client-v2/685.1030370b309b7d4b.js +0 -10
- package/dist/client-v2/97.36a42eff36bb3d8a.js +0 -10
- package/dist/server/collections/ai-api-user-permissions.js +0 -67
- package/dist/server/collections/ai-api-user-quota-buckets.js +0 -54
- package/dist/server/collections/ai-api-user-quota-policies.js +0 -63
- package/dist/server/resource/ai-api-usage-groups.js +0 -168
- package/src/server/collections/ai-api-user-permissions.ts +0 -46
- package/src/server/collections/ai-api-user-quota-buckets.ts +0 -24
- package/src/server/collections/ai-api-user-quota-policies.ts +0 -33
- package/src/server/resource/ai-api-usage-groups.ts +0 -171
package/dist/swagger.js
CHANGED
|
@@ -197,6 +197,143 @@ var swagger_default = {
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
},
|
|
200
|
+
"/ai-llm/v1/responses": {
|
|
201
|
+
post: {
|
|
202
|
+
tags: ["ai-llm"],
|
|
203
|
+
summary: "Create a model response",
|
|
204
|
+
description: "OpenAI Responses API endpoint. Supports text/image/file inputs, function calling, streaming, reasoning output when exposed by the provider, and owner-scoped previous_response_id chains. Built-in web_search and file_search tools are not supported.",
|
|
205
|
+
security: [{ BearerAuth: [] }],
|
|
206
|
+
requestBody: {
|
|
207
|
+
required: true,
|
|
208
|
+
content: {
|
|
209
|
+
"application/json": {
|
|
210
|
+
schema: {
|
|
211
|
+
type: "object",
|
|
212
|
+
properties: {
|
|
213
|
+
model: { type: "string", example: "openai/gpt-4o" },
|
|
214
|
+
input: {
|
|
215
|
+
oneOf: [{ type: "string" }, { type: "array", items: { type: "object" } }]
|
|
216
|
+
},
|
|
217
|
+
instructions: { type: "string" },
|
|
218
|
+
max_output_tokens: { type: "integer" },
|
|
219
|
+
temperature: { type: "number", minimum: 0, maximum: 2 },
|
|
220
|
+
top_p: { type: "number", minimum: 0, maximum: 1 },
|
|
221
|
+
tools: { type: "array", description: "Function tools only", items: { type: "object" } },
|
|
222
|
+
tool_choice: { oneOf: [{ type: "string" }, { type: "object" }] },
|
|
223
|
+
metadata: { type: "object" },
|
|
224
|
+
previous_response_id: { type: "string" },
|
|
225
|
+
store: { type: "boolean", default: true },
|
|
226
|
+
stream: { type: "boolean", default: false },
|
|
227
|
+
truncation: { type: "string", enum: ["auto", "disabled"], default: "disabled" },
|
|
228
|
+
service_tier: { type: "string", enum: ["auto", "default", "flex", "scale", "priority"] },
|
|
229
|
+
prompt_cache_key: { type: "string" },
|
|
230
|
+
prompt_cache_retention: { type: "string", enum: ["in_memory", "24h"], nullable: true },
|
|
231
|
+
reasoning: { type: "object" },
|
|
232
|
+
safety_identifier: { type: "string", maxLength: 64 },
|
|
233
|
+
parallel_tool_calls: { type: "boolean", default: true },
|
|
234
|
+
text: { type: "object" }
|
|
235
|
+
},
|
|
236
|
+
required: ["model", "input"]
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
responses: {
|
|
242
|
+
200: {
|
|
243
|
+
description: "OpenAI Response object, or Responses API SSE events when stream=true",
|
|
244
|
+
content: {
|
|
245
|
+
"application/json": {
|
|
246
|
+
schema: {
|
|
247
|
+
type: "object",
|
|
248
|
+
properties: {
|
|
249
|
+
id: { type: "string", example: "resp_abc123" },
|
|
250
|
+
object: { type: "string", const: "response" },
|
|
251
|
+
created_at: { type: "integer" },
|
|
252
|
+
completed_at: { type: "integer", nullable: true },
|
|
253
|
+
model: { type: "string" },
|
|
254
|
+
status: {
|
|
255
|
+
type: "string",
|
|
256
|
+
enum: ["completed", "failed", "in_progress", "cancelled", "queued", "incomplete"]
|
|
257
|
+
},
|
|
258
|
+
output_text: { type: "string" },
|
|
259
|
+
output: { type: "array", items: { type: "object" } },
|
|
260
|
+
usage: {
|
|
261
|
+
type: "object",
|
|
262
|
+
properties: {
|
|
263
|
+
input_tokens: { type: "integer" },
|
|
264
|
+
input_tokens_details: {
|
|
265
|
+
type: "object",
|
|
266
|
+
properties: { cached_tokens: { type: "integer" } }
|
|
267
|
+
},
|
|
268
|
+
output_tokens: { type: "integer" },
|
|
269
|
+
output_tokens_details: {
|
|
270
|
+
type: "object",
|
|
271
|
+
properties: { reasoning_tokens: { type: "integer" } }
|
|
272
|
+
},
|
|
273
|
+
total_tokens: { type: "integer" }
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
metadata: { type: "object", nullable: true },
|
|
277
|
+
previous_response_id: { type: "string", nullable: true },
|
|
278
|
+
truncation: { type: "string", enum: ["auto", "disabled"] },
|
|
279
|
+
error: { type: "object", nullable: true }
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
"text/event-stream": { schema: { type: "string" } }
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
400: { description: "Invalid request or unsupported tool" },
|
|
287
|
+
404: { description: "Model or previous response not found" },
|
|
288
|
+
429: { description: "Rate limit or quota exceeded" }
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
"/ai-llm/v1/responses/{id}": {
|
|
293
|
+
get: {
|
|
294
|
+
tags: ["ai-llm"],
|
|
295
|
+
summary: "Retrieve a stored model response",
|
|
296
|
+
description: "Returns a non-expired response created by the authenticated user. Responses created with store=false cannot be retrieved.",
|
|
297
|
+
security: [{ BearerAuth: [] }],
|
|
298
|
+
parameters: [
|
|
299
|
+
{
|
|
300
|
+
name: "id",
|
|
301
|
+
in: "path",
|
|
302
|
+
required: true,
|
|
303
|
+
schema: { type: "string", example: "resp_abc123" }
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: "stream",
|
|
307
|
+
in: "query",
|
|
308
|
+
schema: { type: "boolean", default: false },
|
|
309
|
+
description: "Only false is currently supported; stream retrieval is rejected."
|
|
310
|
+
}
|
|
311
|
+
],
|
|
312
|
+
responses: {
|
|
313
|
+
200: { description: "Stored OpenAI Response object" },
|
|
314
|
+
400: { description: "Unsupported retrieve expansion or streaming parameter" },
|
|
315
|
+
404: { description: "Response not found, expired, or owned by another user" }
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
delete: {
|
|
319
|
+
tags: ["ai-llm"],
|
|
320
|
+
summary: "Delete a stored model response",
|
|
321
|
+
description: "Deletes a stored response owned by the authenticated user.",
|
|
322
|
+
security: [{ BearerAuth: [] }],
|
|
323
|
+
parameters: [
|
|
324
|
+
{
|
|
325
|
+
name: "id",
|
|
326
|
+
in: "path",
|
|
327
|
+
required: true,
|
|
328
|
+
schema: { type: "string", example: "resp_abc123" }
|
|
329
|
+
}
|
|
330
|
+
],
|
|
331
|
+
responses: {
|
|
332
|
+
204: { description: "Response deleted" },
|
|
333
|
+
404: { description: "Response not found, expired, or owned by another user" }
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
},
|
|
200
337
|
"/ai-llm/v1/embeddings": {
|
|
201
338
|
post: {
|
|
202
339
|
tags: ["ai-llm"],
|
package/package.json
CHANGED
|
@@ -1,33 +1,35 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "plugin-ai-api",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"main": "dist/server/index.js",
|
|
5
|
-
"dependencies": {},
|
|
6
|
-
"peerDependencies": {
|
|
7
|
-
"@nocobase/client": "2.x",
|
|
8
|
-
"@nocobase/server": "2.x",
|
|
9
|
-
"@nocobase/database": "2.x",
|
|
10
|
-
"@nocobase/plugin-acl": "2.x",
|
|
11
|
-
"@nocobase/plugin-ai": "2.x",
|
|
12
|
-
"@nocobase/plugin-api-keys": "2.x",
|
|
13
|
-
"@nocobase/client-v2": "2.x",
|
|
14
|
-
"@nocobase/flow-engine": "2.x"
|
|
15
|
-
},
|
|
16
|
-
"nocobase": {
|
|
17
|
-
"supportedVersions": [
|
|
18
|
-
"2.x"
|
|
19
|
-
],
|
|
20
|
-
"editionLevel": 0
|
|
21
|
-
},
|
|
22
|
-
"files": [
|
|
23
|
-
"dist",
|
|
24
|
-
"src",
|
|
25
|
-
"client.js",
|
|
26
|
-
"client-v2.js",
|
|
27
|
-
"server.js",
|
|
28
|
-
"client.d.ts",
|
|
29
|
-
"client-v2.d.ts",
|
|
30
|
-
"server.d.ts"
|
|
31
|
-
],
|
|
32
|
-
"license": "Apache-2.0"
|
|
1
|
+
{
|
|
2
|
+
"name": "plugin-ai-api",
|
|
3
|
+
"version": "1.1.2",
|
|
4
|
+
"main": "dist/server/index.js",
|
|
5
|
+
"dependencies": {},
|
|
6
|
+
"peerDependencies": {
|
|
7
|
+
"@nocobase/client": "2.x",
|
|
8
|
+
"@nocobase/server": "2.x",
|
|
9
|
+
"@nocobase/database": "2.x",
|
|
10
|
+
"@nocobase/plugin-acl": "2.x",
|
|
11
|
+
"@nocobase/plugin-ai": "2.x",
|
|
12
|
+
"@nocobase/plugin-api-keys": "2.x",
|
|
13
|
+
"@nocobase/client-v2": "2.x",
|
|
14
|
+
"@nocobase/flow-engine": "2.x"
|
|
15
|
+
},
|
|
16
|
+
"nocobase": {
|
|
17
|
+
"supportedVersions": [
|
|
18
|
+
"2.x"
|
|
19
|
+
],
|
|
20
|
+
"editionLevel": 0
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"src",
|
|
25
|
+
"client.js",
|
|
26
|
+
"client-v2.js",
|
|
27
|
+
"server.js",
|
|
28
|
+
"client.d.ts",
|
|
29
|
+
"client-v2.d.ts",
|
|
30
|
+
"server.d.ts"
|
|
31
|
+
],
|
|
32
|
+
"license": "Apache-2.0"
|
|
33
|
+
,
|
|
34
|
+
"keywords": "AI"
|
|
33
35
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
import { describe, expect, it } from 'vitest';
|
|
13
|
+
|
|
14
|
+
const LOCALE_DIR = join(__dirname, '..', 'locale');
|
|
15
|
+
const FILES = ['en-US.json', 'zh-CN.json', 'vi-VN.json'] as const;
|
|
16
|
+
|
|
17
|
+
function loadLocale(file: string): Record<string, string> {
|
|
18
|
+
return JSON.parse(readFileSync(join(LOCALE_DIR, file), 'utf8')) as Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('plugin-ai-api locale files', () => {
|
|
22
|
+
it('all locale files are valid JSON with no empty values', () => {
|
|
23
|
+
for (const file of FILES) {
|
|
24
|
+
const locale = loadLocale(file);
|
|
25
|
+
expect(Object.keys(locale).length, `${file} should not be empty`).toBeGreaterThan(0);
|
|
26
|
+
for (const [key, value] of Object.entries(locale)) {
|
|
27
|
+
expect(typeof value, `${file}[${JSON.stringify(key)}] must be a string`).toBe('string');
|
|
28
|
+
expect(value.trim().length, `${file}[${JSON.stringify(key)}] must not be blank`).toBeGreaterThan(0);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('all locales expose the identical set of keys', () => {
|
|
34
|
+
const [base, ...rest] = FILES;
|
|
35
|
+
const baseKeys = Object.keys(loadLocale(base)).sort();
|
|
36
|
+
for (const file of rest) {
|
|
37
|
+
const keys = Object.keys(loadLocale(file)).sort();
|
|
38
|
+
const missing = baseKeys.filter((key) => !keys.includes(key));
|
|
39
|
+
const extra = keys.filter((key) => !baseKeys.includes(key));
|
|
40
|
+
expect({ file, missing, extra }).toEqual({ file, missing: [], extra: [] });
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
package/src/client/index.tsx
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export { default } from './plugin';
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export { default } from './plugin';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { ModelConstructor } from '@nocobase/flow-engine';
|
|
11
|
-
|
|
12
|
-
export default {} as Record<string, ModelConstructor>;
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { ModelConstructor } from '@nocobase/flow-engine';
|
|
11
|
+
|
|
12
|
+
export default {} as Record<string, ModelConstructor>;
|
package/src/client/plugin.tsx
CHANGED
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { Plugin, lazy } from '@nocobase/client';
|
|
11
|
-
import PluginACLClient from '@nocobase/plugin-acl
|
|
11
|
+
import PluginACLClient from '@nocobase/plugin-acl';
|
|
12
12
|
import { AI_API_ACL_SNIPPET } from '../constants';
|
|
13
13
|
import React from 'react';
|
|
14
14
|
|
|
15
15
|
const AiApiConfigPage = React.lazy(() => import('../client-v2/pages/GeneralPage'));
|
|
16
16
|
const AiApiModelPricingPage = React.lazy(() => import('../client-v2/pages/ModelPricingPage'));
|
|
17
17
|
const AiApiModelMetadataPage = React.lazy(() => import('../client-v2/pages/ModelMetadataPage'));
|
|
18
|
+
const AiApiModelRoutingPage = React.lazy(() => import('../client-v2/pages/ModelRoutingPage'));
|
|
18
19
|
const AiApiUsageGroupsPage = React.lazy(() => import('../client-v2/pages/UsageGroupsPage'));
|
|
19
20
|
const AiApiUsagePage = React.lazy(() => import('../client-v2/pages/UsagePage'));
|
|
20
21
|
const { AiApiRolePermissions } = lazy(() => import('./components/AiApiRolePermissions'), 'AiApiRolePermissions');
|
|
@@ -48,6 +49,13 @@ export class PluginAiApiClient extends Plugin {
|
|
|
48
49
|
sort: 3,
|
|
49
50
|
});
|
|
50
51
|
|
|
52
|
+
this.app.pluginSettingsManager.add('ai-api.model-routing', {
|
|
53
|
+
title: this.t('Model routing'),
|
|
54
|
+
Component: AiApiModelRoutingPage,
|
|
55
|
+
aclSnippet: AI_API_ACL_SNIPPET,
|
|
56
|
+
sort: 4,
|
|
57
|
+
});
|
|
58
|
+
|
|
51
59
|
this.app.pluginSettingsManager.add('ai-api.usage-groups', {
|
|
52
60
|
title: this.t('Usage groups'),
|
|
53
61
|
Component: AiApiUsageGroupsPage,
|
|
@@ -23,6 +23,10 @@ interface ModelMetadata {
|
|
|
23
23
|
id: string | number;
|
|
24
24
|
llmService: string;
|
|
25
25
|
model: string;
|
|
26
|
+
supportsVision?: boolean;
|
|
27
|
+
supportsToolCalling?: boolean;
|
|
28
|
+
reasoningTier?: string;
|
|
29
|
+
sortOrder?: number;
|
|
26
30
|
contextWindow?: number | null;
|
|
27
31
|
maxCompletionTokens?: number | null;
|
|
28
32
|
ownedByOverride?: string | null;
|
|
@@ -138,6 +142,10 @@ export default function ModelMetadataPage() {
|
|
|
138
142
|
displayName: values.displayName?.trim() || null,
|
|
139
143
|
description: values.description?.trim() || null,
|
|
140
144
|
systemPrompt: values.systemPrompt?.trim() || null,
|
|
145
|
+
supportsVision: !!values.supportsVision,
|
|
146
|
+
supportsToolCalling: values.supportsToolCalling !== false,
|
|
147
|
+
reasoningTier: values.reasoningTier || 'general',
|
|
148
|
+
sortOrder: values.sortOrder ?? 0,
|
|
141
149
|
};
|
|
142
150
|
setSaving(true);
|
|
143
151
|
try {
|
|
@@ -289,6 +297,42 @@ export default function ModelMetadataPage() {
|
|
|
289
297
|
>
|
|
290
298
|
<Input.TextArea rows={4} placeholder={t('Leave empty to not override')} />
|
|
291
299
|
</Form.Item>
|
|
300
|
+
<Form.Item
|
|
301
|
+
name="supportsVision"
|
|
302
|
+
label={t('Supports vision')}
|
|
303
|
+
valuePropName="checked"
|
|
304
|
+
tooltip={t('Used by virtual-model routing for image/file requests.')}
|
|
305
|
+
>
|
|
306
|
+
<Switch />
|
|
307
|
+
</Form.Item>
|
|
308
|
+
<Form.Item
|
|
309
|
+
name="supportsToolCalling"
|
|
310
|
+
label={t('Supports tool calling')}
|
|
311
|
+
valuePropName="checked"
|
|
312
|
+
tooltip={t('Used by virtual-model routing for requests with tools/tool_choice.')}
|
|
313
|
+
>
|
|
314
|
+
<Switch defaultChecked />
|
|
315
|
+
</Form.Item>
|
|
316
|
+
<Form.Item
|
|
317
|
+
name="reasoningTier"
|
|
318
|
+
label={t('Reasoning tier')}
|
|
319
|
+
tooltip={t('cheap | general | reasoning. Used by virtual-model routing buckets.')}
|
|
320
|
+
>
|
|
321
|
+
<Select
|
|
322
|
+
options={[
|
|
323
|
+
{ value: 'cheap', label: t('Cheap') },
|
|
324
|
+
{ value: 'general', label: t('General') },
|
|
325
|
+
{ value: 'reasoning', label: t('Reasoning') },
|
|
326
|
+
]}
|
|
327
|
+
/>
|
|
328
|
+
</Form.Item>
|
|
329
|
+
<Form.Item
|
|
330
|
+
name="sortOrder"
|
|
331
|
+
label={t('Routing priority')}
|
|
332
|
+
tooltip={t('Ascending — lower is preferred when a bucket is derived from metadata.')}
|
|
333
|
+
>
|
|
334
|
+
<InputNumber precision={0} style={{ width: '100%' }} placeholder="0" />
|
|
335
|
+
</Form.Item>
|
|
292
336
|
<Form.Item name="enabled" label={t('Enabled')} valuePropName="checked">
|
|
293
337
|
<Switch />
|
|
294
338
|
</Form.Item>
|
|
@@ -28,6 +28,7 @@ interface ModelPrice {
|
|
|
28
28
|
model: string;
|
|
29
29
|
currency: string;
|
|
30
30
|
inputPricePerMillionTokens: string;
|
|
31
|
+
cacheInputPricePerMillionTokens: string;
|
|
31
32
|
outputPricePerMillionTokens: string;
|
|
32
33
|
fixedCostPerRequest: string;
|
|
33
34
|
effectiveFrom: string;
|
|
@@ -95,6 +96,7 @@ export default function ModelPricingPage() {
|
|
|
95
96
|
form.setFieldsValue({
|
|
96
97
|
currency: 'USD',
|
|
97
98
|
inputPricePerMillionTokens: '0',
|
|
99
|
+
cacheInputPricePerMillionTokens: '0',
|
|
98
100
|
outputPricePerMillionTokens: '0',
|
|
99
101
|
fixedCostPerRequest: '0',
|
|
100
102
|
effectiveFrom: dayjs(),
|
|
@@ -182,6 +184,12 @@ export default function ModelPricingPage() {
|
|
|
182
184
|
{ title: t('LLM service'), dataIndex: 'llmService', key: 'llmService', width: 180 },
|
|
183
185
|
{ title: t('Model'), dataIndex: 'model', key: 'model', width: 180 },
|
|
184
186
|
{ title: t('Input price / 1M'), dataIndex: 'inputPricePerMillionTokens', key: 'inputPrice', width: 150 },
|
|
187
|
+
{
|
|
188
|
+
title: t('Cache input price / 1M'),
|
|
189
|
+
dataIndex: 'cacheInputPricePerMillionTokens',
|
|
190
|
+
key: 'cacheInputPrice',
|
|
191
|
+
width: 170,
|
|
192
|
+
},
|
|
185
193
|
{ title: t('Output price / 1M'), dataIndex: 'outputPricePerMillionTokens', key: 'outputPrice', width: 150 },
|
|
186
194
|
{ title: t('Fixed request cost'), dataIndex: 'fixedCostPerRequest', key: 'fixedCost', width: 150 },
|
|
187
195
|
{ title: t('Currency'), dataIndex: 'currency', key: 'currency', width: 90 },
|
|
@@ -260,6 +268,13 @@ export default function ModelPricingPage() {
|
|
|
260
268
|
<Form.Item name="inputPricePerMillionTokens" label={t('Input price / 1M')} rules={[{ required: true }]}>
|
|
261
269
|
<InputNumber min={0} stringMode style={{ width: '100%' }} />
|
|
262
270
|
</Form.Item>
|
|
271
|
+
<Form.Item
|
|
272
|
+
name="cacheInputPricePerMillionTokens"
|
|
273
|
+
label={t('Cache input price / 1M')}
|
|
274
|
+
rules={[{ required: true }]}
|
|
275
|
+
>
|
|
276
|
+
<InputNumber min={0} stringMode style={{ width: '100%' }} />
|
|
277
|
+
</Form.Item>
|
|
263
278
|
<Form.Item name="outputPricePerMillionTokens" label={t('Output price / 1M')} rules={[{ required: true }]}>
|
|
264
279
|
<InputNumber min={0} stringMode style={{ width: '100%' }} />
|
|
265
280
|
</Form.Item>
|