ima2-gen 1.1.0 → 1.1.2
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 +47 -7
- package/assets/card-news/templates/academy-lesson-square/base.png +0 -0
- package/assets/card-news/templates/academy-lesson-square/preview.png +0 -0
- package/assets/card-news/templates/academy-lesson-square/template.json +20 -0
- package/assets/card-news/templates/clean-report-square/base.png +0 -0
- package/assets/card-news/templates/clean-report-square/preview.png +0 -0
- package/assets/card-news/templates/clean-report-square/template.json +20 -0
- package/bin/commands/cancel.js +45 -0
- package/bin/commands/edit.js +33 -4
- package/bin/commands/gen.js +26 -3
- package/bin/commands/ps.js +48 -16
- package/bin/ima2.js +56 -12
- package/bin/lib/client.js +4 -1
- package/bin/lib/error-hints.js +23 -0
- package/bin/lib/output.js +10 -0
- package/config.js +19 -1
- package/docs/API.md +67 -0
- package/docs/FAQ.ko.md +248 -0
- package/docs/FAQ.md +256 -0
- package/docs/README.ja.md +4 -0
- package/docs/README.ko.md +14 -1
- package/docs/README.zh-CN.md +4 -0
- package/docs/RECOVER_OLD_IMAGES.md +2 -0
- package/lib/cardNewsGenerator.js +162 -0
- package/lib/cardNewsJobStore.js +107 -0
- package/lib/cardNewsManifestStore.js +112 -0
- package/lib/cardNewsPlanner.js +180 -0
- package/lib/cardNewsPlannerClient.js +112 -0
- package/lib/cardNewsPlannerPrompt.js +60 -0
- package/lib/cardNewsPlannerSchema.js +259 -0
- package/lib/cardNewsRoleTemplateStore.js +47 -0
- package/lib/cardNewsTemplateStore.js +210 -0
- package/lib/db.js +20 -3
- package/lib/errorClassify.js +2 -2
- package/lib/generationErrors.js +51 -0
- package/lib/historyList.js +82 -8
- package/lib/inflight.js +117 -34
- package/lib/logger.js +37 -3
- package/lib/oauthLauncher.js +52 -19
- package/lib/oauthProxy.js +81 -14
- package/lib/requestLogger.js +48 -0
- package/lib/runtimePorts.js +93 -0
- package/lib/sessionStore.js +48 -7
- package/package.json +3 -2
- package/routes/cardNews.js +183 -0
- package/routes/edit.js +1 -1
- package/routes/generate.js +10 -10
- package/routes/health.js +27 -3
- package/routes/index.js +2 -0
- package/routes/nodes.js +93 -26
- package/server.js +91 -18
- package/ui/dist/assets/index-BjX_nzuK.js +23 -0
- package/ui/dist/assets/index-BjX_nzuK.js.map +1 -0
- package/ui/dist/assets/index-DHyUax4_.css +1 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/index-CqpVoXpZ.css +0 -1
- package/ui/dist/assets/index-IHSd1z1a.js +0 -22
- package/ui/dist/assets/index-IHSd1z1a.js.map +0 -1
package/routes/nodes.js
CHANGED
|
@@ -12,6 +12,7 @@ import { classifyUpstreamError } from "../lib/errorClassify.js";
|
|
|
12
12
|
import { normalizeOAuthParams } from "../lib/oauthNormalize.js";
|
|
13
13
|
import { normalizeImageModel } from "../lib/imageModels.js";
|
|
14
14
|
import { generateViaOAuth, editViaOAuth } from "../lib/oauthProxy.js";
|
|
15
|
+
import { normalizeGenerationFailure } from "../lib/generationErrors.js";
|
|
15
16
|
import { getStyleSheet } from "../lib/sessionStore.js";
|
|
16
17
|
import { renderStyleSheetPrefix } from "../lib/styleSheet.js";
|
|
17
18
|
import { logEvent, logError } from "../lib/logger.js";
|
|
@@ -33,12 +34,13 @@ function writeSse(res, event, data) {
|
|
|
33
34
|
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
function writeNodeError(res, status, code, message, parentNodeId) {
|
|
37
|
+
function writeNodeError(res, status, code, message, parentNodeId, details = {}) {
|
|
37
38
|
if (res.headersSent) {
|
|
38
39
|
writeSse(res, "error", {
|
|
39
40
|
error: { code, message },
|
|
40
41
|
parentNodeId,
|
|
41
42
|
status,
|
|
43
|
+
...details,
|
|
42
44
|
});
|
|
43
45
|
res.end();
|
|
44
46
|
return;
|
|
@@ -46,6 +48,7 @@ function writeNodeError(res, status, code, message, parentNodeId) {
|
|
|
46
48
|
res.status(status).json({
|
|
47
49
|
error: { code, message },
|
|
48
50
|
parentNodeId,
|
|
51
|
+
...details,
|
|
49
52
|
});
|
|
50
53
|
}
|
|
51
54
|
|
|
@@ -58,7 +61,7 @@ export function registerNodeRoutes(app, ctx) {
|
|
|
58
61
|
const body = req.body || {};
|
|
59
62
|
const streamResponse = wantsSse(req);
|
|
60
63
|
const parentNodeId = body.parentNodeId ?? null;
|
|
61
|
-
const requestId = typeof body.requestId === "string" ? body.requestId :
|
|
64
|
+
const requestId = typeof body.requestId === "string" ? body.requestId : req.id;
|
|
62
65
|
const sessionId = typeof body.sessionId === "string" ? body.sessionId : null;
|
|
63
66
|
const clientNodeId = typeof body.clientNodeId === "string" ? body.clientNodeId : null;
|
|
64
67
|
let finishMeta = {};
|
|
@@ -82,6 +85,8 @@ export function registerNodeRoutes(app, ctx) {
|
|
|
82
85
|
references = [],
|
|
83
86
|
externalSrc = null,
|
|
84
87
|
mode: promptMode = "auto",
|
|
88
|
+
contextMode: rawContextMode = "parent-plus-refs",
|
|
89
|
+
searchMode: rawSearchMode = "off",
|
|
85
90
|
model: rawModel,
|
|
86
91
|
} = body;
|
|
87
92
|
const { provider = "oauth" } = body;
|
|
@@ -98,6 +103,19 @@ export function registerNodeRoutes(app, ctx) {
|
|
|
98
103
|
}
|
|
99
104
|
const imageModel = modelCheck.model;
|
|
100
105
|
const normalizedPromptMode = promptMode === "direct" ? "direct" : "auto";
|
|
106
|
+
const contextMode = ["parent-plus-refs", "parent-only", "ancestry"].includes(rawContextMode)
|
|
107
|
+
? rawContextMode
|
|
108
|
+
: "parent-plus-refs";
|
|
109
|
+
const searchMode = ["off", "auto", "on"].includes(rawSearchMode) ? rawSearchMode : "off";
|
|
110
|
+
if (contextMode === "ancestry") {
|
|
111
|
+
finishStatus = "error";
|
|
112
|
+
finishHttpStatus = 400;
|
|
113
|
+
finishErrorCode = "CONTEXT_MODE_UNSUPPORTED";
|
|
114
|
+
return res.status(400).json({
|
|
115
|
+
error: { code: "CONTEXT_MODE_UNSUPPORTED", message: "Ancestry context is not supported yet." },
|
|
116
|
+
parentNodeId,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
101
119
|
|
|
102
120
|
if (provider === "api") {
|
|
103
121
|
finishStatus = "error";
|
|
@@ -128,18 +146,6 @@ export function registerNodeRoutes(app, ctx) {
|
|
|
128
146
|
parentNodeId,
|
|
129
147
|
});
|
|
130
148
|
}
|
|
131
|
-
if ((parentNodeId || externalSrc) && refCheck.refs.length > 0) {
|
|
132
|
-
finishStatus = "error";
|
|
133
|
-
finishHttpStatus = 400;
|
|
134
|
-
finishErrorCode = "NODE_REFS_UNSUPPORTED_FOR_EDIT";
|
|
135
|
-
return res.status(400).json({
|
|
136
|
-
error: {
|
|
137
|
-
code: "NODE_REFS_UNSUPPORTED_FOR_EDIT",
|
|
138
|
-
message: "Extra references are only supported for root node generation.",
|
|
139
|
-
},
|
|
140
|
-
parentNodeId,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
149
|
const moderationCheck = validateModeration(ctx, moderation);
|
|
144
150
|
if (moderationCheck.error) {
|
|
145
151
|
finishStatus = "error";
|
|
@@ -173,9 +179,14 @@ export function registerNodeRoutes(app, ctx) {
|
|
|
173
179
|
} else if (typeof externalSrc === "string" && externalSrc.length > 0) {
|
|
174
180
|
parentB64 = await loadAssetB64(ctx.rootDir, externalSrc, ctx.config.storage.generatedDir);
|
|
175
181
|
}
|
|
182
|
+
const operation = parentB64 ? "edit" : "generate";
|
|
183
|
+
const refsForRequest = contextMode === "parent-only" ? [] : refCheck.refs;
|
|
184
|
+
const webSearchEnabled = !parentB64 || searchMode === "on";
|
|
185
|
+
const parentImagePresent = !!parentB64;
|
|
186
|
+
const inputImageCount = (parentImagePresent ? 1 : 0) + refsForRequest.length;
|
|
176
187
|
logEvent("node", "request", {
|
|
177
188
|
requestId,
|
|
178
|
-
operation
|
|
189
|
+
operation,
|
|
179
190
|
sessionId,
|
|
180
191
|
parentNodeId,
|
|
181
192
|
clientNodeId,
|
|
@@ -183,7 +194,12 @@ export function registerNodeRoutes(app, ctx) {
|
|
|
183
194
|
model: imageModel,
|
|
184
195
|
size,
|
|
185
196
|
moderation,
|
|
186
|
-
refs:
|
|
197
|
+
refs: refsForRequest.length,
|
|
198
|
+
inputImageCount,
|
|
199
|
+
parentImagePresent,
|
|
200
|
+
contextMode,
|
|
201
|
+
searchMode,
|
|
202
|
+
webSearchEnabled,
|
|
187
203
|
promptChars: prompt.length,
|
|
188
204
|
promptMode: normalizedPromptMode,
|
|
189
205
|
styleSheetApplied: !!styleSheetApplied,
|
|
@@ -203,14 +219,32 @@ export function registerNodeRoutes(app, ctx) {
|
|
|
203
219
|
let lastErr;
|
|
204
220
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
205
221
|
try {
|
|
222
|
+
logEvent("node", "attempt", {
|
|
223
|
+
requestId,
|
|
224
|
+
attempt,
|
|
225
|
+
operation,
|
|
226
|
+
sessionId,
|
|
227
|
+
parentNodeId,
|
|
228
|
+
clientNodeId,
|
|
229
|
+
model: imageModel,
|
|
230
|
+
moderation,
|
|
231
|
+
quality,
|
|
232
|
+
size,
|
|
233
|
+
refs: refsForRequest.length,
|
|
234
|
+
inputImageCount,
|
|
235
|
+
parentImagePresent,
|
|
236
|
+
contextMode,
|
|
237
|
+
searchMode,
|
|
238
|
+
webSearchEnabled,
|
|
239
|
+
});
|
|
206
240
|
const r = parentB64
|
|
207
|
-
? await editViaOAuth(effectivePrompt, parentB64, quality, size, moderation, normalizedPromptMode, ctx, requestId, { model: imageModel })
|
|
241
|
+
? await editViaOAuth(effectivePrompt, parentB64, quality, size, moderation, normalizedPromptMode, ctx, requestId, { model: imageModel, references: refsForRequest, searchMode: searchMode === "on" ? "on" : "off" })
|
|
208
242
|
: await generateViaOAuth(
|
|
209
243
|
effectivePrompt,
|
|
210
244
|
quality,
|
|
211
245
|
size,
|
|
212
246
|
moderation,
|
|
213
|
-
|
|
247
|
+
refsForRequest,
|
|
214
248
|
requestId,
|
|
215
249
|
normalizedPromptMode,
|
|
216
250
|
ctx,
|
|
@@ -239,20 +273,48 @@ export function registerNodeRoutes(app, ctx) {
|
|
|
239
273
|
lastErr = e;
|
|
240
274
|
}
|
|
241
275
|
if (attempt < MAX_RETRIES) {
|
|
242
|
-
logEvent("node", "retry", {
|
|
276
|
+
logEvent("node", "retry", {
|
|
277
|
+
requestId,
|
|
278
|
+
attempt: attempt + 1,
|
|
279
|
+
operation,
|
|
280
|
+
parentNodeId,
|
|
281
|
+
clientNodeId,
|
|
282
|
+
errorCode: lastErr?.code,
|
|
283
|
+
errorEventType: lastErr?.eventType,
|
|
284
|
+
errorEventCount: lastErr?.eventCount,
|
|
285
|
+
});
|
|
243
286
|
}
|
|
244
287
|
}
|
|
245
288
|
|
|
246
289
|
if (!b64) {
|
|
290
|
+
const finalErr = normalizeGenerationFailure(lastErr, {
|
|
291
|
+
safetyMessage: lastErr?.message || "Empty response after retry",
|
|
292
|
+
});
|
|
247
293
|
finishStatus = "error";
|
|
248
|
-
finishHttpStatus =
|
|
249
|
-
finishErrorCode = "
|
|
294
|
+
finishHttpStatus = finalErr.status || 500;
|
|
295
|
+
finishErrorCode = finalErr.code || "NODE_GEN_FAILED";
|
|
296
|
+
logEvent("node", "final_error", {
|
|
297
|
+
requestId,
|
|
298
|
+
operation,
|
|
299
|
+
finalCode: finishErrorCode,
|
|
300
|
+
upstreamCode: lastErr?.code,
|
|
301
|
+
errorEventType: lastErr?.eventType,
|
|
302
|
+
errorEventCount: lastErr?.eventCount,
|
|
303
|
+
attempts: MAX_RETRIES + 1,
|
|
304
|
+
outerHttpAlreadyCommitted: res.headersSent,
|
|
305
|
+
sseErrorSent: streamResponse,
|
|
306
|
+
});
|
|
250
307
|
return writeNodeError(
|
|
251
308
|
res,
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
309
|
+
finishHttpStatus,
|
|
310
|
+
finishErrorCode,
|
|
311
|
+
finalErr.message,
|
|
255
312
|
parentNodeId,
|
|
313
|
+
{
|
|
314
|
+
upstreamCode: lastErr?.code || null,
|
|
315
|
+
errorEventType: lastErr?.eventType || null,
|
|
316
|
+
errorEventCount: lastErr?.eventCount ?? null,
|
|
317
|
+
},
|
|
256
318
|
);
|
|
257
319
|
}
|
|
258
320
|
|
|
@@ -276,10 +338,12 @@ export function registerNodeRoutes(app, ctx) {
|
|
|
276
338
|
elapsed,
|
|
277
339
|
usage: usage || null,
|
|
278
340
|
webSearchCalls,
|
|
341
|
+
contextMode,
|
|
342
|
+
searchMode,
|
|
279
343
|
provider: "oauth",
|
|
280
344
|
kind: parentB64 ? "edit" : "generate",
|
|
281
345
|
requestId,
|
|
282
|
-
refsCount:
|
|
346
|
+
refsCount: refsForRequest.length,
|
|
283
347
|
quality,
|
|
284
348
|
size,
|
|
285
349
|
format,
|
|
@@ -315,8 +379,11 @@ export function registerNodeRoutes(app, ctx) {
|
|
|
315
379
|
webSearchCalls,
|
|
316
380
|
provider: "oauth",
|
|
317
381
|
model: imageModel,
|
|
382
|
+
size,
|
|
318
383
|
moderation,
|
|
319
|
-
refsCount:
|
|
384
|
+
refsCount: refsForRequest.length,
|
|
385
|
+
contextMode,
|
|
386
|
+
searchMode,
|
|
320
387
|
warnings: qualityWarnings,
|
|
321
388
|
revisedPrompt,
|
|
322
389
|
promptMode: normalizedPromptMode,
|
package/server.js
CHANGED
|
@@ -14,8 +14,12 @@ import { onShutdown } from "./bin/lib/platform.js";
|
|
|
14
14
|
import { ensureDefaultSession } from "./lib/sessionStore.js";
|
|
15
15
|
import { startOAuthProxy } from "./lib/oauthLauncher.js";
|
|
16
16
|
import { migrateGeneratedStorage } from "./lib/storageMigration.js";
|
|
17
|
+
import { purgeStaleJobs } from "./lib/inflight.js";
|
|
18
|
+
import { configureLogger } from "./lib/logger.js";
|
|
19
|
+
import { createRequestLogger } from "./lib/requestLogger.js";
|
|
17
20
|
import { configureRoutes } from "./routes/index.js";
|
|
18
21
|
import { config } from "./config.js";
|
|
22
|
+
import { getServerPort, listenWithPortFallback } from "./lib/runtimePorts.js";
|
|
19
23
|
|
|
20
24
|
const rootDir = dirname(fileURLToPath(import.meta.url));
|
|
21
25
|
|
|
@@ -51,10 +55,28 @@ function readPackageVersion() {
|
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
function setUiStaticHeaders(res, filePath) {
|
|
59
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
60
|
+
if (normalized.endsWith("/index.html")) {
|
|
61
|
+
res.setHeader("Cache-Control", "no-store, max-age=0");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (normalized.includes("/assets/")) {
|
|
65
|
+
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
54
69
|
export function buildApp(ctx) {
|
|
55
70
|
const app = express();
|
|
71
|
+
configureLogger({ level: ctx.config.log.level });
|
|
72
|
+
app.use(createRequestLogger());
|
|
56
73
|
app.use(express.json({ limit: ctx.config.server.bodyLimit }));
|
|
57
|
-
app.use(express.static(join(ctx.rootDir, "ui", "dist")
|
|
74
|
+
app.use(express.static(join(ctx.rootDir, "ui", "dist"), {
|
|
75
|
+
setHeaders: setUiStaticHeaders,
|
|
76
|
+
}));
|
|
77
|
+
app.use("/assets", (_req, res) => {
|
|
78
|
+
res.status(404).type("text/plain").send("Asset not found");
|
|
79
|
+
});
|
|
58
80
|
app.use("/generated", express.static(ctx.config.storage.generatedDir, {
|
|
59
81
|
maxAge: ctx.config.storage.staticMaxAge,
|
|
60
82
|
immutable: true,
|
|
@@ -63,16 +85,33 @@ export function buildApp(ctx) {
|
|
|
63
85
|
return app;
|
|
64
86
|
}
|
|
65
87
|
|
|
88
|
+
function runtimeHostUrl(host) {
|
|
89
|
+
if (!host || host === "0.0.0.0" || host === "::") return "localhost";
|
|
90
|
+
return host;
|
|
91
|
+
}
|
|
92
|
+
|
|
66
93
|
function advertise(ctx) {
|
|
67
94
|
try {
|
|
68
95
|
mkdirSync(dirname(ctx.config.storage.advertiseFile), { recursive: true });
|
|
69
96
|
writeFileSync(
|
|
70
97
|
ctx.config.storage.advertiseFile,
|
|
71
98
|
JSON.stringify({
|
|
72
|
-
port: Number(ctx.config.server.port),
|
|
99
|
+
port: Number(ctx.serverActualPort || ctx.config.server.port),
|
|
100
|
+
url: ctx.serverUrl,
|
|
73
101
|
pid: process.pid,
|
|
74
102
|
startedAt: ctx.startedAt,
|
|
75
103
|
version: ctx.packageVersion,
|
|
104
|
+
backend: {
|
|
105
|
+
configuredPort: Number(ctx.serverConfiguredPort || ctx.config.server.port),
|
|
106
|
+
actualPort: Number(ctx.serverActualPort || ctx.config.server.port),
|
|
107
|
+
url: ctx.serverUrl,
|
|
108
|
+
},
|
|
109
|
+
oauth: {
|
|
110
|
+
configuredPort: Number(ctx.oauthPort),
|
|
111
|
+
actualPort: Number(ctx.oauthActualPort || ctx.oauthPort),
|
|
112
|
+
url: ctx.oauthUrl,
|
|
113
|
+
status: ctx.oauthReadyState,
|
|
114
|
+
},
|
|
76
115
|
}),
|
|
77
116
|
);
|
|
78
117
|
} catch (e) {
|
|
@@ -99,11 +138,16 @@ export async function createRuntimeContext(overrides = {}) {
|
|
|
99
138
|
const apiKey = loadedKey.apiKey;
|
|
100
139
|
const openai = overrides.openai ?? await createOpenAI(apiKey);
|
|
101
140
|
const oauthPort = config.oauth.proxyPort;
|
|
102
|
-
|
|
141
|
+
const ctx = {
|
|
103
142
|
rootDir,
|
|
104
143
|
config,
|
|
144
|
+
serverConfiguredPort: config.server.port,
|
|
145
|
+
serverActualPort: null,
|
|
146
|
+
serverUrl: `http://${runtimeHostUrl(config.server.host)}:${config.server.port}`,
|
|
105
147
|
oauthPort,
|
|
148
|
+
oauthActualPort: oauthPort,
|
|
106
149
|
oauthUrl: `http://127.0.0.1:${oauthPort}`,
|
|
150
|
+
oauthReadyState: config.oauth.autoStart ? "starting" : "disabled",
|
|
107
151
|
hasApiKey: !!apiKey,
|
|
108
152
|
apiKey,
|
|
109
153
|
apiKeySource: loadedKey.apiKeySource,
|
|
@@ -111,11 +155,28 @@ export async function createRuntimeContext(overrides = {}) {
|
|
|
111
155
|
startedAt: overrides.startedAt ?? Date.now(),
|
|
112
156
|
packageVersion: overrides.packageVersion ?? readPackageVersion(),
|
|
113
157
|
};
|
|
158
|
+
let resolveOAuthReady;
|
|
159
|
+
ctx.oauthReadyPromise = new Promise((resolve) => {
|
|
160
|
+
resolveOAuthReady = resolve;
|
|
161
|
+
});
|
|
162
|
+
ctx.markOAuthReady = ({ url, port } = {}) => {
|
|
163
|
+
if (url) ctx.oauthUrl = url;
|
|
164
|
+
if (port) ctx.oauthActualPort = port;
|
|
165
|
+
ctx.oauthReadyState = "ready";
|
|
166
|
+
resolveOAuthReady(ctx.oauthUrl);
|
|
167
|
+
};
|
|
168
|
+
ctx.markOAuthFailed = () => {
|
|
169
|
+
ctx.oauthReadyState = "failed";
|
|
170
|
+
resolveOAuthReady(null);
|
|
171
|
+
};
|
|
172
|
+
if (!config.oauth.autoStart) ctx.markOAuthReady({ url: ctx.oauthUrl, port: ctx.oauthPort });
|
|
173
|
+
return ctx;
|
|
114
174
|
}
|
|
115
175
|
|
|
116
176
|
export async function startServer(overrides = {}) {
|
|
117
177
|
const ctx = await createRuntimeContext(overrides);
|
|
118
178
|
await migrateGeneratedStorage(ctx);
|
|
179
|
+
purgeStaleJobs();
|
|
119
180
|
const app = buildApp(ctx);
|
|
120
181
|
const oauthChild =
|
|
121
182
|
overrides.oauthChild !== undefined
|
|
@@ -125,31 +186,43 @@ export async function startServer(overrides = {}) {
|
|
|
125
186
|
: startOAuthProxy({
|
|
126
187
|
oauthPort: ctx.oauthPort,
|
|
127
188
|
restartDelayMs: ctx.config.oauth.restartDelayMs,
|
|
189
|
+
onReady: ({ url, port }) => {
|
|
190
|
+
ctx.markOAuthReady({ url, port });
|
|
191
|
+
advertise(ctx);
|
|
192
|
+
},
|
|
193
|
+
onExit: () => ctx.markOAuthFailed(),
|
|
128
194
|
});
|
|
195
|
+
if (overrides.oauthChild !== undefined || !ctx.config.oauth.autoStart) {
|
|
196
|
+
ctx.markOAuthReady({ url: ctx.oauthUrl, port: ctx.oauthPort });
|
|
197
|
+
}
|
|
129
198
|
|
|
130
199
|
onShutdown(() => {
|
|
131
200
|
unadvertise(ctx);
|
|
132
|
-
try { oauthChild?.
|
|
201
|
+
try { oauthChild?.stop?.(); } catch {}
|
|
202
|
+
try { oauthChild?.kill?.(); } catch {}
|
|
133
203
|
});
|
|
134
204
|
process.on("exit", () => unadvertise(ctx));
|
|
135
205
|
|
|
136
|
-
const server = app
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
console.log(`[db] default session: ${s.id} (${s.title})`);
|
|
143
|
-
} catch (err) {
|
|
144
|
-
console.error("[db] bootstrap failed:", err.message);
|
|
145
|
-
}
|
|
206
|
+
const server = await listenWithPortFallback(app, ctx.config.server.port, {
|
|
207
|
+
host: ctx.config.server.host,
|
|
208
|
+
label: "server",
|
|
209
|
+
onFallback: ({ requestedPort, actualPort }) => {
|
|
210
|
+
console.log(`[server.port] requested=${requestedPort} actual=${actualPort} reason=EADDRINUSE`);
|
|
211
|
+
},
|
|
146
212
|
});
|
|
213
|
+
ctx.serverActualPort = getServerPort(server) || ctx.config.server.port;
|
|
214
|
+
ctx.serverUrl = `http://${runtimeHostUrl(ctx.config.server.host)}:${ctx.serverActualPort}`;
|
|
215
|
+
console.log(`Image Gen running at ${ctx.serverUrl}`);
|
|
216
|
+
console.log(`Provider policy: OAuth only (API key hard-disabled). OAuth proxy port ${ctx.oauthPort}.`);
|
|
217
|
+
advertise(ctx);
|
|
218
|
+
try {
|
|
219
|
+
const s = ensureDefaultSession();
|
|
220
|
+
console.log(`[db] default session: ${s.id} (${s.title})`);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.error("[db] bootstrap failed:", err.message);
|
|
223
|
+
}
|
|
147
224
|
|
|
148
225
|
server.on("error", (err) => {
|
|
149
|
-
if (err?.code === "EADDRINUSE") {
|
|
150
|
-
console.error(`[server] Port ${ctx.config.server.port} is already in use. Stop the existing image_gen server before starting another dev server.`);
|
|
151
|
-
process.exit(1);
|
|
152
|
-
}
|
|
153
226
|
console.error("[server] Failed to start:", err?.message || err);
|
|
154
227
|
process.exit(1);
|
|
155
228
|
});
|