找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 17|回复: 0

[AI] 豆包AI超能模式编程一个网页

[复制链接]

1922

主题

543

回帖

7753

积分

管理员

积分
7753
发表于 2026-5-1 16:44:31 | 显示全部楼层 |阅读模式
  1. 做一个 chatgpt 一样的 html 页面,要求如下:
  2. 【一、功能】
  3. 1、文生图:用户输入文字描述,生成图片
  4. 2、图生图:用户上传 1 张参考图 + 输入文字描述,生成图片
  5. 【二、配置区】
  6. 1、BaseURL 输入框
  7. 2、API Key 输入框,密码类型显示
  8. 3、模型默认 gpt-image-2
  9. 三个值都浏览器本地保存,刷新不丢失
  10. 调用接口时,完整 URL = BaseURL + 接口路径
  11. 请求头必须带:Authorization: Bearer {APIKey}
  12. 【三、文生图接口】
  13. 路径:POST {baseURL}/v1/images/generations
  14. Content-Type: application/json
  15. 请求体参数:
  16. {
  17. "model": "gpt-image-2", // 用户选择的模型
  18. "prompt": "用户输入的文字",
  19. "n": 1, // 永远传 1,不要其他值
  20. "size": "1024x1024", // 用户可选 2024x1024 / 1536x1024 / 1024x1536
  21. "quality": "high" // 用户可选 auto /high/medium /low
  22. }
  23. 【四、图生图接口】
  24. 路径:POST {baseURL}/v1/images/edits
  25. Content-Type: multipart/form-data(用 FormData 提交,不要手动设置 Content-Type,让浏览器自动加)
  26. FormData 字段:
  27. model: "gpt-image-2"(用户选择的模型)
  28. image: 用户上传的图片文件(File 对象,字段名必须是 image,不是 image [])
  29. prompt: 用户输入的文字
  30. n: "1"(字符串 "1",永远传 1)
  31. size: "1024x1024"(用户可选)
  32. 【五、生成数量说明】
  33. 用户可以选择生成数量:1 张 / 2 张 / 4 张 / 6 张
  34. 但调用 API 时,n 参数永远传 1
  35. 通过 Promise.all 同时发起 N 次请求实现批量生成
  36. 每张图独立显示的状态和结果,某一张失败不影响其他
  37. 【六、响应处理】
  38. 接口返回格式:
  39. {
  40. "created": 1713833628,
  41. "data": [{"b64_json": "一张的 base64 字符串..."}],
  42. "usage": {"total_tokens": 100, "input_tokens": 50, "output_tokens": 50 }
  43. }
  44. 处理方式:
  45. 取 response.data [0].b64_json
  46. 拼接成 data:image/png;base64,${b64_json} 作为 img 的 src
  47. 显示在结果区域
  48. 【七、图片展示】
  49. 多张图片用网格布局(grid),自适应排列
  50. 每张图片要同时显示「放大」「下载」两个按钮
  51. 点击图片放大按钮:弹出全屏弹窗,居中显示原图,点击遮罩关闭
  52. 点击下载按钮:直接下载图片,文件名 image_时间戳.png
  53. 生成中显示加载或 loading 动画
  54. 【八、错误处理】
  55. API 报错时,把后端返回的 error.message 显示在对应图片位置
  56. 网络错误显示「网络异常,请检查 BaseURL 是否正确」
  57. API Key 为空时同步直接返回,显示「请先填写 API Key」
  58. 【九、技术栈】
  59. 原生 HTML + JavaScript + Tailwind CSS(CDN 引入)
  60. 不要使用 React/Vue 等任何框架
  61. 所有代码放在一个 HTML 文件里
  62. UI 风格参考 ChatGPT,深色主题,简约现代
复制代码

豆包选择【超能模式】,然后发送上面的指令。

