ima2-gen 1.1.7 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -27
- package/bin/commands/annotate.js +137 -0
- package/bin/commands/annotate.ts +118 -0
- package/bin/commands/cancel.js +37 -33
- package/bin/commands/cancel.ts +45 -0
- package/bin/commands/canvas-versions.js +91 -0
- package/bin/commands/canvas-versions.ts +80 -0
- package/bin/commands/cardnews.js +293 -0
- package/bin/commands/cardnews.ts +248 -0
- package/bin/commands/comfy.js +63 -0
- package/bin/commands/comfy.ts +54 -0
- package/bin/commands/config.js +270 -0
- package/bin/commands/config.ts +265 -0
- package/bin/commands/edit.js +97 -72
- package/bin/commands/edit.ts +116 -0
- package/bin/commands/gen.js +140 -118
- package/bin/commands/gen.ts +176 -0
- package/bin/commands/history.js +164 -0
- package/bin/commands/history.ts +145 -0
- package/bin/commands/ls.js +60 -42
- package/bin/commands/ls.ts +60 -0
- package/bin/commands/metadata.js +45 -0
- package/bin/commands/metadata.ts +36 -0
- package/bin/commands/multimode.js +159 -0
- package/bin/commands/multimode.ts +146 -0
- package/bin/commands/node.js +176 -0
- package/bin/commands/node.ts +157 -0
- package/bin/commands/observability.js +201 -0
- package/bin/commands/observability.ts +176 -0
- package/bin/commands/ping.js +26 -20
- package/bin/commands/ping.ts +29 -0
- package/bin/commands/prompt.js +506 -0
- package/bin/commands/prompt.ts +421 -0
- package/bin/commands/ps.js +78 -71
- package/bin/commands/ps.ts +78 -0
- package/bin/commands/session.js +308 -0
- package/bin/commands/session.ts +265 -0
- package/bin/commands/show.js +75 -40
- package/bin/commands/show.ts +69 -0
- package/bin/ima2.js +324 -310
- package/bin/ima2.ts +444 -0
- package/bin/lib/args.js +75 -66
- package/bin/lib/args.ts +73 -0
- package/bin/lib/browser-id.js +15 -0
- package/bin/lib/browser-id.ts +16 -0
- package/bin/lib/client.js +91 -83
- package/bin/lib/client.ts +109 -0
- package/bin/lib/error-hints.js +14 -17
- package/bin/lib/error-hints.ts +23 -0
- package/bin/lib/files.js +26 -28
- package/bin/lib/files.ts +39 -0
- package/bin/lib/output.js +44 -42
- package/bin/lib/output.ts +58 -0
- package/bin/lib/platform.js +60 -56
- package/bin/lib/platform.ts +97 -0
- package/bin/lib/sse.js +73 -0
- package/bin/lib/sse.ts +73 -0
- package/bin/lib/star-prompt.js +69 -76
- package/bin/lib/star-prompt.ts +97 -0
- package/bin/lib/storage-doctor.js +34 -35
- package/bin/lib/storage-doctor.ts +38 -0
- package/config.js +147 -190
- package/config.ts +331 -0
- package/docs/API.md +48 -8
- package/docs/CLI.md +190 -0
- package/docs/FAQ.ko.md +5 -5
- package/docs/FAQ.md +5 -5
- package/docs/README.ja.md +71 -25
- package/docs/README.ko.md +61 -24
- package/docs/README.zh-CN.md +73 -27
- package/lib/assetLifecycle.js +130 -130
- package/lib/assetLifecycle.ts +142 -0
- package/lib/canvasVersionStore.js +135 -153
- package/lib/canvasVersionStore.ts +181 -0
- package/lib/cardNewsGenerator.js +127 -142
- package/lib/cardNewsGenerator.ts +162 -0
- package/lib/cardNewsJobStore.js +78 -84
- package/lib/cardNewsJobStore.ts +107 -0
- package/lib/cardNewsManifestStore.js +88 -93
- package/lib/cardNewsManifestStore.ts +112 -0
- package/lib/cardNewsPlanner.js +157 -152
- package/lib/cardNewsPlanner.ts +180 -0
- package/lib/cardNewsPlannerClient.js +101 -98
- package/lib/cardNewsPlannerClient.ts +114 -0
- package/lib/cardNewsPlannerPrompt.js +56 -56
- package/lib/cardNewsPlannerPrompt.ts +60 -0
- package/lib/cardNewsPlannerSchema.js +231 -223
- package/lib/cardNewsPlannerSchema.ts +259 -0
- package/lib/cardNewsRoleTemplateStore.js +39 -41
- package/lib/cardNewsRoleTemplateStore.ts +47 -0
- package/lib/cardNewsTemplateStore.js +171 -175
- package/lib/cardNewsTemplateStore.ts +210 -0
- package/lib/codexDetect.js +44 -47
- package/lib/codexDetect.ts +69 -0
- package/lib/comfyBridge.js +164 -184
- package/lib/comfyBridge.ts +214 -0
- package/lib/db.js +41 -51
- package/lib/db.ts +166 -0
- package/lib/errorClassify.js +62 -78
- package/lib/errorClassify.ts +100 -0
- package/lib/generationErrors.js +140 -103
- package/lib/generationErrors.ts +125 -0
- package/lib/historyList.js +149 -147
- package/lib/historyList.ts +164 -0
- package/lib/imageMetadata.js +86 -89
- package/lib/imageMetadata.ts +111 -0
- package/lib/imageMetadataStore.js +46 -51
- package/lib/imageMetadataStore.ts +67 -0
- package/lib/imageModels.js +38 -45
- package/lib/imageModels.ts +52 -0
- package/lib/inflight.js +131 -150
- package/lib/inflight.ts +204 -0
- package/lib/localImportStore.js +105 -0
- package/lib/localImportStore.ts +111 -0
- package/lib/logger.js +105 -112
- package/lib/logger.ts +150 -0
- package/lib/nodeStore.js +65 -64
- package/lib/nodeStore.ts +81 -0
- package/lib/oauthLauncher.js +61 -59
- package/lib/oauthLauncher.ts +64 -0
- package/lib/oauthNormalize.js +15 -19
- package/lib/oauthNormalize.ts +30 -0
- package/lib/oauthProxy.js +834 -832
- package/lib/oauthProxy.ts +995 -0
- package/lib/openDirectory.js +41 -40
- package/lib/openDirectory.ts +45 -0
- package/lib/pngInfo.js +18 -20
- package/lib/pngInfo.ts +26 -0
- package/lib/promptImport/curatedSources.js +135 -0
- package/lib/promptImport/curatedSources.ts +139 -0
- package/lib/promptImport/discoveryRegistry.js +218 -0
- package/lib/promptImport/discoveryRegistry.ts +236 -0
- package/lib/promptImport/errors.js +10 -10
- package/lib/promptImport/errors.ts +18 -0
- package/lib/promptImport/githubDiscovery.js +238 -0
- package/lib/promptImport/githubDiscovery.ts +248 -0
- package/lib/promptImport/githubFolder.js +302 -0
- package/lib/promptImport/githubFolder.ts +308 -0
- package/lib/promptImport/githubSource.js +194 -171
- package/lib/promptImport/githubSource.ts +239 -0
- package/lib/promptImport/gptImageHints.js +61 -0
- package/lib/promptImport/gptImageHints.ts +68 -0
- package/lib/promptImport/parsePromptCandidates.js +110 -112
- package/lib/promptImport/parsePromptCandidates.ts +153 -0
- package/lib/promptImport/promptIndex.js +230 -0
- package/lib/promptImport/promptIndex.ts +248 -0
- package/lib/promptImport/rankPromptCandidates.js +52 -0
- package/lib/promptImport/rankPromptCandidates.ts +49 -0
- package/lib/providerOptions.js +31 -0
- package/lib/providerOptions.ts +41 -0
- package/lib/referenceImageCompress.js +51 -62
- package/lib/referenceImageCompress.ts +75 -0
- package/lib/refs.js +93 -81
- package/lib/refs.ts +117 -0
- package/lib/requestLogger.js +32 -38
- package/lib/requestLogger.ts +48 -0
- package/lib/responsesImageAdapter.js +351 -0
- package/lib/responsesImageAdapter.ts +352 -0
- package/lib/runtimePorts.js +71 -73
- package/lib/runtimePorts.ts +93 -0
- package/lib/sessionStore.js +179 -230
- package/lib/sessionStore.ts +272 -0
- package/lib/storageMigration.js +247 -245
- package/lib/storageMigration.ts +284 -0
- package/lib/styleSheet.js +86 -90
- package/lib/styleSheet.ts +128 -0
- package/lib/systemTrash.js +18 -0
- package/lib/systemTrash.ts +20 -0
- package/package.json +26 -10
- package/routes/annotations.js +76 -79
- package/routes/annotations.ts +95 -0
- package/routes/canvasVersions.js +50 -54
- package/routes/canvasVersions.ts +64 -0
- package/routes/cardNews.js +158 -171
- package/routes/cardNews.ts +183 -0
- package/routes/comfy.js +23 -31
- package/routes/comfy.ts +39 -0
- package/routes/edit.js +183 -214
- package/routes/edit.ts +230 -0
- package/routes/generate.js +269 -291
- package/routes/generate.ts +309 -0
- package/routes/health.js +102 -107
- package/routes/health.ts +114 -0
- package/routes/history.js +136 -144
- package/routes/history.ts +153 -0
- package/routes/imageImport.js +33 -0
- package/routes/imageImport.ts +33 -0
- package/routes/index.js +18 -16
- package/routes/index.ts +35 -0
- package/routes/metadata.js +60 -64
- package/routes/metadata.ts +71 -0
- package/routes/multimode.js +228 -263
- package/routes/multimode.ts +280 -0
- package/routes/nodes.js +378 -424
- package/routes/nodes.ts +455 -0
- package/routes/promptImport.js +291 -152
- package/routes/promptImport.ts +354 -0
- package/routes/prompts.js +333 -360
- package/routes/prompts.ts +379 -0
- package/routes/sessions.js +277 -285
- package/routes/sessions.ts +292 -0
- package/routes/storage.js +29 -31
- package/routes/storage.ts +39 -0
- package/server.js +189 -196
- package/server.ts +235 -0
- package/ui/dist/.vite/manifest.json +101 -0
- package/ui/dist/assets/CardNewsWorkspace-BJOCey7Z.js +2 -0
- package/ui/dist/assets/NodeCanvas-BZV40eAE.css +1 -0
- package/ui/dist/assets/NodeCanvas-C3dzYNsk.js +7 -0
- package/ui/dist/assets/PromptImportDialog-Dqu1VpUh.js +2 -0
- package/ui/dist/assets/PromptImportDiscoverySection-Dg8T9X0L.js +1 -0
- package/ui/dist/assets/PromptImportFolderSection-DBaqsFO4.js +1 -0
- package/ui/dist/assets/PromptLibraryPanel-p5QqR97M.js +2 -0
- package/ui/dist/assets/SettingsWorkspace-B5bSAZ6u.js +1 -0
- package/ui/dist/assets/index-C9cXwiWE.js +25 -0
- package/ui/dist/assets/index-CGMIkZXn.css +1 -0
- package/ui/dist/assets/index-Cvld7dUZ.js +1 -0
- package/ui/dist/index.html +6 -3
- package/assets/screenshot.png +0 -0
- package/assets/screenshots/classic-generate-light.png +0 -0
- package/assets/screenshots/node-graph-branching.png +0 -0
- package/assets/screenshots/settings-oauth-generation.png +0 -0
- package/assets/screenshots/settings-workspace.png +0 -0
- package/assets/screenshots/style-sheet-editor.png +0 -0
- package/integrations/comfyui/ima2_gen_bridge/__pycache__/__init__.cpython-313.pyc +0 -0
- package/integrations/comfyui/ima2_gen_bridge/__pycache__/nodes.cpython-313.pyc +0 -0
- package/ui/dist/assets/index-DARPdT4Q.css +0 -1
- package/ui/dist/assets/index-ht80GMq4.js +0 -31
- package/ui/dist/assets/index-ht80GMq4.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ima2-gen",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.9",
|
|
4
4
|
"description": "Local OAuth image generation studio with classic and node workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/ima2.js serve",
|
|
11
11
|
"dev": "node scripts/dev.mjs",
|
|
12
|
-
"dev:server": "
|
|
12
|
+
"dev:server": "tsx watch server.ts",
|
|
13
13
|
"ui:install": "cd ui && npm install",
|
|
14
14
|
"ui:dev": "cd ui && npm run dev",
|
|
15
15
|
"ui:build": "cd ui && npm run build",
|
|
@@ -17,11 +17,15 @@
|
|
|
17
17
|
"test": "node scripts/run-tests.mjs",
|
|
18
18
|
"test:package-install": "node --test tests/package-install-smoke.mjs",
|
|
19
19
|
"setup": "node bin/ima2.js setup",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
20
|
+
"prepack": "npm run ui:build && npm run build:server && npm run build:cli",
|
|
21
|
+
"prepublishOnly": "npm run typecheck && npm run ui:build && npm run build:server && npm run build:cli && npm run lint:pkg && npm run test:package-install",
|
|
22
|
+
"lint:pkg": "node -e \"const p=require('./package.json'); const req=['name','version','bin']; for(const k of req){if(!p[k])throw new Error('missing '+k)} const mustInclude=['bin/','lib/','routes/','integrations/comfyui/ima2_gen_bridge/__init__.py','integrations/comfyui/ima2_gen_bridge/nodes.py','integrations/comfyui/ima2_gen_bridge/README.md','assets/card-news/templates/','server.js']; for(const f of mustInclude){if(!p.files.includes(f))throw new Error('files[] must include '+f)}\"",
|
|
22
23
|
"release:patch": "npm version patch && npm publish && git push origin main --tags",
|
|
23
24
|
"release:minor": "npm version minor && npm publish && git push origin main --tags",
|
|
24
|
-
"release:major": "npm version major && npm publish && git push origin main --tags"
|
|
25
|
+
"release:major": "npm version major && npm publish && git push origin main --tags",
|
|
26
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
27
|
+
"build:server": "tsc -p tsconfig.build.json",
|
|
28
|
+
"build:cli": "tsc -p tsconfig.bin.json && node scripts/fix-shebangs.mjs"
|
|
25
29
|
},
|
|
26
30
|
"keywords": [
|
|
27
31
|
"openai",
|
|
@@ -39,14 +43,18 @@
|
|
|
39
43
|
"bin/",
|
|
40
44
|
"lib/",
|
|
41
45
|
"routes/",
|
|
42
|
-
"integrations/",
|
|
46
|
+
"integrations/comfyui/ima2_gen_bridge/__init__.py",
|
|
47
|
+
"integrations/comfyui/ima2_gen_bridge/nodes.py",
|
|
48
|
+
"integrations/comfyui/ima2_gen_bridge/README.md",
|
|
43
49
|
"ui/dist/",
|
|
44
50
|
"docs/",
|
|
45
|
-
"assets/",
|
|
46
|
-
"server.
|
|
47
|
-
"config.
|
|
51
|
+
"assets/card-news/templates/",
|
|
52
|
+
"server.ts",
|
|
53
|
+
"config.ts",
|
|
48
54
|
".env.example",
|
|
49
|
-
"README.md"
|
|
55
|
+
"README.md",
|
|
56
|
+
"server.js",
|
|
57
|
+
"config.js"
|
|
50
58
|
],
|
|
51
59
|
"engines": {
|
|
52
60
|
"node": ">=20"
|
|
@@ -58,6 +66,14 @@
|
|
|
58
66
|
"openai": "^5.8.2",
|
|
59
67
|
"openai-oauth": "^1.0.2",
|
|
60
68
|
"sharp": "^0.34.5",
|
|
69
|
+
"trash": "^10.1.1",
|
|
61
70
|
"ulid": "^3.0.2"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
74
|
+
"@types/express": "^5.0.6",
|
|
75
|
+
"@types/node": "^22.19.17",
|
|
76
|
+
"tsx": "^4.21.0",
|
|
77
|
+
"typescript": "^5.9.3"
|
|
62
78
|
}
|
|
63
79
|
}
|
package/routes/annotations.js
CHANGED
|
@@ -1,69 +1,64 @@
|
|
|
1
1
|
import { getDb } from "../lib/db.js";
|
|
2
|
-
|
|
3
2
|
const MAX_ANNOTATION_PAYLOAD_CHARS = 256 * 1024;
|
|
4
|
-
|
|
5
3
|
function getBrowserId(req) {
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
const browserId = req.headers["x-ima2-browser-id"];
|
|
5
|
+
return typeof browserId === "string" && browserId.trim() ? browserId.trim() : null;
|
|
8
6
|
}
|
|
9
|
-
|
|
10
7
|
function isSafeFilename(filename) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
!filename.includes("\\")
|
|
18
|
-
);
|
|
8
|
+
return (typeof filename === "string" &&
|
|
9
|
+
filename.length > 0 &&
|
|
10
|
+
filename.length <= 240 &&
|
|
11
|
+
!filename.includes("..") &&
|
|
12
|
+
!filename.startsWith("/") &&
|
|
13
|
+
!filename.includes("\\"));
|
|
19
14
|
}
|
|
20
|
-
|
|
21
15
|
function normalizePayload(value) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
return { payload: normalized, text };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function registerAnnotationRoutes(app) {
|
|
38
|
-
app.get("/api/annotations/:filename", (req, res) => {
|
|
39
|
-
try {
|
|
40
|
-
const browserId = getBrowserId(req);
|
|
41
|
-
const filename = decodeURIComponent(req.params.filename);
|
|
42
|
-
if (!browserId) return res.status(400).json({ error: "X-Ima2-Browser-Id header is required" });
|
|
43
|
-
if (!isSafeFilename(filename)) return res.status(400).json({ error: "invalid filename" });
|
|
44
|
-
|
|
45
|
-
const row = getDb()
|
|
46
|
-
.prepare("SELECT payload FROM image_annotations WHERE browser_id = ? AND filename = ?")
|
|
47
|
-
.get(browserId, filename);
|
|
48
|
-
const annotations = row ? JSON.parse(row.payload) : null;
|
|
49
|
-
res.json({ annotations });
|
|
50
|
-
} catch (err) {
|
|
51
|
-
res.status(500).json({ error: err.message });
|
|
16
|
+
const payload = value?.annotations ?? value;
|
|
17
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
18
|
+
return { error: "annotations payload is required" };
|
|
19
|
+
}
|
|
20
|
+
const paths = Array.isArray(payload.paths) ? payload.paths : [];
|
|
21
|
+
const boxes = Array.isArray(payload.boxes) ? payload.boxes : [];
|
|
22
|
+
const memos = Array.isArray(payload.memos) ? payload.memos : [];
|
|
23
|
+
const normalized = { paths, boxes, memos };
|
|
24
|
+
const text = JSON.stringify(normalized);
|
|
25
|
+
if (text.length > MAX_ANNOTATION_PAYLOAD_CHARS) {
|
|
26
|
+
return { error: "annotations payload is too large" };
|
|
52
27
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
28
|
+
return { payload: normalized, text };
|
|
29
|
+
}
|
|
30
|
+
export function registerAnnotationRoutes(app, _ctx) {
|
|
31
|
+
app.get("/api/annotations/:filename", (req, res) => {
|
|
32
|
+
try {
|
|
33
|
+
const browserId = getBrowserId(req);
|
|
34
|
+
const filename = decodeURIComponent(req.params.filename);
|
|
35
|
+
if (!browserId)
|
|
36
|
+
return res.status(400).json({ error: "X-Ima2-Browser-Id header is required" });
|
|
37
|
+
if (!isSafeFilename(filename))
|
|
38
|
+
return res.status(400).json({ error: "invalid filename" });
|
|
39
|
+
const row = getDb()
|
|
40
|
+
.prepare("SELECT payload FROM image_annotations WHERE browser_id = ? AND filename = ?")
|
|
41
|
+
.get(browserId, filename);
|
|
42
|
+
const annotations = row ? JSON.parse(row.payload) : null;
|
|
43
|
+
res.json({ annotations });
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
res.status(500).json({ error: err.message });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
app.put("/api/annotations/:filename", (req, res) => {
|
|
50
|
+
try {
|
|
51
|
+
const browserId = getBrowserId(req);
|
|
52
|
+
const filename = decodeURIComponent(req.params.filename);
|
|
53
|
+
if (!browserId)
|
|
54
|
+
return res.status(400).json({ error: "X-Ima2-Browser-Id header is required" });
|
|
55
|
+
if (!isSafeFilename(filename))
|
|
56
|
+
return res.status(400).json({ error: "invalid filename" });
|
|
57
|
+
const normalized = normalizePayload(req.body);
|
|
58
|
+
if (normalized.error)
|
|
59
|
+
return res.status(400).json({ error: normalized.error });
|
|
60
|
+
const id = `${browserId}:${filename}`;
|
|
61
|
+
getDb().prepare(`
|
|
67
62
|
INSERT INTO image_annotations (id, browser_id, filename, payload, schema_version, updated_at)
|
|
68
63
|
VALUES (?, ?, ?, ?, 1, unixepoch())
|
|
69
64
|
ON CONFLICT(browser_id, filename) DO UPDATE SET
|
|
@@ -71,25 +66,27 @@ export function registerAnnotationRoutes(app) {
|
|
|
71
66
|
schema_version = excluded.schema_version,
|
|
72
67
|
updated_at = unixepoch()
|
|
73
68
|
`).run(id, browserId, filename, normalized.text);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
69
|
+
res.json({ ok: true });
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
res.status(500).json({ error: err.message });
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
app.delete("/api/annotations/:filename", (req, res) => {
|
|
76
|
+
try {
|
|
77
|
+
const browserId = getBrowserId(req);
|
|
78
|
+
const filename = decodeURIComponent(req.params.filename);
|
|
79
|
+
if (!browserId)
|
|
80
|
+
return res.status(400).json({ error: "X-Ima2-Browser-Id header is required" });
|
|
81
|
+
if (!isSafeFilename(filename))
|
|
82
|
+
return res.status(400).json({ error: "invalid filename" });
|
|
83
|
+
getDb()
|
|
84
|
+
.prepare("DELETE FROM image_annotations WHERE browser_id = ? AND filename = ?")
|
|
85
|
+
.run(browserId, filename);
|
|
86
|
+
res.json({ ok: true });
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
res.status(500).json({ error: err.message });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
95
92
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { getDb } from "../lib/db.js";
|
|
2
|
+
|
|
3
|
+
const MAX_ANNOTATION_PAYLOAD_CHARS = 256 * 1024;
|
|
4
|
+
|
|
5
|
+
function getBrowserId(req) {
|
|
6
|
+
const browserId = req.headers["x-ima2-browser-id"];
|
|
7
|
+
return typeof browserId === "string" && browserId.trim() ? browserId.trim() : null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isSafeFilename(filename) {
|
|
11
|
+
return (
|
|
12
|
+
typeof filename === "string" &&
|
|
13
|
+
filename.length > 0 &&
|
|
14
|
+
filename.length <= 240 &&
|
|
15
|
+
!filename.includes("..") &&
|
|
16
|
+
!filename.startsWith("/") &&
|
|
17
|
+
!filename.includes("\\")
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizePayload(value) {
|
|
22
|
+
const payload = value?.annotations ?? value;
|
|
23
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
24
|
+
return { error: "annotations payload is required" };
|
|
25
|
+
}
|
|
26
|
+
const paths = Array.isArray(payload.paths) ? payload.paths : [];
|
|
27
|
+
const boxes = Array.isArray(payload.boxes) ? payload.boxes : [];
|
|
28
|
+
const memos = Array.isArray(payload.memos) ? payload.memos : [];
|
|
29
|
+
const normalized = { paths, boxes, memos };
|
|
30
|
+
const text = JSON.stringify(normalized);
|
|
31
|
+
if (text.length > MAX_ANNOTATION_PAYLOAD_CHARS) {
|
|
32
|
+
return { error: "annotations payload is too large" };
|
|
33
|
+
}
|
|
34
|
+
return { payload: normalized, text };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function registerAnnotationRoutes(app, _ctx?: any) {
|
|
38
|
+
app.get("/api/annotations/:filename", (req, res) => {
|
|
39
|
+
try {
|
|
40
|
+
const browserId = getBrowserId(req);
|
|
41
|
+
const filename = decodeURIComponent(req.params.filename);
|
|
42
|
+
if (!browserId) return res.status(400).json({ error: "X-Ima2-Browser-Id header is required" });
|
|
43
|
+
if (!isSafeFilename(filename)) return res.status(400).json({ error: "invalid filename" });
|
|
44
|
+
|
|
45
|
+
const row = getDb()
|
|
46
|
+
.prepare("SELECT payload FROM image_annotations WHERE browser_id = ? AND filename = ?")
|
|
47
|
+
.get(browserId, filename);
|
|
48
|
+
const annotations = row ? JSON.parse(row.payload) : null;
|
|
49
|
+
res.json({ annotations });
|
|
50
|
+
} catch (err) {
|
|
51
|
+
res.status(500).json({ error: err.message });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
app.put("/api/annotations/:filename", (req, res) => {
|
|
56
|
+
try {
|
|
57
|
+
const browserId = getBrowserId(req);
|
|
58
|
+
const filename = decodeURIComponent(req.params.filename);
|
|
59
|
+
if (!browserId) return res.status(400).json({ error: "X-Ima2-Browser-Id header is required" });
|
|
60
|
+
if (!isSafeFilename(filename)) return res.status(400).json({ error: "invalid filename" });
|
|
61
|
+
|
|
62
|
+
const normalized = normalizePayload(req.body);
|
|
63
|
+
if (normalized.error) return res.status(400).json({ error: normalized.error });
|
|
64
|
+
|
|
65
|
+
const id = `${browserId}:${filename}`;
|
|
66
|
+
getDb().prepare(`
|
|
67
|
+
INSERT INTO image_annotations (id, browser_id, filename, payload, schema_version, updated_at)
|
|
68
|
+
VALUES (?, ?, ?, ?, 1, unixepoch())
|
|
69
|
+
ON CONFLICT(browser_id, filename) DO UPDATE SET
|
|
70
|
+
payload = excluded.payload,
|
|
71
|
+
schema_version = excluded.schema_version,
|
|
72
|
+
updated_at = unixepoch()
|
|
73
|
+
`).run(id, browserId, filename, normalized.text);
|
|
74
|
+
res.json({ ok: true });
|
|
75
|
+
} catch (err) {
|
|
76
|
+
res.status(500).json({ error: err.message });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
app.delete("/api/annotations/:filename", (req, res) => {
|
|
81
|
+
try {
|
|
82
|
+
const browserId = getBrowserId(req);
|
|
83
|
+
const filename = decodeURIComponent(req.params.filename);
|
|
84
|
+
if (!browserId) return res.status(400).json({ error: "X-Ima2-Browser-Id header is required" });
|
|
85
|
+
if (!isSafeFilename(filename)) return res.status(400).json({ error: "invalid filename" });
|
|
86
|
+
|
|
87
|
+
getDb()
|
|
88
|
+
.prepare("DELETE FROM image_annotations WHERE browser_id = ? AND filename = ?")
|
|
89
|
+
.run(browserId, filename);
|
|
90
|
+
res.json({ ok: true });
|
|
91
|
+
} catch (err) {
|
|
92
|
+
res.status(500).json({ error: err.message });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
package/routes/canvasVersions.js
CHANGED
|
@@ -1,64 +1,60 @@
|
|
|
1
1
|
import express from "express";
|
|
2
2
|
import { createCanvasVersion, updateCanvasVersion } from "../lib/canvasVersionStore.js";
|
|
3
|
-
|
|
4
3
|
function decodeHeader(value) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
if (typeof value !== "string" || !value)
|
|
5
|
+
return null;
|
|
6
|
+
try {
|
|
7
|
+
return decodeURIComponent(value);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
11
12
|
}
|
|
12
|
-
|
|
13
13
|
function getRequestBuffer(req) {
|
|
14
|
-
|
|
14
|
+
return Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0);
|
|
15
15
|
}
|
|
16
|
-
|
|
17
16
|
function getPrompt(req) {
|
|
18
|
-
|
|
17
|
+
return decodeHeader(req.headers["x-ima2-canvas-prompt"]);
|
|
19
18
|
}
|
|
20
|
-
|
|
21
19
|
export function registerCanvasVersionRoutes(app, ctx) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
});
|
|
20
|
+
const rawPng = express.raw({ type: "image/png", limit: ctx.config.server.bodyLimit });
|
|
21
|
+
app.post("/api/canvas-versions", rawPng, async (req, res) => {
|
|
22
|
+
try {
|
|
23
|
+
const sourceFilename = typeof req.query.sourceFilename === "string"
|
|
24
|
+
? req.query.sourceFilename
|
|
25
|
+
: decodeHeader(req.headers["x-ima2-canvas-source-filename"]);
|
|
26
|
+
const item = await createCanvasVersion(ctx, {
|
|
27
|
+
sourceFilename,
|
|
28
|
+
prompt: getPrompt(req),
|
|
29
|
+
buffer: getRequestBuffer(req),
|
|
30
|
+
});
|
|
31
|
+
res.status(201).json({ item });
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
res.status(err.status || 500).json({
|
|
35
|
+
error: err.message,
|
|
36
|
+
code: err.code || "CANVAS_VERSION_SAVE_FAILED",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
app.put("/api/canvas-versions/:filename", rawPng, async (req, res) => {
|
|
41
|
+
try {
|
|
42
|
+
const filename = decodeURIComponent(req.params.filename);
|
|
43
|
+
const sourceFilename = typeof req.query.sourceFilename === "string"
|
|
44
|
+
? req.query.sourceFilename
|
|
45
|
+
: decodeHeader(req.headers["x-ima2-canvas-source-filename"]);
|
|
46
|
+
const item = await updateCanvasVersion(ctx, filename, {
|
|
47
|
+
sourceFilename,
|
|
48
|
+
prompt: getPrompt(req),
|
|
49
|
+
buffer: getRequestBuffer(req),
|
|
50
|
+
});
|
|
51
|
+
res.json({ item });
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
res.status(err.status || 500).json({
|
|
55
|
+
error: err.message,
|
|
56
|
+
code: err.code || "CANVAS_VERSION_SAVE_FAILED",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
64
60
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { createCanvasVersion, updateCanvasVersion } from "../lib/canvasVersionStore.js";
|
|
3
|
+
|
|
4
|
+
function decodeHeader(value) {
|
|
5
|
+
if (typeof value !== "string" || !value) return null;
|
|
6
|
+
try {
|
|
7
|
+
return decodeURIComponent(value);
|
|
8
|
+
} catch {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getRequestBuffer(req) {
|
|
14
|
+
return Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getPrompt(req) {
|
|
18
|
+
return decodeHeader(req.headers["x-ima2-canvas-prompt"]);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function registerCanvasVersionRoutes(app, ctx) {
|
|
22
|
+
const rawPng = express.raw({ type: "image/png", limit: ctx.config.server.bodyLimit });
|
|
23
|
+
|
|
24
|
+
app.post("/api/canvas-versions", rawPng, async (req, res) => {
|
|
25
|
+
try {
|
|
26
|
+
const sourceFilename =
|
|
27
|
+
typeof req.query.sourceFilename === "string"
|
|
28
|
+
? req.query.sourceFilename
|
|
29
|
+
: decodeHeader(req.headers["x-ima2-canvas-source-filename"]);
|
|
30
|
+
const item = await createCanvasVersion(ctx, {
|
|
31
|
+
sourceFilename,
|
|
32
|
+
prompt: getPrompt(req),
|
|
33
|
+
buffer: getRequestBuffer(req),
|
|
34
|
+
});
|
|
35
|
+
res.status(201).json({ item });
|
|
36
|
+
} catch (err) {
|
|
37
|
+
res.status(err.status || 500).json({
|
|
38
|
+
error: err.message,
|
|
39
|
+
code: err.code || "CANVAS_VERSION_SAVE_FAILED",
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
app.put("/api/canvas-versions/:filename", rawPng, async (req, res) => {
|
|
45
|
+
try {
|
|
46
|
+
const filename = decodeURIComponent(req.params.filename);
|
|
47
|
+
const sourceFilename =
|
|
48
|
+
typeof req.query.sourceFilename === "string"
|
|
49
|
+
? req.query.sourceFilename
|
|
50
|
+
: decodeHeader(req.headers["x-ima2-canvas-source-filename"]);
|
|
51
|
+
const item = await updateCanvasVersion(ctx, filename, {
|
|
52
|
+
sourceFilename,
|
|
53
|
+
prompt: getPrompt(req),
|
|
54
|
+
buffer: getRequestBuffer(req),
|
|
55
|
+
});
|
|
56
|
+
res.json({ item });
|
|
57
|
+
} catch (err) {
|
|
58
|
+
res.status(err.status || 500).json({
|
|
59
|
+
error: err.message,
|
|
60
|
+
code: err.code || "CANVAS_VERSION_SAVE_FAILED",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|