ima2-gen 1.1.7 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -27
- package/bin/commands/annotate.js +137 -0
- package/bin/commands/annotate.ts +118 -0
- package/bin/commands/cancel.js +37 -33
- package/bin/commands/cancel.ts +45 -0
- package/bin/commands/canvas-versions.js +91 -0
- package/bin/commands/canvas-versions.ts +80 -0
- package/bin/commands/cardnews.js +293 -0
- package/bin/commands/cardnews.ts +248 -0
- package/bin/commands/comfy.js +63 -0
- package/bin/commands/comfy.ts +54 -0
- package/bin/commands/config.js +270 -0
- package/bin/commands/config.ts +265 -0
- package/bin/commands/edit.js +97 -72
- package/bin/commands/edit.ts +116 -0
- package/bin/commands/gen.js +140 -118
- package/bin/commands/gen.ts +176 -0
- package/bin/commands/history.js +164 -0
- package/bin/commands/history.ts +145 -0
- package/bin/commands/ls.js +60 -42
- package/bin/commands/ls.ts +60 -0
- package/bin/commands/metadata.js +45 -0
- package/bin/commands/metadata.ts +36 -0
- package/bin/commands/multimode.js +159 -0
- package/bin/commands/multimode.ts +146 -0
- package/bin/commands/node.js +176 -0
- package/bin/commands/node.ts +157 -0
- package/bin/commands/observability.js +201 -0
- package/bin/commands/observability.ts +176 -0
- package/bin/commands/ping.js +26 -20
- package/bin/commands/ping.ts +29 -0
- package/bin/commands/prompt.js +506 -0
- package/bin/commands/prompt.ts +421 -0
- package/bin/commands/ps.js +78 -71
- package/bin/commands/ps.ts +78 -0
- package/bin/commands/session.js +308 -0
- package/bin/commands/session.ts +265 -0
- package/bin/commands/show.js +75 -40
- package/bin/commands/show.ts +69 -0
- package/bin/ima2.js +324 -310
- package/bin/ima2.ts +444 -0
- package/bin/lib/args.js +75 -66
- package/bin/lib/args.ts +73 -0
- package/bin/lib/browser-id.js +15 -0
- package/bin/lib/browser-id.ts +16 -0
- package/bin/lib/client.js +91 -83
- package/bin/lib/client.ts +109 -0
- package/bin/lib/error-hints.js +14 -17
- package/bin/lib/error-hints.ts +23 -0
- package/bin/lib/files.js +26 -28
- package/bin/lib/files.ts +39 -0
- package/bin/lib/output.js +44 -42
- package/bin/lib/output.ts +58 -0
- package/bin/lib/platform.js +60 -56
- package/bin/lib/platform.ts +97 -0
- package/bin/lib/sse.js +73 -0
- package/bin/lib/sse.ts +73 -0
- package/bin/lib/star-prompt.js +69 -76
- package/bin/lib/star-prompt.ts +97 -0
- package/bin/lib/storage-doctor.js +34 -35
- package/bin/lib/storage-doctor.ts +38 -0
- package/config.js +147 -190
- package/config.ts +331 -0
- package/docs/API.md +48 -8
- package/docs/CLI.md +190 -0
- package/docs/FAQ.ko.md +5 -5
- package/docs/FAQ.md +5 -5
- package/docs/README.ja.md +71 -25
- package/docs/README.ko.md +61 -24
- package/docs/README.zh-CN.md +73 -27
- package/lib/assetLifecycle.js +130 -130
- package/lib/assetLifecycle.ts +142 -0
- package/lib/canvasVersionStore.js +135 -153
- package/lib/canvasVersionStore.ts +181 -0
- package/lib/cardNewsGenerator.js +127 -142
- package/lib/cardNewsGenerator.ts +162 -0
- package/lib/cardNewsJobStore.js +78 -84
- package/lib/cardNewsJobStore.ts +107 -0
- package/lib/cardNewsManifestStore.js +88 -93
- package/lib/cardNewsManifestStore.ts +112 -0
- package/lib/cardNewsPlanner.js +157 -152
- package/lib/cardNewsPlanner.ts +180 -0
- package/lib/cardNewsPlannerClient.js +101 -98
- package/lib/cardNewsPlannerClient.ts +114 -0
- package/lib/cardNewsPlannerPrompt.js +56 -56
- package/lib/cardNewsPlannerPrompt.ts +60 -0
- package/lib/cardNewsPlannerSchema.js +231 -223
- package/lib/cardNewsPlannerSchema.ts +259 -0
- package/lib/cardNewsRoleTemplateStore.js +39 -41
- package/lib/cardNewsRoleTemplateStore.ts +47 -0
- package/lib/cardNewsTemplateStore.js +171 -175
- package/lib/cardNewsTemplateStore.ts +210 -0
- package/lib/codexDetect.js +44 -47
- package/lib/codexDetect.ts +69 -0
- package/lib/comfyBridge.js +164 -184
- package/lib/comfyBridge.ts +214 -0
- package/lib/db.js +41 -51
- package/lib/db.ts +166 -0
- package/lib/errorClassify.js +62 -78
- package/lib/errorClassify.ts +100 -0
- package/lib/generationErrors.js +140 -103
- package/lib/generationErrors.ts +125 -0
- package/lib/historyList.js +149 -147
- package/lib/historyList.ts +164 -0
- package/lib/imageMetadata.js +86 -89
- package/lib/imageMetadata.ts +111 -0
- package/lib/imageMetadataStore.js +46 -51
- package/lib/imageMetadataStore.ts +67 -0
- package/lib/imageModels.js +38 -45
- package/lib/imageModels.ts +52 -0
- package/lib/inflight.js +131 -150
- package/lib/inflight.ts +204 -0
- package/lib/localImportStore.js +105 -0
- package/lib/localImportStore.ts +111 -0
- package/lib/logger.js +105 -112
- package/lib/logger.ts +150 -0
- package/lib/nodeStore.js +65 -64
- package/lib/nodeStore.ts +81 -0
- package/lib/oauthLauncher.js +61 -59
- package/lib/oauthLauncher.ts +64 -0
- package/lib/oauthNormalize.js +15 -19
- package/lib/oauthNormalize.ts +30 -0
- package/lib/oauthProxy.js +834 -832
- package/lib/oauthProxy.ts +995 -0
- package/lib/openDirectory.js +41 -40
- package/lib/openDirectory.ts +45 -0
- package/lib/pngInfo.js +18 -20
- package/lib/pngInfo.ts +26 -0
- package/lib/promptImport/curatedSources.js +135 -0
- package/lib/promptImport/curatedSources.ts +139 -0
- package/lib/promptImport/discoveryRegistry.js +218 -0
- package/lib/promptImport/discoveryRegistry.ts +236 -0
- package/lib/promptImport/errors.js +10 -10
- package/lib/promptImport/errors.ts +18 -0
- package/lib/promptImport/githubDiscovery.js +238 -0
- package/lib/promptImport/githubDiscovery.ts +248 -0
- package/lib/promptImport/githubFolder.js +302 -0
- package/lib/promptImport/githubFolder.ts +308 -0
- package/lib/promptImport/githubSource.js +194 -171
- package/lib/promptImport/githubSource.ts +239 -0
- package/lib/promptImport/gptImageHints.js +61 -0
- package/lib/promptImport/gptImageHints.ts +68 -0
- package/lib/promptImport/parsePromptCandidates.js +110 -112
- package/lib/promptImport/parsePromptCandidates.ts +153 -0
- package/lib/promptImport/promptIndex.js +230 -0
- package/lib/promptImport/promptIndex.ts +248 -0
- package/lib/promptImport/rankPromptCandidates.js +52 -0
- package/lib/promptImport/rankPromptCandidates.ts +49 -0
- package/lib/providerOptions.js +31 -0
- package/lib/providerOptions.ts +41 -0
- package/lib/referenceImageCompress.js +51 -62
- package/lib/referenceImageCompress.ts +75 -0
- package/lib/refs.js +93 -81
- package/lib/refs.ts +117 -0
- package/lib/requestLogger.js +32 -38
- package/lib/requestLogger.ts +48 -0
- package/lib/responsesImageAdapter.js +351 -0
- package/lib/responsesImageAdapter.ts +352 -0
- package/lib/runtimePorts.js +71 -73
- package/lib/runtimePorts.ts +93 -0
- package/lib/sessionStore.js +179 -230
- package/lib/sessionStore.ts +272 -0
- package/lib/storageMigration.js +247 -245
- package/lib/storageMigration.ts +284 -0
- package/lib/styleSheet.js +86 -90
- package/lib/styleSheet.ts +128 -0
- package/lib/systemTrash.js +18 -0
- package/lib/systemTrash.ts +20 -0
- package/package.json +26 -10
- package/routes/annotations.js +76 -79
- package/routes/annotations.ts +95 -0
- package/routes/canvasVersions.js +50 -54
- package/routes/canvasVersions.ts +64 -0
- package/routes/cardNews.js +158 -171
- package/routes/cardNews.ts +183 -0
- package/routes/comfy.js +23 -31
- package/routes/comfy.ts +39 -0
- package/routes/edit.js +183 -214
- package/routes/edit.ts +230 -0
- package/routes/generate.js +269 -291
- package/routes/generate.ts +309 -0
- package/routes/health.js +102 -107
- package/routes/health.ts +114 -0
- package/routes/history.js +136 -144
- package/routes/history.ts +153 -0
- package/routes/imageImport.js +33 -0
- package/routes/imageImport.ts +33 -0
- package/routes/index.js +18 -16
- package/routes/index.ts +35 -0
- package/routes/metadata.js +60 -64
- package/routes/metadata.ts +71 -0
- package/routes/multimode.js +228 -263
- package/routes/multimode.ts +280 -0
- package/routes/nodes.js +378 -424
- package/routes/nodes.ts +455 -0
- package/routes/promptImport.js +291 -152
- package/routes/promptImport.ts +354 -0
- package/routes/prompts.js +333 -360
- package/routes/prompts.ts +379 -0
- package/routes/sessions.js +277 -285
- package/routes/sessions.ts +292 -0
- package/routes/storage.js +29 -31
- package/routes/storage.ts +39 -0
- package/server.js +189 -196
- package/server.ts +235 -0
- package/ui/dist/.vite/manifest.json +101 -0
- package/ui/dist/assets/CardNewsWorkspace-BJOCey7Z.js +2 -0
- package/ui/dist/assets/NodeCanvas-BZV40eAE.css +1 -0
- package/ui/dist/assets/NodeCanvas-C3dzYNsk.js +7 -0
- package/ui/dist/assets/PromptImportDialog-Dqu1VpUh.js +2 -0
- package/ui/dist/assets/PromptImportDiscoverySection-Dg8T9X0L.js +1 -0
- package/ui/dist/assets/PromptImportFolderSection-DBaqsFO4.js +1 -0
- package/ui/dist/assets/PromptLibraryPanel-p5QqR97M.js +2 -0
- package/ui/dist/assets/SettingsWorkspace-B5bSAZ6u.js +1 -0
- package/ui/dist/assets/index-C9cXwiWE.js +25 -0
- package/ui/dist/assets/index-CGMIkZXn.css +1 -0
- package/ui/dist/assets/index-Cvld7dUZ.js +1 -0
- package/ui/dist/index.html +6 -3
- package/assets/screenshot.png +0 -0
- package/assets/screenshots/classic-generate-light.png +0 -0
- package/assets/screenshots/node-graph-branching.png +0 -0
- package/assets/screenshots/settings-oauth-generation.png +0 -0
- package/assets/screenshots/settings-workspace.png +0 -0
- package/assets/screenshots/style-sheet-editor.png +0 -0
- package/integrations/comfyui/ima2_gen_bridge/__pycache__/__init__.cpython-313.pyc +0 -0
- package/integrations/comfyui/ima2_gen_bridge/__pycache__/nodes.cpython-313.pyc +0 -0
- package/ui/dist/assets/index-DARPdT4Q.css +0 -1
- package/ui/dist/assets/index-ht80GMq4.js +0 -31
- package/ui/dist/assets/index-ht80GMq4.js.map +0 -1
package/lib/assetLifecycle.js
CHANGED
|
@@ -1,144 +1,144 @@
|
|
|
1
1
|
import { getDb } from "./db.js";
|
|
2
|
-
import { rename, unlink,
|
|
3
|
-
import {
|
|
2
|
+
import { rename, unlink, access } from "fs/promises";
|
|
3
|
+
import { resolve, sep } from "path";
|
|
4
|
+
import { moveToSystemTrash } from "./systemTrash.js";
|
|
4
5
|
import { config } from "../config.js";
|
|
5
|
-
|
|
6
|
-
const DIR = config.storage.generatedDirName;
|
|
7
|
-
const TRASH = config.storage.trashDirName;
|
|
8
|
-
const TRASH_TTL_MS = config.trash.ttlMs;
|
|
9
|
-
|
|
10
6
|
function resolveInGenerated(rootDir, relPath) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
7
|
+
void rootDir;
|
|
8
|
+
if (typeof relPath !== "string" || relPath.length === 0) {
|
|
9
|
+
const err = new Error("filename required");
|
|
10
|
+
err.status = 400;
|
|
11
|
+
err.code = "INVALID_FILENAME";
|
|
12
|
+
throw err;
|
|
13
|
+
}
|
|
14
|
+
if (relPath.includes("\0")) {
|
|
15
|
+
const err = new Error("invalid filename");
|
|
16
|
+
err.status = 400;
|
|
17
|
+
err.code = "INVALID_FILENAME";
|
|
18
|
+
throw err;
|
|
19
|
+
}
|
|
20
|
+
const baseDir = resolve(config.storage.generatedDir);
|
|
21
|
+
const target = resolve(baseDir, relPath);
|
|
22
|
+
if (target !== baseDir && !target.startsWith(baseDir + sep)) {
|
|
23
|
+
const err = new Error("filename escapes generated/");
|
|
24
|
+
err.status = 400;
|
|
25
|
+
err.code = "INVALID_FILENAME";
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
28
|
+
return target;
|
|
33
29
|
}
|
|
34
|
-
|
|
35
30
|
function nodesReferencingFilename(filename) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
31
|
+
// The client stores imageUrl as `/generated/<encoded filename>` in node data JSON.
|
|
32
|
+
// We scan all sessions' nodes for substring match on the decoded and encoded forms.
|
|
33
|
+
const db = getDb();
|
|
34
|
+
const encoded = encodeURIComponent(filename);
|
|
35
|
+
const rows = db
|
|
36
|
+
.prepare("SELECT session_id AS sessionId, id, data FROM nodes WHERE data LIKE ? OR data LIKE ?")
|
|
37
|
+
.all(`%${filename}%`, `%${encoded}%`);
|
|
38
|
+
return rows;
|
|
44
39
|
}
|
|
45
|
-
|
|
46
40
|
function markNodesAssetMissing(filename) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
41
|
+
const db = getDb();
|
|
42
|
+
const rows = nodesReferencingFilename(filename);
|
|
43
|
+
if (rows.length === 0)
|
|
44
|
+
return { sessionsTouched: 0, nodesTouched: 0 };
|
|
45
|
+
const touchedSessions = new Set();
|
|
46
|
+
const update = db.prepare("UPDATE nodes SET data = ? WHERE session_id = ? AND id = ?");
|
|
47
|
+
const bumpSession = db.prepare("UPDATE sessions SET graph_version = graph_version + 1, updated_at = ? WHERE id = ?");
|
|
48
|
+
const tx = db.transaction(() => {
|
|
49
|
+
for (const r of rows) {
|
|
50
|
+
let data;
|
|
51
|
+
try {
|
|
52
|
+
data = JSON.parse(r.data);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
data = {};
|
|
56
|
+
}
|
|
57
|
+
const imgRef = data?.imageUrl || "";
|
|
58
|
+
if (imgRef.includes(filename) || imgRef.includes(encodeURIComponent(filename))) {
|
|
59
|
+
data.imageUrl = null;
|
|
60
|
+
data.status = "asset-missing";
|
|
61
|
+
update.run(JSON.stringify(data), r.sessionId, r.id);
|
|
62
|
+
touchedSessions.add(r.sessionId);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const t = Date.now();
|
|
66
|
+
for (const sid of touchedSessions)
|
|
67
|
+
bumpSession.run(t, sid);
|
|
68
|
+
});
|
|
69
|
+
tx();
|
|
70
|
+
return { sessionsTouched: touchedSessions.size, nodesTouched: rows.length };
|
|
70
71
|
}
|
|
71
|
-
|
|
72
72
|
export async function trashAsset(rootDir, filename) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
73
|
+
const src = resolveInGenerated(rootDir, filename);
|
|
74
|
+
try {
|
|
75
|
+
await access(src);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
const err = new Error("Asset not found");
|
|
79
|
+
err.status = 404;
|
|
80
|
+
err.code = "ASSET_NOT_FOUND";
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
const sidecar = `${src}.json`;
|
|
84
|
+
const paths = [src];
|
|
85
|
+
try {
|
|
86
|
+
await access(sidecar);
|
|
87
|
+
paths.push(sidecar);
|
|
88
|
+
}
|
|
89
|
+
catch { }
|
|
90
|
+
try {
|
|
91
|
+
await moveToSystemTrash(paths);
|
|
92
|
+
}
|
|
93
|
+
catch (cause) {
|
|
94
|
+
const err = new Error("Could not move asset to system trash");
|
|
95
|
+
err.status = 500;
|
|
96
|
+
err.code = "SYSTEM_TRASH_FAILED";
|
|
97
|
+
err.cause = cause;
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
const summary = markNodesAssetMissing(filename);
|
|
101
|
+
return {
|
|
102
|
+
ok: true,
|
|
103
|
+
filename,
|
|
104
|
+
trash: "system",
|
|
105
|
+
undoableInApp: false,
|
|
106
|
+
sessionsTouched: summary.sessionsTouched,
|
|
107
|
+
nodesTouched: summary.nodesTouched,
|
|
108
|
+
};
|
|
108
109
|
}
|
|
109
|
-
|
|
110
110
|
export async function deleteAssetPermanent(rootDir, filename) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
111
|
+
const src = resolveInGenerated(rootDir, filename);
|
|
112
|
+
try {
|
|
113
|
+
await access(src);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
const err = new Error("Asset not found");
|
|
117
|
+
err.status = 404;
|
|
118
|
+
err.code = "ASSET_NOT_FOUND";
|
|
119
|
+
throw err;
|
|
120
|
+
}
|
|
121
|
+
await unlink(src);
|
|
122
|
+
await unlink(src + ".json").catch(() => { });
|
|
123
|
+
const summary = markNodesAssetMissing(filename);
|
|
124
|
+
return {
|
|
125
|
+
ok: true,
|
|
126
|
+
filename,
|
|
127
|
+
sessionsTouched: summary.sessionsTouched,
|
|
128
|
+
nodesTouched: summary.nodesTouched,
|
|
129
|
+
};
|
|
129
130
|
}
|
|
130
|
-
|
|
131
131
|
export async function restoreAsset(rootDir, trashId, originalFilename) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
132
|
+
void rootDir;
|
|
133
|
+
const trashDir = resolve(config.storage.trashDir);
|
|
134
|
+
const src = resolve(trashDir, trashId);
|
|
135
|
+
if (!src.startsWith(trashDir + sep) && src !== trashDir) {
|
|
136
|
+
const err = new Error("invalid trashId");
|
|
137
|
+
err.status = 400;
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
const dst = resolveInGenerated(rootDir, originalFilename);
|
|
141
|
+
await rename(src, dst);
|
|
142
|
+
await rename(src + ".json", dst + ".json").catch(() => { });
|
|
143
|
+
return { ok: true };
|
|
144
144
|
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { getDb } from "./db.js";
|
|
2
|
+
import { rename, unlink, access } from "fs/promises";
|
|
3
|
+
import { resolve, sep } from "path";
|
|
4
|
+
import { moveToSystemTrash } from "./systemTrash.js";
|
|
5
|
+
import { config } from "../config.js";
|
|
6
|
+
|
|
7
|
+
function resolveInGenerated(rootDir, relPath) {
|
|
8
|
+
void rootDir;
|
|
9
|
+
if (typeof relPath !== "string" || relPath.length === 0) {
|
|
10
|
+
const err: any = new Error("filename required");
|
|
11
|
+
err.status = 400;
|
|
12
|
+
err.code = "INVALID_FILENAME";
|
|
13
|
+
throw err;
|
|
14
|
+
}
|
|
15
|
+
if (relPath.includes("\0")) {
|
|
16
|
+
const err: any = new Error("invalid filename");
|
|
17
|
+
err.status = 400;
|
|
18
|
+
err.code = "INVALID_FILENAME";
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
const baseDir = resolve(config.storage.generatedDir);
|
|
22
|
+
const target = resolve(baseDir, relPath);
|
|
23
|
+
if (target !== baseDir && !target.startsWith(baseDir + sep)) {
|
|
24
|
+
const err: any = new Error("filename escapes generated/");
|
|
25
|
+
err.status = 400;
|
|
26
|
+
err.code = "INVALID_FILENAME";
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
return target;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function nodesReferencingFilename(filename) {
|
|
33
|
+
// The client stores imageUrl as `/generated/<encoded filename>` in node data JSON.
|
|
34
|
+
// We scan all sessions' nodes for substring match on the decoded and encoded forms.
|
|
35
|
+
const db = getDb();
|
|
36
|
+
const encoded = encodeURIComponent(filename);
|
|
37
|
+
const rows = db
|
|
38
|
+
.prepare("SELECT session_id AS sessionId, id, data FROM nodes WHERE data LIKE ? OR data LIKE ?")
|
|
39
|
+
.all(`%${filename}%`, `%${encoded}%`);
|
|
40
|
+
return rows;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function markNodesAssetMissing(filename) {
|
|
44
|
+
const db = getDb();
|
|
45
|
+
const rows = nodesReferencingFilename(filename);
|
|
46
|
+
if (rows.length === 0) return { sessionsTouched: 0, nodesTouched: 0 };
|
|
47
|
+
const touchedSessions = new Set();
|
|
48
|
+
const update = db.prepare("UPDATE nodes SET data = ? WHERE session_id = ? AND id = ?");
|
|
49
|
+
const bumpSession = db.prepare("UPDATE sessions SET graph_version = graph_version + 1, updated_at = ? WHERE id = ?");
|
|
50
|
+
const tx = db.transaction(() => {
|
|
51
|
+
for (const r of rows) {
|
|
52
|
+
let data;
|
|
53
|
+
try { data = JSON.parse(r.data); } catch { data = {}; }
|
|
54
|
+
const imgRef = data?.imageUrl || "";
|
|
55
|
+
if (imgRef.includes(filename) || imgRef.includes(encodeURIComponent(filename))) {
|
|
56
|
+
data.imageUrl = null;
|
|
57
|
+
data.status = "asset-missing";
|
|
58
|
+
update.run(JSON.stringify(data), r.sessionId, r.id);
|
|
59
|
+
touchedSessions.add(r.sessionId);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const t = Date.now();
|
|
63
|
+
for (const sid of touchedSessions) bumpSession.run(t, sid);
|
|
64
|
+
});
|
|
65
|
+
tx();
|
|
66
|
+
return { sessionsTouched: touchedSessions.size, nodesTouched: rows.length };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function trashAsset(rootDir, filename) {
|
|
70
|
+
const src = resolveInGenerated(rootDir, filename);
|
|
71
|
+
try {
|
|
72
|
+
await access(src);
|
|
73
|
+
} catch {
|
|
74
|
+
const err: any = new Error("Asset not found");
|
|
75
|
+
err.status = 404;
|
|
76
|
+
err.code = "ASSET_NOT_FOUND";
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const sidecar = `${src}.json`;
|
|
81
|
+
const paths = [src];
|
|
82
|
+
try {
|
|
83
|
+
await access(sidecar);
|
|
84
|
+
paths.push(sidecar);
|
|
85
|
+
} catch {}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
await moveToSystemTrash(paths);
|
|
89
|
+
} catch (cause) {
|
|
90
|
+
const err: any = new Error("Could not move asset to system trash");
|
|
91
|
+
err.status = 500;
|
|
92
|
+
err.code = "SYSTEM_TRASH_FAILED";
|
|
93
|
+
err.cause = cause;
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const summary = markNodesAssetMissing(filename);
|
|
98
|
+
return {
|
|
99
|
+
ok: true,
|
|
100
|
+
filename,
|
|
101
|
+
trash: "system",
|
|
102
|
+
undoableInApp: false,
|
|
103
|
+
sessionsTouched: summary.sessionsTouched,
|
|
104
|
+
nodesTouched: summary.nodesTouched,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function deleteAssetPermanent(rootDir, filename) {
|
|
109
|
+
const src = resolveInGenerated(rootDir, filename);
|
|
110
|
+
try {
|
|
111
|
+
await access(src);
|
|
112
|
+
} catch {
|
|
113
|
+
const err: any = new Error("Asset not found");
|
|
114
|
+
err.status = 404;
|
|
115
|
+
err.code = "ASSET_NOT_FOUND";
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
await unlink(src);
|
|
119
|
+
await unlink(src + ".json").catch(() => {});
|
|
120
|
+
const summary = markNodesAssetMissing(filename);
|
|
121
|
+
return {
|
|
122
|
+
ok: true,
|
|
123
|
+
filename,
|
|
124
|
+
sessionsTouched: summary.sessionsTouched,
|
|
125
|
+
nodesTouched: summary.nodesTouched,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function restoreAsset(rootDir, trashId, originalFilename) {
|
|
130
|
+
void rootDir;
|
|
131
|
+
const trashDir = resolve(config.storage.trashDir);
|
|
132
|
+
const src = resolve(trashDir, trashId);
|
|
133
|
+
if (!src.startsWith(trashDir + sep) && src !== trashDir) {
|
|
134
|
+
const err: any = new Error("invalid trashId");
|
|
135
|
+
err.status = 400;
|
|
136
|
+
throw err;
|
|
137
|
+
}
|
|
138
|
+
const dst = resolveInGenerated(rootDir, originalFilename);
|
|
139
|
+
await rename(src, dst);
|
|
140
|
+
await rename(src + ".json", dst + ".json").catch(() => {});
|
|
141
|
+
return { ok: true };
|
|
142
|
+
}
|