@goodandready/dsh-image-gen 0.10.19 → 0.10.20
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 +132 -939
- 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')
|
|
@@ -1832,941 +1962,4 @@ window.__ModuleLoader__.load({
|
|
|
1832
1962
|
exports.inject = inject
|
|
1833
1963
|
return module.exports
|
|
1834
1964
|
},
|
|
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
|
-
})
|
|
1965
|
+
})
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-image-gen",
|
|
3
|
-
"version": "0.10.
|
|
4
|
-
"description": "Image generation for DeepSeek Harness: a generate_image tool with pluggable providers
|
|
3
|
+
"version": "0.10.20",
|
|
4
|
+
"description": "Image generation for DeepSeek Harness: a generate_image tool with pluggable providers — the FAL queue, any OpenAI-compatible images API, or a ChatGPT/Grok subscription with no API key at all. The picture is shown inline in the conversation; the model receives either a link (works with any chat model) or the image itself (needs dsh-vision-bridge or a vision-capable model).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
7
7
|
"dsh",
|
|
@@ -66,6 +66,6 @@
|
|
|
66
66
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
67
67
|
},
|
|
68
68
|
"scripts": {
|
|
69
|
-
"test": "node --test test/*.test.mjs"
|
|
69
|
+
"test": "node --check lib/client.js && node --test test/*.test.mjs"
|
|
70
70
|
}
|
|
71
71
|
}
|