plugin-custom-llm 1.2.0 → 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.
@@ -8,13 +8,15 @@
8
8
  */
9
9
 
10
10
  module.exports = {
11
- "@nocobase/client": "2.0.20",
12
- "@nocobase/plugin-ai": "2.0.20",
11
+ "@nocobase/client": "2.0.32",
12
+ "@nocobase/plugin-ai": "2.0.32",
13
13
  "react-i18next": "11.18.6",
14
- "@nocobase/server": "2.0.20",
15
- "@nocobase/flow-engine": "2.0.20",
16
- "@nocobase/database": "2.0.20",
17
- "react": "18.2.0",
18
- "@nocobase/utils": "2.0.20",
14
+ "@nocobase/server": "2.0.32",
15
+ "@nocobase/flow-engine": "2.0.32",
16
+ "@nocobase/database": "2.0.32",
17
+ "axios": "1.14.0",
18
+ "@nocobase/actions": "2.0.32",
19
+ "react": "18.3.1",
20
+ "@nocobase/utils": "2.0.32",
19
21
  "antd": "5.24.2"
20
22
  };
@@ -42,6 +42,8 @@ __export(custom_llm_exports, {
42
42
  module.exports = __toCommonJS(custom_llm_exports);
43
43
  var import_plugin_ai = require("@nocobase/plugin-ai");
44
44
  var import_node_path = __toESM(require("node:path"));
45
+ var import_promises = __toESM(require("node:fs/promises"));
46
+ var import_axios = __toESM(require("axios"));
45
47
  const KEEPALIVE_PREFIX = "\u200B\u200B\u200B";
46
48
  function requireFromApp(moduleName) {
47
49
  const appNodeModules = process.env.NODE_MODULES_PATH || import_node_path.default.join(process.cwd(), "node_modules");
@@ -100,11 +102,41 @@ function extractTextContent(content, contentPath) {
100
102
  }
101
103
  return "";
102
104
  }
103
- function safeParseJSON(str) {
105
+ function isTextMimetype(mimetype) {
106
+ if (!mimetype) return false;
107
+ if (mimetype.startsWith("text/")) return true;
108
+ const TEXT_APPLICATION_TYPES = /* @__PURE__ */ new Set([
109
+ "application/json",
110
+ "application/xml",
111
+ "application/xhtml+xml",
112
+ "application/atom+xml",
113
+ "application/rss+xml",
114
+ "application/csv",
115
+ "application/javascript",
116
+ "application/typescript",
117
+ "application/x-javascript",
118
+ "application/x-typescript",
119
+ "application/x-yaml",
120
+ "application/yaml",
121
+ "application/x-json",
122
+ "application/geo+json",
123
+ "application/ld+json",
124
+ "application/manifest+json",
125
+ "application/graphql",
126
+ "application/x-www-form-urlencoded",
127
+ "application/toml",
128
+ "application/x-sh",
129
+ "application/x-shellscript",
130
+ "application/sql"
131
+ ]);
132
+ return TEXT_APPLICATION_TYPES.has(mimetype);
133
+ }
134
+ function safeParseJSON(str, fieldName) {
104
135
  if (!str || typeof str !== "string") return {};
105
136
  try {
106
137
  return JSON.parse(str);
107
- } catch {
138
+ } catch (e) {
139
+ console.warn(`[CustomLLM] Failed to parse ${fieldName || "JSON config"}: ${e.message}`);
108
140
  return {};
109
141
  }
110
142
  }
@@ -170,13 +202,13 @@ function createMappingFetch(responseMapping) {
170
202
 
171
203
  `));
172
204
  } else {
173
- controller.enqueue(encoder.encode(line + "\n"));
205
+ controller.enqueue(encoder.encode(line + "\n\n"));
174
206
  }
175
207
  } catch {
176
- controller.enqueue(encoder.encode(line + "\n"));
208
+ controller.enqueue(encoder.encode(line + "\n\n"));
177
209
  }
178
210
  } else {
179
- controller.enqueue(encoder.encode(line + "\n"));
211
+ controller.enqueue(encoder.encode(line + "\n\n"));
180
212
  }
181
213
  }
182
214
  }
@@ -391,11 +423,11 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
391
423
  }
392
424
  get requestConfig() {
393
425
  var _a;
394
- return safeParseJSON((_a = this.serviceOptions) == null ? void 0 : _a.requestConfig);
426
+ return safeParseJSON((_a = this.serviceOptions) == null ? void 0 : _a.requestConfig, "requestConfig");
395
427
  }
396
428
  get responseConfig() {
397
429
  var _a;
398
- return safeParseJSON((_a = this.serviceOptions) == null ? void 0 : _a.responseConfig);
430
+ return safeParseJSON((_a = this.serviceOptions) == null ? void 0 : _a.responseConfig, "responseConfig");
399
431
  }
400
432
  createModel() {
401
433
  var _a;
@@ -528,6 +560,231 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
528
560
  parseResponseError(err) {
529
561
  return (err == null ? void 0 : err.message) ?? "Unexpected LLM service error";
530
562
  }
563
+ /**
564
+ * Self-contained file reading that correctly handles the APP_PUBLIC_PATH prefix.
565
+ *
566
+ * plugin-ai's encodeLocalFile does path.join(cwd, url) without stripping
567
+ * APP_PUBLIC_PATH, so when the app is deployed under a sub-path (e.g. /my-app)
568
+ * the resolved path becomes '{cwd}/my-app/storage/uploads/…' which does not exist.
569
+ * We cannot fix that in plugin-ai (core), so we re-implement file reading here
570
+ * with the prefix stripped before the cwd join.
571
+ */
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) {
579
+ const fileManager = this.app.pm.get("file-manager");
580
+ const rawUrl = await fileManager.getFileURL(attachment);
581
+ const url = decodeURIComponent(rawUrl);
582
+ if (url.startsWith("http://") || url.startsWith("https://")) {
583
+ const referer = ctx.get("referer") || "";
584
+ const ua = ctx.get("user-agent") || "";
585
+ const response = await import_axios.default.get(url, {
586
+ responseType: "arraybuffer",
587
+ timeout: 3e4,
588
+ headers: { referer, "User-Agent": ua }
589
+ });
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") };
599
+ }
600
+ let localPath = url;
601
+ const appPublicPath = (process.env.APP_PUBLIC_PATH || "/").replace(/\/+$/, "");
602
+ if (appPublicPath && localPath.startsWith(appPublicPath + "/")) {
603
+ localPath = localPath.slice(appPublicPath.length);
604
+ }
605
+ const storageRoot = import_node_path.default.resolve(process.cwd());
606
+ const absPath = import_node_path.default.resolve(storageRoot, localPath.replace(/^\//, ""));
607
+ if (!absPath.startsWith(storageRoot + import_node_path.default.sep) && absPath !== storageRoot) {
608
+ throw new Error(`Attachment path escapes storage root: ${localPath}`);
609
+ }
610
+ const data = await import_promises.default.readFile(absPath);
611
+ return { base64: Buffer.from(data).toString("base64"), absPath };
612
+ }
613
+ /**
614
+ * Override parseAttachment to convert all attachments into formats that
615
+ * generic OpenAI-compatible endpoints actually support:
616
+ *
617
+ * - Images → image_url block with base64 data URI (vision models)
618
+ * - Text files → text block with decoded UTF-8 content
619
+ * - Binary → text block with base64 data URI (multi-modal or fallback)
620
+ *
621
+ * The base-class implementation returns a LangChain ContentBlock.Multimodal.File
622
+ * (`type: 'file'`) for non-image attachments. LangChain serialises this as the
623
+ * newer OpenAI Files API format which most custom/local endpoints do NOT understand,
624
+ * causing file content to be silently dropped.
625
+ *
626
+ * This method is entirely self-contained — it does not call super — so it is
627
+ * safe to use without modifying plugin-ai core.
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
+ }
722
+ async parseAttachment(ctx, attachment) {
723
+ const mimetype = attachment.mimetype || "application/octet-stream";
724
+ const filename = attachment.filename || attachment.name || "file";
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
+ }
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
+ }
764
+ return {
765
+ placement: "contentBlocks",
766
+ content: {
767
+ type: "image_url",
768
+ image_url: { url: `data:${mimetype};base64,${data}` }
769
+ }
770
+ };
771
+ }
772
+ let textContent;
773
+ if (isTextMimetype(mimetype)) {
774
+ const decoded = Buffer.from(data, "base64").toString("utf-8");
775
+ textContent = `<attachment filename="${filename}" type="${mimetype}">
776
+ ${decoded}
777
+ </attachment>`;
778
+ } else {
779
+ textContent = `<attachment filename="${filename}" type="${mimetype}">
780
+ data:${mimetype};base64,${data}
781
+ </attachment>`;
782
+ }
783
+ return {
784
+ placement: "contentBlocks",
785
+ content: { type: "text", text: textContent }
786
+ };
787
+ }
531
788
  }
532
789
  const customLLMProviderOptions = {
533
790
  title: "Custom LLM (OpenAI Compatible)",
@@ -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.0",
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;