ima2-gen 1.1.8 → 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 -243
- 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 +87 -93
- 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 +125 -129
- package/lib/promptImport/curatedSources.ts +139 -0
- package/lib/promptImport/discoveryRegistry.js +185 -203
- 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 +209 -219
- package/lib/promptImport/githubDiscovery.ts +248 -0
- package/lib/promptImport/githubFolder.js +253 -259
- package/lib/promptImport/githubFolder.ts +308 -0
- package/lib/promptImport/githubSource.js +189 -200
- package/lib/promptImport/githubSource.ts +239 -0
- package/lib/promptImport/gptImageHints.js +49 -56
- package/lib/promptImport/gptImageHints.ts +68 -0
- package/lib/promptImport/parsePromptCandidates.js +108 -123
- package/lib/promptImport/parsePromptCandidates.ts +153 -0
- package/lib/promptImport/promptIndex.js +190 -208
- package/lib/promptImport/promptIndex.ts +248 -0
- package/lib/promptImport/rankPromptCandidates.js +46 -43
- 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 +27 -27
- package/routes/imageImport.ts +33 -0
- package/routes/index.js +17 -17
- 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 +284 -324
- 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 +2 -2
- package/assets/phase-a-bg-cleanup-test.png +0 -0
- 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-BDffwmLs.css +0 -1
- package/ui/dist/assets/index-D0fdHLkJ.js +0 -31
- package/ui/dist/assets/index-D0fdHLkJ.js.map +0 -1
package/lib/inflight.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { config } from "../config.js";
|
|
2
2
|
import { getDb } from "./db.js";
|
|
3
3
|
import { logEvent } from "./logger.js";
|
|
4
|
-
|
|
5
4
|
// SQLite-backed inflight job registry.
|
|
6
5
|
// Tracks generation requests that are currently running on the server so clients
|
|
7
6
|
// can reconcile optimistic UI state after a reload or across tabs.
|
|
@@ -9,18 +8,17 @@ import { logEvent } from "./logger.js";
|
|
|
9
8
|
// A restarted process cannot continue the original upstream fetch, but keeping
|
|
10
9
|
// metadata durable lets the UI reconcile requestIds and eventually prune stale
|
|
11
10
|
// work without losing the recovery breadcrumb.
|
|
12
|
-
|
|
13
11
|
const terminalJobs = new Map(); // requestId -> terminal snapshot, active-only API stays default
|
|
14
|
-
|
|
15
12
|
// Phases: "queued" → "streaming" (upstream connection open, waiting for image)
|
|
16
13
|
// → "decoding" (b64 received, writing to disk)
|
|
17
14
|
export function startJob({ requestId, kind, prompt, meta = {} }) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
if (!requestId)
|
|
16
|
+
return;
|
|
17
|
+
const startedAt = Date.now();
|
|
18
|
+
const normalizedPrompt = typeof prompt === "string" ? prompt.slice(0, 500) : "";
|
|
19
|
+
const normalizedMeta = normalizeMeta(meta);
|
|
20
|
+
getDb()
|
|
21
|
+
.prepare(`
|
|
24
22
|
INSERT OR REPLACE INTO inflight (
|
|
25
23
|
request_id,
|
|
26
24
|
kind,
|
|
@@ -35,170 +33,153 @@ export function startJob({ requestId, kind, prompt, meta = {} }) {
|
|
|
35
33
|
)
|
|
36
34
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
37
35
|
`)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
startedAt,
|
|
49
|
-
);
|
|
50
|
-
terminalJobs.delete(requestId);
|
|
51
|
-
logEvent("inflight", "start", {
|
|
52
|
-
requestId,
|
|
53
|
-
kind,
|
|
54
|
-
sessionId: normalizedMeta.sessionId || null,
|
|
55
|
-
parentNodeId: normalizedMeta.parentNodeId || null,
|
|
56
|
-
clientNodeId: normalizedMeta.clientNodeId || null,
|
|
57
|
-
promptChars: typeof prompt === "string" ? prompt.length : 0,
|
|
58
|
-
});
|
|
36
|
+
.run(requestId, kind, normalizedPrompt, JSON.stringify(normalizedMeta), stringOrNull(normalizedMeta.sessionId), stringOrNull(normalizedMeta.parentNodeId), stringOrNull(normalizedMeta.clientNodeId), startedAt, "queued", startedAt);
|
|
37
|
+
terminalJobs.delete(requestId);
|
|
38
|
+
logEvent("inflight", "start", {
|
|
39
|
+
requestId,
|
|
40
|
+
kind,
|
|
41
|
+
sessionId: normalizedMeta.sessionId || null,
|
|
42
|
+
parentNodeId: normalizedMeta.parentNodeId || null,
|
|
43
|
+
clientNodeId: normalizedMeta.clientNodeId || null,
|
|
44
|
+
promptChars: typeof prompt === "string" ? prompt.length : 0,
|
|
45
|
+
});
|
|
59
46
|
}
|
|
60
|
-
|
|
61
47
|
export function setJobPhase(requestId, phase) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
48
|
+
if (!requestId)
|
|
49
|
+
return;
|
|
50
|
+
const j = getJob(requestId);
|
|
51
|
+
if (!j)
|
|
52
|
+
return;
|
|
53
|
+
getDb()
|
|
54
|
+
.prepare("UPDATE inflight SET phase = ?, phase_at = ? WHERE request_id = ?")
|
|
55
|
+
.run(phase, Date.now(), requestId);
|
|
56
|
+
logEvent("inflight", "phase", { requestId, kind: j.kind, phase });
|
|
69
57
|
}
|
|
70
|
-
|
|
71
58
|
export function finishJob(requestId, options = {}) {
|
|
72
|
-
|
|
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
|
-
|
|
59
|
+
if (!requestId)
|
|
60
|
+
return;
|
|
61
|
+
const j = getJob(requestId);
|
|
62
|
+
if (j) {
|
|
63
|
+
const finishedAt = Date.now();
|
|
64
|
+
const status = options.canceled ? "canceled" : options.status || "completed";
|
|
65
|
+
terminalJobs.set(requestId, {
|
|
66
|
+
requestId,
|
|
67
|
+
kind: j.kind,
|
|
68
|
+
status,
|
|
69
|
+
startedAt: j.startedAt,
|
|
70
|
+
finishedAt,
|
|
71
|
+
durationMs: finishedAt - j.startedAt,
|
|
72
|
+
phase: j.phase,
|
|
73
|
+
phaseAt: j.phaseAt,
|
|
74
|
+
httpStatus: options.httpStatus,
|
|
75
|
+
errorCode: options.errorCode,
|
|
76
|
+
meta: {
|
|
77
|
+
...j.meta,
|
|
78
|
+
...(options.meta || {}),
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
logEvent("inflight", "finish", {
|
|
82
|
+
requestId,
|
|
83
|
+
kind: j.kind,
|
|
84
|
+
status,
|
|
85
|
+
durationMs: finishedAt - j.startedAt,
|
|
86
|
+
httpStatus: options.httpStatus,
|
|
87
|
+
errorCode: options.errorCode,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
getDb().prepare("DELETE FROM inflight WHERE request_id = ?").run(requestId);
|
|
91
|
+
reapTerminalJobs();
|
|
104
92
|
}
|
|
105
|
-
|
|
106
93
|
function reapTerminalJobs() {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
for (const [id, j] of terminalJobs) {
|
|
96
|
+
if (now - j.finishedAt > config.inflight.terminalTtlMs)
|
|
97
|
+
terminalJobs.delete(id);
|
|
98
|
+
}
|
|
111
99
|
}
|
|
112
|
-
|
|
113
100
|
export function listJobs(filters = {}) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
101
|
+
purgeStaleJobs();
|
|
102
|
+
const { kind, sessionId } = filters;
|
|
103
|
+
const clauses = [];
|
|
104
|
+
const params = [];
|
|
105
|
+
if (kind) {
|
|
106
|
+
clauses.push("kind = ?");
|
|
107
|
+
params.push(kind);
|
|
108
|
+
}
|
|
109
|
+
if (sessionId) {
|
|
110
|
+
clauses.push("session_id = ?");
|
|
111
|
+
params.push(sessionId);
|
|
112
|
+
}
|
|
113
|
+
const where = clauses.length ? ` WHERE ${clauses.join(" AND ")}` : "";
|
|
114
|
+
return getDb()
|
|
115
|
+
.prepare(`SELECT * FROM inflight${where} ORDER BY started_at ASC`)
|
|
116
|
+
.all(...params)
|
|
117
|
+
.map(rowToJob);
|
|
131
118
|
}
|
|
132
|
-
|
|
133
119
|
export function listTerminalJobs(filters = {}) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
120
|
+
reapTerminalJobs();
|
|
121
|
+
const { kind, sessionId } = filters;
|
|
122
|
+
return Array.from(terminalJobs.values())
|
|
123
|
+
.filter((j) => {
|
|
124
|
+
if (kind && j.kind !== kind)
|
|
125
|
+
return false;
|
|
126
|
+
if (sessionId && j.meta?.sessionId !== sessionId)
|
|
127
|
+
return false;
|
|
128
|
+
return true;
|
|
141
129
|
})
|
|
142
|
-
|
|
130
|
+
.sort((a, b) => b.finishedAt - a.finishedAt);
|
|
143
131
|
}
|
|
144
|
-
|
|
145
132
|
export function _resetForTests() {
|
|
146
|
-
|
|
147
|
-
|
|
133
|
+
getDb().prepare("DELETE FROM inflight").run();
|
|
134
|
+
terminalJobs.clear();
|
|
148
135
|
}
|
|
149
|
-
|
|
150
136
|
export function purgeStaleJobs(now = Date.now()) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
137
|
+
getDb()
|
|
138
|
+
.prepare("DELETE FROM inflight WHERE started_at < ?")
|
|
139
|
+
.run(now - config.inflight.ttlMs);
|
|
154
140
|
}
|
|
155
|
-
|
|
156
141
|
function getJob(requestId) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
142
|
+
const row = getDb()
|
|
143
|
+
.prepare("SELECT * FROM inflight WHERE request_id = ?")
|
|
144
|
+
.get(requestId);
|
|
145
|
+
return row ? rowToJob(row) : null;
|
|
161
146
|
}
|
|
162
|
-
|
|
163
147
|
function rowToJob(row) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
stringOrNull(row.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
phaseAt: Number(row.phase_at || row.started_at),
|
|
183
|
-
};
|
|
148
|
+
const meta = normalizeMeta(parseMeta(row.meta));
|
|
149
|
+
const sessionId = stringOrNull(row.session_id) ?? stringOrNull(meta.sessionId);
|
|
150
|
+
const parentNodeId = stringOrNull(row.parent_node_id) ?? stringOrNull(meta.parentNodeId);
|
|
151
|
+
const clientNodeId = stringOrNull(row.client_node_id) ?? stringOrNull(meta.clientNodeId);
|
|
152
|
+
return {
|
|
153
|
+
requestId: row.request_id,
|
|
154
|
+
kind: row.kind,
|
|
155
|
+
prompt: row.prompt || "",
|
|
156
|
+
meta: {
|
|
157
|
+
...meta,
|
|
158
|
+
...(sessionId ? { sessionId } : {}),
|
|
159
|
+
...(parentNodeId ? { parentNodeId } : {}),
|
|
160
|
+
...(clientNodeId ? { clientNodeId } : {}),
|
|
161
|
+
},
|
|
162
|
+
startedAt: Number(row.started_at),
|
|
163
|
+
phase: row.phase || "queued",
|
|
164
|
+
phaseAt: Number(row.phase_at || row.started_at),
|
|
165
|
+
};
|
|
184
166
|
}
|
|
185
|
-
|
|
186
167
|
function parseMeta(raw) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
168
|
+
if (typeof raw !== "string" || !raw)
|
|
169
|
+
return {};
|
|
170
|
+
try {
|
|
171
|
+
const parsed = JSON.parse(raw);
|
|
172
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
173
|
+
? parsed
|
|
174
|
+
: {};
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return {};
|
|
178
|
+
}
|
|
196
179
|
}
|
|
197
|
-
|
|
198
180
|
function normalizeMeta(meta) {
|
|
199
|
-
|
|
181
|
+
return meta && typeof meta === "object" && !Array.isArray(meta) ? meta : {};
|
|
200
182
|
}
|
|
201
|
-
|
|
202
183
|
function stringOrNull(value) {
|
|
203
|
-
|
|
184
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
204
185
|
}
|
package/lib/inflight.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { config } from "../config.js";
|
|
2
|
+
import { getDb } from "./db.js";
|
|
3
|
+
import { logEvent } from "./logger.js";
|
|
4
|
+
|
|
5
|
+
// SQLite-backed inflight job registry.
|
|
6
|
+
// Tracks generation requests that are currently running on the server so clients
|
|
7
|
+
// can reconcile optimistic UI state after a reload or across tabs.
|
|
8
|
+
//
|
|
9
|
+
// A restarted process cannot continue the original upstream fetch, but keeping
|
|
10
|
+
// metadata durable lets the UI reconcile requestIds and eventually prune stale
|
|
11
|
+
// work without losing the recovery breadcrumb.
|
|
12
|
+
|
|
13
|
+
const terminalJobs = new Map(); // requestId -> terminal snapshot, active-only API stays default
|
|
14
|
+
|
|
15
|
+
// Phases: "queued" → "streaming" (upstream connection open, waiting for image)
|
|
16
|
+
// → "decoding" (b64 received, writing to disk)
|
|
17
|
+
export function startJob({ requestId, kind, prompt, meta = {} }) {
|
|
18
|
+
if (!requestId) return;
|
|
19
|
+
const startedAt = Date.now();
|
|
20
|
+
const normalizedPrompt = typeof prompt === "string" ? prompt.slice(0, 500) : "";
|
|
21
|
+
const normalizedMeta = normalizeMeta(meta);
|
|
22
|
+
getDb()
|
|
23
|
+
.prepare(`
|
|
24
|
+
INSERT OR REPLACE INTO inflight (
|
|
25
|
+
request_id,
|
|
26
|
+
kind,
|
|
27
|
+
prompt,
|
|
28
|
+
meta,
|
|
29
|
+
session_id,
|
|
30
|
+
parent_node_id,
|
|
31
|
+
client_node_id,
|
|
32
|
+
started_at,
|
|
33
|
+
phase,
|
|
34
|
+
phase_at
|
|
35
|
+
)
|
|
36
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
37
|
+
`)
|
|
38
|
+
.run(
|
|
39
|
+
requestId,
|
|
40
|
+
kind,
|
|
41
|
+
normalizedPrompt,
|
|
42
|
+
JSON.stringify(normalizedMeta),
|
|
43
|
+
stringOrNull(normalizedMeta.sessionId),
|
|
44
|
+
stringOrNull(normalizedMeta.parentNodeId),
|
|
45
|
+
stringOrNull(normalizedMeta.clientNodeId),
|
|
46
|
+
startedAt,
|
|
47
|
+
"queued",
|
|
48
|
+
startedAt,
|
|
49
|
+
);
|
|
50
|
+
terminalJobs.delete(requestId);
|
|
51
|
+
logEvent("inflight", "start", {
|
|
52
|
+
requestId,
|
|
53
|
+
kind,
|
|
54
|
+
sessionId: normalizedMeta.sessionId || null,
|
|
55
|
+
parentNodeId: normalizedMeta.parentNodeId || null,
|
|
56
|
+
clientNodeId: normalizedMeta.clientNodeId || null,
|
|
57
|
+
promptChars: typeof prompt === "string" ? prompt.length : 0,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function setJobPhase(requestId, phase) {
|
|
62
|
+
if (!requestId) return;
|
|
63
|
+
const j = getJob(requestId);
|
|
64
|
+
if (!j) return;
|
|
65
|
+
getDb()
|
|
66
|
+
.prepare("UPDATE inflight SET phase = ?, phase_at = ? WHERE request_id = ?")
|
|
67
|
+
.run(phase, Date.now(), requestId);
|
|
68
|
+
logEvent("inflight", "phase", { requestId, kind: j.kind, phase });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function finishJob(requestId, options: any = {}) {
|
|
72
|
+
if (!requestId) return;
|
|
73
|
+
const j = getJob(requestId);
|
|
74
|
+
if (j) {
|
|
75
|
+
const finishedAt = Date.now();
|
|
76
|
+
const status = options.canceled ? "canceled" : options.status || "completed";
|
|
77
|
+
terminalJobs.set(requestId, {
|
|
78
|
+
requestId,
|
|
79
|
+
kind: j.kind,
|
|
80
|
+
status,
|
|
81
|
+
startedAt: j.startedAt,
|
|
82
|
+
finishedAt,
|
|
83
|
+
durationMs: finishedAt - j.startedAt,
|
|
84
|
+
phase: j.phase,
|
|
85
|
+
phaseAt: j.phaseAt,
|
|
86
|
+
httpStatus: options.httpStatus,
|
|
87
|
+
errorCode: options.errorCode,
|
|
88
|
+
meta: {
|
|
89
|
+
...j.meta,
|
|
90
|
+
...(options.meta || {}),
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
logEvent("inflight", "finish", {
|
|
94
|
+
requestId,
|
|
95
|
+
kind: j.kind,
|
|
96
|
+
status,
|
|
97
|
+
durationMs: finishedAt - j.startedAt,
|
|
98
|
+
httpStatus: options.httpStatus,
|
|
99
|
+
errorCode: options.errorCode,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
getDb().prepare("DELETE FROM inflight WHERE request_id = ?").run(requestId);
|
|
103
|
+
reapTerminalJobs();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function reapTerminalJobs() {
|
|
107
|
+
const now = Date.now();
|
|
108
|
+
for (const [id, j] of terminalJobs) {
|
|
109
|
+
if (now - j.finishedAt > config.inflight.terminalTtlMs) terminalJobs.delete(id);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function listJobs(filters: any = {}) {
|
|
114
|
+
purgeStaleJobs();
|
|
115
|
+
const { kind, sessionId } = filters;
|
|
116
|
+
const clauses = [];
|
|
117
|
+
const params = [];
|
|
118
|
+
if (kind) {
|
|
119
|
+
clauses.push("kind = ?");
|
|
120
|
+
params.push(kind);
|
|
121
|
+
}
|
|
122
|
+
if (sessionId) {
|
|
123
|
+
clauses.push("session_id = ?");
|
|
124
|
+
params.push(sessionId);
|
|
125
|
+
}
|
|
126
|
+
const where = clauses.length ? ` WHERE ${clauses.join(" AND ")}` : "";
|
|
127
|
+
return getDb()
|
|
128
|
+
.prepare(`SELECT * FROM inflight${where} ORDER BY started_at ASC`)
|
|
129
|
+
.all(...params)
|
|
130
|
+
.map(rowToJob);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function listTerminalJobs(filters: any = {}) {
|
|
134
|
+
reapTerminalJobs();
|
|
135
|
+
const { kind, sessionId } = filters;
|
|
136
|
+
return Array.from(terminalJobs.values())
|
|
137
|
+
.filter((j) => {
|
|
138
|
+
if (kind && j.kind !== kind) return false;
|
|
139
|
+
if (sessionId && j.meta?.sessionId !== sessionId) return false;
|
|
140
|
+
return true;
|
|
141
|
+
})
|
|
142
|
+
.sort((a, b) => b.finishedAt - a.finishedAt);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function _resetForTests() {
|
|
146
|
+
getDb().prepare("DELETE FROM inflight").run();
|
|
147
|
+
terminalJobs.clear();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function purgeStaleJobs(now = Date.now()) {
|
|
151
|
+
getDb()
|
|
152
|
+
.prepare("DELETE FROM inflight WHERE started_at < ?")
|
|
153
|
+
.run(now - config.inflight.ttlMs);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function getJob(requestId) {
|
|
157
|
+
const row = getDb()
|
|
158
|
+
.prepare("SELECT * FROM inflight WHERE request_id = ?")
|
|
159
|
+
.get(requestId);
|
|
160
|
+
return row ? rowToJob(row) : null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function rowToJob(row) {
|
|
164
|
+
const meta = normalizeMeta(parseMeta(row.meta));
|
|
165
|
+
const sessionId = stringOrNull(row.session_id) ?? stringOrNull(meta.sessionId);
|
|
166
|
+
const parentNodeId =
|
|
167
|
+
stringOrNull(row.parent_node_id) ?? stringOrNull(meta.parentNodeId);
|
|
168
|
+
const clientNodeId =
|
|
169
|
+
stringOrNull(row.client_node_id) ?? stringOrNull(meta.clientNodeId);
|
|
170
|
+
return {
|
|
171
|
+
requestId: row.request_id,
|
|
172
|
+
kind: row.kind,
|
|
173
|
+
prompt: row.prompt || "",
|
|
174
|
+
meta: {
|
|
175
|
+
...meta,
|
|
176
|
+
...(sessionId ? { sessionId } : {}),
|
|
177
|
+
...(parentNodeId ? { parentNodeId } : {}),
|
|
178
|
+
...(clientNodeId ? { clientNodeId } : {}),
|
|
179
|
+
},
|
|
180
|
+
startedAt: Number(row.started_at),
|
|
181
|
+
phase: row.phase || "queued",
|
|
182
|
+
phaseAt: Number(row.phase_at || row.started_at),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function parseMeta(raw) {
|
|
187
|
+
if (typeof raw !== "string" || !raw) return {};
|
|
188
|
+
try {
|
|
189
|
+
const parsed = JSON.parse(raw);
|
|
190
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
191
|
+
? parsed
|
|
192
|
+
: {};
|
|
193
|
+
} catch {
|
|
194
|
+
return {};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function normalizeMeta(meta) {
|
|
199
|
+
return meta && typeof meta === "object" && !Array.isArray(meta) ? meta : {};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function stringOrNull(value) {
|
|
203
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
204
|
+
}
|