Help
RSS
API
Feed
Maltego
Contact
Domain > aigen.cuem.qzz.io
×
More information on this domain is in
AlienVault OTX
Is this malicious?
Yes
No
DNS Resolutions
Date
IP Address
2025-09-12
172.67.168.166
(
ClassC
)
2025-10-06
104.21.94.199
(
ClassC
)
Port 80
HTTP/1.1 200 OKDate: Mon, 06 Oct 2025 10:01:06 GMTContent-Type: text/htmlContent-Length: 67774Connection: keep-aliveAccess-Control-Allow-Origin: *Access-Control-Allow-Headers: Content-TypeAccess-Control-Allow-Methods: GET, POST, OPTIONSVary: accept-encodingReport-To: {group:cf-nel,max_age:604800,endpoints:{url:https://a.nel.cloudflare.com/report/v4?svB27BFm2Z98dJDK%2FQgt6nbjhVE4hbyonBm2nyOokNJGSa4j4KB27ESy%2B4ZMBE%2FX9eSBvQTQ%2FrpjcykJu7o%2BGoeKT%2BBGeMkAXySbnUaaogCu5}}Nel: {report_to:cf-nel,success_fraction:0.0,max_age:604800}Server: cloudflareCF-RAY: 98a450c609a931df-PDXalt-svc: h3:443; ma86400 !DOCTYPE html>html langzh classlight>head> meta charsetUTF-8> meta nameviewport contentwidthdevice-width, initial-scale1.0> title>基于 CloudFlare 的在线文生图服务/title> link hrefhttps://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css relstylesheet crossoriginanonymous> link relstylesheet hrefhttps://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css> link relicon typeimage/x-icon hrefhttps://cdn.jsdelivr.net/gh/huarzone/Text2img-Cloudflare-Workers@main/public/favicon.ico> script srchttps://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js>/script> style> :root { --primary: #5046e5; --primary-light: #6e67eb; --primary-dark: #4338ca; --secondary: #f0f4f8; --text: #1a202c; --text-light: #4a5568; --background: #ffffff; --card-bg: #f7fafc; --border: #e2e8f0; --success: #10b981; --error: #ef4444; --warning: #f59e0b; --info: #3b82f6; } .dark { --primary: #6e67eb; --primary-light: #8a84ee; --primary-dark: #5046e5; --secondary: #2d3748; --text: #f7fafc; --text-light: #cbd5e0; --background: #111827; --card-bg: #1f2937; --border: #374151; --success: #10b981; --error: #ef4444; --warning: #f59e0b; --info: #3b82f6; } body { background-color: var(--background); color: var(--text); transition: background-color 0.3s ease, color 0.3s ease; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif; } .btn { padding: 1rem 1.5rem; border-radius: 0.375rem; transition: all 0.3s; } .btn:focus { outline: none; } .btn-primary { background-color: var(--primary); color: white; } .btn-primary:hover { background-color: var(--primary-light); transform: translateY(-1px); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); } .btn-primary:active { background-color: var(--primary-dark); transform: translateY(0); } .btn-secondary { background-color: var(--secondary); color: var(--text); border: 1px solid var(--border); } .btn-secondary:hover { background-color: var(--border); transform: translateY(-1px); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); } .btn-secondary:active { transform: translateY(0); } .card { background-color: var(--card-bg); border: 1px solid var(--border); border-radius: 0.5rem; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; } .card:hover { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); } input, select, textarea { background-color: var(--background); color: var(--text); border: 1px solid var(--border); border-radius: 0.375rem; padding: 0.5rem 0.75rem; transition: all 0.3s ease; width: 100%; } input:focus, select:focus, textarea:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(80, 70, 229, 0.1); } .slider { -webkit-appearance: none; appearance: none; width: 100%; height: 6px; border-radius: 5px; background: var(--border); outline: none; margin: 10px 0; } .slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 18px; height: 18px; border-radius: 50%; background: var(--primary); cursor: pointer; transition: all 0.2s ease; } .slider::-webkit-slider-thumb:hover { transform: scale(1.2); box-shadow: 0 0 0 3px rgba(80, 70, 229, 0.2); } .slider::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: var(--primary); cursor: pointer; transition: all 0.2s ease; border: none; } .slider::-moz-range-thumb:hover { transform: scale(1.2); box-shadow: 0 0 0 3px rgba(80, 70, 229, 0.2); } .animate-pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .5; } } .loading-mask { position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center; background-color: rgba(0,0,0,0.6); border-radius: 0.5rem; z-index: 10; backdrop-filter: blur(4px); } .image-container { aspect-ratio: 1 / 1; overflow: hidden; display: flex; align-items: center; justify-content: center; background-color: var(--card-bg); position: relative; border-radius: 0.5rem; max-height: 400px; /* 添加最大高度限制 */ margin: 0 auto; /* 居中显示 */ width: 100%; /* 保持宽度响应式 */ } #imageStatus { position: absolute; bottom: 1rem; left: 1rem; z-index: 20; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 500; box-shadow: 0 2px 4px rgba(0,0,0,0.1); transition: all 0.3s ease; } .param-badge { background-color: var(--secondary); color: var(--text); padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-size: 0.75rem; margin-right: 0.5rem; margin-bottom: 0.5rem; display: inline-block; border: 1px solid var(--border); } .fade-in { animation: fadeIn 0.5s ease-in-out; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .tooltip { position: relative; display: inline-block; } .tooltip .tooltiptext { visibility: hidden; width: 200px; background-color: var(--card-bg); color: var(--text); text-align: center; border-radius: 6px; padding: 8px; position: absolute; z-index: 1; bottom: 125%; left: 50%; margin-left: -100px; opacity: 0; transition: opacity 0.3s; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); border: 1px solid var(--border); font-size: 0.75rem; } .tooltip:hover .tooltiptext { visibility: visible; opacity: 1; } .hidden { display: none !important; } @media (max-width: 768px) { .mobile-flex-col { flex-direction: column; } .container { padding-left: 1rem; padding-right: 1rem; } .image-container { max-height: 400px; /* 移动端适当减小最大高度 */ } } @media (min-width: 1024px) { .container { max-width: 1200px; } } /style>/head>body classmin-h-screen py-4> div classcontainer mx-auto px-4 py-4 max-w-6xl> div classflex items-center justify-between mb-6> h1 classtext-2xl md:text-3xl font-bold flex items-center> 🐳 在线文生图服务 /h1> div classflex items-center space-x-2> button idthemeToggle classbtn btn-secondary p-2 h-10 w-10 flex items-center justify-center aria-label切换暗色主题> i classfa-solid fa-moon>/i> /button> button idgithub classbtn btn-secondary p-2 h-10 w-10 flex items-center justify-center aria-label项目地址 onclickwindow.open(https://github.com/huarzone/Text2img-Cloudflare-Workers, _blank)> i classfa-brands fa-github>/i> /button> /div> /div> div classflex flex-col lg:flex-row gap-6 mobile-flex-col> !-- 左侧控制面板 --> div classw-full lg:w-2/5 space-y-4> div classcard p-4 space-y-4 fade-in> div classflex justify-between items-center> h2 classtext-lg font-semibold flex items-center> i classfa-solid fa-sliders mr-2 text-primary>/i> 基本设置 /h2> button idrandomButton classbtn btn-secondary text-sm py-1 px-3 flex items-center> i classfa-solid fa-dice mr-1>/i> 随机提示词 /button> /div> div> label forpassword classblock text-sm font-medium mb-1 flex items-center> i classfa-solid fa-key mr-1 text-xs>/i> 访问密码 /label> input typepassword idpassword placeholder请输入访问密码 classw-full valueadmin123> /div> div> label forprompt classblock text-sm font-medium mb-1 flex items-center> i classfa-solid fa-wand-magic-sparkles mr-1 text-xs>/i> 正向提示词 /label> textarea idprompt rows3 placeholder描述您想要生成的图像内容及样式... classw-full>cyberpunk cat/textarea> /div> div> label fornegative_prompt classblock text-sm font-medium mb-1 flex items-center> i classfa-solid fa-ban mr-1 text-xs>/i> 反向提示词 /label> textarea idnegative_prompt rows2 placeholder描述在生成的图像中要避免的元素文本... classw-full>/textarea> /div> div> label formodel classblock text-sm font-medium mb-1 flex items-center> i classfa-solid fa-robot mr-1 text-xs>/i> 文生图模型 /label> select idmodel classw-full> option valueloading disabled selected>加载中.../option> /select> p classtext-xs text-gray-500 mt-2>部分模型需要提供图片URL或遮罩URL(见下方输入框)。/p> /div> div idimg2imgInputs classhidden space-y-2> div> label forimage_url classblock text-sm font-medium mb-1 flex items-center> i classfa-solid fa-image mr-1 text-xs>/i> 输入图像URL(必填) /label> input typeurl idimage_url placeholderhttps://example.com/your-image.png classw-full> /div> div> label formask_url classblock text-sm font-medium mb-1 flex items-center> i classfa-solid fa-mask mr-1 text-xs>/i> 遮罩图像URL(必填,inpainting 使用) /label> input typeurl idmask_url placeholderhttps://example.com/your-mask.png classw-full> /div> /div> /div> div classcard p-4 space-y-4 fade-in> div classflex justify-between items-center> h2 classtext-lg font-semibold flex items-center> i classfa-solid fa-gear mr-2 text-primary>/i> 高级选项 /h2> button idtoggleAdvanced classtext-xs btn btn-secondary py-1 px-3 flex items-center> i classfa-solid fa-chevron-down mr-1 idadvancedIcon>/i> 显示/隐藏 /button> /div> div idadvancedOptions classspace-y-3 hidden> div> div classflex justify-between items-center> label forwidth classblock text-sm font-medium flex items-center> i classfa-solid fa-arrows-left-right mr-1 text-xs>/i> 图像宽度 /label> span idwidthValue classtext-sm font-mono>1024px/span> /div> input typerange idwidth min256 max2048 step64 value1024 classslider w-full> /div> div> div classflex justify-between items-center> label forheight classblock text-sm font-medium flex items-center> i classfa-solid fa-arrows-up-down mr-1 text-xs>/i> 图像高度 /label> span idheightValue classtext-sm font-mono>1024px/span> /div> input typerange idheight min256 max2048 step64 value1024 classslider w-full> /div> div> div classflex justify-between items-center> label fornum_steps classblock text-sm font-medium flex items-center tooltip> i classfa-solid fa-shoe-prints mr-1 text-xs>/i> 迭代步数 span classtooltiptext>更高的步数通常会产生更精细的细节,但需要更长的处理时间/span> /label> span idnum_stepsValue classtext-sm font-mono>20/span> /div> input typerange idnum_steps min1 max20 step1 value20 classslider w-full> /div> div> div classflex justify-between items-center> label forguidance classblock text-sm font-medium flex items-center tooltip> i classfa-solid fa-compass mr-1 text-xs>/i> 引导系数 span classtooltiptext>控制生成图像与提示词的匹配程度,较高的值会更严格遵循提示词/span> /label> span idguidanceValue classtext-sm font-mono>7.5/span> /div> input typerange idguidance min0 max30 step0.5 value7.5 classslider w-full> /div> div> label forseed classblock text-sm font-medium mb-1 flex items-center tooltip> i classfa-solid fa-seedling mr-1 text-xs>/i> 随机种子 span classtooltiptext>使用相同的种子值可以在其他参数相同的情况下生成相似的图像/span> /label> div classflex gap-2> input typenumber idseed placeholder随机种子值 classw-full> button idrandomSeed classbtn btn-secondary text-sm py-1 px-3> i classfa-solid fa-random>/i> /button> /div> p classtext-xs text-gray-500 mt-1>留空则随机生成/p> /div> /div> /div> button idsubmitButton classbtn btn-primary w-full py-3 flex items-center justify-center> i classfa-solid fa-wand-magic-sparkles mr-2>/i> 生成图像 /button> /div> !-- 右侧图像展示 --> div classw-full lg:w-3/5> div classcard h-full p-4 space-y-4 fade-in> div classflex justify-between items-center> h2 classtext-lg font-semibold flex items-center> i classfa-solid fa-image mr-2 text-primary>/i> 生成结果 /h2> div classflex space-x-2> button idcopyParamsButton classbtn btn-secondary text-sm py-1 px-3 hidden> i classfa-solid fa-copy mr-1>/i> 复制参数 /button> button iddownloadButton classbtn btn-secondary text-sm py-1 px-3 hidden> i classfa-solid fa-download mr-1>/i> 下载图像 /button> button iddownloadZipButton classbtn btn-secondary text-sm py-1 px-3 hidden> i classfa-solid fa-file-zipper mr-1>/i> 下载ZIP /button> /div> /div> div classimage-container card> div idloadingOverlay classloading-mask hidden> div classtext-center w-full px-6> div classinline-block animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-white>/div> p classtext-white mt-3 font-medium>生成中,请稍候.../p> p classtext-white text-sm mt-1>这可能需要几秒到几十秒不等/p> div idprogressBarContainer classw-full mt-4> div classw-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700> div idprogressBar classbg-blue-500 h-2.5 rounded-full stylewidth: 0%>/div> /div> div classflex justify-between text-xs mt-1 text-white> span idprogressText>0%/span> span idprogressExtra>估算/span> /div> /div> /div> /div> div idinitialPrompt classtext-center text-gray-400 dark:text-gray-600> i classfa-solid fa-image-portrait text-4xl mb-2>/i> p>点击生成按钮开始创建图像/p> /div> span idimageStatus classbg-gray-300 text-gray-700 hidden>状态/span> img idaiImage classmax-h-full max-w-full rounded hidden alt生成的图像> div idimageGallery classgrid grid-cols-4 gap-2 mt-3 hidden>/div> /div> div idimageInfo classspace-y-3 mt-2> div classgrid grid-cols-2 gap-3> div classtext-sm flex items-center> span classfont-medium flex items-center> i classfa-regular fa-clock mr-1 text-xs>/i> 生成时间: /span> span idgenerationTime classml-1>-/span> /div> div classtext-sm flex items-center> span classfont-medium flex items-center> i classfa-solid fa-microchip mr-1 text-xs>/i> 使用模型: /span> span idusedModel classml-1>-/span> /div> div classtext-sm flex items-center> span classfont-medium flex items-center> i classfa-solid fa-bolt mr-1 text-xs>/i> 算力估计: /span> span idcomputeInfo classml-1>-/span> span classtext-xs text-gray-500 ml-2>(it/s)/span> /div> div classtext-sm flex items-center> span classfont-medium flex items-center> i classfa-solid fa-database mr-1 text-xs>/i> 输出大小: /span> span idimageSize classml-1>-/span> /div> /div> div idallParamsContainer classhidden mt-3 border-t border-gray-200 dark:border-gray-700 pt-3> h3 classtext-sm font-medium mb-2 flex items-center> i classfa-solid fa-list-check mr-1>/i> 所有参数 /h3> div idallParams classflex flex-wrap>/div> /div> div idimageMeta classhidden mt-3 border-t border-gray-200 dark:border-gray-700 pt-3> h3 classtext-sm font-medium mb-2 flex items-center> i classfa-solid fa-table mr-1>/i> 每张图片信息 /h3> table classw-full text-xs> thead> tr classtext-left> th classpy-1>#/th> th classpy-1>尺寸/th> th classpy-1>大小/th> /tr> /thead> tbody idimageMetaBody>/tbody> /table> /div> /div> /div> /div> /div> /div> script> document.addEventListener(DOMContentLoaded, function () { // 初始化模型列表 let availableModels ; let randomPromptsList ; let currentImageParams {}; let currentImagesArray null; // 多图时的 dataURL 数组 // 加载模型列表 async function loadModels() { try { const response await fetch(/api/models); if (!response.ok) { throw new Error(加载模型列表失败); } availableModels await response.json(); const modelSelect document.getElementById(model); // 清空当前选项 modelSelect.innerHTML ; // 添加新选项 availableModels.forEach(model > { const option document.createElement(option); option.value model.id; option.textContent `${model.name} - ${model.description}`; option.dataset.requiresImage model.requiresImage ? 1 : 0; modelSelect.appendChild(option); }); // 默认选择第二个模型(通常是更好的模型) if (availableModels.length > 1) { modelSelect.value availableModels1.id; } } catch (error) { console.error(加载模型列表错误:, error); showStatus(加载模型列表失败, error); } } // 加载随机提示词 async function loadRandomPrompts() { try { const response await fetch(/api/prompts); if (!response.ok) { throw new Error(加载提示词失败); } randomPromptsList await response.json(); } catch (error) { console.error(加载提示词错误:, error); randomPromptsList 未能加载提示词列表,请重试或手动输入; } } // 初始化加载资源 loadModels(); loadRandomPrompts(); // 检查是否需要登录 (async () > { try { const res await fetch(/api/config); if (res.ok) { const cfg await res.json(); if (cfg.require_password) { showLogin(); } } } catch (_) {} })(); // 主题切换功能相关代码 const themeToggle document.getElementById(themeToggle); const html document.documentElement; const moonIcon `i classfa-solid fa-moon>/i>`; const sunIcon `i classfa-solid fa-sun>/i>`; // 检查系统主题或存储的主题并设置初始状态 if (localStorage.theme dark || (!(theme in localStorage) && window.matchMedia((prefers-color-scheme: dark)).matches)) { html.classList.add(dark); themeToggle.innerHTML sunIcon; themeToggle.setAttribute(aria-label, 切换亮色主题); } else { html.classList.remove(dark); themeToggle.innerHTML moonIcon; themeToggle.setAttribute(aria-label, 切换暗色主题); } themeToggle.addEventListener(click, function() { if (html.classList.contains(dark)) { html.classList.remove(dark); localStorage.theme light; themeToggle.innerHTML moonIcon; themeToggle.setAttribute(aria-label, 切换暗色主题); } else { html.classList.add(dark); localStorage.theme dark; themeToggle.innerHTML sunIcon; themeToggle.setAttribute(aria-label, 切换亮色主题); } }); // 高级选项切换 const toggleAdvanced document.getElementById(toggleAdvanced); const advancedOptions document.getElementById(advancedOptions); const advancedIcon document.getElementById(advancedIcon); toggleAdvanced.addEventListener(click, function() { if (advancedOptions.classList.contains(hidden)) { advancedOptions.classList.remove(hidden); advancedIcon.classList.remove(fa-chevron-down); advancedIcon.classList.add(fa-chevron-up); } else { advancedOptions.classList.add(hidden); advancedIcon.classList.remove(fa-chevron-up); advancedIcon.classList.add(fa-chevron-down); } }); // 模型切换时显示/隐藏图生图字段 const modelSelectEl document.getElementById(model); const img2imgInputs document.getElementById(img2imgInputs); if (modelSelectEl && img2imgInputs) { modelSelectEl.addEventListener(change, function() { const selected availableModels.find(m > m.id this.value); if (selected && selected.requiresImage) { img2imgInputs.classList.remove(hidden); } else { img2imgInputs.classList.add(hidden); } }); } // 滑块值显示 const sliders width, height, num_steps, guidance; sliders.forEach(id > { const slider document.getElementById(id); const valueDisplay document.getElementById(`${id}Value`); slider.addEventListener(input, function() { if (id width || id height) { valueDisplay.textContent `${this.value}px`; } else if (id guidance) { valueDisplay.textContent parseFloat(this.value).toFixed(2); } else { valueDisplay.textContent this.value; } }); }); // 生成数量控件 const countContainer document.createElement(div); countContainer.className mt-2; countContainer.innerHTML ` label fornum_outputs classblock text-sm font-medium mb-1 flex items-center> i classfa-solid fa-layer-group mr-1 text-xs>/i> 生成数量 /label> select idnum_outputs classw-full> option value1 selected>1/option> option value2>2/option> option value3>3/option> option value4>4/option> option value5>5/option> option value6>6/option> option value7>7/option> option value8>8/option> /select> `; document.querySelector(.card.p-4.space-y-4.fade-in).appendChild(countContainer); // 随机种子 document.getElementById(randomSeed).addEventListener(click, function() { const randomSeed Math.floor(Math.random() * 4294967295); document.getElementById(seed).value randomSeed; }); // 随机提示词 document.getElementById(randomButton).addEventListener(click, function() { if (randomPromptsList.length > 0) { const randomIndex Math.floor(Math.random() * randomPromptsList.length); document.getElementById(prompt).value randomPromptsListrandomIndex; } else { showStatus(提示词列表未加载,请稍后再试, error); } }); // 复制参数 document.getElementById(copyParamsButton).addEventListener(click, function() { if (!currentImageParams) return; // 创建参数文本 let paramsText --- AI绘图创作生成参数 ---\n; for (const key, value of Object.entries(currentImageParams)) { if (key password) continue; // 不复制密码 paramsText + `${formatParamName(key)}: ${value}\n`; } // 复制到剪贴板 navigator.clipboard.writeText(paramsText) .then(() > { showStatus(参数已复制到剪贴板, success); }) .catch(err > { console.error(复制失败:, err); showStatus(复制参数失败, error); }); }); // 格式化参数名称 function formatParamName(name) { const nameMap { prompt: 正向提示词, negative_prompt: 反向提示词, model: 文生图模型, width: 图像宽度, height: 图像高度, num_steps: 迭代步数, guidance: 引导系数, seed: 随机种子 }; return nameMapname || name; } // 下载图像 document.getElementById(downloadButton).addEventListener(click, async function() { const img document.getElementById(aiImage); const gallery document.getElementById(imageGallery); let src ; if (!gallery.classList.contains(hidden) && currentImagesArray && currentImagesArray.length > 0) { src currentImagesArray0; } else { src img.src; } if (!src) { showStatus(没有可下载的图像, error); return; } try { // 从图像数据创建blob const response await fetch(src); const blob await response.blob(); // 创建下载链接 const url window.URL.createObjectURL(blob); const link document.createElement(a); link.href url; // 生成文件名 const timestamp new Date().toISOString().replace(/:./g, -); const model document.getElementById(usedModel).textContent || ai-image; link.download `${model}-${timestamp}.png`; // 触发下载 document.body.appendChild(link); link.click(); // 清理 document.body.removeChild(link); window.URL.revokeObjectURL(url); showStatus(图像下载成功, success); } catch (error) { console.error(下载图像错误:, error); showStatus(下载图像失败, error); } }); // 下载 ZIP(多图) document.getElementById(downloadZipButton).addEventListener(click, async function() { if (!currentImagesArray || currentImagesArray.length 0) { showStatus(没有可打包的图片, error); return; } try { const zip new JSZip(); const model document.getElementById(usedModel).textContent || ai-image; const timestamp new Date().toISOString().replace(/:./g, -); for (let i 0; i currentImagesArray.length; i++) { const dataURL currentImagesArrayi; const base64 dataURL.split(,)1; zip.file(`${model}-${timestamp}-${i+1}.png`, base64, { base64: true }); } const blob await zip.generateAsync({ type: blob }); const url window.URL.createObjectURL(blob); const link document.createElement(a); link.href url; link.download `${model}-${timestamp}.zip`; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); showStatus(ZIP 下载已开始, success); } catch (e) { console.error(ZIP打包失败:, e); showStatus(打包ZIP失败, error); } }); // 提交生成请求 // 复用全局的计时器与控制器,避免闪烁 let progressTimer null; let pendingController null; document.getElementById(submitButton).addEventListener(click, async function() { // 显示加载中状态 const loadingOverlay document.getElementById(loadingOverlay); const initialPrompt document.getElementById(initialPrompt); const aiImage document.getElementById(aiImage); const progressBarContainer document.getElementById(progressBarContainer); const progressBar document.getElementById(progressBar); const progressText document.getElementById(progressText); const progressExtra document.getElementById(progressExtra); if (!loadingOverlay || !initialPrompt || !aiImage) { console.error(必要的DOM元素未找到); return; } // 隐藏初始提示和图像 initialPrompt.classList.add(hidden); aiImage.classList.add(hidden); loadingOverlay.classList.remove(hidden); // 隐藏之前的提示和按钮,并清理上一次状态 const imageStatus document.getElementById(imageStatus); const copyParamsButton document.getElementById(copyParamsButton); const downloadButton document.getElementById(downloadButton); if (imageStatus) imageStatus.classList.add(hidden); if (copyParamsButton) copyParamsButton.classList.add(hidden); if (downloadButton) downloadButton.classList.add(hidden); if (progressTimer) { clearInterval(progressTimer); progressTimer null; } if (pendingController) { try { pendingController.abort(); } catch(_){} pendingController null; } // 获取参数(初始草案) const rawParams { password: document.getElementById(password)?.value || , prompt: document.getElementById(prompt)?.value || , negative_prompt: document.getElementById(negative_prompt)?.value || , model: document.getElementById(model)?.value, width: parseInt(document.getElementById(width)?.value) || 1024, height: parseInt(document.getElementById(height)?.value) || 1024, num_steps: parseInt(document.getElementById(num_steps)?.value) || 20, guidance: parseFloat(document.getElementById(guidance)?.value) || 7.5, seed: parseInt(document.getElementById(seed)?.value) || Math.floor(Math.random() * 4294967295), image_url: document.getElementById(image_url)?.value || , mask_url: document.getElementById(mask_url)?.value || , num_outputs: parseInt(document.getElementById(num_outputs)?.value) || 1 }; // 基于模型特性,清洗出最终要发送/展示的参数 const selectedModelMeta availableModels.find(m > m.id rawParams.model); const params { password: rawParams.password, prompt: rawParams.prompt, negative_prompt: rawParams.negative_prompt, model: rawParams.model, width: rawParams.width, height: rawParams.height, num_steps: rawParams.num_steps, guidance: rawParams.guidance, seed: rawParams.seed, num_outputs: rawParams.num_outputs }; if (selectedModelMeta?.requiresImage) { params.image_url rawParams.image_url; } if (selectedModelMeta?.requiresMask) { params.mask_url rawParams.mask_url; } // 保存当前参数 currentImageParams {...params}; // 前端必填校验(依据模型元信息) if (selectedModelMeta && selectedModelMeta.requiresImage) { if (!rawParams.image_url) { showStatus(该模型需要提供输入图像URL, error); loadingOverlay.classList.add(hidden); initialPrompt.classList.remove(hidden); return; } } if (selectedModelMeta && selectedModelMeta.requiresMask) { if (!rawParams.mask_url) { showStatus(局部重绘模型需要提供遮罩URL, error); loadingOverlay.classList.add(hidden); initialPrompt.classList.remove(hidden); return; } } try { // 发送请求 const startTime performance.now(); // 启动拟真进度条 let progress 0; if (progressBarContainer && progressBar && progressText) { progressBarContainer.classList.remove(hidden); progress 0; progressBar.style.width 0%; progressText.textContent 0%; if (progressExtra) progressExtra.textContent 估算; const steps params.num_steps || 20; const baseMs Math.max(4000, Math.min(20000, steps * 600)); const startTs Date.now(); progressTimer setInterval(() > { const elapsed Date.now() - startTs; const ratio Math.min(0.95, elapsed / baseMs); // 最高到95%,等待真实完成再封顶 const cur Math.floor(ratio * 100); if (cur > progress) { progress cur; progressBar.style.width `${progress}%`; progressText.textContent `${progress}%`; } }, 150); } // 60秒超时控制 pendingController new AbortController(); const timeoutId setTimeout(() > pendingController && pendingController.abort(timeout), 60000); const response await fetch(/, { method: POST, headers: { Content-Type: application/json, Accept: image/* }, body: JSON.stringify(params), signal: pendingController.signal }); clearTimeout(timeoutId); if (!response.ok) { const contentType response.headers.get(content-type); if (contentType?.includes(application/json)) { const errorData await response.json(); const msg errorData.error || errorData.message || 生成失败; const details errorData.details ? `(${errorData.details})` : ; throw new Error(`${msg}${details}`); } else { const errorText await response.text(); console.error(服务器错误:, errorText); throw new Error(生成失败); } } const respType response.headers.get(content-type) || ; const serverSecondsHeader response.headers.get(X-Server-Seconds); let serverSeconds serverSecondsHeader ? parseFloat(serverSecondsHeader) : null; let base64Image ; let imageBlob null; let imagesArray null; if (respType.includes(application/json)) { const json await response.json(); if (Array.isArray(json.images)) { imagesArray json.images; base64Image imagesArray0; } else { throw new Error(响应格式错误); } } else { imageBlob await response.blob(); base64Image await blobToBase64(imageBlob); } const endTime performance.now(); const generationTime ((endTime - startTime) / 1000).toFixed(2); // 设置图像信息并显示图像 const gallery document.getElementById(imageGallery); gallery.innerHTML ; if (imagesArray && imagesArray.length > 1) { aiImage.classList.add(hidden); gallery.classList.remove(hidden); currentImagesArray imagesArray.slice(); imagesArray.forEach((src, idx) > { const wrap document.createElement(div); wrap.className relative group; const img document.createElement(img); img.src src; img.alt `生成的图像 ${idx+1}`; img.className w-full h-auto rounded; img.style.cursor zoom-in; img.addEventListener(click, () > openImageModal(src)); // 悬浮操作条 const bar document.createElement(div); bar.className absolute bottom-1 left-1 right-1 hidden group-hover:flex bg-black bg-opacity-50 text-white text-xs rounded px-1 py-0.5 gap-1 justify-center; const btnZoom document.createElement(button); btnZoom.innerHTML i classfa-solid fa-magnifying-glass-plus>/i>; btnZoom.title 放大; btnZoom.onclick (e) > { e.stopPropagation(); openImageModal(src); }; const btnCopy document.createElement(button); btnCopy.innerHTML i classfa-solid fa-copy>/i>; btnCopy.title 复制到剪贴板; btnCopy.onclick async (e) > { e.stopPropagation(); try { await navigator.clipboard.writeText(src); showStatus(图片已复制到剪贴板, success); } catch (_) { showStatus(复制失败, error); } }; const btnDl document.createElement(button); btnDl.innerHTML i classfa-solid fa-download>/i>; btnDl.title 下载此图; btnDl.onclick async (e) > { e.stopPropagation(); try { const response await fetch(src); const blob await response.blob(); const url window.URL.createObjectURL(blob); const link document.createElement(a); const model document.getElementById(usedModel).textContent || ai-image; const timestamp new Date().toISOString().replace(/:./g, -); link.href url; link.download `${model}-${timestamp}-${idx+1}.png`; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); } catch (_) { showStatus(下载失败, error); } }; bar.appendChild(btnZoom); bar.appendChild(btnCopy); bar.appendChild(btnDl); wrap.appendChild(img); wrap.appendChild(bar); gallery.appendChild(wrap); }); } else { gallery.classList.add(hidden); currentImagesArray null; aiImage.src base64Image; } // 统一的UI完成逻辑(不依赖 onload 也能执行一次) const finalize () > { // 图像加载完成后更新UI loadingOverlay.classList.add(hidden); if (!imagesArray || imagesArray.length 1) { aiImage.classList.remove(hidden); } // 安全地更新信息显示 const elements { generationTime: document.getElementById(generationTime), usedModel: document.getElementById(usedModel), computeInfo: document.getElementById(computeInfo), imageSize: document.getElementById(imageSize) }; if (elements.generationTime) { elements.generationTime.textContent `${generationTime}秒`; } if (elements.usedModel) { elements.usedModel.textContent getModelNameById(params.model); } // 从响应头提取信息 const usedModelHeader response.headers.get(X-Used-Model); const usedModelName usedModelHeader ? getModelNameById(usedModelHeader) : getModelNameById(params.model); if (elements.usedModel) elements.usedModel.textContent usedModelName; const bytesStr response.headers.get(X-Image-Bytes); if (elements.imageSize) { if (bytesStr) { const bytes parseInt(bytesStr, 10); elements.imageSize.textContent formatBytes(bytes); } else if (imageBlob) { // 回退:用blob大小 elements.imageSize.textContent formatBytes(imageBlob.size); } else if (imagesArray && imagesArray0) { elements.imageSize.textContent formatBytes(dataURLBytes(imagesArray0)); } } // 计算真实 it/s:迭代步数 / 实际耗时 const itPerSec (() > { const steps Number(params.num_steps) || 0; const seconds serverSeconds && serverSeconds > 0 ? serverSeconds : (parseFloat(generationTime) || 0); if (steps > 0 && seconds > 0) { return (steps / seconds).toFixed(2); } return -; })(); if (elements.computeInfo) { elements.computeInfo.textContent `${itPerSec}`; } // 更新所有参数面板(隐藏不相关字段) updateParamsDisplay(params); // 填充每张图片的尺寸与大小 const metaPanel document.getElementById(imageMeta); const metaBody document.getElementById(imageMetaBody); if (imagesArray && imagesArray.length > 0 && metaPanel && metaBody) { metaBody.innerHTML ; const loadPromises imagesArray.map((src, i) > new Promise((resolve) > { const probe new Image(); probe.onload () > { const tr document.createElement(tr); const sizeBytes dataURLBytes(src); tr.innerHTML `td class\py-1 pr-3\>${i+1}/td>td class\py-1 pr-3\>${probe.width}×${probe.height}/td>td class\py-1\>${formatBytes(sizeBytes)}/td>`; metaBody.appendChild(tr); resolve(); }; probe.onerror () > resolve(); probe.src src; })); Promise.all(loadPromises).then(() > metaPanel.classList.remove(hidden)); } else { const panel document.getElementById(imageMeta); if (panel) panel.classList.add(hidden); } // 进度条收口到100% if (progressBarContainer && progressBar && progressText) { if (progressTimer) clearInterval(progressTimer); progressBar.style.width 100%; progressText.textContent 100%; setTimeout(() > progressBarContainer.classList.add(hidden), 800); } // 显示状态和操作按钮 showStatus(imagesArray ? `生成成功(${imagesArray.length} 张)` : 生成成功, success); if (copyParamsButton) copyParamsButton.classList.remove(hidden); if (downloadButton) downloadButton.classList.remove(hidden); const downloadZipButton document.getElementById(downloadZipButton); if (downloadZipButton) { if (imagesArray && imagesArray.length > 1) downloadZipButton.classList.remove(hidden); else downloadZipButton.classList.add(hidden); } }; // 触发 finalize if (imagesArray && imagesArray.length > 1) { finalize(); } else { aiImage.onload finalize; } } catch (error) { console.error(生成图像错误:, error); if (error && (error.name AbortError || error.message timeout)) { showStatus(生成超时,请尝试更换模型或降低宽高后重试, error); } else { showStatus(error.message || 生成失败, error); } // 显示初始提示 initialPrompt.classList.remove(hidden); aiImage.classList.add(hidden); } finally { loadingOverlay.classList.add(hidden); if (progressTimer) { clearInterval(progressTimer); progressTimer null; } if (progressBarContainer) progressBarContainer.classList.add(hidden); pendingController null; } }); // 将Blob转换为Base64 function blobToBase64(blob) { return new Promise((resolve, reject) > { const reader new FileReader(); reader.onloadend () > resolve(reader.result); reader.onerror reject; reader.readAsDataURL(blob); }); } // 字节转可读字符串 function formatBytes(bytes) { if (!bytes && bytes ! 0) return -; const sizes B, KB, MB, GB; if (bytes 0) return 0 B; const i Math.floor(Math.log(bytes) / Math.log(1024)); const value (bytes / Math.pow(1024, i)).toFixed(2); return `${value} ${sizesi}`; } // 计算 dataURL 的字节大小(粗略) function dataURLBytes(dataURL) { try { const base64 dataURL.split(,)1 || ; // base64 长度 * 3/4 约等于字节数 return Math.floor((base64.length * 3) / 4); } catch (_) { return 0; } } // 简易图片放大预览 function openImageModal(src) { const existing document.getElementById(imgModal); if (existing) existing.remove(); const modal document.createElement(div); modal.id imgModal; modal.style.position fixed; modal.style.inset 0; modal.style.background rgba(0,0,0,0.7); modal.style.display flex; modal.style.alignItems center; modal.style.justifyContent center; modal.style.zIndex 1000; modal.innerHTML ` div classmax-w-5xl max-h-90vh p-2> img src${src} classrounded shadow-lg max-h-85vh mx-auto /> div classtext-center mt-2> button idcloseImgModal classbtn btn-secondary>关闭/button> /div> /div>`; document.body.appendChild(modal); document.getElementById(closeImgModal).onclick () > modal.remove(); modal.addEventListener(click, (e) > { if (e.target modal) modal.remove(); }); } // 通过ID获取模型名称 function getModelNameById(id) { const model availableModels.find(m > m.id id); return model ? model.name : id; } // 更新参数显示 function updateParamsDisplay(params) { const allParamsContainer document.getElementById(allParamsContainer); const allParamsElement document.getElementById(allParams); if (!allParamsContainer || !allParamsElement) return; // 清空现有参数 allParamsElement.innerHTML ; // 添加新参数 for (const key, value of Object.entries(params)) { if (key password) continue; // 不显示密码 const paramName formatParamName(key); const paramValue value; // 创建参数徽章 const badge document.createElement(div); badge.className param-badge; badge.innerHTML `span classfont-medium>${paramName}:/span> ${paramValue}`; allParamsElement.appendChild(badge); } // 显示参数容器 allParamsContainer.classList.remove(hidden); } // 显示状态提示 function showStatus(message, type info) { const statusElement document.getElementById(imageStatus); if (!statusElement) return; // 设置样式 statusElement.className ; switch (type) { case success: statusElement.classList.add(bg-green-100, text-green-800, dark:bg-green-900, dark:text-green-100); break; case error: statusElement.classList.add(bg-red-100, text-red-800, dark:bg-red-900, dark:text-red-100); break; case warning: statusElement.classList.add(bg-yellow-100, text-yellow-800, dark:bg-yellow-900, dark:text-yellow-100); break; default: statusElement.classList.add(bg-blue-100, text-blue-800, dark:bg-blue-900, dark:text-blue-100); } // 设置消息 statusElement.textContent message; // 显示 statusElement.classList.remove(hidden); // 5秒后自动隐藏 setTimeout(() > { statusElement.classList.add(hidden); }, 5000); } }); // 登录遮罩及逻辑 function showLogin() { if (document.getElementById(loginOverlay)) return; const overlay document.createElement(div); overlay.id loginOverlay; overlay.style.position fixed; overlay.style.inset 0; overlay.style.backdropFilter blur(3px); overlay.style.background rgba(0,0,0,0.4); overlay.style.zIndex 1000; overlay.innerHTML ` div classw-full h-full flex items-center justify-center> div classcard p-6 w-96 bg-white dark:bg-gray-800> h3 classtext-lg font-semibold mb-4>请输入访问密码/h3> input typepassword idloginPassword classw-full placeholder访问密码 /> button idloginButton classbtn btn-primary w-full mt-4>登录/button> p idloginError classtext-red-500 text-sm mt-2 hidden>密码错误/p> /div> /div>`; document.body.appendChild(overlay); document.getElementById(loginPassword).focus(); const submitLogin async () > { const pwd (document.getElementById(loginPassword).value || ).trim(); try { const resp await fetch(/api/auth, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ password: pwd }) }); if (!resp.ok) { document.getElementById(loginError).classList.remove(hidden); return; } overlay.remove(); } catch (_) { document.getElementById(loginError).classList.remove(hidden); } }; document.getElementById(loginButton).addEventListener(click, submitLogin); document.getElementById(loginPassword).addEventListener(keydown, (e) > { if (e.key Enter) submitLogin(); }); } /script>/body>/html>
Port 443
HTTP/1.1 200 OKDate: Mon, 06 Oct 2025 10:01:06 GMTContent-Type: text/htmlContent-Length: 67774Connection: keep-aliveAccess-Control-Allow-Origin: *Access-Control-Allow-Headers: Content-TypeAccess-Control-Allow-Methods: GET, POST, OPTIONSVary: accept-encodingReport-To: {group:cf-nel,max_age:604800,endpoints:{url:https://a.nel.cloudflare.com/report/v4?s7l5g5edCKbYLUsvCrvhavaqLIugojrDNYTWPdl0RfAsT63b5u0CxMxlkGNI6MufqJzyBCZLa%2BcFFRXUy%2BbMXhGzFYwSHBVIA6wu7EphsNA7Z}}Nel: {report_to:cf-nel,success_fraction:0.0,max_age:604800}Server: cloudflareCF-RAY: 98a450c7ddee87cd-PDXalt-svc: h3:443; ma86400 !DOCTYPE html>html langzh classlight>head> meta charsetUTF-8> meta nameviewport contentwidthdevice-width, initial-scale1.0> title>基于 CloudFlare 的在线文生图服务/title> link hrefhttps://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css relstylesheet crossoriginanonymous> link relstylesheet hrefhttps://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css> link relicon typeimage/x-icon hrefhttps://cdn.jsdelivr.net/gh/huarzone/Text2img-Cloudflare-Workers@main/public/favicon.ico> script srchttps://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js>/script> style> :root { --primary: #5046e5; --primary-light: #6e67eb; --primary-dark: #4338ca; --secondary: #f0f4f8; --text: #1a202c; --text-light: #4a5568; --background: #ffffff; --card-bg: #f7fafc; --border: #e2e8f0; --success: #10b981; --error: #ef4444; --warning: #f59e0b; --info: #3b82f6; } .dark { --primary: #6e67eb; --primary-light: #8a84ee; --primary-dark: #5046e5; --secondary: #2d3748; --text: #f7fafc; --text-light: #cbd5e0; --background: #111827; --card-bg: #1f2937; --border: #374151; --success: #10b981; --error: #ef4444; --warning: #f59e0b; --info: #3b82f6; } body { background-color: var(--background); color: var(--text); transition: background-color 0.3s ease, color 0.3s ease; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif; } .btn { padding: 1rem 1.5rem; border-radius: 0.375rem; transition: all 0.3s; } .btn:focus { outline: none; } .btn-primary { background-color: var(--primary); color: white; } .btn-primary:hover { background-color: var(--primary-light); transform: translateY(-1px); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); } .btn-primary:active { background-color: var(--primary-dark); transform: translateY(0); } .btn-secondary { background-color: var(--secondary); color: var(--text); border: 1px solid var(--border); } .btn-secondary:hover { background-color: var(--border); transform: translateY(-1px); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); } .btn-secondary:active { transform: translateY(0); } .card { background-color: var(--card-bg); border: 1px solid var(--border); border-radius: 0.5rem; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; } .card:hover { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); } input, select, textarea { background-color: var(--background); color: var(--text); border: 1px solid var(--border); border-radius: 0.375rem; padding: 0.5rem 0.75rem; transition: all 0.3s ease; width: 100%; } input:focus, select:focus, textarea:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(80, 70, 229, 0.1); } .slider { -webkit-appearance: none; appearance: none; width: 100%; height: 6px; border-radius: 5px; background: var(--border); outline: none; margin: 10px 0; } .slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 18px; height: 18px; border-radius: 50%; background: var(--primary); cursor: pointer; transition: all 0.2s ease; } .slider::-webkit-slider-thumb:hover { transform: scale(1.2); box-shadow: 0 0 0 3px rgba(80, 70, 229, 0.2); } .slider::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: var(--primary); cursor: pointer; transition: all 0.2s ease; border: none; } .slider::-moz-range-thumb:hover { transform: scale(1.2); box-shadow: 0 0 0 3px rgba(80, 70, 229, 0.2); } .animate-pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .5; } } .loading-mask { position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center; background-color: rgba(0,0,0,0.6); border-radius: 0.5rem; z-index: 10; backdrop-filter: blur(4px); } .image-container { aspect-ratio: 1 / 1; overflow: hidden; display: flex; align-items: center; justify-content: center; background-color: var(--card-bg); position: relative; border-radius: 0.5rem; max-height: 400px; /* 添加最大高度限制 */ margin: 0 auto; /* 居中显示 */ width: 100%; /* 保持宽度响应式 */ } #imageStatus { position: absolute; bottom: 1rem; left: 1rem; z-index: 20; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 500; box-shadow: 0 2px 4px rgba(0,0,0,0.1); transition: all 0.3s ease; } .param-badge { background-color: var(--secondary); color: var(--text); padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-size: 0.75rem; margin-right: 0.5rem; margin-bottom: 0.5rem; display: inline-block; border: 1px solid var(--border); } .fade-in { animation: fadeIn 0.5s ease-in-out; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .tooltip { position: relative; display: inline-block; } .tooltip .tooltiptext { visibility: hidden; width: 200px; background-color: var(--card-bg); color: var(--text); text-align: center; border-radius: 6px; padding: 8px; position: absolute; z-index: 1; bottom: 125%; left: 50%; margin-left: -100px; opacity: 0; transition: opacity 0.3s; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); border: 1px solid var(--border); font-size: 0.75rem; } .tooltip:hover .tooltiptext { visibility: visible; opacity: 1; } .hidden { display: none !important; } @media (max-width: 768px) { .mobile-flex-col { flex-direction: column; } .container { padding-left: 1rem; padding-right: 1rem; } .image-container { max-height: 400px; /* 移动端适当减小最大高度 */ } } @media (min-width: 1024px) { .container { max-width: 1200px; } } /style>/head>body classmin-h-screen py-4> div classcontainer mx-auto px-4 py-4 max-w-6xl> div classflex items-center justify-between mb-6> h1 classtext-2xl md:text-3xl font-bold flex items-center> 🐳 在线文生图服务 /h1> div classflex items-center space-x-2> button idthemeToggle classbtn btn-secondary p-2 h-10 w-10 flex items-center justify-center aria-label切换暗色主题> i classfa-solid fa-moon>/i> /button> button idgithub classbtn btn-secondary p-2 h-10 w-10 flex items-center justify-center aria-label项目地址 onclickwindow.open(https://github.com/huarzone/Text2img-Cloudflare-Workers, _blank)> i classfa-brands fa-github>/i> /button> /div> /div> div classflex flex-col lg:flex-row gap-6 mobile-flex-col> !-- 左侧控制面板 --> div classw-full lg:w-2/5 space-y-4> div classcard p-4 space-y-4 fade-in> div classflex justify-between items-center> h2 classtext-lg font-semibold flex items-center> i classfa-solid fa-sliders mr-2 text-primary>/i> 基本设置 /h2> button idrandomButton classbtn btn-secondary text-sm py-1 px-3 flex items-center> i classfa-solid fa-dice mr-1>/i> 随机提示词 /button> /div> div> label forpassword classblock text-sm font-medium mb-1 flex items-center> i classfa-solid fa-key mr-1 text-xs>/i> 访问密码 /label> input typepassword idpassword placeholder请输入访问密码 classw-full valueadmin123> /div> div> label forprompt classblock text-sm font-medium mb-1 flex items-center> i classfa-solid fa-wand-magic-sparkles mr-1 text-xs>/i> 正向提示词 /label> textarea idprompt rows3 placeholder描述您想要生成的图像内容及样式... classw-full>cyberpunk cat/textarea> /div> div> label fornegative_prompt classblock text-sm font-medium mb-1 flex items-center> i classfa-solid fa-ban mr-1 text-xs>/i> 反向提示词 /label> textarea idnegative_prompt rows2 placeholder描述在生成的图像中要避免的元素文本... classw-full>/textarea> /div> div> label formodel classblock text-sm font-medium mb-1 flex items-center> i classfa-solid fa-robot mr-1 text-xs>/i> 文生图模型 /label> select idmodel classw-full> option valueloading disabled selected>加载中.../option> /select> p classtext-xs text-gray-500 mt-2>部分模型需要提供图片URL或遮罩URL(见下方输入框)。/p> /div> div idimg2imgInputs classhidden space-y-2> div> label forimage_url classblock text-sm font-medium mb-1 flex items-center> i classfa-solid fa-image mr-1 text-xs>/i> 输入图像URL(必填) /label> input typeurl idimage_url placeholderhttps://example.com/your-image.png classw-full> /div> div> label formask_url classblock text-sm font-medium mb-1 flex items-center> i classfa-solid fa-mask mr-1 text-xs>/i> 遮罩图像URL(必填,inpainting 使用) /label> input typeurl idmask_url placeholderhttps://example.com/your-mask.png classw-full> /div> /div> /div> div classcard p-4 space-y-4 fade-in> div classflex justify-between items-center> h2 classtext-lg font-semibold flex items-center> i classfa-solid fa-gear mr-2 text-primary>/i> 高级选项 /h2> button idtoggleAdvanced classtext-xs btn btn-secondary py-1 px-3 flex items-center> i classfa-solid fa-chevron-down mr-1 idadvancedIcon>/i> 显示/隐藏 /button> /div> div idadvancedOptions classspace-y-3 hidden> div> div classflex justify-between items-center> label forwidth classblock text-sm font-medium flex items-center> i classfa-solid fa-arrows-left-right mr-1 text-xs>/i> 图像宽度 /label> span idwidthValue classtext-sm font-mono>1024px/span> /div> input typerange idwidth min256 max2048 step64 value1024 classslider w-full> /div> div> div classflex justify-between items-center> label forheight classblock text-sm font-medium flex items-center> i classfa-solid fa-arrows-up-down mr-1 text-xs>/i> 图像高度 /label> span idheightValue classtext-sm font-mono>1024px/span> /div> input typerange idheight min256 max2048 step64 value1024 classslider w-full> /div> div> div classflex justify-between items-center> label fornum_steps classblock text-sm font-medium flex items-center tooltip> i classfa-solid fa-shoe-prints mr-1 text-xs>/i> 迭代步数 span classtooltiptext>更高的步数通常会产生更精细的细节,但需要更长的处理时间/span> /label> span idnum_stepsValue classtext-sm font-mono>20/span> /div> input typerange idnum_steps min1 max20 step1 value20 classslider w-full> /div> div> div classflex justify-between items-center> label forguidance classblock text-sm font-medium flex items-center tooltip> i classfa-solid fa-compass mr-1 text-xs>/i> 引导系数 span classtooltiptext>控制生成图像与提示词的匹配程度,较高的值会更严格遵循提示词/span> /label> span idguidanceValue classtext-sm font-mono>7.5/span> /div> input typerange idguidance min0 max30 step0.5 value7.5 classslider w-full> /div> div> label forseed classblock text-sm font-medium mb-1 flex items-center tooltip> i classfa-solid fa-seedling mr-1 text-xs>/i> 随机种子 span classtooltiptext>使用相同的种子值可以在其他参数相同的情况下生成相似的图像/span> /label> div classflex gap-2> input typenumber idseed placeholder随机种子值 classw-full> button idrandomSeed classbtn btn-secondary text-sm py-1 px-3> i classfa-solid fa-random>/i> /button> /div> p classtext-xs text-gray-500 mt-1>留空则随机生成/p> /div> /div> /div> button idsubmitButton classbtn btn-primary w-full py-3 flex items-center justify-center> i classfa-solid fa-wand-magic-sparkles mr-2>/i> 生成图像 /button> /div> !-- 右侧图像展示 --> div classw-full lg:w-3/5> div classcard h-full p-4 space-y-4 fade-in> div classflex justify-between items-center> h2 classtext-lg font-semibold flex items-center> i classfa-solid fa-image mr-2 text-primary>/i> 生成结果 /h2> div classflex space-x-2> button idcopyParamsButton classbtn btn-secondary text-sm py-1 px-3 hidden> i classfa-solid fa-copy mr-1>/i> 复制参数 /button> button iddownloadButton classbtn btn-secondary text-sm py-1 px-3 hidden> i classfa-solid fa-download mr-1>/i> 下载图像 /button> button iddownloadZipButton classbtn btn-secondary text-sm py-1 px-3 hidden> i classfa-solid fa-file-zipper mr-1>/i> 下载ZIP /button> /div> /div> div classimage-container card> div idloadingOverlay classloading-mask hidden> div classtext-center w-full px-6> div classinline-block animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-white>/div> p classtext-white mt-3 font-medium>生成中,请稍候.../p> p classtext-white text-sm mt-1>这可能需要几秒到几十秒不等/p> div idprogressBarContainer classw-full mt-4> div classw-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700> div idprogressBar classbg-blue-500 h-2.5 rounded-full stylewidth: 0%>/div> /div> div classflex justify-between text-xs mt-1 text-white> span idprogressText>0%/span> span idprogressExtra>估算/span> /div> /div> /div> /div> div idinitialPrompt classtext-center text-gray-400 dark:text-gray-600> i classfa-solid fa-image-portrait text-4xl mb-2>/i> p>点击生成按钮开始创建图像/p> /div> span idimageStatus classbg-gray-300 text-gray-700 hidden>状态/span> img idaiImage classmax-h-full max-w-full rounded hidden alt生成的图像> div idimageGallery classgrid grid-cols-4 gap-2 mt-3 hidden>/div> /div> div idimageInfo classspace-y-3 mt-2> div classgrid grid-cols-2 gap-3> div classtext-sm flex items-center> span classfont-medium flex items-center> i classfa-regular fa-clock mr-1 text-xs>/i> 生成时间: /span> span idgenerationTime classml-1>-/span> /div> div classtext-sm flex items-center> span classfont-medium flex items-center> i classfa-solid fa-microchip mr-1 text-xs>/i> 使用模型: /span> span idusedModel classml-1>-/span> /div> div classtext-sm flex items-center> span classfont-medium flex items-center> i classfa-solid fa-bolt mr-1 text-xs>/i> 算力估计: /span> span idcomputeInfo classml-1>-/span> span classtext-xs text-gray-500 ml-2>(it/s)/span> /div> div classtext-sm flex items-center> span classfont-medium flex items-center> i classfa-solid fa-database mr-1 text-xs>/i> 输出大小: /span> span idimageSize classml-1>-/span> /div> /div> div idallParamsContainer classhidden mt-3 border-t border-gray-200 dark:border-gray-700 pt-3> h3 classtext-sm font-medium mb-2 flex items-center> i classfa-solid fa-list-check mr-1>/i> 所有参数 /h3> div idallParams classflex flex-wrap>/div> /div> div idimageMeta classhidden mt-3 border-t border-gray-200 dark:border-gray-700 pt-3> h3 classtext-sm font-medium mb-2 flex items-center> i classfa-solid fa-table mr-1>/i> 每张图片信息 /h3> table classw-full text-xs> thead> tr classtext-left> th classpy-1>#/th> th classpy-1>尺寸/th> th classpy-1>大小/th> /tr> /thead> tbody idimageMetaBody>/tbody> /table> /div> /div> /div> /div> /div> /div> script> document.addEventListener(DOMContentLoaded, function () { // 初始化模型列表 let availableModels ; let randomPromptsList ; let currentImageParams {}; let currentImagesArray null; // 多图时的 dataURL 数组 // 加载模型列表 async function loadModels() { try { const response await fetch(/api/models); if (!response.ok) { throw new Error(加载模型列表失败); } availableModels await response.json(); const modelSelect document.getElementById(model); // 清空当前选项 modelSelect.innerHTML ; // 添加新选项 availableModels.forEach(model > { const option document.createElement(option); option.value model.id; option.textContent `${model.name} - ${model.description}`; option.dataset.requiresImage model.requiresImage ? 1 : 0; modelSelect.appendChild(option); }); // 默认选择第二个模型(通常是更好的模型) if (availableModels.length > 1) { modelSelect.value availableModels1.id; } } catch (error) { console.error(加载模型列表错误:, error); showStatus(加载模型列表失败, error); } } // 加载随机提示词 async function loadRandomPrompts() { try { const response await fetch(/api/prompts); if (!response.ok) { throw new Error(加载提示词失败); } randomPromptsList await response.json(); } catch (error) { console.error(加载提示词错误:, error); randomPromptsList 未能加载提示词列表,请重试或手动输入; } } // 初始化加载资源 loadModels(); loadRandomPrompts(); // 检查是否需要登录 (async () > { try { const res await fetch(/api/config); if (res.ok) { const cfg await res.json(); if (cfg.require_password) { showLogin(); } } } catch (_) {} })(); // 主题切换功能相关代码 const themeToggle document.getElementById(themeToggle); const html document.documentElement; const moonIcon `i classfa-solid fa-moon>/i>`; const sunIcon `i classfa-solid fa-sun>/i>`; // 检查系统主题或存储的主题并设置初始状态 if (localStorage.theme dark || (!(theme in localStorage) && window.matchMedia((prefers-color-scheme: dark)).matches)) { html.classList.add(dark); themeToggle.innerHTML sunIcon; themeToggle.setAttribute(aria-label, 切换亮色主题); } else { html.classList.remove(dark); themeToggle.innerHTML moonIcon; themeToggle.setAttribute(aria-label, 切换暗色主题); } themeToggle.addEventListener(click, function() { if (html.classList.contains(dark)) { html.classList.remove(dark); localStorage.theme light; themeToggle.innerHTML moonIcon; themeToggle.setAttribute(aria-label, 切换暗色主题); } else { html.classList.add(dark); localStorage.theme dark; themeToggle.innerHTML sunIcon; themeToggle.setAttribute(aria-label, 切换亮色主题); } }); // 高级选项切换 const toggleAdvanced document.getElementById(toggleAdvanced); const advancedOptions document.getElementById(advancedOptions); const advancedIcon document.getElementById(advancedIcon); toggleAdvanced.addEventListener(click, function() { if (advancedOptions.classList.contains(hidden)) { advancedOptions.classList.remove(hidden); advancedIcon.classList.remove(fa-chevron-down); advancedIcon.classList.add(fa-chevron-up); } else { advancedOptions.classList.add(hidden); advancedIcon.classList.remove(fa-chevron-up); advancedIcon.classList.add(fa-chevron-down); } }); // 模型切换时显示/隐藏图生图字段 const modelSelectEl document.getElementById(model); const img2imgInputs document.getElementById(img2imgInputs); if (modelSelectEl && img2imgInputs) { modelSelectEl.addEventListener(change, function() { const selected availableModels.find(m > m.id this.value); if (selected && selected.requiresImage) { img2imgInputs.classList.remove(hidden); } else { img2imgInputs.classList.add(hidden); } }); } // 滑块值显示 const sliders width, height, num_steps, guidance; sliders.forEach(id > { const slider document.getElementById(id); const valueDisplay document.getElementById(`${id}Value`); slider.addEventListener(input, function() { if (id width || id height) { valueDisplay.textContent `${this.value}px`; } else if (id guidance) { valueDisplay.textContent parseFloat(this.value).toFixed(2); } else { valueDisplay.textContent this.value; } }); }); // 生成数量控件 const countContainer document.createElement(div); countContainer.className mt-2; countContainer.innerHTML ` label fornum_outputs classblock text-sm font-medium mb-1 flex items-center> i classfa-solid fa-layer-group mr-1 text-xs>/i> 生成数量 /label> select idnum_outputs classw-full> option value1 selected>1/option> option value2>2/option> option value3>3/option> option value4>4/option> option value5>5/option> option value6>6/option> option value7>7/option> option value8>8/option> /select> `; document.querySelector(.card.p-4.space-y-4.fade-in).appendChild(countContainer); // 随机种子 document.getElementById(randomSeed).addEventListener(click, function() { const randomSeed Math.floor(Math.random() * 4294967295); document.getElementById(seed).value randomSeed; }); // 随机提示词 document.getElementById(randomButton).addEventListener(click, function() { if (randomPromptsList.length > 0) { const randomIndex Math.floor(Math.random() * randomPromptsList.length); document.getElementById(prompt).value randomPromptsListrandomIndex; } else { showStatus(提示词列表未加载,请稍后再试, error); } }); // 复制参数 document.getElementById(copyParamsButton).addEventListener(click, function() { if (!currentImageParams) return; // 创建参数文本 let paramsText --- AI绘图创作生成参数 ---\n; for (const key, value of Object.entries(currentImageParams)) { if (key password) continue; // 不复制密码 paramsText + `${formatParamName(key)}: ${value}\n`; } // 复制到剪贴板 navigator.clipboard.writeText(paramsText) .then(() > { showStatus(参数已复制到剪贴板, success); }) .catch(err > { console.error(复制失败:, err); showStatus(复制参数失败, error); }); }); // 格式化参数名称 function formatParamName(name) { const nameMap { prompt: 正向提示词, negative_prompt: 反向提示词, model: 文生图模型, width: 图像宽度, height: 图像高度, num_steps: 迭代步数, guidance: 引导系数, seed: 随机种子 }; return nameMapname || name; } // 下载图像 document.getElementById(downloadButton).addEventListener(click, async function() { const img document.getElementById(aiImage); const gallery document.getElementById(imageGallery); let src ; if (!gallery.classList.contains(hidden) && currentImagesArray && currentImagesArray.length > 0) { src currentImagesArray0; } else { src img.src; } if (!src) { showStatus(没有可下载的图像, error); return; } try { // 从图像数据创建blob const response await fetch(src); const blob await response.blob(); // 创建下载链接 const url window.URL.createObjectURL(blob); const link document.createElement(a); link.href url; // 生成文件名 const timestamp new Date().toISOString().replace(/:./g, -); const model document.getElementById(usedModel).textContent || ai-image; link.download `${model}-${timestamp}.png`; // 触发下载 document.body.appendChild(link); link.click(); // 清理 document.body.removeChild(link); window.URL.revokeObjectURL(url); showStatus(图像下载成功, success); } catch (error) { console.error(下载图像错误:, error); showStatus(下载图像失败, error); } }); // 下载 ZIP(多图) document.getElementById(downloadZipButton).addEventListener(click, async function() { if (!currentImagesArray || currentImagesArray.length 0) { showStatus(没有可打包的图片, error); return; } try { const zip new JSZip(); const model document.getElementById(usedModel).textContent || ai-image; const timestamp new Date().toISOString().replace(/:./g, -); for (let i 0; i currentImagesArray.length; i++) { const dataURL currentImagesArrayi; const base64 dataURL.split(,)1; zip.file(`${model}-${timestamp}-${i+1}.png`, base64, { base64: true }); } const blob await zip.generateAsync({ type: blob }); const url window.URL.createObjectURL(blob); const link document.createElement(a); link.href url; link.download `${model}-${timestamp}.zip`; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); showStatus(ZIP 下载已开始, success); } catch (e) { console.error(ZIP打包失败:, e); showStatus(打包ZIP失败, error); } }); // 提交生成请求 // 复用全局的计时器与控制器,避免闪烁 let progressTimer null; let pendingController null; document.getElementById(submitButton).addEventListener(click, async function() { // 显示加载中状态 const loadingOverlay document.getElementById(loadingOverlay); const initialPrompt document.getElementById(initialPrompt); const aiImage document.getElementById(aiImage); const progressBarContainer document.getElementById(progressBarContainer); const progressBar document.getElementById(progressBar); const progressText document.getElementById(progressText); const progressExtra document.getElementById(progressExtra); if (!loadingOverlay || !initialPrompt || !aiImage) { console.error(必要的DOM元素未找到); return; } // 隐藏初始提示和图像 initialPrompt.classList.add(hidden); aiImage.classList.add(hidden); loadingOverlay.classList.remove(hidden); // 隐藏之前的提示和按钮,并清理上一次状态 const imageStatus document.getElementById(imageStatus); const copyParamsButton document.getElementById(copyParamsButton); const downloadButton document.getElementById(downloadButton); if (imageStatus) imageStatus.classList.add(hidden); if (copyParamsButton) copyParamsButton.classList.add(hidden); if (downloadButton) downloadButton.classList.add(hidden); if (progressTimer) { clearInterval(progressTimer); progressTimer null; } if (pendingController) { try { pendingController.abort(); } catch(_){} pendingController null; } // 获取参数(初始草案) const rawParams { password: document.getElementById(password)?.value || , prompt: document.getElementById(prompt)?.value || , negative_prompt: document.getElementById(negative_prompt)?.value || , model: document.getElementById(model)?.value, width: parseInt(document.getElementById(width)?.value) || 1024, height: parseInt(document.getElementById(height)?.value) || 1024, num_steps: parseInt(document.getElementById(num_steps)?.value) || 20, guidance: parseFloat(document.getElementById(guidance)?.value) || 7.5, seed: parseInt(document.getElementById(seed)?.value) || Math.floor(Math.random() * 4294967295), image_url: document.getElementById(image_url)?.value || , mask_url: document.getElementById(mask_url)?.value || , num_outputs: parseInt(document.getElementById(num_outputs)?.value) || 1 }; // 基于模型特性,清洗出最终要发送/展示的参数 const selectedModelMeta availableModels.find(m > m.id rawParams.model); const params { password: rawParams.password, prompt: rawParams.prompt, negative_prompt: rawParams.negative_prompt, model: rawParams.model, width: rawParams.width, height: rawParams.height, num_steps: rawParams.num_steps, guidance: rawParams.guidance, seed: rawParams.seed, num_outputs: rawParams.num_outputs }; if (selectedModelMeta?.requiresImage) { params.image_url rawParams.image_url; } if (selectedModelMeta?.requiresMask) { params.mask_url rawParams.mask_url; } // 保存当前参数 currentImageParams {...params}; // 前端必填校验(依据模型元信息) if (selectedModelMeta && selectedModelMeta.requiresImage) { if (!rawParams.image_url) { showStatus(该模型需要提供输入图像URL, error); loadingOverlay.classList.add(hidden); initialPrompt.classList.remove(hidden); return; } } if (selectedModelMeta && selectedModelMeta.requiresMask) { if (!rawParams.mask_url) { showStatus(局部重绘模型需要提供遮罩URL, error); loadingOverlay.classList.add(hidden); initialPrompt.classList.remove(hidden); return; } } try { // 发送请求 const startTime performance.now(); // 启动拟真进度条 let progress 0; if (progressBarContainer && progressBar && progressText) { progressBarContainer.classList.remove(hidden); progress 0; progressBar.style.width 0%; progressText.textContent 0%; if (progressExtra) progressExtra.textContent 估算; const steps params.num_steps || 20; const baseMs Math.max(4000, Math.min(20000, steps * 600)); const startTs Date.now(); progressTimer setInterval(() > { const elapsed Date.now() - startTs; const ratio Math.min(0.95, elapsed / baseMs); // 最高到95%,等待真实完成再封顶 const cur Math.floor(ratio * 100); if (cur > progress) { progress cur; progressBar.style.width `${progress}%`; progressText.textContent `${progress}%`; } }, 150); } // 60秒超时控制 pendingController new AbortController(); const timeoutId setTimeout(() > pendingController && pendingController.abort(timeout), 60000); const response await fetch(/, { method: POST, headers: { Content-Type: application/json, Accept: image/* }, body: JSON.stringify(params), signal: pendingController.signal }); clearTimeout(timeoutId); if (!response.ok) { const contentType response.headers.get(content-type); if (contentType?.includes(application/json)) { const errorData await response.json(); const msg errorData.error || errorData.message || 生成失败; const details errorData.details ? `(${errorData.details})` : ; throw new Error(`${msg}${details}`); } else { const errorText await response.text(); console.error(服务器错误:, errorText); throw new Error(生成失败); } } const respType response.headers.get(content-type) || ; const serverSecondsHeader response.headers.get(X-Server-Seconds); let serverSeconds serverSecondsHeader ? parseFloat(serverSecondsHeader) : null; let base64Image ; let imageBlob null; let imagesArray null; if (respType.includes(application/json)) { const json await response.json(); if (Array.isArray(json.images)) { imagesArray json.images; base64Image imagesArray0; } else { throw new Error(响应格式错误); } } else { imageBlob await response.blob(); base64Image await blobToBase64(imageBlob); } const endTime performance.now(); const generationTime ((endTime - startTime) / 1000).toFixed(2); // 设置图像信息并显示图像 const gallery document.getElementById(imageGallery); gallery.innerHTML ; if (imagesArray && imagesArray.length > 1) { aiImage.classList.add(hidden); gallery.classList.remove(hidden); currentImagesArray imagesArray.slice(); imagesArray.forEach((src, idx) > { const wrap document.createElement(div); wrap.className relative group; const img document.createElement(img); img.src src; img.alt `生成的图像 ${idx+1}`; img.className w-full h-auto rounded; img.style.cursor zoom-in; img.addEventListener(click, () > openImageModal(src)); // 悬浮操作条 const bar document.createElement(div); bar.className absolute bottom-1 left-1 right-1 hidden group-hover:flex bg-black bg-opacity-50 text-white text-xs rounded px-1 py-0.5 gap-1 justify-center; const btnZoom document.createElement(button); btnZoom.innerHTML i classfa-solid fa-magnifying-glass-plus>/i>; btnZoom.title 放大; btnZoom.onclick (e) > { e.stopPropagation(); openImageModal(src); }; const btnCopy document.createElement(button); btnCopy.innerHTML i classfa-solid fa-copy>/i>; btnCopy.title 复制到剪贴板; btnCopy.onclick async (e) > { e.stopPropagation(); try { await navigator.clipboard.writeText(src); showStatus(图片已复制到剪贴板, success); } catch (_) { showStatus(复制失败, error); } }; const btnDl document.createElement(button); btnDl.innerHTML i classfa-solid fa-download>/i>; btnDl.title 下载此图; btnDl.onclick async (e) > { e.stopPropagation(); try { const response await fetch(src); const blob await response.blob(); const url window.URL.createObjectURL(blob); const link document.createElement(a); const model document.getElementById(usedModel).textContent || ai-image; const timestamp new Date().toISOString().replace(/:./g, -); link.href url; link.download `${model}-${timestamp}-${idx+1}.png`; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); } catch (_) { showStatus(下载失败, error); } }; bar.appendChild(btnZoom); bar.appendChild(btnCopy); bar.appendChild(btnDl); wrap.appendChild(img); wrap.appendChild(bar); gallery.appendChild(wrap); }); } else { gallery.classList.add(hidden); currentImagesArray null; aiImage.src base64Image; } // 统一的UI完成逻辑(不依赖 onload 也能执行一次) const finalize () > { // 图像加载完成后更新UI loadingOverlay.classList.add(hidden); if (!imagesArray || imagesArray.length 1) { aiImage.classList.remove(hidden); } // 安全地更新信息显示 const elements { generationTime: document.getElementById(generationTime), usedModel: document.getElementById(usedModel), computeInfo: document.getElementById(computeInfo), imageSize: document.getElementById(imageSize) }; if (elements.generationTime) { elements.generationTime.textContent `${generationTime}秒`; } if (elements.usedModel) { elements.usedModel.textContent getModelNameById(params.model); } // 从响应头提取信息 const usedModelHeader response.headers.get(X-Used-Model); const usedModelName usedModelHeader ? getModelNameById(usedModelHeader) : getModelNameById(params.model); if (elements.usedModel) elements.usedModel.textContent usedModelName; const bytesStr response.headers.get(X-Image-Bytes); if (elements.imageSize) { if (bytesStr) { const bytes parseInt(bytesStr, 10); elements.imageSize.textContent formatBytes(bytes); } else if (imageBlob) { // 回退:用blob大小 elements.imageSize.textContent formatBytes(imageBlob.size); } else if (imagesArray && imagesArray0) { elements.imageSize.textContent formatBytes(dataURLBytes(imagesArray0)); } } // 计算真实 it/s:迭代步数 / 实际耗时 const itPerSec (() > { const steps Number(params.num_steps) || 0; const seconds serverSeconds && serverSeconds > 0 ? serverSeconds : (parseFloat(generationTime) || 0); if (steps > 0 && seconds > 0) { return (steps / seconds).toFixed(2); } return -; })(); if (elements.computeInfo) { elements.computeInfo.textContent `${itPerSec}`; } // 更新所有参数面板(隐藏不相关字段) updateParamsDisplay(params); // 填充每张图片的尺寸与大小 const metaPanel document.getElementById(imageMeta); const metaBody document.getElementById(imageMetaBody); if (imagesArray && imagesArray.length > 0 && metaPanel && metaBody) { metaBody.innerHTML ; const loadPromises imagesArray.map((src, i) > new Promise((resolve) > { const probe new Image(); probe.onload () > { const tr document.createElement(tr); const sizeBytes dataURLBytes(src); tr.innerHTML `td class\py-1 pr-3\>${i+1}/td>td class\py-1 pr-3\>${probe.width}×${probe.height}/td>td class\py-1\>${formatBytes(sizeBytes)}/td>`; metaBody.appendChild(tr); resolve(); }; probe.onerror () > resolve(); probe.src src; })); Promise.all(loadPromises).then(() > metaPanel.classList.remove(hidden)); } else { const panel document.getElementById(imageMeta); if (panel) panel.classList.add(hidden); } // 进度条收口到100% if (progressBarContainer && progressBar && progressText) { if (progressTimer) clearInterval(progressTimer); progressBar.style.width 100%; progressText.textContent 100%; setTimeout(() > progressBarContainer.classList.add(hidden), 800); } // 显示状态和操作按钮 showStatus(imagesArray ? `生成成功(${imagesArray.length} 张)` : 生成成功, success); if (copyParamsButton) copyParamsButton.classList.remove(hidden); if (downloadButton) downloadButton.classList.remove(hidden); const downloadZipButton document.getElementById(downloadZipButton); if (downloadZipButton) { if (imagesArray && imagesArray.length > 1) downloadZipButton.classList.remove(hidden); else downloadZipButton.classList.add(hidden); } }; // 触发 finalize if (imagesArray && imagesArray.length > 1) { finalize(); } else { aiImage.onload finalize; } } catch (error) { console.error(生成图像错误:, error); if (error && (error.name AbortError || error.message timeout)) { showStatus(生成超时,请尝试更换模型或降低宽高后重试, error); } else { showStatus(error.message || 生成失败, error); } // 显示初始提示 initialPrompt.classList.remove(hidden); aiImage.classList.add(hidden); } finally { loadingOverlay.classList.add(hidden); if (progressTimer) { clearInterval(progressTimer); progressTimer null; } if (progressBarContainer) progressBarContainer.classList.add(hidden); pendingController null; } }); // 将Blob转换为Base64 function blobToBase64(blob) { return new Promise((resolve, reject) > { const reader new FileReader(); reader.onloadend () > resolve(reader.result); reader.onerror reject; reader.readAsDataURL(blob); }); } // 字节转可读字符串 function formatBytes(bytes) { if (!bytes && bytes ! 0) return -; const sizes B, KB, MB, GB; if (bytes 0) return 0 B; const i Math.floor(Math.log(bytes) / Math.log(1024)); const value (bytes / Math.pow(1024, i)).toFixed(2); return `${value} ${sizesi}`; } // 计算 dataURL 的字节大小(粗略) function dataURLBytes(dataURL) { try { const base64 dataURL.split(,)1 || ; // base64 长度 * 3/4 约等于字节数 return Math.floor((base64.length * 3) / 4); } catch (_) { return 0; } } // 简易图片放大预览 function openImageModal(src) { const existing document.getElementById(imgModal); if (existing) existing.remove(); const modal document.createElement(div); modal.id imgModal; modal.style.position fixed; modal.style.inset 0; modal.style.background rgba(0,0,0,0.7); modal.style.display flex; modal.style.alignItems center; modal.style.justifyContent center; modal.style.zIndex 1000; modal.innerHTML ` div classmax-w-5xl max-h-90vh p-2> img src${src} classrounded shadow-lg max-h-85vh mx-auto /> div classtext-center mt-2> button idcloseImgModal classbtn btn-secondary>关闭/button> /div> /div>`; document.body.appendChild(modal); document.getElementById(closeImgModal).onclick () > modal.remove(); modal.addEventListener(click, (e) > { if (e.target modal) modal.remove(); }); } // 通过ID获取模型名称 function getModelNameById(id) { const model availableModels.find(m > m.id id); return model ? model.name : id; } // 更新参数显示 function updateParamsDisplay(params) { const allParamsContainer document.getElementById(allParamsContainer); const allParamsElement document.getElementById(allParams); if (!allParamsContainer || !allParamsElement) return; // 清空现有参数 allParamsElement.innerHTML ; // 添加新参数 for (const key, value of Object.entries(params)) { if (key password) continue; // 不显示密码 const paramName formatParamName(key); const paramValue value; // 创建参数徽章 const badge document.createElement(div); badge.className param-badge; badge.innerHTML `span classfont-medium>${paramName}:/span> ${paramValue}`; allParamsElement.appendChild(badge); } // 显示参数容器 allParamsContainer.classList.remove(hidden); } // 显示状态提示 function showStatus(message, type info) { const statusElement document.getElementById(imageStatus); if (!statusElement) return; // 设置样式 statusElement.className ; switch (type) { case success: statusElement.classList.add(bg-green-100, text-green-800, dark:bg-green-900, dark:text-green-100); break; case error: statusElement.classList.add(bg-red-100, text-red-800, dark:bg-red-900, dark:text-red-100); break; case warning: statusElement.classList.add(bg-yellow-100, text-yellow-800, dark:bg-yellow-900, dark:text-yellow-100); break; default: statusElement.classList.add(bg-blue-100, text-blue-800, dark:bg-blue-900, dark:text-blue-100); } // 设置消息 statusElement.textContent message; // 显示 statusElement.classList.remove(hidden); // 5秒后自动隐藏 setTimeout(() > { statusElement.classList.add(hidden); }, 5000); } }); // 登录遮罩及逻辑 function showLogin() { if (document.getElementById(loginOverlay)) return; const overlay document.createElement(div); overlay.id loginOverlay; overlay.style.position fixed; overlay.style.inset 0; overlay.style.backdropFilter blur(3px); overlay.style.background rgba(0,0,0,0.4); overlay.style.zIndex 1000; overlay.innerHTML ` div classw-full h-full flex items-center justify-center> div classcard p-6 w-96 bg-white dark:bg-gray-800> h3 classtext-lg font-semibold mb-4>请输入访问密码/h3> input typepassword idloginPassword classw-full placeholder访问密码 /> button idloginButton classbtn btn-primary w-full mt-4>登录/button> p idloginError classtext-red-500 text-sm mt-2 hidden>密码错误/p> /div> /div>`; document.body.appendChild(overlay); document.getElementById(loginPassword).focus(); const submitLogin async () > { const pwd (document.getElementById(loginPassword).value || ).trim(); try { const resp await fetch(/api/auth, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ password: pwd }) }); if (!resp.ok) { document.getElementById(loginError).classList.remove(hidden); return; } overlay.remove(); } catch (_) { document.getElementById(loginError).classList.remove(hidden); } }; document.getElementById(loginButton).addEventListener(click, submitLogin); document.getElementById(loginPassword).addEventListener(keydown, (e) > { if (e.key Enter) submitLogin(); }); } /script>/body>/html>
View on OTX
|
View on ThreatMiner
Please enable JavaScript to view the
comments powered by Disqus.
Data with thanks to
AlienVault OTX
,
VirusTotal
,
Malwr
and
others
. [
Sitemap
]