plugin-ai-api 1.0.24 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/dist/client/{286.01c0e3c5fff3cccb.js → 286.a1ee0420172cd5de.js} +1 -1
  2. package/dist/client/302.fbc46ebf5bf300d7.js +10 -0
  3. package/dist/client/562.44b16aad4718b4c7.js +10 -0
  4. package/dist/client/685.ae483e17b6b49c98.js +10 -0
  5. package/dist/client/757.6568d3504ad29352.js +10 -0
  6. package/dist/client/{97.72979a11a067a7c9.js → 97.9b6b2d2b01a4c060.js} +1 -1
  7. package/dist/client/index.js +1 -1
  8. package/dist/client-v2/302.3971233415999b2c.js +10 -0
  9. package/dist/client-v2/562.45d5c504433be38b.js +10 -0
  10. package/dist/client-v2/685.1030370b309b7d4b.js +10 -0
  11. package/dist/client-v2/757.f2bc9cfba07004b0.js +10 -0
  12. package/dist/client-v2/{952.94100128b7757f56.js → 952.f0249eddc153bde1.js} +1 -1
  13. package/dist/client-v2/{97.29c663318eebbd57.js → 97.36a42eff36bb3d8a.js} +1 -1
  14. package/dist/client-v2/index.js +1 -1
  15. package/dist/constants.js +2 -5
  16. package/dist/externalVersion.js +8 -8
  17. package/dist/locale/en-US.json +27 -8
  18. package/dist/locale/vi-VN.json +27 -8
  19. package/dist/locale/zh-CN.json +27 -8
  20. package/dist/server/billing.js +31 -33
  21. package/dist/server/collections/ai-api-config.js +7 -7
  22. package/dist/server/collections/ai-api-group-members.js +62 -0
  23. package/dist/server/collections/ai-api-group-quota-buckets.js +63 -0
  24. package/dist/server/collections/ai-api-model-metadata.js +6 -0
  25. package/dist/server/collections/ai-api-usage-groups.js +74 -0
  26. package/dist/server/collections/ai-api-usage-records.js +2 -0
  27. package/dist/server/middleware/rate-limit.js +7 -6
  28. package/dist/server/migrations/20260813000000-add-prompt-cache-tokens.js +69 -0
  29. package/dist/server/migrations/20260815000000-add-usage-groups.js +149 -0
  30. package/dist/server/migrations/20260816000000-migrate-user-permissions-to-groups.js +169 -0
  31. package/dist/server/migrations/20260816100000-add-model-metadata-system-prompt.js +69 -0
  32. package/dist/server/plugin.js +100 -22
  33. package/dist/server/quota-groups.js +108 -0
  34. package/dist/server/resource/ai-api-config.js +5 -3
  35. package/dist/server/resource/ai-api-usage-groups.js +168 -0
  36. package/dist/server/resource/ai-api-usage-monitor.js +3 -1
  37. package/dist/server/routes/agent-completions.js +2 -1
  38. package/dist/server/routes/chat-completions.js +121 -42
  39. package/dist/server/routes/completions.js +48 -29
  40. package/dist/server/routes/embeddings.js +2 -1
  41. package/dist/server/routes/models.js +2 -1
  42. package/dist/server/routes/router.js +3 -2
  43. package/dist/server/services/file-processor.js +426 -0
  44. package/dist/server/usage.js +37 -3
  45. package/dist/server/utils/direct-llm-context.js +163 -26
  46. package/dist/server/utils/openai-format.js +21 -2
  47. package/dist/server/utils/rate-limiter.js +1 -1
  48. package/dist/server/utils/request-cache.js +61 -0
  49. package/dist/server/utils/resolve-service.js +2 -1
  50. package/dist/server/utils/user-permissions.js +25 -39
  51. package/dist/server/validation.js +7 -0
  52. package/dist/swagger.js +48 -10
  53. package/package.json +1 -1
  54. package/src/client/__tests__/settings-registration.test.tsx +6 -29
  55. package/src/client/plugin.tsx +5 -16
  56. package/src/client-v2/__tests__/settings-registration.test.tsx +6 -32
  57. package/src/client-v2/locale.ts +3 -1
  58. package/src/client-v2/pages/GeneralPage.tsx +0 -5
  59. package/src/client-v2/pages/ModelMetadataPage.tsx +20 -1
  60. package/src/client-v2/pages/UsageGroupsPage.tsx +548 -0
  61. package/src/client-v2/pages/UsagePage.tsx +9 -0
  62. package/src/client-v2/plugin.tsx +4 -13
  63. package/src/constants.ts +0 -7
  64. package/src/locale/en-US.json +27 -8
  65. package/src/locale/vi-VN.json +27 -8
  66. package/src/locale/zh-CN.json +27 -8
  67. package/src/server/__tests__/billing-quota.test.ts +28 -9
  68. package/src/server/__tests__/direct-llm-context.test.ts +209 -10
  69. package/src/server/__tests__/file-processor.test.ts +225 -0
  70. package/src/server/__tests__/models.test.ts +1 -1
  71. package/src/server/__tests__/openai-format.test.ts +12 -2
  72. package/src/server/__tests__/permission-sync.test.ts +34 -35
  73. package/src/server/__tests__/request-body.test.ts +45 -2
  74. package/src/server/__tests__/usage-groups.test.ts +160 -0
  75. package/src/server/__tests__/usage-monitor.test.ts +2 -0
  76. package/src/server/__tests__/usage-route.test.ts +382 -5
  77. package/src/server/__tests__/usage.test.ts +57 -0
  78. package/src/server/__tests__/user-permissions.test.ts +214 -133
  79. package/src/server/__tests__/validation.test.ts +11 -0
  80. package/src/server/billing.ts +36 -39
  81. package/src/server/collections/ai-api-config.ts +9 -7
  82. package/src/server/collections/ai-api-group-members.ts +41 -0
  83. package/src/server/collections/ai-api-group-quota-buckets.ts +42 -0
  84. package/src/server/collections/ai-api-model-metadata.ts +7 -0
  85. package/src/server/collections/ai-api-role-permissions.ts +41 -41
  86. package/src/server/collections/ai-api-usage-groups.ts +53 -0
  87. package/src/server/collections/ai-api-usage-records.ts +2 -0
  88. package/src/server/index.ts +10 -10
  89. package/src/server/middleware/rate-limit.ts +68 -70
  90. package/src/server/migrations/20260813000000-add-prompt-cache-tokens.ts +46 -0
  91. package/src/server/migrations/20260815000000-add-usage-groups.ts +147 -0
  92. package/src/server/migrations/20260816000000-migrate-user-permissions-to-groups.ts +190 -0
  93. package/src/server/migrations/20260816100000-add-model-metadata-system-prompt.ts +46 -0
  94. package/src/server/plugin.ts +121 -30
  95. package/src/server/quota-groups.ts +117 -0
  96. package/src/server/resource/ai-api-config.ts +5 -3
  97. package/src/server/resource/ai-api-usage-groups.ts +171 -0
  98. package/src/server/resource/ai-api-usage-monitor.ts +3 -0
  99. package/src/server/routes/agent-completions.ts +2 -1
  100. package/src/server/routes/chat-completions.ts +173 -47
  101. package/src/server/routes/completions.ts +50 -27
  102. package/src/server/routes/embeddings.ts +2 -1
  103. package/src/server/routes/models.ts +4 -3
  104. package/src/server/routes/router.ts +4 -3
  105. package/src/server/services/__tests__/file-processor.test.ts +184 -0
  106. package/src/server/services/file-processor.ts +513 -0
  107. package/src/server/usage.ts +51 -1
  108. package/src/server/utils/direct-llm-context.ts +218 -31
  109. package/src/server/utils/openai-format.ts +25 -2
  110. package/src/server/utils/rate-limiter.ts +83 -83
  111. package/src/server/utils/request-cache.ts +59 -0
  112. package/src/server/utils/resolve-service.ts +83 -82
  113. package/src/server/utils/user-permissions.ts +49 -69
  114. package/src/server/validation.ts +7 -0
  115. package/src/swagger.ts +52 -11
  116. package/dist/client/123.e6fe04c856ce6417.js +0 -10
  117. package/dist/client/302.fc3a3491b4ec2dfd.js +0 -10
  118. package/dist/client/562.17a0a299d2e5152c.js +0 -10
  119. package/dist/client/757.a01403fb7a1bea01.js +0 -10
  120. package/dist/client/902.e74518750f1e4201.js +0 -10
  121. package/dist/client-v2/123.05f1f649923f93eb.js +0 -10
  122. package/dist/client-v2/302.d27fe4ea9b0b3bf5.js +0 -10
  123. package/dist/client-v2/562.fb2948ee6402de95.js +0 -10
  124. package/dist/client-v2/757.a117ce1cf7119cea.js +0 -10
  125. package/dist/client-v2/902.c7c00a565085438a.js +0 -10
  126. package/dist/server/resource/ai-api-user-permissions.js +0 -75
  127. package/src/client-v2/pages/UserPermissionsPage.tsx +0 -322
  128. package/src/client-v2/pages/UserQuotasPage.tsx +0 -276
  129. package/src/server/__tests__/user-permissions-resource.test.ts +0 -66
  130. package/src/server/resource/ai-api-user-permissions.ts +0 -76
