@goodandready/dsh-image-gen 0.10.30 → 0.10.32
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/anchor-helpers.js +103 -0
- package/lib/client.js +462 -52
- package/lib/fallback-router.js +163 -0
- package/lib/index.js +27 -5
- package/lib/register-tools.js +6 -0
- package/lib/security.js +166 -0
- package/lib/style-matrix-helpers.js +85 -0
- package/lib/tools/anchor.js +129 -0
- package/lib/tools/generation.js +21 -5
- package/lib/tools/style-matrix.js +227 -0
- package/lib/tools/ui-asset.js +238 -0
- package/lib/ui-asset-helpers.js +44 -0
- package/lib/updater.js +9 -19
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -112,6 +112,12 @@ window.__ModuleLoader__.load({
|
|
|
112
112
|
.ig-alert-err{padding:10px 14px;border-radius:8px;background:var(--dsw-alias-state-error-bg, color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent));color:var(--dsw-alias-state-error-primary);font-size:13px}
|
|
113
113
|
.ig-banner-warning{padding:12px 16px;border-radius:8px;background:var(--dsw-alias-state-warning-bg, color-mix(in srgb, var(--dsw-alias-state-warning-primary) 12%, transparent));border:1px solid var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);font-size:13px;display:flex;align-items:center;gap:10px;font-weight:500}
|
|
114
114
|
|
|
115
|
+
.ig-inpaint-box{position:relative;margin-top:10px;padding:12px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);display:flex;flex-direction:column;gap:8px}
|
|
116
|
+
.ig-matrix-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(220px, 1fr));gap:10px;margin-top:8px}
|
|
117
|
+
.ig-matrix-cell{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;overflow:hidden;background:var(--dsw-alias-bg-layer-2);display:flex;flex-direction:column}
|
|
118
|
+
.ig-matrix-img{width:100%;aspect-ratio:1/1;object-fit:cover;cursor:pointer}
|
|
119
|
+
.ig-matrix-bar{display:flex;justify-content:space-between;align-items:center;padding:6px 10px;font-size:11px}
|
|
120
|
+
|
|
115
121
|
/* Backward-compatibility alias styles for toolview */
|
|
116
122
|
.fal_head{font-size:13px;font-weight:600;margin-bottom:8px;color:var(--dsw-alias-label-primary)}
|
|
117
123
|
.ig-prompt{font-size:13px;padding:8px 12px;background:var(--dsw-alias-bg-layer-2);border-radius:6px;border:1px solid var(--dsw-alias-border-l2);margin-bottom:8px;word-break:break-word}
|
|
@@ -120,7 +126,6 @@ window.__ModuleLoader__.load({
|
|
|
120
126
|
`
|
|
121
127
|
document.head.appendChild(style)
|
|
122
128
|
}
|
|
123
|
-
|
|
124
129
|
function createErrorBoundary() {
|
|
125
130
|
if (!React || typeof React.Component !== 'function') {
|
|
126
131
|
return function NoopBoundary(props) { return props?.children || null }
|
|
@@ -273,6 +278,7 @@ window.__ModuleLoader__.load({
|
|
|
273
278
|
|
|
274
279
|
// Provider: Codex / Grok
|
|
275
280
|
{ field: 'subscriptionQuality', spec: selectField('subscriptionQuality', ['', 'low', 'medium', 'high']), kind: 'select', options: ['', 'low', 'medium', 'high'], labelKey: 'f.subscriptionQuality', hintKey: 'f.subscriptionQualityHint', when: ['codex', 'grok'], tab: 'provider' },
|
|
281
|
+
{ field: 'fallbackProviders', spec: textField('fallbackProviders'), kind: 'text', labelKey: 'f.fallbackProviders', hintKey: 'f.fallbackProvidersHint', placeholderKey: 'p.fallbackProviders', tab: 'provider' },
|
|
276
282
|
|
|
277
283
|
// Enhancer & Style
|
|
278
284
|
{ field: 'autoEnhancePrompt', spec: booleanField('autoEnhancePrompt'), kind: 'select', options: ['true', 'false'], labelKey: 'f.autoEnhancePrompt', hintKey: 'f.autoEnhancePromptHint', tab: 'enhancer' },
|
|
@@ -1332,14 +1338,8 @@ window.__ModuleLoader__.load({
|
|
|
1332
1338
|
const attachment = item.attachment || null
|
|
1333
1339
|
const url = attachmentImageUrl(attachment) || item.url || ''
|
|
1334
1340
|
const rawDevice = String(item.device || '').toLowerCase()
|
|
1335
|
-
const device = ['mobile', 'tablet', 'desktop'].includes(rawDevice)
|
|
1336
|
-
|
|
1337
|
-
: `device-${index + 1}`
|
|
1338
|
-
const label = item.label
|
|
1339
|
-
|| (device === 'mobile' ? 'Mobile'
|
|
1340
|
-
: device === 'tablet' ? 'Tablet'
|
|
1341
|
-
: device === 'desktop' ? 'Desktop'
|
|
1342
|
-
: `#${index + 1}`)
|
|
1341
|
+
const device = ['mobile', 'tablet', 'desktop'].includes(rawDevice) ? rawDevice : `device-${index + 1}`
|
|
1342
|
+
const label = item.label || (device === 'mobile' ? 'Mobile' : device === 'tablet' ? 'Tablet' : device === 'desktop' ? 'Desktop' : `#${index + 1}`)
|
|
1343
1343
|
return {
|
|
1344
1344
|
device,
|
|
1345
1345
|
label,
|
|
@@ -1366,10 +1366,12 @@ window.__ModuleLoader__.load({
|
|
|
1366
1366
|
const responsiveImages = !failed && !running ? readResponsiveImages(block) : []
|
|
1367
1367
|
const t = props.t || ((k) => k)
|
|
1368
1368
|
const [remixOpen, setRemixOpen] = react.useState(false)
|
|
1369
|
+
const [inpaintOpen, setInpaintOpen] = react.useState(false)
|
|
1369
1370
|
const [remixStrength, setRemixStrength] = react.useState(0.45)
|
|
1370
1371
|
const [remixPrompt, setRemixPrompt] = react.useState('')
|
|
1371
1372
|
const [copiedRemix, setCopiedRemix] = react.useState(false)
|
|
1372
1373
|
const [responsiveTab, setResponsiveTab] = react.useState(0)
|
|
1374
|
+
const [copied, setCopied] = react.useState(false)
|
|
1373
1375
|
|
|
1374
1376
|
react.useEffect(() => {
|
|
1375
1377
|
ensureCss()
|
|
@@ -1383,9 +1385,7 @@ window.__ModuleLoader__.load({
|
|
|
1383
1385
|
args = JSON.parse(raw)
|
|
1384
1386
|
prompt = args.prompt || ''
|
|
1385
1387
|
}
|
|
1386
|
-
} catch (_) { /* malformed tool args
|
|
1387
|
-
|
|
1388
|
-
const [copied, setCopied] = react.useState(false)
|
|
1388
|
+
} catch (_) { /* malformed tool args */ }
|
|
1389
1389
|
|
|
1390
1390
|
const sendActionPrompt = async (text) => {
|
|
1391
1391
|
try {
|
|
@@ -1394,7 +1394,7 @@ window.__ModuleLoader__.load({
|
|
|
1394
1394
|
const binding = current && sessions.binding(current)
|
|
1395
1395
|
const session = binding && binding.session
|
|
1396
1396
|
if (session && session.prompt) await session.prompt([{ type: 'text', text }], 'queue')
|
|
1397
|
-
} catch (_) { /* session prompt delivery failed
|
|
1397
|
+
} catch (_) { /* session prompt delivery failed */ }
|
|
1398
1398
|
}
|
|
1399
1399
|
|
|
1400
1400
|
const onCopyPrompt = () => {
|
|
@@ -1438,12 +1438,7 @@ window.__ModuleLoader__.load({
|
|
|
1438
1438
|
body.push(
|
|
1439
1439
|
react.createElement(
|
|
1440
1440
|
'div',
|
|
1441
|
-
{
|
|
1442
|
-
className: 'ig-tabs',
|
|
1443
|
-
role: 'tablist',
|
|
1444
|
-
'aria-label': t('card.responsiveTabs') || 'Responsive viewports',
|
|
1445
|
-
key: 'rtabs',
|
|
1446
|
-
},
|
|
1441
|
+
{ className: 'ig-tabs', role: 'tablist', 'aria-label': t('card.responsiveTabs') || 'Responsive viewports', key: 'rtabs' },
|
|
1447
1442
|
responsiveImages.map((img, idx) =>
|
|
1448
1443
|
react.createElement(
|
|
1449
1444
|
'button',
|
|
@@ -1459,36 +1454,18 @@ window.__ModuleLoader__.load({
|
|
|
1459
1454
|
)
|
|
1460
1455
|
)
|
|
1461
1456
|
),
|
|
1462
|
-
active && active.url
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
: null,
|
|
1475
|
-
active
|
|
1476
|
-
? react.createElement(
|
|
1477
|
-
'div',
|
|
1478
|
-
{
|
|
1479
|
-
key: 'rmeta',
|
|
1480
|
-
style: {
|
|
1481
|
-
marginTop: '6px',
|
|
1482
|
-
fontSize: '11px',
|
|
1483
|
-
color: 'var(--dsw-alias-label-secondary)',
|
|
1484
|
-
},
|
|
1485
|
-
},
|
|
1486
|
-
[
|
|
1487
|
-
active.width && active.height ? `${active.width}×${active.height}` : null,
|
|
1488
|
-
active.path ? String(active.path) : null,
|
|
1489
|
-
].filter(Boolean).join(' · ')
|
|
1490
|
-
)
|
|
1491
|
-
: null
|
|
1457
|
+
active && active.url ? react.createElement('img', {
|
|
1458
|
+
key: 'ri',
|
|
1459
|
+
src: active.url,
|
|
1460
|
+
alt: prompt || (active.label + ' responsive mockup'),
|
|
1461
|
+
loading: 'lazy',
|
|
1462
|
+
style: { maxWidth: '100%', borderRadius: '8px', border: '1px solid var(--dsw-alias-border-l2)' },
|
|
1463
|
+
}) : null,
|
|
1464
|
+
active ? react.createElement(
|
|
1465
|
+
'div',
|
|
1466
|
+
{ key: 'rmeta', style: { marginTop: '6px', fontSize: '11px', color: 'var(--dsw-alias-label-secondary)' } },
|
|
1467
|
+
[active.width && active.height ? `${active.width}×${active.height}` : null, active.path ? String(active.path) : null].filter(Boolean).join(' · ')
|
|
1468
|
+
) : null
|
|
1492
1469
|
)
|
|
1493
1470
|
} else if (!parsed.attachment && parsed.url) {
|
|
1494
1471
|
body.push(react.createElement('img', {
|
|
@@ -1499,10 +1476,9 @@ window.__ModuleLoader__.load({
|
|
|
1499
1476
|
style: { maxWidth: '100%', borderRadius: '8px', border: '1px solid var(--dsw-alias-border-l2)' },
|
|
1500
1477
|
}))
|
|
1501
1478
|
} else if (parsed.attachment) {
|
|
1502
|
-
const a = parsed.attachment
|
|
1503
1479
|
body.push(react.createElement('img', {
|
|
1504
1480
|
key: 'i',
|
|
1505
|
-
src: attachmentImageUrl(
|
|
1481
|
+
src: attachmentImageUrl(parsed.attachment),
|
|
1506
1482
|
alt: prompt || 'generated image',
|
|
1507
1483
|
loading: 'lazy',
|
|
1508
1484
|
style: { maxWidth: '100%', borderRadius: '8px', border: '1px solid var(--dsw-alias-border-l2)' },
|
|
@@ -1512,6 +1488,7 @@ window.__ModuleLoader__.load({
|
|
|
1512
1488
|
const actions = []
|
|
1513
1489
|
if (!running && !failed && (parsed.url || parsed.attachment || responsiveImages.length)) {
|
|
1514
1490
|
actions.push(
|
|
1491
|
+
react.createElement('button', { key: 'inpaint', type: 'button', className: 'ig-btn', onClick: () => setInpaintOpen(!inpaintOpen) }, '🖌️ ' + (t('card.inpaint') || 'Inpaint')),
|
|
1515
1492
|
react.createElement('button', { key: 'remix', type: 'button', className: 'ig-btn', onClick: () => setRemixOpen(!remixOpen) }, '⚡ ' + (t('card.remix') || 'Remix')),
|
|
1516
1493
|
react.createElement('button', { key: 'reroll', type: 'button', className: 'ig-btn', onClick: onReroll }, '🎲 ' + t('card.reroll')),
|
|
1517
1494
|
react.createElement('button', { key: 'upscale', type: 'button', className: 'ig-btn', onClick: onUpscale }, '🔍 ' + t('card.upscale')),
|
|
@@ -1601,17 +1578,403 @@ window.__ModuleLoader__.load({
|
|
|
1601
1578
|
)
|
|
1602
1579
|
) : null
|
|
1603
1580
|
|
|
1581
|
+
const inpaintDrawer = inpaintOpen ? react.createElement(InpaintCanvasOverlay, {
|
|
1582
|
+
react,
|
|
1583
|
+
t,
|
|
1584
|
+
parsed,
|
|
1585
|
+
onClose: () => setInpaintOpen(false),
|
|
1586
|
+
}) : null
|
|
1587
|
+
|
|
1604
1588
|
return react.createElement(
|
|
1605
1589
|
'div',
|
|
1606
1590
|
{ className: 'ig-section-card', style: { margin: '8px 0' } },
|
|
1607
1591
|
head,
|
|
1608
1592
|
body,
|
|
1609
1593
|
actions.length > 0 ? react.createElement('div', { className: 'ig-row', style: { marginTop: '8px' } }, actions) : null,
|
|
1610
|
-
remixDrawer
|
|
1594
|
+
remixDrawer,
|
|
1595
|
+
inpaintDrawer
|
|
1611
1596
|
)
|
|
1612
1597
|
}
|
|
1613
1598
|
|
|
1614
1599
|
// -------------------------------------------------------------- Dictionaries
|
|
1600
|
+
// 105-inpaint-canvas.js — Interactive in-chat canvas drawing overlay for inpainting (#285).
|
|
1601
|
+
|
|
1602
|
+
function InpaintCanvasOverlay({ react, t, parsed, onClose }) {
|
|
1603
|
+
const canvasRef = react.useRef(null)
|
|
1604
|
+
const isDrawingRef = react.useRef(false)
|
|
1605
|
+
const [brushSize, setBrushSize] = react.useState(24)
|
|
1606
|
+
const [inpaintPrompt, setInpaintPrompt] = react.useState('')
|
|
1607
|
+
const [copied, setCopied] = react.useState(false)
|
|
1608
|
+
const [hasStrokes, setHasStrokes] = react.useState(false)
|
|
1609
|
+
const undoStackRef = react.useRef([])
|
|
1610
|
+
|
|
1611
|
+
const imgUrl = (parsed && (parsed.url || (parsed.attachment && attachmentImageUrl(parsed.attachment)))) || ''
|
|
1612
|
+
const targetRef = (parsed && (parsed.attachment?.attachmentId || parsed.path || parsed.url)) || 'current image'
|
|
1613
|
+
|
|
1614
|
+
const initCanvas = react.useCallback(() => {
|
|
1615
|
+
const canvas = canvasRef.current
|
|
1616
|
+
if (!canvas) return
|
|
1617
|
+
const ctx = canvas.getContext('2d')
|
|
1618
|
+
if (!ctx) return
|
|
1619
|
+
ctx.fillStyle = '#000000'
|
|
1620
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
|
1621
|
+
undoStackRef.current = [ctx.getImageData(0, 0, canvas.width, canvas.height)]
|
|
1622
|
+
}, [])
|
|
1623
|
+
|
|
1624
|
+
react.useEffect(() => {
|
|
1625
|
+
initCanvas()
|
|
1626
|
+
}, [initCanvas])
|
|
1627
|
+
|
|
1628
|
+
function getPos(e) {
|
|
1629
|
+
const canvas = canvasRef.current
|
|
1630
|
+
if (!canvas) return { x: 0, y: 0 }
|
|
1631
|
+
const rect = canvas.getBoundingClientRect()
|
|
1632
|
+
const clientX = e.touches ? e.touches[0].clientX : e.clientX
|
|
1633
|
+
const clientY = e.touches ? e.touches[0].clientY : e.clientY
|
|
1634
|
+
const scaleX = canvas.width / rect.width
|
|
1635
|
+
const scaleY = canvas.height / rect.height
|
|
1636
|
+
return {
|
|
1637
|
+
x: (clientX - rect.left) * scaleX,
|
|
1638
|
+
y: (clientY - rect.top) * scaleY,
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
function startDraw(e) {
|
|
1643
|
+
if (e.touches && e.touches.length > 1) return
|
|
1644
|
+
e.preventDefault()
|
|
1645
|
+
const canvas = canvasRef.current
|
|
1646
|
+
if (!canvas) return
|
|
1647
|
+
const ctx = canvas.getContext('2d')
|
|
1648
|
+
if (!ctx) return
|
|
1649
|
+
|
|
1650
|
+
isDrawingRef.current = true
|
|
1651
|
+
const pos = getPos(e)
|
|
1652
|
+
ctx.lineWidth = brushSize
|
|
1653
|
+
ctx.lineCap = 'round'
|
|
1654
|
+
ctx.lineJoin = 'round'
|
|
1655
|
+
ctx.strokeStyle = '#ffffff'
|
|
1656
|
+
ctx.fillStyle = '#ffffff'
|
|
1657
|
+
|
|
1658
|
+
ctx.beginPath()
|
|
1659
|
+
ctx.arc(pos.x, pos.y, brushSize / 2, 0, Math.PI * 2)
|
|
1660
|
+
ctx.fill()
|
|
1661
|
+
ctx.beginPath()
|
|
1662
|
+
ctx.moveTo(pos.x, pos.y)
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
function draw(e) {
|
|
1666
|
+
if (!isDrawingRef.current) return
|
|
1667
|
+
if (e.touches && e.touches.length > 1) return
|
|
1668
|
+
e.preventDefault()
|
|
1669
|
+
const canvas = canvasRef.current
|
|
1670
|
+
if (!canvas) return
|
|
1671
|
+
const ctx = canvas.getContext('2d')
|
|
1672
|
+
if (!ctx) return
|
|
1673
|
+
|
|
1674
|
+
const pos = getPos(e)
|
|
1675
|
+
ctx.lineWidth = brushSize
|
|
1676
|
+
ctx.lineCap = 'round'
|
|
1677
|
+
ctx.lineJoin = 'round'
|
|
1678
|
+
ctx.strokeStyle = '#ffffff'
|
|
1679
|
+
ctx.lineTo(pos.x, pos.y)
|
|
1680
|
+
ctx.stroke()
|
|
1681
|
+
setHasStrokes(true)
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
function stopDraw(e) {
|
|
1685
|
+
if (!isDrawingRef.current) return
|
|
1686
|
+
isDrawingRef.current = false
|
|
1687
|
+
const canvas = canvasRef.current
|
|
1688
|
+
if (!canvas) return
|
|
1689
|
+
const ctx = canvas.getContext('2d')
|
|
1690
|
+
if (!ctx) return
|
|
1691
|
+
ctx.closePath()
|
|
1692
|
+
|
|
1693
|
+
if (undoStackRef.current.length >= 15) {
|
|
1694
|
+
undoStackRef.current.shift()
|
|
1695
|
+
}
|
|
1696
|
+
undoStackRef.current.push(ctx.getImageData(0, 0, canvas.width, canvas.height))
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
function handleUndo() {
|
|
1700
|
+
const canvas = canvasRef.current
|
|
1701
|
+
if (!canvas) return
|
|
1702
|
+
const ctx = canvas.getContext('2d')
|
|
1703
|
+
if (!ctx || undoStackRef.current.length <= 1) return
|
|
1704
|
+
undoStackRef.current.pop()
|
|
1705
|
+
const prev = undoStackRef.current[undoStackRef.current.length - 1]
|
|
1706
|
+
if (prev) {
|
|
1707
|
+
ctx.putImageData(prev, 0, 0)
|
|
1708
|
+
}
|
|
1709
|
+
if (undoStackRef.current.length <= 1) {
|
|
1710
|
+
setHasStrokes(false)
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
function handleClear() {
|
|
1715
|
+
initCanvas()
|
|
1716
|
+
setHasStrokes(false)
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
function handleApply() {
|
|
1720
|
+
const canvas = canvasRef.current
|
|
1721
|
+
if (!canvas) return
|
|
1722
|
+
const maskDataUrl = canvas.toDataURL('image/png')
|
|
1723
|
+
const promptText = inpaintPrompt.trim() || 'inpaint masked region'
|
|
1724
|
+
const instruction = 'Use edit_image with prompt: "' + promptText + '", source_image: "' + targetRef + '", mask: "' + maskDataUrl + '"'
|
|
1725
|
+
|
|
1726
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
1727
|
+
navigator.clipboard.writeText(instruction)
|
|
1728
|
+
setCopied(true)
|
|
1729
|
+
setTimeout(() => setCopied(false), 2500)
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
return react.createElement(
|
|
1734
|
+
'div',
|
|
1735
|
+
{ className: 'ig-inpaint-box' },
|
|
1736
|
+
react.createElement(
|
|
1737
|
+
'div',
|
|
1738
|
+
{ style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
|
|
1739
|
+
react.createElement(
|
|
1740
|
+
'span',
|
|
1741
|
+
{ style: { fontSize: '13px', fontWeight: '700', color: 'var(--dsw-alias-label-primary)' } },
|
|
1742
|
+
'🖌️ ' + (t('inpaint.title') || 'Inpainting Canvas (Paint white mask over region to edit)')
|
|
1743
|
+
),
|
|
1744
|
+
react.createElement(
|
|
1745
|
+
'button',
|
|
1746
|
+
{
|
|
1747
|
+
type: 'button',
|
|
1748
|
+
className: 'ig-btn',
|
|
1749
|
+
style: { padding: '2px 8px', fontSize: '11px' },
|
|
1750
|
+
onClick: onClose,
|
|
1751
|
+
},
|
|
1752
|
+
'✕'
|
|
1753
|
+
)
|
|
1754
|
+
),
|
|
1755
|
+
react.createElement(
|
|
1756
|
+
'div',
|
|
1757
|
+
{ style: { display: 'flex', gap: '8px', alignItems: 'center', flexWrap: 'wrap' } },
|
|
1758
|
+
react.createElement('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)' } }, (t('inpaint.brush_size') || 'Brush') + ': ' + brushSize + 'px'),
|
|
1759
|
+
react.createElement('input', {
|
|
1760
|
+
type: 'range',
|
|
1761
|
+
min: '6',
|
|
1762
|
+
max: '64',
|
|
1763
|
+
value: brushSize,
|
|
1764
|
+
onChange: (e) => setBrushSize(parseInt(e.target.value, 10)),
|
|
1765
|
+
style: { width: '120px', cursor: 'pointer' },
|
|
1766
|
+
}),
|
|
1767
|
+
react.createElement(
|
|
1768
|
+
'button',
|
|
1769
|
+
{
|
|
1770
|
+
type: 'button',
|
|
1771
|
+
className: 'ig-btn',
|
|
1772
|
+
style: { padding: '3px 8px', fontSize: '11px' },
|
|
1773
|
+
onClick: handleUndo,
|
|
1774
|
+
disabled: !hasStrokes,
|
|
1775
|
+
},
|
|
1776
|
+
'↩ ' + (t('inpaint.undo') || 'Undo')
|
|
1777
|
+
),
|
|
1778
|
+
react.createElement(
|
|
1779
|
+
'button',
|
|
1780
|
+
{
|
|
1781
|
+
type: 'button',
|
|
1782
|
+
className: 'ig-btn',
|
|
1783
|
+
style: { padding: '3px 8px', fontSize: '11px' },
|
|
1784
|
+
onClick: handleClear,
|
|
1785
|
+
},
|
|
1786
|
+
'🗑️ ' + (t('inpaint.clear') || 'Clear Mask')
|
|
1787
|
+
)
|
|
1788
|
+
),
|
|
1789
|
+
react.createElement(
|
|
1790
|
+
'div',
|
|
1791
|
+
{
|
|
1792
|
+
style: {
|
|
1793
|
+
position: 'relative',
|
|
1794
|
+
width: '100%',
|
|
1795
|
+
maxWidth: '512px',
|
|
1796
|
+
aspectRatio: '1/1',
|
|
1797
|
+
borderRadius: '8px',
|
|
1798
|
+
overflow: 'hidden',
|
|
1799
|
+
border: '1px solid var(--dsw-alias-border-l2)',
|
|
1800
|
+
background: '#000000',
|
|
1801
|
+
margin: '4px 0',
|
|
1802
|
+
userSelect: 'none',
|
|
1803
|
+
touchAction: 'none',
|
|
1804
|
+
},
|
|
1805
|
+
},
|
|
1806
|
+
imgUrl ? react.createElement('img', {
|
|
1807
|
+
src: imgUrl,
|
|
1808
|
+
alt: 'Source for Inpaint',
|
|
1809
|
+
style: {
|
|
1810
|
+
position: 'absolute',
|
|
1811
|
+
top: 0,
|
|
1812
|
+
left: 0,
|
|
1813
|
+
width: '100%',
|
|
1814
|
+
height: '100%',
|
|
1815
|
+
objectFit: 'contain',
|
|
1816
|
+
opacity: 0.45,
|
|
1817
|
+
pointerEvents: 'none',
|
|
1818
|
+
},
|
|
1819
|
+
}) : null,
|
|
1820
|
+
react.createElement('canvas', {
|
|
1821
|
+
ref: canvasRef,
|
|
1822
|
+
width: 512,
|
|
1823
|
+
height: 512,
|
|
1824
|
+
style: {
|
|
1825
|
+
position: 'absolute',
|
|
1826
|
+
top: 0,
|
|
1827
|
+
left: 0,
|
|
1828
|
+
width: '100%',
|
|
1829
|
+
height: '100%',
|
|
1830
|
+
cursor: 'crosshair',
|
|
1831
|
+
mixBlendMode: 'screen',
|
|
1832
|
+
},
|
|
1833
|
+
onMouseDown: startDraw,
|
|
1834
|
+
onMouseMove: draw,
|
|
1835
|
+
onMouseUp: stopDraw,
|
|
1836
|
+
onMouseLeave: stopDraw,
|
|
1837
|
+
onTouchStart: startDraw,
|
|
1838
|
+
onTouchMove: draw,
|
|
1839
|
+
onTouchEnd: stopDraw,
|
|
1840
|
+
})
|
|
1841
|
+
),
|
|
1842
|
+
react.createElement('input', {
|
|
1843
|
+
type: 'text',
|
|
1844
|
+
placeholder: t('inpaint.prompt_placeholder') || 'Describe what to generate in the masked area...',
|
|
1845
|
+
value: inpaintPrompt,
|
|
1846
|
+
onChange: (e) => setInpaintPrompt(e.target.value),
|
|
1847
|
+
className: 'ig-field-input',
|
|
1848
|
+
style: { fontSize: '12px', padding: '6px 10px', width: '100%' },
|
|
1849
|
+
}),
|
|
1850
|
+
react.createElement(
|
|
1851
|
+
'div',
|
|
1852
|
+
{ style: { display: 'flex', justifyContent: 'flex-end', marginTop: '2px' } },
|
|
1853
|
+
react.createElement(
|
|
1854
|
+
'button',
|
|
1855
|
+
{
|
|
1856
|
+
type: 'button',
|
|
1857
|
+
className: 'ig-btn ig-btn-primary',
|
|
1858
|
+
style: { padding: '5px 12px', fontSize: '12px' },
|
|
1859
|
+
onClick: handleApply,
|
|
1860
|
+
},
|
|
1861
|
+
copied ? (t('inpaint.copied') || '✓ Instruction Copied!') : ('🖌️ ' + (t('inpaint.apply') || 'Apply Inpainting'))
|
|
1862
|
+
)
|
|
1863
|
+
)
|
|
1864
|
+
)
|
|
1865
|
+
}
|
|
1866
|
+
// 106-style-matrix-view.js — 2×2 Style Matrix toolview with Blind A/B Compare (#286).
|
|
1867
|
+
|
|
1868
|
+
function StyleMatrixCard(props) {
|
|
1869
|
+
const block = props.block || {}
|
|
1870
|
+
const parsed = react.useMemo(() => {
|
|
1871
|
+
const raw = block.output || block.text || ''
|
|
1872
|
+
const obj = tryParseJsonObject(raw)
|
|
1873
|
+
return obj || { base_prompt: '', blind_mode: false, cells: [] }
|
|
1874
|
+
}, [block])
|
|
1875
|
+
|
|
1876
|
+
const [blind, setBlind] = react.useState(Boolean(parsed.blind_mode))
|
|
1877
|
+
const [copiedId, setCopiedId] = react.useState(null)
|
|
1878
|
+
const [previewImg, setPreviewImg] = react.useState(null)
|
|
1879
|
+
|
|
1880
|
+
const cells = parsed.cells || []
|
|
1881
|
+
|
|
1882
|
+
function handleSelectStyle(c) {
|
|
1883
|
+
const styleName = c.style || 'chosen_style'
|
|
1884
|
+
const instruction = 'Set style preset to "' + styleName + '" for subsequent image generations.'
|
|
1885
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
1886
|
+
navigator.clipboard.writeText(instruction)
|
|
1887
|
+
setCopiedId(c.id)
|
|
1888
|
+
setTimeout(() => setCopiedId(null), 2500)
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
const cellNodes = cells.map((c) => {
|
|
1893
|
+
const imgUrl = c.url || (c.attachment && attachmentImageUrl(c.attachment)) || ''
|
|
1894
|
+
const styleLabel = blind ? ('Option ' + c.id) : c.style
|
|
1895
|
+
const isCopied = copiedId === c.id
|
|
1896
|
+
|
|
1897
|
+
return react.createElement(
|
|
1898
|
+
'div',
|
|
1899
|
+
{ key: c.id, className: 'ig-matrix-cell' },
|
|
1900
|
+
imgUrl ? react.createElement('img', {
|
|
1901
|
+
src: imgUrl,
|
|
1902
|
+
alt: styleLabel,
|
|
1903
|
+
className: 'ig-matrix-img',
|
|
1904
|
+
onClick: () => setPreviewImg(imgUrl),
|
|
1905
|
+
}) : react.createElement('div', { style: { height: '140px', background: 'var(--dsw-alias-bg-layer-2)' } }),
|
|
1906
|
+
react.createElement(
|
|
1907
|
+
'div',
|
|
1908
|
+
{ className: 'ig-matrix-bar' },
|
|
1909
|
+
react.createElement(
|
|
1910
|
+
'span',
|
|
1911
|
+
{ style: { fontWeight: '600', color: 'var(--dsw-alias-label-primary)' } },
|
|
1912
|
+
'[' + c.id + '] ' + styleLabel
|
|
1913
|
+
),
|
|
1914
|
+
react.createElement(
|
|
1915
|
+
'button',
|
|
1916
|
+
{
|
|
1917
|
+
type: 'button',
|
|
1918
|
+
className: 'ig-btn',
|
|
1919
|
+
style: { padding: '2px 8px', fontSize: '11px' },
|
|
1920
|
+
onClick: () => handleSelectStyle(c),
|
|
1921
|
+
},
|
|
1922
|
+
isCopied ? (t('matrix.copied') || '✓ Selected!') : ('⭐ ' + (t('matrix.use_style') || 'Use Style'))
|
|
1923
|
+
)
|
|
1924
|
+
)
|
|
1925
|
+
)
|
|
1926
|
+
})
|
|
1927
|
+
|
|
1928
|
+
return react.createElement(
|
|
1929
|
+
'div',
|
|
1930
|
+
{ className: 'ig-section-card', style: { margin: '8px 0' } },
|
|
1931
|
+
react.createElement(
|
|
1932
|
+
'div',
|
|
1933
|
+
{ style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
|
|
1934
|
+
react.createElement(
|
|
1935
|
+
'span',
|
|
1936
|
+
{ className: 'ig-section-title' },
|
|
1937
|
+
'⚡ ' + (t('matrix.title') || 'Style Matrix Benchmark (2×2)')
|
|
1938
|
+
),
|
|
1939
|
+
react.createElement(
|
|
1940
|
+
'button',
|
|
1941
|
+
{
|
|
1942
|
+
type: 'button',
|
|
1943
|
+
className: 'ig-tab-btn',
|
|
1944
|
+
style: { padding: '4px 10px', fontSize: '12px' },
|
|
1945
|
+
onClick: () => setBlind(!blind),
|
|
1946
|
+
},
|
|
1947
|
+
blind ? ('🙈 ' + (t('matrix.blind_toggle') || 'Reveal Styles')) : '👁️ Mask Styles (Blind)'
|
|
1948
|
+
)
|
|
1949
|
+
),
|
|
1950
|
+
parsed.base_prompt ? react.createElement('div', { className: 'ig-prompt' }, parsed.base_prompt) : null,
|
|
1951
|
+
react.createElement('div', { className: 'ig-matrix-grid' }, cellNodes),
|
|
1952
|
+
previewImg ? react.createElement(
|
|
1953
|
+
'div',
|
|
1954
|
+
{
|
|
1955
|
+
style: {
|
|
1956
|
+
position: 'fixed',
|
|
1957
|
+
top: 0,
|
|
1958
|
+
left: 0,
|
|
1959
|
+
right: 0,
|
|
1960
|
+
bottom: 0,
|
|
1961
|
+
background: 'rgba(0,0,0,0.85)',
|
|
1962
|
+
display: 'flex',
|
|
1963
|
+
alignItems: 'center',
|
|
1964
|
+
justifyContent: 'center',
|
|
1965
|
+
zIndex: 99999,
|
|
1966
|
+
cursor: 'zoom-out',
|
|
1967
|
+
},
|
|
1968
|
+
onClick: () => setPreviewImg(null),
|
|
1969
|
+
},
|
|
1970
|
+
react.createElement('img', {
|
|
1971
|
+
src: previewImg,
|
|
1972
|
+
alt: 'Enlarged Preview',
|
|
1973
|
+
style: { maxWidth: '90vw', maxHeight: '90vh', borderRadius: '8px' },
|
|
1974
|
+
})
|
|
1975
|
+
) : null
|
|
1976
|
+
)
|
|
1977
|
+
}
|
|
1615
1978
|
const en = {
|
|
1616
1979
|
'settings.navLabel': 'Image Studio',
|
|
1617
1980
|
'settings.title': 'Image Studio',
|
|
@@ -1650,6 +2013,23 @@ window.__ModuleLoader__.load({
|
|
|
1650
2013
|
'card.actionReroll': 'Reroll image',
|
|
1651
2014
|
'card.actionUpscale': 'Upscale image',
|
|
1652
2015
|
'card.actionRemoveBg': 'Remove background',
|
|
2016
|
+
'card.inpaint': 'Inpaint',
|
|
2017
|
+
'inpaint.title': 'Inpainting Canvas (Paint white mask over region to edit)',
|
|
2018
|
+
'inpaint.brush_size': 'Brush',
|
|
2019
|
+
'inpaint.clear': 'Clear Mask',
|
|
2020
|
+
'inpaint.undo': 'Undo',
|
|
2021
|
+
'inpaint.apply': 'Apply Inpaint',
|
|
2022
|
+
'inpaint.prompt_placeholder': 'Describe what to generate in the masked area...',
|
|
2023
|
+
'inpaint.copied': '✓ Instruction Copied!',
|
|
2024
|
+
'matrix.title': 'Style Matrix Benchmark (2×2)',
|
|
2025
|
+
'matrix.blind_on': 'Blind Mode Active',
|
|
2026
|
+
'matrix.blind_toggle': 'Reveal Styles',
|
|
2027
|
+
'matrix.use_style': 'Use Style',
|
|
2028
|
+
'matrix.copied': '✓ Selected!',
|
|
2029
|
+
'f.fallbackProviders': 'Fallback Providers Chain',
|
|
2030
|
+
'f.fallbackProvidersHint': 'Comma-separated list of fallback providers to cascade to on 429, 5xx, or quota limits (e.g. replicate, custom, gemini).',
|
|
2031
|
+
'p.fallbackProviders': 'replicate, custom, gemini',
|
|
2032
|
+
|
|
1653
2033
|
'card.reroll': 'Reroll',
|
|
1654
2034
|
'card.upscale': 'Upscale 2x/4x',
|
|
1655
2035
|
'card.removeBg': 'Remove BG',
|
|
@@ -1854,6 +2234,23 @@ window.__ModuleLoader__.load({
|
|
|
1854
2234
|
'card.actionReroll': '重新生成图像',
|
|
1855
2235
|
'card.actionUpscale': '放大图像',
|
|
1856
2236
|
'card.actionRemoveBg': '移除背景',
|
|
2237
|
+
'card.inpaint': '局部重绘',
|
|
2238
|
+
'inpaint.title': '画布局部重绘(涂抹白色蒙版标记区域)',
|
|
2239
|
+
'inpaint.brush_size': '画笔大小',
|
|
2240
|
+
'inpaint.clear': '清空蒙版',
|
|
2241
|
+
'inpaint.undo': '撤销',
|
|
2242
|
+
'inpaint.apply': '生成局部重绘',
|
|
2243
|
+
'inpaint.prompt_placeholder': '描述在涂抹区域生成的内容...',
|
|
2244
|
+
'inpaint.copied': '✓ 已复制重绘指令!',
|
|
2245
|
+
'matrix.title': '2×2 风格探索矩阵',
|
|
2246
|
+
'matrix.blind_on': '盲测模式进行中',
|
|
2247
|
+
'matrix.blind_toggle': '显示风格标签',
|
|
2248
|
+
'matrix.use_style': '使用此风格',
|
|
2249
|
+
'matrix.copied': '✓ 风格已选定!',
|
|
2250
|
+
'f.fallbackProviders': '备用提供商级联',
|
|
2251
|
+
'f.fallbackProvidersHint': '当主提供商遇到 429 限流、5xx 故障或余额不足时依次自动切换的备用服务商列表(如 replicate, custom, gemini)。',
|
|
2252
|
+
'p.fallbackProviders': 'replicate, custom, gemini',
|
|
2253
|
+
|
|
1857
2254
|
'card.reroll': '重绘',
|
|
1858
2255
|
'card.upscale': '放大 2x/4x',
|
|
1859
2256
|
'card.removeBg': '抠图背景',
|
|
@@ -2078,6 +2475,7 @@ window.__ModuleLoader__.load({
|
|
|
2078
2475
|
'assemble_image_grid',
|
|
2079
2476
|
'smart_crop_image',
|
|
2080
2477
|
'export_asset_pack',
|
|
2478
|
+
'generate_ui_asset',
|
|
2081
2479
|
]
|
|
2082
2480
|
|
|
2083
2481
|
for (const toolName of toolviewTools) {
|
|
@@ -2094,6 +2492,18 @@ window.__ModuleLoader__.load({
|
|
|
2094
2492
|
)
|
|
2095
2493
|
}
|
|
2096
2494
|
|
|
2495
|
+
registerSlotWhenReady('tool.call.toolview', () =>
|
|
2496
|
+
ctx.slots.register(
|
|
2497
|
+
{
|
|
2498
|
+
name: 'tool.call.toolview',
|
|
2499
|
+
key: 'generate_style_matrix',
|
|
2500
|
+
locale: NS,
|
|
2501
|
+
inject: () => ({ sessions: ctx.sessions }),
|
|
2502
|
+
},
|
|
2503
|
+
(props) => react.createElement(ErrorBoundary, null, react.createElement(StyleMatrixCard, props))
|
|
2504
|
+
)
|
|
2505
|
+
)
|
|
2506
|
+
|
|
2097
2507
|
|
|
2098
2508
|
// Native Sidebar Right Pane Tab & BetterSidebar
|
|
2099
2509
|
if (typeof ctx.inject === 'function') {
|