@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 CHANGED
@@ -808,17 +808,38 @@ window.__ModuleLoader__.load({
808
808
  }
809
809
  }
810
810
 
811
- function FalImageCard(props) {
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
- const [inpaintOpen, setInpaintOpen] = react.useState(false)
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
- const [editText, setEditText] = react.useState('')
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
- let args = {}
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
- return { bytes, mediaType: 'image/png' }
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
- try { require('node:fs').accessSync(entry.path) } catch (e) { return undefined }
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 { require('node:fs').unlinkSync(e.path) } catch (err) { /* файл уже удалён */ }
330
- try { require('node:fs').unlinkSync(e.path.replace(/\.[^.]+$/, '.json')) } catch (err) { /* sidecar */ }
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) => { try { return require('node:fs').existsSync(p) } catch { return false } }
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: false,
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 styleSuffix = cfg.stylePreset ? `, ${cfg.stylePreset}` : ''
668
- const styledPrompt = args.style ? applyStylePreset(enhanced.prompt, args.style) : enhanced.prompt;
669
- const effectivePrompt = styledPrompt + styleSuffix
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: args.negative_prompt, guidanceScale: args.guidance_scale, source, mask, strength: args.strength, quality: args.quality, style: args.style, aspectPixels },
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.aborted) throw new Error('FAL generation cancelled')
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.aborted) throw new Error('Local ComfyUI generation cancelled')
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.aborted) throw new Error('Replicate generation cancelled')
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.0",
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",