plugin-ai-api 1.0.15 → 1.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/client/302.25edd5d75460acbf.js +10 -0
  2. package/dist/client/757.71e30f2a1306562d.js +10 -0
  3. package/dist/client/902.4238b04ac667c30a.js +10 -0
  4. package/dist/client/97.37cda285d7da3a26.js +10 -0
  5. package/dist/client/index.js +1 -1
  6. package/dist/client-v2/302.9b27a263901d54d8.js +10 -0
  7. package/dist/client-v2/757.c377e2f2b054d89d.js +10 -0
  8. package/dist/client-v2/902.d40d7bda106124c8.js +10 -0
  9. package/dist/client-v2/97.fc922c37ced86831.js +10 -0
  10. package/dist/client-v2/index.js +1 -1
  11. package/dist/externalVersion.js +9 -9
  12. package/dist/locale/en-US.json +78 -2
  13. package/dist/locale/vi-VN.json +86 -0
  14. package/dist/locale/zh-CN.json +86 -10
  15. package/dist/server/billing.js +331 -0
  16. package/dist/server/collections/ai-api-config.js +12 -0
  17. package/dist/server/collections/ai-api-model-prices.js +55 -0
  18. package/dist/server/collections/ai-api-usage-records.js +9 -0
  19. package/dist/server/collections/ai-api-user-quota-buckets.js +54 -0
  20. package/dist/server/collections/ai-api-user-quota-policies.js +62 -0
  21. package/dist/server/plugin.js +23 -2
  22. package/dist/server/resource/ai-api-config.js +8 -0
  23. package/dist/server/resource/ai-api-usage-monitor.js +86 -0
  24. package/dist/server/routes/chat-completions.js +12 -2
  25. package/dist/server/routes/completions.js +12 -2
  26. package/dist/server/routes/router.js +14 -1
  27. package/dist/server/usage.js +17 -2
  28. package/dist/server/validation.js +102 -0
  29. package/package.json +1 -1
  30. package/src/client/plugin.tsx +73 -48
  31. package/src/client-v2/locale.ts +1 -0
  32. package/src/client-v2/pages/GeneralPage.tsx +170 -0
  33. package/src/client-v2/pages/ModelPricingPage.tsx +285 -0
  34. package/src/client-v2/pages/UsagePage.tsx +248 -0
  35. package/src/client-v2/pages/UserQuotasPage.tsx +258 -0
  36. package/src/client-v2/pages/api.ts +16 -0
  37. package/src/client-v2/plugin.tsx +21 -3
  38. package/src/locale/en-US.json +78 -2
  39. package/src/locale/vi-VN.json +86 -0
  40. package/src/locale/zh-CN.json +86 -10
  41. package/src/server/__tests__/billing-quota.test.ts +134 -0
  42. package/src/server/__tests__/billing.test.ts +33 -0
  43. package/src/server/__tests__/usage-monitor.test.ts +63 -0
  44. package/src/server/__tests__/usage-route.test.ts +4 -0
  45. package/src/server/billing.ts +387 -0
  46. package/src/server/collections/ai-api-config.ts +63 -51
  47. package/src/server/collections/ai-api-model-prices.ts +25 -0
  48. package/src/server/collections/ai-api-usage-records.ts +9 -0
  49. package/src/server/collections/ai-api-user-quota-buckets.ts +24 -0
  50. package/src/server/collections/ai-api-user-quota-policies.ts +32 -0
  51. package/src/server/plugin.ts +24 -2
  52. package/src/server/resource/ai-api-config.ts +82 -74
  53. package/src/server/resource/ai-api-usage-monitor.ts +74 -0
  54. package/src/server/routes/chat-completions.ts +13 -2
  55. package/src/server/routes/completions.ts +13 -2
  56. package/src/server/routes/router.ts +16 -1
  57. package/src/server/usage.ts +17 -1
  58. package/src/server/validation.ts +62 -0
  59. package/dist/client/950.83390c5f1d5a97fb.js +0 -10
  60. package/dist/client-v2/950.42b30b5cc9e32b8f.js +0 -10
