plugin-custom-llm 1.2.1 → 1.2.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.
Files changed (33) hide show
  1. package/dist/client/index.js +9 -0
  2. package/dist/externalVersion.js +16 -7
  3. package/dist/index.js +9 -0
  4. package/dist/server/index.js +9 -0
  5. package/dist/server/llm-providers/custom-llm.js +157 -5
  6. package/dist/server/plugin.js +9 -0
  7. package/dist/swagger.js +39 -0
  8. package/package.json +9 -1
  9. package/src/client/client.d.ts +249 -0
  10. package/src/client/index.tsx +19 -0
  11. package/src/client/llm-providers/custom-llm/ModelSettings.tsx +139 -0
  12. package/src/client/llm-providers/custom-llm/ProviderSettings.tsx +115 -0
  13. package/src/client/llm-providers/custom-llm/index.ts +10 -0
  14. package/src/client/locale.ts +8 -0
  15. package/{dist/client/models/index.d.ts → src/client/models/index.ts} +12 -10
  16. package/src/client/plugin.tsx +10 -0
  17. package/{dist/index.d.ts → src/index.ts} +2 -2
  18. package/src/locale/en-US.json +29 -0
  19. package/src/locale/vi-VN.json +29 -0
  20. package/src/locale/zh-CN.json +16 -0
  21. package/src/server/collections/.gitkeep +0 -0
  22. package/{dist/server/index.d.ts → src/server/index.ts} +1 -1
  23. package/src/server/llm-providers/custom-llm.ts +992 -0
  24. package/src/server/plugin.ts +27 -0
  25. package/src/swagger.ts +9 -0
  26. package/dist/client/index.d.ts +0 -8
  27. package/dist/client/llm-providers/custom-llm/ModelSettings.d.ts +0 -2
  28. package/dist/client/llm-providers/custom-llm/ProviderSettings.d.ts +0 -2
  29. package/dist/client/llm-providers/custom-llm/index.d.ts +0 -2
  30. package/dist/client/locale.d.ts +0 -2
  31. package/dist/client/plugin.d.ts +0 -5
  32. package/dist/server/llm-providers/custom-llm.d.ts +0 -54
  33. package/dist/server/plugin.d.ts +0 -12
