AI图片生成器:前端实现与后端API对接详解

发布于:2025-08-17 ⋅ 阅读:(16) ⋅ 点赞:(0)

AI图片生成器:前端实现与后端API对接详解

本文将详细介绍一个完整的AI图片生成器项目,包含美观的前端界面和Spring Boot后端API实现。通过这个项目,您可以学习如何构建一个功能完整的AI图像生成应用。
在这里插入图片描述

项目概述

该AI图片生成器具有以下核心功能:

  • 选择不同的AI模型进行图片生成
  • 自定义图片尺寸、提示词、负向提示词等参数
  • 实时显示生成进度和状态
  • 支持暗夜/明亮模式切换
  • 图片放大查看和下载功能

前端实现详解

1. 界面布局与主题切换

<!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>
    <style>
        :root {
            --bg-color: #f5f7fa;
            --text-color: #333;
            --panel-bg: #ffffff;
            --border-color: #e1e4e8;
            --primary-color: #4a6bdf;
            --secondary-color: #f0f2f5;
            --button-hover: #3a5bd9;
            --input-bg: #ffffff;
            --progress-bg: #e0e0e0;
            --image-bg: #f0f0f0;
            --box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            --transition: all 0.3s ease;
        }

        .dark-mode {
            --bg-color: #1a1a1a;
            --text-color: #e0e0e0;
            --panel-bg: #2d2d2d;
            --border-color: #444;
            --primary-color: #5d7eff;
            --secondary-color: #3a3a3a;
            --button-hover: #4a6bdf;
            --input-bg: #3a3a3a;
            --progress-bg: #444;
            --image-bg: #2a2a2a;
            --box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
        }

        /* 其他样式省略... */
    </style>
</head>
<body>
<div class="container">
    <header>
        <h1>AI 图片生成器</h1>
        <button class="theme-toggle" id="themeToggle">
            <span id="themeIcon">🌙</span> <span id="themeText">暗夜模式</span>
        </button>
    </header>

    <div class="main-content">
        <!-- 参数面板 -->
        <div class="panel params-panel">
            <!-- 参数设置区域 -->
        </div>
        
        <!-- 预览面板 -->
        <div class="panel preview-panel">
            <!-- 图片预览区域 -->
        </div>
    </div>
</div>

2. 参数设置区域

<div class="panel params-panel">
    <h2 class="panel-title">生成参数</h2>
    <div class="action-buttons">
        <button id="generateBtn">生成图片</button>
        <button id="downloadBtn" class="secondary-button" disabled>下载图片</button>
    </div>
    <div class="status-area" id="statusArea">就绪</div>
    <div class="time-info" id="timeInfo"></div>

    <div class="form-group">
        <label for="model">模型选择</label>
        <select id="model">
            <option value="Qwen/Qwen-Image">Qwen/Qwen-Image</option>
            <option value="Liudef/XB_F.1_MIX">Liudef/XB_F.1_MIX</option>
            <option value="Liudef/XB_PONY">Liudef/XB_PONY</option>
            <option value="Liudef/XB_Illustrious">Liudef/XB_Illustrious</option>
        </select>
    </div>

    <div class="form-group">
        <label for="prompt">正向提示词</label>
        <textarea id="prompt" placeholder="请输入详细的描述词以获得更好的效果...">一只棕色的猫</textarea>
    </div>

    <div class="form-group">
        <label for="negativePrompt">负向提示词 (可选)</label>
        <textarea id="negativePrompt" placeholder="请输入不希望出现在图片中的内容...">lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry</textarea>
    </div>

    <div class="advanced-params">
        <h3 class="panel-title">高级参数</h3>
        <div class="param-row">
            <div class="param-item">
                <label for="width">宽度</label>
                <input type="number" id="width" min="64" max="2048" step="64" value="1024">
            </div>
            <div class="param-item">
                <label for="height">高度</label>
                <input type="number" id="height" min="64" max="2048" step="64" value="1024">
            </div>
        </div>
        <div class="param-row">
            <div class="param-item">
                <label for="steps">采样步数</label>
                <input type="number" id="steps" min="1" max="100" value="30">
            </div>
            <div class="param-item">
                <label for="guidance">引导系数</label>
                <input type="number" id="guidance" min="1.5" max="20" step="0.1" value="3.5">
            </div>
        </div>
    </div>

    <div class="progress-container">
        <progress id="progressBar" value="0" max="100" style="display: none;"></progress>
        <div class="loader" id="loader"></div>
    </div>
