ima2-gen 1.1.7 → 1.1.9
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 +56 -27
- package/bin/commands/annotate.js +137 -0
- package/bin/commands/annotate.ts +118 -0
- package/bin/commands/cancel.js +37 -33
- package/bin/commands/cancel.ts +45 -0
- package/bin/commands/canvas-versions.js +91 -0
- package/bin/commands/canvas-versions.ts +80 -0
- package/bin/commands/cardnews.js +293 -0
- package/bin/commands/cardnews.ts +248 -0
- package/bin/commands/comfy.js +63 -0
- package/bin/commands/comfy.ts +54 -0
- package/bin/commands/config.js +270 -0
- package/bin/commands/config.ts +265 -0
- package/bin/commands/edit.js +97 -72
- package/bin/commands/edit.ts +116 -0
- package/bin/commands/gen.js +140 -118
- package/bin/commands/gen.ts +176 -0
- package/bin/commands/history.js +164 -0
- package/bin/commands/history.ts +145 -0
- package/bin/commands/ls.js +60 -42
- package/bin/commands/ls.ts +60 -0
- package/bin/commands/metadata.js +45 -0
- package/bin/commands/metadata.ts +36 -0
- package/bin/commands/multimode.js +159 -0
- package/bin/commands/multimode.ts +146 -0
- package/bin/commands/node.js +176 -0
- package/bin/commands/node.ts +157 -0
- package/bin/commands/observability.js +201 -0
- package/bin/commands/observability.ts +176 -0
- package/bin/commands/ping.js +26 -20
- package/bin/commands/ping.ts +29 -0
- package/bin/commands/prompt.js +506 -0
- package/bin/commands/prompt.ts +421 -0
- package/bin/commands/ps.js +78 -71
- package/bin/commands/ps.ts +78 -0
- package/bin/commands/session.js +308 -0
- package/bin/commands/session.ts +265 -0
- package/bin/commands/show.js +75 -40
- package/bin/commands/show.ts +69 -0
- package/bin/ima2.js +324 -310
- package/bin/ima2.ts +444 -0
- package/bin/lib/args.js +75 -66
- package/bin/lib/args.ts +73 -0
- package/bin/lib/browser-id.js +15 -0
- package/bin/lib/browser-id.ts +16 -0
- package/bin/lib/client.js +91 -83
- package/bin/lib/client.ts +109 -0
- package/bin/lib/error-hints.js +14 -17
- package/bin/lib/error-hints.ts +23 -0
- package/bin/lib/files.js +26 -28
- package/bin/lib/files.ts +39 -0
- package/bin/lib/output.js +44 -42
- package/bin/lib/output.ts +58 -0
- package/bin/lib/platform.js +60 -56
- package/bin/lib/platform.ts +97 -0
- package/bin/lib/sse.js +73 -0
- package/bin/lib/sse.ts +73 -0
- package/bin/lib/star-prompt.js +69 -76
- package/bin/lib/star-prompt.ts +97 -0
- package/bin/lib/storage-doctor.js +34 -35
- package/bin/lib/storage-doctor.ts +38 -0
- package/config.js +147 -190
- package/config.ts +331 -0
- package/docs/API.md +48 -8
- package/docs/CLI.md +190 -0
- package/docs/FAQ.ko.md +5 -5
- package/docs/FAQ.md +5 -5
- package/docs/README.ja.md +71 -25
- package/docs/README.ko.md +61 -24
- package/docs/README.zh-CN.md +73 -27
- package/lib/assetLifecycle.js +130 -130
- package/lib/assetLifecycle.ts +142 -0
- package/lib/canvasVersionStore.js +135 -153
- package/lib/canvasVersionStore.ts +181 -0
- package/lib/cardNewsGenerator.js +127 -142
- package/lib/cardNewsGenerator.ts +162 -0
- package/lib/cardNewsJobStore.js +78 -84
- package/lib/cardNewsJobStore.ts +107 -0
- package/lib/cardNewsManifestStore.js +88 -93
- package/lib/cardNewsManifestStore.ts +112 -0
- package/lib/cardNewsPlanner.js +157 -152
- package/lib/cardNewsPlanner.ts +180 -0
- package/lib/cardNewsPlannerClient.js +101 -98
- package/lib/cardNewsPlannerClient.ts +114 -0
- package/lib/cardNewsPlannerPrompt.js +56 -56
- package/lib/cardNewsPlannerPrompt.ts +60 -0
- package/lib/cardNewsPlannerSchema.js +231 -223
- package/lib/cardNewsPlannerSchema.ts +259 -0
- package/lib/cardNewsRoleTemplateStore.js +39 -41
- package/lib/cardNewsRoleTemplateStore.ts +47 -0
- package/lib/cardNewsTemplateStore.js +171 -175
- package/lib/cardNewsTemplateStore.ts +210 -0
- package/lib/codexDetect.js +44 -47
- package/lib/codexDetect.ts +69 -0
- package/lib/comfyBridge.js +164 -184
- package/lib/comfyBridge.ts +214 -0
- package/lib/db.js +41 -51
- package/lib/db.ts +166 -0
- package/lib/errorClassify.js +62 -78
- package/lib/errorClassify.ts +100 -0
- package/lib/generationErrors.js +140 -103
- package/lib/generationErrors.ts +125 -0
- package/lib/historyList.js +149 -147
- package/lib/historyList.ts +164 -0
- package/lib/imageMetadata.js +86 -89
- package/lib/imageMetadata.ts +111 -0
- package/lib/imageMetadataStore.js +46 -51
- package/lib/imageMetadataStore.ts +67 -0
- package/lib/imageModels.js +38 -45
- package/lib/imageModels.ts +52 -0
- package/lib/inflight.js +131 -150
- package/lib/inflight.ts +204 -0
- package/lib/localImportStore.js +105 -0
- package/lib/localImportStore.ts +111 -0
- package/lib/logger.js +105 -112
- package/lib/logger.ts +150 -0
- package/lib/nodeStore.js +65 -64
- package/lib/nodeStore.ts +81 -0
- package/lib/oauthLauncher.js +61 -59
- package/lib/oauthLauncher.ts +64 -0
- package/lib/oauthNormalize.js +15 -19
- package/lib/oauthNormalize.ts +30 -0
- package/lib/oauthProxy.js +834 -832
- package/lib/oauthProxy.ts +995 -0
- package/lib/openDirectory.js +41 -40
- package/lib/openDirectory.ts +45 -0
- package/lib/pngInfo.js +18 -20
- package/lib/pngInfo.ts +26 -0
- package/lib/promptImport/curatedSources.js +135 -0
- package/lib/promptImport/curatedSources.ts +139 -0
- package/lib/promptImport/discoveryRegistry.js +218 -0
- package/lib/promptImport/discoveryRegistry.ts +236 -0
- package/lib/promptImport/errors.js +10 -10
- package/lib/promptImport/errors.ts +18 -0
- package/lib/promptImport/githubDiscovery.js +238 -0
- package/lib/promptImport/githubDiscovery.ts +248 -0
- package/lib/promptImport/githubFolder.js +302 -0
- package/lib/promptImport/githubFolder.ts +308 -0
- package/lib/promptImport/githubSource.js +194 -171
- package/lib/promptImport/githubSource.ts +239 -0
- package/lib/promptImport/gptImageHints.js +61 -0
- package/lib/promptImport/gptImageHints.ts +68 -0
- package/lib/promptImport/parsePromptCandidates.js +110 -112
- package/lib/promptImport/parsePromptCandidates.ts +153 -0
- package/lib/promptImport/promptIndex.js +230 -0
- package/lib/promptImport/promptIndex.ts +248 -0
- package/lib/promptImport/rankPromptCandidates.js +52 -0
- package/lib/promptImport/rankPromptCandidates.ts +49 -0
- package/lib/providerOptions.js +31 -0
- package/lib/providerOptions.ts +41 -0
- package/lib/referenceImageCompress.js +51 -62
- package/lib/referenceImageCompress.ts +75 -0
- package/lib/refs.js +93 -81
- package/lib/refs.ts +117 -0
- package/lib/requestLogger.js +32 -38
- package/lib/requestLogger.ts +48 -0
- package/lib/responsesImageAdapter.js +351 -0
- package/lib/responsesImageAdapter.ts +352 -0
- package/lib/runtimePorts.js +71 -73
- package/lib/runtimePorts.ts +93 -0
- package/lib/sessionStore.js +179 -230
- package/lib/sessionStore.ts +272 -0
- package/lib/storageMigration.js +247 -245
- package/lib/storageMigration.ts +284 -0
- package/lib/styleSheet.js +86 -90
- package/lib/styleSheet.ts +128 -0
- package/lib/systemTrash.js +18 -0
- package/lib/systemTrash.ts +20 -0
- package/package.json +26 -10
- package/routes/annotations.js +76 -79
- package/routes/annotations.ts +95 -0
- package/routes/canvasVersions.js +50 -54
- package/routes/canvasVersions.ts +64 -0
- package/routes/cardNews.js +158 -171
- package/routes/cardNews.ts +183 -0
- package/routes/comfy.js +23 -31
- package/routes/comfy.ts +39 -0
- package/routes/edit.js +183 -214
- package/routes/edit.ts +230 -0
- package/routes/generate.js +269 -291
- package/routes/generate.ts +309 -0
- package/routes/health.js +102 -107
- package/routes/health.ts +114 -0
- package/routes/history.js +136 -144
- package/routes/history.ts +153 -0
- package/routes/imageImport.js +33 -0
- package/routes/imageImport.ts +33 -0
- package/routes/index.js +18 -16
- package/routes/index.ts +35 -0
- package/routes/metadata.js +60 -64
- package/routes/metadata.ts +71 -0
- package/routes/multimode.js +228 -263
- package/routes/multimode.ts +280 -0
- package/routes/nodes.js +378 -424
- package/routes/nodes.ts +455 -0
- package/routes/promptImport.js +291 -152
- package/routes/promptImport.ts +354 -0
- package/routes/prompts.js +333 -360
- package/routes/prompts.ts +379 -0
- package/routes/sessions.js +277 -285
- package/routes/sessions.ts +292 -0
- package/routes/storage.js +29 -31
- package/routes/storage.ts +39 -0
- package/server.js +189 -196
- package/server.ts +235 -0
- package/ui/dist/.vite/manifest.json +101 -0
- package/ui/dist/assets/CardNewsWorkspace-BJOCey7Z.js +2 -0
- package/ui/dist/assets/NodeCanvas-BZV40eAE.css +1 -0
- package/ui/dist/assets/NodeCanvas-C3dzYNsk.js +7 -0
- package/ui/dist/assets/PromptImportDialog-Dqu1VpUh.js +2 -0
- package/ui/dist/assets/PromptImportDiscoverySection-Dg8T9X0L.js +1 -0
- package/ui/dist/assets/PromptImportFolderSection-DBaqsFO4.js +1 -0
- package/ui/dist/assets/PromptLibraryPanel-p5QqR97M.js +2 -0
- package/ui/dist/assets/SettingsWorkspace-B5bSAZ6u.js +1 -0
- package/ui/dist/assets/index-C9cXwiWE.js +25 -0
- package/ui/dist/assets/index-CGMIkZXn.css +1 -0
- package/ui/dist/assets/index-Cvld7dUZ.js +1 -0
- package/ui/dist/index.html +6 -3
- package/assets/screenshot.png +0 -0
- package/assets/screenshots/classic-generate-light.png +0 -0
- package/assets/screenshots/node-graph-branching.png +0 -0
- package/assets/screenshots/settings-oauth-generation.png +0 -0
- package/assets/screenshots/settings-workspace.png +0 -0
- package/assets/screenshots/style-sheet-editor.png +0 -0
- package/integrations/comfyui/ima2_gen_bridge/__pycache__/__init__.cpython-313.pyc +0 -0
- package/integrations/comfyui/ima2_gen_bridge/__pycache__/nodes.cpython-313.pyc +0 -0
- package/ui/dist/assets/index-DARPdT4Q.css +0 -1
- package/ui/dist/assets/index-ht80GMq4.js +0 -31
- package/ui/dist/assets/index-ht80GMq4.js.map +0 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { ulid } from "ulid";
|
|
2
|
+
|
|
3
|
+
const jobs = new Map();
|
|
4
|
+
const TTL_MS = 30 * 60 * 1000;
|
|
5
|
+
|
|
6
|
+
function summarize(job) {
|
|
7
|
+
const generated = job.cards.filter((card) => card.status === "generated").length;
|
|
8
|
+
const errors = job.cards.filter((card) => card.status === "error").length;
|
|
9
|
+
return {
|
|
10
|
+
jobId: job.jobId,
|
|
11
|
+
setId: job.setId,
|
|
12
|
+
status: job.status,
|
|
13
|
+
total: job.cards.length,
|
|
14
|
+
generated,
|
|
15
|
+
errors,
|
|
16
|
+
cards: job.cards,
|
|
17
|
+
updatedAt: job.updatedAt,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function statusFromCards(cards) {
|
|
22
|
+
const active = cards.some((card) => card.status === "queued" || card.status === "generating");
|
|
23
|
+
if (active) return "running";
|
|
24
|
+
const errors = cards.some((card) => card.status === "error");
|
|
25
|
+
const generated = cards.some((card) => card.status === "generated");
|
|
26
|
+
if (errors && generated) return "partial";
|
|
27
|
+
if (errors) return "error";
|
|
28
|
+
return "done";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createCardNewsJob(plan) {
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
const job = {
|
|
34
|
+
jobId: `cj_${ulid()}`,
|
|
35
|
+
setId: plan.setId,
|
|
36
|
+
status: "queued",
|
|
37
|
+
plan,
|
|
38
|
+
cards: (plan.cards || []).map((card) => ({
|
|
39
|
+
id: card.id,
|
|
40
|
+
order: card.order,
|
|
41
|
+
status: card.locked ? "skipped" : "queued",
|
|
42
|
+
textFields: Array.isArray(card.textFields) ? card.textFields : [],
|
|
43
|
+
})),
|
|
44
|
+
createdAt: now,
|
|
45
|
+
updatedAt: now,
|
|
46
|
+
};
|
|
47
|
+
jobs.set(job.jobId, job);
|
|
48
|
+
return summarize(job);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getCardNewsJob(jobId) {
|
|
52
|
+
const job = jobs.get(jobId);
|
|
53
|
+
if (!job) return null;
|
|
54
|
+
return summarize(job);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function updateCardNewsJob(jobId, patch) {
|
|
58
|
+
const job = jobs.get(jobId);
|
|
59
|
+
if (!job) return null;
|
|
60
|
+
Object.assign(job, patch, { updatedAt: Date.now() });
|
|
61
|
+
return summarize(job);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function updateCardNewsJobCard(jobId, cardId, patch) {
|
|
65
|
+
const job = jobs.get(jobId);
|
|
66
|
+
if (!job) return null;
|
|
67
|
+
job.cards = job.cards.map((card) => (
|
|
68
|
+
card.id === cardId ? {
|
|
69
|
+
...card,
|
|
70
|
+
...patch,
|
|
71
|
+
textFields: Array.isArray(patch.textFields) ? patch.textFields : card.textFields,
|
|
72
|
+
} : card
|
|
73
|
+
));
|
|
74
|
+
job.status = statusFromCards(job.cards);
|
|
75
|
+
job.updatedAt = Date.now();
|
|
76
|
+
return summarize(job);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function finishCardNewsJob(jobId) {
|
|
80
|
+
const job = jobs.get(jobId);
|
|
81
|
+
if (!job) return null;
|
|
82
|
+
job.status = statusFromCards(job.cards);
|
|
83
|
+
job.updatedAt = Date.now();
|
|
84
|
+
return summarize(job);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getCardNewsJobPlan(jobId) {
|
|
88
|
+
return jobs.get(jobId)?.plan || null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function retryCardNewsJob(jobId, cardIds) {
|
|
92
|
+
const job = jobs.get(jobId);
|
|
93
|
+
if (!job) return null;
|
|
94
|
+
const wanted = new Set(cardIds || []);
|
|
95
|
+
job.cards = job.cards.map((card) => (
|
|
96
|
+
wanted.has(card.id) && card.status === "error" ? { ...card, status: "queued", error: undefined } : card
|
|
97
|
+
));
|
|
98
|
+
job.status = "queued";
|
|
99
|
+
job.updatedAt = Date.now();
|
|
100
|
+
return summarize(job);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function reapCardNewsJobs(now = Date.now()) {
|
|
104
|
+
for (const [jobId, job] of jobs.entries()) {
|
|
105
|
+
if (now - job.updatedAt > TTL_MS) jobs.delete(jobId);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -1,112 +1,107 @@
|
|
|
1
1
|
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
|
|
4
3
|
export async function writeCardNewsManifest(generatedDir, manifest) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
const dir = join(generatedDir, "cardnews", manifest.setId);
|
|
5
|
+
await mkdir(dir, { recursive: true });
|
|
6
|
+
await writeFile(join(dir, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
7
|
+
return { dir, manifestFilename: "manifest.json" };
|
|
9
8
|
}
|
|
10
|
-
|
|
11
9
|
export async function writeCardSidecar(dir, filename, sidecar) {
|
|
12
|
-
|
|
10
|
+
await writeFile(join(dir, filename), JSON.stringify(sidecar, null, 2));
|
|
13
11
|
}
|
|
14
|
-
|
|
15
12
|
function cardUrl(setId, imageFilename) {
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
if (!imageFilename)
|
|
14
|
+
return undefined;
|
|
15
|
+
return `/generated/cardnews/${encodeURIComponent(setId)}/${encodeURIComponent(imageFilename)}`;
|
|
18
16
|
}
|
|
19
|
-
|
|
20
17
|
function assertSafeSetId(setId) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
if (typeof setId === "string" && /^[a-zA-Z0-9_-]{3,120}$/.test(setId))
|
|
19
|
+
return setId;
|
|
20
|
+
const err = new Error("Card News set not found");
|
|
21
|
+
err.status = 404;
|
|
22
|
+
err.code = "CARD_NEWS_SET_NOT_FOUND";
|
|
23
|
+
throw err;
|
|
26
24
|
}
|
|
27
|
-
|
|
28
25
|
function manifestToPlan(manifest) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
26
|
+
return {
|
|
27
|
+
setId: manifest.setId,
|
|
28
|
+
title: manifest.title || "Untitled card news",
|
|
29
|
+
topic: manifest.topic || manifest.title || "Untitled card news",
|
|
30
|
+
imageTemplateId: manifest.imageTemplateId || "academy-lesson-square",
|
|
31
|
+
roleTemplateId: manifest.roleTemplateId || "mid-5",
|
|
32
|
+
size: manifest.size || "2048x2048",
|
|
33
|
+
generationStrategy: manifest.generationStrategy || "parallel-template-i2i",
|
|
34
|
+
cards: (manifest.cards || []).map((card, index) => ({
|
|
35
|
+
id: card.cardId || card.id || `card_${index + 1}`,
|
|
36
|
+
order: card.cardOrder || card.order || index + 1,
|
|
37
|
+
role: card.role || "card",
|
|
38
|
+
headline: card.headline || "",
|
|
39
|
+
body: card.body || "",
|
|
40
|
+
visualPrompt: card.visualPrompt || "",
|
|
41
|
+
textFields: Array.isArray(card.textFields) ? card.textFields : [],
|
|
42
|
+
references: card.references || [],
|
|
43
|
+
locked: !!card.locked,
|
|
44
|
+
status: card.status || "generated",
|
|
45
|
+
error: card.error?.message || card.error || undefined,
|
|
46
|
+
imageFilename: card.imageFilename || undefined,
|
|
47
|
+
url: card.url || cardUrl(manifest.setId, card.imageFilename),
|
|
48
|
+
})),
|
|
49
|
+
};
|
|
53
50
|
}
|
|
54
|
-
|
|
55
51
|
export async function readCardNewsSetPlan(ctx, setId) {
|
|
56
|
-
|
|
52
|
+
return manifestToPlan(await readCardNewsManifest(ctx, setId));
|
|
57
53
|
}
|
|
58
|
-
|
|
59
54
|
export async function readCardNewsManifest(ctx, setId) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
55
|
+
const safeSetId = assertSafeSetId(setId);
|
|
56
|
+
try {
|
|
57
|
+
const raw = await readFile(join(ctx.config.storage.generatedDir, "cardnews", safeSetId, "manifest.json"), "utf8");
|
|
58
|
+
return JSON.parse(raw);
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
if (err.code === "CARD_NEWS_SET_NOT_FOUND")
|
|
62
|
+
throw err;
|
|
63
|
+
const notFound = new Error("Card News set not found");
|
|
64
|
+
notFound.status = 404;
|
|
65
|
+
notFound.code = "CARD_NEWS_SET_NOT_FOUND";
|
|
66
|
+
throw notFound;
|
|
67
|
+
}
|
|
74
68
|
}
|
|
75
|
-
|
|
76
69
|
export async function listCardNewsSets(ctx) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
70
|
+
const root = join(ctx.config.storage.generatedDir, "cardnews");
|
|
71
|
+
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
|
|
72
|
+
const sets = [];
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
if (!entry.isDirectory())
|
|
75
|
+
continue;
|
|
76
|
+
try {
|
|
77
|
+
const raw = await readFile(join(root, entry.name, "manifest.json"), "utf8");
|
|
78
|
+
const manifest = JSON.parse(raw);
|
|
79
|
+
const first = (manifest.cards || []).find((card) => card.imageFilename);
|
|
80
|
+
sets.push({
|
|
81
|
+
setId: manifest.setId || entry.name,
|
|
82
|
+
title: manifest.title || "Untitled card news",
|
|
83
|
+
cardCount: manifest.cardCount || manifest.cards?.length || 0,
|
|
84
|
+
createdAt: manifest.createdAt || 0,
|
|
85
|
+
sessionId: manifest.sessionId || null,
|
|
86
|
+
manifestUrl: `/api/cardnews/sets/${encodeURIComponent(manifest.setId || entry.name)}/manifest`,
|
|
87
|
+
folderLabel: `generated/cardnews/${manifest.setId || entry.name}`,
|
|
88
|
+
url: cardUrl(manifest.setId || entry.name, first?.imageFilename),
|
|
89
|
+
cards: (manifest.cards || []).map((card) => ({
|
|
90
|
+
id: card.cardId,
|
|
91
|
+
order: card.cardOrder,
|
|
92
|
+
headline: card.headline,
|
|
93
|
+
body: card.body,
|
|
94
|
+
textFields: Array.isArray(card.textFields) ? card.textFields : [],
|
|
95
|
+
imageFilename: card.imageFilename,
|
|
96
|
+
status: card.status || "generated",
|
|
97
|
+
url: cardUrl(manifest.setId || entry.name, card.imageFilename),
|
|
98
|
+
})),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
console.warn("[card-news] set manifest read failed", entry.name, err.message);
|
|
103
|
+
}
|
|
108
104
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return sets;
|
|
105
|
+
sets.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
|
|
106
|
+
return sets;
|
|
112
107
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export async function writeCardNewsManifest(generatedDir, manifest) {
|
|
5
|
+
const dir = join(generatedDir, "cardnews", manifest.setId);
|
|
6
|
+
await mkdir(dir, { recursive: true });
|
|
7
|
+
await writeFile(join(dir, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
8
|
+
return { dir, manifestFilename: "manifest.json" };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function writeCardSidecar(dir, filename, sidecar) {
|
|
12
|
+
await writeFile(join(dir, filename), JSON.stringify(sidecar, null, 2));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function cardUrl(setId, imageFilename) {
|
|
16
|
+
if (!imageFilename) return undefined;
|
|
17
|
+
return `/generated/cardnews/${encodeURIComponent(setId)}/${encodeURIComponent(imageFilename)}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function assertSafeSetId(setId) {
|
|
21
|
+
if (typeof setId === "string" && /^[a-zA-Z0-9_-]{3,120}$/.test(setId)) return setId;
|
|
22
|
+
const err: any = new Error("Card News set not found");
|
|
23
|
+
err.status = 404;
|
|
24
|
+
err.code = "CARD_NEWS_SET_NOT_FOUND";
|
|
25
|
+
throw err;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function manifestToPlan(manifest) {
|
|
29
|
+
return {
|
|
30
|
+
setId: manifest.setId,
|
|
31
|
+
title: manifest.title || "Untitled card news",
|
|
32
|
+
topic: manifest.topic || manifest.title || "Untitled card news",
|
|
33
|
+
imageTemplateId: manifest.imageTemplateId || "academy-lesson-square",
|
|
34
|
+
roleTemplateId: manifest.roleTemplateId || "mid-5",
|
|
35
|
+
size: manifest.size || "2048x2048",
|
|
36
|
+
generationStrategy: manifest.generationStrategy || "parallel-template-i2i",
|
|
37
|
+
cards: (manifest.cards || []).map((card, index) => ({
|
|
38
|
+
id: card.cardId || card.id || `card_${index + 1}`,
|
|
39
|
+
order: card.cardOrder || card.order || index + 1,
|
|
40
|
+
role: card.role || "card",
|
|
41
|
+
headline: card.headline || "",
|
|
42
|
+
body: card.body || "",
|
|
43
|
+
visualPrompt: card.visualPrompt || "",
|
|
44
|
+
textFields: Array.isArray(card.textFields) ? card.textFields : [],
|
|
45
|
+
references: card.references || [],
|
|
46
|
+
locked: !!card.locked,
|
|
47
|
+
status: card.status || "generated",
|
|
48
|
+
error: card.error?.message || card.error || undefined,
|
|
49
|
+
imageFilename: card.imageFilename || undefined,
|
|
50
|
+
url: card.url || cardUrl(manifest.setId, card.imageFilename),
|
|
51
|
+
})),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function readCardNewsSetPlan(ctx, setId) {
|
|
56
|
+
return manifestToPlan(await readCardNewsManifest(ctx, setId));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function readCardNewsManifest(ctx, setId) {
|
|
60
|
+
const safeSetId = assertSafeSetId(setId);
|
|
61
|
+
try {
|
|
62
|
+
const raw = await readFile(
|
|
63
|
+
join(ctx.config.storage.generatedDir, "cardnews", safeSetId, "manifest.json"),
|
|
64
|
+
"utf8",
|
|
65
|
+
);
|
|
66
|
+
return JSON.parse(raw);
|
|
67
|
+
} catch (err: any) {
|
|
68
|
+
if (err.code === "CARD_NEWS_SET_NOT_FOUND") throw err;
|
|
69
|
+
const notFound: any = new Error("Card News set not found");
|
|
70
|
+
notFound.status = 404;
|
|
71
|
+
notFound.code = "CARD_NEWS_SET_NOT_FOUND";
|
|
72
|
+
throw notFound;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function listCardNewsSets(ctx) {
|
|
77
|
+
const root = join(ctx.config.storage.generatedDir, "cardnews");
|
|
78
|
+
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
|
|
79
|
+
const sets = [];
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
if (!entry.isDirectory()) continue;
|
|
82
|
+
try {
|
|
83
|
+
const raw = await readFile(join(root, entry.name, "manifest.json"), "utf8");
|
|
84
|
+
const manifest = JSON.parse(raw);
|
|
85
|
+
const first = (manifest.cards || []).find((card) => card.imageFilename);
|
|
86
|
+
sets.push({
|
|
87
|
+
setId: manifest.setId || entry.name,
|
|
88
|
+
title: manifest.title || "Untitled card news",
|
|
89
|
+
cardCount: manifest.cardCount || manifest.cards?.length || 0,
|
|
90
|
+
createdAt: manifest.createdAt || 0,
|
|
91
|
+
sessionId: manifest.sessionId || null,
|
|
92
|
+
manifestUrl: `/api/cardnews/sets/${encodeURIComponent(manifest.setId || entry.name)}/manifest`,
|
|
93
|
+
folderLabel: `generated/cardnews/${manifest.setId || entry.name}`,
|
|
94
|
+
url: cardUrl(manifest.setId || entry.name, first?.imageFilename),
|
|
95
|
+
cards: (manifest.cards || []).map((card) => ({
|
|
96
|
+
id: card.cardId,
|
|
97
|
+
order: card.cardOrder,
|
|
98
|
+
headline: card.headline,
|
|
99
|
+
body: card.body,
|
|
100
|
+
textFields: Array.isArray(card.textFields) ? card.textFields : [],
|
|
101
|
+
imageFilename: card.imageFilename,
|
|
102
|
+
status: card.status || "generated",
|
|
103
|
+
url: cardUrl(manifest.setId || entry.name, card.imageFilename),
|
|
104
|
+
})),
|
|
105
|
+
});
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.warn("[card-news] set manifest read failed", entry.name, err.message);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
sets.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
|
|
111
|
+
return sets;
|
|
112
|
+
}
|