@dickpy/dsh-imagegen 1.5.3 → 1.5.5
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 +23 -1
- package/lib/client.js +5412 -1935
- package/lib/client.js.map +1 -1
- package/lib/index.js +1082 -111
- package/package.json +1 -1
- package/src/canvas-store.ts +376 -0
- package/src/client/CanvasWorkspace.tsx +1628 -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 +1446 -1146
- package/src/client/panel.module.css +83 -33
- package/src/engine.ts +255 -95
- 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
package/src/history-store.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { promises as fs } from 'node:fs'
|
|
|
12
12
|
import { homedir } from 'node:os'
|
|
13
13
|
import path from 'node:path'
|
|
14
14
|
import { HISTORY_MAX, type GenerateMode, type HistoryEntry, type HistoryEntryInput } from './protocol.ts'
|
|
15
|
+
import { notifyImageSaved } from './storage-sync.ts'
|
|
15
16
|
|
|
16
17
|
const HISTORY_DIR = path.join(homedir(), '.dsh', 'dsh-imagegen')
|
|
17
18
|
const INDEX_PATH = path.join(HISTORY_DIR, 'index.json')
|
|
@@ -56,6 +57,7 @@ interface StoredEntry {
|
|
|
56
57
|
projectName?: string
|
|
57
58
|
slotKey?: string
|
|
58
59
|
slotLabel?: string
|
|
60
|
+
canvas?: HistoryEntryInput['canvas']
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
/** The index.json shape. */
|
|
@@ -174,6 +176,7 @@ function toWire(entry: StoredEntry): HistoryEntry {
|
|
|
174
176
|
...entry.projectName === undefined ? {} : { projectName: entry.projectName },
|
|
175
177
|
...entry.slotKey === undefined ? {} : { slotKey: entry.slotKey },
|
|
176
178
|
...entry.slotLabel === undefined ? {} : { slotLabel: entry.slotLabel },
|
|
179
|
+
...entry.canvas === undefined ? {} : { canvas: entry.canvas },
|
|
177
180
|
}
|
|
178
181
|
}
|
|
179
182
|
|
|
@@ -194,6 +197,7 @@ export async function appendHistory(input: HistoryEntryInput): Promise<HistoryEn
|
|
|
194
197
|
const image = input.images[index]!
|
|
195
198
|
const file = `${prefix}-${index}.${extensionOf(image.mime)}`
|
|
196
199
|
await fs.writeFile(path.join(IMAGES_DIR, file), Buffer.from(image.b64, 'base64'))
|
|
200
|
+
notifyImageSaved('history', path.join(IMAGES_DIR, file))
|
|
197
201
|
storedImages.push({
|
|
198
202
|
file,
|
|
199
203
|
mime: image.mime,
|
|
@@ -225,6 +229,7 @@ export async function appendHistory(input: HistoryEntryInput): Promise<HistoryEn
|
|
|
225
229
|
...input.projectName === undefined ? {} : { projectName: input.projectName },
|
|
226
230
|
...input.slotKey === undefined ? {} : { slotKey: input.slotKey },
|
|
227
231
|
...input.slotLabel === undefined ? {} : { slotLabel: input.slotLabel },
|
|
232
|
+
...input.canvas === undefined ? {} : { canvas: input.canvas },
|
|
228
233
|
}
|
|
229
234
|
const merged = [entry, ...await readIndex()]
|
|
230
235
|
const kept = merged.slice(0, HISTORY_MAX)
|
package/src/index.ts
CHANGED
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { Context } from '@deepseek-ai/cordis'
|
|
11
|
+
import { readFileSync } from 'node:fs'
|
|
12
|
+
import path from 'node:path'
|
|
11
13
|
import { installSettingsSectionCompat, settingsNamespaceCompat } from './settings-compat.ts'
|
|
12
|
-
import z from 'schemastery'
|
|
13
|
-
// Type-only: pulls the webServer Context merge (route registration).
|
|
14
|
+
import z from 'schemastery'// Type-only: pulls the webServer Context merge (route registration).
|
|
14
15
|
import type {} from '@deepseek-ai/dsh-host-webserver'
|
|
15
16
|
// Type-only: pulls the systemPrompt Context merge (announcement section).
|
|
16
17
|
import type {} from '@deepseek-ai/dsh-system-prompt'
|
|
@@ -21,6 +22,18 @@ import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
|
|
|
21
22
|
import { IMAGEGEN_SETTINGS_NAMESPACE, type ChannelConfig, type ModelMapping } from './protocol.ts'
|
|
22
23
|
import { makeRoutes, type SettingsSeam } from './routes.ts'
|
|
23
24
|
import { syncAllTemplates } from './templates-store.ts'
|
|
25
|
+
import { setStorageSyncHandler, putObject, type StorageSyncConfig } from './storage-sync.ts'
|
|
26
|
+
|
|
27
|
+
/** Content type for a saved image file name (object uploads). */
|
|
28
|
+
function mimeOfPath(filePath: string): string {
|
|
29
|
+
switch (path.extname(filePath).toLowerCase()) {
|
|
30
|
+
case '.jpg':
|
|
31
|
+
case '.jpeg': return 'image/jpeg'
|
|
32
|
+
case '.webp': return 'image/webp'
|
|
33
|
+
case '.gif': return 'image/gif'
|
|
34
|
+
default: return 'image/png'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
24
37
|
import { ImageGenerationRuntime, type ChannelsView, type RuntimeChannel } from './generation-runtime.ts'
|
|
25
38
|
import { registerAgentImageTools } from './agent-image-tools.ts'
|
|
26
39
|
import { registerEditImageCommand } from './edit-image-command.ts'
|
|
@@ -42,6 +55,7 @@ export { latestSessionImage, registerEditImageCommand } from './edit-image-comma
|
|
|
42
55
|
export { appendGallery, clearGallery, listGallery, readGalleryImage, removeGallery, updateGalleryTags } from './gallery-store.ts'
|
|
43
56
|
export { listTemplates, readTemplateImage, refreshTemplates, sampleTemplates, syncAllTemplates, clearTemplateMemo } from './templates-store.ts'
|
|
44
57
|
export { addTemplateFavorite, clearTemplateFavoritesMemo, listTemplateFavorites, removeTemplateFavorite } from './template-favorites.ts'
|
|
58
|
+
export { putObject, setStorageSyncHandler, testStorage, type StorageSyncConfig } from './storage-sync.ts'
|
|
45
59
|
export { checkForUpdate, clearUpdateCache, compareVersions, CURRENT_VERSION, installUpdate, profileFromProcess } from './updater.ts'
|
|
46
60
|
|
|
47
61
|
/** The branded settings namespace of this plugin (the card edits it). */
|
|
@@ -75,6 +89,22 @@ export interface Config {
|
|
|
75
89
|
promptApiKey?: string
|
|
76
90
|
/** Chat model used to expand short image prompts. */
|
|
77
91
|
promptModel?: string
|
|
92
|
+
/** Sync saved images to an S3-compatible object store (COS / OSS / Qiniu S3 …). */
|
|
93
|
+
storageEnabled?: boolean
|
|
94
|
+
/** S3-compatible endpoint URL including the bucket (virtual-hosted or path style). */
|
|
95
|
+
storageEndpoint?: string
|
|
96
|
+
/** Provider region for SigV4 scope, e.g. ap-guangzhou / oss-cn-hangzhou. */
|
|
97
|
+
storageRegion?: string
|
|
98
|
+
/** Object key prefix, default 'dsh-imagegen'. */
|
|
99
|
+
storagePrefix?: string
|
|
100
|
+
/** S3 access key id. */
|
|
101
|
+
storageAccessKey?: string
|
|
102
|
+
/** S3 secret access key (stored redacted). */
|
|
103
|
+
storageSecretKey?: string
|
|
104
|
+
/** Upload gallery additions (default on when storage is enabled). */
|
|
105
|
+
storageSyncGallery?: boolean
|
|
106
|
+
/** Also upload history images. */
|
|
107
|
+
storageSyncHistory?: boolean
|
|
78
108
|
/* ----- deprecated legacy single-endpoint fields (migrated to channels) ----- */
|
|
79
109
|
/** Legacy base URL; synthesized into the default channel on upgrade. */
|
|
80
110
|
apiUrl?: string
|
|
@@ -103,6 +133,14 @@ export const Config: z<Config> = z.object({
|
|
|
103
133
|
promptApiUrl: z.string().default(''),
|
|
104
134
|
promptApiKey: z.string().role('secret').default(''),
|
|
105
135
|
promptModel: z.string().default(''),
|
|
136
|
+
storageEnabled: z.boolean().default(false),
|
|
137
|
+
storageEndpoint: z.string().default(''),
|
|
138
|
+
storageRegion: z.string().default(''),
|
|
139
|
+
storagePrefix: z.string().default('dsh-imagegen'),
|
|
140
|
+
storageAccessKey: z.string().default(''),
|
|
141
|
+
storageSecretKey: z.string().role('secret').default(''),
|
|
142
|
+
storageSyncGallery: z.boolean().default(true),
|
|
143
|
+
storageSyncHistory: z.boolean().default(false),
|
|
106
144
|
apiUrl: z.string().default(''),
|
|
107
145
|
apiKey: z.string().role('secret').default(''),
|
|
108
146
|
imageModels: z.array(z.string()).default([]),
|
|
@@ -175,6 +213,7 @@ export interface EffectiveConfig {
|
|
|
175
213
|
promptApiUrl: string
|
|
176
214
|
promptApiKey: string
|
|
177
215
|
promptModel: string
|
|
216
|
+
storage: StorageSyncConfig & { enabled: boolean; syncGallery: boolean; syncHistory: boolean }
|
|
178
217
|
}
|
|
179
218
|
|
|
180
219
|
/**
|
|
@@ -182,7 +221,7 @@ export interface EffectiveConfig {
|
|
|
182
221
|
* @param ctx - host plugin context carrying webServer/systemPrompt.
|
|
183
222
|
* @param config - resolved plugin config (schema defaults applied by the loader).
|
|
184
223
|
*/
|
|
185
|
-
export function apply(ctx: Context, config?: Config): void {
|
|
224
|
+
export function apply(ctx: Context, config?: Config): (() => void) | void {
|
|
186
225
|
// The live source the surfaces read: the settings section once the settings
|
|
187
226
|
// service is attached, the composition entry otherwise.
|
|
188
227
|
let current: () => Config = () => config ?? {}
|
|
@@ -226,6 +265,16 @@ export function apply(ctx: Context, config?: Config): void {
|
|
|
226
265
|
promptApiUrl: typeof value.promptApiUrl === 'string' ? value.promptApiUrl.trim() : '',
|
|
227
266
|
promptApiKey: typeof value.promptApiKey === 'string' ? value.promptApiKey.trim() : '',
|
|
228
267
|
promptModel: typeof value.promptModel === 'string' ? value.promptModel.trim() : '',
|
|
268
|
+
storage: {
|
|
269
|
+
enabled: value.storageEnabled ?? false,
|
|
270
|
+
endpoint: typeof value.storageEndpoint === 'string' ? value.storageEndpoint.trim() : '',
|
|
271
|
+
region: typeof value.storageRegion === 'string' ? value.storageRegion.trim() : '',
|
|
272
|
+
accessKey: typeof value.storageAccessKey === 'string' ? value.storageAccessKey.trim() : '',
|
|
273
|
+
secretKey: typeof value.storageSecretKey === 'string' ? value.storageSecretKey.trim() : '',
|
|
274
|
+
prefix: typeof value.storagePrefix === 'string' && value.storagePrefix.trim() !== '' ? value.storagePrefix.trim() : 'dsh-imagegen',
|
|
275
|
+
syncGallery: value.storageSyncGallery ?? true,
|
|
276
|
+
syncHistory: value.storageSyncHistory ?? false,
|
|
277
|
+
},
|
|
229
278
|
}
|
|
230
279
|
}
|
|
231
280
|
|
|
@@ -236,6 +285,21 @@ export function apply(ctx: Context, config?: Config): void {
|
|
|
236
285
|
return { channels: value.channels, defaultChannelId: value.defaultChannelId }
|
|
237
286
|
}
|
|
238
287
|
|
|
288
|
+
// Object-storage sync: the image stores announce every file they write; the
|
|
289
|
+
// handler resolves the live settings and uploads when enabled. Fire and
|
|
290
|
+
// forget — a sync failure never blocks the save path.
|
|
291
|
+
setStorageSyncHandler((kind, filePath) => {
|
|
292
|
+
const storage = resolve().storage
|
|
293
|
+
if (!storage.enabled || !storage.endpoint.trim() || storage.secretKey.trim() === '') return
|
|
294
|
+
if (kind === 'gallery' && !storage.syncGallery) return
|
|
295
|
+
if (kind === 'history' && !storage.syncHistory) return
|
|
296
|
+
const key = `${storage.prefix}/${kind === 'gallery' ? 'gallery' : 'images'}/${path.basename(filePath)}`
|
|
297
|
+
const data = readFileSync(filePath)
|
|
298
|
+
void putObject(storage, key, data, mimeOfPath(filePath)).catch(() => {
|
|
299
|
+
// Best-effort sync: surfaced through the settings test, never fatal here.
|
|
300
|
+
})
|
|
301
|
+
})
|
|
302
|
+
|
|
239
303
|
// Browser endpoints and Agent tools share the exact same serial queue. This
|
|
240
304
|
// keeps image persistence, cancellation, and retries coherent across both
|
|
241
305
|
// entry points; Agent tools wait for their task result by default and render
|
|
@@ -276,6 +340,7 @@ export function apply(ctx: Context, config?: Config): void {
|
|
|
276
340
|
attachments: sctx.attachments,
|
|
277
341
|
pendingConversationImages,
|
|
278
342
|
runtime,
|
|
343
|
+
resolveStorage: () => resolve().storage,
|
|
279
344
|
})
|
|
280
345
|
const disposers = routes.map(route => ctx.webServer.register(route))
|
|
281
346
|
// Background template sync: the upstream sources update on their own
|
|
@@ -354,4 +419,6 @@ export function apply(ctx: Context, config?: Config): void {
|
|
|
354
419
|
// Initial registration from the composition entry (covers deployments with
|
|
355
420
|
// no settings service, whose installSettingsSection never fires its hooks).
|
|
356
421
|
sync()
|
|
422
|
+
|
|
423
|
+
return () => { setStorageSyncHandler(undefined) }
|
|
357
424
|
}
|
package/src/protocol.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
export const IMAGEGEN_SETTINGS_NAMESPACE = 'dsh-imagegen'
|
|
9
9
|
|
|
10
10
|
/** Published package version shared by the host updater and the client UI. */
|
|
11
|
-
export const PLUGIN_VERSION = '1.5.
|
|
11
|
+
export const PLUGIN_VERSION = '1.5.5'
|
|
12
12
|
|
|
13
13
|
/** Same-origin route family (loopback-only, mirroring the dsh-ssh fence). */
|
|
14
14
|
export const SETTINGS_API = {
|
|
@@ -54,6 +54,14 @@ export const TASK_API = {
|
|
|
54
54
|
retry: '/api/dsh-imagegen/tasks/retry',
|
|
55
55
|
} as const
|
|
56
56
|
|
|
57
|
+
/** Reveal the host data directory (saved images) in the OS file manager. */
|
|
58
|
+
export const DATA_FOLDER_API = '/api/dsh-imagegen/data-folder/open' as const
|
|
59
|
+
|
|
60
|
+
/** Probe the configured S3-compatible object storage. */
|
|
61
|
+
export const STORAGE_API = {
|
|
62
|
+
test: '/api/dsh-imagegen/storage/test',
|
|
63
|
+
} as const
|
|
64
|
+
|
|
57
65
|
/** Host-mediated GitHub Release update routes. */
|
|
58
66
|
export const UPDATE_API = {
|
|
59
67
|
check: '/api/dsh-imagegen/update/check',
|
|
@@ -88,6 +96,18 @@ export const GALLERY_API = {
|
|
|
88
96
|
image: '/api/dsh-imagegen/gallery/image',
|
|
89
97
|
} as const
|
|
90
98
|
|
|
99
|
+
/** Host-persisted infinite canvas projects and their content-addressed assets. */
|
|
100
|
+
export const CANVAS_API = {
|
|
101
|
+
list: '/api/dsh-imagegen/canvas/list',
|
|
102
|
+
create: '/api/dsh-imagegen/canvas/create',
|
|
103
|
+
read: '/api/dsh-imagegen/canvas/read',
|
|
104
|
+
save: '/api/dsh-imagegen/canvas/save',
|
|
105
|
+
remove: '/api/dsh-imagegen/canvas/remove',
|
|
106
|
+
assetUpload: '/api/dsh-imagegen/canvas/asset/upload',
|
|
107
|
+
assetImport: '/api/dsh-imagegen/canvas/asset/import',
|
|
108
|
+
asset: '/api/dsh-imagegen/canvas/asset',
|
|
109
|
+
} as const
|
|
110
|
+
|
|
91
111
|
/** Maximum number of history entries retained host-side (oldest evicted). */
|
|
92
112
|
export const HISTORY_MAX = 50
|
|
93
113
|
|
|
@@ -226,6 +246,100 @@ export interface TemplateFavorite {
|
|
|
226
246
|
/** Generation modes. */
|
|
227
247
|
export type GenerateMode = 'text' | 'edit'
|
|
228
248
|
|
|
249
|
+
/** Origin information carried by a generation started from the canvas. */
|
|
250
|
+
export interface CanvasTaskMeta {
|
|
251
|
+
canvasId: string
|
|
252
|
+
sourceNodeId?: string
|
|
253
|
+
/** Legacy v1 annotation workflow; kept so old history entries still parse. */
|
|
254
|
+
annotationNodeId?: string
|
|
255
|
+
parentNodeId?: string
|
|
256
|
+
placement?: 'right' | 'below'
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** One image asset referenced by a canvas node. */
|
|
260
|
+
export interface CanvasAssetRef {
|
|
261
|
+
assetId: string
|
|
262
|
+
url: string
|
|
263
|
+
mime: string
|
|
264
|
+
bytes: number
|
|
265
|
+
width: number
|
|
266
|
+
height: number
|
|
267
|
+
origin: 'upload' | 'history' | 'gallery' | 'generated'
|
|
268
|
+
originId?: string
|
|
269
|
+
entryId?: string
|
|
270
|
+
imageIndex?: number
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export type CanvasNodeType = 'image' | 'text' | 'config'
|
|
274
|
+
|
|
275
|
+
export interface CanvasViewport {
|
|
276
|
+
x: number
|
|
277
|
+
y: number
|
|
278
|
+
/** Zoom factor. */
|
|
279
|
+
k: number
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Free-form per-node state, mirroring the node-graph canvas model. */
|
|
283
|
+
export interface CanvasNodeMetadata {
|
|
284
|
+
/** Image nodes: the rendered asset. */
|
|
285
|
+
asset?: CanvasAssetRef
|
|
286
|
+
status?: 'idle' | 'generating' | 'success' | 'error'
|
|
287
|
+
error?: string
|
|
288
|
+
/** Config/image nodes: generation settings. */
|
|
289
|
+
prompt?: string
|
|
290
|
+
model?: string
|
|
291
|
+
size?: string
|
|
292
|
+
quality?: string
|
|
293
|
+
/** Config nodes: how many images to generate (1-4). */
|
|
294
|
+
count?: number
|
|
295
|
+
/** Config nodes: generation mode (image) or plain writing (text). */
|
|
296
|
+
mode?: 'image' | 'text'
|
|
297
|
+
taskId?: string
|
|
298
|
+
sourceNodeId?: string
|
|
299
|
+
/** Text nodes. */
|
|
300
|
+
text?: string
|
|
301
|
+
fontSize?: number
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export interface CanvasNode {
|
|
305
|
+
id: string
|
|
306
|
+
type: CanvasNodeType
|
|
307
|
+
title: string
|
|
308
|
+
x: number
|
|
309
|
+
y: number
|
|
310
|
+
width: number
|
|
311
|
+
height: number
|
|
312
|
+
metadata?: CanvasNodeMetadata
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export interface CanvasConnection {
|
|
316
|
+
id: string
|
|
317
|
+
fromNodeId: string
|
|
318
|
+
toNodeId: string
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface CanvasDocument {
|
|
322
|
+
version: 2
|
|
323
|
+
id: string
|
|
324
|
+
title: string
|
|
325
|
+
revision: number
|
|
326
|
+
viewport: CanvasViewport
|
|
327
|
+
background: 'dots' | 'lines' | 'blank'
|
|
328
|
+
nodes: CanvasNode[]
|
|
329
|
+
connections: CanvasConnection[]
|
|
330
|
+
createdAt: number
|
|
331
|
+
updatedAt: number
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export interface CanvasSummary {
|
|
335
|
+
id: string
|
|
336
|
+
title: string
|
|
337
|
+
revision: number
|
|
338
|
+
nodeCount: number
|
|
339
|
+
createdAt: number
|
|
340
|
+
updatedAt: number
|
|
341
|
+
}
|
|
342
|
+
|
|
229
343
|
/** Metadata shared by the ecommerce product-set workflow. */
|
|
230
344
|
export interface EcommerceTaskMeta {
|
|
231
345
|
workflow?: 'ecommerce'
|
|
@@ -257,6 +371,8 @@ export interface ProductSetDraft {
|
|
|
257
371
|
category: string
|
|
258
372
|
platform: string
|
|
259
373
|
language: string
|
|
374
|
+
/** Custom copy language when language is 'custom'. */
|
|
375
|
+
customLanguage?: string
|
|
260
376
|
size: string
|
|
261
377
|
productName: string
|
|
262
378
|
sellingPoints: string
|
|
@@ -311,6 +427,8 @@ export interface GenerateRequest extends EcommerceTaskMeta {
|
|
|
311
427
|
comparisonId?: string
|
|
312
428
|
/** All model aliases selected for one comparison run. */
|
|
313
429
|
comparisonModels?: string[]
|
|
430
|
+
/** Optional canvas lineage metadata. */
|
|
431
|
+
canvas?: CanvasTaskMeta
|
|
314
432
|
}
|
|
315
433
|
|
|
316
434
|
/** One generated image, normalized host-side to base64 so the browser never
|
|
@@ -428,6 +546,7 @@ export interface HistoryEntry extends EcommerceTaskMeta {
|
|
|
428
546
|
comparisonId?: string
|
|
429
547
|
/** Model aliases included in the comparison run. */
|
|
430
548
|
comparisonModels?: string[]
|
|
549
|
+
canvas?: CanvasTaskMeta
|
|
431
550
|
}
|
|
432
551
|
|
|
433
552
|
/** A history entry the client submits for persistence (images still carry base64). */
|
|
@@ -451,4 +570,5 @@ export interface HistoryEntryInput extends EcommerceTaskMeta {
|
|
|
451
570
|
comparisonId?: string
|
|
452
571
|
/** Model aliases included in the comparison run. */
|
|
453
572
|
comparisonModels?: string[]
|
|
573
|
+
canvas?: CanvasTaskMeta
|
|
454
574
|
}
|