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
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
for (const
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
+
}
|