@@ -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("@nocobase/client-v2"),require("dayjs"),require("react"),require("antd"),require("@nocobase/flow-engine")):"function"==typeof define&&define.amd?define("plugin-ai-api/client-v2",["@nocobase/client-v2","dayjs","react","antd","@nocobase/flow-engine"],t):"object"==typeof exports?exports["plugin-ai-api/client-v2"]=t(require("@nocobase/client-v2"),require("dayjs"),require("react"),require("antd"),require("@nocobase/flow-engine")):e["plugin-ai-api/client-v2"]=t(e["@nocobase/client-v2"],e.dayjs,e.react,e.antd,e["@nocobase/flow-engine"])}(self,function(e,t,n,r,i){return function(){"use strict";var o,a,u,c={485:function(t){t.exports=e},694:function(e){e.exports=i},59:function(e){e.exports=r},185:function(e){e.exports=t},155:function(e){e.exports=n}},l={};function p(e){var t=l[e];if(void 0!==t)return t.exports;var n=l[e]={exports:{}};return c[e](n,n.exports,p),n.exports}p.m=c,p.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return p.d(t,{a:t}),t},p.d=function(e,t){for(var n in t)p.o(t,n)&&!p.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},p.f={},p.e=function(e){return Promise.all(Object.keys(p.f).reduce(function(t,n){return p.f[n](e,t),t},[]))},p.u=function(e){return""+e+"."+({123:"05f1f649923f93eb",302:"d27fe4ea9b0b3bf5",562:"fb2948ee6402de95",757:"a117ce1cf7119cea",902:"c7c00a565085438a",952:"94100128b7757f56",97:"29c663318eebbd57"})[e]+".js"},p.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),p.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},s={},p.l=function(e,t,n,r){if(s[e])return void s[e].push(t);if(void 0!==n)for(var i,o,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){i=c;break}}i||(o=!0,(i=document.createElement("script")).timeout=120,p.nc&&i.setAttribute("nonce",p.nc),i.setAttribute("data-rspack","plugin-ai-api/client-v2:"+n),i.src=e),s[e]=[t];var l=function(t,n){i.onerror=i.onload=null,clearTimeout(f);var r=s[e];if(delete s[e],i.parentNode&&i.parentNode.removeChild(i),r&&r.forEach(function(e){return e(n)}),t)return t(n)},f=setTimeout(l.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=l.bind(null,i.onerror),i.onload=l.bind(null,i.onload),o&&document.head.appendChild(i)},p.r=function(e){"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},p.g.importScripts&&(f=p.g.location+"");var s,f,d=p.g.document;if(!f&&d&&(d.currentScript&&"SCRIPT"===d.currentScript.tagName.toUpperCase()&&(f=d.currentScript.src),!f)){var b=d.getElementsByTagName("script");if(b.length)for(var g=b.length-1;g>-1&&(!f||!/^http(s?):/.test(f));)f=b[g--].src}if(!f)throw Error("Automatic publicPath is not supported in this browser");p.p=f.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),o={889:0},p.f.j=function(e,t){var n=p.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=p.p+p.u(e),a=Error();p.l(i,function(t){if(p.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)}},a=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)p.o(a,n)&&(p.m[n]=a[n]);u&&u(p)}for(e&&e(t);c<i.length;c++)r=i[c],p.o(o,r)&&o[r]&&o[r][0](),o[r]=0},(u=self.webpackChunkplugin_ai_api_client_v2=self.webpackChunkplugin_ai_api_client_v2||[]).forEach(a.bind(null,0)),u.push=a.bind(null,u.push.bind(u));var h={};return!function(){var e="",t="u">typeof document?document.currentScript:null;if(t&&t.src){var n=t.src.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"");n.indexOf("/static/plugins/plugin-ai-api/dist/client-v2/")>=0&&(e=n.replace(/\/[^\/]+$/,"/"))}if(!e){var r=window.__webpack_public_path__||"";r&&("/"!==r.charAt(r.length-1)&&(r+="/"),e=r+"static/plugins/plugin-ai-api/dist/client-v2/")}if(!e){var i=window.__nocobase_modern_client_prefix__||"v",o="/"+(i=String(i).replace(/^\/+|\/+$/g,"")||"v")+"/";if(!(e=window.__nocobase_public_path__||"")&&window.location&&window.location.pathname){var a=window.location.pathname||"/",u=a.indexOf(o);e=u>=0?a.slice(0,u+1):"/"}e&&(e=e.replace(RegExp("/"+i+"/?$"),"/")),e||(e="/"),"/"!==e.charAt(e.length-1)&&(e+="/"),e+="static/plugins/plugin-ai-api/dist/client-v2/"}p.p=e}(),!function(){p.r(h),p.d(h,{default:function(){return c}}),p(155);var e=p(485),t="pm.plugin-ai-api.configuration";function n(e,t,n,r,i,o,a){try{var u=e[o](a),c=u.value}catch(e){n(e);return}u.done?t(c):Promise.resolve(c).then(r,i)}function r(e,t,n){return(r=u()?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)}function i(e){return(i=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 a(e){var t="function"==typeof Map?new Map:void 0;return(a=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 r(e,arguments,i(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),o(n,e)})(e)}function u(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(e){}return(u=function(){return!!e})()}var c=function(e){var r;if("function"!=typeof e&&null!==e)throw TypeError("Super expression must either be null or a function");function a(){var e,t;if(!(this instanceof a))throw TypeError("Cannot call a class as a function");return e=a,t=arguments,e=i(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,u()?Reflect.construct(e,t||[],i(this).constructor):e.apply(this,t))}return a.prototype=Object.create(e&&e.prototype,{constructor:{value:a,writable:!0,configurable:!0}}),e&&o(a,e),r=[{key:"load",value:function(){var e;return(e=function(){var e,n,r;return function(e,t){var n,r,i,o={label:0,sent:function(){if(1&i[0])throw i[1];return i[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]&&(o=0)),o;)try{if(n=1,r&&(i=2&l[0]?r.return:l[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,l[1])).done)return i;switch(r=0,i&&(l=[2&l[0],i.value]),l[0]){case 0:case 1:i=l;break;case 4:return o.label++,{value:l[1],done:!1};case 5:o.label++,r=l[1],l=[0];continue;case 7:l=o.ops.pop(),o.trys.pop();continue;default:if(!(i=(i=o.trys).length>0&&i[i.length-1])&&(6===l[0]||2===l[0])){o=0;continue}if(3===l[0]&&(!i||l[1]>i[0]&&l[1]<i[3])){o.label=l[1];break}if(6===l[0]&&o.label<i[1]){o.label=i[1],i=l;break}if(i&&o.label<i[2]){o.label=i[2],o.ops.push(l);break}i[2]&&o.ops.pop(),o.trys.pop();continue}l=t.call(e,o)}catch(e){l=[6,e],r=0}finally{n=i=0}if(5&l[0])throw l[1];return{value:l[0]?l[1]:void 0,done:!0}}}}(this,function(i){return this.pluginSettingsManager.addMenuItem({key:"ai-api",title:this.t("AI API Gateway"),icon:"ApiOutlined",aclSnippet:t}),this.pluginSettingsManager.addPageTabItem({menuKey:"ai-api",key:"index",title:this.t("Configuration"),aclSnippet:t,sort:1,componentLoader:function(){return p.e("302").then(p.bind(p,581))}}),this.pluginSettingsManager.addPageTabItem({menuKey:"ai-api",key:"model-pricing",title:this.t("Model pricing"),aclSnippet:t,sort:2,componentLoader:function(){return p.e("97").then(p.bind(p,760))}}),this.pluginSettingsManager.addPageTabItem({menuKey:"ai-api",key:"model-metadata",title:this.t("Model metadata"),aclSnippet:t,sort:3,componentLoader:function(){return p.e("562").then(p.bind(p,641))}}),this.pluginSettingsManager.addPageTabItem({menuKey:"ai-api",key:"user-permissions",title:this.t("User LLM permissions"),aclSnippet:"pm.plugin-ai-api.user-permissions",sort:4,componentLoader:function(){return p.e("123").then(p.bind(p,46))}}),this.pluginSettingsManager.addPageTabItem({menuKey:"ai-api",key:"user-quotas",title:this.t("User quotas"),aclSnippet:t,sort:5,componentLoader:function(){return p.e("902").then(p.bind(p,77))}}),this.pluginSettingsManager.addPageTabItem({menuKey:"ai-api",key:"usage",title:this.t("Usage"),aclSnippet:t,sort:6,componentLoader:function(){return p.e("757").then(p.bind(p,364))}}),null==(r=this.app.pm.get("@nocobase/plugin-acl"))||null==(n=r.settingsUI)||null==(e=n.addPermissionsTab)||e.call(n,{key:"aiApi",label:this.t("AI API"),sort:25,componentLoader:function(){return p.e("952").then(p.bind(p,699))}}),[2]})},function(){var t=this,r=arguments;return new Promise(function(i,o){var a=e.apply(t,r);function u(e){n(a,i,o,u,c,"next",e)}function c(e){n(a,i,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)}}(a.prototype,r),a}(a(e.Plugin))}(),h}()});
10
+ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("@nocobase/client-v2"),require("dayjs"),require("react"),require("antd"),require("@nocobase/flow-engine")):"function"==typeof define&&define.amd?define("plugin-ai-api/client-v2",["@nocobase/client-v2","dayjs","react","antd","@nocobase/flow-engine"],t):"object"==typeof exports?exports["plugin-ai-api/client-v2"]=t(require("@nocobase/client-v2"),require("dayjs"),require("react"),require("antd"),require("@nocobase/flow-engine")):e["plugin-ai-api/client-v2"]=t(e["@nocobase/client-v2"],e.dayjs,e.react,e.antd,e["@nocobase/flow-engine"])}(self,function(e,t,n,r,o){return function(){"use strict";var i,a,u,c={485:function(t){t.exports=e},694:function(e){e.exports=o},59:function(e){e.exports=r},185:function(e){e.exports=t},155:function(e){e.exports=n}},l={};function p(e){var t=l[e];if(void 0!==t)return t.exports;var n=l[e]={exports:{}};return c[e](n,n.exports,p),n.exports}p.m=c,p.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return p.d(t,{a:t}),t},p.d=function(e,t){for(var n in t)p.o(t,n)&&!p.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},p.f={},p.e=function(e){return Promise.all(Object.keys(p.f).reduce(function(t,n){return p.f[n](e,t),t},[]))},p.u=function(e){return""+e+"."+({302:"3971233415999b2c",562:"45d5c504433be38b",685:"1030370b309b7d4b",757:"f2bc9cfba07004b0",952:"f0249eddc153bde1",97:"36a42eff36bb3d8a"})[e]+".js"},p.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),p.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},s={},p.l=function(e,t,n,r){if(s[e])return void s[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,p.nc&&o.setAttribute("nonce",p.nc),o.setAttribute("data-rspack","plugin-ai-api/client-v2:"+n),o.src=e),s[e]=[t];var l=function(t,n){o.onerror=o.onload=null,clearTimeout(f);var r=s[e];if(delete s[e],o.parentNode&&o.parentNode.removeChild(o),r&&r.forEach(function(e){return e(n)}),t)return t(n)},f=setTimeout(l.bind(null,void 0,{type:"timeout",target:o}),12e4);o.onerror=l.bind(null,o.onerror),o.onload=l.bind(null,o.onload),i&&document.head.appendChild(o)},p.r=function(e){"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},p.g.importScripts&&(f=p.g.location+"");var s,f,d=p.g.document;if(!f&&d&&(d.currentScript&&"SCRIPT"===d.currentScript.tagName.toUpperCase()&&(f=d.currentScript.src),!f)){var b=d.getElementsByTagName("script");if(b.length)for(var g=b.length-1;g>-1&&(!f||!/^http(s?):/.test(f));)f=b[g--].src}if(!f)throw Error("Automatic publicPath is not supported in this browser");p.p=f.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),i={889:0},p.f.j=function(e,t){var n=p.o(i,e)?i[e]:void 0;if(0!==n)if(n)t.push(n[2]);else{var r=new Promise(function(t,r){n=i[e]=[t,r]});t.push(n[2]=r);var o=p.p+p.u(e),a=Error();p.l(o,function(t){if(p.o(i,e)&&(0!==(n=i[e])&&(i[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;a.message="Loading chunk "+e+" failed.\n("+r+": "+o+")",a.name="ChunkLoadError",a.type=r,a.request=o,n[1](a)}},"chunk-"+e,e)}},a=function(e,t){var n,r,o=t[0],a=t[1],u=t[2],c=0;if(o.some(function(e){return 0!==i[e]})){for(n in a)p.o(a,n)&&(p.m[n]=a[n]);u&&u(p)}for(e&&e(t);c<o.length;c++)r=o[c],p.o(i,r)&&i[r]&&i[r][0](),i[r]=0},(u=self.webpackChunkplugin_ai_api_client_v2=self.webpackChunkplugin_ai_api_client_v2||[]).forEach(a.bind(null,0)),u.push=a.bind(null,u.push.bind(u));var h={};return!function(){var e="",t="u">typeof document?document.currentScript:null;if(t&&t.src){var n=t.src.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"");n.indexOf("/static/plugins/plugin-ai-api/dist/client-v2/")>=0&&(e=n.replace(/\/[^\/]+$/,"/"))}if(!e){var r=window.__webpack_public_path__||"";r&&("/"!==r.charAt(r.length-1)&&(r+="/"),e=r+"static/plugins/plugin-ai-api/dist/client-v2/")}if(!e){var o=window.__nocobase_modern_client_prefix__||"v",i="/"+(o=String(o).replace(/^\/+|\/+$/g,"")||"v")+"/";if(!(e=window.__nocobase_public_path__||"")&&window.location&&window.location.pathname){var a=window.location.pathname||"/",u=a.indexOf(i);e=u>=0?a.slice(0,u+1):"/"}e&&(e=e.replace(RegExp("/"+o+"/?$"),"/")),e||(e="/"),"/"!==e.charAt(e.length-1)&&(e+="/"),e+="static/plugins/plugin-ai-api/dist/client-v2/"}p.p=e}(),!function(){p.r(h),p.d(h,{default:function(){return c}}),p(155);var e=p(485),t="pm.plugin-ai-api.configuration";function n(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 r(e,t,n){return(r=u()?Reflect.construct:function(e,t,n){var r=[null];r.push.apply(r,t);var o=new(Function.bind.apply(e,r));return n&&i(o,n.prototype),o}).apply(null,arguments)}function o(e){return(o=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function i(e,t){return(i=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function a(e){var t="function"==typeof Map?new Map:void 0;return(a=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 r(e,arguments,o(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),i(n,e)})(e)}function u(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(e){}return(u=function(){return!!e})()}var c=function(e){var r;if("function"!=typeof e&&null!==e)throw TypeError("Super expression must either be null or a function");function a(){var e,t;if(!(this instanceof a))throw TypeError("Cannot call a class as a function");return e=a,t=arguments,e=o(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,u()?Reflect.construct(e,t||[],o(this).constructor):e.apply(this,t))}return a.prototype=Object.create(e&&e.prototype,{constructor:{value:a,writable:!0,configurable:!0}}),e&&i(a,e),r=[{key:"load",value:function(){var e;return(e=function(){var e,n,r;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(o){return this.pluginSettingsManager.addMenuItem({key:"ai-api",title:this.t("AI API Gateway"),icon:"ApiOutlined",aclSnippet:t}),this.pluginSettingsManager.addPageTabItem({menuKey:"ai-api",key:"index",title:this.t("Configuration"),aclSnippet:t,sort:1,componentLoader:function(){return p.e("302").then(p.bind(p,581))}}),this.pluginSettingsManager.addPageTabItem({menuKey:"ai-api",key:"model-pricing",title:this.t("Model pricing"),aclSnippet:t,sort:2,componentLoader:function(){return p.e("97").then(p.bind(p,760))}}),this.pluginSettingsManager.addPageTabItem({menuKey:"ai-api",key:"model-metadata",title:this.t("Model metadata"),aclSnippet:t,sort:3,componentLoader:function(){return p.e("562").then(p.bind(p,641))}}),this.pluginSettingsManager.addPageTabItem({menuKey:"ai-api",key:"usage-groups",title:this.t("Usage groups"),aclSnippet:t,sort:5,componentLoader:function(){return p.e("685").then(p.bind(p,244))}}),this.pluginSettingsManager.addPageTabItem({menuKey:"ai-api",key:"usage",title:this.t("Usage"),aclSnippet:t,sort:6,componentLoader:function(){return p.e("757").then(p.bind(p,364))}}),null==(r=this.app.pm.get("@nocobase/plugin-acl"))||null==(n=r.settingsUI)||null==(e=n.addPermissionsTab)||e.call(n,{key:"aiApi",label:this.t("AI API"),sort:25,componentLoader:function(){return p.e("952").then(p.bind(p,699))}}),[2]})},function(){var t=this,r=arguments;return new Promise(function(o,i){var a=e.apply(t,r);function u(e){n(a,o,i,u,c,"next",e)}function c(e){n(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)}}(a.prototype,r),a}(a(e.Plugin))}(),h}()});
package/dist/constants.js CHANGED
@@ -26,14 +26,11 @@ var __copyProps = (to, from, except, desc) => {
26
26
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
27
  var constants_exports = {};
28
28
  __export(constants_exports, {
29
- AI_API_ACL_SNIPPET: () => AI_API_ACL_SNIPPET,
30
- AI_API_USER_PERMISSIONS_SNIPPET: () => AI_API_USER_PERMISSIONS_SNIPPET
29
+ AI_API_ACL_SNIPPET: () => AI_API_ACL_SNIPPET
31
30
  });
32
31
  module.exports = __toCommonJS(constants_exports);
33
32
  const AI_API_ACL_SNIPPET = "pm.plugin-ai-api.configuration";
34
- const AI_API_USER_PERMISSIONS_SNIPPET = "pm.plugin-ai-api.user-permissions";
35
33
  // Annotate the CommonJS export names for ESM import in node:
36
34
  0 && (module.exports = {
37
- AI_API_ACL_SNIPPET,
38
- AI_API_USER_PERMISSIONS_SNIPPET
35
+ AI_API_ACL_SNIPPET
39
36
  });
@@ -8,16 +8,16 @@
8
8
  */
9
9
 
10
10
  module.exports = {
11
- "@nocobase/client": "2.1.34",
12
- "@nocobase/plugin-acl": "2.1.34",
11
+ "@nocobase/client": "2.1.39",
12
+ "@nocobase/plugin-acl": "2.1.39",
13
13
  "react": "18.2.0",
14
- "@nocobase/flow-engine": "2.1.34",
15
- "@nocobase/client-v2": "2.1.34",
16
- "@nocobase/actions": "2.1.34",
14
+ "@nocobase/flow-engine": "2.1.39",
15
+ "@nocobase/client-v2": "2.1.39",
16
+ "@nocobase/actions": "2.1.39",
17
17
  "dayjs": "1.11.13",
18
- "@nocobase/database": "2.1.34",
18
+ "@nocobase/database": "2.1.39",
19
19
  "sequelize": "6.35.2",
20
- "@nocobase/server": "2.1.34",
20
+ "@nocobase/server": "2.1.39",
21
21
  "antd": "5.24.2",
22
- "@nocobase/resourcer": "2.1.34"
22
+ "@nocobase/resourcer": "2.1.39"
23
23
  };
@@ -63,6 +63,7 @@
63
63
  "Input tokens": "Input tokens",
64
64
  "Output tokens": "Output tokens",
65
65
  "Total tokens": "Total tokens",
66
+ "Prompt cache tokens": "Prompt cache tokens",
66
67
  "Cost": "Cost",
67
68
  "Cost status": "Cost status",
68
69
  "Request ID": "Request ID",
@@ -98,6 +99,8 @@
98
99
  "Leave empty to not override": "Leave empty to not override",
99
100
  "Total input + output token capacity reported to clients.": "Total input + output token capacity reported to clients.",
100
101
  "Maximum output tokens reported to clients.": "Maximum output tokens reported to clients.",
102
+ "Initial system prompt": "Initial system prompt",
103
+ "Prepended as the first system message, before any system prompt sent by the client. If the client sends no system prompt, this becomes the system prompt sent to the provider.": "Prepended as the first system message, before any system prompt sent by the client. If the client sends no system prompt, this becomes the system prompt sent to the provider.",
101
104
  "AI API": "AI API",
102
105
  "Allow this role to use the AI API": "Allow this role to use the AI API",
103
106
  "Allow all AI Employees": "Allow all AI Employees",
@@ -105,15 +108,31 @@
105
108
  "Select allowed AI Employees": "Select allowed AI Employees",
106
109
  "Max request body size (MB)": "Max request body size (MB)",
107
110
  "Raise this to accept inline base64 images. Base64 adds about 33% to the original file size.": "Raise this to accept inline base64 images. Base64 adds about 33% to the original file size.",
108
- "User LLM permissions": "User LLM permissions",
109
- "Add permission": "Add permission",
110
- "Edit permission": "Edit permission",
111
- "Delete this permission?": "Delete this permission?",
111
+ "Usage groups": "Usage groups",
112
+ "Add group": "Add group",
113
+ "Edit group": "Edit group",
114
+ "Delete this group?": "Delete this group?",
115
+ "Mode": "Mode",
116
+ "Share": "Share",
117
+ "Per user": "Per user",
118
+ "Rate limit per minute": "Rate limit per minute",
119
+ "Members": "Members",
120
+ "Add member": "Add member",
121
+ "Member added": "Member added",
122
+ "Member removed": "Member removed",
123
+ "Remove member?": "Remove member?",
124
+ "Remove": "Remove",
125
+ "Search group by user": "Search group by user",
126
+ "User belongs to": "User belongs to",
127
+ "User not found": "User not found",
128
+ "Default": "Default",
112
129
  "Allowed LLM services": "Allowed LLM services",
113
130
  "Allow all models": "Allow all models",
114
131
  "Allowed models": "Allowed models",
115
- "No service allowed": "No service allowed",
116
- "All models of allowed services": "All models of allowed services",
117
- "Users listed here are limited to the services selected below. Users without a record fall back to the general configuration.": "Users listed here are limited to the services selected below. Users without a record fall back to the general configuration.",
118
- "Only services also enabled in the general configuration take effect.": "Only services also enabled in the general configuration take effect."
132
+ "Model access": "Model access",
133
+ "All models": "All models",
134
+ "All services": "All services",
135
+ "No models": "No models",
136
+ "Leave empty to allow every service enabled in the general configuration.": "Leave empty to allow every service enabled in the general configuration.",
137
+ "Users who do not belong to any other group automatically use this default group — no need to add members.": "Users who do not belong to any other group automatically use this default group — no need to add members."
119
138
  }
@@ -63,6 +63,7 @@
63
63
  "Input tokens": "Input token",
64
64
  "Output tokens": "Output token",
65
65
  "Total tokens": "Tổng token",
66
+ "Prompt cache tokens": "Prompt cache token",
66
67
  "Cost": "Chi phí",
67
68
  "Cost status": "Trạng thái chi phí",
68
69
  "Request ID": "Request ID",
@@ -98,6 +99,8 @@
98
99
  "Leave empty to not override": "Để trống nếu không override",
99
100
  "Total input + output token capacity reported to clients.": "Tổng dung lượng token (input + output) trả về cho client.",
100
101
  "Maximum output tokens reported to clients.": "Số token output tối đa trả về cho client.",
102
+ "Initial system prompt": "System prompt khởi tạo",
103
+ "Prepended as the first system message, before any system prompt sent by the client. If the client sends no system prompt, this becomes the system prompt sent to the provider.": "Được thêm vào làm system message đầu tiên, đứng trước mọi system prompt mà client gửi sang. Nếu client không gửi system prompt, đây sẽ là system prompt được gửi tới provider.",
101
104
  "AI API": "AI API",
102
105
  "Allow this role to use the AI API": "Cho phép vai trò này sử dụng AI API",
103
106
  "Allow all AI Employees": "Cho phép tất cả AI Employee",
@@ -105,15 +108,31 @@
105
108
  "Select allowed AI Employees": "Chọn AI Employee được phép",
106
109
  "Max request body size (MB)": "Giới hạn kích thước request body (MB)",
107
110
  "Raise this to accept inline base64 images. Base64 adds about 33% to the original file size.": "Tăng giá trị này để nhận ảnh base64 gửi trực tiếp. Base64 làm tăng khoảng 33% so với kích thước tệp gốc.",
108
- "User LLM permissions": "Phân quyền LLM theo người dùng",
109
- "Add permission": "Thêm phân quyền",
110
- "Edit permission": "Sửa phân quyền",
111
- "Delete this permission?": "Xoá phân quyền này?",
111
+ "Usage groups": "Nhóm usage",
112
+ "Add group": "Thêm nhóm",
113
+ "Edit group": "Sửa nhóm",
114
+ "Delete this group?": "Xóa nhóm này?",
115
+ "Mode": "Chế độ",
116
+ "Share": "Chia sẻ",
117
+ "Per user": "Theo người dùng",
118
+ "Rate limit per minute": "Giới hạn request/phút",
119
+ "Members": "Thành viên",
120
+ "Add member": "Thêm thành viên",
121
+ "Member added": "Đã thêm thành viên",
122
+ "Member removed": "Đã xóa thành viên",
123
+ "Remove member?": "Xóa thành viên?",
124
+ "Remove": "Xóa",
125
+ "Search group by user": "Tìm nhóm theo người dùng",
126
+ "User belongs to": "Người dùng thuộc nhóm",
127
+ "User not found": "Không tìm thấy người dùng",
128
+ "Default": "Mặc định",
112
129
  "Allowed LLM services": "Dịch vụ LLM được phép",
113
130
  "Allow all models": "Cho phép tất cả model",
114
131
  "Allowed models": "Model được phép",
115
- "No service allowed": "Không được phép dịch vụ nào",
116
- "All models of allowed services": "Tất cả model của các dịch vụ được phép",
117
- "Users listed here are limited to the services selected below. Users without a record fall back to the general configuration.": "Người dùng có trong danh sách này chỉ được dùng các dịch vụ được chọn bên dưới. Người dùng không có bản ghi sẽ dùng theo cấu hình chung.",
118
- "Only services also enabled in the general configuration take effect.": "Chỉ những dịch vụ đồng thời được bật trong cấu hình chung mới có hiệu lực."
132
+ "Model access": "Quyền truy cập model",
133
+ "All models": "Tất cả model",
134
+ "All services": "Tất cả dịch vụ",
135
+ "No models": "Không model nào",
136
+ "Leave empty to allow every service enabled in the general configuration.": "Để trống để cho phép mọi dịch vụ đang được bật trong cấu hình chung.",
137
+ "Users who do not belong to any other group automatically use this default group — no need to add members.": "Người dùng không thuộc nhóm nào khác sẽ tự động dùng nhóm mặc định này — không cần thêm thành viên."
119
138
  }
