@goodandready/dsh-image-gen 0.11.3 → 0.11.4
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/lib/attachment-helper.js +6 -1
- package/lib/index.js +12 -46
- package/lib/prompt-enhancer.js +51 -0
- package/lib/providers.js +9 -2
- package/lib/tools/processing-basic.js +6 -10
- package/package.json +1 -1
package/lib/attachment-helper.js
CHANGED
|
@@ -81,10 +81,15 @@ export async function saveAndAttachResult(ctx, exec, cfg, {
|
|
|
81
81
|
})
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/** Raster media types safe for multimodal LLM context (DeepSeek, OpenAI, Claude, Gemini). */
|
|
85
|
+
const SAFE_IMAGE_TYPES = new Set(['image/png', 'image/jpeg', 'image/webp', 'image/gif'])
|
|
86
|
+
|
|
84
87
|
export function renderToolOutput(value) {
|
|
85
88
|
const text = (value && value.summary) || (typeof value === "string" ? value : JSON.stringify(value || ""))
|
|
86
89
|
const blocks = [{ type: "text", text }]
|
|
87
|
-
if (value && value.attachment
|
|
90
|
+
if (value && value.attachment
|
|
91
|
+
&& value.attachment.attachmentId
|
|
92
|
+
&& SAFE_IMAGE_TYPES.has(value.attachment.mediaType)) {
|
|
88
93
|
blocks.push({ type: "image", attachment: value.attachment })
|
|
89
94
|
}
|
|
90
95
|
return blocks
|
package/lib/index.js
CHANGED
|
@@ -409,54 +409,20 @@ function migrateLegacySettings(sctx, scope) {
|
|
|
409
409
|
}
|
|
410
410
|
}
|
|
411
411
|
|
|
412
|
-
/** Collect text chunks from llm.stream iterator.
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
let sawDelta = false
|
|
416
|
-
for await (const chunk of iterable) {
|
|
417
|
-
if (chunk && chunk.type === 'text-delta' && typeof chunk.text === 'string') {
|
|
418
|
-
out += chunk.text
|
|
419
|
-
sawDelta = true
|
|
420
|
-
} else if (
|
|
421
|
-
!sawDelta && chunk && chunk.type === 'block-end'
|
|
422
|
-
&& chunk.block && chunk.block.type === 'text' && typeof chunk.block.text === 'string'
|
|
423
|
-
) {
|
|
424
|
-
out += chunk.block.text
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
return out.trim()
|
|
428
|
-
}
|
|
412
|
+
/** Collect text chunks from llm.stream iterator.
|
|
413
|
+
* Canonical implementation lives in prompt-enhancer.js; re-exported for backwards compat. */
|
|
414
|
+
export { collectText } from './prompt-enhancer.js'
|
|
429
415
|
|
|
430
|
-
/** Expand short prompt via chat model; return original prompt on error. */
|
|
431
|
-
export function buildEnhancePromptSystemMessage(provider, model) {
|
|
432
|
-
const isFlux = String(model || '').toLowerCase().includes('flux') || provider === 'fal'
|
|
433
|
-
if (isFlux) {
|
|
434
|
-
return 'You are an expert prompt engineer for FLUX image models. Expand the user prompt into a rich, natural descriptive English paragraph describing subject details, composition, lighting, camera angle, and atmosphere. Reply with ONLY the expanded prompt, no commentary, no markdown quotes.'
|
|
435
|
-
}
|
|
436
|
-
return 'You are an expert prompt engineer for Stable Diffusion models. Expand the user prompt into detailed comma-separated descriptive visual tags including subject, composition, studio lighting, materials, and artistic medium. Reply with ONLY the expanded prompt, no commentary.'
|
|
437
|
-
}
|
|
438
416
|
|
|
439
|
-
/** Expand short prompt via chat model; return original prompt on error.
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
messages: [
|
|
449
|
-
{ role: 'system', content: sysMsg },
|
|
450
|
-
{ role: 'user', content: prompt },
|
|
451
|
-
],
|
|
452
|
-
})
|
|
453
|
-
const text = await collectText(chunks)
|
|
454
|
-
if (!text) return { prompt, enhanced: false }
|
|
455
|
-
return { prompt: text, enhanced: true }
|
|
456
|
-
} catch (_) {
|
|
457
|
-
return { prompt, enhanced: false }
|
|
458
|
-
}
|
|
459
|
-
}
|
|
417
|
+
/** Expand short prompt via chat model; return original prompt on error.
|
|
418
|
+
* Canonical implementation lives in prompt-enhancer.js; re-exported for backwards compat. */
|
|
419
|
+
export { buildEnhancePromptSystemMessage } from './prompt-enhancer.js'
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
/** Expand short prompt via chat model; return original prompt on error.
|
|
423
|
+
* Canonical implementation lives in prompt-enhancer.js; re-exported for backwards compat. */
|
|
424
|
+
export { enhancePrompt } from './prompt-enhancer.js'
|
|
425
|
+
|
|
460
426
|
|
|
461
427
|
function sanitizeRaw(raw) {
|
|
462
428
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// prompt-enhancer.js — Prompt enhancement via LLM (#4 fix)
|
|
2
|
+
// Extracted from index.js to avoid circular dependency with tools/generation.js
|
|
3
|
+
|
|
4
|
+
/** Collect text chunks from llm.stream iterator. */
|
|
5
|
+
export async function collectText(iterable) {
|
|
6
|
+
let out = ''
|
|
7
|
+
let sawDelta = false
|
|
8
|
+
for await (const chunk of iterable) {
|
|
9
|
+
if (chunk && chunk.type === 'text-delta' && typeof chunk.text === 'string') {
|
|
10
|
+
out += chunk.text
|
|
11
|
+
sawDelta = true
|
|
12
|
+
} else if (
|
|
13
|
+
!sawDelta && chunk && chunk.type === 'block-end'
|
|
14
|
+
&& chunk.block && chunk.block.type === 'text' && typeof chunk.block.text === 'string'
|
|
15
|
+
) {
|
|
16
|
+
out += chunk.block.text
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return out.trim()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Build system message for prompt enhancement. */
|
|
23
|
+
export function buildEnhancePromptSystemMessage(provider, model) {
|
|
24
|
+
const isFlux = String(model || '').toLowerCase().includes('flux') || provider === 'fal'
|
|
25
|
+
if (isFlux) {
|
|
26
|
+
return 'You are an expert prompt engineer for FLUX image models. Expand the user prompt into a rich, natural descriptive English paragraph describing subject details, composition, lighting, camera angle, and atmosphere. Reply with ONLY the expanded prompt, no commentary, no markdown quotes.'
|
|
27
|
+
}
|
|
28
|
+
return 'You are an expert prompt engineer for Stable Diffusion models. Expand the user prompt into detailed comma-separated descriptive visual tags including subject, composition, studio lighting, materials, and artistic medium. Reply with ONLY the expanded prompt, no commentary.'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Expand short prompt via chat model; return original prompt on error. */
|
|
32
|
+
export async function enhancePrompt(ctx, cfg, prompt, signal, provider) {
|
|
33
|
+
if (!cfg.enhancePrompt) return { prompt, enhanced: false }
|
|
34
|
+
if (String(prompt).length >= (cfg.enhanceBelowChars || 200)) return { prompt, enhanced: false }
|
|
35
|
+
try {
|
|
36
|
+
const sysMsg = buildEnhancePromptSystemMessage(provider || cfg.provider, cfg.enhanceModel || cfg.model)
|
|
37
|
+
const chunks = ctx.llm.stream({
|
|
38
|
+
...(signal ? { signal } : {}),
|
|
39
|
+
...(cfg.enhanceModel ? { model: cfg.enhanceModel } : {}),
|
|
40
|
+
messages: [
|
|
41
|
+
{ role: 'system', content: sysMsg },
|
|
42
|
+
{ role: 'user', content: prompt },
|
|
43
|
+
],
|
|
44
|
+
})
|
|
45
|
+
const text = await collectText(chunks)
|
|
46
|
+
if (!text) return { prompt, enhanced: false }
|
|
47
|
+
return { prompt: text, enhanced: true }
|
|
48
|
+
} catch (_) {
|
|
49
|
+
return { prompt, enhanced: false }
|
|
50
|
+
}
|
|
51
|
+
}
|
package/lib/providers.js
CHANGED
|
@@ -128,9 +128,16 @@ export async function pixelDiff(a, b) {
|
|
|
128
128
|
|
|
129
129
|
/** Extract detailed ComfyUI node error messages from /history/{pid} response. */
|
|
130
130
|
|
|
131
|
-
/**
|
|
131
|
+
/** Raster media types that DSH attachment store (Sharp) can process. */
|
|
132
|
+
const RASTER_IMAGE_TYPES = new Set(['image/png', 'image/jpeg', 'image/webp', 'image/gif'])
|
|
133
|
+
|
|
134
|
+
/** Safe attachment persistence with fallback when ctx.attachments is unavailable.
|
|
135
|
+
* SVG and other non-raster types are never sent to saveImage (Sharp rejects them)
|
|
136
|
+
* and never emitted as image content blocks (LLMs reject them). */
|
|
132
137
|
export async function saveAttachmentSafe(ctx, { bytes, mediaType, name }) {
|
|
133
|
-
|
|
138
|
+
// Only attempt saveImage for raster formats that Sharp/LLMs can handle
|
|
139
|
+
if (RASTER_IMAGE_TYPES.has(mediaType)
|
|
140
|
+
&& ctx && ctx.attachments && typeof ctx.attachments.saveImage === 'function') {
|
|
134
141
|
try {
|
|
135
142
|
const att = await ctx.attachments.saveImage({
|
|
136
143
|
data: new Uint8Array(bytes),
|
|
@@ -198,16 +198,12 @@ export function registerProcessingTools(ctx, deps) {
|
|
|
198
198
|
const filePath = path.join(outDir, name)
|
|
199
199
|
await writeFile(filePath, res.bytes)
|
|
200
200
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
name,
|
|
208
|
-
})
|
|
209
|
-
localUrl = '/dsh-image-gen/image?id=' + encodeURIComponent(attachment.attachmentId)
|
|
210
|
-
}
|
|
201
|
+
// SVG is not a raster format — do not create image attachment (#5)
|
|
202
|
+
// saveAttachmentSafe already guards against SVG, but we skip entirely
|
|
203
|
+
// to avoid even a fallback stub that could confuse renderToolOutput
|
|
204
|
+
const attachment = null
|
|
205
|
+
const localUrl = ''
|
|
206
|
+
|
|
211
207
|
|
|
212
208
|
const summary = `### Vectorized SVG Export\n- **File**: ${filePath}\n- **Format**: SVG (XML vector)\n- **Color mode**: ${args.color_mode || 'color'}\n- **Palette size**: ${res.palette ? res.palette.length : 16} colors\n`
|
|
213
209
|
return toLosslessJson({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-image-gen",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.4",
|
|
4
4
|
"description": "Image generation for DeepSeek Harness: a generate_image tool with pluggable providers — the FAL queue, any OpenAI-compatible images API, or a ChatGPT/Grok subscription with no API key at all. The picture is shown inline in the conversation; the model receives either a link (works with any chat model) or the image itself (needs dsh-vision-bridge or a vision-capable model).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|