然后自动会生成网页代码:
你只需要把下面的代码完整复制,保存为一个 .html 文件,直接用浏览器打开就能用了!
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>AI 绘图工具 | 文生图 & 图生图</title>
  7.     <script src="https://cdn.tailwindcss.com"></script>
  8.     <script>
  9.         tailwind.config = {
  10.             theme: {
  11.                 extend: {
  12.                     colors: {
  13.                         sidebar: '#202123',
  14.                         main: '#343541',
  15.                         input: '#40414f',
  16.                     },
  17.                 }
  18.             }
  19.         }
  20.     </script>
  21.     <style type="text/tailwindcss">
  22.         @layer utilities {
  23.             .content-auto {
  24.                 content-visibility: auto;
  25.             }
  26.             .scrollbar-thin {
  27.                 scrollbar-width: thin;
  28.                 scrollbar-color: #565869 transparent;
  29.             }
  30.             .spinner {
  31.                 border: 3px solid rgba(255, 255, 255, 0.3);
  32.                 border-radius: 50%;
  33.                 border-top: 3px solid #fff;
  34.                 width: 24px;
  35.                 height: 24px;
  36.                 animation: spin 1s linear infinite;
  37.             }
  38.             @keyframes spin {
  39.                 0% { transform: rotate(0deg); }
  40.                 100% { transform: rotate(360deg); }
  41.             }
  42.         }
  43.     </style>
  44. </head>
  45. <body class="bg-main text-gray-100 h-screen flex overflow-hidden">
  46.     <!-- 左侧侧边栏 - 配置区 -->
  47.     <aside class="w-72 bg-sidebar p-4 flex flex-col overflow-y-auto scrollbar-thin">
  48.         <div class="mb-6">
  49.             <h1 class="text-xl font-bold flex items-center gap-2">
  50.                 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  51.                     <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
  52.                 </svg>
  53.                 AI 绘图工具
  54.             </h1>
  55.             <p class="text-gray-400 text-sm mt-1">文生图 & 图生图</p>
  56.         </div>

  57.         <div class="space-y-4 flex-1">
  58.             <h2 class="text-sm font-semibold text-gray-400 uppercase tracking-wider">API 配置</h2>
  59.             
  60.             <div>
  61.                 <label class="block text-sm font-medium text-gray-300 mb-1">Base URL</label>
  62.                 <input
  63.                     type="text"
  64.                     id="baseUrl"
  65.                     placeholder="https://api.openai.com"
  66.                     class="w-full bg-input border-0 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
  67.                 >
  68.             </div>

  69.             <div>
  70.                 <label class="block text-sm font-medium text-gray-300 mb-1">API Key</label>
  71.                 <input
  72.                     type="password"
  73.                     id="apiKey"
  74.                     placeholder="sk-xxxxxx"
  75.                     class="w-full bg-input border-0 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
  76.                 >
  77.             </div>

  78.             <div>
  79.                 <label class="block text-sm font-medium text-gray-300 mb-1">模型</label>
  80.                 <input
  81.                     type="text"
  82.                     id="model"
  83.                     value="gpt-image-2"
  84.                     class="w-full bg-input border-0 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
  85.                 >
  86.             </div>
  87.         </div>

  88.         <div class="mt-6 pt-4 border-t border-gray-700">
  89.             <p class="text-xs text-gray-500">
  90.                 配置自动保存在本地浏览器中
  91.             </p>
  92.         </div>
  93.     </aside>

  94.     <!-- 右侧主内容区 -->
  95.     <main class="flex-1 flex flex-col overflow-hidden">
  96.         <div class="flex-1 overflow-y-auto p-4 md:p-6 scrollbar-thin">
  97.             <!-- 标签页切换 -->
  98.             <div class="flex gap-1 mb-6 bg-input rounded-lg p-1 w-fit">
  99.                 <button
  100.                     id="tabTextToImage"
  101.                     class="px-4 py-2 rounded-md text-sm font-medium bg-blue-600 text-white transition-all"
  102.                     onclick="switchTab('text')"
  103.                 >
  104.                     文生图
  105.                 </button>
  106.                 <button
  107.                     id="tabImageToImage"
  108.                     class="px-4 py-2 rounded-md text-sm font-medium text-gray-300 hover:text-white transition-all"
  109.                     onclick="switchTab('image')"
  110.                 >
  111.                     图生图
  112.                 </button>
  113.             </div>

  114.             <!-- 文生图输入区 -->
  115.             <div id="textToImagePanel" class="max-w-3xl mx-auto">
  116.                 <div class="bg-gray-800 rounded-xl p-4 mb-6">
  117.                     <label class="block text-sm font-medium text-gray-300 mb-2">图片描述</label>
  118.                     <textarea
  119.                         id="textPrompt"
  120.                         rows="3"
  121.                         placeholder="描述你想要生成的图片,例如:一只可爱的猫咪在草地上晒太阳,卡通风格,高清..."
  122.                         class="w-full bg-input border-0 rounded-lg px-4 py-3 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none resize-none"
  123.                     ></textarea>

  124.                     <!-- 参数选项 -->
  125.                     <div class="flex flex-wrap gap-4 mt-4">
  126.                         <div>
  127.                             <label class="block text-xs text-gray-400 mb-1">生成数量</label>
  128.                             <select id="textCount" class="bg-input border-0 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none">
  129.                                 <option value="1">1 张</option>
  130.                                 <option value="2">2 张</option>
  131.                                 <option value="4" selected>4 张</option>
  132.                                 <option value="6">6 张</option>
  133.                             </select>
  134.                         </div>
  135.                         <div>
  136.                             <label class="block text-xs text-gray-400 mb-1">图片尺寸</label>
  137.                             <select id="textSize" class="bg-input border-0 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none">
  138.                                 <option value="1024x1024" selected>1024×1024</option>
  139.                                 <option value="2024x1024">2024×1024</option>
  140.                                 <option value="1536x1024">1536×1024</option>
  141.                                 <option value="1024x1536">1024×1536</option>
  142.                             </select>
  143.                         </div>
  144.                         <div>
  145.                             <label class="block text-xs text-gray-400 mb-1">图片质量</label>
  146.                             <select id="textQuality" class="bg-input border-0 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none">
  147.                                 <option value="auto">Auto</option>
  148.                                 <option value="high" selected>High</option>
  149.                                 <option value="medium">Medium</option>
  150.                                 <option value="low">Low</option>
  151.                             </select>
  152.                         </div>
  153.                     </div>

  154.                     <button
  155.                         id="textGenerateBtn"
  156.                         onclick="generateTextToImage()"
  157.                         class="w-full mt-4 bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 rounded-lg transition-all flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
  158.                     >
  159.                         <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  160.                             <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
  161.                         </svg>
  162.                         开始生成
  163.                     </button>
  164.                 </div>
  165.             </div>

  166.             <!-- 图生图输入区 -->
  167.             <div id="imageToImagePanel" class="max-w-3xl mx-auto hidden">
  168.                 <div class="bg-gray-800 rounded-xl p-4 mb-6">
  169.                     <label class="block text-sm font-medium text-gray-300 mb-2">参考图片</label>
  170.                     <div id="imageUploadArea" class="border-2 border-dashed border-gray-600 rounded-lg p-8 text-center cursor-pointer hover:border-blue-500 transition-all">
  171.                         <input type="file" id="imageInput" accept="image/*" class="hidden" onchange="handleImageUpload(event)">
  172.                         <svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto text-gray-500 mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  173.                             <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
  174.                         </svg>
  175.                         <p class="text-gray-400 text-sm">点击上传图片,或拖拽到这里</p>
  176.                         <p class="text-gray-500 text-xs mt-1">支持 JPG、PNG 格式</p>
  177.                     </div>
  178.                     <div id="imagePreview" class="hidden mt-3">
  179.                         <img id="previewImg" class="max-h-48 rounded-lg mx-auto" alt="预览">
  180.                     </div>

  181.                     <label class="block text-sm font-medium text-gray-300 mb-2 mt-4">图片描述</label>
  182.                     <textarea
  183.                         id="imagePrompt"
  184.                         rows="3"
  185.                         placeholder="基于参考图,描述你想要修改/生成的效果..."
  186.                         class="w-full bg-input border-0 rounded-lg px-4 py-3 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none resize-none"
  187.                     ></textarea>

  188.                     <!-- 参数选项 -->
  189.                     <div class="flex flex-wrap gap-4 mt-4">
  190.                         <div>
  191.                             <label class="block text-xs text-gray-400 mb-1">生成数量</label>
  192.                             <select id="imageCount" class="bg-input border-0 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none">
  193.                                 <option value="1">1 张</option>
  194.                                 <option value="2">2 张</option>
  195.                                 <option value="4" selected>4 张</option>
  196.                                 <option value="6">6 张</option>
  197.                             </select>
  198.                         </div>
  199.                         <div>
  200.                             <label class="block text-xs text-gray-400 mb-1">图片尺寸</label>
  201.                             <select id="imageSize" class="bg-input border-0 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none">
  202.                                 <option value="1024x1024" selected>1024×1024</option>
  203.                                 <option value="2024x1024">2024×1024</option>
  204.                                 <option value="1536x1024">1536×1024</option>
  205.                                 <option value="1024x1536">1024×1536</option>
  206.                             </select>
  207.                         </div>
  208.                     </div>

  209.                     <button
  210.                         id="imageGenerateBtn"
  211.                         onclick="generateImageToImage()"
  212.                         class="w-full mt-4 bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 rounded-lg transition-all flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
  213.                     >
  214.                         <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  215.                             <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
  216.                         </svg>
  217.                         开始生成
  218.                     </button>
  219.                 </div>
  220.             </div>

  221.             <!-- 结果展示区 -->
  222.             <div id="resultsArea" class="max-w-5xl mx-auto hidden">
  223.                 <h3 class="text-lg font-medium mb-4">生成结果</h3>
  224.                 <div id="resultsGrid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  225.                     <!-- 动态生成结果卡片 -->
  226.                 </div>
  227.             </div>
  228.         </div>
  229.     </main>

  230.     <!-- 图片放大弹窗 -->
  231.     <div id="imageModal" class="fixed inset-0 bg-black/80 z-50 hidden items-center justify-center p-4" onclick="closeModal(event)">
  232.         <button class="absolute top-4 right-4 text-white hover:text-gray-300" onclick="closeModal()">
  233.             <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  234.                 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
  235.             </svg>
  236.         </button>
  237.         <img id="modalImage" class="max-w-full max-h-full rounded-lg" alt="大图预览">
  238.     </div>

  239.     <script>
  240.         // 全局变量
  241.         let uploadedImage = null;

  242.         // 页面加载时读取本地配置
  243.         document.addEventListener('DOMContentLoaded', function() {
  244.             loadConfig();
  245.             bindConfigSave();
  246.             // 上传区域点击触发文件选择
  247.             document.getElementById('imageUploadArea').addEventListener('click', () => {
  248.                 document.getElementById('imageInput').click();
  249.             });
  250.         });

  251.         // 加载本地配置
  252.         function loadConfig() {
  253.             const baseUrl = localStorage.getItem('ai_draw_baseUrl');
  254.             const apiKey = localStorage.getItem('ai_draw_apiKey');
  255.             const model = localStorage.getItem('ai_draw_model');

  256.             if (baseUrl) document.getElementById('baseUrl').value = baseUrl;
  257.             if (apiKey) document.getElementById('apiKey').value = apiKey;
  258.             if (model) document.getElementById('model').value = model;
  259.         }

  260.         // 配置变更自动保存
  261.         function bindConfigSave() {
  262.             const inputs = ['baseUrl', 'apiKey', 'model'];
  263.             inputs.forEach(id => {
  264.                 document.getElementById(id).addEventListener('input', function() {
  265.                     localStorage.setItem(`ai_draw_${id}`, this.value);
  266.                 });
  267.             });
  268.         }

  269.         // 切换标签页
  270.         function switchTab(tab) {
  271.             const textTab = document.getElementById('tabTextToImage');
  272.             const imageTab = document.getElementById('tabImageToImage');
  273.             const textPanel = document.getElementById('textToImagePanel');
  274.             const imagePanel = document.getElementById('imageToImagePanel');

  275.             if (tab === 'text') {
  276.                 textTab.classList.add('bg-blue-600', 'text-white');
  277.                 textTab.classList.remove('text-gray-300');
  278.                 imageTab.classList.remove('bg-blue-600', 'text-white');
  279.                 imageTab.classList.add('text-gray-300');
  280.                 textPanel.classList.remove('hidden');
  281.                 imagePanel.classList.add('hidden');
  282.             } else {
  283.                 imageTab.classList.add('bg-blue-600', 'text-white');
  284.                 imageTab.classList.remove('text-gray-300');
  285.                 textTab.classList.remove('bg-blue-600', 'text-white');
  286.                 textTab.classList.add('text-gray-300');
  287.                 imagePanel.classList.remove('hidden');
  288.                 textPanel.classList.add('hidden');
  289.             }
  290.         }

  291.         // 处理图片上传
  292.         function handleImageUpload(event) {
  293.             const file = event.target.files[0];
  294.             if (!file) return;

  295.             uploadedImage = file;
  296.             const reader = new FileReader();
  297.             reader.onload = function(e) {
  298.                 document.getElementById('previewImg').src = e.target.result;
  299.                 document.getElementById('imagePreview').classList.remove('hidden');
  300.                 document.getElementById('imageUploadArea').classList.add('hidden');
  301.             };
  302.             reader.readAsDataURL(file);
  303.         }

  304.         // 获取配置
  305.         function getConfig() {
  306.             return {
  307.                 baseUrl: document.getElementById('baseUrl').value.trim().replace(/\/$/, ''),
  308.                 apiKey: document.getElementById('apiKey').value.trim(),
  309.                 model: document.getElementById('model').value.trim()
  310.             };
  311.         }

  312.         // 文生图
  313.         async function generateTextToImage() {
  314.             const config = getConfig();
  315.             const prompt = document.getElementById('textPrompt').value.trim();
  316.             const count = parseInt(document.getElementById('textCount').value);
  317.             const size = document.getElementById('textSize').value;
  318.             const quality = document.getElementById('textQuality').value;

  319.             if (!config.apiKey) {
  320.                 alert('请先填写 API Key');
  321.                 return;
  322.             }
  323.             if (!prompt) {
  324.                 alert('请输入图片描述');
  325.                 return;
  326.             }

  327.             // 禁用按钮
  328.             const btn = document.getElementById('textGenerateBtn');
  329.             btn.disabled = true;

  330.             // 初始化结果区
  331.             initResults(count);

  332.             try {
  333.                 const promises = [];
  334.                 for (let i = 0; i < count; i++) {
  335.                     promises.push(
  336.                         callTextToImageApi(config, prompt, size, quality, i)
  337.                     );
  338.                 }
  339.                 await Promise.all(promises);
  340.             } finally {
  341.                 btn.disabled = false;
  342.             }
  343.         }

  344.         // 调用文生图API
  345.         async function callTextToImageApi(config, prompt, size, quality, index) {
  346.             const card = document.getElementById(`result-card-${index}`);
  347.             
  348.             try {
  349.                 const response = await fetch(`${config.baseUrl}/v1/images/generations`, {
  350.                     method: 'POST',
  351.                     headers: {
  352.                         'Authorization': `Bearer ${config.apiKey}`,
  353.                         'Content-Type': 'application/json'
  354.                     },
  355.                     body: JSON.stringify({
  356.                         model: config.model,
  357.                         prompt: prompt,
  358.                         n: 1,
  359.                         size: size,
  360.                         quality: quality
  361.                     })
  362.                 });

  363.                 const data = await response.json();

  364.                 if (!response.ok) {
  365.                     throw new Error(data.error?.message || `请求失败: ${response.status}`);
  366.                 }

  367.                 // 处理结果
  368.                 const b64 = data.data[0].b64_json;
  369.                 const imgSrc = `data:image/png;base64,${b64}`;
  370.                 renderResultCard(card, imgSrc);

  371.             } catch (error) {
  372.                 let errorMsg = error.message;
  373.                 if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
  374.                     errorMsg = '网络异常,请检查BaseURL是否正确';
  375.                 }
  376.                 renderErrorCard(card, errorMsg);
  377.             }
  378.         }

  379.         // 图生图
  380.         async function generateImageToImage() {
  381.             const config = getConfig();
  382.             const prompt = document.getElementById('imagePrompt').value.trim();
  383.             const count = parseInt(document.getElementById('imageCount').value);
  384.             const size = document.getElementById('imageSize').value;

  385.             if (!config.apiKey) {
  386.                 alert('请先填写 API Key');
  387.                 return;
  388.             }
  389.             if (!uploadedImage) {
  390.                 alert('请先上传参考图片');
  391.                 return;
  392.             }
  393.             if (!prompt) {
  394.                 alert('请输入图片描述');
  395.                 return;
  396.             }

  397.             // 禁用按钮
  398.             const btn = document.getElementById('imageGenerateBtn');
  399.             btn.disabled = true;

  400.             // 初始化结果区
  401.             initResults(count);

  402.             try {
  403.                 const promises = [];
  404.                 for (let i = 0; i < count; i++) {
  405.                     promises.push(
  406.                         callImageToImageApi(config, prompt, size, i)
  407.                     );
  408.                 }
  409.                 await Promise.all(promises);
  410.             } finally {
  411.                 btn.disabled = false;
  412.             }
  413.         }

  414.         // 调用图生图API
  415.         async function callImageToImageApi(config, prompt, size, index) {
  416.             const card = document.getElementById(`result-card-${index}`);
  417.             
  418.             try {
  419.                 const formData = new FormData();
  420.                 formData.append('model', config.model);
  421.                 formData.append('image', uploadedImage);
  422.                 formData.append('prompt', prompt);
  423.                 formData.append('n', "1");
  424.                 formData.append('size', size);

  425.                 const response = await fetch(`${config.baseUrl}/v1/images/edits`, {
  426.                     method: 'POST',
  427.                     headers: {
  428.                         'Authorization': `Bearer ${config.apiKey}`
  429.                         // 不要手动设置 Content-Type,让浏览器自动处理 FormData
  430.                     },
  431.                     body: formData
  432.                 });

  433.                 const data = await response.json();

  434.                 if (!response.ok) {
  435.                     throw new Error(data.error?.message || `请求失败: ${response.status}`);
  436.                 }

  437.                 // 处理结果
  438.                 const b64 = data.data[0].b64_json;
  439.                 const imgSrc = `data:image/png;base64,${b64}`;
  440.                 renderResultCard(card, imgSrc);

  441.             } catch (error) {
  442.                 let errorMsg = error.message;
  443.                 if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
  444.                     errorMsg = '网络异常,请检查BaseURL是否正确';
  445.                 }
  446.                 renderErrorCard(card, errorMsg);
  447.             }
  448.         }

  449.         // 初始化结果区
  450.         function initResults(count) {
  451.             const resultsArea = document.getElementById('resultsArea');
  452.             const resultsGrid = document.getElementById('resultsGrid');
  453.             
  454.             resultsGrid.innerHTML = '';
  455.             resultsArea.classList.remove('hidden');

  456.             for (let i = 0; i < count; i++) {
  457.                 const card = document.createElement('div');
  458.                 card.id = `result-card-${i}`;
  459.                 card.className = 'bg-gray-800 rounded-xl overflow-hidden';
  460.                 card.innerHTML = `
  461.                     <div class="aspect-square flex items-center justify-center">
  462.                         <div class="text-center">
  463.                             <div class="spinner mx-auto mb-2"></div>
  464.                             <p class="text-gray-400 text-sm">生成中...</p>
  465.                         </div>
  466.                     </div>
  467.                 `;
  468.                 resultsGrid.appendChild(card);
  469.             }

  470.             // 滚动到结果区
  471.             resultsArea.scrollIntoView({ behavior: 'smooth' });
  472.         }

  473.         // 渲染成功的结果卡片
  474.         function renderResultCard(card, imgSrc) {
  475.             const timestamp = Date.now();
  476.             card.innerHTML = `
  477.                 <div class="relative group">
  478.                     <img src="${imgSrc}" class="w-full aspect-square object-cover" alt="生成的图片">
  479.                     <div class="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-2">
  480.                         <button
  481.                             onclick="openModal('${imgSrc}')"
  482.                             class="p-2 bg-white/20 hover:bg-white/30 rounded-lg text-white transition-all"
  483.                             title="放大"
  484.                         >
  485.                             <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  486.                                 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v3m0 0v3m0-3h3m-3 0H7" />
  487.                             </svg>
  488.                         </button>
  489.                         <button
  490.                             onclick="downloadImage('${imgSrc}', '${timestamp}')"
  491.                             class="p-2 bg-white/20 hover:bg-white/30 rounded-lg text-white transition-all"
  492.                             title="下载"
  493.                         >
  494.                             <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  495.                                 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
  496.                             </svg>
  497.                         </button>
  498.                     </div>
  499.                 </div>
  500.             `;
  501.         }

  502.         // 渲染错误卡片
  503.         function renderErrorCard(card, errorMsg) {
  504.             card.innerHTML = `
  505.                 <div class="aspect-square flex items-center justify-center p-4">
  506.                     <div class="text-center">
  507.                         <svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 mx-auto text-red-500 mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  508.                             <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
  509.                         </svg>
  510.                         <p class="text-red-400 text-sm">${errorMsg}</p>
  511.                     </div>
  512.                 </div>
  513.             `;
  514.         }

  515.         // 打开放大弹窗
  516.         function openModal(imgSrc) {
  517.             const modal = document.getElementById('imageModal');
  518.             const modalImg = document.getElementById('modalImage');
  519.             modalImg.src = imgSrc;
  520.             modal.classList.remove('hidden');
  521.             modal.classList.add('flex');
  522.             document.body.style.overflow = 'hidden';
  523.         }

  524.         // 关闭弹窗
  525.         function closeModal(event) {
  526.             if (event && event.target !== event.currentTarget) return;
  527.             const modal = document.getElementById('imageModal');
  528.             modal.classList.add('hidden');
  529.             modal.classList.remove('flex');
  530.             document.body.style.overflow = '';
  531.         }

  532.         // 下载图片
  533.         function downloadImage(imgSrc, timestamp) {
  534.             const link = document.createElement('a');
  535.             link.href = imgSrc;
  536.             link.download = `image_${timestamp}.png`;
  537.             document.body.appendChild(link);
  538.             link.click();
  539.             document.body.removeChild(link);
  540.         }

  541.         // ESC 键关闭弹窗
  542.         document.addEventListener('keydown', function(e) {
  543.             if (e.key === 'Escape') {
  544.                 closeModal();
  545.             }
  546.         });
  547.     </script>
  548. </body>
  549. </html>
复制代码

📌 使用说明
  • 保存文件:把上面的代码复制,保存为 ai-draw.html
  • 打开使用:直接用浏览器打开这个文件就行
  • 配置 API
    • 左边填入你的 API 服务商的 Base URL
    • 填入你的 API Key
    • 模型默认是 gpt-image-2,可以根据你的服务商修改

  • 配置自动保存:你填的配置会自动存在浏览器里,下次打开不用重新填
  • 开始绘图
    • 文生图:输入描述,选参数,点生成
    • 图生图:上传参考图,输入描述,点生成


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表