|
|
- 做一个 chatgpt 一样的 html 页面,要求如下:
- 【一、功能】
- 1、文生图:用户输入文字描述,生成图片
- 2、图生图:用户上传 1 张参考图 + 输入文字描述,生成图片
- 【二、配置区】
- 1、BaseURL 输入框
- 2、API Key 输入框,密码类型显示
- 3、模型默认 gpt-image-2
- 三个值都浏览器本地保存,刷新不丢失
- 调用接口时,完整 URL = BaseURL + 接口路径
- 请求头必须带:Authorization: Bearer {APIKey}
- 【三、文生图接口】
- 路径:POST {baseURL}/v1/images/generations
- Content-Type: application/json
- 请求体参数:
- {
- "model": "gpt-image-2", // 用户选择的模型
- "prompt": "用户输入的文字",
- "n": 1, // 永远传 1,不要其他值
- "size": "1024x1024", // 用户可选 2024x1024 / 1536x1024 / 1024x1536
- "quality": "high" // 用户可选 auto /high/medium /low
- }
- 【四、图生图接口】
- 路径:POST {baseURL}/v1/images/edits
- Content-Type: multipart/form-data(用 FormData 提交,不要手动设置 Content-Type,让浏览器自动加)
- FormData 字段:
- model: "gpt-image-2"(用户选择的模型)
- image: 用户上传的图片文件(File 对象,字段名必须是 image,不是 image [])
- prompt: 用户输入的文字
- n: "1"(字符串 "1",永远传 1)
- size: "1024x1024"(用户可选)
- 【五、生成数量说明】
- 用户可以选择生成数量:1 张 / 2 张 / 4 张 / 6 张
- 但调用 API 时,n 参数永远传 1
- 通过 Promise.all 同时发起 N 次请求实现批量生成
- 每张图独立显示的状态和结果,某一张失败不影响其他
- 【六、响应处理】
- 接口返回格式:
- {
- "created": 1713833628,
- "data": [{"b64_json": "一张的 base64 字符串..."}],
- "usage": {"total_tokens": 100, "input_tokens": 50, "output_tokens": 50 }
- }
- 处理方式:
- 取 response.data [0].b64_json
- 拼接成 data:image/png;base64,${b64_json} 作为 img 的 src
- 显示在结果区域
- 【七、图片展示】
- 多张图片用网格布局(grid),自适应排列
- 每张图片要同时显示「放大」「下载」两个按钮
- 点击图片放大按钮:弹出全屏弹窗,居中显示原图,点击遮罩关闭
- 点击下载按钮:直接下载图片,文件名 image_时间戳.png
- 生成中显示加载或 loading 动画
- 【八、错误处理】
- API 报错时,把后端返回的 error.message 显示在对应图片位置
- 网络错误显示「网络异常,请检查 BaseURL 是否正确」
- API Key 为空时同步直接返回,显示「请先填写 API Key」
- 【九、技术栈】
- 原生 HTML + JavaScript + Tailwind CSS(CDN 引入)
- 不要使用 React/Vue 等任何框架
- 所有代码放在一个 HTML 文件里
- UI 风格参考 ChatGPT,深色主题,简约现代
复制代码
豆包选择【超能模式】,然后发送上面的指令。
然后自动会生成网页代码:
你只需要把下面的代码完整复制,保存为一个 .html 文件,直接用浏览器打开就能用了!
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>AI 绘图工具 | 文生图 & 图生图</title>
- <script src="https://cdn.tailwindcss.com"></script>
- <script>
- tailwind.config = {
- theme: {
- extend: {
- colors: {
- sidebar: '#202123',
- main: '#343541',
- input: '#40414f',
- },
- }
- }
- }
- </script>
- <style type="text/tailwindcss">
- @layer utilities {
- .content-auto {
- content-visibility: auto;
- }
- .scrollbar-thin {
- scrollbar-width: thin;
- scrollbar-color: #565869 transparent;
- }
- .spinner {
- border: 3px solid rgba(255, 255, 255, 0.3);
- border-radius: 50%;
- border-top: 3px solid #fff;
- width: 24px;
- height: 24px;
- animation: spin 1s linear infinite;
- }
- @keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
- }
- }
- </style>
- </head>
- <body class="bg-main text-gray-100 h-screen flex overflow-hidden">
- <!-- 左侧侧边栏 - 配置区 -->
- <aside class="w-72 bg-sidebar p-4 flex flex-col overflow-y-auto scrollbar-thin">
- <div class="mb-6">
- <h1 class="text-xl font-bold flex items-center gap-2">
- <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">
- <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" />
- </svg>
- AI 绘图工具
- </h1>
- <p class="text-gray-400 text-sm mt-1">文生图 & 图生图</p>
- </div>
- <div class="space-y-4 flex-1">
- <h2 class="text-sm font-semibold text-gray-400 uppercase tracking-wider">API 配置</h2>
-
- <div>
- <label class="block text-sm font-medium text-gray-300 mb-1">Base URL</label>
- <input
- type="text"
- id="baseUrl"
- placeholder="https://api.openai.com"
- 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"
- >
- </div>
- <div>
- <label class="block text-sm font-medium text-gray-300 mb-1">API Key</label>
- <input
- type="password"
- id="apiKey"
- placeholder="sk-xxxxxx"
- 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"
- >
- </div>
- <div>
- <label class="block text-sm font-medium text-gray-300 mb-1">模型</label>
- <input
- type="text"
- id="model"
- value="gpt-image-2"
- 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"
- >
- </div>
- </div>
- <div class="mt-6 pt-4 border-t border-gray-700">
- <p class="text-xs text-gray-500">
- 配置自动保存在本地浏览器中
- </p>
- </div>
- </aside>
- <!-- 右侧主内容区 -->
- <main class="flex-1 flex flex-col overflow-hidden">
- <div class="flex-1 overflow-y-auto p-4 md:p-6 scrollbar-thin">
- <!-- 标签页切换 -->
- <div class="flex gap-1 mb-6 bg-input rounded-lg p-1 w-fit">
- <button
- id="tabTextToImage"
- class="px-4 py-2 rounded-md text-sm font-medium bg-blue-600 text-white transition-all"
- onclick="switchTab('text')"
- >
- 文生图
- </button>
- <button
- id="tabImageToImage"
- class="px-4 py-2 rounded-md text-sm font-medium text-gray-300 hover:text-white transition-all"
- onclick="switchTab('image')"
- >
- 图生图
- </button>
- </div>
- <!-- 文生图输入区 -->
- <div id="textToImagePanel" class="max-w-3xl mx-auto">
- <div class="bg-gray-800 rounded-xl p-4 mb-6">
- <label class="block text-sm font-medium text-gray-300 mb-2">图片描述</label>
- <textarea
- id="textPrompt"
- rows="3"
- placeholder="描述你想要生成的图片,例如:一只可爱的猫咪在草地上晒太阳,卡通风格,高清..."
- 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"
- ></textarea>
- <!-- 参数选项 -->
- <div class="flex flex-wrap gap-4 mt-4">
- <div>
- <label class="block text-xs text-gray-400 mb-1">生成数量</label>
- <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">
- <option value="1">1 张</option>
- <option value="2">2 张</option>
- <option value="4" selected>4 张</option>
- <option value="6">6 张</option>
- </select>
- </div>
- <div>
- <label class="block text-xs text-gray-400 mb-1">图片尺寸</label>
- <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">
- <option value="1024x1024" selected>1024×1024</option>
- <option value="2024x1024">2024×1024</option>
- <option value="1536x1024">1536×1024</option>
- <option value="1024x1536">1024×1536</option>
- </select>
- </div>
- <div>
- <label class="block text-xs text-gray-400 mb-1">图片质量</label>
- <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">
- <option value="auto">Auto</option>
- <option value="high" selected>High</option>
- <option value="medium">Medium</option>
- <option value="low">Low</option>
- </select>
- </div>
- </div>
- <button
- id="textGenerateBtn"
- onclick="generateTextToImage()"
- 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"
- >
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
- </svg>
- 开始生成
- </button>
- </div>
- </div>
- <!-- 图生图输入区 -->
- <div id="imageToImagePanel" class="max-w-3xl mx-auto hidden">
- <div class="bg-gray-800 rounded-xl p-4 mb-6">
- <label class="block text-sm font-medium text-gray-300 mb-2">参考图片</label>
- <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">
- <input type="file" id="imageInput" accept="image/*" class="hidden" onchange="handleImageUpload(event)">
- <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">
- <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" />
- </svg>
- <p class="text-gray-400 text-sm">点击上传图片,或拖拽到这里</p>
- <p class="text-gray-500 text-xs mt-1">支持 JPG、PNG 格式</p>
- </div>
- <div id="imagePreview" class="hidden mt-3">
- <img id="previewImg" class="max-h-48 rounded-lg mx-auto" alt="预览">
- </div>
- <label class="block text-sm font-medium text-gray-300 mb-2 mt-4">图片描述</label>
- <textarea
- id="imagePrompt"
- rows="3"
- placeholder="基于参考图,描述你想要修改/生成的效果..."
- 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"
- ></textarea>
- <!-- 参数选项 -->
- <div class="flex flex-wrap gap-4 mt-4">
- <div>
- <label class="block text-xs text-gray-400 mb-1">生成数量</label>
- <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">
- <option value="1">1 张</option>
- <option value="2">2 张</option>
- <option value="4" selected>4 张</option>
- <option value="6">6 张</option>
- </select>
- </div>
- <div>
- <label class="block text-xs text-gray-400 mb-1">图片尺寸</label>
- <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">
- <option value="1024x1024" selected>1024×1024</option>
- <option value="2024x1024">2024×1024</option>
- <option value="1536x1024">1536×1024</option>
- <option value="1024x1536">1024×1536</option>
- </select>
- </div>
- </div>
- <button
- id="imageGenerateBtn"
- onclick="generateImageToImage()"
- 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"
- >
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
- </svg>
- 开始生成
- </button>
- </div>
- </div>
- <!-- 结果展示区 -->
- <div id="resultsArea" class="max-w-5xl mx-auto hidden">
- <h3 class="text-lg font-medium mb-4">生成结果</h3>
- <div id="resultsGrid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
- <!-- 动态生成结果卡片 -->
- </div>
- </div>
- </div>
- </main>
- <!-- 图片放大弹窗 -->
- <div id="imageModal" class="fixed inset-0 bg-black/80 z-50 hidden items-center justify-center p-4" onclick="closeModal(event)">
- <button class="absolute top-4 right-4 text-white hover:text-gray-300" onclick="closeModal()">
- <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
- </svg>
- </button>
- <img id="modalImage" class="max-w-full max-h-full rounded-lg" alt="大图预览">
- </div>
- <script>
- // 全局变量
- let uploadedImage = null;
- // 页面加载时读取本地配置
- document.addEventListener('DOMContentLoaded', function() {
- loadConfig();
- bindConfigSave();
- // 上传区域点击触发文件选择
- document.getElementById('imageUploadArea').addEventListener('click', () => {
- document.getElementById('imageInput').click();
- });
- });
- // 加载本地配置
- function loadConfig() {
- const baseUrl = localStorage.getItem('ai_draw_baseUrl');
- const apiKey = localStorage.getItem('ai_draw_apiKey');
- const model = localStorage.getItem('ai_draw_model');
- if (baseUrl) document.getElementById('baseUrl').value = baseUrl;
- if (apiKey) document.getElementById('apiKey').value = apiKey;
- if (model) document.getElementById('model').value = model;
- }
- // 配置变更自动保存
- function bindConfigSave() {
- const inputs = ['baseUrl', 'apiKey', 'model'];
- inputs.forEach(id => {
- document.getElementById(id).addEventListener('input', function() {
- localStorage.setItem(`ai_draw_${id}`, this.value);
- });
- });
- }
- // 切换标签页
- function switchTab(tab) {
- const textTab = document.getElementById('tabTextToImage');
- const imageTab = document.getElementById('tabImageToImage');
- const textPanel = document.getElementById('textToImagePanel');
- const imagePanel = document.getElementById('imageToImagePanel');
- if (tab === 'text') {
- textTab.classList.add('bg-blue-600', 'text-white');
- textTab.classList.remove('text-gray-300');
- imageTab.classList.remove('bg-blue-600', 'text-white');
- imageTab.classList.add('text-gray-300');
- textPanel.classList.remove('hidden');
- imagePanel.classList.add('hidden');
- } else {
- imageTab.classList.add('bg-blue-600', 'text-white');
- imageTab.classList.remove('text-gray-300');
- textTab.classList.remove('bg-blue-600', 'text-white');
- textTab.classList.add('text-gray-300');
- imagePanel.classList.remove('hidden');
- textPanel.classList.add('hidden');
- }
- }
- // 处理图片上传
- function handleImageUpload(event) {
- const file = event.target.files[0];
- if (!file) return;
- uploadedImage = file;
- const reader = new FileReader();
- reader.onload = function(e) {
- document.getElementById('previewImg').src = e.target.result;
- document.getElementById('imagePreview').classList.remove('hidden');
- document.getElementById('imageUploadArea').classList.add('hidden');
- };
- reader.readAsDataURL(file);
- }
- // 获取配置
- function getConfig() {
- return {
- baseUrl: document.getElementById('baseUrl').value.trim().replace(/\/$/, ''),
- apiKey: document.getElementById('apiKey').value.trim(),
- model: document.getElementById('model').value.trim()
- };
- }
- // 文生图
- async function generateTextToImage() {
- const config = getConfig();
- const prompt = document.getElementById('textPrompt').value.trim();
- const count = parseInt(document.getElementById('textCount').value);
- const size = document.getElementById('textSize').value;
- const quality = document.getElementById('textQuality').value;
- if (!config.apiKey) {
- alert('请先填写 API Key');
- return;
- }
- if (!prompt) {
- alert('请输入图片描述');
- return;
- }
- // 禁用按钮
- const btn = document.getElementById('textGenerateBtn');
- btn.disabled = true;
- // 初始化结果区
- initResults(count);
- try {
- const promises = [];
- for (let i = 0; i < count; i++) {
- promises.push(
- callTextToImageApi(config, prompt, size, quality, i)
- );
- }
- await Promise.all(promises);
- } finally {
- btn.disabled = false;
- }
- }
- // 调用文生图API
- async function callTextToImageApi(config, prompt, size, quality, index) {
- const card = document.getElementById(`result-card-${index}`);
-
- try {
- const response = await fetch(`${config.baseUrl}/v1/images/generations`, {
- method: 'POST',
- headers: {
- 'Authorization': `Bearer ${config.apiKey}`,
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- model: config.model,
- prompt: prompt,
- n: 1,
- size: size,
- quality: quality
- })
- });
- const data = await response.json();
- if (!response.ok) {
- throw new Error(data.error?.message || `请求失败: ${response.status}`);
- }
- // 处理结果
- const b64 = data.data[0].b64_json;
- const imgSrc = `data:image/png;base64,${b64}`;
- renderResultCard(card, imgSrc);
- } catch (error) {
- let errorMsg = error.message;
- if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
- errorMsg = '网络异常,请检查BaseURL是否正确';
- }
- renderErrorCard(card, errorMsg);
- }
- }
- // 图生图
- async function generateImageToImage() {
- const config = getConfig();
- const prompt = document.getElementById('imagePrompt').value.trim();
- const count = parseInt(document.getElementById('imageCount').value);
- const size = document.getElementById('imageSize').value;
- if (!config.apiKey) {
- alert('请先填写 API Key');
- return;
- }
- if (!uploadedImage) {
- alert('请先上传参考图片');
- return;
- }
- if (!prompt) {
- alert('请输入图片描述');
- return;
- }
- // 禁用按钮
- const btn = document.getElementById('imageGenerateBtn');
- btn.disabled = true;
- // 初始化结果区
- initResults(count);
- try {
- const promises = [];
- for (let i = 0; i < count; i++) {
- promises.push(
- callImageToImageApi(config, prompt, size, i)
- );
- }
- await Promise.all(promises);
- } finally {
- btn.disabled = false;
- }
- }
- // 调用图生图API
- async function callImageToImageApi(config, prompt, size, index) {
- const card = document.getElementById(`result-card-${index}`);
-
- try {
- const formData = new FormData();
- formData.append('model', config.model);
- formData.append('image', uploadedImage);
- formData.append('prompt', prompt);
- formData.append('n', "1");
- formData.append('size', size);
- const response = await fetch(`${config.baseUrl}/v1/images/edits`, {
- method: 'POST',
- headers: {
- 'Authorization': `Bearer ${config.apiKey}`
- // 不要手动设置 Content-Type,让浏览器自动处理 FormData
- },
- body: formData
- });
- const data = await response.json();
- if (!response.ok) {
- throw new Error(data.error?.message || `请求失败: ${response.status}`);
- }
- // 处理结果
- const b64 = data.data[0].b64_json;
- const imgSrc = `data:image/png;base64,${b64}`;
- renderResultCard(card, imgSrc);
- } catch (error) {
- let errorMsg = error.message;
- if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
- errorMsg = '网络异常,请检查BaseURL是否正确';
- }
- renderErrorCard(card, errorMsg);
- }
- }
- // 初始化结果区
- function initResults(count) {
- const resultsArea = document.getElementById('resultsArea');
- const resultsGrid = document.getElementById('resultsGrid');
-
- resultsGrid.innerHTML = '';
- resultsArea.classList.remove('hidden');
- for (let i = 0; i < count; i++) {
- const card = document.createElement('div');
- card.id = `result-card-${i}`;
- card.className = 'bg-gray-800 rounded-xl overflow-hidden';
- card.innerHTML = `
- <div class="aspect-square flex items-center justify-center">
- <div class="text-center">
- <div class="spinner mx-auto mb-2"></div>
- <p class="text-gray-400 text-sm">生成中...</p>
- </div>
- </div>
- `;
- resultsGrid.appendChild(card);
- }
- // 滚动到结果区
- resultsArea.scrollIntoView({ behavior: 'smooth' });
- }
- // 渲染成功的结果卡片
- function renderResultCard(card, imgSrc) {
- const timestamp = Date.now();
- card.innerHTML = `
- <div class="relative group">
- <img src="${imgSrc}" class="w-full aspect-square object-cover" alt="生成的图片">
- <div class="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-2">
- <button
- onclick="openModal('${imgSrc}')"
- class="p-2 bg-white/20 hover:bg-white/30 rounded-lg text-white transition-all"
- title="放大"
- >
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
- <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" />
- </svg>
- </button>
- <button
- onclick="downloadImage('${imgSrc}', '${timestamp}')"
- class="p-2 bg-white/20 hover:bg-white/30 rounded-lg text-white transition-all"
- title="下载"
- >
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
- <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" />
- </svg>
- </button>
- </div>
- </div>
- `;
- }
- // 渲染错误卡片
- function renderErrorCard(card, errorMsg) {
- card.innerHTML = `
- <div class="aspect-square flex items-center justify-center p-4">
- <div class="text-center">
- <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">
- <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" />
- </svg>
- <p class="text-red-400 text-sm">${errorMsg}</p>
- </div>
- </div>
- `;
- }
- // 打开放大弹窗
- function openModal(imgSrc) {
- const modal = document.getElementById('imageModal');
- const modalImg = document.getElementById('modalImage');
- modalImg.src = imgSrc;
- modal.classList.remove('hidden');
- modal.classList.add('flex');
- document.body.style.overflow = 'hidden';
- }
- // 关闭弹窗
- function closeModal(event) {
- if (event && event.target !== event.currentTarget) return;
- const modal = document.getElementById('imageModal');
- modal.classList.add('hidden');
- modal.classList.remove('flex');
- document.body.style.overflow = '';
- }
- // 下载图片
- function downloadImage(imgSrc, timestamp) {
- const link = document.createElement('a');
- link.href = imgSrc;
- link.download = `image_${timestamp}.png`;
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- }
- // ESC 键关闭弹窗
- document.addEventListener('keydown', function(e) {
- if (e.key === 'Escape') {
- closeModal();
- }
- });
- </script>
- </body>
- </html>
复制代码
📌 使用说明
- 保存文件:把上面的代码复制,保存为 ai-draw.html
- 打开使用:直接用浏览器打开这个文件就行
- 配置 API:
- 左边填入你的 API 服务商的 Base URL
- 填入你的 API Key
- 模型默认是 gpt-image-2,可以根据你的服务商修改
- 配置自动保存:你填的配置会自动存在浏览器里,下次打开不用重新填
- 开始绘图:
- 文生图:输入描述,选参数,点生成
- 图生图:上传参考图,输入描述,点生成
|
|