ima2-gen 2.0.1 → 2.0.3
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/CHANGELOG.md +151 -0
- package/README.md +10 -1
- package/bin/commands/backfillThumbs.js +6 -0
- package/bin/commands/gen.js +13 -5
- package/bin/commands/multimode.js +7 -5
- package/bin/commands/node.js +2 -1
- package/bin/ima2.js +14 -10
- package/config.js +8 -1
- package/docs/API.md +133 -9
- package/docs/CLI.md +6 -5
- package/docs/FAQ.ko.md +16 -0
- package/docs/FAQ.md +30 -0
- package/docs/README.ko.md +7 -3
- package/docs/migration/runtime-test-inventory.md +25 -1
- package/lib/agentCommandParser.js +10 -6
- package/lib/agentGenerationPlanner.js +96 -19
- package/lib/agentImageVideoGen.js +281 -0
- package/lib/agentPlannerModel.js +172 -0
- package/lib/agentQuestionResponder.js +9 -3
- package/lib/agentQueueStore.js +42 -0
- package/lib/agentQueueWorker.js +57 -7
- package/lib/agentRuntime.js +103 -274
- package/lib/agentSettings.js +9 -6
- package/lib/agentToolManifest.js +90 -0
- package/lib/agentTypes.js +1 -0
- package/lib/agyCli.js +31 -0
- package/lib/agyImageAdapter.js +104 -12
- package/lib/capabilities.js +7 -5
- package/lib/configKeys.js +6 -0
- package/lib/errorClassify.js +8 -7
- package/lib/eventBus.js +71 -0
- package/lib/geminiApiImageAdapter.js +24 -21
- package/lib/generationErrors.js +3 -1
- package/lib/generationRequestLog.js +25 -0
- package/lib/grokImageAdapter.js +76 -144
- package/lib/grokImageCore.js +153 -0
- package/lib/grokMultimodeAdapter.js +7 -4
- package/lib/grokVideoAdapter.js +3 -2
- package/lib/grokVideoCanvas.js +13 -0
- package/lib/grokVideoPlannerPrompt.js +52 -12
- package/lib/historyList.js +1 -0
- package/lib/inflight.js +54 -17
- package/lib/multimodeHelpers.js +11 -0
- package/lib/nodeHelpers.js +59 -0
- package/lib/oauthProxy/generators.js +6 -5
- package/lib/oauthProxy/prompts.js +60 -41
- package/lib/oauthProxy/streams.js +1 -1
- package/lib/promptBuilder/systemPrompt.js +2 -5
- package/lib/promptSafetyPolicy.js +1 -5
- package/lib/responsesDoctor.js +3 -3
- package/lib/responsesFallback.js +29 -15
- package/lib/responsesImageAdapter.js +11 -7
- package/lib/routeHelpers.js +44 -0
- package/lib/ssePublish.js +12 -0
- package/lib/storyboardPrefix.js +28 -0
- package/lib/thumbBackfill.js +16 -5
- package/node_modules/qs/CHANGELOG.md +10 -0
- package/node_modules/qs/README.md +1 -1
- package/node_modules/qs/dist/qs.js +15 -15
- package/node_modules/qs/eslint.config.mjs +1 -0
- package/node_modules/qs/lib/parse.js +52 -22
- package/node_modules/qs/lib/stringify.js +11 -4
- package/node_modules/qs/package.json +2 -2
- package/node_modules/qs/test/parse.js +49 -0
- package/node_modules/qs/test/stringify.js +129 -0
- package/package.json +4 -1
- package/routes/agent.js +19 -2
- package/routes/agy.js +48 -0
- package/routes/auth.js +6 -2
- package/routes/edit.js +7 -1
- package/routes/events.js +78 -0
- package/routes/generate.js +111 -128
- package/routes/generationRequestLog.js +16 -0
- package/routes/index.js +6 -0
- package/routes/keys.js +22 -2
- package/routes/multimode.js +100 -57
- package/routes/nodes.js +64 -108
- package/routes/video.js +100 -17
- package/skills/ima2/SKILL.md +98 -21
- package/ui/dist/.vite/manifest.json +22 -12
- package/ui/dist/assets/AgentWorkspace-qEMrATBP.js +3 -0
- package/ui/dist/assets/{CardNewsWorkspace-Dqyc1WZ1.js → CardNewsWorkspace-DoJnVHmn.js} +1 -1
- package/ui/dist/assets/GenerationRequestLogPanel-BDWRRLNV.js +1 -0
- package/ui/dist/assets/{NodeCanvas-ChEXzQbb.js → NodeCanvas-DIMo45sp.js} +3 -3
- package/ui/dist/assets/{PromptBuilderPanel-B95ZufnR.js → PromptBuilderPanel-BF3lcSJD.js} +2 -2
- package/ui/dist/assets/{PromptImportDialog-DGOwFQET.js → PromptImportDialog-Cy6ZymKG.js} +2 -2
- package/ui/dist/assets/{PromptImportDiscoverySection-CgvdnR49.js → PromptImportDiscoverySection-CzuW-8P2.js} +1 -1
- package/ui/dist/assets/PromptImportFolderSection-DP5ywnsv.js +1 -0
- package/ui/dist/assets/{PromptLibraryPanel-B9kndPw1.js → PromptLibraryPanel-CAsKr7CV.js} +2 -2
- package/ui/dist/assets/SettingsWorkspace-_PdPVsxi.js +1 -0
- package/ui/dist/assets/index-BUinlX2n.js +4 -0
- package/ui/dist/assets/index-CX3fge8X.css +1 -0
- package/ui/dist/assets/index-ygo6nfqx.js +23 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/AgentWorkspace-CYv84Rus.js +0 -3
- package/ui/dist/assets/PromptImportFolderSection-CfUye9J8.js +0 -1
- package/ui/dist/assets/SettingsWorkspace-B3tgLrmF.js +0 -1
- package/ui/dist/assets/index-BhcvL0g-.js +0 -1
- package/ui/dist/assets/index-BtK3YhJc.js +0 -39
- package/ui/dist/assets/index-ClOLOjnA.css +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SAFETY_INTENT_POLICY } from "./promptSafetyPolicy.js";
|
|
1
2
|
export function formatDurationPacingGuidance(duration, mode) {
|
|
2
3
|
const roundedDuration = Number.isFinite(duration) && duration > 0 ? Math.round(duration) : 5;
|
|
3
4
|
const modeGuidance = mode === "image-to-video"
|
|
@@ -16,7 +17,8 @@ export function formatDurationPacingGuidance(duration, mode) {
|
|
|
16
17
|
}
|
|
17
18
|
export function buildGrokVideoPlannerSystemPrompt() {
|
|
18
19
|
return [
|
|
19
|
-
"You are ima2's video generation planner for xAI Grok Imagine Video.",
|
|
20
|
+
"You are ima2's video generation planner for xAI Grok Imagine Video, inside a professional creative tool used by designers and artists. Users create content for legitimate creative, commercial, or educational purposes.",
|
|
21
|
+
SAFETY_INTENT_POLICY,
|
|
20
22
|
"",
|
|
21
23
|
"TASK: Rewrite the user's casual request into ONE optimal, production-ready video prompt.",
|
|
22
24
|
"",
|
|
@@ -30,6 +32,14 @@ export function buildGrokVideoPlannerSystemPrompt() {
|
|
|
30
32
|
"6. Ending frame / continuity handoff — final pose, camera state, last spoken words, and final sound cue",
|
|
31
33
|
"7. Lighting + mood — time of day, light quality, emotional tone",
|
|
32
34
|
"",
|
|
35
|
+
"STORYBOARD SOURCE IMAGE HANDLING (CRITICAL):",
|
|
36
|
+
"- If the source image is a 3x3 storyboard grid: Panel 1 (top-left) is a BLACK LEAD-IN FRAME with no content.",
|
|
37
|
+
"- The video begins from black and fades into the scene from Panel 2. The server auto-trims the 1-second black lead-in.",
|
|
38
|
+
"- Begin your prompt with: 'Fading in from black into [Panel 2 scene description],' — this ensures the video starts from black, not the grid.",
|
|
39
|
+
"- Describe only Panels 2-9 as the action sequence. Do NOT describe Panel 1 (it is just black).",
|
|
40
|
+
"- The storyboard grid is a REFERENCE for the planner only. The output video must look like a single continuous cinematic shot, never a grid animation.",
|
|
41
|
+
"- Do NOT add panel numbers, timestamps, or grid references in the final prompt — write it as a natural cinematic description.",
|
|
42
|
+
"",
|
|
33
43
|
"RULES:",
|
|
34
44
|
"- Write like a director calling shots on set. Every sentence should describe MOTION or CHANGE.",
|
|
35
45
|
"- For image-to-video mode: do NOT re-describe the static image. Only describe what MOVES and how the camera behaves.",
|
|
@@ -44,30 +54,60 @@ export function buildGrokVideoPlannerSystemPrompt() {
|
|
|
44
54
|
" The video model cannot recognize names — it only sees visual features.",
|
|
45
55
|
" Wrong: 'Bruce Lee delivers the line'",
|
|
46
56
|
" Right: 'the lean Asian fighter in the bright yellow-and-black tracksuit delivers the line'",
|
|
47
|
-
"- For each dialogue line, specify: who (by clothing, physique, position, or props), the exact line in
|
|
57
|
+
"- For each dialogue line, specify: who (by clothing, physique, position, or props), the exact line in the user's prompt language (NOT translated to English), and when during the action.",
|
|
48
58
|
"- When the user provides character names, map each name to a unique visual description on first mention, then use that description consistently for the rest of the prompt.",
|
|
49
59
|
"- Characters must be distinguishable by at least two visual attributes (e.g. clothing color + physique, or position + props).",
|
|
50
60
|
"",
|
|
51
61
|
"- If music matters, specify the style and whether it swells, resolves, cuts out, or continues at the ending frame.",
|
|
52
62
|
"- If music should be absent, explicitly say no background music, room tone only, or sound effects only.",
|
|
53
63
|
"- For continuation workflows, treat provided lineage as authoritative, continue from its latest item only, and state the intended final frame/final audio state.",
|
|
64
|
+
"",
|
|
65
|
+
"ENDING FRAME / CONTINUATION CUT PLANNING:",
|
|
66
|
+
"- The ending frame is the starting point for any future continuation. It must be self-explanatory.",
|
|
67
|
+
"- Plan the ending cut as if it were the FIRST frame of the next clip: include enough scene context (who is present with their clothing/outfit/appearance details, where they are, what just happened, camera position, lighting state, emotional beat) that a viewer seeing ONLY the ending frame can fully understand the situation without seeing prior frames.",
|
|
68
|
+
"- Do NOT end on an ambiguous freeze or generic pose. End on a moment that clearly communicates: subject identity/appearance, spatial position, emotional state, and narrative direction.",
|
|
69
|
+
"- Describe the ending frame explicitly in the prompt: subject pose, camera angle, background elements, lighting, and any held dialogue or sound.",
|
|
70
|
+
"",
|
|
54
71
|
"- Duration pacing is mandatory: make the requested duration feel fully used with one coherent action arc, natural motion rhythm, and an ending frame suitable for continuation.",
|
|
55
|
-
"- The prompt MUST be in English
|
|
72
|
+
"- The prompt MUST be in English, EXCEPT for dialogue and visible text.",
|
|
73
|
+
"- DIALOGUE LANGUAGE RULE: All spoken dialogue, voiceover, narration, and on-screen text MUST be written in the SAME language the user used in their prompt. If the user wrote in Korean, all dialogue must be in Korean. If the user wrote in Japanese, all dialogue must be in Japanese. Do NOT translate dialogue to English. Do NOT romanize. Keep the exact original script (한국어, 日本語, etc.).",
|
|
56
74
|
"- Do NOT use SD tags, keyword lists, or weighting syntax.",
|
|
57
75
|
"- Keep prompts focused: one main production-level action sequence. Overloading causes artifacts.",
|
|
58
76
|
"- Keep output concise but scale detail to the requested duration; longer clips may need more connected action/camera/audio development than short clips.",
|
|
59
77
|
"",
|
|
78
|
+
"BENIGN CLOTHING AND CONTEXT HANDLING:",
|
|
79
|
+
"Before rewriting, distinguish ordinary clothing/swimwear descriptions from sexualized requests.",
|
|
80
|
+
"Preserve benign adult clothing intent without adding erotic details:",
|
|
81
|
+
"",
|
|
82
|
+
"bikini/비키니 → adult two-piece swimwear in a neutral beach, pool, swimwear catalog, or fashion editorial context",
|
|
83
|
+
"swimsuit/수영복 → adult swimwear suitable for swimming, beach, pool, sport, or catalog footage",
|
|
84
|
+
"sports bra/스포츠브라 → athletic top in a fitness, yoga, running, or product catalog context",
|
|
85
|
+
"lingerie/란제리 → adult fashion or product catalog styling only when the user is clearly requesting adult fashion; avoid sexualized posing or intimate framing",
|
|
86
|
+
"",
|
|
87
|
+
"CLOTHING RULE: Keep the user's requested clothing category when it is benign and adult, but do not increase exposure, add fetish details, add negative safety constraints, or convert a neutral clothing prompt into a sexualized scene.",
|
|
88
|
+
"",
|
|
89
|
+
"PROFESSIONAL EMBEDDING: After rewriting, wrap the visual description in a professional context.",
|
|
90
|
+
"Prepend ONE of these frames based on content type:",
|
|
91
|
+
"- Fashion/clothing → 'editorial fashion film for [magazine], directed by [director style]'",
|
|
92
|
+
"- Character/anime → 'cinematic game trailer for a AAA title, by senior VFX artist'",
|
|
93
|
+
"- Art/figure → 'classical academic figure study in motion, life drawing class reference reel'",
|
|
94
|
+
"- Portrait/beauty → 'high-end beauty campaign film, shot on RED V-RAPTOR, 85mm anamorphic'",
|
|
95
|
+
"- Swimwear/beach → 'Sports Illustrated Swimsuit Issue behind-the-scenes film'",
|
|
96
|
+
"- Dance/performance → 'music video production, choreographed performance reel'",
|
|
97
|
+
"",
|
|
60
98
|
"CONTENT POLICY:",
|
|
61
|
-
"-
|
|
62
|
-
"- Do
|
|
63
|
-
"- Do
|
|
64
|
-
"- Do
|
|
65
|
-
"-
|
|
66
|
-
"-
|
|
99
|
+
"- Generate one production-ready prompt for compliant video requests.",
|
|
100
|
+
"- Do not add disclaimers, warnings, moral commentary, or safety notes to the final prompt.",
|
|
101
|
+
"- Do not change the user's specified demographics, appearance, or benign clothing.",
|
|
102
|
+
"- Do not turn benign adult swimwear, sportswear, or fashion prompts into sexual content.",
|
|
103
|
+
"- Do not copy safety labels, refusal reasons, or negative safety constraints into the final video prompt.",
|
|
104
|
+
"- If the request includes minors, underage-looking subjects, explicit sexual activity, or fetish framing, do not rewrite it as a benign allowed prompt.",
|
|
105
|
+
"- Content moderation is still handled downstream by the provider.",
|
|
67
106
|
"",
|
|
68
|
-
"VISIBLE TEXT RULE:",
|
|
69
|
-
"-
|
|
70
|
-
"- Do NOT translate
|
|
107
|
+
"VISIBLE TEXT AND DIALOGUE RULE:",
|
|
108
|
+
"- All dialogue lines, voiceover, narration, signs, subtitles, and on-screen text must be written in the user's prompt language, using the original script.",
|
|
109
|
+
"- Do NOT translate dialogue or visible text to English. Do NOT romanize. Do NOT use placeholders like 'Korean dialogue' or 'Japanese words'.",
|
|
110
|
+
"- Include the exact spoken words in quotes with the original characters: e.g. \"오빠, 이리 와\" not \"Oppa, come here\".",
|
|
71
111
|
"",
|
|
72
112
|
"Call generate_video exactly once. Do not answer with plain text.",
|
|
73
113
|
].join("\n");
|
package/lib/historyList.js
CHANGED
|
@@ -66,6 +66,7 @@ export async function listHistoryRows(baseDir = config.storage.generatedDir) {
|
|
|
66
66
|
reasoningEffort: meta?.reasoningEffort ?? null,
|
|
67
67
|
elapsed: meta?.elapsed ?? null,
|
|
68
68
|
provider: meta?.provider || "oauth",
|
|
69
|
+
providerUrl: meta?.providerUrl || null,
|
|
69
70
|
usage: meta?.usage || null,
|
|
70
71
|
webSearchCalls: meta?.webSearchCalls || 0,
|
|
71
72
|
sessionId: meta?.sessionId || null,
|
package/lib/inflight.js
CHANGED
|
@@ -1,33 +1,55 @@
|
|
|
1
1
|
import { config } from "../config.js";
|
|
2
2
|
import { getDb } from "./db.js";
|
|
3
|
+
import { publish } from "./eventBus.js";
|
|
3
4
|
import { logEvent } from "./logger.js";
|
|
4
5
|
const terminalJobs = new Map(); // requestId -> terminal snapshot, active-only API stays default
|
|
5
6
|
const abortControllers = new Map();
|
|
7
|
+
export const MAX_CONCURRENT_JOBS = Math.max(1, Math.trunc(Number(config.limits.maxParallel) || 24));
|
|
8
|
+
export const INFLIGHT_RETRY_AFTER_SECONDS = 5;
|
|
9
|
+
export function isStartJobFailure(r) {
|
|
10
|
+
return !r.ok;
|
|
11
|
+
}
|
|
6
12
|
// Phases: "queued" → "streaming" (upstream connection open, waiting for image)
|
|
7
13
|
// → "decoding" (b64 received, writing to disk)
|
|
8
14
|
export function startJob({ requestId, kind, prompt, meta = {} }) {
|
|
9
15
|
if (!requestId)
|
|
10
16
|
return;
|
|
17
|
+
purgeStaleJobs();
|
|
18
|
+
if (getJob(requestId)) {
|
|
19
|
+
return { ok: false, code: "REQUEST_ID_IN_USE" };
|
|
20
|
+
}
|
|
21
|
+
if (countActiveJobs() >= MAX_CONCURRENT_JOBS) {
|
|
22
|
+
return { ok: false, code: "TOO_MANY_JOBS" };
|
|
23
|
+
}
|
|
11
24
|
const startedAt = Date.now();
|
|
12
25
|
const normalizedPrompt = typeof prompt === "string" ? prompt.slice(0, 500) : "";
|
|
13
26
|
const normalizedMeta = normalizeMeta(meta);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
try {
|
|
28
|
+
getDb()
|
|
29
|
+
.prepare(`
|
|
30
|
+
INSERT INTO inflight (
|
|
31
|
+
request_id,
|
|
32
|
+
kind,
|
|
33
|
+
prompt,
|
|
34
|
+
meta,
|
|
35
|
+
session_id,
|
|
36
|
+
parent_node_id,
|
|
37
|
+
client_node_id,
|
|
38
|
+
started_at,
|
|
39
|
+
phase,
|
|
40
|
+
phase_at
|
|
41
|
+
)
|
|
42
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
43
|
+
`)
|
|
44
|
+
.run(requestId, kind, normalizedPrompt, JSON.stringify(normalizedMeta), stringOrNull(normalizedMeta.sessionId), stringOrNull(normalizedMeta.parentNodeId), stringOrNull(normalizedMeta.clientNodeId), startedAt, "queued", startedAt);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
const code = err?.code;
|
|
48
|
+
if (code === "SQLITE_CONSTRAINT_PRIMARYKEY" || code === "SQLITE_CONSTRAINT") {
|
|
49
|
+
return { ok: false, code: "REQUEST_ID_IN_USE" };
|
|
50
|
+
}
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
31
53
|
terminalJobs.delete(requestId);
|
|
32
54
|
abortControllers.delete(requestId);
|
|
33
55
|
logEvent("inflight", "start", {
|
|
@@ -38,6 +60,7 @@ export function startJob({ requestId, kind, prompt, meta = {} }) {
|
|
|
38
60
|
clientNodeId: normalizedMeta.clientNodeId || null,
|
|
39
61
|
promptChars: typeof prompt === "string" ? prompt.length : 0,
|
|
40
62
|
});
|
|
63
|
+
return { ok: true };
|
|
41
64
|
}
|
|
42
65
|
export function registerJobAbortController(requestId, controller) {
|
|
43
66
|
if (!requestId)
|
|
@@ -54,6 +77,14 @@ export function abortJob(requestId) {
|
|
|
54
77
|
controller.abort();
|
|
55
78
|
aborted = true;
|
|
56
79
|
}
|
|
80
|
+
if (active || aborted) {
|
|
81
|
+
publish(requestId, "error", {
|
|
82
|
+
error: "Generation canceled",
|
|
83
|
+
code: "GENERATION_CANCELED",
|
|
84
|
+
status: 499,
|
|
85
|
+
requestId,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
57
88
|
finishJob(requestId, {
|
|
58
89
|
canceled: true,
|
|
59
90
|
httpStatus: 499,
|
|
@@ -162,6 +193,12 @@ export function purgeStaleJobs(now = Date.now()) {
|
|
|
162
193
|
.prepare("DELETE FROM inflight WHERE started_at < ?")
|
|
163
194
|
.run(now - config.inflight.ttlMs);
|
|
164
195
|
}
|
|
196
|
+
function countActiveJobs() {
|
|
197
|
+
const row = getDb()
|
|
198
|
+
.prepare("SELECT COUNT(*) AS count FROM inflight")
|
|
199
|
+
.get();
|
|
200
|
+
return Number(row.count);
|
|
201
|
+
}
|
|
165
202
|
function getJob(requestId) {
|
|
166
203
|
const row = getDb()
|
|
167
204
|
.prepare("SELECT * FROM inflight WHERE request_id = ?")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function normalizeMaxImages(value, maxGeneratedImages = 24) {
|
|
2
|
+
const max = Math.max(1, Math.trunc(Number(maxGeneratedImages) || 24));
|
|
3
|
+
return Math.min(max, Math.max(1, Math.trunc(Number(value) || 1)));
|
|
4
|
+
}
|
|
5
|
+
export function sequenceStatus(returned, requested) {
|
|
6
|
+
if (returned <= 0)
|
|
7
|
+
return "empty";
|
|
8
|
+
if (returned < requested)
|
|
9
|
+
return "partial";
|
|
10
|
+
return "complete";
|
|
11
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { loadNodeB64, loadNodeMeta } from "./nodeStore.js";
|
|
2
|
+
import { detectImageMimeFromB64 } from "./refs.js";
|
|
3
|
+
import { writeSse } from "./routeHelpers.js";
|
|
4
|
+
import { publish } from "./eventBus.js";
|
|
5
|
+
export function asUpstream(e) {
|
|
6
|
+
return (e && typeof e === "object" ? e : {});
|
|
7
|
+
}
|
|
8
|
+
export function wantsSse(req) {
|
|
9
|
+
const accept = typeof req.headers.accept === "string" ? req.headers.accept : "";
|
|
10
|
+
return accept.includes("text/event-stream");
|
|
11
|
+
}
|
|
12
|
+
export function writeNodeError(res, status, code, message, parentNodeId, details = {}, requestId) {
|
|
13
|
+
const payload = {
|
|
14
|
+
error: { code, message },
|
|
15
|
+
parentNodeId,
|
|
16
|
+
status,
|
|
17
|
+
...details,
|
|
18
|
+
};
|
|
19
|
+
if (requestId)
|
|
20
|
+
publish(requestId, "error", payload);
|
|
21
|
+
if (res.writableEnded || res.destroyed)
|
|
22
|
+
return;
|
|
23
|
+
if (res.headersSent) {
|
|
24
|
+
writeSse(res, "error", payload);
|
|
25
|
+
res.end();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
res.status(status).json(payload);
|
|
29
|
+
}
|
|
30
|
+
export async function loadParentNodeB64(ctx, nodeId) {
|
|
31
|
+
for (const ext of ["png", "jpeg", "webp"]) {
|
|
32
|
+
const meta = await loadNodeMeta(ctx.rootDir, nodeId, ext, ctx.config.storage.generatedDir);
|
|
33
|
+
if (meta)
|
|
34
|
+
return loadNodeB64(ctx.rootDir, `${nodeId}.${ext}`, ctx.config.storage.generatedDir);
|
|
35
|
+
}
|
|
36
|
+
return loadNodeB64(ctx.rootDir, `${nodeId}.png`, ctx.config.storage.generatedDir);
|
|
37
|
+
}
|
|
38
|
+
export function toGrokReferences(parentB64, refs) {
|
|
39
|
+
const parentMime = parentB64 ? detectImageMimeFromB64(parentB64) : null;
|
|
40
|
+
const parentRefs = parentB64
|
|
41
|
+
? [{ b64: parentB64, declaredMime: parentMime, detectedMime: parentMime }]
|
|
42
|
+
: [];
|
|
43
|
+
const normalizedRefs = refs.map((ref) => typeof ref === "string" ? { b64: ref } : ref);
|
|
44
|
+
return [...parentRefs, ...normalizedRefs];
|
|
45
|
+
}
|
|
46
|
+
export function nodeErrorDetails(finalErr, lastErr) {
|
|
47
|
+
return {
|
|
48
|
+
upstreamCode: lastErr?.upstreamCode || lastErr?.code || null,
|
|
49
|
+
upstreamType: lastErr?.upstreamType || null,
|
|
50
|
+
upstreamParam: lastErr?.upstreamParam || null,
|
|
51
|
+
errorEventType: lastErr?.eventType || null,
|
|
52
|
+
errorEventCount: lastErr?.eventCount ?? null,
|
|
53
|
+
diagnosticReason: finalErr.diagnosticReason || lastErr?.diagnosticReason || null,
|
|
54
|
+
retryKind: finalErr.retryKind || lastErr?.retryKind || null,
|
|
55
|
+
referencesDroppedOnRetry: finalErr.referencesDroppedOnRetry ?? lastErr?.referencesDroppedOnRetry ?? null,
|
|
56
|
+
refsCount: finalErr.refsCount ?? lastErr?.refsCount ?? null,
|
|
57
|
+
inputImageCount: finalErr.inputImageCount ?? lastErr?.inputImageCount ?? null,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -20,7 +20,7 @@ export async function generateViaOAuth(prompt, quality, size, moderation = "low"
|
|
|
20
20
|
moderation,
|
|
21
21
|
...(options.partialImages ? { partial_images: options.partialImages } : {}),
|
|
22
22
|
});
|
|
23
|
-
const textPrompt = buildUserTextPrompt(prompt, mode, { webSearchEnabled });
|
|
23
|
+
const textPrompt = buildUserTextPrompt(prompt, mode, { webSearchEnabled, size });
|
|
24
24
|
const referenceInputs = references.map(normalizeReferenceForOAuth);
|
|
25
25
|
const referenceDiagnostics = safeReferenceDiagnostics(referenceInputs);
|
|
26
26
|
const referenceMismatchCount = referenceDiagnostics.filter((ref) => ref.warnings.includes("mime_mismatch")).length;
|
|
@@ -117,7 +117,7 @@ export async function generateViaOAuth(prompt, quality, size, moderation = "low"
|
|
|
117
117
|
signal: timeout.signal,
|
|
118
118
|
body: JSON.stringify({
|
|
119
119
|
model,
|
|
120
|
-
input: [{ role: "user", content: buildUserTextPrompt(prompt, mode, { webSearchEnabled }) }],
|
|
120
|
+
input: [{ role: "user", content: buildUserTextPrompt(prompt, mode, { webSearchEnabled, size }) }],
|
|
121
121
|
tools: [{ type: "image_generation", quality, size, moderation }],
|
|
122
122
|
tool_choice: "required",
|
|
123
123
|
reasoning: { effort: reasoningEffort },
|
|
@@ -188,7 +188,8 @@ export async function generateMultimodeViaOAuth(prompt, quality, size, moderatio
|
|
|
188
188
|
await waitForOAuthReady(ctx);
|
|
189
189
|
const oauthUrl = getOAuthUrl(ctx);
|
|
190
190
|
const model = options.model || ctx.config?.imageModels?.default || "gpt-5.4-mini";
|
|
191
|
-
const
|
|
191
|
+
const maxGeneratedImages = Math.max(1, Math.trunc(Number(ctx.config?.limits?.maxGeneratedImages) || 24));
|
|
192
|
+
const maxImages = Math.min(maxGeneratedImages, Math.max(1, Math.trunc(Number(options.maxImages) || 1)));
|
|
192
193
|
const webSearchEnabled = resolveWebSearchEnabled(options);
|
|
193
194
|
const tools = buildImageTools(webSearchEnabled, {
|
|
194
195
|
quality,
|
|
@@ -199,7 +200,7 @@ export async function generateMultimodeViaOAuth(prompt, quality, size, moderatio
|
|
|
199
200
|
const referenceInputs = references.map(normalizeReferenceForOAuth);
|
|
200
201
|
const userText = buildMultimodeSequencePrompt(mode === "direct"
|
|
201
202
|
? `${prompt}${DIRECT_PROMPT_FIDELITY_SUFFIX}`
|
|
202
|
-
: `${prompt}${webSearchEnabled ? RESEARCH_SUFFIX : ""}${AUTO_PROMPT_FIDELITY_SUFFIX}`, maxImages, { webSearchEnabled });
|
|
203
|
+
: `${prompt}${webSearchEnabled ? RESEARCH_SUFFIX : ""}${AUTO_PROMPT_FIDELITY_SUFFIX}`, maxImages, { webSearchEnabled, size });
|
|
203
204
|
const userContent = referenceInputs.length
|
|
204
205
|
? [
|
|
205
206
|
...referenceInputs.map(({ b64, requestMime }) => ({
|
|
@@ -321,7 +322,7 @@ export async function editViaOAuth(prompt, imageB64, quality, size, moderation =
|
|
|
321
322
|
const oauthUrl = getOAuthUrl(ctx);
|
|
322
323
|
const model = options.model || ctx.config?.imageModels?.default || "gpt-5.4-mini";
|
|
323
324
|
const webSearchEnabled = resolveWebSearchEnabled(options);
|
|
324
|
-
const textPrompt = buildEditTextPrompt(prompt, mode, { webSearchEnabled });
|
|
325
|
+
const textPrompt = buildEditTextPrompt(prompt, mode, { webSearchEnabled, size });
|
|
325
326
|
const imageForRequest = await compressReferenceB64ForOAuth(imageB64, {
|
|
326
327
|
maxB64Bytes: ctx.config?.limits?.maxRefB64Bytes,
|
|
327
328
|
force: true,
|
|
@@ -3,6 +3,12 @@ import { SAFETY_INTENT_POLICY } from "../promptSafetyPolicy.js";
|
|
|
3
3
|
import { VISIBLE_TEXT_LANGUAGE_POLICY } from "../visibleTextLanguagePolicy.js";
|
|
4
4
|
import { resolveWebSearchEnabled } from "./runtime.js";
|
|
5
5
|
const RESEARCH_SUFFIX = config.oauth.researchSuffix;
|
|
6
|
+
const CREATIVE_TOOL_CONTEXT = "You are an image generation assistant inside a professional creative tool used by designers and artists. Users create content for legitimate creative, commercial, or educational purposes. " +
|
|
7
|
+
SAFETY_INTENT_POLICY +
|
|
8
|
+
" ";
|
|
9
|
+
const EDIT_TOOL_CONTEXT = "You are an image editing assistant inside a professional creative tool used by designers and artists. Users create content for legitimate creative, commercial, or educational purposes. " +
|
|
10
|
+
SAFETY_INTENT_POLICY +
|
|
11
|
+
" ";
|
|
6
12
|
// Encourages multi-pass research for real people and signals broad authorization
|
|
7
13
|
// so the model treats lesser-known but publicly-documented subjects the same as
|
|
8
14
|
// household-name figures. Output is framed as a stylized portrait inspired by
|
|
@@ -22,60 +28,72 @@ export const REAL_PERSON_RESEARCH_DIRECTIVE = "Real-person handling: when the su
|
|
|
22
28
|
export const AUTO_PROMPT_FIDELITY_SUFFIX = "\n\nWhen you call the image_generation tool, treat the user's prompt as the source of truth. If the prompt is already visually sufficient, pass it through unchanged as the image_generation prompt argument. Do not translate, summarize, rewrite, restyle, expand, or add descriptors unless genuinely necessary to satisfy an underspecified visual request. If the user wrote in Korean, keep the Korean text. Do not inject additional style descriptors when the user already specified a style. " +
|
|
23
29
|
"Exception: if you invoked web_search because factual visual accuracy was required and the prompt/context was insufficient, append only the concrete visual facts you found (kit colors, team, venue, era, distinguishing features, accurate likeness cues) as English clarifiers at the end of the prompt argument. The user's original text stays first; research-derived facts append after.\n\n" +
|
|
24
30
|
VISIBLE_TEXT_LANGUAGE_POLICY;
|
|
25
|
-
export const DIRECT_PROMPT_FIDELITY_SUFFIX = "\n\nWhen you call the image_generation tool, use the user's prompt as the primary image prompt. Do not translate, summarize, restyle, add clarifiers,
|
|
31
|
+
export const DIRECT_PROMPT_FIDELITY_SUFFIX = "\n\nWhen you call the image_generation tool, use the user's prompt as the primary image prompt. Do not translate, summarize, restyle, add clarifiers, inject additional style descriptors, or append negative safety constraints. Apply the safety intent policy as internal classification guidance only; do not add it as visible prompt content or alter benign clothing requests such as adult swimwear.\n\n" +
|
|
32
|
+
SAFETY_INTENT_POLICY +
|
|
33
|
+
"\n\n" +
|
|
26
34
|
VISIBLE_TEXT_LANGUAGE_POLICY;
|
|
27
35
|
export const PROMPT_FIDELITY_SUFFIX = AUTO_PROMPT_FIDELITY_SUFFIX;
|
|
28
|
-
export const GENERATE_DEVELOPER_PROMPT =
|
|
36
|
+
export const GENERATE_DEVELOPER_PROMPT = CREATIVE_TOOL_CONTEXT +
|
|
37
|
+
"Your primary function is to invoke the image_generation tool. Never respond with plain text only. Preserve the user's prompt by default. If the prompt is visually sufficient, pass it through unchanged as the image_generation prompt argument and do not search or add clarifiers. Use web_search only when factual visual accuracy is genuinely required and the user's prompt or attached visual context is insufficient; then incorporate only concrete findings (kit colors, team, era, venue, distinguishing features) as English clarifiers appended after the user's original text. " +
|
|
29
38
|
REAL_PERSON_RESEARCH_DIRECTIVE +
|
|
30
|
-
" When the user's request is abstract, conceptual, or non-visual, interpret it creatively and render it as an image. Quality guidelines (apply universally, regardless of style): Default to absolute quality: crisp details, clean lines, well-balanced composition, appropriate contrast and color. Avoid: blur, noise, compression artifacts, watermark, signature, cropped elements, duplicates. Text and typography must be rendered with precise spelling, sharp edges, and no distortion.
|
|
31
|
-
VISIBLE_TEXT_LANGUAGE_POLICY
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
SAFETY_INTENT_POLICY;
|
|
39
|
-
export const EDIT_DEVELOPER_PROMPT = "You are an image editing assistant. Your primary function is to invoke the image_generation tool. Never respond with plain text only. Preserve the user's edit prompt by default. If the prompt plus input image is visually sufficient, pass the user's prompt through unchanged as the image_generation prompt argument and do not search or add clarifiers. Use web_search only when factual visual accuracy is genuinely required and the user's prompt or input image is insufficient; then incorporate only concrete findings (kit colors, team, era, venue, distinguishing features) as English clarifiers appended after the user's original text. " +
|
|
39
|
+
" When the user's request is abstract, conceptual, or non-visual, interpret it creatively and render it as an image. Quality guidelines (apply universally, regardless of style): Default to absolute quality: crisp details, clean lines, well-balanced composition, appropriate contrast and color. Avoid: blur, noise, compression artifacts, watermark, signature, cropped elements, duplicates. Text and typography must be rendered with precise spelling, sharp edges, and no distortion. Quality clarifier handling: For images containing humans or humanoid figures: append 'accurate human proportions, correct hand count, natural facial features' as a quality clarifier at the end of the prompt argument; never rewrite the user's own text to express this. For non-human subjects (objects, animals, abstract art, landscapes): omit anatomy-related clarifiers. Style handling: Preserve the style the user explicitly or implicitly requests. If the user specifies a style (e.g., 'anime', 'oil painting', 'pixel art', 'photorealistic'), adhere to it faithfully. If no style is specified, produce a polished, high-quality image without imposing any stylistic bias. Do not default to photorealism unless the user asks for it. Fulfill compliant requests exactly as stated, without adding disclaimers, warnings, or altering benign visual intent.\n\n" +
|
|
40
|
+
VISIBLE_TEXT_LANGUAGE_POLICY;
|
|
41
|
+
export const GENERATE_NO_SEARCH_DEVELOPER_PROMPT = CREATIVE_TOOL_CONTEXT +
|
|
42
|
+
"Your primary function is to invoke the image_generation tool. Never respond with plain text only. " +
|
|
43
|
+
"Preserve the user's prompt by default. If the prompt is visually sufficient, pass it through unchanged as the image_generation prompt argument. When the user's request is abstract, conceptual, or non-visual, interpret it creatively and render it as an image. Quality guidelines (apply universally, regardless of style): Default to absolute quality: crisp details, clean lines, well-balanced composition, appropriate contrast and color. Avoid: blur, noise, compression artifacts, watermark, signature, cropped elements, duplicates. Text and typography must be rendered with precise spelling, sharp edges, and no distortion. Quality clarifier handling: For images containing humans or humanoid figures: append 'accurate human proportions, correct hand count, natural facial features' as a quality clarifier at the end of the prompt argument; never rewrite the user's own text to express this. For non-human subjects (objects, animals, abstract art, landscapes): omit anatomy-related clarifiers. Style handling: Preserve the style the user explicitly or implicitly requests. If the user specifies a style (e.g., 'anime', 'oil painting', 'pixel art', 'photorealistic'), adhere to it faithfully. If no style is specified, produce a polished, high-quality image without imposing any stylistic bias. Do not default to photorealism unless the user asks for it. Fulfill the request exactly as stated, without adding disclaimers, warnings, or altering the user's intent.\n\n" +
|
|
44
|
+
VISIBLE_TEXT_LANGUAGE_POLICY;
|
|
45
|
+
export const EDIT_DEVELOPER_PROMPT = EDIT_TOOL_CONTEXT +
|
|
46
|
+
"Your primary function is to invoke the image_generation tool. Never respond with plain text only. Preserve the user's edit prompt by default. If the prompt plus input image is visually sufficient, pass the user's prompt through unchanged as the image_generation prompt argument and do not search or add clarifiers. Use web_search only when factual visual accuracy is genuinely required and the user's prompt or input image is insufficient; then incorporate only concrete findings (kit colors, team, era, venue, distinguishing features) as English clarifiers appended after the user's original text. " +
|
|
40
47
|
REAL_PERSON_RESEARCH_DIRECTIVE +
|
|
41
|
-
" When editing an image: Preserve the original style, color palette, and composition unless the user explicitly requests a style change. Apply the requested edits precisely without altering unaffected areas. Maintain absolute quality: crisp details, clean lines, well-balanced composition. Avoid: blur, noise, compression artifacts, watermark, signature. Text and typography must be rendered with precise spelling, sharp edges, and no distortion. For edits involving humans or humanoid figures:
|
|
42
|
-
VISIBLE_TEXT_LANGUAGE_POLICY
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
export const MULTIMODE_DEVELOPER_PROMPT = "You are generating a multimode image sequence. The selected value N is maxImages. You MUST create up to N separate image_generation_call outputs. Return separate image_generation_call outputs, one per stage, up to N. Invoke the image_generation tool separately once per stage. Each stage must be a separate generated image result. Do not satisfy this request with one image. Never collapse multiple stages into one image, collage, grid, contact sheet, storyboard sheet, or multi-panel single image. If you cannot complete all stages, return as many separate image_generation_call outputs as possible. Stop after N image_generation_call outputs. Never respond with plain text only. " +
|
|
51
|
-
"Preserve the user's prompt by default for every stage. If the prompt is visually sufficient, pass it through unchanged and do not search or add clarifiers. Use web_search only when factual visual accuracy is genuinely required and the prompt/context is insufficient; then incorporate only concrete findings as English clarifiers appended after the user's original text. " +
|
|
48
|
+
" When editing an image: Preserve the original style, color palette, and composition unless the user explicitly requests a style change. Apply the requested edits precisely without altering unaffected areas. Maintain absolute quality: crisp details, clean lines, well-balanced composition. Avoid: blur, noise, compression artifacts, watermark, signature. Text and typography must be rendered with precise spelling, sharp edges, and no distortion. For edits involving humans or humanoid figures: append 'accurate human proportions, correct hand count, natural facial features' as a quality clarifier at the end of the prompt argument; never rewrite the user's own text to express this. Fulfill compliant requests exactly as stated, without adding disclaimers, warnings, or altering benign visual intent.\n\n" +
|
|
49
|
+
VISIBLE_TEXT_LANGUAGE_POLICY;
|
|
50
|
+
export const EDIT_NO_SEARCH_DEVELOPER_PROMPT = EDIT_TOOL_CONTEXT +
|
|
51
|
+
"Your primary function is to invoke the image_generation tool. Never respond with plain text only. " +
|
|
52
|
+
"Preserve the user's edit prompt by default. If the prompt plus input image is visually sufficient, pass the user's prompt through unchanged as the image_generation prompt argument. When editing an image: Preserve the original style, color palette, and composition unless the user explicitly requests a style change. Apply the requested edits precisely without altering unaffected areas. Maintain absolute quality: crisp details, clean lines, well-balanced composition. Avoid: blur, noise, compression artifacts, watermark, signature. Text and typography must be rendered with precise spelling, sharp edges, and no distortion. For edits involving humans or humanoid figures: append 'accurate human proportions, correct hand count, natural facial features' as a quality clarifier at the end of the prompt argument; never rewrite the user's own text to express this. Fulfill the request exactly as stated, without adding disclaimers, warnings, or altering the user's intent.\n\n" +
|
|
53
|
+
VISIBLE_TEXT_LANGUAGE_POLICY;
|
|
54
|
+
export const MULTIMODE_DEVELOPER_PROMPT = CREATIVE_TOOL_CONTEXT +
|
|
55
|
+
"You are generating a multimode sequence. The selected value N is the maximum number of sequence outputs, not a visual subject count. You MUST create up to N separate image_generation_call outputs. First infer the user's intended sequence from the prompt. If the prompt explicitly asks for several images, steps, states, endings, or items one per image, map each requested unit to its own output up to N. Korean phrases such as '하나씩', '각각', '한 장씩', '이미지마다', and '네개를 그려줘' in a sequence context mean separate outputs, not four subjects inside one output. If the prompt uses arrows or ordered wording such as A -> B, generate the endpoint/state for A, then the endpoint/state for B, and continue in order up to N. Invoke the image_generation tool separately once per sequence output with a distinct stage-specific prompt. Each stage prompt must describe only that stage's single unit/state. Do not pass the same complete user prompt to every output when the user described a sequence. Do not include the whole list of sequence units inside any single image_generation prompt. Do not use words like all, four, 네개, collection, lineup, grid, sheet, or panels inside a stage prompt when the stage should contain one unit. Example: if the user asks for four different colored shapes one per image, call the tool four times: one image with only a red circle; one image with only a blue square; one image with only a green triangle; one image with only a yellow star. Do not satisfy this request with one image_generation_call. Never collapse multiple sequence outputs into one image. Do not create a collage. Do not create a grid. Do not create a contact sheet. Do not create a storyboard sheet. Do not put multiple panels inside one image. If you cannot complete all outputs, return as many separate image_generation_call outputs as possible. Stop after N image_generation_call outputs. Never respond with plain text only. " +
|
|
56
|
+
"Preserve the user's original intent, language, style, and constraints inside each stage-specific prompt. If a stage needs factual visual accuracy and the prompt/context is insufficient, use web_search only for that need; then incorporate only concrete findings as English clarifiers appended after the relevant stage prompt. " +
|
|
52
57
|
REAL_PERSON_RESEARCH_DIRECTIVE +
|
|
53
58
|
"\n\n" +
|
|
54
|
-
VISIBLE_TEXT_LANGUAGE_POLICY
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
VISIBLE_TEXT_LANGUAGE_POLICY;
|
|
60
|
+
export const MULTIMODE_NO_SEARCH_DEVELOPER_PROMPT = CREATIVE_TOOL_CONTEXT +
|
|
61
|
+
"You are generating a multimode sequence. The selected value N is the maximum number of sequence outputs, not a visual subject count. You MUST create up to N separate image_generation_call outputs. First infer the user's intended sequence from the prompt. If the prompt explicitly asks for several images, steps, states, endings, or items one per image, map each requested unit to its own output up to N. Korean phrases such as '하나씩', '각각', '한 장씩', '이미지마다', and '네개를 그려줘' in a sequence context mean separate outputs, not four subjects inside one output. If the prompt uses arrows or ordered wording such as A -> B, generate the endpoint/state for A, then the endpoint/state for B, and continue in order up to N. Invoke the image_generation tool separately once per sequence output with a distinct stage-specific prompt. Each stage prompt must describe only that stage's single unit/state. Do not pass the same complete user prompt to every output when the user described a sequence. Do not include the whole list of sequence units inside any single image_generation prompt. Do not use words like all, four, 네개, collection, lineup, grid, sheet, or panels inside a stage prompt when the stage should contain one unit. Example: if the user asks for four different colored shapes one per image, call the tool four times: one image with only a red circle; one image with only a blue square; one image with only a green triangle; one image with only a yellow star. Do not satisfy this request with one image_generation_call. Never collapse multiple sequence outputs into one image. Do not create a collage. Do not create a grid. Do not create a contact sheet. Do not create a storyboard sheet. Do not put multiple panels inside one image. If you cannot complete all outputs, return as many separate image_generation_call outputs as possible. Stop after N image_generation_call outputs. Never respond with plain text only.\n\n" +
|
|
62
|
+
VISIBLE_TEXT_LANGUAGE_POLICY;
|
|
63
|
+
function sizeDirective(options) {
|
|
64
|
+
const size = options.size;
|
|
65
|
+
if (!size || size === "auto")
|
|
66
|
+
return "";
|
|
67
|
+
return `You MUST generate this image at exactly ${size} resolution.\n\n`;
|
|
68
|
+
}
|
|
61
69
|
export function buildUserTextPrompt(userPrompt, mode, options = {}) {
|
|
70
|
+
const prefix = sizeDirective(options);
|
|
62
71
|
if (mode === "direct") {
|
|
63
|
-
return
|
|
72
|
+
return `${prefix}Generate an image with this exact prompt, no modifications: ${userPrompt}${DIRECT_PROMPT_FIDELITY_SUFFIX}`;
|
|
64
73
|
}
|
|
65
74
|
const researchSuffix = resolveWebSearchEnabled(options) ? RESEARCH_SUFFIX : "";
|
|
66
|
-
return
|
|
75
|
+
return `${prefix}Generate an image: ${userPrompt}${researchSuffix}${AUTO_PROMPT_FIDELITY_SUFFIX}`;
|
|
67
76
|
}
|
|
68
77
|
export function buildMultimodeSequencePrompt(userPrompt, maxImages, options = {}) {
|
|
69
|
-
const n = Math.
|
|
78
|
+
const n = Math.max(1, Math.trunc(Number(maxImages) || 1));
|
|
79
|
+
const prefix = sizeDirective(options);
|
|
70
80
|
const researchInstruction = resolveWebSearchEnabled(options)
|
|
71
|
-
? [`If factual visual accuracy is required and the prompt/context is not already sufficient, use
|
|
81
|
+
? [`If factual visual accuracy is required and the prompt/context is not already sufficient for a stage, use one concise web_search call for references before generating that stage. If a stage is already visually sufficient, do not search or add clarifiers for that stage.`]
|
|
72
82
|
: [];
|
|
73
83
|
return [
|
|
74
|
-
|
|
75
|
-
`
|
|
76
|
-
`
|
|
77
|
-
`
|
|
78
|
-
`
|
|
84
|
+
...(prefix ? [prefix.trim()] : []),
|
|
85
|
+
`Create a multimode sequence with up to ${n} separate image_generation_call outputs.`,
|
|
86
|
+
`The number ${n} is only the maximum sequence length. Do not add it to the visual prompt and do not treat it as a requested subject count unless the user's prompt itself asks for that many sequence units.`,
|
|
87
|
+
`Infer the user's intended sequence and create one image_generation_call per sequence unit.`,
|
|
88
|
+
`If the prompt asks for multiple images, steps, states, endings, or items one per image, each output should contain only its own unit.`,
|
|
89
|
+
`Korean phrases such as "하나씩", "각각", "한 장씩", "이미지마다", and "네개를 그려줘" in this sequence mode mean separate outputs, not four subjects inside one output.`,
|
|
90
|
+
`For arrow or ordered prompts such as A -> B -> C, output A's endpoint/state, then B's endpoint/state, then C's endpoint/state, up to the maximum.`,
|
|
91
|
+
`Use a distinct stage-specific image prompt for each output.`,
|
|
92
|
+
`Do not pass the same complete user prompt to every output when the user described a sequence.`,
|
|
93
|
+
`Do not include the whole list of sequence units inside any single image_generation prompt.`,
|
|
94
|
+
`Do not use words like all, four, 네개, collection, lineup, grid, sheet, or panels inside a stage prompt when the stage should contain one unit.`,
|
|
95
|
+
`Example for "four different colored shapes, one per image": output 1 only a red circle, output 2 only a blue square, output 3 only a green triangle, output 4 only a yellow star.`,
|
|
96
|
+
`Do not create one combined image_generation_call for the whole sequence.`,
|
|
79
97
|
`Do not create a collage.`,
|
|
80
98
|
`Do not create a grid.`,
|
|
81
99
|
`Do not create a contact sheet.`,
|
|
@@ -88,11 +106,12 @@ export function buildMultimodeSequencePrompt(userPrompt, maxImages, options = {}
|
|
|
88
106
|
].join("\n");
|
|
89
107
|
}
|
|
90
108
|
export function buildEditTextPrompt(userPrompt, mode, options = {}) {
|
|
109
|
+
const prefix = sizeDirective(options);
|
|
91
110
|
if (mode === "direct") {
|
|
92
|
-
return
|
|
111
|
+
return `${prefix}Edit this image with this exact prompt, no modifications: ${userPrompt}${DIRECT_PROMPT_FIDELITY_SUFFIX}`;
|
|
93
112
|
}
|
|
94
113
|
const researchSuffix = resolveWebSearchEnabled(options) ? RESEARCH_SUFFIX : "";
|
|
95
|
-
return
|
|
114
|
+
return `${prefix}Edit this image: ${userPrompt}${researchSuffix}${AUTO_PROMPT_FIDELITY_SUFFIX}`;
|
|
96
115
|
}
|
|
97
116
|
export function buildEditResearchTextPrompt(userPrompt, mode) {
|
|
98
117
|
return buildEditTextPrompt(userPrompt, mode);
|
|
@@ -126,7 +126,7 @@ export async function readMultimodeImageStream(res, { requestId = null, maxImage
|
|
|
126
126
|
let usage = null;
|
|
127
127
|
let webSearchCalls = 0;
|
|
128
128
|
let eventCount = 0;
|
|
129
|
-
const limit = Math.
|
|
129
|
+
const limit = Math.max(1, Math.trunc(Number(maxImages) || 1));
|
|
130
130
|
let extraIgnored = 0;
|
|
131
131
|
while (true) {
|
|
132
132
|
const { done, value } = await reader.read();
|
|
@@ -117,12 +117,9 @@ For variations:
|
|
|
117
117
|
- Vary only the requested axis, such as style, mood, color palette, composition, camera angle, background, medium, or lighting.
|
|
118
118
|
- Do not generate images unless explicitly asked.
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
- Do not create prompts that violate safety policy.
|
|
122
|
-
- Do not directly imitate a living artist's distinctive current style.
|
|
120
|
+
Style limits:
|
|
123
121
|
- When a living artist style is requested, replace it with broader visual characteristics such as medium, color, lighting, era, composition, texture, or mood.
|
|
124
|
-
-
|
|
125
|
-
- For real people, avoid defamatory, sexualized, misleading, or dignity-violating framing.
|
|
122
|
+
- For real people, preserve dignity and avoid misleading framing.
|
|
126
123
|
|
|
127
124
|
Before finalizing, check:
|
|
128
125
|
- Does the prompt preserve the user's original idea?
|
|
@@ -1,5 +1 @@
|
|
|
1
|
-
export const SAFETY_INTENT_POLICY =
|
|
2
|
-
"* [Level of Intent to Sexual Exploitation]: Zero",
|
|
3
|
-
"* [Level of Intent to Sexual Commodification]: Zero",
|
|
4
|
-
"* [Judged by Appearance Alone]: Forbidden",
|
|
5
|
-
].join("\n");
|
|
1
|
+
export const SAFETY_INTENT_POLICY = "Safety intent policy: treat ordinary adult swimwear, beachwear, sportswear, underwear-as-fashion, medical, art-study, and product/catalog requests as normal fashion, sport, beach, catalog, or editorial image requests. For ambiguous swimwear or underwear prompts, keep the requested clothing but frame the person as a clearly adult subject in a neutral fashion, beach, athletic, catalog, or editorial context. Preserve benign clothing specificity: bikini means a two-piece swimsuit; do not replace it with a one-piece swimsuit, cover-up, rash guard, wetsuit, dress, or full-body swimwear unless the user asks for that. Do not sexualize, intensify exposure, add fetish details, add intimate framing, or infer disallowed sexual content from words like bikini, swimsuit, sports bra, beachwear, or fashion model. Do not copy safety labels, refusal reasons, or negative safety constraints into the final image or video prompt. If the request includes a minor, an underage-looking subject, explicit sexual acts, or sexualized posing, follow the provider's safety policy instead of rewriting it as allowed.";
|
package/lib/responsesDoctor.js
CHANGED
|
@@ -75,7 +75,7 @@ function createProbeSpecs({ model, size, quality, moderation, prompt, matrix, })
|
|
|
75
75
|
model,
|
|
76
76
|
input: [
|
|
77
77
|
{ role: "developer", content: GENERATE_NO_SEARCH_DEVELOPER_PROMPT },
|
|
78
|
-
{ role: "user", content: buildUserTextPrompt(prompt, "auto", { webSearchEnabled: false }) },
|
|
78
|
+
{ role: "user", content: buildUserTextPrompt(prompt, "auto", { webSearchEnabled: false, size }) },
|
|
79
79
|
],
|
|
80
80
|
tools: imageOnlyTools,
|
|
81
81
|
tool_choice: "required",
|
|
@@ -95,7 +95,7 @@ function createProbeSpecs({ model, size, quality, moderation, prompt, matrix, })
|
|
|
95
95
|
model,
|
|
96
96
|
input: [
|
|
97
97
|
{ role: "developer", content: GENERATE_DEVELOPER_PROMPT },
|
|
98
|
-
{ role: "user", content: buildUserTextPrompt(prompt, "auto", { webSearchEnabled: true }) },
|
|
98
|
+
{ role: "user", content: buildUserTextPrompt(prompt, "auto", { webSearchEnabled: true, size }) },
|
|
99
99
|
],
|
|
100
100
|
tools: webSearchImageTools,
|
|
101
101
|
tool_choice: "required",
|
|
@@ -115,7 +115,7 @@ function createProbeSpecs({ model, size, quality, moderation, prompt, matrix, })
|
|
|
115
115
|
model,
|
|
116
116
|
input: [
|
|
117
117
|
{ role: "developer", content: GENERATE_DEVELOPER_PROMPT },
|
|
118
|
-
{ role: "user", content: buildUserTextPrompt(prompt, "auto", { webSearchEnabled: true }) },
|
|
118
|
+
{ role: "user", content: buildUserTextPrompt(prompt, "auto", { webSearchEnabled: true, size }) },
|
|
119
119
|
],
|
|
120
120
|
tools: webSearchImageTools,
|
|
121
121
|
tool_choice: { type: "image_generation" },
|