</div>

3. 图片预览区域

<div class="panel preview-panel">
    <h2 class="panel-title">图片预览</h2>
    <div class="preview-container" id="previewContainer">
        <img id="previewImage" class="preview-image" alt="生成的图片">
        <div class="preview-placeholder" id="previewPlaceholder">
            <p>图片将在此处显示</p>
            <p>点击生成的图片可以放大查看</p>
        </div>
    </div>
</div>

<!-- 图片放大模态框 -->
<div class="modal" id="imageModal">
    <span class="close-modal" id="closeModal">&times;</span>
    <img class="modal-content" id="modalImage">
</div>

4. JavaScript功能实现

// 主题切换
const themeToggle = document.getElementById('themeToggle');
const themeIcon = document.getElementById('themeIcon');
const themeText = document.getElementById('themeText');

// 检查本地存储中的主题偏好
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
    document.body.classList.add('dark-mode');
    themeIcon.textContent = '☀️';
    themeText.textContent = '明亮模式';
}

themeToggle.addEventListener('click', () => {
    document.body.classList.toggle('dark-mode');

    if (document.body.classList.contains('dark-mode')) {
        localStorage.setItem('theme', 'dark');
        themeIcon.textContent = '☀️';
        themeText.textContent = '明亮模式';
    } else {
        localStorage.setItem('theme', 'light');
        themeIcon.textContent = '🌙';
        themeText.textContent = '暗夜模式';
    }
});

