Spaces:
Sleeping
Sleeping
Upload 13 files (#12)
Browse files- Upload 13 files (ca29530d9fe01f4b3bd43d8e66bdf1b7398c2e83)
- README.md +151 -10
- data/public-gallery.json +23 -1
- index.html +19 -3
- mock-api.js +93 -0
- server.js +41 -3
- start.sh +50 -0
- test-system.sh +42 -0
README.md
CHANGED
|
@@ -1,10 +1,151 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Banana Pro AI - 创意画板
|
| 2 |
+
|
| 3 |
+
一个基于 Node.js + Express 的 AI 图片生成平台,支持多图上传、社区画廊分享等功能。
|
| 4 |
+
|
| 5 |
+
## 🚀 快速开始
|
| 6 |
+
|
| 7 |
+
### 方法1: 使用启动脚本(推荐)
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
./start.sh
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
这会同时启动主应用和模拟API服务器。
|
| 14 |
+
|
| 15 |
+
### 方法2: 手动启动
|
| 16 |
+
|
| 17 |
+
1. 启动模拟API服务器:
|
| 18 |
+
```bash
|
| 19 |
+
node mock-api.js
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
2. 在新终端启动主应用:
|
| 23 |
+
```bash
|
| 24 |
+
OPENAI_API_URL=http://127.0.0.1:8000/v1/chat/completions node server.js
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
3. 访问 http://localhost:3000
|
| 28 |
+
|
| 29 |
+
## 🔐 登录信息
|
| 30 |
+
|
| 31 |
+
- **密码**: `123456`
|
| 32 |
+
|
| 33 |
+
## 🎨 功能特性
|
| 34 |
+
|
| 35 |
+
- ✅ **AI图片生成**: 支持文本提示词生成图片
|
| 36 |
+
- ✅ **多图上传**: 可上传最多16张参考图片
|
| 37 |
+
- ✅ **拖拽上传**: 支持拖拽文件到输入区域
|
| 38 |
+
- ✅ **个人画廊**: 本地存储历史作品
|
| 39 |
+
- ✅ **社区画廊**: 分享作品到公共画廊
|
| 40 |
+
- ✅ **响应式设计**: 支持移动端和桌面端
|
| 41 |
+
- ✅ **图片下载**: 支持下载生成的图片
|
| 42 |
+
- ✅ **参数复用**: 可复用历史作品的参数和图片
|
| 43 |
+
|
| 44 |
+
## 📁 项目结构
|
| 45 |
+
|
| 46 |
+
```
|
| 47 |
+
banana-pro-ai/
|
| 48 |
+
├── index.html # 前端主页面
|
| 49 |
+
├── style.css # 样式文件
|
| 50 |
+
├── server.js # 后端服务器
|
| 51 |
+
├── mock-api.js # 模拟API服务器
|
| 52 |
+
├── start.sh # 启动脚本
|
| 53 |
+
├── .env # 环境配置
|
| 54 |
+
├── data/ # 数据目录
|
| 55 |
+
│ └── public-gallery.json # 公共画廊数据
|
| 56 |
+
└── package.json # 项目配置
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
## ⚙️ 配置说明
|
| 60 |
+
|
| 61 |
+
### 环境变量 (.env)
|
| 62 |
+
|
| 63 |
+
```env
|
| 64 |
+
# API Configuration
|
| 65 |
+
OPENAI_API_KEY=sk-123456
|
| 66 |
+
OPENAI_API_URL=http://127.0.0.1:8000/v1/chat/completions
|
| 67 |
+
MODEL_NAME=banana-pro
|
| 68 |
+
|
| 69 |
+
# Site Configuration
|
| 70 |
+
SITE_PASSWORD=123456
|
| 71 |
+
PORT=3000
|
| 72 |
+
|
| 73 |
+
# Gallery Configuration
|
| 74 |
+
PUBLIC_GALLERY_LIMIT=80
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
## 🛠️ 开发说明
|
| 78 |
+
|
| 79 |
+
### 模拟API
|
| 80 |
+
|
| 81 |
+
项目包含一个模拟API服务器 (`mock-api.js`),用于演示和测试:
|
| 82 |
+
|
| 83 |
+
- 生成彩色SVG图片作为响应
|
| 84 |
+
- 模拟2-5秒的处理延迟
|
| 85 |
+
- 兼容OpenAI API格式
|
| 86 |
+
|
| 87 |
+
### 真实API配置
|
| 88 |
+
|
| 89 |
+
要使用真实的图片生成API,请修改 `.env` 文件:
|
| 90 |
+
|
| 91 |
+
```env
|
| 92 |
+
OPENAI_API_KEY=your_actual_api_key
|
| 93 |
+
OPENAI_API_URL=https://your-api-provider.com/v1/chat/completions
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
### API响应格式
|
| 97 |
+
|
| 98 |
+
API应返回OpenAI兼容格式:
|
| 99 |
+
|
| 100 |
+
```json
|
| 101 |
+
{
|
| 102 |
+
"choices": [{
|
| 103 |
+
"message": {
|
| 104 |
+
"content": ""
|
| 105 |
+
}
|
| 106 |
+
}]
|
| 107 |
+
}
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
## 🐛 故障排除
|
| 111 |
+
|
| 112 |
+
### 常见问题
|
| 113 |
+
|
| 114 |
+
1. **生图失败: 无法从 API 响应中提取图片数据**
|
| 115 |
+
- 检查API地址配置是否正确
|
| 116 |
+
- 确认API密钥有效
|
| 117 |
+
- 查看服务器日志了解详细错误
|
| 118 |
+
|
| 119 |
+
2. **社区创意画廊加载很慢**
|
| 120 |
+
- 检查网络连接
|
| 121 |
+
- 查看浏览器控制台错误信息
|
| 122 |
+
- 确认服务器正常运行
|
| 123 |
+
|
| 124 |
+
3. **端口占用**
|
| 125 |
+
```bash
|
| 126 |
+
# 查看端口占用
|
| 127 |
+
lsof -i :3000
|
| 128 |
+
lsof -i :8000
|
| 129 |
+
|
| 130 |
+
# 停止相关进程
|
| 131 |
+
pkill -f "node server.js"
|
| 132 |
+
pkill -f "node mock-api.js"
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
## 📱 移动端优化
|
| 136 |
+
|
| 137 |
+
- 单列布局,便于触摸操作
|
| 138 |
+
- 减少动画复杂度,提升性能
|
| 139 |
+
- 图片自动压缩,减少网络负载
|
| 140 |
+
- 响应式设计,适配各种屏幕
|
| 141 |
+
|
| 142 |
+
## 🔄 版本历史
|
| 143 |
+
|
| 144 |
+
- v1.0.0: 初始版本,支持基础生图功能
|
| 145 |
+
- v1.1.0: 添加多图上传、社区画廊
|
| 146 |
+
- v1.2.0: 移动端优化、性能改进
|
| 147 |
+
- v1.3.0: 错误处理改进、模拟API
|
| 148 |
+
|
| 149 |
+
## 📄 许可证
|
| 150 |
+
|
| 151 |
+
MIT License
|
data/public-gallery.json
CHANGED
|
@@ -1 +1,23 @@
|
|
| 1 |
-
[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"id": "demo-1",
|
| 4 |
+
"prompt": "一只可爱的橙色小猫坐在阳光下的窗台上,背景是模糊的花园景色",
|
| 5 |
+
"image": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='300'%3E%3Crect fill='%23FFE4B5' width='400' height='300'/%3E%3Ccircle cx='200' cy='150' r='30' fill='%23FF8C00'/%3E%3Ccircle cx='190' cy='140' r='3' fill='%23000'/%3E%3Ccircle cx='210' cy='140' r='3' fill='%23000'/%3E%3Cpath d='M 190 160 Q 200 170 210 160' stroke='%23000' fill='none'/%3E%3Ctext x='200' y='250' text-anchor='middle' fill='%23666' font-family='Arial' font-size='14'%3E示例图片 1%3C/text%3E%3C/svg%3E",
|
| 6 |
+
"inputImages": [],
|
| 7 |
+
"timestamp": "2024-12-01T10:00:00.000Z"
|
| 8 |
+
},
|
| 9 |
+
{
|
| 10 |
+
"id": "demo-2",
|
| 11 |
+
"prompt": "未来科技城市的夜景,霓虹灯闪烁,飞行汽车在空中穿梭",
|
| 12 |
+
"image": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='300'%3E%3Crect fill='%23001a33' width='400' height='300'/%3E%3Crect x='50' y='100' width='30' height='150' fill='%23444'/%3E%3Crect x='120' y='50' width='40' height='200' fill='%23555'/%3E%3Crect x='200' y='80' width='35' height='170' fill='%23444'/%3E%3Crect x='280' y='60' width='45' height='190' fill='%23555'/%3E%3Ccircle cx='100' cy='50' r='3' fill='%23ff0'/%3E%3Ccircle cx='150' cy='30' r='2' fill='%23f0f'/%3E%3Ccircle cx='250' cy='40' r='3' fill='%230ff'/%3E%3Ccircle cx='320' cy='25' r='2' fill='%23ff0'/%3E%3Ctext x='200' y='250' text-anchor='middle' fill='%23666' font-family='Arial' font-size='14'%3E示例图片 2%3C/text%3E%3C/svg%3E",
|
| 13 |
+
"inputImages": [],
|
| 14 |
+
"timestamp": "2024-12-01T14:30:00.000Z"
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"id": "demo-3",
|
| 18 |
+
"prompt": "中国传统山水画风格的山峦,云雾缭绕,古松挺立",
|
| 19 |
+
"image": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='300'%3E%3Crect fill='%23f5f5dc' width='400' height='300'/%3E%3Cpath d='M 0 200 Q 100 150 200 180 T 400 160 L 400 300 L 0 300 Z' fill='%238B7355'/%3E%3Cpath d='M 50 100 Q 80 80 120 100' stroke='%23654321' fill='none' stroke-width='3'/%3E%3Ccircle cx='100' cy='70' r='20' fill='%23fff' opacity='0.7'/%3E%3Ccircle cx='300' cy='50' r='15' fill='%23fff' opacity='0.6'/%3E%3Ctext x='200' y='250' text-anchor='middle' fill='%23666' font-family='Arial' font-size='14'%3E示例图片 3%3C/text%3E%3C/svg%3E",
|
| 20 |
+
"inputImages": [],
|
| 21 |
+
"timestamp": "2024-12-01T18:45:00.000Z"
|
| 22 |
+
}
|
| 23 |
+
]
|
index.html
CHANGED
|
@@ -739,13 +739,17 @@
|
|
| 739 |
this.setHint(this.defaultHint);
|
| 740 |
} catch (error) {
|
| 741 |
console.error('公共画廊加载失败:', error);
|
|
|
|
|
|
|
| 742 |
if (error.name === 'AbortError') {
|
|
|
|
| 743 |
this.setHint('加载超时,请检查网络', 'error');
|
| 744 |
} else {
|
| 745 |
-
this.setHint(
|
| 746 |
}
|
|
|
|
| 747 |
if (AppState.publicGalleryData.length === 0) {
|
| 748 |
-
this.showEmpty(
|
| 749 |
}
|
| 750 |
} finally {
|
| 751 |
this.setLoading(false);
|
|
@@ -1038,12 +1042,24 @@
|
|
| 1038 |
|
| 1039 |
} catch (err) {
|
| 1040 |
console.error('生成失败:', err);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1041 |
if (err.name === 'AbortError') {
|
| 1042 |
this.statusBar.textContent = '⚠️ 生成超时';
|
| 1043 |
alert('生成超时,请检查网络后重试');
|
| 1044 |
} else {
|
| 1045 |
this.statusBar.textContent = '❌ 生成失败';
|
| 1046 |
-
alert(
|
| 1047 |
}
|
| 1048 |
} finally {
|
| 1049 |
this.setLoading(false);
|
|
|
|
| 739 |
this.setHint(this.defaultHint);
|
| 740 |
} catch (error) {
|
| 741 |
console.error('公共画廊加载失败:', error);
|
| 742 |
+
|
| 743 |
+
let errorMessage = '加载失败,请稍后重试';
|
| 744 |
if (error.name === 'AbortError') {
|
| 745 |
+
errorMessage = '加载超时,请检查网络';
|
| 746 |
this.setHint('加载超时,请检查网络', 'error');
|
| 747 |
} else {
|
| 748 |
+
this.setHint(errorMessage, 'error');
|
| 749 |
}
|
| 750 |
+
|
| 751 |
if (AppState.publicGalleryData.length === 0) {
|
| 752 |
+
this.showEmpty(errorMessage);
|
| 753 |
}
|
| 754 |
} finally {
|
| 755 |
this.setLoading(false);
|
|
|
|
| 1042 |
|
| 1043 |
} catch (err) {
|
| 1044 |
console.error('生成失败:', err);
|
| 1045 |
+
|
| 1046 |
+
let errorMessage = '生成失败,请重试';
|
| 1047 |
+
if (err.message.includes('API认证失败')) {
|
| 1048 |
+
errorMessage = 'API认证失败,请联系管理员检查API配置';
|
| 1049 |
+
} else if (err.message.includes('无法连接到API服务器')) {
|
| 1050 |
+
errorMessage = '无法连接到API服务器,请检查网络连接';
|
| 1051 |
+
} else if (err.message.includes('API请求超时')) {
|
| 1052 |
+
errorMessage = 'API请求超时,请稍后重试';
|
| 1053 |
+
} else if (err.message) {
|
| 1054 |
+
errorMessage = err.message;
|
| 1055 |
+
}
|
| 1056 |
+
|
| 1057 |
if (err.name === 'AbortError') {
|
| 1058 |
this.statusBar.textContent = '⚠️ 生成超时';
|
| 1059 |
alert('生成超时,请检查网络后重试');
|
| 1060 |
} else {
|
| 1061 |
this.statusBar.textContent = '❌ 生成失败';
|
| 1062 |
+
alert(errorMessage);
|
| 1063 |
}
|
| 1064 |
} finally {
|
| 1065 |
this.setLoading(false);
|
mock-api.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* mock-api.js
|
| 3 |
+
* 模拟图片生成API服务器,用于测试和演示
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import express from 'express';
|
| 7 |
+
import { randomBytes } from 'crypto';
|
| 8 |
+
|
| 9 |
+
const app = express();
|
| 10 |
+
const port = 8000;
|
| 11 |
+
|
| 12 |
+
app.use(express.json());
|
| 13 |
+
|
| 14 |
+
// 生成简单的SVG图片作为响应
|
| 15 |
+
function generateMockImage(prompt) {
|
| 16 |
+
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD'];
|
| 17 |
+
const color = colors[Math.floor(Math.random() * colors.length)];
|
| 18 |
+
const seed = randomBytes(4).toString('hex');
|
| 19 |
+
|
| 20 |
+
const svg = `
|
| 21 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
|
| 22 |
+
<defs>
|
| 23 |
+
<linearGradient id="grad${seed}" x1="0%" y1="0%" x2="100%" y2="100%">
|
| 24 |
+
<stop offset="0%" style="stop-color:${color};stop-opacity:1" />
|
| 25 |
+
<stop offset="100%" style="stop-color:#FFF;stop-opacity:1" />
|
| 26 |
+
</linearGradient>
|
| 27 |
+
</defs>
|
| 28 |
+
<rect width="512" height="512" fill="url(#grad${seed})"/>
|
| 29 |
+
<circle cx="256" cy="256" r="100" fill="${color}" opacity="0.8"/>
|
| 30 |
+
<text x="256" y="280" text-anchor="middle" fill="white" font-family="Arial" font-size="16" font-weight="bold">
|
| 31 |
+
AI Generated
|
| 32 |
+
</text>
|
| 33 |
+
<text x="256" y="300" text-anchor="middle" fill="white" font-family="Arial" font-size="12">
|
| 34 |
+
${prompt.substring(0, 20)}${prompt.length > 20 ? '...' : ''}
|
| 35 |
+
</text>
|
| 36 |
+
</svg>`;
|
| 37 |
+
|
| 38 |
+
return `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
// OpenAI兼容的聊天完成端点
|
| 42 |
+
app.post('/v1/chat/completions', (req, res) => {
|
| 43 |
+
const { messages, model } = req.body;
|
| 44 |
+
|
| 45 |
+
console.log(`[Mock API] 收到生成请求:`, {
|
| 46 |
+
model,
|
| 47 |
+
messageCount: messages?.length,
|
| 48 |
+
lastMessage: messages?.[messages.length - 1]?.content?.substring(0, 100) + '...'
|
| 49 |
+
});
|
| 50 |
+
|
| 51 |
+
const userPrompt = messages?.[messages.length - 1]?.content || '';
|
| 52 |
+
|
| 53 |
+
// 模拟处理延迟
|
| 54 |
+
setTimeout(() => {
|
| 55 |
+
const mockImage = generateMockImage(userPrompt);
|
| 56 |
+
|
| 57 |
+
const response = {
|
| 58 |
+
id: `chatcmpl-${randomBytes(8).toString('hex')}`,
|
| 59 |
+
object: 'chat.completion',
|
| 60 |
+
created: Math.floor(Date.now() / 1000),
|
| 61 |
+
model: model || 'banana-pro',
|
| 62 |
+
choices: [{
|
| 63 |
+
index: 0,
|
| 64 |
+
message: {
|
| 65 |
+
role: 'assistant',
|
| 66 |
+
content: ``
|
| 67 |
+
},
|
| 68 |
+
finish_reason: 'stop'
|
| 69 |
+
}],
|
| 70 |
+
usage: {
|
| 71 |
+
prompt_tokens: 50,
|
| 72 |
+
completion_tokens: 10,
|
| 73 |
+
total_tokens: 60
|
| 74 |
+
}
|
| 75 |
+
};
|
| 76 |
+
|
| 77 |
+
res.json(response);
|
| 78 |
+
}, 2000 + Math.random() * 3000); // 2-5秒随机延迟
|
| 79 |
+
});
|
| 80 |
+
|
| 81 |
+
// 健康检查
|
| 82 |
+
app.get('/health', (req, res) => {
|
| 83 |
+
res.json({ status: 'ok', message: 'Mock API is running' });
|
| 84 |
+
});
|
| 85 |
+
|
| 86 |
+
app.listen(port, () => {
|
| 87 |
+
console.log('==========================================');
|
| 88 |
+
console.log('🤖 Mock API 服务器已启动');
|
| 89 |
+
console.log('==========================================');
|
| 90 |
+
console.log(`📡 服务地址: http://localhost:${port}`);
|
| 91 |
+
console.log('🎨 模拟图片生成功能');
|
| 92 |
+
console.log('==========================================');
|
| 93 |
+
});
|
server.js
CHANGED
|
@@ -35,6 +35,10 @@ const CONFIG = {
|
|
| 35 |
maxPublicGalleryItems: parsePositiveInt(process.env.PUBLIC_GALLERY_LIMIT, 80)
|
| 36 |
};
|
| 37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
const DATA_DIR = path.join(__dirname, 'data');
|
| 39 |
const PUBLIC_GALLERY_FILE = path.join(DATA_DIR, 'public-gallery.json');
|
| 40 |
|
|
@@ -199,29 +203,45 @@ const APIService = {
|
|
| 199 |
* 从响应中提取图片
|
| 200 |
*/
|
| 201 |
extractImageFromResponse(data) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
if (data.choices && data.choices[0] && data.choices[0].message) {
|
| 203 |
const content = data.choices[0].message.content;
|
| 204 |
|
| 205 |
console.log(`[${new Date().toISOString()}] 收到响应内容长度: ${content?.length || 0}`);
|
|
|
|
| 206 |
|
| 207 |
const imageData = ImageParser.extractBase64FromMarkdown(content);
|
| 208 |
|
| 209 |
if (imageData && ImageParser.isValidBase64Image(imageData)) {
|
| 210 |
-
console.log(`[${new Date().toISOString()}]
|
| 211 |
return imageData;
|
|
|
|
|
|
|
| 212 |
}
|
| 213 |
}
|
| 214 |
|
|
|
|
| 215 |
if (data.data && data.data[0]) {
|
| 216 |
if (data.data[0].b64_json) {
|
|
|
|
| 217 |
return `data:image/png;base64,${data.data[0].b64_json}`;
|
| 218 |
}
|
| 219 |
if (data.data[0].url) {
|
|
|
|
| 220 |
return data.data[0].url;
|
| 221 |
}
|
| 222 |
}
|
| 223 |
|
| 224 |
-
|
|
|
|
|
|
|
| 225 |
}
|
| 226 |
};
|
| 227 |
|
|
@@ -386,6 +406,8 @@ app.post('/api/generate', authMiddleware, async (req, res) => {
|
|
| 386 |
|
| 387 |
try {
|
| 388 |
console.log(`[${new Date().toISOString()}] 开始生成图片...`);
|
|
|
|
|
|
|
| 389 |
|
| 390 |
const apiResponse = await APIService.generateImage(trimmedPrompt, uploadedImages);
|
| 391 |
const imageData = APIService.extractImageFromResponse(apiResponse);
|
|
@@ -402,10 +424,21 @@ app.post('/api/generate', authMiddleware, async (req, res) => {
|
|
| 402 |
|
| 403 |
} catch (error) {
|
| 404 |
console.error(`[${new Date().toISOString()}] 生成失败:`, error.message);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
|
| 406 |
res.status(500).json({
|
| 407 |
success: false,
|
| 408 |
-
message:
|
| 409 |
});
|
| 410 |
}
|
| 411 |
});
|
|
@@ -413,14 +446,19 @@ app.post('/api/generate', authMiddleware, async (req, res) => {
|
|
| 413 |
// 公共画廊 - 获取列表
|
| 414 |
app.get('/api/public-gallery', async (req, res) => {
|
| 415 |
try {
|
|
|
|
| 416 |
const items = await PublicGalleryStore.getAll();
|
| 417 |
const sanitized = items.map(({ deleteToken, ...rest }) => rest);
|
|
|
|
|
|
|
|
|
|
| 418 |
res.json({
|
| 419 |
success: true,
|
| 420 |
items: sanitized
|
| 421 |
});
|
| 422 |
} catch (error) {
|
| 423 |
console.error(`[${new Date().toISOString()}] 加载公��画廊失败:`, error);
|
|
|
|
| 424 |
res.status(500).json({
|
| 425 |
success: false,
|
| 426 |
message: '无法加载公共画廊,请稍后重试'
|
|
|
|
| 35 |
maxPublicGalleryItems: parsePositiveInt(process.env.PUBLIC_GALLERY_LIMIT, 80)
|
| 36 |
};
|
| 37 |
|
| 38 |
+
// 调试输出环境变量
|
| 39 |
+
console.log(`[DEBUG] 环境变量 OPENAI_API_URL:`, process.env.OPENAI_API_URL);
|
| 40 |
+
console.log(`[DEBUG] 最终 CONFIG.apiUrl:`, CONFIG.apiUrl);
|
| 41 |
+
|
| 42 |
const DATA_DIR = path.join(__dirname, 'data');
|
| 43 |
const PUBLIC_GALLERY_FILE = path.join(DATA_DIR, 'public-gallery.json');
|
| 44 |
|
|
|
|
| 203 |
* 从响应中提取图片
|
| 204 |
*/
|
| 205 |
extractImageFromResponse(data) {
|
| 206 |
+
console.log(`[${new Date().toISOString()}] API响应类型:`, typeof data);
|
| 207 |
+
console.log(`[${new Date().toISOString()}] API响应结构:`, Object.keys(data || {}));
|
| 208 |
+
|
| 209 |
+
// 检查错误响应
|
| 210 |
+
if (data.error) {
|
| 211 |
+
throw new Error(`API返回错误: ${data.error}`);
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
if (data.choices && data.choices[0] && data.choices[0].message) {
|
| 215 |
const content = data.choices[0].message.content;
|
| 216 |
|
| 217 |
console.log(`[${new Date().toISOString()}] 收到响应内容长度: ${content?.length || 0}`);
|
| 218 |
+
console.log(`[${new Date().toISOString()}] 响应内容预览:`, content?.substring(0, 200) + (content?.length > 200 ? '...' : ''));
|
| 219 |
|
| 220 |
const imageData = ImageParser.extractBase64FromMarkdown(content);
|
| 221 |
|
| 222 |
if (imageData && ImageParser.isValidBase64Image(imageData)) {
|
| 223 |
+
console.log(`[${new Date().toISOString()}] 成功提取图片数据,长度: ${imageData.length}`);
|
| 224 |
return imageData;
|
| 225 |
+
} else {
|
| 226 |
+
console.log(`[${new Date().toISOString()}] 未能从内容中提取有效图片数据`);
|
| 227 |
}
|
| 228 |
}
|
| 229 |
|
| 230 |
+
// 检查 DALL-E 格式
|
| 231 |
if (data.data && data.data[0]) {
|
| 232 |
if (data.data[0].b64_json) {
|
| 233 |
+
console.log(`[${new Date().toISOString()}] 使用 DALL-E b64_json 格式`);
|
| 234 |
return `data:image/png;base64,${data.data[0].b64_json}`;
|
| 235 |
}
|
| 236 |
if (data.data[0].url) {
|
| 237 |
+
console.log(`[${new Date().toISOString()}] 使用 DALL-E URL 格式`);
|
| 238 |
return data.data[0].url;
|
| 239 |
}
|
| 240 |
}
|
| 241 |
|
| 242 |
+
// 如果到这里还没有找到图片,输出完整的响应用于调试
|
| 243 |
+
console.log(`[${new Date().toISOString()}] 完整API响应:`, JSON.stringify(data, null, 2));
|
| 244 |
+
throw new Error('无法从 API 响应中提取图片数据。请检查API配置和模型响应格式。');
|
| 245 |
}
|
| 246 |
};
|
| 247 |
|
|
|
|
| 406 |
|
| 407 |
try {
|
| 408 |
console.log(`[${new Date().toISOString()}] 开始生成图片...`);
|
| 409 |
+
console.log(`[${new Date().toISOString()}] API地址: ${CONFIG.apiUrl}`);
|
| 410 |
+
console.log(`[${new Date().toISOString()}] API密钥: ${CONFIG.apiKey.substring(0, 10)}...`);
|
| 411 |
|
| 412 |
const apiResponse = await APIService.generateImage(trimmedPrompt, uploadedImages);
|
| 413 |
const imageData = APIService.extractImageFromResponse(apiResponse);
|
|
|
|
| 424 |
|
| 425 |
} catch (error) {
|
| 426 |
console.error(`[${new Date().toISOString()}] 生成失败:`, error.message);
|
| 427 |
+
console.error(`[${new Date().toISOString()}] 错误堆栈:`, error.stack);
|
| 428 |
+
|
| 429 |
+
// 根据错误类型提供更具体的错误信息
|
| 430 |
+
let errorMessage = error.message;
|
| 431 |
+
if (error.message.includes('API返回错误: 未授权') || error.message.includes('401') || error.message.includes('Unauthorized')) {
|
| 432 |
+
errorMessage = 'API认证失败,请检查API密钥配置';
|
| 433 |
+
} else if (error.message.includes('ECONNREFUSED') || error.message.includes('ENOTFOUND')) {
|
| 434 |
+
errorMessage = '无法连接到API服务器,请检查网络连接和API地址配置';
|
| 435 |
+
} else if (error.message.includes('timeout')) {
|
| 436 |
+
errorMessage = 'API请求超时,请稍后重试';
|
| 437 |
+
}
|
| 438 |
|
| 439 |
res.status(500).json({
|
| 440 |
success: false,
|
| 441 |
+
message: errorMessage || '图片生成失败,请稍后重试'
|
| 442 |
});
|
| 443 |
}
|
| 444 |
});
|
|
|
|
| 446 |
// 公共画廊 - 获取列表
|
| 447 |
app.get('/api/public-gallery', async (req, res) => {
|
| 448 |
try {
|
| 449 |
+
console.log(`[${new Date().toISOString()}] 开始加载公共画廊...`);
|
| 450 |
const items = await PublicGalleryStore.getAll();
|
| 451 |
const sanitized = items.map(({ deleteToken, ...rest }) => rest);
|
| 452 |
+
|
| 453 |
+
console.log(`[${new Date().toISOString()}] 公共画廊加载完成,项目数: ${sanitized.length}`);
|
| 454 |
+
|
| 455 |
res.json({
|
| 456 |
success: true,
|
| 457 |
items: sanitized
|
| 458 |
});
|
| 459 |
} catch (error) {
|
| 460 |
console.error(`[${new Date().toISOString()}] 加载公��画廊失败:`, error);
|
| 461 |
+
console.error(`[${new Date().toISOString()}] 错误详情:`, error.stack);
|
| 462 |
res.status(500).json({
|
| 463 |
success: false,
|
| 464 |
message: '无法加载公共画廊,请稍后重试'
|
start.sh
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# Banana Pro AI 启动脚本
|
| 4 |
+
# 同时启动主应用和模拟API服务器
|
| 5 |
+
|
| 6 |
+
echo "🍌 启动 Banana Pro AI 应用..."
|
| 7 |
+
|
| 8 |
+
# 检查是否已有进程在运行
|
| 9 |
+
if pgrep -f "node server.js" > /dev/null; then
|
| 10 |
+
echo "⚠️ 检测到主应用已在运行,先停止..."
|
| 11 |
+
pkill -f "node server.js"
|
| 12 |
+
sleep 2
|
| 13 |
+
fi
|
| 14 |
+
|
| 15 |
+
if pgrep -f "node mock-api.js" > /dev/null; then
|
| 16 |
+
echo "⚠️ 检测到模拟API已在运行,先停止..."
|
| 17 |
+
pkill -f "node mock-api.js"
|
| 18 |
+
sleep 2
|
| 19 |
+
fi
|
| 20 |
+
|
| 21 |
+
# 启动模拟API服务器
|
| 22 |
+
echo "🤖 启动模拟API服务器 (端口8000)..."
|
| 23 |
+
OPENAI_API_URL=http://127.0.0.1:8000/v1/chat/completions node mock-api.js &
|
| 24 |
+
API_PID=$!
|
| 25 |
+
|
| 26 |
+
# 等待API服务器启动
|
| 27 |
+
sleep 3
|
| 28 |
+
|
| 29 |
+
# 启动主应用
|
| 30 |
+
echo "🍌 启动主应用服务器 (端口3000)..."
|
| 31 |
+
OPENAI_API_URL=http://127.0.0.1:8000/v1/chat/completions node server.js &
|
| 32 |
+
MAIN_PID=$!
|
| 33 |
+
|
| 34 |
+
echo ""
|
| 35 |
+
echo "=========================================="
|
| 36 |
+
echo "✅ Banana Pro AI 已成功启动!"
|
| 37 |
+
echo "=========================================="
|
| 38 |
+
echo "🎨 Web应用地址: http://localhost:3000"
|
| 39 |
+
echo "🤖 模拟API地址: http://localhost:8000"
|
| 40 |
+
echo "🔑 登录密码: 123456"
|
| 41 |
+
echo ""
|
| 42 |
+
echo "💡 提示:"
|
| 43 |
+
echo " - 模拟API会生成彩色SVG图片作为演示"
|
| 44 |
+
echo " - 社区画廊已包含示例作品"
|
| 45 |
+
echo " - 按 Ctrl+C 停止所有服务"
|
| 46 |
+
echo "=========================================="
|
| 47 |
+
|
| 48 |
+
# 等待用户中断
|
| 49 |
+
trap "echo '🛑 正在停止服务...'; kill $API_PID $MAIN_PID 2>/dev/null; exit" INT
|
| 50 |
+
wait
|
test-system.sh
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
echo "🍌 测试 Banana Pro AI 系统..."
|
| 4 |
+
|
| 5 |
+
# 检查端口
|
| 6 |
+
echo "📡 检查端口状态..."
|
| 7 |
+
if ! lsof -i :3000 > /dev/null 2>&1; then
|
| 8 |
+
echo "❌ 端口3000未被占用"
|
| 9 |
+
else
|
| 10 |
+
echo "✅ 端口3000已被占用"
|
| 11 |
+
fi
|
| 12 |
+
|
| 13 |
+
if ! lsof -i :8000 > /dev/null 2>&1; then
|
| 14 |
+
echo "❌ 端口8000未被占用"
|
| 15 |
+
else
|
| 16 |
+
echo "✅ 端口8000已被占用"
|
| 17 |
+
fi
|
| 18 |
+
|
| 19 |
+
# 测试API
|
| 20 |
+
echo ""
|
| 21 |
+
echo "🔍 测试API连接..."
|
| 22 |
+
|
| 23 |
+
echo "测试主应用健康检查..."
|
| 24 |
+
if curl -s http://localhost:3000/api/health > /dev/null 2>&1; then
|
| 25 |
+
echo "✅ 主应用API正常"
|
| 26 |
+
else
|
| 27 |
+
echo "❌ 主应用API无法访问"
|
| 28 |
+
fi
|
| 29 |
+
|
| 30 |
+
echo "测试模拟API健康检查..."
|
| 31 |
+
if curl -s http://localhost:8000/health > /dev/null 2>&1; then
|
| 32 |
+
echo "✅ 模拟API正常"
|
| 33 |
+
else
|
| 34 |
+
echo "❌ 模拟API无法访问"
|
| 35 |
+
fi
|
| 36 |
+
|
| 37 |
+
echo ""
|
| 38 |
+
echo "📊 当前运行的Node进程:"
|
| 39 |
+
ps aux | grep -E "(node.*server|node.*mock)" | grep -v grep || echo "无相关进程"
|
| 40 |
+
|
| 41 |
+
echo ""
|
| 42 |
+
echo "🌐 测试完成!"
|