@dickpy/dsh-imagegen 1.5.1 → 1.5.3
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 +13 -9
- package/docs/images/ecommerce-mode.png +0 -0
- package/docs/images/gallery-workspace.png +0 -0
- package/docs/images/image-generation-studio-three-column.png +0 -0
- package/docs/images/imagegen-overview.png +0 -0
- package/docs/images/plugin-settings.png +0 -0
- package/docs/images/prompt-template-library.png +0 -0
- package/lib/client.js +1304 -447
- package/lib/client.js.map +1 -1
- package/lib/index.js +632 -76
- package/package.json +1 -1
- package/src/client/ImageGenPanel.tsx +15 -9
- package/src/client/InspirationGallery.tsx +106 -0
- package/src/client/SettingsCard.tsx +9 -3
- package/src/client/TemplateLibrary.tsx +159 -48
- package/src/client/api.ts +41 -8
- package/src/client/helpers.ts +71 -33
- package/src/client/index.ts +59 -7
- package/src/client/inspiration.module.css +148 -0
- package/src/client/locales.ts +417 -8
- package/src/client/templates.module.css +95 -0
- package/src/client/use-language.ts +14 -0
- package/src/engine.ts +148 -0
- package/src/index.ts +23 -3
- package/src/model-catalog.ts +10 -1
- package/src/presets.ts +15 -0
- package/src/protocol.ts +81 -7
- package/src/routes.ts +124 -10
- package/src/template-favorites.ts +108 -0
- package/src/templates/canghe-cases.json +11126 -0
- package/src/templates-store.ts +179 -68
- package/docs/images/image-generation-studio-single.png +0 -0
- package/docs/images/image-generation-studio.png +0 -0
- package/docs/images/poster-features-16x9.png +0 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Favorites store for the prompt-template library.
|
|
3
|
+
*
|
|
4
|
+
* The user's starred templates persist host-side as full case snapshots under
|
|
5
|
+
* ~/.dsh/dsh-imagegen/templates/favorites.json, keyed by
|
|
6
|
+
* `${sourceId}:${caseId}` — the snapshot means a favorite stays usable even
|
|
7
|
+
* after the upstream list drops or renumbers the case. Framework-free
|
|
8
|
+
* (node:fs only) so the route layer and tests can drive it directly.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { promises as fs } from 'node:fs'
|
|
12
|
+
import { homedir } from 'node:os'
|
|
13
|
+
import path from 'node:path'
|
|
14
|
+
import { isTemplateSourceId, type TemplateCase, type TemplateFavorite } from './protocol.ts'
|
|
15
|
+
|
|
16
|
+
const DATA_DIR = path.join(homedir(), '.dsh', 'dsh-imagegen')
|
|
17
|
+
const FAVORITES_PATH = path.join(DATA_DIR, 'templates', 'favorites.json')
|
|
18
|
+
|
|
19
|
+
/** Refuse to grow the file without bound; the user curates this list. */
|
|
20
|
+
const MAX_FAVORITES = 1000
|
|
21
|
+
|
|
22
|
+
/** In-memory memo of the persisted list. */
|
|
23
|
+
let memo: TemplateFavorite[] | undefined
|
|
24
|
+
|
|
25
|
+
/** Build the stable key of one case within a source. */
|
|
26
|
+
export function templateFavoriteKey(sourceId: string, caseId: number): string {
|
|
27
|
+
return `${sourceId}:${caseId}`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Validate + normalize one raw stored favorite; undefined when unusable. */
|
|
31
|
+
function normalizeFavorite(raw: unknown): TemplateFavorite | undefined {
|
|
32
|
+
if (raw === null || typeof raw !== 'object') return undefined
|
|
33
|
+
const record = raw as Record<string, unknown>
|
|
34
|
+
if (typeof record.key !== 'string' || typeof record.savedAt !== 'string') return undefined
|
|
35
|
+
const sourceId = typeof record.sourceId === 'string' ? record.sourceId : ''
|
|
36
|
+
if (!isTemplateSourceId(sourceId)) return undefined
|
|
37
|
+
if (record.key !== templateFavoriteKey(sourceId, Number(record.case && (record.case as TemplateCase).id))) return undefined
|
|
38
|
+
const rawCase = record.case
|
|
39
|
+
if (rawCase === null || typeof rawCase !== 'object') return undefined
|
|
40
|
+
const item = rawCase as Record<string, unknown>
|
|
41
|
+
const id = Number(item.id)
|
|
42
|
+
const title = typeof item.title === 'string' ? item.title : ''
|
|
43
|
+
const prompt = typeof item.prompt === 'string' ? item.prompt : ''
|
|
44
|
+
if (!Number.isInteger(id) || title === '' || prompt === '') return undefined
|
|
45
|
+
// Keep only the wire fields so hand-edited files cannot smuggle extras.
|
|
46
|
+
const snapshot: TemplateCase = {
|
|
47
|
+
id,
|
|
48
|
+
title,
|
|
49
|
+
prompt,
|
|
50
|
+
category: typeof item.category === 'string' ? item.category : '',
|
|
51
|
+
categoryZh: typeof item.categoryZh === 'string' ? item.categoryZh : '',
|
|
52
|
+
styles: Array.isArray(item.styles) ? item.styles.map(String) : [],
|
|
53
|
+
scenes: Array.isArray(item.scenes) ? item.scenes.map(String) : [],
|
|
54
|
+
sourceLabel: typeof item.sourceLabel === 'string' ? item.sourceLabel : '',
|
|
55
|
+
sourceUrl: typeof item.sourceUrl === 'string' ? item.sourceUrl : '',
|
|
56
|
+
githubUrl: typeof item.githubUrl === 'string' ? item.githubUrl : '',
|
|
57
|
+
image: typeof item.image === 'string' ? item.image : '',
|
|
58
|
+
featured: item.featured === true,
|
|
59
|
+
}
|
|
60
|
+
return { key: record.key, sourceId, savedAt: record.savedAt, case: snapshot }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Read + parse the favorites file (memoized). */
|
|
64
|
+
export async function listTemplateFavorites(): Promise<TemplateFavorite[]> {
|
|
65
|
+
if (memo !== undefined) return memo
|
|
66
|
+
try {
|
|
67
|
+
const parsed: unknown = JSON.parse(await fs.readFile(FAVORITES_PATH, 'utf8'))
|
|
68
|
+
memo = Array.isArray(parsed)
|
|
69
|
+
? parsed.map(normalizeFavorite).filter((entry): entry is TemplateFavorite => entry !== undefined)
|
|
70
|
+
: []
|
|
71
|
+
} catch {
|
|
72
|
+
memo = []
|
|
73
|
+
}
|
|
74
|
+
return memo
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Persist the list atomically and update the memo. */
|
|
78
|
+
async function writeFavorites(entries: TemplateFavorite[]): Promise<void> {
|
|
79
|
+
memo = entries
|
|
80
|
+
await fs.mkdir(path.dirname(FAVORITES_PATH), { recursive: true })
|
|
81
|
+
const tmp = `${FAVORITES_PATH}.tmp-${process.pid}`
|
|
82
|
+
await fs.writeFile(tmp, JSON.stringify(entries, null, 2), 'utf8')
|
|
83
|
+
await fs.rename(tmp, FAVORITES_PATH)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Star one template. Re-starring refreshes the snapshot and is idempotent. */
|
|
87
|
+
export async function addTemplateFavorite(sourceId: string, item: TemplateCase): Promise<TemplateFavorite[]> {
|
|
88
|
+
if (!isTemplateSourceId(sourceId)) throw new Error(`未知的模板库来源:${sourceId}`)
|
|
89
|
+
const key = templateFavoriteKey(sourceId, item.id)
|
|
90
|
+
const rest = (await listTemplateFavorites()).filter(entry => entry.key !== key)
|
|
91
|
+
const entry: TemplateFavorite = { key, sourceId, savedAt: new Date().toISOString(), case: item }
|
|
92
|
+
const next = [entry, ...rest].slice(0, MAX_FAVORITES)
|
|
93
|
+
await writeFavorites(next)
|
|
94
|
+
return next
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Unstar one template by key; unknown keys are a no-op. */
|
|
98
|
+
export async function removeTemplateFavorite(key: string): Promise<TemplateFavorite[]> {
|
|
99
|
+
const next = (await listTemplateFavorites()).filter(entry => entry.key !== key)
|
|
100
|
+
if (next.length === memo?.length) return next
|
|
101
|
+
await writeFavorites(next)
|
|
102
|
+
return next
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Drop the in-memory memo (tests). */
|
|
106
|
+
export function clearTemplateFavoritesMemo(): void {
|
|
107
|
+
memo = undefined
|
|
108
|
+
}
|