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.
Files changed (229) 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 -190
  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 +105 -0
  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 +135 -0
  130. package/lib/promptImport/curatedSources.ts +139 -0
  131. package/lib/promptImport/discoveryRegistry.js +218 -0
  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 +238 -0
  136. package/lib/promptImport/githubDiscovery.ts +248 -0
  137. package/lib/promptImport/githubFolder.js +302 -0
  138. package/lib/promptImport/githubFolder.ts +308 -0
  139. package/lib/promptImport/githubSource.js +194 -171
  140. package/lib/promptImport/githubSource.ts +239 -0
  141. package/lib/promptImport/gptImageHints.js +61 -0
  142. package/lib/promptImport/gptImageHints.ts +68 -0
  143. package/lib/promptImport/parsePromptCandidates.js +110 -112
  144. package/lib/promptImport/parsePromptCandidates.ts +153 -0
  145. package/lib/promptImport/promptIndex.js +230 -0
  146. package/lib/promptImport/promptIndex.ts +248 -0
  147. package/lib/promptImport/rankPromptCandidates.js +52 -0
  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 +33 -0
  187. package/routes/imageImport.ts +33 -0
  188. package/routes/index.js +18 -16
  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 +291 -152
  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 +6 -3
  219. package/assets/screenshot.png +0 -0
  220. package/assets/screenshots/classic-generate-light.png +0 -0
  221. package/assets/screenshots/node-graph-branching.png +0 -0
  222. package/assets/screenshots/settings-oauth-generation.png +0 -0
  223. package/assets/screenshots/settings-workspace.png +0 -0
  224. package/assets/screenshots/style-sheet-editor.png +0 -0
  225. package/integrations/comfyui/ima2_gen_bridge/__pycache__/__init__.cpython-313.pyc +0 -0
  226. package/integrations/comfyui/ima2_gen_bridge/__pycache__/nodes.cpython-313.pyc +0 -0
  227. package/ui/dist/assets/index-DARPdT4Q.css +0 -1
  228. package/ui/dist/assets/index-ht80GMq4.js +0 -31
  229. package/ui/dist/assets/index-ht80GMq4.js.map +0 -1
@@ -1,114 +1,117 @@
1
1
  import { config } from "../config.js";
2
2
  import { CARD_NEWS_PLANNER_SCHEMA } from "./cardNewsPlannerSchema.js";
3
3
  import { logEvent } from "./logger.js";
4
-
5
4
  function plannerError(message, code, status = 502) {
6
- const err = new Error(message);
7
- err.code = code;
8
- err.status = status;
9
- return err;
5
+ const err = new Error(message);
6
+ err.code = code;
7
+ err.status = status;
8
+ return err;
10
9
  }