@@ -63,6 +63,7 @@
63
63
  "Input tokens": "输入令牌",
64
64
  "Output tokens": "输出令牌",
65
65
  "Total tokens": "总令牌",
66
+ "Prompt cache tokens": "提示缓存令牌",
66
67
  "Cost": "费用",
67
68
  "Cost status": "费用状态",
68
69
  "Request ID": "请求 ID",
@@ -98,6 +99,8 @@
98
99
  "Leave empty to not override": "留空则不覆盖",
99
100
  "Total input + output token capacity reported to clients.": "返回给客户端的输入+输出 token 总容量。",
100
101
  "Maximum output tokens reported to clients.": "返回给客户端的最大输出 token 数。",
102
+ "Initial system prompt": "初始系统提示词",
103
+ "Prepended as the first system message, before any system prompt sent by the client. If the client sends no system prompt, this becomes the system prompt sent to the provider.": "作为第一条 system 消息插入到客户端发送的任何 system 提示词之前。如果客户端未发送 system 提示词,此提示词将作为发送给提供商的 system 提示词。",
101
104
  "AI API": "AI API",
102
105
  "Allow this role to use the AI API": "允许此角色使用 AI API",
103
106
  "Allow all AI Employees": "允许所有 AI 员工",
@@ -105,15 +108,31 @@
105
108
  "Select allowed AI Employees": "选择允许的 AI 员工",
