plugin-ai-api 1.0.20 → 1.0.23

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 (91) hide show
  1. package/dist/client/123.e6fe04c856ce6417.js +10 -0
  2. package/dist/client/286.01c0e3c5fff3cccb.js +10 -0
  3. package/dist/client/302.fc3a3491b4ec2dfd.js +10 -0
  4. package/dist/client/562.17a0a299d2e5152c.js +10 -0
  5. package/dist/client/{757.71e30f2a1306562d.js → 757.a01403fb7a1bea01.js} +1 -1
  6. package/dist/client/{902.4238b04ac667c30a.js → 902.92e1daaf1ab16ebf.js} +1 -1
  7. package/dist/client/{97.37cda285d7da3a26.js → 97.72979a11a067a7c9.js} +1 -1
  8. package/dist/client/index.js +1 -1
  9. package/dist/client-v2/123.05f1f649923f93eb.js +10 -0
  10. package/dist/client-v2/302.d27fe4ea9b0b3bf5.js +10 -0
  11. package/dist/client-v2/562.fb2948ee6402de95.js +10 -0
  12. package/dist/client-v2/{757.c377e2f2b054d89d.js → 757.a117ce1cf7119cea.js} +1 -1
  13. package/dist/client-v2/{902.d40d7bda106124c8.js → 902.9054d990ddc223ac.js} +1 -1
  14. package/dist/client-v2/952.94100128b7757f56.js +10 -0
  15. package/dist/client-v2/{97.fc922c37ced86831.js → 97.29c663318eebbd57.js} +1 -1
  16. package/dist/client-v2/index.js +1 -1
  17. package/dist/constants.js +39 -0
  18. package/dist/externalVersion.js +9 -10
  19. package/dist/locale/en-US.json +39 -9
  20. package/dist/locale/vi-VN.json +31 -1
  21. package/dist/locale/zh-CN.json +31 -1
  22. package/dist/server/collections/ai-api-config.js +6 -0
  23. package/dist/server/collections/ai-api-model-metadata.js +83 -0
  24. package/dist/server/collections/ai-api-user-permissions.js +67 -0
  25. package/dist/server/plugin.js +45 -1
  26. package/dist/server/resource/ai-api-config.js +17 -0
  27. package/dist/server/resource/ai-api-user-permissions.js +75 -0
  28. package/dist/server/routes/agent-completions.js +67 -51
  29. package/dist/server/routes/auth.js +11 -1
  30. package/dist/server/routes/chat-completions.js +174 -20
  31. package/dist/server/routes/completions.js +41 -21
  32. package/dist/server/routes/embeddings.js +6 -14
  33. package/dist/server/routes/models.js +102 -20
  34. package/dist/server/routes/router.js +94 -22
  35. package/dist/server/usage.js +2 -0
  36. package/dist/server/utils/app-observability.js +110 -0
  37. package/dist/server/utils/openai-format.js +17 -3
  38. package/dist/server/utils/streaming.js +15 -1
  39. package/dist/server/utils/user-permissions.js +160 -0
  40. package/dist/server/validation.js +18 -0
  41. package/dist/swagger.js +36 -4
  42. package/package.json +2 -2
  43. package/src/client/__tests__/settings-registration.test.tsx +69 -0
  44. package/src/client/components/AiApiRolePermissions.tsx +11 -169
  45. package/src/client/locale.ts +11 -21
  46. package/src/client/plugin.tsx +28 -8
  47. package/src/client-v2/__tests__/settings-registration.test.tsx +87 -0
  48. package/src/client-v2/components/AiApiRolePermissions.tsx +173 -0
  49. package/src/client-v2/locale.ts +21 -1
  50. package/src/client-v2/pages/GeneralPage.tsx +13 -0
  51. package/src/client-v2/pages/ModelMetadataPage.tsx +280 -0
  52. package/src/client-v2/pages/RolePermissionsTab.tsx +14 -0
  53. package/src/client-v2/pages/UserPermissionsPage.tsx +322 -0
  54. package/src/client-v2/plugin.tsx +50 -1
  55. package/src/constants.ts +28 -0
  56. package/src/locale/en-US.json +39 -9
  57. package/src/locale/vi-VN.json +31 -1
  58. package/src/locale/zh-CN.json +31 -1
  59. package/src/server/__tests__/app-observability.test.ts +98 -0
  60. package/src/server/__tests__/models.test.ts +116 -0
  61. package/src/server/__tests__/openai-format.test.ts +52 -1
  62. package/src/server/__tests__/permission-sync.test.ts +109 -0
  63. package/src/server/__tests__/request-body.test.ts +310 -0
  64. package/src/server/__tests__/streaming-observability.test.ts +51 -0
  65. package/src/server/__tests__/usage-route.test.ts +213 -0
  66. package/src/server/__tests__/user-permissions-resource.test.ts +66 -0
  67. package/src/server/__tests__/user-permissions.test.ts +284 -0
  68. package/src/server/collections/ai-api-config.ts +6 -0
  69. package/src/server/collections/ai-api-model-metadata.ts +72 -0
  70. package/src/server/collections/ai-api-user-permissions.ts +46 -0
  71. package/src/server/plugin.ts +65 -4
  72. package/src/server/resource/ai-api-config.ts +23 -0
  73. package/src/server/resource/ai-api-user-permissions.ts +76 -0
  74. package/src/server/routes/agent-completions.ts +84 -62
  75. package/src/server/routes/auth.ts +14 -1
  76. package/src/server/routes/chat-completions.ts +294 -20
  77. package/src/server/routes/completions.ts +54 -20
  78. package/src/server/routes/embeddings.ts +10 -15
  79. package/src/server/routes/models.ts +318 -195
  80. package/src/server/routes/router.ts +136 -26
  81. package/src/server/usage.ts +2 -0
  82. package/src/server/utils/app-observability.ts +105 -0
  83. package/src/server/utils/openai-format.ts +26 -0
  84. package/src/server/utils/streaming.ts +13 -1
  85. package/src/server/utils/user-permissions.ts +218 -0
  86. package/src/server/validation.ts +27 -0
  87. package/src/swagger.ts +47 -4
  88. package/dist/client/302.25edd5d75460acbf.js +0 -10
  89. package/dist/client/778.5c452944cb747975.js +0 -10
  90. package/dist/client-v2/302.9b27a263901d54d8.js +0 -10
  91. package/src/client/AiApiConfigPage.tsx +0 -309
