ima2-gen 3.12.3 → 3.13.0
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 +17 -3
- package/bin/commands/prompt-sub/build.js +5 -1
- package/config.js +14 -0
- package/docs/API.md +21 -0
- package/docs/CLI.md +7 -0
- package/docs/PROMPT_STUDIO.md +9 -0
- package/docs/migration/runtime-test-inventory.md +4 -1
- package/lib/configFileStore.js +44 -0
- package/lib/configKeys.js +4 -0
- package/lib/promptBuilder/client.js +106 -64
- package/lib/promptBuilder/constants.js +20 -2
- package/lib/promptBuilder/requestSchema.js +42 -7
- package/lib/promptBuilder/router.js +70 -0
- package/lib/promptBuilder/transport.js +5 -4
- package/package.json +2 -2
- package/routes/keys.js +6 -32
- package/routes/promptBuilder.js +68 -14
- package/skills/ima2/SKILL.md +40 -3
- package/skills/ima2-front/SKILL.md +1 -1
- package/skills/ima2-front/references/asset-requirements.md +16 -0
- package/ui/dist/.vite/manifest.json +44 -35
- package/ui/dist/assets/{AgentWorkspace-AJkVTaU5.js → AgentWorkspace-CBwn1WUC.js} +1 -1
- package/ui/dist/assets/AssetGenWorkspace-CleWSwPz.js +2 -0
- package/ui/dist/assets/AssetsWorkspace-DdjXb45L.js +1 -0
- package/ui/dist/assets/{CardNewsWorkspace-C_Eb2g7d.js → CardNewsWorkspace-BaNpBvJw.js} +1 -1
- package/ui/dist/assets/{GenerationRequestLogPanel-giH1e4hp.js → GenerationRequestLogPanel-Cd2ArsWr.js} +1 -1
- package/ui/dist/assets/HomeWorkspace-nKDhivwR.js +1 -0
- package/ui/dist/assets/KeyingPanel-DT8zsqpi.js +1 -0
- package/ui/dist/assets/{NodeCanvas-CF_NHudI.js → NodeCanvas-DcKs0w36.js} +1 -1
- package/ui/dist/assets/PromptBuilderPanel-e6BSRAdj.js +2 -0
- package/ui/dist/assets/{PromptImportDialog-B40X9UAa.js → PromptImportDialog-CLkjHz7L.js} +2 -2
- package/ui/dist/assets/{PromptImportDiscoverySection-BWoUDHj1.js → PromptImportDiscoverySection-DoQw4zcd.js} +1 -1
- package/ui/dist/assets/{PromptImportFolderSection-DBSEyJb-.js → PromptImportFolderSection-CcLiliAZ.js} +1 -1
- package/ui/dist/assets/{PromptLibraryPanel-DLywJBpt.js → PromptLibraryPanel-BjxzL7t5.js} +2 -2
- package/ui/dist/assets/SettingsWorkspace-Ctu_usKk.js +1 -0
- package/ui/dist/assets/SpriteRecipeWorkspace-DZjsabce.js +1 -0
- package/ui/dist/assets/index-0fpfY8vu.css +1 -0
- package/ui/dist/assets/index-DueH_AmZ.js +30 -0
- package/ui/dist/assets/index-ICf98ZcU.js +5 -0
- package/ui/dist/assets/{pptxgen.es-CSd_gCsx.js → pptxgen.es-C--aL3JM.js} +1 -1
- package/ui/dist/assets/promptBuilderStore-CkwnWMbR.js +1 -0
- package/ui/dist/assets/useAgentDialogFocus-DPB8JITN.js +1 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/AssetGenWorkspace-Cguej69V.js +0 -2
- package/ui/dist/assets/AssetsWorkspace-DIQ9wBuD.js +0 -1
- package/ui/dist/assets/HomeWorkspace-DHmXGtqC.js +0 -1
- package/ui/dist/assets/PromptBuilderPanel-D7NJto-1.js +0 -2
- package/ui/dist/assets/SettingsWorkspace-BawWId1t.js +0 -1
- package/ui/dist/assets/SpriteRecipeWorkspace-Biy2yRAk.js +0 -1
- package/ui/dist/assets/VectorizePanel-DdlVvwV1.js +0 -1
- package/ui/dist/assets/index-D05Ong5g.js +0 -5
- package/ui/dist/assets/index-DGRIdnsL.css +0 -1
- package/ui/dist/assets/index-Sch70vBs.js +0 -30
- package/ui/dist/assets/useAgentDialogFocus-DcWwFfYd.js +0 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { getGrokEndpoint } from "../grokImageCore.js";
|
|
2
|
+
import { waitForOAuthReady } from "../oauthProxy/runtime.js";
|
|
3
|
+
import { PROMPT_BUILDER_AUTO_ORDER, } from "./constants.js";
|
|
4
|
+
import { promptBuilderError } from "./errors.js";
|
|
5
|
+
function unavailableBackendError(backend) {
|
|
6
|
+
if (backend === "api") {
|
|
7
|
+
return promptBuilderError("OpenAI API key is required for Prompt Builder", "PROMPT_BUILDER_API_KEY_REQUIRED", 401);
|
|
8
|
+
}
|
|
9
|
+
if (backend === "grok-api") {
|
|
10
|
+
return promptBuilderError("xAI API key is required for Prompt Builder", "PROMPT_BUILDER_XAI_KEY_REQUIRED", 401);
|
|
11
|
+
}
|
|
12
|
+
return promptBuilderError(`${backend === "oauth" ? "OAuth" : "Grok"} backend is unavailable for Prompt Builder`, backend === "oauth"
|
|
13
|
+
? "PROMPT_BUILDER_OAUTH_UNAVAILABLE"
|
|
14
|
+
: "PROMPT_BUILDER_GROK_UNAVAILABLE", 503);
|
|
15
|
+
}
|
|
16
|
+
export function selectPromptBuilderBackend(requestedBackend, lanes, allowed = PROMPT_BUILDER_AUTO_ORDER) {
|
|
17
|
+
if (requestedBackend !== "auto") {
|
|
18
|
+
if (lanes[requestedBackend]?.status !== "ready") {
|
|
19
|
+
throw unavailableBackendError(requestedBackend);
|
|
20
|
+
}
|
|
21
|
+
return { requestedBackend, backend: requestedBackend };
|
|
22
|
+
}
|
|
23
|
+
const backend = allowed.find((candidate) => lanes[candidate]?.status === "ready");
|
|
24
|
+
if (!backend) {
|
|
25
|
+
throw promptBuilderError("No Prompt Builder backend is ready", "PROMPT_BUILDER_NO_BACKEND_READY", 503);
|
|
26
|
+
}
|
|
27
|
+
const first = allowed[0];
|
|
28
|
+
return first === undefined || backend === first
|
|
29
|
+
? { requestedBackend, backend }
|
|
30
|
+
: {
|
|
31
|
+
requestedBackend,
|
|
32
|
+
backend,
|
|
33
|
+
fallbackFrom: first,
|
|
34
|
+
fallbackReason: lanes[first]?.reason || lanes[first]?.status || "not-ready",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export async function resolvePromptBuilderTransport(ctx, backend, endpoint) {
|
|
38
|
+
try {
|
|
39
|
+
if (backend === "oauth") {
|
|
40
|
+
await waitForOAuthReady(ctx);
|
|
41
|
+
return {
|
|
42
|
+
url: `${ctx.oauthUrl}${endpoint === "responses" ? "/v1/responses" : "/v1/chat/completions"}`,
|
|
43
|
+
headers: { "Content-Type": "application/json" },
|
|
44
|
+
useOAuthFetch: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (backend === "api") {
|
|
48
|
+
if (!ctx.apiKey)
|
|
49
|
+
throw unavailableBackendError("api");
|
|
50
|
+
return {
|
|
51
|
+
url: "https://api.openai.com/v1/responses",
|
|
52
|
+
headers: {
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
Accept: "text/event-stream",
|
|
55
|
+
Authorization: `Bearer ${ctx.apiKey}`,
|
|
56
|
+
},
|
|
57
|
+
useOAuthFetch: false,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const directApiKey = backend === "grok-api" ? ctx.xaiApiKey : undefined;
|
|
61
|
+
if (backend === "grok-api" && !directApiKey) {
|
|
62
|
+
throw unavailableBackendError("grok-api");
|
|
63
|
+
}
|
|
64
|
+
const target = getGrokEndpoint(ctx, "/v1/chat/completions", directApiKey);
|
|
65
|
+
return { ...target, useOAuthFetch: false };
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { attachmentText, hasImageAttachments } from "./attachments.js";
|
|
2
|
-
import { PROMPT_BUILDER_RESPONSE_MAX_OUTPUT_TOKENS } from "./constants.js";
|
|
2
|
+
import { PROMPT_BUILDER_RESPONSE_MAX_OUTPUT_TOKENS, } from "./constants.js";
|
|
3
3
|
import { PROMPT_BUILDER_SYSTEM_PROMPT } from "./systemPrompt.js";
|
|
4
4
|
import { contextText } from "./context.js";
|
|
5
5
|
function toChatContent(message) {
|
|
@@ -30,7 +30,7 @@ function toResponsesContent(message) {
|
|
|
30
30
|
return text;
|
|
31
31
|
return [{ type: "input_text", text }, ...imageParts];
|
|
32
32
|
}
|
|
33
|
-
export function buildTransportPayload(model, messages, context) {
|
|
33
|
+
export function buildTransportPayload(backend, model, messages, context) {
|
|
34
34
|
const currentContextText = contextText(context);
|
|
35
35
|
const systemText = [
|
|
36
36
|
PROMPT_BUILDER_SYSTEM_PROMPT,
|
|
@@ -38,7 +38,8 @@ export function buildTransportPayload(model, messages, context) {
|
|
|
38
38
|
]
|
|
39
39
|
.filter(Boolean)
|
|
40
40
|
.join("\n\n");
|
|
41
|
-
const useResponses =
|
|
41
|
+
const useResponses = backend === "api"
|
|
42
|
+
|| (backend === "oauth" && hasImageAttachments(messages));
|
|
42
43
|
const endpoint = useResponses ? "responses" : "chat";
|
|
43
44
|
const body = useResponses
|
|
44
45
|
? {
|
|
@@ -61,7 +62,7 @@ export function buildTransportPayload(model, messages, context) {
|
|
|
61
62
|
})),
|
|
62
63
|
],
|
|
63
64
|
stream: false,
|
|
64
|
-
reasoning_effort: "low",
|
|
65
|
+
...(backend === "oauth" ? { reasoning_effort: "low" } : {}),
|
|
65
66
|
};
|
|
66
67
|
return { endpoint, body };
|
|
67
68
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ima2-gen",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.13.0",
|
|
4
4
|
"packageManager": "npm@11.18.0",
|
|
5
5
|
"description": "Local-first visual generation runtime and studio for people and coding agents, with reproducible image and video workflows across multiple providers.",
|
|
6
6
|
"type": "module",
|
|
@@ -121,5 +121,5 @@
|
|
|
121
121
|
"typescript": "^5.9.3",
|
|
122
122
|
"yaml": "2.9.0"
|
|
123
123
|
},
|
|
124
|
-
"gitHead": "
|
|
124
|
+
"gitHead": "d39f9ea2205d47b4aae1df2d98648571f435ef7c"
|
|
125
125
|
}
|
package/routes/keys.js
CHANGED
|
@@ -1,31 +1,5 @@
|
|
|
1
|
-
import { readFile, writeFile, rename } from "node:fs/promises";
|
|
2
|
-
import { randomBytes } from "node:crypto";
|
|
3
1
|
import { initVertexAuth, clearVertexAuth } from "../lib/vertexAuth.js";
|
|
4
|
-
|
|
5
|
-
// save can't corrupt config.json (which may hold API keys). Rename also forces
|
|
6
|
-
// 0600 perms even if a looser-perm config pre-existed.
|
|
7
|
-
async function writeConfigAtomic(cfgPath, data) {
|
|
8
|
-
const tmp = `${cfgPath}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`;
|
|
9
|
-
await writeFile(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
10
|
-
await rename(tmp, cfgPath);
|
|
11
|
-
}
|
|
12
|
-
let configMutationQueue = Promise.resolve();
|
|
13
|
-
function serializeConfigMutation(mutation) {
|
|
14
|
-
const result = configMutationQueue.then(mutation, mutation);
|
|
15
|
-
configMutationQueue = result.then(() => undefined, () => undefined);
|
|
16
|
-
return result;
|
|
17
|
-
}
|
|
18
|
-
async function updateConfigFile(cfgPath, mutate) {
|
|
19
|
-
await serializeConfigMutation(async () => {
|
|
20
|
-
let existing = {};
|
|
21
|
-
try {
|
|
22
|
-
existing = JSON.parse(await readFile(cfgPath, "utf-8"));
|
|
23
|
-
}
|
|
24
|
-
catch { /* new file */ }
|
|
25
|
-
mutate(existing);
|
|
26
|
-
await writeConfigAtomic(cfgPath, existing);
|
|
27
|
-
});
|
|
28
|
-
}
|
|
2
|
+
import { updateConfigFileAtomic } from "../lib/configFileStore.js";
|
|
29
3
|
const KEY_PREFIX_MAP = {
|
|
30
4
|
openai: ["sk-"],
|
|
31
5
|
xai: ["xai-"],
|
|
@@ -130,7 +104,7 @@ export function mountKeyRoutes(app, ctx) {
|
|
|
130
104
|
return res.status(400).json({ ok: false, error: "mode must be apikey|vertex", code: "INVALID_MODE" });
|
|
131
105
|
}
|
|
132
106
|
const cfgPath = ctx.config.storage.configFile;
|
|
133
|
-
await
|
|
107
|
+
await updateConfigFileAtomic(cfgPath, (existing) => { existing.geminiAuthMode = mode; });
|
|
134
108
|
ctx.geminiAuthMode = mode;
|
|
135
109
|
return res.json({ ok: true, geminiAuthMode: mode });
|
|
136
110
|
});
|
|
@@ -167,7 +141,7 @@ export function mountKeyRoutes(app, ctx) {
|
|
|
167
141
|
}
|
|
168
142
|
// Save to config.json
|
|
169
143
|
const cfgPath = ctx.config.storage.configFile;
|
|
170
|
-
await
|
|
144
|
+
await updateConfigFileAtomic(cfgPath, (existing) => {
|
|
171
145
|
existing.vertexServiceAccountJson = trimmed;
|
|
172
146
|
existing.geminiAuthMode = "vertex";
|
|
173
147
|
});
|
|
@@ -186,7 +160,7 @@ export function mountKeyRoutes(app, ctx) {
|
|
|
186
160
|
return res.status(400).json({ ok: false, error: "Cannot remove env-sourced key", code: "ENV_KEY_IMMUTABLE" });
|
|
187
161
|
}
|
|
188
162
|
const cfgPath = ctx.config.storage.configFile;
|
|
189
|
-
await
|
|
163
|
+
await updateConfigFileAtomic(cfgPath, (existing) => { delete existing.vertexServiceAccountJson; });
|
|
190
164
|
clearVertexAuth();
|
|
191
165
|
ctx.vertexServiceAccountJson = undefined;
|
|
192
166
|
ctx.vertexProjectId = undefined;
|
|
@@ -266,7 +240,7 @@ export function mountKeyRoutes(app, ctx) {
|
|
|
266
240
|
}
|
|
267
241
|
// Save to config.json
|
|
268
242
|
const cfgPath = ctx.config.storage.configFile;
|
|
269
|
-
await
|
|
243
|
+
await updateConfigFileAtomic(cfgPath, (existing) => {
|
|
270
244
|
existing[CONFIG_KEY_MAP[provider]] = trimmed;
|
|
271
245
|
if (provider === "gemini")
|
|
272
246
|
existing.geminiAuthMode = "apikey";
|
|
@@ -321,7 +295,7 @@ export function mountKeyRoutes(app, ctx) {
|
|
|
321
295
|
}
|
|
322
296
|
// Remove from config.json
|
|
323
297
|
const cfgPath = ctx.config.storage.configFile;
|
|
324
|
-
await
|
|
298
|
+
await updateConfigFileAtomic(cfgPath, (existing) => { delete existing[CONFIG_KEY_MAP[provider]]; });
|
|
325
299
|
// Clear runtime
|
|
326
300
|
if (provider === "openai") {
|
|
327
301
|
ctx.apiKey = undefined;
|
package/routes/promptBuilder.js
CHANGED
|
@@ -1,27 +1,81 @@
|
|
|
1
|
+
import { updateConfigFileAtomic } from "../lib/configFileStore.js";
|
|
1
2
|
import { errInfo } from "../lib/errInfo.js";
|
|
2
3
|
import { logError } from "../lib/logger.js";
|
|
3
4
|
import { requestPromptBuilderChat } from "../lib/promptBuilder/client.js";
|
|
4
|
-
import {
|
|
5
|
+
import { PROMPT_BUILDER_AUTO_ORDER, PROMPT_BUILDER_BACKENDS, PROMPT_BUILDER_MODELS, } from "../lib/promptBuilder/constants.js";
|
|
6
|
+
import { promptBuilderError } from "../lib/promptBuilder/errors.js";
|
|
7
|
+
import { normalizePromptBuilderConfig } from "../lib/promptBuilder/requestSchema.js";
|
|
8
|
+
import { requireRuntimeContext, } from "../lib/runtimeContext.js";
|
|
9
|
+
import { buildLaneSummary } from "./models.js";
|
|
10
|
+
function configLocks() {
|
|
11
|
+
return {
|
|
12
|
+
backend: process.env.IMA2_PROMPT_BUILDER_BACKEND !== undefined,
|
|
13
|
+
model: process.env.IMA2_PROMPT_BUILDER_MODEL !== undefined,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function configPayload(ctx) {
|
|
17
|
+
return {
|
|
18
|
+
...ctx.config.promptBuilder,
|
|
19
|
+
options: {
|
|
20
|
+
backends: [...PROMPT_BUILDER_BACKENDS],
|
|
21
|
+
models: PROMPT_BUILDER_MODELS,
|
|
22
|
+
autoOrder: [...PROMPT_BUILDER_AUTO_ORDER],
|
|
23
|
+
},
|
|
24
|
+
locked: configLocks(),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function sendPromptBuilderError(res, error) {
|
|
28
|
+
const info = errInfo(error);
|
|
29
|
+
const unreadable = info.code === "CONFIG_UNREADABLE";
|
|
30
|
+
const code = unreadable
|
|
31
|
+
? "PROMPT_BUILDER_CONFIG_UNREADABLE"
|
|
32
|
+
: info.code ?? "PROMPT_BUILDER_UNKNOWN";
|
|
33
|
+
const status = unreadable
|
|
34
|
+
? 500
|
|
35
|
+
: typeof info.status === "number" && info.status >= 400 ? info.status : 500;
|
|
36
|
+
logError("prompt-builder", code, error, { status });
|
|
37
|
+
res.status(status).json({ error: { code, message: info.message } });
|
|
38
|
+
}
|
|
39
|
+
async function savePromptBuilderConfig(ctx, raw) {
|
|
40
|
+
const current = ctx.config.promptBuilder;
|
|
41
|
+
const next = normalizePromptBuilderConfig(raw, current);
|
|
42
|
+
const locked = configLocks();
|
|
43
|
+
if ((locked.backend && next.backend !== current.backend)
|
|
44
|
+
|| (locked.model && next.model !== current.model)) {
|
|
45
|
+
throw promptBuilderError("Prompt Builder config is managed by environment variables", "PROMPT_BUILDER_CONFIG_ENV_LOCKED", 409);
|
|
46
|
+
}
|
|
47
|
+
await updateConfigFileAtomic(ctx.config.storage.configFile, (existing) => {
|
|
48
|
+
const saved = existing.promptBuilder;
|
|
49
|
+
const base = saved && typeof saved === "object" && !Array.isArray(saved)
|
|
50
|
+
? saved
|
|
51
|
+
: {};
|
|
52
|
+
existing.promptBuilder = { ...base, ...next };
|
|
53
|
+
});
|
|
54
|
+
ctx.config.promptBuilder.backend = next.backend;
|
|
55
|
+
ctx.config.promptBuilder.model = next.model;
|
|
56
|
+
}
|
|
5
57
|
export function registerPromptBuilderRoutes(app, ctxRaw) {
|
|
6
58
|
const ctx = requireRuntimeContext(ctxRaw);
|
|
59
|
+
app.get("/api/prompt-builder/config", (_req, res) => {
|
|
60
|
+
res.json(configPayload(ctx));
|
|
61
|
+
});
|
|
62
|
+
app.put("/api/prompt-builder/config", async (req, res) => {
|
|
63
|
+
try {
|
|
64
|
+
await savePromptBuilderConfig(ctx, req.body);
|
|
65
|
+
res.json(configPayload(ctx));
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
sendPromptBuilderError(res, error);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
7
71
|
app.post("/api/prompt-builder/chat", async (req, res) => {
|
|
8
72
|
try {
|
|
9
|
-
const
|
|
73
|
+
const lanes = await buildLaneSummary(ctx);
|
|
74
|
+
const result = await requestPromptBuilderChat(ctx, req.body, lanes);
|
|
10
75
|
res.json(result);
|
|
11
76
|
}
|
|
12
77
|
catch (error) {
|
|
13
|
-
|
|
14
|
-
const code = typeof info.code === "string" ? info.code : "PROMPT_BUILDER_UNKNOWN";
|
|
15
|
-
const status = typeof info.status === "number" && info.status >= 400
|
|
16
|
-
? info.status
|
|
17
|
-
: 500;
|
|
18
|
-
logError("prompt-builder", code, {
|
|
19
|
-
message: info.message,
|
|
20
|
-
status,
|
|
21
|
-
});
|
|
22
|
-
res.status(status).json({
|
|
23
|
-
error: { code, message: info.message },
|
|
24
|
-
});
|
|
78
|
+
sendPromptBuilderError(res, error);
|
|
25
79
|
}
|
|
26
80
|
});
|
|
27
81
|
}
|
package/skills/ima2/SKILL.md
CHANGED
|
@@ -131,6 +131,12 @@ ima2 gen "1girl, blue hair, city at night" \
|
|
|
131
131
|
--nai-auto-smea --nai-decrisper --nai-variety-plus
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
+
In the web app, selecting NovelAI changes the composer into two peer panes:
|
|
135
|
+
**Positive prompt** and **Undesired content**. They sit side by side when the
|
|
136
|
+
composer is wide enough and stack below a 719px container width. Other providers
|
|
137
|
+
keep the normal single prompt because they do not share NovelAI's dedicated
|
|
138
|
+
negative-prompt contract.
|
|
139
|
+
|
|
134
140
|
For V5 native alpha, pair the request flag with an alpha-aware prompt:
|
|
135
141
|
|
|
136
142
|
```bash
|
|
@@ -172,6 +178,34 @@ Native control details come from the official
|
|
|
172
178
|
[quality tags](https://docs.novelai.net/en/image/qualitytags/), and
|
|
173
179
|
[seed](https://docs.novelai.net/en/image/seed/) pages, checked the same date.
|
|
174
180
|
|
|
181
|
+
## Prompt Builder
|
|
182
|
+
|
|
183
|
+
The right-sidebar Prompt Builder refines prompts without generating an image. Open
|
|
184
|
+
Settings > Providers > Prompt Builder backend to choose where its text request runs,
|
|
185
|
+
and use Builder model to select a model from that backend's catalog. `Auto` is the
|
|
186
|
+
default: it tries GPT OAuth, Grok, OpenAI API, then Grok API and selects the first
|
|
187
|
+
ready backend. Each successful reply carries a `via <backend>` badge showing the
|
|
188
|
+
backend that actually answered.
|
|
189
|
+
|
|
190
|
+
Choose an explicit backend when cost, credentials, or model behavior must be stable.
|
|
191
|
+
An unavailable explicit backend returns a typed error and never falls back. Only Auto
|
|
192
|
+
performs readiness fallback, and there is no retry on a different backend after an
|
|
193
|
+
upstream request has been sent.
|
|
194
|
+
|
|
195
|
+
The CLI wrapper accepts the same per-request selection:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
ima2 prompt build \
|
|
199
|
+
--message "Turn this rough idea into a production image prompt" \
|
|
200
|
+
--backend <auto|oauth|api|grok|grok-api> \
|
|
201
|
+
--model <model>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Run `ima2 config get promptBuilder.backend` and
|
|
205
|
+
`ima2 config get promptBuilder.model` to inspect the persisted server preference.
|
|
206
|
+
The Builder's model choices depend on the selected backend; do not assume the GPT
|
|
207
|
+
model list applies to Grok.
|
|
208
|
+
|
|
175
209
|
## Prompting Guidance
|
|
176
210
|
|
|
177
211
|
GPT Image 2 can follow detailed visual instructions and can render visible text
|
|
@@ -420,9 +454,12 @@ generate a clean cutout FIRST; tracing quality follows input flatness.
|
|
|
420
454
|
`mono` has no alpha channel, so a transparent cutout becomes a silhouette on a
|
|
421
455
|
black field. Use it for stencils and line art, not for cutouts.
|
|
422
456
|
|
|
423
|
-
In the app, the same operation is
|
|
424
|
-
|
|
425
|
-
|
|
457
|
+
In the app, the same operation is **Convert to SVG** on an AssetGen tile or Assets
|
|
458
|
+
library preview. Canvas Mode also exposes **Trace to SVG (vector)** in Export: it
|
|
459
|
+
flattens the current canvas composition to PNG, saves that hidden canvas version,
|
|
460
|
+
then opens the same preset and fine-tuning panel. Do not confuse it with
|
|
461
|
+
**SVG (embedded raster)**, which keeps the base image as bitmap data and vectorizes
|
|
462
|
+
only Canvas annotations.
|
|
426
463
|
|
|
427
464
|
### Korean Text in Images
|
|
428
465
|
|
|
@@ -226,7 +226,7 @@ Read `references/aesthetics.md` for full guidelines. Summary:
|
|
|
226
226
|
- **Motion**: See `references/motion.md`. One signature moment + a few
|
|
227
227
|
supporting reveals > 10 scattered effects; landing-bucket floor/ceiling per
|
|
228
228
|
FE-MOTION-BUCKET-01.
|
|
229
|
-
- **Assets**: Use screenshots, product images, diagrams, charts, illustrations, generated bitmaps, or soft 3D only when they add product meaning. When a real bitmap is needed (icon, hero, illustration), generate it with `ima2` — probe `ima2 status`, attempt `ima2 serve` if down, inspect `ima2 models --kind image`, and set `ima2 defaults set image <lane>/<model>` before using bare generation — falling back to the native `imagegen` tool only when ima2 is truly unavailable; never ship a placeholder. `ima2` is preferred because it supports reference images, multi-candidate generation (`-n N`, multimode, independent CLI parallel — see `asset-requirements.md` FE-ASSET-PARALLEL-01), prompt builder, session style sheets, provider routing (GPT/Grok/Gemini — see `asset-requirements.md` FE-ASSET-PROVIDER-01), variant selection with element-ledger synthesis (`asset-requirements.md` FE-ASSET-SELECT-01), cutout asset background strategy (`asset-requirements.md` FE-ASSET-BG-01), and video (`ima2 video` — see `motion.md` FE-MOTION-VIDEO-01) for motion assets. For parallel generation, monitor with `ima2 ps --json` and cancel unwanted jobs with `ima2 cancel <id>`. Write **very explicit long prompts** (subject, composition, palette, lighting, style, aspect) per `asset-requirements.md`; prefer real/generated image or video assets over CSS gradient washes. Read any design reference or captured screenshot back into context with `view_image` before matching it. Third-party captures follow `reference-capture.md` (analysis-only, provenance manifest).
|
|
229
|
+
- **Assets**: Use screenshots, product images, diagrams, charts, illustrations, generated bitmaps, or soft 3D only when they add product meaning. When a real bitmap is needed (icon, hero, illustration), generate it with `ima2` — probe `ima2 status`, attempt `ima2 serve` if down, inspect `ima2 models --kind image`, and set `ima2 defaults set image <lane>/<model>` before using bare generation — falling back to the native `imagegen` tool only when ima2 is truly unavailable; never ship a placeholder. `ima2` is preferred because it supports reference images, multi-candidate generation (`-n N`, multimode, independent CLI parallel — see `asset-requirements.md` FE-ASSET-PARALLEL-01), a backend-selectable prompt builder, raster-to-vector tracing (`ima2 vectorize` and Canvas trace — see `asset-requirements.md` Raster vs real vector), session style sheets, provider routing (GPT/Grok/Gemini — see `asset-requirements.md` FE-ASSET-PROVIDER-01), variant selection with element-ledger synthesis (`asset-requirements.md` FE-ASSET-SELECT-01), cutout asset background strategy (`asset-requirements.md` FE-ASSET-BG-01), and video (`ima2 video` — see `motion.md` FE-MOTION-VIDEO-01) for motion assets. For parallel generation, monitor with `ima2 ps --json` and cancel unwanted jobs with `ima2 cancel <id>`. Write **very explicit long prompts** (subject, composition, palette, lighting, style, aspect) per `asset-requirements.md`; prefer real/generated image or video assets over CSS gradient washes. Read any design reference or captured screenshot back into context with `view_image` before matching it. Third-party captures follow `reference-capture.md` (analysis-only, provenance manifest).
|
|
230
230
|
- **Visual verification**: after UI changes, exercise the flow per visual verification (screenshot -> view_image) — `browser:control-in-app-browser` on the dev server, screenshot, `view_image` — instead of claiming visual correctness from code alone.
|
|
231
231
|
|
|
232
232
|
### Cutout Asset Generation (FE-ASSET-BG-01 surface — STRICT)
|
|
@@ -45,6 +45,22 @@ usable asset.
|
|
|
45
45
|
| 4 | CSS device frame + screenshot | App mockup heroes (Toss/카카오 style). Pure CSS phone/laptop frame wrapping a real screenshot or UI |
|
|
46
46
|
| 5 | Placeholder service (last resort) | `picsum.photos/800/600`, `placehold.co`. Mark as TODO for replacement |
|
|
47
47
|
|
|
48
|
+
### Raster vs real vector
|
|
49
|
+
|
|
50
|
+
An image prompted as "flat vector style" is still a raster bitmap. When the shipped
|
|
51
|
+
asset must contain scalable paths (logo mark, icon, simple sprite, stencil), generate a
|
|
52
|
+
clean flat or transparent raster first, then run:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
ima2 vectorize input.png -o output.svg --preset auto --json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The same tracer is available in AssetGen, Assets, and Canvas Mode. In Canvas Export,
|
|
59
|
+
choose **Trace to SVG (vector)**; **SVG (embedded raster)** is self-contained but is
|
|
60
|
+
not a pixel trace. Inspect path count and render the SVG back to PNG before shipping.
|
|
61
|
+
Tracing works best for flat color, strong edges, cutouts, and logos; photographs,
|
|
62
|
+
gradients, and small text are not acceptable vectorization targets.
|
|
63
|
+
|
|
48
64
|
### Korean Service Patterns
|
|
49
65
|
|
|
50
66
|
Korean product pages almost always use concrete visual evidence in the first viewport:
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"file": "assets/AssetGenWorkspace-B7tTK0Sx.css",
|
|
4
4
|
"src": "_AssetGenWorkspace-B7tTK0Sx.css"
|
|
5
5
|
},
|
|
6
|
-
"_AssetGenWorkspace-
|
|
7
|
-
"file": "assets/AssetGenWorkspace-
|
|
6
|
+
"_AssetGenWorkspace-CleWSwPz.js": {
|
|
7
|
+
"file": "assets/AssetGenWorkspace-CleWSwPz.js",
|
|
8
8
|
"name": "AssetGenWorkspace",
|
|
9
9
|
"isDynamicEntry": true,
|
|
10
10
|
"imports": [
|
|
11
11
|
"index.html",
|
|
12
|
-
"
|
|
12
|
+
"_KeyingPanel-DT8zsqpi.js"
|
|
13
13
|
],
|
|
14
14
|
"dynamicImports": [
|
|
15
15
|
"src/components/assetgen/SpriteRecipeWorkspace.tsx"
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
"assets/AssetGenWorkspace-B7tTK0Sx.css"
|
|
19
19
|
]
|
|
20
20
|
},
|
|
21
|
-
"
|
|
22
|
-
"file": "assets/
|
|
23
|
-
"name": "
|
|
21
|
+
"_KeyingPanel-DT8zsqpi.js": {
|
|
22
|
+
"file": "assets/KeyingPanel-DT8zsqpi.js",
|
|
23
|
+
"name": "KeyingPanel",
|
|
24
24
|
"imports": [
|
|
25
25
|
"index.html",
|
|
26
|
-
"_useAgentDialogFocus-
|
|
26
|
+
"_useAgentDialogFocus-DPB8JITN.js"
|
|
27
27
|
]
|
|
28
28
|
},
|
|
29
29
|
"__vite-browser-external": {
|
|
@@ -32,15 +32,22 @@
|
|
|
32
32
|
"src": "__vite-browser-external",
|
|
33
33
|
"isDynamicEntry": true
|
|
34
34
|
},
|
|
35
|
-
"
|
|
36
|
-
"file": "assets/
|
|
35
|
+
"_promptBuilderStore-CkwnWMbR.js": {
|
|
36
|
+
"file": "assets/promptBuilderStore-CkwnWMbR.js",
|
|
37
|
+
"name": "promptBuilderStore",
|
|
38
|
+
"imports": [
|
|
39
|
+
"index.html"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"_useAgentDialogFocus-DPB8JITN.js": {
|
|
43
|
+
"file": "assets/useAgentDialogFocus-DPB8JITN.js",
|
|
37
44
|
"name": "useAgentDialogFocus",
|
|
38
45
|
"imports": [
|
|
39
46
|
"index.html"
|
|
40
47
|
]
|
|
41
48
|
},
|
|
42
49
|
"index.html": {
|
|
43
|
-
"file": "assets/index-
|
|
50
|
+
"file": "assets/index-DueH_AmZ.js",
|
|
44
51
|
"name": "index",
|
|
45
52
|
"src": "index.html",
|
|
46
53
|
"isEntry": true,
|
|
@@ -55,16 +62,16 @@
|
|
|
55
62
|
"src/components/card-news/CardNewsWorkspace.tsx",
|
|
56
63
|
"src/components/agent/AgentWorkspace.tsx",
|
|
57
64
|
"src/components/assets/AssetsWorkspace.tsx",
|
|
58
|
-
"_AssetGenWorkspace-
|
|
65
|
+
"_AssetGenWorkspace-CleWSwPz.js",
|
|
59
66
|
"src/components/home/HomeWorkspace.tsx",
|
|
60
67
|
"src/components/PromptLibraryPanel.tsx"
|
|
61
68
|
],
|
|
62
69
|
"css": [
|
|
63
|
-
"assets/index-
|
|
70
|
+
"assets/index-0fpfY8vu.css"
|
|
64
71
|
]
|
|
65
72
|
},
|
|
66
73
|
"node_modules/pptxgenjs/dist/pptxgen.es.js": {
|
|
67
|
-
"file": "assets/pptxgen.es-
|
|
74
|
+
"file": "assets/pptxgen.es-C--aL3JM.js",
|
|
68
75
|
"name": "pptxgen.es",
|
|
69
76
|
"src": "node_modules/pptxgenjs/dist/pptxgen.es.js",
|
|
70
77
|
"isDynamicEntry": true,
|
|
@@ -78,7 +85,7 @@
|
|
|
78
85
|
]
|
|
79
86
|
},
|
|
80
87
|
"src/components/GenerationRequestLogPanel.tsx": {
|
|
81
|
-
"file": "assets/GenerationRequestLogPanel-
|
|
88
|
+
"file": "assets/GenerationRequestLogPanel-Cd2ArsWr.js",
|
|
82
89
|
"name": "GenerationRequestLogPanel",
|
|
83
90
|
"src": "src/components/GenerationRequestLogPanel.tsx",
|
|
84
91
|
"isDynamicEntry": true,
|
|
@@ -87,7 +94,7 @@
|
|
|
87
94
|
]
|
|
88
95
|
},
|
|
89
96
|
"src/components/NodeCanvas.tsx": {
|
|
90
|
-
"file": "assets/NodeCanvas-
|
|
97
|
+
"file": "assets/NodeCanvas-DcKs0w36.js",
|
|
91
98
|
"name": "NodeCanvas",
|
|
92
99
|
"src": "src/components/NodeCanvas.tsx",
|
|
93
100
|
"isDynamicEntry": true,
|
|
@@ -99,7 +106,7 @@
|
|
|
99
106
|
]
|
|
100
107
|
},
|
|
101
108
|
"src/components/PromptImportDialog.tsx": {
|
|
102
|
-
"file": "assets/PromptImportDialog-
|
|
109
|
+
"file": "assets/PromptImportDialog-CLkjHz7L.js",
|
|
103
110
|
"name": "PromptImportDialog",
|
|
104
111
|
"src": "src/components/PromptImportDialog.tsx",
|
|
105
112
|
"isDynamicEntry": true,
|
|
@@ -112,7 +119,7 @@
|
|
|
112
119
|
]
|
|
113
120
|
},
|
|
114
121
|
"src/components/PromptImportDiscoverySection.tsx": {
|
|
115
|
-
"file": "assets/PromptImportDiscoverySection-
|
|
122
|
+
"file": "assets/PromptImportDiscoverySection-DoQw4zcd.js",
|
|
116
123
|
"name": "PromptImportDiscoverySection",
|
|
117
124
|
"src": "src/components/PromptImportDiscoverySection.tsx",
|
|
118
125
|
"isDynamicEntry": true,
|
|
@@ -121,7 +128,7 @@
|
|
|
121
128
|
]
|
|
122
129
|
},
|
|
123
130
|
"src/components/PromptImportFolderSection.tsx": {
|
|
124
|
-
"file": "assets/PromptImportFolderSection-
|
|
131
|
+
"file": "assets/PromptImportFolderSection-CcLiliAZ.js",
|
|
125
132
|
"name": "PromptImportFolderSection",
|
|
126
133
|
"src": "src/components/PromptImportFolderSection.tsx",
|
|
127
134
|
"isDynamicEntry": true,
|
|
@@ -130,7 +137,7 @@
|
|
|
130
137
|
]
|
|
131
138
|
},
|
|
132
139
|
"src/components/PromptLibraryPanel.tsx": {
|
|
133
|
-
"file": "assets/PromptLibraryPanel-
|
|
140
|
+
"file": "assets/PromptLibraryPanel-BjxzL7t5.js",
|
|
134
141
|
"name": "PromptLibraryPanel",
|
|
135
142
|
"src": "src/components/PromptLibraryPanel.tsx",
|
|
136
143
|
"isDynamicEntry": true,
|
|
@@ -142,52 +149,53 @@
|
|
|
142
149
|
]
|
|
143
150
|
},
|
|
144
151
|
"src/components/SettingsWorkspace.tsx": {
|
|
145
|
-
"file": "assets/SettingsWorkspace-
|
|
152
|
+
"file": "assets/SettingsWorkspace-Ctu_usKk.js",
|
|
146
153
|
"name": "SettingsWorkspace",
|
|
147
154
|
"src": "src/components/SettingsWorkspace.tsx",
|
|
148
155
|
"isDynamicEntry": true,
|
|
149
156
|
"imports": [
|
|
150
|
-
"index.html"
|
|
157
|
+
"index.html",
|
|
158
|
+
"_promptBuilderStore-CkwnWMbR.js"
|
|
151
159
|
]
|
|
152
160
|
},
|
|
153
161
|
"src/components/agent/AgentWorkspace.tsx": {
|
|
154
|
-
"file": "assets/AgentWorkspace-
|
|
162
|
+
"file": "assets/AgentWorkspace-CBwn1WUC.js",
|
|
155
163
|
"name": "AgentWorkspace",
|
|
156
164
|
"src": "src/components/agent/AgentWorkspace.tsx",
|
|
157
165
|
"isDynamicEntry": true,
|
|
158
166
|
"imports": [
|
|
159
167
|
"index.html",
|
|
160
|
-
"_useAgentDialogFocus-
|
|
168
|
+
"_useAgentDialogFocus-DPB8JITN.js"
|
|
161
169
|
]
|
|
162
170
|
},
|
|
163
171
|
"src/components/assetgen/SpriteRecipeWorkspace.tsx": {
|
|
164
|
-
"file": "assets/SpriteRecipeWorkspace-
|
|
172
|
+
"file": "assets/SpriteRecipeWorkspace-DZjsabce.js",
|
|
165
173
|
"name": "SpriteRecipeWorkspace",
|
|
166
174
|
"src": "src/components/assetgen/SpriteRecipeWorkspace.tsx",
|
|
167
175
|
"isDynamicEntry": true,
|
|
168
176
|
"imports": [
|
|
169
177
|
"index.html",
|
|
170
|
-
"_AssetGenWorkspace-
|
|
171
|
-
"
|
|
172
|
-
"_useAgentDialogFocus-
|
|
178
|
+
"_AssetGenWorkspace-CleWSwPz.js",
|
|
179
|
+
"_KeyingPanel-DT8zsqpi.js",
|
|
180
|
+
"_useAgentDialogFocus-DPB8JITN.js"
|
|
173
181
|
]
|
|
174
182
|
},
|
|
175
183
|
"src/components/assets/AssetsWorkspace.tsx": {
|
|
176
|
-
"file": "assets/AssetsWorkspace-
|
|
184
|
+
"file": "assets/AssetsWorkspace-DdjXb45L.js",
|
|
177
185
|
"name": "AssetsWorkspace",
|
|
178
186
|
"src": "src/components/assets/AssetsWorkspace.tsx",
|
|
179
187
|
"isDynamicEntry": true,
|
|
180
188
|
"imports": [
|
|
181
189
|
"index.html",
|
|
182
|
-
"
|
|
183
|
-
"_useAgentDialogFocus-
|
|
190
|
+
"_KeyingPanel-DT8zsqpi.js",
|
|
191
|
+
"_useAgentDialogFocus-DPB8JITN.js"
|
|
184
192
|
],
|
|
185
193
|
"css": [
|
|
186
194
|
"assets/AssetsWorkspace-BdptASFf.css"
|
|
187
195
|
]
|
|
188
196
|
},
|
|
189
197
|
"src/components/canvas-mode/index.ts": {
|
|
190
|
-
"file": "assets/index-
|
|
198
|
+
"file": "assets/index-ICf98ZcU.js",
|
|
191
199
|
"name": "index",
|
|
192
200
|
"src": "src/components/canvas-mode/index.ts",
|
|
193
201
|
"isDynamicEntry": true,
|
|
@@ -199,7 +207,7 @@
|
|
|
199
207
|
]
|
|
200
208
|
},
|
|
201
209
|
"src/components/card-news/CardNewsWorkspace.tsx": {
|
|
202
|
-
"file": "assets/CardNewsWorkspace-
|
|
210
|
+
"file": "assets/CardNewsWorkspace-BaNpBvJw.js",
|
|
203
211
|
"name": "CardNewsWorkspace",
|
|
204
212
|
"src": "src/components/card-news/CardNewsWorkspace.tsx",
|
|
205
213
|
"isDynamicEntry": true,
|
|
@@ -208,7 +216,7 @@
|
|
|
208
216
|
]
|
|
209
217
|
},
|
|
210
218
|
"src/components/home/HomeWorkspace.tsx": {
|
|
211
|
-
"file": "assets/HomeWorkspace-
|
|
219
|
+
"file": "assets/HomeWorkspace-nKDhivwR.js",
|
|
212
220
|
"name": "HomeWorkspace",
|
|
213
221
|
"src": "src/components/home/HomeWorkspace.tsx",
|
|
214
222
|
"isDynamicEntry": true,
|
|
@@ -217,12 +225,13 @@
|
|
|
217
225
|
]
|
|
218
226
|
},
|
|
219
227
|
"src/components/prompt-builder/PromptBuilderPanel.tsx": {
|
|
220
|
-
"file": "assets/PromptBuilderPanel-
|
|
228
|
+
"file": "assets/PromptBuilderPanel-e6BSRAdj.js",
|
|
221
229
|
"name": "PromptBuilderPanel",
|
|
222
230
|
"src": "src/components/prompt-builder/PromptBuilderPanel.tsx",
|
|
223
231
|
"isDynamicEntry": true,
|
|
224
232
|
"imports": [
|
|
225
|
-
"index.html"
|
|
233
|
+
"index.html",
|
|
234
|
+
"_promptBuilderStore-CkwnWMbR.js"
|
|
226
235
|
]
|
|
227
236
|
}
|
|
228
237
|
}
|