9b6295c7 by 柴进

nanobanana初版

0 parents
<!-- OPENSPEC:START -->
# OpenSpec Instructions
These instructions are for AI assistants working in this project.
Always open `@/openspec/AGENTS.md` when the request:
- Mentions planning or proposals (words like proposal, spec, change, plan)
- Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work
- Sounds ambiguous and you need the authoritative spec before coding
Use `@/openspec/AGENTS.md` to learn:
- How to create and apply change proposals
- Spec format and conventions
- Project structure and guidelines
Keep this managed block so 'openspec update' can refresh the instructions.
<!-- OPENSPEC:END -->
\ No newline at end of file
<!-- OPENSPEC:START -->
# OpenSpec Instructions
These instructions are for AI assistants working in this project.
Always open `@/openspec/AGENTS.md` when the request:
- Mentions planning or proposals (words like proposal, spec, change, plan)
- Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work
- Sounds ambiguous and you need the authoritative spec before coding
Use `@/openspec/AGENTS.md` to learn:
- How to create and apply change proposals
- Spec format and conventions
- Project structure and guidelines
Keep this managed block so 'openspec update' can refresh the instructions.
<!-- OPENSPEC:END -->
\ No newline at end of file
# Gemini Image Generator
简洁的跨平台桌面应用,使用 Google Gemini API 生成图片。
## 功能特性
- **用户认证**: 基于 MySQL 的登录系统,确保只有授权用户可访问
- 输入文本 Prompt 生成图片
- 支持上传多张参考图片
- 多种图片比例选择(1:1, 16:9, 4:3 等)
- 多种分辨率选择(1K, 2K, 4K)
- 支持两种模型:gemini-2.5-flash-image 和 gemini-3-pro-image-preview
- 实时预览生成的图片
- 一键下载生成的图片
- 跨平台支持(Windows 和 macOS)
## 系统要求
- Python 3.8 或更高版本
- 有效的 Google AI API 密钥
- MySQL 数据库访问权限(用于用户认证)
## 快速开始
### 开发模式运行
1. 安装依赖:
```bash
pip install -r requirements.txt
```
2. 配置数据库和 API 密钥:
- 编辑 `config.json` 文件,配置以下字段:
```json
{
"api_key": "你的Google AI API密钥",
"db_config": {
"host": "你的MySQL主机地址",
"port": 3306,
"user": "数据库用户名",
"password": "数据库密码",
"database": "数据库名",
"table": "nano_banana_users"
},
"last_user": ""
}
```
3. 创建数据库表:
```sql
CREATE TABLE `nano_banana_users` (
`user_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`passwd` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`status` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```
4. 添加初始用户:
```bash
python user_util.py add admin your_password
```
5. 运行应用:
```bash
python image_generator.py
```
首次运行会显示登录界面,使用创建的账户登录。
### 打包为可执行文件
#### Windows
双击运行 `build_windows.bat` 或在命令行执行:
```cmd
build_windows.bat
```
生成的可执行文件位于 `dist\GeminiImageGenerator.exe`
#### macOS
在终端执行:
```bash
chmod +x build_mac.sh
./build_mac.sh
```
生成的应用程序位于 `dist/GeminiImageGenerator.app`
## 配置文件位置
应用会根据运行模式自动选择配置文件位置:
**开发模式**(直接运行 Python):
- 配置文件:`./config.json`(当前目录)
**打包后的应用**:
- **macOS**: `~/Library/Application Support/ZB100ImageGenerator/config.json`
- **Windows**: `%APPDATA%\ZB100ImageGenerator\config.json`
- **Linux**: `~/.config/zb100imagegenerator/config.json`
首次运行打包应用时,会自动从打包的模板复制 API 密钥到用户目录。
## 用户管理
应用内置用户管理工具 `user_util.py`(仅管理员使用,不随客户端分发)。
### 用户管理命令
```bash
# 添加新用户
python user_util.py add <username> <password>
# 列出所有用户
python user_util.py list
# 禁用用户
python user_util.py disable <username>
# 启用用户
python user_util.py enable <username>
# 重置密码
python user_util.py reset <username> <new_password>
```
### 示例
```bash
# 添加管理员账户
python user_util.py add admin MySecurePass123
# 查看所有用户
python user_util.py list
# 禁用某个用户
python user_util.py disable testuser
# 重置用户密码
python user_util.py reset admin NewPassword456
```
### 安全说明
- 密码使用 SHA256 哈希存储,数据库和本地均不保存明文
- 所有数据库操作使用参数化查询,防止 SQL 注入
- user_util.py 工具仅供管理员使用,不应分发给普通用户
- 应用会记住上次登录的用户名(可选),但不会保存密码
## 使用说明
1. **登录应用**
- 应用启动时显示登录界面
- 输入用户名和密码
- 可勾选"记住用户名"选项,下次自动填充用户名
- 登录成功后进入主界面
2. **配置 API 密钥**
- 编辑配置文件中的 `api_key` 字段
- 或通过应用界面的收藏提示词功能自动保存
3. **上传参考图片(可选)**
- 点击 "添加图片" 按钮选择一张或多张参考图片
- 这些图片将作为生成图片的参考
- 可以单独删除每张图片
3. **输入 Prompt**
- 在提示词文本框中输入你想生成的图片描述
- 可以使用 "收藏" 功能保存常用提示词
- 从下拉菜单快速选择已保存的提示词
4. **选择生成参数**
- **宽高比**: 选择图片的宽高比(1:1, 16:9 等)
- **图片尺寸**: 选择图片的分辨率(1K/2K/4K)
- **AI 模型**: 选择使用的模型
5. **生成图片**
- 点击 "生成图片" 按钮
- 等待生成完成,生成的图片会显示在预览区域
- 双击预览图可用系统默认查看器打开
6. **下载图片**
- 点击 "下载图片" 按钮
- 选择保存位置和文件格式(PNG/JPEG)
## 项目结构
```
Nano_Banana_App/
├── image_generator.py # 主程序文件(含登录界面和数据库认证)
├── user_util.py # 用户管理工具(管理员专用)
├── requirements.txt # Python 依赖
├── config.json # 配置文件(API密钥+数据库配置)
├── build_windows.bat # Windows 打包脚本
├── build_mac.sh # macOS 打包脚本
└── README.md # 本文件
```
## 技术栈
- **GUI 框架**: Tkinter(Python 内置)
- **图片处理**: Pillow
- **API 客户端**: google-genai
- **数据库**: PyMySQL
- **打包工具**: PyInstaller
## 获取 API 密钥
访问 [Google AI Studio](https://makersuite.google.com/app/apikey) 获取免费的 API 密钥。
## 注意事项
- API 密钥会保存在 `config.json` 文件中,请妥善保管
- 使用 API 可能会产生费用,请查看 Google AI 的定价信息
- 生成高分辨率图片(4K)需要更多时间和 API 配额
## 故障排查
### 登录相关问题
**无法登录 / 数据库连接失败**
- 检查 `config.json` 中的 `db_config` 配置是否正确
- 确认数据库服务器可访问(检查防火墙/网络)
- 验证数据库用户名和密码是否正确
- 确认表 `nano_banana_users` 已创建
**"未找到数据库配置" 错误**
- 确保 `config.json` 包含 `db_config` 字段
- 参考快速开始章节的配置示例
**"用户名或密码错误" 提示**
- 使用 `python user_util.py list` 查看用户列表
- 确认用户状态为 'active'
- 使用 `user_util.py` 重置密码或创建新用户
**密码不匹配**
- 确保数据库中存储的是 SHA256 哈希值,而非明文密码
- 使用 `user_util.py add` 添加用户,会自动计算哈希
### 配置文件只读错误(macOS/Windows 打包应用)
**问题**: 提示 "read-only file system: config.json"
**原因**: 打包后的应用资源目录是只读的,无法在应用包内写入文件
**解决方案**:
- ✅ 已修复:应用现在会自动将配置保存到用户目录
- macOS: `~/Library/Application Support/ZB100ImageGenerator/config.json`
- Windows: `%APPDATA%\ZB100ImageGenerator\config.json`
- 首次运行会自动创建配置文件夹和文件
### 生成失败
- 检查 API 密钥是否正确
- 检查网络连接是否正常
- 查看错误信息,确认是否超出配额
### 找不到 API 密钥
- 开发模式:检查项目目录下的 `config.json` 文件
- 打包应用:检查用户目录下的配置文件(见上方配置文件位置)
- 手动创建配置文件并添加完整配置(参考快速开始章节)
### 打包失败
- 确保安装了所有依赖:`pip install -r requirements.txt`
- 检查 Python 版本是否符合要求(3.8+)
- Windows: 确保有 `zb100_kehuan.ico` 图标文件(或修改打包脚本移除 `--icon` 参数)
- 注意: `user_util.py` 不应打包进客户端版本(仅管理员使用)
## 技术设计原则
本项目遵循 Linus Torvalds 的设计哲学:
- **简洁至上**: 使用 Tkinter 内置 GUI,避免重型框架
- **零特殊情况**: 统一的错误处理和数据流
- **实用主义**: 直接解决问题,不过度设计
- **清晰数据结构**: 简单的配置管理和图片数据流
## 许可证
MIT License
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['image_generator.py'],
pathex=[],
binaries=[],
datas=[('config.json', '.')],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='ZB100ImageGenerator',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['zb100_kehuan.ico'],
)
#!/bin/bash
# macOS Build Script for Gemini Image Generator
echo "================================"
echo "Building Gemini Image Generator"
echo "================================"
# Check if virtual environment exists
if [ ! -d "venv" ]; then
echo "Creating virtual environment..."
python3 -m venv venv
fi
# Activate virtual environment
echo "Activating virtual environment..."
source venv/bin/activate
# Install dependencies
echo "Installing dependencies..."
pip install --upgrade pip
pip install -r requirements.txt
# Clean previous builds
echo "Cleaning previous builds..."
rm -rf build dist *.spec
# Build executable
echo "Building executable..."
pyinstaller --name="ZB100ImageGenerator" \
--onefile \
--windowed \
--add-data "config.json:." \
image_generator_qt.py
# Check if build was successful
if [ -f "dist/ZB100ImageGenerator.app/Contents/MacOS/ZB100ImageGenerator" ] || [ -f "dist/ZB100ImageGenerator" ]; then
echo "================================"
echo "Build successful!"
echo "Application: dist/ZB100ImageGenerator.app (or dist/ZB100ImageGenerator)"
echo "================================"
else
echo "================================"
echo "Build failed!"
echo "================================"
fi
@echo off
REM Windows Build Script for Gemini Image Generator
echo ================================
echo Building Gemini Image Generator
echo ================================
REM Check if virtual environment exists
if not exist "venv" (
echo Creating virtual environment...
python -m venv venv
)
REM Activate virtual environment
echo Activating virtual environment...
call venv\Scripts\activate.bat
REM Install dependencies
echo Installing dependencies...
pip install --upgrade pip
pip install -r requirements.txt
REM Clean previous builds
echo Cleaning previous builds...
if exist "build" rd /s /q build
if exist "dist" rd /s /q dist
if exist "*.spec" del /q *.spec
REM Build executable
echo Building executable...
pyinstaller --name="ZB100ImageGenerator" ^
--onefile ^
--windowed ^
--icon=zb100_kehuan.ico ^
--add-data "config.json;." ^
image_generator_qt.py
REM Check if build was successful
if exist "dist\ZB100ImageGenerator.exe" (
echo ================================
echo Build successful!
echo Executable: dist\ZB100ImageGenerator.exe
echo ================================
) else (
echo ================================
echo Build failed!
echo ================================
)
pause
@echo off
REM 导出 Windows 环境的精确依赖版本
echo 正在导出依赖版本...
pip freeze | findstr /C:"google-genai" /C:"Pillow" /C:"PyQt5" /C:"pyinstaller" > requirements-lock.txt
echo.
echo 已导出到 requirements-lock.txt
echo 请将此文件复制到 Mac 上使用
echo.
type requirements-lock.txt
pause
# Qt GUI 版本依赖
# 核心依赖
google-genai>=1.0.0,<2.0.0
Pillow>=10.0.0,<11.0.0
PyQt5>=5.15.0,<6.0.0
# 数据库依赖
pymysql>=1.0.0,<2.0.0
# 打包工具
pyinstaller>=6.0.0,<7.0.0
#!/bin/bash
# Mac 环境设置脚本
echo "================================"
echo "Mac 环境设置 - Qt 版本"
echo "================================"
# 检查 Python 版本
python_version=$(python3 --version 2>&1)
echo "Python 版本: $python_version"
# 创建虚拟环境
if [ ! -d "venv" ]; then
echo "创建虚拟环境..."
python3 -m venv venv
fi
# 激活虚拟环境
echo "激活虚拟环境..."
source venv/bin/activate
# 升级 pip
echo "升级 pip..."
pip install --upgrade pip
# 卸载可能冲突的旧版本
echo "清理旧版本..."
pip uninstall -y google-genai Pillow PyQt5 pyinstaller 2>/dev/null
# 安装依赖
echo "================================"
echo "安装依赖..."
echo "================================"
# 如果有锁定版本文件,优先使用
if [ -f "requirements-lock.txt" ]; then
echo "使用锁定版本 (requirements-lock.txt)..."
pip install -r requirements-lock.txt
else
echo "使用范围版本 (requirements.txt)..."
pip install -r requirements.txt
fi
# 验证安装
echo "================================"
echo "验证安装..."
echo "================================"
pip list | grep -E "google-genai|Pillow|PyQt5|pyinstaller"
echo "================================"
echo "设置完成!"
echo "================================"
echo ""
echo "运行应用: python3 image_generator_qt.py"
echo ""
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
用户管理工具 - 管理员专用
用法:
python user_util.py add <username> <password> # 添加用户
python user_util.py list # 列出用户
python user_util.py disable <username> # 禁用用户
python user_util.py enable <username> # 启用用户
python user_util.py reset <username> <password> # 重置密码
安全提示:
- 此工具仅供管理员使用,请勿分发给用户
- 避免在命令行直接输入密码(可被 shell 历史记录)
- 建议使用环境变量或交互式输入密码
"""
import hashlib
import pymysql
import json
import sys
import os
from pathlib import Path
# Windows 控制台编码修复
if sys.platform == 'win32':
import codecs
sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer, 'strict')
sys.stderr = codecs.getwriter('utf-8')(sys.stderr.buffer, 'strict')
def hash_password(password: str) -> str:
"""使用 SHA256 哈希密码"""
return hashlib.sha256(password.encode('utf-8')).hexdigest()
class UserManager:
"""用户管理类"""
def __init__(self, db_config):
self.config = db_config
def add_user(self, username, password):
"""添加新用户"""
hashed = hash_password(password)
conn = None
try:
conn = pymysql.connect(
host=self.config['host'],
port=self.config.get('port', 3306),
user=self.config['user'],
password=self.config['password'],
database=self.config['database'],
connect_timeout=5
)
with conn.cursor() as cursor:
sql = f"INSERT INTO {self.config['table']} (user_name, passwd, status) VALUES (%s, %s, %s)"
cursor.execute(sql, (username, hashed, 'active'))
conn.commit()
print(f"✓ 用户 '{username}' 添加成功")
print(f" 密码哈希: {hashed[:16]}...")
except pymysql.IntegrityError:
print(f"✗ 用户 '{username}' 已存在")
except pymysql.OperationalError as e:
print(f"✗ 数据库连接失败: {e}")
except Exception as e:
print(f"✗ 添加用户失败: {e}")
finally:
if conn:
conn.close()
def list_users(self):
"""列出所有用户"""
conn = None
try:
conn = pymysql.connect(
host=self.config['host'],
port=self.config.get('port', 3306),
user=self.config['user'],
password=self.config['password'],
database=self.config['database'],
connect_timeout=5
)
with conn.cursor() as cursor:
sql = f"SELECT user_name, passwd, status FROM {self.config['table']}"
cursor.execute(sql)
results = cursor.fetchall()
if not results:
print("没有找到任何用户")
return
# 打印表格
print("\n用户列表:")
print(" ┌────────────────────────┬──────────┬──────────────────┐")
print(" │ 用户名 │ 状态 │ 密码哈希(前8位) │")
print(" ├────────────────────────┼──────────┼──────────────────┤")
for row in results:
username = row[0] or ""
passwd_hash = row[1] or ""
status = row[2] or "NULL"
# 填充空格使对齐
username_display = username[:20].ljust(20)
status_display = status[:8].ljust(8)
hash_display = passwd_hash[:16] if passwd_hash else "N/A".ljust(16)
print(f" │ {username_display} │ {status_display} │ {hash_display} │")
print(" └────────────────────────┴──────────┴──────────────────┘")
print(f"\n总计: {len(results)} 个用户\n")
except pymysql.OperationalError as e:
print(f"✗ 数据库连接失败: {e}")
except Exception as e:
print(f"✗ 查询用户失败: {e}")
finally:
if conn:
conn.close()
def disable_user(self, username):
"""禁用用户"""
self._update_user_status(username, 'disabled', "已禁用")
def enable_user(self, username):
"""启用用户"""
self._update_user_status(username, 'active', "已启用")
def _update_user_status(self, username, status, action_name):
"""更新用户状态"""
conn = None
try:
conn = pymysql.connect(
host=self.config['host'],
port=self.config.get('port', 3306),
user=self.config['user'],
password=self.config['password'],
database=self.config['database'],
connect_timeout=5
)
with conn.cursor() as cursor:
sql = f"UPDATE {self.config['table']} SET status=%s WHERE user_name=%s"
affected = cursor.execute(sql, (status, username))
if affected > 0:
conn.commit()
print(f"✓ 用户 '{username}' {action_name}")
else:
print(f"✗ 用户 '{username}' 不存在")
except pymysql.OperationalError as e:
print(f"✗ 数据库连接失败: {e}")
except Exception as e:
print(f"✗ 更新用户状态失败: {e}")
finally:
if conn:
conn.close()
def reset_password(self, username, new_password):
"""重置用户密码"""
hashed = hash_password(new_password)
conn = None
try:
conn = pymysql.connect(
host=self.config['host'],
port=self.config.get('port', 3306),
user=self.config['user'],
password=self.config['password'],
database=self.config['database'],
connect_timeout=5
)
with conn.cursor() as cursor:
sql = f"UPDATE {self.config['table']} SET passwd=%s WHERE user_name=%s"
affected = cursor.execute(sql, (hashed, username))
if affected > 0:
conn.commit()
print(f"✓ 用户 '{username}' 密码已重置")
print(f" 新密码哈希: {hashed[:16]}...")
else:
print(f"✗ 用户 '{username}' 不存在")
except pymysql.OperationalError as e:
print(f"✗ 数据库连接失败: {e}")
except Exception as e:
print(f"✗ 重置密码失败: {e}")
finally:
if conn:
conn.close()
def load_db_config():
"""从 config.json 加载数据库配置"""
config_path = Path('config.json')
if not config_path.exists():
print(f"✗ 配置文件不存在: {config_path}")
print(f" 请确保在项目目录下运行此工具")
return None
try:
with open(config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
db_config = config.get('db_config')
if not db_config:
print("✗ 配置文件中未找到 db_config 字段")
return None
required_fields = ['host', 'user', 'password', 'database', 'table']
missing = [f for f in required_fields if f not in db_config]
if missing:
print(f"✗ db_config 缺少必需字段: {', '.join(missing)}")
return None
return db_config
except json.JSONDecodeError:
print(f"✗ 配置文件格式错误: {config_path}")
return None
except Exception as e:
print(f"✗ 加载配置失败: {e}")
return None
def print_help():
"""打印帮助信息"""
print(__doc__)
def main():
"""主函数"""
if len(sys.argv) < 2:
print_help()
sys.exit(1)
command = sys.argv[1].lower()
# 加载数据库配置
db_config = load_db_config()
if not db_config:
sys.exit(1)
manager = UserManager(db_config)
# 处理命令
if command == 'add':
if len(sys.argv) != 4:
print("用法: python user_util.py add <username> <password>")
sys.exit(1)
username = sys.argv[2]
password = sys.argv[3]
manager.add_user(username, password)
elif command == 'list':
manager.list_users()
elif command == 'disable':
if len(sys.argv) != 3:
print("用法: python user_util.py disable <username>")
sys.exit(1)
username = sys.argv[2]
manager.disable_user(username)
elif command == 'enable':
if len(sys.argv) != 3:
print("用法: python user_util.py enable <username>")
sys.exit(1)
username = sys.argv[2]
manager.enable_user(username)
elif command == 'reset':
if len(sys.argv) != 4:
print("用法: python user_util.py reset <username> <password>")
sys.exit(1)
username = sys.argv[2]
new_password = sys.argv[3]
manager.reset_password(username, new_password)
else:
print(f"✗ 未知命令: {command}")
print_help()
sys.exit(1)
if __name__ == '__main__':
main()
No preview for this file type