dsh-knowledge-library 0.2.0 → 0.3.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/CHANGELOG.md +11 -0
- package/README.md +34 -10
- package/dist/client.js +605 -372
- package/dist/core/extract.d.ts +4 -0
- package/dist/core/extract.js +19 -6
- package/dist/core/fetch.d.ts +5 -0
- package/dist/core/fetch.js +25 -19
- package/dist/core/image-capture.d.ts +33 -0
- package/dist/core/image-capture.js +196 -0
- package/dist/core/jobs.d.ts +20 -1
- package/dist/core/jobs.js +97 -12
- package/dist/core/knowledge.js +10 -8
- package/dist/core/library.d.ts +17 -1
- package/dist/core/library.js +93 -6
- package/dist/core/page-images.d.ts +15 -0
- package/dist/core/page-images.js +97 -0
- package/dist/http.js +31 -0
- package/dist/index.js +6 -3
- package/dist/knowledge-model.d.ts +13 -1
- package/dist/knowledge-model.js +110 -32
- package/dist/public/app.css +19 -0
- package/dist/public/app.js +8 -8
- package/dist/standalone.js +6 -3
- package/dist/types.d.ts +56 -0
- package/dist/ui/App.js +9 -4
- package/dist/ui/ImageCapture.d.ts +18 -0
- package/dist/ui/ImageCapture.js +76 -0
- package/dist/ui/Knowledge.js +1 -1
- package/package.json +2 -1
package/dist/knowledge-model.js
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
const unavailable = '请先在 Harness 设置中配置默认模型;独立预览可通过环境变量配置模型。';
|
|
2
2
|
class ModelResponseError extends Error {
|
|
3
3
|
}
|
|
4
|
+
function outputLimitMessage() {
|
|
5
|
+
return '模型输出达到当前模型或服务的长度上限。插件沿用模型默认输出配置,未另设 token 上限;思考可能与正文共用额度,本次未自动重试。请检查模型配置,或分批整理后主动重试。';
|
|
6
|
+
}
|
|
7
|
+
async function boundedJson(response, limit = 512_000) {
|
|
8
|
+
const reader = response.body?.getReader();
|
|
9
|
+
if (!reader)
|
|
10
|
+
throw new Error('模型响应为空');
|
|
11
|
+
const chunks = [];
|
|
12
|
+
let length = 0;
|
|
13
|
+
try {
|
|
14
|
+
for (;;) {
|
|
15
|
+
const part = await reader.read();
|
|
16
|
+
if (part.done)
|
|
17
|
+
break;
|
|
18
|
+
length += part.value.length;
|
|
19
|
+
if (length > limit)
|
|
20
|
+
throw new Error('模型响应过大,请缩小整理范围');
|
|
21
|
+
chunks.push(part.value);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
await reader.cancel().catch(() => { });
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw new Error('模型响应不是有效 JSON');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function checkImages(images) {
|
|
35
|
+
if (!images?.length)
|
|
36
|
+
return;
|
|
37
|
+
if (images.length > 10 || images.some(image => !image.data.byteLength || image.data.byteLength > 6 * 1024 * 1024 || !['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(image.mediaType)) || images.reduce((sum, image) => sum + image.data.byteLength, 0) > 20 * 1024 * 1024)
|
|
38
|
+
throw new Error('图片请求超过数量、格式或字节限制');
|
|
39
|
+
}
|
|
4
40
|
export function harnessKnowledgeModel(ctx) {
|
|
5
41
|
function services() {
|
|
6
42
|
const llm = ctx.get('llm');
|
|
@@ -10,7 +46,21 @@ export function harnessKnowledgeModel(ctx) {
|
|
|
10
46
|
throw new Error(unavailable);
|
|
11
47
|
return { llm, selection };
|
|
12
48
|
}
|
|
49
|
+
async function visionStatus() {
|
|
50
|
+
try {
|
|
51
|
+
const { llm, selection } = services();
|
|
52
|
+
const model = `${selection.provider} / ${selection.model}`;
|
|
53
|
+
if (!ctx.get('attachments'))
|
|
54
|
+
return { available: false, model, reason: '当前宿主未提供图片附件服务,请升级或启用附件服务' };
|
|
55
|
+
const info = await llm.resolveModelInfo(selection.provider, selection.model, AbortSignal.timeout(5000));
|
|
56
|
+
return info.inputModalities?.includes('image') ? { available: true, model } : { available: false, model, reason: '当前默认模型未声明图片输入能力,请在 Harness 中选择支持视觉的模型(如 deepseek-flash)' };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { available: false, reason: '无法确认默认模型的图片能力,请检查 Harness 模型设置' };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
13
62
|
return {
|
|
63
|
+
visionStatus,
|
|
14
64
|
status() {
|
|
15
65
|
try {
|
|
16
66
|
const { selection } = services();
|
|
@@ -20,13 +70,31 @@ export function harnessKnowledgeModel(ctx) {
|
|
|
20
70
|
return { available: false, reason: unavailable };
|
|
21
71
|
}
|
|
22
72
|
},
|
|
23
|
-
async generate({ system, prompt,
|
|
73
|
+
async generate({ system, prompt, signal, images, expectedModel }) {
|
|
74
|
+
checkImages(images);
|
|
24
75
|
const { llm, selection } = services();
|
|
76
|
+
if (expectedModel !== undefined && expectedModel !== `${selection.provider} / ${selection.model}`)
|
|
77
|
+
throw new Error('模型已改变,请重新核对');
|
|
25
78
|
signal.throwIfAborted();
|
|
79
|
+
const content = [{ type: 'text', text: prompt }];
|
|
80
|
+
if (images?.length) {
|
|
81
|
+
const status = await visionStatus();
|
|
82
|
+
signal.throwIfAborted();
|
|
83
|
+
if (!status.available || status.model !== `${selection.provider} / ${selection.model}`)
|
|
84
|
+
throw new Error(status.reason || '图片模型配置已改变');
|
|
85
|
+
const attachments = ctx.get('attachments');
|
|
86
|
+
const refs = await attachments.saveImages(images);
|
|
87
|
+
signal.throwIfAborted();
|
|
88
|
+
content.push(...refs.map(attachment => ({ type: 'image', attachment })));
|
|
89
|
+
}
|
|
90
|
+
const current = services().selection;
|
|
91
|
+
if (current.provider !== selection.provider || current.model !== selection.model || current.reasoningEffort !== selection.reasoningEffort)
|
|
92
|
+
throw new Error('模型配置已改变,请重新核对');
|
|
26
93
|
let text = '';
|
|
27
94
|
let finished = false;
|
|
95
|
+
// Omit maxTokens so the Harness runtime materializes the model's own defaults.
|
|
28
96
|
try {
|
|
29
|
-
for await (const chunk of llm.stream({ ...selection, system, messages: [{ role: 'user', content
|
|
97
|
+
for await (const chunk of llm.stream({ ...selection, system, messages: [{ role: 'user', content }], signal })) {
|
|
30
98
|
signal.throwIfAborted();
|
|
31
99
|
// Complete blocks are authoritative; deltas may include reasoning or retries.
|
|
32
100
|
if (chunk.type === 'block-end' && chunk.block.type === 'text')
|
|
@@ -35,7 +103,7 @@ export function harnessKnowledgeModel(ctx) {
|
|
|
35
103
|
throw new ModelResponseError('模型输出过长,请缩小整理范围');
|
|
36
104
|
if (chunk.type === 'finish') {
|
|
37
105
|
if (chunk.reason.kind !== 'stop')
|
|
38
|
-
throw new ModelResponseError(chunk.reason.kind === 'max-tokens' ?
|
|
106
|
+
throw new ModelResponseError(chunk.reason.kind === 'max-tokens' ? outputLimitMessage() : '模型调用未完成,请检查 Harness 模型设置后重试');
|
|
39
107
|
finished = true;
|
|
40
108
|
}
|
|
41
109
|
}
|
|
@@ -58,11 +126,45 @@ export function previewKnowledgeModel(env = process.env) {
|
|
|
58
126
|
const key = env.KNOWLEDGE_LIBRARY_MODEL_API_KEY || env.DEEPSEEK_API_KEY;
|
|
59
127
|
const model = env.KNOWLEDGE_LIBRARY_MODEL || 'deepseek-chat';
|
|
60
128
|
const base = env.KNOWLEDGE_LIBRARY_MODEL_BASE_URL || 'https://api.deepseek.com';
|
|
129
|
+
let cached;
|
|
130
|
+
async function visionStatus() {
|
|
131
|
+
if (!key)
|
|
132
|
+
return { available: false, model, reason: unavailable };
|
|
133
|
+
if (cached && Date.now() - cached.time < 15_000)
|
|
134
|
+
return cached.value;
|
|
135
|
+
let value;
|
|
136
|
+
try {
|
|
137
|
+
const response = await fetch(`${base.replace(/\/$/, '')}/models`, { headers: { Authorization: `Bearer ${key}` }, redirect: 'error', signal: AbortSignal.timeout(5000) });
|
|
138
|
+
if (!response.ok) {
|
|
139
|
+
await response.body?.cancel();
|
|
140
|
+
throw new Error();
|
|
141
|
+
}
|
|
142
|
+
const data = await boundedJson(response);
|
|
143
|
+
const entry = Array.isArray(data.data) && data.data.find((item) => item.id === model);
|
|
144
|
+
value = entry?.input_modalities?.includes('image') ? { available: true, model } : { available: false, model, reason: '当前模型未声明图片输入能力,请设置支持视觉的 KNOWLEDGE_LIBRARY_MODEL(如 deepseek-flash)' };
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
value = { available: false, model, reason: '无法读取模型图片能力,请检查服务地址的 /models 接口和网络' };
|
|
148
|
+
}
|
|
149
|
+
cached = { time: Date.now(), value };
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
61
152
|
return {
|
|
153
|
+
visionStatus,
|
|
62
154
|
status: () => key ? { available: true, model } : { available: false, reason: unavailable },
|
|
63
|
-
async generate({ system, prompt,
|
|
155
|
+
async generate({ system, prompt, signal, images, expectedModel }) {
|
|
156
|
+
checkImages(images);
|
|
64
157
|
if (!key)
|
|
65
158
|
throw new Error(unavailable);
|
|
159
|
+
if (expectedModel !== undefined && expectedModel !== model)
|
|
160
|
+
throw new Error('模型已改变,请重新核对');
|
|
161
|
+
signal.throwIfAborted();
|
|
162
|
+
if (images?.length) {
|
|
163
|
+
const info = await visionStatus();
|
|
164
|
+
signal.throwIfAborted();
|
|
165
|
+
if (!info.available)
|
|
166
|
+
throw new Error(info.reason);
|
|
167
|
+
}
|
|
66
168
|
let response;
|
|
67
169
|
try {
|
|
68
170
|
const url = new URL(`${base.replace(/\/$/, '')}/chat/completions`);
|
|
@@ -70,7 +172,7 @@ export function previewKnowledgeModel(env = process.env) {
|
|
|
70
172
|
throw new Error();
|
|
71
173
|
response = await fetch(url, { method: 'POST', redirect: 'error', signal,
|
|
72
174
|
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${key}` },
|
|
73
|
-
body: JSON.stringify({ model, stream: false,
|
|
175
|
+
body: JSON.stringify({ model, stream: false, messages: [{ role: 'system', content: system }, { role: 'user', content: images?.length ? [{ type: 'text', text: prompt }, ...images.map(image => ({ type: 'image_url', image_url: { url: `data:${image.mediaType};base64,${Buffer.from(image.data).toString('base64')}`, detail: 'original' } }))] : prompt }] }) });
|
|
74
176
|
}
|
|
75
177
|
catch {
|
|
76
178
|
signal.throwIfAborted();
|
|
@@ -80,34 +182,10 @@ export function previewKnowledgeModel(env = process.env) {
|
|
|
80
182
|
await response.body?.cancel();
|
|
81
183
|
throw new Error(`模型调用失败(HTTP ${response.status}),请检查模型配置或额度`);
|
|
82
184
|
}
|
|
83
|
-
|
|
84
|
-
const reader = response.body?.getReader();
|
|
85
|
-
if (!reader)
|
|
86
|
-
throw new Error('模型响应为空');
|
|
87
|
-
const chunks = [];
|
|
88
|
-
let length = 0;
|
|
89
|
-
try {
|
|
90
|
-
while (true) {
|
|
91
|
-
const part = await reader.read();
|
|
92
|
-
if (part.done)
|
|
93
|
-
break;
|
|
94
|
-
length += part.value.length;
|
|
95
|
-
if (length > 512_000)
|
|
96
|
-
throw new Error('模型响应过大,请缩小整理范围');
|
|
97
|
-
chunks.push(part.value);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
finally {
|
|
101
|
-
await reader.cancel().catch(() => { });
|
|
102
|
-
}
|
|
103
|
-
let data;
|
|
104
|
-
try {
|
|
105
|
-
data = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
throw new Error('模型响应不是有效 JSON');
|
|
109
|
-
}
|
|
185
|
+
const data = await boundedJson(response);
|
|
110
186
|
const choice = data.choices?.[0];
|
|
187
|
+
if (choice?.finish_reason === 'length' || choice?.finish_reason === 'max_tokens')
|
|
188
|
+
throw new Error(outputLimitMessage());
|
|
111
189
|
if (choice?.finish_reason !== 'stop' || typeof choice.message?.content !== 'string' || !choice.message.content.trim())
|
|
112
190
|
throw new Error('模型未返回完整正文,可能达到输出上限,请缩小范围后重试');
|
|
113
191
|
return choice.message.content;
|
package/dist/public/app.css
CHANGED
|
@@ -327,6 +327,25 @@
|
|
|
327
327
|
.kl-info .kl-proxy-option { display: flex; align-items: flex-start; gap: 8px; color: var(--kl-ink); font-size: 13px; line-height: 20px; cursor: pointer; }
|
|
328
328
|
.kl-proxy-option input { flex: none; width: 16px; height: 16px; margin: 2px 0; accent-color: var(--kl-ink); }
|
|
329
329
|
.kl-network-settings p { font-size: 12px; margin: 8px 0 12px; }
|
|
330
|
+
.kl-image-settings { margin-top: 20px; padding-top: 16px; border-top: .5px solid var(--kl-line); }
|
|
331
|
+
.kl-image-settings h3, .kl-page-images h3 { font-size: 13px; font-weight: 500; margin: 0 0 12px; }
|
|
332
|
+
.kl-image-settings p, .kl-image-record p { font-size: 12px; overflow-wrap: anywhere; }
|
|
333
|
+
.kl-info .kl-image-limit { display: flex; align-items: center; gap: 12px; margin: 14px 0; }
|
|
334
|
+
.kl-image-limit input { width: 68px; padding: 6px 8px; border: .5px solid var(--kl-line); border-radius: 8px; background: var(--kl-bg); }
|
|
335
|
+
.kl-image-policy { padding: 10px 12px; border-left: 2px solid var(--kl-link); background: var(--kl-sidebar); color: var(--kl-secondary); font-size: 12px; line-height: 1.8; margin: 0 0 16px; overflow-wrap: anywhere; }
|
|
336
|
+
.kl-page-images { margin: 24px 0; padding-block: 16px; border-block: .5px solid var(--kl-line); }
|
|
337
|
+
.kl-image-heading { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; }
|
|
338
|
+
.kl-image-heading h3 { margin: 0; }
|
|
339
|
+
.kl-image-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr)); gap: 16px; }
|
|
340
|
+
.kl-image-record { min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 6px; font-size: 12px; overflow-wrap: anywhere; }
|
|
341
|
+
.kl-image-record > a:has(img) { width: 100%; }
|
|
342
|
+
.kl-image-record img { display: block; width: 100%; height: 140px; object-fit: contain; border: .5px solid var(--kl-line); border-radius: 8px; background: var(--kl-sidebar); }
|
|
343
|
+
.kl-image-record small { color: var(--kl-muted); }
|
|
344
|
+
.kl-image-record a { color: var(--kl-link); }
|
|
345
|
+
.kl-image-record p { margin: 0; color: var(--kl-secondary); }
|
|
346
|
+
.kl-delete-dialog li { overflow-wrap: anywhere; }
|
|
347
|
+
.kl-job > p { color: var(--kl-secondary); }
|
|
348
|
+
.kl-job > p[role="alert"] { color: var(--kl-error); }
|
|
330
349
|
.kl-error, .kl-form-error { background: var(--kl-sidebar); color: var(--kl-error); font-size: 13px; line-height: 1.7; padding: 10px 12px; border-radius: 8px; overflow-wrap: anywhere; }
|
|
331
350
|
.kl-error { display: flex; align-items: center; gap: 12px; margin: 0 var(--kl-gutter) 14px; }
|
|
332
351
|
.kl-error button { margin-left: auto; border: 0; background: transparent; color: inherit; display: flex; padding: 4px; }
|