11
-
12
10
  function extractText(json) {
13
- if (typeof json.output_text === "string") return json.output_text;
14
- for (const item of json.output || []) {
15
- for (const content of item.content || []) {
16
- if (typeof content.text === "string") return content.text;
17
- if (typeof content.value === "string") return content.value;
11
+ if (typeof json.output_text === "string")
12
+ return json.output_text;
13
+ for (const item of json.output || []) {
14
+ for (const content of item.content || []) {
15
+ if (typeof content.text === "string")
16
+ return content.text;
17
+ if (typeof content.value === "string")
18
+ return content.value;
19
+ }
18
20
  }
19
- }
20
- return "";
21
+ return "";
21
22
  }
22
-
23
23
  async function requestJson({ oauthUrl, model, messages, timeoutMs, structured, reasoningEffort }) {
24
- const controller = new AbortController();
25
- const timer = setTimeout(() => controller.abort(), timeoutMs);
26
- try {
27
- const body = {
28
- model,
29
- input: messages,
30
- stream: false,
31
- ...(reasoningEffort ? { reasoning: { effort: reasoningEffort } } : {}),
32
- ...(structured
33
- ? {
34
- text: {
35
- format: {
36
- type: "json_schema",
37
- name: "card_news_planner_output",
38
- strict: true,
39
- schema: CARD_NEWS_PLANNER_SCHEMA,
40
- },
41
- },
42
- }
43
- : { text: { format: { type: "json_object" } } }),
44
- };
45
- const res = await fetch(`${oauthUrl}/v1/responses`, {
46
- method: "POST",
47
- headers: { "Content-Type": "application/json" },
48
- signal: controller.signal,
49
- body: JSON.stringify(body),
50
- });
51
- logEvent("card-news-planner", "response", { model, status: res.status, structured });
52
- if (!res.ok) {
53
- const text = await res.text();
54
- const err = plannerError("Planner upstream failed", "PLANNER_UPSTREAM_FAILED", 502);
55
- err.upstreamStatus = res.status;
56
- err.upstreamBodyChars = text.length;
57
- throw err;
24
+ const controller = new AbortController();
25
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
26
+ try {
27
+ const body = {
28
+ model,
29
+ input: messages,
30
+ stream: false,
31
+ ...(reasoningEffort ? { reasoning: { effort: reasoningEffort } } : {}),
32
+ ...(structured
33
+ ? {
34
+ text: {
35
+ format: {
36
+ type: "json_schema",
37
+ name: "card_news_planner_output",
38
+ strict: true,
39
+ schema: CARD_NEWS_PLANNER_SCHEMA,
40
+ },
41
+ },
42
+ }
43
+ : { text: { format: { type: "json_object" } } }),
44
+ };
45
+ const res = await fetch(`${oauthUrl}/v1/responses`, {
46
+ method: "POST",
47
+ headers: { "Content-Type": "application/json" },
48
+ signal: controller.signal,
49
+ body: JSON.stringify(body),
50
+ });
51
+ logEvent("card-news-planner", "response", { model, status: res.status, structured });
52
+ if (!res.ok) {
53
+ const text = await res.text();
54
+ const err = plannerError("Planner upstream failed", "PLANNER_UPSTREAM_FAILED", 502);
55
+ err.upstreamStatus = res.status;
56
+ err.upstreamBodyChars = text.length;
57
+ throw err;
58
+ }
59
+ const json = await res.json();
60
+ return extractText(json);
61
+ }
62
+ finally {
63
+ clearTimeout(timer);
58
64
  }
59
- const json = await res.json();
60
- return extractText(json);
61
- } finally {
62
- clearTimeout(timer);
63
- }
64
65
  }
65
-
66
66
  async function requestChatJson({ oauthUrl, model, messages, timeoutMs }) {
67
- const controller = new AbortController();
68
- const timer = setTimeout(() => controller.abort(), timeoutMs);
69
- try {
70
- const res = await fetch(`${oauthUrl}/v1/chat/completions`, {
71
- method: "POST",
72
- headers: { "Content-Type": "application/json" },
73
- signal: controller.signal,
74
- body: JSON.stringify({
75
- model,
76
- messages,
77
- response_format: { type: "json_object" },
78
- }),
79
- });
80
- logEvent("card-news-planner", "chat_response", { model, status: res.status });
81
- if (!res.ok) {
82
- const text = await res.text();
83
- const err = plannerError("Planner upstream failed", "PLANNER_UPSTREAM_FAILED", 502);
84
- err.upstreamStatus = res.status;
85
- err.upstreamBodyChars = text.length;
86
- throw err;
67
+ const controller = new AbortController();
68
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
69
+ try {
70
+ const res = await fetch(`${oauthUrl}/v1/chat/completions`, {
71
+ method: "POST",
72
+ headers: { "Content-Type": "application/json" },
73
+ signal: controller.signal,
74
+ body: JSON.stringify({
75
+ model,
76
+ messages,
77
+ response_format: { type: "json_object" },
78
+ }),
79
+ });
80
+ logEvent("card-news-planner", "chat_response", { model, status: res.status });
81
+ if (!res.ok) {
82
+ const text = await res.text();
83
+ const err = plannerError("Planner upstream failed", "PLANNER_UPSTREAM_FAILED", 502);
84
+ err.upstreamStatus = res.status;
85
+ err.upstreamBodyChars = text.length;
86
+ throw err;
87
+ }
88
+ const json = await res.json();
89
+ return json.choices?.[0]?.message?.content || "";
90
+ }
91
+ finally {
92
+ clearTimeout(timer);
87
93
  }
88
- const json = await res.json();
89
- return json.choices?.[0]?.message?.content || "";
90
- } finally {
91
- clearTimeout(timer);
92
- }
93
94
  }
94
-
95
95
  export async function requestCardNewsPlannerJson(input, options = {}) {
96
- const oauthUrl = options.oauthUrl || `http://127.0.0.1:${config.oauth.proxyPort}`;
97
- const model = options.model || config.cardNewsPlanner.model;
98
- const timeoutMs = options.timeoutMs || config.cardNewsPlanner.timeoutMs;
99
- const reasoningEffort = options.reasoningEffort || config.imageModels?.reasoningEffort || "medium";
100
- let text = "";
101
- let mode = "structured-output";
102
- try {
103
- text = await requestJson({ oauthUrl, model, messages: input.messages, timeoutMs, structured: true, reasoningEffort });
104
- } catch (err) {
105
- if (err.code !== "PLANNER_UPSTREAM_FAILED") throw err;
106
- mode = "json-mode";
107
- text = await requestChatJson({ oauthUrl, model, messages: input.messages, timeoutMs });
108
- }
109
- try {
110
- return { output: JSON.parse(text), mode, model };
111
- } catch {
112
- throw plannerError("Planner returned invalid JSON", "PLANNER_INVALID_JSON", 502);
113
- }
96
+ const oauthUrl = options.oauthUrl || `http://127.0.0.1:${config.oauth.proxyPort}`;
97
+ const model = options.model || config.cardNewsPlanner.model;
98
+ const timeoutMs = options.timeoutMs || config.cardNewsPlanner.timeoutMs;
99
+ const reasoningEffort = options.reasoningEffort || config.imageModels?.reasoningEffort || "medium";
100
+ let text = "";
101
+ let mode = "structured-output";
102
+ try {
103
+ text = await requestJson({ oauthUrl, model, messages: input.messages, timeoutMs, structured: true, reasoningEffort });
104
+ }
105
+ catch (err) {
106
+ if (err.code !== "PLANNER_UPSTREAM_FAILED")
107
+ throw err;
108
+ mode = "json-mode";
109
+ text = await requestChatJson({ oauthUrl, model, messages: input.messages, timeoutMs });
110
+ }
111
+ try {
112
+ return { output: JSON.parse(text), mode, model };
113
+ }
114
+ catch {
115
+ throw plannerError("Planner returned invalid JSON", "PLANNER_INVALID_JSON", 502);
116
+ }
114
117
  }
@@ -0,0 +1,114 @@
1
+ import { config } from "../config.js";
2
+ import { CARD_NEWS_PLANNER_SCHEMA } from "./cardNewsPlannerSchema.js";
3
+ import { logEvent } from "./logger.js";
4
+
5
+ function plannerError(message, code, status = 502) {
6
+ const err: any = new Error(message);
7
+ err.code = code;
8
+ err.status = status;
9
+ return err;
10
+ }
11
+
12
+ function extractText(json) {
13
+ if (typeof json.output_text === "string") return json.output_text;
14
+ for (const item of json.output || []) {
15
+ for (const content of item.content || []) {
16
+ if (typeof content.text === "string") return content.text;
17
+ if (typeof content.value === "string") return content.value;
18
+ }
19
+ }
20
+ return "";
21
+ }
22
+
23
+ async function requestJson({ oauthUrl, model, messages, timeoutMs, structured, reasoningEffort }) {
24
+ const controller = new AbortController();
25
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
26
+ try {
27
+ const body = {
28
+ model,
29
+ input: messages,
30
+ stream: false,
31
+ ...(reasoningEffort ? { reasoning: { effort: reasoningEffort } } : {}),
32
+ ...(structured
33
+ ? {
34
+ text: {
35
+ format: {
36
+ type: "json_schema",
37
+ name: "card_news_planner_output",
38
+ strict: true,
39
+ schema: CARD_NEWS_PLANNER_SCHEMA,
40
+ },
41
+ },
42
+ }
43
+ : { text: { format: { type: "json_object" } } }),
44
+ };
45
+ const res = await fetch(`${oauthUrl}/v1/responses`, {
46
+ method: "POST",
47
+ headers: { "Content-Type": "application/json" },
48
+ signal: controller.signal,
49
+ body: JSON.stringify(body),
50
+ });
51
+ logEvent("card-news-planner", "response", { model, status: res.status, structured });
52
+ if (!res.ok) {
53
+ const text = await res.text();
54
+ const err: any = plannerError("Planner upstream failed", "PLANNER_UPSTREAM_FAILED", 502);
55
+ err.upstreamStatus = res.status;
56
+ err.upstreamBodyChars = text.length;
57
+ throw err;
58
+ }
59
+ const json = await res.json();
60
+ return extractText(json);
61
+ } finally {
62
+ clearTimeout(timer);
63
+ }
64
+ }
65
+
66
+ async function requestChatJson({ oauthUrl, model, messages, timeoutMs }) {
67
+ const controller = new AbortController();
68
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
69
+ try {
70
+ const res = await fetch(`${oauthUrl}/v1/chat/completions`, {
71
+ method: "POST",
72
+ headers: { "Content-Type": "application/json" },
73
+ signal: controller.signal,
74
+ body: JSON.stringify({
75
+ model,
76
+ messages,
77
+ response_format: { type: "json_object" },
78
+ }),
79
+ });
80
+ logEvent("card-news-planner", "chat_response", { model, status: res.status });
81
+ if (!res.ok) {
82
+ const text = await res.text();
83
+ const err: any = plannerError("Planner upstream failed", "PLANNER_UPSTREAM_FAILED", 502);
84
+ err.upstreamStatus = res.status;
85
+ err.upstreamBodyChars = text.length;
86
+ throw err;
87
+ }
88
+ const json: any = await res.json();
89
+ return json.choices?.[0]?.message?.content || "";
90
+ } finally {
91
+ clearTimeout(timer);
92
+ }
93
+ }
94
+
95
+ export async function requestCardNewsPlannerJson(input, options: any = {}) {
96
+ const oauthUrl = options.oauthUrl || `http://127.0.0.1:${config.oauth.proxyPort}`;
97
+ const model = options.model || config.cardNewsPlanner.model;
98
+ const timeoutMs = options.timeoutMs || config.cardNewsPlanner.timeoutMs;
99
+ const reasoningEffort = options.reasoningEffort || config.imageModels?.reasoningEffort || "medium";
100
+ let text = "";
101
+ let mode = "structured-output";
102
+ try {
103
+ text = await requestJson({ oauthUrl, model, messages: input.messages, timeoutMs, structured: true, reasoningEffort });
104
+ } catch (err: any) {
105
+ if (err.code !== "PLANNER_UPSTREAM_FAILED") throw err;
106
+ mode = "json-mode";
107
+ text = await requestChatJson({ oauthUrl, model, messages: input.messages, timeoutMs });
108
+ }
109
+ try {
110
+ return { output: JSON.parse(text), mode, model };
111
+ } catch {
112
+ throw plannerError("Planner returned invalid JSON", "PLANNER_INVALID_JSON", 502);
113
+ }
114
+ }
@@ -1,60 +1,60 @@
1
1
  export function buildCardNewsPlannerMessages(input = {}) {
2
- const roleTemplate = input.roleTemplate || {};
3
- const imageTemplate = input.imageTemplate || {};
4
- return [
5
- {
6
- role: "developer",
7
- content: [
8
- "You are a Card News planning assistant.",
9
- "Return JSON only. Do not call image tools. Do not generate images.",
10
- "Keep the selected card count and role order exactly.",
11
- "Preserve the user's original language for headline, body, textFields, and visualPrompt.",
12
- "Do not translate to English unless explicitly requested.",
13
- "If the user mixes languages, preserve the mix.",
14
- "If the user provides exact text, preserve it exactly.",
15
- "Role names such as cover/problem/cta are structural labels, not visible design text.",
16
- "Keep headline and body as UI/manifest text.",
17
- "Only textFields[].text with renderMode=\"in-image\" is intended to appear inside the image.",
18
- "Create textFields only for text that should be readable inside the image.",
19
- "Prefer template slot ids and placements when assigning textFields.",
20
- "Use headline textFields for hook/cover cards, body or caption textFields for explanation cards, and cta textFields only for action cards.",
21
- "Never put structural role labels such as CTA, cover, problem, insight, or example into textFields unless the user explicitly requested that exact visible wording.",
22
- "Placement examples: top-right badge, top-center headline, center-left supporting caption, bottom-center CTA.",
23
- "Make visualPrompt describe scene, layout, style, spatial composition, and text-box placement only.",
24
- "Do not duplicate visible text in visualPrompt except to reference a text box position.",
25
- "visualPrompt may mention a text box location, but the exact readable copy must live only in textFields[].text.",
26
- ].join("\n"),
27
- },
28
- {
29
- role: "user",
30
- content: JSON.stringify({
31
- topic: input.topic || "",
32
- audience: input.audience || "",
33
- goal: input.goal || "",
34
- contentBrief: input.contentBrief || "",
35
- size: input.size || "2048x2048",
36
- imageTemplate: {
37
- id: imageTemplate.id,
38
- name: imageTemplate.name,
39
- stylePrompt: imageTemplate.stylePrompt,
40
- recommendedOutputSizes: imageTemplate.recommendedOutputSizes || [],
41
- slots: imageTemplate.slots || [],
42
- palette: imageTemplate.palette || [],
2
+ const roleTemplate = input.roleTemplate || {};
3
+ const imageTemplate = input.imageTemplate || {};
4
+ return [
5
+ {
6
+ role: "developer",
7
+ content: [
8
+ "You are a Card News planning assistant.",
9
+ "Return JSON only. Do not call image tools. Do not generate images.",
10
+ "Keep the selected card count and role order exactly.",
11
+ "Preserve the user's original language for headline, body, textFields, and visualPrompt.",
12
+ "Do not translate to English unless explicitly requested.",
13
+ "If the user mixes languages, preserve the mix.",
14
+ "If the user provides exact text, preserve it exactly.",
15
+ "Role names such as cover/problem/cta are structural labels, not visible design text.",
16
+ "Keep headline and body as UI/manifest text.",
17
+ "Only textFields[].text with renderMode=\"in-image\" is intended to appear inside the image.",
18
+ "Create textFields only for text that should be readable inside the image.",
19
+ "Prefer template slot ids and placements when assigning textFields.",
20
+ "Use headline textFields for hook/cover cards, body or caption textFields for explanation cards, and cta textFields only for action cards.",
21
+ "Never put structural role labels such as CTA, cover, problem, insight, or example into textFields unless the user explicitly requested that exact visible wording.",
22
+ "Placement examples: top-right badge, top-center headline, center-left supporting caption, bottom-center CTA.",
23
+ "Make visualPrompt describe scene, layout, style, spatial composition, and text-box placement only.",
24
+ "Do not duplicate visible text in visualPrompt except to reference a text box position.",
25
+ "visualPrompt may mention a text box location, but the exact readable copy must live only in textFields[].text.",
26
+ ].join("\n"),
43
27
  },
44
- roleTemplate: {
45
- id: roleTemplate.id,
46
- roles: (roleTemplate.roles || []).map((role) => ({
47
- role: role.role,
48
- promptHint: role.promptHint,
49
- preferredSlots: role.preferredSlots || [],
50
- })),
28
+ {
29
+ role: "user",
30
+ content: JSON.stringify({
31
+ topic: input.topic || "",
32
+ audience: input.audience || "",
33
+ goal: input.goal || "",
34
+ contentBrief: input.contentBrief || "",
35
+ size: input.size || "2048x2048",
36
+ imageTemplate: {
37
+ id: imageTemplate.id,
38
+ name: imageTemplate.name,
39
+ stylePrompt: imageTemplate.stylePrompt,
40
+ recommendedOutputSizes: imageTemplate.recommendedOutputSizes || [],
41
+ slots: imageTemplate.slots || [],
42
+ palette: imageTemplate.palette || [],
43
+ },
44
+ roleTemplate: {
45
+ id: roleTemplate.id,
46
+ roles: (roleTemplate.roles || []).map((role) => ({
47
+ role: role.role,
48
+ promptHint: role.promptHint,
49
+ preferredSlots: role.preferredSlots || [],
50
+ })),
51
+ },
52
+ textFieldPolicy: {
53
+ visibleTextSource: "textFields[].text",
54
+ renderableMode: "in-image",
55
+ structuralRoleLabelsAreNotVisibleText: true,
56
+ },
57
+ }),
51
58
  },
52
- textFieldPolicy: {
53
- visibleTextSource: "textFields[].text",
54
- renderableMode: "in-image",
55
- structuralRoleLabelsAreNotVisibleText: true,
56
- },
57
- }),
58
- },
59
- ];
59
+ ];
60
60
  }
@@ -0,0 +1,60 @@
1
+ export function buildCardNewsPlannerMessages(input: any = {}) {
2
+ const roleTemplate: any = input.roleTemplate || {};
3
+ const imageTemplate: any = input.imageTemplate || {};
4
+ return [
5
+ {
6
+ role: "developer",
7
+ content: [
8
+ "You are a Card News planning assistant.",
9
+ "Return JSON only. Do not call image tools. Do not generate images.",
10
+ "Keep the selected card count and role order exactly.",
11
+ "Preserve the user's original language for headline, body, textFields, and visualPrompt.",
12
+ "Do not translate to English unless explicitly requested.",
13
+ "If the user mixes languages, preserve the mix.",
14
+ "If the user provides exact text, preserve it exactly.",
15
+ "Role names such as cover/problem/cta are structural labels, not visible design text.",
16
+ "Keep headline and body as UI/manifest text.",
17
+ "Only textFields[].text with renderMode=\"in-image\" is intended to appear inside the image.",
18
+ "Create textFields only for text that should be readable inside the image.",
19
+ "Prefer template slot ids and placements when assigning textFields.",
20
+ "Use headline textFields for hook/cover cards, body or caption textFields for explanation cards, and cta textFields only for action cards.",
21
+ "Never put structural role labels such as CTA, cover, problem, insight, or example into textFields unless the user explicitly requested that exact visible wording.",
22
+ "Placement examples: top-right badge, top-center headline, center-left supporting caption, bottom-center CTA.",
23
+ "Make visualPrompt describe scene, layout, style, spatial composition, and text-box placement only.",
24
+ "Do not duplicate visible text in visualPrompt except to reference a text box position.",
25
+ "visualPrompt may mention a text box location, but the exact readable copy must live only in textFields[].text.",
26
+ ].join("\n"),
27
+ },
28
+ {
29
+ role: "user",
30
+ content: JSON.stringify({
31
+ topic: input.topic || "",
32
+ audience: input.audience || "",
33
+ goal: input.goal || "",
34
+ contentBrief: input.contentBrief || "",
35
+ size: input.size || "2048x2048",
36
+ imageTemplate: {
37
+ id: imageTemplate.id,
38
+ name: imageTemplate.name,
39
+ stylePrompt: imageTemplate.stylePrompt,
40
+ recommendedOutputSizes: imageTemplate.recommendedOutputSizes || [],
41
+ slots: imageTemplate.slots || [],
42
+ palette: imageTemplate.palette || [],
43
+ },
44
+ roleTemplate: {
45
+ id: roleTemplate.id,
46
+ roles: (roleTemplate.roles || []).map((role: any) => ({
47
+ role: role.role,
48
+ promptHint: role.promptHint,
49
+ preferredSlots: role.preferredSlots || [],
50
+ })),
51
+ },
52
+ textFieldPolicy: {
53
+ visibleTextSource: "textFields[].text",
54
+ renderableMode: "in-image",
55
+ structuralRoleLabelsAreNotVisibleText: true,
56
+ },
57
+ }),
58
+ },
59
+ ];
60
+ }