plugin-ai-api 1.0.3 → 1.0.4
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 +15 -1
- package/client-v2.d.ts +2 -0
- package/client-v2.js +1 -0
- package/dist/client/778.180aac1b13a38702.js +10 -0
- package/dist/client/950.2797862732563625.js +10 -0
- package/dist/client/index.js +1 -1
- package/dist/client-v2/950.8991b849cd7c1351.js +10 -0
- package/dist/client-v2/index.js +10 -0
- package/dist/externalVersion.js +9 -8
- package/package.json +32 -14
- package/src/client/AiApiConfigPage.tsx +309 -0
- package/src/client/client.d.ts +258 -0
- package/src/client/components/AiApiRolePermissions.tsx +169 -0
- package/src/client/index.tsx +10 -0
- package/src/client/locale.ts +21 -0
- package/src/client/models/index.ts +12 -0
- package/src/client/plugin.tsx +48 -0
- package/src/client-v2/index.tsx +1 -0
- package/src/client-v2/plugin.tsx +24 -0
- package/src/index.ts +11 -0
- package/src/locale/en-US.json +10 -0
- package/src/locale/zh-CN.json +10 -0
- package/src/server/collections/.gitkeep +0 -0
- package/src/server/collections/ai-api-config.ts +51 -0
- package/src/server/collections/ai-api-role-permissions.ts +41 -0
- package/src/server/index.ts +10 -0
- package/src/server/middleware/rate-limit.ts +70 -0
- package/src/server/middleware/role-permission.ts +66 -0
- package/src/server/plugin.ts +89 -0
- package/src/server/resource/ai-api-config.ts +74 -0
- package/src/server/routes/agent-completions.ts +428 -0
- package/src/server/routes/auth.ts +111 -0
- package/src/server/routes/chat-completions.ts +318 -0
- package/src/server/routes/completions.ts +299 -0
- package/src/server/routes/embeddings.ts +191 -0
- package/src/server/routes/models.ts +195 -0
- package/src/server/routes/router.ts +283 -0
- package/src/server/utils/openai-format.ts +142 -0
- package/src/server/utils/rate-limiter.ts +83 -0
- package/src/server/utils/resolve-service.ts +82 -0
- package/src/swagger.ts +325 -0
- package/dist/client/23.e96ecf13e6072dce.js +0 -10
- package/dist/client/503.29bcdb426b01e715.js +0 -10
package/README.md
CHANGED
|
@@ -1 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# plugin-ai-api
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
Provides an internal API layer for routing and managing AI model interactions in NocoBase.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
- **Centralized AI Routing**: Acts as the middleware for all AI-related requests.
|
|
8
|
+
- **Provider Abstraction**: Normalizes requests and responses across different LLM providers (OpenAI, Anthropic, Custom LLMs).
|
|
9
|
+
- **Rate Limiting & Logging**: Internal mechanisms to track token usage and API limits.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
*This is primarily an internal system plugin.*
|
|
13
|
+
1. Enable the plugin via the Plugin Manager.
|
|
14
|
+
2. Developers can use the exported API services in their custom plugins by importing `@nocobase/plugin-ai-api`.
|
|
15
|
+
3. Configure default AI models in the global NocoBase Settings under "AI API Provider".
|
package/client-v2.d.ts
ADDED
package/client-v2.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./dist/client-v2/index.js');
|
|
@@ -0,0 +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
|
+
"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(342),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.useAPIClient)(),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],P=null==t?void 0:t.name;(0,n.useEffect)(function(){P&&S()},[P]);var S=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:P},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:P,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})))))}}}]);
|
|
@@ -0,0 +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
|
+
"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 r=n(155),a=n.n(r),l=n(59),o=n(342);function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}function c(e,t,n,r,a,l,o){try{var i=e[l](o),c=i.value}catch(e){n(e);return}i.done?t(c):Promise.resolve(c).then(r,a)}function u(e){return function(){var t=this,n=arguments;return new Promise(function(r,a){var l=e.apply(t,n);function o(e){c(l,r,a,o,i,"next",e)}function i(e){c(l,r,a,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,r,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=(n=a.next()).done)&&(l.push(n.value),!t||l.length!==t);o=!0);}catch(e){i=!0,r=e}finally{try{o||null==a.return||a.return()}finally{if(i)throw r}}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,r,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: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,r&&(a=2&u[0]?r.return:u[0]?r.throw||((a=r.return)&&a.call(r),0):r.next)&&!(a=a.call(r,u[1])).done)return a;switch(r=0,a&&(u=[2&u[0],a.value]),u[0]){case 0:case 1:a=u;break;case 4:return l.label++,{value:u[1],done:!1};case 5:l.label++,r=u[1],u=[0];continue;case 7:u=l.ops.pop(),l.trys.pop();continue;default:if(!(a=(a=l.trys).length>0&&a[a.length-1])&&(6===u[0]||2===u[0])){l=0;continue}if(3===u[0]&&(!a||u[1]>a[0]&&u[1]<a[3])){l.label=u[1];break}if(6===u[0]&&l.label<a[1]){l.label=a[1],a=u;break}if(a&&l.label<a[2]){l.label=a[2],l.ops.push(u);break}a[2]&&l.ops.pop(),l.trys.pop();continue}u=t.call(e,l)}catch(e){u=[6,e],r=0}finally{n=a=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.useAPIClient)(),t=s(l.Form.useForm(),1)[0],n=s((0,r.useState)(!0),2),i=n[0],c=n[1],y=s((0,r.useState)(!1),2),v=y[0],h=y[1],b=s((0,r.useState)([]),2),g=b[0],E=b[1],A=s((0,r.useState)([]),2),I=A[0],S=A[1],w=s((0,r.useState)(""),2),L=w[0],k=w[1];(0,r.useEffect)(function(){P(),k("".concat(window.location.origin,"/api/ai-llm/v1"))},[]);var P=function(){return u(function(){var n,r,a,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==(r=i.data)?void 0:r.data)||[]).map(function(e){return{username:e.username,nickname:e.nickname||e.username}})),[4,e.request({url:"ai:listLLMServices"})];case 4:return S(((null==(u=s.sent())||null==(a=u.data)?void 0:a.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?a().createElement("div",{style:{display:"flex",justifyContent:"center",padding:60}},a().createElement(l.Spin,{size:"large"})):a().createElement("div",{style:{maxWidth:800,margin:"0 auto",padding:"24px 0"}},a().createElement(p,{level:3},"AI API Gateway Configuration"),a().createElement(f,{type:"secondary"},"Configure the OpenAI-compatible API endpoint. External applications can connect using the base URL and a NocoBase API key."),a().createElement(l.Card,{style:{marginBottom:24}},a().createElement(l.Alert,{type:"info",showIcon:!0,message:"API Endpoint",description:a().createElement(l.Space,{direction:"vertical",size:4},a().createElement(d,null,"Base URL:"," ",a().createElement(d,{code:!0,copyable:!0},L)),a().createElement(d,{type:"secondary"},"Use this as the base URL in any OpenAI-compatible client (Cursor, Continue.dev, n8n, etc.)")),style:{marginBottom:16}}),a().createElement(l.Alert,{type:"warning",showIcon:!0,message:"Authentication",description:a().createElement(d,null,"Clients must include a NocoBase API key as a Bearer token:",a().createElement("br",null),a().createElement(d,{code:!0},"Authorization: Bearer ","<your-nocobase-api-key>"),a().createElement("br",null),a().createElement(d,{type:"secondary"},"API keys can be created in Settings → API keys."))})),a().createElement(l.Card,{title:"Configuration"},a().createElement(l.Form,{form:t,layout:"vertical",onFinish:function(){return u(function(){var n;return m(this,function(r){switch(r.label){case 0:h(!0),r.label=1;case 1:return r.trys.push([1,3,4,5]),n=t.getFieldsValue(),[4,e.request({url:"aiApiConfig:save",method:"post",data:n})];case 2:return r.sent(),l.message.success("Configuration saved"),[3,5];case 3:return r.sent(),l.message.error("Failed to save configuration"),[3,5];case 4:return h(!1),[7];case 5:return[2]}})})()},initialValues:{mode:"llm"}},a().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."},a().createElement(l.Radio.Group,null,a().createElement(l.Radio.Button,{value:"llm"},"LLM Proxy"),a().createElement(l.Radio.Button,{value:"agent"},"AI Employee Agent"))),a().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)"},a().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}})})),a().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"}]},a().createElement(l.Select,{allowClear:!0,placeholder:"Select an LLM Service",options:I.map(function(e){return{label:"".concat(e.title," (").concat(e.provider,")"),value:e.name}})})),a().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."},a().createElement(l.Select,{mode:"multiple",allowClear:!0,placeholder:"All enabled services (default)",options:I.map(function(e){return{label:"".concat(e.title," (").concat(e.provider,")"),value:e.name}})})),a().createElement(l.Form.Item,{name:"rateLimitPerMinute",label:"Rate Limit (requests/minute)",tooltip:"Maximum API requests per user per minute. Set 0 for unlimited."},a().createElement(l.InputNumber,{min:0,max:1e4,style:{width:200}})),a().createElement(l.Form.Item,null,a().createElement(l.Button,{type:"primary",htmlType:"submit",loading:v},"Save Configuration")))),a().createElement(l.Card,{title:"Quick Start",style:{marginTop:24}},a().createElement(p,{level:5},"cURL Example"),a().createElement(f,null,a().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 }\''))),a().createElement(p,{level:5},"Python (OpenAI SDK)"),a().createElement(f,null,a().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)'))),a().createElement(p,{level:5},"List Available Models"),a().createElement(f,null,a().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}}]);
|
package/dist/client/index.js
CHANGED
|
@@ -7,4 +7,4 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("
|
|
10
|
+
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("antd"),require("@nocobase/client"),require("@nocobase/plugin-acl/client")):"function"==typeof define&&define.amd?define("plugin-ai-api",["react","antd","@nocobase/client","@nocobase/plugin-acl/client"],t):"object"==typeof exports?exports["plugin-ai-api"]=t(require("react"),require("antd"),require("@nocobase/client"),require("@nocobase/plugin-acl/client")):e["plugin-ai-api"]=t(e.react,e.antd,e["@nocobase/client"],e["@nocobase/plugin-acl/client"])}(self,function(e,t,n,r){return function(){"use strict";var o,i,a,u={342:function(e){e.exports=n},79:function(e){e.exports=r},59:function(e){e.exports=t},155:function(t){t.exports=e}},c={};function l(e){var t=c[e];if(void 0!==t)return t.exports;var n=c[e]={exports:{}};return u[e](n,n.exports,l),n.exports}l.m=u,l.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(t,{a:t}),t},l.d=function(e,t){for(var n in t)l.o(t,n)&&!l.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},l.f={},l.e=function(e){return Promise.all(Object.keys(l.f).reduce(function(t,n){return l.f[n](e,t),t},[]))},l.u=function(e){return""+e+"."+({778:"180aac1b13a38702",950:"2797862732563625"})[e]+".js"},l.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),l.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},p={},l.l=function(e,t,n,r){if(p[e])return void p[e].push(t);if(void 0!==n)for(var o,i,a=document.getElementsByTagName("script"),u=0;u<a.length;u++){var c=a[u];if(c.getAttribute("src")==e||c.getAttribute("data-rspack")=="plugin-ai-api:"+n){o=c;break}}o||(i=!0,(o=document.createElement("script")).timeout=120,l.nc&&o.setAttribute("nonce",l.nc),o.setAttribute("data-rspack","plugin-ai-api:"+n),o.src=e),p[e]=[t];var f=function(t,n){o.onerror=o.onload=null,clearTimeout(s);var r=p[e];if(delete p[e],o.parentNode&&o.parentNode.removeChild(o),r&&r.forEach(function(e){return e(n)}),t)return t(n)},s=setTimeout(f.bind(null,void 0,{type:"timeout",target:o}),12e4);o.onerror=f.bind(null,o.onerror),o.onload=f.bind(null,o.onload),i&&document.head.appendChild(o)},l.r=function(e){"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.g.importScripts&&(f=l.g.location+"");var p,f,s=l.g.document;if(!f&&s&&(s.currentScript&&"SCRIPT"===s.currentScript.tagName.toUpperCase()&&(f=s.currentScript.src),!f)){var d=s.getElementsByTagName("script");if(d.length)for(var b=d.length-1;b>-1&&(!f||!/^http(s?):/.test(f));)f=d[b--].src}if(!f)throw Error("Automatic publicPath is not supported in this browser");l.p=f.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),o={889:0},l.f.j=function(e,t){var n=l.o(o,e)?o[e]:void 0;if(0!==n)if(n)t.push(n[2]);else{var r=new Promise(function(t,r){n=o[e]=[t,r]});t.push(n[2]=r);var i=l.p+l.u(e),a=Error();l.l(i,function(t){if(l.o(o,e)&&(0!==(n=o[e])&&(o[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),i=t&&t.target&&t.target.src;a.message="Loading chunk "+e+" failed.\n("+r+": "+i+")",a.name="ChunkLoadError",a.type=r,a.request=i,n[1](a)}},"chunk-"+e,e)}},i=function(e,t){var n,r,i=t[0],a=t[1],u=t[2],c=0;if(i.some(function(e){return 0!==o[e]})){for(n in a)l.o(a,n)&&(l.m[n]=a[n]);u&&u(l)}for(e&&e(t);c<i.length;c++)r=i[c],l.o(o,r)&&o[r]&&o[r][0](),o[r]=0},(a=self.webpackChunkplugin_ai_api=self.webpackChunkplugin_ai_api||[]).forEach(i.bind(null,0)),a.push=i.bind(null,a.push.bind(a));var h={};return!function(){var e="",t="u">typeof document?document.currentScript:null;if(t&&t.src&&(e=t.src.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/")),!e){var n=window.__webpack_public_path__||"";n&&("/"!==n.charAt(n.length-1)&&(n+="/"),e=n+"static/plugins/plugin-ai-api/dist/client/")}if(!e){var r=window.__nocobase_modern_client_prefix__||"v",o="/"+(r=String(r).replace(/^\/+|\/+$/g,"")||"v")+"/";if(!(e=window.__nocobase_public_path__||"")&&window.location&&window.location.pathname){var i=window.location.pathname||"/",a=i.indexOf(o);e=a>=0?i.slice(0,a+1):"/"}e&&(e=e.replace(RegExp("/"+r+"/?$"),"/")),e||(e="/"),"/"!==e.charAt(e.length-1)&&(e+="/"),e+="static/plugins/plugin-ai-api/dist/client/"}l.p=e}(),!function(){l.r(h),l.d(h,{default:function(){return b}});var e=l(342),t=l(79),n=l.n(t),r=l(155),o=l.n(r);function i(e,t,n,r,o,i,a){try{var u=e[i](a),c=u.value}catch(e){n(e);return}u.done?t(c):Promise.resolve(c).then(r,o)}function a(e,t,n){return(a=f()?Reflect.construct:function(e,t,n){var r=[null];r.push.apply(r,t);var o=new(Function.bind.apply(e,r));return n&&c(o,n.prototype),o}).apply(null,arguments)}function u(e){return(u=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function c(e,t){return(c=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function p(e){var t="function"==typeof Map?new Map:void 0;return(p=function(e){if(null===e||-1===Function.toString.call(e).indexOf("[native code]"))return e;if("function"!=typeof e)throw TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,n)}function n(){return a(e,arguments,u(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),c(n,e)})(e)}function f(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(e){}return(f=function(){return!!e})()}var s=o().lazy(function(){return l.e("950").then(l.bind(l,693))}),d=(0,e.lazy)(function(){return l.e("778").then(l.bind(l,905))},"AiApiRolePermissions").AiApiRolePermissions,b=function(e){var t;if("function"!=typeof e&&null!==e)throw TypeError("Super expression must either be null or a function");function r(){var e,t;if(!(this instanceof r))throw TypeError("Cannot call a class as a function");return e=r,t=arguments,e=u(e),function(e,t){var n;if(t&&("object"==((n=t)&&"u">typeof Symbol&&n.constructor===Symbol?"symbol":typeof n)||"function"==typeof t))return t;if(void 0===e)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(this,f()?Reflect.construct(e,t||[],u(this).constructor):e.apply(this,t))}return r.prototype=Object.create(e&&e.prototype,{constructor:{value:r,writable:!0,configurable:!0}}),e&&c(r,e),t=[{key:"load",value:function(){var e;return(e=function(){var e;return function(e,t){var n,r,o,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]},a=Object.create(("function"==typeof Iterator?Iterator:Object).prototype),u=Object.defineProperty;return u(a,"next",{value:c(0)}),u(a,"throw",{value:c(1)}),u(a,"return",{value:c(2)}),"function"==typeof Symbol&&u(a,Symbol.iterator,{value:function(){return this}}),a;function c(u){return function(c){var l=[u,c];if(n)throw TypeError("Generator is already executing.");for(;a&&(a=0,l[0]&&(i=0)),i;)try{if(n=1,r&&(o=2&l[0]?r.return:l[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,l[1])).done)return o;switch(r=0,o&&(l=[2&l[0],o.value]),l[0]){case 0:case 1:o=l;break;case 4:return i.label++,{value:l[1],done:!1};case 5:i.label++,r=l[1],l=[0];continue;case 7:l=i.ops.pop(),i.trys.pop();continue;default:if(!(o=(o=i.trys).length>0&&o[o.length-1])&&(6===l[0]||2===l[0])){i=0;continue}if(3===l[0]&&(!o||l[1]>o[0]&&l[1]<o[3])){i.label=l[1];break}if(6===l[0]&&i.label<o[1]){i.label=o[1],o=l;break}if(o&&i.label<o[2]){i.label=o[2],i.ops.push(l);break}o[2]&&i.ops.pop(),i.trys.pop();continue}l=t.call(e,i)}catch(e){l=[6,e],r=0}finally{n=o=0}if(5&l[0])throw l[1];return{value:l[0]?l[1]:void 0,done:!0}}}}(this,function(t){return this.app.pluginSettingsManager.add("ai-api",{icon:"ApiOutlined",title:this.t("AI API Gateway"),aclSnippet:"pm.ai-api.configuration"}),this.app.pluginSettingsManager.add("ai-api.config",{title:this.t("Configuration"),Component:s}),(null==(e=this.app.pm.get(n()))?void 0:e.settingsUI)&&e.settingsUI.addPermissionsTab(function(e){e.t;var t=e.TabLayout,n=e.activeRole;return{key:"aiApi",label:"AI API",sort:25,children:o().createElement(t,null,o().createElement(d,{role:n}))}}),[2]})},function(){var t=this,n=arguments;return new Promise(function(r,o){var a=e.apply(t,n);function u(e){i(a,r,o,u,c,"next",e)}function c(e){i(a,r,o,u,c,"throw",e)}u(void 0)})}).call(this)}}],function(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(r.prototype,t),r}(p(e.Plugin))}(),h}()});
|
|
@@ -0,0 +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
|
+
"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 r=n(155),a=n.n(r),l=n(59),o=n(342);function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}function c(e,t,n,r,a,l,o){try{var i=e[l](o),c=i.value}catch(e){n(e);return}i.done?t(c):Promise.resolve(c).then(r,a)}function u(e){return function(){var t=this,n=arguments;return new Promise(function(r,a){var l=e.apply(t,n);function o(e){c(l,r,a,o,i,"next",e)}function i(e){c(l,r,a,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,r,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=(n=a.next()).done)&&(l.push(n.value),!t||l.length!==t);o=!0);}catch(e){i=!0,r=e}finally{try{o||null==a.return||a.return()}finally{if(i)throw r}}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,r,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: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,r&&(a=2&u[0]?r.return:u[0]?r.throw||((a=r.return)&&a.call(r),0):r.next)&&!(a=a.call(r,u[1])).done)return a;switch(r=0,a&&(u=[2&u[0],a.value]),u[0]){case 0:case 1:a=u;break;case 4:return l.label++,{value:u[1],done:!1};case 5:l.label++,r=u[1],u=[0];continue;case 7:u=l.ops.pop(),l.trys.pop();continue;default:if(!(a=(a=l.trys).length>0&&a[a.length-1])&&(6===u[0]||2===u[0])){l=0;continue}if(3===u[0]&&(!a||u[1]>a[0]&&u[1]<a[3])){l.label=u[1];break}if(6===u[0]&&l.label<a[1]){l.label=a[1],a=u;break}if(a&&l.label<a[2]){l.label=a[2],l.ops.push(u);break}a[2]&&l.ops.pop(),l.trys.pop();continue}u=t.call(e,l)}catch(e){u=[6,e],r=0}finally{n=a=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.useAPIClient)(),t=s(l.Form.useForm(),1)[0],n=s((0,r.useState)(!0),2),i=n[0],c=n[1],y=s((0,r.useState)(!1),2),v=y[0],h=y[1],b=s((0,r.useState)([]),2),g=b[0],E=b[1],A=s((0,r.useState)([]),2),I=A[0],S=A[1],w=s((0,r.useState)(""),2),L=w[0],k=w[1];(0,r.useEffect)(function(){P(),k("".concat(window.location.origin,"/api/ai-llm/v1"))},[]);var P=function(){return u(function(){var n,r,a,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==(r=i.data)?void 0:r.data)||[]).map(function(e){return{username:e.username,nickname:e.nickname||e.username}})),[4,e.request({url:"ai:listLLMServices"})];case 4:return S(((null==(u=s.sent())||null==(a=u.data)?void 0:a.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?a().createElement("div",{style:{display:"flex",justifyContent:"center",padding:60}},a().createElement(l.Spin,{size:"large"})):a().createElement("div",{style:{maxWidth:800,margin:"0 auto",padding:"24px 0"}},a().createElement(p,{level:3},"AI API Gateway Configuration"),a().createElement(f,{type:"secondary"},"Configure the OpenAI-compatible API endpoint. External applications can connect using the base URL and a NocoBase API key."),a().createElement(l.Card,{style:{marginBottom:24}},a().createElement(l.Alert,{type:"info",showIcon:!0,message:"API Endpoint",description:a().createElement(l.Space,{direction:"vertical",size:4},a().createElement(d,null,"Base URL:"," ",a().createElement(d,{code:!0,copyable:!0},L)),a().createElement(d,{type:"secondary"},"Use this as the base URL in any OpenAI-compatible client (Cursor, Continue.dev, n8n, etc.)")),style:{marginBottom:16}}),a().createElement(l.Alert,{type:"warning",showIcon:!0,message:"Authentication",description:a().createElement(d,null,"Clients must include a NocoBase API key as a Bearer token:",a().createElement("br",null),a().createElement(d,{code:!0},"Authorization: Bearer ","<your-nocobase-api-key>"),a().createElement("br",null),a().createElement(d,{type:"secondary"},"API keys can be created in Settings → API keys."))})),a().createElement(l.Card,{title:"Configuration"},a().createElement(l.Form,{form:t,layout:"vertical",onFinish:function(){return u(function(){var n;return m(this,function(r){switch(r.label){case 0:h(!0),r.label=1;case 1:return r.trys.push([1,3,4,5]),n=t.getFieldsValue(),[4,e.request({url:"aiApiConfig:save",method:"post",data:n})];case 2:return r.sent(),l.message.success("Configuration saved"),[3,5];case 3:return r.sent(),l.message.error("Failed to save configuration"),[3,5];case 4:return h(!1),[7];case 5:return[2]}})})()},initialValues:{mode:"llm"}},a().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."},a().createElement(l.Radio.Group,null,a().createElement(l.Radio.Button,{value:"llm"},"LLM Proxy"),a().createElement(l.Radio.Button,{value:"agent"},"AI Employee Agent"))),a().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)"},a().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}})})),a().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"}]},a().createElement(l.Select,{allowClear:!0,placeholder:"Select an LLM Service",options:I.map(function(e){return{label:"".concat(e.title," (").concat(e.provider,")"),value:e.name}})})),a().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."},a().createElement(l.Select,{mode:"multiple",allowClear:!0,placeholder:"All enabled services (default)",options:I.map(function(e){return{label:"".concat(e.title," (").concat(e.provider,")"),value:e.name}})})),a().createElement(l.Form.Item,{name:"rateLimitPerMinute",label:"Rate Limit (requests/minute)",tooltip:"Maximum API requests per user per minute. Set 0 for unlimited."},a().createElement(l.InputNumber,{min:0,max:1e4,style:{width:200}})),a().createElement(l.Form.Item,null,a().createElement(l.Button,{type:"primary",htmlType:"submit",loading:v},"Save Configuration")))),a().createElement(l.Card,{title:"Quick Start",style:{marginTop:24}},a().createElement(p,{level:5},"cURL Example"),a().createElement(f,null,a().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 }\''))),a().createElement(p,{level:5},"Python (OpenAI SDK)"),a().createElement(f,null,a().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)'))),a().createElement(p,{level:5},"List Available Models"),a().createElement(f,null,a().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}}]);
|
|
@@ -0,0 +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
|
+
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("@nocobase/client-v2"),require("react"),require("antd"),require("@nocobase/client")):"function"==typeof define&&define.amd?define("plugin-ai-api/client-v2",["@nocobase/client-v2","react","antd","@nocobase/client"],t):"object"==typeof exports?exports["plugin-ai-api/client-v2"]=t(require("@nocobase/client-v2"),require("react"),require("antd"),require("@nocobase/client")):e["plugin-ai-api/client-v2"]=t(e["@nocobase/client-v2"],e.react,e.antd,e["@nocobase/client"])}(self,function(e,t,n,r){return function(){"use strict";var o,i,a,u={342:function(e){e.exports=r},485:function(t){t.exports=e},59:function(e){e.exports=n},155:function(e){e.exports=t}},c={};function l(e){var t=c[e];if(void 0!==t)return t.exports;var n=c[e]={exports:{}};return u[e](n,n.exports,l),n.exports}l.m=u,l.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(t,{a:t}),t},l.d=function(e,t){for(var n in t)l.o(t,n)&&!l.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},l.f={},l.e=function(e){return Promise.all(Object.keys(l.f).reduce(function(t,n){return l.f[n](e,t),t},[]))},l.u=function(e){return""+e+".8991b849cd7c1351.js"},l.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),l.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},p={},l.l=function(e,t,n,r){if(p[e])return void p[e].push(t);if(void 0!==n)for(var o,i,a=document.getElementsByTagName("script"),u=0;u<a.length;u++){var c=a[u];if(c.getAttribute("src")==e||c.getAttribute("data-rspack")=="plugin-ai-api/client-v2:"+n){o=c;break}}o||(i=!0,(o=document.createElement("script")).timeout=120,l.nc&&o.setAttribute("nonce",l.nc),o.setAttribute("data-rspack","plugin-ai-api/client-v2:"+n),o.src=e),p[e]=[t];var f=function(t,n){o.onerror=o.onload=null,clearTimeout(s);var r=p[e];if(delete p[e],o.parentNode&&o.parentNode.removeChild(o),r&&r.forEach(function(e){return e(n)}),t)return t(n)},s=setTimeout(f.bind(null,void 0,{type:"timeout",target:o}),12e4);o.onerror=f.bind(null,o.onerror),o.onload=f.bind(null,o.onload),i&&document.head.appendChild(o)},l.r=function(e){"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.g.importScripts&&(f=l.g.location+"");var p,f,s=l.g.document;if(!f&&s&&(s.currentScript&&"SCRIPT"===s.currentScript.tagName.toUpperCase()&&(f=s.currentScript.src),!f)){var d=s.getElementsByTagName("script");if(d.length)for(var b=d.length-1;b>-1&&(!f||!/^http(s?):/.test(f));)f=d[b--].src}if(!f)throw Error("Automatic publicPath is not supported in this browser");l.p=f.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),o={889:0},l.f.j=function(e,t){var n=l.o(o,e)?o[e]:void 0;if(0!==n)if(n)t.push(n[2]);else{var r=new Promise(function(t,r){n=o[e]=[t,r]});t.push(n[2]=r);var i=l.p+l.u(e),a=Error();l.l(i,function(t){if(l.o(o,e)&&(0!==(n=o[e])&&(o[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),i=t&&t.target&&t.target.src;a.message="Loading chunk "+e+" failed.\n("+r+": "+i+")",a.name="ChunkLoadError",a.type=r,a.request=i,n[1](a)}},"chunk-"+e,e)}},i=function(e,t){var n,r,i=t[0],a=t[1],u=t[2],c=0;if(i.some(function(e){return 0!==o[e]})){for(n in a)l.o(a,n)&&(l.m[n]=a[n]);u&&u(l)}for(e&&e(t);c<i.length;c++)r=i[c],l.o(o,r)&&o[r]&&o[r][0](),o[r]=0},(a=self.webpackChunkplugin_ai_api_client_v2=self.webpackChunkplugin_ai_api_client_v2||[]).forEach(i.bind(null,0)),a.push=i.bind(null,a.push.bind(a));var v={};return!function(){var e="",t="u">typeof document?document.currentScript:null;if(t&&t.src&&(e=t.src.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/")),!e){var n=window.__webpack_public_path__||"";n&&("/"!==n.charAt(n.length-1)&&(n+="/"),e=n+"static/plugins/plugin-ai-api/dist/client-v2/")}if(!e){var r=window.__nocobase_modern_client_prefix__||"v",o="/"+(r=String(r).replace(/^\/+|\/+$/g,"")||"v")+"/";if(!(e=window.__nocobase_public_path__||"")&&window.location&&window.location.pathname){var i=window.location.pathname||"/",a=i.indexOf(o);e=a>=0?i.slice(0,a+1):"/"}e&&(e=e.replace(RegExp("/"+r+"/?$"),"/")),e||(e="/"),"/"!==e.charAt(e.length-1)&&(e+="/"),e+="static/plugins/plugin-ai-api/dist/client-v2/"}l.p=e}(),!function(){l.r(v),l.d(v,{default:function(){return u}});var e=l(485);function t(e,t,n,r,o,i,a){try{var u=e[i](a),c=u.value}catch(e){n(e);return}u.done?t(c):Promise.resolve(c).then(r,o)}function n(e,t,r){return(n=a()?Reflect.construct:function(e,t,n){var r=[null];r.push.apply(r,t);var i=new(Function.bind.apply(e,r));return n&&o(i,n.prototype),i}).apply(null,arguments)}l(155);function r(e){return(r=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function o(e,t){return(o=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function i(e){var t="function"==typeof Map?new Map:void 0;return(i=function(e){if(null===e||-1===Function.toString.call(e).indexOf("[native code]"))return e;if("function"!=typeof e)throw TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,i)}function i(){return n(e,arguments,r(this).constructor)}return i.prototype=Object.create(e.prototype,{constructor:{value:i,enumerable:!1,writable:!0,configurable:!0}}),o(i,e)})(e)}function a(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(e){}return(a=function(){return!!e})()}var u=function(e){var n;if("function"!=typeof e&&null!==e)throw TypeError("Super expression must either be null or a function");function i(){var e,t;if(!(this instanceof i))throw TypeError("Cannot call a class as a function");return e=i,t=arguments,e=r(e),function(e,t){var n;if(t&&("object"==((n=t)&&"u">typeof Symbol&&n.constructor===Symbol?"symbol":typeof n)||"function"==typeof t))return t;if(void 0===e)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(this,a()?Reflect.construct(e,t||[],r(this).constructor):e.apply(this,t))}return i.prototype=Object.create(e&&e.prototype,{constructor:{value:i,writable:!0,configurable:!0}}),e&&o(i,e),n=[{key:"load",value:function(){var e;return(e=function(){return function(e,t){var n,r,o,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]},a=Object.create(("function"==typeof Iterator?Iterator:Object).prototype),u=Object.defineProperty;return u(a,"next",{value:c(0)}),u(a,"throw",{value:c(1)}),u(a,"return",{value:c(2)}),"function"==typeof Symbol&&u(a,Symbol.iterator,{value:function(){return this}}),a;function c(u){return function(c){var l=[u,c];if(n)throw TypeError("Generator is already executing.");for(;a&&(a=0,l[0]&&(i=0)),i;)try{if(n=1,r&&(o=2&l[0]?r.return:l[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,l[1])).done)return o;switch(r=0,o&&(l=[2&l[0],o.value]),l[0]){case 0:case 1:o=l;break;case 4:return i.label++,{value:l[1],done:!1};case 5:i.label++,r=l[1],l=[0];continue;case 7:l=i.ops.pop(),i.trys.pop();continue;default:if(!(o=(o=i.trys).length>0&&o[o.length-1])&&(6===l[0]||2===l[0])){i=0;continue}if(3===l[0]&&(!o||l[1]>o[0]&&l[1]<o[3])){i.label=l[1];break}if(6===l[0]&&i.label<o[1]){i.label=o[1],o=l;break}if(o&&i.label<o[2]){i.label=o[2],i.ops.push(l);break}o[2]&&i.ops.pop(),i.trys.pop();continue}l=t.call(e,i)}catch(e){l=[6,e],r=0}finally{n=o=0}if(5&l[0])throw l[1];return{value:l[0]?l[1]:void 0,done:!0}}}}(this,function(e){return this.pluginSettingsManager.addMenuItem({key:"ai-api",title:this.t("AI API Gateway"),icon:"ApiOutlined",aclSnippet:"pm.ai-api.configuration"}),this.pluginSettingsManager.addPageTabItem({menuKey:"ai-api",key:"index",title:this.t("Configuration"),componentLoader:function(){return l.e("950").then(l.bind(l,693))}}),[2]})},function(){var n=this,r=arguments;return new Promise(function(o,i){var a=e.apply(n,r);function u(e){t(a,o,i,u,c,"next",e)}function c(e){t(a,o,i,u,c,"throw",e)}u(void 0)})}).call(this)}}],function(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(i.prototype,n),i}(i(e.Plugin))}(),v}()});
|
package/dist/externalVersion.js
CHANGED
|
@@ -10,13 +10,14 @@
|
|
|
10
10
|
module.exports = {
|
|
11
11
|
"react": "18.2.0",
|
|
12
12
|
"antd": "5.24.2",
|
|
13
|
-
"@nocobase/client": "2.
|
|
14
|
-
"@nocobase/flow-engine": "2.
|
|
15
|
-
"@nocobase/plugin-acl": "2.
|
|
16
|
-
"@nocobase/
|
|
13
|
+
"@nocobase/client": "2.1.6",
|
|
14
|
+
"@nocobase/flow-engine": "2.1.6",
|
|
15
|
+
"@nocobase/plugin-acl": "2.1.6",
|
|
16
|
+
"@nocobase/client-v2": "2.1.6",
|
|
17
|
+
"@nocobase/server": "2.1.6",
|
|
17
18
|
"dayjs": "1.11.13",
|
|
18
|
-
"@nocobase/database": "2.
|
|
19
|
-
"@nocobase/actions": "2.
|
|
20
|
-
"@nocobase/resourcer": "2.
|
|
21
|
-
"@nocobase/plugin-ai": "2.
|
|
19
|
+
"@nocobase/database": "2.1.6",
|
|
20
|
+
"@nocobase/actions": "2.1.6",
|
|
21
|
+
"@nocobase/resourcer": "2.1.6",
|
|
22
|
+
"@nocobase/plugin-ai": "2.1.6"
|
|
22
23
|
};
|
package/package.json
CHANGED
|
@@ -1,14 +1,32 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "plugin-ai-api",
|
|
3
|
-
"version": "1.0.
|
|
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
|
-
|
|
14
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "plugin-ai-api",
|
|
3
|
+
"version": "1.0.4",
|
|
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
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
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 { useAPIClient } from '@nocobase/client';
|
|
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 = useAPIClient();
|
|
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;
|