@@ -1,4 +1,5 @@
1
1
  import { Context } from '@nocobase/actions';
2
+ import { finalizeLlmBilling, type LlmBillingState } from './billing';
2
3
 
3
4
  export type Usage = {
4
5
  prompt_tokens: number | null;
@@ -35,6 +36,7 @@ interface AiApiContextState {
35
36
  currentRoles?: string[];
36
37
  currentUser?: { id?: string | number | bigint };
37
38
  oauthPrincipal?: OAuthPrincipal;
39
+ aiApiLlmBilling?: LlmBillingState;
38
40
  }
39
41
 
40
42
  function getAiApiState(ctx: Context): AiApiContextState {
@@ -158,7 +160,10 @@ export async function finishUsageRecord(ctx: Context, id: unknown, startedAt: nu
158
160
  const state = getAiApiState(ctx);
159
161
  const streamResult = state.aiApiStreamResult;
160
162
  const usageResult = state.aiApiUsageResult ?? { source: 'unavailable' as const };
161
- const usage = usageResult.source === 'provider' ? usageResult.usage : undefined;
163
+ const providerUsage = usageResult.source === 'provider' ? usageResult.usage : undefined;
164
+ const succeeded = streamResult ? streamResult.succeeded : status === 'succeeded';
165
+ const billing = await finalizeLlmBilling(ctx, providerUsage, succeeded);
166
+ const usage = billing.usage ?? providerUsage;
162
167
  const gatewayResponseId = usageResult.gatewayResponseId || response.id || streamResult?.id;
163
168
  const values = {
164
169
  status: streamResult ? (streamResult.succeeded ? 'succeeded' : 'failed') : status,
@@ -167,6 +172,17 @@ export async function finishUsageRecord(ctx: Context, id: unknown, startedAt: nu
167
172
  inputTokens: usage?.prompt_tokens ?? null,
168
173
  outputTokens: usage?.completion_tokens ?? null,
169
174
  totalTokens: usage?.total_tokens ?? null,
175
+ resolvedService: state.aiApiLlmBilling?.resolution?.service ?? null,
176
+ resolvedProvider: state.aiApiLlmBilling?.resolution?.provider ?? null,
177
+ resolvedModel: state.aiApiLlmBilling?.resolution?.model ?? null,
178
+ estimatedCost: billing.estimatedCost ?? null,
179
+ currency: billing.currency ?? null,
180
+ costStatus: billing.costStatus ?? null,
181
+ modelPriceId: billing.modelPriceId ?? null,
182
+ quotaPolicyId: billing.quotaPolicyId ?? null,
183
+ inputPricePerMillionTokens: billing.inputPricePerMillionTokens ?? null,
184
+ outputPricePerMillionTokens: billing.outputPricePerMillionTokens ?? null,
185
+ fixedCostPerRequest: billing.fixedCostPerRequest ?? null,
170
186
  providerRequestId: usageResult.providerRequestId ?? null,
171
187
  completedAt: new Date(),
172
188
  durationMs: Date.now() - startedAt,
@@ -0,0 +1,62 @@
1
+ import dayjs from 'dayjs';
2
+ import type { Database, Model } from '@nocobase/database';
3
+
4
+ function requireNonNegativeDecimal(value: unknown, field: string): void {
5
+ const normalized = String(value ?? '').trim();
6
+ if (!/^\d+(?:\.\d+)?$/.test(normalized)) {
7
+ throw new Error(`${field} must be a non-negative decimal.`);
8
+ }
9
+ }
10
+
11
+ function requireNonNegativeIntegerOrNull(value: unknown, field: string): void {
12
+ if (value === null || value === undefined || value === '') return;
13
+ const parsed = Number(value);
14
+ if (!Number.isSafeInteger(parsed) || parsed < 0) throw new Error(`${field} must be a non-negative integer.`);
15
+ }
16
+
17
+ export async function validateModelPrice(db: Database, model: Model): Promise<void> {
18
+ requireNonNegativeDecimal(model.get('inputPricePerMillionTokens'), 'inputPricePerMillionTokens');
19
+ requireNonNegativeDecimal(model.get('outputPricePerMillionTokens'), 'outputPricePerMillionTokens');
20
+ requireNonNegativeDecimal(model.get('fixedCostPerRequest') ?? 0, 'fixedCostPerRequest');
21
+
22
+ const effectiveFrom = new Date(String(model.get('effectiveFrom')));
23
+ const effectiveToValue = model.get('effectiveTo');
24
+ const effectiveTo = effectiveToValue ? new Date(String(effectiveToValue)) : undefined;
25
+ if (Number.isNaN(effectiveFrom.getTime())) throw new Error('effectiveFrom must be a valid date.');
26
+ if (effectiveTo && (Number.isNaN(effectiveTo.getTime()) || effectiveTo <= effectiveFrom)) {
27
+ throw new Error('effectiveTo must be later than effectiveFrom.');
28
+ }
29
+ if (model.get('enabled') === false) return;
30
+
31
+ const overlapFilter: Record<string, unknown> = {
32
+ llmService: model.get('llmService'),
33
+ model: model.get('model'),
34
+ enabled: true,
35
+ effectiveFrom: { $lt: effectiveTo ?? new Date('9999-12-31T23:59:59.999Z') },
36
+ $or: [{ effectiveTo: null }, { effectiveTo: { $gt: effectiveFrom } }],
37
+ };
38
+ if (model.get('id')) overlapFilter.id = { $ne: model.get('id') };
39
+ const overlap = await db.getRepository('aiApiModelPrices').findOne({
40
+ filter: overlapFilter,
41
+ });
42
+ if (overlap) throw new Error('An enabled price already overlaps this effective period.');
43
+ }
44
+
45
+ export function validateQuotaPolicy(model: Model): void {
46
+ if (!['daily', 'monthly'].includes(String(model.get('periodType')))) {
47
+ throw new Error('periodType must be daily or monthly.');
48
+ }
49
+ if (!['allow', 'use_reserved'].includes(String(model.get('missingUsageBehavior')))) {
50
+ throw new Error('missingUsageBehavior must be allow or use_reserved.');
51
+ }
52
+ try {
53
+ dayjs().tz(String(model.get('timezone') || 'UTC'));
54
+ } catch {
55
+ throw new Error('timezone must be a valid IANA timezone.');
56
+ }
57
+ requireNonNegativeIntegerOrNull(model.get('requestLimit'), 'requestLimit');
58
+ requireNonNegativeIntegerOrNull(model.get('totalTokenLimit'), 'totalTokenLimit');
59
+ if (model.get('costLimit') !== null && model.get('costLimit') !== undefined) {
60
+ requireNonNegativeDecimal(model.get('costLimit'), 'costLimit');
61
+ }
62
+ }
@@ -1,10 +0,0 @@
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
- "use strict";(self.webpackChunkplugin_ai_api=self.webpackChunkplugin_ai_api||[]).push([["950"],{693:function(e,t,n){n.r(t),n.d(t,{AiApiConfigPage:function(){return y}});var a=n(155),r=n.n(a),l=n(59),o=n(485);function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,a=Array(t);n<t;n++)a[n]=e[n];return a}function c(e,t,n,a,r,l,o){try{var i=e[l](o),c=i.value}catch(e){n(e);return}i.done?t(c):Promise.resolve(c).then(a,r)}function u(e){return function(){var t=this,n=arguments;return new Promise(function(a,r){var l=e.apply(t,n);function o(e){c(l,a,r,o,i,"next",e)}function i(e){c(l,a,r,o,i,"throw",e)}o(void 0)})}}function s(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n,a,r=null==e?null:"u">typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=r){var l=[],o=!0,i=!1;try{for(r=r.call(e);!(o=(n=r.next()).done)&&(l.push(n.value),!t||l.length!==t);o=!0);}catch(e){i=!0,a=e}finally{try{o||null==r.return||r.return()}finally{if(i)throw a}}return l}}(e,t)||function(e,t){if(e){if("string"==typeof e)return i(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if("Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n)return Array.from(n);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return i(e,t)}}(e,t)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function m(e,t){var n,a,r,l={label:0,sent:function(){if(1&r[0])throw r[1];return r[1]},trys:[],ops:[]},o=Object.create(("function"==typeof Iterator?Iterator:Object).prototype),i=Object.defineProperty;return i(o,"next",{value:c(0)}),i(o,"throw",{value:c(1)}),i(o,"return",{value:c(2)}),"function"==typeof Symbol&&i(o,Symbol.iterator,{value:function(){return this}}),o;function c(i){return function(c){var u=[i,c];if(n)throw TypeError("Generator is already executing.");for(;o&&(o=0,u[0]&&(l=0)),l;)try{if(n=1,a&&(r=2&u[0]?a.return:u[0]?a.throw||((r=a.return)&&r.call(a),0):a.next)&&!(r=r.call(a,u[1])).done)return r;switch(a=0,r&&(u=[2&u[0],r.value]),u[0]){case 0:case 1:r=u;break;case 4:return l.label++,{value:u[1],done:!1};case 5:l.label++,a=u[1],u=[0];continue;case 7:u=l.ops.pop(),l.trys.pop();continue;default:if(!(r=(r=l.trys).length>0&&r[r.length-1])&&(6===u[0]||2===u[0])){l=0;continue}if(3===u[0]&&(!r||u[1]>r[0]&&u[1]<r[3])){l.label=u[1];break}if(6===u[0]&&l.label<r[1]){l.label=r[1],r=u;break}if(r&&l.label<r[2]){l.label=r[2],l.ops.push(u);break}r[2]&&l.ops.pop(),l.trys.pop();continue}u=t.call(e,l)}catch(e){u=[6,e],a=0}finally{n=r=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}}}var p=l.Typography.Title,d=l.Typography.Text,f=l.Typography.Paragraph;function y(){var e=(0,o.useApp)().apiClient,t=s(l.Form.useForm(),1)[0],n=s((0,a.useState)(!0),2),i=n[0],c=n[1],y=s((0,a.useState)(!1),2),v=y[0],h=y[1],b=s((0,a.useState)([]),2),g=b[0],E=b[1],A=s((0,a.useState)([]),2),S=A[0],w=A[1],I=s((0,a.useState)(""),2),L=I[0],k=I[1];(0,a.useEffect)(function(){C(),k("".concat(window.location.origin,"/api/ai-llm/v1"))},[]);var C=function(){return u(function(){var n,a,r,l,o,i,u;return m(this,function(s){switch(s.label){case 0:c(!0),s.label=1;case 1:return s.trys.push([1,5,6,7]),[4,e.request({url:"aiApiConfig:get"})];case 2:return(o=null==(l=s.sent())||null==(n=l.data)?void 0:n.data)&&t.setFieldsValue({mode:o.mode||"llm",defaultAiEmployee:o.defaultAiEmployee||void 0,defaultLlmService:o.defaultLlmService||void 0,enabledLlmServices:o.enabledLlmServices||[],rateLimitPerMinute:o.rateLimitPerMinute||60}),[4,e.request({url:"aiEmployees:list",params:{paginate:!1}})];case 3:return E(((null==(i=s.sent())||null==(a=i.data)?void 0:a.data)||[]).map(function(e){return{username:e.username,nickname:e.nickname||e.username}})),[4,e.request({url:"ai:listLLMServices"})];case 4:return w(((null==(u=s.sent())||null==(r=u.data)?void 0:r.data)||[]).map(function(e){return{name:e.name,title:e.title||e.name,provider:e.provider,enabled:!1!==e.enabled}})),[3,7];case 5:return console.error("Failed to load config:",s.sent()),[3,7];case 6:return c(!1),[7];case 7:return[2]}})})()};return i?r().createElement("div",{style:{display:"flex",justifyContent:"center",padding:60}},r().createElement(l.Spin,{size:"large"})):r().createElement("div",{style:{maxWidth:800,margin:"0 auto",padding:"24px 0"}},r().createElement(p,{level:3},"AI API Gateway Configuration"),r().createElement(f,{type:"secondary"},"Configure the OpenAI-compatible API endpoint. External applications can connect using the base URL and a NocoBase API key."),r().createElement(l.Card,{style:{marginBottom:24}},r().createElement(l.Alert,{type:"info",showIcon:!0,message:"API Endpoint",description:r().createElement(l.Space,{direction:"vertical",size:4},r().createElement(d,null,"Base URL:"," ",r().createElement(d,{code:!0,copyable:!0},L)),r().createElement(d,{type:"secondary"},"Use this as the base URL in any OpenAI-compatible client (Cursor, Continue.dev, n8n, etc.)")),style:{marginBottom:16}}),r().createElement(l.Alert,{type:"warning",showIcon:!0,message:"Authentication",description:r().createElement(d,null,"Clients must include a NocoBase API key as a Bearer token:",r().createElement("br",null),r().createElement(d,{code:!0},"Authorization: Bearer ","<your-nocobase-api-key>"),r().createElement("br",null),r().createElement(d,{type:"secondary"},"API keys can be created in Settings → API keys."))})),r().createElement(l.Card,{title:"Configuration"},r().createElement(l.Form,{form:t,layout:"vertical",onFinish:function(){return u(function(){var n;return m(this,function(a){switch(a.label){case 0:h(!0),a.label=1;case 1:return a.trys.push([1,3,4,5]),n=t.getFieldsValue(),[4,e.request({url:"aiApiConfig:save",method:"post",data:n})];case 2:return a.sent(),l.message.success("Configuration saved"),[3,5];case 3:return a.sent(),l.message.error("Failed to save configuration"),[3,5];case 4:return h(!1),[7];case 5:return[2]}})})()},initialValues:{mode:"llm"}},r().createElement(l.Form.Item,{name:"mode",label:"API Mode",tooltip:"LLM Proxy: Direct access to the LLM model. Agent: Full AI Employee with tools, knowledge base, and agent capabilities."},r().createElement(l.Radio.Group,null,r().createElement(l.Radio.Button,{value:"llm"},"LLM Proxy"),r().createElement(l.Radio.Button,{value:"agent"},"AI Employee Agent"))),r().createElement(l.Form.Item,{name:"defaultAiEmployee",label:"Default AI Employee",tooltip:"The AI Employee whose system prompt will be injected into chat completions (when client doesn't provide a system message)"},r().createElement(l.Select,{allowClear:!0,placeholder:"Select an AI Employee (optional)",options:g.map(function(e){return{label:"".concat(e.nickname," (").concat(e.username,")"),value:e.username}})})),r().createElement(l.Form.Item,{name:"defaultLlmService",label:"Default LLM Service",tooltip:"The LLM service used when clients send only a model name (e.g. 'gpt-4o') without a service prefix. This is the main service that powers the API.",rules:[{required:!0,message:"Please select a default LLM service"}]},r().createElement(l.Select,{allowClear:!0,placeholder:"Select an LLM Service",options:S.map(function(e){return{label:"".concat(e.title," (").concat(e.provider,")"),value:e.name}})})),r().createElement(l.Form.Item,{name:"enabledLlmServices",label:"Enabled LLM Services",tooltip:"Only these services will be exposed via the API. Leave empty to expose all enabled services."},r().createElement(l.Select,{mode:"multiple",allowClear:!0,placeholder:"All enabled services (default)",options:S.map(function(e){return{label:"".concat(e.title," (").concat(e.provider,")"),value:e.name}})})),r().createElement(l.Form.Item,{name:"rateLimitPerMinute",label:"Rate Limit (requests/minute)",tooltip:"Maximum API requests per user per minute. Set 0 for unlimited."},r().createElement(l.InputNumber,{min:0,max:1e4,style:{width:200}})),r().createElement(l.Form.Item,null,r().createElement(l.Button,{type:"primary",htmlType:"submit",loading:v},"Save Configuration")))),r().createElement(l.Card,{title:"Quick Start",style:{marginTop:24}},r().createElement(p,{level:5},"cURL Example"),r().createElement(f,null,r().createElement("pre",{style:{background:"#1a1a2e",color:"#e0e0e0",padding:16,borderRadius:8,fontSize:13,overflow:"auto"}},"curl ".concat(L,'/chat/completions \\\n -H "Authorization: Bearer <your-api-key>" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "model": "<service-name>/<model-id>",\n "messages": [\n {"role": "user", "content": "Hello!"}\n ]\n }\''))),r().createElement(p,{level:5},"Python (OpenAI SDK)"),r().createElement(f,null,r().createElement("pre",{style:{background:"#1a1a2e",color:"#e0e0e0",padding:16,borderRadius:8,fontSize:13,overflow:"auto"}},'from openai import OpenAI\n\nclient = OpenAI(\n base_url="'.concat(L,'",\n api_key="<your-nocobase-api-key>"\n)\n\nresponse = client.chat.completions.create(\n model="<service-name>/<model-id>",\n messages=[{"role": "user", "content": "Hello!"}]\n)'))),r().createElement(p,{level:5},"List Available Models"),r().createElement(f,null,r().createElement("pre",{style:{background:"#1a1a2e",color:"#e0e0e0",padding:16,borderRadius:8,fontSize:13,overflow:"auto"}},"curl ".concat(L,'/models \\\n -H "Authorization: Bearer <your-api-key>"')))))}t.default=y}}]);
@@ -1,10 +0,0 @@
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
- "use strict";(self.webpackChunkplugin_ai_api_client_v2=self.webpackChunkplugin_ai_api_client_v2||[]).push([["950"],{693:function(e,t,n){n.r(t),n.d(t,{AiApiConfigPage:function(){return y}});var a=n(155),r=n.n(a),l=n(59),o=n(485);function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,a=Array(t);n<t;n++)a[n]=e[n];return a}function c(e,t,n,a,r,l,o){try{var i=e[l](o),c=i.value}catch(e){n(e);return}i.done?t(c):Promise.resolve(c).then(a,r)}function u(e){return function(){var t=this,n=arguments;return new Promise(function(a,r){var l=e.apply(t,n);function o(e){c(l,a,r,o,i,"next",e)}function i(e){c(l,a,r,o,i,"throw",e)}o(void 0)})}}function s(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n,a,r=null==e?null:"u">typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=r){var l=[],o=!0,i=!1;try{for(r=r.call(e);!(o=(n=r.next()).done)&&(l.push(n.value),!t||l.length!==t);o=!0);}catch(e){i=!0,a=e}finally{try{o||null==r.return||r.return()}finally{if(i)throw a}}return l}}(e,t)||function(e,t){if(e){if("string"==typeof e)return i(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if("Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n)return Array.from(n);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return i(e,t)}}(e,t)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function m(e,t){var n,a,r,l={label:0,sent:function(){if(1&r[0])throw r[1];return r[1]},trys:[],ops:[]},o=Object.create(("function"==typeof Iterator?Iterator:Object).prototype),i=Object.defineProperty;return i(o,"next",{value:c(0)}),i(o,"throw",{value:c(1)}),i(o,"return",{value:c(2)}),"function"==typeof Symbol&&i(o,Symbol.iterator,{value:function(){return this}}),o;function c(i){return function(c){var u=[i,c];if(n)throw TypeError("Generator is already executing.");for(;o&&(o=0,u[0]&&(l=0)),l;)try{if(n=1,a&&(r=2&u[0]?a.return:u[0]?a.throw||((r=a.return)&&r.call(a),0):a.next)&&!(r=r.call(a,u[1])).done)return r;switch(a=0,r&&(u=[2&u[0],r.value]),u[0]){case 0:case 1:r=u;break;case 4:return l.label++,{value:u[1],done:!1};case 5:l.label++,a=u[1],u=[0];continue;case 7:u=l.ops.pop(),l.trys.pop();continue;default:if(!(r=(r=l.trys).length>0&&r[r.length-1])&&(6===u[0]||2===u[0])){l=0;continue}if(3===u[0]&&(!r||u[1]>r[0]&&u[1]<r[3])){l.label=u[1];break}if(6===u[0]&&l.label<r[1]){l.label=r[1],r=u;break}if(r&&l.label<r[2]){l.label=r[2],l.ops.push(u);break}r[2]&&l.ops.pop(),l.trys.pop();continue}u=t.call(e,l)}catch(e){u=[6,e],a=0}finally{n=r=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}}}var p=l.Typography.Title,d=l.Typography.Text,f=l.Typography.Paragraph;function y(){var e=(0,o.useApp)().apiClient,t=s(l.Form.useForm(),1)[0],n=s((0,a.useState)(!0),2),i=n[0],c=n[1],y=s((0,a.useState)(!1),2),v=y[0],h=y[1],b=s((0,a.useState)([]),2),g=b[0],E=b[1],A=s((0,a.useState)([]),2),S=A[0],w=A[1],I=s((0,a.useState)(""),2),L=I[0],k=I[1];(0,a.useEffect)(function(){C(),k("".concat(window.location.origin,"/api/ai-llm/v1"))},[]);var C=function(){return u(function(){var n,a,r,l,o,i,u;return m(this,function(s){switch(s.label){case 0:c(!0),s.label=1;case 1:return s.trys.push([1,5,6,7]),[4,e.request({url:"aiApiConfig:get"})];case 2:return(o=null==(l=s.sent())||null==(n=l.data)?void 0:n.data)&&t.setFieldsValue({mode:o.mode||"llm",defaultAiEmployee:o.defaultAiEmployee||void 0,defaultLlmService:o.defaultLlmService||void 0,enabledLlmServices:o.enabledLlmServices||[],rateLimitPerMinute:o.rateLimitPerMinute||60}),[4,e.request({url:"aiEmployees:list",params:{paginate:!1}})];case 3:return E(((null==(i=s.sent())||null==(a=i.data)?void 0:a.data)||[]).map(function(e){return{username:e.username,nickname:e.nickname||e.username}})),[4,e.request({url:"ai:listLLMServices"})];case 4:return w(((null==(u=s.sent())||null==(r=u.data)?void 0:r.data)||[]).map(function(e){return{name:e.name,title:e.title||e.name,provider:e.provider,enabled:!1!==e.enabled}})),[3,7];case 5:return console.error("Failed to load config:",s.sent()),[3,7];case 6:return c(!1),[7];case 7:return[2]}})})()};return i?r().createElement("div",{style:{display:"flex",justifyContent:"center",padding:60}},r().createElement(l.Spin,{size:"large"})):r().createElement("div",{style:{maxWidth:800,margin:"0 auto",padding:"24px 0"}},r().createElement(p,{level:3},"AI API Gateway Configuration"),r().createElement(f,{type:"secondary"},"Configure the OpenAI-compatible API endpoint. External applications can connect using the base URL and a NocoBase API key."),r().createElement(l.Card,{style:{marginBottom:24}},r().createElement(l.Alert,{type:"info",showIcon:!0,message:"API Endpoint",description:r().createElement(l.Space,{direction:"vertical",size:4},r().createElement(d,null,"Base URL:"," ",r().createElement(d,{code:!0,copyable:!0},L)),r().createElement(d,{type:"secondary"},"Use this as the base URL in any OpenAI-compatible client (Cursor, Continue.dev, n8n, etc.)")),style:{marginBottom:16}}),r().createElement(l.Alert,{type:"warning",showIcon:!0,message:"Authentication",description:r().createElement(d,null,"Clients must include a NocoBase API key as a Bearer token:",r().createElement("br",null),r().createElement(d,{code:!0},"Authorization: Bearer ","<your-nocobase-api-key>"),r().createElement("br",null),r().createElement(d,{type:"secondary"},"API keys can be created in Settings → API keys."))})),r().createElement(l.Card,{title:"Configuration"},r().createElement(l.Form,{form:t,layout:"vertical",onFinish:function(){return u(function(){var n;return m(this,function(a){switch(a.label){case 0:h(!0),a.label=1;case 1:return a.trys.push([1,3,4,5]),n=t.getFieldsValue(),[4,e.request({url:"aiApiConfig:save",method:"post",data:n})];case 2:return a.sent(),l.message.success("Configuration saved"),[3,5];case 3:return a.sent(),l.message.error("Failed to save configuration"),[3,5];case 4:return h(!1),[7];case 5:return[2]}})})()},initialValues:{mode:"llm"}},r().createElement(l.Form.Item,{name:"mode",label:"API Mode",tooltip:"LLM Proxy: Direct access to the LLM model. Agent: Full AI Employee with tools, knowledge base, and agent capabilities."},r().createElement(l.Radio.Group,null,r().createElement(l.Radio.Button,{value:"llm"},"LLM Proxy"),r().createElement(l.Radio.Button,{value:"agent"},"AI Employee Agent"))),r().createElement(l.Form.Item,{name:"defaultAiEmployee",label:"Default AI Employee",tooltip:"The AI Employee whose system prompt will be injected into chat completions (when client doesn't provide a system message)"},r().createElement(l.Select,{allowClear:!0,placeholder:"Select an AI Employee (optional)",options:g.map(function(e){return{label:"".concat(e.nickname," (").concat(e.username,")"),value:e.username}})})),r().createElement(l.Form.Item,{name:"defaultLlmService",label:"Default LLM Service",tooltip:"The LLM service used when clients send only a model name (e.g. 'gpt-4o') without a service prefix. This is the main service that powers the API.",rules:[{required:!0,message:"Please select a default LLM service"}]},r().createElement(l.Select,{allowClear:!0,placeholder:"Select an LLM Service",options:S.map(function(e){return{label:"".concat(e.title," (").concat(e.provider,")"),value:e.name}})})),r().createElement(l.Form.Item,{name:"enabledLlmServices",label:"Enabled LLM Services",tooltip:"Only these services will be exposed via the API. Leave empty to expose all enabled services."},r().createElement(l.Select,{mode:"multiple",allowClear:!0,placeholder:"All enabled services (default)",options:S.map(function(e){return{label:"".concat(e.title," (").concat(e.provider,")"),value:e.name}})})),r().createElement(l.Form.Item,{name:"rateLimitPerMinute",label:"Rate Limit (requests/minute)",tooltip:"Maximum API requests per user per minute. Set 0 for unlimited."},r().createElement(l.InputNumber,{min:0,max:1e4,style:{width:200}})),r().createElement(l.Form.Item,null,r().createElement(l.Button,{type:"primary",htmlType:"submit",loading:v},"Save Configuration")))),r().createElement(l.Card,{title:"Quick Start",style:{marginTop:24}},r().createElement(p,{level:5},"cURL Example"),r().createElement(f,null,r().createElement("pre",{style:{background:"#1a1a2e",color:"#e0e0e0",padding:16,borderRadius:8,fontSize:13,overflow:"auto"}},"curl ".concat(L,'/chat/completions \\\n -H "Authorization: Bearer <your-api-key>" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "model": "<service-name>/<model-id>",\n "messages": [\n {"role": "user", "content": "Hello!"}\n ]\n }\''))),r().createElement(p,{level:5},"Python (OpenAI SDK)"),r().createElement(f,null,r().createElement("pre",{style:{background:"#1a1a2e",color:"#e0e0e0",padding:16,borderRadius:8,fontSize:13,overflow:"auto"}},'from openai import OpenAI\n\nclient = OpenAI(\n base_url="'.concat(L,'",\n api_key="<your-nocobase-api-key>"\n)\n\nresponse = client.chat.completions.create(\n model="<service-name>/<model-id>",\n messages=[{"role": "user", "content": "Hello!"}]\n)'))),r().createElement(p,{level:5},"List Available Models"),r().createElement(f,null,r().createElement("pre",{style:{background:"#1a1a2e",color:"#e0e0e0",padding:16,borderRadius:8,fontSize:13,overflow:"auto"}},"curl ".concat(L,'/models \\\n -H "Authorization: Bearer <your-api-key>"')))))}t.default=y}}]);