picturereader 2.0.1 → 3.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.
- package/README.md +160 -132
- package/client.js +536 -0
- package/package.json +15 -3
- package/scripts/doc-to-image.py +194 -0
- package/scripts/setup-doc-venv.mjs +66 -0
- package/scripts/setup-rapid.mjs +85 -0
- package/src/bridge.js +162 -0
- package/src/config.js +70 -0
- package/src/core.js +94 -2
- package/src/doc-tools.js +326 -0
- package/src/image-batch.js +504 -0
- package/src/index.js +258 -32
- package/src/more-tools.js +695 -0
- package/src/picturereader-vision.mjs +164 -0
- package/src/routing.js +130 -0
- package/src/runtime.js +127 -0
- package/src/settings-expose.js +75 -0
- package/src/tool.js +19 -14
- package/src/vision-analyze.js +35 -13
- package/src/vlm.js +198 -15
package/src/vlm.js
CHANGED
|
@@ -10,16 +10,153 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { spawn } from 'node:child_process';
|
|
13
|
-
import { stat } from 'node:fs/promises';
|
|
13
|
+
import { stat, readFile } from 'node:fs/promises';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { homedir } from 'node:os';
|
|
16
|
+
import { getRuntimeConfig } from './runtime.js';
|
|
14
17
|
|
|
15
18
|
// ---------------------------------------------------------------------------
|
|
16
|
-
// Configuration (
|
|
19
|
+
// Configuration (environment variables first, then DSH settings.yaml fallback)
|
|
17
20
|
// ---------------------------------------------------------------------------
|
|
18
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Try to read DSH settings.yaml and extract tool-vision config.
|
|
24
|
+
* @returns {Promise<{baseURL: string, model: string, apiKey: string}|null>}
|
|
25
|
+
*/
|
|
26
|
+
async function readDshToolVisionConfig() {
|
|
27
|
+
try {
|
|
28
|
+
const settingsPath = join(homedir(), '.dsh', 'settings.yaml');
|
|
29
|
+
const content = await readFile(settingsPath, 'utf-8');
|
|
30
|
+
// Simple YAML parsing for tool-vision section
|
|
31
|
+
const lines = content.split('\n');
|
|
32
|
+
let inToolVision = false;
|
|
33
|
+
let baseURL = '';
|
|
34
|
+
let model = '';
|
|
35
|
+
let apiKey = '';
|
|
36
|
+
let indent = -1;
|
|
37
|
+
|
|
38
|
+
for (const line of lines) {
|
|
39
|
+
// Detect start of tool-vision section
|
|
40
|
+
if (line.match(/^tool-vision:\s*$/)) {
|
|
41
|
+
inToolVision = true;
|
|
42
|
+
indent = -1;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (inToolVision) {
|
|
47
|
+
// Check if we've left the section (non-empty line with less or equal indent)
|
|
48
|
+
const match = line.match(/^(\s*)\S/);
|
|
49
|
+
if (match) {
|
|
50
|
+
const currentIndent = match[1].length;
|
|
51
|
+
if (indent === -1) {
|
|
52
|
+
indent = currentIndent;
|
|
53
|
+
} else if (currentIndent <= 0) {
|
|
54
|
+
// New top-level key, exit tool-vision section
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Parse key-value pairs
|
|
60
|
+
const kvMatch = line.match(/^\s+(\w+):\s*(.+)$/);
|
|
61
|
+
if (kvMatch) {
|
|
62
|
+
const [, key, value] = kvMatch;
|
|
63
|
+
const cleanValue = value.replace(/^["']|["']$/g, '').trim();
|
|
64
|
+
if (key === 'baseURL') baseURL = cleanValue;
|
|
65
|
+
else if (key === 'model') model = cleanValue;
|
|
66
|
+
else if (key === 'apiKey') apiKey = cleanValue;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (baseURL) {
|
|
72
|
+
return { baseURL, model, apiKey };
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
// Settings file not found or parse error, ignore
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Read DSH settings.yaml and extract the `picturereader` namespace's VLM
|
|
82
|
+
* fields (vlm_base / vlm_model / vlm_key). These come from the plugin's own
|
|
83
|
+
* settings card and take priority over the legacy tool-vision namespace,
|
|
84
|
+
* so the user's configured endpoint is honoured even when the plugin packs
|
|
85
|
+
* a newer schema.
|
|
86
|
+
* @returns {Promise<{baseURL: string, model: string, apiKey: string}|null>}
|
|
87
|
+
*/
|
|
88
|
+
async function readDshPicturereaderConfig() {
|
|
89
|
+
try {
|
|
90
|
+
const settingsPath = join(homedir(), '.dsh', 'settings.yaml');
|
|
91
|
+
const content = await readFile(settingsPath, 'utf-8');
|
|
92
|
+
const lines = content.split('\n');
|
|
93
|
+
let inNs = false;
|
|
94
|
+
let baseURL = '';
|
|
95
|
+
let model = '';
|
|
96
|
+
let apiKey = '';
|
|
97
|
+
let indent = -1;
|
|
98
|
+
const KEY_MAP = { vlm_base: 'baseURL', vlm_model: 'model', vlm_key: 'apiKey' };
|
|
99
|
+
|
|
100
|
+
for (const line of lines) {
|
|
101
|
+
if (line.match(/^picturereader:\s*$/)) {
|
|
102
|
+
inNs = true;
|
|
103
|
+
indent = -1;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (inNs) {
|
|
107
|
+
const match = line.match(/^(\s*)\S/);
|
|
108
|
+
if (match) {
|
|
109
|
+
const currentIndent = match[1].length;
|
|
110
|
+
if (indent === -1) {
|
|
111
|
+
indent = currentIndent;
|
|
112
|
+
} else if (currentIndent <= 0) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const kvMatch = line.match(/^\s+(\w+):\s*(.+)$/);
|
|
117
|
+
if (kvMatch) {
|
|
118
|
+
const [, key, value] = kvMatch;
|
|
119
|
+
const target = KEY_MAP[key];
|
|
120
|
+
const cleanValue = value.replace(/^["']|["']$/g, '').trim();
|
|
121
|
+
if (target === 'baseURL') baseURL = cleanValue;
|
|
122
|
+
else if (target === 'model') model = cleanValue;
|
|
123
|
+
else if (target === 'apiKey') apiKey = cleanValue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (baseURL) return { baseURL, model, apiKey };
|
|
128
|
+
} catch {
|
|
129
|
+
// ignore parse errors
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Read config: priority env → picturereader namespace → legacy tool-vision namespace
|
|
135
|
+
const dshConfig = await readDshToolVisionConfig();
|
|
136
|
+
const dshPictConfig = await readDshPicturereaderConfig();
|
|
137
|
+
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// GLM-4V-Flash: free built-in vision model from Zhipu AI
|
|
140
|
+
// https://open.bigmodel.cn — register to get your API key
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
/** GLM-4V-Flash default endpoint (OpenAI-compatible). */
|
|
144
|
+
const GLM4V_BASE = 'https://open.bigmodel.cn/api/paas/v4';
|
|
145
|
+
/** GLM-4V-Flash model name. */
|
|
146
|
+
const GLM4V_MODEL = 'glm-4v-flash';
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Resolve the GLM-4V-Flash API key.
|
|
150
|
+
* Priority: env SEE_API_KEY > env GLM_API_KEY > DSH settings.yaml apiKey > empty.
|
|
151
|
+
*/
|
|
152
|
+
function resolveGlm4vKey() {
|
|
153
|
+
return process.env.SEE_API_KEY ?? process.env.GLM_API_KEY ?? dshConfig?.apiKey ?? '';
|
|
154
|
+
}
|
|
155
|
+
|
|
19
156
|
/** OpenAI-compatible VLM endpoint (empty = VLM disabled). */
|
|
20
|
-
export const DEFAULT_BASE = process.env.SEE_BASE ??
|
|
157
|
+
export const DEFAULT_BASE = process.env.SEE_BASE ?? dshPictConfig?.baseURL ?? dshConfig?.baseURL ?? GLM4V_BASE;
|
|
21
158
|
/** VLM model name. */
|
|
22
|
-
export const DEFAULT_MODEL = process.env.SEE_MODEL ??
|
|
159
|
+
export const DEFAULT_MODEL = process.env.SEE_MODEL ?? dshPictConfig?.model ?? dshConfig?.model ?? GLM4V_MODEL;
|
|
23
160
|
/** Local llama-server executable path. */
|
|
24
161
|
export const DEFAULT_SERVER_EXE = process.env.SEE_SERVER_EXE ?? '';
|
|
25
162
|
/** Local model GGUF path. */
|
|
@@ -32,18 +169,53 @@ export const DEFAULT_PORT = Number(process.env.SEE_SERVER_PORT ?? 8080);
|
|
|
32
169
|
export const DEFAULT_NGL = process.env.SEE_SERVER_NGL ?? '20';
|
|
33
170
|
/** Context size for local server. */
|
|
34
171
|
export const DEFAULT_CTX = Number(process.env.SEE_SERVER_CTX ?? 16384);
|
|
35
|
-
/** API key for remote endpoints. */
|
|
36
|
-
export const DEFAULT_API_KEY =
|
|
172
|
+
/** API key for remote endpoints (env > picturereader ns > legacy tool-vision ns > GLM). */
|
|
173
|
+
export const DEFAULT_API_KEY =
|
|
174
|
+
process.env.SEE_API_KEY ?? dshPictConfig?.apiKey ?? dshConfig?.apiKey ?? process.env.GLM_API_KEY ?? '';
|
|
37
175
|
|
|
38
176
|
let serverStartPromise = null;
|
|
39
177
|
let serverChild = null;
|
|
40
178
|
|
|
41
179
|
/**
|
|
42
|
-
*
|
|
43
|
-
* @
|
|
180
|
+
* Effective API key: runtime explicit key → runtime env var → static default.
|
|
181
|
+
* @param {object} rt - runtime config (may be undefined).
|
|
182
|
+
* @returns {string}
|
|
183
|
+
*/
|
|
184
|
+
function effectiveApiKey(rt) {
|
|
185
|
+
if (rt?.vlm?.apiKey) return rt.vlm.apiKey;
|
|
186
|
+
const envName = rt?.vlm?.apiKeyEnv;
|
|
187
|
+
if (envName) {
|
|
188
|
+
const fromEnv = process.env[envName];
|
|
189
|
+
if (fromEnv) return fromEnv;
|
|
190
|
+
}
|
|
191
|
+
return DEFAULT_API_KEY;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Effective endpoint base URL: runtime explicit → static default.
|
|
196
|
+
* @param {object} rt
|
|
197
|
+
* @returns {string}
|
|
198
|
+
*/
|
|
199
|
+
function effectiveBase(rt) {
|
|
200
|
+
return (rt?.vlm?.baseUrl || '').trim() || DEFAULT_BASE;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Check if VLM is configured (has a base URL and API key for cloud endpoints).
|
|
205
|
+
* 隐私模式(privacy)为硬门禁:即使配置了外部 API 也返回 false,绝不外呼。
|
|
206
|
+
* @returns {boolean} true when VLM endpoint is configured and ready to use.
|
|
44
207
|
*/
|
|
45
208
|
export function isVlmConfigured() {
|
|
46
|
-
|
|
209
|
+
const rt = getRuntimeConfig();
|
|
210
|
+
if (rt?.mode === 'privacy') return false;
|
|
211
|
+
// 选配(vlm_enabled)未勾选:即使配置了端点/Key 也视为未启用外部 VLM。
|
|
212
|
+
if (rt?.vlm?.enabled === false) return false;
|
|
213
|
+
const base = effectiveBase(rt);
|
|
214
|
+
if (base.length === 0) return false;
|
|
215
|
+
// Cloud endpoints require an API key
|
|
216
|
+
const isLocal = isManagedEndpoint(base, DEFAULT_PORT);
|
|
217
|
+
if (!isLocal && effectiveApiKey(rt).length === 0) return false;
|
|
218
|
+
return true;
|
|
47
219
|
}
|
|
48
220
|
|
|
49
221
|
/**
|
|
@@ -224,7 +396,17 @@ export async function sendVisionRequest(config, images, prompt) {
|
|
|
224
396
|
headers.authorization = `Bearer ${config.apiKey}`;
|
|
225
397
|
}
|
|
226
398
|
|
|
227
|
-
const
|
|
399
|
+
const base = String(config.baseURL || '').trim().replace(/\/+$/, '');
|
|
400
|
+
let endpoint;
|
|
401
|
+
if (/\/v\d+\/chat\/completions$/.test(base) || /\/chat\/completions$/.test(base)) {
|
|
402
|
+
endpoint = base;
|
|
403
|
+
} else if (/\/v\d+$/.test(base)) {
|
|
404
|
+
endpoint = `${base}/chat/completions`;
|
|
405
|
+
} else {
|
|
406
|
+
// OpenAI 兼容端点统一用 /v1/chat/completions(LM Studio / llama-server / 云端网关等)
|
|
407
|
+
endpoint = `${base}/v1/chat/completions`;
|
|
408
|
+
}
|
|
409
|
+
const res = await fetch(endpoint, {
|
|
228
410
|
method: 'POST',
|
|
229
411
|
headers,
|
|
230
412
|
body: JSON.stringify(body),
|
|
@@ -250,10 +432,11 @@ export async function sendVisionRequest(config, images, prompt) {
|
|
|
250
432
|
* @returns {object} VLM configuration.
|
|
251
433
|
*/
|
|
252
434
|
export function defaultVlmConfig(overrides = {}) {
|
|
435
|
+
const rt = getRuntimeConfig();
|
|
253
436
|
return {
|
|
254
|
-
baseURL:
|
|
255
|
-
apiKey:
|
|
256
|
-
model: DEFAULT_MODEL,
|
|
437
|
+
baseURL: effectiveBase(rt),
|
|
438
|
+
apiKey: effectiveApiKey(rt),
|
|
439
|
+
model: (rt?.vlm?.model || '').trim() || DEFAULT_MODEL,
|
|
257
440
|
serverExe: DEFAULT_SERVER_EXE,
|
|
258
441
|
serverModel: DEFAULT_SERVER_MODEL,
|
|
259
442
|
serverMmproj: DEFAULT_SERVER_MMPROJ,
|
|
@@ -262,8 +445,8 @@ export function defaultVlmConfig(overrides = {}) {
|
|
|
262
445
|
ctxSize: DEFAULT_CTX,
|
|
263
446
|
autoStart: true,
|
|
264
447
|
healthTimeoutMs: 120_000,
|
|
265
|
-
requestTimeoutMs: 300_000,
|
|
266
|
-
maxTokens: 8192,
|
|
448
|
+
requestTimeoutMs: rt?.vlm?.requestTimeoutMs ?? 300_000,
|
|
449
|
+
maxTokens: rt?.vlm?.maxTokens ?? 8192,
|
|
267
450
|
...overrides,
|
|
268
451
|
};
|
|
269
452
|
}
|