ima2-gen 1.1.21 → 1.1.23
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 +44 -7
- package/bin/commands/video.js +14 -0
- package/bin/ima2.js +14 -4
- package/bin/lib/platform.js +34 -5
- package/docs/README.ko.md +43 -2
- package/lib/agentQueueWorker.js +6 -0
- package/lib/agentRuntime.js +3 -2
- package/lib/atomicWrite.js +14 -0
- package/lib/grokImageAdapter.js +6 -0
- package/lib/grokProxyLauncher.js +5 -3
- package/lib/grokVideoAdapter.js +1 -1
- package/lib/grokVideoPlannerPrompt.js +10 -0
- package/lib/inflight.js +1 -1
- package/lib/oauthLauncher.js +5 -0
- package/lib/videoFrameExtract.js +3 -3
- package/package.json +5 -7
- package/routes/capabilities.js +13 -0
- package/routes/edit.js +2 -1
- package/routes/generate.js +32 -6
- package/routes/health.js +4 -3
- package/routes/multimode.js +2 -1
- package/routes/video.js +35 -3
- package/server.js +29 -2
- package/skills/ima2/SKILL.md +48 -6
- package/ui/dist/.vite/manifest.json +12 -12
- package/ui/dist/assets/{AgentWorkspace-B_hq9CLg.js → AgentWorkspace-C21zqdTZ.js} +1 -1
- package/ui/dist/assets/{CardNewsWorkspace-wD12J7qk.js → CardNewsWorkspace-BN-ga1lG.js} +1 -1
- package/ui/dist/assets/{NodeCanvas-CI_wuPMf.js → NodeCanvas-BbMa4IhI.js} +1 -1
- package/ui/dist/assets/{PromptBuilderPanel-CUTujJUV.js → PromptBuilderPanel-DRwBJRDQ.js} +1 -1
- package/ui/dist/assets/{PromptImportDialog-CUi66jPK.js → PromptImportDialog-Dp85kHCq.js} +2 -2
- package/ui/dist/assets/{PromptImportDiscoverySection-Cm3vrjY4.js → PromptImportDiscoverySection-BE8Q8MLD.js} +1 -1
- package/ui/dist/assets/{PromptImportFolderSection-DOtWTD9n.js → PromptImportFolderSection-PtH5x0sc.js} +1 -1
- package/ui/dist/assets/{PromptLibraryPanel-BMjQegRa.js → PromptLibraryPanel-FnM9tHI9.js} +2 -2
- package/ui/dist/assets/SettingsWorkspace-MARPGyBL.js +1 -0
- package/ui/dist/assets/index-BAFI6htx.js +42 -0
- package/ui/dist/assets/{index-31uVIdt4.js → index-BSXxr_Bt.js} +1 -1
- package/ui/dist/assets/index-DS-ADE7U.css +1 -0
- package/ui/dist/index.html +2 -2
- package/bin/commands/annotate.ts +0 -119
- package/bin/commands/cancel.ts +0 -48
- package/bin/commands/canvas-versions.ts +0 -80
- package/bin/commands/capabilities.ts +0 -110
- package/bin/commands/cardnews.ts +0 -249
- package/bin/commands/comfy.ts +0 -54
- package/bin/commands/config.ts +0 -186
- package/bin/commands/defaults.ts +0 -192
- package/bin/commands/doctor.ts +0 -202
- package/bin/commands/edit.ts +0 -150
- package/bin/commands/gen.ts +0 -214
- package/bin/commands/grok.ts +0 -90
- package/bin/commands/history.ts +0 -146
- package/bin/commands/ls.ts +0 -64
- package/bin/commands/metadata.ts +0 -39
- package/bin/commands/multimode.ts +0 -196
- package/bin/commands/node.ts +0 -166
- package/bin/commands/observability.ts +0 -176
- package/bin/commands/ping.ts +0 -31
- package/bin/commands/prompt-sub/build.ts +0 -101
- package/bin/commands/prompt.ts +0 -492
- package/bin/commands/ps.ts +0 -81
- package/bin/commands/session.ts +0 -266
- package/bin/commands/show.ts +0 -72
- package/bin/commands/skill.ts +0 -70
- package/bin/commands/video.ts +0 -442
- package/bin/ima2.ts +0 -430
- package/bin/lib/args.ts +0 -92
- package/bin/lib/browser-id.ts +0 -16
- package/bin/lib/client.ts +0 -122
- package/bin/lib/config-store.ts +0 -120
- package/bin/lib/destructive-confirm.ts +0 -19
- package/bin/lib/doctor-checks.ts +0 -91
- package/bin/lib/error-hints.ts +0 -23
- package/bin/lib/files.ts +0 -39
- package/bin/lib/output.ts +0 -73
- package/bin/lib/platform.ts +0 -99
- package/bin/lib/recover-output.ts +0 -139
- package/bin/lib/sse.ts +0 -73
- package/bin/lib/star-prompt.ts +0 -97
- package/bin/lib/storage-doctor.ts +0 -39
- package/bin/lib/ui-build.ts +0 -85
- package/config.ts +0 -354
- package/lib/agentCommandParser.ts +0 -69
- package/lib/agentGenerationPlanner.ts +0 -273
- package/lib/agentQuestionResponder.ts +0 -266
- package/lib/agentQueueStore.ts +0 -270
- package/lib/agentQueueWorker.ts +0 -89
- package/lib/agentRuntime.ts +0 -604
- package/lib/agentSettings.ts +0 -72
- package/lib/agentStore.ts +0 -422
- package/lib/agentStoreRows.ts +0 -136
- package/lib/agentTypes.ts +0 -154
- package/lib/apiCachePolicy.ts +0 -11
- package/lib/assetLifecycle.ts +0 -146
- package/lib/canvasVersionStore.ts +0 -223
- package/lib/capabilities.ts +0 -126
- package/lib/cardNewsGenerator.ts +0 -271
- package/lib/cardNewsJobStore.ts +0 -142
- package/lib/cardNewsManifestStore.ts +0 -154
- package/lib/cardNewsPlanner.ts +0 -236
- package/lib/cardNewsPlannerClient.ts +0 -155
- package/lib/cardNewsPlannerPrompt.ts +0 -62
- package/lib/cardNewsPlannerSchema.ts +0 -321
- package/lib/cardNewsRoleTemplateStore.ts +0 -47
- package/lib/cardNewsTemplateStore.ts +0 -252
- package/lib/codexDetect.ts +0 -71
- package/lib/comfyBridge.ts +0 -235
- package/lib/composerSnapshot.ts +0 -33
- package/lib/configKeys.ts +0 -62
- package/lib/db.ts +0 -295
- package/lib/errInfo.ts +0 -43
- package/lib/errorClassify.ts +0 -100
- package/lib/generationCancel.ts +0 -28
- package/lib/generationErrors.ts +0 -238
- package/lib/grokImageAdapter.ts +0 -513
- package/lib/grokMultimodeAdapter.ts +0 -84
- package/lib/grokProxyLauncher.ts +0 -153
- package/lib/grokRuntime.ts +0 -23
- package/lib/grokSizeMapper.ts +0 -71
- package/lib/grokVideoAdapter.ts +0 -458
- package/lib/grokVideoCanvas.ts +0 -26
- package/lib/grokVideoDownload.ts +0 -59
- package/lib/grokVideoPlannerPrompt.ts +0 -67
- package/lib/historyIndex.ts +0 -51
- package/lib/historyList.ts +0 -181
- package/lib/imageMetadata.ts +0 -113
- package/lib/imageMetadataStore.ts +0 -67
- package/lib/imageModels.ts +0 -165
- package/lib/inflight.ts +0 -281
- package/lib/localImportStore.ts +0 -114
- package/lib/logger.ts +0 -161
- package/lib/nodeStore.ts +0 -91
- package/lib/oauthLauncher.ts +0 -94
- package/lib/oauthNormalize.ts +0 -30
- package/lib/oauthProxy/errors.ts +0 -128
- package/lib/oauthProxy/generators.ts +0 -494
- package/lib/oauthProxy/index.ts +0 -28
- package/lib/oauthProxy/prompts.ts +0 -123
- package/lib/oauthProxy/references.ts +0 -45
- package/lib/oauthProxy/runtime.ts +0 -115
- package/lib/oauthProxy/streams.ts +0 -232
- package/lib/oauthProxy/types.ts +0 -9
- package/lib/oauthProxy.ts +0 -3
- package/lib/openDirectory.ts +0 -47
- package/lib/pngInfo.ts +0 -26
- package/lib/promptBuilder/attachments.ts +0 -74
- package/lib/promptBuilder/client.ts +0 -130
- package/lib/promptBuilder/constants.ts +0 -9
- package/lib/promptBuilder/context.ts +0 -36
- package/lib/promptBuilder/errors.ts +0 -12
- package/lib/promptBuilder/requestSchema.ts +0 -56
- package/lib/promptBuilder/responseParser.ts +0 -219
- package/lib/promptBuilder/systemPrompt.ts +0 -135
- package/lib/promptBuilder/transport.ts +0 -94
- package/lib/promptBuilder/types.ts +0 -109
- package/lib/promptImport/curatedSources.ts +0 -141
- package/lib/promptImport/discoveryRegistry.ts +0 -329
- package/lib/promptImport/errors.ts +0 -18
- package/lib/promptImport/githubDiscovery.ts +0 -309
- package/lib/promptImport/githubFolder.ts +0 -397
- package/lib/promptImport/githubSource.ts +0 -257
- package/lib/promptImport/gptImageHints.ts +0 -70
- package/lib/promptImport/parsePromptCandidates.ts +0 -179
- package/lib/promptImport/promptIndex.ts +0 -326
- package/lib/promptImport/rankPromptCandidates.ts +0 -65
- package/lib/promptImport/types.ts +0 -103
- package/lib/promptSafetyPolicy.ts +0 -5
- package/lib/providerOptions.ts +0 -56
- package/lib/referenceImageCompress.ts +0 -84
- package/lib/refs.ts +0 -133
- package/lib/requestLogger.ts +0 -49
- package/lib/responsesDoctor.ts +0 -456
- package/lib/responsesErrors.ts +0 -83
- package/lib/responsesFallback.ts +0 -114
- package/lib/responsesImageAdapter.ts +0 -466
- package/lib/responsesParse.ts +0 -452
- package/lib/responsesTools.ts +0 -28
- package/lib/runtimeContext.ts +0 -146
- package/lib/runtimePorts.ts +0 -105
- package/lib/sessionStore.ts +0 -308
- package/lib/storageMigration.ts +0 -310
- package/lib/styleSheet.ts +0 -139
- package/lib/systemTrash.ts +0 -20
- package/lib/videoContinuity.ts +0 -180
- package/lib/videoFrameExtract.ts +0 -78
- package/lib/videoSeriesChain.ts +0 -29
- package/lib/visibleTextLanguagePolicy.ts +0 -7
- package/routes/agent.ts +0 -308
- package/routes/annotations.ts +0 -118
- package/routes/canvasVersions.ts +0 -69
- package/routes/capabilities.ts +0 -18
- package/routes/cardNews.ts +0 -211
- package/routes/comfy.ts +0 -43
- package/routes/edit.ts +0 -352
- package/routes/generate.ts +0 -492
- package/routes/grok.ts +0 -24
- package/routes/health.ts +0 -123
- package/routes/history.ts +0 -221
- package/routes/imageImport.ts +0 -37
- package/routes/index.ts +0 -52
- package/routes/metadata.ts +0 -77
- package/routes/multimode.ts +0 -499
- package/routes/nodes.ts +0 -578
- package/routes/promptBuilder.ts +0 -37
- package/routes/promptImport.ts +0 -379
- package/routes/prompts.ts +0 -428
- package/routes/quota.ts +0 -89
- package/routes/sessions.ts +0 -317
- package/routes/storage.ts +0 -47
- package/routes/video.ts +0 -300
- package/routes/videoExtended.ts +0 -284
- package/server.ts +0 -293
- package/ui/dist/assets/SettingsWorkspace-PiaVnsdA.js +0 -1
- package/ui/dist/assets/index-CjgnNtgt.css +0 -1
- package/ui/dist/assets/index-Da2s4_-5.js +0 -36
package/lib/videoFrameExtract.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import { randomBytes } from "node:crypto";
|
|
3
|
-
import { open, readFile, realpath, stat, unlink } from "node:fs/promises";
|
|
4
|
-
import { extname, join, resolve, sep } from "node:path";
|
|
5
|
-
import { promisify } from "node:util";
|
|
6
|
-
|
|
7
|
-
const execFileAsync = promisify(execFile);
|
|
8
|
-
const MAX_LOCAL_VIDEO_BYTES = 100 * 1024 * 1024;
|
|
9
|
-
const MAX_FRAME_POSITION_SECONDS = 60 * 60;
|
|
10
|
-
const FFMPEG_TIMEOUT_MS = 30_000;
|
|
11
|
-
|
|
12
|
-
function routeError(message: string, status = 400): Error & { status: number } {
|
|
13
|
-
return Object.assign(new Error(message), { status });
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function safeGeneratedFilePath(generatedDir: string, file: string, options: { requireMp4?: boolean } = {}): Promise<string> {
|
|
17
|
-
const base = resolve(generatedDir);
|
|
18
|
-
const target = file.startsWith("/") ? resolve(file) : resolve(base, file);
|
|
19
|
-
if (target !== base && !target.startsWith(`${base}${sep}`)) {
|
|
20
|
-
throw routeError("invalid file path", 400);
|
|
21
|
-
}
|
|
22
|
-
let baseReal: string;
|
|
23
|
-
let targetReal: string;
|
|
24
|
-
try {
|
|
25
|
-
baseReal = await realpath(base);
|
|
26
|
-
targetReal = await realpath(target);
|
|
27
|
-
} catch {
|
|
28
|
-
throw routeError("video file not found", 404);
|
|
29
|
-
}
|
|
30
|
-
if (targetReal !== baseReal && !targetReal.startsWith(`${baseReal}${sep}`)) {
|
|
31
|
-
throw routeError("invalid file path", 400);
|
|
32
|
-
}
|
|
33
|
-
if (options.requireMp4 && extname(targetReal).toLowerCase() !== ".mp4") {
|
|
34
|
-
throw routeError("generated video input must be an .mp4 file", 400);
|
|
35
|
-
}
|
|
36
|
-
return targetReal;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export async function assertLocalMp4(path: string): Promise<void> {
|
|
40
|
-
const info = await stat(path);
|
|
41
|
-
if (!info.isFile()) throw routeError("generated video input must be a file", 400);
|
|
42
|
-
if (info.size <= 0) throw routeError("generated video input is empty", 400);
|
|
43
|
-
if (info.size > MAX_LOCAL_VIDEO_BYTES) throw routeError("generated video input exceeds the 100MB limit", 400);
|
|
44
|
-
const fh = await open(path, "r");
|
|
45
|
-
try {
|
|
46
|
-
const header = Buffer.alloc(12);
|
|
47
|
-
const { bytesRead } = await fh.read(header, 0, header.length, 0);
|
|
48
|
-
if (bytesRead < 12 || header.subarray(4, 8).toString("ascii") !== "ftyp") {
|
|
49
|
-
throw routeError("generated video input must be an MP4 container", 400);
|
|
50
|
-
}
|
|
51
|
-
} finally {
|
|
52
|
-
await fh.close();
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export async function extractVideoFrame(input: string, output: string, position: string): Promise<void> {
|
|
57
|
-
const options = { timeout: FFMPEG_TIMEOUT_MS, killSignal: "SIGKILL" as const, maxBuffer: 1024 * 1024 };
|
|
58
|
-
if (position === "last") {
|
|
59
|
-
await execFileAsync("ffmpeg", ["-y", "-sseof", "-3", "-i", input, "-update", "1", "-q:v", "1", output], options);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
const sec = Number(position);
|
|
63
|
-
if (!Number.isFinite(sec) || sec < 0) throw new Error("position must be a non-negative number or 'last'");
|
|
64
|
-
if (sec > MAX_FRAME_POSITION_SECONDS) throw new Error("position exceeds the maximum supported seek time");
|
|
65
|
-
await execFileAsync("ffmpeg", ["-y", "-ss", String(sec), "-i", input, "-vframes", "1", output], options);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export async function extractGeneratedVideoFrameB64(generatedDir: string, filename: string, position = "last"): Promise<string> {
|
|
69
|
-
const inputPath = await safeGeneratedFilePath(generatedDir, filename, { requireMp4: true });
|
|
70
|
-
await assertLocalMp4(inputPath);
|
|
71
|
-
const tmpOut = join(generatedDir, `frame_tmp_${randomBytes(4).toString("hex")}.png`);
|
|
72
|
-
try {
|
|
73
|
-
await extractVideoFrame(inputPath, tmpOut, position);
|
|
74
|
-
return (await readFile(tmpOut)).toString("base64");
|
|
75
|
-
} finally {
|
|
76
|
-
await unlink(tmpOut).catch(() => {});
|
|
77
|
-
}
|
|
78
|
-
}
|
package/lib/videoSeriesChain.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { readdir, readFile } from "fs/promises";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
|
|
4
|
-
interface VideoSeriesMeta {
|
|
5
|
-
revisedPrompt?: string;
|
|
6
|
-
createdAt?: number;
|
|
7
|
-
videoSeries?: { topic: string; chainIndex?: number };
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Scan generatedDir for videos with matching topic, return the most recent N revisedPrompts.
|
|
12
|
-
*/
|
|
13
|
-
export async function getVideoSeriesChain(generatedDir: string, topic: string, limit = 4): Promise<string[]> {
|
|
14
|
-
if (!topic.trim()) return [];
|
|
15
|
-
const entries = await readdir(generatedDir).catch(() => [] as string[]);
|
|
16
|
-
const sidecars = entries.filter((e) => e.endsWith(".mp4.json"));
|
|
17
|
-
const matches: Array<{ revisedPrompt: string; createdAt: number }> = [];
|
|
18
|
-
for (const sidecar of sidecars) {
|
|
19
|
-
try {
|
|
20
|
-
const raw = await readFile(join(generatedDir, sidecar), "utf-8");
|
|
21
|
-
const meta: VideoSeriesMeta = JSON.parse(raw);
|
|
22
|
-
if (meta.videoSeries?.topic === topic && meta.revisedPrompt) {
|
|
23
|
-
matches.push({ revisedPrompt: meta.revisedPrompt, createdAt: meta.createdAt ?? 0 });
|
|
24
|
-
}
|
|
25
|
-
} catch { /* skip unreadable */ }
|
|
26
|
-
}
|
|
27
|
-
matches.sort((a, b) => b.createdAt - a.createdAt);
|
|
28
|
-
return matches.slice(0, limit).reverse().map((m) => m.revisedPrompt);
|
|
29
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export const VISIBLE_TEXT_LANGUAGE_POLICY = [
|
|
2
|
-
"Visible text and language rule:",
|
|
3
|
-
"If the image must contain readable text, signage, labels, UI copy, captions, slogans, typography, or keywords in a specific language, explicitly list the exact visible words in that language and script.",
|
|
4
|
-
"Do not translate, romanize, summarize, substitute, or invent alternate wording.",
|
|
5
|
-
"Do not use vague placeholders such as \"Korean text\", \"Japanese words\", or \"foreign language text\".",
|
|
6
|
-
"If exact words are needed, state them as exact visible text items and render only those listed items.",
|
|
7
|
-
].join(" ");
|
package/routes/agent.ts
DELETED
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
import type { Express, Request, Response } from "express";
|
|
2
|
-
import {
|
|
3
|
-
appendAgentTurn,
|
|
4
|
-
compactAgentSession,
|
|
5
|
-
createAgentSession,
|
|
6
|
-
deleteAgentSession,
|
|
7
|
-
getAgentGenerationSettings,
|
|
8
|
-
getAgentSession,
|
|
9
|
-
getAgentWorkspacePayload,
|
|
10
|
-
renameAgentSession,
|
|
11
|
-
setAgentCurrentImage,
|
|
12
|
-
setAgentGenerationSettings,
|
|
13
|
-
setAgentLocks,
|
|
14
|
-
setAgentWebSearch,
|
|
15
|
-
} from "../lib/agentStore.js";
|
|
16
|
-
import {
|
|
17
|
-
cancelAgentQueueItem,
|
|
18
|
-
createAgentQueueItem,
|
|
19
|
-
getAgentQueueItem,
|
|
20
|
-
listAgentQueueItems,
|
|
21
|
-
retryAgentQueueItem,
|
|
22
|
-
} from "../lib/agentQueueStore.js";
|
|
23
|
-
import { ensureAgentQueueWorker, tickAgentQueueWorker } from "../lib/agentQueueWorker.js";
|
|
24
|
-
import { parseAgentSlashCommand, formatAgentQuestionReply, formatAgentSlashHelp } from "../lib/agentCommandParser.js";
|
|
25
|
-
import { requestAgentQuestionAnswer } from "../lib/agentQuestionResponder.js";
|
|
26
|
-
import { agentAllowedToolPayload, runAgentTurn } from "../lib/agentRuntime.js";
|
|
27
|
-
import { errInfo } from "../lib/errInfo.js";
|
|
28
|
-
import { requireRuntimeContext, type RouteRuntimeContext } from "../lib/runtimeContext.js";
|
|
29
|
-
|
|
30
|
-
type AgentSessionBody = {
|
|
31
|
-
title?: unknown;
|
|
32
|
-
currentImage?: unknown;
|
|
33
|
-
webSearchEnabled?: unknown;
|
|
34
|
-
currentImageId?: unknown;
|
|
35
|
-
styleLocks?: unknown;
|
|
36
|
-
subjectLocks?: unknown;
|
|
37
|
-
generationSettings?: unknown;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
type AgentTurnBody = {
|
|
41
|
-
prompt?: unknown;
|
|
42
|
-
provider?: unknown;
|
|
43
|
-
quality?: unknown;
|
|
44
|
-
size?: unknown;
|
|
45
|
-
format?: unknown;
|
|
46
|
-
moderation?: unknown;
|
|
47
|
-
model?: unknown;
|
|
48
|
-
reasoningEffort?: unknown;
|
|
49
|
-
requestId?: unknown;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
type AgentQueueBody = AgentTurnBody & {
|
|
53
|
-
options?: unknown;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
export function registerAgentRoutes(app: Express, ctxRaw: RouteRuntimeContext) {
|
|
57
|
-
const ctx = requireRuntimeContext(ctxRaw);
|
|
58
|
-
ensureAgentQueueWorker(ctx);
|
|
59
|
-
|
|
60
|
-
app.get("/api/agent/tools", (_req: Request, res: Response) => {
|
|
61
|
-
res.json(agentAllowedToolPayload());
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
app.get("/api/agent/sessions", (req: Request, res: Response) => {
|
|
65
|
-
const selectedId = typeof req.query.selectedSessionId === "string" ? req.query.selectedSessionId : null;
|
|
66
|
-
res.json(getAgentWorkspacePayload(selectedId));
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
app.post("/api/agent/sessions", (req: Request, res: Response) => {
|
|
70
|
-
try {
|
|
71
|
-
const body = (req.body ?? {}) as AgentSessionBody;
|
|
72
|
-
const session = createAgentSession({
|
|
73
|
-
title: body.title,
|
|
74
|
-
currentImage: normalizeCurrentImage(body.currentImage),
|
|
75
|
-
webSearchEnabled: body.webSearchEnabled !== false,
|
|
76
|
-
});
|
|
77
|
-
res.status(201).json(getAgentWorkspacePayload(session.id));
|
|
78
|
-
} catch (error) {
|
|
79
|
-
sendError(res, error);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
app.get("/api/agent/sessions/:sessionId", (req: Request<{ sessionId: string }>, res: Response) => {
|
|
84
|
-
const session = getAgentSession(req.params.sessionId);
|
|
85
|
-
if (!session) return sendError(res, notFound(req.params.sessionId));
|
|
86
|
-
res.json(getAgentWorkspacePayload(req.params.sessionId));
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
app.patch("/api/agent/sessions/:sessionId", (req: Request<{ sessionId: string }>, res: Response) => {
|
|
90
|
-
try {
|
|
91
|
-
const body = (req.body ?? {}) as AgentSessionBody;
|
|
92
|
-
if (Object.prototype.hasOwnProperty.call(body, "title")) renameAgentSession(req.params.sessionId, body.title);
|
|
93
|
-
if (typeof body.webSearchEnabled === "boolean") setAgentWebSearch(req.params.sessionId, body.webSearchEnabled);
|
|
94
|
-
if (Object.prototype.hasOwnProperty.call(body, "generationSettings")) {
|
|
95
|
-
setAgentGenerationSettings(req.params.sessionId, body.generationSettings);
|
|
96
|
-
}
|
|
97
|
-
if (Object.prototype.hasOwnProperty.call(body, "currentImageId")) {
|
|
98
|
-
const ok = setAgentCurrentImage(req.params.sessionId, body.currentImageId);
|
|
99
|
-
if (!ok) throw imageNotFound(req.params.sessionId);
|
|
100
|
-
}
|
|
101
|
-
if (Array.isArray(body.styleLocks) || Array.isArray(body.subjectLocks)) setAgentLocks(req.params.sessionId, body);
|
|
102
|
-
res.json(getAgentWorkspacePayload(req.params.sessionId));
|
|
103
|
-
} catch (error) {
|
|
104
|
-
sendError(res, error);
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
app.delete("/api/agent/sessions/:sessionId", (req: Request<{ sessionId: string }>, res: Response) => {
|
|
109
|
-
const ok = deleteAgentSession(req.params.sessionId);
|
|
110
|
-
if (!ok) return sendError(res, notFound(req.params.sessionId));
|
|
111
|
-
res.json(getAgentWorkspacePayload(null));
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
app.post("/api/agent/sessions/:sessionId/compact", (req: Request<{ sessionId: string }>, res: Response) => {
|
|
115
|
-
try {
|
|
116
|
-
if (!getAgentSession(req.params.sessionId)) throw notFound(req.params.sessionId);
|
|
117
|
-
compactAgentSession(req.params.sessionId);
|
|
118
|
-
res.json(getAgentWorkspacePayload(req.params.sessionId));
|
|
119
|
-
} catch (error) {
|
|
120
|
-
sendError(res, error);
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
app.get("/api/agent/sessions/:sessionId/manifest", (req: Request<{ sessionId: string }>, res: Response) => {
|
|
125
|
-
const payload = getAgentWorkspacePayload(req.params.sessionId);
|
|
126
|
-
if (!payload.selectedSessionId) return sendError(res, notFound(req.params.sessionId));
|
|
127
|
-
res.type("application/xml").send(payload.manifest ?? "");
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
app.post("/api/agent/sessions/:sessionId/turns", async (req: Request<{ sessionId: string }>, res: Response) => {
|
|
131
|
-
try {
|
|
132
|
-
const body = (req.body ?? {}) as AgentTurnBody;
|
|
133
|
-
const prompt = cleanPrompt(body.prompt);
|
|
134
|
-
await runAgentTurn(ctx, req.params.sessionId, prompt, {
|
|
135
|
-
provider: cleanOption(body.provider),
|
|
136
|
-
quality: cleanOption(body.quality),
|
|
137
|
-
size: cleanOption(body.size),
|
|
138
|
-
format: cleanOption(body.format),
|
|
139
|
-
moderation: cleanOption(body.moderation),
|
|
140
|
-
model: cleanOption(body.model),
|
|
141
|
-
reasoningEffort: cleanOption(body.reasoningEffort),
|
|
142
|
-
requestId: cleanOption(body.requestId),
|
|
143
|
-
});
|
|
144
|
-
res.json(getAgentWorkspacePayload(req.params.sessionId));
|
|
145
|
-
} catch (error) {
|
|
146
|
-
sendError(res, error);
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
app.get("/api/agent/queue", (_req: Request, res: Response) => {
|
|
151
|
-
res.json({ queue: listAgentQueueItems() });
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
app.get("/api/agent/sessions/:sessionId/queue", (req: Request<{ sessionId: string }>, res: Response) => {
|
|
155
|
-
if (!getAgentSession(req.params.sessionId)) return sendError(res, notFound(req.params.sessionId));
|
|
156
|
-
res.json({ queue: listAgentQueueItems(req.params.sessionId) });
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
app.post("/api/agent/sessions/:sessionId/queue", async (req: Request<{ sessionId: string }>, res: Response) => {
|
|
160
|
-
try {
|
|
161
|
-
if (!getAgentSession(req.params.sessionId)) throw notFound(req.params.sessionId);
|
|
162
|
-
const body = (req.body ?? {}) as AgentQueueBody;
|
|
163
|
-
const rawPrompt = cleanPrompt(body.prompt);
|
|
164
|
-
const command = parseAgentSlashCommand(rawPrompt);
|
|
165
|
-
appendAgentTurn({ sessionId: req.params.sessionId, role: "user", text: rawPrompt, status: "complete" });
|
|
166
|
-
if (command?.name === "help") {
|
|
167
|
-
appendAgentTurn({
|
|
168
|
-
sessionId: req.params.sessionId,
|
|
169
|
-
role: "assistant",
|
|
170
|
-
text: formatAgentSlashHelp(),
|
|
171
|
-
status: "complete",
|
|
172
|
-
});
|
|
173
|
-
return res.status(200).json({ queueItem: null, workspace: getAgentWorkspacePayload(req.params.sessionId) });
|
|
174
|
-
}
|
|
175
|
-
if (command?.name === "question") {
|
|
176
|
-
const options = normalizeQueueOptions(req.params.sessionId, body);
|
|
177
|
-
const question = command.prompt.trim();
|
|
178
|
-
const answer = question
|
|
179
|
-
? (await requestAgentQuestionAnswer(ctx, question, {
|
|
180
|
-
provider: cleanOption(options.provider),
|
|
181
|
-
model: cleanOption(options.model),
|
|
182
|
-
reasoningEffort: cleanOption(options.reasoningEffort),
|
|
183
|
-
requestId: cleanOption(body.requestId),
|
|
184
|
-
})).text
|
|
185
|
-
: formatAgentQuestionReply(command.prompt);
|
|
186
|
-
appendAgentTurn({
|
|
187
|
-
sessionId: req.params.sessionId,
|
|
188
|
-
role: "assistant",
|
|
189
|
-
text: answer,
|
|
190
|
-
status: "complete",
|
|
191
|
-
});
|
|
192
|
-
return res.status(200).json({ queueItem: null, workspace: getAgentWorkspacePayload(req.params.sessionId) });
|
|
193
|
-
}
|
|
194
|
-
const prompt = command ? cleanPrompt(command.prompt) : rawPrompt;
|
|
195
|
-
const queueItem = createAgentQueueItem({
|
|
196
|
-
sessionId: req.params.sessionId,
|
|
197
|
-
prompt,
|
|
198
|
-
options: normalizeQueueOptions(req.params.sessionId, body),
|
|
199
|
-
command,
|
|
200
|
-
});
|
|
201
|
-
void tickAgentQueueWorker(ctx);
|
|
202
|
-
res.status(202).json({ queueItem, workspace: getAgentWorkspacePayload(req.params.sessionId) });
|
|
203
|
-
} catch (error) {
|
|
204
|
-
sendError(res, error);
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
app.post("/api/agent/queue/:itemId/cancel", (req: Request<{ itemId: string }>, res: Response) => {
|
|
209
|
-
const item = getAgentQueueItem(req.params.itemId);
|
|
210
|
-
if (!item) return sendError(res, queueItemNotFound(req.params.itemId));
|
|
211
|
-
const ok = cancelAgentQueueItem(item.id);
|
|
212
|
-
if (!ok) return sendError(res, queueActionError("AGENT_QUEUE_CANCEL_FAILED", "Only queued Agent work can be canceled."));
|
|
213
|
-
res.json(getAgentWorkspacePayload(item.sessionId));
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
app.post("/api/agent/queue/:itemId/retry", (req: Request<{ itemId: string }>, res: Response) => {
|
|
217
|
-
const item = getAgentQueueItem(req.params.itemId);
|
|
218
|
-
if (!item) return sendError(res, queueItemNotFound(req.params.itemId));
|
|
219
|
-
const ok = retryAgentQueueItem(item.id);
|
|
220
|
-
if (!ok) return sendError(res, queueActionError("AGENT_QUEUE_RETRY_FAILED", "Only failed or canceled Agent work can be retried."));
|
|
221
|
-
void tickAgentQueueWorker(ctx);
|
|
222
|
-
res.json(getAgentWorkspacePayload(item.sessionId));
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function normalizeCurrentImage(value: unknown) {
|
|
227
|
-
if (!value || typeof value !== "object") return null;
|
|
228
|
-
const item = value as Record<string, unknown>;
|
|
229
|
-
return {
|
|
230
|
-
id: cleanOption(item.id),
|
|
231
|
-
filename: cleanOption(item.filename),
|
|
232
|
-
url: cleanOption(item.url) ?? cleanOption(item.image),
|
|
233
|
-
thumbUrl: cleanOption(item.thumbUrl) ?? cleanOption(item.thumb),
|
|
234
|
-
prompt: cleanOption(item.prompt) ?? cleanOption(item.userPrompt),
|
|
235
|
-
revisedPrompt: cleanOption(item.revisedPrompt),
|
|
236
|
-
createdAt: typeof item.createdAt === "number" ? item.createdAt : null,
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function cleanOption(value: unknown) {
|
|
241
|
-
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function cleanPrompt(value: unknown) {
|
|
245
|
-
const prompt = cleanOption(value);
|
|
246
|
-
if (prompt) return prompt;
|
|
247
|
-
const err = new Error("Prompt is required") as Error & { code?: string; status?: number };
|
|
248
|
-
err.code = "AGENT_PROMPT_REQUIRED";
|
|
249
|
-
err.status = 400;
|
|
250
|
-
throw err;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function normalizeQueueOptions(sessionId: string, body: AgentQueueBody) {
|
|
254
|
-
const current = getAgentGenerationSettings(sessionId);
|
|
255
|
-
const input = body.options && typeof body.options === "object" ? body.options as Record<string, unknown> : {};
|
|
256
|
-
return {
|
|
257
|
-
...current,
|
|
258
|
-
...input,
|
|
259
|
-
provider: cleanOption(body.provider) ?? input.provider ?? current.provider,
|
|
260
|
-
quality: cleanOption(body.quality) ?? input.quality ?? current.quality,
|
|
261
|
-
size: cleanOption(body.size) ?? input.size ?? current.size,
|
|
262
|
-
format: cleanOption(body.format) ?? input.format ?? current.format,
|
|
263
|
-
moderation: cleanOption(body.moderation) ?? input.moderation ?? current.moderation,
|
|
264
|
-
model: cleanOption(body.model) ?? input.model ?? current.model,
|
|
265
|
-
reasoningEffort: cleanOption(body.reasoningEffort) ?? input.reasoningEffort ?? current.reasoningEffort,
|
|
266
|
-
webSearchEnabled: typeof input.webSearchEnabled === "boolean" ? input.webSearchEnabled : current.webSearchEnabled,
|
|
267
|
-
generationStrategy: input.generationStrategy ?? current.generationStrategy,
|
|
268
|
-
variants: input.variants ?? current.variants,
|
|
269
|
-
maxAutoVariants: input.maxAutoVariants ?? current.maxAutoVariants,
|
|
270
|
-
parallelism: input.parallelism ?? current.parallelism,
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function queueActionError(code: string, message: string) {
|
|
275
|
-
const err = new Error(message) as Error & { code?: string; status?: number };
|
|
276
|
-
err.code = code;
|
|
277
|
-
err.status = 409;
|
|
278
|
-
return err;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function queueItemNotFound(itemId: string) {
|
|
282
|
-
const err = new Error(`Agent queue item not found: ${itemId}`) as Error & { code?: string; status?: number };
|
|
283
|
-
err.code = "AGENT_QUEUE_ITEM_NOT_FOUND";
|
|
284
|
-
err.status = 404;
|
|
285
|
-
return err;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function sendError(res: Response, error: unknown) {
|
|
289
|
-
const err = errInfo(error);
|
|
290
|
-
res.status(err.status || 500).json({
|
|
291
|
-
error: { code: err.code || "AGENT_ERROR", message: err.message },
|
|
292
|
-
code: err.code || "AGENT_ERROR",
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function notFound(sessionId: string) {
|
|
297
|
-
const err = new Error(`Agent session not found: ${sessionId}`) as Error & { code?: string; status?: number };
|
|
298
|
-
err.code = "AGENT_SESSION_NOT_FOUND";
|
|
299
|
-
err.status = 404;
|
|
300
|
-
return err;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function imageNotFound(sessionId: string) {
|
|
304
|
-
const err = new Error(`Agent image not found in session: ${sessionId}`) as Error & { code?: string; status?: number };
|
|
305
|
-
err.code = "AGENT_IMAGE_NOT_FOUND";
|
|
306
|
-
err.status = 404;
|
|
307
|
-
return err;
|
|
308
|
-
}
|
package/routes/annotations.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import type { Express, Request, Response } from "express";
|
|
2
|
-
import { getDb } from "../lib/db.js";
|
|
3
|
-
|
|
4
|
-
import { errInfo } from "../lib/errInfo.js";
|
|
5
|
-
import type { RouteRuntimeContext } from "../lib/runtimeContext.js";
|
|
6
|
-
const MAX_ANNOTATION_PAYLOAD_CHARS = 256 * 1024;
|
|
7
|
-
|
|
8
|
-
function getBrowserId(req: Request): string | null {
|
|
9
|
-
const browserId = req.headers["x-ima2-browser-id"];
|
|
10
|
-
return typeof browserId === "string" && browserId.trim() ? browserId.trim() : null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function isSafeFilename(filename: unknown): filename is string {
|
|
14
|
-
return (
|
|
15
|
-
typeof filename === "string" &&
|
|
16
|
-
filename.length > 0 &&
|
|
17
|
-
filename.length <= 240 &&
|
|
18
|
-
!filename.includes("..") &&
|
|
19
|
-
!filename.startsWith("/") &&
|
|
20
|
-
!filename.includes("\\")
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface AnnotationPayload {
|
|
25
|
-
paths?: unknown;
|
|
26
|
-
boxes?: unknown;
|
|
27
|
-
memos?: unknown;
|
|
28
|
-
annotations?: AnnotationPayload;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface NormalizedPayload {
|
|
32
|
-
payload?: { paths: unknown[]; boxes: unknown[]; memos: unknown[] };
|
|
33
|
-
text?: string;
|
|
34
|
-
error?: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function normalizePayload(value: unknown): NormalizedPayload {
|
|
38
|
-
const v = value as AnnotationPayload | null | undefined;
|
|
39
|
-
const payload = (v && typeof v === "object" && "annotations" in v ? v.annotations : v) as
|
|
40
|
-
| AnnotationPayload
|
|
41
|
-
| null
|
|
42
|
-
| undefined;
|
|
43
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
44
|
-
return { error: "annotations payload is required" };
|
|
45
|
-
}
|
|
46
|
-
const paths = Array.isArray(payload.paths) ? payload.paths : [];
|
|
47
|
-
const boxes = Array.isArray(payload.boxes) ? payload.boxes : [];
|
|
48
|
-
const memos = Array.isArray(payload.memos) ? payload.memos : [];
|
|
49
|
-
const normalized = { paths, boxes, memos };
|
|
50
|
-
const text = JSON.stringify(normalized);
|
|
51
|
-
if (text.length > MAX_ANNOTATION_PAYLOAD_CHARS) {
|
|
52
|
-
return { error: "annotations payload is too large" };
|
|
53
|
-
}
|
|
54
|
-
return { payload: normalized, text };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function registerAnnotationRoutes(app: Express, _ctx: RouteRuntimeContext) {
|
|
58
|
-
app.get("/api/annotations/:filename", (req: Request<{ filename: string }>, res: Response) => {
|
|
59
|
-
try {
|
|
60
|
-
const browserId = getBrowserId(req);
|
|
61
|
-
const filename = decodeURIComponent(req.params.filename);
|
|
62
|
-
if (!browserId) return res.status(400).json({ error: "X-Ima2-Browser-Id header is required" });
|
|
63
|
-
if (!isSafeFilename(filename)) return res.status(400).json({ error: "invalid filename" });
|
|
64
|
-
|
|
65
|
-
const row = getDb()
|
|
66
|
-
.prepare("SELECT payload FROM image_annotations WHERE browser_id = ? AND filename = ?")
|
|
67
|
-
.get(browserId, filename) as { payload: string } | undefined;
|
|
68
|
-
const annotations = row ? JSON.parse(row.payload) : null;
|
|
69
|
-
res.json({ annotations });
|
|
70
|
-
} catch (e) {
|
|
71
|
-
const err = errInfo(e);
|
|
72
|
-
res.status(500).json({ error: err.message });
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
app.put("/api/annotations/:filename", (req: Request<{ filename: string }>, res: Response) => {
|
|
77
|
-
try {
|
|
78
|
-
const browserId = getBrowserId(req);
|
|
79
|
-
const filename = decodeURIComponent(req.params.filename);
|
|
80
|
-
if (!browserId) return res.status(400).json({ error: "X-Ima2-Browser-Id header is required" });
|
|
81
|
-
if (!isSafeFilename(filename)) return res.status(400).json({ error: "invalid filename" });
|
|
82
|
-
|
|
83
|
-
const normalized = normalizePayload(req.body);
|
|
84
|
-
if (normalized.error) return res.status(400).json({ error: normalized.error });
|
|
85
|
-
|
|
86
|
-
const id = `${browserId}:${filename}`;
|
|
87
|
-
getDb().prepare(`
|
|
88
|
-
INSERT INTO image_annotations (id, browser_id, filename, payload, schema_version, updated_at)
|
|
89
|
-
VALUES (?, ?, ?, ?, 1, unixepoch())
|
|
90
|
-
ON CONFLICT(browser_id, filename) DO UPDATE SET
|
|
91
|
-
payload = excluded.payload,
|
|
92
|
-
schema_version = excluded.schema_version,
|
|
93
|
-
updated_at = unixepoch()
|
|
94
|
-
`).run(id, browserId, filename, normalized.text);
|
|
95
|
-
res.json({ ok: true });
|
|
96
|
-
} catch (e) {
|
|
97
|
-
const err = errInfo(e);
|
|
98
|
-
res.status(500).json({ error: err.message });
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
app.delete("/api/annotations/:filename", (req: Request<{ filename: string }>, res: Response) => {
|
|
103
|
-
try {
|
|
104
|
-
const browserId = getBrowserId(req);
|
|
105
|
-
const filename = decodeURIComponent(req.params.filename);
|
|
106
|
-
if (!browserId) return res.status(400).json({ error: "X-Ima2-Browser-Id header is required" });
|
|
107
|
-
if (!isSafeFilename(filename)) return res.status(400).json({ error: "invalid filename" });
|
|
108
|
-
|
|
109
|
-
getDb()
|
|
110
|
-
.prepare("DELETE FROM image_annotations WHERE browser_id = ? AND filename = ?")
|
|
111
|
-
.run(browserId, filename);
|
|
112
|
-
res.json({ ok: true });
|
|
113
|
-
} catch (e) {
|
|
114
|
-
const err = errInfo(e);
|
|
115
|
-
res.status(500).json({ error: err.message });
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
}
|
package/routes/canvasVersions.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import express, { type Express, type Request, type Response } from "express";
|
|
2
|
-
import { createCanvasVersion, updateCanvasVersion } from "../lib/canvasVersionStore.js";
|
|
3
|
-
|
|
4
|
-
import { errInfo } from "../lib/errInfo.js";
|
|
5
|
-
import { requireRuntimeContext, type RouteRuntimeContext } from "../lib/runtimeContext.js";
|
|
6
|
-
function decodeHeader(value: unknown): string | null {
|
|
7
|
-
if (typeof value !== "string" || !value) return null;
|
|
8
|
-
try {
|
|
9
|
-
return decodeURIComponent(value);
|
|
10
|
-
} catch {
|
|
11
|
-
return value;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function getRequestBuffer(req: Request): Buffer {
|
|
16
|
-
return Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function getPrompt(req: Request): string | null {
|
|
20
|
-
return decodeHeader(req.headers["x-ima2-canvas-prompt"]);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function registerCanvasVersionRoutes(app: Express, ctxRaw: RouteRuntimeContext) {
|
|
24
|
-
const ctx = requireRuntimeContext(ctxRaw);
|
|
25
|
-
const rawPng = express.raw({ type: "image/png", limit: ctx.config.server.bodyLimit });
|
|
26
|
-
|
|
27
|
-
app.post("/api/canvas-versions", rawPng, async (req: Request, res: Response) => {
|
|
28
|
-
try {
|
|
29
|
-
const sourceFilename =
|
|
30
|
-
typeof req.query.sourceFilename === "string"
|
|
31
|
-
? req.query.sourceFilename
|
|
32
|
-
: decodeHeader(req.headers["x-ima2-canvas-source-filename"]);
|
|
33
|
-
const item = await createCanvasVersion(ctx, {
|
|
34
|
-
sourceFilename,
|
|
35
|
-
prompt: getPrompt(req),
|
|
36
|
-
buffer: getRequestBuffer(req),
|
|
37
|
-
});
|
|
38
|
-
res.status(201).json({ item });
|
|
39
|
-
} catch (e) {
|
|
40
|
-
const err = errInfo(e);
|
|
41
|
-
res.status(err.status || 500).json({
|
|
42
|
-
error: err.message,
|
|
43
|
-
code: err.code || "CANVAS_VERSION_SAVE_FAILED",
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
app.put("/api/canvas-versions/:filename", rawPng, async (req: Request<{ filename: string }>, res: Response) => {
|
|
49
|
-
try {
|
|
50
|
-
const filename = decodeURIComponent(req.params.filename);
|
|
51
|
-
const sourceFilename =
|
|
52
|
-
typeof req.query.sourceFilename === "string"
|
|
53
|
-
? req.query.sourceFilename
|
|
54
|
-
: decodeHeader(req.headers["x-ima2-canvas-source-filename"]);
|
|
55
|
-
const item = await updateCanvasVersion(ctx, filename, {
|
|
56
|
-
sourceFilename,
|
|
57
|
-
prompt: getPrompt(req),
|
|
58
|
-
buffer: getRequestBuffer(req),
|
|
59
|
-
});
|
|
60
|
-
res.json({ item });
|
|
61
|
-
} catch (e) {
|
|
62
|
-
const err = errInfo(e);
|
|
63
|
-
res.status(err.status || 500).json({
|
|
64
|
-
error: err.message,
|
|
65
|
-
code: err.code || "CANVAS_VERSION_SAVE_FAILED",
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
}
|
package/routes/capabilities.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { Express, Request, Response } from "express";
|
|
2
|
-
import { buildIma2Capabilities } from "../lib/capabilities.js";
|
|
3
|
-
import { requireRuntimeContext, type RouteRuntimeContext } from "../lib/runtimeContext.js";
|
|
4
|
-
|
|
5
|
-
export function registerCapabilitiesRoutes(app: Express, ctxRaw: RouteRuntimeContext) {
|
|
6
|
-
const ctx = requireRuntimeContext(ctxRaw);
|
|
7
|
-
|
|
8
|
-
app.get("/api/capabilities", (_req: Request, res: Response) => {
|
|
9
|
-
res.json(
|
|
10
|
-
buildIma2Capabilities({
|
|
11
|
-
appConfig: ctx.config,
|
|
12
|
-
packageVersion: ctx.packageVersion,
|
|
13
|
-
source: "server",
|
|
14
|
-
server: ctx.serverUrl || `http://localhost:${ctx.serverActualPort || ctx.config.server.port}`,
|
|
15
|
-
}),
|
|
16
|
-
);
|
|
17
|
-
});
|
|
18
|
-
}
|