ima2-gen 1.1.8 → 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.
Files changed (230) hide show
  1. package/README.md +56 -27
  2. package/bin/commands/annotate.js +137 -0
  3. package/bin/commands/annotate.ts +118 -0
  4. package/bin/commands/cancel.js +37 -33
  5. package/bin/commands/cancel.ts +45 -0
  6. package/bin/commands/canvas-versions.js +91 -0
  7. package/bin/commands/canvas-versions.ts +80 -0
  8. package/bin/commands/cardnews.js +293 -0
  9. package/bin/commands/cardnews.ts +248 -0
  10. package/bin/commands/comfy.js +63 -0
  11. package/bin/commands/comfy.ts +54 -0
  12. package/bin/commands/config.js +270 -0
  13. package/bin/commands/config.ts +265 -0
  14. package/bin/commands/edit.js +97 -72
  15. package/bin/commands/edit.ts +116 -0
  16. package/bin/commands/gen.js +140 -118
  17. package/bin/commands/gen.ts +176 -0
  18. package/bin/commands/history.js +164 -0
  19. package/bin/commands/history.ts +145 -0
  20. package/bin/commands/ls.js +60 -42
  21. package/bin/commands/ls.ts +60 -0
  22. package/bin/commands/metadata.js +45 -0
  23. package/bin/commands/metadata.ts +36 -0
  24. package/bin/commands/multimode.js +159 -0
  25. package/bin/commands/multimode.ts +146 -0
  26. package/bin/commands/node.js +176 -0
  27. package/bin/commands/node.ts +157 -0
  28. package/bin/commands/observability.js +201 -0
  29. package/bin/commands/observability.ts +176 -0
  30. package/bin/commands/ping.js +26 -20
  31. package/bin/commands/ping.ts +29 -0
  32. package/bin/commands/prompt.js +506 -0
  33. package/bin/commands/prompt.ts +421 -0
  34. package/bin/commands/ps.js +78 -71
  35. package/bin/commands/ps.ts +78 -0
  36. package/bin/commands/session.js +308 -0
  37. package/bin/commands/session.ts +265 -0
  38. package/bin/commands/show.js +75 -40
  39. package/bin/commands/show.ts +69 -0
  40. package/bin/ima2.js +324 -310
  41. package/bin/ima2.ts +444 -0
  42. package/bin/lib/args.js +75 -66
  43. package/bin/lib/args.ts +73 -0
  44. package/bin/lib/browser-id.js +15 -0
  45. package/bin/lib/browser-id.ts +16 -0
  46. package/bin/lib/client.js +91 -83
  47. package/bin/lib/client.ts +109 -0
  48. package/bin/lib/error-hints.js +14 -17
  49. package/bin/lib/error-hints.ts +23 -0
  50. package/bin/lib/files.js +26 -28
  51. package/bin/lib/files.ts +39 -0
  52. package/bin/lib/output.js +44 -42
  53. package/bin/lib/output.ts +58 -0
  54. package/bin/lib/platform.js +60 -56
  55. package/bin/lib/platform.ts +97 -0
  56. package/bin/lib/sse.js +73 -0
  57. package/bin/lib/sse.ts +73 -0
  58. package/bin/lib/star-prompt.js +69 -76
  59. package/bin/lib/star-prompt.ts +97 -0
  60. package/bin/lib/storage-doctor.js +34 -35
  61. package/bin/lib/storage-doctor.ts +38 -0
  62. package/config.js +147 -243
  63. package/config.ts +331 -0
  64. package/docs/API.md +48 -8
  65. package/docs/CLI.md +190 -0
  66. package/docs/FAQ.ko.md +5 -5
  67. package/docs/FAQ.md +5 -5
  68. package/docs/README.ja.md +71 -25
  69. package/docs/README.ko.md +61 -24
  70. package/docs/README.zh-CN.md +73 -27
  71. package/lib/assetLifecycle.js +130 -130
  72. package/lib/assetLifecycle.ts +142 -0
  73. package/lib/canvasVersionStore.js +135 -153
  74. package/lib/canvasVersionStore.ts +181 -0
  75. package/lib/cardNewsGenerator.js +127 -142
  76. package/lib/cardNewsGenerator.ts +162 -0
  77. package/lib/cardNewsJobStore.js +78 -84
  78. package/lib/cardNewsJobStore.ts +107 -0
  79. package/lib/cardNewsManifestStore.js +88 -93
  80. package/lib/cardNewsManifestStore.ts +112 -0
  81. package/lib/cardNewsPlanner.js +157 -152
  82. package/lib/cardNewsPlanner.ts +180 -0
  83. package/lib/cardNewsPlannerClient.js +101 -98
  84. package/lib/cardNewsPlannerClient.ts +114 -0
  85. package/lib/cardNewsPlannerPrompt.js +56 -56
  86. package/lib/cardNewsPlannerPrompt.ts +60 -0
  87. package/lib/cardNewsPlannerSchema.js +231 -223
  88. package/lib/cardNewsPlannerSchema.ts +259 -0
  89. package/lib/cardNewsRoleTemplateStore.js +39 -41
  90. package/lib/cardNewsRoleTemplateStore.ts +47 -0
  91. package/lib/cardNewsTemplateStore.js +171 -175
  92. package/lib/cardNewsTemplateStore.ts +210 -0
  93. package/lib/codexDetect.js +44 -47
  94. package/lib/codexDetect.ts +69 -0
  95. package/lib/comfyBridge.js +164 -184
  96. package/lib/comfyBridge.ts +214 -0
  97. package/lib/db.js +41 -51
  98. package/lib/db.ts +166 -0
  99. package/lib/errorClassify.js +62 -78
  100. package/lib/errorClassify.ts +100 -0
  101. package/lib/generationErrors.js +140 -103
  102. package/lib/generationErrors.ts +125 -0
  103. package/lib/historyList.js +149 -147
  104. package/lib/historyList.ts +164 -0
  105. package/lib/imageMetadata.js +86 -89
  106. package/lib/imageMetadata.ts +111 -0
  107. package/lib/imageMetadataStore.js +46 -51
  108. package/lib/imageMetadataStore.ts +67 -0
  109. package/lib/imageModels.js +38 -45
  110. package/lib/imageModels.ts +52 -0
  111. package/lib/inflight.js +131 -150
  112. package/lib/inflight.ts +204 -0
  113. package/lib/localImportStore.js +87 -93
  114. package/lib/localImportStore.ts +111 -0
  115. package/lib/logger.js +105 -112
  116. package/lib/logger.ts +150 -0
  117. package/lib/nodeStore.js +65 -64
  118. package/lib/nodeStore.ts +81 -0
  119. package/lib/oauthLauncher.js +61 -59
  120. package/lib/oauthLauncher.ts +64 -0
  121. package/lib/oauthNormalize.js +15 -19
  122. package/lib/oauthNormalize.ts +30 -0
  123. package/lib/oauthProxy.js +834 -832
  124. package/lib/oauthProxy.ts +995 -0
  125. package/lib/openDirectory.js +41 -40
  126. package/lib/openDirectory.ts +45 -0
  127. package/lib/pngInfo.js +18 -20
  128. package/lib/pngInfo.ts +26 -0
  129. package/lib/promptImport/curatedSources.js +125 -129
  130. package/lib/promptImport/curatedSources.ts +139 -0
  131. package/lib/promptImport/discoveryRegistry.js +185 -203
  132. package/lib/promptImport/discoveryRegistry.ts +236 -0
  133. package/lib/promptImport/errors.js +10 -10
  134. package/lib/promptImport/errors.ts +18 -0
  135. package/lib/promptImport/githubDiscovery.js +209 -219
  136. package/lib/promptImport/githubDiscovery.ts +248 -0
  137. package/lib/promptImport/githubFolder.js +253 -259
  138. package/lib/promptImport/githubFolder.ts +308 -0
  139. package/lib/promptImport/githubSource.js +189 -200
  140. package/lib/promptImport/githubSource.ts +239 -0
  141. package/lib/promptImport/gptImageHints.js +49 -56
  142. package/lib/promptImport/gptImageHints.ts +68 -0
  143. package/lib/promptImport/parsePromptCandidates.js +108 -123
  144. package/lib/promptImport/parsePromptCandidates.ts +153 -0
  145. package/lib/promptImport/promptIndex.js +190 -208
  146. package/lib/promptImport/promptIndex.ts +248 -0
  147. package/lib/promptImport/rankPromptCandidates.js +46 -43
  148. package/lib/promptImport/rankPromptCandidates.ts +49 -0
  149. package/lib/providerOptions.js +31 -0
  150. package/lib/providerOptions.ts +41 -0
  151. package/lib/referenceImageCompress.js +51 -62
  152. package/lib/referenceImageCompress.ts +75 -0
  153. package/lib/refs.js +93 -81
  154. package/lib/refs.ts +117 -0
  155. package/lib/requestLogger.js +32 -38
  156. package/lib/requestLogger.ts +48 -0
  157. package/lib/responsesImageAdapter.js +351 -0
  158. package/lib/responsesImageAdapter.ts +352 -0
  159. package/lib/runtimePorts.js +71 -73
  160. package/lib/runtimePorts.ts +93 -0
  161. package/lib/sessionStore.js +179 -230
  162. package/lib/sessionStore.ts +272 -0
  163. package/lib/storageMigration.js +247 -245
  164. package/lib/storageMigration.ts +284 -0
  165. package/lib/styleSheet.js +86 -90
  166. package/lib/styleSheet.ts +128 -0
  167. package/lib/systemTrash.js +18 -0
  168. package/lib/systemTrash.ts +20 -0
  169. package/package.json +26 -10
  170. package/routes/annotations.js +76 -79
  171. package/routes/annotations.ts +95 -0
  172. package/routes/canvasVersions.js +50 -54
  173. package/routes/canvasVersions.ts +64 -0
  174. package/routes/cardNews.js +158 -171
  175. package/routes/cardNews.ts +183 -0
  176. package/routes/comfy.js +23 -31
  177. package/routes/comfy.ts +39 -0
  178. package/routes/edit.js +183 -214
  179. package/routes/edit.ts +230 -0
  180. package/routes/generate.js +269 -291
  181. package/routes/generate.ts +309 -0
  182. package/routes/health.js +102 -107
  183. package/routes/health.ts +114 -0
  184. package/routes/history.js +136 -144
  185. package/routes/history.ts +153 -0
  186. package/routes/imageImport.js +27 -27
  187. package/routes/imageImport.ts +33 -0
  188. package/routes/index.js +17 -17
  189. package/routes/index.ts +35 -0
  190. package/routes/metadata.js +60 -64
  191. package/routes/metadata.ts +71 -0
  192. package/routes/multimode.js +228 -263
  193. package/routes/multimode.ts +280 -0
  194. package/routes/nodes.js +378 -424
  195. package/routes/nodes.ts +455 -0
  196. package/routes/promptImport.js +284 -324
  197. package/routes/promptImport.ts +354 -0
  198. package/routes/prompts.js +333 -360
  199. package/routes/prompts.ts +379 -0
  200. package/routes/sessions.js +277 -285
  201. package/routes/sessions.ts +292 -0
  202. package/routes/storage.js +29 -31
  203. package/routes/storage.ts +39 -0
  204. package/server.js +189 -196
  205. package/server.ts +235 -0
  206. package/ui/dist/.vite/manifest.json +101 -0
  207. package/ui/dist/assets/CardNewsWorkspace-BJOCey7Z.js +2 -0
  208. package/ui/dist/assets/NodeCanvas-BZV40eAE.css +1 -0
  209. package/ui/dist/assets/NodeCanvas-C3dzYNsk.js +7 -0
  210. package/ui/dist/assets/PromptImportDialog-Dqu1VpUh.js +2 -0
  211. package/ui/dist/assets/PromptImportDiscoverySection-Dg8T9X0L.js +1 -0
  212. package/ui/dist/assets/PromptImportFolderSection-DBaqsFO4.js +1 -0
  213. package/ui/dist/assets/PromptLibraryPanel-p5QqR97M.js +2 -0
  214. package/ui/dist/assets/SettingsWorkspace-B5bSAZ6u.js +1 -0
  215. package/ui/dist/assets/index-C9cXwiWE.js +25 -0
  216. package/ui/dist/assets/index-CGMIkZXn.css +1 -0
  217. package/ui/dist/assets/index-Cvld7dUZ.js +1 -0
  218. package/ui/dist/index.html +2 -2
  219. package/assets/phase-a-bg-cleanup-test.png +0 -0
  220. package/assets/screenshot.png +0 -0
  221. package/assets/screenshots/classic-generate-light.png +0 -0
  222. package/assets/screenshots/node-graph-branching.png +0 -0
  223. package/assets/screenshots/settings-oauth-generation.png +0 -0
  224. package/assets/screenshots/settings-workspace.png +0 -0
  225. package/assets/screenshots/style-sheet-editor.png +0 -0
  226. package/integrations/comfyui/ima2_gen_bridge/__pycache__/__init__.cpython-313.pyc +0 -0
  227. package/integrations/comfyui/ima2_gen_bridge/__pycache__/nodes.cpython-313.pyc +0 -0
  228. package/ui/dist/assets/index-BDffwmLs.css +0 -1
  229. package/ui/dist/assets/index-D0fdHLkJ.js +0 -31
  230. package/ui/dist/assets/index-D0fdHLkJ.js.map +0 -1
