plugin-ai-api 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/2c126424a58bcb27.js +1 -0
- package/dist/client/components/AiApiRolePermissions.d.ts +8 -0
- package/dist/client/index.js +1 -1
- package/dist/externalVersion.js +9 -8
- package/dist/server/collections/ai-api-role-permissions.d.ts +2 -0
- package/dist/server/collections/ai-api-role-permissions.js +53 -0
- package/dist/server/middleware/role-permission.d.ts +21 -0
- package/dist/server/middleware/role-permission.js +57 -0
- package/dist/server/plugin.js +1 -1
- package/dist/server/resource/ai-api-config.js +1 -1
- package/dist/server/routes/agent-completions.js +18 -1
- package/dist/server/routes/chat-completions.js +11 -0
- package/dist/server/routes/router.js +18 -1
- package/dist/swagger.d.ts +472 -0
- package/dist/swagger.js +339 -0
- package/package.json +2 -1
- package/plugin-ai-api-1.0.1.tgz +0 -0
- package/plugin-ai-api-1.0.2.tgz +0 -0
- package/nocobase-plugin-ai-api-2.0.20.tgz +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";(self.webpackChunkplugin_ai_api=self.webpackChunkplugin_ai_api||[]).push([["23"],{928:function(e,t,r){r.r(t),r.d(t,{AiApiRolePermissions:function(){return f}});var n=r(156),l=r.n(n),a=r(772),o=r(721);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:"undefined"!=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 d(e,t){var r,n,l,a,o={label:0,sent:function(){if(1&l[0])throw l[1];return l[1]},trys:[],ops:[]};return a={next:i(0),throw:i(1),return:i(2)},"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function i(a){return function(i){var u=[a,i];if(r)throw TypeError("Generator is already executing.");for(;o;)try{if(r=1,n&&(l=2&u[0]?n.return:u[0]?n.throw||((l=n.return)&&l.call(n),0):n.next)&&!(l=l.call(n,u[1])).done)return l;switch(n=0,l&&(u=[2&u[0],l.value]),u[0]){case 0:case 1:l=u;break;case 4:return o.label++,{value:u[1],done:!1};case 5:o.label++,n=u[1],u=[0];continue;case 7:u=o.ops.pop(),o.trys.pop();continue;default:if(!(l=(l=o.trys).length>0&&l[l.length-1])&&(6===u[0]||2===u[0])){o=0;continue}if(3===u[0]&&(!l||u[1]>l[0]&&u[1]<l[3])){o.label=u[1];break}if(6===u[0]&&o.label<l[1]){o.label=l[1],l=u;break}if(l&&o.label<l[2]){o.label=l[2],o.ops.push(u);break}l[2]&&o.ops.pop(),o.trys.pop();continue}u=t.call(e,o)}catch(e){u=[6,e],n=0}finally{r=l=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}}}var m=o.Typography.Text;function f(e){var t,r,i=e.role,u=(0,a.useAPIClient)(),f=p((0,n.useState)(!0),2),y=f[0],b=f[1],h=p((0,n.useState)(!1),2),v=h[0],w=h[1],E=p((0,n.useState)([]),2),g=E[0],A=E[1],O=p((0,n.useState)(null),2),S=O[0],P=O[1],j=null==i?void 0:i.name;(0,n.useEffect)(function(){j&&k()},[j]);var k=(t=c(function(){var e,t,r,n,l,a,o;return d(this,function(i){switch(i.label){case 0:b(!0),i.label=1;case 1:return i.trys.push([1,3,4,5]),[4,Promise.all([u.request({url:"aiApiRolePermissions",params:{filter:{roleName:j},paginate:!1}}),u.request({url:"aiEmployees:list",params:{paginate:!1}})])];case 2:return l=(n=p.apply(void 0,[i.sent(),2]))[0],a=n[1],P((o=null==l||null==(t=l.data)||null==(e=t.data)?void 0:e[0])?{id:o.id,roleName:o.roleName,enabled:!!o.enabled,allowAllEmployees:!1!==o.allowAllEmployees,allowedEmployees:o.allowedEmployees||[]}:{roleName:j,enabled:!1,allowAllEmployees:!0,allowedEmployees:[]}),A(((null==a||null==(r=a.data)?void 0:r.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:",i.sent()),[3,5];case 4:return b(!1),[7];case 5:return[2]}})}),function(){return t.apply(this,arguments)}),I=(r=c(function(e){var t,r,n,l;return d(this,function(a){switch(a.label){case 0:if(!S)return[2];P(t=s({},S,e)),w(!0),a.label=1;case 1:if(a.trys.push([1,6,7,8]),!t.id)return[3,3];return[4,u.request({url:"aiApiRolePermissions/".concat(t.id),method:"PUT",data:t})];case 2:return a.sent(),[3,5];case 3:return[4,u.request({url:"aiApiRolePermissions",method:"POST",data:t})];case 4:var o,i;(null==(l=null==(n=a.sent())||null==(r=n.data)?void 0:r.data)?void 0:l.id)&&P((o=s({},t),i=i={id:l.id},Object.getOwnPropertyDescriptors?Object.defineProperties(o,Object.getOwnPropertyDescriptors(i)):(function(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);r.push.apply(r,n)}return r})(Object(i)).forEach(function(e){Object.defineProperty(o,e,Object.getOwnPropertyDescriptor(i,e))}),o)),a.label=5;case 5:return[3,8];case 6:return console.error("Failed to save AI API role permissions:",a.sent()),P(S),[3,8];case 7:return w(!1),[7];case 8:return[2]}})}),function(e){return r.apply(this,arguments)});return y?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==S?void 0:S.enabled),loading:v,onChange:function(e){return I({enabled:e})}}),l().createElement(m,{strong:!0},"Allow this role to use the AI API")),(null==S?void 0:S.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==S?void 0:S.allowAllEmployees),loading:v,onChange:function(e){return I({allowAllEmployees:e})}}),l().createElement(m,null,"Allow all AI Employees")),!(null==S?void 0:S.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==S?void 0:S.allowedEmployees)||[],options:g.map(function(e){return{label:"".concat(e.nickname," (").concat(e.username,")"),value:e.username}}),onChange:function(e){return I({allowedEmployees:e})},disabled:v})))))}}}]);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Permission tab shown in Settings → Users & Permissions → [Role] → "AI API" tab.
|
|
4
|
+
* Lets admins control whether a role can use the AI API and which AI Employees it may access.
|
|
5
|
+
*/
|
|
6
|
+
export declare function AiApiRolePermissions({ role }: {
|
|
7
|
+
role: any;
|
|
8
|
+
}): React.JSX.Element;
|
package/dist/client/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("@nocobase/client"),require("react"),require("antd")):"function"==typeof define&&define.amd?define("plugin-ai-api",["@nocobase/client","react","antd"],t):"object"==typeof exports?exports["plugin-ai-api"]=t(require("@nocobase/client"),require("react"),require("antd")):e["plugin-ai-api"]=t(e["@nocobase/client"],e.react,e.antd)}(self,function(e,t,n){return function(){var
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("@nocobase/plugin-acl/client"),require("@nocobase/client"),require("react"),require("antd")):"function"==typeof define&&define.amd?define("plugin-ai-api",["@nocobase/plugin-acl/client","@nocobase/client","react","antd"],t):"object"==typeof exports?exports["plugin-ai-api"]=t(require("@nocobase/plugin-acl/client"),require("@nocobase/client"),require("react"),require("antd")):e["plugin-ai-api"]=t(e["@nocobase/plugin-acl/client"],e["@nocobase/client"],e.react,e.antd)}(self,function(e,t,n,r){return function(){var o,i,u,a,c,l,f={581:function(e){e.exports=function(e,t){return"undefined"!=typeof __deoptimization_sideEffect__&&__deoptimization_sideEffect__(e,t),t}},772:function(e){"use strict";e.exports=t},465:function(t){"use strict";t.exports=e},721:function(e){"use strict";e.exports=r},156:function(e){"use strict";e.exports=n}},p={};function s(e){var t=p[e];if(void 0!==t)return t.exports;var n=p[e]={exports:{}};return f[e](n,n.exports,s),n.exports}s.m=f,s.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return s.d(t,{a:t}),t},s.d=function(e,t){for(var n in t)s.o(t,n)&&!s.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},s.f={},s.e=function(e){return Promise.all(Object.keys(s.f).reduce(function(t,n){return s.f[n](e,t),t},[]))},s.u=function(e){return""+({23:"2c126424a58bcb27",503:"824c2f7487ee05fd"})[e]+".js"},s.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),s.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o={},i="plugin-ai-api:",s.l=function(e,t,n,r){if(o[e])return void o[e].push(t);if(void 0!==n)for(var u,a,c=document.getElementsByTagName("script"),l=0;l<c.length;l++){var f=c[l];if(f.getAttribute("src")==e||f.getAttribute("data-webpack")==i+n){u=f;break}}u||(a=!0,(u=document.createElement("script")).charset="utf-8",u.timeout=120,s.nc&&u.setAttribute("nonce",s.nc),u.setAttribute("data-webpack",i+n),u.src=e),o[e]=[t];var p=function(t,n){u.onerror=u.onload=null,clearTimeout(d);var r=o[e];if(delete o[e],u.parentNode&&u.parentNode.removeChild(u),r&&r.forEach(function(e){return e(n)}),t)return t(n)},d=setTimeout(p.bind(null,void 0,{type:"timeout",target:u}),12e4);u.onerror=p.bind(null,u.onerror),u.onload=p.bind(null,u.onload),a&&document.head.appendChild(u)},s.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(u=window.__webpack_public_path__||"/").endsWith("/")||(u+="/"),s.p=u+"static/plugins/plugin-ai-api/dist/client/",a={909:0},s.f.j=function(e,t){var n=s.o(a,e)?a[e]:void 0;if(0!==n)if(n)t.push(n[2]);else{var r=new Promise(function(t,r){n=a[e]=[t,r]});t.push(n[2]=r);var o=s.p+s.u(e),i=Error();s.l(o,function(t){if(s.o(a,e)&&(0!==(n=a[e])&&(a[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;i.message="Loading chunk "+e+" failed.\n("+r+": "+o+")",i.name="ChunkLoadError",i.type=r,i.request=o,n[1](i)}},"chunk-"+e,e)}},c=function(e,t){var n,r,o=t[0],i=t[1],u=t[2],c=0;if(o.some(function(e){return 0!==a[e]})){for(n in i)s.o(i,n)&&(s.m[n]=i[n]);u&&u(s)}for(e&&e(t);c<o.length;c++)r=o[c],s.o(a,r)&&a[r]&&a[r][0](),a[r]=0},(l=self.webpackChunkplugin_ai_api=self.webpackChunkplugin_ai_api||[]).forEach(c.bind(null,0)),l.push=c.bind(null,l.push.bind(l));var d={};return!function(){"use strict";s.r(d),s.d(d,{default:function(){return h}});var e=s(772),t=s(465),n=s.n(t),r=s(156),o=s.n(r),i=s(581);function u(e,t,n,r,o,i,u){try{var a=e[i](u),c=a.value}catch(e){n(e);return}a.done?t(c):Promise.resolve(c).then(r,o)}function a(e,t,n){return(a=p()?Reflect.construct:function(e,t,n){var r=[null];r.push.apply(r,t);var o=new(Function.bind.apply(e,r));return n&&l(o,n.prototype),o}).apply(null,arguments)}function c(e){return(c=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function l(e,t){return(l=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function f(e){var t="function"==typeof Map?new Map:void 0;return(f=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,c(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),l(n,e)})(e)}function p(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(e){}return(p=function(){return!!e})()}var b=o().lazy(function(){return i("imported_c1deu3_component",s.e("503").then(s.bind(s,968)))}),y=(0,e.lazy)(function(){return i("imported_-1fpkg57_component",s.e("23").then(s.bind(s,928)))},"AiApiRolePermissions").AiApiRolePermissions,h=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,n,o,i;if(!(this instanceof r))throw TypeError("Cannot call a class as a function");return t=r,n=arguments,t=c(t),o=e=function(e,t){var n;if(t&&("object"==((n=t)&&"undefined"!=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,p()?Reflect.construct(t,n||[],c(this).constructor):t.apply(this,n)),i=function(e){return e},"t"in o?Object.defineProperty(o,"t",{value:i,enumerable:!0,configurable:!0,writable:!0}):o.t=i,e}return r.prototype=Object.create(e&&e.prototype,{constructor:{value:r,writable:!0,configurable:!0}}),e&&l(r,e),t=[{key:"load",value:function(){var e,t=this;return(e=function(){var e;return function(e,t){var n,r,o,i,u={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function a(i){return function(a){var c=[i,a];if(n)throw TypeError("Generator is already executing.");for(;u;)try{if(n=1,r&&(o=2&c[0]?r.return:c[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,c[1])).done)return o;switch(r=0,o&&(c=[2&c[0],o.value]),c[0]){case 0:case 1:o=c;break;case 4:return u.label++,{value:c[1],done:!1};case 5:u.label++,r=c[1],c=[0];continue;case 7:c=u.ops.pop(),u.trys.pop();continue;default:if(!(o=(o=u.trys).length>0&&o[o.length-1])&&(6===c[0]||2===c[0])){u=0;continue}if(3===c[0]&&(!o||c[1]>o[0]&&c[1]<o[3])){u.label=c[1];break}if(6===c[0]&&u.label<o[1]){u.label=o[1],o=c;break}if(o&&u.label<o[2]){u.label=o[2],u.ops.push(c);break}o[2]&&u.ops.pop(),u.trys.pop();continue}c=t.call(e,u)}catch(e){c=[6,e],r=0}finally{n=o=0}if(5&c[0])throw c[1];return{value:c[0]?c[1]:void 0,done:!0}}}}(this,function(r){return t.app.pluginSettingsManager.add("ai-api",{icon:"ApiOutlined",title:t.t("AI API Gateway"),aclSnippet:"pm.plugin-ai-api.configuration"}),t.app.pluginSettingsManager.add("ai-api.config",{title:t.t("Configuration"),Component:b}),(null==(e=t.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(y,{role:n}))}}),[2]})},function(){var t=this,n=arguments;return new Promise(function(r,o){var i=e.apply(t,n);function a(e){u(i,r,o,a,c,"next",e)}function c(e){u(i,r,o,a,c,"throw",e)}a(void 0)})})()}}],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}(f(e.Plugin))}(),d}()});
|
package/dist/externalVersion.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
-
"react": "18.
|
|
2
|
+
"react": "18.2.0",
|
|
3
3
|
"antd": "5.24.2",
|
|
4
|
-
"@nocobase/client": "2.0.
|
|
5
|
-
"@nocobase/flow-engine": "2.0.
|
|
6
|
-
"@nocobase/
|
|
7
|
-
"@nocobase/
|
|
8
|
-
"@nocobase/
|
|
9
|
-
"@nocobase/
|
|
10
|
-
"@nocobase/
|
|
4
|
+
"@nocobase/client": "2.0.32",
|
|
5
|
+
"@nocobase/flow-engine": "2.0.32",
|
|
6
|
+
"@nocobase/plugin-acl": "2.0.32",
|
|
7
|
+
"@nocobase/server": "2.0.32",
|
|
8
|
+
"@nocobase/database": "2.0.32",
|
|
9
|
+
"@nocobase/actions": "2.0.32",
|
|
10
|
+
"@nocobase/resourcer": "2.0.32",
|
|
11
|
+
"@nocobase/plugin-ai": "2.0.32"
|
|
11
12
|
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var ai_api_role_permissions_exports = {};
|
|
19
|
+
__export(ai_api_role_permissions_exports, {
|
|
20
|
+
default: () => ai_api_role_permissions_default
|
|
21
|
+
});
|
|
22
|
+
module.exports = __toCommonJS(ai_api_role_permissions_exports);
|
|
23
|
+
var import_database = require("@nocobase/database");
|
|
24
|
+
var ai_api_role_permissions_default = (0, import_database.defineCollection)({
|
|
25
|
+
name: "aiApiRolePermissions",
|
|
26
|
+
autoGenId: true,
|
|
27
|
+
fields: [
|
|
28
|
+
{
|
|
29
|
+
name: "roleName",
|
|
30
|
+
type: "string",
|
|
31
|
+
unique: true,
|
|
32
|
+
comment: "Role name (links to roles.name)"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "enabled",
|
|
36
|
+
type: "boolean",
|
|
37
|
+
defaultValue: false,
|
|
38
|
+
comment: "Whether this role can use the AI API at all"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "allowAllEmployees",
|
|
42
|
+
type: "boolean",
|
|
43
|
+
defaultValue: true,
|
|
44
|
+
comment: "If true, the role may use any AI Employee. If false, only those in allowedEmployees."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "allowedEmployees",
|
|
48
|
+
type: "json",
|
|
49
|
+
defaultValue: [],
|
|
50
|
+
comment: "Array of AI Employee usernames this role is allowed to use (when allowAllEmployees=false)"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Context } from '@nocobase/actions';
|
|
2
|
+
/**
|
|
3
|
+
* Check whether the authenticated role is allowed to use the AI API.
|
|
4
|
+
* Loads the permission record and stores it in ctx.state.aiApiRolePermission.
|
|
5
|
+
*
|
|
6
|
+
* Returns true if access is allowed (caller may proceed).
|
|
7
|
+
* Returns false if access is denied (403 already written to ctx, caller must return).
|
|
8
|
+
*
|
|
9
|
+
* The 'root' and 'admin' roles always bypass the check.
|
|
10
|
+
*/
|
|
11
|
+
export declare function checkRolePermission(ctx: Context): Promise<boolean>;
|
|
12
|
+
/**
|
|
13
|
+
* Check whether the current role is allowed to use a specific AI Employee.
|
|
14
|
+
* Must be called after checkRolePermission (so ctx.state.aiApiRolePermission is set).
|
|
15
|
+
*
|
|
16
|
+
* Returns true when:
|
|
17
|
+
* - Role is admin/root (no permission record stored)
|
|
18
|
+
* - allowAllEmployees is true
|
|
19
|
+
* - The employeeUsername is in the allowedEmployees list
|
|
20
|
+
*/
|
|
21
|
+
export declare function checkEmployeeAccess(ctx: Context, employeeUsername: string): boolean;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var role_permission_exports = {};
|
|
19
|
+
__export(role_permission_exports, {
|
|
20
|
+
checkEmployeeAccess: () => checkEmployeeAccess,
|
|
21
|
+
checkRolePermission: () => checkRolePermission
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(role_permission_exports);
|
|
24
|
+
var import_openai_format = require("../utils/openai-format");
|
|
25
|
+
async function checkRolePermission(ctx) {
|
|
26
|
+
var _a;
|
|
27
|
+
const roleName = ((_a = ctx.state.currentRoles) == null ? void 0 : _a[0]) || "member";
|
|
28
|
+
if (roleName === "root" || roleName === "admin") {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
const record = await ctx.db.getRepository("aiApiRolePermissions").findOne({
|
|
32
|
+
filter: { roleName }
|
|
33
|
+
});
|
|
34
|
+
if (!(record == null ? void 0 : record.enabled)) {
|
|
35
|
+
ctx.status = 403;
|
|
36
|
+
ctx.body = (0, import_openai_format.toOpenAIError)(
|
|
37
|
+
403,
|
|
38
|
+
`Role '${roleName}' is not authorized to use the AI API. An admin must enable access in Settings \u2192 Users & Permissions \u2192 [Role] \u2192 AI API.`,
|
|
39
|
+
"permission_denied",
|
|
40
|
+
"role_not_permitted"
|
|
41
|
+
);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
ctx.state.aiApiRolePermission = record;
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
function checkEmployeeAccess(ctx, employeeUsername) {
|
|
48
|
+
const perm = ctx.state.aiApiRolePermission;
|
|
49
|
+
if (!perm) return true;
|
|
50
|
+
if (perm.allowAllEmployees) return true;
|
|
51
|
+
return (perm.allowedEmployees || []).includes(employeeUsername);
|
|
52
|
+
}
|
|
53
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
54
|
+
0 && (module.exports = {
|
|
55
|
+
checkEmployeeAccess,
|
|
56
|
+
checkRolePermission
|
|
57
|
+
});
|
package/dist/server/plugin.js
CHANGED
|
@@ -51,7 +51,7 @@ class PluginAiApiServer extends import_server.Plugin {
|
|
|
51
51
|
this.app.resourceManager.define(import_ai_api_config.default);
|
|
52
52
|
this.app.acl.registerSnippet({
|
|
53
53
|
name: `pm.${this.name}.configuration`,
|
|
54
|
-
actions: ["aiApiConfig:*"]
|
|
54
|
+
actions: ["aiApiConfig:*", "aiApiRolePermissions:*"]
|
|
55
55
|
});
|
|
56
56
|
this.gcInterval = setInterval(() => this.rateLimiter.gc(), 5 * 60 * 1e3);
|
|
57
57
|
this.gcInterval.unref();
|
|
@@ -41,7 +41,7 @@ const aiApiConfigResource = {
|
|
|
41
41
|
await next();
|
|
42
42
|
},
|
|
43
43
|
async save(ctx, next) {
|
|
44
|
-
const values = ctx.action.params.values || {};
|
|
44
|
+
const values = ctx.action.params.values || ctx.request.body || {};
|
|
45
45
|
const repo = ctx.db.getRepository("aiApiConfig");
|
|
46
46
|
let config = await repo.findOne();
|
|
47
47
|
if (!config) {
|
|
@@ -32,6 +32,7 @@ __export(agent_completions_exports, {
|
|
|
32
32
|
module.exports = __toCommonJS(agent_completions_exports);
|
|
33
33
|
var import_openai_format = require("../utils/openai-format");
|
|
34
34
|
var import_resolve_service = require("../utils/resolve-service");
|
|
35
|
+
var import_role_permission = require("../middleware/role-permission");
|
|
35
36
|
async function handleAgentCompletions(ctx, plugin) {
|
|
36
37
|
var _a;
|
|
37
38
|
const body = ctx.request.body;
|
|
@@ -84,6 +85,16 @@ async function handleAgentCompletions(ctx, plugin) {
|
|
|
84
85
|
return;
|
|
85
86
|
}
|
|
86
87
|
const employeeUsername = config.defaultAiEmployee;
|
|
88
|
+
if (!(0, import_role_permission.checkEmployeeAccess)(ctx, employeeUsername)) {
|
|
89
|
+
ctx.status = 403;
|
|
90
|
+
ctx.body = (0, import_openai_format.toOpenAIError)(
|
|
91
|
+
403,
|
|
92
|
+
`Role is not permitted to use AI Employee '${employeeUsername}'. An admin must grant access in Settings \u2192 Users & Permissions \u2192 [Role] \u2192 AI API.`,
|
|
93
|
+
"permission_denied",
|
|
94
|
+
"employee_not_permitted"
|
|
95
|
+
);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
87
98
|
const wantStream = body.stream === true;
|
|
88
99
|
try {
|
|
89
100
|
const employeeRecord = await ctx.db.getRepository("aiEmployees").findOne({
|
|
@@ -122,7 +133,7 @@ async function handleAgentCompletions(ctx, plugin) {
|
|
|
122
133
|
try {
|
|
123
134
|
const mod = await import(
|
|
124
135
|
/* webpackIgnore: true */
|
|
125
|
-
"@nocobase/plugin-ai/
|
|
136
|
+
"@nocobase/plugin-ai/dist/server/ai-employees/ai-employee.js"
|
|
126
137
|
);
|
|
127
138
|
AIEmployee = mod.AIEmployee;
|
|
128
139
|
} catch (importErr) {
|
|
@@ -136,6 +147,12 @@ async function handleAgentCompletions(ctx, plugin) {
|
|
|
136
147
|
ctx.body = (0, import_openai_format.toOpenAIError)(500, "Agent mode unavailable: AIEmployee class not exported by plugin-ai", "server_error");
|
|
137
148
|
return;
|
|
138
149
|
}
|
|
150
|
+
if (!ctx.get("x-timezone")) {
|
|
151
|
+
ctx.req.headers["x-timezone"] = "UTC";
|
|
152
|
+
}
|
|
153
|
+
if (!ctx.get("x-locale")) {
|
|
154
|
+
ctx.req.headers["x-locale"] = "en-US";
|
|
155
|
+
}
|
|
139
156
|
if (wantStream) {
|
|
140
157
|
ctx.set({
|
|
141
158
|
"Content-Type": "text/event-stream",
|
|
@@ -22,6 +22,7 @@ __export(chat_completions_exports, {
|
|
|
22
22
|
module.exports = __toCommonJS(chat_completions_exports);
|
|
23
23
|
var import_openai_format = require("../utils/openai-format");
|
|
24
24
|
var import_resolve_service = require("../utils/resolve-service");
|
|
25
|
+
var import_role_permission = require("../middleware/role-permission");
|
|
25
26
|
async function handleChatCompletions(ctx, plugin) {
|
|
26
27
|
var _a;
|
|
27
28
|
const body = ctx.request.body;
|
|
@@ -112,6 +113,16 @@ async function handleChatCompletions(ctx, plugin) {
|
|
|
112
113
|
});
|
|
113
114
|
let systemPrompt = "";
|
|
114
115
|
if (config == null ? void 0 : config.defaultAiEmployee) {
|
|
116
|
+
if (!(0, import_role_permission.checkEmployeeAccess)(ctx, config.defaultAiEmployee)) {
|
|
117
|
+
ctx.status = 403;
|
|
118
|
+
ctx.body = (0, import_openai_format.toOpenAIError)(
|
|
119
|
+
403,
|
|
120
|
+
`Role is not permitted to use AI Employee '${config.defaultAiEmployee}'. An admin must grant access in Settings \u2192 Users & Permissions \u2192 [Role] \u2192 AI API.`,
|
|
121
|
+
"permission_denied",
|
|
122
|
+
"employee_not_permitted"
|
|
123
|
+
);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
115
126
|
const employee = await ctx.db.getRepository("aiEmployees").findOne({
|
|
116
127
|
filter: { username: config.defaultAiEmployee }
|
|
117
128
|
});
|
|
@@ -39,6 +39,7 @@ var import_agent_completions = require("./agent-completions");
|
|
|
39
39
|
var import_embeddings = require("./embeddings");
|
|
40
40
|
var import_openai_format = require("../utils/openai-format");
|
|
41
41
|
var import_rate_limit = require("../middleware/rate-limit");
|
|
42
|
+
var import_role_permission = require("../middleware/role-permission");
|
|
42
43
|
const API_PREFIX = "/api/ai-llm/v1";
|
|
43
44
|
function createAiLlmRouter(plugin) {
|
|
44
45
|
const checkRateLimit = (0, import_rate_limit.createRateLimitMiddleware)(plugin.rateLimiter);
|
|
@@ -48,6 +49,7 @@ function createAiLlmRouter(plugin) {
|
|
|
48
49
|
if (!path.startsWith(API_PREFIX)) {
|
|
49
50
|
return next();
|
|
50
51
|
}
|
|
52
|
+
ctx.withoutDataWrapping = true;
|
|
51
53
|
const subPath = path.substring(API_PREFIX.length);
|
|
52
54
|
ctx.set("Access-Control-Allow-Origin", "*");
|
|
53
55
|
ctx.set("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
@@ -77,6 +79,11 @@ function createAiLlmRouter(plugin) {
|
|
|
77
79
|
logRequest(ctx, requestId, "-", "auth_failed", 0);
|
|
78
80
|
return;
|
|
79
81
|
}
|
|
82
|
+
const permitted = await (0, import_role_permission.checkRolePermission)(ctx);
|
|
83
|
+
if (!permitted) {
|
|
84
|
+
logRequest(ctx, requestId, "-", "forbidden", 0);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
80
87
|
const allowed = await checkRateLimit(ctx);
|
|
81
88
|
if (!allowed) {
|
|
82
89
|
logRequest(ctx, requestId, "-", "rate_limited", 0);
|
|
@@ -97,7 +104,17 @@ function createAiLlmRouter(plugin) {
|
|
|
97
104
|
return;
|
|
98
105
|
}
|
|
99
106
|
if (method === "POST" && subPath === "/completions") {
|
|
100
|
-
await (
|
|
107
|
+
const completionsMode = await resolveMode(ctx);
|
|
108
|
+
if (completionsMode === "agent") {
|
|
109
|
+
const reqBody = ctx.request.body;
|
|
110
|
+
if ((reqBody == null ? void 0 : reqBody.prompt) !== void 0) {
|
|
111
|
+
const prompt = typeof reqBody.prompt === "string" ? reqBody.prompt : Array.isArray(reqBody.prompt) ? reqBody.prompt.join("\n") : String(reqBody.prompt);
|
|
112
|
+
ctx.request.body = { ...reqBody, messages: [{ role: "user", content: prompt }] };
|
|
113
|
+
}
|
|
114
|
+
await (0, import_agent_completions.handleAgentCompletions)(ctx, plugin);
|
|
115
|
+
} else {
|
|
116
|
+
await (0, import_completions.handleCompletions)(ctx, plugin);
|
|
117
|
+
}
|
|
101
118
|
logRequest(ctx, requestId, model, "ok", Date.now() - t0);
|
|
102
119
|
return;
|
|
103
120
|
}
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
info: {
|
|
3
|
+
title: string;
|
|
4
|
+
description: string;
|
|
5
|
+
};
|
|
6
|
+
tags: {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
}[];
|
|
10
|
+
paths: {
|
|
11
|
+
'/aiApiConfig:get': {
|
|
12
|
+
get: {
|
|
13
|
+
tags: string[];
|
|
14
|
+
summary: string;
|
|
15
|
+
security: {
|
|
16
|
+
BearerAuth: any[];
|
|
17
|
+
}[];
|
|
18
|
+
responses: {
|
|
19
|
+
200: {
|
|
20
|
+
description: string;
|
|
21
|
+
content: {
|
|
22
|
+
'application/json': {
|
|
23
|
+
schema: {
|
|
24
|
+
$ref: string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
'/aiApiConfig:save': {
|
|
33
|
+
post: {
|
|
34
|
+
tags: string[];
|
|
35
|
+
summary: string;
|
|
36
|
+
security: {
|
|
37
|
+
BearerAuth: any[];
|
|
38
|
+
}[];
|
|
39
|
+
requestBody: {
|
|
40
|
+
required: boolean;
|
|
41
|
+
content: {
|
|
42
|
+
'application/json': {
|
|
43
|
+
schema: {
|
|
44
|
+
$ref: string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
responses: {
|
|
50
|
+
200: {
|
|
51
|
+
description: string;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
'/ai-llm/v1/models': {
|
|
57
|
+
get: {
|
|
58
|
+
tags: string[];
|
|
59
|
+
summary: string;
|
|
60
|
+
description: string;
|
|
61
|
+
security: {
|
|
62
|
+
BearerAuth: any[];
|
|
63
|
+
}[];
|
|
64
|
+
responses: {
|
|
65
|
+
200: {
|
|
66
|
+
description: string;
|
|
67
|
+
content: {
|
|
68
|
+
'application/json': {
|
|
69
|
+
schema: {
|
|
70
|
+
type: string;
|
|
71
|
+
properties: {
|
|
72
|
+
object: {
|
|
73
|
+
type: string;
|
|
74
|
+
example: string;
|
|
75
|
+
};
|
|
76
|
+
data: {
|
|
77
|
+
type: string;
|
|
78
|
+
items: {
|
|
79
|
+
$ref: string;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
'/ai-llm/v1/models/{id}': {
|
|
91
|
+
get: {
|
|
92
|
+
tags: string[];
|
|
93
|
+
summary: string;
|
|
94
|
+
security: {
|
|
95
|
+
BearerAuth: any[];
|
|
96
|
+
}[];
|
|
97
|
+
parameters: {
|
|
98
|
+
name: string;
|
|
99
|
+
in: string;
|
|
100
|
+
required: boolean;
|
|
101
|
+
description: string;
|
|
102
|
+
schema: {
|
|
103
|
+
type: string;
|
|
104
|
+
example: string;
|
|
105
|
+
};
|
|
106
|
+
}[];
|
|
107
|
+
responses: {
|
|
108
|
+
200: {
|
|
109
|
+
description: string;
|
|
110
|
+
content: {
|
|
111
|
+
'application/json': {
|
|
112
|
+
schema: {
|
|
113
|
+
$ref: string;
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
404: {
|
|
119
|
+
description: string;
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
'/ai-llm/v1/chat/completions': {
|
|
125
|
+
post: {
|
|
126
|
+
tags: string[];
|
|
127
|
+
summary: string;
|
|
128
|
+
description: string;
|
|
129
|
+
security: {
|
|
130
|
+
BearerAuth: any[];
|
|
131
|
+
}[];
|
|
132
|
+
parameters: {
|
|
133
|
+
name: string;
|
|
134
|
+
in: string;
|
|
135
|
+
required: boolean;
|
|
136
|
+
description: string;
|
|
137
|
+
schema: {
|
|
138
|
+
type: string;
|
|
139
|
+
enum: string[];
|
|
140
|
+
default: string;
|
|
141
|
+
};
|
|
142
|
+
}[];
|
|
143
|
+
requestBody: {
|
|
144
|
+
required: boolean;
|
|
145
|
+
content: {
|
|
146
|
+
'application/json': {
|
|
147
|
+
schema: {
|
|
148
|
+
$ref: string;
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
responses: {
|
|
154
|
+
200: {
|
|
155
|
+
description: string;
|
|
156
|
+
content: {
|
|
157
|
+
'application/json': {
|
|
158
|
+
schema: {
|
|
159
|
+
$ref: string;
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
'text/event-stream': {
|
|
163
|
+
schema: {
|
|
164
|
+
type: string;
|
|
165
|
+
description: string;
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
429: {
|
|
171
|
+
description: string;
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
'/ai-llm/v1/completions': {
|
|
177
|
+
post: {
|
|
178
|
+
tags: string[];
|
|
179
|
+
summary: string;
|
|
180
|
+
description: string;
|
|
181
|
+
security: {
|
|
182
|
+
BearerAuth: any[];
|
|
183
|
+
}[];
|
|
184
|
+
requestBody: {
|
|
185
|
+
required: boolean;
|
|
186
|
+
content: {
|
|
187
|
+
'application/json': {
|
|
188
|
+
schema: {
|
|
189
|
+
type: string;
|
|
190
|
+
properties: {
|
|
191
|
+
model: {
|
|
192
|
+
type: string;
|
|
193
|
+
};
|
|
194
|
+
prompt: {
|
|
195
|
+
type: string;
|
|
196
|
+
};
|
|
197
|
+
max_tokens: {
|
|
198
|
+
type: string;
|
|
199
|
+
};
|
|
200
|
+
stream: {
|
|
201
|
+
type: string;
|
|
202
|
+
default: boolean;
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
required: string[];
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
};
|
|
210
|
+
responses: {
|
|
211
|
+
200: {
|
|
212
|
+
description: string;
|
|
213
|
+
};
|
|
214
|
+
429: {
|
|
215
|
+
description: string;
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
'/ai-llm/v1/embeddings': {
|
|
221
|
+
post: {
|
|
222
|
+
tags: string[];
|
|
223
|
+
summary: string;
|
|
224
|
+
description: string;
|
|
225
|
+
security: {
|
|
226
|
+
BearerAuth: any[];
|
|
227
|
+
}[];
|
|
228
|
+
requestBody: {
|
|
229
|
+
required: boolean;
|
|
230
|
+
content: {
|
|
231
|
+
'application/json': {
|
|
232
|
+
schema: {
|
|
233
|
+
type: string;
|
|
234
|
+
properties: {
|
|
235
|
+
model: {
|
|
236
|
+
type: string;
|
|
237
|
+
example: string;
|
|
238
|
+
};
|
|
239
|
+
input: {
|
|
240
|
+
oneOf: ({
|
|
241
|
+
type: string;
|
|
242
|
+
items?: undefined;
|
|
243
|
+
} | {
|
|
244
|
+
type: string;
|
|
245
|
+
items: {
|
|
246
|
+
type: string;
|
|
247
|
+
};
|
|
248
|
+
})[];
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
required: string[];
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
responses: {
|
|
257
|
+
200: {
|
|
258
|
+
description: string;
|
|
259
|
+
content: {
|
|
260
|
+
'application/json': {
|
|
261
|
+
schema: {
|
|
262
|
+
type: string;
|
|
263
|
+
properties: {
|
|
264
|
+
object: {
|
|
265
|
+
type: string;
|
|
266
|
+
example: string;
|
|
267
|
+
};
|
|
268
|
+
data: {
|
|
269
|
+
type: string;
|
|
270
|
+
items: {
|
|
271
|
+
type: string;
|
|
272
|
+
properties: {
|
|
273
|
+
object: {
|
|
274
|
+
type: string;
|
|
275
|
+
example: string;
|
|
276
|
+
};
|
|
277
|
+
embedding: {
|
|
278
|
+
type: string;
|
|
279
|
+
items: {
|
|
280
|
+
type: string;
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
index: {
|
|
284
|
+
type: string;
|
|
285
|
+
};
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
model: {
|
|
290
|
+
type: string;
|
|
291
|
+
};
|
|
292
|
+
usage: {
|
|
293
|
+
type: string;
|
|
294
|
+
properties: {
|
|
295
|
+
prompt_tokens: {
|
|
296
|
+
type: string;
|
|
297
|
+
};
|
|
298
|
+
total_tokens: {
|
|
299
|
+
type: string;
|
|
300
|
+
};
|
|
301
|
+
};
|
|
302
|
+
};
|
|
303
|
+
};
|
|
304
|
+
};
|
|
305
|
+
};
|
|
306
|
+
};
|
|
307
|
+
};
|
|
308
|
+
429: {
|
|
309
|
+
description: string;
|
|
310
|
+
};
|
|
311
|
+
};
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
};
|
|
315
|
+
components: {
|
|
316
|
+
schemas: {
|
|
317
|
+
AiApiConfig: {
|
|
318
|
+
type: string;
|
|
319
|
+
properties: {
|
|
320
|
+
mode: {
|
|
321
|
+
type: string;
|
|
322
|
+
enum: string[];
|
|
323
|
+
description: string;
|
|
324
|
+
};
|
|
325
|
+
defaultAiEmployee: {
|
|
326
|
+
type: string;
|
|
327
|
+
description: string;
|
|
328
|
+
};
|
|
329
|
+
defaultLlmService: {
|
|
330
|
+
type: string;
|
|
331
|
+
description: string;
|
|
332
|
+
};
|
|
333
|
+
enabledLlmServices: {
|
|
334
|
+
type: string;
|
|
335
|
+
items: {
|
|
336
|
+
type: string;
|
|
337
|
+
};
|
|
338
|
+
description: string;
|
|
339
|
+
};
|
|
340
|
+
rateLimitPerMinute: {
|
|
341
|
+
type: string;
|
|
342
|
+
description: string;
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
};
|
|
346
|
+
ModelObject: {
|
|
347
|
+
type: string;
|
|
348
|
+
properties: {
|
|
349
|
+
id: {
|
|
350
|
+
type: string;
|
|
351
|
+
example: string;
|
|
352
|
+
description: string;
|
|
353
|
+
};
|
|
354
|
+
object: {
|
|
355
|
+
type: string;
|
|
356
|
+
example: string;
|
|
357
|
+
};
|
|
358
|
+
created: {
|
|
359
|
+
type: string;
|
|
360
|
+
};
|
|
361
|
+
owned_by: {
|
|
362
|
+
type: string;
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
};
|
|
366
|
+
ChatMessage: {
|
|
367
|
+
type: string;
|
|
368
|
+
properties: {
|
|
369
|
+
role: {
|
|
370
|
+
type: string;
|
|
371
|
+
enum: string[];
|
|
372
|
+
};
|
|
373
|
+
content: {
|
|
374
|
+
type: string;
|
|
375
|
+
};
|
|
376
|
+
name: {
|
|
377
|
+
type: string;
|
|
378
|
+
};
|
|
379
|
+
};
|
|
380
|
+
required: string[];
|
|
381
|
+
};
|
|
382
|
+
ChatCompletionRequest: {
|
|
383
|
+
type: string;
|
|
384
|
+
properties: {
|
|
385
|
+
model: {
|
|
386
|
+
type: string;
|
|
387
|
+
example: string;
|
|
388
|
+
};
|
|
389
|
+
messages: {
|
|
390
|
+
type: string;
|
|
391
|
+
items: {
|
|
392
|
+
$ref: string;
|
|
393
|
+
};
|
|
394
|
+
};
|
|
395
|
+
stream: {
|
|
396
|
+
type: string;
|
|
397
|
+
default: boolean;
|
|
398
|
+
};
|
|
399
|
+
temperature: {
|
|
400
|
+
type: string;
|
|
401
|
+
minimum: number;
|
|
402
|
+
maximum: number;
|
|
403
|
+
};
|
|
404
|
+
max_tokens: {
|
|
405
|
+
type: string;
|
|
406
|
+
};
|
|
407
|
+
top_p: {
|
|
408
|
+
type: string;
|
|
409
|
+
};
|
|
410
|
+
frequency_penalty: {
|
|
411
|
+
type: string;
|
|
412
|
+
};
|
|
413
|
+
presence_penalty: {
|
|
414
|
+
type: string;
|
|
415
|
+
};
|
|
416
|
+
};
|
|
417
|
+
required: string[];
|
|
418
|
+
};
|
|
419
|
+
ChatCompletionResponse: {
|
|
420
|
+
type: string;
|
|
421
|
+
properties: {
|
|
422
|
+
id: {
|
|
423
|
+
type: string;
|
|
424
|
+
};
|
|
425
|
+
object: {
|
|
426
|
+
type: string;
|
|
427
|
+
example: string;
|
|
428
|
+
};
|
|
429
|
+
created: {
|
|
430
|
+
type: string;
|
|
431
|
+
};
|
|
432
|
+
model: {
|
|
433
|
+
type: string;
|
|
434
|
+
};
|
|
435
|
+
choices: {
|
|
436
|
+
type: string;
|
|
437
|
+
items: {
|
|
438
|
+
type: string;
|
|
439
|
+
properties: {
|
|
440
|
+
index: {
|
|
441
|
+
type: string;
|
|
442
|
+
};
|
|
443
|
+
message: {
|
|
444
|
+
$ref: string;
|
|
445
|
+
};
|
|
446
|
+
finish_reason: {
|
|
447
|
+
type: string;
|
|
448
|
+
enum: string[];
|
|
449
|
+
};
|
|
450
|
+
};
|
|
451
|
+
};
|
|
452
|
+
};
|
|
453
|
+
usage: {
|
|
454
|
+
type: string;
|
|
455
|
+
properties: {
|
|
456
|
+
prompt_tokens: {
|
|
457
|
+
type: string;
|
|
458
|
+
};
|
|
459
|
+
completion_tokens: {
|
|
460
|
+
type: string;
|
|
461
|
+
};
|
|
462
|
+
total_tokens: {
|
|
463
|
+
type: string;
|
|
464
|
+
};
|
|
465
|
+
};
|
|
466
|
+
};
|
|
467
|
+
};
|
|
468
|
+
};
|
|
469
|
+
};
|
|
470
|
+
};
|
|
471
|
+
};
|
|
472
|
+
export default _default;
|
package/dist/swagger.js
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var swagger_exports = {};
|
|
19
|
+
__export(swagger_exports, {
|
|
20
|
+
default: () => swagger_default
|
|
21
|
+
});
|
|
22
|
+
module.exports = __toCommonJS(swagger_exports);
|
|
23
|
+
var swagger_default = {
|
|
24
|
+
info: {
|
|
25
|
+
title: "NocoBase API - AI API Plugin",
|
|
26
|
+
description: "OpenAI-compatible AI gateway and configuration API"
|
|
27
|
+
},
|
|
28
|
+
tags: [
|
|
29
|
+
{ name: "aiApiConfig", description: "AI API configuration management" },
|
|
30
|
+
{ name: "ai-llm", description: "OpenAI-compatible LLM gateway (prefix: /api/ai-llm/v1)" }
|
|
31
|
+
],
|
|
32
|
+
paths: {
|
|
33
|
+
"/aiApiConfig:get": {
|
|
34
|
+
get: {
|
|
35
|
+
tags: ["aiApiConfig"],
|
|
36
|
+
summary: "Get AI API configuration",
|
|
37
|
+
security: [{ BearerAuth: [] }],
|
|
38
|
+
responses: {
|
|
39
|
+
200: {
|
|
40
|
+
description: "Current AI API configuration",
|
|
41
|
+
content: {
|
|
42
|
+
"application/json": {
|
|
43
|
+
schema: { $ref: "#/components/schemas/AiApiConfig" }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"/aiApiConfig:save": {
|
|
51
|
+
post: {
|
|
52
|
+
tags: ["aiApiConfig"],
|
|
53
|
+
summary: "Save AI API configuration",
|
|
54
|
+
security: [{ BearerAuth: [] }],
|
|
55
|
+
requestBody: {
|
|
56
|
+
required: true,
|
|
57
|
+
content: {
|
|
58
|
+
"application/json": {
|
|
59
|
+
schema: { $ref: "#/components/schemas/AiApiConfig" }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
responses: {
|
|
64
|
+
200: { description: "Configuration saved successfully" }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"/ai-llm/v1/models": {
|
|
69
|
+
get: {
|
|
70
|
+
tags: ["ai-llm"],
|
|
71
|
+
summary: "List available models",
|
|
72
|
+
description: "Returns all LLM models available across registered services. Model IDs are formatted as `serviceName/modelId`.",
|
|
73
|
+
security: [{ BearerAuth: [] }],
|
|
74
|
+
responses: {
|
|
75
|
+
200: {
|
|
76
|
+
description: "List of available models",
|
|
77
|
+
content: {
|
|
78
|
+
"application/json": {
|
|
79
|
+
schema: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
object: { type: "string", example: "list" },
|
|
83
|
+
data: {
|
|
84
|
+
type: "array",
|
|
85
|
+
items: { $ref: "#/components/schemas/ModelObject" }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"/ai-llm/v1/models/{id}": {
|
|
96
|
+
get: {
|
|
97
|
+
tags: ["ai-llm"],
|
|
98
|
+
summary: "Get model details",
|
|
99
|
+
security: [{ BearerAuth: [] }],
|
|
100
|
+
parameters: [
|
|
101
|
+
{
|
|
102
|
+
name: "id",
|
|
103
|
+
in: "path",
|
|
104
|
+
required: true,
|
|
105
|
+
description: "Model ID in format `serviceName/modelId`",
|
|
106
|
+
schema: { type: "string", example: "openai/gpt-4o" }
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
responses: {
|
|
110
|
+
200: {
|
|
111
|
+
description: "Model details",
|
|
112
|
+
content: {
|
|
113
|
+
"application/json": {
|
|
114
|
+
schema: { $ref: "#/components/schemas/ModelObject" }
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
404: { description: "Model not found" }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"/ai-llm/v1/chat/completions": {
|
|
123
|
+
post: {
|
|
124
|
+
tags: ["ai-llm"],
|
|
125
|
+
summary: "Chat completions",
|
|
126
|
+
description: "OpenAI-compatible chat completions endpoint. Supports streaming (`stream: true`). Use header `X-AI-Mode: llm` (default) or `X-AI-Mode: agent` to switch between direct LLM and agent mode.",
|
|
127
|
+
security: [{ BearerAuth: [] }],
|
|
128
|
+
parameters: [
|
|
129
|
+
{
|
|
130
|
+
name: "X-AI-Mode",
|
|
131
|
+
in: "header",
|
|
132
|
+
required: false,
|
|
133
|
+
description: "AI mode: `llm` (default) or `agent`",
|
|
134
|
+
schema: { type: "string", enum: ["llm", "agent"], default: "llm" }
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
requestBody: {
|
|
138
|
+
required: true,
|
|
139
|
+
content: {
|
|
140
|
+
"application/json": {
|
|
141
|
+
schema: { $ref: "#/components/schemas/ChatCompletionRequest" }
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
responses: {
|
|
146
|
+
200: {
|
|
147
|
+
description: "Chat completion response (or SSE stream when `stream: true`)",
|
|
148
|
+
content: {
|
|
149
|
+
"application/json": {
|
|
150
|
+
schema: { $ref: "#/components/schemas/ChatCompletionResponse" }
|
|
151
|
+
},
|
|
152
|
+
"text/event-stream": {
|
|
153
|
+
schema: { type: "string", description: "SSE stream of completion chunks" }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
429: { description: "Rate limit exceeded" }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"/ai-llm/v1/completions": {
|
|
162
|
+
post: {
|
|
163
|
+
tags: ["ai-llm"],
|
|
164
|
+
summary: "Text completions (legacy)",
|
|
165
|
+
description: "Legacy text completions for LiteLLM compatibility.",
|
|
166
|
+
security: [{ BearerAuth: [] }],
|
|
167
|
+
requestBody: {
|
|
168
|
+
required: true,
|
|
169
|
+
content: {
|
|
170
|
+
"application/json": {
|
|
171
|
+
schema: {
|
|
172
|
+
type: "object",
|
|
173
|
+
properties: {
|
|
174
|
+
model: { type: "string" },
|
|
175
|
+
prompt: { type: "string" },
|
|
176
|
+
max_tokens: { type: "integer" },
|
|
177
|
+
stream: { type: "boolean", default: false }
|
|
178
|
+
},
|
|
179
|
+
required: ["model", "prompt"]
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
responses: {
|
|
185
|
+
200: { description: "Completion response" },
|
|
186
|
+
429: { description: "Rate limit exceeded" }
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"/ai-llm/v1/embeddings": {
|
|
191
|
+
post: {
|
|
192
|
+
tags: ["ai-llm"],
|
|
193
|
+
summary: "Generate embeddings",
|
|
194
|
+
description: "OpenAI-compatible embeddings endpoint.",
|
|
195
|
+
security: [{ BearerAuth: [] }],
|
|
196
|
+
requestBody: {
|
|
197
|
+
required: true,
|
|
198
|
+
content: {
|
|
199
|
+
"application/json": {
|
|
200
|
+
schema: {
|
|
201
|
+
type: "object",
|
|
202
|
+
properties: {
|
|
203
|
+
model: { type: "string", example: "openai/text-embedding-ada-002" },
|
|
204
|
+
input: {
|
|
205
|
+
oneOf: [
|
|
206
|
+
{ type: "string" },
|
|
207
|
+
{ type: "array", items: { type: "string" } }
|
|
208
|
+
]
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
required: ["model", "input"]
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
responses: {
|
|
217
|
+
200: {
|
|
218
|
+
description: "Embedding vectors",
|
|
219
|
+
content: {
|
|
220
|
+
"application/json": {
|
|
221
|
+
schema: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {
|
|
224
|
+
object: { type: "string", example: "list" },
|
|
225
|
+
data: {
|
|
226
|
+
type: "array",
|
|
227
|
+
items: {
|
|
228
|
+
type: "object",
|
|
229
|
+
properties: {
|
|
230
|
+
object: { type: "string", example: "embedding" },
|
|
231
|
+
embedding: { type: "array", items: { type: "number" } },
|
|
232
|
+
index: { type: "integer" }
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
model: { type: "string" },
|
|
237
|
+
usage: {
|
|
238
|
+
type: "object",
|
|
239
|
+
properties: {
|
|
240
|
+
prompt_tokens: { type: "integer" },
|
|
241
|
+
total_tokens: { type: "integer" }
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
429: { description: "Rate limit exceeded" }
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
components: {
|
|
255
|
+
schemas: {
|
|
256
|
+
AiApiConfig: {
|
|
257
|
+
type: "object",
|
|
258
|
+
properties: {
|
|
259
|
+
mode: {
|
|
260
|
+
type: "string",
|
|
261
|
+
enum: ["llm", "agent"],
|
|
262
|
+
description: "Default AI mode"
|
|
263
|
+
},
|
|
264
|
+
defaultAiEmployee: { type: "string", description: "Default AI employee name" },
|
|
265
|
+
defaultLlmService: { type: "string", description: "Default LLM service name" },
|
|
266
|
+
enabledLlmServices: {
|
|
267
|
+
type: "array",
|
|
268
|
+
items: { type: "string" },
|
|
269
|
+
description: "List of enabled LLM service names"
|
|
270
|
+
},
|
|
271
|
+
rateLimitPerMinute: {
|
|
272
|
+
type: "integer",
|
|
273
|
+
description: "Max requests per minute per user (0 = unlimited)"
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
ModelObject: {
|
|
278
|
+
type: "object",
|
|
279
|
+
properties: {
|
|
280
|
+
id: { type: "string", example: "openai/gpt-4o", description: "Model ID (serviceName/modelId)" },
|
|
281
|
+
object: { type: "string", example: "model" },
|
|
282
|
+
created: { type: "integer" },
|
|
283
|
+
owned_by: { type: "string" }
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
ChatMessage: {
|
|
287
|
+
type: "object",
|
|
288
|
+
properties: {
|
|
289
|
+
role: { type: "string", enum: ["system", "user", "assistant", "tool"] },
|
|
290
|
+
content: { type: "string" },
|
|
291
|
+
name: { type: "string" }
|
|
292
|
+
},
|
|
293
|
+
required: ["role", "content"]
|
|
294
|
+
},
|
|
295
|
+
ChatCompletionRequest: {
|
|
296
|
+
type: "object",
|
|
297
|
+
properties: {
|
|
298
|
+
model: { type: "string", example: "openai/gpt-4o" },
|
|
299
|
+
messages: { type: "array", items: { $ref: "#/components/schemas/ChatMessage" } },
|
|
300
|
+
stream: { type: "boolean", default: false },
|
|
301
|
+
temperature: { type: "number", minimum: 0, maximum: 2 },
|
|
302
|
+
max_tokens: { type: "integer" },
|
|
303
|
+
top_p: { type: "number" },
|
|
304
|
+
frequency_penalty: { type: "number" },
|
|
305
|
+
presence_penalty: { type: "number" }
|
|
306
|
+
},
|
|
307
|
+
required: ["model", "messages"]
|
|
308
|
+
},
|
|
309
|
+
ChatCompletionResponse: {
|
|
310
|
+
type: "object",
|
|
311
|
+
properties: {
|
|
312
|
+
id: { type: "string" },
|
|
313
|
+
object: { type: "string", example: "chat.completion" },
|
|
314
|
+
created: { type: "integer" },
|
|
315
|
+
model: { type: "string" },
|
|
316
|
+
choices: {
|
|
317
|
+
type: "array",
|
|
318
|
+
items: {
|
|
319
|
+
type: "object",
|
|
320
|
+
properties: {
|
|
321
|
+
index: { type: "integer" },
|
|
322
|
+
message: { $ref: "#/components/schemas/ChatMessage" },
|
|
323
|
+
finish_reason: { type: "string", enum: ["stop", "length", "tool_calls"] }
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
usage: {
|
|
328
|
+
type: "object",
|
|
329
|
+
properties: {
|
|
330
|
+
prompt_tokens: { type: "integer" },
|
|
331
|
+
completion_tokens: { type: "integer" },
|
|
332
|
+
total_tokens: { type: "integer" }
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plugin-ai-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"main": "dist/server/index.js",
|
|
5
5
|
"dependencies": {},
|
|
6
6
|
"peerDependencies": {
|
|
7
7
|
"@nocobase/client": "2.x",
|
|
8
8
|
"@nocobase/server": "2.x",
|
|
9
9
|
"@nocobase/database": "2.x",
|
|
10
|
+
"@nocobase/plugin-acl": "2.x",
|
|
10
11
|
"@nocobase/plugin-ai": "2.x",
|
|
11
12
|
"@nocobase/plugin-api-keys": "2.x"
|
|
12
13
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|