@goodandready/dsh-image-gen 0.10.19 → 0.10.21
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 +7 -0
- package/README.ru.md +7 -0
- package/README.zh.md +7 -0
- package/lib/client.js +247 -940
- package/lib/tools/editing.js +149 -0
- package/lib/tools/processing.js +358 -0
- package/package.json +3 -3
package/lib/client.js
CHANGED
|
@@ -764,7 +764,137 @@ window.__ModuleLoader__.load({
|
|
|
764
764
|
selected.seed !== undefined && react.createElement('span', { className: 'ig-badge' }, 'Seed: ' + selected.seed),
|
|
765
765
|
selected.provider && react.createElement('span', { className: 'ig-badge' }, 'Provider: ' + selected.provider),
|
|
766
766
|
selected.width && react.createElement('span', { className: 'ig-badge' }, selected.width + '×' + selected.height),
|
|
767
|
-
selected.cost ? react.createElement('span', { className: 'ig-badge' }, '
|
|
767
|
+
selected.cost ? react.createElement('span', { className: 'ig-badge' }, '$' + Number(selected.cost).toFixed(4)) : null
|
|
768
|
+
),
|
|
769
|
+
react.createElement(
|
|
770
|
+
'div',
|
|
771
|
+
{ style: { display: 'flex', flexDirection: 'column', gap: '4px' } },
|
|
772
|
+
react.createElement('span', { style: { fontSize: '11px', fontWeight: '600', color: 'var(--dsw-alias-label-secondary)' } }, 'Prompt:'),
|
|
773
|
+
react.createElement(
|
|
774
|
+
'p',
|
|
775
|
+
{
|
|
776
|
+
style: {
|
|
777
|
+
fontSize: '12px',
|
|
778
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
779
|
+
lineHeight: '1.4',
|
|
780
|
+
background: 'var(--dsw-alias-bg-layer-2)',
|
|
781
|
+
padding: '8px',
|
|
782
|
+
borderRadius: '6px',
|
|
783
|
+
margin: 0,
|
|
784
|
+
},
|
|
785
|
+
},
|
|
786
|
+
selected.prompt || 'No prompt recorded'
|
|
787
|
+
)
|
|
788
|
+
),
|
|
789
|
+
react.createElement(
|
|
790
|
+
'div',
|
|
791
|
+
{ style: { display: 'flex', gap: '8px', justifyContent: 'flex-end', marginTop: '4px' } },
|
|
792
|
+
react.createElement(
|
|
793
|
+
'button',
|
|
794
|
+
{
|
|
795
|
+
type: 'button',
|
|
796
|
+
className: 'ig-tab-btn',
|
|
797
|
+
style: { border: '1px solid var(--dsw-alias-border-l2)' },
|
|
798
|
+
onClick: () => {
|
|
799
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard && selected.prompt) {
|
|
800
|
+
navigator.clipboard.writeText(selected.prompt)
|
|
801
|
+
setCopiedPrompt(true)
|
|
802
|
+
setTimeout(() => setCopiedPrompt(false), 2000)
|
|
803
|
+
}
|
|
804
|
+
},
|
|
805
|
+
},
|
|
806
|
+
copiedPrompt ? '✓ Copied!' : t('gallery.copy_prompt')
|
|
807
|
+
),
|
|
808
|
+
react.createElement(
|
|
809
|
+
'button',
|
|
810
|
+
{
|
|
811
|
+
type: 'button',
|
|
812
|
+
className: 'ig-save-btn',
|
|
813
|
+
onClick: () => {
|
|
814
|
+
const url = selected.thumbnailUrl || (selected.attachmentId ? '/dsh-image-gen/image?id=' + encodeURIComponent(selected.attachmentId) : '')
|
|
815
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard && url) {
|
|
816
|
+
navigator.clipboard.writeText('')
|
|
817
|
+
setCopiedLink(true)
|
|
818
|
+
setTimeout(() => setCopiedLink(false), 2000)
|
|
819
|
+
}
|
|
820
|
+
},
|
|
821
|
+
},
|
|
822
|
+
copiedLink ? '✓ Copied!' : t('gallery.copy_link')
|
|
823
|
+
)
|
|
824
|
+
)
|
|
825
|
+
)
|
|
826
|
+
)
|
|
827
|
+
)
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// -------------------------------------------------------------- Header Quick-Access Chip
|
|
831
|
+
function GalleryHeaderChip(props) {
|
|
832
|
+
const [open, setOpen] = react.useState(false)
|
|
833
|
+
return react.createElement(
|
|
834
|
+
'div',
|
|
835
|
+
{ style: { display: 'inline-flex', alignItems: 'center', position: 'relative' } },
|
|
836
|
+
react.createElement(
|
|
837
|
+
'button',
|
|
838
|
+
{
|
|
839
|
+
type: 'button',
|
|
840
|
+
title: 'Image Studio Gallery',
|
|
841
|
+
onClick: () => setOpen(!open),
|
|
842
|
+
style: {
|
|
843
|
+
appearance: 'none',
|
|
844
|
+
background: 'none',
|
|
845
|
+
border: '1px solid var(--dsw-alias-border-l2)',
|
|
846
|
+
borderRadius: '6px',
|
|
847
|
+
padding: '4px 8px',
|
|
848
|
+
cursor: 'pointer',
|
|
849
|
+
display: 'flex',
|
|
850
|
+
alignItems: 'center',
|
|
851
|
+
gap: '5px',
|
|
852
|
+
fontSize: '12px',
|
|
853
|
+
color: 'var(--dsw-alias-label-secondary)',
|
|
854
|
+
},
|
|
855
|
+
},
|
|
856
|
+
react.createElement('span', null, '🖼️'),
|
|
857
|
+
react.createElement('span', null, 'Gallery')
|
|
858
|
+
),
|
|
859
|
+
open &&
|
|
860
|
+
react.createElement(
|
|
861
|
+
'div',
|
|
862
|
+
{
|
|
863
|
+
style: {
|
|
864
|
+
position: 'fixed',
|
|
865
|
+
top: '56px',
|
|
866
|
+
right: '16px',
|
|
867
|
+
width: '380px',
|
|
868
|
+
maxHeight: '520px',
|
|
869
|
+
background: 'var(--dsw-alias-bg-layer-3)',
|
|
870
|
+
border: '1px solid var(--dsw-alias-border-l2)',
|
|
871
|
+
borderRadius: '12px',
|
|
872
|
+
boxShadow: '0 8px 32px rgba(0,0,0,0.4)',
|
|
873
|
+
zIndex: 9999,
|
|
874
|
+
padding: '16px',
|
|
875
|
+
overflowY: 'auto',
|
|
876
|
+
},
|
|
877
|
+
},
|
|
878
|
+
react.createElement(
|
|
879
|
+
'div',
|
|
880
|
+
{ style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '10px' } },
|
|
881
|
+
react.createElement('span', { style: { fontWeight: '700', fontSize: '14px', color: 'var(--dsw-alias-label-primary)' } }, 'Image Studio Gallery'),
|
|
882
|
+
react.createElement(
|
|
883
|
+
'button',
|
|
884
|
+
{
|
|
885
|
+
type: 'button',
|
|
886
|
+
onClick: () => setOpen(false),
|
|
887
|
+
style: { background: 'none', border: 'none', cursor: 'pointer', fontSize: '14px', color: 'var(--dsw-alias-label-secondary)' },
|
|
888
|
+
},
|
|
889
|
+
'✕'
|
|
890
|
+
)
|
|
891
|
+
),
|
|
892
|
+
react.createElement(GalleryView, { ctx: props.ctx })
|
|
893
|
+
)
|
|
894
|
+
)
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function FalSettingsCard(props) {
|
|
768
898
|
const t = props.t || ((k) => k)
|
|
769
899
|
const [open, setOpen] = react.useState(props.defaultOpen ?? true)
|
|
770
900
|
const [activeTab, setActiveTab] = react.useState('general')
|
|
@@ -1009,6 +1139,10 @@ window.__ModuleLoader__.load({
|
|
|
1009
1139
|
const failed = block && (block.isError || block.error !== undefined)
|
|
1010
1140
|
const parsed = readResult(block)
|
|
1011
1141
|
const t = props.t || ((k) => k)
|
|
1142
|
+
const [remixOpen, setRemixOpen] = react.useState(false)
|
|
1143
|
+
const [remixStrength, setRemixStrength] = react.useState(0.45)
|
|
1144
|
+
const [remixPrompt, setRemixPrompt] = react.useState('')
|
|
1145
|
+
const [copiedRemix, setCopiedRemix] = react.useState(false)
|
|
1012
1146
|
|
|
1013
1147
|
react.useEffect(() => {
|
|
1014
1148
|
ensureCss()
|
|
@@ -1098,6 +1232,7 @@ window.__ModuleLoader__.load({
|
|
|
1098
1232
|
const actions = []
|
|
1099
1233
|
if (!running && !failed && (parsed.url || parsed.attachment)) {
|
|
1100
1234
|
actions.push(
|
|
1235
|
+
react.createElement('button', { key: 'remix', type: 'button', className: 'ig-btn', onClick: () => setRemixOpen(!remixOpen) }, '⚡ ' + (t('card.remix') || 'Remix')),
|
|
1101
1236
|
react.createElement('button', { key: 'reroll', type: 'button', className: 'ig-btn', onClick: onReroll }, '🎲 ' + t('card.reroll')),
|
|
1102
1237
|
react.createElement('button', { key: 'upscale', type: 'button', className: 'ig-btn', onClick: onUpscale }, '🔍 ' + t('card.upscale')),
|
|
1103
1238
|
react.createElement('button', { key: 'removeBg', type: 'button', className: 'ig-btn', onClick: onRemoveBg }, '✂️ ' + t('card.removeBg')),
|
|
@@ -1105,12 +1240,94 @@ window.__ModuleLoader__.load({
|
|
|
1105
1240
|
)
|
|
1106
1241
|
}
|
|
1107
1242
|
|
|
1243
|
+
const remixDrawer = remixOpen ? react.createElement(
|
|
1244
|
+
'div',
|
|
1245
|
+
{
|
|
1246
|
+
style: {
|
|
1247
|
+
marginTop: '10px',
|
|
1248
|
+
padding: '12px 14px',
|
|
1249
|
+
borderRadius: '8px',
|
|
1250
|
+
border: '1px solid var(--dsw-alias-border-l2)',
|
|
1251
|
+
background: 'var(--dsw-alias-bg-layer-2)',
|
|
1252
|
+
display: 'flex',
|
|
1253
|
+
flexDirection: 'column',
|
|
1254
|
+
gap: '8px',
|
|
1255
|
+
},
|
|
1256
|
+
},
|
|
1257
|
+
react.createElement(
|
|
1258
|
+
'div',
|
|
1259
|
+
{ style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
|
|
1260
|
+
react.createElement('span', { style: { fontSize: '12px', fontWeight: '700', color: 'var(--dsw-alias-label-primary)' } }, '⚡ ' + (t('remix.drawer_title') || 'Variations & Style Remix Workbench')),
|
|
1261
|
+
react.createElement('span', { style: { fontSize: '11px', color: 'var(--dsw-alias-state-brand)' } }, 'Creativity: ' + remixStrength)
|
|
1262
|
+
),
|
|
1263
|
+
react.createElement('input', {
|
|
1264
|
+
type: 'range',
|
|
1265
|
+
min: '0.1',
|
|
1266
|
+
max: '0.9',
|
|
1267
|
+
step: '0.05',
|
|
1268
|
+
value: remixStrength,
|
|
1269
|
+
onChange: (e) => setRemixStrength(parseFloat(e.target.value)),
|
|
1270
|
+
style: { width: '100%', cursor: 'pointer' },
|
|
1271
|
+
}),
|
|
1272
|
+
react.createElement('input', {
|
|
1273
|
+
type: 'text',
|
|
1274
|
+
placeholder: t('remix.prompt_placeholder') || 'Describe remix changes (e.g. cyberpunk neon, watercolor)...',
|
|
1275
|
+
value: remixPrompt,
|
|
1276
|
+
onChange: (e) => setRemixPrompt(e.target.value),
|
|
1277
|
+
className: 'ig-field-input',
|
|
1278
|
+
style: { fontSize: '12px', padding: '6px 10px' },
|
|
1279
|
+
}),
|
|
1280
|
+
react.createElement(
|
|
1281
|
+
'div',
|
|
1282
|
+
{ style: { display: 'flex', gap: '6px', justifyContent: 'flex-end', marginTop: '4px' } },
|
|
1283
|
+
react.createElement(
|
|
1284
|
+
'button',
|
|
1285
|
+
{
|
|
1286
|
+
type: 'button',
|
|
1287
|
+
className: 'ig-tab-btn',
|
|
1288
|
+
style: { padding: '4px 8px', fontSize: '11px' },
|
|
1289
|
+
onClick: () => setRemixStrength(0.25),
|
|
1290
|
+
},
|
|
1291
|
+
t('remix.subtle_btn') || 'Subtle (0.25)'
|
|
1292
|
+
),
|
|
1293
|
+
react.createElement(
|
|
1294
|
+
'button',
|
|
1295
|
+
{
|
|
1296
|
+
type: 'button',
|
|
1297
|
+
className: 'ig-tab-btn',
|
|
1298
|
+
style: { padding: '4px 8px', fontSize: '11px' },
|
|
1299
|
+
onClick: () => setRemixStrength(0.65),
|
|
1300
|
+
},
|
|
1301
|
+
t('remix.creative_btn') || 'Creative (0.65)'
|
|
1302
|
+
),
|
|
1303
|
+
react.createElement(
|
|
1304
|
+
'button',
|
|
1305
|
+
{
|
|
1306
|
+
type: 'button',
|
|
1307
|
+
className: 'ig-save-btn',
|
|
1308
|
+
style: { padding: '4px 10px', fontSize: '11px' },
|
|
1309
|
+
onClick: () => {
|
|
1310
|
+
const targetRef = parsed.attachment?.attachmentId || parsed.url || 'current image'
|
|
1311
|
+
const cmd = `Use remix_image on "${targetRef}" with prompt: "${remixPrompt || 'variation of this image'}" and creativity: ${remixStrength}`
|
|
1312
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
1313
|
+
navigator.clipboard.writeText(cmd)
|
|
1314
|
+
setCopiedRemix(true)
|
|
1315
|
+
setTimeout(() => setCopiedRemix(false), 2000)
|
|
1316
|
+
}
|
|
1317
|
+
},
|
|
1318
|
+
},
|
|
1319
|
+
copiedRemix ? (t('remix.copied') || '✓ Copied!') : (t('remix.copy_instruction') || '📋 Copy Instruction')
|
|
1320
|
+
)
|
|
1321
|
+
)
|
|
1322
|
+
) : null
|
|
1323
|
+
|
|
1108
1324
|
return react.createElement(
|
|
1109
1325
|
'div',
|
|
1110
1326
|
{ className: 'ig-section-card', style: { margin: '8px 0' } },
|
|
1111
1327
|
head,
|
|
1112
1328
|
body,
|
|
1113
|
-
actions.length > 0 ? react.createElement('div', { className: 'ig-row', style: { marginTop: '8px' } }, actions) : null
|
|
1329
|
+
actions.length > 0 ? react.createElement('div', { className: 'ig-row', style: { marginTop: '8px' } }, actions) : null,
|
|
1330
|
+
remixDrawer
|
|
1114
1331
|
)
|
|
1115
1332
|
}
|
|
1116
1333
|
|
|
@@ -1158,6 +1375,14 @@ window.__ModuleLoader__.load({
|
|
|
1158
1375
|
'card.removeBg': 'Remove BG',
|
|
1159
1376
|
'card.copyPrompt': 'Copy Prompt',
|
|
1160
1377
|
'card.origPrompt': 'Original Prompt',
|
|
1378
|
+
'card.remix': 'Remix',
|
|
1379
|
+
'remix.drawer_title': 'Variations & Style Remix Workbench',
|
|
1380
|
+
'remix.creativity_label': 'Creativity / Denoising Strength',
|
|
1381
|
+
'remix.prompt_placeholder': 'Describe stylistic adjustments, new background, or variations...',
|
|
1382
|
+
'remix.subtle_btn': 'Subtle (0.25)',
|
|
1383
|
+
'remix.creative_btn': 'Creative (0.65)',
|
|
1384
|
+
'remix.copy_instruction': 'Copy Instruction',
|
|
1385
|
+
'remix.copied': '✓ Copied!',
|
|
1161
1386
|
|
|
1162
1387
|
'f.enabled': 'Master Switch',
|
|
1163
1388
|
'f.enabledHint': 'Enable or disable image generation tools across the workspace.',
|
|
@@ -1328,6 +1553,22 @@ window.__ModuleLoader__.load({
|
|
|
1328
1553
|
'card.save': '保存修改',
|
|
1329
1554
|
'card.discard': '放弃修改',
|
|
1330
1555
|
'card.failed': '保存失败,请检查填写内容。',
|
|
1556
|
+
'card.actionReroll': '重新生成图像',
|
|
1557
|
+
'card.actionUpscale': '放大图像',
|
|
1558
|
+
'card.actionRemoveBg': '移除背景',
|
|
1559
|
+
'card.reroll': '重绘',
|
|
1560
|
+
'card.upscale': '放大 2x/4x',
|
|
1561
|
+
'card.removeBg': '抠图背景',
|
|
1562
|
+
'card.copyPrompt': '复制提示词',
|
|
1563
|
+
'card.origPrompt': '原始提示词',
|
|
1564
|
+
'card.remix': '重混变体',
|
|
1565
|
+
'remix.drawer_title': '变体与风格重混工作台',
|
|
1566
|
+
'remix.creativity_label': '创意度 / 去噪强度',
|
|
1567
|
+
'remix.prompt_placeholder': '描述风格调整、新背景或变体细节...',
|
|
1568
|
+
'remix.subtle_btn': '微调 (0.25)',
|
|
1569
|
+
'remix.creative_btn': '强创意 (0.65)',
|
|
1570
|
+
'remix.copy_instruction': '复制指令',
|
|
1571
|
+
'remix.copied': '✓ 已复制!',
|
|
1331
1572
|
|
|
1332
1573
|
'settings.overridden': '已自定义',
|
|
1333
1574
|
'settings.reset': '重置',
|
|
@@ -1703,12 +1944,15 @@ window.__ModuleLoader__.load({
|
|
|
1703
1944
|
'generate_image',
|
|
1704
1945
|
'edit_image',
|
|
1705
1946
|
'vary_image',
|
|
1947
|
+
'remix_image',
|
|
1706
1948
|
'blend_images',
|
|
1707
1949
|
'generate_image_pack',
|
|
1708
1950
|
'remove_background',
|
|
1709
1951
|
'upscale_image',
|
|
1710
1952
|
'vectorize_image',
|
|
1711
1953
|
'assemble_image_grid',
|
|
1954
|
+
'smart_crop_image',
|
|
1955
|
+
'export_asset_pack',
|
|
1712
1956
|
]
|
|
1713
1957
|
|
|
1714
1958
|
for (const toolName of toolviewTools) {
|
|
@@ -1832,941 +2076,4 @@ window.__ModuleLoader__.load({
|
|
|
1832
2076
|
exports.inject = inject
|
|
1833
2077
|
return module.exports
|
|
1834
2078
|
},
|
|
1835
|
-
})
|
|
1836
|
-
+ Number(selected.cost).toFixed(4)) : null
|
|
1837
|
-
),
|
|
1838
|
-
react.createElement(
|
|
1839
|
-
'div',
|
|
1840
|
-
{ style: { display: 'flex', flexDirection: 'column', gap: '4px' } },
|
|
1841
|
-
react.createElement('span', { style: { fontSize: '11px', fontWeight: '600', color: 'var(--dsw-alias-label-secondary)' } }, 'Prompt:'),
|
|
1842
|
-
react.createElement(
|
|
1843
|
-
'p',
|
|
1844
|
-
{
|
|
1845
|
-
style: {
|
|
1846
|
-
fontSize: '12px',
|
|
1847
|
-
color: 'var(--dsw-alias-label-primary)',
|
|
1848
|
-
lineHeight: '1.4',
|
|
1849
|
-
background: 'var(--dsw-alias-bg-layer-2)',
|
|
1850
|
-
padding: '8px',
|
|
1851
|
-
borderRadius: '6px',
|
|
1852
|
-
margin: 0,
|
|
1853
|
-
},
|
|
1854
|
-
},
|
|
1855
|
-
selected.prompt || 'No prompt recorded'
|
|
1856
|
-
)
|
|
1857
|
-
),
|
|
1858
|
-
react.createElement(
|
|
1859
|
-
'div',
|
|
1860
|
-
{ style: { display: 'flex', gap: '8px', justifyContent: 'flex-end', marginTop: '4px' } },
|
|
1861
|
-
react.createElement(
|
|
1862
|
-
'button',
|
|
1863
|
-
{
|
|
1864
|
-
type: 'button',
|
|
1865
|
-
className: 'ig-tab-btn',
|
|
1866
|
-
style: { border: '1px solid var(--dsw-alias-border-l2)' },
|
|
1867
|
-
onClick: () => {
|
|
1868
|
-
if (typeof navigator !== 'undefined' && navigator.clipboard && selected.prompt) {
|
|
1869
|
-
navigator.clipboard.writeText(selected.prompt)
|
|
1870
|
-
setCopiedPrompt(true)
|
|
1871
|
-
setTimeout(() => setCopiedPrompt(false), 2000)
|
|
1872
|
-
}
|
|
1873
|
-
},
|
|
1874
|
-
},
|
|
1875
|
-
copiedPrompt ? '✓ Copied!' : t('gallery.copy_prompt')
|
|
1876
|
-
),
|
|
1877
|
-
react.createElement(
|
|
1878
|
-
'button',
|
|
1879
|
-
{
|
|
1880
|
-
type: 'button',
|
|
1881
|
-
className: 'ig-save-btn',
|
|
1882
|
-
onClick: () => {
|
|
1883
|
-
const url = selected.thumbnailUrl || (selected.attachmentId ? '/dsh-image-gen/image?id=' + encodeURIComponent(selected.attachmentId) : '')
|
|
1884
|
-
if (typeof navigator !== 'undefined' && navigator.clipboard && url) {
|
|
1885
|
-
navigator.clipboard.writeText('')
|
|
1886
|
-
setCopiedLink(true)
|
|
1887
|
-
setTimeout(() => setCopiedLink(false), 2000)
|
|
1888
|
-
}
|
|
1889
|
-
},
|
|
1890
|
-
},
|
|
1891
|
-
copiedLink ? '✓ Copied!' : t('gallery.copy_link')
|
|
1892
|
-
)
|
|
1893
|
-
)
|
|
1894
|
-
)
|
|
1895
|
-
)
|
|
1896
|
-
)
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
// -------------------------------------------------------------- Header Quick-Access Chip
|
|
1900
|
-
function GalleryHeaderChip(props) {
|
|
1901
|
-
const [open, setOpen] = react.useState(false)
|
|
1902
|
-
return react.createElement(
|
|
1903
|
-
'div',
|
|
1904
|
-
{ style: { display: 'inline-flex', alignItems: 'center', position: 'relative' } },
|
|
1905
|
-
react.createElement(
|
|
1906
|
-
'button',
|
|
1907
|
-
{
|
|
1908
|
-
type: 'button',
|
|
1909
|
-
title: 'Image Studio Gallery',
|
|
1910
|
-
onClick: () => setOpen(!open),
|
|
1911
|
-
style: {
|
|
1912
|
-
appearance: 'none',
|
|
1913
|
-
background: 'none',
|
|
1914
|
-
border: '1px solid var(--dsw-alias-border-l2)',
|
|
1915
|
-
borderRadius: '6px',
|
|
1916
|
-
padding: '4px 8px',
|
|
1917
|
-
cursor: 'pointer',
|
|
1918
|
-
display: 'flex',
|
|
1919
|
-
alignItems: 'center',
|
|
1920
|
-
gap: '5px',
|
|
1921
|
-
fontSize: '12px',
|
|
1922
|
-
color: 'var(--dsw-alias-label-secondary)',
|
|
1923
|
-
},
|
|
1924
|
-
},
|
|
1925
|
-
react.createElement('span', null, '🖼️'),
|
|
1926
|
-
react.createElement('span', null, 'Gallery')
|
|
1927
|
-
),
|
|
1928
|
-
open &&
|
|
1929
|
-
react.createElement(
|
|
1930
|
-
'div',
|
|
1931
|
-
{
|
|
1932
|
-
style: {
|
|
1933
|
-
position: 'fixed',
|
|
1934
|
-
top: '56px',
|
|
1935
|
-
right: '16px',
|
|
1936
|
-
width: '380px',
|
|
1937
|
-
maxHeight: '520px',
|
|
1938
|
-
background: 'var(--dsw-alias-bg-layer-3)',
|
|
1939
|
-
border: '1px solid var(--dsw-alias-border-l2)',
|
|
1940
|
-
borderRadius: '12px',
|
|
1941
|
-
boxShadow: '0 8px 32px rgba(0,0,0,0.4)',
|
|
1942
|
-
zIndex: 9999,
|
|
1943
|
-
padding: '16px',
|
|
1944
|
-
overflowY: 'auto',
|
|
1945
|
-
},
|
|
1946
|
-
},
|
|
1947
|
-
react.createElement(
|
|
1948
|
-
'div',
|
|
1949
|
-
{ style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '10px' } },
|
|
1950
|
-
react.createElement('span', { style: { fontWeight: '700', fontSize: '14px', color: 'var(--dsw-alias-label-primary)' } }, 'Image Studio Gallery'),
|
|
1951
|
-
react.createElement(
|
|
1952
|
-
'button',
|
|
1953
|
-
{
|
|
1954
|
-
type: 'button',
|
|
1955
|
-
onClick: () => setOpen(false),
|
|
1956
|
-
style: { background: 'none', border: 'none', cursor: 'pointer', fontSize: '14px', color: 'var(--dsw-alias-label-secondary)' },
|
|
1957
|
-
},
|
|
1958
|
-
'✕'
|
|
1959
|
-
)
|
|
1960
|
-
),
|
|
1961
|
-
react.createElement(GalleryView, { ctx: props.ctx })
|
|
1962
|
-
)
|
|
1963
|
-
)
|
|
1964
|
-
}
|
|
1965
|
-
|
|
1966
|
-
function FalSettingsCard(props) {
|
|
1967
|
-
const t = props.t || ((k) => k)
|
|
1968
|
-
const [open, setOpen] = react.useState(props.defaultOpen ?? true)
|
|
1969
|
-
const [activeTab, setActiveTab] = react.useState('general')
|
|
1970
|
-
|
|
1971
|
-
react.useEffect(() => {
|
|
1972
|
-
ensureCss()
|
|
1973
|
-
}, [])
|
|
1974
|
-
|
|
1975
|
-
const state = (typeof props.useFalSettingsCard === 'function'
|
|
1976
|
-
? props.useFalSettingsCard((s) => s)
|
|
1977
|
-
: null) || props.state || { available: true, writable: true, provider: { text: 'fal' } }
|
|
1978
|
-
|
|
1979
|
-
const disabled = !state.writable
|
|
1980
|
-
const currentProvider = (state.provider && state.provider.text) || 'fal'
|
|
1981
|
-
const blocked = !state.dirty || state.invalid || state.saving
|
|
1982
|
-
|
|
1983
|
-
const fieldProps = {
|
|
1984
|
-
overriddenLabel: t('settings.overridden'),
|
|
1985
|
-
resetLabel: t('settings.reset'),
|
|
1986
|
-
invalidLabel: t('settings.invalidNumber'),
|
|
1987
|
-
disabled,
|
|
1988
|
-
}
|
|
1989
|
-
|
|
1990
|
-
if (!state.available) {
|
|
1991
|
-
return react.createElement(
|
|
1992
|
-
'li',
|
|
1993
|
-
{ className: 'ig-section-card', style: { listStyle: 'none' } },
|
|
1994
|
-
react.createElement('p', { className: 'ig-field-hint' }, t('settings.unavailable'))
|
|
1995
|
-
)
|
|
1996
|
-
}
|
|
1997
|
-
|
|
1998
|
-
const tabs = [
|
|
1999
|
-
{ id: 'general', label: t('tab.general'), icon: '⚙️' },
|
|
2000
|
-
{ id: 'provider', label: t('tab.provider') + ' (' + currentProvider.toUpperCase() + ')', icon: '🔌' },
|
|
2001
|
-
{ id: 'enhancer', label: t('tab.enhancer'), icon: '✨' },
|
|
2002
|
-
{ id: 'safety', label: t('tab.safety'), icon: '🛡️' },
|
|
2003
|
-
{ id: 'cache', label: t('tab.cache'), icon: '⚡' },
|
|
2004
|
-
]
|
|
2005
|
-
|
|
2006
|
-
const currentTabFields = FIELDS.filter((entry) => {
|
|
2007
|
-
if (entry.tab !== activeTab) return false
|
|
2008
|
-
if (entry.when && !entry.when.includes(currentProvider)) return false
|
|
2009
|
-
return true
|
|
2010
|
-
})
|
|
2011
|
-
|
|
2012
|
-
const diskCacheOn = state.diskCache && state.diskCache.text !== 'false'
|
|
2013
|
-
const qualityGateOn = state.qualityGate && state.qualityGate.text !== 'false'
|
|
2014
|
-
const budgetText = (state.dailyBudgetUsd && state.dailyBudgetUsd.text) || '0'
|
|
2015
|
-
const loopLimit = (state.loopGuardLimit && state.loopGuardLimit.text) || '3'
|
|
2016
|
-
|
|
2017
|
-
return react.createElement(
|
|
2018
|
-
'li',
|
|
2019
|
-
{ className: 'ig-section-card', style: { listStyle: 'none', marginBottom: '12px' } },
|
|
2020
|
-
// Header
|
|
2021
|
-
react.createElement(
|
|
2022
|
-
'button',
|
|
2023
|
-
{
|
|
2024
|
-
type: 'button',
|
|
2025
|
-
style: {
|
|
2026
|
-
background: 'none',
|
|
2027
|
-
border: 'none',
|
|
2028
|
-
cursor: 'pointer',
|
|
2029
|
-
display: 'flex',
|
|
2030
|
-
alignItems: 'center',
|
|
2031
|
-
width: '100%',
|
|
2032
|
-
padding: 0,
|
|
2033
|
-
textAlign: 'left',
|
|
2034
|
-
},
|
|
2035
|
-
'aria-expanded': open,
|
|
2036
|
-
onClick: () => setOpen(!open),
|
|
2037
|
-
},
|
|
2038
|
-
react.createElement(
|
|
2039
|
-
'div',
|
|
2040
|
-
{ style: { flex: 1 } },
|
|
2041
|
-
react.createElement(
|
|
2042
|
-
'div',
|
|
2043
|
-
{ style: { fontWeight: 700, fontSize: '16px', display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
2044
|
-
'🎨 ' + t('settings.title')
|
|
2045
|
-
),
|
|
2046
|
-
react.createElement(
|
|
2047
|
-
'div',
|
|
2048
|
-
{ style: { fontSize: '13px', color: 'var(--dsw-alias-label-secondary)' } },
|
|
2049
|
-
t('settings.description')
|
|
2050
|
-
)
|
|
2051
|
-
),
|
|
2052
|
-
state.dirty
|
|
2053
|
-
? react.createElement('span', { className: 'ig-badge ig-badge-warn', style: { marginRight: '8px' } }, t('settings.unsaved'))
|
|
2054
|
-
: null,
|
|
2055
|
-
react.createElement(
|
|
2056
|
-
'span',
|
|
2057
|
-
{ style: { transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .16s' } },
|
|
2058
|
-
ChevronIconNode
|
|
2059
|
-
)
|
|
2060
|
-
),
|
|
2061
|
-
open
|
|
2062
|
-
? react.createElement(
|
|
2063
|
-
'div',
|
|
2064
|
-
{ className: 'ig-page' },
|
|
2065
|
-
// Quick Stats Row
|
|
2066
|
-
react.createElement(
|
|
2067
|
-
'div',
|
|
2068
|
-
{ className: 'ig-grid-4' },
|
|
2069
|
-
react.createElement(
|
|
2070
|
-
'div',
|
|
2071
|
-
{ className: 'ig-stat-box' },
|
|
2072
|
-
react.createElement('div', { className: 'ig-stat-val' }, currentProvider.toUpperCase()),
|
|
2073
|
-
react.createElement('div', { className: 'ig-stat-lbl' }, t('stat.active_provider'))
|
|
2074
|
-
),
|
|
2075
|
-
react.createElement(
|
|
2076
|
-
'div',
|
|
2077
|
-
{ className: 'ig-stat-box' },
|
|
2078
|
-
react.createElement('div', { className: 'ig-stat-val' }, ((state.defaultSize && state.defaultSize.text) || '4:3') + ' · ' + ((state.defaultFormat && state.defaultFormat.text) || 'png')),
|
|
2079
|
-
react.createElement('div', { className: 'ig-stat-lbl' }, t('stat.format_size'))
|
|
2080
|
-
),
|
|
2081
|
-
react.createElement(
|
|
2082
|
-
'div',
|
|
2083
|
-
{ className: 'ig-stat-box' },
|
|
2084
|
-
react.createElement(
|
|
2085
|
-
'div',
|
|
2086
|
-
{ className: 'ig-stat-val' },
|
|
2087
|
-
react.createElement(
|
|
2088
|
-
'span',
|
|
2089
|
-
{ className: qualityGateOn ? 'ig-badge ig-badge-ok' : 'ig-badge ig-badge-warn' },
|
|
2090
|
-
qualityGateOn ? 'Gate Active' : 'Off'
|
|
2091
|
-
),
|
|
2092
|
-
' ',
|
|
2093
|
-
react.createElement('span', { className: 'ig-badge ig-badge-ok' }, 'Guard ' + loopLimit)
|
|
2094
|
-
),
|
|
2095
|
-
react.createElement('div', { className: 'ig-stat-lbl' }, t('stat.safety_status'))
|
|
2096
|
-
),
|
|
2097
|
-
react.createElement(
|
|
2098
|
-
'div',
|
|
2099
|
-
{ className: 'ig-stat-box' },
|
|
2100
|
-
react.createElement(
|
|
2101
|
-
'div',
|
|
2102
|
-
{ className: 'ig-stat-val' },
|
|
2103
|
-
react.createElement(
|
|
2104
|
-
'span',
|
|
2105
|
-
{ className: diskCacheOn ? 'ig-badge ig-badge-ok' : 'ig-badge ig-badge-warn' },
|
|
2106
|
-
diskCacheOn ? 'Cache ON' : 'Off'
|
|
2107
|
-
),
|
|
2108
|
-
' ',
|
|
2109
|
-
budgetText !== '0' ? '$' + budgetText : 'No limit'
|
|
2110
|
-
),
|
|
2111
|
-
react.createElement('div', { className: 'ig-stat-lbl' }, t('stat.budget_cache'))
|
|
2112
|
-
)
|
|
2113
|
-
),
|
|
2114
|
-
// Tab Navigation
|
|
2115
|
-
react.createElement(
|
|
2116
|
-
'div',
|
|
2117
|
-
{ className: 'ig-tabs' },
|
|
2118
|
-
tabs.map((tab) =>
|
|
2119
|
-
react.createElement(
|
|
2120
|
-
'button',
|
|
2121
|
-
{
|
|
2122
|
-
key: tab.id,
|
|
2123
|
-
type: 'button',
|
|
2124
|
-
className: activeTab === tab.id ? 'ig-tab-btn ig-tab-btn-active' : 'ig-tab-btn',
|
|
2125
|
-
onClick: () => setActiveTab(tab.id),
|
|
2126
|
-
},
|
|
2127
|
-
tab.icon + ' ' + tab.label
|
|
2128
|
-
)
|
|
2129
|
-
)
|
|
2130
|
-
),
|
|
2131
|
-
// Active Tab Fields
|
|
2132
|
-
react.createElement(
|
|
2133
|
-
'div',
|
|
2134
|
-
{ style: { display: 'flex', flexDirection: 'column', gap: '8px' } },
|
|
2135
|
-
currentTabFields.map((entry) =>
|
|
2136
|
-
react.createElement(Field, {
|
|
2137
|
-
key: entry.field,
|
|
2138
|
-
entry,
|
|
2139
|
-
state: state[entry.field] ?? { text: '', overridden: false, invalid: false },
|
|
2140
|
-
fieldProps,
|
|
2141
|
-
t,
|
|
2142
|
-
onEdit: (val) => props.edit && props.edit(entry.field, val),
|
|
2143
|
-
onReset: () => props.resetField && props.resetField(entry.field),
|
|
2144
|
-
})
|
|
2145
|
-
)
|
|
2146
|
-
),
|
|
2147
|
-
// Footer Action Bar
|
|
2148
|
-
react.createElement(
|
|
2149
|
-
'div',
|
|
2150
|
-
{
|
|
2151
|
-
style: {
|
|
2152
|
-
display: 'flex',
|
|
2153
|
-
justifyContent: 'flex-end',
|
|
2154
|
-
alignItems: 'center',
|
|
2155
|
-
gap: '10px',
|
|
2156
|
-
paddingTop: '12px',
|
|
2157
|
-
borderTop: '1px solid var(--dsw-alias-border-l2)',
|
|
2158
|
-
},
|
|
2159
|
-
},
|
|
2160
|
-
state.failed
|
|
2161
|
-
? react.createElement('span', { className: 'ig-alert-err', style: { marginRight: 'auto', padding: '4px 10px' } }, t('settings.saveFailed'))
|
|
2162
|
-
: null,
|
|
2163
|
-
react.createElement(
|
|
2164
|
-
'button',
|
|
2165
|
-
{
|
|
2166
|
-
type: 'button',
|
|
2167
|
-
className: 'ig-btn',
|
|
2168
|
-
disabled: !state.dirty || state.saving,
|
|
2169
|
-
onClick: props.discard,
|
|
2170
|
-
},
|
|
2171
|
-
t('settings.discard')
|
|
2172
|
-
),
|
|
2173
|
-
react.createElement(
|
|
2174
|
-
'button',
|
|
2175
|
-
{
|
|
2176
|
-
type: 'button',
|
|
2177
|
-
className: 'ig-btn ig-btn-primary',
|
|
2178
|
-
disabled: blocked,
|
|
2179
|
-
onClick: props.save,
|
|
2180
|
-
},
|
|
2181
|
-
t(state.saving ? 'settings.saving' : 'settings.save')
|
|
2182
|
-
)
|
|
2183
|
-
)
|
|
2184
|
-
)
|
|
2185
|
-
: null
|
|
2186
|
-
)
|
|
2187
|
-
}
|
|
2188
|
-
|
|
2189
|
-
// -------------------------------------------------------------- Toolview Component
|
|
2190
|
-
const IMAGE_URL_RE = /https?:\/\/[^\s)]+\.(png|jpg|jpeg|webp|gif)(\?[^\s)]*)?/i
|
|
2191
|
-
|
|
2192
|
-
function readResult(block) {
|
|
2193
|
-
if (!block) return { text: '', url: '', attachment: null }
|
|
2194
|
-
const text = block.output || block.text || ''
|
|
2195
|
-
const attachment = block.attachment || (block.attachments && block.attachments[0]) || null
|
|
2196
|
-
const linked = text.match(IMAGE_URL_RE)
|
|
2197
|
-
return {
|
|
2198
|
-
attachment,
|
|
2199
|
-
url: linked ? linked[0] : '',
|
|
2200
|
-
text: linked ? text.replace(linked[0], '').trim() : text,
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
|
|
2204
|
-
function FalImageCard(props) {
|
|
2205
|
-
const block = props.block
|
|
2206
|
-
const running = !('kind' in (block || {}))
|
|
2207
|
-
const failed = block && (block.isError || block.error !== undefined)
|
|
2208
|
-
const parsed = readResult(block)
|
|
2209
|
-
const t = props.t || ((k) => k)
|
|
2210
|
-
|
|
2211
|
-
react.useEffect(() => {
|
|
2212
|
-
ensureCss()
|
|
2213
|
-
}, [])
|
|
2214
|
-
|
|
2215
|
-
let args = {}
|
|
2216
|
-
let prompt = ''
|
|
2217
|
-
try {
|
|
2218
|
-
const raw = (block && (block.call ? block.call.argsRaw : block.argsRaw)) || ''
|
|
2219
|
-
if (raw) {
|
|
2220
|
-
args = JSON.parse(raw)
|
|
2221
|
-
prompt = args.prompt || ''
|
|
2222
|
-
}
|
|
2223
|
-
} catch (_) {}
|
|
2224
|
-
|
|
2225
|
-
const [copied, setCopied] = react.useState(false)
|
|
2226
|
-
|
|
2227
|
-
const sendActionPrompt = async (text) => {
|
|
2228
|
-
try {
|
|
2229
|
-
const sessions = props.sessions
|
|
2230
|
-
const current = sessions && sessions.list && sessions.list.current
|
|
2231
|
-
const binding = current && sessions.binding(current)
|
|
2232
|
-
const session = binding && binding.session
|
|
2233
|
-
if (session && session.prompt) await session.prompt([{ type: 'text', text }], 'queue')
|
|
2234
|
-
} catch (_) {}
|
|
2235
|
-
}
|
|
2236
|
-
|
|
2237
|
-
const onCopyPrompt = () => {
|
|
2238
|
-
if (prompt && typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
2239
|
-
navigator.clipboard.writeText(prompt)
|
|
2240
|
-
setCopied(true)
|
|
2241
|
-
setTimeout(() => setCopied(false), 2000)
|
|
2242
|
-
}
|
|
2243
|
-
}
|
|
2244
|
-
|
|
2245
|
-
const onReroll = () => {
|
|
2246
|
-
const nextSeed = typeof args.seed === 'number' ? args.seed + 1 : Math.floor(Math.random() * 100000)
|
|
2247
|
-
sendActionPrompt(t('card.actionReroll') + ' (' + nextSeed + '): ' + (prompt || ''))
|
|
2248
|
-
}
|
|
2249
|
-
|
|
2250
|
-
const onUpscale = () => {
|
|
2251
|
-
const ref = parsed.attachment ? (parsed.attachment.attachmentId || parsed.attachment.id) : (parsed.url || '')
|
|
2252
|
-
sendActionPrompt(t('card.actionUpscale') + ': upscale_image(image: "' + ref + '")')
|
|
2253
|
-
}
|
|
2254
|
-
|
|
2255
|
-
const onRemoveBg = () => {
|
|
2256
|
-
const ref = parsed.attachment ? (parsed.attachment.attachmentId || parsed.attachment.id) : (parsed.url || '')
|
|
2257
|
-
sendActionPrompt(t('card.actionRemoveBg') + ': remove_background(image: "' + ref + '")')
|
|
2258
|
-
}
|
|
2259
|
-
|
|
2260
|
-
const head = react.createElement(
|
|
2261
|
-
'div',
|
|
2262
|
-
{ className: 'fal_head' },
|
|
2263
|
-
running ? '⏳ ' + t('card.generating') : failed ? '❌ ' + t('card.failed') : '🖼️ ' + t('card.image')
|
|
2264
|
-
)
|
|
2265
|
-
|
|
2266
|
-
const body = []
|
|
2267
|
-
if (prompt) {
|
|
2268
|
-
body.push(react.createElement('div', { className: 'fal-prompt', key: 'p' }, prompt))
|
|
2269
|
-
}
|
|
2270
|
-
if (failed) {
|
|
2271
|
-
body.push(react.createElement('div', { className: 'fal-err', key: 'e' }, parsed.text || t('card.unknownError')))
|
|
2272
|
-
} else if (!parsed.attachment && parsed.url) {
|
|
2273
|
-
body.push(react.createElement('img', {
|
|
2274
|
-
key: 'i',
|
|
2275
|
-
src: parsed.url,
|
|
2276
|
-
alt: prompt || 'generated image',
|
|
2277
|
-
loading: 'lazy',
|
|
2278
|
-
style: { maxWidth: '100%', borderRadius: '8px', border: '1px solid var(--dsw-alias-border-l2)' },
|
|
2279
|
-
}))
|
|
2280
|
-
} else if (parsed.attachment) {
|
|
2281
|
-
const a = parsed.attachment
|
|
2282
|
-
const query = 'id=' + encodeURIComponent(a.attachmentId ?? a.id ?? '')
|
|
2283
|
-
+ '&mt=' + encodeURIComponent(a.mediaType || 'image/png')
|
|
2284
|
-
+ '&b=' + encodeURIComponent(String(a.bytes ?? 0))
|
|
2285
|
-
+ '&w=' + encodeURIComponent(String(a.width ?? 0))
|
|
2286
|
-
+ '&h=' + encodeURIComponent(String(a.height ?? 0))
|
|
2287
|
-
body.push(react.createElement('img', {
|
|
2288
|
-
key: 'i',
|
|
2289
|
-
src: '/dsh-image-gen/image?' + query,
|
|
2290
|
-
alt: prompt || 'generated image',
|
|
2291
|
-
loading: 'lazy',
|
|
2292
|
-
style: { maxWidth: '100%', borderRadius: '8px', border: '1px solid var(--dsw-alias-border-l2)' },
|
|
2293
|
-
}))
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
|
-
const actions = []
|
|
2297
|
-
if (!running && !failed && (parsed.url || parsed.attachment)) {
|
|
2298
|
-
actions.push(
|
|
2299
|
-
react.createElement('button', { key: 'reroll', type: 'button', className: 'ig-btn', onClick: onReroll }, '🎲 ' + t('card.reroll')),
|
|
2300
|
-
react.createElement('button', { key: 'upscale', type: 'button', className: 'ig-btn', onClick: onUpscale }, '🔍 ' + t('card.upscale')),
|
|
2301
|
-
react.createElement('button', { key: 'removeBg', type: 'button', className: 'ig-btn', onClick: onRemoveBg }, '✂️ ' + t('card.removeBg')),
|
|
2302
|
-
react.createElement('button', { key: 'copy', type: 'button', className: 'ig-btn', onClick: onCopyPrompt }, copied ? '✓ Copied' : '📋 ' + t('card.copyPrompt'))
|
|
2303
|
-
)
|
|
2304
|
-
}
|
|
2305
|
-
|
|
2306
|
-
return react.createElement(
|
|
2307
|
-
'div',
|
|
2308
|
-
{ className: 'ig-section-card', style: { margin: '8px 0' } },
|
|
2309
|
-
head,
|
|
2310
|
-
body,
|
|
2311
|
-
actions.length > 0 ? react.createElement('div', { className: 'ig-row', style: { marginTop: '8px' } }, actions) : null
|
|
2312
|
-
)
|
|
2313
|
-
}
|
|
2314
|
-
|
|
2315
|
-
// -------------------------------------------------------------- Dictionaries
|
|
2316
|
-
const en = {
|
|
2317
|
-
'settings.navLabel': 'Image Studio',
|
|
2318
|
-
'settings.title': 'Image Studio',
|
|
2319
|
-
'settings.description': 'AI Image generation, inpainting, variations, upscale & vectorization.',
|
|
2320
|
-
'settings.unavailable': 'Plugin settings are initializing. They will appear automatically in a few seconds.',
|
|
2321
|
-
'settings.unsaved': 'Unsaved changes',
|
|
2322
|
-
'settings.saving': 'Saving…',
|
|
2323
|
-
'settings.save': 'Save settings',
|
|
2324
|
-
'settings.discard': 'Discard changes',
|
|
2325
|
-
'settings.saveFailed': 'Failed to save settings. Please check credentials and try again.',
|
|
2326
|
-
'settings.readOnly': 'Settings are read-only in this context.',
|
|
2327
|
-
'settings.overridden': 'overridden',
|
|
2328
|
-
'settings.reset': 'Reset to default',
|
|
2329
|
-
'settings.invalidNumber': 'Please enter a valid number',
|
|
2330
|
-
'settings.collapse': 'Collapse',
|
|
2331
|
-
'settings.expand': 'Expand',
|
|
2332
|
-
'settings.credentialHint': 'API keys are stored in DSH Credentials (~/.dsh/.credentials.yaml) and never leaked in config files.',
|
|
2333
|
-
|
|
2334
|
-
'tab.general': 'General',
|
|
2335
|
-
'tab.provider': 'Provider',
|
|
2336
|
-
'tab.enhancer': 'Prompt & Styles',
|
|
2337
|
-
'tab.safety': 'Safety & Budget',
|
|
2338
|
-
'tab.cache': 'Cache & Storage',
|
|
2339
|
-
|
|
2340
|
-
'stat.active_provider': 'Active Provider',
|
|
2341
|
-
'stat.format_size': 'Default Specs',
|
|
2342
|
-
'stat.safety_status': 'Safety & Loop Guard',
|
|
2343
|
-
'stat.budget_cache': 'Budget & Cache',
|
|
2344
|
-
|
|
2345
|
-
'card.generating': 'Generating image…',
|
|
2346
|
-
'card.failed': 'Generation failed',
|
|
2347
|
-
'card.image': 'Image Studio Result',
|
|
2348
|
-
'card.unknownError': 'An unknown error occurred during generation.',
|
|
2349
|
-
'card.sizePrefix': 'Size',
|
|
2350
|
-
'card.seedPrefix': 'Seed',
|
|
2351
|
-
'card.actionReroll': 'Reroll image',
|
|
2352
|
-
'card.actionUpscale': 'Upscale image',
|
|
2353
|
-
'card.actionRemoveBg': 'Remove background',
|
|
2354
|
-
'card.reroll': 'Reroll',
|
|
2355
|
-
'card.upscale': 'Upscale 2x/4x',
|
|
2356
|
-
'card.removeBg': 'Remove BG',
|
|
2357
|
-
'card.copyPrompt': 'Copy Prompt',
|
|
2358
|
-
'card.origPrompt': 'Original Prompt',
|
|
2359
|
-
|
|
2360
|
-
'f.enabled': 'Master Switch',
|
|
2361
|
-
'f.enabledHint': 'Enable or disable image generation tools across the workspace.',
|
|
2362
|
-
'f.provider': 'Image Provider',
|
|
2363
|
-
'f.providerHint': 'Backend service used for image generation.',
|
|
2364
|
-
'f.inherit': 'Default / Inherit',
|
|
2365
|
-
'f.needFal': 'Requires FAL API key',
|
|
2366
|
-
'f.needCustom': 'Requires OpenAI-compatible endpoint',
|
|
2367
|
-
'f.needCodex': 'Built-in Codex subscription',
|
|
2368
|
-
'f.needGrok': 'Built-in Grok subscription',
|
|
2369
|
-
'f.needLocal': 'Local ComfyUI or A1111',
|
|
2370
|
-
'f.needSeedream': 'Requires SeaDream API key',
|
|
2371
|
-
'f.needGemini': 'Requires Google Gemini API key',
|
|
2372
|
-
'f.needReplicate': 'Requires Replicate API token',
|
|
2373
|
-
|
|
2374
|
-
'f.defaultSize': 'Default Aspect Ratio',
|
|
2375
|
-
'f.defaultSizeHint': 'Aspect ratio and resolution applied when image_size is omitted.',
|
|
2376
|
-
'f.defaultFormat': 'Output Format',
|
|
2377
|
-
'f.defaultFormatHint': 'Target file format (PNG, JPEG, or WEBP).',
|
|
2378
|
-
'f.deliverAs': 'Delivery Mode',
|
|
2379
|
-
'f.deliverAsHint': 'How images reach conversation: "link" (text LLM safe) or "image" (multimodal vision).',
|
|
2380
|
-
'f.outputDir': 'Output Directory',
|
|
2381
|
-
'f.outputDirHint': 'Directory path where generated images are saved on disk.',
|
|
2382
|
-
'p.outputDir': 'generated/images',
|
|
2383
|
-
'f.historyLimit': 'History Limit',
|
|
2384
|
-
'f.historyLimitHint': 'Maximum number of recent generation records kept in memory.',
|
|
2385
|
-
'p.historyLimit': '50',
|
|
2386
|
-
|
|
2387
|
-
'f.model': 'FAL Model ID',
|
|
2388
|
-
'f.modelHint': 'Target model on FAL endpoint (e.g. fal-ai/flux-2/klein/9b).',
|
|
2389
|
-
'p.model': 'fal-ai/flux-2/klein/9b',
|
|
2390
|
-
'f.apiKeyEnv': 'FAL Key Reference',
|
|
2391
|
-
'f.apiKeyEnvHint': 'Credential reference holding the FAL API key.',
|
|
2392
|
-
'p.apiKeyEnv': 'FAL_API_KEY',
|
|
2393
|
-
'f.baseURL': 'FAL Base URL',
|
|
2394
|
-
'f.baseURLHint': 'FAL queue service endpoint.',
|
|
2395
|
-
'p.baseURL': 'https://queue.fal.run',
|
|
2396
|
-
'f.pollIntervalMs': 'Poll Interval (ms)',
|
|
2397
|
-
'f.pollIntervalMsHint': 'Frequency of checking task progress on asynchronous queues.',
|
|
2398
|
-
'p.pollIntervalMs': '2000',
|
|
2399
|
-
'f.timeoutMs': 'Timeout (ms)',
|
|
2400
|
-
'f.timeoutMsHint': 'Maximum overall timeout before failing a generation request.',
|
|
2401
|
-
'p.timeoutMs': '180000',
|
|
2402
|
-
|
|
2403
|
-
'f.customBaseURL': 'Custom API Base URL',
|
|
2404
|
-
'f.customBaseURLHint': 'OpenAI-compatible image endpoint root (e.g. https://api.openai.com/v1).',
|
|
2405
|
-
'p.customBaseURL': 'https://api.openai.com/v1',
|
|
2406
|
-
'f.customModel': 'Custom Model ID',
|
|
2407
|
-
'f.customModelHint': 'Model name sent to custom images endpoint.',
|
|
2408
|
-
'p.customModel': 'dall-e-3',
|
|
2409
|
-
'f.customKeyEnv': 'Custom Key Reference',
|
|
2410
|
-
'f.customKeyEnvHint': 'Credential reference holding the API key for custom endpoint.',
|
|
2411
|
-
'p.customKeyEnv': 'OPENAI_API_KEY',
|
|
2412
|
-
'f.customSize': 'Custom Image Size',
|
|
2413
|
-
'f.customSizeHint': 'Fixed dimension sent to custom endpoint (e.g. 1024x1024).',
|
|
2414
|
-
'p.customSize': '1024x1024',
|
|
2415
|
-
|
|
2416
|
-
'f.replicateModel': 'Replicate Model ID',
|
|
2417
|
-
'f.replicateModelHint': 'Target model identifier on Replicate.',
|
|
2418
|
-
'p.replicateModel': 'black-forest-labs/flux-schnell',
|
|
2419
|
-
'f.replicateKeyEnv': 'Replicate Key Reference',
|
|
2420
|
-
'f.replicateKeyEnvHint': 'Credential reference holding the Replicate API token.',
|
|
2421
|
-
'p.replicateKeyEnv': 'REPLICATE_API_TOKEN',
|
|
2422
|
-
|
|
2423
|
-
'f.seedreamModel': 'SeaDream Model ID',
|
|
2424
|
-
'f.seedreamModelHint': 'ByteDance SeaDream model identifier.',
|
|
2425
|
-
'p.seedreamModel': 'seedream-4.0',
|
|
2426
|
-
'f.seedreamKeyEnv': 'SeaDream Key Reference',
|
|
2427
|
-
'f.seedreamKeyEnvHint': 'Credential reference holding the SeaDream API key.',
|
|
2428
|
-
'p.seedreamKeyEnv': 'SEEDREAM_API_KEY',
|
|
2429
|
-
'f.seedreamBaseURL': 'SeaDream Base URL',
|
|
2430
|
-
'f.seedreamBaseURLHint': 'SeaDream API endpoint URL (ByteDance/Volcengine Ark).',
|
|
2431
|
-
'p.seedreamBaseURL': 'https://api.bytedanceapi.com/v1',
|
|
2432
|
-
|
|
2433
|
-
'f.geminiModel': 'Gemini Model ID',
|
|
2434
|
-
'f.geminiModelHint': 'Google Gemini image generation model identifier.',
|
|
2435
|
-
'p.geminiModel': 'gemini-2.0-flash-exp-image-generation',
|
|
2436
|
-
'f.geminiKeyEnv': 'Gemini Key Reference',
|
|
2437
|
-
'f.geminiKeyEnvHint': 'Credential reference holding Google Gemini API key.',
|
|
2438
|
-
'p.geminiKeyEnv': 'GEMINI_API_KEY',
|
|
2439
|
-
|
|
2440
|
-
'f.localKind': 'Local Engine Architecture',
|
|
2441
|
-
'f.localKindHint': 'Select between ComfyUI API or Automatic1111 WebUI backend.',
|
|
2442
|
-
'f.localBaseURL': 'Local Server URL',
|
|
2443
|
-
'f.localBaseURLHint': 'Server address (e.g. http://127.0.0.1:8188 for ComfyUI, http://127.0.0.1:7860 for A1111).',
|
|
2444
|
-
'p.localBaseURL': 'http://127.0.0.1:8188',
|
|
2445
|
-
'f.localModel': 'Local Model / Checkpoint',
|
|
2446
|
-
'f.localModelHint': 'Checkpoint name or ComfyUI workflow name.',
|
|
2447
|
-
'p.localModel': 'v1-5-pruned-emaonly.safetensors',
|
|
2448
|
-
'f.localSteps': 'Sampling Steps',
|
|
2449
|
-
'f.localStepsHint': 'Number of denoising sampling steps.',
|
|
2450
|
-
'p.localSteps': '20',
|
|
2451
|
-
'f.localCfg': 'CFG Scale',
|
|
2452
|
-
'f.localCfgHint': 'Classifier-free guidance scale.',
|
|
2453
|
-
'p.localCfg': '7',
|
|
2454
|
-
'f.localComfyUrl': 'Local ComfyUI URL (Legacy)',
|
|
2455
|
-
'f.localComfyUrlHint': 'Direct ComfyUI endpoint URL.',
|
|
2456
|
-
'p.localComfyUrl': 'http://127.0.0.1:8188',
|
|
2457
|
-
'f.localA1111Url': 'Local A1111 URL (Legacy)',
|
|
2458
|
-
'f.localA1111UrlHint': 'Direct Automatic1111 endpoint URL.',
|
|
2459
|
-
'p.localA1111Url': 'http://127.0.0.1:7860',
|
|
2460
|
-
|
|
2461
|
-
'f.subscriptionQuality': 'Subscription Quality',
|
|
2462
|
-
'f.subscriptionQualityHint': 'Target quality profile when using Codex or Grok subscriptions.',
|
|
2463
|
-
|
|
2464
|
-
'f.enhancePrompt': 'LLM Prompt Enhancer',
|
|
2465
|
-
'f.enhancePromptHint': 'Expand concise prompts with rich artistic details before generation.',
|
|
2466
|
-
'f.enableLlmEnhancer': 'Enable LLM Enhancer (Legacy)',
|
|
2467
|
-
'f.enableLlmEnhancerHint': 'Legacy toggle for LLM prompt expansion.',
|
|
2468
|
-
'f.enhanceModel': 'Enhancement Model',
|
|
2469
|
-
'f.enhanceModelHint': 'Dedicated LLM model used to expand prompts (empty = conversation model).',
|
|
2470
|
-
'p.enhanceModel': 'Leave empty for default chat model',
|
|
2471
|
-
'f.enhanceBelowChars': 'Enhancement Threshold',
|
|
2472
|
-
'f.enhanceBelowCharsHint': 'Prompts longer than this character count will skip expansion.',
|
|
2473
|
-
'p.enhanceBelowChars': '200',
|
|
2474
|
-
'f.stylePreset': 'Artistic Style Preset',
|
|
2475
|
-
'f.stylePresetHint': 'Automatic visual style suffix added to all generated prompts.',
|
|
2476
|
-
|
|
2477
|
-
'f.qualityGate': 'Silent Quality Gate',
|
|
2478
|
-
'f.qualityGateHint': 'Automatically inspects generated frames and silently re-rolls blank or broken outputs.',
|
|
2479
|
-
'f.dailyBudgetUsd': 'Daily Spend Budget ($ USD)',
|
|
2480
|
-
'f.dailyBudgetUsdHint': 'Hard spending cap per day across all visual generation tools (0 = no limit).',
|
|
2481
|
-
'p.dailyBudgetUsd': '0.00',
|
|
2482
|
-
'f.loopGuardLimit': 'Session Loop Guard Limit',
|
|
2483
|
-
'f.loopGuardLimitHint': 'Maximum consecutive image generations allowed before requiring user input (0 = off).',
|
|
2484
|
-
'p.loopGuardLimit': '3',
|
|
2485
|
-
|
|
2486
|
-
'f.diskCache': 'Content-Addressed Disk Cache',
|
|
2487
|
-
'f.diskCacheHint': 'Instant <50ms retrieval and zero API cost for repeated identical generations.',
|
|
2488
|
-
'f.cacheBySeed': 'Cache by Seed + Prompt',
|
|
2489
|
-
'f.cacheBySeedHint': 'Reuse existing result if the exact same seed and prompt are requested.',
|
|
2490
|
-
'f.cacheByPrompt': 'Cache by Prompt Only',
|
|
2491
|
-
'f.cacheByPromptHint': 'Reuse existing result if the prompt has already been generated.',
|
|
2492
|
-
'f.pruneDays': 'Retention Period (Days)',
|
|
2493
|
-
'f.pruneDaysHint': 'Automatically delete image files and cache older than this many days (0 = keep forever).',
|
|
2494
|
-
'p.pruneDays': '0',
|
|
2495
|
-
'f.cacheTtlDays': 'Cache Retention (Legacy)',
|
|
2496
|
-
'f.cacheTtlDaysHint': 'Legacy name for retention period in days.',
|
|
2497
|
-
'p.cacheTtlDays': '0',
|
|
2498
|
-
}
|
|
2499
|
-
|
|
2500
|
-
const ru = {
|
|
2501
|
-
'settings.navLabel': 'Image Studio',
|
|
2502
|
-
'settings.title': 'Генерация изображений',
|
|
2503
|
-
'settings.description': 'Мультипровайдерная студия: генерация, инпейнтинг, вариации, апскейл и векторизация.',
|
|
2504
|
-
'settings.unavailable': 'Настройки плагина инициализируются. Они появятся автоматически через несколько секунд.',
|
|
2505
|
-
'settings.unsaved': 'Несохранённые изменения',
|
|
2506
|
-
'settings.saving': 'Сохранение…',
|
|
2507
|
-
'settings.save': 'Сохранить настройки',
|
|
2508
|
-
'settings.discard': 'Сбросить изменения',
|
|
2509
|
-
'settings.saveFailed': 'Не удалось сохранить настройки. Проверьте параметры и попробуйте снова.',
|
|
2510
|
-
'settings.readOnly': 'Настройки доступны только для чтения.',
|
|
2511
|
-
'settings.overridden': 'переопределено',
|
|
2512
|
-
'settings.reset': 'Сбросить к умолчанию',
|
|
2513
|
-
'settings.invalidNumber': 'Введите корректное число',
|
|
2514
|
-
'settings.collapse': 'Свернуть',
|
|
2515
|
-
'settings.expand': 'Развернуть',
|
|
2516
|
-
'settings.credentialHint': 'Ключи API хранятся в DSH Credentials (~/.dsh/.credentials.yaml) и не попадают в файлы конфигурации.',
|
|
2517
|
-
|
|
2518
|
-
'tab.general': 'Основные',
|
|
2519
|
-
'tab.provider': 'Провайдер',
|
|
2520
|
-
'tab.enhancer': 'Промпт и стили',
|
|
2521
|
-
'tab.safety': 'Безопасность и бюджет',
|
|
2522
|
-
'tab.cache': 'Кэш и хранение',
|
|
2523
|
-
|
|
2524
|
-
'stat.active_provider': 'Активный провайдер',
|
|
2525
|
-
'stat.format_size': 'Параметры по умолчанию',
|
|
2526
|
-
'stat.safety_status': 'Защита и Loop Guard',
|
|
2527
|
-
'stat.budget_cache': 'Бюджет и кэширование',
|
|
2528
|
-
|
|
2529
|
-
'card.generating': 'Генерация изображения…',
|
|
2530
|
-
'card.failed': 'Ошибка генерации',
|
|
2531
|
-
'card.image': 'Результат Image Studio',
|
|
2532
|
-
'card.unknownError': 'Неизвестная ошибка во время создания изображения.',
|
|
2533
|
-
'card.sizePrefix': 'Размер',
|
|
2534
|
-
'card.seedPrefix': 'Сид',
|
|
2535
|
-
'card.actionReroll': 'Перегенерировать картинку',
|
|
2536
|
-
'card.actionUpscale': 'Увеличить разрешение',
|
|
2537
|
-
'card.actionRemoveBg': 'Удалить фон',
|
|
2538
|
-
'card.reroll': 'Реролл',
|
|
2539
|
-
'card.upscale': 'Апскейл 2x/4x',
|
|
2540
|
-
'card.removeBg': 'Без фона',
|
|
2541
|
-
'card.copyPrompt': 'Копировать промпт',
|
|
2542
|
-
'card.origPrompt': 'Исходный промпт',
|
|
2543
|
-
|
|
2544
|
-
'f.enabled': 'Главный выключатель',
|
|
2545
|
-
'f.enabledHint': 'Включение или отключение всех инструментов генерации картинок.',
|
|
2546
|
-
'f.provider': 'Провайдер генерации',
|
|
2547
|
-
'f.providerHint': 'Сервис, выполняющий непосредственное создание изображений.',
|
|
2548
|
-
'f.inherit': 'По умолчанию / наследовать',
|
|
2549
|
-
'f.needFal': 'Требуется ключ FAL API',
|
|
2550
|
-
'f.needCustom': 'Требуется OpenAI-совместимый эндпоинт',
|
|
2551
|
-
'f.needCodex': 'Встроенная подписка Codex',
|
|
2552
|
-
'f.needGrok': 'Встроенная подписка Grok',
|
|
2553
|
-
'f.needLocal': 'Локальный ComfyUI или Automatic1111',
|
|
2554
|
-
'f.needSeedream': 'Требуется ключ SeaDream API',
|
|
2555
|
-
'f.needGemini': 'Требуется ключ Google Gemini API',
|
|
2556
|
-
'f.needReplicate': 'Требуется токен Replicate API',
|
|
2557
|
-
|
|
2558
|
-
'f.defaultSize': 'Соотношение сторон по умолчанию',
|
|
2559
|
-
'f.defaultSizeHint': 'Разрешение и пропорции кадра, когда параметр image_size не передан.',
|
|
2560
|
-
'f.defaultFormat': 'Формат файла',
|
|
2561
|
-
'f.defaultFormatHint': 'Формат сохранения результатов (PNG, JPEG или WEBP).',
|
|
2562
|
-
'f.deliverAs': 'Способ доставки',
|
|
2563
|
-
'f.deliverAsHint': 'Способ передачи: "link" (безопасно для текстовых моделей) или "image" (для vision-моделей).',
|
|
2564
|
-
'f.outputDir': 'Каталог сохранения',
|
|
2565
|
-
'f.outputDirHint': 'Путь к папке сохранения сгенерированных файлов на диске.',
|
|
2566
|
-
'p.outputDir': 'generated/images',
|
|
2567
|
-
'f.historyLimit': 'Лимит истории',
|
|
2568
|
-
'f.historyLimitHint': 'Максимальное число последних генераций, удерживаемых в памяти.',
|
|
2569
|
-
'p.historyLimit': '50',
|
|
2570
|
-
|
|
2571
|
-
'f.model': 'Модель FAL',
|
|
2572
|
-
'f.modelHint': 'Идентификатор модели на сервисе FAL (например, fal-ai/flux-2/klein/9b).',
|
|
2573
|
-
'p.model': 'fal-ai/flux-2/klein/9b',
|
|
2574
|
-
'f.apiKeyEnv': 'Ключ FAL (credential-ref)',
|
|
2575
|
-
'f.apiKeyEnvHint': 'Ссылка на ключ доступа FAL в хранилище credentials.',
|
|
2576
|
-
'p.apiKeyEnv': 'FAL_API_KEY',
|
|
2577
|
-
'f.baseURL': 'Базовый URL FAL',
|
|
2578
|
-
'f.baseURLHint': 'Адрес сервиса очередей FAL.',
|
|
2579
|
-
'p.baseURL': 'https://queue.fal.run',
|
|
2580
|
-
'f.pollIntervalMs': 'Интервал опроса (мс)',
|
|
2581
|
-
'f.pollIntervalMsHint': 'Периодичность проверки статуса при асинхронной генерации.',
|
|
2582
|
-
'p.pollIntervalMs': '2000',
|
|
2583
|
-
'f.timeoutMs': 'Таймаут (мс)',
|
|
2584
|
-
'f.timeoutMsHint': 'Предельное время ожидания генерации до возврата ошибки.',
|
|
2585
|
-
'p.timeoutMs': '180000',
|
|
2586
|
-
|
|
2587
|
-
'f.customBaseURL': 'URL стороннего API',
|
|
2588
|
-
'f.customBaseURLHint': 'Адрес OpenAI-совместимого эндпоинта (например, https://api.openai.com/v1).',
|
|
2589
|
-
'p.customBaseURL': 'https://api.openai.com/v1',
|
|
2590
|
-
'f.customModel': 'Модель стороннего API',
|
|
2591
|
-
'f.customModelHint': 'Имя модели для отправки в сторонний API.',
|
|
2592
|
-
'p.customModel': 'dall-e-3',
|
|
2593
|
-
'f.customKeyEnv': 'Ключ стороннего API',
|
|
2594
|
-
'f.customKeyEnvHint': 'Имя ссылки на ключ для стороннего API.',
|
|
2595
|
-
'p.customKeyEnv': 'OPENAI_API_KEY',
|
|
2596
|
-
'f.customSize': 'Фиксированный размер',
|
|
2597
|
-
'f.customSizeHint': 'Точный размер кадра для стороннего API (например, 1024x1024).',
|
|
2598
|
-
'p.customSize': '1024x1024',
|
|
2599
|
-
|
|
2600
|
-
'f.replicateModel': 'Модель Replicate',
|
|
2601
|
-
'f.replicateModelHint': 'Идентификатор модели на Replicate.',
|
|
2602
|
-
'p.replicateModel': 'black-forest-labs/flux-schnell',
|
|
2603
|
-
'f.replicateKeyEnv': 'Ключ Replicate',
|
|
2604
|
-
'f.replicateKeyEnvHint': 'Имя ссылки на API-токен Replicate.',
|
|
2605
|
-
'p.replicateKeyEnv': 'REPLICATE_API_TOKEN',
|
|
2606
|
-
|
|
2607
|
-
'f.seedreamModel': 'Модель SeaDream',
|
|
2608
|
-
'f.seedreamModelHint': 'Идентификатор модели ByteDance SeaDream.',
|
|
2609
|
-
'p.seedreamModel': 'seedream-4.0',
|
|
2610
|
-
'f.seedreamKeyEnv': 'Ключ SeaDream',
|
|
2611
|
-
'f.seedreamKeyEnvHint': 'Имя ссылки на ключ API SeaDream.',
|
|
2612
|
-
'p.seedreamKeyEnv': 'SEEDREAM_API_KEY',
|
|
2613
|
-
'f.seedreamBaseURL': 'Базовый URL SeaDream',
|
|
2614
|
-
'f.seedreamBaseURLHint': 'Адрес API SeaDream (ByteDance/Volcengine Ark).',
|
|
2615
|
-
'p.seedreamBaseURL': 'https://api.bytedanceapi.com/v1',
|
|
2616
|
-
|
|
2617
|
-
'f.geminiModel': 'Модель Gemini',
|
|
2618
|
-
'f.geminiModelHint': 'Идентификатор модели генерации Google Gemini.',
|
|
2619
|
-
'p.geminiModel': 'gemini-2.0-flash-exp-image-generation',
|
|
2620
|
-
'f.geminiKeyEnv': 'Ключ Gemini',
|
|
2621
|
-
'f.geminiKeyEnvHint': 'Имя ссылки на ключ Google Gemini API.',
|
|
2622
|
-
'p.geminiKeyEnv': 'GEMINI_API_KEY',
|
|
2623
|
-
|
|
2624
|
-
'f.localKind': 'Тип локального движка',
|
|
2625
|
-
'f.localKindHint': 'Выбор между ComfyUI API и Automatic1111 WebUI.',
|
|
2626
|
-
'f.localBaseURL': 'Адрес локального сервера',
|
|
2627
|
-
'f.localBaseURLHint': 'Адрес локального сервера (ComfyUI: 8188, A1111: 7860).',
|
|
2628
|
-
'p.localBaseURL': 'http://127.0.0.1:8188',
|
|
2629
|
-
'f.localModel': 'Модель / Чекпоинт',
|
|
2630
|
-
'f.localModelHint': 'Имя чекпоинта или воркфлоу ComfyUI.',
|
|
2631
|
-
'p.localModel': 'v1-5-pruned-emaonly.safetensors',
|
|
2632
|
-
'f.localSteps': 'Шаги сэмплирования',
|
|
2633
|
-
'f.localStepsHint': 'Количество шагов генерации.',
|
|
2634
|
-
'p.localSteps': '20',
|
|
2635
|
-
'f.localCfg': 'Шкала CFG',
|
|
2636
|
-
'f.localCfgHint': 'Сила следования промпту.',
|
|
2637
|
-
'p.localCfg': '7',
|
|
2638
|
-
'f.localComfyUrl': 'Локальный ComfyUI URL (Legacy)',
|
|
2639
|
-
'f.localComfyUrlHint': 'Прямой адрес ComfyUI.',
|
|
2640
|
-
'p.localComfyUrl': 'http://127.0.0.1:8188',
|
|
2641
|
-
'f.localA1111Url': 'Локальный A1111 URL (Legacy)',
|
|
2642
|
-
'f.localA1111UrlHint': 'Прямой адрес Automatic1111.',
|
|
2643
|
-
'p.localA1111Url': 'http://127.0.0.1:7860',
|
|
2644
|
-
|
|
2645
|
-
'f.subscriptionQuality': 'Качество подписки',
|
|
2646
|
-
'f.subscriptionQualityHint': 'Профиль качества для провайдеров Codex и Grok.',
|
|
2647
|
-
|
|
2648
|
-
'f.enhancePrompt': 'LLM-улучшение промпта',
|
|
2649
|
-
'f.enhancePromptHint': 'Автоматическое обогащение коротких запросов художественными деталями.',
|
|
2650
|
-
'f.enableLlmEnhancer': 'Включить LLM Enhancer (Legacy)',
|
|
2651
|
-
'f.enableLlmEnhancerHint': 'Устаревший переключатель улучшения промптов.',
|
|
2652
|
-
'f.enhanceModel': 'Модель улучшения',
|
|
2653
|
-
'f.enhanceModelHint': 'Модель для расширения промптов (пусто = основная модель чата).',
|
|
2654
|
-
'p.enhanceModel': 'Пусто для основной модели чата',
|
|
2655
|
-
'f.enhanceBelowChars': 'Порог длины промпта',
|
|
2656
|
-
'f.enhanceBelowCharsHint': 'Промпты длиннее этого количества символов не расширяются.',
|
|
2657
|
-
'p.enhanceBelowChars': '200',
|
|
2658
|
-
'f.stylePreset': 'Художественный стиль',
|
|
2659
|
-
'f.stylePresetHint': 'Автоматический суффикс стиля для всех создаваемых изображений.',
|
|
2660
|
-
|
|
2661
|
-
'f.qualityGate': 'Контроль качества (Quality Gate)',
|
|
2662
|
-
'f.qualityGateHint': 'Автоматическая проверка кадров и скрытый реролл при дефектах или пустом кадре.',
|
|
2663
|
-
'f.dailyBudgetUsd': 'Дневной бюджет ($ USD)',
|
|
2664
|
-
'f.dailyBudgetUsdHint': 'Предельный лимит затрат в сутки по всем графическим инструментам (0 = без лимита).',
|
|
2665
|
-
'p.dailyBudgetUsd': '0.00',
|
|
2666
|
-
'f.loopGuardLimit': 'Защита от циклов (Loop Guard)',
|
|
2667
|
-
'f.loopGuardLimitHint': 'Максимум последовательных генераций подряд без участия пользователя (0 = выкл).',
|
|
2668
|
-
'p.loopGuardLimit': '3',
|
|
2669
|
-
|
|
2670
|
-
'f.diskCache': 'Дисковый кэш по хэшу',
|
|
2671
|
-
'f.diskCacheHint': 'Мгновенная выдача (<50 мс) и нулевая стоимость для повторных идентичных генераций.',
|
|
2672
|
-
'f.cacheBySeed': 'Кэш по сиду и промпту',
|
|
2673
|
-
'f.cacheBySeedHint': 'Возвращать кэш при полном совпадении сида и текста промпта.',
|
|
2674
|
-
'f.cacheByPrompt': 'Кэш по тексту промпта',
|
|
2675
|
-
'f.cacheByPromptHint': 'Возвращать кэш при повторении того же текста промпта.',
|
|
2676
|
-
'f.pruneDays': 'Срок хранения файлов (дни)',
|
|
2677
|
-
'f.pruneDaysHint': 'Автоматическое удаление файлов и записей старше указанного числа дней (0 = бессрочно).',
|
|
2678
|
-
'p.pruneDays': '0',
|
|
2679
|
-
'f.cacheTtlDays': 'Срок хранения кэша (Legacy)',
|
|
2680
|
-
'f.cacheTtlDaysHint': 'Устаревшее имя параметра срока хранения.',
|
|
2681
|
-
'p.cacheTtlDays': '0',
|
|
2682
|
-
}
|
|
2683
|
-
|
|
2684
|
-
// -------------------------------------------------------------- Registration & Apply
|
|
2685
|
-
const inject = ['slots', 'settingsScope', 'locale', 'sessions']
|
|
2686
|
-
|
|
2687
|
-
function apply(ctx) {
|
|
2688
|
-
if (ctx.locale && typeof ctx.locale.define === 'function') {
|
|
2689
|
-
ctx.locale.define('en', NS, en)
|
|
2690
|
-
ctx.locale.define('ru', NS, ru)
|
|
2691
|
-
}
|
|
2692
|
-
|
|
2693
|
-
let card
|
|
2694
|
-
const cardOnce = () => {
|
|
2695
|
-
if (card === undefined) {
|
|
2696
|
-
const scope = ((ctx.get && ctx.get('lanSettings')) || ctx.settingsScope).bind({ namespace: SETTINGS_NS })
|
|
2697
|
-
card = new FalSettingsCardController(scope)
|
|
2698
|
-
}
|
|
2699
|
-
return card
|
|
2700
|
-
}
|
|
2701
|
-
|
|
2702
|
-
function registerSlotWhenReady(slotName, registerFn) {
|
|
2703
|
-
if (!ctx.slots) return
|
|
2704
|
-
if (typeof ctx.slots.inject === 'function') {
|
|
2705
|
-
try {
|
|
2706
|
-
ctx.slots.inject(slotName, () => {
|
|
2707
|
-
try {
|
|
2708
|
-
return registerFn()
|
|
2709
|
-
} catch (err) {
|
|
2710
|
-
console.warn('[dsh-image-gen] Error registering slot ' + slotName + ':', err)
|
|
2711
|
-
}
|
|
2712
|
-
})
|
|
2713
|
-
return
|
|
2714
|
-
} catch (err) {
|
|
2715
|
-
console.warn('[dsh-image-gen] Failed to inject slot ' + slotName + ':', err)
|
|
2716
|
-
}
|
|
2717
|
-
}
|
|
2718
|
-
if (typeof ctx.slots.register === 'function') {
|
|
2719
|
-
try {
|
|
2720
|
-
registerFn()
|
|
2721
|
-
} catch (err) {
|
|
2722
|
-
console.warn('[dsh-image-gen] Failed direct registration for ' + slotName + ':', err)
|
|
2723
|
-
}
|
|
2724
|
-
}
|
|
2725
|
-
}
|
|
2726
|
-
|
|
2727
|
-
// Register toolviews for all 8 visual tools
|
|
2728
|
-
const toolviewTools = [
|
|
2729
|
-
'generate_image',
|
|
2730
|
-
'edit_image',
|
|
2731
|
-
'vary_image',
|
|
2732
|
-
'blend_images',
|
|
2733
|
-
'generate_image_pack',
|
|
2734
|
-
'remove_background',
|
|
2735
|
-
'upscale_image',
|
|
2736
|
-
'vectorize_image',
|
|
2737
|
-
'assemble_image_grid',
|
|
2738
|
-
]
|
|
2739
|
-
|
|
2740
|
-
for (const toolName of toolviewTools) {
|
|
2741
|
-
registerSlotWhenReady('tool.call.toolview', () =>
|
|
2742
|
-
ctx.slots.register(
|
|
2743
|
-
{
|
|
2744
|
-
name: 'tool.call.toolview',
|
|
2745
|
-
key: toolName,
|
|
2746
|
-
locale: NS,
|
|
2747
|
-
inject: () => ({ sessions: ctx.sessions }),
|
|
2748
|
-
},
|
|
2749
|
-
(props) => react.createElement(ErrorBoundary, null, react.createElement(FalImageCard, props))
|
|
2750
|
-
)
|
|
2751
|
-
)
|
|
2752
|
-
}
|
|
2753
|
-
|
|
2754
|
-
// Settings card item
|
|
2755
|
-
registerSlotWhenReady('settings.plugin.item', () =>
|
|
2756
|
-
ctx.slots.register(
|
|
2757
|
-
{
|
|
2758
|
-
name: 'settings.plugin.item',
|
|
2759
|
-
key: SETTINGS_NS,
|
|
2760
|
-
locale: NS,
|
|
2761
|
-
inject: () => cardOnce().inject(),
|
|
2762
|
-
},
|
|
2763
|
-
(props) => react.createElement(ErrorBoundary, null, react.createElement(FalSettingsCard, props))
|
|
2764
|
-
)
|
|
2765
|
-
)
|
|
2766
|
-
}
|
|
2767
|
-
|
|
2768
|
-
exports.apply = apply
|
|
2769
|
-
exports.inject = inject
|
|
2770
|
-
return module.exports
|
|
2771
|
-
},
|
|
2772
|
-
})
|
|
2079
|
+
})
|