@@ -1,259 +1,267 @@
1
1
  export const CARD_NEWS_TEXT_KINDS = ["headline", "body", "caption", "cta", "badge", "number"];
2
2
  export const CARD_NEWS_RENDER_MODES = ["in-image", "ui-only"];
3
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",
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
14
  ];
15
15
  export const CARD_NEWS_HIERARCHIES = ["primary", "secondary", "supporting"];
16
16
  export const CARD_NEWS_TEXT_SOURCES = ["planner", "user"];
17
-
18
17
  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
- },
18
+ type: "object",
19
+ additionalProperties: false,
20
+ required: [
21
+ "id",
22
+ "kind",
23
+ "text",
24
+ "renderMode",
25
+ "placement",
26
+ "slotId",
27
+ "hierarchy",
28
+ "maxChars",
29
+ "language",
30
+ "source",
31
+ ],
32
+ properties: {
33
+ id: { type: "string" },
34
+ kind: { type: "string", enum: CARD_NEWS_TEXT_KINDS },
35
+ text: { type: "string" },
36
+ renderMode: { type: "string", enum: CARD_NEWS_RENDER_MODES },
37
+ placement: { type: "string", enum: CARD_NEWS_PLACEMENTS },
38
+ slotId: { type: ["string", "null"] },
39
+ hierarchy: { type: "string", enum: CARD_NEWS_HIERARCHIES },
40
+ maxChars: { type: ["integer", "null"] },
41
+ language: { type: ["string", "null"] },
42
+ source: { type: "string", enum: CARD_NEWS_TEXT_SOURCES },
43
+ },
45
44
  };
