@goodandready/dsh-image-gen 0.10.32 → 0.10.34

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
@@ -123,6 +123,11 @@
123
123
  ## 🛠️ Complete Tools Reference
124
124
 
125
125
  * **`generate_image`**: Generate images with pluggable providers, seeds, aspect ratios, and style presets.
126
+ * **`set_style_anchor` (#283)**: Lock session visual identity (subject, outfit, medium, color palette, lighting) for consistent multi-turn generation.
127
+ * **`generate_ui_asset` (#284)**: Layout-aware UI asset generator for icons, stickers, badges, spot graphics, and heroes with negative space constraints (`isolated`, `centered`, `left_empty`, `right_empty`, `top_empty`, `bottom_empty`) and auto-transparency.
128
+ * **`generate_style_matrix` (#286)**: 2x2 visual benchmarking matrix exploring 4 aesthetic presets simultaneously with blind compare mode and 1-click style preset adoption.
129
+ * **`In-Chat Inpainting Canvas` (#285)**: Interactive brush overlay and mask drawer mounted directly over image message cards in chat.
130
+ * **`Smart Provider Fallback Chain` (#282)**: Intelligent cascading failover across providers on 429, 5xx, timeouts, or depleted balance.
126
131
  * **`edit_image`**: Targeted inpainting and modification with automatic session reference resolution (#142, #144, #145).
127
132
  * **`vary_image`**: Controlled variation generation preserving composition (#143, #144).
128
133
  * **`remove_background`**: Extract subject with transparent PNG output (FAL BiRefNet / Rembg).
package/README.ru.md CHANGED
@@ -163,6 +163,9 @@ graph LR
163
163
  | Инструмент | Назначение | Ключевые параметры |
164
164
  |---|---|---|
165
165
  | **`generate_image`** | Генерация изображения по тексту | `prompt`, `image_size`, `seed`, `style`, `negative_prompt`, `count` |
166
+ | **`set_style_anchor`** | Фиксация визуального стиля и персонажа сессии (объект, костюм, палитра, свет) (#283) | `mode` (`character`/`style`), `image`, `description`, `strength`, `clear` |
167
+ | **`generate_ui_asset`** | Генерация интерфейсных ассетов с контролем свободного пространства и авто-прозрачностью (#284) | `prompt`, `asset_type`, `layout_composition`, `transparent`, `output_format` |
168
+ | **`generate_style_matrix`** | Сетка 2x2 для одновременного бенчмаркинга концепта в 4 стилях со слепым сравнением (#286) | `prompt`, `styles`, `blind_mode`, `output_format` |
166
169
  | **`edit_image`** | Направленное редактирование / inpainting с авто-поиском оригинала (#142, #144, #145) | `prompt`, `image` (по умолч. `latest`), `mask`, `strength` |
167
170
  | **`vary_image`** | Генерация вариаций существующей картинки (#143, #144) | `image` (по умолч. `latest`), `prompt`, `variation_strength` (0.1–0.9), `count` |
168
171
  | **`remove_background`** | Удаление фона с сохранением прозрачного PNG | `image`, `model`, `output_name` |
package/README.zh.md CHANGED
@@ -113,4 +113,12 @@ dsh plugin --profile web add @goodandready/dsh-image-gen
113
113
 
114
114
  ## 📄 许可证
115
115
 
116
- MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
116
+ MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
117
+
118
+ ## 🛠️ 新增核心功能 (v0.10.32)
119
+
120
+ * **`set_style_anchor` (#283)**: 会话级角色与风格参考锚点,在连续对话中锁定主体、配色、光照与渲染媒介。
121
+ * **`generate_ui_asset` (#284)**: 布局感知与留白控制 UI 资产生成器,支持精准留白(`isolated`、`centered`、`left_empty`、`right_empty`、`top_empty`、`bottom_empty`)与自动透明通道。
122
+ * **`generate_style_matrix` (#286)**: 2x2 风格矩阵探索与双盲对比,并发生成 4 种美学预设切片并支持一键应用。
123
+ * **`对话内局部重绘画布` (#285)**: 在消息卡片上直接唤起交互式笔刷画布,支持选区涂抹与即时 Inpainting 局部重绘。
124
+ * **`智能服务商自动故障转移链` (#282)**: 主服务商 429、5xx、超时或额度耗尽时自动无缝切换备选渠道。
@@ -80,3 +80,12 @@ export async function saveAndAttachResult(ctx, exec, cfg, {
80
80
  },
81
81
  })
82
82
  }
83
+
84
+ export function renderToolOutput(value) {
85
+ const text = (value && value.summary) || (typeof value === "string" ? value : JSON.stringify(value || ""))
86
+ const blocks = [{ type: "text", text }]
87
+ if (value && value.attachment) {
88
+ blocks.push({ type: "image", attachment: value.attachment })
89
+ }
90
+ return blocks
91
+ }
package/lib/client.js CHANGED
@@ -100,6 +100,7 @@ window.__ModuleLoader__.load({
100
100
  .ig-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-2));border-color:var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2))}
101
101
  .ig-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}
102
102
  .ig-btn-primary:hover:not(:disabled){opacity:0.9}
103
+ .ig-btn-active{background:var(--dsw-alias-brand-primary, #3b82f6);color:#ffffff;border-color:transparent}
103
104
  .ig-btn-danger{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}
104
105
  .ig-btn-danger:hover:not(:disabled){background:var(--dsw-alias-state-error-bg, color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent));border-color:var(--dsw-alias-state-error-primary)}
105
106
  .ig-btn-disabled{opacity:0.5;cursor:not-allowed}
@@ -1603,6 +1604,7 @@ window.__ModuleLoader__.load({
1603
1604
  const canvasRef = react.useRef(null)
1604
1605
  const isDrawingRef = react.useRef(false)
1605
1606
  const [brushSize, setBrushSize] = react.useState(24)
1607
+ const [toolMode, setToolMode] = react.useState('brush')
1606
1608
  const [inpaintPrompt, setInpaintPrompt] = react.useState('')
1607
1609
  const [copied, setCopied] = react.useState(false)
1608
1610
  const [hasStrokes, setHasStrokes] = react.useState(false)
@@ -1652,8 +1654,9 @@ window.__ModuleLoader__.load({
1652
1654
  ctx.lineWidth = brushSize
1653
1655
  ctx.lineCap = 'round'
1654
1656
  ctx.lineJoin = 'round'
1655
- ctx.strokeStyle = '#ffffff'
1656
- ctx.fillStyle = '#ffffff'
1657
+ const color = toolMode === 'eraser' ? '#000000' : '#ffffff'
1658
+ ctx.strokeStyle = color
1659
+ ctx.fillStyle = color
1657
1660
 
1658
1661
  ctx.beginPath()
1659
1662
  ctx.arc(pos.x, pos.y, brushSize / 2, 0, Math.PI * 2)
@@ -1675,7 +1678,8 @@ window.__ModuleLoader__.load({
1675
1678
  ctx.lineWidth = brushSize
1676
1679
  ctx.lineCap = 'round'
1677
1680
  ctx.lineJoin = 'round'
1678
- ctx.strokeStyle = '#ffffff'
1681
+ const color = toolMode === 'eraser' ? '#000000' : '#ffffff'
1682
+ ctx.strokeStyle = color
1679
1683
  ctx.lineTo(pos.x, pos.y)
1680
1684
  ctx.stroke()
1681
1685
  setHasStrokes(true)
@@ -1711,6 +1715,27 @@ window.__ModuleLoader__.load({
1711
1715
  }
1712
1716
  }
1713
1717
 
1718
+ function handleInvert() {
1719
+ const canvas = canvasRef.current
1720
+ if (!canvas) return
1721
+ const ctx = canvas.getContext('2d')
1722
+ if (!ctx) return
1723
+ const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height)
1724
+ const d = imgData.data
1725
+ for (let i = 0; i < d.length; i += 4) {
1726
+ d[i] = 255 - d[i]
1727
+ d[i + 1] = 255 - d[i + 1]
1728
+ d[i + 2] = 255 - d[i + 2]
1729
+ d[i + 3] = 255
1730
+ }
1731
+ ctx.putImageData(imgData, 0, 0)
1732
+ if (undoStackRef.current.length >= 15) {
1733
+ undoStackRef.current.shift()
1734
+ }
1735
+ undoStackRef.current.push(ctx.getImageData(0, 0, canvas.width, canvas.height))
1736
+ setHasStrokes(true)
1737
+ }
1738
+
1714
1739
  function handleClear() {
1715
1740
  initCanvas()
1716
1741
  setHasStrokes(false)
@@ -1762,8 +1787,38 @@ window.__ModuleLoader__.load({
1762
1787
  max: '64',
1763
1788
  value: brushSize,
1764
1789
  onChange: (e) => setBrushSize(parseInt(e.target.value, 10)),
1765
- style: { width: '120px', cursor: 'pointer' },
1790
+ style: { width: '100px', cursor: 'pointer' },
1766
1791
  }),
1792
+ react.createElement(
1793
+ 'button',
1794
+ {
1795
+ type: 'button',
1796
+ className: 'ig-btn' + (toolMode === 'brush' ? ' ig-btn-active' : ''),
1797
+ style: { padding: '3px 8px', fontSize: '11px' },
1798
+ onClick: () => setToolMode('brush'),
1799
+ },
1800
+ '🖌️ ' + (t('inpaint.brush') || 'Brush')
1801
+ ),
1802
+ react.createElement(
1803
+ 'button',
1804
+ {
1805
+ type: 'button',
1806
+ className: 'ig-btn' + (toolMode === 'eraser' ? ' ig-btn-active' : ''),
1807
+ style: { padding: '3px 8px', fontSize: '11px' },
1808
+ onClick: () => setToolMode('eraser'),
1809
+ },
1810
+ '🧹 ' + (t('inpaint.eraser') || 'Eraser')
1811
+ ),
1812
+ react.createElement(
1813
+ 'button',
1814
+ {
1815
+ type: 'button',
1816
+ className: 'ig-btn',
1817
+ style: { padding: '3px 8px', fontSize: '11px' },
1818
+ onClick: handleInvert,
1819
+ },
1820
+ '🔄 ' + (t('inpaint.invert') || 'Invert')
1821
+ ),
1767
1822
  react.createElement(
1768
1823
  'button',
1769
1824
  {
@@ -2015,7 +2070,10 @@ window.__ModuleLoader__.load({
2015
2070
  'card.actionRemoveBg': 'Remove background',
2016
2071
  'card.inpaint': 'Inpaint',
2017
2072
  'inpaint.title': 'Inpainting Canvas (Paint white mask over region to edit)',
2018
- 'inpaint.brush_size': 'Brush',
2073
+ 'inpaint.brush_size': 'Size',
2074
+ 'inpaint.brush': 'Brush',
2075
+ 'inpaint.eraser': 'Eraser',
2076
+ 'inpaint.invert': 'Invert Mask',
2019
2077
  'inpaint.clear': 'Clear Mask',
2020
2078
  'inpaint.undo': 'Undo',
2021
2079
  'inpaint.apply': 'Apply Inpaint',
@@ -2236,7 +2294,10 @@ window.__ModuleLoader__.load({
2236
2294
  'card.actionRemoveBg': '移除背景',
2237
2295
  'card.inpaint': '局部重绘',
2238
2296
  'inpaint.title': '画布局部重绘(涂抹白色蒙版标记区域)',
2239
- 'inpaint.brush_size': '画笔大小',
2297
+ 'inpaint.brush_size': '大小',
2298
+ 'inpaint.brush': '画笔',
2299
+ 'inpaint.eraser': '橡皮擦',
2300
+ 'inpaint.invert': '反转蒙版',
2240
2301
  'inpaint.clear': '清空蒙版',
2241
2302
  'inpaint.undo': '撤销',
2242
2303
  'inpaint.apply': '生成局部重绘',
@@ -1,3 +1,4 @@
1
+ import { renderToolOutput } from "../attachment-helper.js"
1
2
  // editing — image-gen tools (edit_image, vary_image). Extracted from apply() (#216).
2
3
 
3
4
  import { defineTool } from '@deepseek-ai/dsh-tools'
@@ -98,7 +99,8 @@ export function registerEditingTools(ctx, deps) {
98
99
  },
99
100
  },
100
101
  output: {
101
- schema: {
102
+ render: (_args, value) => renderToolOutput(value),
103
+ schema: {
102
104
  type: 'object',
103
105
  additionalProperties: true,
104
106
  properties: {
@@ -231,7 +233,8 @@ export function registerEditingTools(ctx, deps) {
231
233
  },
232
234
  },
233
235
  output: {
234
- schema: {
236
+ render: (_args, value) => renderToolOutput(value),
237
+ schema: {
235
238
  type: 'object',
236
239
  additionalProperties: true,
237
240
  properties: {
@@ -370,7 +373,8 @@ export function registerEditingTools(ctx, deps) {
370
373
  },
371
374
  },
372
375
  output: {
373
- schema: {
376
+ render: (_args, value) => renderToolOutput(value),
377
+ schema: {
374
378
  type: 'object',
375
379
  additionalProperties: true,
376
380
  properties: {
@@ -1,3 +1,4 @@
1
+ import { renderToolOutput } from "../attachment-helper.js"
1
2
  // frontend — image-gen tools (extract_design_tokens, image_to_css_gradient, check_image_contrast, optimize_vector_svg, generate_pwa_icon_suite). Extracted from apply() (#216).
2
3
 
3
4
  import { defineTool } from '@deepseek-ai/dsh-tools'
@@ -57,7 +58,8 @@ export function registerFrontendTools(ctx, deps) {
57
58
  prefix: { type: 'string', description: 'Token naming prefix (default: color).' },
58
59
  },
59
60
  output: {
60
- schema: {
61
+ render: (_args, value) => renderToolOutput(value),
62
+ schema: {
61
63
  type: 'object',
62
64
  additionalProperties: true,
63
65
  properties: {
@@ -95,7 +97,8 @@ export function registerFrontendTools(ctx, deps) {
95
97
  type: { type: 'string', enum: ['mesh', 'linear', 'radial'], description: 'Gradient type: mesh, linear, or radial (default: mesh).' },
96
98
  },
97
99
  output: {
98
- schema: {
100
+ render: (_args, value) => renderToolOutput(value),
101
+ schema: {
99
102
  type: 'object',
100
103
  additionalProperties: true,
101
104
  properties: {
@@ -134,7 +137,8 @@ export function registerFrontendTools(ctx, deps) {
134
137
  text_color: { type: 'string', description: 'Text hex color to test against (default: #ffffff).' },
135
138
  },
136
139
  output: {
137
- schema: {
140
+ render: (_args, value) => renderToolOutput(value),
141
+ schema: {
138
142
  type: 'object',
139
143
  additionalProperties: true,
140
144
  properties: {
@@ -176,7 +180,8 @@ export function registerFrontendTools(ctx, deps) {
176
180
  component_name: { type: 'string', description: 'React component name for TSX export (default: VectorIcon).' },
177
181
  },
178
182
  output: {
179
- schema: {
183
+ render: (_args, value) => renderToolOutput(value),
184
+ schema: {
180
185
  type: 'object',
181
186
  additionalProperties: true,
182
187
  properties: {
@@ -220,7 +225,8 @@ export function registerFrontendTools(ctx, deps) {
220
225
  icons_dir: { type: 'string', description: 'Directory for icons relative to webroot (default: icons).' },
221
226
  },
222
227
  output: {
223
- schema: {
228
+ render: (_args, value) => renderToolOutput(value),
229
+ schema: {
224
230
  type: 'object',
225
231
  additionalProperties: true,
226
232
  properties: {
@@ -1,3 +1,4 @@
1
+ import { renderToolOutput } from "../attachment-helper.js"
1
2
  import { defineTool } from '@deepseek-ai/dsh-tools'
2
3
  import { toLosslessJson } from '../providers.js'
3
4
  import { slugify } from '../index.js'
@@ -16,7 +17,8 @@ export function registerGenerationPackTool(ctx, deps) {
16
17
  output_name: { type: 'string', description: 'Base file name for the pack.' },
17
18
  },
18
19
  output: {
19
- schema: {
20
+ render: (_args, value) => renderToolOutput(value),
21
+ schema: {
20
22
  type: 'object',
21
23
  additionalProperties: true,
22
24
  properties: {
@@ -1,3 +1,4 @@
1
+ import { renderToolOutput } from "../attachment-helper.js"
1
2
  // inspect — image-gen tools (compare_images, inspect_image_quality). Extracted from apply() (#216).
2
3
 
3
4
  import { defineTool } from '@deepseek-ai/dsh-tools'
@@ -57,7 +58,8 @@ export function registerInspectTools(ctx, deps) {
57
58
  image_b: { type: 'string', required: true, description: 'Path or attachment id of the second image.' },
58
59
  },
59
60
  output: {
60
- schema: {
61
+ render: (_args, value) => renderToolOutput(value),
62
+ schema: {
61
63
  type: 'object',
62
64
  additionalProperties: false,
63
65
  properties: {
@@ -84,7 +86,8 @@ export function registerInspectTools(ctx, deps) {
84
86
  expected_elements: { type: 'string', description: 'What elements should be present and verified.' },
85
87
  },
86
88
  output: {
87
- schema: {
89
+ render: (_args, value) => renderToolOutput(value),
90
+ schema: {
88
91
  type: 'object',
89
92
  additionalProperties: false,
90
93
  properties: {
@@ -1,3 +1,4 @@
1
+ import { renderToolOutput } from "../attachment-helper.js"
1
2
  // pattern — generate_seamless_pattern tool (#178)
2
3
 
3
4
  import { defineTool } from '@deepseek-ai/dsh-tools'
@@ -46,7 +47,8 @@ export function registerPatternTools(ctx, deps) {
46
47
  },
47
48
  },
48
49
  output: {
49
- schema: {
50
+ render: (_args, value) => renderToolOutput(value),
51
+ schema: {
50
52
  type: 'object',
51
53
  additionalProperties: true,
52
54
  properties: {
@@ -1,3 +1,4 @@
1
+ import { renderToolOutput } from "../attachment-helper.js"
1
2
  // processing-basic — core image-gen tools (remove_background, upscale_image, vectorize_image, blend_images). Split from processing.js (#239).
2
3
 
3
4
  // processing — image-gen tools (remove_background, upscale_image, vectorize_image, blend_images). Extracted from apply() (#216).
@@ -80,7 +81,8 @@ export function registerProcessingAdvancedTools(ctx, deps) {
80
81
  },
81
82
  },
82
83
  output: {
83
- schema: {
84
+ render: (_args, value) => renderToolOutput(value),
85
+ schema: {
84
86
  type: 'object',
85
87
  additionalProperties: true,
86
88
  properties: {
@@ -254,7 +256,8 @@ export function registerProcessingAdvancedTools(ctx, deps) {
254
256
  },
255
257
  },
256
258
  output: {
257
- schema: {
259
+ render: (_args, value) => renderToolOutput(value),
260
+ schema: {
258
261
  type: 'object',
259
262
  additionalProperties: true,
260
263
  properties: {
@@ -420,7 +423,8 @@ export function registerProcessingAdvancedTools(ctx, deps) {
420
423
  },
421
424
  },
422
425
  output: {
423
- schema: {
426
+ render: (_args, value) => renderToolOutput(value),
427
+ schema: {
424
428
  type: 'object',
425
429
  additionalProperties: true,
426
430
  properties: {
@@ -1,3 +1,4 @@
1
+ import { renderToolOutput } from "../attachment-helper.js"
1
2
  // processing-basic — core image-gen tools (remove_background, upscale_image, vectorize_image, blend_images). Split from processing.js (#239).
2
3
 
3
4
  // processing — image-gen tools (remove_background, upscale_image, vectorize_image, blend_images). Extracted from apply() (#216).
@@ -59,7 +60,8 @@ export function registerProcessingTools(ctx, deps) {
59
60
  output_name: { type: 'string', description: 'Custom output file name without extension.' },
60
61
  },
61
62
  output: {
62
- schema: {
63
+ render: (_args, value) => renderToolOutput(value),
64
+ schema: {
63
65
  type: 'object',
64
66
  additionalProperties: true,
65
67
  properties: {
@@ -127,7 +129,8 @@ export function registerProcessingTools(ctx, deps) {
127
129
  output_name: { type: 'string', description: 'Custom output file name.' },
128
130
  },
129
131
  output: {
130
- schema: {
132
+ render: (_args, value) => renderToolOutput(value),
133
+ schema: {
131
134
  type: 'object',
132
135
  additionalProperties: true,
133
136
  properties: {
@@ -193,7 +196,8 @@ export function registerProcessingTools(ctx, deps) {
193
196
  output_name: { type: 'string', description: 'Custom output file name.' },
194
197
  },
195
198
  output: {
196
- schema: {
199
+ render: (_args, value) => renderToolOutput(value),
200
+ schema: {
197
201
  type: 'object',
198
202
  additionalProperties: true,
199
203
  properties: {
@@ -256,7 +260,8 @@ export function registerProcessingTools(ctx, deps) {
256
260
  output_name: { type: 'string', description: 'Custom output file name.' },
257
261
  },
258
262
  output: {
259
- schema: {
263
+ render: (_args, value) => renderToolOutput(value),
264
+ schema: {
260
265
  type: 'object',
261
266
  additionalProperties: true,
262
267
  properties: {
@@ -1,3 +1,4 @@
1
+ import { renderToolOutput } from "../attachment-helper.js"
1
2
  // responsive — generate_responsive_mockups tool (#173)
2
3
 
3
4
  import { defineTool } from '@deepseek-ai/dsh-tools'
@@ -82,7 +83,8 @@ export function registerResponsiveTools(ctx, deps) {
82
83
  },
83
84
  },
84
85
  output: {
85
- schema: {
86
+ render: (_args, value) => renderToolOutput(value),
87
+ schema: {
86
88
  type: 'object',
87
89
  additionalProperties: true,
88
90
  properties: {
@@ -1,3 +1,4 @@
1
+ import { renderToolOutput } from "../attachment-helper.js"
1
2
  // sketch — sketch_to_image tool (#146)
2
3
 
3
4
  import { defineTool } from '@deepseek-ai/dsh-tools'
@@ -63,7 +64,8 @@ export function registerSketchTools(ctx, deps) {
63
64
  },
64
65
  },
65
66
  output: {
66
- schema: {
67
+ render: (_args, value) => renderToolOutput(value),
68
+ schema: {
67
69
  type: 'object',
68
70
  additionalProperties: true,
69
71
  properties: {
@@ -1,3 +1,4 @@
1
+ import { renderToolOutput } from "../attachment-helper.js"
1
2
  // spritesheet — generate_spritesheet tool (#177)
2
3
 
3
4
  import { defineTool } from '@deepseek-ai/dsh-tools'
@@ -53,7 +54,8 @@ export function registerSpritesheetTools(ctx, deps) {
53
54
  },
54
55
  },
55
56
  output: {
56
- schema: {
57
+ render: (_args, value) => renderToolOutput(value),
58
+ schema: {
57
59
  type: 'object',
58
60
  additionalProperties: true,
59
61
  properties: {
@@ -94,7 +94,7 @@ export function registerStyleMatrixTools(ctx, deps) {
94
94
  seed: { type: 'integer' },
95
95
  width: { type: 'integer' },
96
96
  height: { type: 'integer' },
97
- attachment: { type: 'object' },
97
+ attachment: { type: 'object', additionalProperties: true },
98
98
  },
99
99
  },
100
100
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-image-gen",
3
- "version": "0.10.32",
3
+ "version": "0.10.34",
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",