package/src/swagger.ts CHANGED
@@ -57,7 +57,10 @@ export default {
57
57
  tags: ['ai-llm'],
58
58
  summary: 'List available models',
59
59
  description:
60
- 'Returns all LLM models available across registered services. Model IDs are formatted as `serviceName/modelId`.',
60
+ 'Returns the LLM models available to the authenticated caller across registered services. Model IDs are formatted as `serviceName/modelId`.\n\n' +
61
+ "The catalog is user-scoped: it starts from `enabledLlmServices` in the AI API configuration, then narrows to the caller's " +
62
+ '`aiApiUserPermissions` record when one exists. A user grant can only narrow the global whitelist, never widen it, so two ' +
63
+ 'users may receive different lists from the same request.',
61
64
  security: [{ BearerAuth: [] }],
62
65
  responses: {
63
66
  200: {
@@ -84,6 +87,8 @@ export default {
84
87
  get: {
85
88
  tags: ['ai-llm'],
86
89
  summary: 'Get model details',
90
+ description:
91
+ 'User-scoped in the same way as `GET /v1/models`: a model the caller is not granted is reported as not found rather than disclosed.',
87
92
  security: [{ BearerAuth: [] }],
88
93
  parameters: [
89
94
  {
@@ -103,7 +108,7 @@ export default {
103
108
  },
104
109
  },
105
110
  },
106
- 404: { description: 'Model not found' },
111
+ 404: { description: 'Model not found, or not available to this user' },
107
112
  },
108
113
  },
109
114
  },
@@ -252,12 +257,22 @@ export default {
252
257
  enabledLlmServices: {
253
258
  type: 'array',
254
259
  items: { type: 'string' },
255
- description: 'List of enabled LLM service names',
260
+ description:
261
+ 'List of enabled LLM service names. This is the outer bound for every caller; per-user `aiApiUserPermissions` records can only narrow it further.',
256
262
  },
257
263
  rateLimitPerMinute: {
258
264
  type: 'integer',
259
265
  description: 'Max requests per minute per user (0 = unlimited)',
260
266
  },
267
+ maxRequestBodyMb: {
268
+ type: 'integer',
269
+ minimum: 1,
270
+ maximum: 100,
271
+ default: 10,
272
+ description:
273
+ 'Max request body size in megabytes. Requests above this return 413. ' +
274
+ 'The gateway buffers each body in memory, so values above 100 are rejected.',
275
+ },
261
276
  },
262
277
  },
263
278
  ModelObject: {
@@ -269,11 +284,39 @@ export default {
269
284
  owned_by: { type: 'string' },
270
285
  },
271
286
  },
287
+ ContentBlock: {
288
+ type: 'object',
289
+ description:
290
+ 'A multimodal content block. Only text and image_url blocks are forwarded to the provider; ' +
291
+ 'any other type is rejected with 400 unsupported_content_block.',
292
+ properties: {
293
+ type: { type: 'string', enum: ['text', 'image_url'] },
294
+ text: { type: 'string' },
295
+ image_url: {
296
+ type: 'object',
297
+ properties: {
298
+ url: {
299
+ type: 'string',
300
+ description: 'An https URL or a base64 data URL, e.g. data:image/png;base64,iVBORw0KGgo...',
301
+ example: 'data:image/png;base64,iVBORw0KGgo...',
302
+ },
303
+ detail: { type: 'string', enum: ['auto', 'low', 'high'] },
304
+ },
305
+ required: ['url'],
306
+ },
307
+ },
308
+ required: ['type'],
309
+ },
272
310
  ChatMessage: {
273
311
  type: 'object',
274
312
  properties: {
275
313
  role: { type: 'string', enum: ['system', 'user', 'assistant', 'tool'] },
276
- content: { type: 'string' },
314
+ content: {
315
+ description:
316
+ 'Plain text, or an array of content blocks for multimodal requests. ' +
317
+ 'Inline base64 images inflate the payload by about 33%; see "Max request body size" in the gateway settings.',
318
+ oneOf: [{ type: 'string' }, { type: 'array', items: { $ref: '#/components/schemas/ContentBlock' } }],
319
+ },
277
320
  name: { type: 'string' },
278
321
  tool_call_id: { type: 'string' },
279
322
  tool_calls: { type: 'array', items: { $ref: '#/components/schemas/ToolCall' } },
@@ -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([["302"],{581:function(e,t,r){r.r(t),r.d(t,{default:function(){return d}});var n=r(155),a=r.n(n),l=r(59),o=r(694),i=r(235),u=r(630);function c(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=Array(t);r<t;r++)n[r]=e[r];return n}function s(e,t,r,n,a,l,o){try{var i=e[l](o),u=i.value}catch(e){r(e);return}i.done?t(u):Promise.resolve(u).then(n,a)}function m(e){return function(){var t=this,r=arguments;return new Promise(function(n,a){var l=e.apply(t,r);function o(e){s(l,n,a,o,i,"next",e)}function i(e){s(l,n,a,o,i,"throw",e)}o(void 0)})}}function p(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var r,n,a=null==e?null:"u">typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=a){var l=[],o=!0,i=!1;try{for(a=a.call(e);!(o=(r=a.next()).done)&&(l.push(r.value),!t||l.length!==t);o=!0);}catch(e){i=!0,n=e}finally{try{o||null==a.return||a.return()}finally{if(i)throw n}}return l}}(e,t)||function(e,t){if(e){if("string"==typeof e)return c(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);if("Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r)return Array.from(r);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return c(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 f(e,t){var r,n,a,l={label:0,sent:function(){if(1&a[0])throw a[1];return a[1]},trys:[],ops:[]},o=Object.create(("function"==typeof Iterator?Iterator:Object).prototype),i=Object.defineProperty;return i(o,"next",{value:u(0)}),i(o,"throw",{value:u(1)}),i(o,"return",{value:u(2)}),"function"==typeof Symbol&&i(o,Symbol.iterator,{value:function(){return this}}),o;function u(i){return function(u){var c=[i,u];if(r)throw TypeError("Generator is already executing.");for(;o&&(o=0,c[0]&&(l=0)),l;)try{if(r=1,n&&(a=2&c[0]?n.return:c[0]?n.throw||((a=n.return)&&a.call(n),0):n.next)&&!(a=a.call(n,c[1])).done)return a;switch(n=0,a&&(c=[2&c[0],a.value]),c[0]){case 0:case 1:a=c;break;case 4:return l.label++,{value:c[1],done:!1};case 5:l.label++,n=c[1],c=[0];continue;case 7:c=l.ops.pop(),l.trys.pop();continue;default:if(!(a=(a=l.trys).length>0&&a[a.length-1])&&(6===c[0]||2===c[0])){l=0;continue}if(3===c[0]&&(!a||c[1]>a[0]&&c[1]<a[3])){l.label=c[1];break}if(6===c[0]&&l.label<a[1]){l.label=a[1],a=c;break}if(a&&l.label<a[2]){l.label=a[2],l.ops.push(c);break}a[2]&&l.ops.pop(),l.trys.pop();continue}c=t.call(e,l)}catch(e){c=[6,e],n=0}finally{r=a=0}if(5&c[0])throw c[1];return{value:c[0]?c[1]:void 0,done:!0}}}}var y={mode:"llm",enabledLlmServices:[],rateLimitPerMinute:60,quotaEnabled:!1,defaultReservationOutputTokens:4096};function d(){var e=(0,o.useFlowContext)(),t=(0,i.k)(),r=p(l.Form.useForm(),1)[0],c=l.Form.useWatch("mode",r),s=p((0,n.useState)(!0),2),d=s[0],b=s[1],h=p((0,n.useState)(!1),2),v=h[0],g=h[1],E=p((0,n.useState)([]),2),S=E[0],w=E[1],k=p((0,n.useState)([]),2),I=k[0],P=k[1],A=p((0,n.useState)(),2),F=A[0],C=A[1],O=(0,n.useCallback)(function(){return m(function(){var t,n,a,l,o;return f(this,function(i){switch(i.label){case 0:b(!0),C(void 0),i.label=1;case 1:return i.trys.push([1,3,4,5]),[4,Promise.all([e.api.request({url:"aiApiConfig:get",method:"get"}),e.api.request({url:"ai:listLLMServices",method:"get"}),e.api.request({url:"aiEmployees:list",method:"get",params:{paginate:!1}})])];case 2:return n=(t=p.apply(void 0,[i.sent(),3]))[0],a=t[1],l=t[2],r.setFieldsValue(function(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{},n=Object.keys(r);"function"==typeof Object.getOwnPropertySymbols&&(n=n.concat(Object.getOwnPropertySymbols(r).filter(function(e){return Object.getOwnPropertyDescriptor(r,e).enumerable}))),n.forEach(function(t){var n;n=r[t],t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n})}return e}({},y,(0,u.m)(n,{}))),w((0,u.m)(a,[])),P((0,u.m)(l,[])),[3,5];case 3:return o=i.sent(),C((0,u.g)(o)),[3,5];case 4:return b(!1),[7];case 5:return[2]}})})()},[e.api,r]);(0,n.useEffect)(function(){O()},[O]);var L=S.map(function(e){return{label:e.title||e.name,value:e.name}}),T=I.map(function(e){return{label:e.nickname?"".concat(e.nickname," (").concat(e.username,")"):e.username,value:e.username}}),j="".concat(window.location.origin,"/api/ai-llm/v1");return a().createElement(l.Card,{title:t("Configuration"),loading:d},F?a().createElement(l.Alert,{type:"error",showIcon:!0,message:F,style:{marginBottom:16}}):null,a().createElement(l.Form,{form:r,layout:"vertical",style:{maxWidth:720},initialValues:y},a().createElement(l.Form.Item,{name:"mode",label:t("API mode"),rules:[{required:!0}]},a().createElement(l.Select,{options:[{label:t("Direct LLM"),value:"llm"},{label:t("AI Employee agent"),value:"agent"}]})),a().createElement(l.Form.Item,{name:"defaultLlmService",label:t("Default LLM service")},a().createElement(l.Select,{allowClear:!0,showSearch:!0,optionFilterProp:"label",options:L})),a().createElement(l.Form.Item,{name:"enabledLlmServices",label:t("Enabled LLM Services")},a().createElement(l.Select,{mode:"multiple",allowClear:!0,showSearch:!0,optionFilterProp:"label",options:L})),"agent"===c?a().createElement(l.Form.Item,{name:"defaultAiEmployee",label:t("Default AI Employee")},a().createElement(l.Select,{allowClear:!0,showSearch:!0,optionFilterProp:"label",placeholder:t("Select an AI Employee"),options:T})):null,a().createElement(l.Form.Item,{name:"rateLimitPerMinute",label:t("Rate Limit"),rules:[{required:!0}]},a().createElement(l.InputNumber,{min:1,style:{width:"100%"}})),a().createElement(l.Form.Item,{name:"quotaEnabled",label:t("Enable user quotas"),valuePropName:"checked"},a().createElement(l.Switch,null)),a().createElement(l.Form.Item,{name:"defaultReservationOutputTokens",label:t("Default reserved output tokens"),rules:[{required:!0}]},a().createElement(l.InputNumber,{min:1,style:{width:"100%"}})),a().createElement(l.Space,null,a().createElement(l.Button,{type:"primary",loading:v,onClick:function(){return m(function(){var n,a;return f(this,function(o){switch(o.label){case 0:return[4,r.validateFields()];case 1:n=o.sent(),g(!0),o.label=2;case 2:return o.trys.push([2,4,5,6]),[4,e.api.request({url:"aiApiConfig:save",method:"post",data:n})];case 3:return o.sent(),l.message.success(t("Configuration saved")),[3,6];case 4:return a=o.sent(),l.message.error("".concat(t("Failed to save configuration"),": ").concat((0,u.g)(a))),[3,6];case 5:return g(!1),[7];case 6:return[2]}})})()}},t("Save Configuration")),a().createElement(l.Button,{onClick:O},t("Refresh")))),a().createElement(l.Card,{title:t("Usage guide"),size:"small",style:{marginTop:24}},a().createElement(l.Alert,{type:"info",showIcon:!0,message:t("OpenAI-compatible endpoint"),description:a().createElement(l.Space,{direction:"vertical",size:4},a().createElement(l.Typography.Text,null,t("Base URL")),a().createElement(l.Typography.Text,{code:!0,copyable:!0},j),a().createElement(l.Typography.Text,null,t("Use a NocoBase API key as the Bearer token."))),style:{marginBottom:16}}),a().createElement(l.Typography.Paragraph,null,t("List available models")),a().createElement(l.Typography.Paragraph,{code:!0,copyable:!0},"curl ".concat(j,'/models -H "Authorization: Bearer <your-api-key>"')),a().createElement(l.Typography.Paragraph,null,t("Send a chat completion")),a().createElement(l.Typography.Paragraph,{code:!0,copyable:!0},"curl ".concat(j,'/chat/completions \\\n -H "Authorization: Bearer <your-api-key>" \\\n -H "Content-Type: application/json" \\\n -d \'{"model":"<service>/<model>","messages":[{"role":"user","content":"Hello"}]}\''))))}},235:function(e,t,r){r.d(t,{k:function(){return l}});var n=r(694),a=JSON.parse('{"UU":"plugin-ai-api"}');function l(){var e=(0,n.useFlowEngine)();return function(t){return e.context.t(t,{ns:[a.UU,"client"]})}}},630:function(e,t,r){function n(e,t){var r,n;return e&&(void 0===e?"undefined":e&&"u">typeof Symbol&&e.constructor===Symbol?"symbol":typeof e)=="object"?null!=(r=null==(n=e.data)?void 0:n.data)?r:t:t}function a(e){var t;return(null!=(t=Error)&&"u">typeof Symbol&&t[Symbol.hasInstance]?!!t[Symbol.hasInstance](e):e instanceof t)?e.message:String(e)}r.d(t,{g:function(){return a},m:function(){return n}})}}]);
@@ -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([["778"],{905:function(e,t,r){r.r(t),r.d(t,{AiApiRolePermissions:function(){return d}});var n=r(155),l=r.n(n),a=r(485),o=r(59);function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=Array(t);r<t;r++)n[r]=e[r];return n}function u(e,t,r,n,l,a,o){try{var i=e[a](o),u=i.value}catch(e){r(e);return}i.done?t(u):Promise.resolve(u).then(n,l)}function c(e){return function(){var t=this,r=arguments;return new Promise(function(n,l){var a=e.apply(t,r);function o(e){u(a,n,l,o,i,"next",e)}function i(e){u(a,n,l,o,i,"throw",e)}o(void 0)})}}function s(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{},n=Object.keys(r);"function"==typeof Object.getOwnPropertySymbols&&(n=n.concat(Object.getOwnPropertySymbols(r).filter(function(e){return Object.getOwnPropertyDescriptor(r,e).enumerable}))),n.forEach(function(t){var n;n=r[t],t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n})}return e}function p(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var r,n,l=null==e?null:"u">typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=l){var a=[],o=!0,i=!1;try{for(l=l.call(e);!(o=(r=l.next()).done)&&(a.push(r.value),!t||a.length!==t);o=!0);}catch(e){i=!0,n=e}finally{try{o||null==l.return||l.return()}finally{if(i)throw n}}return a}}(e,t)||function(e,t){if(e){if("string"==typeof e)return i(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);if("Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r)return Array.from(r);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))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 f(e,t){var r,n,l,a={label:0,sent:function(){if(1&l[0])throw l[1];return l[1]},trys:[],ops:[]},o=Object.create(("function"==typeof Iterator?Iterator:Object).prototype),i=Object.defineProperty;return i(o,"next",{value:u(0)}),i(o,"throw",{value:u(1)}),i(o,"return",{value:u(2)}),"function"==typeof Symbol&&i(o,Symbol.iterator,{value:function(){return this}}),o;function u(i){return function(u){var c=[i,u];if(r)throw TypeError("Generator is already executing.");for(;o&&(o=0,c[0]&&(a=0)),a;)try{if(r=1,n&&(l=2&c[0]?n.return:c[0]?n.throw||((l=n.return)&&l.call(n),0):n.next)&&!(l=l.call(n,c[1])).done)return l;switch(n=0,l&&(c=[2&c[0],l.value]),c[0]){case 0:case 1:l=c;break;case 4:return a.label++,{value:c[1],done:!1};case 5:a.label++,n=c[1],c=[0];continue;case 7:c=a.ops.pop(),a.trys.pop();continue;default:if(!(l=(l=a.trys).length>0&&l[l.length-1])&&(6===c[0]||2===c[0])){a=0;continue}if(3===c[0]&&(!l||c[1]>l[0]&&c[1]<l[3])){a.label=c[1];break}if(6===c[0]&&a.label<l[1]){a.label=l[1],l=c;break}if(l&&a.label<l[2]){a.label=l[2],a.ops.push(c);break}l[2]&&a.ops.pop(),a.trys.pop();continue}c=t.call(e,a)}catch(e){c=[6,e],n=0}finally{r=l=0}if(5&c[0])throw c[1];return{value:c[0]?c[1]:void 0,done:!0}}}}var m=o.Typography.Text;function d(e){var t=e.role,r=(0,a.useApp)().apiClient,i=p((0,n.useState)(!0),2),u=i[0],d=i[1],y=p((0,n.useState)(!1),2),b=y[0],h=y[1],v=p((0,n.useState)([]),2),w=v[0],E=v[1],g=p((0,n.useState)(null),2),A=g[0],O=g[1],S=null==t?void 0:t.name;(0,n.useEffect)(function(){S&&P()},[S]);var P=function(){return c(function(){var e,t,n,l,a,o,i;return f(this,function(u){switch(u.label){case 0:d(!0),u.label=1;case 1:return u.trys.push([1,3,4,5]),[4,Promise.all([r.request({url:"aiApiRolePermissions",params:{filter:{roleName:S},paginate:!1}}),r.request({url:"aiEmployees:list",params:{paginate:!1}})])];case 2:return a=(l=p.apply(void 0,[u.sent(),2]))[0],o=l[1],O((i=null==a||null==(t=a.data)||null==(e=t.data)?void 0:e[0])?{id:i.id,roleName:i.roleName,enabled:!!i.enabled,allowAllEmployees:!1!==i.allowAllEmployees,allowedEmployees:i.allowedEmployees||[]}:{roleName:S,enabled:!1,allowAllEmployees:!0,allowedEmployees:[]}),E(((null==o||null==(n=o.data)?void 0:n.data)||[]).map(function(e){return{username:e.username,nickname:e.nickname||e.username}})),[3,5];case 3:return console.error("Failed to load AI API role permissions:",u.sent()),[3,5];case 4:return d(!1),[7];case 5:return[2]}})})()},j=function(e){return c(function(){var t,n,l,a;return f(this,function(o){switch(o.label){case 0:if(!A)return[2];O(t=s({},A,e)),h(!0),o.label=1;case 1:if(o.trys.push([1,6,7,8]),!t.id)return[3,3];return[4,r.request({url:"aiApiRolePermissions/".concat(t.id),method:"PUT",data:t})];case 2:return o.sent(),[3,5];case 3:return[4,r.request({url:"aiApiRolePermissions",method:"POST",data:t})];case 4:var i,u;(null==(a=null==(l=o.sent())||null==(n=l.data)?void 0:n.data)?void 0:a.id)&&O((i=s({},t),u=u={id:a.id},Object.getOwnPropertyDescriptors?Object.defineProperties(i,Object.getOwnPropertyDescriptors(u)):(function(e){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t.push.apply(t,r)}return t})(Object(u)).forEach(function(e){Object.defineProperty(i,e,Object.getOwnPropertyDescriptor(u,e))}),i)),o.label=5;case 5:return[3,8];case 6:return console.error("Failed to save AI API role permissions:",o.sent()),O(A),[3,8];case 7:return h(!1),[7];case 8:return[2]}})})()};return u?l().createElement(o.Spin,null):l().createElement(o.Card,{bordered:!1},l().createElement(o.Space,{direction:"vertical",style:{width:"100%"},size:"middle"},l().createElement(o.Space,null,l().createElement(o.Switch,{checked:!!(null==A?void 0:A.enabled),loading:b,onChange:function(e){return j({enabled:e})}}),l().createElement(m,{strong:!0},"Allow this role to use the AI API")),(null==A?void 0:A.enabled)&&l().createElement(l().Fragment,null,l().createElement(o.Divider,{style:{margin:"8px 0"}}),l().createElement(o.Space,null,l().createElement(o.Switch,{checked:!!(null==A?void 0:A.allowAllEmployees),loading:b,onChange:function(e){return j({allowAllEmployees:e})}}),l().createElement(m,null,"Allow all AI Employees")),!(null==A?void 0:A.allowAllEmployees)&&l().createElement("div",null,l().createElement(m,{type:"secondary",style:{display:"block",marginBottom:8}},"Select which AI Employees this role may use:"),l().createElement(o.Select,{mode:"multiple",allowClear:!0,style:{width:"100%",maxWidth:480},placeholder:"Select allowed AI Employees",value:(null==A?void 0:A.allowedEmployees)||[],options:w.map(function(e){return{label:"".concat(e.nickname," (").concat(e.username,")"),value:e.username}}),onChange:function(e){return j({allowedEmployees:e})},disabled:b})))))}}}]);
@@ -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([["302"],{581:function(e,t,r){r.r(t),r.d(t,{default:function(){return d}});var n=r(155),a=r.n(n),l=r(59),o=r(694),i=r(235),u=r(630);function c(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=Array(t);r<t;r++)n[r]=e[r];return n}function s(e,t,r,n,a,l,o){try{var i=e[l](o),u=i.value}catch(e){r(e);return}i.done?t(u):Promise.resolve(u).then(n,a)}function m(e){return function(){var t=this,r=arguments;return new Promise(function(n,a){var l=e.apply(t,r);function o(e){s(l,n,a,o,i,"next",e)}function i(e){s(l,n,a,o,i,"throw",e)}o(void 0)})}}function p(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var r,n,a=null==e?null:"u">typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=a){var l=[],o=!0,i=!1;try{for(a=a.call(e);!(o=(r=a.next()).done)&&(l.push(r.value),!t||l.length!==t);o=!0);}catch(e){i=!0,n=e}finally{try{o||null==a.return||a.return()}finally{if(i)throw n}}return l}}(e,t)||function(e,t){if(e){if("string"==typeof e)return c(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);if("Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r)return Array.from(r);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return c(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 f(e,t){var r,n,a,l={label:0,sent:function(){if(1&a[0])throw a[1];return a[1]},trys:[],ops:[]},o=Object.create(("function"==typeof Iterator?Iterator:Object).prototype),i=Object.defineProperty;return i(o,"next",{value:u(0)}),i(o,"throw",{value:u(1)}),i(o,"return",{value:u(2)}),"function"==typeof Symbol&&i(o,Symbol.iterator,{value:function(){return this}}),o;function u(i){return function(u){var c=[i,u];if(r)throw TypeError("Generator is already executing.");for(;o&&(o=0,c[0]&&(l=0)),l;)try{if(r=1,n&&(a=2&c[0]?n.return:c[0]?n.throw||((a=n.return)&&a.call(n),0):n.next)&&!(a=a.call(n,c[1])).done)return a;switch(n=0,a&&(c=[2&c[0],a.value]),c[0]){case 0:case 1:a=c;break;case 4:return l.label++,{value:c[1],done:!1};case 5:l.label++,n=c[1],c=[0];continue;case 7:c=l.ops.pop(),l.trys.pop();continue;default:if(!(a=(a=l.trys).length>0&&a[a.length-1])&&(6===c[0]||2===c[0])){l=0;continue}if(3===c[0]&&(!a||c[1]>a[0]&&c[1]<a[3])){l.label=c[1];break}if(6===c[0]&&l.label<a[1]){l.label=a[1],a=c;break}if(a&&l.label<a[2]){l.label=a[2],l.ops.push(c);break}a[2]&&l.ops.pop(),l.trys.pop();continue}c=t.call(e,l)}catch(e){c=[6,e],n=0}finally{r=a=0}if(5&c[0])throw c[1];return{value:c[0]?c[1]:void 0,done:!0}}}}var y={mode:"llm",enabledLlmServices:[],rateLimitPerMinute:60,quotaEnabled:!1,defaultReservationOutputTokens:4096};function d(){var e=(0,o.useFlowContext)(),t=(0,i.k)(),r=p(l.Form.useForm(),1)[0],c=l.Form.useWatch("mode",r),s=p((0,n.useState)(!0),2),d=s[0],b=s[1],h=p((0,n.useState)(!1),2),v=h[0],g=h[1],E=p((0,n.useState)([]),2),S=E[0],w=E[1],k=p((0,n.useState)([]),2),I=k[0],P=k[1],A=p((0,n.useState)(),2),F=A[0],C=A[1],O=(0,n.useCallback)(function(){return m(function(){var t,n,a,l,o;return f(this,function(i){switch(i.label){case 0:b(!0),C(void 0),i.label=1;case 1:return i.trys.push([1,3,4,5]),[4,Promise.all([e.api.request({url:"aiApiConfig:get",method:"get"}),e.api.request({url:"ai:listLLMServices",method:"get"}),e.api.request({url:"aiEmployees:list",method:"get",params:{paginate:!1}})])];case 2:return n=(t=p.apply(void 0,[i.sent(),3]))[0],a=t[1],l=t[2],r.setFieldsValue(function(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{},n=Object.keys(r);"function"==typeof Object.getOwnPropertySymbols&&(n=n.concat(Object.getOwnPropertySymbols(r).filter(function(e){return Object.getOwnPropertyDescriptor(r,e).enumerable}))),n.forEach(function(t){var n;n=r[t],t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n})}return e}({},y,(0,u.m)(n,{}))),w((0,u.m)(a,[])),P((0,u.m)(l,[])),[3,5];case 3:return o=i.sent(),C((0,u.g)(o)),[3,5];case 4:return b(!1),[7];case 5:return[2]}})})()},[e.api,r]);(0,n.useEffect)(function(){O()},[O]);var L=S.map(function(e){return{label:e.title||e.name,value:e.name}}),T=I.map(function(e){return{label:e.nickname?"".concat(e.nickname," (").concat(e.username,")"):e.username,value:e.username}}),j="".concat(window.location.origin,"/api/ai-llm/v1");return a().createElement(l.Card,{title:t("Configuration"),loading:d},F?a().createElement(l.Alert,{type:"error",showIcon:!0,message:F,style:{marginBottom:16}}):null,a().createElement(l.Form,{form:r,layout:"vertical",style:{maxWidth:720},initialValues:y},a().createElement(l.Form.Item,{name:"mode",label:t("API mode"),rules:[{required:!0}]},a().createElement(l.Select,{options:[{label:t("Direct LLM"),value:"llm"},{label:t("AI Employee agent"),value:"agent"}]})),a().createElement(l.Form.Item,{name:"defaultLlmService",label:t("Default LLM service")},a().createElement(l.Select,{allowClear:!0,showSearch:!0,optionFilterProp:"label",options:L})),a().createElement(l.Form.Item,{name:"enabledLlmServices",label:t("Enabled LLM Services")},a().createElement(l.Select,{mode:"multiple",allowClear:!0,showSearch:!0,optionFilterProp:"label",options:L})),"agent"===c?a().createElement(l.Form.Item,{name:"defaultAiEmployee",label:t("Default AI Employee")},a().createElement(l.Select,{allowClear:!0,showSearch:!0,optionFilterProp:"label",placeholder:t("Select an AI Employee"),options:T})):null,a().createElement(l.Form.Item,{name:"rateLimitPerMinute",label:t("Rate Limit"),rules:[{required:!0}]},a().createElement(l.InputNumber,{min:1,style:{width:"100%"}})),a().createElement(l.Form.Item,{name:"quotaEnabled",label:t("Enable user quotas"),valuePropName:"checked"},a().createElement(l.Switch,null)),a().createElement(l.Form.Item,{name:"defaultReservationOutputTokens",label:t("Default reserved output tokens"),rules:[{required:!0}]},a().createElement(l.InputNumber,{min:1,style:{width:"100%"}})),a().createElement(l.Space,null,a().createElement(l.Button,{type:"primary",loading:v,onClick:function(){return m(function(){var n,a;return f(this,function(o){switch(o.label){case 0:return[4,r.validateFields()];case 1:n=o.sent(),g(!0),o.label=2;case 2:return o.trys.push([2,4,5,6]),[4,e.api.request({url:"aiApiConfig:save",method:"post",data:n})];case 3:return o.sent(),l.message.success(t("Configuration saved")),[3,6];case 4:return a=o.sent(),l.message.error("".concat(t("Failed to save configuration"),": ").concat((0,u.g)(a))),[3,6];case 5:return g(!1),[7];case 6:return[2]}})})()}},t("Save Configuration")),a().createElement(l.Button,{onClick:O},t("Refresh")))),a().createElement(l.Card,{title:t("Usage guide"),size:"small",style:{marginTop:24}},a().createElement(l.Alert,{type:"info",showIcon:!0,message:t("OpenAI-compatible endpoint"),description:a().createElement(l.Space,{direction:"vertical",size:4},a().createElement(l.Typography.Text,null,t("Base URL")),a().createElement(l.Typography.Text,{code:!0,copyable:!0},j),a().createElement(l.Typography.Text,null,t("Use a NocoBase API key as the Bearer token."))),style:{marginBottom:16}}),a().createElement(l.Typography.Paragraph,null,t("List available models")),a().createElement(l.Typography.Paragraph,{code:!0,copyable:!0},"curl ".concat(j,'/models -H "Authorization: Bearer <your-api-key>"')),a().createElement(l.Typography.Paragraph,null,t("Send a chat completion")),a().createElement(l.Typography.Paragraph,{code:!0,copyable:!0},"curl ".concat(j,'/chat/completions \\\n -H "Authorization: Bearer <your-api-key>" \\\n -H "Content-Type: application/json" \\\n -d \'{"model":"<service>/<model>","messages":[{"role":"user","content":"Hello"}]}\''))))}},235:function(e,t,r){r.d(t,{k:function(){return l}});var n=r(694),a=JSON.parse('{"UU":"plugin-ai-api"}');function l(){var e=(0,n.useFlowEngine)();return function(t){return e.context.t(t,{ns:[a.UU,"client"]})}}},630:function(e,t,r){function n(e,t){var r,n;return e&&(void 0===e?"undefined":e&&"u">typeof Symbol&&e.constructor===Symbol?"symbol":typeof e)=="object"?null!=(r=null==(n=e.data)?void 0:n.data)?r:t:t}function a(e){var t;return(null!=(t=Error)&&"u">typeof Symbol&&t[Symbol.hasInstance]?!!t[Symbol.hasInstance](e):e instanceof t)?e.message:String(e)}r.d(t,{g:function(){return a},m:function(){return n}})}}]);
@@ -1,309 +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
- import React, { useEffect, useState } from 'react';
11
- import { Card, Form, Select, InputNumber, Button, message, Typography, Space, Alert, Divider, Spin, Radio } from 'antd';
12
- import { useApp } from '@nocobase/client-v2';
13
-
14
- const { Title, Text, Paragraph } = Typography;
15
-
16
- interface AiApiConfig {
17
- mode: string;
18
- defaultAiEmployee: string;
19
- defaultLlmService: string;
20
- enabledLlmServices: string[];
21
- rateLimitPerMinute: number;
22
- }
23
-
24
- interface AiEmployee {
25
- username: string;
26
- nickname: string;
27
- }
28
-
29
- interface LlmService {
30
- name: string;
31
- title: string;
32
- provider: string;
33
- enabled: boolean;
34
- }
35
-
36
- export function AiApiConfigPage() {
37
- const api = useApp().apiClient;
38
- const [form] = Form.useForm();
39
- const [loading, setLoading] = useState(true);
40
- const [saving, setSaving] = useState(false);
41
- const [employees, setEmployees] = useState<AiEmployee[]>([]);
42
- const [services, setServices] = useState<LlmService[]>([]);
43
- const [baseUrl, setBaseUrl] = useState('');
44
-
45
- useEffect(() => {
46
- loadData();
47
- setBaseUrl(`${window.location.origin}/api/ai-llm/v1`);
48
- }, []);
49
-
50
- const loadData = async () => {
51
- setLoading(true);
52
- try {
53
- // Load config
54
- const configRes = await api.request({ url: 'aiApiConfig:get' });
55
- const config = configRes?.data?.data;
56
- if (config) {
57
- form.setFieldsValue({
58
- mode: config.mode || 'llm',
59
- defaultAiEmployee: config.defaultAiEmployee || undefined,
60
- defaultLlmService: config.defaultLlmService || undefined,
61
- enabledLlmServices: config.enabledLlmServices || [],
62
- rateLimitPerMinute: config.rateLimitPerMinute || 60,
63
- });
64
- }
65
-
66
- // Load AI employees
67
- const empRes = await api.request({
68
- url: 'aiEmployees:list',
69
- params: { paginate: false },
70
- });
71
- setEmployees(
72
- (empRes?.data?.data || []).map((e: any) => ({
73
- username: e.username,
74
- nickname: e.nickname || e.username,
75
- })),
76
- );
77
-
78
- // Load LLM services
79
- const svcRes = await api.request({
80
- url: 'ai:listLLMServices',
81
- });
82
- setServices(
83
- (svcRes?.data?.data || []).map((s: any) => ({
84
- name: s.name,
85
- title: s.title || s.name,
86
- provider: s.provider,
87
- enabled: s.enabled !== false,
88
- })),
89
- );
90
- } catch (err) {
91
- console.error('Failed to load config:', err);
92
- } finally {
93
- setLoading(false);
94
- }
95
- };
96
-
97
- const handleSave = async () => {
98
- setSaving(true);
99
- try {
100
- const values = form.getFieldsValue();
101
- await api.request({
102
- url: 'aiApiConfig:save',
103
- method: 'post',
104
- data: values,
105
- });
106
- message.success('Configuration saved');
107
- } catch (err) {
108
- message.error('Failed to save configuration');
109
- } finally {
110
- setSaving(false);
111
- }
112
- };
113
-
114
- if (loading) {
115
- return (
116
- <div style={{ display: 'flex', justifyContent: 'center', padding: 60 }}>
117
- <Spin size="large" />
118
- </div>
119
- );
120
- }
121
-
122
- return (
123
- <div style={{ maxWidth: 800, margin: '0 auto', padding: '24px 0' }}>
124
- <Title level={3}>AI API Gateway Configuration</Title>
125
- <Paragraph type="secondary">
126
- Configure the OpenAI-compatible API endpoint. External applications can connect using the base URL and a
127
- NocoBase API key.
128
- </Paragraph>
129
-
130
- <Card style={{ marginBottom: 24 }}>
131
- <Alert
132
- type="info"
133
- showIcon
134
- message="API Endpoint"
135
- description={
136
- <Space direction="vertical" size={4}>
137
- <Text>
138
- Base URL:{' '}
139
- <Text code copyable>
140
- {baseUrl}
141
- </Text>
142
- </Text>
143
- <Text type="secondary">
144
- Use this as the base URL in any OpenAI-compatible client (Cursor, Continue.dev, n8n, etc.)
145
- </Text>
146
- </Space>
147
- }
148
- style={{ marginBottom: 16 }}
149
- />
150
-
151
- <Alert
152
- type="warning"
153
- showIcon
154
- message="Authentication"
155
- description={
156
- <Text>
157
- Clients must include a NocoBase API key as a Bearer token:
158
- <br />
159
- <Text code>Authorization: Bearer {'<your-nocobase-api-key>'}</Text>
160
- <br />
161
- <Text type="secondary">API keys can be created in Settings → API keys.</Text>
162
- </Text>
163
- }
164
- />
165
- </Card>
166
-
167
- <Card title="Configuration">
168
- <Form form={form} layout="vertical" onFinish={handleSave} initialValues={{ mode: 'llm' }}>
169
- <Form.Item
170
- name="mode"
171
- label="API Mode"
172
- tooltip="LLM Proxy: Direct access to the LLM model. Agent: Full AI Employee with tools, knowledge base, and agent capabilities."
173
- >
174
- <Radio.Group>
175
- <Radio.Button value="llm">LLM Proxy</Radio.Button>
176
- <Radio.Button value="agent">AI Employee Agent</Radio.Button>
177
- </Radio.Group>
178
- </Form.Item>
179
-
180
- <Form.Item
181
- name="defaultAiEmployee"
182
- label="Default AI Employee"
183
- tooltip="The AI Employee whose system prompt will be injected into chat completions (when client doesn't provide a system message)"
184
- >
185
- <Select
186
- allowClear
187
- placeholder="Select an AI Employee (optional)"
188
- options={employees.map((e) => ({
189
- label: `${e.nickname} (${e.username})`,
190
- value: e.username,
191
- }))}
192
- />
193
- </Form.Item>
194
-
195
- <Form.Item
196
- name="defaultLlmService"
197
- label="Default LLM Service"
198
- 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."
199
- rules={[{ required: true, message: 'Please select a default LLM service' }]}
200
- >
201
- <Select
202
- allowClear
203
- placeholder="Select an LLM Service"
204
- options={services.map((s) => ({
205
- label: `${s.title} (${s.provider})`,
206
- value: s.name,
207
- }))}
208
- />
209
- </Form.Item>
210
-
211
- <Form.Item
212
- name="enabledLlmServices"
213
- label="Enabled LLM Services"
214
- tooltip="Only these services will be exposed via the API. Leave empty to expose all enabled services."
215
- >
216
- <Select
217
- mode="multiple"
218
- allowClear
219
- placeholder="All enabled services (default)"
220
- options={services.map((s) => ({
221
- label: `${s.title} (${s.provider})`,
222
- value: s.name,
223
- }))}
224
- />
225
- </Form.Item>
226
-
227
- <Form.Item
228
- name="rateLimitPerMinute"
229
- label="Rate Limit (requests/minute)"
230
- tooltip="Maximum API requests per user per minute. Set 0 for unlimited."
231
- >
232
- <InputNumber min={0} max={10000} style={{ width: 200 }} />
233
- </Form.Item>
234
-
235
- <Form.Item>
236
- <Button type="primary" htmlType="submit" loading={saving}>
237
- Save Configuration
238
- </Button>
239
- </Form.Item>
240
- </Form>
241
- </Card>
242
-
243
- <Card title="Quick Start" style={{ marginTop: 24 }}>
244
- <Title level={5}>cURL Example</Title>
245
- <Paragraph>
246
- <pre
247
- style={{
248
- background: '#1a1a2e',
249
- color: '#e0e0e0',
250
- padding: 16,
251
- borderRadius: 8,
252
- fontSize: 13,
253
- overflow: 'auto',
254
- }}
255
- >{`curl ${baseUrl}/chat/completions \\
256
- -H "Authorization: Bearer <your-api-key>" \\
257
- -H "Content-Type: application/json" \\
258
- -d '{
259
- "model": "<service-name>/<model-id>",
260
- "messages": [
261
- {"role": "user", "content": "Hello!"}
262
- ]
263
- }'`}</pre>
264
- </Paragraph>
265
-
266
- <Title level={5}>Python (OpenAI SDK)</Title>
267
- <Paragraph>
268
- <pre
269
- style={{
270
- background: '#1a1a2e',
271
- color: '#e0e0e0',
272
- padding: 16,
273
- borderRadius: 8,
274
- fontSize: 13,
275
- overflow: 'auto',
276
- }}
277
- >{`from openai import OpenAI
278
-
279
- client = OpenAI(
280
- base_url="${baseUrl}",
281
- api_key="<your-nocobase-api-key>"
282
- )
283
-
284
- response = client.chat.completions.create(
285
- model="<service-name>/<model-id>",
286
- messages=[{"role": "user", "content": "Hello!"}]
287
- )`}</pre>
288
- </Paragraph>
289
-
290
- <Title level={5}>List Available Models</Title>
291
- <Paragraph>
292
- <pre
293
- style={{
294
- background: '#1a1a2e',
295
- color: '#e0e0e0',
296
- padding: 16,
297
- borderRadius: 8,
298
- fontSize: 13,
299
- overflow: 'auto',
300
- }}
301
- >{`curl ${baseUrl}/models \\
302
- -H "Authorization: Bearer <your-api-key>"`}</pre>
303
- </Paragraph>
304
- </Card>
305
- </div>
306
- );
307
- }
308
-
309
- export default AiApiConfigPage;