46
-
47
45
  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" },
46
+ type: "object",
47
+ additionalProperties: false,
48
+ required: ["title", "topic", "cards"],
49
+ properties: {
50
+ title: { type: "string" },
51
+ topic: { type: "string" },
52
+ audience: { type: "string" },
53
+ goal: { type: "string" },
54
+ cards: {
55
+ type: "array",
56
+ items: {
57
+ type: "object",
58
+ additionalProperties: false,
59
+ required: ["order", "role", "headline", "body", "visualPrompt", "textFields", "references", "locked"],
60
+ properties: {
61
+ order: { type: "integer" },
62
+ role: { type: "string" },
63
+ headline: { type: "string" },
64
+ body: { type: "string" },
65
+ visualPrompt: { type: "string" },
66
+ textFields: { type: "array", items: TEXT_FIELD_SCHEMA },
67
+ references: { type: "array", items: { type: "string" } },
68
+ locked: { type: "boolean" },
69
+ },
70
+ },
71
71
  },
72
- },
73
72
  },
74
- },
75
73
  };
76
-
77
74
  function asText(value, fallback = "") {
78
- return typeof value === "string" && value.trim() ? value.trim() : fallback;
75
+ return typeof value === "string" && value.trim() ? value.trim() : fallback;
79
76
  }
