From 50e35d42c087e4ee95f441601f71c0697e2cceb8 Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 29 Sep 2026 18:09:39 +0800 Subject: [PATCH] =?UTF-8?q?AI=20=E7=94=9F=E6=88=90:=20=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E5=9F=BA=E4=BA=8EFlask=E7=9A=84AI=E4=B8=80=E5=8F=A5=E8=AF=9D?= =?UTF-8?q?=E7=94=9F=E6=88=90=E5=9B=BE=E7=89=87=E3=80=81=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E3=80=81Logo=E7=9A=84Web=E5=BA=94=E7=94=A8=EF=BC=8C=E4=BD=BF?= =?UTF-8?q?=E7=94=A8Pollinations=20AI=E5=85=8D=E8=B4=B9=E5=9B=BE=E5=83=8F?= =?UTF-8?q?=E7=94=9F=E6=88=90API=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 93 ++++++++++++++++++++ app.py | 105 ++++++++++++++++++++++ requirements.txt | 2 + static/script.js | 78 ++++++++++++++++ static/style.css | 205 +++++++++++++++++++++++++++++++++++++++++++ templates/index.html | 81 +++++++++++++++++ 6 files changed, 564 insertions(+) create mode 100644 README.md create mode 100644 app.py create mode 100644 requirements.txt create mode 100644 static/script.js create mode 100644 static/style.css create mode 100644 templates/index.html diff --git a/README.md b/README.md new file mode 100644 index 0000000..f101a45 --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +# AI Image Generator + +一个基于 Flask 的 AI 图片生成 Web 应用。输入一句话描述,即可生成图片、图标或 Logo。 + +## 功能特性 + +- 🎨 一句话生成图片 +- 🖼️ 支持多种图片类型(图片 / 图标 / Logo) +- 📐 支持多种尺寸(正方形、横向、纵向) +- 🎭 支持多种风格(写实、卡通、水彩、赛博朋克、极简等) +- 💾 一键下载生成的图片 +- 🚀 使用免费 API,无需 API Key + +## 技术栈 + +- 后端:Python 3.8+ / Flask +- 前端:HTML / CSS / JavaScript +- 图像生成:Pollinations AI(免费) + +## 目录结构 + +``` +ai-image-generator/ +├── app.py # Flask 主程序 +├── requirements.txt # 依赖列表 +├── README.md # 说明文档 +├── templates/ +│ └── index.html # 前端页面 +└── static/ + ├── style.css # 样式 + └── script.js # 前端逻辑 +``` + +## 快速开始 + +### 1. 安装依赖 + +```bash +pip install -r requirements.txt +``` + +### 2. 启动服务 + +```bash +python app.py +``` + +### 3. 打开浏览器 + +访问 http://127.0.0.1:5000 + +## 使用方法 + +1. 在输入框中输入描述,例如:`一只在星空下奔跑的机械猫` +2. 选择图片类型(图片 / 图标 / Logo) +3. 选择尺寸和风格 +4. 点击“生成图片”按钮 +5. 等待几秒后查看结果,可点击“下载图片”保存 + +## API 说明 + +### POST /api/generate + +请求体: + +```json +{ + "prompt": "描述文字", + "type": "image | icon | logo", + "size": "square | landscape | portrait", + "style": "realistic | cartoon | watercolor | cyberpunk | minimal" +} +``` + +响应: + +```json +{ + "success": true, + "image_url": "https://...", + "prompt": "完整提示词" +} +``` + +## 说明 + +- 本应用使用 Pollinations AI 的免费图像生成接口,无需注册或 API Key。 +- 生成速度取决于网络状况,通常 3~10 秒。 +- 如遇接口不稳定,可稍后重试。 + +## License + +MIT diff --git a/app.py b/app.py new file mode 100644 index 0000000..1dbc658 --- /dev/null +++ b/app.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +""" +AI 图片生成器 - Flask 后端 +使用 Pollinations AI 免费接口,一句话生成图片/图标/Logo +""" + +import urllib.parse + +import requests +from flask import Flask, jsonify, render_template, request + +app = Flask(__name__) + +# Pollinations AI 免费图像生成接口 +POLLINATIONS_URL = "https://image.pollinations.ai/prompt/{prompt}" + +# 图片类型对应的提示词前缀 +TYPE_PREFIX = { + "image": "a high quality image of", + "icon": "a clean flat icon of", + "logo": "a minimalist logo design of", +} + +# 风格对应的提示词后缀 +STYLE_SUFFIX = { + "realistic": "realistic, detailed, 4k, photographic", + "cartoon": "cartoon style, colorful, cute, flat illustration", + "watercolor": "watercolor painting, soft colors, artistic", + "cyberpunk": "cyberpunk style, neon lights, futuristic, sci-fi", + "minimal": "minimalist, clean, simple, flat design, white background", +} + +# 尺寸映射(宽 x 高) +SIZE_MAP = { + "square": (1024, 1024), + "landscape": (1280, 768), + "portrait": (768, 1280), +} + + +@app.route("/") +def index(): + """渲染首页""" + return render_template("index.html") + + +@app.route("/api/generate", methods=["POST"]) +def generate(): + """生成图片接口""" + data = request.get_json(silent=True) or {} + prompt = (data.get("prompt") or "").strip() + img_type = data.get("type", "image") + size = data.get("size", "square") + style = data.get("style", "realistic") + + # 参数校验 + if not prompt: + return jsonify({"success": False, "error": "请输入图片描述"}), 400 + + if img_type not in TYPE_PREFIX: + img_type = "image" + if size not in SIZE_MAP: + size = "square" + if style not in STYLE_SUFFIX: + style = "realistic" + + # 组装完整提示词 + full_prompt = f"{TYPE_PREFIX[img_type]} {prompt}, {STYLE_SUFFIX[style]}" + + width, height = SIZE_MAP[size] + encoded_prompt = urllib.parse.quote(full_prompt) + image_url = ( + f"{POLLINATIONS_URL.format(prompt=encoded_prompt)}" + f"?width={width}&height={height}&nologo=true&seed={_random_seed()}" + ) + + # 预检图片是否可访问(Pollinations 有时会延迟生成) + try: + head = requests.head(image_url, timeout=30, allow_redirects=True) + if head.status_code >= 400: + return jsonify({ + "success": False, + "error": f"图像服务返回异常状态:{head.status_code},请稍后重试", + }), 502 + except requests.RequestException as exc: + return jsonify({ + "success": False, + "error": f"无法连接图像服务:{exc}", + }), 502 + + return jsonify({ + "success": True, + "image_url": image_url, + "prompt": full_prompt, + }) + + +def _random_seed() -> int: + """生成随机种子,避免相同提示词返回相同图片""" + import random + return random.randint(1, 10_000_000) + + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000, debug=True) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..76d9083 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Flask==3.0.3 +requests==2.32.3 diff --git a/static/script.js b/static/script.js new file mode 100644 index 0000000..ea466fa --- /dev/null +++ b/static/script.js @@ -0,0 +1,78 @@ +// AI 图片生成器 - 前端逻辑 + +const promptEl = document.getElementById("prompt"); +const typeEl = document.getElementById("type"); +const sizeEl = document.getElementById("size"); +const styleEl = document.getElementById("style"); +const generateBtn = document.getElementById("generateBtn"); +const statusEl = document.getElementById("status"); +const placeholderEl = document.getElementById("placeholder"); +const resultImageEl = document.getElementById("resultImage"); +const actionsEl = document.getElementById("actions"); +const downloadBtn = document.getElementById("downloadBtn"); + +// 设置状态提示 +function setStatus(message, isError = false) { + statusEl.textContent = message || ""; + statusEl.classList.toggle("error", Boolean(isError)); +} + +// 显示加载中 +function setLoading(loading) { + generateBtn.disabled = loading; + generateBtn.textContent = loading ? "⏳ 生成中..." : "✨ 生成图片"; +} + +// 调用后端生成图片 +async function generateImage() { + const prompt = promptEl.value.trim(); + if (!prompt) { + setStatus("请先输入图片描述", true); + promptEl.focus(); + return; + } + + setLoading(true); + setStatus("正在生成,请稍候..."); + + try { + const response = await fetch("/api/generate", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + prompt: prompt, + type: typeEl.value, + size: sizeEl.value, + style: styleEl.value, + }), + }); + + const data = await response.json(); + + if (!response.ok || !data.success) { + throw new Error(data.error || "生成失败,请稍后重试"); + } + + // 显示结果图 + resultImageEl.src = data.image_url; + resultImageEl.classList.remove("hidden"); + placeholderEl.classList.add("hidden"); + actionsEl.classList.remove("hidden"); + downloadBtn.href = data.image_url; + setStatus("生成成功 ✅"); + } catch (err) { + setStatus(err.message || "网络错误,请稍后重试", true); + } finally { + setLoading(false); + } +} + +// 绑定事件 +generateBtn.addEventListener("click", generateImage); + +// 支持 Ctrl + Enter 快捷生成 +promptEl.addEventListener("keydown", (e) => { + if ((e.ctrlKey || e.metaKey) && e.key === "Enter") { + generateImage(); + } +}); diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..78a4e80 --- /dev/null +++ b/static/style.css @@ -0,0 +1,205 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +:root { + --bg: #0f1220; + --panel: #1a1f35; + --panel-2: #232a47; + --text: #e8ecff; + --muted: #9aa3c7; + --primary: #7c5cff; + --primary-hover: #6a4bee; + --border: #2c3457; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", + "Microsoft YaHei", sans-serif; + background: radial-gradient(circle at top, #1b2140 0%, var(--bg) 60%); + color: var(--text); + min-height: 100vh; + padding: 24px 16px; +} + +.app { + max-width: 1100px; + margin: 0 auto; +} + +.header { + text-align: center; + margin-bottom: 24px; +} + +.header h1 { + font-size: 28px; + margin-bottom: 6px; +} + +.subtitle { + color: var(--muted); + font-size: 14px; +} + +.main { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; +} + +@media (max-width: 860px) { + .main { + grid-template-columns: 1fr; + } +} + +.panel { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 16px; + padding: 20px; +} + +.field { + display: block; + margin-bottom: 14px; +} + +.field-label { + display: block; + font-size: 13px; + color: var(--muted); + margin-bottom: 6px; +} + +textarea, +select { + width: 100%; + background: var(--panel-2); + border: 1px solid var(--border); + border-radius: 10px; + color: var(--text); + padding: 10px 12px; + font-size: 14px; + font-family: inherit; + outline: none; + transition: border-color 0.2s; +} + +textarea:focus, +select:focus { + border-color: var(--primary); +} + +textarea { + resize: vertical; +} + +.row { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 10px; +} + +@media (max-width: 520px) { + .row { + grid-template-columns: 1fr; + } +} + +.btn-primary { + width: 100%; + background: var(--primary); + color: #fff; + border: none; + border-radius: 10px; + padding: 12px; + font-size: 15px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s, transform 0.1s; +} + +.btn-primary:hover:not(:disabled) { + background: var(--primary-hover); +} + +.btn-primary:active:not(:disabled) { + transform: scale(0.99); +} + +.btn-primary:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.btn-secondary { + display: inline-block; + background: var(--panel-2); + color: var(--text); + border: 1px solid var(--border); + border-radius: 10px; + padding: 10px 16px; + font-size: 14px; + text-decoration: none; + cursor: pointer; + transition: background 0.2s; +} + +.btn-secondary:hover { + background: #2c3560; +} + +.status { + margin-top: 10px; + font-size: 13px; + color: var(--muted); + min-height: 18px; +} + +.status.error { + color: #ff7b7b; +} + +.preview-panel { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 380px; +} + +.placeholder { + text-align: center; + color: var(--muted); +} + +.placeholder-icon { + font-size: 48px; + margin-bottom: 10px; +} + +.result-image { + max-width: 100%; + border-radius: 12px; + border: 1px solid var(--border); + display: block; +} + +.actions { + margin-top: 16px; +} + +.hidden { + display: none !important; +} + +.footer { + text-align: center; + color: var(--muted); + font-size: 12px; + margin-top: 24px; +} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..a0b6741 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,81 @@ + + + + + + AI 图片生成器 · 一句话生成图片 + + + +
+
+

🎨 AI 图片生成器

+

输入一句话,生成图片 / 图标 / Logo

+
+ +
+
+ + +
+ + + + + +
+ + +

+
+ +
+
+
🖼️
+

生成的图片会显示在这里

+
+ + +
+
+ +
+

Powered by Pollinations AI · 仅供学习交流

+
+
+ + + +