每日不定时更新,涵盖软件、教程、素材等资源下载。找免费资源就来酷玩资源网。

gpt-image2生图网站源码

酸奶丿果冻 手机软件 0


之前很多小伙伴问我,能不能分享gpt-image2的生图源码,今天它来啦✨ 不管你是编程爱好者、开发者,还是想搭建自己的生图网站,这份源码直接安排,零保留分享!
✅ 源码完整可直接部署:无需二次开发,下载后按照教程操作,就能快速搭建属于自己的AI生图网站,省去从零开发的麻烦,小白开发者也能轻松上手
✅ 核心功能无删减:保留原网站全部核心能力——零门槛文字生图、高清无水印输出、多风格适配、快速出图,所有功能均可正常使用、自由修改
✅ 开源可定制:源码完全开源,支持自由修改功能、调整界面风格、添加专属模块,满足不同开发者的个性化需求,商用、自用都可(合规范围内);补充说明:源码接口调用的是第三方API,单次调用成本约0.04,成本透明,可根据自身需求调整调用频次;大家只需从指定链接注册,获取APIKey后放到网站对应配置处,即可直接使用,操作简单无门槛
✅ 附带详细部署教程:担心不会操作?配套完整图文教程,从下载、配置到部署,一步一步讲解,全程无难点,跟着做就能成功搭建
不管你是想练手编程、搭建个人项目,还是想批量部署生图网站,这份gpt-image2源码都能帮你少走弯路,省去大量开发时间!
无需转发、无需集赞,直接获取源码👇
【api接口地址】:接口地址
温馨提示:源码可自由修改,但禁止用于违规用途哦~ 拿到源码的小伙伴,欢迎评论区反馈部署效果,互相交流学习!🎉

gptimage2源码 #AI生图源码 #开源源码 #编程福利 #开发者必备 #生图网站搭建 #源码分享

源码