106
109
  "Max request body size (MB)": "请求体大小上限(MB)",
107
110
  "Raise this to accept inline base64 images. Base64 adds about 33% to the original file size.": "调高此值以接收内联 base64 图片。base64 编码会使体积增加约 33%。",
108
- "User LLM permissions": "用户 LLM 权限",
109
- "Add permission": "添加权限",
110
- "Edit permission": "编辑权限",
111
- "Delete this permission?": "确定删除此权限?",
111
+ "Usage groups": "用量组",
112
+ "Add group": "添加分组",
113
+ "Edit group": "编辑分组",
114
+ "Delete this group?": "删除此分组?",
115
+ "Mode": "模式",
116
+ "Share": "共享",
117
+ "Per user": "按用户",
118
+ "Rate limit per minute": "每分钟速率限制",
119
+ "Members": "成员",
120
+ "Add member": "添加成员",
121
+ "Member added": "成员已添加",
122
+ "Member removed": "成员已移除",
123
+ "Remove member?": "移除成员?",
124
+ "Remove": "移除",
125
+ "Search group by user": "按用户搜索分组",
126
+ "User belongs to": "用户属于",
127
+ "User not found": "未找到用户",
128
+ "Default": "默认",
112
129
  "Allowed LLM services": "允许的 LLM 服务",
