@goodandready/dsh-image-gen 0.10.34 → 0.11.0
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 +1020 -39
- package/lib/comfy-workflow-helpers.js +161 -0
- package/lib/index.js +4 -0
- package/lib/providers/backends/local.js +33 -11
- package/lib/register-tools.js +3 -1
- package/lib/theme-pair-helpers.js +83 -0
- package/lib/tools/theme-pair.js +251 -0
- package/lib/vault.js +183 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -100,7 +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
|
|
103
|
+
.ig-btn-active{background:var(--dsw-alias-state-brand-primary);color:var(--dsw-alias-bg-layer-1);border-color:transparent}
|
|
104
104
|
.ig-btn-danger{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}
|
|
105
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)}
|
|
106
106
|
.ig-btn-disabled{opacity:0.5;cursor:not-allowed}
|
|
@@ -119,6 +119,39 @@ window.__ModuleLoader__.load({
|
|
|
119
119
|
.ig-matrix-img{width:100%;aspect-ratio:1/1;object-fit:cover;cursor:pointer}
|
|
120
120
|
.ig-matrix-bar{display:flex;justify-content:space-between;align-items:center;padding:6px 10px;font-size:11px}
|
|
121
121
|
|
|
122
|
+
/* Revision History (#148) */
|
|
123
|
+
.ig-rev-bar{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:6px 10px;background:var(--dsw-alias-bg-layer-2);border-radius:8px;border:1px solid var(--dsw-alias-border-l2);margin-bottom:8px;font-size:12px}
|
|
124
|
+
.ig-rev-controls{display:flex;align-items:center;gap:6px}
|
|
125
|
+
.ig-rev-btn{appearance:none;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);border-radius:4px;padding:2px 8px;cursor:pointer;font-size:11px;font-weight:600;transition:all .15s ease}
|
|
126
|
+
.ig-rev-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-secondary)}
|
|
127
|
+
.ig-rev-btn:disabled{opacity:0.4;cursor:not-allowed}
|
|
128
|
+
.ig-rev-badge{font-size:11px;color:var(--dsw-alias-label-secondary)}
|
|
129
|
+
|
|
130
|
+
/* Progressive Draft Preview (#147) */
|
|
131
|
+
.ig-progressive-wrap{position:relative;width:100%;overflow:hidden;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);min-height:160px;display:flex;align-items:center;justify-content:center}
|
|
132
|
+
.ig-draft-img{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;filter:blur(6px);opacity:0.75;transform:scale(1.02);transition:opacity 0.4s ease}
|
|
133
|
+
.ig-final-img{position:relative;width:100%;height:auto;display:block;opacity:1;transition:opacity 0.35s ease}
|
|
134
|
+
.ig-draft-overlay{position:absolute;bottom:8px;left:8px;padding:3px 8px;border-radius:4px;background:color-mix(in srgb, var(--dsw-alias-bg-layer-1) 80%, transparent);font-size:11px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:6px;backdrop-filter:blur(4px)}
|
|
135
|
+
|
|
136
|
+
@media (prefers-reduced-motion: reduce){
|
|
137
|
+
.ig-draft-img,.ig-final-img{transition:none !important}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Asset Vault & Studio (#158, #159) */
|
|
141
|
+
.ig-vault-header{display:flex;flex-wrap:wrap;gap:10px;align-items:center;justify-content:space-between;margin-bottom:12px}
|
|
142
|
+
.ig-vault-grid{display:grid;grid-template-columns:repeat(auto-fill, minmax(180px, 1fr));gap:12px}
|
|
143
|
+
.ig-vault-card{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;overflow:hidden;background:var(--dsw-alias-bg-layer-2);display:flex;flex-direction:column;transition:transform .15s ease, border-color .15s ease}
|
|
144
|
+
.ig-vault-card:hover{border-color:var(--dsw-alias-state-brand-primary);transform:translateY(-2px)}
|
|
145
|
+
.ig-vault-thumb{width:100%;aspect-ratio:1/1;object-fit:cover;cursor:pointer;background:var(--dsw-alias-bg-layer-3)}
|
|
146
|
+
.ig-vault-body{padding:8px 10px;display:flex;flex-direction:column;gap:4px;font-size:12px}
|
|
147
|
+
.ig-vault-prompt{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500;color:var(--dsw-alias-label-primary)}
|
|
148
|
+
.ig-vault-meta{font-size:11px;color:var(--dsw-alias-label-secondary);display:flex;justify-content:space-between}
|
|
149
|
+
|
|
150
|
+
.ig-studio-layout{display:grid;grid-template-columns:320px 1fr;gap:18px;min-height:550px}
|
|
151
|
+
@media (max-width: 800px){.ig-studio-layout{grid-template-columns:1fr}}
|
|
152
|
+
.ig-studio-sidebar{display:flex;flex-direction:column;gap:12px;padding:16px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px}
|
|
153
|
+
.ig-studio-canvas{display:flex;flex-direction:column;gap:12px;padding:16px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:12px;align-items:center;justify-content:center;position:relative}
|
|
154
|
+
|
|
122
155
|
/* Backward-compatibility alias styles for toolview */
|
|
123
156
|
.fal_head{font-size:13px;font-weight:600;margin-bottom:8px;color:var(--dsw-alias-label-primary)}
|
|
124
157
|
.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}
|
|
@@ -126,8 +159,7 @@ window.__ModuleLoader__.load({
|
|
|
126
159
|
.ig-err{padding:10px;border-radius:6px;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:12px}
|
|
127
160
|
`
|
|
128
161
|
document.head.appendChild(style)
|
|
129
|
-
}
|
|
130
|
-
function createErrorBoundary() {
|
|
162
|
+
} function createErrorBoundary() {
|
|
131
163
|
if (!React || typeof React.Component !== 'function') {
|
|
132
164
|
return function NoopBoundary(props) { return props?.children || null }
|
|
133
165
|
}
|
|
@@ -1374,10 +1406,6 @@ window.__ModuleLoader__.load({
|
|
|
1374
1406
|
const [responsiveTab, setResponsiveTab] = react.useState(0)
|
|
1375
1407
|
const [copied, setCopied] = react.useState(false)
|
|
1376
1408
|
|
|
1377
|
-
react.useEffect(() => {
|
|
1378
|
-
ensureCss()
|
|
1379
|
-
}, [])
|
|
1380
|
-
|
|
1381
1409
|
let args = {}
|
|
1382
1410
|
let prompt = ''
|
|
1383
1411
|
try {
|
|
@@ -1388,6 +1416,24 @@ window.__ModuleLoader__.load({
|
|
|
1388
1416
|
}
|
|
1389
1417
|
} catch (_) { /* malformed tool args */ }
|
|
1390
1418
|
|
|
1419
|
+
const initialUrl = parsed.attachment ? attachmentImageUrl(parsed.attachment) : parsed.url
|
|
1420
|
+
const [revisions, setRevisions] = react.useState(() => initialUrl ? [{ url: initialUrl, attachment: parsed.attachment, seed: args.seed, timestamp: Date.now() }] : [])
|
|
1421
|
+
const [activeRev, setActiveRev] = react.useState(0)
|
|
1422
|
+
|
|
1423
|
+
react.useEffect(() => {
|
|
1424
|
+
ensureCss()
|
|
1425
|
+
}, [])
|
|
1426
|
+
|
|
1427
|
+
react.useEffect(() => {
|
|
1428
|
+
if (!initialUrl) return
|
|
1429
|
+
setRevisions((prev) => {
|
|
1430
|
+
if (prev.some((r) => r.url === initialUrl)) return prev
|
|
1431
|
+
const next = [...prev, { url: initialUrl, attachment: parsed.attachment, seed: args.seed, timestamp: Date.now() }]
|
|
1432
|
+
setActiveRev(next.length - 1)
|
|
1433
|
+
return next
|
|
1434
|
+
})
|
|
1435
|
+
}, [initialUrl, args.seed])
|
|
1436
|
+
|
|
1391
1437
|
const sendActionPrompt = async (text) => {
|
|
1392
1438
|
try {
|
|
1393
1439
|
const sessions = props.sessions
|
|
@@ -1468,21 +1514,28 @@ window.__ModuleLoader__.load({
|
|
|
1468
1514
|
[active.width && active.height ? `${active.width}×${active.height}` : null, active.path ? String(active.path) : null].filter(Boolean).join(' · ')
|
|
1469
1515
|
) : null
|
|
1470
1516
|
)
|
|
1471
|
-
} else
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1517
|
+
} else {
|
|
1518
|
+
// Revision bar (#148)
|
|
1519
|
+
if (revisions.length > 1) {
|
|
1520
|
+
body.push(react.createElement(ImageRevisionBar, {
|
|
1521
|
+
key: 'revs',
|
|
1522
|
+
revisions,
|
|
1523
|
+
activeIndex: activeRev,
|
|
1524
|
+
onSelect: (idx) => setActiveRev(idx),
|
|
1525
|
+
t,
|
|
1526
|
+
}))
|
|
1527
|
+
}
|
|
1528
|
+
// Progressive preview (#147)
|
|
1529
|
+
const currentRev = revisions[activeRev] || {}
|
|
1530
|
+
const displaySrc = currentRev.url || initialUrl
|
|
1531
|
+
const draftSrc = block && (block.draftUrl || block.previewUrl || '')
|
|
1532
|
+
body.push(react.createElement(ProgressiveImagePreview, {
|
|
1533
|
+
key: 'prog-img',
|
|
1534
|
+
src: displaySrc,
|
|
1535
|
+
draftSrc,
|
|
1536
|
+
isRunning: running,
|
|
1483
1537
|
alt: prompt || 'generated image',
|
|
1484
|
-
|
|
1485
|
-
style: { maxWidth: '100%', borderRadius: '8px', border: '1px solid var(--dsw-alias-border-l2)' },
|
|
1538
|
+
t,
|
|
1486
1539
|
}))
|
|
1487
1540
|
}
|
|
1488
1541
|
|
|
@@ -1595,10 +1648,110 @@ window.__ModuleLoader__.load({
|
|
|
1595
1648
|
remixDrawer,
|
|
1596
1649
|
inpaintDrawer
|
|
1597
1650
|
)
|
|
1598
|
-
}
|
|
1651
|
+
} // -------------------------------------------------------------- Image Revision History (#148)
|
|
1652
|
+
function ImageRevisionBar(props) {
|
|
1653
|
+
const revisions = props.revisions
|
|
1654
|
+
const activeIndex = props.activeIndex
|
|
1655
|
+
const onSelect = props.onSelect
|
|
1656
|
+
const t = props.t || ((k) => k)
|
|
1657
|
+
|
|
1658
|
+
if (!Array.isArray(revisions) || revisions.length <= 1) return null
|
|
1659
|
+
|
|
1660
|
+
const current = revisions[activeIndex] || {}
|
|
1661
|
+
const hasPrev = activeIndex > 0
|
|
1662
|
+
const hasNext = activeIndex < revisions.length - 1
|
|
1663
|
+
|
|
1664
|
+
return react.createElement(
|
|
1665
|
+
'div',
|
|
1666
|
+
{ className: 'ig-rev-bar', role: 'navigation', 'aria-label': t('card.revisions') || 'Image revisions' },
|
|
1667
|
+
react.createElement(
|
|
1668
|
+
'div',
|
|
1669
|
+
{ className: 'ig-rev-controls' },
|
|
1670
|
+
react.createElement(
|
|
1671
|
+
'button',
|
|
1672
|
+
{
|
|
1673
|
+
type: 'button',
|
|
1674
|
+
className: 'ig-rev-btn',
|
|
1675
|
+
disabled: !hasPrev,
|
|
1676
|
+
onClick: () => onSelect(activeIndex - 1),
|
|
1677
|
+
title: t('card.prevRevision') || 'Previous revision',
|
|
1678
|
+
},
|
|
1679
|
+
'◀'
|
|
1680
|
+
),
|
|
1681
|
+
react.createElement(
|
|
1682
|
+
'span',
|
|
1683
|
+
{ style: { fontWeight: '600', color: 'var(--dsw-alias-label-primary)' } },
|
|
1684
|
+
`v${activeIndex + 1} / ${revisions.length}`
|
|
1685
|
+
),
|
|
1686
|
+
react.createElement(
|
|
1687
|
+
'button',
|
|
1688
|
+
{
|
|
1689
|
+
type: 'button',
|
|
1690
|
+
className: 'ig-rev-btn',
|
|
1691
|
+
disabled: !hasNext,
|
|
1692
|
+
onClick: () => onSelect(activeIndex + 1),
|
|
1693
|
+
title: t('card.nextRevision') || 'Next revision',
|
|
1694
|
+
},
|
|
1695
|
+
'▶'
|
|
1696
|
+
)
|
|
1697
|
+
),
|
|
1698
|
+
react.createElement(
|
|
1699
|
+
'div',
|
|
1700
|
+
{ className: 'ig-rev-badge' },
|
|
1701
|
+
current.seed !== undefined ? `seed: ${current.seed}` : (current.timestamp ? new Date(current.timestamp).toLocaleTimeString() : '')
|
|
1702
|
+
)
|
|
1703
|
+
)
|
|
1704
|
+
} // -------------------------------------------------------------- Progressive Draft Preview (#147)
|
|
1705
|
+
function ProgressiveImagePreview(props) {
|
|
1706
|
+
const src = props.src
|
|
1707
|
+
const draftSrc = props.draftSrc
|
|
1708
|
+
const isRunning = props.isRunning
|
|
1709
|
+
const alt = props.alt || 'Generated image'
|
|
1710
|
+
const t = props.t || ((k) => k)
|
|
1711
|
+
|
|
1712
|
+
const [loaded, setLoaded] = react.useState(false)
|
|
1599
1713
|
|
|
1600
|
-
|
|
1601
|
-
|
|
1714
|
+
react.useEffect(() => {
|
|
1715
|
+
setLoaded(false)
|
|
1716
|
+
}, [src])
|
|
1717
|
+
|
|
1718
|
+
if (isRunning && !src && draftSrc) {
|
|
1719
|
+
return react.createElement(
|
|
1720
|
+
'div',
|
|
1721
|
+
{ className: 'ig-progressive-wrap' },
|
|
1722
|
+
react.createElement('img', {
|
|
1723
|
+
src: draftSrc,
|
|
1724
|
+
alt: alt + ' (preview)',
|
|
1725
|
+
className: 'ig-draft-img',
|
|
1726
|
+
}),
|
|
1727
|
+
react.createElement(
|
|
1728
|
+
'div',
|
|
1729
|
+
{ className: 'ig-draft-overlay' },
|
|
1730
|
+
'⚡ ' + (t('card.draftPreview') || 'Rendering draft...')
|
|
1731
|
+
)
|
|
1732
|
+
)
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
if (!src && !draftSrc) return null
|
|
1736
|
+
|
|
1737
|
+
return react.createElement(
|
|
1738
|
+
'div',
|
|
1739
|
+
{ className: 'ig-progressive-wrap', style: { minHeight: 'auto' } },
|
|
1740
|
+
draftSrc && !loaded ? react.createElement('img', {
|
|
1741
|
+
src: draftSrc,
|
|
1742
|
+
alt: alt + ' (draft)',
|
|
1743
|
+
className: 'ig-draft-img',
|
|
1744
|
+
}) : null,
|
|
1745
|
+
src ? react.createElement('img', {
|
|
1746
|
+
src,
|
|
1747
|
+
alt,
|
|
1748
|
+
loading: 'lazy',
|
|
1749
|
+
className: 'ig-final-img',
|
|
1750
|
+
style: { opacity: loaded || !draftSrc ? 1 : 0.8 },
|
|
1751
|
+
onLoad: () => setLoaded(true),
|
|
1752
|
+
}) : null
|
|
1753
|
+
)
|
|
1754
|
+
}// 105-inpaint-canvas.js — Interactive in-chat canvas drawing overlay for inpainting (#285).
|
|
1602
1755
|
|
|
1603
1756
|
function InpaintCanvasOverlay({ react, t, parsed, onClose }) {
|
|
1604
1757
|
const canvasRef = react.useRef(null)
|
|
@@ -2030,7 +2183,109 @@ window.__ModuleLoader__.load({
|
|
|
2030
2183
|
) : null
|
|
2031
2184
|
)
|
|
2032
2185
|
}
|
|
2033
|
-
|
|
2186
|
+
// -------------------------------------------------------------- Theme Pair Toolview (#189)
|
|
2187
|
+
function ThemePairToolView(props) {
|
|
2188
|
+
const block = props.block
|
|
2189
|
+
const t = props.t || ((k) => k)
|
|
2190
|
+
const [copiedHtml, setCopiedHtml] = react.useState(false)
|
|
2191
|
+
const [copiedCss, setCopiedCss] = react.useState(false)
|
|
2192
|
+
const [themeTab, setThemeTab] = react.useState('light')
|
|
2193
|
+
|
|
2194
|
+
const raw = (block && (block.call ? block.call.argsRaw : block.argsRaw)) || ''
|
|
2195
|
+
let args = {}
|
|
2196
|
+
try { if (raw) args = JSON.parse(raw) } catch (_) { /* invalid json */ }
|
|
2197
|
+
|
|
2198
|
+
let data = {}
|
|
2199
|
+
try {
|
|
2200
|
+
const out = block && (block.output || block.result || block.text || '')
|
|
2201
|
+
if (typeof out === 'object') data = out
|
|
2202
|
+
else if (typeof out === 'string') data = JSON.parse(out)
|
|
2203
|
+
} catch (_) { /* invalid json */ }
|
|
2204
|
+
|
|
2205
|
+
const light = data.light || {}
|
|
2206
|
+
const dark = data.dark || {}
|
|
2207
|
+
const htmlSnippet = data.html_snippet || ''
|
|
2208
|
+
const cssSnippet = data.css_snippet || ''
|
|
2209
|
+
|
|
2210
|
+
const copyCode = (text, isHtml) => {
|
|
2211
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
2212
|
+
navigator.clipboard.writeText(text)
|
|
2213
|
+
if (isHtml) {
|
|
2214
|
+
setCopiedHtml(true)
|
|
2215
|
+
setTimeout(() => setCopiedHtml(false), 2000)
|
|
2216
|
+
} else {
|
|
2217
|
+
setCopiedCss(true)
|
|
2218
|
+
setTimeout(() => setCopiedCss(false), 2000)
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
const activeImg = themeTab === 'light' ? light : dark
|
|
2224
|
+
|
|
2225
|
+
return react.createElement(
|
|
2226
|
+
'div',
|
|
2227
|
+
{ className: 'ig-page', style: { padding: '4px 0', gap: '10px' } },
|
|
2228
|
+
react.createElement('div', { className: 'fal_head' }, '🌓 ' + (t('themePair.title') || 'Theme Pair Graphic')),
|
|
2229
|
+
args.prompt ? react.createElement('div', { className: 'ig-prompt' }, args.prompt) : null,
|
|
2230
|
+
react.createElement(
|
|
2231
|
+
'div',
|
|
2232
|
+
{ className: 'ig-tabs' },
|
|
2233
|
+
react.createElement(
|
|
2234
|
+
'button',
|
|
2235
|
+
{
|
|
2236
|
+
type: 'button',
|
|
2237
|
+
className: themeTab === 'light' ? 'ig-tab-btn ig-tab-btn-active' : 'ig-tab-btn',
|
|
2238
|
+
onClick: () => setThemeTab('light'),
|
|
2239
|
+
},
|
|
2240
|
+
'☀️ ' + (t('themePair.light') || 'Light Theme')
|
|
2241
|
+
),
|
|
2242
|
+
react.createElement(
|
|
2243
|
+
'button',
|
|
2244
|
+
{
|
|
2245
|
+
type: 'button',
|
|
2246
|
+
className: themeTab === 'dark' ? 'ig-tab-btn ig-tab-btn-active' : 'ig-tab-btn',
|
|
2247
|
+
onClick: () => setThemeTab('dark'),
|
|
2248
|
+
},
|
|
2249
|
+
'🌙 ' + (t('themePair.dark') || 'Dark Theme')
|
|
2250
|
+
)
|
|
2251
|
+
),
|
|
2252
|
+
activeImg.url || activeImg.path ? react.createElement('img', {
|
|
2253
|
+
src: activeImg.url || activeImg.path,
|
|
2254
|
+
alt: args.prompt || 'theme image',
|
|
2255
|
+
loading: 'lazy',
|
|
2256
|
+
style: {
|
|
2257
|
+
width: '100%',
|
|
2258
|
+
maxHeight: '400px',
|
|
2259
|
+
objectFit: 'contain',
|
|
2260
|
+
borderRadius: '8px',
|
|
2261
|
+
border: '1px solid var(--dsw-alias-border-l2)',
|
|
2262
|
+
background: themeTab === 'light' ? 'var(--dsw-alias-bg-layer-1)' : 'var(--dsw-alias-bg-layer-3)',
|
|
2263
|
+
},
|
|
2264
|
+
}) : null,
|
|
2265
|
+
htmlSnippet ? react.createElement(
|
|
2266
|
+
'div',
|
|
2267
|
+
{ style: { display: 'flex', gap: '8px', marginTop: '4px' } },
|
|
2268
|
+
react.createElement(
|
|
2269
|
+
'button',
|
|
2270
|
+
{
|
|
2271
|
+
type: 'button',
|
|
2272
|
+
className: 'ig-btn',
|
|
2273
|
+
onClick: () => copyCode(htmlSnippet, true),
|
|
2274
|
+
},
|
|
2275
|
+
copiedHtml ? '✓ ' + (t('themePair.copiedHtml') || 'Copied HTML') : '📋 ' + (t('themePair.copyHtml') || 'Copy <picture> HTML')
|
|
2276
|
+
),
|
|
2277
|
+
cssSnippet ? react.createElement(
|
|
2278
|
+
'button',
|
|
2279
|
+
{
|
|
2280
|
+
type: 'button',
|
|
2281
|
+
className: 'ig-btn',
|
|
2282
|
+
onClick: () => copyCode(cssSnippet, false),
|
|
2283
|
+
},
|
|
2284
|
+
copiedCss ? '✓ ' + (t('themePair.copiedCss') || 'Copied CSS') : '🎨 ' + (t('themePair.copyCss') || 'Copy Theme CSS')
|
|
2285
|
+
) : null
|
|
2286
|
+
) : null
|
|
2287
|
+
)
|
|
2288
|
+
} const en = {
|
|
2034
2289
|
'settings.navLabel': 'Image Studio',
|
|
2035
2290
|
'settings.title': 'Image Studio',
|
|
2036
2291
|
'settings.description': 'AI Image generation, inpainting, variations, upscale & vectorization.',
|
|
@@ -2258,6 +2513,22 @@ window.__ModuleLoader__.load({
|
|
|
2258
2513
|
'f.cacheTtlDays': 'Cache Retention (Legacy)',
|
|
2259
2514
|
'f.cacheTtlDaysHint': 'Legacy name for retention period in days.',
|
|
2260
2515
|
'p.cacheTtlDays': '0',
|
|
2516
|
+
|
|
2517
|
+
'studio.title': 'Image Studio',
|
|
2518
|
+
'studio.canvas': 'Studio Canvas',
|
|
2519
|
+
'studio.vault': 'Asset Vault',
|
|
2520
|
+
'studio.parameters': 'Generation Parameters',
|
|
2521
|
+
'studio.generate': 'Generate in Chat',
|
|
2522
|
+
'studio.recent': 'Recent Generations',
|
|
2523
|
+
'vault.title': 'Asset Browser & Vault',
|
|
2524
|
+
'vault.search': 'Search prompt or tags...',
|
|
2525
|
+
'vault.loadMore': 'Load more assets',
|
|
2526
|
+
'vault.empty': 'No assets found in vault.',
|
|
2527
|
+
'vault.inspector': 'Asset Inspector',
|
|
2528
|
+
'vault.reroll': 'Re-roll with seed',
|
|
2529
|
+
'vault.insert': 'Insert in Chat',
|
|
2530
|
+
'vault.download': 'Download',
|
|
2531
|
+
'vault.delete': 'Delete',
|
|
2261
2532
|
}
|
|
2262
2533
|
|
|
2263
2534
|
const zh = {
|
|
@@ -2476,10 +2747,143 @@ window.__ModuleLoader__.load({
|
|
|
2476
2747
|
'f.cacheTtlDays': '缓存生命周期 (Legacy)',
|
|
2477
2748
|
'f.cacheTtlDaysHint': '已废弃的缓存保留时长参数。',
|
|
2478
2749
|
'p.cacheTtlDays': '0',
|
|
2750
|
+
|
|
2751
|
+
'studio.title': '图像创作工作台',
|
|
2752
|
+
'studio.canvas': '创作画布',
|
|
2753
|
+
'studio.vault': '素材资产库',
|
|
2754
|
+
'studio.parameters': '生成参数配置',
|
|
2755
|
+
'studio.generate': '在对话中生成',
|
|
2756
|
+
'studio.recent': '最近生成历史',
|
|
2757
|
+
'vault.title': '图像素材库与浏览器',
|
|
2758
|
+
'vault.search': '搜索提示词或标签...',
|
|
2759
|
+
'vault.loadMore': '加载更多素材',
|
|
2760
|
+
'vault.empty': '素材库中暂无图像。',
|
|
2761
|
+
'vault.inspector': '素材检查器',
|
|
2762
|
+
'vault.reroll': '使用此种子重新生成',
|
|
2763
|
+
'vault.insert': '插入到当前对话',
|
|
2764
|
+
'vault.download': '下载图像',
|
|
2765
|
+
'vault.delete': '删除',
|
|
2479
2766
|
}
|
|
2480
2767
|
|
|
2481
2768
|
// -------------------------------------------------------------- Registration & Apply
|
|
2482
|
-
|
|
2769
|
+
// Dual compatibility with DSH 0.1.5-rc.3 (settingsScope) and 0.1.6+ (configForms) (#291)
|
|
2770
|
+
const inject = ['slots', 'locale', 'sessions']
|
|
2771
|
+
|
|
2772
|
+
function resolveActiveScope(ctx) {
|
|
2773
|
+
if (!ctx) return null
|
|
2774
|
+
if (ctx.get && typeof ctx.get === 'function') {
|
|
2775
|
+
try {
|
|
2776
|
+
const lan = ctx.get('lanSettings')
|
|
2777
|
+
if (lan && typeof lan.get === 'function') {
|
|
2778
|
+
const s = lan.get(SETTINGS_NS)
|
|
2779
|
+
if (s) return s
|
|
2780
|
+
}
|
|
2781
|
+
} catch (_) { /* scope unavailable */ }
|
|
2782
|
+
try {
|
|
2783
|
+
const cf = ctx.get('configForms')
|
|
2784
|
+
if (cf && typeof cf.get === 'function') {
|
|
2785
|
+
const s = cf.get(SETTINGS_NS)
|
|
2786
|
+
if (s) return s
|
|
2787
|
+
}
|
|
2788
|
+
} catch (_) { /* scope unavailable */ }
|
|
2789
|
+
try {
|
|
2790
|
+
const ss = ctx.get('settingsScope')
|
|
2791
|
+
if (ss && typeof ss.bind === 'function') {
|
|
2792
|
+
const s = ss.bind({ namespace: SETTINGS_NS })
|
|
2793
|
+
if (s) return s
|
|
2794
|
+
}
|
|
2795
|
+
} catch (_) { /* scope unavailable */ }
|
|
2796
|
+
}
|
|
2797
|
+
if (ctx.configForms && typeof ctx.configForms.get === 'function') {
|
|
2798
|
+
try {
|
|
2799
|
+
const s = ctx.configForms.get(SETTINGS_NS)
|
|
2800
|
+
if (s) return s
|
|
2801
|
+
} catch (_) { /* scope unavailable */ }
|
|
2802
|
+
}
|
|
2803
|
+
if (ctx.settingsScope && typeof ctx.settingsScope.bind === 'function') {
|
|
2804
|
+
try {
|
|
2805
|
+
const s = ctx.settingsScope.bind({ namespace: SETTINGS_NS })
|
|
2806
|
+
if (s) return s
|
|
2807
|
+
} catch (_) { /* scope unavailable */ }
|
|
2808
|
+
}
|
|
2809
|
+
return null
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
function createDynamicSettingsScope(ctx) {
|
|
2813
|
+
const listeners = new Set()
|
|
2814
|
+
let activeTarget = resolveActiveScope(ctx)
|
|
2815
|
+
let activeUnsub = null
|
|
2816
|
+
|
|
2817
|
+
const bindTarget = (target) => {
|
|
2818
|
+
if (!target || target === activeTarget) return
|
|
2819
|
+
if (typeof activeUnsub === 'function') {
|
|
2820
|
+
try { activeUnsub() } catch (_) { /* scope unavailable */ }
|
|
2821
|
+
activeUnsub = null
|
|
2822
|
+
}
|
|
2823
|
+
activeTarget = target
|
|
2824
|
+
if (typeof activeTarget.subscribe === 'function') {
|
|
2825
|
+
try {
|
|
2826
|
+
activeUnsub = activeTarget.subscribe(() => {
|
|
2827
|
+
for (const fn of listeners) fn()
|
|
2828
|
+
})
|
|
2829
|
+
} catch (_) { /* scope unavailable */ }
|
|
2830
|
+
}
|
|
2831
|
+
for (const fn of listeners) fn()
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
if (activeTarget && typeof activeTarget.subscribe === 'function') {
|
|
2835
|
+
try {
|
|
2836
|
+
activeUnsub = activeTarget.subscribe(() => {
|
|
2837
|
+
for (const fn of listeners) fn()
|
|
2838
|
+
})
|
|
2839
|
+
} catch (_) { /* scope unavailable */ }
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
if (typeof ctx?.inject === 'function') {
|
|
2843
|
+
try {
|
|
2844
|
+
ctx.inject(['configForms'], (sctx) => {
|
|
2845
|
+
try {
|
|
2846
|
+
const s = sctx?.configForms?.get?.(SETTINGS_NS)
|
|
2847
|
+
if (s) bindTarget(s)
|
|
2848
|
+
} catch (_) { /* scope unavailable */ }
|
|
2849
|
+
})
|
|
2850
|
+
} catch (_) { /* scope unavailable */ }
|
|
2851
|
+
try {
|
|
2852
|
+
ctx.inject(['settingsScope'], (sctx) => {
|
|
2853
|
+
try {
|
|
2854
|
+
const s = sctx?.settingsScope?.bind?.({ namespace: SETTINGS_NS })
|
|
2855
|
+
if (s) bindTarget(s)
|
|
2856
|
+
} catch (_) { /* scope unavailable */ }
|
|
2857
|
+
})
|
|
2858
|
+
} catch (_) { /* scope unavailable */ }
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
return {
|
|
2862
|
+
getSnapshot() {
|
|
2863
|
+
const target = activeTarget || resolveActiveScope(ctx)
|
|
2864
|
+
if (target && typeof target.getSnapshot === 'function') {
|
|
2865
|
+
return target.getSnapshot()
|
|
2866
|
+
}
|
|
2867
|
+
return { status: 'unavailable', writable: false, value: {} }
|
|
2868
|
+
},
|
|
2869
|
+
async set(key, val) {
|
|
2870
|
+
const target = activeTarget || resolveActiveScope(ctx)
|
|
2871
|
+
if (target && typeof target.set === 'function') {
|
|
2872
|
+
return target.set(key, val)
|
|
2873
|
+
}
|
|
2874
|
+
},
|
|
2875
|
+
async delete(key) {
|
|
2876
|
+
const target = activeTarget || resolveActiveScope(ctx)
|
|
2877
|
+
if (target && typeof target.delete === 'function') {
|
|
2878
|
+
return target.delete(key)
|
|
2879
|
+
}
|
|
2880
|
+
},
|
|
2881
|
+
subscribe(fn) {
|
|
2882
|
+
listeners.add(fn)
|
|
2883
|
+
return () => listeners.delete(fn)
|
|
2884
|
+
},
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2483
2887
|
|
|
2484
2888
|
function apply(ctx) {
|
|
2485
2889
|
if (ctx.locale && typeof ctx.locale.define === 'function') {
|
|
@@ -2490,7 +2894,7 @@ window.__ModuleLoader__.load({
|
|
|
2490
2894
|
let card
|
|
2491
2895
|
const cardOnce = () => {
|
|
2492
2896
|
if (card === undefined) {
|
|
2493
|
-
const scope = (
|
|
2897
|
+
const scope = createDynamicSettingsScope(ctx)
|
|
2494
2898
|
card = new FalSettingsCardController(scope)
|
|
2495
2899
|
}
|
|
2496
2900
|
return card
|
|
@@ -2565,6 +2969,18 @@ window.__ModuleLoader__.load({
|
|
|
2565
2969
|
)
|
|
2566
2970
|
)
|
|
2567
2971
|
|
|
2972
|
+
registerSlotWhenReady('tool.call.toolview', () =>
|
|
2973
|
+
ctx.slots.register(
|
|
2974
|
+
{
|
|
2975
|
+
name: 'tool.call.toolview',
|
|
2976
|
+
key: 'generate_theme_pair',
|
|
2977
|
+
locale: NS,
|
|
2978
|
+
inject: () => ({ sessions: ctx.sessions }),
|
|
2979
|
+
},
|
|
2980
|
+
(props) => react.createElement(ErrorBoundary, null, react.createElement(ThemePairView, props))
|
|
2981
|
+
)
|
|
2982
|
+
)
|
|
2983
|
+
|
|
2568
2984
|
|
|
2569
2985
|
// Native Sidebar Right Pane Tab & BetterSidebar
|
|
2570
2986
|
if (typeof ctx.inject === 'function') {
|
|
@@ -2574,16 +2990,16 @@ window.__ModuleLoader__.load({
|
|
|
2574
2990
|
if (!tabs || typeof tabs.register !== 'function') return
|
|
2575
2991
|
try {
|
|
2576
2992
|
const def = {
|
|
2577
|
-
id: '@goodandready/dsh-image-gen:
|
|
2578
|
-
kind: 'image-
|
|
2993
|
+
id: '@goodandready/dsh-image-gen:studio',
|
|
2994
|
+
kind: 'image-studio',
|
|
2579
2995
|
priority: 'extension',
|
|
2580
|
-
title: () => '
|
|
2996
|
+
title: () => 'Image Studio',
|
|
2581
2997
|
guide: [
|
|
2582
2998
|
{
|
|
2583
2999
|
order: 45,
|
|
2584
|
-
title: () => 'Image Studio
|
|
2585
|
-
description: () => '
|
|
2586
|
-
icon: () => react.createElement('span', null, '
|
|
3000
|
+
title: () => 'Image Studio & Vault',
|
|
3001
|
+
description: () => 'Interactive visual studio & asset browser',
|
|
3002
|
+
icon: () => react.createElement('span', null, '🎨'),
|
|
2587
3003
|
},
|
|
2588
3004
|
],
|
|
2589
3005
|
}
|
|
@@ -2603,7 +3019,7 @@ window.__ModuleLoader__.load({
|
|
|
2603
3019
|
locale: NS,
|
|
2604
3020
|
inject: () => ({ ctx }),
|
|
2605
3021
|
},
|
|
2606
|
-
(props) => react.createElement(ErrorBoundary, null, react.createElement(
|
|
3022
|
+
(props) => react.createElement(ErrorBoundary, null, react.createElement(ImageStudioView, { ...props, ctx }))
|
|
2607
3023
|
)
|
|
2608
3024
|
} catch (e) {
|
|
2609
3025
|
console.warn('[dsh-image-gen] native sidebar pane tab register failed', e)
|
|
@@ -2631,7 +3047,7 @@ window.__ModuleLoader__.load({
|
|
|
2631
3047
|
title: () => 'Gallery',
|
|
2632
3048
|
icon: () => react.createElement('span', null, '🖼️'),
|
|
2633
3049
|
order: 45,
|
|
2634
|
-
component: ({ scope }) => react.createElement(ErrorBoundary, null, react.createElement(
|
|
3050
|
+
component: ({ scope }) => react.createElement(ErrorBoundary, null, react.createElement(ImageStudioView, { ctx, scope })),
|
|
2635
3051
|
})
|
|
2636
3052
|
} catch (e) {
|
|
2637
3053
|
console.warn('[dsh-image-gen] betterSidebar registerTab failed', e)
|
|
@@ -2654,10 +3070,7 @@ window.__ModuleLoader__.load({
|
|
|
2654
3070
|
)
|
|
2655
3071
|
)
|
|
2656
3072
|
|
|
2657
|
-
// List seat (plugins.item)
|
|
2658
|
-
// page with its configuration. The label is a static string on purpose — it is
|
|
2659
|
-
// resolved while the page renders, and a locale lookup there would take the whole
|
|
2660
|
-
// client batch down with it.
|
|
3073
|
+
// List seat (plugins.item)
|
|
2661
3074
|
registerSlotWhenReady('plugins.item', () =>
|
|
2662
3075
|
ctx.slots.register(
|
|
2663
3076
|
{
|
|
@@ -2686,6 +3099,574 @@ window.__ModuleLoader__.load({
|
|
|
2686
3099
|
)
|
|
2687
3100
|
}
|
|
2688
3101
|
|
|
3102
|
+
// -------------------------------------------------------------- Asset Vault View (#159)
|
|
3103
|
+
function AssetVaultView(props) {
|
|
3104
|
+
const { ctx, onSelectAsset } = props
|
|
3105
|
+
const [items, setItems] = react.useState([])
|
|
3106
|
+
const [loading, setLoading] = react.useState(false)
|
|
3107
|
+
const [q, setQ] = react.useState('')
|
|
3108
|
+
const [provider, setProvider] = react.useState('all')
|
|
3109
|
+
const [aspect, setAspect] = react.useState('all')
|
|
3110
|
+
const [sort, setSort] = react.useState('newest')
|
|
3111
|
+
const [offset, setOffset] = react.useState(0)
|
|
3112
|
+
const [hasMore, setHasMore] = react.useState(false)
|
|
3113
|
+
const [selected, setSelected] = react.useState(null)
|
|
3114
|
+
const [feedback, setFeedback] = react.useState('')
|
|
3115
|
+
|
|
3116
|
+
const showFeedback = (msg) => {
|
|
3117
|
+
setFeedback(msg)
|
|
3118
|
+
setTimeout(() => setFeedback(''), 2500)
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
const loadItems = (newOffset = 0, append = false) => {
|
|
3122
|
+
setLoading(true)
|
|
3123
|
+
const params = new URLSearchParams({
|
|
3124
|
+
q,
|
|
3125
|
+
provider: provider === 'all' ? '' : provider,
|
|
3126
|
+
aspect: aspect === 'all' ? '' : aspect,
|
|
3127
|
+
sort,
|
|
3128
|
+
offset: String(newOffset),
|
|
3129
|
+
limit: '24',
|
|
3130
|
+
})
|
|
3131
|
+
fetch('/dsh-image-gen/vault?' + params.toString())
|
|
3132
|
+
.then((r) => r.json())
|
|
3133
|
+
.then((data) => {
|
|
3134
|
+
setLoading(false)
|
|
3135
|
+
if (data && data.ok) {
|
|
3136
|
+
setItems(append ? (prev) => [...prev, ...data.items] : data.items)
|
|
3137
|
+
setOffset(data.offset + data.items.length)
|
|
3138
|
+
setHasMore(data.hasMore)
|
|
3139
|
+
}
|
|
3140
|
+
})
|
|
3141
|
+
.catch(() => setLoading(false))
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
react.useEffect(() => {
|
|
3145
|
+
loadItems(0, false)
|
|
3146
|
+
}, [q, provider, aspect, sort])
|
|
3147
|
+
|
|
3148
|
+
const handleDelete = (id) => {
|
|
3149
|
+
if (!confirm('Are you sure you want to delete this asset?')) return
|
|
3150
|
+
fetch('/dsh-image-gen/vault?id=' + encodeURIComponent(id), { method: 'DELETE' })
|
|
3151
|
+
.then((r) => r.json())
|
|
3152
|
+
.then((res) => {
|
|
3153
|
+
if (res.ok) {
|
|
3154
|
+
setItems((prev) => prev.filter((it) => it.id !== id && it.attachmentId !== id))
|
|
3155
|
+
if (selected && (selected.id === id || selected.attachmentId === id)) {
|
|
3156
|
+
setSelected(null)
|
|
3157
|
+
}
|
|
3158
|
+
showFeedback('Asset deleted')
|
|
3159
|
+
}
|
|
3160
|
+
})
|
|
3161
|
+
.catch(() => showFeedback('Failed to delete asset'))
|
|
3162
|
+
}
|
|
3163
|
+
|
|
3164
|
+
const handleInsert = (item) => {
|
|
3165
|
+
const link = ' + ')'
|
|
3166
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
3167
|
+
navigator.clipboard.writeText(link)
|
|
3168
|
+
showFeedback('Markdown snippet copied to clipboard!')
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
const handleReroll = (item) => {
|
|
3173
|
+
if (typeof onSelectAsset === 'function') {
|
|
3174
|
+
onSelectAsset(item)
|
|
3175
|
+
return
|
|
3176
|
+
}
|
|
3177
|
+
const cmd = '/image ' + (item.prompt || '') + (item.seed !== undefined ? ' --seed ' + item.seed : '')
|
|
3178
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
3179
|
+
navigator.clipboard.writeText(cmd)
|
|
3180
|
+
showFeedback('Prompt & seed copied to clipboard!')
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
return react.createElement(
|
|
3185
|
+
'div',
|
|
3186
|
+
{ className: 'ig-vault-container', style: { display: 'flex', flexDirection: 'column', gap: '14px', width: '100%' } },
|
|
3187
|
+
react.createElement(
|
|
3188
|
+
'div',
|
|
3189
|
+
{ className: 'ig-vault-header' },
|
|
3190
|
+
react.createElement('input', {
|
|
3191
|
+
type: 'text',
|
|
3192
|
+
className: 'ig-input',
|
|
3193
|
+
style: { flex: '1 1 200px', padding: '6px 12px', borderRadius: '6px', border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-primary)' },
|
|
3194
|
+
placeholder: 'Search prompt or tags...',
|
|
3195
|
+
value: q,
|
|
3196
|
+
onChange: (e) => setQ(e.target.value),
|
|
3197
|
+
}),
|
|
3198
|
+
react.createElement(
|
|
3199
|
+
'select',
|
|
3200
|
+
{
|
|
3201
|
+
className: 'ig-select',
|
|
3202
|
+
value: provider,
|
|
3203
|
+
onChange: (e) => setProvider(e.target.value),
|
|
3204
|
+
style: { padding: '6px 10px', borderRadius: '6px', border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-primary)' },
|
|
3205
|
+
},
|
|
3206
|
+
react.createElement('option', { value: 'all' }, 'All Providers'),
|
|
3207
|
+
react.createElement('option', { value: 'fal' }, 'FAL.ai'),
|
|
3208
|
+
react.createElement('option', { value: 'openai' }, 'OpenAI'),
|
|
3209
|
+
react.createElement('option', { value: 'local' }, 'Local (ComfyUI)')
|
|
3210
|
+
),
|
|
3211
|
+
react.createElement(
|
|
3212
|
+
'select',
|
|
3213
|
+
{
|
|
3214
|
+
className: 'ig-select',
|
|
3215
|
+
value: aspect,
|
|
3216
|
+
onChange: (e) => setAspect(e.target.value),
|
|
3217
|
+
style: { padding: '6px 10px', borderRadius: '6px', border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-primary)' },
|
|
3218
|
+
},
|
|
3219
|
+
react.createElement('option', { value: 'all' }, 'All Ratios'),
|
|
3220
|
+
react.createElement('option', { value: '1:1' }, '1:1 Square'),
|
|
3221
|
+
react.createElement('option', { value: '16:9' }, '16:9 Landscape'),
|
|
3222
|
+
react.createElement('option', { value: '9:16' }, '9:16 Portrait'),
|
|
3223
|
+
react.createElement('option', { value: '4:3' }, '4:3 Standard'),
|
|
3224
|
+
react.createElement('option', { value: '21:9' }, '21:9 Ultrawide')
|
|
3225
|
+
),
|
|
3226
|
+
react.createElement(
|
|
3227
|
+
'button',
|
|
3228
|
+
{
|
|
3229
|
+
type: 'button',
|
|
3230
|
+
className: 'ig-tab-btn',
|
|
3231
|
+
onClick: () => setSort(sort === 'newest' ? 'oldest' : 'newest'),
|
|
3232
|
+
style: { border: '1px solid var(--dsw-alias-border-l2)', padding: '6px 10px', borderRadius: '6px', background: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-primary)', cursor: 'pointer' },
|
|
3233
|
+
},
|
|
3234
|
+
sort === 'newest' ? '↓ Newest' : '↑ Oldest'
|
|
3235
|
+
)
|
|
3236
|
+
),
|
|
3237
|
+
feedback && react.createElement('div', { style: { padding: '6px 12px', borderRadius: '6px', background: 'color-mix(in srgb, var(--dsw-alias-state-brand-primary) 15%, transparent)', color: 'var(--dsw-alias-state-brand-primary)', fontSize: '12px' } }, feedback),
|
|
3238
|
+
items.length === 0 && !loading
|
|
3239
|
+
? react.createElement('div', { style: { padding: '32px', textAlign: 'center', color: 'var(--dsw-alias-label-secondary)', fontSize: '13px' } }, 'No assets found in vault.')
|
|
3240
|
+
: react.createElement(
|
|
3241
|
+
'div',
|
|
3242
|
+
{ className: 'ig-vault-grid' },
|
|
3243
|
+
items.map((item) =>
|
|
3244
|
+
react.createElement(
|
|
3245
|
+
'div',
|
|
3246
|
+
{ key: item.id || item.attachmentId, className: 'ig-vault-card' },
|
|
3247
|
+
react.createElement('img', {
|
|
3248
|
+
src: item.thumbnailUrl || item.url,
|
|
3249
|
+
className: 'ig-vault-thumb',
|
|
3250
|
+
loading: 'lazy',
|
|
3251
|
+
onClick: () => setSelected(item),
|
|
3252
|
+
}),
|
|
3253
|
+
react.createElement(
|
|
3254
|
+
'div',
|
|
3255
|
+
{ className: 'ig-vault-body' },
|
|
3256
|
+
react.createElement('div', { className: 'ig-vault-prompt', title: item.prompt }, item.prompt || 'Untitled'),
|
|
3257
|
+
react.createElement(
|
|
3258
|
+
'div',
|
|
3259
|
+
{ className: 'ig-vault-meta' },
|
|
3260
|
+
react.createElement('span', null, item.provider || 'local'),
|
|
3261
|
+
item.seed !== undefined && react.createElement('span', null, 'Seed ' + item.seed)
|
|
3262
|
+
),
|
|
3263
|
+
react.createElement(
|
|
3264
|
+
'div',
|
|
3265
|
+
{ style: { display: 'flex', gap: '4px', marginTop: '6px' } },
|
|
3266
|
+
react.createElement('button', { type: 'button', className: 'ig-rev-btn', style: { flex: 1 }, onClick: () => handleReroll(item), title: 'Re-roll with seed' }, '🎲 Re-roll'),
|
|
3267
|
+
react.createElement('button', { type: 'button', className: 'ig-rev-btn', style: { flex: 1 }, onClick: () => handleInsert(item), title: 'Insert into chat' }, '💬 Insert'),
|
|
3268
|
+
react.createElement('button', { type: 'button', className: 'ig-rev-btn', onClick: () => handleDelete(item.id || item.attachmentId), title: 'Delete' }, '🗑️')
|
|
3269
|
+
)
|
|
3270
|
+
)
|
|
3271
|
+
)
|
|
3272
|
+
)
|
|
3273
|
+
),
|
|
3274
|
+
hasMore &&
|
|
3275
|
+
react.createElement(
|
|
3276
|
+
'div',
|
|
3277
|
+
{ style: { textAlign: 'center', margin: '14px 0' } },
|
|
3278
|
+
react.createElement(
|
|
3279
|
+
'button',
|
|
3280
|
+
{
|
|
3281
|
+
type: 'button',
|
|
3282
|
+
className: 'ig-tab-btn',
|
|
3283
|
+
onClick: () => loadItems(offset, true),
|
|
3284
|
+
disabled: loading,
|
|
3285
|
+
style: { padding: '8px 20px', borderRadius: '6px', border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-primary)', cursor: 'pointer' },
|
|
3286
|
+
},
|
|
3287
|
+
loading ? 'Loading...' : 'Load more assets'
|
|
3288
|
+
)
|
|
3289
|
+
),
|
|
3290
|
+
selected &&
|
|
3291
|
+
react.createElement(
|
|
3292
|
+
'div',
|
|
3293
|
+
{
|
|
3294
|
+
style: { position: 'fixed', inset: 0, background: 'var(--dsw-alias-overlay-backdrop, color-mix(in srgb, var(--dsw-alias-bg-layer-0, black) 70%, transparent))', zIndex: 10000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '20px' },
|
|
3295
|
+
onClick: () => setSelected(null),
|
|
3296
|
+
},
|
|
3297
|
+
react.createElement(
|
|
3298
|
+
'div',
|
|
3299
|
+
{
|
|
3300
|
+
style: { background: 'var(--dsw-alias-bg-layer-3)', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: '12px', maxWidth: '580px', width: '100%', maxHeight: '90vh', overflowY: 'auto', padding: '18px', display: 'flex', flexDirection: 'column', gap: '12px' },
|
|
3301
|
+
onClick: (e) => e.stopPropagation(),
|
|
3302
|
+
},
|
|
3303
|
+
react.createElement(
|
|
3304
|
+
'div',
|
|
3305
|
+
{ style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
|
|
3306
|
+
react.createElement('span', { style: { fontWeight: '700', fontSize: '14px', color: 'var(--dsw-alias-label-primary)' } }, 'Asset Inspector'),
|
|
3307
|
+
react.createElement('button', { type: 'button', onClick: () => setSelected(null), style: { background: 'none', border: 'none', cursor: 'pointer', fontSize: '16px', color: 'var(--dsw-alias-label-secondary)' } }, '✕')
|
|
3308
|
+
),
|
|
3309
|
+
react.createElement('img', {
|
|
3310
|
+
src: selected.url || selected.thumbnailUrl,
|
|
3311
|
+
style: { width: '100%', borderRadius: '8px', maxHeight: '360px', objectFit: 'contain', background: 'var(--dsw-alias-bg-layer-2)' },
|
|
3312
|
+
}),
|
|
3313
|
+
react.createElement(
|
|
3314
|
+
'div',
|
|
3315
|
+
{ style: { display: 'flex', flexWrap: 'wrap', gap: '6px', fontSize: '11px' } },
|
|
3316
|
+
selected.seed !== undefined && react.createElement('span', { className: 'ig-badge' }, 'Seed: ' + selected.seed),
|
|
3317
|
+
selected.provider && react.createElement('span', { className: 'ig-badge' }, 'Provider: ' + selected.provider),
|
|
3318
|
+
selected.width && react.createElement('span', { className: 'ig-badge' }, selected.width + '×' + selected.height),
|
|
3319
|
+
selected.cost ? react.createElement('span', { className: 'ig-badge' }, '$' + Number(selected.cost).toFixed(4)) : null
|
|
3320
|
+
),
|
|
3321
|
+
react.createElement('div', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-primary)', background: 'var(--dsw-alias-bg-layer-2)', padding: '10px', borderRadius: '6px', lineHeight: '1.4' } }, selected.prompt || 'No prompt'),
|
|
3322
|
+
react.createElement(
|
|
3323
|
+
'div',
|
|
3324
|
+
{ style: { display: 'flex', gap: '8px', justifyContent: 'flex-end', marginTop: '6px' } },
|
|
3325
|
+
react.createElement('button', { type: 'button', className: 'ig-tab-btn', onClick: () => handleReroll(selected) }, '🎲 Re-roll'),
|
|
3326
|
+
react.createElement('button', { type: 'button', className: 'ig-tab-btn', onClick: () => handleInsert(selected) }, '💬 Insert in Chat'),
|
|
3327
|
+
react.createElement(
|
|
3328
|
+
'a',
|
|
3329
|
+
{ href: selected.url || selected.thumbnailUrl, download: 'vault-asset-' + (selected.id || 'image') + '.png', className: 'ig-tab-btn', style: { textDecoration: 'none', display: 'inline-flex', alignItems: 'center' } },
|
|
3330
|
+
'⬇ Download'
|
|
3331
|
+
),
|
|
3332
|
+
react.createElement('button', { type: 'button', className: 'ig-tab-btn', style: { color: 'var(--dsw-alias-state-error-primary)' }, onClick: () => handleDelete(selected.id || selected.attachmentId) }, '🗑️ Delete')
|
|
3333
|
+
)
|
|
3334
|
+
)
|
|
3335
|
+
)
|
|
3336
|
+
)
|
|
3337
|
+
}
|
|
3338
|
+
// -------------------------------------------------------------- Fullscreen Image Studio (#158)
|
|
3339
|
+
function ImageStudioView(props) {
|
|
3340
|
+
const { ctx } = props
|
|
3341
|
+
const [viewMode, setViewMode] = react.useState('studio') // 'studio' | 'vault'
|
|
3342
|
+
const [isFullscreen, setIsFullscreen] = react.useState(false)
|
|
3343
|
+
const [layoutGrid, setLayoutGrid] = react.useState('1x1') // '1x1' | '2x2' | '1x4'
|
|
3344
|
+
const [prompt, setPrompt] = react.useState(() => {
|
|
3345
|
+
try { return localStorage.getItem('dsh_studio_prompt') || '' } catch (_) { return '' }
|
|
3346
|
+
})
|
|
3347
|
+
const [stylePreset, setStylePreset] = react.useState('none')
|
|
3348
|
+
const [aspectRatio, setAspectRatio] = react.useState('1:1')
|
|
3349
|
+
const [provider, setProvider] = react.useState('fal')
|
|
3350
|
+
const [seed, setSeed] = react.useState('')
|
|
3351
|
+
const [activeImages, setActiveImages] = react.useState([])
|
|
3352
|
+
const [recentStrip, setRecentStrip] = react.useState([])
|
|
3353
|
+
const [status, setStatus] = react.useState('')
|
|
3354
|
+
|
|
3355
|
+
const presets = [
|
|
3356
|
+
{ id: 'none', label: 'Default' },
|
|
3357
|
+
{ id: 'photorealistic', label: 'Photo' },
|
|
3358
|
+
{ id: 'anime', label: 'Anime' },
|
|
3359
|
+
{ id: 'cyberpunk', label: 'Cyberpunk' },
|
|
3360
|
+
{ id: 'flat_vector', label: 'Vector' },
|
|
3361
|
+
{ id: '3d_render', label: '3D Render' },
|
|
3362
|
+
]
|
|
3363
|
+
|
|
3364
|
+
const refreshRecent = () => {
|
|
3365
|
+
fetch('/dsh-image-gen/vault?limit=12')
|
|
3366
|
+
.then((r) => r.json())
|
|
3367
|
+
.then((data) => {
|
|
3368
|
+
if (data && data.ok && Array.isArray(data.items)) {
|
|
3369
|
+
setRecentStrip(data.items)
|
|
3370
|
+
if (activeImages.length === 0 && data.items.length > 0) {
|
|
3371
|
+
setActiveImages([data.items[0]])
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
})
|
|
3375
|
+
.catch(() => {})
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
react.useEffect(() => {
|
|
3379
|
+
refreshRecent()
|
|
3380
|
+
}, [])
|
|
3381
|
+
|
|
3382
|
+
const updatePrompt = (val) => {
|
|
3383
|
+
setPrompt(val)
|
|
3384
|
+
try { localStorage.setItem('dsh_studio_prompt', val) } catch (_) { /* storage unavailable */ }
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
const handleSelectFromVault = (asset) => {
|
|
3388
|
+
if (!asset) return
|
|
3389
|
+
setActiveImages([asset])
|
|
3390
|
+
if (asset.prompt) updatePrompt(asset.prompt)
|
|
3391
|
+
if (asset.seed !== undefined) setSeed(String(asset.seed))
|
|
3392
|
+
if (asset.provider) setProvider(asset.provider)
|
|
3393
|
+
setViewMode('studio')
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
const handleCopySnippet = (format) => {
|
|
3397
|
+
const first = activeImages[0]
|
|
3398
|
+
if (!first) return
|
|
3399
|
+
const url = first.url || first.thumbnailUrl
|
|
3400
|
+
let snippet = ''
|
|
3401
|
+
if (format === 'markdown') snippet = ''
|
|
3402
|
+
if (format === 'html') snippet = '<img src="' + url + '" alt="' + (first.prompt || 'Artwork') + '" />'
|
|
3403
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
3404
|
+
navigator.clipboard.writeText(snippet)
|
|
3405
|
+
setStatus('Copied ' + format.toUpperCase() + ' snippet!')
|
|
3406
|
+
setTimeout(() => setStatus(''), 2000)
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
const containerStyle = isFullscreen
|
|
3411
|
+
? { position: 'fixed', inset: 0, zIndex: 99999, background: 'var(--dsw-alias-bg-layer-1)', padding: '20px', overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: '14px' }
|
|
3412
|
+
: { display: 'flex', flexDirection: 'column', gap: '14px', width: '100%' }
|
|
3413
|
+
|
|
3414
|
+
return react.createElement(
|
|
3415
|
+
'div',
|
|
3416
|
+
{ className: 'ig-studio-container', style: containerStyle },
|
|
3417
|
+
react.createElement(
|
|
3418
|
+
'div',
|
|
3419
|
+
{ style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '10px', borderBottom: '1px solid var(--dsw-alias-border-l2)', paddingBottom: '10px' } },
|
|
3420
|
+
react.createElement(
|
|
3421
|
+
'div',
|
|
3422
|
+
{ style: { display: 'flex', gap: '8px', alignItems: 'center' } },
|
|
3423
|
+
react.createElement('span', { style: { fontWeight: '700', fontSize: '15px', color: 'var(--dsw-alias-label-primary)' } }, '🎨 Image Studio'),
|
|
3424
|
+
react.createElement(
|
|
3425
|
+
'button',
|
|
3426
|
+
{
|
|
3427
|
+
type: 'button',
|
|
3428
|
+
className: 'ig-tab-btn ' + (viewMode === 'studio' ? 'active' : ''),
|
|
3429
|
+
onClick: () => setViewMode('studio'),
|
|
3430
|
+
},
|
|
3431
|
+
'Studio Canvas'
|
|
3432
|
+
),
|
|
3433
|
+
react.createElement(
|
|
3434
|
+
'button',
|
|
3435
|
+
{
|
|
3436
|
+
type: 'button',
|
|
3437
|
+
className: 'ig-tab-btn ' + (viewMode === 'vault' ? 'active' : ''),
|
|
3438
|
+
onClick: () => setViewMode('vault'),
|
|
3439
|
+
},
|
|
3440
|
+
'🗃️ Asset Vault'
|
|
3441
|
+
)
|
|
3442
|
+
),
|
|
3443
|
+
react.createElement(
|
|
3444
|
+
'div',
|
|
3445
|
+
{ style: { display: 'flex', gap: '8px', alignItems: 'center' } },
|
|
3446
|
+
viewMode === 'studio' &&
|
|
3447
|
+
react.createElement(
|
|
3448
|
+
'div',
|
|
3449
|
+
{ style: { display: 'flex', gap: '4px' } },
|
|
3450
|
+
['1x1', '2x2', '1x4'].map((grid) =>
|
|
3451
|
+
react.createElement(
|
|
3452
|
+
'button',
|
|
3453
|
+
{
|
|
3454
|
+
key: grid,
|
|
3455
|
+
type: 'button',
|
|
3456
|
+
className: 'ig-tab-btn ' + (layoutGrid === grid ? 'active' : ''),
|
|
3457
|
+
onClick: () => setLayoutGrid(grid),
|
|
3458
|
+
title: 'Grid layout: ' + grid,
|
|
3459
|
+
},
|
|
3460
|
+
grid
|
|
3461
|
+
)
|
|
3462
|
+
)
|
|
3463
|
+
),
|
|
3464
|
+
react.createElement(
|
|
3465
|
+
'button',
|
|
3466
|
+
{
|
|
3467
|
+
type: 'button',
|
|
3468
|
+
className: 'ig-tab-btn',
|
|
3469
|
+
onClick: () => setIsFullscreen(!isFullscreen),
|
|
3470
|
+
style: { border: '1px solid var(--dsw-alias-border-l2)' },
|
|
3471
|
+
},
|
|
3472
|
+
isFullscreen ? '✕ Exit Fullscreen' : '⛶ Fullscreen'
|
|
3473
|
+
)
|
|
3474
|
+
)
|
|
3475
|
+
),
|
|
3476
|
+
viewMode === 'vault'
|
|
3477
|
+
? react.createElement(AssetVaultView, { ctx, onSelectAsset: handleSelectFromVault })
|
|
3478
|
+
: react.createElement(
|
|
3479
|
+
react.Fragment,
|
|
3480
|
+
null,
|
|
3481
|
+
react.createElement(
|
|
3482
|
+
'div',
|
|
3483
|
+
{ className: 'ig-studio-layout' },
|
|
3484
|
+
react.createElement(
|
|
3485
|
+
'div',
|
|
3486
|
+
{ className: 'ig-studio-sidebar' },
|
|
3487
|
+
react.createElement('span', { style: { fontWeight: '600', fontSize: '13px', color: 'var(--dsw-alias-label-primary)' } }, 'Generation Parameters'),
|
|
3488
|
+
react.createElement(
|
|
3489
|
+
'div',
|
|
3490
|
+
null,
|
|
3491
|
+
react.createElement('label', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary)', display: 'block', marginBottom: '4px' } }, 'Prompt:'),
|
|
3492
|
+
react.createElement('textarea', {
|
|
3493
|
+
className: 'ig-input',
|
|
3494
|
+
rows: 4,
|
|
3495
|
+
style: { width: '100%', resize: 'vertical', borderRadius: '6px', padding: '8px', fontSize: '12px' },
|
|
3496
|
+
placeholder: 'Describe your vision...',
|
|
3497
|
+
value: prompt,
|
|
3498
|
+
onChange: (e) => updatePrompt(e.target.value),
|
|
3499
|
+
})
|
|
3500
|
+
),
|
|
3501
|
+
react.createElement(
|
|
3502
|
+
'div',
|
|
3503
|
+
null,
|
|
3504
|
+
react.createElement('label', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary)', display: 'block', marginBottom: '4px' } }, 'Style Preset:'),
|
|
3505
|
+
react.createElement(
|
|
3506
|
+
'div',
|
|
3507
|
+
{ style: { display: 'flex', flexWrap: 'wrap', gap: '4px' } },
|
|
3508
|
+
presets.map((p) =>
|
|
3509
|
+
react.createElement(
|
|
3510
|
+
'button',
|
|
3511
|
+
{
|
|
3512
|
+
key: p.id,
|
|
3513
|
+
type: 'button',
|
|
3514
|
+
className: 'ig-tab-btn ' + (stylePreset === p.id ? 'active' : ''),
|
|
3515
|
+
style: { fontSize: '11px', padding: '3px 8px' },
|
|
3516
|
+
onClick: () => setStylePreset(p.id),
|
|
3517
|
+
},
|
|
3518
|
+
p.label
|
|
3519
|
+
)
|
|
3520
|
+
)
|
|
3521
|
+
)
|
|
3522
|
+
),
|
|
3523
|
+
react.createElement(
|
|
3524
|
+
'div',
|
|
3525
|
+
{ style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px' } },
|
|
3526
|
+
react.createElement(
|
|
3527
|
+
'div',
|
|
3528
|
+
null,
|
|
3529
|
+
react.createElement('label', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary)', display: 'block', marginBottom: '4px' } }, 'Aspect Ratio:'),
|
|
3530
|
+
react.createElement(
|
|
3531
|
+
'select',
|
|
3532
|
+
{
|
|
3533
|
+
className: 'ig-select',
|
|
3534
|
+
style: { width: '100%', padding: '6px', fontSize: '12px' },
|
|
3535
|
+
value: aspectRatio,
|
|
3536
|
+
onChange: (e) => setAspectRatio(e.target.value),
|
|
3537
|
+
},
|
|
3538
|
+
react.createElement('option', { value: '1:1' }, '1:1 Square'),
|
|
3539
|
+
react.createElement('option', { value: '16:9' }, '16:9 Landscape'),
|
|
3540
|
+
react.createElement('option', { value: '9:16' }, '9:16 Portrait'),
|
|
3541
|
+
react.createElement('option', { value: '4:3' }, '4:3 Standard'),
|
|
3542
|
+
react.createElement('option', { value: '21:9' }, '21:9 Ultrawide')
|
|
3543
|
+
)
|
|
3544
|
+
),
|
|
3545
|
+
react.createElement(
|
|
3546
|
+
'div',
|
|
3547
|
+
null,
|
|
3548
|
+
react.createElement('label', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary)', display: 'block', marginBottom: '4px' } }, 'Provider:'),
|
|
3549
|
+
react.createElement(
|
|
3550
|
+
'select',
|
|
3551
|
+
{
|
|
3552
|
+
className: 'ig-select',
|
|
3553
|
+
style: { width: '100%', padding: '6px', fontSize: '12px' },
|
|
3554
|
+
value: provider,
|
|
3555
|
+
onChange: (e) => setProvider(e.target.value),
|
|
3556
|
+
},
|
|
3557
|
+
react.createElement('option', { value: 'fal' }, 'FAL.ai'),
|
|
3558
|
+
react.createElement('option', { value: 'openai' }, 'OpenAI'),
|
|
3559
|
+
react.createElement('option', { value: 'local' }, 'Local (ComfyUI)')
|
|
3560
|
+
)
|
|
3561
|
+
)
|
|
3562
|
+
),
|
|
3563
|
+
react.createElement(
|
|
3564
|
+
'div',
|
|
3565
|
+
null,
|
|
3566
|
+
react.createElement('label', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary)', display: 'block', marginBottom: '4px' } }, 'Seed (optional):'),
|
|
3567
|
+
react.createElement(
|
|
3568
|
+
'div',
|
|
3569
|
+
{ style: { display: 'flex', gap: '6px' } },
|
|
3570
|
+
react.createElement('input', {
|
|
3571
|
+
type: 'number',
|
|
3572
|
+
className: 'ig-input',
|
|
3573
|
+
style: { flex: 1, padding: '6px', fontSize: '12px' },
|
|
3574
|
+
placeholder: 'Random',
|
|
3575
|
+
value: seed,
|
|
3576
|
+
onChange: (e) => setSeed(e.target.value),
|
|
3577
|
+
}),
|
|
3578
|
+
react.createElement(
|
|
3579
|
+
'button',
|
|
3580
|
+
{
|
|
3581
|
+
type: 'button',
|
|
3582
|
+
className: 'ig-tab-btn',
|
|
3583
|
+
onClick: () => setSeed(String(Math.floor(Math.random() * 2147483647))),
|
|
3584
|
+
title: 'Randomize seed',
|
|
3585
|
+
},
|
|
3586
|
+
'🎲'
|
|
3587
|
+
)
|
|
3588
|
+
)
|
|
3589
|
+
),
|
|
3590
|
+
react.createElement(
|
|
3591
|
+
'button',
|
|
3592
|
+
{
|
|
3593
|
+
type: 'button',
|
|
3594
|
+
className: 'ig-save-btn',
|
|
3595
|
+
style: { marginTop: '8px', padding: '10px' },
|
|
3596
|
+
onClick: () => {
|
|
3597
|
+
const cmd = '/image ' + (prompt || 'Artwork') + (stylePreset !== 'none' ? ' --style ' + stylePreset : '') + ' --aspect ' + aspectRatio + ' --provider ' + provider + (seed ? ' --seed ' + seed : '')
|
|
3598
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
3599
|
+
navigator.clipboard.writeText(cmd)
|
|
3600
|
+
setStatus('Copied generation command to clipboard!')
|
|
3601
|
+
setTimeout(() => setStatus(''), 2500)
|
|
3602
|
+
}
|
|
3603
|
+
},
|
|
3604
|
+
},
|
|
3605
|
+
'🎨 Generate in Chat'
|
|
3606
|
+
),
|
|
3607
|
+
status && react.createElement('div', { style: { fontSize: '11px', color: 'var(--dsw-alias-state-brand-primary)', textAlign: 'center' } }, status)
|
|
3608
|
+
),
|
|
3609
|
+
react.createElement(
|
|
3610
|
+
'div',
|
|
3611
|
+
{ className: 'ig-studio-canvas' },
|
|
3612
|
+
activeImages.length === 0
|
|
3613
|
+
? react.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary)', fontSize: '13px' } }, 'Select an image from the recent strip below or click Generate.')
|
|
3614
|
+
: react.createElement(
|
|
3615
|
+
'div',
|
|
3616
|
+
{
|
|
3617
|
+
style: {
|
|
3618
|
+
display: 'grid',
|
|
3619
|
+
gridTemplateColumns: layoutGrid === '2x2' ? 'repeat(2, 1fr)' : layoutGrid === '1x4' ? 'repeat(4, 1fr)' : '1fr',
|
|
3620
|
+
gap: '12px',
|
|
3621
|
+
width: '100%',
|
|
3622
|
+
maxHeight: '440px',
|
|
3623
|
+
overflowY: 'auto',
|
|
3624
|
+
},
|
|
3625
|
+
},
|
|
3626
|
+
(layoutGrid === '1x1' ? activeImages.slice(0, 1) : activeImages.slice(0, layoutGrid === '2x2' ? 4 : 4)).map((img, i) =>
|
|
3627
|
+
react.createElement('img', {
|
|
3628
|
+
key: img.id || i,
|
|
3629
|
+
src: img.url || img.thumbnailUrl,
|
|
3630
|
+
style: { width: '100%', height: 'auto', maxHeight: '400px', objectFit: 'contain', borderRadius: '8px', background: 'var(--dsw-alias-bg-layer-3)' },
|
|
3631
|
+
})
|
|
3632
|
+
)
|
|
3633
|
+
),
|
|
3634
|
+
activeImages.length > 0 &&
|
|
3635
|
+
react.createElement(
|
|
3636
|
+
'div',
|
|
3637
|
+
{ style: { display: 'flex', gap: '8px', marginTop: '10px' } },
|
|
3638
|
+
react.createElement('button', { type: 'button', className: 'ig-tab-btn', onClick: () => handleCopySnippet('markdown') }, 'Copy Markdown'),
|
|
3639
|
+
react.createElement('button', { type: 'button', className: 'ig-tab-btn', onClick: () => handleCopySnippet('html') }, 'Copy HTML'),
|
|
3640
|
+
react.createElement(
|
|
3641
|
+
'a',
|
|
3642
|
+
{ href: activeImages[0].url || activeImages[0].thumbnailUrl, download: 'studio-image.png', className: 'ig-tab-btn', style: { textDecoration: 'none' } },
|
|
3643
|
+
'⬇ Download'
|
|
3644
|
+
)
|
|
3645
|
+
)
|
|
3646
|
+
)
|
|
3647
|
+
),
|
|
3648
|
+
react.createElement(
|
|
3649
|
+
'div',
|
|
3650
|
+
{ style: { display: 'flex', flexDirection: 'column', gap: '6px', marginTop: '10px' } },
|
|
3651
|
+
react.createElement('span', { style: { fontSize: '12px', fontWeight: '600', color: 'var(--dsw-alias-label-secondary)' } }, 'Recent Generations:'),
|
|
3652
|
+
react.createElement(
|
|
3653
|
+
'div',
|
|
3654
|
+
{ className: 'ig-studio-strip', style: { display: 'flex', gap: '8px', overflowX: 'auto', padding: '4px 0' } },
|
|
3655
|
+
recentStrip.map((item) =>
|
|
3656
|
+
react.createElement('img', {
|
|
3657
|
+
key: item.id || item.attachmentId,
|
|
3658
|
+
src: item.thumbnailUrl || item.url,
|
|
3659
|
+
className: 'ig-studio-strip-thumb',
|
|
3660
|
+
style: { width: '64px', height: '64px', borderRadius: '6px', objectFit: 'cover', cursor: 'pointer', border: '1px solid var(--dsw-alias-border-l2)', flexShrink: 0 },
|
|
3661
|
+
title: item.prompt || 'Artwork',
|
|
3662
|
+
onClick: () => handleSelectFromVault(item),
|
|
3663
|
+
})
|
|
3664
|
+
)
|
|
3665
|
+
)
|
|
3666
|
+
)
|
|
3667
|
+
)
|
|
3668
|
+
)
|
|
3669
|
+
}
|
|
2689
3670
|
exports.apply = apply
|
|
2690
3671
|
exports.inject = inject
|
|
2691
3672
|
return module.exports
|