@goodandready/dsh-image-gen 0.10.3 → 0.10.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/README.md CHANGED
@@ -55,6 +55,14 @@
55
55
 
56
56
  ---
57
57
 
58
+
59
+ ## 🚀 Updates in v0.10.4: Cordis Lifecycle, Full Settings GUI, and i18n
60
+ - **Cordis Lifecycle (#136)**: wrapped all 7 tool registrations in `ctx.effect` for proper disposal on reload.
61
+ - **Settings GUI Completeness (#137)**: exposed fields for Replicate, SeaDream, Gemini, Local ComfyUI/A1111, style presets, and LLM enhancer.
62
+ - **Package Manifest (#138)**: declared kernel `peerDependencies` (`host-webserver`, `settings`, `llm`, `system-prompt`).
63
+ - **React Cleanup (#139)**: removed dead state hooks from `FalImageCard`.
64
+ - **Complete Localization (#140)**: eliminated hardcoded strings, wiring comprehensive dictionaries for en, ru, and zh.
65
+
58
66
  ## 🎨 Supported Generation Backends
59
67
 
60
68
  * **`fal`** (Default): FAL.ai queue for FLUX.1, SDXL, Clarity Upscaler, and BiRefNet.
package/README.ru.md CHANGED
@@ -111,6 +111,14 @@ graph LR
111
111
 
112
112
  ---
113
113
 
114
+
115
+ ## 🚀 Обновления v0.10.4: жизненный цикл Cordis, полнота настроек и интернационализация
116
+ - **Жизненный цикл Cordis (#136)**: регистрация всех 7 инструментов обёрнута в `ctx.effect` для штатной отписки при перезагрузке плагина.
117
+ - **Полнота настроек в GUI (#137)**: в карточку настроек добавлены поля для Replicate, SeaDream, Gemini, Local ComfyUI/A1111, пресетов стилей и LLM-улучшителя.
118
+ - **Манифест зависимостей (#138)**: в `peerDependencies` задекларированы все используемые службы ядра (`host-webserver`, `settings`, `llm`, `system-prompt`).
119
+ - **Оптимизация компонентов (#139)**: из `FalImageCard` удалён неиспользуемый мёртвый стейт.
120
+ - **100% интернационализация (#140)**: устранены захардкоженные русские строки, добавлены переводы на русский, английский и китайский языки.
121
+
114
122
  ## 🎨 Поддерживаемые провайдеры генерации
115
123
 
116
124
  * **`fal`** *(дефолт)*: Сверхбыстрая очередь FAL.ai (`FLUX.1-schnell`, `FLUX-dev`, `SDXL`, BiRefNet, Clarity Upscaler).
package/README.zh.md CHANGED
@@ -37,6 +37,14 @@
37
37
 
38
38
  ---
39
39
 
40
+
41
+ ## 🚀 v0.10.4 更新:Cordis 生命周期、完整设置界面与国际化
42
+ - **Cordis 生命周期 (#136)**:将所有 7 个工具注册包装在 `ctx.effect` 中,以便在热重载时正常释放。
43
+ - **设置界面完整性 (#137)**:添加了 Replicate、SeaDream、Gemini、本地 ComfyUI/A1111、风格预设和 LLM 增强器的配置字段。
44
+ - **包清单依赖 (#138)**:在 `peerDependencies` 中补齐了内核服务依赖。
45
+ - **React 组件优化 (#139)**:从 `FalImageCard` 中移除了未使用的无用状态。
46
+ - **全面国际化 (#140)**:消除了硬编码文本,为中、英、俄语提供完整翻译。
47
+
40
48
  ## 📦 安装
41
49
 
42
50
  ```bash
package/lib/client.js CHANGED
@@ -465,22 +465,53 @@ window.__ModuleLoader__.load({
465
465
 
466
466
  /** Field specs in display order. */
467
467
  const booleanField = (name) => ({ name, type: 'boolean', parse: (v) => v === 'true' || v === true, format: (v) => String(!!v) })
468
+ const STYLE_PRESET_OPTIONS = ['', 'cinematic', 'anime', 'digital_art', 'photographic', 'fantasy', 'isometric', 'cyberpunk', 'watercolor', 'origami', 'oil_painting', 'pixel_art', 'logo']
469
+
468
470
  const FIELDS = [
469
471
  { field: 'enabled', spec: booleanField('enabled'), kind: 'select', options: ['true', 'false'], labelKey: 'f.enabled', hintKey: 'f.enabledHint' },
470
- { field: 'provider', spec: selectField('provider', PROVIDERS), kind: 'select', options: PROVIDERS, labelKey: 'f.provider', hintKey: 'f.providerHint', providerNeeds: { fal: 'ключ FAL', custom: 'адрес + ключ', codex: 'подписка ChatGPT', grok: 'подписка Grok', local: 'адрес сервера' } },
472
+ { field: 'provider', spec: selectField('provider', PROVIDERS), kind: 'select', options: PROVIDERS, labelKey: 'f.provider', hintKey: 'f.providerHint', providerNeeds: { fal: 'f.needFal', custom: 'f.needCustom', codex: 'f.needCodex', grok: 'f.needGrok', local: 'f.needLocal', seedream: 'f.needSeedream', gemini: 'f.needGemini', replicate: 'f.needReplicate' } },
473
+
474
+ // FAL
471
475
  { field: 'model', spec: textField('model'), kind: 'text', labelKey: 'f.model', hintKey: 'f.modelHint', placeholderKey: 'p.model', when: ['fal'] },
472
476
  { field: 'apiKeyEnv', spec: textField('apiKeyEnv'), kind: 'text', labelKey: 'f.apiKeyEnv', hintKey: 'f.apiKeyEnvHint', placeholderKey: 'p.apiKeyEnv', when: ['fal'] },
473
477
  { field: 'baseURL', spec: textField('baseURL'), kind: 'text', labelKey: 'f.baseURL', hintKey: 'f.baseURLHint', placeholderKey: 'p.baseURL', when: ['fal'] },
474
- { field: 'defaultSize', spec: selectField('defaultSize', IMAGE_SIZES), kind: 'select', options: IMAGE_SIZES, labelKey: 'f.defaultSize', hintKey: 'f.defaultSizeHint', sizePixels: SIZE_PIXELS },
475
- { field: 'defaultFormat', spec: selectField('defaultFormat', OUTPUT_FORMATS), kind: 'select', options: OUTPUT_FORMATS, labelKey: 'f.defaultFormat', hintKey: 'f.defaultFormatHint' },
476
478
  { field: 'pollIntervalMs', spec: numberField('pollIntervalMs'), kind: 'number', labelKey: 'f.pollIntervalMs', hintKey: 'f.pollIntervalMsHint', placeholderKey: 'p.pollIntervalMs', when: ['fal'] },
477
479
  { field: 'timeoutMs', spec: numberField('timeoutMs'), kind: 'number', labelKey: 'f.timeoutMs', hintKey: 'f.timeoutMsHint', placeholderKey: 'p.timeoutMs', when: ['fal'] },
478
- { field: 'deliverAs', spec: selectField('deliverAs', DELIVERY_MODES), kind: 'select', options: DELIVERY_MODES, labelKey: 'f.deliverAs', hintKey: 'f.deliverAsHint' },
480
+
481
+ // Custom
479
482
  { field: 'customBaseURL', spec: textField('customBaseURL'), kind: 'text', labelKey: 'f.customBaseURL', hintKey: 'f.customBaseURLHint', placeholderKey: 'p.customBaseURL', when: ['custom'] },
480
483
  { field: 'customModel', spec: textField('customModel'), kind: 'text', labelKey: 'f.customModel', hintKey: 'f.customModelHint', placeholderKey: 'p.customModel', when: ['custom'] },
481
484
  { field: 'customKeyEnv', spec: textField('customKeyEnv'), kind: 'text', labelKey: 'f.customKeyEnv', hintKey: 'f.customKeyEnvHint', placeholderKey: 'p.customKeyEnv', when: ['custom'] },
482
485
  { field: 'customSize', spec: textField('customSize'), kind: 'text', labelKey: 'f.customSize', hintKey: 'f.customSizeHint', placeholderKey: 'p.customSize', when: ['custom'] },
486
+
487
+ // Replicate
488
+ { field: 'replicateModel', spec: textField('replicateModel'), kind: 'text', labelKey: 'f.replicateModel', hintKey: 'f.replicateModelHint', placeholderKey: 'p.replicateModel', when: ['replicate'] },
489
+ { field: 'replicateKeyEnv', spec: textField('replicateKeyEnv'), kind: 'text', labelKey: 'f.replicateKeyEnv', hintKey: 'f.replicateKeyEnvHint', placeholderKey: 'p.replicateKeyEnv', when: ['replicate'] },
490
+
491
+ // SeaDream
492
+ { field: 'seedreamModel', spec: textField('seedreamModel'), kind: 'text', labelKey: 'f.seedreamModel', hintKey: 'f.seedreamModelHint', placeholderKey: 'p.seedreamModel', when: ['seedream'] },
493
+ { field: 'seedreamKeyEnv', spec: textField('seedreamKeyEnv'), kind: 'text', labelKey: 'f.seedreamKeyEnv', hintKey: 'f.seedreamKeyEnvHint', placeholderKey: 'p.seedreamKeyEnv', when: ['seedream'] },
494
+ { field: 'seedreamBaseURL', spec: textField('seedreamBaseURL'), kind: 'text', labelKey: 'f.seedreamBaseURL', hintKey: 'f.seedreamBaseURLHint', placeholderKey: 'p.seedreamBaseURL', when: ['seedream'] },
495
+
496
+ // Gemini
497
+ { field: 'geminiModel', spec: textField('geminiModel'), kind: 'text', labelKey: 'f.geminiModel', hintKey: 'f.geminiModelHint', placeholderKey: 'p.geminiModel', when: ['gemini'] },
498
+ { field: 'geminiKeyEnv', spec: textField('geminiKeyEnv'), kind: 'text', labelKey: 'f.geminiKeyEnv', hintKey: 'f.geminiKeyEnvHint', placeholderKey: 'p.geminiKeyEnv', when: ['gemini'] },
499
+
500
+ // Local ComfyUI / A1111
501
+ { field: 'localComfyUrl', spec: textField('localComfyUrl'), kind: 'text', labelKey: 'f.localComfyUrl', hintKey: 'f.localComfyUrlHint', placeholderKey: 'p.localComfyUrl', when: ['local'] },
502
+ { field: 'localA1111Url', spec: textField('localA1111Url'), kind: 'text', labelKey: 'f.localA1111Url', hintKey: 'f.localA1111UrlHint', placeholderKey: 'p.localA1111Url', when: ['local'] },
503
+
504
+ // Common Style & LLM Enhancer
505
+ { field: 'stylePreset', spec: selectField('stylePreset', STYLE_PRESET_OPTIONS), kind: 'select', options: STYLE_PRESET_OPTIONS, labelKey: 'f.stylePreset', hintKey: 'f.stylePresetHint' },
506
+ { field: 'enableLlmEnhancer', spec: booleanField('enableLlmEnhancer'), kind: 'select', options: ['true', 'false'], labelKey: 'f.enableLlmEnhancer', hintKey: 'f.enableLlmEnhancerHint' },
507
+
508
+ // Output & Defaults
509
+ { field: 'defaultSize', spec: selectField('defaultSize', IMAGE_SIZES), kind: 'select', options: IMAGE_SIZES, labelKey: 'f.defaultSize', hintKey: 'f.defaultSizeHint', sizePixels: SIZE_PIXELS },
510
+ { field: 'defaultFormat', spec: selectField('defaultFormat', OUTPUT_FORMATS), kind: 'select', options: OUTPUT_FORMATS, labelKey: 'f.defaultFormat', hintKey: 'f.defaultFormatHint' },
511
+ { field: 'deliverAs', spec: selectField('deliverAs', DELIVERY_MODES), kind: 'select', options: DELIVERY_MODES, labelKey: 'f.deliverAs', hintKey: 'f.deliverAsHint' },
483
512
  { field: 'outputDir', spec: textField('outputDir'), kind: 'text', labelKey: 'f.outputDir', hintKey: 'f.outputDirHint', placeholderKey: 'p.outputDir' },
513
+ { field: 'cacheTtlDays', spec: numberField('cacheTtlDays'), kind: 'number', labelKey: 'f.cacheTtlDays', hintKey: 'f.cacheTtlDaysHint', placeholderKey: 'p.cacheTtlDays' },
514
+ { field: 'maxHistoryEntries', spec: numberField('maxHistoryEntries'), kind: 'number', labelKey: 'f.maxHistoryEntries', hintKey: 'f.maxHistoryEntriesHint', placeholderKey: 'p.maxHistoryEntries' },
484
515
  ]
485
516
 
486
517
  function Field(props) {
@@ -501,7 +532,7 @@ window.__ModuleLoader__.load({
501
532
  }
502
533
  if (entry.kind === 'select') {
503
534
  const px = entry.sizePixels && entry.sizePixels[state.text] ? ` — ${entry.sizePixels[state.text]}` : ''
504
- const need = entry.providerNeeds && entry.providerNeeds[state.text] ? ` (нужно: ${entry.providerNeeds[state.text]})` : ''
535
+ const need = entry.providerNeeds && entry.providerNeeds[state.text] ? ` (${t(entry.providerNeeds[state.text])})` : ''
505
536
  const hint = base.hint + px + need
506
537
  return react_jsx_runtime.jsx(SelectField, { ...base, hint, options: entry.options, inheritLabel: t('f.inherit') })
507
538
  }
@@ -828,19 +859,17 @@ function FalImageCard(props) {
828
859
  prompt = raw ? (JSON.parse(raw).prompt || '') : ''
829
860
  } catch (e) { prompt = '' }
830
861
 
862
+ const t = props.t || ((k) => k)
831
863
  const repeatText = (() => {
832
864
  const parts = [String(args.prompt || '').trim()]
833
865
  const size = args.image_size || args.size
834
866
  const seed = args.seed
835
- if (size) parts.push(`Размер: ${size}`)
836
- if (seed !== undefined) parts.push(`Сид: ${seed}`)
867
+ if (size) parts.push(`${t('card.sizePrefix')}: ${size}`)
868
+ if (seed !== undefined) parts.push(`${t('card.seedPrefix')}: ${seed}`)
837
869
  return parts.filter(Boolean).join(', ')
838
870
  })()
839
871
 
840
872
  const [editing, setEditing] = react.useState(false)
841
- const [inpaintOpen, setInpaintOpen] = react.useState(false)
842
- const [compareSlider, setCompareSlider] = react.useState(50)
843
- const [showCompare, setShowCompare] = react.useState(false)
844
873
  const [editText, setEditText] = react.useState('')
845
874
 
846
875
  const onEditPrompt = () => {
@@ -862,8 +891,6 @@ function FalImageCard(props) {
862
891
  }
863
892
 
864
893
  const [copied, setCopied] = react.useState(false)
865
- const canvasRef = react.useRef(null)
866
- const [drawing, setDrawing] = react.useState(false)
867
894
 
868
895
  const sendActionPrompt = async (text) => {
869
896
  try {
@@ -893,23 +920,23 @@ function FalImageCard(props) {
893
920
 
894
921
  const onReroll = () => {
895
922
  const nextSeed = (typeof args.seed === 'number' ? args.seed + 1 : Math.floor(Math.random() * 100000))
896
- sendActionPrompt(`Сгенерируй заново с другим сидом (${nextSeed}): ${prompt || repeatText}`)
923
+ sendActionPrompt(`${t('card.actionReroll')} (${nextSeed}): ${prompt || repeatText}`)
897
924
  }
898
925
 
899
926
  const onUpscale = () => {
900
927
  const ref = parsed.attachment ? parsed.attachment.attachmentId : (parsed.url || '')
901
- sendActionPrompt(`Увеличь разрешение этого изображения в 2 раза: upscale_image(image: "${ref}")`)
928
+ sendActionPrompt(`${t('card.actionUpscale')}: upscale_image(image: "${ref}")`)
902
929
  }
903
930
 
904
931
  const onRemoveBg = () => {
905
932
  const ref = parsed.attachment ? parsed.attachment.attachmentId : (parsed.url || '')
906
- sendActionPrompt(`Удали фон у этого изображения: remove_background(image: "${ref}")`)
933
+ sendActionPrompt(`${t('card.actionRemoveBg')}: remove_background(image: "${ref}")`)
907
934
  }
908
935
 
909
936
 
910
937
 
911
938
  const head = react.createElement('div', { className: 'fal-head' },
912
- running ? 'Генерирую изображение…' : failed ? 'Генерация не удалась' : 'Изображение')
939
+ running ? t('card.generating') : failed ? t('card.failed') : t('card.image'))
913
940
 
914
941
  // «Повторить генерацию»: отправляет в чат новое сообщение с теми же
915
942
  // промптом и параметрами, извлечёнными из исходного вызова инструмента.
@@ -933,10 +960,10 @@ function FalImageCard(props) {
933
960
  const body = []
934
961
  if (prompt) body.push(react.createElement('div', { className: 'fal-prompt', key: 'p' }, prompt))
935
962
  if (args.originalPrompt && args.originalPrompt !== prompt) {
936
- body.push(react.createElement('div', { className: 'fal-meta', key: 'orig' }, `Исходный запрос: ${args.originalPrompt}`))
963
+ body.push(react.createElement('div', { className: 'fal-meta', key: 'orig' }, `${t('card.origPrompt')}: ${args.originalPrompt}`))
937
964
  }
938
965
  if (failed) {
939
- body.push(react.createElement('div', { className: 'fal-err', key: 'e' }, parsed.text || 'неизвестная ошибка'))
966
+ body.push(react.createElement('div', { className: 'fal-err', key: 'e' }, parsed.text || t('card.unknownError')))
940
967
  } else if (!parsed.attachment && parsed.url) {
941
968
  body.push(react.createElement('img', {
942
969
  key: 'i',
@@ -990,7 +1017,7 @@ function FalImageCard(props) {
990
1017
  type: 'button',
991
1018
  className: 'fal-action-btn',
992
1019
  onClick: onReroll,
993
- title: 'Перегенерировать со следующим сидом'
1020
+ title: t('card.rerollTitle')
994
1021
  }, '🔄 Re-roll'))
995
1022
 
996
1023
  btns.push(react.createElement('button', {
@@ -998,7 +1025,7 @@ function FalImageCard(props) {
998
1025
  type: 'button',
999
1026
  className: 'fal-action-btn',
1000
1027
  onClick: onUpscale,
1001
- title: 'Увеличить разрешение 2x'
1028
+ title: t('card.upscaleTitle')
1002
1029
  }, '🔍 2x Upscale'))
1003
1030
 
1004
1031
  btns.push(react.createElement('button', {
@@ -1006,7 +1033,7 @@ function FalImageCard(props) {
1006
1033
  type: 'button',
1007
1034
  className: 'fal-action-btn',
1008
1035
  onClick: onRemoveBg,
1009
- title: 'Удалить фон'
1036
+ title: t('card.removeBgTitle')
1010
1037
  }, '✂️ Remove BG'))
1011
1038
 
1012
1039
  if (prompt) {
@@ -1015,8 +1042,8 @@ function FalImageCard(props) {
1015
1042
  type: 'button',
1016
1043
  className: 'fal-action-btn',
1017
1044
  onClick: onCopyPrompt,
1018
- title: 'Скопировать промпт'
1019
- }, copied ? '✓ Скопировано' : '📋 Промпт'))
1045
+ title: t('card.copyPromptTitle')
1046
+ }, copied ? t('card.copied') : t('card.copyPrompt')))
1020
1047
  }
1021
1048
 
1022
1049
  if (args.seed !== undefined) {
@@ -1025,8 +1052,8 @@ function FalImageCard(props) {
1025
1052
  type: 'button',
1026
1053
  className: 'fal-action-btn',
1027
1054
  onClick: onCopySeed,
1028
- title: 'Скопировать сид'
1029
- }, `🎲 Сид: ${args.seed}`))
1055
+ title: t('card.copySeedTitle')
1056
+ }, `🎲 ${t('card.seedPrefix')}: ${args.seed}`))
1030
1057
  }
1031
1058
 
1032
1059
  body.push(react.createElement('div', { key: 'action-row', className: 'fal-btn-row' }, btns))
@@ -1042,7 +1069,7 @@ function FalImageCard(props) {
1042
1069
  type: 'button',
1043
1070
  className: 'fal-repeat',
1044
1071
  onClick: repeat,
1045
- }, 'Повторить генерацию'))
1072
+ }, t('card.repeatGen')))
1046
1073
  }
1047
1074
  if (!running && !failed) {
1048
1075
  if (editing) {
@@ -1051,7 +1078,7 @@ function FalImageCard(props) {
1051
1078
  type: 'text',
1052
1079
  className: 'fal-input',
1053
1080
  value: editText,
1054
- placeholder: prompt || 'Новый промпт',
1081
+ placeholder: prompt || t('card.newPromptPlaceholder'),
1055
1082
  onChange: (e) => setEditText(e.target.value),
1056
1083
  }))
1057
1084
  body.push(react.createElement('button', {
@@ -1071,14 +1098,14 @@ function FalImageCard(props) {
1071
1098
  setEditing(false)
1072
1099
  setEditText('')
1073
1100
  },
1074
- }, 'Отправить'))
1101
+ }, t('card.send')))
1075
1102
  } else {
1076
1103
  body.push(react.createElement('button', {
1077
1104
  key: 'edit',
1078
1105
  type: 'button',
1079
1106
  className: 'fal-repeat',
1080
1107
  onClick: () => { setEditText(prompt || ''); setEditing(true) },
1081
- }, 'Изменить промпт'))
1108
+ }, t('card.editPrompt')))
1082
1109
  }
1083
1110
  }
1084
1111
  if (!running && !failed && downloadUrl) {
@@ -1087,7 +1114,7 @@ function FalImageCard(props) {
1087
1114
  className: 'fal-repeat',
1088
1115
  href: downloadUrl,
1089
1116
  download: 'generated-image.png',
1090
- }, 'Скачать'))
1117
+ }, t('card.download')))
1091
1118
  }
1092
1119
 
1093
1120
  return react.createElement('div', { className: 'fal-card' }, head, body)
package/lib/index.js CHANGED
@@ -534,7 +534,9 @@ export function apply(ctx, config) {
534
534
  },
535
535
  }), 'dsh-image-gen: history route')
536
536
 
537
- ctx.tools.register(
537
+ // Регистрация инструментов обёрнута в ctx.effect для соблюдения жизненного цикла Cordis (#136)
538
+ ctx.effect(() => {
539
+ ctx.tools.register(
538
540
  defineTool({
539
541
  name: 'generate_image',
540
542
  description:
@@ -1212,6 +1214,7 @@ export function apply(ctx, config) {
1212
1214
  }
1213
1215
  },
1214
1216
  }),
1215
- )
1217
+ )
1218
+ })
1216
1219
 
1217
1220
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-image-gen",
3
- "version": "0.10.3",
3
+ "version": "0.10.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",
@@ -59,6 +59,10 @@
59
59
  "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
60
60
  "@deepseek-ai/dsh-attachment": "^0.1.0-rc.6",
61
61
  "@deepseek-ai/dsh-credentials": "^0.1.0-rc.6",
62
+ "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
63
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
64
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
65
+ "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6",
62
66
  "@deepseek-ai/schemastery": "^3.18.1"
63
67
  },
64
68
  "scripts": {