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
package/lib/cardNewsPlanner.js
CHANGED
|
@@ -5,176 +5,181 @@ import { buildCardNewsPlannerMessages } from "./cardNewsPlannerPrompt.js";
|
|
|
5
5
|
import { requestCardNewsPlannerJson } from "./cardNewsPlannerClient.js";
|
|
6
6
|
import { repairPlannerOutput, validatePlannerOutput } from "./cardNewsPlannerSchema.js";
|
|
7
7
|
import { waitForOAuthReady } from "./oauthProxy.js";
|
|
8
|
-
|
|
9
8
|
function compactText(value, fallback) {
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const text = typeof value === "string" ? value.trim() : "";
|
|
10
|
+
return text || fallback;
|
|
12
11
|
}
|
|
13
|
-
|
|
14
12
|
function detectBriefLanguage(input) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
const text = [input.topic, input.audience, input.goal, input.contentBrief].filter(Boolean).join(" ");
|
|
14
|
+
if (/[가-힣]/.test(text))
|
|
15
|
+
return "ko";
|
|
16
|
+
if (/[A-Za-z]/.test(text))
|
|
17
|
+
return "en";
|
|
18
|
+
return "und";
|
|
19
19
|
}
|
|
20
|
-
|
|
21
20
|
function fallbackLabel(role, lang) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
21
|
+
const ko = {
|
|
22
|
+
cta: "다음 행동",
|
|
23
|
+
problem: "왜 중요한가",
|
|
24
|
+
insight: "핵심 인사이트",
|
|
25
|
+
example: "예시로 보기",
|
|
26
|
+
data: "숫자로 확인",
|
|
27
|
+
summary: "요약",
|
|
28
|
+
};
|
|
29
|
+
const en = {
|
|
30
|
+
cta: "Next action",
|
|
31
|
+
problem: "Why it matters",
|
|
32
|
+
insight: "Key insight",
|
|
33
|
+
example: "Example",
|
|
34
|
+
data: "By the numbers",
|
|
35
|
+
summary: "Summary",
|
|
36
|
+
};
|
|
37
|
+
if (lang === "ko")
|
|
38
|
+
return ko[role] || role;
|
|
39
|
+
if (lang === "en")
|
|
40
|
+
return en[role] || role;
|
|
41
|
+
return "";
|
|
41
42
|
}
|
|
42
|
-
|
|
43
43
|
function headlineFor(role, topic, lang) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
const label = compactText(topic, "Card news");
|
|
45
|
+
if (role === "cover" || role === "hook")
|
|
46
|
+
return label;
|
|
47
|
+
return fallbackLabel(role, lang) || label;
|
|
47
48
|
}
|
|
48
|
-
|
|
49
49
|
function bodyFor(role, brief, lang) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
50
|
+
const content = compactText(brief.content, "");
|
|
51
|
+
if (content)
|
|
52
|
+
return content;
|
|
53
|
+
const target = compactText(brief.audience, lang === "ko" ? "독자" : "reader");
|
|
54
|
+
const goal = compactText(brief.goal, lang === "ko" ? "핵심 메시지" : "the key message");
|
|
55
|
+
if (lang === "ko") {
|
|
56
|
+
if (role === "cta")
|
|
57
|
+
return `${target}가 바로 실행할 수 있는 다음 단계를 제안합니다.`;
|
|
58
|
+
if (role === "problem")
|
|
59
|
+
return `${target}가 겪는 문제를 짧고 분명하게 보여줍니다.`;
|
|
60
|
+
if (role === "insight")
|
|
61
|
+
return `${goal}을 이해하기 쉬운 한 문장으로 정리합니다.`;
|
|
62
|
+
return `${goal}을 카드 역할에 맞춰 전달합니다.`;
|
|
63
|
+
}
|
|
64
|
+
if (role === "cta")
|
|
65
|
+
return `Suggest a next step ${target} can take immediately.`;
|
|
66
|
+
if (role === "problem")
|
|
67
|
+
return `Show the problem ${target} faces in a concise way.`;
|
|
68
|
+
if (role === "insight")
|
|
69
|
+
return `Explain ${goal} in one clear sentence.`;
|
|
70
|
+
return `Present ${goal} for this card.`;
|
|
64
71
|
}
|
|
65
|
-
|
|
66
72
|
function normalizeTextFields(fields) {
|
|
67
|
-
|
|
73
|
+
return Array.isArray(fields) ? fields : [];
|
|
68
74
|
}
|
|
69
|
-
|
|
70
75
|
function toCardNewsPlan(plannerOutput, input, roleTemplate) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
76
|
+
const topic = compactText(plannerOutput.topic, compactText(input.topic, input.title || "Untitled card news"));
|
|
77
|
+
return {
|
|
78
|
+
setId: input.setId || `cs_${ulid()}`,
|
|
79
|
+
title: compactText(plannerOutput.title, topic),
|
|
80
|
+
topic,
|
|
81
|
+
imageTemplateId: input.imageTemplateId || "academy-lesson-square",
|
|
82
|
+
roleTemplateId: roleTemplate.id,
|
|
83
|
+
size: input.size || "2048x2048",
|
|
84
|
+
generationStrategy: "parallel-template-i2i",
|
|
85
|
+
cards: plannerOutput.cards.map((card, index) => ({
|
|
86
|
+
id: `card_${index + 1}`,
|
|
87
|
+
order: index + 1,
|
|
88
|
+
role: card.role,
|
|
89
|
+
headline: card.headline,
|
|
90
|
+
body: card.body,
|
|
91
|
+
visualPrompt: card.visualPrompt,
|
|
92
|
+
textFields: normalizeTextFields(card.textFields),
|
|
93
|
+
templateSlotAssignments: {
|
|
94
|
+
title: "headline",
|
|
95
|
+
body: "body",
|
|
96
|
+
image: "visual",
|
|
97
|
+
},
|
|
98
|
+
references: card.references || [],
|
|
99
|
+
locked: false,
|
|
100
|
+
status: "draft",
|
|
101
|
+
})),
|
|
102
|
+
};
|
|
98
103
|
}
|
|
99
|
-
|
|
100
104
|
export function createDeterministicCardNewsDraft(input = {}) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
105
|
+
const roleTemplate = getRoleTemplate(input.roleTemplateId);
|
|
106
|
+
const topic = compactText(input.topic, input.title || "Untitled card news");
|
|
107
|
+
const title = compactText(input.title, topic);
|
|
108
|
+
const brief = {
|
|
109
|
+
audience: input.audience,
|
|
110
|
+
goal: input.goal,
|
|
111
|
+
content: input.contentBrief,
|
|
112
|
+
};
|
|
113
|
+
const lang = detectBriefLanguage(input);
|
|
114
|
+
const output = {
|
|
115
|
+
title,
|
|
116
|
+
topic,
|
|
117
|
+
audience: compactText(input.audience, ""),
|
|
118
|
+
goal: compactText(input.goal, ""),
|
|
119
|
+
cards: roleTemplate.roles.map((role, idx) => ({
|
|
120
|
+
order: idx + 1,
|
|
121
|
+
role: role.role,
|
|
122
|
+
headline: headlineFor(role.role, topic, lang),
|
|
123
|
+
body: bodyFor(role.role, brief, lang),
|
|
124
|
+
visualPrompt: `${role.promptHint}, ${topic}`,
|
|
125
|
+
textFields: [],
|
|
126
|
+
references: [],
|
|
127
|
+
locked: false,
|
|
128
|
+
})),
|
|
129
|
+
};
|
|
130
|
+
return toCardNewsPlan(output, input, roleTemplate);
|
|
127
131
|
}
|
|
128
|
-
|
|
129
132
|
function plannerError(message, code, status) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
const err = new Error(message);
|
|
134
|
+
err.code = code;
|
|
135
|
+
err.status = status;
|
|
136
|
+
return err;
|
|
134
137
|
}
|
|
135
|
-
|
|
136
138
|
export async function createCardNewsDraft(ctxOrInput = {}, maybeInput = {}) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
139
|
+
const hasCtx = !!ctxOrInput?.config;
|
|
140
|
+
const ctx = hasCtx ? ctxOrInput : null;
|
|
141
|
+
const input = hasCtx ? maybeInput : ctxOrInput;
|
|
142
|
+
const roleTemplate = getRoleTemplate(input.roleTemplateId);
|
|
143
|
+
if (!ctx)
|
|
144
|
+
return createDeterministicCardNewsDraft(input);
|
|
145
|
+
if (!ctx.config.cardNewsPlanner?.enabled) {
|
|
146
|
+
return {
|
|
147
|
+
plan: createDeterministicCardNewsDraft(input),
|
|
148
|
+
planner: { mode: "deterministic-fallback", model: "none", repaired: false },
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const imageTemplate = await getImageTemplate(ctx, input.imageTemplateId || "academy-lesson-square");
|
|
152
|
+
try {
|
|
153
|
+
await waitForOAuthReady(ctx);
|
|
154
|
+
const messages = buildCardNewsPlannerMessages({ ...input, roleTemplate, imageTemplate });
|
|
155
|
+
const raw = await requestCardNewsPlannerJson({ messages }, {
|
|
156
|
+
oauthUrl: ctx.oauthUrl,
|
|
157
|
+
model: ctx.config.cardNewsPlanner.model,
|
|
158
|
+
timeoutMs: ctx.config.cardNewsPlanner.timeoutMs,
|
|
159
|
+
});
|
|
160
|
+
let result = validatePlannerOutput(raw.output, roleTemplate);
|
|
161
|
+
if (!result.ok)
|
|
162
|
+
result = repairPlannerOutput(raw.output, { ...input, roleTemplate });
|
|
163
|
+
if (!result.ok)
|
|
164
|
+
throw plannerError("Planner schema invalid", "PLANNER_SCHEMA_INVALID", 422);
|
|
165
|
+
return {
|
|
166
|
+
plan: toCardNewsPlan(result.plan, input, roleTemplate),
|
|
167
|
+
planner: { mode: raw.mode, model: raw.model, repaired: result.repaired },
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
if (ctx.config.cardNewsPlanner.deterministicFallback) {
|
|
172
|
+
return {
|
|
173
|
+
plan: createDeterministicCardNewsDraft(input),
|
|
174
|
+
planner: {
|
|
175
|
+
mode: "deterministic-fallback",
|
|
176
|
+
model: ctx.config.cardNewsPlanner.model,
|
|
177
|
+
repaired: true,
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (err.code)
|
|
182
|
+
throw err;
|
|
183
|
+
throw plannerError(err.message || "Planner unavailable", "PLANNER_UNAVAILABLE", 503);
|
|
176
184
|
}
|
|
177
|
-
if (err.code) throw err;
|
|
178
|
-
throw plannerError(err.message || "Planner unavailable", "PLANNER_UNAVAILABLE", 503);
|
|
179
|
-
}
|
|
180
185
|
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { ulid } from "ulid";
|
|
2
|
+
import { getRoleTemplate } from "./cardNewsRoleTemplateStore.js";
|
|
3
|
+
import { getImageTemplate } from "./cardNewsTemplateStore.js";
|
|
4
|
+
import { buildCardNewsPlannerMessages } from "./cardNewsPlannerPrompt.js";
|
|
5
|
+
import { requestCardNewsPlannerJson } from "./cardNewsPlannerClient.js";
|
|
6
|
+
import { repairPlannerOutput, validatePlannerOutput } from "./cardNewsPlannerSchema.js";
|
|
7
|
+
import { waitForOAuthReady } from "./oauthProxy.js";
|
|
8
|
+
|
|
9
|
+
function compactText(value, fallback) {
|
|
10
|
+
const text = typeof value === "string" ? value.trim() : "";
|
|
11
|
+
return text || fallback;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function detectBriefLanguage(input) {
|
|
15
|
+
const text = [input.topic, input.audience, input.goal, input.contentBrief].filter(Boolean).join(" ");
|
|
16
|
+
if (/[가-힣]/.test(text)) return "ko";
|
|
17
|
+
if (/[A-Za-z]/.test(text)) return "en";
|
|
18
|
+
return "und";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function fallbackLabel(role, lang) {
|
|
22
|
+
const ko = {
|
|
23
|
+
cta: "다음 행동",
|
|
24
|
+
problem: "왜 중요한가",
|
|
25
|
+
insight: "핵심 인사이트",
|
|
26
|
+
example: "예시로 보기",
|
|
27
|
+
data: "숫자로 확인",
|
|
28
|
+
summary: "요약",
|
|
29
|
+
};
|
|
30
|
+
const en = {
|
|
31
|
+
cta: "Next action",
|
|
32
|
+
problem: "Why it matters",
|
|
33
|
+
insight: "Key insight",
|
|
34
|
+
example: "Example",
|
|
35
|
+
data: "By the numbers",
|
|
36
|
+
summary: "Summary",
|
|
37
|
+
};
|
|
38
|
+
if (lang === "ko") return ko[role] || role;
|
|
39
|
+
if (lang === "en") return en[role] || role;
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function headlineFor(role, topic, lang) {
|
|
44
|
+
const label = compactText(topic, "Card news");
|
|
45
|
+
if (role === "cover" || role === "hook") return label;
|
|
46
|
+
return fallbackLabel(role, lang) || label;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function bodyFor(role, brief, lang) {
|
|
50
|
+
const content = compactText(brief.content, "");
|
|
51
|
+
if (content) return content;
|
|
52
|
+
const target = compactText(brief.audience, lang === "ko" ? "독자" : "reader");
|
|
53
|
+
const goal = compactText(brief.goal, lang === "ko" ? "핵심 메시지" : "the key message");
|
|
54
|
+
if (lang === "ko") {
|
|
55
|
+
if (role === "cta") return `${target}가 바로 실행할 수 있는 다음 단계를 제안합니다.`;
|
|
56
|
+
if (role === "problem") return `${target}가 겪는 문제를 짧고 분명하게 보여줍니다.`;
|
|
57
|
+
if (role === "insight") return `${goal}을 이해하기 쉬운 한 문장으로 정리합니다.`;
|
|
58
|
+
return `${goal}을 카드 역할에 맞춰 전달합니다.`;
|
|
59
|
+
}
|
|
60
|
+
if (role === "cta") return `Suggest a next step ${target} can take immediately.`;
|
|
61
|
+
if (role === "problem") return `Show the problem ${target} faces in a concise way.`;
|
|
62
|
+
if (role === "insight") return `Explain ${goal} in one clear sentence.`;
|
|
63
|
+
return `Present ${goal} for this card.`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function normalizeTextFields(fields) {
|
|
67
|
+
return Array.isArray(fields) ? fields : [];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function toCardNewsPlan(plannerOutput, input, roleTemplate) {
|
|
71
|
+
const topic = compactText(plannerOutput.topic, compactText(input.topic, input.title || "Untitled card news"));
|
|
72
|
+
return {
|
|
73
|
+
setId: input.setId || `cs_${ulid()}`,
|
|
74
|
+
title: compactText(plannerOutput.title, topic),
|
|
75
|
+
topic,
|
|
76
|
+
imageTemplateId: input.imageTemplateId || "academy-lesson-square",
|
|
77
|
+
roleTemplateId: roleTemplate.id,
|
|
78
|
+
size: input.size || "2048x2048",
|
|
79
|
+
generationStrategy: "parallel-template-i2i",
|
|
80
|
+
cards: plannerOutput.cards.map((card, index) => ({
|
|
81
|
+
id: `card_${index + 1}`,
|
|
82
|
+
order: index + 1,
|
|
83
|
+
role: card.role,
|
|
84
|
+
headline: card.headline,
|
|
85
|
+
body: card.body,
|
|
86
|
+
visualPrompt: card.visualPrompt,
|
|
87
|
+
textFields: normalizeTextFields(card.textFields),
|
|
88
|
+
templateSlotAssignments: {
|
|
89
|
+
title: "headline",
|
|
90
|
+
body: "body",
|
|
91
|
+
image: "visual",
|
|
92
|
+
},
|
|
93
|
+
references: card.references || [],
|
|
94
|
+
locked: false,
|
|
95
|
+
status: "draft",
|
|
96
|
+
})),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function createDeterministicCardNewsDraft(input: any = {}) {
|
|
101
|
+
const roleTemplate = getRoleTemplate(input.roleTemplateId);
|
|
102
|
+
const topic = compactText(input.topic, input.title || "Untitled card news");
|
|
103
|
+
const title = compactText(input.title, topic);
|
|
104
|
+
const brief = {
|
|
105
|
+
audience: input.audience,
|
|
106
|
+
goal: input.goal,
|
|
107
|
+
content: input.contentBrief,
|
|
108
|
+
};
|
|
109
|
+
const lang = detectBriefLanguage(input);
|
|
110
|
+
const output = {
|
|
111
|
+
title,
|
|
112
|
+
topic,
|
|
113
|
+
audience: compactText(input.audience, ""),
|
|
114
|
+
goal: compactText(input.goal, ""),
|
|
115
|
+
cards: roleTemplate.roles.map((role, idx) => ({
|
|
116
|
+
order: idx + 1,
|
|
117
|
+
role: role.role,
|
|
118
|
+
headline: headlineFor(role.role, topic, lang),
|
|
119
|
+
body: bodyFor(role.role, brief, lang),
|
|
120
|
+
visualPrompt: `${role.promptHint}, ${topic}`,
|
|
121
|
+
textFields: [],
|
|
122
|
+
references: [],
|
|
123
|
+
locked: false,
|
|
124
|
+
})),
|
|
125
|
+
};
|
|
126
|
+
return toCardNewsPlan(output, input, roleTemplate);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function plannerError(message, code, status) {
|
|
130
|
+
const err: any = new Error(message);
|
|
131
|
+
err.code = code;
|
|
132
|
+
err.status = status;
|
|
133
|
+
return err;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function createCardNewsDraft(ctxOrInput: any = {}, maybeInput: any = {}) {
|
|
137
|
+
const hasCtx = !!ctxOrInput?.config;
|
|
138
|
+
const ctx = hasCtx ? ctxOrInput : null;
|
|
139
|
+
const input = hasCtx ? maybeInput : ctxOrInput;
|
|
140
|
+
const roleTemplate = getRoleTemplate(input.roleTemplateId);
|
|
141
|
+
|
|
142
|
+
if (!ctx) return createDeterministicCardNewsDraft(input);
|
|
143
|
+
if (!ctx.config.cardNewsPlanner?.enabled) {
|
|
144
|
+
return {
|
|
145
|
+
plan: createDeterministicCardNewsDraft(input),
|
|
146
|
+
planner: { mode: "deterministic-fallback", model: "none", repaired: false },
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const imageTemplate = await getImageTemplate(ctx, input.imageTemplateId || "academy-lesson-square");
|
|
151
|
+
try {
|
|
152
|
+
await waitForOAuthReady(ctx);
|
|
153
|
+
const messages = buildCardNewsPlannerMessages({ ...input, roleTemplate, imageTemplate });
|
|
154
|
+
const raw = await requestCardNewsPlannerJson({ messages }, {
|
|
155
|
+
oauthUrl: ctx.oauthUrl,
|
|
156
|
+
model: ctx.config.cardNewsPlanner.model,
|
|
157
|
+
timeoutMs: ctx.config.cardNewsPlanner.timeoutMs,
|
|
158
|
+
});
|
|
159
|
+
let result = validatePlannerOutput(raw.output, roleTemplate);
|
|
160
|
+
if (!result.ok) result = repairPlannerOutput(raw.output, { ...input, roleTemplate });
|
|
161
|
+
if (!result.ok) throw plannerError("Planner schema invalid", "PLANNER_SCHEMA_INVALID", 422);
|
|
162
|
+
return {
|
|
163
|
+
plan: toCardNewsPlan(result.plan, input, roleTemplate),
|
|
164
|
+
planner: { mode: raw.mode, model: raw.model, repaired: result.repaired },
|
|
165
|
+
};
|
|
166
|
+
} catch (err) {
|
|
167
|
+
if (ctx.config.cardNewsPlanner.deterministicFallback) {
|
|
168
|
+
return {
|
|
169
|
+
plan: createDeterministicCardNewsDraft(input),
|
|
170
|
+
planner: {
|
|
171
|
+
mode: "deterministic-fallback",
|
|
172
|
+
model: ctx.config.cardNewsPlanner.model,
|
|
173
|
+
repaired: true,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
if (err.code) throw err;
|
|
178
|
+
throw plannerError(err.message || "Planner unavailable", "PLANNER_UNAVAILABLE", 503);
|
|
179
|
+
}
|
|
180
|
+
}
|