@goodandready/dsh-image-gen 0.10.0 → 0.10.1
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/client.js +26 -22
- package/lib/index.js +33 -10
- package/lib/providers.js +3 -3
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -808,17 +808,38 @@ window.__ModuleLoader__.load({
|
|
|
808
808
|
}
|
|
809
809
|
}
|
|
810
810
|
|
|
811
|
-
|
|
811
|
+
function FalImageCard(props) {
|
|
812
812
|
const block = props.block
|
|
813
813
|
const running = !('kind' in (block || {}))
|
|
814
814
|
const failed = block && (block.isError || block.error !== undefined)
|
|
815
815
|
const parsed = readResult(block)
|
|
816
|
-
const [editing, setEditing] = react.useState(false)
|
|
817
816
|
|
|
818
|
-
|
|
817
|
+
let args = {}
|
|
818
|
+
try {
|
|
819
|
+
const raw = (block && (block.call ? block.call.argsRaw : block.argsRaw)) || ''
|
|
820
|
+
if (raw) args = JSON.parse(raw)
|
|
821
|
+
} catch (e) { /* ignore */ }
|
|
819
822
|
|
|
823
|
+
let prompt = ''
|
|
824
|
+
try {
|
|
825
|
+
const raw = (block && (block.call ? block.call.argsRaw : block.argsRaw)) || ''
|
|
826
|
+
prompt = raw ? (JSON.parse(raw).prompt || '') : ''
|
|
827
|
+
} catch (e) { prompt = '' }
|
|
828
|
+
|
|
829
|
+
const repeatText = (() => {
|
|
830
|
+
const parts = [String(args.prompt || '').trim()]
|
|
831
|
+
const size = args.image_size || args.size
|
|
832
|
+
const seed = args.seed
|
|
833
|
+
if (size) parts.push(`Размер: ${size}`)
|
|
834
|
+
if (seed !== undefined) parts.push(`Сид: ${seed}`)
|
|
835
|
+
return parts.filter(Boolean).join(', ')
|
|
836
|
+
})()
|
|
837
|
+
|
|
838
|
+
const [editing, setEditing] = react.useState(false)
|
|
839
|
+
const [inpaintOpen, setInpaintOpen] = react.useState(false)
|
|
820
840
|
const [compareSlider, setCompareSlider] = react.useState(50)
|
|
821
841
|
const [showCompare, setShowCompare] = react.useState(false)
|
|
842
|
+
const [editText, setEditText] = react.useState('')
|
|
822
843
|
|
|
823
844
|
const onEditPrompt = () => {
|
|
824
845
|
const textToInsert = prompt || repeatText || ''
|
|
@@ -883,12 +904,7 @@ window.__ModuleLoader__.load({
|
|
|
883
904
|
sendActionPrompt(`Удали фон у этого изображения: remove_background(image: "${ref}")`)
|
|
884
905
|
}
|
|
885
906
|
|
|
886
|
-
|
|
887
|
-
let prompt = ''
|
|
888
|
-
try {
|
|
889
|
-
const raw = (block && (block.call ? block.call.argsRaw : block.argsRaw)) || ''
|
|
890
|
-
prompt = raw ? (JSON.parse(raw).prompt || '') : ''
|
|
891
|
-
} catch (e) { prompt = '' }
|
|
907
|
+
|
|
892
908
|
|
|
893
909
|
const head = react.createElement('div', { className: 'fal-head' },
|
|
894
910
|
running ? 'Генерирую изображение…' : failed ? 'Генерация не удалась' : 'Изображение')
|
|
@@ -897,19 +913,7 @@ window.__ModuleLoader__.load({
|
|
|
897
913
|
// промптом и параметрами, извлечёнными из исходного вызова инструмента.
|
|
898
914
|
// Дальше модель сама решает, как сгенерировать, — это просто повторный
|
|
899
915
|
// запрос, а не вызов инструмента из карточки.
|
|
900
|
-
|
|
901
|
-
try {
|
|
902
|
-
const raw = (block && (block.call ? block.call.argsRaw : block.argsRaw)) || ''
|
|
903
|
-
if (raw) args = JSON.parse(raw)
|
|
904
|
-
} catch (e) { /* ignore */ }
|
|
905
|
-
const repeatText = (() => {
|
|
906
|
-
const parts = [String(args.prompt || '').trim()]
|
|
907
|
-
const size = args.image_size || args.size
|
|
908
|
-
const seed = args.seed
|
|
909
|
-
if (size) parts.push(`Размер: ${size}`)
|
|
910
|
-
if (seed !== undefined) parts.push(`Сид: ${seed}`)
|
|
911
|
-
return parts.filter(Boolean).join(', ')
|
|
912
|
-
})()
|
|
916
|
+
|
|
913
917
|
const repeat = async () => {
|
|
914
918
|
try {
|
|
915
919
|
const sessions = props.sessions
|
package/lib/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import z from '@deepseek-ai/schemastery'
|
|
|
16
16
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
17
17
|
import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
|
18
18
|
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
19
|
+
import { existsSync, unlinkSync, accessSync } from 'node:fs'
|
|
19
20
|
import os from 'node:os'
|
|
20
21
|
import path from 'node:path'
|
|
21
22
|
import {
|
|
@@ -217,7 +218,12 @@ export async function resolveSource(ctx, exec, ref) {
|
|
|
217
218
|
}
|
|
218
219
|
const p = path.resolve(sessionCwd || process.cwd(), ref)
|
|
219
220
|
const bytes = await readFile(p)
|
|
220
|
-
|
|
221
|
+
const ext = path.extname(p).toLowerCase()
|
|
222
|
+
const mediaType = ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg'
|
|
223
|
+
: ext === '.webp' ? 'image/webp'
|
|
224
|
+
: ext === '.svg' ? 'image/svg+xml'
|
|
225
|
+
: 'image/png'
|
|
226
|
+
return { bytes, mediaType }
|
|
221
227
|
}
|
|
222
228
|
|
|
223
229
|
export function slugify(input) {
|
|
@@ -295,7 +301,7 @@ export async function findCached(entries, seed, prompt) {
|
|
|
295
301
|
/** Вернуть запись из кэша, если файл существует; иначе undefined. */
|
|
296
302
|
export async function cachedResult(entry) {
|
|
297
303
|
if (!entry || !entry.path) return undefined
|
|
298
|
-
|
|
304
|
+
if (!entry.path || !existsSync(entry.path)) return undefined
|
|
299
305
|
return {
|
|
300
306
|
path: entry.path,
|
|
301
307
|
url: entry.attachmentId ? `/dsh-image-gen/image?id=${encodeURIComponent(entry.attachmentId)}` : '',
|
|
@@ -326,8 +332,8 @@ export async function pruneHistory(entries, pruneDays) {
|
|
|
326
332
|
for (const e of entries) {
|
|
327
333
|
const created = e.createdAt ? Date.parse(e.createdAt) : NaN
|
|
328
334
|
if (Number.isFinite(created) && created < cutoff) {
|
|
329
|
-
try {
|
|
330
|
-
try {
|
|
335
|
+
try { unlinkSync(e.path) } catch (err) { /* файл уже удалён */ }
|
|
336
|
+
try { unlinkSync(e.path.replace(/\.[^.]+$/, '.json')) } catch (err) { /* sidecar */ }
|
|
331
337
|
continue
|
|
332
338
|
}
|
|
333
339
|
kept.push(e)
|
|
@@ -494,7 +500,7 @@ export function apply(ctx, config) {
|
|
|
494
500
|
res.end(JSON.stringify({ error: 'GET only' }))
|
|
495
501
|
return
|
|
496
502
|
}
|
|
497
|
-
const exists = (p) =>
|
|
503
|
+
const exists = (p) => existsSync(p)
|
|
498
504
|
const entries = await readHistory()
|
|
499
505
|
const withThumbs = filterHistory(entries, exists).map((e) => ({
|
|
500
506
|
...e,
|
|
@@ -589,7 +595,7 @@ export function apply(ctx, config) {
|
|
|
589
595
|
output: {
|
|
590
596
|
schema: {
|
|
591
597
|
type: 'object',
|
|
592
|
-
additionalProperties:
|
|
598
|
+
additionalProperties: true,
|
|
593
599
|
properties: {
|
|
594
600
|
path: { type: 'string' },
|
|
595
601
|
url: { type: 'string' },
|
|
@@ -664,12 +670,14 @@ export function apply(ctx, config) {
|
|
|
664
670
|
const source = args.source_image ? await resolveSource(ctx, exec, args.source_image) : undefined
|
|
665
671
|
const mask = args.mask ? await resolveSource(ctx, exec, args.mask) : undefined
|
|
666
672
|
const enhanced = await enhancePrompt(ctx, cfg, args.prompt, exec.signal, provider)
|
|
667
|
-
const
|
|
668
|
-
const
|
|
669
|
-
const effectivePrompt =
|
|
673
|
+
const effectiveStyle = args.style || cfg.stylePreset
|
|
674
|
+
const styleInfo = resolveStylePreset(effectiveStyle, args.negative_prompt, args.guidance_scale)
|
|
675
|
+
const effectivePrompt = styleInfo.promptSuffix ? `${enhanced.prompt}, ${styleInfo.promptSuffix}` : enhanced.prompt
|
|
676
|
+
const effectiveNegative = styleInfo.negativePrompt
|
|
677
|
+
const effectiveGuidance = styleInfo.guidanceScale
|
|
670
678
|
const providers = makeProviders(
|
|
671
679
|
{ fetchImpl: fetch, resolveKey: (ref) => resolveApiKey(ctx, ref), cfg, subscriptionImages },
|
|
672
|
-
{ prompt: effectivePrompt, size, format, seed: args.seed, signal: exec.signal, negativePrompt:
|
|
680
|
+
{ prompt: effectivePrompt, size, format, seed: args.seed, signal: exec.signal, negativePrompt: effectiveNegative, guidanceScale: effectiveGuidance, source, mask, strength: args.strength, quality: args.quality, style: args.style, aspectPixels },
|
|
673
681
|
)
|
|
674
682
|
// Подписочные провайдеры (codex/grok) отдают {ok:false, reason} вместо исключения:
|
|
675
683
|
// отказ должен дойти до модели текстом. Без проверки execute шёл дальше с пустыми
|
|
@@ -734,13 +742,28 @@ export function apply(ctx, config) {
|
|
|
734
742
|
}), null, 2),
|
|
735
743
|
)
|
|
736
744
|
|
|
745
|
+
const cacheHash = computeGenerationHash({
|
|
746
|
+
provider,
|
|
747
|
+
model: cfg.model,
|
|
748
|
+
prompt: promptArg,
|
|
749
|
+
seed: gen.seed,
|
|
750
|
+
size: args.image_size || cfg.defaultSize,
|
|
751
|
+
style: args.style || cfg.stylePreset,
|
|
752
|
+
})
|
|
737
753
|
const entry = {
|
|
738
754
|
path: filePath,
|
|
739
755
|
prompt: promptArg,
|
|
740
756
|
provider,
|
|
757
|
+
model: cfg.model,
|
|
741
758
|
size,
|
|
742
759
|
format,
|
|
743
760
|
seed: gen.seed,
|
|
761
|
+
cacheHash,
|
|
762
|
+
thumbnailUrl: localUrl,
|
|
763
|
+
width: gen.width || attachment.width,
|
|
764
|
+
height: gen.height || attachment.height,
|
|
765
|
+
mediaType,
|
|
766
|
+
bytes: attachment.bytes,
|
|
744
767
|
createdAt: new Date().toISOString(),
|
|
745
768
|
attachmentId: attachment.attachmentId,
|
|
746
769
|
}
|
package/lib/providers.js
CHANGED
|
@@ -238,7 +238,7 @@ export async function pollStatus(fetchImpl, statusUrl, key, signal, pollInterval
|
|
|
238
238
|
}, { once: true })
|
|
239
239
|
}
|
|
240
240
|
})
|
|
241
|
-
if (signal
|
|
241
|
+
if (signal?.aborted) throw new Error('FAL generation cancelled')
|
|
242
242
|
const res = await fetchImpl(statusUrl, { headers: { Authorization: falAuthHeader(key) }, signal })
|
|
243
243
|
const data = await res.json().catch(() => ({}))
|
|
244
244
|
const status = data.status
|
|
@@ -540,7 +540,7 @@ export function makeProviders(deps, job) {
|
|
|
540
540
|
const deadline = Date.now() + cfg.timeoutMs
|
|
541
541
|
let attempt = 0
|
|
542
542
|
for (;;) {
|
|
543
|
-
if (signal
|
|
543
|
+
if (signal?.aborted) throw new Error('Local ComfyUI generation cancelled')
|
|
544
544
|
if (Date.now() > deadline) throw new Error(`Local ComfyUI timed out after ${cfg.timeoutMs} ms`)
|
|
545
545
|
const cDelay = calculateBackoff(attempt++, cfg.pollIntervalMs || 1000, 5000)
|
|
546
546
|
await new Promise((resolve) => {
|
|
@@ -663,7 +663,7 @@ export function makeProviders(deps, job) {
|
|
|
663
663
|
const deadline = Date.now() + (cfg.timeoutMs || 180000)
|
|
664
664
|
let attempt = 0
|
|
665
665
|
while (pred.status !== 'succeeded') {
|
|
666
|
-
if (signal
|
|
666
|
+
if (signal?.aborted) throw new Error('Replicate generation cancelled')
|
|
667
667
|
if (Date.now() > deadline) throw new Error('Replicate generation timed out')
|
|
668
668
|
if (pred.status === 'failed' || pred.status === 'canceled') {
|
|
669
669
|
throw new Error(`Replicate failed: ${pred.error || 'unknown error'}`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-image-gen",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
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",
|