@dickpy/dsh-imagegen 1.5.3 → 1.5.4
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 +21 -1
- package/lib/client.js +5310 -1934
- package/lib/client.js.map +1 -1
- package/lib/index.js +986 -32
- package/package.json +1 -1
- package/src/canvas-store.ts +376 -0
- package/src/client/CanvasWorkspace.tsx +1569 -0
- package/src/client/ImageGenPanel.tsx +159 -96
- package/src/client/SettingsCard.tsx +173 -1
- package/src/client/api.ts +48 -1
- package/src/client/canvas-workspace.module.css +929 -0
- package/src/client/locales.ts +1443 -1146
- package/src/client/panel.module.css +83 -33
- package/src/engine.ts +156 -14
- package/src/gallery-store.ts +5 -0
- package/src/generation-runtime.ts +1 -0
- package/src/history-store.ts +5 -0
- package/src/index.ts +70 -3
- package/src/protocol.ts +121 -1
- package/src/routes.ts +231 -1
- package/src/storage-sync.ts +105 -0
|
@@ -33,6 +33,14 @@ export interface ImageGenSettings {
|
|
|
33
33
|
promptApiUrl?: string
|
|
34
34
|
promptApiKey?: string
|
|
35
35
|
promptModel?: string
|
|
36
|
+
storageEnabled?: boolean
|
|
37
|
+
storageEndpoint?: string
|
|
38
|
+
storageRegion?: string
|
|
39
|
+
storagePrefix?: string
|
|
40
|
+
storageAccessKey?: string
|
|
41
|
+
storageSecretKey?: string
|
|
42
|
+
storageSyncGallery?: boolean
|
|
43
|
+
storageSyncHistory?: boolean
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
/** What the card renders. */
|
|
@@ -47,12 +55,29 @@ export interface ImageGenSettingsCardState extends CardShell {
|
|
|
47
55
|
promptApiUrl: CardFieldState
|
|
48
56
|
promptApiKey: CardFieldState
|
|
49
57
|
promptModel: CardFieldState
|
|
58
|
+
storageEnabled: CardFieldState
|
|
59
|
+
storageEndpoint: CardFieldState
|
|
60
|
+
storageRegion: CardFieldState
|
|
61
|
+
storagePrefix: CardFieldState
|
|
62
|
+
storageAccessKey: CardFieldState
|
|
63
|
+
storageSecretKey: CardFieldState
|
|
64
|
+
storageSyncGallery: CardFieldState
|
|
65
|
+
storageSyncHistory: CardFieldState
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Result of probing the configured object storage from the card. */
|
|
69
|
+
export interface StorageTestOutcome {
|
|
70
|
+
ok: boolean
|
|
71
|
+
ms?: number
|
|
72
|
+
message?: string
|
|
50
73
|
}
|
|
51
74
|
|
|
52
75
|
/** The registration-side face the card's slot entry injects. */
|
|
53
76
|
export interface ImageGenSettingsCardFace extends CardActions {
|
|
54
77
|
/** Channel staging actions (committed together with the card's save). */
|
|
55
78
|
channels: ChannelsFormActions
|
|
79
|
+
/** Save staged edits, then upload a probe object to the configured store. */
|
|
80
|
+
storageTest: () => Promise<StorageTestOutcome>
|
|
56
81
|
hooks: {
|
|
57
82
|
/** Card snapshot bound by the renderer as useImageGenSettingsCard. */
|
|
58
83
|
imageGenSettingsCard: SnapshotStore<ImageGenSettingsCardState>
|
|
@@ -73,8 +98,16 @@ export class ImageGenSettingsCardController {
|
|
|
73
98
|
textField('promptApiUrl'),
|
|
74
99
|
secretField('promptApiKey'),
|
|
75
100
|
textField('promptModel'),
|
|
101
|
+
booleanField('storageEnabled'),
|
|
102
|
+
textField('storageEndpoint'),
|
|
103
|
+
textField('storageRegion'),
|
|
104
|
+
textField('storagePrefix'),
|
|
105
|
+
textField('storageAccessKey'),
|
|
106
|
+
secretField('storageSecretKey'),
|
|
107
|
+
booleanField('storageSyncGallery'),
|
|
108
|
+
booleanField('storageSyncHistory'),
|
|
76
109
|
], {
|
|
77
|
-
secretSettled: () => this.scope.getSecretSetSnapshot(
|
|
110
|
+
secretSettled: (field) => this.scope.getSecretSetSnapshot(field),
|
|
78
111
|
})
|
|
79
112
|
this.channelsForm = new ChannelsForm(scope)
|
|
80
113
|
}
|
|
@@ -91,6 +124,14 @@ export class ImageGenSettingsCardController {
|
|
|
91
124
|
promptApiUrl: this.form.field('promptApiUrl'),
|
|
92
125
|
promptApiKey: this.form.field('promptApiKey'),
|
|
93
126
|
promptModel: this.form.field('promptModel'),
|
|
127
|
+
storageEnabled: this.form.field('storageEnabled'),
|
|
128
|
+
storageEndpoint: this.form.field('storageEndpoint'),
|
|
129
|
+
storageRegion: this.form.field('storageRegion'),
|
|
130
|
+
storagePrefix: this.form.field('storagePrefix'),
|
|
131
|
+
storageAccessKey: this.form.field('storageAccessKey'),
|
|
132
|
+
storageSecretKey: this.form.field('storageSecretKey'),
|
|
133
|
+
storageSyncGallery: this.form.field('storageSyncGallery'),
|
|
134
|
+
storageSyncHistory: this.form.field('storageSyncHistory'),
|
|
94
135
|
}
|
|
95
136
|
}
|
|
96
137
|
|
|
@@ -106,6 +147,19 @@ export class ImageGenSettingsCardController {
|
|
|
106
147
|
imageGenSettingsCard: cardStore,
|
|
107
148
|
},
|
|
108
149
|
channels: this.channelsForm.actions(),
|
|
150
|
+
// The probe needs the values the user is looking at, so staged edits are
|
|
151
|
+
// committed first; the host route then resolves the saved config itself.
|
|
152
|
+
storageTest: async (): Promise<StorageTestOutcome> => {
|
|
153
|
+
await this.form.save()
|
|
154
|
+
try {
|
|
155
|
+
const response = await fetch('/api/dsh-imagegen/storage/test', { method: 'POST' })
|
|
156
|
+
const body = await response.json() as { ok?: unknown; ms?: unknown; message?: unknown }
|
|
157
|
+
if (body.ok === true) return { ok: true, ms: typeof body.ms === 'number' ? body.ms : undefined }
|
|
158
|
+
return { ok: false, message: typeof body.message === 'string' ? body.message : `HTTP ${response.status}` }
|
|
159
|
+
} catch (error) {
|
|
160
|
+
return { ok: false, message: error instanceof Error ? error.message : String(error) }
|
|
161
|
+
}
|
|
162
|
+
},
|
|
109
163
|
...this.form.actions(),
|
|
110
164
|
}
|
|
111
165
|
}
|
|
@@ -144,6 +198,9 @@ export function ImageGenSettingsCard(props: ImageGenSettingsCardProps) {
|
|
|
144
198
|
const [manualPromptModel, setManualPromptModel] = useState('')
|
|
145
199
|
const [enhancementOpen, setEnhancementOpen] = useState(false)
|
|
146
200
|
const [promptApiOpen, setPromptApiOpen] = useState(false)
|
|
201
|
+
const [storageOpen, setStorageOpen] = useState(false)
|
|
202
|
+
const [storageTesting, setStorageTesting] = useState(false)
|
|
203
|
+
const [storageTestResult, setStorageTestResult] = useState<string | null>(null)
|
|
147
204
|
const [moreOpen, setMoreOpen] = useState(false)
|
|
148
205
|
// Channel list local states.
|
|
149
206
|
const [editingId, setEditingId] = useState<string | null>(null)
|
|
@@ -413,6 +470,121 @@ export function ImageGenSettingsCard(props: ImageGenSettingsCardProps) {
|
|
|
413
470
|
</div> : null}
|
|
414
471
|
</section> : null}
|
|
415
472
|
|
|
473
|
+
<button type="button" className={css.disclosure} aria-expanded={storageOpen} onClick={() => { setStorageOpen(open => !open) }}>
|
|
474
|
+
<span>{t('settings.storageTitle')}</span>
|
|
475
|
+
<span aria-hidden="true">{storageOpen ? '⌃' : '⌄'}</span>
|
|
476
|
+
</button>
|
|
477
|
+
{storageOpen ? <div className={css.optionalContent}>
|
|
478
|
+
<BooleanField
|
|
479
|
+
id="dsh-imagegen-settings-storage-enabled"
|
|
480
|
+
label={t('settings.storageEnabled')}
|
|
481
|
+
hint={t('settings.storageHint')}
|
|
482
|
+
inheritLabel={t('settings.inherit')}
|
|
483
|
+
onLabel={t('settings.on')}
|
|
484
|
+
offLabel={t('settings.off')}
|
|
485
|
+
{...fieldProps}
|
|
486
|
+
{...state.storageEnabled}
|
|
487
|
+
onEdit={(text) => { props.edit('storageEnabled', text) }}
|
|
488
|
+
onReset={() => { props.resetField('storageEnabled') }}
|
|
489
|
+
/>
|
|
490
|
+
<ValueField
|
|
491
|
+
id="dsh-imagegen-settings-storage-endpoint"
|
|
492
|
+
label={t('settings.storageEndpoint')}
|
|
493
|
+
hint={t('settings.storageEndpointHint')}
|
|
494
|
+
placeholder="https://bucket-appid.cos.ap-guangzhou.myqcloud.com"
|
|
495
|
+
{...fieldProps}
|
|
496
|
+
{...state.storageEndpoint}
|
|
497
|
+
onEdit={(text) => { props.edit('storageEndpoint', text) }}
|
|
498
|
+
onReset={() => { props.resetField('storageEndpoint') }}
|
|
499
|
+
/>
|
|
500
|
+
<ValueField
|
|
501
|
+
id="dsh-imagegen-settings-storage-region"
|
|
502
|
+
label={t('settings.storageRegion')}
|
|
503
|
+
hint={t('settings.storageRegionHint')}
|
|
504
|
+
placeholder="ap-guangzhou"
|
|
505
|
+
{...fieldProps}
|
|
506
|
+
{...state.storageRegion}
|
|
507
|
+
onEdit={(text) => { props.edit('storageRegion', text) }}
|
|
508
|
+
onReset={() => { props.resetField('storageRegion') }}
|
|
509
|
+
/>
|
|
510
|
+
<ValueField
|
|
511
|
+
id="dsh-imagegen-settings-storage-prefix"
|
|
512
|
+
label={t('settings.storagePrefix')}
|
|
513
|
+
hint={t('settings.storagePrefixHint')}
|
|
514
|
+
placeholder="dsh-imagegen"
|
|
515
|
+
{...fieldProps}
|
|
516
|
+
{...state.storagePrefix}
|
|
517
|
+
onEdit={(text) => { props.edit('storagePrefix', text) }}
|
|
518
|
+
onReset={() => { props.resetField('storagePrefix') }}
|
|
519
|
+
/>
|
|
520
|
+
<ValueField
|
|
521
|
+
id="dsh-imagegen-settings-storage-accesskey"
|
|
522
|
+
label={t('settings.storageAccessKey')}
|
|
523
|
+
hint={t('settings.storageAccessKeyHint')}
|
|
524
|
+
placeholder="AKID…"
|
|
525
|
+
{...fieldProps}
|
|
526
|
+
{...state.storageAccessKey}
|
|
527
|
+
onEdit={(text) => { props.edit('storageAccessKey', text) }}
|
|
528
|
+
onReset={() => { props.resetField('storageAccessKey') }}
|
|
529
|
+
/>
|
|
530
|
+
<ValueField
|
|
531
|
+
id="dsh-imagegen-settings-storage-secretkey"
|
|
532
|
+
label={t('settings.storageSecretKey')}
|
|
533
|
+
hint={t('settings.storageSecretKeyHint')}
|
|
534
|
+
placeholder="…"
|
|
535
|
+
secret
|
|
536
|
+
{...fieldProps}
|
|
537
|
+
{...state.storageSecretKey}
|
|
538
|
+
overridden={false}
|
|
539
|
+
onEdit={(text) => { props.edit('storageSecretKey', text) }}
|
|
540
|
+
onReset={() => { props.resetField('storageSecretKey') }}
|
|
541
|
+
/>
|
|
542
|
+
<BooleanField
|
|
543
|
+
id="dsh-imagegen-settings-storage-gallery"
|
|
544
|
+
label={t('settings.storageSyncGallery')}
|
|
545
|
+
hint={t('settings.storageSyncGalleryHint')}
|
|
546
|
+
inheritLabel={t('settings.inherit')}
|
|
547
|
+
onLabel={t('settings.on')}
|
|
548
|
+
offLabel={t('settings.off')}
|
|
549
|
+
{...fieldProps}
|
|
550
|
+
{...state.storageSyncGallery}
|
|
551
|
+
onEdit={(text) => { props.edit('storageSyncGallery', text) }}
|
|
552
|
+
onReset={() => { props.resetField('storageSyncGallery') }}
|
|
553
|
+
/>
|
|
554
|
+
<BooleanField
|
|
555
|
+
id="dsh-imagegen-settings-storage-history"
|
|
556
|
+
label={t('settings.storageSyncHistory')}
|
|
557
|
+
hint={t('settings.storageSyncHistoryHint')}
|
|
558
|
+
inheritLabel={t('settings.inherit')}
|
|
559
|
+
onLabel={t('settings.on')}
|
|
560
|
+
offLabel={t('settings.off')}
|
|
561
|
+
{...fieldProps}
|
|
562
|
+
{...state.storageSyncHistory}
|
|
563
|
+
onEdit={(text) => { props.edit('storageSyncHistory', text) }}
|
|
564
|
+
onReset={() => { props.resetField('storageSyncHistory') }}
|
|
565
|
+
/>
|
|
566
|
+
<div className={css.modelSummary}>
|
|
567
|
+
<button
|
|
568
|
+
type="button"
|
|
569
|
+
className={css.addModel}
|
|
570
|
+
disabled={disabled || storageTesting}
|
|
571
|
+
onClick={() => {
|
|
572
|
+
setStorageTesting(true)
|
|
573
|
+
setStorageTestResult(null)
|
|
574
|
+
void props.storageTest().then(outcome => {
|
|
575
|
+
setStorageTestResult(outcome.ok
|
|
576
|
+
? t('settings.storageTestOk', { ms: outcome.ms ?? 0 })
|
|
577
|
+
: t('settings.storageTestFailed', { error: outcome.message ?? 'error' }))
|
|
578
|
+
}).finally(() => { setStorageTesting(false) })
|
|
579
|
+
}}
|
|
580
|
+
>
|
|
581
|
+
{storageTesting ? t('settings.storageTesting') : t('settings.storageTest')}
|
|
582
|
+
</button>
|
|
583
|
+
{storageTestResult !== null ? <p className={css.failed} role="status">{storageTestResult}</p> : null}
|
|
584
|
+
</div>
|
|
585
|
+
<p className={css.hint}>{t('settings.storageKeyHint')}</p>
|
|
586
|
+
</div> : null}
|
|
587
|
+
|
|
416
588
|
<button type="button" className={css.disclosure} aria-expanded={moreOpen} onClick={() => { setMoreOpen(open => !open) }}>
|
|
417
589
|
<span>{t('settings.moreOptions')}</span>
|
|
418
590
|
<span aria-hidden="true">{moreOpen ? '⌃' : '⌄'}</span>
|
package/src/client/api.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* data access path the panel uses — plain fetch, same origin.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { CONVERSATION_IMAGE_API, GALLERY_API, GENERATE_API, HISTORY_API, PROMPT_ENHANCE_API, TASK_API, TEMPLATE_FAVORITES_API, TEMPLATES_API, UPDATE_API, type GenerateRequest, type GenerateResult, type GenerationTask, type HistoryEntry, type HistoryEntryInput, type TemplateCase, type TemplateFavorite, type TemplateListResult, type TemplateRefreshResult, type TemplateSample, type UpdateInfo } from '../protocol.ts'
|
|
6
|
+
import { CANVAS_API, CONVERSATION_IMAGE_API, DATA_FOLDER_API, GALLERY_API, GENERATE_API, HISTORY_API, PROMPT_ENHANCE_API, STORAGE_API, TASK_API, TEMPLATE_FAVORITES_API, TEMPLATES_API, UPDATE_API, type CanvasAssetRef, type CanvasDocument, type CanvasSummary, type GenerateRequest, type GenerateResult, type GenerationTask, type HistoryEntry, type HistoryEntryInput, type TemplateCase, type TemplateFavorite, type TemplateListResult, type TemplateRefreshResult, type TemplateSample, type UpdateInfo } from '../protocol.ts'
|
|
7
7
|
|
|
8
8
|
/** Error carrying the route's JSON error message. */
|
|
9
9
|
export class ImageGenApiError extends Error {
|
|
@@ -181,6 +181,41 @@ export class ImageGenApi {
|
|
|
181
181
|
return (await readEnvelope<{ ok: true; entries: HistoryEntry[] }>(response)).entries
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
async canvasList(): Promise<CanvasSummary[]> {
|
|
185
|
+
const response = await fetch(CANVAS_API.list, { method: 'POST' })
|
|
186
|
+
return (await readEnvelope<{ ok: true; projects: CanvasSummary[] }>(response)).projects
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async canvasCreate(title?: string): Promise<CanvasDocument> {
|
|
190
|
+
const response = await fetch(CANVAS_API.create, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ title }) })
|
|
191
|
+
return (await readEnvelope<{ ok: true; document: CanvasDocument }>(response)).document
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async canvasRead(id: string): Promise<CanvasDocument> {
|
|
195
|
+
const response = await fetch(CANVAS_API.read, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id }) })
|
|
196
|
+
return (await readEnvelope<{ ok: true; document: CanvasDocument }>(response)).document
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async canvasSave(document: CanvasDocument, expectedRevision: number): Promise<CanvasDocument> {
|
|
200
|
+
const response = await fetch(CANVAS_API.save, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ document, expectedRevision }) })
|
|
201
|
+
return (await readEnvelope<{ ok: true; document: CanvasDocument }>(response)).document
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async canvasRemove(id: string): Promise<CanvasSummary[]> {
|
|
205
|
+
const response = await fetch(CANVAS_API.remove, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id }) })
|
|
206
|
+
return (await readEnvelope<{ ok: true; projects: CanvasSummary[] }>(response)).projects
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async canvasUpload(dataUrl: string, width: number, height: number, meta: { origin?: string; originId?: string; entryId?: string; imageIndex?: number } = {}): Promise<CanvasAssetRef> {
|
|
210
|
+
const response = await fetch(CANVAS_API.assetUpload, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ dataUrl, width, height, ...meta }) })
|
|
211
|
+
return (await readEnvelope<{ ok: true; asset: CanvasAssetRef }>(response)).asset
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async canvasImport(source: 'history' | 'gallery', entryId: string, imageIndex: number, width: number, height: number): Promise<CanvasAssetRef> {
|
|
215
|
+
const response = await fetch(CANVAS_API.assetImport, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ source, entryId, imageIndex, width, height }) })
|
|
216
|
+
return (await readEnvelope<{ ok: true; asset: CanvasAssetRef }>(response)).asset
|
|
217
|
+
}
|
|
218
|
+
|
|
184
219
|
/** Fetch one template source's list (bundled snapshot or refreshed copy). */
|
|
185
220
|
async templatesList(sourceId: string): Promise<TemplateListResult> {
|
|
186
221
|
const response = await fetch(TEMPLATES_API.list, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ source: sourceId }) })
|
|
@@ -214,6 +249,18 @@ export class ImageGenApi {
|
|
|
214
249
|
return (await readEnvelope<{ ok: true; favorites: TemplateFavorite[] }>(response)).favorites
|
|
215
250
|
}
|
|
216
251
|
|
|
252
|
+
/** Reveal the host data directory (saved images) in the OS file manager. */
|
|
253
|
+
async openDataFolder(): Promise<{ ok: boolean; path?: string; message?: string }> {
|
|
254
|
+
const response = await fetch(DATA_FOLDER_API, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({}) })
|
|
255
|
+
return await readEnvelope<{ ok: true; path: string }>(response)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Probe the configured S3-compatible object storage with a small upload. */
|
|
259
|
+
async storageTest(): Promise<{ ok: boolean; ms?: number; key?: string; message?: string }> {
|
|
260
|
+
const response = await fetch(STORAGE_API.test, { method: 'POST' })
|
|
261
|
+
return await readEnvelope<{ ok: true; ms: number; key: string }>(response)
|
|
262
|
+
}
|
|
263
|
+
|
|
217
264
|
/** Star one template (the host keeps a full case snapshot). */
|
|
218
265
|
async favoritesAdd(sourceId: string, item: TemplateCase): Promise<TemplateFavorite[]> {
|
|
219
266
|
const response = await fetch(TEMPLATE_FAVORITES_API.add, {
|