// DOM 元素
const generateBtn = document.getElementById('generateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const progressBar = document.getElementById('progressBar');
const statusArea = document.getElementById('statusArea');
const timeInfo = document.getElementById('timeInfo');
const previewImage = document.getElementById('previewImage');
const previewPlaceholder = document.getElementById('previewPlaceholder');
const previewContainer = document.getElementById('previewContainer');
const loader = document.getElementById('loader');

// 图片放大相关元素
const imageModal = document.getElementById('imageModal');
const modalImage = document.getElementById('modalImage');
const closeModal = document.getElementById('closeModal');

// 图片放大功能
previewContainer.addEventListener('click', () => {
    if (!previewImage.src) return;

    modalImage.src = previewImage.src;
    imageModal.classList.add('show');
});

closeModal.addEventListener('click', () => {
    imageModal.classList.remove('show');
});

imageModal.addEventListener('click', (e) => {
    if (e.target === imageModal) {
        imageModal.classList.remove('show');
    }
});

// 状态变量
let isGenerating = false;
let startTime = null;
let currentImageUrl = null;
let currentTaskId = null;

// 更新状态
function updateStatus(message, isError = false) {
    statusArea.textContent = message;
    statusArea.style.color = isError ? '#ff4d4f' : 'inherit';
    statusArea.style.borderLeftColor = isError ? '#ff4d4f' : 'var(--primary-color)';
}

// 更新计时器
function updateTimer() {
    if (!isGenerating || !startTime) return;

    const elapsed = Math.floor((Date.now() - startTime) / 1000);
    const minutes = Math.floor(elapsed / 60).toString().padStart(2, '0');
    const seconds = (elapsed % 60).toString().padStart(2, '0');

    timeInfo.textContent = `生成耗时: ${minutes}:${seconds}`;

    if (isGenerating) {
        requestAnimationFrame(updateTimer);
    }
}

// 显示图片
function showImage(imageUrl) {
    previewPlaceholder.style.display = 'none';
    previewImage.style.display = 'block';
    previewImage.src = imageUrl;
    currentImageUrl = imageUrl;
    downloadBtn.disabled = false;

    // 添加图片加载动画
    previewImage.style.opacity = '0';
    setTimeout(() => {
        previewImage.style.opacity = '1';
    }, 10);
}

// 下载图片
downloadBtn.addEventListener('click', () => {
    if (!currentImageUrl) return;

    const link = document.createElement('a');
    link.href = currentImageUrl;
    link.download = `ai_image_${new Date().toISOString().slice(0, 10)}.jpg`;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
});

// 生成图片
generateBtn.addEventListener('click', async () => {
    if (isGenerating) {
        updateStatus('当前已有任务正在运行', true);
        return;
    }

    const model = document.getElementById('model').value;
    const prompt = document.getElementById('prompt').value.trim();
    const negativePrompt = document.getElementById('negativePrompt').value.trim();
    const width = parseInt(document.getElementById('width').value);
    const height = parseInt(document.getElementById('height').value);
    const steps = parseInt(document.getElementById('steps').value);
    const guidance = parseFloat(document.getElementById('guidance').value);

    // 验证输入
    if (!prompt) {
        updateStatus('请输入提示词', true);
        return;
    }

    if (width < 64 || width > 2048 || height < 64 || height > 2048) {
        updateStatus('图像尺寸必须在64-2048像素之间', true);
        return;
    }

    if (steps < 1 || steps > 100) {
        updateStatus('采样步数必须在1-100之间', true);
        return;
    }

    if (guidance < 1.5 || guidance > 20) {
        updateStatus('引导系数必须在1.5-20.0之间', true);
        return;
    }

    // 开始生成
    isGenerating = true;
    startTime = Date.now();
    generateBtn.disabled = true;
    progressBar.style.display = 'none';
    loader.style.display = 'block';
    updateStatus('提交任务...');
    updateTimer();

    try {
        // 提交任务到后端代理
        const response = await fetch('/api/modelScope/generate', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                model: model,
                prompt: prompt,
                negativePrompt: negativePrompt || undefined,
                width: width,
                height: height,
                steps: steps,
                guidance: guidance
            })
        });

        if (!response.ok) {
            throw new Error(`API 错误: ${response.status} ${response.statusText}`);
        }

        const data = await response.json();

        if (data.error) {
            throw new Error(data.error);
        }

        currentTaskId = data.task_id;
        updateStatus(`任务已提交 (ID: ${currentTaskId})`);

        // 开始轮询任务状态
        checkTaskStatus(currentTaskId);

    } catch (error) {
        handleGenerationError(error);
    }
});

// 轮询任务状态
async function checkTaskStatus(taskId) {
    try {
        const response = await fetch(`/api/modelScope/checkStatus?taskId=${taskId}`);

        if (!response.ok) {
            throw new Error(`状态检查失败: ${response.status} ${response.statusText}`);
        }

        const data = await response.json();

        if (data.error) {
            throw new Error(data.error);
        }

        const status = data.task_status;

        // 更新进度和状态
        switch (status) {
            case 'PENDING':
                updateStatus('任务排队中...');
                progressBar.value = 10;
                break;
            case 'PROCESSING':
                updateStatus('图片生成中...');
                progressBar.value = 30;
                break;
            case 'SUCCEED':
                progressBar.value = 100;
                updateStatus('生成成功!');

                // 获取图片
                const imageUrl = data.output_images[0];
                showImage(imageUrl);

                // 计算总耗时
                const totalTime = Math.floor((Date.now() - startTime) / 1000);
                const minutes = Math.floor(totalTime / 60);
                const seconds = totalTime % 60;
                timeInfo.textContent = `总耗时: ${minutes}${seconds}`;

                // 完成
                finishGeneration();
                return;
            case 'FAILED':
                throw new Error('图片生成失败: ' + (data.message || '未知错误'));
            default:
                // 继续轮询
                break;
        }

        // 如果任务未完成,继续轮询
        setTimeout(() => checkTaskStatus(taskId), 3000);

    } catch (error) {
        handleGenerationError(error);
    }
}