113
130
  "Allow all models": "允许所有模型",
114
131
  "Allowed models": "允许的模型",
115
- "No service allowed": "未允许任何服务",
116
- "All models of allowed services": "允许服务下的所有模型",
117
- "Users listed here are limited to the services selected below. Users without a record fall back to the general configuration.": "此处列出的用户仅能使用下方所选的服务;没有记录的用户按通用配置处理。",
118
- "Only services also enabled in the general configuration take effect.": "仅当服务同时在通用配置中启用时才会生效。"
132
+ "Model access": "模型访问",
133
+ "All models": "所有模型",
134
+ "All services": "所有服务",
135
+ "No models": "未允许任何模型",
136
+ "Leave empty to allow every service enabled in the general configuration.": "留空则允许通用配置中已启用的所有服务。",
137
+ "Users who do not belong to any other group automatically use this default group — no need to add members.": "不属于其他分组的用户会自动使用此默认分组,无需手动添加成员。"
119
138
  }
@@ -46,6 +46,7 @@ module.exports = __toCommonJS(billing_exports);
46
46
  var import_dayjs = __toESM(require("dayjs"));
47
47
  var import_utc = __toESM(require("dayjs/plugin/utc"));
48
48
  var import_timezone = __toESM(require("dayjs/plugin/timezone"));