80
-
81
77
  function detectBriefLanguage(input = {}) {
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";
78
+ const text = [input.topic, input.audience, input.goal, input.contentBrief].filter(Boolean).join(" ");
79
+ if (/[가-힣]/.test(text))
80
+ return "ko";
81
+ if (/[A-Za-z]/.test(text))
82
+ return "en";
83
+ return "und";
86
84
  }
87
-
88
85
  function fallbackCopy(input = {}, 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;
86
+ const lang = detectBriefLanguage(input);
87
+ const topic = asText(input.topic, asText(input.title, "Card news"));
88
+ const goal = asText(input.goal, topic);
89
+ const brief = asText(input.contentBrief, goal);
90
+ if (kind === "headline")
91
+ return topic;
92
+ if (lang === "ko")
93
+ return brief || `${topic} 핵심 내용을 정리합니다.`;
94
+ if (lang === "en")
95
+ return brief || `Summarize the key point for ${topic}.`;
96
+ return brief || topic;
97
97
  }
98
-
99
98
  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
- };
99
+ if (!field || typeof field !== "object")
100
+ return null;
101
+ const text = asText(field.text);
102
+ if (!text)
103
+ return null;
104
+ return {
105
+ id: asText(field.id, `tf_${index + 1}`),
106
+ kind: CARD_NEWS_TEXT_KINDS.includes(field.kind) ? field.kind : "body",
107
+ text,
108
+ renderMode: CARD_NEWS_RENDER_MODES.includes(field.renderMode) ? field.renderMode : "in-image",
109
+ placement: CARD_NEWS_PLACEMENTS.includes(field.placement) ? field.placement : "free",
110
+ slotId: typeof field.slotId === "string" && field.slotId.trim() ? field.slotId.trim() : null,
111
+ hierarchy: CARD_NEWS_HIERARCHIES.includes(field.hierarchy) ? field.hierarchy : "supporting",
112
+ maxChars: Number.isInteger(field.maxChars) ? field.maxChars : null,
113
+ language: typeof field.language === "string" && field.language.trim() ? field.language.trim() : null,
114
+ source: CARD_NEWS_TEXT_SOURCES.includes(field.source) ? field.source : "planner",
115
+ };
115
116
  }
