import { EXAMPLE_PROMPTS, GENERATION_DEFAULTS, PROCESSING_STAGES, STORAGE_KEY, buildDownloadStem, buildPromptStats, createProcessingSnapshot, sanitizePrompt, } from "./prompt-builder.js"; const sessionStorageKey = "image-prompt-studio:session-id:v1"; const elements = { promptInput: document.querySelector("#promptInput"), promptStats: document.querySelector("#promptStats"), examplePromptList: document.querySelector("#examplePromptList"), clearPromptButton: document.querySelector("#clearPromptButton"), generateButton: document.querySelector("#generateButton"), healthLabel: document.querySelector("#healthLabel"), modelLabel: document.querySelector("#modelLabel"), processingBadge: document.querySelector("#processingBadge"), processingLead: document.querySelector("#processingLead"), processingSteps: document.querySelector("#processingSteps"), progressFill: document.querySelector("#progressFill"), requestMeta: document.querySelector("#requestMeta"), resultStage: document.querySelector("#resultStage"), errorBanner: document.querySelector("#errorBanner"), downloadButton: document.querySelector("#downloadButton"), }; const state = { prompt: loadPrompt(), isGenerating: false, health: { configured: false, model: "gpt-image-2", }, results: [], error: "", requestMeta: null, sessionId: getSessionId(), processing: { progress: 0, activeStageIndex: -1, lead: "准备就绪,输入 Prompt 后即可开始生成。", }, processingTimer: null, startedAt: 0, }; renderExamplePrompts(); elements.promptInput.value = state.prompt; updatePromptStats(); render(); wireEvents(); void checkHealth(); function wireEvents() { elements.promptInput.addEventListener("input", () => { state.prompt = elements.promptInput.value; persistPrompt(); updatePromptStats(); render(); }); elements.clearPromptButton.addEventListener("click", () => { state.prompt = ""; state.results = []; state.requestMeta = null; state.error = ""; state.processing = { progress: 0, activeStageIndex: -1, lead: "准备就绪,输入 Prompt 后即可开始生成。", }; elements.promptInput.value = ""; persistPrompt(); updatePromptStats(); render(); }); elements.generateButton.addEventListener("click", () => { void generateImages(); }); elements.downloadButton.addEventListener("click", () => { const result = state.results[0]; if (result) { downloadResult(result); } }); document.body.addEventListener("keydown", (event) => { if ((event.metaKey || event.ctrlKey) && event.key === "Enter") { void generateImages(); } }); elements.examplePromptList.addEventListener("click", (event) => { const button = event.target.closest("[data-prompt]"); if (!button) { return; } state.prompt = button.dataset.prompt ?? ""; elements.promptInput.value = state.prompt; persistPrompt(); updatePromptStats(); render(); }); } async function generateImages() { if (state.isGenerating) { return; } const prompt = sanitizePrompt(state.prompt); if (!prompt) { setError("请输入 Prompt 后再生成图片。"); return; } state.prompt = prompt; elements.promptInput.value = prompt; persistPrompt(); state.isGenerating = true; state.error = ""; state.results = []; state.requestMeta = null; startProcessingTicker(); render(); try { const response = await fetch("/api/generate", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ prompt, ...GENERATION_DEFAULTS, user: state.sessionId, }), }); const payload = await response.json(); if (!response.ok || !payload.ok) { throw new Error(payload.error || "图片生成失败。"); } const stem = buildDownloadStem(prompt); state.results = payload.images.map((image, index) => ({ ...image, previewUrl: `data:${image.mimeType};base64,${image.base64}`, fileName: `${stem}-${index + 1}.${resolveExtension(image.mimeType)}`, })); state.requestMeta = payload.meta; state.processing = createProcessingSnapshot( Date.now() - state.startedAt, true, ); state.error = ""; } catch (error) { setError(error.message || "图片生成失败,请稍后重试。"); } finally { state.isGenerating = false; stopProcessingTicker(); render(); } } async function checkHealth() { try { const response = await fetch("/api/health"); const payload = await response.json(); state.health = payload; } catch { state.health = { configured: false, model: "gpt-image-2", }; } render(); } function startProcessingTicker() { stopProcessingTicker(); state.startedAt = Date.now(); state.processing = createProcessingSnapshot(0); state.processingTimer = window.setInterval(() => { state.processing = createProcessingSnapshot(Date.now() - state.startedAt); render(); }, 140); } function stopProcessingTicker() { if (state.processingTimer) { window.clearInterval(state.processingTimer); state.processingTimer = null; } } function render() { elements.modelLabel.textContent = state.health.model || "gpt-image-2"; elements.healthLabel.textContent = state.health.configured ? "已连接服务端" : "缺少 OPENAI_API_KEY"; elements.processingLead.textContent = state.processing.lead; elements.processingBadge.textContent = state.isGenerating ? "处理中" : state.results.length > 0 ? "已完成" : "待命"; elements.progressFill.style.width = `${Math.round(state.processing.progress * 100)}%`; elements.requestMeta.textContent = buildRequestMeta(); elements.errorBanner.hidden = !state.error; elements.errorBanner.textContent = state.error; elements.generateButton.disabled = state.isGenerating; elements.generateButton.textContent = state.isGenerating ? "生成中..." : "生成图片"; elements.downloadButton.disabled = state.results.length === 0; renderProcessingSteps(); renderResultStage(); } function renderExamplePrompts() { elements.examplePromptList.innerHTML = EXAMPLE_PROMPTS.map( (prompt) => ` `, ).join(""); } function renderProcessingSteps() { elements.processingSteps.innerHTML = PROCESSING_STAGES.map((stage, index) => { const status = resolveStageStatus(index); return `
${escapeHtml(stage.detail)}
当前只保留最简流程:输入 Prompt、点击生成、等待完成、下载图片。
`; return; } const result = state.results[0]; const revisedPromptMarkup = result.revisedPrompt ? `${escapeHtml(result.revisedPrompt)}
${escapeHtml(state.prompt)}