初始提交
This commit is contained in:
126
.gitignore
vendored
Normal file
126
.gitignore
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Production build
|
||||
dist/
|
||||
build/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage/
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Dependency directories
|
||||
jspm_packages/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
public
|
||||
|
||||
# Storybook build outputs
|
||||
.out
|
||||
.storybook-out
|
||||
|
||||
# Temporary folders
|
||||
tmp/
|
||||
temp/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Project specific
|
||||
*.local
|
||||
*.secret
|
||||
*.key
|
||||
*.pem
|
||||
*.p12
|
||||
*.pfx
|
||||
|
||||
# Cache
|
||||
.cache/
|
||||
*.cache
|
||||
|
||||
# Test files
|
||||
test/
|
||||
tests/
|
||||
*.test.*
|
||||
*.spec.*
|
||||
|
||||
# Documentation
|
||||
docs/
|
||||
*.md
|
||||
!README.md
|
||||
|
||||
# Backup files
|
||||
*.bak
|
||||
*.backup
|
||||
*.swp
|
||||
*.swo
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 OVINC CN
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
185
README.md
Normal file
185
README.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# OpenRouter Image Generator Web应用
|
||||
|
||||
这是一个基于OpenRouter API的图像生成Web应用,实现了与原始Python代码相同的功能,但提供了用户友好的Web界面。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 核心功能
|
||||
- 🎨 **图像生成**: 通过OpenRouter API生成高质量图像
|
||||
- 📤 **图像上传**: 支持拖拽上传参考图像
|
||||
- 💬 **对话界面**: 直观的聊天式交互界面
|
||||
- 🖼️ **图像画廊**: 展示和管理生成的图像
|
||||
- ⚙️ **API配置**: 灵活的API设置和连接测试
|
||||
|
||||
### 技术特性
|
||||
- 🌐 **响应式设计**: 适配各种设备屏幕
|
||||
- 🎯 **实时预览**: 即时显示上传的图像
|
||||
- 💾 **本地存储**: 自动保存用户设置
|
||||
- 🔗 **连接状态**: 实时显示API连接状态
|
||||
- 📱 **移动友好**: 支持触摸操作
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 获取OpenRouter API密钥
|
||||
|
||||
1. 访问 [OpenRouter官网](https://openrouter.ai/)
|
||||
2. 注册账户并登录
|
||||
3. 在控制台中生成API密钥
|
||||
|
||||
### 2. 配置应用
|
||||
|
||||
1. 打开 `index.html` 文件
|
||||
2. 在"API设置"面板中输入您的API密钥
|
||||
3. 点击"测试连接"按钮验证配置
|
||||
|
||||
### 3. 使用应用
|
||||
|
||||
#### 上传参考图像(可选)
|
||||
- 拖拽图像文件到上传区域
|
||||
- 或点击"选择图像"按钮
|
||||
- 支持多种图像格式(JPG、PNG、GIF等)
|
||||
|
||||
#### 生成图像
|
||||
1. 在对话输入框中输入您的图像生成请求
|
||||
2. 点击"发送"按钮或按Enter键
|
||||
3. 等待API处理并生成结果
|
||||
|
||||
#### 管理生成的图像
|
||||
- 在图像画廊中查看所有生成的图像
|
||||
- 悬停在图像上显示操作按钮
|
||||
- 下载图像或复制图像URL
|
||||
|
||||
## 配置选项
|
||||
|
||||
### API设置
|
||||
- **API Key**: 您的OpenRouter API密钥
|
||||
- **Base URL**: OpenRouter API的基础URL(默认:https://openrouter.ai/api/v1)
|
||||
- **模型**: 选择使用的AI模型
|
||||
- Google Gemini 2.5 Flash Image Preview (Free)
|
||||
- OpenAI GPT-4 Vision Preview
|
||||
- Anthropic Claude 3 Sonnet
|
||||
- **超时时间**: 请求超时时间(秒)
|
||||
- **代理**: 可选的代理服务器设置
|
||||
|
||||
### 支持的模型
|
||||
- `google/gemini-2.5-flash-image-preview:free` - 免费的Google Gemini模型
|
||||
- `openai/gpt-4-vision-preview` - OpenAI的GPT-4视觉预览版
|
||||
- `anthropic/claude-3-sonnet-20240229` - Anthropic的Claude 3 Sonnet模型
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本图像生成
|
||||
```
|
||||
请生成一张美丽的日落风景图像
|
||||
```
|
||||
|
||||
### 基于参考图像的生成
|
||||
1. 上传一张参考图像
|
||||
2. 输入提示:`请根据上传的图像风格,生成一张类似的现代建筑图像`
|
||||
|
||||
### 复杂场景生成
|
||||
```
|
||||
请生成一张未来城市的图像,包含飞行汽车、玻璃建筑和绿色植物,风格要写实且具有科技感
|
||||
```
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 前端技术栈
|
||||
- **HTML5**: 语义化标记和现代Web API
|
||||
- **CSS3**: 响应式设计和动画效果
|
||||
- **JavaScript**: 原生JS实现所有交互功能
|
||||
- **Bootstrap 5**: UI组件和响应式布局
|
||||
- **Font Awesome**: 图标库
|
||||
|
||||
### 核心功能实现
|
||||
|
||||
#### API调用
|
||||
```javascript
|
||||
async function generateImage(message) {
|
||||
const payload = {
|
||||
model: model,
|
||||
messages: messages,
|
||||
stream: false
|
||||
};
|
||||
|
||||
const response = await fetch(`${baseUrl}/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
```
|
||||
|
||||
#### 图像处理
|
||||
- 支持Base64编码的图像数据
|
||||
- 文件拖拽上传功能
|
||||
- 图像预览和管理
|
||||
- 图像下载和URL复制
|
||||
|
||||
#### 状态管理
|
||||
- 本地存储用户设置
|
||||
- 实时连接状态显示
|
||||
- 加载状态指示器
|
||||
- 错误处理和用户反馈
|
||||
|
||||
## 浏览器兼容性
|
||||
|
||||
- ✅ Chrome 80+
|
||||
- ✅ Firefox 75+
|
||||
- ✅ Safari 13+
|
||||
- ✅ Edge 80+
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **API限制**: 请注意OpenRouter API的使用限制和费用
|
||||
2. **图像大小**: 上传的图像文件大小建议不超过10MB
|
||||
3. **网络要求**: 需要稳定的网络连接以访问OpenRouter API
|
||||
4. **隐私安全**: API密钥存储在浏览器本地,请勿在公共设备上使用
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
**Q: 连接测试失败**
|
||||
- 检查API密钥是否正确
|
||||
- 确认网络连接正常
|
||||
- 验证Base URL设置
|
||||
|
||||
**Q: 图像生成失败**
|
||||
- 检查输入的提示词是否合适
|
||||
- 确认选择的模型支持图像生成
|
||||
- 查看浏览器控制台错误信息
|
||||
|
||||
**Q: 图像上传失败**
|
||||
- 检查图像格式是否支持
|
||||
- 确认图像文件大小是否合理
|
||||
- 尝试使用不同的浏览器
|
||||
|
||||
### 调试技巧
|
||||
1. 打开浏览器开发者工具(F12)
|
||||
2. 查看Console标签的错误信息
|
||||
3. 检查Network标签的API请求状态
|
||||
4. 验证API响应数据格式
|
||||
|
||||
## 许可证
|
||||
|
||||
本项目基于MIT许可证开源,详见LICENSE文件。
|
||||
|
||||
## 贡献
|
||||
|
||||
欢迎提交Issue和Pull Request来改进这个项目!
|
||||
|
||||
## 联系方式
|
||||
|
||||
如有问题或建议,请通过以下方式联系:
|
||||
- GitHub Issues
|
||||
- Email: [您的邮箱]
|
||||
|
||||
---
|
||||
|
||||
**注意**: 这是一个前端演示应用,生产环境使用时请注意API密钥的安全性。
|
||||
75
config.json
Normal file
75
config.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"app": {
|
||||
"name": "OpenRouter Image Generator",
|
||||
"version": "1.0.0",
|
||||
"description": "基于OpenRouter API的图像生成Web应用",
|
||||
"author": "OVINC CN",
|
||||
"license": "MIT"
|
||||
},
|
||||
"api": {
|
||||
"default_base_url": "https://openrouter.ai/api/v1",
|
||||
"default_timeout": 600,
|
||||
"default_model": "google/gemini-2.5-flash-image-preview:free",
|
||||
"supported_models": [
|
||||
{
|
||||
"id": "google/gemini-2.5-flash-image-preview:free",
|
||||
"name": "Google Gemini 2.5 Flash Image Preview",
|
||||
"description": "免费的Google Gemini模型,支持图像生成和视觉理解",
|
||||
"pricing": "free"
|
||||
},
|
||||
{
|
||||
"id": "openai/gpt-4-vision-preview",
|
||||
"name": "OpenAI GPT-4 Vision Preview",
|
||||
"description": "OpenAI的GPT-4视觉预览版,强大的图像理解和生成能力",
|
||||
"pricing": "paid"
|
||||
},
|
||||
{
|
||||
"id": "anthropic/claude-3-sonnet-20240229",
|
||||
"name": "Anthropic Claude 3 Sonnet",
|
||||
"description": "Anthropic的Claude 3 Sonnet模型,优秀的图像分析能力",
|
||||
"pricing": "paid"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ui": {
|
||||
"theme": {
|
||||
"primary_color": "#667eea",
|
||||
"secondary_color": "#764ba2",
|
||||
"background_gradient": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
|
||||
},
|
||||
"features": {
|
||||
"drag_drop": true,
|
||||
"image_preview": true,
|
||||
"chat_history": true,
|
||||
"image_gallery": true,
|
||||
"settings_panel": true,
|
||||
"connection_status": true
|
||||
},
|
||||
"limits": {
|
||||
"max_file_size": 10485760,
|
||||
"max_files_per_upload": 5,
|
||||
"supported_image_formats": ["image/jpeg", "image/png", "image/gif", "image/webp"],
|
||||
"max_chat_history": 100,
|
||||
"max_generated_images": 50
|
||||
}
|
||||
},
|
||||
"storage": {
|
||||
"settings_key": "openRouterSettings",
|
||||
"chat_history_key": "openRouterChatHistory",
|
||||
"generated_images_key": "openRouterGeneratedImages"
|
||||
},
|
||||
"endpoints": {
|
||||
"models": "/models",
|
||||
"chat_completions": "/chat/completions",
|
||||
"image_generation": "/images/generations"
|
||||
},
|
||||
"error_messages": {
|
||||
"no_api_key": "请先输入API Key",
|
||||
"connection_failed": "连接失败,请检查网络和API设置",
|
||||
"invalid_response": "API响应格式错误",
|
||||
"image_generation_failed": "图像生成失败",
|
||||
"file_too_large": "文件大小超过限制",
|
||||
"unsupported_format": "不支持的文件格式",
|
||||
"upload_failed": "文件上传失败"
|
||||
}
|
||||
}
|
||||
162
index.html
Normal file
162
index.html
Normal file
@@ -0,0 +1,162 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>OpenRouter Image Generator</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||
<link href="styles.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="main-container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h1 class="mb-0">
|
||||
<i class="fas fa-image me-2"></i>
|
||||
OpenRouter Image Generator
|
||||
</h1>
|
||||
<p class="mb-0 mt-2">基于OpenRouter API的智能图像生成工具</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- 设置面板 -->
|
||||
<div class="settings-panel">
|
||||
<h5><i class="fas fa-cog me-2"></i>API设置</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="apiKey" class="form-label">API Key</label>
|
||||
<input type="password" class="form-control" id="apiKey" placeholder="输入您的OpenRouter API Key">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="baseUrl" class="form-label">Base URL</label>
|
||||
<input type="url" class="form-control" id="baseUrl" value="https://openrouter.ai/api/v1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label for="model" class="form-label">模型</label>
|
||||
<select class="form-select" id="model">
|
||||
<option value="google/gemini-2.5-flash-image-preview:free">Google Gemini 2.5 Flash Image Preview (Free)</option>
|
||||
<option value="openai/gpt-4-vision-preview">OpenAI GPT-4 Vision Preview</option>
|
||||
<option value="anthropic/claude-3-sonnet-20240229">Anthropic Claude 3 Sonnet</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label for="timeout" class="form-label">超时时间 (秒)</label>
|
||||
<input type="number" class="form-control" id="timeout" value="600">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label for="proxy" class="form-label">代理 (可选)</label>
|
||||
<input type="url" class="form-control" id="proxy" placeholder="http://proxy:port">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<span class="status-indicator status-disconnected" id="connectionStatus"></span>
|
||||
<span id="connectionText">未连接</span>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-outline-primary me-2" onclick="testConnection()">
|
||||
<i class="fas fa-plug me-2"></i>测试连接
|
||||
</button>
|
||||
<button class="btn btn-outline-warning" onclick="app.clearStorage()">
|
||||
<i class="fas fa-broom me-2"></i>清除存储数据
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图像上传区域 -->
|
||||
<div class="mb-4">
|
||||
<h5><i class="fas fa-upload me-2"></i>上传参考图像</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="border-2 border-dashed rounded p-4 text-center h-100 d-flex flex-column justify-content-center" id="dropZone">
|
||||
<i class="fas fa-cloud-upload-alt fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">拖拽图像到此处或点击选择文件</p>
|
||||
<input type="file" class="d-none" id="imageInput" accept="image/*">
|
||||
<button class="btn btn-outline-primary" onclick="document.getElementById('imageInput').click()">
|
||||
选择图像
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div id="imagePreview" class="h-100 d-flex align-items-center justify-content-center">
|
||||
<p class="text-muted">未选择图像</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 聊天输入区域 -->
|
||||
<div class="mb-4">
|
||||
<h5><i class="fas fa-comments me-2"></i>对话输入</h5>
|
||||
<div class="input-group">
|
||||
<textarea class="form-control" id="messageInput" rows="3" placeholder="输入您的图像生成请求..."></textarea>
|
||||
<button class="btn btn-primary" onclick="sendMessage()">
|
||||
<i class="fas fa-paper-plane me-2"></i>发送
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载指示器 -->
|
||||
<div class="loading-spinner" id="loadingSpinner">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">加载中...</span>
|
||||
</div>
|
||||
<p class="mt-2">正在生成图像,请稍候...</p>
|
||||
</div>
|
||||
|
||||
<!-- 聊天历史 -->
|
||||
<div class="mb-4">
|
||||
<h5><i class="fas fa-history me-2"></i>对话历史</h5>
|
||||
<div id="chatHistory" style="max-height: 400px; overflow-y: auto;"></div>
|
||||
</div>
|
||||
|
||||
<!-- 生成的图像画廊 -->
|
||||
<div class="mb-4">
|
||||
<h5><i class="fas fa-images me-2"></i>生成的图像</h5>
|
||||
<div class="image-gallery" id="imageGallery"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 新的图像查看模态框 -->
|
||||
<div class="modal fade" id="imageViewerModal" tabindex="-1" aria-labelledby="imageViewerModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="imageViewerModalLabel">图像查看</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body text-center">
|
||||
<img id="viewerModalImage" src="" alt="查看图像" class="img-fluid">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||||
<button type="button" class="btn btn-primary" id="viewerDownloadImage">
|
||||
<i class="fas fa-download me-2"></i>下载图像
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
462
styles.css
Normal file
462
styles.css
Normal file
@@ -0,0 +1,462 @@
|
||||
/* OpenRouter Image Generator - 自定义样式 */
|
||||
|
||||
/* 全局样式 */
|
||||
:root {
|
||||
--primary-color: #667eea;
|
||||
--secondary-color: #764ba2;
|
||||
--success-color: #28a745;
|
||||
--danger-color: #dc3545;
|
||||
--warning-color: #ffc107;
|
||||
--info-color: #17a2b8;
|
||||
--light-color: #f8f9fa;
|
||||
--dark-color: #343a40;
|
||||
--border-radius: 15px;
|
||||
--box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
--transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 基础样式重置 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
||||
min-height: 100vh;
|
||||
line-height: 1.6;
|
||||
color: var(--dark-color);
|
||||
}
|
||||
|
||||
/* 主容器 */
|
||||
.main-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--box-shadow);
|
||||
backdrop-filter: blur(10px);
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
||||
color: white;
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0 !important;
|
||||
padding: 20px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 表单控件样式 */
|
||||
.form-control, .form-select {
|
||||
border-radius: 10px;
|
||||
border: 2px solid #e9ecef;
|
||||
padding: 12px;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.form-control:focus, .form-select:focus {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn {
|
||||
border-radius: 10px;
|
||||
padding: 12px 30px;
|
||||
font-weight: 600;
|
||||
transition: var(--transition);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||
background: linear-gradient(135deg, var(--secondary-color) 0%, var(--primary-color) 100%);
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
border: 2px solid var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.btn-outline-primary:hover {
|
||||
background: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.btn-outline-danger {
|
||||
border: 2px solid var(--danger-color);
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
.btn-outline-danger:hover {
|
||||
background: var(--danger-color);
|
||||
border-color: var(--danger-color);
|
||||
}
|
||||
|
||||
/* 图像预览样式 */
|
||||
.image-preview {
|
||||
max-width: 100%;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.image-preview:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* 图像预览区域 */
|
||||
#imagePreview {
|
||||
max-height: 20rem;
|
||||
}
|
||||
|
||||
/* 拖拽区域样式 */
|
||||
#dropZone {
|
||||
border: 2px dashed #ccc;
|
||||
border-radius: 10px;
|
||||
transition: var(--transition);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#dropZone:hover {
|
||||
border-color: var(--primary-color);
|
||||
background: rgba(102, 126, 234, 0.05);
|
||||
}
|
||||
|
||||
#dropZone.dragover {
|
||||
border-color: var(--primary-color);
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
/* 聊天消息样式 */
|
||||
.chat-message {
|
||||
background: var(--light-color);
|
||||
border-radius: 15px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
border-left: 4px solid var(--primary-color);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.chat-message:hover {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.chat-message.user {
|
||||
background: #e3f2fd;
|
||||
border-left-color: #2196f3;
|
||||
}
|
||||
|
||||
.chat-message.assistant {
|
||||
background: #f3e5f5;
|
||||
border-left-color: #9c27b0;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading-spinner {
|
||||
display: none;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.spinner-border {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border-width: 0.3rem;
|
||||
}
|
||||
|
||||
/* 设置面板样式 */
|
||||
.settings-panel {
|
||||
background: var(--light-color);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
/* 图像画廊样式 */
|
||||
.image-gallery {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.image-item {
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.image-item:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.image-item img {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.image-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.image-item:hover .image-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 状态指示器 */
|
||||
.status-indicator {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.status-connected {
|
||||
background-color: var(--success-color);
|
||||
}
|
||||
|
||||
.status-disconnected {
|
||||
background-color: var(--danger-color);
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(40, 167, 69, 0.7);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 10px rgba(40, 167, 69, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(40, 167, 69, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn 0.5s ease-in;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.main-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.image-gallery {
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-control, .form-select {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.image-gallery {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.settings-panel {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 工具提示样式 */
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tooltip .tooltiptext {
|
||||
visibility: hidden;
|
||||
width: 200px;
|
||||
background-color: #555;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: 125%;
|
||||
left: 50%;
|
||||
margin-left: -100px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--primary-color);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--secondary-color);
|
||||
}
|
||||
|
||||
/* 特殊效果 */
|
||||
.glass-effect {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.text-gradient {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* 加载骨架屏 */
|
||||
.skeleton {
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: loading 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 图像查看模态框样式 */
|
||||
#viewerModalImage {
|
||||
max-height: 80vh;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
#viewerModalImage:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
/* 图像查看模态框特定样式 */
|
||||
#imageViewerModal .modal-content {
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#imageViewerModal .modal-header {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#imageViewerModal .modal-footer {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 按钮网格布局 - 2排2列 */
|
||||
.btn-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.btn-grid-row {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.btn-grid-row .btn {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 错误状态 */
|
||||
.error-state {
|
||||
border-color: var(--danger-color) !important;
|
||||
background-color: rgba(220, 53, 69, 0.1) !important;
|
||||
}
|
||||
|
||||
/* 成功状态 */
|
||||
.success-state {
|
||||
border-color: var(--success-color) !important;
|
||||
background-color: rgba(40, 167, 69, 0.1) !important;
|
||||
}
|
||||
138
test.html
Normal file
138
test.html
Normal file
@@ -0,0 +1,138 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>OpenRouter Image Generator - 测试页面</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||
<link href="styles.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2 class="text-center">OpenRouter Image Generator - 测试页面</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
这是测试页面,用于验证应用是否正常工作。请检查浏览器控制台是否有错误信息。
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<h5>功能测试清单:</h5>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<i class="fas fa-check-circle text-success me-2"></i>
|
||||
页面加载正常
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<i class="fas fa-check-circle text-success me-2"></i>
|
||||
JavaScript文件加载成功
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<i class="fas fa-check-circle text-success me-2"></i>
|
||||
事件监听器初始化完成
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<i class="fas fa-check-circle text-success me-2"></i>
|
||||
UI控制器正常工作
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<i class="fas fa-check-circle text-success me-2"></i>
|
||||
文件处理功能正常
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<a href="index.html" class="btn btn-primary">
|
||||
<i class="fas fa-home me-2"></i>
|
||||
返回主应用
|
||||
</a>
|
||||
<button class="btn btn-secondary" onclick="testFunctions()">
|
||||
<i class="fas fa-flask me-2"></i>
|
||||
测试核心功能
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="testResults" class="mt-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
function testFunctions() {
|
||||
const results = document.getElementById('testResults');
|
||||
results.innerHTML = '<div class="alert alert-info">正在测试功能...</div>';
|
||||
|
||||
setTimeout(() => {
|
||||
let testResults = '<div class="alert alert-success"><h5>测试结果:</h5><ul>';
|
||||
|
||||
// 测试1: 检查app对象是否存在
|
||||
if (typeof app !== 'undefined') {
|
||||
testResults += '<li class="text-success">✓ app对象存在</li>';
|
||||
} else {
|
||||
testResults += '<li class="text-danger">✗ app对象不存在</li>';
|
||||
}
|
||||
|
||||
// 测试2: 检查uiController是否存在
|
||||
if (typeof uiController !== 'undefined') {
|
||||
testResults += '<li class="text-success">✓ uiController存在</li>';
|
||||
} else {
|
||||
testResults += '<li class="text-danger">✗ uiController不存在</li>';
|
||||
}
|
||||
|
||||
// 测试3: 检查apiService是否存在
|
||||
if (typeof apiService !== 'undefined') {
|
||||
testResults += '<li class="text-success">✓ apiService存在</li>';
|
||||
} else {
|
||||
testResults += '<li class="text-danger">✗ apiService不存在</li>';
|
||||
}
|
||||
|
||||
// 测试4: 检查utils是否存在
|
||||
if (typeof utils !== 'undefined') {
|
||||
testResults += '<li class="text-success">✓ utils存在</li>';
|
||||
} else {
|
||||
testResults += '<li class="text-danger">✗ utils不存在</li>';
|
||||
}
|
||||
|
||||
// 测试5: 检查fileHandler是否存在
|
||||
if (typeof fileHandler !== 'undefined') {
|
||||
testResults += '<li class="text-success">✓ fileHandler存在</li>';
|
||||
} else {
|
||||
testResults += '<li class="text-danger">✗ fileHandler不存在</li>';
|
||||
}
|
||||
|
||||
// 测试6: 检查CONFIG是否存在
|
||||
if (typeof CONFIG !== 'undefined') {
|
||||
testResults += '<li class="text-success">✓ CONFIG存在</li>';
|
||||
} else {
|
||||
testResults += '<li class="text-danger">✗ CONFIG不存在</li>';
|
||||
}
|
||||
|
||||
testResults += '</ul></div>';
|
||||
results.innerHTML = testResults;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// 页面加载完成后自动测试
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('测试页面加载完成');
|
||||
console.log('检查全局对象:');
|
||||
console.log('app:', typeof app);
|
||||
console.log('uiController:', typeof uiController);
|
||||
console.log('apiService:', typeof apiService);
|
||||
console.log('utils:', typeof utils);
|
||||
console.log('fileHandler:', typeof fileHandler);
|
||||
console.log('CONFIG:', typeof CONFIG);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user