<!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>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" />
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap');

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Inter', 'Noto Sans SC', system-ui, sans-serif;
            background: #f5f7fb;
            color: #1e1e2f;
            min-height: 100vh;
            -webkit-font-smoothing: antialiased;
        }

        /* ===== 滚动条 ===== */
        ::-webkit-scrollbar {
            width: 4px;
            height: 4px;
        }
        ::-webkit-scrollbar-track {
            background: transparent;
        }
        ::-webkit-scrollbar-thumb {
            background: rgba(99, 102, 241, .2);
            border-radius: 20px;
        }
        ::-webkit-scrollbar-thumb:hover {
            background: rgba(99, 102, 241, .4);
        }

        /* ===== 顶部导航 ===== */
        .navbar-light {
            background: rgba(255, 255, 255, 0.85);
            backdrop-filter: blur(18px) saturate(1.6);
            border-bottom: 1px solid rgba(0, 0, 0, 0.05);
        }

        /* ===== 卡片通用 ===== */
        .card-light {
            background: #ffffff;
            border: 1px solid rgba(0, 0, 0, 0.06);
            border-radius: 24px;
            transition: border-color 0.25s, box-shadow 0.3s;
        }
        .card-light:hover {
            border-color: rgba(99, 102, 241, 0.15);
            box-shadow: 0 12px 40px -12px rgba(0, 0, 0, 0.06);
        }

        /* ===== 输入框 ===== */
        .input-light {
            background: #ffffff;
            border: 1px solid #e2e4ea;
            border-radius: 14px;
            padding: 12px 18px;
            color: #1e1e2f;
            font-size: 0.9rem;
            transition: all 0.25s;
            outline: none;
            width: 100%;
        }
        .input-light:focus {
            border-color: #818cf8;
            box-shadow: 0 0 0 4px rgba(129, 140, 248, 0.10);
        }
        .input-light::placeholder {
            color: #a0a3b1;
            font-weight: 300;
        }

        /* ===== 文本域 ===== */
        .textarea-light {
            background: #ffffff;
            border: 1px solid #e2e4ea;
            border-radius: 18px;
            padding: 18px 22px;
            color: #1e1e2f;
            font-size: 0.95rem;
            line-height: 1.7;
            transition: all 0.25s;
            outline: none;
            resize: vertical;
            min-height: 100px;
            width: 100%;
            font-family: inherit;
        }
        .textarea-light:focus {
            border-color: #818cf8;
            box-shadow: 0 0 0 4px rgba(129, 140, 248, 0.08);
        }
        .textarea-light::placeholder {
            color: #a0a3b1;
            font-weight: 300;
        }

        /* ===== 按钮主色 ===== */
        .btn-primary {
            background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
            border: none;
            border-radius: 18px;
            padding: 16px 32px;
            font-weight: 600;
            font-size: 1.05rem;
            color: #ffffff;
            cursor: pointer;
            transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
            box-shadow: 0 8px 28px -8px rgba(99, 102, 241, 0.35);
            display: inline-flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
            width: 100%;
            letter-spacing: 0.2px;
        }
        .btn-primary:hover {
            transform: scale(1.008);
            box-shadow: 0 12px 40px -8px rgba(99, 102, 241, 0.45);
            filter: brightness(1.04);
        }
        .btn-primary:active {
            transform: scale(0.98);
        }
        .btn-primary:disabled {
            opacity: 0.5;
            cursor: not-allowed;
            transform: none;
            filter: none;
            box-shadow: none;
        }

        /* ===== 比例按钮 ===== */
        .ratio-btn {
            background: #ffffff;
            border: 1px solid #e2e4ea;
            border-radius: 14px;
            padding: 10px 8px;
            text-align: center;
            cursor: pointer;
            transition: all 0.25s;
            user-select: none;
        }
        .ratio-btn:hover {
            border-color: #a5b4fc;
            background: #f8f9ff;
        }
        .ratio-btn.active {
            background: #6366f1;
            border-color: #6366f1;
            color: #ffffff;
            box-shadow: 0 4px 12px rgba(99, 102, 241, 0.20);
        }
        .ratio-btn.active .desc {
            color: rgba(255, 255, 255, 0.7);
        }
        .ratio-btn .label {
            font-size: 1rem;
            font-weight: 700;
            font-family: 'Inter', monospace;
            letter-spacing: 0.3px;
        }
        .ratio-btn .desc {
            font-size: 0.65rem;
            color: #9ca3af;
            margin-top: 1px;
        }
        .ratio-btn.active .desc {
            color: rgba(255, 255, 255, 0.7);
        }

        /* ===== 灵感标签 ===== */
        .prompt-tag {
            background: #f8f9ff;
            border: 1px solid #e8eaf0;
            border-radius: 100px;
            padding: 8px 16px;
            font-size: 0.8rem;
            color: #4b4d5c;
            cursor: pointer;
            transition: all 0.25s;
            user-select: none;
            display: inline-block;
            line-height: 1.4;
        }
        .prompt-tag:hover {
            background: #eef0ff;
            border-color: #a5b4fc;
            color: #4338ca;
            transform: translateY(-1px);
        }

        /* ===== 历史卡片 ===== */
        .history-card {
            border-radius: 14px;
            overflow: hidden;
            background: #ffffff;
            border: 1px solid #f0f1f5;
            transition: all 0.3s;
            cursor: pointer;
        }
        .history-card:hover {
            border-color: #d0d5f0;
            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
            transform: translateY(-2px);
        }
        .history-card img {
            width: 100%;
            aspect-ratio: 16/9;
            object-fit: cover;
            display: block;
            background: #f5f6fa;
        }
        .history-card .info {
            padding: 10px 12px 12px;
        }
        .history-card .info p {
            font-size: 0.75rem;
            color: #6b7280;
            display: -webkit-box;
            -webkit-line-clamp: 2;
            -webkit-box-orient: vertical;
            overflow: hidden;
            line-height: 1.5;
        }

        /* ===== 结果卡片 ===== */
        .result-card {
            border-radius: 18px;
            overflow: hidden;
            background: #ffffff;
            border: 1px solid #f0f1f5;
            transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
            cursor: pointer;
        }
        .result-card:hover {
            transform: translateY(-4px) scale(1.01);
            border-color: #c7d2fe;
            box-shadow: 0 16px 48px -12px rgba(99, 102, 241, 0.10);
        }
        .result-card img {
            width: 100%;
            aspect-ratio: 16/9;
            object-fit: cover;
            display: block;
            background: #f5f6fa;
        }
        .result-card .actions {
            padding: 12px 14px 14px;
            display: flex;
            gap: 8px;
        }
        .result-card .actions button {
            flex: 1;
            padding: 8px 0;
            border-radius: 12px;
            border: none;
            background: #f5f6fa;
            color: #4b4d5c;
            font-size: 0.75rem;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 6px;
        }
        .result-card .actions button:hover {
            background: #eef0ff;
            color: #4338ca;
        }

        /* ===== 进度条 ===== */
        .progress-track {
            height: 4px;
            background: #f0f1f5;
            border-radius: 20px;
            overflow: hidden;
        }
        .progress-bar {
            height: 100%;
            background: linear-gradient(90deg, #6366f1, #8b5cf6, #22d3ee);
            border-radius: 20px;
            transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
            width: 0%;
        }

        /* ===== Toast ===== */
        .toast {
            position: fixed;
            bottom: 32px;
            right: 32px;
            background: rgba(255, 255, 255, 0.95);
            backdrop-filter: blur(16px);
            border: 1px solid rgba(99, 102, 241, 0.12);
            border-radius: 18px;
            padding: 14px 24px;
            color: #1e1e2f;
            font-size: 0.85rem;
            display: flex;
            align-items: center;
            gap: 10px;
            box-shadow: 0 16px 40px -12px rgba(0, 0, 0, 0.12);
            opacity: 0;
            transform: translateY(12px) scale(0.96);
            transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
            pointer-events: none;
            z-index: 999;
        }
        .toast.show {
            opacity: 1;
            transform: translateY(0) scale(1);
            pointer-events: auto;
        }
        .toast i {
            font-size: 1.1rem;
            color: #6366f1;
        }

        /* ===== 状态指示器 ===== */
        .status-dot {
            width: 10px;
            height: 10px;
            border-radius: 50%;
            display: inline-block;
            background: #6366f1;
            animation: pulse-dot 1.6s ease-in-out infinite;
        }
        @keyframes pulse-dot {
            0%,
            100% {
                opacity: 0.4;
                transform: scale(0.9);
            }
            50% {
                opacity: 1;
                transform: scale(1.2);
            }
        }

        /* ===== 辅助 ===== */
        .line-clamp-2 {
            display: -webkit-box;
            -webkit-line-clamp: 2;
            -webkit-box-orient: vertical;
            overflow: hidden;
        }

        /* ===== 响应式 ===== */
        @media (max-width: 768px) {
            .navbar-light .row {
                flex-direction: column;
                gap: 10px;
            }
            .btn-primary {
                font-size: 0.95rem;
                padding: 14px 18px;
            }
            .ratio-btn .label {
                font-size: 0.9rem;
            }
            .toast {
                bottom: 16px;
                right: 16px;
                left: 16px;
                border-radius: 14px;
                padding: 12px 18px;
                font-size: 0.8rem;
            }
            .result-card .actions button {
                font-size: 0.7rem;
                padding: 6px 0;
            }
        }

        /* ===== 选择框 ===== */
        select.input-light {
            appearance: none;
            background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%236b7280' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
            background-repeat: no-repeat;
            background-position: right 16px center;
            padding-right: 40px;
            cursor: pointer;
        }
        select.input-light option {
            background: #ffffff;
            color: #1e1e2f;
        }

        /* ===== 徽章 ===== */
        .badge-soft {
            display: inline-flex;
            align-items: center;
            gap: 4px;
            padding: 2px 10px;
            border-radius: 100px;
            font-size: 0.7rem;
            font-weight: 500;
            background: #f0f1ff;
            color: #6366f1;
        }
    </style>
</head>
<body>

    <!-- ===== 顶部导航 ===== -->
    <header class="navbar-light sticky top-0 z-50">
        <div class="max-w-7xl mx-auto px-5 md:px-8 py-4 flex items-center justify-between gap-4 row">
            <div class="flex items-center gap-4 shrink-0">
                <div class="flex items-center gap-2.5">
                    <span class="w-9 h-9 rounded-xl bg-gradient-to-br from-indigo-500 to-purple-600 flex items-center justify-center text-white font-bold text-lg shadow-sm shadow-indigo-200">✦</span>
                    <span class="text-lg font-semibold tracking-tight text-gray-800">多米<span class="text-indigo-500">AI</span></span>
                </div>
                <span class="hidden sm:inline text-xs text-gray-400 font-light border-l border-gray-200/60 pl-4">图像工坊</span>
            </div>

            <div class="flex items-center gap-3 w-full max-w-md">
                <div class="relative flex-1">
                    <i class="fas fa-key absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 text-xs"></i>
                    <input id="apiKey" type="password" placeholder="API Key · 一次保存,永久使用" class="input-light pl-10 pr-4 text-sm" />
                </div>
                <button onclick="toggleKeyVisibility()" class="w-10 h-10 rounded-xl bg-gray-100 hover:bg-gray-200 flex items-center justify-center shrink-0 transition-colors border border-gray-200/60">
                    <i id="keyEye" class="fas fa-eye text-gray-500 text-sm"></i>
                </button>
            </div>
        </div>
    </header>

    <!-- ===== 主布局 ===== -->
    <div class="max-w-7xl mx-auto px-5 md:px-8 py-8 flex gap-8">

        <!-- ===== 侧边栏 · 历史 ===== -->
        <aside class="hidden xl:block w-64 shrink-0">
            <div class="card-light p-5 sticky top-28 max-h-[calc(100vh-10rem)] overflow-y-auto">
                <div class="flex items-center justify-between mb-5">
                    <h3 class="text-sm font-semibold text-gray-700 flex items-center gap-2">
                        <i class="fas fa-clock-rotate-left text-indigo-400 text-xs"></i> 生成历史
                    </h3>
                    <button onclick="clearHistory()" class="text-xs text-gray-400 hover:text-red-500 transition-colors">
                        <i class="fas fa-trash-can"></i>
                    </button>
                </div>
                <div id="history" class="space-y-3"></div>
                <div id="historyEmpty" class="text-center py-10 text-gray-400 text-xs">
                    <i class="fas fa-image text-xl mb-3 block opacity-50"></i>
                    暂无记录
                </div>
            </div>
        </aside>

        <!-- ===== 主内容 ===== -->
        <main class="flex-1 min-w-0">

            <!-- Hero -->
            <div class="text-center mb-10">
                <h1 class="text-4xl md:text-5xl font-extrabold tracking-tight text-gray-900 leading-tight">
                    AI <span class="text-indigo-500">爆款</span>工坊
                </h1>
                <p class="mt-3 text-gray-500 text-sm md:text-base flex items-center justify-center gap-2 flex-wrap">
                    <span class="badge-soft">
                        <i class="fas fa-sparkles text-indigo-400 text-[10px]"></i> image2 4K
                    </span>
                    <span class="text-gray-300">·</span>
                    <span>多种比例</span>
                    <span class="text-gray-300">·</span>
                    <span>海量灵感</span>
                </p>
            </div>

            <!-- ===== 生成卡片 ===== -->
            <div class="card-light p-6 md:p-8 shadow-sm">

                <!-- 提示词 -->
                <textarea id="prompt" class="textarea-light" placeholder="描述你的爆款图片,或点击下方灵感标签一键使用 …"></textarea>

                <!-- 灵感标签库 -->
                <div class="mt-7">
                    <div class="flex items-center justify-between mb-4">
                        <span class="text-xs text-gray-500 flex items-center gap-2">
                            <i class="fas fa-lightbulb text-amber-500/80 text-[10px]"></i> 精选灵感 · 点击即用
                        </span>
                        <button onclick="shufflePrompts()" class="text-xs px-4 py-1.5 rounded-full bg-gray-100 hover:bg-gray-200 text-gray-500 transition-colors flex items-center gap-1.5 border border-gray-200/60">
                            <i class="fas fa-arrows-rotate text-[10px]"></i> 换一批
                        </button>
                    </div>
                    <div id="promptLibrary" class="flex flex-wrap gap-2 max-h-44 overflow-y-auto pr-1"></div>
                </div>

                <!-- 比例 -->
                <div class="mt-7">
                    <span class="text-xs text-gray-500 flex items-center gap-2 mb-3">
                        <i class="fas fa-crop text-[10px]"></i> 输出比例
                    </span>
                    <div id="ratioButtons" class="grid grid-cols-5 gap-2.5"></div>
                </div>

                <!-- 参数行 -->
                <div class="mt-7 grid grid-cols-1 md:grid-cols-3 gap-4">
                    <div>
                        <label class="block text-xs text-gray-500 mb-1.5 font-medium">模型</label>
                        <select id="model" class="input-light">
                            <option value="gpt-image-2">image2 4K · 推荐</option>
                        </select>
                    </div>
                    <div>
                        <label class="block text-xs text-gray-500 mb-1.5 font-medium">生成数量</label>
                        <select id="n" class="input-light">
                            <option value="1">1 张</option>
                            <option value="2" selected>2 张</option>
                            <option value="4">4 张</option>
                        </select>
                    </div>
                    <div>
                        <label class="block text-xs text-gray-500 mb-1.5 font-medium">参考图像(可选)</label>
                        <input id="refImage" type="text" placeholder="https://..." class="input-light text-sm" />
                    </div>
                </div>

                <!-- 生成按钮 -->
                <button onclick="generateImage()" id="generateBtn" class="btn-primary mt-8">
                    <i class="fas fa-bolt"></i>
                    <span id="btnText">生成爆款图片</span>
                </button>
            </div>

            <!-- ===== 结果区域 ===== -->
            <div id="resultArea" class="mt-10 hidden">
                <div class="card-light p-6 md:p-8 shadow-sm">
                    <!-- 状态头 -->
                    <div class="flex items-center justify-between mb-5">
                        <div class="flex items-center gap-3">
                            <span id="statusIcon" class="status-dot"></span>
                            <div>
                                <div id="statusText" class="text-base font-semibold text-gray-800">任务提交中 …</div>
                                <div id="taskIdDisplay" class="text-[11px] text-gray-400 font-mono mt-0.5"></div>
                            </div>
                        </div>
                        <button onclick="cancelTask()" class="text-xs px-4 py-2 rounded-xl bg-gray-100 hover:bg-red-100 text-gray-500 hover:text-red-600 transition-colors border border-gray-200/60">
                            <i class="fas fa-xmark mr-1.5"></i>取消
                        </button>
                    </div>

                    <!-- 进度 -->
                    <div class="progress-track mb-8">
                        <div id="progress" class="progress-bar"></div>
                    </div>

                    <!-- 图片网格 -->
                    <div id="images" class="grid grid-cols-1 sm:grid-cols-2 gap-5"></div>
                </div>
            </div>

        </main>
    </div>

    <!-- ===== Toast ===== -->
    <div id="toast" class="toast">
        <i id="toastIcon" class="fas fa-check-circle"></i>
        <span id="toastMsg">已完成</span>
    </div>

    <script>
        // ============================================================
        //  状态
        // ============================================================
        let currentTaskId = null;
        let pollingInterval = null;
        let history = JSON.parse(localStorage.getItem('duomi_history') || '[]');
        let promptLibrary = [
            "赛博朋克东京夜景,雨夜霓虹灯反射在湿漉漉的街道上,飞行汽车穿梭,超现实主义,电影级光影,4K",
            "梦幻治愈系森林小屋,夕阳洒进窗户,飘浮的萤火虫,温暖色调,吉卜力风格,超详细",
            "一位身穿汉服的古代女子站在长城上眺望,晨雾缭绕,金色阳光,史诗级电影感,写实摄影",
            "蒸汽朋克风格的巨大飞艇在天空中航行,云海翻腾,铜管机械细节丰富,复古色彩",
            "极简日式禅意庭院,枯山水,晨雾弥漫,极简构图,宁静氛围,4K超清",
            "水下梦幻珊瑚城堡,彩色鱼群游弋,光线从海面穿透,魔幻现实主义",
            "未来赛博格少女在霓虹城市中漫步,反射在玻璃上的全息投影,cyberpunk 2077风格",
            "中国古风山水画意境,墨色淡彩,山川云雾,飞鸟点缀,国风插画大师级",
            "一只戴着王冠的胖橘猫坐在宝座上,宫廷背景,搞笑奢华,卡通写实混合",
            "黄金时刻的薰衣草花田,一位少女奔跑其中,长发飞扬,电影画面,温暖光影",
            "黑暗奇幻:黑龙盘踞在古老城堡废墟上,月光照耀,史诗级概念艺术",
            "极简产品渲染:透明玻璃香水瓶,漂浮在水面,反射梦幻光斑,工作室灯光",
            "80年代复古霓虹街机厅,赛博朋克少女玩游戏,胶片颗粒,怀旧氛围",
            "魔法森林中的精灵少女,长发飘逸,萤火虫环绕,梦幻光效,吉卜力+写实",
            "雪山湖泊日出,镜面反射,极致风景摄影,4K国家地理级别"
        ];

        const ratios = [
            { label: "9:16", value: "1024x1792", desc: "竖版" },
            { label: "3:4", value: "1152x1536", desc: "主图" },
            { label: "1:1", value: "1024x1024", desc: "正方" },
            { label: "16:9", value: "1792x1024", desc: "横版" },
            { label: "21:9", value: "1792x768", desc: "宽屏" }
        ];

        // ============================================================
        //  API Key 持久化
        // ============================================================
        function loadApiKey() {
            const saved = localStorage.getItem('duomi_api_key');
            if (saved) {
                document.getElementById('apiKey').value = saved;
            }
        }

        function saveApiKey() {
            const key = document.getElementById('apiKey').value.trim();
            if (key) {
                localStorage.setItem('duomi_api_key', key);
            } else {
                localStorage.removeItem('duomi_api_key');
            }
        }

        // 输入时自动保存
        document.addEventListener('DOMContentLoaded', () => {
            loadApiKey();
            document.getElementById('apiKey').addEventListener('input', saveApiKey);
        });

        // 切换 Key 可见性
        function toggleKeyVisibility() {
            const el = document.getElementById('apiKey');
            const eye = document.getElementById('keyEye');
            if (el.type === 'password') {
                el.type = 'text';
                eye.className = 'fas fa-eye-slash text-gray-500 text-sm';
            } else {
                el.type = 'password';
                eye.className = 'fas fa-eye text-gray-500 text-sm';
            }
        }

        // ============================================================
        //  渲染函数
        // ============================================================

        function renderPromptLibrary() {
            const container = document.getElementById('promptLibrary');
            container.innerHTML = '';
            promptLibrary.forEach(text => {
                const tag = document.createElement('span');
                tag.className = 'prompt-tag';
                tag.textContent = text.length > 58 ? text.slice(0, 55) + '…' : text;
                tag.title = text;
                tag.onclick = () => {
                    document.getElementById('prompt').value = text;
                    showToast('灵感已加载', 'fa-lightbulb');
                };
                container.appendChild(tag);
            });
        }

        function shufflePrompts() {
            promptLibrary = promptLibrary.sort(() => Math.random() - 0.5);
            renderPromptLibrary();
            showToast('灵感已刷新', 'fa-arrows-rotate');
        }

        function renderRatios() {
            const container = document.getElementById('ratioButtons');
            container.innerHTML = '';
            ratios.forEach((r, i) => {
                const btn = document.createElement('div');
                btn.className = `ratio-btn ${i === 3 ? 'active' : ''}`;
                btn.innerHTML = `<div class="label">${r.label}</div><div class="desc">${r.desc}</div>`;
                btn.onclick = () => {
                    document.querySelectorAll('.ratio-btn').forEach(b => b.classList.remove('active'));
                    btn.classList.add('active');
                };
                container.appendChild(btn);
            });
        }

        function renderHistory() {
            const container = document.getElementById('history');
            const empty = document.getElementById('historyEmpty');
            container.innerHTML = '';
            if (!history.length) {
                empty.style.display = 'block';
                return;
            }
            empty.style.display = 'none';
            history.slice(0, 10).forEach(item => {
                const div = document.createElement('div');
                div.className = 'history-card';
                div.innerHTML = `
                    <img src="${item.images[0]}" alt="历史" loading="lazy" />
                    <div class="info">
                        <p>${item.prompt}</p>
                    </div>
                `;
                div.onclick = () => showResult(item);
                container.appendChild(div);
            });
        }

        // ============================================================
        //  核心生成
        // ============================================================

        async function generateImage() {
            const apiKey = document.getElementById('apiKey').value.trim();
            if (!apiKey) {
                showToast('请先输入 API Key', 'fa-key');
                document.getElementById('apiKey').focus();
                return;
            }
            // 再次保存
            localStorage.setItem('duomi_api_key', apiKey);

            let prompt = document.getElementById('prompt').value.trim();
            if (!prompt) {
                showToast('请输入或选择提示词', 'fa-pencil');
                return;
            }

            const btn = document.getElementById('generateBtn');
            btn.disabled = true;
            document.getElementById('btnText').innerHTML = `<i class="fas fa-spinner fa-spin"></i> 提交中 …`;

            // 显示结果区
            const area = document.getElementById('resultArea');
            area.classList.remove('hidden');
            document.getElementById('statusText').textContent = '任务已提交,正在生成 …';
            document.getElementById('statusIcon').className = 'status-dot';
            document.getElementById('progress').style.width = '20%';
            document.getElementById('images').innerHTML = '';

            // 获取当前比例
            const activeRatio = document.querySelector('.ratio-btn.active');
            const size = activeRatio ? activeRatio.querySelector('.label').textContent : '16:9';
            const sizeMap = {
                '9:16': '1024x1792',
                '3:4': '1152x1536',
                '1:1': '1024x1024',
                '16:9': '1792x1024',
                '21:9': '1792x768'
            };
            const finalSize = sizeMap[size] || '1792x1024';

            const payload = {
                model: 'gpt-image-2',
                prompt: prompt,
                size: finalSize,
                n: parseInt(document.getElementById('n').value)
            };

            try {
                const res = await fetch('https://duomiapi.com/v1/images/generations?async=true', {
                    method: 'POST',
                    headers: {
                        'Authorization': apiKey,
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(payload)
                });
                const data = await res.json();
                if (data.id) {
                    currentTaskId = data.id;
                    document.getElementById('taskIdDisplay').textContent = `Task: ${currentTaskId}`;
                    document.getElementById('progress').style.width = '40%';
                    startPolling(apiKey);
                } else {
                    showToast('提交失败,请检查 API Key', 'fa-circle-exclamation');
                    area.classList.add('hidden');
                }
            } catch (e) {
                showToast('网络错误,请重试', 'fa-wifi-slash');
                area.classList.add('hidden');
            } finally {
                btn.disabled = false;
                document.getElementById('btnText').innerHTML = `<i class="fas fa-bolt"></i> 生成爆款图片`;
            }
        }

        function startPolling(apiKey) {
            if (pollingInterval) clearInterval(pollingInterval);
            let attempts = 0;
            pollingInterval = setInterval(async () => {
                attempts++;
                try {
                    const res = await fetch(`https://duomiapi.com/v1/tasks/${currentTaskId}`, {
                        headers: { 'Authorization': apiKey }
                    });
                    const data = await res.json();
                    if (data.state === 'succeeded') {
                        clearInterval(pollingInterval);
                        pollingInterval = null;
                        const images = data.data.images.map(i => i.url);
                        const resultData = {
                            prompt: document.getElementById('prompt').value,
                            images,
                            ratio: '16:9'
                        };
                        showResult(resultData);
                        saveToHistory(resultData);
                        document.getElementById('progress').style.width = '100%';
                        showToast('4K 爆款生成完成 🎉', 'fa-sparkles');
                        document.getElementById('statusIcon').className = 'w-3 h-3 rounded-full bg-emerald-500';
                        document.getElementById('statusText').innerHTML =
                            '生成完成 <span class="text-emerald-600 font-semibold">✓ 4K</span>';
                    } else if (data.state === 'failed') {
                        clearInterval(pollingInterval);
                        pollingInterval = null;
                        showToast('生成失败,请重试', 'fa-circle-exclamation');
                        document.getElementById('statusIcon').className = 'w-3 h-3 rounded-full bg-red-400';
                        document.getElementById('statusText').textContent = '生成失败';
                    } else {
                        const p = Math.min(40 + attempts * 6, 92);
                        document.getElementById('progress').style.width = p + '%';
                        document.getElementById('statusText').textContent = `正在生成 … ${Math.round(p)}%`;
                    }
                } catch (e) {
                    // silent
                }
            }, 2600);
        }

        function showResult(data) {
            const container = document.getElementById('images');
            container.innerHTML = '';
            data.images.forEach(url => {
                const card = document.createElement('div');
                card.className = 'result-card';
                card.innerHTML = `
                    <img src="${url}" alt="生成结果" loading="lazy" />
                    <div class="actions">
                        <button onclick="downloadImage('${url}');event.stopImmediatePropagation()">
                            <i class="fas fa-download"></i> 下载
                        </button>
                        <button onclick="copyImageUrl('${url}');event.stopImmediatePropagation()">
                            <i class="fas fa-link"></i> 复制
                        </button>
                    </div>
                `;
                card.onclick = () => window.open(url, '_blank');
                container.appendChild(card);
            });
            document.getElementById('resultArea').classList.remove('hidden');
        }

        function saveToHistory(data) {
            history.unshift(data);
            if (history.length > 20) history.pop();
            localStorage.setItem('duomi_history', JSON.stringify(history));
            renderHistory();
        }

        function clearHistory() {
            if (!confirm('确定清空所有历史记录?')) return;
            history = [];
            localStorage.setItem('duomi_history', JSON.stringify(history));
            renderHistory();
            showToast('历史已清空', 'fa-trash-can');
        }

        // ============================================================
        //  工具函数
        // ============================================================

        function downloadImage(url) {
            const a = document.createElement('a');
            a.href = url;
            a.download = `domi-${Date.now()}.jpg`;
            a.click();
            showToast('下载已开始', 'fa-download');
        }

        function copyImageUrl(url) {
            navigator.clipboard.writeText(url).then(() => {
                showToast('链接已复制', 'fa-link');
            }).catch(() => {
                showToast('复制失败', 'fa-triangle-exclamation');
            });
        }

        function cancelTask() {
            if (pollingInterval) {
                clearInterval(pollingInterval);
                pollingInterval = null;
            }
            document.getElementById('resultArea').classList.add('hidden');
            showToast('已取消', 'fa-ban');
        }

        let toastTimer = null;

        function showToast(msg, icon = 'fa-check-circle') {
            const toast = document.getElementById('toast');
            document.getElementById('toastIcon').className = `fas ${icon}`;
            document.getElementById('toastMsg').textContent = msg;
            toast.classList.add('show');
            clearTimeout(toastTimer);
            toastTimer = setTimeout(() => toast.classList.remove('show'), 2800);
        }

        // ============================================================
        //  初始化
        // ============================================================

        window.onload = () => {
            renderRatios();
            renderPromptLibrary();
            renderHistory();
            console.log('%c✦ 多米AI 图像工坊 ✦', 'color:#6366f1;font-size:18px;font-weight:600;');
            console.log('%cAPI Key 已本地持久化,刷新无需重新输入', 'color:#22d3ee;font-size:12px;');
        };
    </script>

</body>
</html>

免责声明:

本站提供的资源,都来自网络,版权争议与本站无关,所有内容及软件的文章仅限用于学习和研究目的。不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负,我们不保证内容的长久可用性,通过使用本站内容随之而来的风险与本站无关,您必须在下载后的24个小时之内,从您的电脑/手机中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。侵删请致信E-mail: kuwanw@qq.com

同类推荐
评论列表
签到