@@ -1 +1,10 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
1
10
  !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("@nocobase/plugin-ai/client"),require("@nocobase/client"),require("@nocobase/utils/client"),require("antd"),require("react-i18next")):"function"==typeof define&&define.amd?define("plugin-custom-llm",["react","@nocobase/plugin-ai/client","@nocobase/client","@nocobase/utils/client","antd","react-i18next"],t):"object"==typeof exports?exports["plugin-custom-llm"]=t(require("react"),require("@nocobase/plugin-ai/client"),require("@nocobase/client"),require("@nocobase/utils/client"),require("antd"),require("react-i18next")):e["plugin-custom-llm"]=t(e.react,e["@nocobase/plugin-ai/client"],e["@nocobase/client"],e["@nocobase/utils/client"],e.antd,e["react-i18next"])}(self,function(e,t,n,o,r,i){return function(){"use strict";var a={772:function(e){e.exports=n},645:function(e){e.exports=t},584:function(e){e.exports=o},721:function(e){e.exports=r},156:function(t){t.exports=e},238:function(e){e.exports=i}},c={};function u(e){var t=c[e];if(void 0!==t)return t.exports;var n=c[e]={exports:{}};return a[e](n,n.exports,u),n.exports}u.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return u.d(t,{a:t}),t},u.d=function(e,t){for(var n in t)u.o(t,n)&&!u.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},u.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},u.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var l={};return!function(){u.r(l),u.d(l,{PluginCustomLLMClient:function(){return g},default:function(){return S}});var e=u(772),t=u(156),n=u.n(t),o=u(584),r=u(238),i="@nocobase/plugin-custom-llm",a=u(721),c=u(645),p=function(){var t=(0,r.useTranslation)(i,{nsMode:"fallback"}).t;return n().createElement("div",{style:{marginBottom:24}},n().createElement(a.Collapse,{bordered:!1,size:"small",items:[{key:"options",label:t("Options"),forceRender:!0,children:n().createElement(e.SchemaComponent,{schema:{type:"void",name:"custom-llm",properties:{temperature:{title:(0,o.tval)("Temperature",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:.7,"x-component-props":{step:.1,min:0,max:2}},maxCompletionTokens:{title:(0,o.tval)("Max completion tokens",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:-1},topP:{title:(0,o.tval)("Top P",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:1,"x-component-props":{step:.1,min:0,max:1}},frequencyPenalty:{title:(0,o.tval)("Frequency penalty",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:0,"x-component-props":{step:.1,min:-2,max:2}},presencePenalty:{title:(0,o.tval)("Presence penalty",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:0,"x-component-props":{step:.1,min:-2,max:2}},responseFormat:{title:(0,o.tval)("Response format",{ns:i}),type:"string","x-decorator":"FormItem","x-component":"Select",enum:[{label:t("Text"),value:"text"},{label:t("JSON"),value:"json_object"}],default:"text"},timeout:{title:(0,o.tval)("Timeout (ms)",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:6e4},maxRetries:{title:(0,o.tval)("Max retries",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:1}}}})}]}))},s={components:{ProviderSettingsForm:function(){return n().createElement(e.SchemaComponent,{schema:{type:"void",properties:{apiKey:{title:(0,o.tval)("API Key",{ns:i}),type:"string",required:!0,"x-decorator":"FormItem","x-component":"TextAreaWithGlobalScope"},disableStream:{title:(0,o.tval)("Disable streaming",{ns:i}),type:"boolean","x-decorator":"FormItem","x-component":"Checkbox","x-content":(0,o.tval)("Disable streaming description",{ns:i})},streamKeepAlive:{title:(0,o.tval)("Stream keepalive",{ns:i}),type:"boolean","x-decorator":"FormItem","x-component":"Checkbox","x-content":(0,o.tval)("Stream keepalive description",{ns:i})},keepAliveIntervalMs:{title:(0,o.tval)("Keepalive interval (ms)",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber","x-component-props":{placeholder:"5000",min:1e3,step:1e3,style:{width:"100%"}},description:(0,o.tval)("Keepalive interval description",{ns:i})},keepAliveContent:{title:(0,o.tval)("Keepalive content",{ns:i}),type:"string","x-decorator":"FormItem","x-component":"Input","x-component-props":{placeholder:"..."},description:(0,o.tval)("Keepalive content description",{ns:i})},timeout:{title:(0,o.tval)("Timeout (ms)",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber","x-component-props":{placeholder:"120000",min:0,step:1e3,style:{width:"100%"}},description:(0,o.tval)("Timeout description",{ns:i})},requestConfig:{title:(0,o.tval)("Request config (JSON)",{ns:i}),type:"string","x-decorator":"FormItem","x-component":"Input.TextArea","x-component-props":{placeholder:JSON.stringify({extraHeaders:{},extraBody:{},modelKwargs:{}},null,2),rows:6,style:{fontFamily:"monospace",fontSize:12}},description:(0,o.tval)("Request config description",{ns:i})},responseConfig:{title:(0,o.tval)("Response config (JSON)",{ns:i}),type:"string","x-decorator":"FormItem","x-component":"Input.TextArea","x-component-props":{placeholder:JSON.stringify({contentPath:"auto",reasoningKey:"reasoning_content",responseMapping:{content:"message.response"}},null,2),rows:8,style:{fontFamily:"monospace",fontSize:12}},description:(0,o.tval)("Response config description",{ns:i})}}}})},ModelSettingsForm:function(){return n().createElement(e.SchemaComponent,{components:{Options:p,ModelSelect:c.ModelSelect},schema:{type:"void",properties:{model:{title:(0,o.tval)("Model",{ns:i}),type:"string",required:!0,"x-decorator":"FormItem","x-component":"ModelSelect"},options:{type:"void","x-component":"Options"}}}})}}};function m(e,t,n,o,r,i,a){try{var c=e[i](a),u=c.value}catch(e){n(e);return}c.done?t(u):Promise.resolve(u).then(o,r)}function f(e){return function(){var t=this,n=arguments;return new Promise(function(o,r){var i=e.apply(t,n);function a(e){m(i,o,r,a,c,"next",e)}function c(e){m(i,o,r,a,c,"throw",e)}a(void 0)})}}function d(e,t,n){return(d=x()?Reflect.construct:function(e,t,n){var o=[null];o.push.apply(o,t);var r=new(Function.bind.apply(e,o));return n&&b(r,n.prototype),r}).apply(null,arguments)}function y(e){return(y=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function b(e,t){return(b=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function v(e){var t="function"==typeof Map?new Map:void 0;return(v=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 d(e,arguments,y(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),b(n,e)})(e)}function x(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(e){}return(x=function(){return!!e})()}function h(e,t){var n,o,r,i,a={label:0,sent:function(){if(1&r[0])throw r[1];return r[1]},trys:[],ops:[]};return i={next:c(0),throw:c(1),return:c(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function c(i){return function(c){var u=[i,c];if(n)throw TypeError("Generator is already executing.");for(;a;)try{if(n=1,o&&(r=2&u[0]?o.return:u[0]?o.throw||((r=o.return)&&r.call(o),0):o.next)&&!(r=r.call(o,u[1])).done)return r;switch(o=0,r&&(u=[2&u[0],r.value]),u[0]){case 0:case 1:r=u;break;case 4:return a.label++,{value:u[1],done:!1};case 5:a.label++,o=u[1],u=[0];continue;case 7:u=a.ops.pop(),a.trys.pop();continue;default:if(!(r=(r=a.trys).length>0&&r[r.length-1])&&(6===u[0]||2===u[0])){a=0;continue}if(3===u[0]&&(!r||u[1]>r[0]&&u[1]<r[3])){a.label=u[1];break}if(6===u[0]&&a.label<r[1]){a.label=r[1],r=u;break}if(r&&a.label<r[2]){a.label=r[2],a.ops.push(u);break}r[2]&&a.ops.pop(),a.trys.pop();continue}u=t.call(e,a)}catch(e){u=[6,e],o=0}finally{n=r=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}}}var g=function(e){var t;if("function"!=typeof e&&null!==e)throw TypeError("Super expression must either be null or a function");function n(){var e,t;if(!(this instanceof n))throw TypeError("Cannot call a class as a function");return e=n,t=arguments,e=y(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,x()?Reflect.construct(e,t||[],y(this).constructor):e.apply(this,t))}return n.prototype=Object.create(e&&e.prototype,{constructor:{value:n,writable:!0,configurable:!0}}),e&&b(n,e),t=[{key:"afterAdd",value:function(){return f(function(){return h(this,function(e){return[2]})})()}},{key:"beforeLoad",value:function(){return f(function(){return h(this,function(e){return[2]})})()}},{key:"load",value:function(){var e=this;return f(function(){return h(this,function(t){return e.aiPlugin.aiManager.registerLLMProvider("custom-llm",s),[2]})})()}},{key:"aiPlugin",get:function(){return this.app.pm.get("ai")}}],function(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}(n.prototype,t),n}(v(e.Plugin)),S=g}(),l}()});
@@ -1,13 +1,22 @@
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
+
1
10
  module.exports = {
2
- "@nocobase/client": "2.0.20",
3
- "@nocobase/plugin-ai": "2.0.20",
11
+ "@nocobase/client": "2.0.32",
12
+ "@nocobase/plugin-ai": "2.0.32",
4
13
  "react-i18next": "11.18.6",
5
- "@nocobase/server": "2.0.20",
6
- "@nocobase/flow-engine": "2.0.20",
7
- "@nocobase/database": "2.0.20",
14
+ "@nocobase/server": "2.0.32",
15
+ "@nocobase/flow-engine": "2.0.32",
16
+ "@nocobase/database": "2.0.32",
8
17
  "axios": "1.14.0",
9
- "@nocobase/actions": "2.0.20",
18
+ "@nocobase/actions": "2.0.32",
10
19
  "react": "18.3.1",
11
- "@nocobase/utils": "2.0.20",
20
+ "@nocobase/utils": "2.0.32",
12
21
  "antd": "5.24.2"
13
22
  };
package/dist/index.js CHANGED
@@ -1,3 +1,12 @@
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
+
1
10
  var __create = Object.create;
2
11
  var __defProp = Object.defineProperty;
3
12
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -1,3 +1,12 @@
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
+
1
10
  var __create = Object.create;
2
11
  var __defProp = Object.defineProperty;
3
12
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -1,3 +1,12 @@
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
+
1
10
  var __create = Object.create;
2
11
  var __defProp = Object.defineProperty;
3
12
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -560,7 +569,13 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
560
569
  * We cannot fix that in plugin-ai (core), so we re-implement file reading here
561
570
  * with the prefix stripped before the cwd join.
562
571
  */
563
- async readFileAsBase64(ctx, attachment) {
572
+ /**
573
+ * Reads the attachment and returns its base64-encoded content plus, when the
574
+ * file lives on the local filesystem, the resolved absolute path so callers
575
+ * can hand that path directly to tools like DocPixie and avoid a second
576
+ * write-to-disk round-trip.
577
+ */
578
+ async readFileData(ctx, attachment) {
564
579
  const fileManager = this.app.pm.get("file-manager");
565
580
  const rawUrl = await fileManager.getFileURL(attachment);
566
581
  const url = decodeURIComponent(rawUrl);
@@ -569,11 +584,18 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
569
584
  const ua = ctx.get("user-agent") || "";
570
585
  const response = await import_axios.default.get(url, {
571
586
  responseType: "arraybuffer",
572
- // Default 30s timeout to prevent slow file servers from blocking indefinitely
573
587
  timeout: 3e4,
574
588
  headers: { referer, "User-Agent": ua }
575
589
  });
576
- return Buffer.from(response.data).toString("base64");
590
+ return { base64: Buffer.from(response.data).toString("base64") };
591
+ }
592
+ if (url.includes("/api/attachments:stream")) {
593
+ const { stream } = await fileManager.getFileStream(attachment);
594
+ const chunks = [];
595
+ for await (const chunk of stream) {
596
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
597
+ }
598
+ return { base64: Buffer.concat(chunks).toString("base64") };
577
599
  }
578
600
  let localPath = url;
579
601
  const appPublicPath = (process.env.APP_PUBLIC_PATH || "/").replace(/\/+$/, "");
@@ -586,7 +608,7 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
586
608
  throw new Error(`Attachment path escapes storage root: ${localPath}`);
587
609
  }
588
610
  const data = await import_promises.default.readFile(absPath);
589
- return Buffer.from(data).toString("base64");
611
+ return { base64: Buffer.from(data).toString("base64"), absPath };
590
612
  }
591
613
  /**
592
614
  * Override parseAttachment to convert all attachments into formats that
@@ -604,11 +626,141 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
604
626
  * This method is entirely self-contained — it does not call super — so it is
605
627
  * safe to use without modifying plugin-ai core.
606
628
  */
629
+ /**
630
+ * Try to extract text from an attachment using DocPixie (if available and
631
+ * the file type is supported). Returns null if DocPixie is unavailable,
632
+ * not ready, or the file type is not supported.
633
+ */
634
+ /**
635
+ * Check whether the DocPixie skill (`docpixie.query.document`) is configured
636
+ * on the AI employee that initiated this request.
637
+ *
638
+ * Reads `ctx.action.params.values.aiEmployee` (the employee username set by the
639
+ * `sendMessages` action handler), then looks up the employee's `skillSettings`
640
+ * from DB. Result is cached on `ctx.state._docPixieActive` for the request lifetime.
641
+ */
642
+ async hasDocPixieSkill(ctx) {
643
+ var _a, _b, _c, _d, _e;
644
+ if (ctx.state._docPixieActive !== void 0) return ctx.state._docPixieActive;
645
+ try {
646
+ const employeeUsername = (_c = (_b = (_a = ctx.action) == null ? void 0 : _a.params) == null ? void 0 : _b.values) == null ? void 0 : _c.aiEmployee;
647
+ if (!employeeUsername) {
648
+ ctx.state._docPixieActive = false;
649
+ return false;
650
+ }
651
+ const employee = await ctx.db.getRepository("aiEmployees").findOne({
652
+ filter: { username: String(employeeUsername) },
653
+ fields: ["skillSettings"]
654
+ });
655
+ const skills = ((_e = (_d = employee == null ? void 0 : employee.get) == null ? void 0 : _d.call(employee, "skillSettings")) == null ? void 0 : _e.skills) ?? [];
656
+ const has = skills.some((s) => s.name === "docpixie.query.document");
657
+ ctx.state._docPixieActive = has;
658
+ return has;
659
+ } catch {
660
+ ctx.state._docPixieActive = false;
661
+ return false;
662
+ }
663
+ }
664
+ /**
665
+ * Run the full DocPixie ingestion pipeline (extract pages → generate summary → index).
666
+ * Returns a formatted `<processed_document>` context block the LLM can use immediately,
667
+ * plus a clear instruction to call the RAG tool with the returned documentId for details.
668
+ *
669
+ * Prefers passing `absPath` directly for local-storage files to avoid a second
670
+ * write-to-disk round-trip. Falls back to Buffer for remote / S3 files.
671
+ *
672
+ * Returns null if DocPixie is unavailable, not configured, or processing fails.
673
+ */
674
+ async tryDocPixieFullProcess(fileData, filename, ctx) {
675
+ var _a, _b, _c, _d;
676
+ try {
677
+ const docpixie = this.app.pm.get("docpixie");
678
+ if (!((_b = (_a = docpixie == null ? void 0 : docpixie.service) == null ? void 0 : _a.isReady) == null ? void 0 : _b.call(_a))) return null;
679
+ const userId = (_d = (_c = ctx.state) == null ? void 0 : _c.currentUser) == null ? void 0 : _d.id;
680
+ let result;
681
+ if (fileData.absPath) {
682
+ result = await docpixie.service.processDocumentFromPath(fileData.absPath, filename, { userId });
683
+ } else {
684
+ const buffer = Buffer.from(fileData.base64, "base64");
685
+ result = await docpixie.service.processDocumentFromBuffer(buffer, filename, { userId });
686
+ }
687
+ const { documentId, summary, pageCount } = result;
688
+ const summaryText = (summary == null ? void 0 : summary.trim()) || "No summary available.";
689
+ return `<processed_document id="${documentId}" filename="${filename}" pages="${pageCount}">
690
+ <summary>
691
+ ${summaryText}
692
+ </summary>
693
+ <rag_instruction>This document is fully indexed. Call docpixie.query.document with documentId=${documentId} to retrieve specific details.</rag_instruction>
694
+ </processed_document>`;
695
+ } catch {
696
+ return null;
697
+ }
698
+ }
699
+ /**
700
+ * Try to extract text from an attachment using DocPixie (transient — no DB indexing).
701
+ * When `absPath` is provided (local-storage file), DocPixie reads the file
702
+ * directly — no Buffer decode/re-encode or extra temp-file write.
703
+ * Falls back to `extractTextFromBuffer` for remote/S3 files.
704
+ * Returns null if DocPixie is unavailable, not ready, or file type unsupported.
705
+ */
706
+ async tryDocPixieExtract(fileData, filename) {
707
+ try {
708
+ const docpixie = this.app.pm.get("docpixie");
709
+ if (!(docpixie == null ? void 0 : docpixie.service)) return null;
710
+ let text;
711
+ if (fileData.absPath) {
712
+ text = await docpixie.service.extractTextFromPath(fileData.absPath, filename);
713
+ } else {
714
+ const buffer = Buffer.from(fileData.base64, "base64");
715
+ text = await docpixie.service.extractTextFromBuffer(buffer, filename);
716
+ }
717
+ return text || null;
718
+ } catch {
719
+ return null;
720
+ }
721
+ }
607
722
  async parseAttachment(ctx, attachment) {
608
723
  const mimetype = attachment.mimetype || "application/octet-stream";
609
724
  const filename = attachment.filename || attachment.name || "file";
610
- const data = await this.readFileAsBase64(ctx, attachment);
725
+ const fileData = await this.readFileData(ctx, attachment);
726
+ const { base64: data } = fileData;
727
+ const isDocPixieSupported = mimetype === "application/pdf" || mimetype.startsWith("image/");
728
+ if (isDocPixieSupported && await this.hasDocPixieSkill(ctx)) {
729
+ const contextBlock = await this.tryDocPixieFullProcess(fileData, filename, ctx);
730
+ if (contextBlock) {
731
+ return {
732
+ placement: "contentBlocks",
733
+ content: { type: "text", text: contextBlock }
734
+ };
735
+ }
736
+ }
737
+ if (mimetype === "application/pdf") {
738
+ const extracted = await this.tryDocPixieExtract(fileData, filename);
739
+ if (extracted) {
740
+ return {
741
+ placement: "contentBlocks",
742
+ content: {
743
+ type: "text",
744
+ text: `<attachment filename="${filename}" type="${mimetype}">
745
+ ${extracted}
746
+ </attachment>`
747
+ }
748
+ };
749
+ }
750
+ }
611
751
  if (mimetype.startsWith("image/")) {
752
+ const extracted = await this.tryDocPixieExtract(fileData, filename);
753
+ if (extracted) {
754
+ return {
755
+ placement: "contentBlocks",
756
+ content: {
757
+ type: "text",
758
+ text: `<attachment filename="${filename}" type="${mimetype}">
759
+ ${extracted}
760
+ </attachment>`
761
+ }
762
+ };
763
+ }
612
764
  return {
613
765
  placement: "contentBlocks",
614
766
  content: {
@@ -1,3 +1,12 @@
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
+
1
10
  var __defProp = Object.defineProperty;
2
11
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
12
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -0,0 +1,39 @@
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 swagger_exports = {};
28
+ __export(swagger_exports, {
29
+ default: () => swagger_default
30
+ });
31
+ module.exports = __toCommonJS(swagger_exports);
32
+ var swagger_default = {
33
+ info: {
34
+ title: "NocoBase API - Custom LLM Plugin",
35
+ description: "Registers a custom OpenAI-compatible LLM provider with the AI plugin. This plugin has no direct HTTP endpoints \u2014 all API access is through the AI API plugin gateway (`/api/ai-llm/v1/*`) using models registered under the `custom-llm` service."
36
+ },
37
+ tags: [{ name: "custom-llm", description: "Custom LLM provider (no direct endpoints)" }],
38
+ paths: {}
39
+ };
package/package.json CHANGED
@@ -3,8 +3,16 @@
3
3
  "displayName": "AI LLM: Custom (OpenAI Compatible)",
4
4
  "displayName.zh-CN": "AI LLM:自定义(OpenAI 兼容)",
5
5
  "description": "OpenAI-compatible LLM provider with auto response format detection for external LLM services.",
6
- "version": "1.2.1",
6
+ "version": "1.2.2",
7
7
  "main": "dist/server/index.js",
8
+ "files": [
9
+ "dist",
10
+ "client.js",
11
+ "client.d.ts",
12
+ "server.js",
13
+ "server.d.ts",
14
+ "src"
15
+ ],
8
16
  "nocobase": {
9
17
  "supportedVersions": [
10
18
  "2.x"
@@ -0,0 +1,249 @@
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
+ // CSS modules
11
+ type CSSModuleClasses = { readonly [key: string]: string };
12
+
13
+ declare module '*.module.css' {
14
+ const classes: CSSModuleClasses;
15
+ export default classes;
16
+ }
17
+ declare module '*.module.scss' {
18
+ const classes: CSSModuleClasses;
19
+ export default classes;
20
+ }
21
+ declare module '*.module.sass' {
22
+ const classes: CSSModuleClasses;
23
+ export default classes;
24
+ }
25
+ declare module '*.module.less' {
26
+ const classes: CSSModuleClasses;
27
+ export default classes;
28
+ }
29
+ declare module '*.module.styl' {
30
+ const classes: CSSModuleClasses;
31
+ export default classes;
32
+ }
33
+ declare module '*.module.stylus' {
34
+ const classes: CSSModuleClasses;
35
+ export default classes;
36
+ }
37
+ declare module '*.module.pcss' {
38
+ const classes: CSSModuleClasses;
39
+ export default classes;
40
+ }
41
+ declare module '*.module.sss' {
42
+ const classes: CSSModuleClasses;
43
+ export default classes;
44
+ }
45
+
46
+ // CSS
47
+ declare module '*.css' { }
48
+ declare module '*.scss' { }
49
+ declare module '*.sass' { }
50
+ declare module '*.less' { }
51
+ declare module '*.styl' { }
52
+ declare module '*.stylus' { }
53
+ declare module '*.pcss' { }
54
+ declare module '*.sss' { }
55
+
56
+ // Built-in asset types
57
+ // see `src/node/constants.ts`
58
+
59
+ // images
60
+ declare module '*.apng' {
61
+ const src: string;
62
+ export default src;
63
+ }
64
+ declare module '*.png' {
65
+ const src: string;
66
+ export default src;
67
+ }
68
+ declare module '*.jpg' {
69
+ const src: string;
70
+ export default src;
71
+ }
72
+ declare module '*.jpeg' {
73
+ const src: string;
74
+ export default src;
75
+ }
76
+ declare module '*.jfif' {
77
+ const src: string;
78
+ export default src;
79
+ }
80
+ declare module '*.pjpeg' {
81
+ const src: string;
82
+ export default src;
83
+ }
84
+ declare module '*.pjp' {
85
+ const src: string;
86
+ export default src;
87
+ }
88
+ declare module '*.gif' {
89
+ const src: string;
90
+ export default src;
91
+ }
92
+ declare module '*.svg' {
93
+ const src: string;
94
+ export default src;
95
+ }
96
+ declare module '*.ico' {
97
+ const src: string;
98
+ export default src;
99
+ }
100
+ declare module '*.webp' {
101
+ const src: string;
102
+ export default src;
103
+ }
104
+ declare module '*.avif' {
105
+ const src: string;
106
+ export default src;
107
+ }
108
+
109
+ // media
110
+ declare module '*.mp4' {
111
+ const src: string;
112
+ export default src;
113
+ }
114
+ declare module '*.webm' {
115
+ const src: string;
116
+ export default src;
117
+ }
118
+ declare module '*.ogg' {
119
+ const src: string;
120
+ export default src;
121
+ }
122
+ declare module '*.mp3' {
123
+ const src: string;
124
+ export default src;
125
+ }
126
+ declare module '*.wav' {
127
+ const src: string;
128
+ export default src;
129
+ }
130
+ declare module '*.flac' {
131
+ const src: string;
132
+ export default src;
133
+ }
134
+ declare module '*.aac' {
135
+ const src: string;
136
+ export default src;
137
+ }
138
+ declare module '*.opus' {
139
+ const src: string;
140
+ export default src;
141
+ }
142
+ declare module '*.mov' {
143
+ const src: string;
144
+ export default src;
145
+ }
146
+ declare module '*.m4a' {
147
+ const src: string;
148
+ export default src;
149
+ }
150
+ declare module '*.vtt' {
151
+ const src: string;
152
+ export default src;
153
+ }
154
+
155
+ // fonts
156
+ declare module '*.woff' {
157
+ const src: string;
158
+ export default src;
159
+ }
160
+ declare module '*.woff2' {
161
+ const src: string;
162
+ export default src;
163
+ }
164
+ declare module '*.eot' {
165
+ const src: string;
166
+ export default src;
167
+ }
168
+ declare module '*.ttf' {
169
+ const src: string;
170
+ export default src;
171
+ }
172
+ declare module '*.otf' {
173
+ const src: string;
174
+ export default src;
175
+ }
176
+
177
+ // other
178
+ declare module '*.webmanifest' {
179
+ const src: string;
180
+ export default src;
181
+ }
182
+ declare module '*.pdf' {
183
+ const src: string;
184
+ export default src;
185
+ }
186
+ declare module '*.txt' {
187
+ const src: string;
188
+ export default src;
189
+ }
190
+
191
+ // wasm?init
192
+ declare module '*.wasm?init' {
193
+ const initWasm: (options?: WebAssembly.Imports) => Promise<WebAssembly.Instance>;
194
+ export default initWasm;
195
+ }
196
+
197
+ // web worker
198
+ declare module '*?worker' {
199
+ const workerConstructor: {
200
+ new(options?: { name?: string }): Worker;
201
+ };
202
+ export default workerConstructor;
203
+ }
204
+
205
+ declare module '*?worker&inline' {
206
+ const workerConstructor: {
207
+ new(options?: { name?: string }): Worker;
208
+ };
209
+ export default workerConstructor;
210
+ }
211
+
212
+ declare module '*?worker&url' {
213
+ const src: string;
214
+ export default src;
215
+ }
216
+
217
+ declare module '*?sharedworker' {
218
+ const sharedWorkerConstructor: {
219
+ new(options?: { name?: string }): SharedWorker;
220
+ };
221
+ export default sharedWorkerConstructor;
222
+ }
223
+
224
+ declare module '*?sharedworker&inline' {
225
+ const sharedWorkerConstructor: {
226
+ new(options?: { name?: string }): SharedWorker;
227
+ };
228
+ export default sharedWorkerConstructor;
229
+ }
230
+
231
+ declare module '*?sharedworker&url' {
232
+ const src: string;
233
+ export default src;
234
+ }
235
+
236
+ declare module '*?raw' {
237
+ const src: string;
238
+ export default src;
239
+ }
240
+
241
+ declare module '*?url' {
242
+ const src: string;
243
+ export default src;
244
+ }
245
+
246
+ declare module '*?inline' {
247
+ const src: string;
248
+ export default src;
249
+ }
@@ -0,0 +1,19 @@
1
+ import { Plugin } from '@nocobase/client';
2
+ import PluginAIClient from '@nocobase/plugin-ai/client';
3
+ import { customLLMProviderOptions } from './llm-providers/custom-llm';
4
+
5
+ export class PluginCustomLLMClient extends Plugin {
6
+ async afterAdd() {}
7
+
8
+ async beforeLoad() {}
9
+
10
+ async load() {
11
+ this.aiPlugin.aiManager.registerLLMProvider('custom-llm', customLLMProviderOptions);
12
+ }
13
+
14
+ private get aiPlugin(): PluginAIClient {
15
+ return this.app.pm.get('ai');
16
+ }
17
+ }
18
+
19
+ export default PluginCustomLLMClient;