49
+ var import_request_cache = require("./utils/request-cache");
49
50
  import_dayjs.default.extend(import_utc.default);
50
51
  import_dayjs.default.extend(import_timezone.default);
51
52
  const PRICE_SCALE = 10;
@@ -159,25 +160,21 @@ async function prepareLlmBilling(ctx, resolved) {
159
160
  price
160
161
  };
161
162
  stateOf(ctx).aiApiLlmBilling = billing;
162
- const config = await ctx.db.getRepository("aiApiConfig").findOne();
163
+ const config = await (0, import_request_cache.getAiApiConfig)(ctx);
163
164
  if (!valueOf(config, "quotaEnabled") || userId === void 0 || userId === null) return;
164
- const policy = await ctx.db.getRepository("aiApiUserQuotaPolicies").findOne({
165
- filter: { userId, enabled: true },
166
- sort: "-updatedAt"
167
- });
168
- if (!policy) return;
169
- const rejectUnpriced = valueOf(policy, "rejectUnpricedModel");
165
+ const group = await (0, import_request_cache.resolveRequestUserGroup)(ctx, userId);
166
+ if (!group.enabled) return;
167
+ const rejectUnpriced = group.rejectUnpricedModel;
170
168
  if (!price && rejectUnpriced) {
171
169
  throw new AiApiQuotaError(
172
170
  "model_price_not_configured",
173
171
  `Pricing is not configured for '${serviceName}/${resolved.modelId}'.`
174
172
  );
175
173
  }
