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.
- package/dist/externalVersion.js +9 -7
- package/dist/server/llm-providers/custom-llm.js +264 -7
- package/dist/swagger.js +39 -0
- package/package.json +9 -1
- package/src/client/client.d.ts +249 -0
- package/src/client/index.tsx +19 -0
- package/src/client/llm-providers/custom-llm/ModelSettings.tsx +139 -0
- package/src/client/llm-providers/custom-llm/ProviderSettings.tsx +115 -0
- package/src/client/llm-providers/custom-llm/index.ts +10 -0
- package/src/client/locale.ts +8 -0
- package/src/client/models/index.ts +12 -0
- package/src/client/plugin.tsx +10 -0
- package/src/index.ts +2 -0
- package/src/locale/en-US.json +29 -0
- package/src/locale/vi-VN.json +29 -0
- package/src/locale/zh-CN.json +16 -0
- package/src/server/collections/.gitkeep +0 -0
- package/src/server/index.ts +1 -0
- package/src/server/llm-providers/custom-llm.ts +992 -0
- package/src/server/plugin.ts +27 -0
- package/src/swagger.ts +9 -0
package/dist/externalVersion.js
CHANGED
|
@@ -8,13 +8,15 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
module.exports = {
|
|
11
|
-
"@nocobase/client": "2.0.
|
|
12
|
-
"@nocobase/plugin-ai": "2.0.
|
|
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.
|
|
15
|
-
"@nocobase/flow-engine": "2.0.
|
|
16
|
-
"@nocobase/database": "2.0.
|
|
17
|
-
"
|
|
18
|
-
"@nocobase/
|
|
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
|
|
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)",
|
package/dist/swagger.js
ADDED
|
@@ -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.
|
|
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;
|