From 15ff261e24b07f4c3adb00c2c6aa6eb1e6e3e6f5 Mon Sep 17 00:00:00 2001 From: yibo Date: Tue, 1 Sep 2026 17:48:57 +0800 Subject: [PATCH] Expand chat prompts with GLM before submitting to H3 --- server.js | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index 37b5d84..6b1f573 100644 --- a/server.js +++ b/server.js @@ -19,6 +19,15 @@ const H3_CONSOLE_BASE = process.env.H3_CONSOLE_BASE || ""; const H3_CONSOLE_TOKEN = process.env.H3_CONSOLE_TOKEN || ""; const H3_CONSOLE_URL = H3_CONSOLE_BASE ? new URL(H3_CONSOLE_BASE) : null; +// GLM Coding Plan — used only to expand a viewer's short chat prompt into a +// detailed shot-by-shot prompt before handing it to H3 (see optimizePrompt). +// Must hit the /api/coding/ path specifically — the plain /api/paas/ path +// bills against a different (pay-per-token) quota than the coding plan. +const GLM_API_KEY = process.env.GLM_API_KEY || ""; +const GLM_HOST = "open.bigmodel.cn"; +const GLM_PATH = "/api/coding/paas/v4/chat/completions"; +const GLM_MODEL = "glm-5.3-flash"; + const MIME = { ".html": "text/html", ".js": "text/javascript", ".css": "text/css", ".json": "application/json" }; const { syncLibrary } = require("./ingest.js"); @@ -91,6 +100,58 @@ function h3ConsoleFetch(pathAndQuery, options) { }); } +function glmChat(messages) { + return new Promise((resolve, reject) => { + const body = JSON.stringify({ model: GLM_MODEL, messages, temperature: 0.7 }); + const req = https.request( + { + hostname: GLM_HOST, + path: GLM_PATH, + method: "POST", + headers: { Authorization: `Bearer ${GLM_API_KEY}`, "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) }, + }, + (res) => { + let data = ""; + res.on("data", (c) => (data += c)); + res.on("end", () => { + try { + resolve(JSON.parse(data)); + } catch (err) { + reject(err); + } + }); + } + ); + req.on("error", reject); + req.setTimeout(15000, () => req.destroy(new Error("GLM request timed out"))); + req.write(body); + req.end(); + }); +} + +const PROMPT_OPTIMIZE_SYSTEM = `You are a prompt engineer for a text-to-video model. Expand the user's short +request (which may be in Chinese or English) into a detailed shot-by-shot generation prompt written in +English: camera angle/movement, lighting and mood, the subject's specific actions, and the setting. +Output only the expanded English prompt itself — no preamble, no quotes, no "Prompt:" prefix — under +150 words.`; + +// Best-effort — a viewer's short chat line ("一只猫跳舞") makes for a weak +// H3 prompt on its own. Never let a GLM outage or slow response block +// generation: on any failure this just falls back to the original text. +async function optimizePrompt(userPrompt) { + if (!GLM_API_KEY) return userPrompt; + try { + const res = await glmChat([ + { role: "system", content: PROMPT_OPTIMIZE_SYSTEM }, + { role: "user", content: userPrompt }, + ]); + const text = res.choices && res.choices[0] && res.choices[0].message && res.choices[0].message.content; + return text ? text.trim() : userPrompt; + } catch { + return userPrompt; + } +} + // ---- Live channel: shared virtual playhead + prompt queue ---- // Persisted to live-state.json (see saveLiveState/loadLiveState) so a // restart resumes the schedule instead of starting cold; ensureSchedule's @@ -277,9 +338,13 @@ function isRateLimited(ip) { async function generateForPrompt(entry) { try { if (!entry.h3JobId) { + if (!entry.expandedPrompt) { + entry.expandedPrompt = await optimizePrompt(entry.prompt); + saveLiveState(); + } const submitted = await h3ConsoleFetch("/v1/videos", { method: "POST", - body: { prompt: entry.prompt, ...H3_GEN_PARAMS }, + body: { prompt: entry.expandedPrompt, ...H3_GEN_PARAMS }, }); if (!submitted.id) throw new Error(submitted.detail || "submit failed"); entry.h3JobId = submitted.id; @@ -295,7 +360,7 @@ async function generateForPrompt(entry) { id: job.id, source: "h3console", prompt: entry.prompt, - fullPrompt: entry.prompt, + fullPrompt: entry.expandedPrompt || entry.prompt, width: job.req.width, height: job.req.height, duration: Math.round((job.req.frames / FPS) * 10) / 10, -- libgit2 0.21.4