初始提交

This commit is contained in:
史悦
2025-08-29 11:15:44 +08:00
commit 960210d961
8 changed files with 2523 additions and 0 deletions

126
.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View 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>

1354
script.js Normal file

File diff suppressed because it is too large Load Diff

462
styles.css Normal file
View 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
View 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>