// 处理生成错误
function handleGenerationError(error) {
    updateStatus(`错误: ${error.message}`, true);

    // 计算总耗时
    if (startTime) {
        const totalTime = Math.floor((Date.now() - startTime) / 1000);
        const minutes = Math.floor(totalTime / 60);
        const seconds = totalTime % 60;
        timeInfo.textContent = `总耗时: ${minutes}${seconds}`;
    }

    finishGeneration();
}

// 完成生成
function finishGeneration() {
    isGenerating = false;
    generateBtn.disabled = false;
    progressBar.style.display = 'none';
    loader.style.display = 'none';
    currentTaskId = null;
}

后端API实现(Spring Boot)

package com.ai.controller;

import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api/modelScope/")
public class AiModelScopeController {

    // 配置您的API密钥
    private static final String MODEL_SCOPE_API_KEY = "从平台获取key";
    private static final String BASE_URL = "https://api-inference.modelscope.cn/";

    @PostMapping("/generate")
    public ResponseEntity<?> generateImage(@RequestBody Map<String, Object> requestData) {
        try {
            // 构建请求体
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("model", requestData.get("model"));
            requestBody.put("prompt", requestData.get("prompt"));
            requestBody.put("negative_prompt", requestData.get("negativePrompt"));
            requestBody.put("size", requestData.get("width") + "x" + requestData.get("height"));
            requestBody.put("steps", requestData.get("steps"));
            requestBody.put("guidance", requestData.get("guidance"));

            // 设置请求头
            HttpHeaders headers = new HttpHeaders();
            headers.set("Authorization", "Bearer " + MODEL_SCOPE_API_KEY);
            headers.set("Content-Type", "application/json");
            headers.set("X-ModelScope-Async-Mode", "true");

            // 发送请求
            RestTemplate restTemplate = new RestTemplate();
            HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);
            ResponseEntity<Map> response = restTemplate.postForEntity(
                    BASE_URL + "v1/images/generations",
                    requestEntity,
                    Map.class);

            return ResponseEntity.ok(response.getBody());
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(Map.of("error", e.getMessage()));
        }
    }

    @GetMapping("/checkStatus")
    public ResponseEntity<?> checkStatus(@RequestParam String taskId) {
        try {
            // 设置请求头
            HttpHeaders headers = new HttpHeaders();
            headers.set("Authorization", "Bearer " + MODEL_SCOPE_API_KEY);
            headers.set("X-ModelScope-Task-Type", "image_generation");

            // 发送请求
            RestTemplate restTemplate = new RestTemplate();
            HttpEntity<String> requestEntity = new HttpEntity<>(headers);
            ResponseEntity<Map> response = restTemplate.exchange(
                    BASE_URL + "v1/tasks/" + taskId,
                    HttpMethod.GET,
                    requestEntity,
                    Map.class);

            return ResponseEntity.ok(response.getBody());
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(Map.of("error", e.getMessage()));
        }
    }
}

项目部署指南

  1. 前端部署

    • 将HTML文件部署在任何静态Web服务器上(如Nginx、Apache)
    • 或者直接在浏览器中打开HTML文件
  2. 后端部署

    • 使用Maven或Gradle构建Spring Boot项目
    • application.properties中添加配置:
      server.port=8080
      
    • 运行Spring Boot应用
  3. API密钥配置

    • 从ModelScope平台获取API密钥
    • 替换后端代码中的MODEL_SCOPE_API_KEY

总结

本文详细介绍了一个完整的AI图片生成器实现方案,包括:

  1. 前端界面

    • 响应式设计,支持移动设备
    • 暗夜/明亮模式切换
    • 参数设置面板
    • 图片预览与放大功能
    • 生成状态和进度显示
  2. 后端API

    • 图片生成任务提交接口
    • 任务状态查询接口
    • 与ModelScope API的对接
  3. 核心功能

    • 多模型支持
    • 自定义图片尺寸和生成参数
    • 进度实时显示
    • 图片下载功能

该项目的完整代码可以直接用于实际开发,只需替换后端的API密钥即可与ModelScope平台对接。项目结构清晰,代码可读性强,适合作为AI应用开发的入门项目。

官方api地址
模型下载地址
官网地址


网站公告

今日签到

点亮在社区的每一天
去签到