plugin-docpixie 1.0.0

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.
@@ -0,0 +1,253 @@
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 __create = Object.create;
11
+ var __defProp = Object.defineProperty;
12
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
13
+ var __getOwnPropNames = Object.getOwnPropertyNames;
14
+ var __getProtoOf = Object.getPrototypeOf;
15
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
16
+ var __export = (target, all) => {
17
+ for (var name in all)
18
+ __defProp(target, name, { get: all[name], enumerable: true });
19
+ };
20
+ var __copyProps = (to, from, except, desc) => {
21
+ if (from && typeof from === "object" || typeof from === "function") {
22
+ for (let key of __getOwnPropNames(from))
23
+ if (!__hasOwnProp.call(to, key) && key !== except)
24
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
25
+ }
26
+ return to;
27
+ };
28
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
29
+ // If the importer is in node compatibility mode or this is not an ESM
30
+ // file that has been converted to a CommonJS file using a Babel-
31
+ // compatible transform (i.e. "__esModule" has not been set), then set
32
+ // "default" to the CommonJS "module.exports" for node compatibility.
33
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
34
+ mod
35
+ ));
36
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
37
+ var llm_adapter_exports = {};
38
+ __export(llm_adapter_exports, {
39
+ NocoBaseLLMAdapter: () => NocoBaseLLMAdapter
40
+ });
41
+ module.exports = __toCommonJS(llm_adapter_exports);
42
+ var fs = __toESM(require("fs"));
43
+ var path = __toESM(require("path"));
44
+ var import_messages = require("@langchain/core/messages");
45
+ var import_exceptions = require("../exceptions");
46
+ const MIME_TYPES = {
47
+ ".jpg": "image/jpeg",
48
+ ".jpeg": "image/jpeg",
49
+ ".png": "image/png",
50
+ ".gif": "image/gif",
51
+ ".webp": "image/webp",
52
+ ".bmp": "image/bmp",
53
+ ".tiff": "image/tiff",
54
+ ".tif": "image/tiff",
55
+ ".pdf": "application/pdf"
56
+ };
57
+ class NocoBaseLLMAdapter {
58
+ textModel;
59
+ visionModel;
60
+ totalCost = 0;
61
+ /**
62
+ * @param textModel - LangChain BaseChatModel for text-only calls
63
+ * @param visionModel - LangChain BaseChatModel for vision/multimodal calls (can be same as textModel)
64
+ */
65
+ constructor(textModel, visionModel) {
66
+ this.textModel = textModel;
67
+ this.visionModel = visionModel || textModel;
68
+ }
69
+ // ═══════════════════════════════════════════
70
+ // ILLMProvider Implementation
71
+ // ═══════════════════════════════════════════
72
+ /**
73
+ * Process text-only messages using the text model.
74
+ */
75
+ async processTextMessages(messages, maxTokens = 500, temperature = 0.3) {
76
+ try {
77
+ const langchainMessages = this.convertToLangChainMessages(messages);
78
+ const model = this.textModel.bind({
79
+ max_tokens: maxTokens,
80
+ temperature
81
+ });
82
+ const result = await model.invoke(langchainMessages);
83
+ this.trackCost(result);
84
+ return this.extractContent(result);
85
+ } catch (err) {
86
+ throw new import_exceptions.ProviderError(
87
+ `Text LLM call failed: ${err.message}`,
88
+ "nocobase-llm"
89
+ );
90
+ }
91
+ }
92
+ /**
93
+ * Process multimodal messages (text + images) using the vision model.
94
+ * Automatically converts `image_path` content items to base64 `image_url`.
95
+ */
96
+ async processMultimodalMessages(messages, maxTokens = 500, temperature = 0.3) {
97
+ try {
98
+ const prepared = this.prepareMessages(messages);
99
+ const langchainMessages = this.convertToLangChainMessages(prepared);
100
+ const model = this.visionModel.bind({
101
+ max_tokens: maxTokens,
102
+ temperature
103
+ });
104
+ const result = await model.invoke(langchainMessages);
105
+ this.trackCost(result);
106
+ return this.extractContent(result);
107
+ } catch (err) {
108
+ throw new import_exceptions.ProviderError(
109
+ `Vision LLM call failed: ${err.message}`,
110
+ "nocobase-llm"
111
+ );
112
+ }
113
+ }
114
+ /** Accumulated cost of all API calls (USD) */
115
+ getTotalCost() {
116
+ return this.totalCost;
117
+ }
118
+ /** Reset cost counter */
119
+ resetCost() {
120
+ this.totalCost = 0;
121
+ }
122
+ // ═══════════════════════════════════════════
123
+ // Message Conversion
124
+ // ═══════════════════════════════════════════
125
+ /**
126
+ * Convert DocPixie's {role, content} messages to LangChain message objects.
127
+ */
128
+ convertToLangChainMessages(messages) {
129
+ return messages.map((msg) => {
130
+ switch (msg.role) {
131
+ case "system":
132
+ return new import_messages.SystemMessage(msg.content);
133
+ case "assistant":
134
+ return new import_messages.AIMessage(typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content));
135
+ case "user":
136
+ default:
137
+ return new import_messages.HumanMessage({ content: msg.content });
138
+ }
139
+ });
140
+ }
141
+ // ═══════════════════════════════════════════
142
+ // Image Processing (ported from OpenAICompatibleProvider)
143
+ // ═══════════════════════════════════════════
144
+ /**
145
+ * Walk through messages and convert `image_path` content items
146
+ * to OpenAI-compatible `image_url` with base64 data URLs.
147
+ *
148
+ * Input format (DocPixie internal):
149
+ * ```json
150
+ * { "type": "image_path", "image_path": "/path/to/img.jpg", "detail": "high" }
151
+ * ```
152
+ *
153
+ * Output format (OpenAI API):
154
+ * ```json
155
+ * { "type": "image_url", "image_url": { "url": "data:image/jpeg;base64,...", "detail": "high" } }
156
+ * ```
157
+ */
158
+ prepareMessages(messages) {
159
+ const prepared = [];
160
+ for (const message of messages) {
161
+ if (message.role === "system") {
162
+ prepared.push(message);
163
+ } else if (message.role === "user" && Array.isArray(message.content)) {
164
+ const processedContent = [];
165
+ for (const item of message.content) {
166
+ if (item.type === "text") {
167
+ processedContent.push(item);
168
+ } else if (item.type === "image_path") {
169
+ const imagePath = item.image_path;
170
+ if (this.validateImagePath(imagePath)) {
171
+ const dataUrl = this.createImageDataUrl(imagePath);
172
+ processedContent.push({
173
+ type: "image_url",
174
+ image_url: {
175
+ url: dataUrl,
176
+ detail: item.detail || "high"
177
+ }
178
+ });
179
+ }
180
+ } else {
181
+ processedContent.push(item);
182
+ }
183
+ }
184
+ prepared.push({ role: message.role, content: processedContent });
185
+ } else {
186
+ prepared.push(message);
187
+ }
188
+ }
189
+ return prepared;
190
+ }
191
+ // ═══════════════════════════════════════════
192
+ // Image Helpers
193
+ // ═══════════════════════════════════════════
194
+ /** Encode an image file to base64 string */
195
+ encodeImage(imagePath) {
196
+ try {
197
+ const buffer = fs.readFileSync(imagePath);
198
+ return buffer.toString("base64");
199
+ } catch (err) {
200
+ throw new import_exceptions.ProviderError(
201
+ `Failed to encode image ${imagePath}: ${err.message}`,
202
+ "nocobase-llm"
203
+ );
204
+ }
205
+ }
206
+ /** Create a data URL for an image (data:image/jpeg;base64,...) */
207
+ createImageDataUrl(imagePath) {
208
+ const ext = path.extname(imagePath).toLowerCase();
209
+ const mime = MIME_TYPES[ext] || "image/jpeg";
210
+ const encoded = this.encodeImage(imagePath);
211
+ return `data:${mime};base64,${encoded}`;
212
+ }
213
+ /** Validate that an image path exists and is readable */
214
+ validateImagePath(imagePath) {
215
+ try {
216
+ fs.accessSync(imagePath, fs.constants.R_OK);
217
+ return true;
218
+ } catch {
219
+ return false;
220
+ }
221
+ }
222
+ // ═══════════════════════════════════════════
223
+ // Response Helpers
224
+ // ═══════════════════════════════════════════
225
+ /** Extract text content from a LangChain response */
226
+ extractContent(result) {
227
+ if (typeof result === "string") return result.trim();
228
+ if (result == null ? void 0 : result.content) {
229
+ if (typeof result.content === "string") return result.content.trim();
230
+ if (Array.isArray(result.content)) {
231
+ return result.content.filter((block) => block.type === "text").map((block) => block.text || "").join("").trim();
232
+ }
233
+ }
234
+ if (result == null ? void 0 : result.text) return result.text.trim();
235
+ throw new import_exceptions.ProviderError(
236
+ `Unexpected LLM response format: ${JSON.stringify(result).substring(0, 200)}`,
237
+ "nocobase-llm"
238
+ );
239
+ }
240
+ /** Track cost from LangChain response metadata */
241
+ trackCost(result) {
242
+ const usage = result == null ? void 0 : result.usage_metadata;
243
+ if (usage) {
244
+ if (typeof usage.cost === "number") {
245
+ this.totalCost += usage.cost;
246
+ }
247
+ }
248
+ }
249
+ }
250
+ // Annotate the CommonJS export names for ESM import in node:
251
+ 0 && (module.exports = {
252
+ NocoBaseLLMAdapter
253
+ });