116
-
117
117
  function normalizeTextFields(value) {
118
- if (!Array.isArray(value)) return [];
119
- return value.map(normalizeTextField).filter(Boolean);
118
+ if (!Array.isArray(value))
119
+ return [];
120
+ return value.map(normalizeTextField).filter(Boolean);
120
121
  }
121
-
122
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");
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
+ }
128
129
  }
129
- }
130
- return next.trim();
130
+ return next.trim();
131
131
  }
132
-
133
132
  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
- };
133
+ const topic = asText(input.topic, "Card news");
134
+ const textFields = normalizeTextFields(card?.textFields);
135
+ return {
136
+ order: index + 1,
137
+ role: role.role,
138
+ headline: asText(card?.headline, index === 0 ? topic : fallbackCopy(input, "headline")),
139
+ body: asText(card?.body, fallbackCopy(input, "body")),
140
+ visualPrompt: stripExactVisibleText(asText(card?.visualPrompt, `${asText(role.promptHint, role.role)}, ${topic}`), textFields),
141
+ textFields,
142
+ references: Array.isArray(card?.references)
143
+ ? card.references.filter((ref) => typeof ref === "string")
144
+ : [],
145
+ locked: false,
146
+ };
151
147
  }
152
-
153
148
  export function repairPlannerOutput(output, input = {}) {
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
- };
149
+ const roles = input.roleTemplate?.roles || [];
150
+ const cards = roles.map((role, index) => {
151
+ const original = Array.isArray(output?.cards) ? output.cards[index] : null;
152
+ return normalizeCard(original, role, index, input);
153
+ });
154
+ return {
155
+ ok: true,
156
+ repaired: true,
157
+ errors: [],
158
+ plan: {
159
+ title: asText(output?.title, asText(input.topic, "Untitled card news")),
160
+ topic: asText(output?.topic, asText(input.topic, "Untitled card news")),
161
+ audience: asText(output?.audience, asText(input.audience)),
162
+ goal: asText(output?.goal, asText(input.goal)),
163
+ cards,
164
+ },
165
+ };
171
166
  }
