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,259 @@
|
|
|
1
|
+
export const CARD_NEWS_TEXT_KINDS = ["headline", "body", "caption", "cta", "badge", "number"];
|
|
2
|
+
export const CARD_NEWS_RENDER_MODES = ["in-image", "ui-only"];
|
|
3
|
+
export const CARD_NEWS_PLACEMENTS = [
|
|
4
|
+
"top-left",
|
|
5
|
+
"top-center",
|
|
6
|
+
"top-right",
|
|
7
|
+
"center-left",
|
|
8
|
+
"center",
|
|
9
|
+
"center-right",
|
|
10
|
+
"bottom-left",
|
|
11
|
+
"bottom-center",
|
|
12
|
+
"bottom-right",
|
|
13
|
+
"free",
|
|
14
|
+
];
|
|
15
|
+
export const CARD_NEWS_HIERARCHIES = ["primary", "secondary", "supporting"];
|
|
16
|
+
export const CARD_NEWS_TEXT_SOURCES = ["planner", "user"];
|
|
17
|
+
|
|
18
|
+
const TEXT_FIELD_SCHEMA = {
|
|
19
|
+
type: "object",
|
|
20
|
+
additionalProperties: false,
|
|
21
|
+
required: [
|
|
22
|
+
"id",
|
|
23
|
+
"kind",
|
|
24
|
+
"text",
|
|
25
|
+
"renderMode",
|
|
26
|
+
"placement",
|
|
27
|
+
"slotId",
|
|
28
|
+
"hierarchy",
|
|
29
|
+
"maxChars",
|
|
30
|
+
"language",
|
|
31
|
+
"source",
|
|
32
|
+
],
|
|
33
|
+
properties: {
|
|
34
|
+
id: { type: "string" },
|
|
35
|
+
kind: { type: "string", enum: CARD_NEWS_TEXT_KINDS },
|
|
36
|
+
text: { type: "string" },
|
|
37
|
+
renderMode: { type: "string", enum: CARD_NEWS_RENDER_MODES },
|
|
38
|
+
placement: { type: "string", enum: CARD_NEWS_PLACEMENTS },
|
|
39
|
+
slotId: { type: ["string", "null"] },
|
|
40
|
+
hierarchy: { type: "string", enum: CARD_NEWS_HIERARCHIES },
|
|
41
|
+
maxChars: { type: ["integer", "null"] },
|
|
42
|
+
language: { type: ["string", "null"] },
|
|
43
|
+
source: { type: "string", enum: CARD_NEWS_TEXT_SOURCES },
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const CARD_NEWS_PLANNER_SCHEMA = {
|
|
48
|
+
type: "object",
|
|
49
|
+
additionalProperties: false,
|
|
50
|
+
required: ["title", "topic", "cards"],
|
|
51
|
+
properties: {
|
|
52
|
+
title: { type: "string" },
|
|
53
|
+
topic: { type: "string" },
|
|
54
|
+
audience: { type: "string" },
|
|
55
|
+
goal: { type: "string" },
|
|
56
|
+
cards: {
|
|
57
|
+
type: "array",
|
|
58
|
+
items: {
|
|
59
|
+
type: "object",
|
|
60
|
+
additionalProperties: false,
|
|
61
|
+
required: ["order", "role", "headline", "body", "visualPrompt", "textFields", "references", "locked"],
|
|
62
|
+
properties: {
|
|
63
|
+
order: { type: "integer" },
|
|
64
|
+
role: { type: "string" },
|
|
65
|
+
headline: { type: "string" },
|
|
66
|
+
body: { type: "string" },
|
|
67
|
+
visualPrompt: { type: "string" },
|
|
68
|
+
textFields: { type: "array", items: TEXT_FIELD_SCHEMA },
|
|
69
|
+
references: { type: "array", items: { type: "string" } },
|
|
70
|
+
locked: { type: "boolean" },
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
function asText(value, fallback = "") {
|
|
78
|
+
return typeof value === "string" && value.trim() ? value.trim() : fallback;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function detectBriefLanguage(input: any = {}) {
|
|
82
|
+
const text = [input.topic, input.audience, input.goal, input.contentBrief].filter(Boolean).join(" ");
|
|
83
|
+
if (/[가-힣]/.test(text)) return "ko";
|
|
84
|
+
if (/[A-Za-z]/.test(text)) return "en";
|
|
85
|
+
return "und";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function fallbackCopy(input: any = {}, kind = "body") {
|
|
89
|
+
const lang = detectBriefLanguage(input);
|
|
90
|
+
const topic = asText(input.topic, asText(input.title, "Card news"));
|
|
91
|
+
const goal = asText(input.goal, topic);
|
|
92
|
+
const brief = asText(input.contentBrief, goal);
|
|
93
|
+
if (kind === "headline") return topic;
|
|
94
|
+
if (lang === "ko") return brief || `${topic} 핵심 내용을 정리합니다.`;
|
|
95
|
+
if (lang === "en") return brief || `Summarize the key point for ${topic}.`;
|
|
96
|
+
return brief || topic;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function normalizeTextField(field, index) {
|
|
100
|
+
if (!field || typeof field !== "object") return null;
|
|
101
|
+
const text = asText(field.text);
|
|
102
|
+
if (!text) return null;
|
|
103
|
+
return {
|
|
104
|
+
id: asText(field.id, `tf_${index + 1}`),
|
|
105
|
+
kind: CARD_NEWS_TEXT_KINDS.includes(field.kind) ? field.kind : "body",
|
|
106
|
+
text,
|
|
107
|
+
renderMode: CARD_NEWS_RENDER_MODES.includes(field.renderMode) ? field.renderMode : "in-image",
|
|
108
|
+
placement: CARD_NEWS_PLACEMENTS.includes(field.placement) ? field.placement : "free",
|
|
109
|
+
slotId: typeof field.slotId === "string" && field.slotId.trim() ? field.slotId.trim() : null,
|
|
110
|
+
hierarchy: CARD_NEWS_HIERARCHIES.includes(field.hierarchy) ? field.hierarchy : "supporting",
|
|
111
|
+
maxChars: Number.isInteger(field.maxChars) ? field.maxChars : null,
|
|
112
|
+
language: typeof field.language === "string" && field.language.trim() ? field.language.trim() : null,
|
|
113
|
+
source: CARD_NEWS_TEXT_SOURCES.includes(field.source) ? field.source : "planner",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function normalizeTextFields(value) {
|
|
118
|
+
if (!Array.isArray(value)) return [];
|
|
119
|
+
return value.map(normalizeTextField).filter(Boolean);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function stripExactVisibleText(visualPrompt, textFields) {
|
|
123
|
+
let next = asText(visualPrompt);
|
|
124
|
+
for (const field of textFields) {
|
|
125
|
+
const text = asText(field.text);
|
|
126
|
+
if (text.length >= 4 && next.includes(text)) {
|
|
127
|
+
next = next.split(text).join("visible text box");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return next.trim();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function normalizeCard(card, role, index, input) {
|
|
134
|
+
const topic = asText(input.topic, "Card news");
|
|
135
|
+
const textFields = normalizeTextFields(card?.textFields);
|
|
136
|
+
return {
|
|
137
|
+
order: index + 1,
|
|
138
|
+
role: role.role,
|
|
139
|
+
headline: asText(card?.headline, index === 0 ? topic : fallbackCopy(input, "headline")),
|
|
140
|
+
body: asText(card?.body, fallbackCopy(input, "body")),
|
|
141
|
+
visualPrompt: stripExactVisibleText(
|
|
142
|
+
asText(card?.visualPrompt, `${asText(role.promptHint, role.role)}, ${topic}`),
|
|
143
|
+
textFields,
|
|
144
|
+
),
|
|
145
|
+
textFields,
|
|
146
|
+
references: Array.isArray(card?.references)
|
|
147
|
+
? card.references.filter((ref) => typeof ref === "string")
|
|
148
|
+
: [],
|
|
149
|
+
locked: false,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function repairPlannerOutput(output, input: any = {}) {
|
|
154
|
+
const roles = input.roleTemplate?.roles || [];
|
|
155
|
+
const cards = roles.map((role, index) => {
|
|
156
|
+
const original = Array.isArray(output?.cards) ? output.cards[index] : null;
|
|
157
|
+
return normalizeCard(original, role, index, input);
|
|
158
|
+
});
|
|
159
|
+
return {
|
|
160
|
+
ok: true,
|
|
161
|
+
repaired: true,
|
|
162
|
+
errors: [],
|
|
163
|
+
plan: {
|
|
164
|
+
title: asText(output?.title, asText(input.topic, "Untitled card news")),
|
|
165
|
+
topic: asText(output?.topic, asText(input.topic, "Untitled card news")),
|
|
166
|
+
audience: asText(output?.audience, asText(input.audience)),
|
|
167
|
+
goal: asText(output?.goal, asText(input.goal)),
|
|
168
|
+
cards,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function validateTextField(field, path, errors) {
|
|
174
|
+
if (!field || typeof field !== "object") {
|
|
175
|
+
errors.push(`${path} must be object`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (typeof field.id !== "string") errors.push(`${path}.id must be string`);
|
|
179
|
+
if (!CARD_NEWS_TEXT_KINDS.includes(field.kind)) errors.push(`${path}.kind invalid`);
|
|
180
|
+
if (typeof field.text !== "string") errors.push(`${path}.text must be string`);
|
|
181
|
+
if (typeof field.text === "string" && !field.text.trim()) errors.push(`${path}.text must not be empty`);
|
|
182
|
+
if (!CARD_NEWS_RENDER_MODES.includes(field.renderMode)) errors.push(`${path}.renderMode invalid`);
|
|
183
|
+
if (!CARD_NEWS_PLACEMENTS.includes(field.placement)) errors.push(`${path}.placement invalid`);
|
|
184
|
+
if (!(typeof field.slotId === "string" || field.slotId === null)) errors.push(`${path}.slotId invalid`);
|
|
185
|
+
if (!CARD_NEWS_HIERARCHIES.includes(field.hierarchy)) errors.push(`${path}.hierarchy invalid`);
|
|
186
|
+
if (!(Number.isInteger(field.maxChars) || field.maxChars === null)) errors.push(`${path}.maxChars invalid`);
|
|
187
|
+
if (!(typeof field.language === "string" || field.language === null)) errors.push(`${path}.language invalid`);
|
|
188
|
+
if (!CARD_NEWS_TEXT_SOURCES.includes(field.source)) errors.push(`${path}.source invalid`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function validatePlannerOutput(output, roleTemplate) {
|
|
192
|
+
const errors = [];
|
|
193
|
+
if (!output || typeof output !== "object" || Array.isArray(output)) {
|
|
194
|
+
return { ok: false, repaired: false, errors: ["output must be an object"] };
|
|
195
|
+
}
|
|
196
|
+
if (typeof output.title !== "string") errors.push("title must be a string");
|
|
197
|
+
if (typeof output.topic !== "string") errors.push("topic must be a string");
|
|
198
|
+
if (!Array.isArray(output.cards)) errors.push("cards must be an array");
|
|
199
|
+
|
|
200
|
+
const roles = roleTemplate?.roles || [];
|
|
201
|
+
if (Array.isArray(output.cards) && output.cards.length !== roles.length) {
|
|
202
|
+
errors.push("cards length must match role template");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const cards = Array.isArray(output.cards) ? output.cards : [];
|
|
206
|
+
cards.forEach((card, index) => {
|
|
207
|
+
const expected = roles[index];
|
|
208
|
+
if (!card || typeof card !== "object") {
|
|
209
|
+
errors.push(`card ${index + 1} must be an object`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (card.order !== index + 1) errors.push(`card ${index + 1} order mismatch`);
|
|
213
|
+
if (expected && card.role !== expected.role) errors.push(`card ${index + 1} role mismatch`);
|
|
214
|
+
for (const key of ["headline", "body", "visualPrompt"]) {
|
|
215
|
+
if (typeof card[key] !== "string") errors.push(`card ${index + 1} ${key} must be string`);
|
|
216
|
+
}
|
|
217
|
+
if (!Array.isArray(card.textFields)) errors.push(`card ${index + 1} textFields must be array`);
|
|
218
|
+
if (Array.isArray(card.textFields)) {
|
|
219
|
+
card.textFields.forEach((field, fieldIndex) =>
|
|
220
|
+
validateTextField(field, `card ${index + 1} textFields ${fieldIndex + 1}`, errors));
|
|
221
|
+
for (const field of card.textFields) {
|
|
222
|
+
if (
|
|
223
|
+
field?.renderMode === "in-image" &&
|
|
224
|
+
typeof field.text === "string" &&
|
|
225
|
+
field.text.trim().length >= 4 &&
|
|
226
|
+
typeof card.visualPrompt === "string" &&
|
|
227
|
+
card.visualPrompt.includes(field.text.trim())
|
|
228
|
+
) {
|
|
229
|
+
errors.push(`card ${index + 1} visualPrompt must not duplicate exact visible text`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (!Array.isArray(card.references)) errors.push(`card ${index + 1} references must be array`);
|
|
234
|
+
if (card.locked !== false) errors.push(`card ${index + 1} locked must be false`);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
if (errors.length) return { ok: false, repaired: false, errors };
|
|
238
|
+
return {
|
|
239
|
+
ok: true,
|
|
240
|
+
repaired: false,
|
|
241
|
+
errors: [],
|
|
242
|
+
plan: {
|
|
243
|
+
title: output.title.trim(),
|
|
244
|
+
topic: output.topic.trim(),
|
|
245
|
+
audience: asText(output.audience),
|
|
246
|
+
goal: asText(output.goal),
|
|
247
|
+
cards: cards.map((card) => ({
|
|
248
|
+
order: card.order,
|
|
249
|
+
role: card.role,
|
|
250
|
+
headline: card.headline.trim(),
|
|
251
|
+
body: card.body.trim(),
|
|
252
|
+
visualPrompt: card.visualPrompt.trim(),
|
|
253
|
+
textFields: normalizeTextFields(card.textFields),
|
|
254
|
+
references: card.references.filter((ref) => typeof ref === "string"),
|
|
255
|
+
locked: false,
|
|
256
|
+
})),
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
@@ -1,47 +1,45 @@
|
|
|
1
1
|
const ROLE_TEMPLATES = [
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
2
|
+
{
|
|
3
|
+
id: "short-3",
|
|
4
|
+
name: "Short 3",
|
|
5
|
+
defaultCount: 3,
|
|
6
|
+
roles: [
|
|
7
|
+
{ role: "hook", required: true, promptHint: "strong opening card", preferredSlots: ["title", "visual"] },
|
|
8
|
+
{ role: "core", required: true, promptHint: "main explanation card", preferredSlots: ["visual", "body"] },
|
|
9
|
+
{ role: "cta", required: true, promptHint: "clear call to action card", preferredSlots: ["cta"] },
|
|
10
|
+
],
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: "mid-5",
|
|
14
|
+
name: "Mid 5",
|
|
15
|
+
defaultCount: 5,
|
|
16
|
+
roles: [
|
|
17
|
+
{ role: "cover", required: true, promptHint: "cover card with strong headline", preferredSlots: ["title", "visual"] },
|
|
18
|
+
{ role: "problem", required: true, promptHint: "problem framing card", preferredSlots: ["title", "body"] },
|
|
19
|
+
{ role: "insight", required: true, promptHint: "insight or solution card", preferredSlots: ["visual", "body"] },
|
|
20
|
+
{ role: "example", required: true, promptHint: "example or proof card", preferredSlots: ["visual", "body"] },
|
|
21
|
+
{ role: "cta", required: true, promptHint: "closing call to action card", preferredSlots: ["cta"] },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "long-8",
|
|
26
|
+
name: "Long 8",
|
|
27
|
+
defaultCount: 8,
|
|
28
|
+
roles: [
|
|
29
|
+
{ role: "cover", required: true, promptHint: "cover card", preferredSlots: ["title"] },
|
|
30
|
+
{ role: "problem", required: true, promptHint: "problem card", preferredSlots: ["body"] },
|
|
31
|
+
{ role: "data", required: true, promptHint: "data card", preferredSlots: ["visual"] },
|
|
32
|
+
{ role: "tip1", required: true, promptHint: "first tip card", preferredSlots: ["body"] },
|
|
33
|
+
{ role: "tip2", required: true, promptHint: "second tip card", preferredSlots: ["body"] },
|
|
34
|
+
{ role: "example", required: true, promptHint: "example card", preferredSlots: ["visual"] },
|
|
35
|
+
{ role: "summary", required: true, promptHint: "summary card", preferredSlots: ["body"] },
|
|
36
|
+
{ role: "cta", required: true, promptHint: "call to action card", preferredSlots: ["cta"] },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
39
|
];
|
|
40
|
-
|
|
41
40
|
export function listRoleTemplates() {
|
|
42
|
-
|
|
41
|
+
return ROLE_TEMPLATES;
|
|
43
42
|
}
|
|
44
|
-
|
|
45
43
|
export function getRoleTemplate(roleTemplateId = "mid-5") {
|
|
46
|
-
|
|
44
|
+
return ROLE_TEMPLATES.find((t) => t.id === roleTemplateId) || ROLE_TEMPLATES[1];
|
|
47
45
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const ROLE_TEMPLATES = [
|
|
2
|
+
{
|
|
3
|
+
id: "short-3",
|
|
4
|
+
name: "Short 3",
|
|
5
|
+
defaultCount: 3,
|
|
6
|
+
roles: [
|
|
7
|
+
{ role: "hook", required: true, promptHint: "strong opening card", preferredSlots: ["title", "visual"] },
|
|
8
|
+
{ role: "core", required: true, promptHint: "main explanation card", preferredSlots: ["visual", "body"] },
|
|
9
|
+
{ role: "cta", required: true, promptHint: "clear call to action card", preferredSlots: ["cta"] },
|
|
10
|
+
],
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: "mid-5",
|
|
14
|
+
name: "Mid 5",
|
|
15
|
+
defaultCount: 5,
|
|
16
|
+
roles: [
|
|
17
|
+
{ role: "cover", required: true, promptHint: "cover card with strong headline", preferredSlots: ["title", "visual"] },
|
|
18
|
+
{ role: "problem", required: true, promptHint: "problem framing card", preferredSlots: ["title", "body"] },
|
|
19
|
+
{ role: "insight", required: true, promptHint: "insight or solution card", preferredSlots: ["visual", "body"] },
|
|
20
|
+
{ role: "example", required: true, promptHint: "example or proof card", preferredSlots: ["visual", "body"] },
|
|
21
|
+
{ role: "cta", required: true, promptHint: "closing call to action card", preferredSlots: ["cta"] },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "long-8",
|
|
26
|
+
name: "Long 8",
|
|
27
|
+
defaultCount: 8,
|
|
28
|
+
roles: [
|
|
29
|
+
{ role: "cover", required: true, promptHint: "cover card", preferredSlots: ["title"] },
|
|
30
|
+
{ role: "problem", required: true, promptHint: "problem card", preferredSlots: ["body"] },
|
|
31
|
+
{ role: "data", required: true, promptHint: "data card", preferredSlots: ["visual"] },
|
|
32
|
+
{ role: "tip1", required: true, promptHint: "first tip card", preferredSlots: ["body"] },
|
|
33
|
+
{ role: "tip2", required: true, promptHint: "second tip card", preferredSlots: ["body"] },
|
|
34
|
+
{ role: "example", required: true, promptHint: "example card", preferredSlots: ["visual"] },
|
|
35
|
+
{ role: "summary", required: true, promptHint: "summary card", preferredSlots: ["body"] },
|
|
36
|
+
{ role: "cta", required: true, promptHint: "call to action card", preferredSlots: ["cta"] },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
export function listRoleTemplates() {
|
|
42
|
+
return ROLE_TEMPLATES;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getRoleTemplate(roleTemplateId = "mid-5") {
|
|
46
|
+
return ROLE_TEMPLATES.find((t) => t.id === roleTemplateId) || ROLE_TEMPLATES[1];
|
|
47
|
+
}
|