ima2-gen 1.1.21 → 1.1.22
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 +30 -4
- package/bin/ima2.js +14 -4
- package/bin/lib/platform.js +34 -5
- package/docs/README.ko.md +31 -0
- package/lib/agentQueueWorker.js +6 -0
- package/lib/agentRuntime.js +3 -2
- package/lib/atomicWrite.js +14 -0
- package/lib/grokProxyLauncher.js +5 -3
- package/lib/inflight.js +1 -1
- package/lib/oauthLauncher.js +5 -0
- package/lib/videoFrameExtract.js +3 -3
- package/package.json +5 -7
- package/routes/edit.js +2 -1
- package/routes/generate.js +4 -3
- package/routes/health.js +4 -3
- package/routes/multimode.js +2 -1
- package/routes/video.js +4 -2
- package/server.js +29 -2
- package/ui/dist/.vite/manifest.json +12 -12
- package/ui/dist/assets/{AgentWorkspace-B_hq9CLg.js → AgentWorkspace-COxQ5TjU.js} +1 -1
- package/ui/dist/assets/{CardNewsWorkspace-wD12J7qk.js → CardNewsWorkspace-B0OkcuVz.js} +1 -1
- package/ui/dist/assets/{NodeCanvas-CI_wuPMf.js → NodeCanvas-BSsclEBh.js} +1 -1
- package/ui/dist/assets/{PromptBuilderPanel-CUTujJUV.js → PromptBuilderPanel-DpC9A5Rz.js} +1 -1
- package/ui/dist/assets/{PromptImportDialog-CUi66jPK.js → PromptImportDialog-CVwT0rLd.js} +2 -2
- package/ui/dist/assets/{PromptImportDiscoverySection-Cm3vrjY4.js → PromptImportDiscoverySection-BDCkRCRs.js} +1 -1
- package/ui/dist/assets/{PromptImportFolderSection-DOtWTD9n.js → PromptImportFolderSection-QoKbZD83.js} +1 -1
- package/ui/dist/assets/{PromptLibraryPanel-BMjQegRa.js → PromptLibraryPanel-BhFgeKnY.js} +2 -2
- package/ui/dist/assets/SettingsWorkspace-CfjrlH5R.js +1 -0
- package/ui/dist/assets/index-C-mur7pa.css +1 -0
- package/ui/dist/assets/index-CCP5nUOj.js +42 -0
- package/ui/dist/assets/{index-31uVIdt4.js → index-Cxhzi3bs.js} +1 -1
- package/ui/dist/index.html +2 -2
- package/bin/commands/annotate.ts +0 -119
- package/bin/commands/cancel.ts +0 -48
- package/bin/commands/canvas-versions.ts +0 -80
- package/bin/commands/capabilities.ts +0 -110
- package/bin/commands/cardnews.ts +0 -249
- package/bin/commands/comfy.ts +0 -54
- package/bin/commands/config.ts +0 -186
- package/bin/commands/defaults.ts +0 -192
- package/bin/commands/doctor.ts +0 -202
- package/bin/commands/edit.ts +0 -150
- package/bin/commands/gen.ts +0 -214
- package/bin/commands/grok.ts +0 -90
- package/bin/commands/history.ts +0 -146
- package/bin/commands/ls.ts +0 -64
- package/bin/commands/metadata.ts +0 -39
- package/bin/commands/multimode.ts +0 -196
- package/bin/commands/node.ts +0 -166
- package/bin/commands/observability.ts +0 -176
- package/bin/commands/ping.ts +0 -31
- package/bin/commands/prompt-sub/build.ts +0 -101
- package/bin/commands/prompt.ts +0 -492
- package/bin/commands/ps.ts +0 -81
- package/bin/commands/session.ts +0 -266
- package/bin/commands/show.ts +0 -72
- package/bin/commands/skill.ts +0 -70
- package/bin/commands/video.ts +0 -442
- package/bin/ima2.ts +0 -430
- package/bin/lib/args.ts +0 -92
- package/bin/lib/browser-id.ts +0 -16
- package/bin/lib/client.ts +0 -122
- package/bin/lib/config-store.ts +0 -120
- package/bin/lib/destructive-confirm.ts +0 -19
- package/bin/lib/doctor-checks.ts +0 -91
- package/bin/lib/error-hints.ts +0 -23
- package/bin/lib/files.ts +0 -39
- package/bin/lib/output.ts +0 -73
- package/bin/lib/platform.ts +0 -99
- package/bin/lib/recover-output.ts +0 -139
- package/bin/lib/sse.ts +0 -73
- package/bin/lib/star-prompt.ts +0 -97
- package/bin/lib/storage-doctor.ts +0 -39
- package/bin/lib/ui-build.ts +0 -85
- package/config.ts +0 -354
- package/lib/agentCommandParser.ts +0 -69
- package/lib/agentGenerationPlanner.ts +0 -273
- package/lib/agentQuestionResponder.ts +0 -266
- package/lib/agentQueueStore.ts +0 -270
- package/lib/agentQueueWorker.ts +0 -89
- package/lib/agentRuntime.ts +0 -604
- package/lib/agentSettings.ts +0 -72
- package/lib/agentStore.ts +0 -422
- package/lib/agentStoreRows.ts +0 -136
- package/lib/agentTypes.ts +0 -154
- package/lib/apiCachePolicy.ts +0 -11
- package/lib/assetLifecycle.ts +0 -146
- package/lib/canvasVersionStore.ts +0 -223
- package/lib/capabilities.ts +0 -126
- package/lib/cardNewsGenerator.ts +0 -271
- package/lib/cardNewsJobStore.ts +0 -142
- package/lib/cardNewsManifestStore.ts +0 -154
- package/lib/cardNewsPlanner.ts +0 -236
- package/lib/cardNewsPlannerClient.ts +0 -155
- package/lib/cardNewsPlannerPrompt.ts +0 -62
- package/lib/cardNewsPlannerSchema.ts +0 -321
- package/lib/cardNewsRoleTemplateStore.ts +0 -47
- package/lib/cardNewsTemplateStore.ts +0 -252
- package/lib/codexDetect.ts +0 -71
- package/lib/comfyBridge.ts +0 -235
- package/lib/composerSnapshot.ts +0 -33
- package/lib/configKeys.ts +0 -62
- package/lib/db.ts +0 -295
- package/lib/errInfo.ts +0 -43
- package/lib/errorClassify.ts +0 -100
- package/lib/generationCancel.ts +0 -28
- package/lib/generationErrors.ts +0 -238
- package/lib/grokImageAdapter.ts +0 -513
- package/lib/grokMultimodeAdapter.ts +0 -84
- package/lib/grokProxyLauncher.ts +0 -153
- package/lib/grokRuntime.ts +0 -23
- package/lib/grokSizeMapper.ts +0 -71
- package/lib/grokVideoAdapter.ts +0 -458
- package/lib/grokVideoCanvas.ts +0 -26
- package/lib/grokVideoDownload.ts +0 -59
- package/lib/grokVideoPlannerPrompt.ts +0 -67
- package/lib/historyIndex.ts +0 -51
- package/lib/historyList.ts +0 -181
- package/lib/imageMetadata.ts +0 -113
- package/lib/imageMetadataStore.ts +0 -67
- package/lib/imageModels.ts +0 -165
- package/lib/inflight.ts +0 -281
- package/lib/localImportStore.ts +0 -114
- package/lib/logger.ts +0 -161
- package/lib/nodeStore.ts +0 -91
- package/lib/oauthLauncher.ts +0 -94
- package/lib/oauthNormalize.ts +0 -30
- package/lib/oauthProxy/errors.ts +0 -128
- package/lib/oauthProxy/generators.ts +0 -494
- package/lib/oauthProxy/index.ts +0 -28
- package/lib/oauthProxy/prompts.ts +0 -123
- package/lib/oauthProxy/references.ts +0 -45
- package/lib/oauthProxy/runtime.ts +0 -115
- package/lib/oauthProxy/streams.ts +0 -232
- package/lib/oauthProxy/types.ts +0 -9
- package/lib/oauthProxy.ts +0 -3
- package/lib/openDirectory.ts +0 -47
- package/lib/pngInfo.ts +0 -26
- package/lib/promptBuilder/attachments.ts +0 -74
- package/lib/promptBuilder/client.ts +0 -130
- package/lib/promptBuilder/constants.ts +0 -9
- package/lib/promptBuilder/context.ts +0 -36
- package/lib/promptBuilder/errors.ts +0 -12
- package/lib/promptBuilder/requestSchema.ts +0 -56
- package/lib/promptBuilder/responseParser.ts +0 -219
- package/lib/promptBuilder/systemPrompt.ts +0 -135
- package/lib/promptBuilder/transport.ts +0 -94
- package/lib/promptBuilder/types.ts +0 -109
- package/lib/promptImport/curatedSources.ts +0 -141
- package/lib/promptImport/discoveryRegistry.ts +0 -329
- package/lib/promptImport/errors.ts +0 -18
- package/lib/promptImport/githubDiscovery.ts +0 -309
- package/lib/promptImport/githubFolder.ts +0 -397
- package/lib/promptImport/githubSource.ts +0 -257
- package/lib/promptImport/gptImageHints.ts +0 -70
- package/lib/promptImport/parsePromptCandidates.ts +0 -179
- package/lib/promptImport/promptIndex.ts +0 -326
- package/lib/promptImport/rankPromptCandidates.ts +0 -65
- package/lib/promptImport/types.ts +0 -103
- package/lib/promptSafetyPolicy.ts +0 -5
- package/lib/providerOptions.ts +0 -56
- package/lib/referenceImageCompress.ts +0 -84
- package/lib/refs.ts +0 -133
- package/lib/requestLogger.ts +0 -49
- package/lib/responsesDoctor.ts +0 -456
- package/lib/responsesErrors.ts +0 -83
- package/lib/responsesFallback.ts +0 -114
- package/lib/responsesImageAdapter.ts +0 -466
- package/lib/responsesParse.ts +0 -452
- package/lib/responsesTools.ts +0 -28
- package/lib/runtimeContext.ts +0 -146
- package/lib/runtimePorts.ts +0 -105
- package/lib/sessionStore.ts +0 -308
- package/lib/storageMigration.ts +0 -310
- package/lib/styleSheet.ts +0 -139
- package/lib/systemTrash.ts +0 -20
- package/lib/videoContinuity.ts +0 -180
- package/lib/videoFrameExtract.ts +0 -78
- package/lib/videoSeriesChain.ts +0 -29
- package/lib/visibleTextLanguagePolicy.ts +0 -7
- package/routes/agent.ts +0 -308
- package/routes/annotations.ts +0 -118
- package/routes/canvasVersions.ts +0 -69
- package/routes/capabilities.ts +0 -18
- package/routes/cardNews.ts +0 -211
- package/routes/comfy.ts +0 -43
- package/routes/edit.ts +0 -352
- package/routes/generate.ts +0 -492
- package/routes/grok.ts +0 -24
- package/routes/health.ts +0 -123
- package/routes/history.ts +0 -221
- package/routes/imageImport.ts +0 -37
- package/routes/index.ts +0 -52
- package/routes/metadata.ts +0 -77
- package/routes/multimode.ts +0 -499
- package/routes/nodes.ts +0 -578
- package/routes/promptBuilder.ts +0 -37
- package/routes/promptImport.ts +0 -379
- package/routes/prompts.ts +0 -428
- package/routes/quota.ts +0 -89
- package/routes/sessions.ts +0 -317
- package/routes/storage.ts +0 -47
- package/routes/video.ts +0 -300
- package/routes/videoExtended.ts +0 -284
- package/server.ts +0 -293
- package/ui/dist/assets/SettingsWorkspace-PiaVnsdA.js +0 -1
- package/ui/dist/assets/index-CjgnNtgt.css +0 -1
- package/ui/dist/assets/index-Da2s4_-5.js +0 -36
package/routes/videoExtended.ts
DELETED
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
import type { Express, Request, Response } from "express";
|
|
2
|
-
import { basename, join } from "node:path";
|
|
3
|
-
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
4
|
-
import { randomBytes } from "node:crypto";
|
|
5
|
-
import type { RouteRuntimeContext, RuntimeContext } from "../lib/runtimeContext.js";
|
|
6
|
-
import { requireRuntimeContext } from "../lib/runtimeContext.js";
|
|
7
|
-
import { getGrokProxyUrl } from "../lib/grokRuntime.js";
|
|
8
|
-
import { logEvent, logError } from "../lib/logger.js";
|
|
9
|
-
import { downloadVideo, pollVideoUntilDone } from "../lib/grokVideoAdapter.js";
|
|
10
|
-
import { invalidateHistoryIndex } from "../lib/historyIndex.js";
|
|
11
|
-
import { ACTIVE_VIDEO_PROMPT_GUIDANCE, appendVideoContinuityEntry, lineageFromVideoMetadata, readVideoSidecar } from "../lib/videoContinuity.js";
|
|
12
|
-
import { assertLocalMp4, extractVideoFrame, safeGeneratedFilePath } from "../lib/videoFrameExtract.js";
|
|
13
|
-
|
|
14
|
-
function videoProxyUrl(ctx: RuntimeContext, path: string) {
|
|
15
|
-
return { url: getGrokProxyUrl(ctx, path), headers: { "Content-Type": "application/json", Authorization: "Bearer dummy" } };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function routeError(message: string, status = 400): Error & { status: number } {
|
|
19
|
-
return Object.assign(new Error(message), { status });
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function sendError(res: Response, err: any): void {
|
|
23
|
-
res.status(typeof err?.status === "number" ? err.status : 500).json({ error: err?.message || String(err) });
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async function safeGeneratedFile(ctx: RuntimeContext, file: string, options: { requireMp4?: boolean } = {}): Promise<string> {
|
|
27
|
-
return safeGeneratedFilePath(ctx.config.storage.generatedDir, file, options);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function summarizeSource(input: string): Record<string, unknown> {
|
|
31
|
-
if (input.startsWith("data:video/")) {
|
|
32
|
-
const encoded = input.split(",", 2)[1] || "";
|
|
33
|
-
return { kind: "data-url", approximateBytes: Math.floor(encoded.length * 0.75) };
|
|
34
|
-
}
|
|
35
|
-
if (/^https?:\/\//i.test(input)) {
|
|
36
|
-
try {
|
|
37
|
-
const parsed = new URL(input);
|
|
38
|
-
return { kind: "url", origin: parsed.origin, pathname: basename(parsed.pathname) };
|
|
39
|
-
} catch {
|
|
40
|
-
return { kind: "url" };
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
if (/^file[-_][A-Za-z0-9._-]+$/.test(input)) return { kind: "file_id" };
|
|
44
|
-
return { kind: "generated-file", filename: basename(input) };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function resolveVideoInput(ctx: RuntimeContext, input: string): Promise<Record<string, string>> {
|
|
48
|
-
if (/^https?:\/\//i.test(input) || input.startsWith("data:video/")) return { url: input };
|
|
49
|
-
if (/^file[-_][A-Za-z0-9._-]+$/.test(input)) return { file_id: input };
|
|
50
|
-
const inputPath = await safeGeneratedFile(ctx, input, { requireMp4: true });
|
|
51
|
-
await assertLocalMp4(inputPath);
|
|
52
|
-
const buf = await readFile(inputPath);
|
|
53
|
-
return { url: `data:video/mp4;base64,${buf.toString("base64")}` };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function validateEditModel(model: unknown): string {
|
|
57
|
-
if (typeof model !== "string") throw routeError("model must be a string", 400);
|
|
58
|
-
if (model !== "grok-imagine-video") throw routeError("Video edit/extension only supports grok-imagine-video", 400);
|
|
59
|
-
return model;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function saveVideoResult(
|
|
63
|
-
ctx: RuntimeContext,
|
|
64
|
-
options: { requestId: string; prompt: string; model: string; operation: "edit" | "extend"; source: string; duration: number | null; videoUrl: string; usage?: Record<string, number> | null; signal?: AbortSignal },
|
|
65
|
-
): Promise<{ filename: string; url: string; sourceUrl: string }> {
|
|
66
|
-
const { buffer, contentType } = await downloadVideo(ctx, options.videoUrl, options.signal);
|
|
67
|
-
await mkdir(ctx.config.storage.generatedDir, { recursive: true });
|
|
68
|
-
const rand = randomBytes(ctx.config.ids.generatedHexBytes).toString("hex");
|
|
69
|
-
const filename = `${Date.now()}_${rand}.mp4`;
|
|
70
|
-
const filePath = join(ctx.config.storage.generatedDir, filename);
|
|
71
|
-
const sourceFilename = /^https?:\/\//i.test(options.source) || options.source.startsWith("data:") || /^file[-_]/.test(options.source)
|
|
72
|
-
? null
|
|
73
|
-
: basename(options.source);
|
|
74
|
-
const sourceMeta = sourceFilename ? await readVideoSidecar(ctx.config.storage.generatedDir, sourceFilename) : null;
|
|
75
|
-
const parentLineage = sourceFilename ? lineageFromVideoMetadata(sourceFilename, sourceMeta) : null;
|
|
76
|
-
const videoContinuity = appendVideoContinuityEntry(parentLineage, {
|
|
77
|
-
filename,
|
|
78
|
-
userPrompt: options.prompt,
|
|
79
|
-
revisedPrompt: options.prompt,
|
|
80
|
-
createdAt: Date.now(),
|
|
81
|
-
});
|
|
82
|
-
await writeFile(filePath, buffer);
|
|
83
|
-
try {
|
|
84
|
-
await writeFile(
|
|
85
|
-
`${filePath}.json`,
|
|
86
|
-
JSON.stringify({
|
|
87
|
-
kind: "video",
|
|
88
|
-
mediaType: "video",
|
|
89
|
-
requestId: options.requestId,
|
|
90
|
-
prompt: options.prompt,
|
|
91
|
-
userPrompt: options.prompt,
|
|
92
|
-
provider: "grok",
|
|
93
|
-
model: options.model,
|
|
94
|
-
createdAt: Date.now(),
|
|
95
|
-
usage: options.usage ?? null,
|
|
96
|
-
revisedPrompt: options.prompt,
|
|
97
|
-
videoContinuity,
|
|
98
|
-
video: {
|
|
99
|
-
operation: options.operation,
|
|
100
|
-
duration: options.duration,
|
|
101
|
-
source: summarizeSource(options.source),
|
|
102
|
-
sourceUrl: summarizeSource(options.videoUrl),
|
|
103
|
-
contentType,
|
|
104
|
-
},
|
|
105
|
-
}),
|
|
106
|
-
);
|
|
107
|
-
} catch (err) {
|
|
108
|
-
await unlink(filePath).catch(() => {});
|
|
109
|
-
throw err;
|
|
110
|
-
}
|
|
111
|
-
invalidateHistoryIndex();
|
|
112
|
-
return { filename, url: `/generated/${encodeURIComponent(filename)}`, sourceUrl: options.videoUrl };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function requestSignal(req: Request, res: Response): AbortSignal {
|
|
116
|
-
const ac = new AbortController();
|
|
117
|
-
const abort = () => {
|
|
118
|
-
if (!res.writableEnded) ac.abort();
|
|
119
|
-
};
|
|
120
|
-
req.on("aborted", abort);
|
|
121
|
-
res.on("close", abort);
|
|
122
|
-
return ac.signal;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function requirePrompt(value: unknown): string | null {
|
|
126
|
-
return typeof value === "string" && value.trim() ? value : null;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function extractOutputText(data: Record<string, unknown>): string {
|
|
130
|
-
const output = Array.isArray(data.output) ? data.output : [];
|
|
131
|
-
const texts: string[] = [];
|
|
132
|
-
for (const item of output) {
|
|
133
|
-
const content = (item as any)?.content;
|
|
134
|
-
if (!Array.isArray(content)) continue;
|
|
135
|
-
for (const part of content) {
|
|
136
|
-
if (part?.type === "output_text" && typeof part.text === "string") texts.push(part.text);
|
|
137
|
-
if (part?.type === "text" && typeof part.text === "string") texts.push(part.text);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return texts.join("\n").trim();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function registerVideoExtendedRoutes(app: Express, ctxRaw: RouteRuntimeContext) {
|
|
144
|
-
const ctx = requireRuntimeContext(ctxRaw);
|
|
145
|
-
|
|
146
|
-
// --- Video Edit (V2V) ---
|
|
147
|
-
app.post("/api/video/edit", async (req: Request, res: Response) => {
|
|
148
|
-
try {
|
|
149
|
-
const { prompt: rawPrompt, videoUrl, model = "grok-imagine-video" } = req.body ?? {};
|
|
150
|
-
const prompt = requirePrompt(rawPrompt);
|
|
151
|
-
if (!prompt) return res.status(400).json({ error: "prompt required", code: "PROMPT_REQUIRED", guidance: ACTIVE_VIDEO_PROMPT_GUIDANCE });
|
|
152
|
-
if (!videoUrl || typeof videoUrl !== "string") return res.status(400).json({ error: "videoUrl required" });
|
|
153
|
-
const validModel = validateEditModel(model);
|
|
154
|
-
const signal = requestSignal(req, res);
|
|
155
|
-
|
|
156
|
-
const { url, headers } = videoProxyUrl(ctx, "/v1/videos/edits");
|
|
157
|
-
const video = await resolveVideoInput(ctx, videoUrl);
|
|
158
|
-
const apiRes = await fetch(url, { method: "POST", headers, body: JSON.stringify({ model: validModel, prompt, video }), signal });
|
|
159
|
-
if (!apiRes.ok) { const t = await apiRes.text(); return res.status(apiRes.status).json({ error: t }); }
|
|
160
|
-
const { request_id } = (await apiRes.json()) as { request_id: string };
|
|
161
|
-
if (!request_id) return res.status(502).json({ error: "No request_id in response" });
|
|
162
|
-
logEvent("video", "edit:start", { requestId: request_id, model: validModel });
|
|
163
|
-
|
|
164
|
-
const result = await pollVideoUntilDone(ctx, request_id, { signal });
|
|
165
|
-
if (result.respectModeration === false) return res.status(502).json({ error: "Grok video blocked by moderation" });
|
|
166
|
-
if (!result.videoUrl) return res.status(502).json({ error: "No video URL in response" });
|
|
167
|
-
const saved = await saveVideoResult(ctx, { requestId: request_id, prompt, model: validModel, operation: "edit", source: videoUrl, duration: result.duration ?? null, videoUrl: result.videoUrl, usage: result.usage, signal });
|
|
168
|
-
|
|
169
|
-
logEvent("video", "edit:done", { requestId: request_id });
|
|
170
|
-
res.json({ requestId: request_id, url: saved.url, filename: saved.filename, sourceUrl: saved.sourceUrl, duration: result.duration, model: validModel });
|
|
171
|
-
} catch (err: any) {
|
|
172
|
-
logError("video", "edit:error", err);
|
|
173
|
-
sendError(res, err);
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
// --- Video Extension ---
|
|
178
|
-
app.post("/api/video/extend", async (req: Request, res: Response) => {
|
|
179
|
-
try {
|
|
180
|
-
const { prompt: rawPrompt, videoUrl, duration = 6, model = "grok-imagine-video" } = req.body ?? {};
|
|
181
|
-
const prompt = requirePrompt(rawPrompt);
|
|
182
|
-
if (!prompt) return res.status(400).json({ error: "prompt required", code: "PROMPT_REQUIRED", guidance: ACTIVE_VIDEO_PROMPT_GUIDANCE });
|
|
183
|
-
if (!videoUrl || typeof videoUrl !== "string") return res.status(400).json({ error: "videoUrl required" });
|
|
184
|
-
const validModel = validateEditModel(model);
|
|
185
|
-
const dur = Number(duration);
|
|
186
|
-
if (!Number.isInteger(dur) || dur < 2 || dur > 10) return res.status(400).json({ error: "duration must be an integer between 2 and 10" });
|
|
187
|
-
const signal = requestSignal(req, res);
|
|
188
|
-
|
|
189
|
-
const { url, headers } = videoProxyUrl(ctx, "/v1/videos/extensions");
|
|
190
|
-
const video = await resolveVideoInput(ctx, videoUrl);
|
|
191
|
-
const apiRes = await fetch(url, { method: "POST", headers, body: JSON.stringify({ model: validModel, prompt, duration: dur, video }), signal });
|
|
192
|
-
if (!apiRes.ok) { const t = await apiRes.text(); return res.status(apiRes.status).json({ error: t }); }
|
|
193
|
-
const { request_id } = (await apiRes.json()) as { request_id: string };
|
|
194
|
-
if (!request_id) return res.status(502).json({ error: "No request_id in response" });
|
|
195
|
-
logEvent("video", "extend:start", { requestId: request_id, model: validModel, duration: dur });
|
|
196
|
-
|
|
197
|
-
const result = await pollVideoUntilDone(ctx, request_id, { signal });
|
|
198
|
-
if (result.respectModeration === false) return res.status(502).json({ error: "Grok video blocked by moderation" });
|
|
199
|
-
if (!result.videoUrl) return res.status(502).json({ error: "No video URL in response" });
|
|
200
|
-
const saved = await saveVideoResult(ctx, { requestId: request_id, prompt, model: validModel, operation: "extend", source: videoUrl, duration: result.duration ?? null, videoUrl: result.videoUrl, usage: result.usage, signal });
|
|
201
|
-
|
|
202
|
-
logEvent("video", "extend:done", { requestId: request_id, totalDuration: result.duration });
|
|
203
|
-
res.json({ requestId: request_id, url: saved.url, filename: saved.filename, sourceUrl: saved.sourceUrl, duration: result.duration, model: validModel });
|
|
204
|
-
} catch (err: any) {
|
|
205
|
-
logError("video", "extend:error", err);
|
|
206
|
-
sendError(res, err);
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
// --- Video Frame Extraction ---
|
|
211
|
-
app.get("/api/video/frame", async (req: Request, res: Response) => {
|
|
212
|
-
try {
|
|
213
|
-
const file = req.query.file as string | undefined;
|
|
214
|
-
const position = (req.query.position as string) || "last";
|
|
215
|
-
if (!file) return res.status(400).json({ error: "file query param required" });
|
|
216
|
-
const inputPath = await safeGeneratedFile(ctx, file, { requireMp4: true });
|
|
217
|
-
await assertLocalMp4(inputPath);
|
|
218
|
-
|
|
219
|
-
const tmpOut = join(ctx.config.storage.generatedDir, `frame_tmp_${randomBytes(4).toString("hex")}.png`);
|
|
220
|
-
try {
|
|
221
|
-
await extractVideoFrame(inputPath, tmpOut, position);
|
|
222
|
-
const frame = await readFile(tmpOut);
|
|
223
|
-
res.type("png").send(frame);
|
|
224
|
-
} catch (err: any) {
|
|
225
|
-
return res.status(500).json({ error: "ffmpeg failed" });
|
|
226
|
-
} finally {
|
|
227
|
-
await unlink(tmpOut).catch(() => {});
|
|
228
|
-
}
|
|
229
|
-
} catch (err: any) {
|
|
230
|
-
logError("video", "frame:error", err);
|
|
231
|
-
sendError(res, err);
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
// --- Video Analysis (Grok 4.3 Vision) ---
|
|
236
|
-
app.post("/api/video/analyze", async (req: Request, res: Response) => {
|
|
237
|
-
try {
|
|
238
|
-
const { videoUrl } = req.body ?? {};
|
|
239
|
-
if (!videoUrl || typeof videoUrl !== "string") return res.status(400).json({ error: "videoUrl required" });
|
|
240
|
-
if (/^https?:\/\//i.test(videoUrl) || videoUrl.startsWith("data:")) {
|
|
241
|
-
return res.status(400).json({ error: "videoUrl must be a generated .mp4 filename" });
|
|
242
|
-
}
|
|
243
|
-
const input = await safeGeneratedFile(ctx, videoUrl, { requireMp4: true });
|
|
244
|
-
await assertLocalMp4(input);
|
|
245
|
-
const firstFrame = join(ctx.config.storage.generatedDir, `analyze_first_${randomBytes(4).toString("hex")}.png`);
|
|
246
|
-
const lastFrame = join(ctx.config.storage.generatedDir, `analyze_last_${randomBytes(4).toString("hex")}.png`);
|
|
247
|
-
|
|
248
|
-
try {
|
|
249
|
-
await extractVideoFrame(input, firstFrame, "0");
|
|
250
|
-
await extractVideoFrame(input, lastFrame, "last");
|
|
251
|
-
const first = (await readFile(firstFrame)).toString("base64");
|
|
252
|
-
const last = (await readFile(lastFrame)).toString("base64");
|
|
253
|
-
const { url, headers } = videoProxyUrl(ctx, "/v1/responses");
|
|
254
|
-
const apiRes = await fetch(url, {
|
|
255
|
-
method: "POST",
|
|
256
|
-
headers,
|
|
257
|
-
body: JSON.stringify({
|
|
258
|
-
model: "grok-4.3",
|
|
259
|
-
input: [{
|
|
260
|
-
role: "user",
|
|
261
|
-
content: [
|
|
262
|
-
{ type: "input_image", image_url: `data:image/png;base64,${first}`, detail: "high" },
|
|
263
|
-
{ type: "input_image", image_url: `data:image/png;base64,${last}`, detail: "high" },
|
|
264
|
-
{ type: "input_text", text: "Analyze these first and last frames from a video for recreation. Infer likely motion between them. Include shot type, camera movement, lighting, color palette, subjects, motion direction/speed, mood, and audio/sound prompt suggestions. Be specific and cinematic." },
|
|
265
|
-
],
|
|
266
|
-
}],
|
|
267
|
-
}),
|
|
268
|
-
});
|
|
269
|
-
if (!apiRes.ok) { const t = await apiRes.text(); return res.status(apiRes.status).json({ error: t }); }
|
|
270
|
-
const data = (await apiRes.json()) as Record<string, unknown>;
|
|
271
|
-
const text = extractOutputText(data);
|
|
272
|
-
if (!text) return res.status(502).json({ error: "No analysis text in response" });
|
|
273
|
-
logEvent("video", "analyze:done", { videoUrl, chars: text.length });
|
|
274
|
-
res.json({ analysis: text, model: "grok-4.3", method: "first-last-frame" });
|
|
275
|
-
} finally {
|
|
276
|
-
await unlink(firstFrame).catch(() => {});
|
|
277
|
-
await unlink(lastFrame).catch(() => {});
|
|
278
|
-
}
|
|
279
|
-
} catch (err: any) {
|
|
280
|
-
logError("video", "analyze:error", err);
|
|
281
|
-
sendError(res, err);
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
}
|
package/server.ts
DELETED
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
import "dotenv/config";
|
|
2
|
-
import express from "express";
|
|
3
|
-
import type { Response } from "express";
|
|
4
|
-
import { readFile } from "fs/promises";
|
|
5
|
-
import {
|
|
6
|
-
existsSync,
|
|
7
|
-
writeFileSync,
|
|
8
|
-
unlinkSync,
|
|
9
|
-
mkdirSync,
|
|
10
|
-
readFileSync as fsReadFileSync,
|
|
11
|
-
} from "fs";
|
|
12
|
-
import { dirname, join } from "path";
|
|
13
|
-
import { fileURLToPath, pathToFileURL } from "url";
|
|
14
|
-
import { onShutdown } from "./bin/lib/platform.js";
|
|
15
|
-
import { ensureDefaultSession } from "./lib/sessionStore.js";
|
|
16
|
-
import { startGrokProxy } from "./lib/grokProxyLauncher.js";
|
|
17
|
-
import { startOAuthProxy } from "./lib/oauthLauncher.js";
|
|
18
|
-
import { migrateGeneratedStorage } from "./lib/storageMigration.js";
|
|
19
|
-
import { purgeStaleJobs } from "./lib/inflight.js";
|
|
20
|
-
import { configureLogger } from "./lib/logger.js";
|
|
21
|
-
import { createRequestLogger } from "./lib/requestLogger.js";
|
|
22
|
-
import { configureApiCachePolicy } from "./lib/apiCachePolicy.js";
|
|
23
|
-
import { configureRoutes } from "./routes/index.js";
|
|
24
|
-
import { config } from "./config.js";
|
|
25
|
-
import { getServerPort, listenWithPortFallback } from "./lib/runtimePorts.js";
|
|
26
|
-
import type { RuntimeContext, RuntimeContextOverrides, ApiKeySource } from "./lib/runtimeContext.js";
|
|
27
|
-
|
|
28
|
-
import { errInfo } from "./lib/errInfo.js";
|
|
29
|
-
|
|
30
|
-
type BootRuntimeContext = RuntimeContext & {
|
|
31
|
-
markGrokProxyPort: (info?: { url?: string; port?: number }) => void;
|
|
32
|
-
markOAuthReady: (info?: { url?: string; port?: number }) => void;
|
|
33
|
-
markOAuthFailed: () => void;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
type ApiKeyLoadResult = { apiKey: string | null; apiKeySource: ApiKeySource };
|
|
37
|
-
|
|
38
|
-
const rootDir = dirname(fileURLToPath(import.meta.url));
|
|
39
|
-
|
|
40
|
-
async function loadApiKey(): Promise<ApiKeyLoadResult> {
|
|
41
|
-
if (process.env.OPENAI_API_KEY) {
|
|
42
|
-
return { apiKey: process.env.OPENAI_API_KEY, apiKeySource: "env" };
|
|
43
|
-
}
|
|
44
|
-
const candidates = [
|
|
45
|
-
config.storage.configFile,
|
|
46
|
-
join(rootDir, ".ima2", "config.json"),
|
|
47
|
-
];
|
|
48
|
-
for (const cfgPath of candidates) {
|
|
49
|
-
if (!existsSync(cfgPath)) continue;
|
|
50
|
-
try {
|
|
51
|
-
const cfg = JSON.parse(await readFile(cfgPath, "utf-8")) as { apiKey?: string };
|
|
52
|
-
if (cfg.apiKey) return { apiKey: cfg.apiKey, apiKeySource: "config" };
|
|
53
|
-
} catch {}
|
|
54
|
-
}
|
|
55
|
-
return { apiKey: null, apiKeySource: "none" };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async function createOpenAI(apiKey: string | null | undefined) {
|
|
59
|
-
if (!apiKey) return null;
|
|
60
|
-
const OpenAI = (await import("openai")).default;
|
|
61
|
-
return new OpenAI({ apiKey });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function readPackageVersion(): string {
|
|
65
|
-
try {
|
|
66
|
-
return (JSON.parse(fsReadFileSync(join(rootDir, "package.json"), "utf-8")) as { version?: string }).version ?? "0.0.0";
|
|
67
|
-
} catch {
|
|
68
|
-
return "0.0.0";
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function setUiStaticHeaders(res: Response, filePath: string) {
|
|
73
|
-
const normalized = filePath.replace(/\\/g, "/");
|
|
74
|
-
if (normalized.endsWith("/index.html")) {
|
|
75
|
-
res.setHeader("Cache-Control", "no-store, max-age=0");
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
if (normalized.includes("/assets/")) {
|
|
79
|
-
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function buildApp(ctx: RuntimeContext) {
|
|
84
|
-
const app = express();
|
|
85
|
-
configureApiCachePolicy(app);
|
|
86
|
-
configureLogger({ level: ctx.config.log.level });
|
|
87
|
-
app.use(createRequestLogger());
|
|
88
|
-
app.use(express.json({ limit: ctx.config.server.bodyLimit }));
|
|
89
|
-
app.use(express.static(join(ctx.rootDir, "ui", "dist"), {
|
|
90
|
-
setHeaders: setUiStaticHeaders,
|
|
91
|
-
}));
|
|
92
|
-
app.use("/assets", (_req, res) => {
|
|
93
|
-
res.status(404).type("text/plain").send("Asset not found");
|
|
94
|
-
});
|
|
95
|
-
app.use("/generated", (req, res, next) => {
|
|
96
|
-
if (req.path.endsWith(".json")) return res.status(404).type("text/plain").send("Generated metadata is not public");
|
|
97
|
-
return next();
|
|
98
|
-
}, express.static(ctx.config.storage.generatedDir, {
|
|
99
|
-
maxAge: ctx.config.storage.staticMaxAge,
|
|
100
|
-
immutable: true,
|
|
101
|
-
}));
|
|
102
|
-
configureRoutes(app, ctx);
|
|
103
|
-
return app;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function runtimeHostUrl(host: string | undefined): string {
|
|
107
|
-
if (!host || host === "0.0.0.0" || host === "::") return "localhost";
|
|
108
|
-
return host;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function advertise(ctx: RuntimeContext) {
|
|
112
|
-
try {
|
|
113
|
-
mkdirSync(dirname(ctx.config.storage.advertiseFile), { recursive: true });
|
|
114
|
-
writeFileSync(
|
|
115
|
-
ctx.config.storage.advertiseFile,
|
|
116
|
-
JSON.stringify({
|
|
117
|
-
port: Number(ctx.serverActualPort || ctx.config.server.port),
|
|
118
|
-
url: ctx.serverUrl,
|
|
119
|
-
pid: process.pid,
|
|
120
|
-
startedAt: ctx.startedAt,
|
|
121
|
-
version: ctx.packageVersion,
|
|
122
|
-
backend: {
|
|
123
|
-
configuredPort: Number(ctx.serverConfiguredPort || ctx.config.server.port),
|
|
124
|
-
actualPort: Number(ctx.serverActualPort || ctx.config.server.port),
|
|
125
|
-
url: ctx.serverUrl,
|
|
126
|
-
},
|
|
127
|
-
oauth: {
|
|
128
|
-
configuredPort: Number(ctx.oauthPort),
|
|
129
|
-
actualPort: Number(ctx.oauthActualPort || ctx.oauthPort),
|
|
130
|
-
url: ctx.oauthUrl,
|
|
131
|
-
status: ctx.oauthReadyState,
|
|
132
|
-
},
|
|
133
|
-
grok: {
|
|
134
|
-
configuredPort: Number(ctx.grokPort),
|
|
135
|
-
actualPort: Number(ctx.grokActualPort || ctx.grokPort),
|
|
136
|
-
url: ctx.grokUrl,
|
|
137
|
-
},
|
|
138
|
-
}),
|
|
139
|
-
);
|
|
140
|
-
} catch (e) {
|
|
141
|
-
const err = errInfo(e);
|
|
142
|
-
console.warn("[advertise] skipped:", err.message);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function unadvertise(ctx: RuntimeContext) {
|
|
147
|
-
try {
|
|
148
|
-
if (!existsSync(ctx.config.storage.advertiseFile)) return;
|
|
149
|
-
const cur = JSON.parse(fsReadFileSync(ctx.config.storage.advertiseFile, "utf-8")) as { pid?: number };
|
|
150
|
-
if (cur.pid === process.pid) unlinkSync(ctx.config.storage.advertiseFile);
|
|
151
|
-
} catch {}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
type StartServerOverrides = RuntimeContextOverrides & {
|
|
155
|
-
startedAt?: number;
|
|
156
|
-
packageVersion?: string;
|
|
157
|
-
oauthChild?: { stop?: () => void; kill?: () => void } | null;
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
export async function createRuntimeContext(overrides: StartServerOverrides = {}): Promise<BootRuntimeContext> {
|
|
161
|
-
const loadedKey =
|
|
162
|
-
overrides.apiKey !== undefined
|
|
163
|
-
? {
|
|
164
|
-
apiKey: overrides.apiKey,
|
|
165
|
-
apiKeySource: overrides.apiKeySource ?? (overrides.apiKey ? "env" : "none"),
|
|
166
|
-
}
|
|
167
|
-
: await loadApiKey();
|
|
168
|
-
const apiKey = loadedKey.apiKey;
|
|
169
|
-
const openai = overrides.openai ?? await createOpenAI(apiKey);
|
|
170
|
-
const oauthPort = config.oauth.proxyPort;
|
|
171
|
-
const grokPort = config.grokProvider.proxyPort;
|
|
172
|
-
let resolveOAuthReady: (value: string | null) => void = () => {};
|
|
173
|
-
const oauthReadyPromise = new Promise<string | null>((resolve) => {
|
|
174
|
-
resolveOAuthReady = resolve;
|
|
175
|
-
});
|
|
176
|
-
const ctx: BootRuntimeContext = {
|
|
177
|
-
rootDir,
|
|
178
|
-
config,
|
|
179
|
-
serverConfiguredPort: config.server.port,
|
|
180
|
-
serverActualPort: undefined,
|
|
181
|
-
serverUrl: `http://${runtimeHostUrl(config.server.host)}:${config.server.port}`,
|
|
182
|
-
grokPort,
|
|
183
|
-
grokActualPort: grokPort,
|
|
184
|
-
grokUrl: `http://${config.grokProvider.proxyHost}:${grokPort}/v1`,
|
|
185
|
-
oauthPort,
|
|
186
|
-
oauthActualPort: oauthPort,
|
|
187
|
-
oauthUrl: `http://127.0.0.1:${oauthPort}`,
|
|
188
|
-
oauthReadyState: config.oauth.autoStart ? "starting" : "disabled",
|
|
189
|
-
hasApiKey: !!apiKey,
|
|
190
|
-
apiKey: apiKey ?? undefined,
|
|
191
|
-
apiKeySource: loadedKey.apiKeySource as ApiKeySource,
|
|
192
|
-
openai,
|
|
193
|
-
startedAt: overrides.startedAt ?? Date.now(),
|
|
194
|
-
packageVersion: overrides.packageVersion ?? readPackageVersion(),
|
|
195
|
-
oauthReadyPromise: oauthReadyPromise as unknown as Promise<void>,
|
|
196
|
-
markGrokProxyPort: ({ url, port }: { url?: string; port?: number } = {}) => {
|
|
197
|
-
if (port) ctx.grokActualPort = port;
|
|
198
|
-
if (url) ctx.grokUrl = url;
|
|
199
|
-
else if (port) ctx.grokUrl = `http://${ctx.config.grokProvider.proxyHost}:${port}/v1`;
|
|
200
|
-
},
|
|
201
|
-
markOAuthReady: ({ url, port }: { url?: string; port?: number } = {}) => {
|
|
202
|
-
if (url) ctx.oauthUrl = url;
|
|
203
|
-
if (port) ctx.oauthActualPort = port;
|
|
204
|
-
ctx.oauthReadyState = "ready";
|
|
205
|
-
resolveOAuthReady(ctx.oauthUrl);
|
|
206
|
-
},
|
|
207
|
-
markOAuthFailed: () => {
|
|
208
|
-
ctx.oauthReadyState = "failed";
|
|
209
|
-
resolveOAuthReady(null);
|
|
210
|
-
},
|
|
211
|
-
};
|
|
212
|
-
if (!config.oauth.autoStart) ctx.markOAuthReady({ url: ctx.oauthUrl, port: ctx.oauthPort });
|
|
213
|
-
return ctx;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export async function startServer(overrides: StartServerOverrides = {}) {
|
|
217
|
-
const ctx = await createRuntimeContext(overrides);
|
|
218
|
-
await migrateGeneratedStorage(ctx);
|
|
219
|
-
purgeStaleJobs();
|
|
220
|
-
const app = buildApp(ctx);
|
|
221
|
-
const oauthChild =
|
|
222
|
-
overrides.oauthChild !== undefined
|
|
223
|
-
? overrides.oauthChild
|
|
224
|
-
: !ctx.config.oauth.autoStart
|
|
225
|
-
? null
|
|
226
|
-
: startOAuthProxy({
|
|
227
|
-
oauthPort: ctx.oauthPort,
|
|
228
|
-
restartDelayMs: ctx.config.oauth.restartDelayMs,
|
|
229
|
-
onReady: ({ url, port }: { url: string; port: number }) => {
|
|
230
|
-
ctx.markOAuthReady({ url, port });
|
|
231
|
-
advertise(ctx);
|
|
232
|
-
},
|
|
233
|
-
onExit: () => ctx.markOAuthFailed(),
|
|
234
|
-
});
|
|
235
|
-
if (overrides.oauthChild !== undefined || !ctx.config.oauth.autoStart) {
|
|
236
|
-
ctx.markOAuthReady({ url: ctx.oauthUrl, port: ctx.oauthPort });
|
|
237
|
-
}
|
|
238
|
-
const grokChild = ctx.config.grokProvider.autoStart
|
|
239
|
-
? await startGrokProxy({
|
|
240
|
-
host: ctx.config.grokProvider.proxyHost,
|
|
241
|
-
port: ctx.config.grokProvider.proxyPort,
|
|
242
|
-
restartDelayMs: ctx.config.grokProvider.restartDelayMs,
|
|
243
|
-
onPortSelected: ({ url, port }: { url: string; port: number }) => {
|
|
244
|
-
ctx.markGrokProxyPort({ url, port });
|
|
245
|
-
advertise(ctx);
|
|
246
|
-
},
|
|
247
|
-
onReady: ({ url, port }: { url: string; port: number }) => {
|
|
248
|
-
ctx.markGrokProxyPort({ url, port });
|
|
249
|
-
advertise(ctx);
|
|
250
|
-
},
|
|
251
|
-
})
|
|
252
|
-
: null;
|
|
253
|
-
|
|
254
|
-
onShutdown(() => {
|
|
255
|
-
unadvertise(ctx);
|
|
256
|
-
try { oauthChild?.stop?.(); } catch {}
|
|
257
|
-
try { oauthChild?.kill?.(); } catch {}
|
|
258
|
-
try { grokChild?.stop?.(); } catch {}
|
|
259
|
-
try { grokChild?.kill?.(); } catch {}
|
|
260
|
-
});
|
|
261
|
-
process.on("exit", () => unadvertise(ctx));
|
|
262
|
-
|
|
263
|
-
const server = await listenWithPortFallback(app, ctx.config.server.port, {
|
|
264
|
-
host: ctx.config.server.host,
|
|
265
|
-
label: "server",
|
|
266
|
-
onFallback: ({ requestedPort, actualPort }: { requestedPort: number; actualPort: number }) => {
|
|
267
|
-
console.log(`[server.port] requested=${requestedPort} actual=${actualPort} reason=EADDRINUSE`);
|
|
268
|
-
},
|
|
269
|
-
});
|
|
270
|
-
ctx.serverActualPort = getServerPort(server) || ctx.config.server.port;
|
|
271
|
-
ctx.serverUrl = `http://${runtimeHostUrl(ctx.config.server.host)}:${ctx.serverActualPort}`;
|
|
272
|
-
console.log(`Image Gen running at ${ctx.serverUrl}`);
|
|
273
|
-
console.log(`Provider policy: GPT OAuth, API-key Responses, and Grok Images providers. GPT OAuth proxy port ${ctx.oauthPort}; Grok proxy port ${ctx.grokActualPort || ctx.grokPort}.`);
|
|
274
|
-
advertise(ctx);
|
|
275
|
-
try {
|
|
276
|
-
const s = ensureDefaultSession();
|
|
277
|
-
console.log(`[db] default session: ${s.id} (${s.title})`);
|
|
278
|
-
} catch (e) {
|
|
279
|
-
const err = errInfo(e);
|
|
280
|
-
console.error("[db] bootstrap failed:", err.message);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
server.on("error", (err: NodeJS.ErrnoException) => {
|
|
284
|
-
console.error("[server] Failed to start:", err?.message || err);
|
|
285
|
-
process.exit(1);
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
return { app, server, oauthChild, ctx };
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
292
|
-
await startServer();
|
|
293
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{u as m,h as E,k as L,l as R,j as e,b as c,m as $,o as q,a as u,T as C,I as O,W as I}from"./index-Da2s4_-5.js";function k(s,a){return s(a==="ready"?"settings.account.status.ready":a==="auth_required"?"settings.account.status.authRequired":a==="starting"?"settings.account.status.starting":a==="offline"?"settings.account.status.offline":a==="no_image_model"?"settings.account.status.noImageModel":a==="error"?"settings.account.status.error":"settings.account.status.checking")}function D(){const{t:s}=m(),a=E(),l=L(),{data:t,error:r}=R(),o=t?.apiKeySource==="env"||t?.apiKeySource==="config"||t?.apiKeyValid===!0,g=a?.status==="ready",h=t?.apiKeySource==="config"?s("settings.account.apiSourceConfig"):s("settings.account.apiSourceEnv"),d=t?.apiKeyValid===!0,i=l?.status==="ready";return e.jsxs(e.Fragment,{children:[e.jsxs("article",{className:"settings-row",children:[e.jsxs("div",{className:"settings-row__copy",children:[e.jsx("p",{className:"settings-eyebrow",children:s("settings.account.primaryEyebrow")}),e.jsx("h4",{children:s("settings.account.oauthTitle")}),e.jsx("p",{children:s("settings.account.oauthBody")})]}),e.jsxs("div",{className:`settings-status${g?" is-ok":""}`,children:[e.jsx("span",{"aria-hidden":"true"}),k(s,a?.status)]})]}),o?e.jsxs("article",{className:"settings-row",children:[e.jsxs("div",{className:"settings-row__copy",children:[e.jsx("p",{className:"settings-eyebrow",children:h}),e.jsx("h4",{children:s("settings.account.apiTitle")}),e.jsx("p",{children:s("settings.account.apiBody")})]}),e.jsxs("div",{className:`settings-status${d?" is-ok":" is-muted"}`,children:[e.jsx("span",{"aria-hidden":"true"}),s(r?"settings.account.apiUnknown":d?"settings.account.apiReady":"settings.account.apiUnavailable")]})]}):null,e.jsxs("article",{className:"settings-row",children:[e.jsxs("div",{className:"settings-row__copy",children:[e.jsx("p",{className:"settings-eyebrow",children:s("settings.account.grokEyebrow")}),e.jsx("h4",{children:s("settings.account.grokTitle")}),e.jsx("p",{children:s("settings.account.grokBody")})]}),e.jsxs("div",{className:`settings-status${i?" is-ok":" is-muted"}`,children:[e.jsx("span",{"aria-hidden":"true"}),k(s,l?.status)]})]})]})}function A(){const{t:s}=m(),a=c(r=>r.reasoningEffort),l=c(r=>r.setReasoningEffort),t=r=>{l(r.target.value)};return e.jsx("div",{className:"image-model-select image-model-select--settings",children:e.jsx("select",{id:"settings-reasoning-effort",value:a,onChange:t,children:$.map(r=>e.jsx("option",{value:r.value,children:s(r.fullLabelKey)},r.value))})})}const K={ko:"KO",en:"EN"};function B(){const{t:s,locale:a}=m(),l=c(t=>t.setLocale);return e.jsx("div",{className:"lang-toggle",role:"group","aria-label":s("language.label"),children:q.map(t=>e.jsx("button",{type:"button",className:`lang-toggle__btn ${a===t?"is-active":""}`,onClick:()=>l(t),"aria-pressed":a===t,title:s(`language.${t}`),children:e.jsx("span",{className:"lang-toggle__label",children:K[t]})},t))})}const P=["system","dark","light"];function F(){const{t:s}=m(),a=c(i=>i.theme),l=c(i=>i.setTheme),t=c(i=>i.themeFamily),r=c(i=>i.setThemeFamily),[o,g]=u.useState(!1),h=u.useRef(null);u.useEffect(()=>{if(!o)return;const i=p=>{h.current&&(h.current.contains(p.target)||g(!1))},x=p=>{p.key==="Escape"&&g(!1)};return document.addEventListener("mousedown",i),document.addEventListener("keydown",x),()=>{document.removeEventListener("mousedown",i),document.removeEventListener("keydown",x)}},[o]);const d=i=>s(`theme.family.${i}`);return e.jsxs("div",{className:"theme-toggle","aria-label":s("theme.label"),children:[e.jsxs("div",{className:"theme-toggle__row",children:[e.jsx("span",{className:"theme-toggle__label",id:"theme-style-label",children:s("theme.styleLabel")}),e.jsxs("div",{className:"theme-toggle__family",ref:h,children:[e.jsxs("button",{type:"button",className:"theme-toggle__family-trigger","aria-haspopup":"listbox","aria-expanded":o,"aria-labelledby":"theme-style-label",onClick:()=>g(i=>!i),children:[e.jsx("span",{className:`theme-toggle__family-dot theme-toggle__family-dot--${t}`,"aria-hidden":"true"}),e.jsx("span",{className:"theme-toggle__family-name",children:d(t)}),e.jsx("span",{className:"theme-toggle__family-caret","aria-hidden":"true",children:"▾"})]}),o?e.jsx("ul",{className:"theme-toggle__family-menu",role:"listbox","aria-labelledby":"theme-style-label",children:C.map(i=>e.jsx("li",{role:"none",children:e.jsxs("button",{type:"button",role:"option","aria-selected":t===i,className:`theme-toggle__family-option ${t===i?"is-active":""}`,onClick:()=>{r(i),g(!1)},children:[e.jsx("span",{className:`theme-toggle__family-dot theme-toggle__family-dot--${i}`,"aria-hidden":"true"}),e.jsx("span",{className:"theme-toggle__family-name",children:d(i)})]})},i))}):null]})]}),e.jsxs("div",{className:"theme-toggle__row",children:[e.jsx("span",{className:"theme-toggle__label",id:"theme-mode-label",children:s("theme.modeLabel")}),e.jsx("div",{className:"theme-toggle__mode",role:"group","aria-labelledby":"theme-mode-label",children:P.map(i=>e.jsx("button",{type:"button",className:`theme-toggle__btn ${a===i?"is-active":""}`,onClick:()=>l(i),"aria-pressed":a===i,title:s(`theme.${i}`),children:s(`theme.${i}`)},i))})]})]})}const M=["rail","horizontal","sidebar"];function G(){const{t:s}=m(),a=c(t=>t.historyStripLayout),l=c(t=>t.setHistoryStripLayout);return e.jsx("div",{className:"history-layout-toggle",role:"group","aria-label":s("settings.appearance.historyStripLayoutTitle"),children:M.map(t=>e.jsx("button",{type:"button",className:`history-layout-toggle__btn ${a===t?"is-active":""}`,onClick:()=>l(t),"aria-pressed":a===t,title:s(`settings.appearance.historyStripLayout.${t}`),children:s(`settings.appearance.historyStripLayout.${t}`)},t))})}const W=[{value:"default",labelKey:"workspace.defaultLabel",descKey:"workspace.defaultDesc"},{value:"prompt-studio",labelKey:"workspace.promptStudioLabel",descKey:"workspace.promptStudioDesc"}];function H(){const s=c(t=>t.workspaceProfile),a=c(t=>t.setWorkspaceProfile),{t:l}=m();return e.jsx("div",{className:"settings-field",children:e.jsx("select",{id:"workspace-profile-select",className:"settings-field__select",value:s,onChange:t=>a(t.target.value),"aria-label":l("workspace.profileLabel"),children:W.map(t=>e.jsx("option",{value:t.value,children:l(t.labelKey)},t.value))})})}function U(s){return s>80?"var(--error, #e53935)":s>50?"var(--warning, #f59e0b)":"var(--info, #3b82f6)"}function V(s){if(!s)return"";const a=new Date(s),l=new Date;return a.toDateString()===l.toDateString()?`${a.getHours()}:${String(a.getMinutes()).padStart(2,"0")}`:`${a.getMonth()+1}/${a.getDate()}`}function Q({window:s}){const a=V(s.resetsAt);return e.jsxs("div",{className:"quota-bar",children:[e.jsx("span",{className:"quota-bar__label",children:s.label}),e.jsx("div",{className:"quota-bar__track",children:e.jsx("div",{className:"quota-bar__fill",style:{width:`${Math.min(s.percent,100)}%`,background:U(s.percent)}})}),e.jsxs("span",{className:"quota-bar__pct",children:[s.percent,"%"]}),a&&e.jsx("span",{className:"quota-bar__reset",children:a})]})}function z(){const{t:s}=m(),[a,l]=u.useState(null),[t,r]=u.useState(!0);u.useEffect(()=>{const d=setTimeout(()=>{fetch("/api/quota").then(i=>i.json()).then(l).catch(()=>l(null)).finally(()=>r(!1))},1500);return()=>clearTimeout(d)},[]);const o=a?.codex,g=o?.windows&&o.windows.length>0,h=o?.account?[o.account.email,o.account.plan].filter(Boolean).join(" · "):null;return e.jsxs("article",{className:"settings-row",children:[e.jsxs("div",{className:"settings-row__copy",children:[e.jsx("p",{className:"settings-eyebrow",children:s("settings.quota.eyebrow")}),e.jsx("h4",{children:s("settings.quota.title")})]}),e.jsxs("div",{className:"settings-row__control quota-cards",children:[e.jsxs("div",{className:"quota-card",children:[e.jsxs("div",{className:"quota-card__header",children:[e.jsx("strong",{children:"Codex"}),h&&e.jsx("span",{className:"quota-card__account",children:h})]}),t?e.jsx("span",{className:"quota-card__loading",children:s("common.loading")}):g?o.windows.map(d=>e.jsx(Q,{window:d},d.label)):o?.authenticated===!1?e.jsx("span",{className:"quota-card__hint",children:s("settings.quota.codexNotLoggedIn")}):o?.error?e.jsx("span",{className:"quota-card__hint",children:s("settings.quota.fetchError")}):e.jsx("span",{className:"quota-card__hint",children:s("settings.quota.noData")})]}),e.jsxs("div",{className:"quota-card",children:[e.jsx("div",{className:"quota-card__header",children:e.jsx("strong",{children:"Grok"})}),e.jsx("a",{href:"https://grok.com/?_s=usage",target:"_blank",rel:"noopener noreferrer",className:"settings-action-btn",children:s("settings.quota.grokUsageLink")})]})]})]})}const _=["account","generation","appearance","workspace","language","future"];function y({id:s,setRef:a,children:l}){const{t}=m();return e.jsxs("section",{id:s,ref:r=>a(s,r),className:"settings-section","aria-labelledby":`settings-section-${s}`,children:[e.jsx("header",{className:"settings-section__header",children:e.jsxs("div",{children:[e.jsx("h3",{id:`settings-section-${s}`,children:t(`settings.sections.${s}.title`)}),e.jsx("p",{children:t(`settings.sections.${s}.hint`)})]})}),e.jsx("div",{className:"settings-section__body",children:l})]})}function J(){const{t:s}=m(),a=c(n=>n.activeSettingsSection),l=c(n=>n.setActiveSettingsSection),t=c(n=>n.closeSettings),r=c(n=>n.openReadinessPopup),o=c(n=>n.galleryDefaultScope),g=c(n=>n.setGalleryDefaultScope),h=c(n=>n.provider),d=u.useRef(null),i=u.useRef(null),x=u.useRef(!1),p=u.useRef({account:null,generation:null,appearance:null,workspace:null,language:null,future:null}),j=(n,f)=>{p.current[n]=f},S=n=>{l(n),x.current=!0,p.current[n]?.scrollIntoView({behavior:"auto",block:"start"}),i.current!==null&&window.clearTimeout(i.current),i.current=window.setTimeout(()=>{x.current=!1,i.current=null},120)};return u.useEffect(()=>{const n=f=>{f.key==="Escape"&&t()};return window.addEventListener("keydown",n),()=>window.removeEventListener("keydown",n)},[t]),u.useEffect(()=>{const n=d.current;if(!n||typeof IntersectionObserver!="function")return;const f=new IntersectionObserver(b=>{if(x.current)return;const v=b.filter(w=>w.isIntersecting).sort((w,T)=>T.intersectionRatio-w.intersectionRatio)[0]?.target.id;v&&_.includes(v)&&l(v)},{root:n,threshold:[.35,.6]});for(const b of _){const N=p.current[b];N&&f.observe(N)}return()=>f.disconnect()},[l]),u.useEffect(()=>()=>{i.current!==null&&window.clearTimeout(i.current)},[]),e.jsx("main",{ref:d,className:"settings-workspace","aria-labelledby":"settings-title",children:e.jsxs("div",{className:"settings-shell",children:[e.jsxs("header",{className:"settings-header",children:[e.jsxs("div",{children:[e.jsx("p",{className:"settings-eyebrow",children:s("settings.eyebrow")}),e.jsx("h2",{id:"settings-title",children:s("settings.title")}),e.jsx("p",{children:s("settings.subtitle")})]}),e.jsx("button",{type:"button",className:"settings-close",onClick:t,"aria-label":s("settings.closeAria"),title:s("settings.closeTitle"),children:"X"})]}),e.jsxs("div",{className:"settings-layout",children:[e.jsx("nav",{className:"settings-nav settings-nav--mobile","aria-label":s("settings.navAria"),children:e.jsx("div",{className:"settings-mobile-nav",role:"list",children:_.map(n=>e.jsxs("button",{type:"button",className:`settings-mobile-nav__item${a===n?" is-active":""}`,onClick:()=>S(n),"aria-current":a===n?"true":void 0,children:[e.jsx("span",{children:s(`settings.sections.${n}.title`)}),e.jsx("small",{children:s(`settings.sections.${n}.hint`)})]},n))})}),e.jsx("nav",{className:"settings-nav","aria-label":s("settings.navAria"),children:_.map(n=>e.jsxs("button",{type:"button",className:`settings-nav__item${a===n?" is-active":""}`,onClick:()=>S(n),"aria-label":s("settings.jumpTo",{section:s(`settings.sections.${n}.title`)}),children:[e.jsx("span",{children:s(`settings.sections.${n}.title`)}),e.jsx("small",{children:s(`settings.sections.${n}.hint`)})]},n))}),e.jsxs("section",{className:"settings-content","aria-label":s("settings.contentAria"),children:[e.jsxs(y,{id:"account",setRef:j,children:[e.jsx(D,{}),e.jsx(z,{}),e.jsxs("article",{className:"settings-row",children:[e.jsxs("div",{className:"settings-row__copy",children:[e.jsx("h4",{children:s("readiness.settingsTitle")}),e.jsx("p",{children:s("readiness.settingsBody")})]}),e.jsx("div",{className:"settings-row__control",children:e.jsx("button",{type:"button",className:"settings-action-btn",onClick:r,children:s("readiness.open")})})]})]}),e.jsxs(y,{id:"generation",setRef:j,children:[e.jsxs("article",{className:"settings-row",children:[e.jsxs("div",{className:"settings-row__copy",children:[e.jsx("h4",{children:s("settings.imageModel.title")}),e.jsx("p",{children:s("settings.imageModel.body")}),e.jsx("p",{className:"settings-row__microcopy",children:s("settings.imageModel.unsupportedHelp")})]}),e.jsx("div",{className:"settings-row__control",children:e.jsx(O,{variant:"settings"})})]}),h==="grok"?e.jsx("article",{className:"settings-row",children:e.jsxs("div",{className:"settings-row__copy",children:[e.jsx("h4",{children:s("settings.grokCompatibility.title")}),e.jsx("p",{children:s("settings.grokCompatibility.body")})]})}):e.jsxs(e.Fragment,{children:[e.jsxs("article",{className:"settings-row",children:[e.jsxs("div",{className:"settings-row__copy",children:[e.jsx("h4",{children:s("settings.reasoning.title")}),e.jsx("p",{children:s("settings.reasoning.body")})]}),e.jsx("div",{className:"settings-row__control",children:e.jsx(A,{})})]}),e.jsxs("article",{className:"settings-row",children:[e.jsxs("div",{className:"settings-row__copy",children:[e.jsx("h4",{children:s("settings.webSearch.title")}),e.jsx("p",{children:s("settings.webSearch.body")})]}),e.jsx("div",{className:"settings-row__control",children:e.jsx(I,{})})]})]}),e.jsxs("article",{className:"settings-row",children:[e.jsxs("div",{className:"settings-row__copy",children:[e.jsx("h4",{children:s("settings.gallery.defaultScopeTitle")}),e.jsx("p",{children:s("settings.gallery.defaultScopeBody")})]}),e.jsx("div",{className:"settings-row__control",children:e.jsxs("select",{value:o,onChange:n=>g(n.target.value),"aria-label":s("settings.gallery.defaultScopeTitle"),children:[e.jsx("option",{value:"current-session",children:s("gallery.scope.current")}),e.jsx("option",{value:"all",children:s("gallery.scope.all")})]})})]})]}),e.jsxs(y,{id:"appearance",setRef:j,children:[e.jsxs("article",{className:"settings-row",children:[e.jsxs("div",{className:"settings-row__copy",children:[e.jsx("h4",{children:s("settings.appearance.themeTitle")}),e.jsx("p",{children:s("settings.appearance.themeBody")})]}),e.jsx("div",{className:"settings-row__control",children:e.jsx(F,{})})]}),e.jsxs("article",{className:"settings-row",children:[e.jsxs("div",{className:"settings-row__copy",children:[e.jsx("h4",{children:s("settings.appearance.historyStripLayoutTitle")}),e.jsx("p",{children:s("settings.appearance.historyStripLayoutBody")})]}),e.jsx("div",{className:"settings-row__control",children:e.jsx(G,{})})]})]}),e.jsx(y,{id:"workspace",setRef:j,children:e.jsxs("article",{className:"settings-row",children:[e.jsxs("div",{className:"settings-row__copy",children:[e.jsx("h4",{children:s("workspace.sectionTitle")}),e.jsx("p",{children:s("workspace.sectionBody")})]}),e.jsx("div",{className:"settings-row__control",children:e.jsx(H,{})})]})}),e.jsx(y,{id:"language",setRef:j,children:e.jsxs("article",{className:"settings-row",children:[e.jsxs("div",{className:"settings-row__copy",children:[e.jsx("h4",{children:s("settings.language.title")}),e.jsx("p",{children:s("settings.language.body")})]}),e.jsx("div",{className:"settings-row__control",children:e.jsx(B,{})})]})}),e.jsx(y,{id:"future",setRef:j,children:e.jsxs("article",{className:"settings-note",children:[e.jsx("h4",{children:s("settings.future.title")}),e.jsx("p",{children:s("settings.future.body")})]})})]})]})]})})}export{J as SettingsWorkspace};
|