172
-
173
167
  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`);
168
+ if (!field || typeof field !== "object") {
169
+ errors.push(`${path} must be object`);
170
+ return;
171
+ }
172
+ if (typeof field.id !== "string")
173
+ errors.push(`${path}.id must be string`);
174
+ if (!CARD_NEWS_TEXT_KINDS.includes(field.kind))
175
+ errors.push(`${path}.kind invalid`);
176
+ if (typeof field.text !== "string")
177
+ errors.push(`${path}.text must be string`);
178
+ if (typeof field.text === "string" && !field.text.trim())
179
+ errors.push(`${path}.text must not be empty`);
180
+ if (!CARD_NEWS_RENDER_MODES.includes(field.renderMode))
181
+ errors.push(`${path}.renderMode invalid`);
182
+ if (!CARD_NEWS_PLACEMENTS.includes(field.placement))
183
+ errors.push(`${path}.placement invalid`);
184
+ if (!(typeof field.slotId === "string" || field.slotId === null))
185
+ errors.push(`${path}.slotId invalid`);
186
+ if (!CARD_NEWS_HIERARCHIES.includes(field.hierarchy))
187
+ errors.push(`${path}.hierarchy invalid`);
188
+ if (!(Number.isInteger(field.maxChars) || field.maxChars === null))
189
+ errors.push(`${path}.maxChars invalid`);
190
+ if (!(typeof field.language === "string" || field.language === null))
191
+ errors.push(`${path}.language invalid`);
192
+ if (!CARD_NEWS_TEXT_SOURCES.includes(field.source))
193
+ errors.push(`${path}.source invalid`);
189
194
  }
190
-
191
195
  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;
196
+ const errors = [];
197
+ if (!output || typeof output !== "object" || Array.isArray(output)) {
198
+ return { ok: false, repaired: false, errors: ["output must be an object"] };
211
199
  }
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`);
200
+ if (typeof output.title !== "string")
201
+ errors.push("title must be a string");
202
+ if (typeof output.topic !== "string")
203
+ errors.push("topic must be a string");
204
+ if (!Array.isArray(output.cards))
205
+ errors.push("cards must be an array");
206
+ const roles = roleTemplate?.roles || [];
207
+ if (Array.isArray(output.cards) && output.cards.length !== roles.length) {
208
+ errors.push("cards length must match role template");
216
209
  }
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`);
210
+ const cards = Array.isArray(output.cards) ? output.cards : [];
211
+ cards.forEach((card, index) => {
212
+ const expected = roles[index];
213
+ if (!card || typeof card !== "object") {
214
+ errors.push(`card ${index + 1} must be an object`);
215
+ return;
230
216
  }
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
- };
217
+ if (card.order !== index + 1)
218
+ errors.push(`card ${index + 1} order mismatch`);
219
+ if (expected && card.role !== expected.role)
220
+ errors.push(`card ${index + 1} role mismatch`);
221
+ for (const key of ["headline", "body", "visualPrompt"]) {
222
+ if (typeof card[key] !== "string")
223
+ errors.push(`card ${index + 1} ${key} must be string`);
224
+ }
225
+ if (!Array.isArray(card.textFields))
226
+ errors.push(`card ${index + 1} textFields must be array`);
227
+ if (Array.isArray(card.textFields)) {
228
+ card.textFields.forEach((field, fieldIndex) => validateTextField(field, `card ${index + 1} textFields ${fieldIndex + 1}`, errors));
229
+ for (const field of card.textFields) {
230
+ if (field?.renderMode === "in-image" &&
231
+ typeof field.text === "string" &&
232
+ field.text.trim().length >= 4 &&
233
+ typeof card.visualPrompt === "string" &&
234
+ card.visualPrompt.includes(field.text.trim())) {
235
+ errors.push(`card ${index + 1} visualPrompt must not duplicate exact visible text`);
236
+ }
237
+ }
238
+ }
239
+ if (!Array.isArray(card.references))
240
+ errors.push(`card ${index + 1} references must be array`);
241
+ if (card.locked !== false)
242
+ errors.push(`card ${index + 1} locked must be false`);
243
+ });
244
+ if (errors.length)
245
+ return { ok: false, repaired: false, errors };
246
+ return {
247
+ ok: true,
248
+ repaired: false,
249
+ errors: [],
250
+ plan: {
251
+ title: output.title.trim(),
252
+ topic: output.topic.trim(),
253
+ audience: asText(output.audience),
254
+ goal: asText(output.goal),
255
+ cards: cards.map((card) => ({
256
+ order: card.order,
257
+ role: card.role,
258
+ headline: card.headline.trim(),
259
+ body: card.body.trim(),
260
+ visualPrompt: card.visualPrompt.trim(),
261
+ textFields: normalizeTextFields(card.textFields),
262
+ references: card.references.filter((ref) => typeof ref === "string"),
263
+ locked: false,
264
+ })),
265
+ },
266
+ };
259
267
  }