176
- const policyCurrency = valueOf(policy, "currency");
177
- if (price && policyCurrency !== price.currency) {
174
+ if (price && group.currency !== price.currency) {
178
175
  throw new AiApiQuotaError(
179
176
  "quota_currency_mismatch",
180
- `Quota currency '${policyCurrency}' does not match model price currency '${price.currency}'.`
177
+ `Quota currency '${group.currency}' does not match model price currency '${price.currency}'.`
181
178
  );
182
179
  }
183
180
  const body = ctx.request.body ?? {};
@@ -186,13 +183,15 @@ async function prepareLlmBilling(ctx, resolved) {
186
183
  const estimatedOutputTokens = normalizePositiveInteger(body.max_completion_tokens ?? body.max_tokens, defaultOutput);
187
184
  const reservedTokens = estimatedInputTokens + estimatedOutputTokens;
188
185
  const reservedCost = price ? calculateLlmCost(estimatedInputTokens, estimatedOutputTokens, price) : "0.00000000";
189
- const period = getPeriodBounds(valueOf(policy, "periodType"), valueOf(policy, "timezone"));
190
- const Bucket = ctx.db.getModel("aiApiUserQuotaBuckets");
186
+ const period = getPeriodBounds(group.periodType, group.timezone);
187
+ const bucketUserId = group.quotaMode === "share" ? 0 : userId;
188
+ const Bucket = ctx.db.getModel("aiApiGroupQuotaBuckets");
191
189
  const reservation = await ctx.db.sequelize.transaction(async (transaction) => {
192
190
  const [bucket] = await Bucket.findOrCreate({
193
- where: { policyId: valueOf(policy, "id"), periodStart: period.start },
191
+ where: { groupId: group.id, userId: bucketUserId, periodStart: period.start },
194
192
  defaults: {
195
- userId,
193
+ groupId: group.id,
194
+ userId: bucketUserId,
196
195
  periodEnd: period.end,
197
196
  requestCount: 0,
198
197
  totalTokens: 0,
@@ -206,26 +205,18 @@ async function prepareLlmBilling(ctx, resolved) {
206
205
  await bucket.reload({ transaction, lock: transaction.LOCK.UPDATE });
207
206
  const requestCount = BigInt(String(bucket.get("requestCount") ?? 0));
208
207
  const reservedRequests = BigInt(String(bucket.get("reservedRequests") ?? 0));
209
- if (exceedsIntegerLimit(requestCount + reservedRequests, 1n, valueOf(policy, "requestLimit"))) {
210
- throw new AiApiQuotaError("request_quota_exceeded", "The request quota for this user has been exceeded.");
208
+ if (exceedsIntegerLimit(requestCount + reservedRequests, 1n, group.requestLimit)) {
209
+ throw new AiApiQuotaError("request_quota_exceeded", "The request quota for this group has been exceeded.");
211
210
  }
212
211
  const totalTokens = BigInt(String(bucket.get("totalTokens") ?? 0));
213
212
  const alreadyReservedTokens = BigInt(String(bucket.get("reservedTokens") ?? 0));
214
- if (exceedsIntegerLimit(
215
- totalTokens + alreadyReservedTokens,
216
- BigInt(reservedTokens),
217
- valueOf(policy, "totalTokenLimit")
218
- )) {
219
- throw new AiApiQuotaError("token_quota_exceeded", "The token quota for this user has been exceeded.");
213
+ if (exceedsIntegerLimit(totalTokens + alreadyReservedTokens, BigInt(reservedTokens), group.totalTokenLimit)) {
214
+ throw new AiApiQuotaError("token_quota_exceeded", "The token quota for this group has been exceeded.");
220
215
  }
221
216
  const cost = decimalUnits(bucket.get("cost"), COST_SCALE);
222
217
  const alreadyReservedCost = decimalUnits(bucket.get("reservedCost"), COST_SCALE);
223
- if (exceedsDecimalLimit(
224
- cost + alreadyReservedCost,
225
- decimalUnits(reservedCost, COST_SCALE),
226
- valueOf(policy, "costLimit")
227
- )) {
228
- throw new AiApiQuotaError("cost_quota_exceeded", "The cost quota for this user has been exceeded.");
218
+ if (exceedsDecimalLimit(cost + alreadyReservedCost, decimalUnits(reservedCost, COST_SCALE), group.costLimit)) {
219
+ throw new AiApiQuotaError("cost_quota_exceeded", "The cost quota for this group has been exceeded.");
229
220
  }
230
221
  await bucket.update(
231
222
  {
@@ -237,12 +228,13 @@ async function prepareLlmBilling(ctx, resolved) {
237
228
  );
238
229
  return {
239
230
  bucketId: bucket.get("id"),
240
- policyId: valueOf(policy, "id"),
231
+ groupId: group.id,
232
+ quotaMode: group.quotaMode,
241
233
  estimatedInputTokens,
242
234
  estimatedOutputTokens,
243
235
  reservedTokens,
244
236
  reservedCost,
245
- missingUsageBehavior: valueOf(policy, "missingUsageBehavior") === "allow" ? "allow" : "use_reserved"
237
+ missingUsageBehavior: group.missingUsageBehavior === "allow" ? "allow" : "use_reserved"
246
238
  };
247
239
  });
248
240
  billing.reservation = reservation;
@@ -280,7 +272,7 @@ async function finalizeLlmBilling(ctx, providerUsage, succeeded) {
280
272
  const cost = numbers && billing.price ? calculateLlmCost(numbers.input, numbers.output, billing.price) : void 0;
281
273
  const reservation = billing.reservation;
282
274
  if (reservation) {
283
- const Bucket = ctx.db.getModel("aiApiUserQuotaBuckets");
275
+ const Bucket = ctx.db.getModel("aiApiGroupQuotaBuckets");
284
276
  await ctx.db.sequelize.transaction(async (transaction) => {
285
277
  const bucket = await Bucket.findByPk(reservation.bucketId, { transaction, lock: transaction.LOCK.UPDATE });
286
278
  if (!bucket) return;
@@ -310,12 +302,18 @@ async function finalizeLlmBilling(ctx, providerUsage, succeeded) {
310
302
  });
311
303
  }
312
304
  return {
313
- usage: numbers ? { prompt_tokens: numbers.input, completion_tokens: numbers.output, total_tokens: numbers.total } : providerUsage,
305
+ usage: numbers ? {
306
+ prompt_tokens: numbers.input,
307
+ completion_tokens: numbers.output,
308
+ total_tokens: numbers.total,
309
+ prompt_cache_tokens: (providerUsage == null ? void 0 : providerUsage.prompt_cache_tokens) ?? null
310
+ } : providerUsage,
314
311
  estimatedCost: cost,
315
312
  currency: (_b = billing.price) == null ? void 0 : _b.currency,
316
313
  costStatus,
317
314
  modelPriceId: (_c = billing.price) == null ? void 0 : _c.id,
318
- quotaPolicyId: reservation == null ? void 0 : reservation.policyId,
315
+ groupId: reservation == null ? void 0 : reservation.groupId,
316
+ quotaMode: reservation == null ? void 0 : reservation.quotaMode,
319
317
  inputPricePerMillionTokens: (_d = billing.price) == null ? void 0 : _d.inputPricePerMillionTokens,
320
318
  outputPricePerMillionTokens: (_e = billing.price) == null ? void 0 : _e.outputPricePerMillionTokens,
321
319
  fixedCostPerRequest: (_f = billing.price) == null ? void 0 : _f.fixedCostPerRequest
@@ -43,7 +43,7 @@ var ai_api_config_default = (0, import_database.defineCollection)({
43
43
  {
44
44
  name: "defaultAiEmployee",
45
45
  type: "string",
46
- comment: "Username of the default AI Employee for system prompt injection"
46
+ comment: "Username of the default AI Employee used by agent mode. Direct LLM mode ignores it."
47
47
  },
48
48
  {
49
49
  name: "defaultLlmService",
@@ -56,18 +56,18 @@ var ai_api_config_default = (0, import_database.defineCollection)({
56
56
  defaultValue: [],
57
57
  comment: "Array of llmService names to expose. Empty = expose all enabled services"
58
58
  },
59
- {
60
- name: "rateLimitPerMinute",
61
- type: "integer",
62
- defaultValue: 60,
63
- comment: "Max requests per user per minute"
64
- },
65
59
  {
66
60
  name: "maxRequestBodyMb",
67
61
  type: "integer",
68
62
  defaultValue: 10,
69
63
  comment: "Max request body size in MB. Raise this to accept inline base64 images in vision requests."
70
64
  },
65
+ {
66
+ name: "pdfRenderPagesAsImages",
67
+ type: "boolean",
68
+ defaultValue: false,
69
+ comment: "When true, PDF file/file_url blocks are rendered to per-page PNG images and sent as image_url blocks. Requires a registered PdfToImageRenderer. When false or no renderer is available, PDFs are forwarded as file blocks."
70
+ },
71
71
  {
72
72
  name: "quotaEnabled",
73
73
  type: "boolean",
@@ -0,0 +1,62 @@
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
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var ai_api_group_members_exports = {};
28
+ __export(ai_api_group_members_exports, {
29
+ default: () => ai_api_group_members_default
30
+ });
31
+ module.exports = __toCommonJS(ai_api_group_members_exports);
32
+ var import_database = require("@nocobase/database");
33
+ var ai_api_group_members_default = (0, import_database.defineCollection)({
34
+ name: "aiApiGroupMembers",
35
+ autoGenId: true,
36
+ fields: [
37
+ { name: "groupId", type: "bigInt", allowNull: false, index: true },
38
+ {
39
+ name: "group",
40
+ type: "belongsTo",
41
+ target: "aiApiUsageGroups",
42
+ targetKey: "id",
43
+ foreignKey: "groupId",
44
+ constraints: false
45
+ },
46
+ { name: "userId", type: "bigInt", allowNull: false, index: true },
47
+ {
48
+ name: "user",
49
+ type: "belongsTo",
50
+ target: "users",
51
+ targetKey: "id",
52
+ foreignKey: "userId",
53
+ constraints: false
54
+ }
55
+ ],
56
+ indexes: [
57
+ {
58
+ fields: ["userId"],
59
+ unique: true
60
+ }
61
+ ]
62
+ });
@@ -0,0 +1,63 @@
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
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var ai_api_group_quota_buckets_exports = {};
28
+ __export(ai_api_group_quota_buckets_exports, {
29
+ default: () => ai_api_group_quota_buckets_default
30
+ });
31
+ module.exports = __toCommonJS(ai_api_group_quota_buckets_exports);
32
+ var import_database = require("@nocobase/database");
33
+ var ai_api_group_quota_buckets_default = (0, import_database.defineCollection)({
34
+ name: "aiApiGroupQuotaBuckets",
35
+ autoGenId: true,
36
+ fields: [
37
+ { name: "groupId", type: "bigInt", allowNull: false, index: true },
38
+ {
39
+ name: "group",
40
+ type: "belongsTo",
41
+ target: "aiApiUsageGroups",
42
+ targetKey: "id",
43
+ foreignKey: "groupId",
44
+ constraints: false
45
+ },
46
+ // userId = 0 means the shared bucket in share mode; real user ids are always > 0.
47
+ { name: "userId", type: "bigInt", allowNull: false, defaultValue: 0, index: true },
48
+ { name: "periodStart", type: "datetimeTz", allowNull: false, index: true },
49
+ { name: "periodEnd", type: "datetimeTz", allowNull: false },
50
+ { name: "requestCount", type: "bigInt", allowNull: false, defaultValue: 0 },
51
+ { name: "totalTokens", type: "bigInt", allowNull: false, defaultValue: 0 },
52
+ { name: "cost", type: "decimal", precision: 20, scale: 8, allowNull: false, defaultValue: 0 },
53
+ { name: "reservedRequests", type: "bigInt", allowNull: false, defaultValue: 0 },
54
+ { name: "reservedTokens", type: "bigInt", allowNull: false, defaultValue: 0 },
55
+ { name: "reservedCost", type: "decimal", precision: 20, scale: 8, allowNull: false, defaultValue: 0 }
56
+ ],
57
+ indexes: [
58
+ {
59
+ fields: ["groupId", "userId", "periodStart"],
60
+ unique: true
61
+ }
62
+ ]
63
+ });
@@ -66,6 +66,12 @@ var ai_api_model_metadata_default = (0, import_database.defineCollection)({
66
66
  allowNull: true,
67
67
  comment: "Human-readable description returned as description in the model object."
68
68
  },
69
+ {
70
+ name: "systemPrompt",
71
+ type: "text",
72
+ allowNull: true,
73
+ comment: "Initial system prompt prepended as the first system message of every request for this model. Never replaces the client system prompt."
74
+ },
69
75
  {
70
76
  name: "enabled",
71
77
  type: "boolean",