Spaces:
Sleeping
Sleeping
Junhui Ji
commited on
Commit
·
3394804
1
Parent(s):
e4b0841
init
Browse files- Dockerfile +38 -0
- README.md +4 -3
- cache/.temp +0 -0
- docker-compose.yml +20 -0
- main.py +345 -0
- requirements.txt +9 -0
- service_readme.md +176 -0
- static/feedback.html +121 -0
- static/index.html +79 -0
- static/script.js +946 -0
- static/styles.css +927 -0
- static/upload.html +35 -0
Dockerfile
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.9-slim
|
| 2 |
+
|
| 3 |
+
# 安装系统依赖
|
| 4 |
+
RUN apt-get update \
|
| 5 |
+
&& apt-get install -y wget gnupg \
|
| 6 |
+
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
|
| 7 |
+
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
|
| 8 |
+
&& apt-get update \
|
| 9 |
+
&& apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
|
| 10 |
+
--no-install-recommends \
|
| 11 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 12 |
+
|
| 13 |
+
# 设置工作目录
|
| 14 |
+
WORKDIR /app
|
| 15 |
+
|
| 16 |
+
# 复制依赖文件
|
| 17 |
+
COPY requirements.txt .
|
| 18 |
+
|
| 19 |
+
# 安装 Python 依赖
|
| 20 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 21 |
+
|
| 22 |
+
# 安装 Playwright 浏览器
|
| 23 |
+
RUN playwright install chromium
|
| 24 |
+
|
| 25 |
+
# 复制应用代码
|
| 26 |
+
COPY . .
|
| 27 |
+
|
| 28 |
+
# 创建缓存目录
|
| 29 |
+
RUN mkdir -p cache
|
| 30 |
+
|
| 31 |
+
# 暴露端口
|
| 32 |
+
EXPOSE 7860
|
| 33 |
+
|
| 34 |
+
# 设置环境变量
|
| 35 |
+
ENV PYTHONUNBUFFERED=1
|
| 36 |
+
|
| 37 |
+
# 启动应用
|
| 38 |
+
CMD ["python", "main.py"]
|
README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
| 1 |
---
|
| 2 |
title: Boss Translator
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
| 2 |
title: Boss Translator
|
| 3 |
+
emoji: 🏃
|
| 4 |
+
colorFrom: purple
|
| 5 |
+
colorTo: blue
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
+
app_port: 7860
|
| 9 |
---
|
| 10 |
|
| 11 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
cache/.temp
ADDED
|
File without changes
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
screenshot-service:
|
| 5 |
+
build: .
|
| 6 |
+
container_name: screenshot-service
|
| 7 |
+
restart: unless-stopped
|
| 8 |
+
ports:
|
| 9 |
+
- "7860:7860"
|
| 10 |
+
volumes:
|
| 11 |
+
- ./cache:/app/cache
|
| 12 |
+
environment:
|
| 13 |
+
- NODE_ENV=production
|
| 14 |
+
- PORT=7860
|
| 15 |
+
healthcheck:
|
| 16 |
+
test: ["CMD", "curl", "-f", "http://localhost:7860/health"]
|
| 17 |
+
interval: 30s
|
| 18 |
+
timeout: 10s
|
| 19 |
+
retries: 3
|
| 20 |
+
start_period: 20s
|
main.py
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, HTTPException
|
| 2 |
+
from fastapi.staticfiles import StaticFiles
|
| 3 |
+
from fastapi.responses import FileResponse
|
| 4 |
+
from fastapi.responses import JSONResponse
|
| 5 |
+
from pydantic import BaseModel
|
| 6 |
+
from playwright.async_api import async_playwright
|
| 7 |
+
import os
|
| 8 |
+
import time
|
| 9 |
+
from urllib.parse import urlparse
|
| 10 |
+
from typing import Optional, Dict, List
|
| 11 |
+
import logging
|
| 12 |
+
import json
|
| 13 |
+
import base64
|
| 14 |
+
from io import BytesIO
|
| 15 |
+
import aiohttp
|
| 16 |
+
import traceback
|
| 17 |
+
import requests
|
| 18 |
+
from openai import OpenAI
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
app = FastAPI()
|
| 22 |
+
|
| 23 |
+
# 确保缓存目录存在
|
| 24 |
+
CACHE_DIR = "cache"
|
| 25 |
+
os.makedirs(CACHE_DIR, exist_ok=True)
|
| 26 |
+
|
| 27 |
+
# 挂载静态文件目录
|
| 28 |
+
app.mount("/screenshots", StaticFiles(directory=CACHE_DIR), name="screenshots")
|
| 29 |
+
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 30 |
+
|
| 31 |
+
# API Keys
|
| 32 |
+
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
| 33 |
+
OPENAI_API_IMAGE_EDIT_KEY = os.getenv("OPENAI_API_IMAGE_EDIT_KEY")
|
| 34 |
+
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
|
| 35 |
+
SEARCH_ENGINE_ID = os.getenv("SEARCH_ENGINE_ID", "27acb0d55ad504716")
|
| 36 |
+
print(OPENAI_API_KEY)
|
| 37 |
+
print(OPENAI_API_IMAGE_EDIT_KEY)
|
| 38 |
+
print(GOOGLE_API_KEY)
|
| 39 |
+
|
| 40 |
+
class ScreenshotRequest(BaseModel):
|
| 41 |
+
url: str
|
| 42 |
+
width: Optional[int] = 1024
|
| 43 |
+
height: Optional[int] = 768
|
| 44 |
+
format: Optional[str] = "png"
|
| 45 |
+
custom_headers: Optional[Dict[str, str]] = {}
|
| 46 |
+
|
| 47 |
+
class AnalysisRequest(BaseModel):
|
| 48 |
+
text: str
|
| 49 |
+
image_data: Optional[str] = None
|
| 50 |
+
request_model_id: str = 'gpt-4.1-mini'
|
| 51 |
+
|
| 52 |
+
class OptimizationRequest(BaseModel):
|
| 53 |
+
text: str
|
| 54 |
+
image_data: str
|
| 55 |
+
suggestions: List[str]
|
| 56 |
+
request_model_id: str = 'gpt-image-1'
|
| 57 |
+
|
| 58 |
+
class TextOptimizationRequest(BaseModel):
|
| 59 |
+
original_feedback: str
|
| 60 |
+
user_input: str
|
| 61 |
+
request_model_id: str = 'gpt-4.1-mini'
|
| 62 |
+
|
| 63 |
+
class SearchRequest(BaseModel):
|
| 64 |
+
query: str
|
| 65 |
+
num_results: Optional[int] = 2
|
| 66 |
+
|
| 67 |
+
@app.post("/capture")
|
| 68 |
+
async def capture_screenshot(request: ScreenshotRequest):
|
| 69 |
+
try:
|
| 70 |
+
if not request.url:
|
| 71 |
+
raise HTTPException(status_code=400, detail="需要提供URL参数")
|
| 72 |
+
|
| 73 |
+
# 生成唯一的文件名
|
| 74 |
+
domain = urlparse(request.url).netloc.replace(".", "_")
|
| 75 |
+
timestamp = int(time.time() * 1000)
|
| 76 |
+
filename = f"{domain}_{timestamp}.{request.format}"
|
| 77 |
+
filepath = os.path.join(CACHE_DIR, filename)
|
| 78 |
+
|
| 79 |
+
logging.log(logging.INFO, f"开始为 {request.url} 生成截图...")
|
| 80 |
+
|
| 81 |
+
# 默认请求头
|
| 82 |
+
default_headers = {
|
| 83 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
|
| 84 |
+
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
| 85 |
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
|
| 86 |
+
"Connection": "keep-alive",
|
| 87 |
+
"Cache-Control": "max-age=0",
|
| 88 |
+
"Sec-Fetch-Dest": "document",
|
| 89 |
+
"Sec-Fetch-Mode": "navigate",
|
| 90 |
+
"Sec-Fetch-Site": "none",
|
| 91 |
+
"Sec-Fetch-User": "?1",
|
| 92 |
+
"Upgrade-Insecure-Requests": "1"
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
# 合并默认请求头和自定义请求头
|
| 96 |
+
headers = {**default_headers, **(request.custom_headers or {})}
|
| 97 |
+
|
| 98 |
+
async with async_playwright() as p:
|
| 99 |
+
browser = await p.chromium.launch()
|
| 100 |
+
page = await browser.new_page()
|
| 101 |
+
|
| 102 |
+
# 设置视口大小
|
| 103 |
+
await page.set_viewport_size({
|
| 104 |
+
"width": request.width,
|
| 105 |
+
"height": request.height
|
| 106 |
+
})
|
| 107 |
+
|
| 108 |
+
# 设置请求头
|
| 109 |
+
await page.set_extra_http_headers(headers)
|
| 110 |
+
|
| 111 |
+
# 访问页面
|
| 112 |
+
await page.goto(request.url, wait_until="networkidle")
|
| 113 |
+
|
| 114 |
+
# 生成截图
|
| 115 |
+
await page.screenshot(path=filepath, type=request.format)
|
| 116 |
+
|
| 117 |
+
await browser.close()
|
| 118 |
+
|
| 119 |
+
logging.log(logging.DEBUG, f"截图完成: {filepath}")
|
| 120 |
+
|
| 121 |
+
# 返回截图URL
|
| 122 |
+
screenshot_url = f"/screenshots/{filename}"
|
| 123 |
+
|
| 124 |
+
return JSONResponse({
|
| 125 |
+
"success": True,
|
| 126 |
+
"imageUrl": screenshot_url,
|
| 127 |
+
"filename": filename
|
| 128 |
+
})
|
| 129 |
+
|
| 130 |
+
except Exception as e:
|
| 131 |
+
logging.error(f"截图过程中出错: {str(e)}")
|
| 132 |
+
raise HTTPException(status_code=500, detail=f"截图生成失败: {str(e)}")
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
@app.get("/health")
|
| 136 |
+
async def health_check():
|
| 137 |
+
return {"status": "ok"}
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
@app.get("/")
|
| 141 |
+
def index() -> FileResponse:
|
| 142 |
+
return FileResponse(path="static/index.html", media_type="text/html")
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
@app.post("/api/analyze")
|
| 146 |
+
async def analyze_feedback(request: AnalysisRequest):
|
| 147 |
+
try:
|
| 148 |
+
context = "以下是老板对设计的反馈内容:\n" + request.text
|
| 149 |
+
|
| 150 |
+
if request.image_data:
|
| 151 |
+
context += "\n\n用户还上传了设计图片作为参考:"
|
| 152 |
+
|
| 153 |
+
# 调用OpenAI API进行分析
|
| 154 |
+
response = await call_openai_api(
|
| 155 |
+
system_prompt='你是一位专业的设计顾问,擅长分析客户反馈,提取关键信息,并提供专业建议。请根据老板的���馈分析情绪值(用emoji表示),并结合给出的设计稿,给出三个具体的修改建议。每个建议应该包含一个标题和详细描述。首先你需要对老板的情绪进行解读,使用"情绪值:"开头并分为五类:1. 非常满意-😊😊😊 2. 比较满意-🙂🙂🙂 3. 一般般-😐😐😐 4. 不太满意-🙁🙁🙁 5. 非常不满意-😠😠😠,然后在下一行用一句话分析老板的情绪,以"情绪分析:"开头。随后,请以"修改建议:\n"开头,并以有序列表分三行说明三个具体建议,比如:"1. 提高对比度:xxx\n 2. ...\n 3. ...\n"。记得结合图片进行分析和提出修改建议。最后,你需要使用网页搜索来获取合适的参考UI设计案例。请在新的一行以"搜索内容:"开头,给出合适的搜索内容,以获取合适的参考设计案例,注意,你只能搜索UI设计案例。',
|
| 156 |
+
user_content=[
|
| 157 |
+
{"type": "input_text", "text": context},
|
| 158 |
+
*([{"type": "input_image", "image_url": request.image_data}] if request.image_data else [])
|
| 159 |
+
],
|
| 160 |
+
request_model_id=request.request_model_id
|
| 161 |
+
)
|
| 162 |
+
return JSONResponse(response)
|
| 163 |
+
except Exception as e:
|
| 164 |
+
logging.error(f'Error: {e}, traceback: {traceback.format_exc()}')
|
| 165 |
+
raise HTTPException(status_code=500, detail=f'Error: {e}, traceback: {traceback.format_exc()}')
|
| 166 |
+
|
| 167 |
+
@app.post("/api/optimize-design")
|
| 168 |
+
async def optimize_design(request: OptimizationRequest):
|
| 169 |
+
try:
|
| 170 |
+
# 构建图像生成提示词
|
| 171 |
+
prompt = f"基于以下设计反馈优化UI设计: {', '.join(request.suggestions)}"
|
| 172 |
+
|
| 173 |
+
# 处理图片数据
|
| 174 |
+
image_data = request.image_data
|
| 175 |
+
|
| 176 |
+
# 调用OpenAI图像编辑API
|
| 177 |
+
response = await call_openai_image_api(
|
| 178 |
+
image_data=image_data,
|
| 179 |
+
prompt=prompt,
|
| 180 |
+
request_model_id=request.request_model_id
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
return JSONResponse(response)
|
| 184 |
+
except Exception as e:
|
| 185 |
+
logging.error(f'Error: {e}, traceback: {traceback.format_exc()}')
|
| 186 |
+
raise HTTPException(status_code=500, detail=f'Error: {e}, traceback: {traceback.format_exc()}')
|
| 187 |
+
|
| 188 |
+
@app.post("/api/optimize-text")
|
| 189 |
+
async def optimize_text(request: TextOptimizationRequest):
|
| 190 |
+
try:
|
| 191 |
+
response = await call_openai_api(
|
| 192 |
+
system_prompt="你是一个专业的文案优化助手,擅长将简单直接的反馈转换为礼貌、专业且保持原意的表达方式。",
|
| 193 |
+
user_content=[{
|
| 194 |
+
"type": "input_text",
|
| 195 |
+
"text": f"原始反馈内容:{request.original_feedback}\n\n我想回复:{request.user_input}\n\n请优化我的回复内容,使其更加礼貌、专业,同时保持原始意思,增加一些共情和专业术语。"
|
| 196 |
+
}],
|
| 197 |
+
request_model_id=request.request_model_id
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
return JSONResponse(response)
|
| 201 |
+
except Exception as e:
|
| 202 |
+
logging.error(f'Error: {e}, traceback: {traceback.format_exc()}')
|
| 203 |
+
raise HTTPException(status_code=500, detail=f'Error: {e}, traceback: {traceback.format_exc()}')
|
| 204 |
+
|
| 205 |
+
@app.post("/api/search")
|
| 206 |
+
async def search_design_examples(request: SearchRequest):
|
| 207 |
+
try:
|
| 208 |
+
# 构建搜索查询
|
| 209 |
+
search_query = f"{request.query} UI设计"
|
| 210 |
+
|
| 211 |
+
# 调用Google Custom Search API
|
| 212 |
+
async with aiohttp.ClientSession() as session:
|
| 213 |
+
async with session.get(
|
| 214 |
+
"https://customsearch.googleapis.com/customsearch/v1",
|
| 215 |
+
params={
|
| 216 |
+
"key": GOOGLE_API_KEY,
|
| 217 |
+
"q": search_query,
|
| 218 |
+
"cx": SEARCH_ENGINE_ID,
|
| 219 |
+
"num": request.num_results
|
| 220 |
+
}
|
| 221 |
+
) as response:
|
| 222 |
+
if response.status != 200:
|
| 223 |
+
raise HTTPException(status_code=response.status, detail="Google Search API调用失败")
|
| 224 |
+
|
| 225 |
+
search_data = await response.json()
|
| 226 |
+
|
| 227 |
+
if not search_data.get("items"):
|
| 228 |
+
return JSONResponse({"items": []})
|
| 229 |
+
|
| 230 |
+
# 处理搜索结果
|
| 231 |
+
results = []
|
| 232 |
+
for item in search_data["items"]:
|
| 233 |
+
result = {
|
| 234 |
+
"title": item["title"].replace("</?b>", ""),
|
| 235 |
+
"link": item["link"],
|
| 236 |
+
"snippet": item.get("snippet", ""),
|
| 237 |
+
"image": None
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
# 尝试获取图片URL
|
| 241 |
+
if "pagemap" in item:
|
| 242 |
+
if "cse_image" in item["pagemap"]:
|
| 243 |
+
result["image"] = item["pagemap"]["cse_image"][0]["src"]
|
| 244 |
+
elif "cse_thumbnail" in item["pagemap"]:
|
| 245 |
+
result["image"] = item["pagemap"]["cse_thumbnail"][0]["src"]
|
| 246 |
+
|
| 247 |
+
# 如果没有图片,使用截图服务
|
| 248 |
+
if not result["image"]:
|
| 249 |
+
try:
|
| 250 |
+
screenshot_response = await capture_screenshot(ScreenshotRequest(
|
| 251 |
+
url=result["link"],
|
| 252 |
+
width=1024,
|
| 253 |
+
height=768,
|
| 254 |
+
format="png"
|
| 255 |
+
))
|
| 256 |
+
if isinstance(screenshot_response, dict) and "imageUrl" in screenshot_response:
|
| 257 |
+
result["image"] = screenshot_response["imageUrl"]
|
| 258 |
+
except Exception as e:
|
| 259 |
+
print(f"获取截图失败: {str(e)}")
|
| 260 |
+
# 使用默认图片
|
| 261 |
+
result["image"] = "https://img.freepik.com/free-vector/gradient-ui-ux-background_23-2149052117.jpg"
|
| 262 |
+
|
| 263 |
+
results.append(result)
|
| 264 |
+
|
| 265 |
+
return JSONResponse({"items": results})
|
| 266 |
+
|
| 267 |
+
except Exception as e:
|
| 268 |
+
logging.error(f'Error: {e}, traceback: {traceback.format_exc()}')
|
| 269 |
+
raise HTTPException(status_code=500, detail=f'Error: {e}, traceback: {traceback.format_exc()}')
|
| 270 |
+
|
| 271 |
+
async def call_openai_api(system_prompt: str, user_content: List[Dict], request_model_id='gpt-4.1-nano'):
|
| 272 |
+
headers = {
|
| 273 |
+
"Authorization": f"Bearer {OPENAI_API_KEY}",
|
| 274 |
+
"Content-Type": "application/json"
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
data = {
|
| 278 |
+
"model": request_model_id,
|
| 279 |
+
"input": [
|
| 280 |
+
{
|
| 281 |
+
"role": "system",
|
| 282 |
+
"content": [{"type": "input_text", "text": system_prompt}]
|
| 283 |
+
},
|
| 284 |
+
{
|
| 285 |
+
"role": "user",
|
| 286 |
+
"content": user_content
|
| 287 |
+
}
|
| 288 |
+
]
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
async with aiohttp.ClientSession() as session:
|
| 292 |
+
async with session.post("https://api.openai.com/v1/responses", headers=headers, json=data) as response:
|
| 293 |
+
if response.status != 200:
|
| 294 |
+
resp = await response.json()
|
| 295 |
+
logging.error(f'response: {resp}')
|
| 296 |
+
raise HTTPException(status_code=response.status, detail=f"OpenAI API调用失败, response: {resp}")
|
| 297 |
+
return await response.json()
|
| 298 |
+
|
| 299 |
+
async def call_openai_image_api(image_data: str, prompt: str, request_model_id='gpt-image-1'):
|
| 300 |
+
try:
|
| 301 |
+
# 从base64字符串中提取纯base64数据(如果包含前缀)
|
| 302 |
+
if image_data and 'base64,' in image_data:
|
| 303 |
+
image_data = image_data.split('base64,')[1]
|
| 304 |
+
|
| 305 |
+
logging.log(logging.INFO, f"Processing image data (first 100 chars): {image_data[:100]}")
|
| 306 |
+
|
| 307 |
+
# 将base64图片数据转换为文件对象
|
| 308 |
+
image_bytes = base64.b64decode(image_data)
|
| 309 |
+
image_file = BytesIO(image_bytes)
|
| 310 |
+
image_file.name = "original-design.png" # 设置文件名,与JS代码一致
|
| 311 |
+
|
| 312 |
+
# 创建OpenAI客户端
|
| 313 |
+
client = OpenAI(api_key=OPENAI_API_IMAGE_EDIT_KEY)
|
| 314 |
+
|
| 315 |
+
# 调用图像编辑API
|
| 316 |
+
response = client.images.edit(
|
| 317 |
+
model=request_model_id,
|
| 318 |
+
image=image_file,
|
| 319 |
+
prompt=prompt # 明确要求返回base64格式
|
| 320 |
+
)
|
| 321 |
+
|
| 322 |
+
# 获取生成的图片数据
|
| 323 |
+
if not response.data or len(response.data) == 0:
|
| 324 |
+
raise ValueError("No image data returned from API")
|
| 325 |
+
|
| 326 |
+
image_result = response.data[0]
|
| 327 |
+
|
| 328 |
+
# 返回与JS代码一致的格式
|
| 329 |
+
return {
|
| 330 |
+
"data": [{
|
| 331 |
+
"url": f"data:image/png;base64,{image_result.b64_json}",
|
| 332 |
+
"b64_json": image_result.b64_json
|
| 333 |
+
}]
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
except Exception as e:
|
| 337 |
+
logging.error(f'Error in call_openai_image_api: {e}, traceback: {traceback.format_exc()}')
|
| 338 |
+
raise HTTPException(
|
| 339 |
+
status_code=500,
|
| 340 |
+
detail=f'Error processing image: {str(e)}'
|
| 341 |
+
)
|
| 342 |
+
|
| 343 |
+
if __name__ == "__main__":
|
| 344 |
+
import uvicorn
|
| 345 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.104.1
|
| 2 |
+
uvicorn==0.24.0
|
| 3 |
+
python-multipart==0.0.6
|
| 4 |
+
aiohttp==3.9.1
|
| 5 |
+
playwright==1.40.0
|
| 6 |
+
python-dotenv==1.0.0
|
| 7 |
+
pydantic==2.5.2
|
| 8 |
+
requests==2.31.0
|
| 9 |
+
openai
|
service_readme.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 网站截图服务
|
| 2 |
+
|
| 3 |
+
这是一个使用 FastAPI 和 Playwright 构建的简单网站截图服务,可以生成任何网页的高质量截图。
|
| 4 |
+
|
| 5 |
+
## 功能特点
|
| 6 |
+
|
| 7 |
+
- 支持任何公开可访问的网页截图
|
| 8 |
+
- 可自定义截图尺寸
|
| 9 |
+
- 支持 PNG 格式输出
|
| 10 |
+
- 自动缓存生成的截图
|
| 11 |
+
- 提供 REST API 接口
|
| 12 |
+
- 异步处理,高性能
|
| 13 |
+
|
| 14 |
+
## 系统要求
|
| 15 |
+
|
| 16 |
+
- Python 3.9 或更高版本
|
| 17 |
+
- pip 包管理器
|
| 18 |
+
|
| 19 |
+
## 安装步骤
|
| 20 |
+
|
| 21 |
+
1. 克隆或下载本项目
|
| 22 |
+
|
| 23 |
+
2. 安装依赖
|
| 24 |
+
```bash
|
| 25 |
+
pip install -r requirements.txt
|
| 26 |
+
playwright install chromium
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
3. 启动服务
|
| 30 |
+
```bash
|
| 31 |
+
python screenshot_service.py
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
服务将在 `http://localhost:7860` 启动。
|
| 35 |
+
|
| 36 |
+
## API 使用说明
|
| 37 |
+
|
| 38 |
+
### 生成网页截图
|
| 39 |
+
|
| 40 |
+
**请求**:
|
| 41 |
+
|
| 42 |
+
```
|
| 43 |
+
POST /capture
|
| 44 |
+
Content-Type: application/json
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
**请求体**:
|
| 48 |
+
|
| 49 |
+
```json
|
| 50 |
+
{
|
| 51 |
+
"url": "https://example.com",
|
| 52 |
+
"width": 1024,
|
| 53 |
+
"height": 768,
|
| 54 |
+
"format": "png",
|
| 55 |
+
"custom_headers": {
|
| 56 |
+
"User-Agent": "Custom User Agent"
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
参数说明:
|
| 62 |
+
- `url`: 必填,要截图的网页地址
|
| 63 |
+
- `width`: 可选,截图宽度,默认 1024
|
| 64 |
+
- `height`: 可选,截图高度,默认 768
|
| 65 |
+
- `format`: 可选,图片格式,目前支持 png,默认为 png
|
| 66 |
+
- `custom_headers`: 可选,自定义请求头
|
| 67 |
+
|
| 68 |
+
**成功响应**:
|
| 69 |
+
|
| 70 |
+
```json
|
| 71 |
+
{
|
| 72 |
+
"success": true,
|
| 73 |
+
"imageUrl": "http://localhost:7860/screenshots/example_com_1633456789.png",
|
| 74 |
+
"filename": "example_com_1633456789.png"
|
| 75 |
+
}
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
**错误响应**:
|
| 79 |
+
|
| 80 |
+
```json
|
| 81 |
+
{
|
| 82 |
+
"detail": "截图生成失败: 导航超时,网页加载时间过长"
|
| 83 |
+
}
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
### 检查服务健康状态
|
| 87 |
+
|
| 88 |
+
**请求**:
|
| 89 |
+
|
| 90 |
+
```
|
| 91 |
+
GET /health
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
**响应**:
|
| 95 |
+
|
| 96 |
+
```json
|
| 97 |
+
{
|
| 98 |
+
"status": "ok"
|
| 99 |
+
}
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
## 在你的应用中使用
|
| 103 |
+
|
| 104 |
+
在客户端 JavaScript 中调用服务:
|
| 105 |
+
|
| 106 |
+
```javascript
|
| 107 |
+
async function getScreenshot(url) {
|
| 108 |
+
try {
|
| 109 |
+
const response = await fetch('http://localhost:7860/capture', {
|
| 110 |
+
method: 'POST',
|
| 111 |
+
headers: {
|
| 112 |
+
'Content-Type': 'application/json'
|
| 113 |
+
},
|
| 114 |
+
body: JSON.stringify({
|
| 115 |
+
url: url,
|
| 116 |
+
width: 1024,
|
| 117 |
+
height: 768
|
| 118 |
+
})
|
| 119 |
+
});
|
| 120 |
+
|
| 121 |
+
const data = await response.json();
|
| 122 |
+
return data.imageUrl;
|
| 123 |
+
} catch (error) {
|
| 124 |
+
console.error('截图服务请求失败:', error);
|
| 125 |
+
return null;
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
在 Python 中调用服务:
|
| 131 |
+
|
| 132 |
+
```python
|
| 133 |
+
import requests
|
| 134 |
+
|
| 135 |
+
def get_screenshot(url):
|
| 136 |
+
try:
|
| 137 |
+
response = requests.post(
|
| 138 |
+
'http://localhost:7860/capture',
|
| 139 |
+
json={
|
| 140 |
+
'url': url,
|
| 141 |
+
'width': 1024,
|
| 142 |
+
'height': 768
|
| 143 |
+
}
|
| 144 |
+
)
|
| 145 |
+
data = response.json()
|
| 146 |
+
return data['imageUrl']
|
| 147 |
+
except Exception as e:
|
| 148 |
+
print(f'截图服务请求失败: {str(e)}')
|
| 149 |
+
return None
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
## 部署建议
|
| 153 |
+
|
| 154 |
+
在生产环境部署时,建议:
|
| 155 |
+
|
| 156 |
+
1. 使用 Docker 容器化部署
|
| 157 |
+
2. 使用 Gunicorn 或 Uvicorn 作为生产级 ASGI 服务器
|
| 158 |
+
3. 设置合适的超时时间和内存限制
|
| 159 |
+
4. 配置 HTTPS 以保证安全性
|
| 160 |
+
5. 添加访问限制或身份验证
|
| 161 |
+
|
| 162 |
+
## Docker 部署
|
| 163 |
+
|
| 164 |
+
1. 构建镜像
|
| 165 |
+
```bash
|
| 166 |
+
docker build -t screenshot-service .
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
2. 运行容器
|
| 170 |
+
```bash
|
| 171 |
+
docker run -p 7860:7860 screenshot-service
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
## 许可证
|
| 175 |
+
|
| 176 |
+
MIT
|
static/feedback.html
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="zh-CN">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>反馈建议 - 解语花</title>
|
| 7 |
+
<link rel="stylesheet" href="/static/styles.css">
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div class="dark-container">
|
| 11 |
+
<header>
|
| 12 |
+
<div class="logo"><a href="/static/index.html">解语花</a></div>
|
| 13 |
+
</header>
|
| 14 |
+
|
| 15 |
+
<main class="feedback-page">
|
| 16 |
+
<section class="feedback-section">
|
| 17 |
+
<h2 class="section-title">情绪值</h2>
|
| 18 |
+
<div class="emotion-card">
|
| 19 |
+
<div class="emotion-icons">
|
| 20 |
+
<span class="emoji">😠</span>
|
| 21 |
+
<span class="emoji">😠</span>
|
| 22 |
+
<span class="emoji">😠</span>
|
| 23 |
+
</div>
|
| 24 |
+
<div class="emotion-text">老板对设计非常不满意,建议全面改进</div>
|
| 25 |
+
</div>
|
| 26 |
+
</section>
|
| 27 |
+
|
| 28 |
+
<section class="feedback-section">
|
| 29 |
+
<h2 class="section-title">修改建议</h2>
|
| 30 |
+
<div class="suggestions-container">
|
| 31 |
+
<div class="suggestion-card yellow-top">
|
| 32 |
+
<h3 class="suggestion-title">色彩调整</h3>
|
| 33 |
+
<p class="suggestion-text">在保持整体色调的基础上,加入一些明亮的点缀色,如淡黄色、淡紫蓝、淡红蓝微粉等。用于按钮、图标或重要文本,以增加吸引力。</p>
|
| 34 |
+
</div>
|
| 35 |
+
|
| 36 |
+
<div class="suggestion-card cyan-top">
|
| 37 |
+
<h3 class="suggestion-title">增加层级感</h3>
|
| 38 |
+
<p class="suggestion-text">利用饱和度(Glassmorphism)效果,为卡片/重要框添加模糊背景和阴影,营造出浮起感,引入3D元素或深浅变化,增加页面的深度和空间感。</p>
|
| 39 |
+
</div>
|
| 40 |
+
|
| 41 |
+
<div class="suggestion-card purple-top">
|
| 42 |
+
<h3 class="suggestion-title">增加互动性</h3>
|
| 43 |
+
<p class="suggestion-text">为按钮和图标添加悬停动画或点击动画,提供用户及时反馈。引入微动画(Micro-interactions),如加载动画、切换动画等等,使页面更具活力。</p>
|
| 44 |
+
</div>
|
| 45 |
+
</div>
|
| 46 |
+
</section>
|
| 47 |
+
|
| 48 |
+
<section class="feedback-section">
|
| 49 |
+
<h2 class="section-title">参考案例</h2>
|
| 50 |
+
<div class="examples-container">
|
| 51 |
+
<div class="example-card">
|
| 52 |
+
<img class="example-image" src="" alt="参考案例1">
|
| 53 |
+
<div class="example-content">
|
| 54 |
+
<p class="example-desc">加载中...</p>
|
| 55 |
+
<div class="example-source">来源: <a href="#" target="_blank">加载中...</a></div>
|
| 56 |
+
</div>
|
| 57 |
+
</div>
|
| 58 |
+
<div class="example-card">
|
| 59 |
+
<img class="example-image" src="" alt="参考案例2">
|
| 60 |
+
<div class="example-content">
|
| 61 |
+
<p class="example-desc">加载中...</p>
|
| 62 |
+
<div class="example-source">来源: <a href="#" target="_blank">加载中...</a></div>
|
| 63 |
+
</div>
|
| 64 |
+
</div>
|
| 65 |
+
</div>
|
| 66 |
+
</section>
|
| 67 |
+
|
| 68 |
+
<section class="feedback-section">
|
| 69 |
+
<h2 class="section-title">优化方案</h2>
|
| 70 |
+
<div class="optimization-container">
|
| 71 |
+
<div class="optimization-card">
|
| 72 |
+
<div class="optimization-header">
|
| 73 |
+
<h3 class="optimization-title">方案一:GPT优化</h3>
|
| 74 |
+
<button class="generate-btn" id="gpt4-btn">生成优化</button>
|
| 75 |
+
</div>
|
| 76 |
+
<div class="optimization-content" id="gpt4-content">
|
| 77 |
+
<div class="optimization-placeholder">
|
| 78 |
+
<p>基于GPT-4o的智能优化,结合老板反馈进行针对性修改</p>
|
| 79 |
+
</div>
|
| 80 |
+
</div>
|
| 81 |
+
</div>
|
| 82 |
+
|
| 83 |
+
<div class="optimization-card">
|
| 84 |
+
<div class="optimization-header">
|
| 85 |
+
<h3 class="optimization-title">方案二:Dalle优化</h3>
|
| 86 |
+
<button class="generate-btn" id="mj-btn">生成创意</button>
|
| 87 |
+
</div>
|
| 88 |
+
<div class="optimization-content" id="mj-content">
|
| 89 |
+
<div class="optimization-placeholder">
|
| 90 |
+
<p>基于Dalle的创意重构,提供全新设计灵感</p>
|
| 91 |
+
</div>
|
| 92 |
+
</div>
|
| 93 |
+
</div>
|
| 94 |
+
</div>
|
| 95 |
+
</section>
|
| 96 |
+
|
| 97 |
+
<section class="feedback-section">
|
| 98 |
+
<h2 class="section-title">润色</h2>
|
| 99 |
+
<div class="polished-card">
|
| 100 |
+
<textarea placeholder="输入你要回复老板的话" id="userInput"></textarea>
|
| 101 |
+
</div>
|
| 102 |
+
|
| 103 |
+
<!-- 生成后的文案框,初始隐藏 -->
|
| 104 |
+
<div class="result-card" id="resultCard" style="display: none;">
|
| 105 |
+
<div class="result-title">优化后的文案</div>
|
| 106 |
+
<div class="result-content" id="resultContent"></div>
|
| 107 |
+
</div>
|
| 108 |
+
</section>
|
| 109 |
+
|
| 110 |
+
<div class="button-container">
|
| 111 |
+
<button type="button" class="generate-button" id="generateBtn">生成</button>
|
| 112 |
+
</div>
|
| 113 |
+
|
| 114 |
+
<!-- Toast提示 -->
|
| 115 |
+
<div class="toast" id="toast">生成中...</div>
|
| 116 |
+
</main>
|
| 117 |
+
</div>
|
| 118 |
+
|
| 119 |
+
<script src="/static/script.js"></script>
|
| 120 |
+
</body>
|
| 121 |
+
</html>
|
static/index.html
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="zh-CN">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>解语花 - AI驱动的文案优化平台</title>
|
| 7 |
+
<link rel="stylesheet" href="/static/styles.css">
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
|
| 11 |
+
<div class="landing-container">
|
| 12 |
+
<header>
|
| 13 |
+
<div class="logo">解语花</div>
|
| 14 |
+
</header>
|
| 15 |
+
|
| 16 |
+
<main class="landing-content">
|
| 17 |
+
<div class="floating-avatar">
|
| 18 |
+
<div class="avatar-icon">
|
| 19 |
+
<img src="/static/assets/avatar.svg" alt="头像" class="avatar-img">
|
| 20 |
+
</div>
|
| 21 |
+
<div class="dynamic-text" id="dynamic-text">"丑"</div>
|
| 22 |
+
</div>
|
| 23 |
+
|
| 24 |
+
<div class="floating-feature">
|
| 25 |
+
<div class="feature-icon">
|
| 26 |
+
<img src="/static/assets/biao.png" alt="解码潜台词图标">
|
| 27 |
+
</div>
|
| 28 |
+
<div class="feature-text">解码潜台词</div>
|
| 29 |
+
</div>
|
| 30 |
+
|
| 31 |
+
<div class="flower-container">
|
| 32 |
+
<img src="/static/assets/flower.webp" alt="花朵" class="flower-img">
|
| 33 |
+
</div>
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
<div class="hero-title">
|
| 38 |
+
<div class="biao-container">
|
| 39 |
+
<img src="/static/assets/biao.png" alt="花朵" class="biao-img">
|
| 40 |
+
</div>
|
| 41 |
+
<p class="description">
|
| 42 |
+
「AI解码潜台词,揭穿《求生欲不强》,让您既能察言观色时,也能随心所欲地表达」<br>
|
| 43 |
+
翻译准确率99.9%,比谷歌不少点艺术,多点幽默。
|
| 44 |
+
</p>
|
| 45 |
+
<a href="/static/upload.html" class="cta-button">一键体验</a>
|
| 46 |
+
</div>
|
| 47 |
+
|
| 48 |
+
<div class="showcase-section">
|
| 49 |
+
<div class="device-wrapper">
|
| 50 |
+
<div class="showcase-device device1">
|
| 51 |
+
<img src="/static/assets/device1.jpg" alt="设备展示1">
|
| 52 |
+
</div>
|
| 53 |
+
<div class="showcase-device device2">
|
| 54 |
+
<img src="/static/assets/device2.jpg" alt="设备展示2">
|
| 55 |
+
</div>
|
| 56 |
+
<div class="showcase-device device3">
|
| 57 |
+
<img src="/static/assets/device3.jpg" alt="设备展示3">
|
| 58 |
+
</div>
|
| 59 |
+
<div class="showcase-device device4">
|
| 60 |
+
<img src="/static/assets/device4.jpg" alt="3D展示">
|
| 61 |
+
</div>
|
| 62 |
+
</div>
|
| 63 |
+
</div>
|
| 64 |
+
|
| 65 |
+
<div class="bottom-section">
|
| 66 |
+
<div class="subtitle-text">改稿PTSD? 不存在的!</div>
|
| 67 |
+
<h2 class="secondary-title">不同设计类型-AI老中医都能治</h2>
|
| 68 |
+
<p class="description-secondary">
|
| 69 |
+
「「UI界面逻辑混乱?品牌视觉割裂?营销图转化率低?
|
| 70 |
+
AI设计老中医上线——把脉图层结构,透视视觉动线,专治设计『我以为这样能过稿』综合症」
|
| 71 |
+
</p>
|
| 72 |
+
<a href="/static/upload.html" class="cta-button secondary-button">一键体验</a>
|
| 73 |
+
</div>
|
| 74 |
+
</main>
|
| 75 |
+
</div>
|
| 76 |
+
|
| 77 |
+
<script src="/static/script.js"></script>
|
| 78 |
+
</body>
|
| 79 |
+
</html>
|
static/script.js
ADDED
|
@@ -0,0 +1,946 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 2 |
+
// 添加基础URL配置
|
| 3 |
+
const BASE_URL = 'https://jasonnoy-url-thumbnail.hf.space/'; // 空字符串表示使用相对路径
|
| 4 |
+
|
| 5 |
+
// 动态文案循环显示 - 打字机效果
|
| 6 |
+
const dynamicText = document.getElementById('dynamic-text');
|
| 7 |
+
if (dynamicText) {
|
| 8 |
+
const texts = ['"丑"', '"不高级"', '"再试试"'];
|
| 9 |
+
let currentIndex = 0;
|
| 10 |
+
let isDeleting = false;
|
| 11 |
+
let charIndex = 0;
|
| 12 |
+
let typingSpeed = 150; // 打字速度
|
| 13 |
+
let pauseTime = 2000; // 完成后暂停时间,调整为2秒
|
| 14 |
+
|
| 15 |
+
function typeEffect() {
|
| 16 |
+
const currentText = texts[currentIndex];
|
| 17 |
+
|
| 18 |
+
if (!isDeleting) {
|
| 19 |
+
// 打字阶段
|
| 20 |
+
if (charIndex < currentText.length) {
|
| 21 |
+
dynamicText.textContent = currentText.substring(0, charIndex + 1);
|
| 22 |
+
charIndex++;
|
| 23 |
+
setTimeout(typeEffect, typingSpeed);
|
| 24 |
+
} else {
|
| 25 |
+
// 完成打字,等待删除
|
| 26 |
+
setTimeout(() => {
|
| 27 |
+
isDeleting = true;
|
| 28 |
+
typeEffect();
|
| 29 |
+
}, pauseTime);
|
| 30 |
+
}
|
| 31 |
+
} else {
|
| 32 |
+
// 删除阶段
|
| 33 |
+
if (charIndex > 0) {
|
| 34 |
+
dynamicText.textContent = currentText.substring(0, charIndex);
|
| 35 |
+
charIndex--;
|
| 36 |
+
setTimeout(typeEffect, typingSpeed / 2);
|
| 37 |
+
} else {
|
| 38 |
+
// 完成删除,切换到下一个文案
|
| 39 |
+
isDeleting = false;
|
| 40 |
+
currentIndex = (currentIndex + 1) % texts.length;
|
| 41 |
+
setTimeout(typeEffect, 500);
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
// 开始打字机效果
|
| 47 |
+
typeEffect();
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
// 添加花朵的微小浮动动画
|
| 51 |
+
const flowerImg = document.querySelector('.flower-img');
|
| 52 |
+
if (flowerImg) {
|
| 53 |
+
flowerImg.style.animation = 'floatFlower 4s ease-in-out infinite alternate';
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
// 添加浮动动画的CSS
|
| 57 |
+
const style = document.createElement('style');
|
| 58 |
+
style.textContent = `
|
| 59 |
+
@keyframes floatFlower {
|
| 60 |
+
0% {
|
| 61 |
+
transform: translateY(0) rotate(0);
|
| 62 |
+
}
|
| 63 |
+
100% {
|
| 64 |
+
transform: translateY(-15px) rotate(2deg);
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
`;
|
| 68 |
+
document.head.appendChild(style);
|
| 69 |
+
|
| 70 |
+
// 图片上传处理
|
| 71 |
+
const imageUploadArea = document.getElementById('image-upload');
|
| 72 |
+
const imageInput = document.getElementById('image-input');
|
| 73 |
+
|
| 74 |
+
if (imageUploadArea && imageInput) {
|
| 75 |
+
imageUploadArea.addEventListener('click', function() {
|
| 76 |
+
imageInput.click();
|
| 77 |
+
});
|
| 78 |
+
|
| 79 |
+
imageInput.addEventListener('change', function() {
|
| 80 |
+
if (this.files && this.files[0]) {
|
| 81 |
+
const reader = new FileReader();
|
| 82 |
+
|
| 83 |
+
reader.onload = function(e) {
|
| 84 |
+
// 移除上传按钮
|
| 85 |
+
while (imageUploadArea.firstChild) {
|
| 86 |
+
imageUploadArea.removeChild(imageUploadArea.firstChild);
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
// 添加预览图片
|
| 90 |
+
const img = document.createElement('img');
|
| 91 |
+
img.src = e.target.result;
|
| 92 |
+
imageUploadArea.appendChild(img);
|
| 93 |
+
imageUploadArea.classList.add('with-image');
|
| 94 |
+
|
| 95 |
+
// 存储图片数据到 sessionStorage 以便在反馈页面使用
|
| 96 |
+
sessionStorage.setItem('uploadedImage', e.target.result);
|
| 97 |
+
};
|
| 98 |
+
|
| 99 |
+
reader.readAsDataURL(this.files[0]);
|
| 100 |
+
}
|
| 101 |
+
});
|
| 102 |
+
|
| 103 |
+
// 拖放上传功能
|
| 104 |
+
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
| 105 |
+
imageUploadArea.addEventListener(eventName, preventDefaults, false);
|
| 106 |
+
});
|
| 107 |
+
|
| 108 |
+
function preventDefaults(e) {
|
| 109 |
+
e.preventDefault();
|
| 110 |
+
e.stopPropagation();
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
['dragenter', 'dragover'].forEach(eventName => {
|
| 114 |
+
imageUploadArea.addEventListener(eventName, function() {
|
| 115 |
+
imageUploadArea.classList.add('highlight');
|
| 116 |
+
}, false);
|
| 117 |
+
});
|
| 118 |
+
|
| 119 |
+
['dragleave', 'drop'].forEach(eventName => {
|
| 120 |
+
imageUploadArea.addEventListener(eventName, function() {
|
| 121 |
+
imageUploadArea.classList.remove('highlight');
|
| 122 |
+
}, false);
|
| 123 |
+
});
|
| 124 |
+
|
| 125 |
+
imageUploadArea.addEventListener('drop', function(e) {
|
| 126 |
+
const dt = e.dataTransfer;
|
| 127 |
+
const files = dt.files;
|
| 128 |
+
|
| 129 |
+
if (files && files[0]) {
|
| 130 |
+
imageInput.files = files;
|
| 131 |
+
|
| 132 |
+
const reader = new FileReader();
|
| 133 |
+
|
| 134 |
+
reader.onload = function(e) {
|
| 135 |
+
// 移除上传按钮
|
| 136 |
+
while (imageUploadArea.firstChild) {
|
| 137 |
+
imageUploadArea.removeChild(imageUploadArea.firstChild);
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
// 添加预览图片
|
| 141 |
+
const img = document.createElement('img');
|
| 142 |
+
img.src = e.target.result;
|
| 143 |
+
imageUploadArea.appendChild(img);
|
| 144 |
+
imageUploadArea.classList.add('with-image');
|
| 145 |
+
|
| 146 |
+
sessionStorage.setItem('uploadedImage', e.target.result);
|
| 147 |
+
console.log("image uploaded")
|
| 148 |
+
};
|
| 149 |
+
|
| 150 |
+
reader.readAsDataURL(files[0]);
|
| 151 |
+
}
|
| 152 |
+
}, false);
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
// 解析按钮处理
|
| 156 |
+
const resolveButton = document.getElementById('resolve-button');
|
| 157 |
+
const textInput = document.getElementById('text-input');
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
if (resolveButton && textInput) {
|
| 161 |
+
resolveButton.addEventListener('click', function() {
|
| 162 |
+
const text = textInput.value.trim();
|
| 163 |
+
// 检查是否有文本输入
|
| 164 |
+
if (text === '') {
|
| 165 |
+
alert('请输入老板反馈内容');
|
| 166 |
+
return;
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
// 显示加载状态
|
| 170 |
+
resolveButton.disabled = true;
|
| 171 |
+
|
| 172 |
+
// 有趣的加载文案
|
| 173 |
+
const loadingTexts = [
|
| 174 |
+
"检测到甲方第8版需求残留怨气...正在召唤阴阳师修改图层结界",
|
| 175 |
+
"监控到大象对话框散发红光!自动生成24K纯杠精防护罩..."
|
| 176 |
+
];
|
| 177 |
+
|
| 178 |
+
// 随机选择一个文案作为起点
|
| 179 |
+
let currentTextIndex = Math.floor(Math.random() * loadingTexts.length);
|
| 180 |
+
let currentText = loadingTexts[currentTextIndex];
|
| 181 |
+
let charIndex = 0;
|
| 182 |
+
let typingSpeed = 100; // 打字速度,从50ms增加到100ms
|
| 183 |
+
|
| 184 |
+
// 添加弹跳动画的CSS
|
| 185 |
+
if (!document.getElementById('bounce-animation-style')) {
|
| 186 |
+
const bounceStyle = document.createElement('style');
|
| 187 |
+
bounceStyle.id = 'bounce-animation-style';
|
| 188 |
+
bounceStyle.textContent = `
|
| 189 |
+
.wave-container {
|
| 190 |
+
position: relative;
|
| 191 |
+
white-space: nowrap;
|
| 192 |
+
}
|
| 193 |
+
.bounce-text {
|
| 194 |
+
display: inline-block;
|
| 195 |
+
position: relative;
|
| 196 |
+
transition: transform 0.3s ease-out;
|
| 197 |
+
}
|
| 198 |
+
`;
|
| 199 |
+
document.head.appendChild(bounceStyle);
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
// 创建一个容器来包含所有字符
|
| 203 |
+
const waveContainer = document.createElement('div');
|
| 204 |
+
waveContainer.className = 'wave-container';
|
| 205 |
+
resolveButton.innerHTML = '';
|
| 206 |
+
resolveButton.appendChild(waveContainer);
|
| 207 |
+
|
| 208 |
+
// 跟踪波峰位置
|
| 209 |
+
let peakPosition = -1;
|
| 210 |
+
let waveInterval;
|
| 211 |
+
|
| 212 |
+
// 打字机效果函数
|
| 213 |
+
function typeLoadingText() {
|
| 214 |
+
if (charIndex < currentText.length) {
|
| 215 |
+
// 创建一个span元素包装字符
|
| 216 |
+
const charSpan = document.createElement('span');
|
| 217 |
+
charSpan.className = 'bounce-text';
|
| 218 |
+
charSpan.textContent = currentText.charAt(charIndex);
|
| 219 |
+
|
| 220 |
+
// 添加到容器
|
| 221 |
+
waveContainer.appendChild(charSpan);
|
| 222 |
+
|
| 223 |
+
charIndex++;
|
| 224 |
+
setTimeout(typeLoadingText, typingSpeed);
|
| 225 |
+
|
| 226 |
+
// 如果这是第一个字符,开始波动动画
|
| 227 |
+
if (charIndex === 1) {
|
| 228 |
+
startWaveAnimation();
|
| 229 |
+
}
|
| 230 |
+
} else {
|
| 231 |
+
// 当前文案打完,等待一段时间后切换到下一个文案
|
| 232 |
+
setTimeout(() => {
|
| 233 |
+
// 清除波动动画
|
| 234 |
+
if (waveInterval) {
|
| 235 |
+
clearInterval(waveInterval);
|
| 236 |
+
waveInterval = null;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
// 切换到下一个文案
|
| 240 |
+
charIndex = 0;
|
| 241 |
+
currentTextIndex = (currentTextIndex + 1) % loadingTexts.length;
|
| 242 |
+
currentText = loadingTexts[currentTextIndex];
|
| 243 |
+
|
| 244 |
+
// 清空容器,准备下一个文案
|
| 245 |
+
waveContainer.innerHTML = '';
|
| 246 |
+
|
| 247 |
+
typeLoadingText();
|
| 248 |
+
}, 4000); // 完整显示时间
|
| 249 |
+
}
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
// 波动动画函数
|
| 253 |
+
function startWaveAnimation() {
|
| 254 |
+
// 清除���前的间隔
|
| 255 |
+
if (waveInterval) clearInterval(waveInterval);
|
| 256 |
+
|
| 257 |
+
// 启动波动动画
|
| 258 |
+
waveInterval = setInterval(() => {
|
| 259 |
+
// 获取所有字符
|
| 260 |
+
const chars = waveContainer.querySelectorAll('.bounce-text');
|
| 261 |
+
if (chars.length === 0) return;
|
| 262 |
+
|
| 263 |
+
// 移动波峰位置
|
| 264 |
+
peakPosition = (peakPosition + 1) % chars.length;
|
| 265 |
+
|
| 266 |
+
// 应用变换
|
| 267 |
+
chars.forEach((char, index) => {
|
| 268 |
+
if (index === peakPosition) {
|
| 269 |
+
// 波峰位置
|
| 270 |
+
char.style.transform = 'translateY(-8px)';
|
| 271 |
+
} else if (Math.abs(index - peakPosition) === 1) {
|
| 272 |
+
// 波峰旁边的字符稍微上移
|
| 273 |
+
char.style.transform = 'translateY(-4px)';
|
| 274 |
+
} else {
|
| 275 |
+
// 其他字符回到原位
|
| 276 |
+
char.style.transform = 'translateY(0)';
|
| 277 |
+
}
|
| 278 |
+
});
|
| 279 |
+
}, 100); // 每100ms更新一次波峰位置
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
// 开始打字机效果
|
| 283 |
+
typeLoadingText();
|
| 284 |
+
|
| 285 |
+
// 存储文本到 sessionStorage
|
| 286 |
+
sessionStorage.setItem('uploadedText', text);
|
| 287 |
+
console.log(text)
|
| 288 |
+
|
| 289 |
+
// 获取上传的图片数据
|
| 290 |
+
const uploadedImage = sessionStorage.getItem('uploadedImage');
|
| 291 |
+
// 分析反馈内容,生成解读结果
|
| 292 |
+
analyzeUserInput(text, uploadedImage).then(() => {
|
| 293 |
+
// 解读完成后跳转到反馈页面
|
| 294 |
+
window.location.href = 'feedback.html';
|
| 295 |
+
}).catch(error => {
|
| 296 |
+
console.error('解读失败:', error);
|
| 297 |
+
alert('解读失败,请重试');
|
| 298 |
+
resolveButton.disabled = false;
|
| 299 |
+
resolveButton.textContent = '一键解读';
|
| 300 |
+
});
|
| 301 |
+
});
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
// 分析用户输入和图片的函数
|
| 306 |
+
async function analyzeUserInput(text, imageData) {
|
| 307 |
+
try {
|
| 308 |
+
const health = await fetch(`${BASE_URL}health`)
|
| 309 |
+
console.log(health)
|
| 310 |
+
const response = await fetch(`${BASE_URL}api/analyze`, {
|
| 311 |
+
method: 'POST',
|
| 312 |
+
headers: {
|
| 313 |
+
'Content-Type': 'application/json'
|
| 314 |
+
},
|
| 315 |
+
body: JSON.stringify({
|
| 316 |
+
text: text,
|
| 317 |
+
image_data: imageData,
|
| 318 |
+
request_model_id: 'gpt-4.1-mini'
|
| 319 |
+
})
|
| 320 |
+
});
|
| 321 |
+
console.log(response)
|
| 322 |
+
const data = await response.json();
|
| 323 |
+
console.log(data)
|
| 324 |
+
const rep = data['output'][0]['content'][0]['text'];
|
| 325 |
+
sessionStorage.setItem('analysisResult', rep);
|
| 326 |
+
} catch (error) {
|
| 327 |
+
console.error('分析失败:', error);
|
| 328 |
+
// 使用默认的分析结果
|
| 329 |
+
const defaultAnalysis = `情绪值:😠😠😠\n\n
|
| 330 |
+
修改建议:
|
| 331 |
+
1. 色彩调整:在保持整体色调的基础上,加入一些明亮的点缀色,如淡黄色、淡紫蓝、淡红蓝微粉等。用于按钮、图标或重要文本,以增加吸引力。
|
| 332 |
+
2. 增加层级感:利用饱和度(Glassmorphism)效果,为卡片/重要框添加模糊背景和阴影,营造出浮起感,引入3D元素或深浅变化,增加页面的深度和空间感。
|
| 333 |
+
3. 增加互动性:为按钮和图标添加悬停动画或点击动画,提供用户及时反馈。引入微动画(Micro-interactions),如加载动画、切换动画等等,使页面更具活力。`;
|
| 334 |
+
|
| 335 |
+
sessionStorage.setItem('analysisResult', defaultAnalysis);
|
| 336 |
+
}
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
// 示例图片添加到参考案例
|
| 340 |
+
const exampleCards = document.querySelectorAll('.example-card');
|
| 341 |
+
if (exampleCards.length > 0) {
|
| 342 |
+
// 为示例卡片添加示例图片背景
|
| 343 |
+
const exampleBgs = [
|
| 344 |
+
'linear-gradient(45deg, #2d2d4e, #1e1e30)',
|
| 345 |
+
'linear-gradient(45deg, #2e2e2e, #1a1a1a)',
|
| 346 |
+
'linear-gradient(45deg, #332e42, #1f1b30)'
|
| 347 |
+
];
|
| 348 |
+
|
| 349 |
+
exampleCards.forEach((card, index) => {
|
| 350 |
+
card.style.background = exampleBgs[index % exampleBgs.length];
|
| 351 |
+
});
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
// 加载并显示反馈页面的分析结果
|
| 355 |
+
function loadAnalysisResult() {
|
| 356 |
+
const analysisResult = sessionStorage.getItem('analysisResult');
|
| 357 |
+
console.log(analysisResult)
|
| 358 |
+
if (!analysisResult) return;
|
| 359 |
+
|
| 360 |
+
try {
|
| 361 |
+
// 设置情绪值
|
| 362 |
+
if (analysisResult) {
|
| 363 |
+
const emotionIcons = document.querySelector('.emotion-icons');
|
| 364 |
+
const emotionText = document.querySelector('.emotion-text');
|
| 365 |
+
|
| 366 |
+
if (emotionIcons && emotionText) {
|
| 367 |
+
// 清空图标区域
|
| 368 |
+
emotionIcons.innerHTML = '';
|
| 369 |
+
|
| 370 |
+
// 提取情绪值部分
|
| 371 |
+
const emojiText = analysisResult.split('情绪值:')[1].split('\n')[0];
|
| 372 |
+
|
| 373 |
+
const emotionAnalyse = analysisResult.split('情绪分析:')[1].split('\n')[0];
|
| 374 |
+
|
| 375 |
+
// 提取emoji
|
| 376 |
+
const emojis = [emojiText.codePointAt(0), emojiText.codePointAt(2), emojiText.codePointAt(4)];
|
| 377 |
+
console.log(emojis)
|
| 378 |
+
emojis.forEach(emoji => {
|
| 379 |
+
console.log(emoji)
|
| 380 |
+
})
|
| 381 |
+
|
| 382 |
+
if (emojis) {
|
| 383 |
+
// 只显示3个emoji,确保一致性
|
| 384 |
+
const displayEmojis = emojis.slice(0, 3);
|
| 385 |
+
displayEmojis.forEach(emoji => {
|
| 386 |
+
const span = document.createElement('span');
|
| 387 |
+
span.className = 'emoji';
|
| 388 |
+
span.textContent = String.fromCodePoint(emoji);
|
| 389 |
+
emotionIcons.appendChild(span);
|
| 390 |
+
});
|
| 391 |
+
} else {
|
| 392 |
+
// 默认情绪
|
| 393 |
+
for (let i = 0; i < 3; i++) {
|
| 394 |
+
const span = document.createElement('span');
|
| 395 |
+
span.className = 'emoji';
|
| 396 |
+
span.textContent = '😠';
|
| 397 |
+
emotionIcons.appendChild(span);
|
| 398 |
+
}
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
// 设置文字描述
|
| 402 |
+
// 查找"情绪分析:"部分
|
| 403 |
+
let textDescription = '';
|
| 404 |
+
if (emotionAnalyse) {
|
| 405 |
+
textDescription = emotionAnalyse.trim();
|
| 406 |
+
} else {
|
| 407 |
+
// 如果没有情绪分析部分,根据emoji生成默认文本
|
| 408 |
+
if (emojis && emojis[0] === '😠') {
|
| 409 |
+
textDescription = '老板对设计非常不满意,建议全面改进';
|
| 410 |
+
} else if (emojis && emojis[0] === '🙁') {
|
| 411 |
+
textDescription = '老板对设计不太满意,需要较多改进';
|
| 412 |
+
} else if (emojis && emojis[0] === '😐') {
|
| 413 |
+
textDescription = '老板对设计感觉一般,有改进空间';
|
| 414 |
+
} else if (emojis && emojis[0] === '🙂') {
|
| 415 |
+
textDescription = '老板对设计比较满意,小幅改进即可';
|
| 416 |
+
} else if (emojis && emojis[0] === '😊') {
|
| 417 |
+
textDescription = '老板对设计非常满意,细节优化即可';
|
| 418 |
+
} else {
|
| 419 |
+
textDescription = '需要基于反馈进行改进';
|
| 420 |
+
}
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
emotionText.textContent = textDescription;
|
| 424 |
+
}
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
// 提取修改建议
|
| 428 |
+
const suggestionsContainer = document.querySelector('.suggestions-container');
|
| 429 |
+
if (suggestionsContainer) {
|
| 430 |
+
try {
|
| 431 |
+
// 使用split提取修改建议部分
|
| 432 |
+
const suggestionsText = analysisResult.split('修改建议:')[1].split('搜索内容:')[0];
|
| 433 |
+
|
| 434 |
+
if (suggestionsText) {
|
| 435 |
+
// 提取三个建议
|
| 436 |
+
const suggestionLines = suggestionsText.trim().split('\n');
|
| 437 |
+
const suggestions = [];
|
| 438 |
+
|
| 439 |
+
// 处理每一行建议
|
| 440 |
+
for (let i = 0; i < suggestionLines.length; i++) {
|
| 441 |
+
const line = suggestionLines[i].trim();
|
| 442 |
+
if (line.match(/^\d+\.\s/)) {
|
| 443 |
+
// 找到了序号开头的行,这是建议的标题和内容
|
| 444 |
+
const suggestionText = line.replace(/^\d+\.\s/, '');
|
| 445 |
+
|
| 446 |
+
// 拆分标题和内容(假设冒号或:分隔)
|
| 447 |
+
let title = suggestionText;
|
| 448 |
+
let content = '';
|
| 449 |
+
|
| 450 |
+
if (suggestionText.includes(':')) {
|
| 451 |
+
[title, content] = suggestionText.split(':', 2);
|
| 452 |
+
} else if (suggestionText.includes(':')) {
|
| 453 |
+
[title, content] = suggestionText.split(':', 2);
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
suggestions.push({
|
| 457 |
+
title: title.trim(),
|
| 458 |
+
content: content.trim()
|
| 459 |
+
});
|
| 460 |
+
|
| 461 |
+
// 最多收集三个建议
|
| 462 |
+
if (suggestions.length >= 3) break;
|
| 463 |
+
}
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
// 更新建议卡片
|
| 467 |
+
if (suggestions.length > 0) {
|
| 468 |
+
const cardStyles = ['yellow-top', 'cyan-top', 'purple-top'];
|
| 469 |
+
suggestionsContainer.innerHTML = '';
|
| 470 |
+
|
| 471 |
+
suggestions.forEach((suggestion, index) => {
|
| 472 |
+
const cardStyle = cardStyles[index % cardStyles.length];
|
| 473 |
+
const card = document.createElement('div');
|
| 474 |
+
card.className = `suggestion-card ${cardStyle}`;
|
| 475 |
+
|
| 476 |
+
const titleElement = document.createElement('h3');
|
| 477 |
+
titleElement.className = 'suggestion-title';
|
| 478 |
+
titleElement.textContent = suggestion.title;
|
| 479 |
+
|
| 480 |
+
const textElement = document.createElement('p');
|
| 481 |
+
textElement.className = 'suggestion-text';
|
| 482 |
+
textElement.textContent = suggestion.content;
|
| 483 |
+
|
| 484 |
+
card.appendChild(titleElement);
|
| 485 |
+
card.appendChild(textElement);
|
| 486 |
+
suggestionsContainer.appendChild(card);
|
| 487 |
+
});
|
| 488 |
+
|
| 489 |
+
// 加载参考案例
|
| 490 |
+
loadReferenceExamples(suggestions);
|
| 491 |
+
}
|
| 492 |
+
}
|
| 493 |
+
} catch (error) {
|
| 494 |
+
console.error('解析建议失败:', error);
|
| 495 |
+
}
|
| 496 |
+
}
|
| 497 |
+
} catch (error) {
|
| 498 |
+
console.error('解析分析结果失败:', error);
|
| 499 |
+
}
|
| 500 |
+
}
|
| 501 |
+
|
| 502 |
+
// 根据建议加载参考案例
|
| 503 |
+
async function loadReferenceExamples(suggestions) {
|
| 504 |
+
const analysisResult = sessionStorage.getItem('analysisResult');
|
| 505 |
+
const searchContent = analysisResult.split('搜索内容:')[1].trim();
|
| 506 |
+
const examplesContainer = document.querySelector('.examples-container');
|
| 507 |
+
if (!examplesContainer) return;
|
| 508 |
+
|
| 509 |
+
// 获取现有的示例卡片元素
|
| 510 |
+
const exampleCards = examplesContainer.querySelectorAll('.example-card');
|
| 511 |
+
if (exampleCards.length !== 2) {
|
| 512 |
+
console.error('未找到预期的两个示例卡片元素');
|
| 513 |
+
return;
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
try {
|
| 517 |
+
// 显示加载状态
|
| 518 |
+
exampleCards.forEach(card => {
|
| 519 |
+
const img = card.querySelector('.example-image');
|
| 520 |
+
const desc = card.querySelector('.example-desc');
|
| 521 |
+
const sourceLink = card.querySelector('.example-source a');
|
| 522 |
+
|
| 523 |
+
if (img) img.src = '';
|
| 524 |
+
if (desc) desc.textContent = '正在搜索设计参考案例...';
|
| 525 |
+
if (sourceLink) {
|
| 526 |
+
sourceLink.href = '#';
|
| 527 |
+
sourceLink.textContent = '加载中...';
|
| 528 |
+
}
|
| 529 |
+
});
|
| 530 |
+
|
| 531 |
+
// 获取原始反馈文本和上传的图片
|
| 532 |
+
const uploadedText = sessionStorage.getItem('uploadedText') || '';
|
| 533 |
+
const uploadedImage = sessionStorage.getItem('uploadedImage');
|
| 534 |
+
|
| 535 |
+
// 调用后端搜索API
|
| 536 |
+
const response = await fetch(`${BASE_URL}api/search`, {
|
| 537 |
+
method: 'POST',
|
| 538 |
+
headers: {
|
| 539 |
+
'Content-Type': 'application/json'
|
| 540 |
+
},
|
| 541 |
+
body: JSON.stringify({
|
| 542 |
+
query: searchContent,
|
| 543 |
+
num_results: 2
|
| 544 |
+
})
|
| 545 |
+
});
|
| 546 |
+
|
| 547 |
+
if (!response.ok) {
|
| 548 |
+
throw new Error('搜索请求失败');
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
const searchData = await response.json();
|
| 552 |
+
|
| 553 |
+
if (!searchData.items || searchData.items.length === 0) {
|
| 554 |
+
throw new Error('未找到搜索结果');
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
// 处理搜索结果
|
| 558 |
+
const examples = searchData.items.map(result => ({
|
| 559 |
+
title: result.title,
|
| 560 |
+
sourceUrl: result.link,
|
| 561 |
+
description: result.snippet || '',
|
| 562 |
+
image: result.image || ''
|
| 563 |
+
}));
|
| 564 |
+
|
| 565 |
+
// 使用GPT生成描述
|
| 566 |
+
for (let i = 0; i < examples.length; i++) {
|
| 567 |
+
const example = examples[i];
|
| 568 |
+
try {
|
| 569 |
+
const response = await fetch(`${BASE_URL}api/optimize-text`, {
|
| 570 |
+
method: 'POST',
|
| 571 |
+
headers: {
|
| 572 |
+
'Content-Type': 'application/json'
|
| 573 |
+
},
|
| 574 |
+
body: JSON.stringify({
|
| 575 |
+
original_feedback: uploadedText,
|
| 576 |
+
user_input: `请分析这个设计案例:${example.title}\n${example.description}\n\n基于原始设计反馈和修改建议,详细分析为什么这个设计案例可以解决用户面临的设计问题。描述要专业、具体,并关注设计细节。`,
|
| 577 |
+
request_model_id: 'gpt-4.1-mini'
|
| 578 |
+
})
|
| 579 |
+
});
|
| 580 |
+
|
| 581 |
+
if (response.ok) {
|
| 582 |
+
const data = await response.json();
|
| 583 |
+
if (data.output && data.output[0].content[0].text) {
|
| 584 |
+
example.description = data.output[0].content[0].text.trim();
|
| 585 |
+
}
|
| 586 |
+
}
|
| 587 |
+
} catch (gptError) {
|
| 588 |
+
console.error('生成案例描述失败:', gptError);
|
| 589 |
+
// 使用默认描述
|
| 590 |
+
example.description = i === 0 ?
|
| 591 |
+
'这个案例展示了清晰的视觉层次结构和吸引人的色彩搭配,能有效解决原设计中的平面感问题。通过对比度增强和微妙的动效,它能提高用户体验和互动性,同时保持整体设计的专业性。' :
|
| 592 |
+
'该设计运用了精细的色彩渐变和现代化的UI元素,完美解决了原始设计中的视觉单调问题。透过精心安排的布局和适当的留白,创造出流畅的用户浏览体验,同时增加了设计的精致感和专业度。';
|
| 593 |
+
}
|
| 594 |
+
}
|
| 595 |
+
|
| 596 |
+
// 更新UI
|
| 597 |
+
examples.forEach((example, index) => {
|
| 598 |
+
const card = exampleCards[index];
|
| 599 |
+
if (!card) return;
|
| 600 |
+
|
| 601 |
+
const img = card.querySelector('.example-image');
|
| 602 |
+
const desc = card.querySelector('.example-desc');
|
| 603 |
+
const source = card.querySelector('.example-source');
|
| 604 |
+
|
| 605 |
+
if (img) {
|
| 606 |
+
img.src = example.image;
|
| 607 |
+
img.alt = example.title;
|
| 608 |
+
}
|
| 609 |
+
|
| 610 |
+
if (desc) {
|
| 611 |
+
desc.textContent = example.description;
|
| 612 |
+
}
|
| 613 |
+
|
| 614 |
+
if (source) {
|
| 615 |
+
const sourceLink = source.querySelector('a');
|
| 616 |
+
if (sourceLink) {
|
| 617 |
+
sourceLink.href = example.sourceUrl;
|
| 618 |
+
sourceLink.textContent = example.title;
|
| 619 |
+
}
|
| 620 |
+
}
|
| 621 |
+
});
|
| 622 |
+
|
| 623 |
+
} catch (error) {
|
| 624 |
+
console.error('加载参考案例失败:', error);
|
| 625 |
+
|
| 626 |
+
// 在错误情况下更新UI
|
| 627 |
+
exampleCards.forEach(card => {
|
| 628 |
+
const desc = card.querySelector('.example-desc');
|
| 629 |
+
if (desc) {
|
| 630 |
+
desc.textContent = '加载参考案例失败,请刷新重试';
|
| 631 |
+
desc.style.color = '#ff6b6b';
|
| 632 |
+
}
|
| 633 |
+
});
|
| 634 |
+
}
|
| 635 |
+
}
|
| 636 |
+
|
| 637 |
+
// 当在反馈页面时,加载分析结果
|
| 638 |
+
if (window.location.pathname.includes('feedback.html')) {
|
| 639 |
+
loadAnalysisResult();
|
| 640 |
+
|
| 641 |
+
// 优化方案功能
|
| 642 |
+
setupOptimizationButtons();
|
| 643 |
+
}
|
| 644 |
+
|
| 645 |
+
// 设置优化方案按钮功能
|
| 646 |
+
function setupOptimizationButtons() {
|
| 647 |
+
const gpt4Button = document.getElementById('gpt4-btn');
|
| 648 |
+
const mjButton = document.getElementById('mj-btn');
|
| 649 |
+
|
| 650 |
+
if (gpt4Button) {
|
| 651 |
+
gpt4Button.addEventListener('click', function() {
|
| 652 |
+
generateGPT4Optimization();
|
| 653 |
+
});
|
| 654 |
+
}
|
| 655 |
+
|
| 656 |
+
if (mjButton) {
|
| 657 |
+
mjButton.addEventListener('click', function() {
|
| 658 |
+
generateDalleOptimization();
|
| 659 |
+
});
|
| 660 |
+
}
|
| 661 |
+
}
|
| 662 |
+
|
| 663 |
+
// 使用GPT-4生成优化方案
|
| 664 |
+
async function generateGPT4Optimization() {
|
| 665 |
+
const gpt4Button = document.getElementById('gpt4-btn');
|
| 666 |
+
const gpt4Content = document.getElementById('gpt4-content');
|
| 667 |
+
|
| 668 |
+
if (!gpt4Button || !gpt4Content) return;
|
| 669 |
+
|
| 670 |
+
try {
|
| 671 |
+
// 禁用按钮
|
| 672 |
+
gpt4Button.disabled = true;
|
| 673 |
+
gpt4Button.textContent = '生成中...';
|
| 674 |
+
|
| 675 |
+
// 显示加载动画
|
| 676 |
+
showLoadingState(gpt4Content, '正在生成优化方案,请稍候...');
|
| 677 |
+
|
| 678 |
+
// 获取原始反馈和图片
|
| 679 |
+
const uploadedText = sessionStorage.getItem('uploadedText') || '';
|
| 680 |
+
const uploadedImage = sessionStorage.getItem('uploadedImage');
|
| 681 |
+
const analysisResult = sessionStorage.getItem('analysisResult') || '';
|
| 682 |
+
|
| 683 |
+
if (!uploadedImage) {
|
| 684 |
+
throw new Error('未找到原始设计图片');
|
| 685 |
+
}
|
| 686 |
+
|
| 687 |
+
// 提取修改建议
|
| 688 |
+
let suggestions = [];
|
| 689 |
+
if (analysisResult.includes('修改建议:')) {
|
| 690 |
+
const suggestionsText = analysisResult.split('修改建议:')[1].split('搜索内容:')[0];
|
| 691 |
+
suggestions = suggestionsText
|
| 692 |
+
.split('\n')
|
| 693 |
+
.filter(line => line.match(/^\d+\./))
|
| 694 |
+
.map(line => line.replace(/^\d+\.\s+/, '').trim());
|
| 695 |
+
}
|
| 696 |
+
|
| 697 |
+
// 调用后端API
|
| 698 |
+
const response = await fetch(`${BASE_URL}api/optimize-design`, {
|
| 699 |
+
method: 'POST',
|
| 700 |
+
headers: {
|
| 701 |
+
'Content-Type': 'application/json'
|
| 702 |
+
},
|
| 703 |
+
body: JSON.stringify({
|
| 704 |
+
text: uploadedText,
|
| 705 |
+
image_data: uploadedImage,
|
| 706 |
+
suggestions: suggestions,
|
| 707 |
+
request_model_id: 'gpt-image-1'
|
| 708 |
+
})
|
| 709 |
+
});
|
| 710 |
+
|
| 711 |
+
if (!response.ok) {
|
| 712 |
+
throw new Error('优化请求失败');
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
const data = await response.json();
|
| 716 |
+
|
| 717 |
+
if (data.data && data.data[0] && data.data[0].url) {
|
| 718 |
+
// 展示生成的图片
|
| 719 |
+
showOptimizationResult(gpt4Content, data.data[0].url);
|
| 720 |
+
gpt4Button.disabled = false;
|
| 721 |
+
gpt4Button.textContent = '重新生成';
|
| 722 |
+
} else {
|
| 723 |
+
throw new Error('API未返回图片数据');
|
| 724 |
+
}
|
| 725 |
+
|
| 726 |
+
} catch (error) {
|
| 727 |
+
console.error('生成优化方案失败:', error);
|
| 728 |
+
gpt4Content.innerHTML = `
|
| 729 |
+
<div class="optimization-placeholder">
|
| 730 |
+
<p style="color: #ff6b6b;">生成失败: ${error.message || '请稍后重试'}</p>
|
| 731 |
+
</div>
|
| 732 |
+
`;
|
| 733 |
+
gpt4Button.disabled = false;
|
| 734 |
+
gpt4Button.textContent = '重试';
|
| 735 |
+
}
|
| 736 |
+
}
|
| 737 |
+
|
| 738 |
+
async function generateDalleOptimization() {
|
| 739 |
+
const mjButton = document.getElementById('mj-btn');
|
| 740 |
+
const mjContent = document.getElementById('mj-content');
|
| 741 |
+
if (!mjButton || !mjContent) return;
|
| 742 |
+
|
| 743 |
+
try {
|
| 744 |
+
mjButton.disabled = true;
|
| 745 |
+
mjButton.textContent = '生成中...';
|
| 746 |
+
showLoadingState(mjContent, '正在优化设计,请稍候...');
|
| 747 |
+
|
| 748 |
+
// 获取原始反馈和建议
|
| 749 |
+
const analysisResult = sessionStorage.getItem('analysisResult') || '';
|
| 750 |
+
let suggestions = [];
|
| 751 |
+
if (analysisResult.includes('修改建议:')) {
|
| 752 |
+
const suggestionsText = analysisResult.split('修改建议:')[1].split('搜索内容:')[0];
|
| 753 |
+
suggestions = suggestionsText
|
| 754 |
+
.split('\n')
|
| 755 |
+
.filter(line => line.match(/^\d+\./))
|
| 756 |
+
.map(line => line.replace(/^\d+\.\s+/, '').trim());
|
| 757 |
+
}
|
| 758 |
+
|
| 759 |
+
// 获取原始图片
|
| 760 |
+
const uploadedImage = sessionStorage.getItem('uploadedImage');
|
| 761 |
+
if (!uploadedImage) {
|
| 762 |
+
throw new Error('未找到原始设计图片');
|
| 763 |
+
}
|
| 764 |
+
|
| 765 |
+
// 构建提示词
|
| 766 |
+
const prompt = `基于以下设计反馈优化UI设计: ${suggestions.join(', ')}`;
|
| 767 |
+
|
| 768 |
+
// 调用后端API
|
| 769 |
+
const response = await fetch(`${BASE_URL}api/optimize-design`, {
|
| 770 |
+
method: 'POST',
|
| 771 |
+
headers: {
|
| 772 |
+
'Content-Type': 'application/json'
|
| 773 |
+
},
|
| 774 |
+
body: JSON.stringify({
|
| 775 |
+
text: analysisResult,
|
| 776 |
+
image_data: uploadedImage,
|
| 777 |
+
suggestions: suggestions,
|
| 778 |
+
request_model_id: 'dall-e-2'
|
| 779 |
+
})
|
| 780 |
+
});
|
| 781 |
+
|
| 782 |
+
if (!response.ok) {
|
| 783 |
+
throw new Error('优化请求失败');
|
| 784 |
+
}
|
| 785 |
+
|
| 786 |
+
const data = await response.json();
|
| 787 |
+
|
| 788 |
+
if (data.data && data.data[0] && data.data[0].url) {
|
| 789 |
+
// 展示生成的图片
|
| 790 |
+
showOptimizationResult(mjContent, data.data[0].url);
|
| 791 |
+
mjButton.disabled = false;
|
| 792 |
+
mjButton.textContent = '重新生成';
|
| 793 |
+
} else {
|
| 794 |
+
throw new Error('API未返回图片数据');
|
| 795 |
+
}
|
| 796 |
+
|
| 797 |
+
} catch (error) {
|
| 798 |
+
console.error('生成优化方案失败:', error);
|
| 799 |
+
mjContent.innerHTML = `
|
| 800 |
+
<div class="optimization-placeholder">
|
| 801 |
+
<p style="color: #ff6b6b;">生成失败: ${error.message || '请稍后重试'}</p>
|
| 802 |
+
</div>
|
| 803 |
+
`;
|
| 804 |
+
mjButton.disabled = false;
|
| 805 |
+
mjButton.textContent = '重试';
|
| 806 |
+
}
|
| 807 |
+
}
|
| 808 |
+
|
| 809 |
+
// 显示加载状态
|
| 810 |
+
function showLoadingState(container, message) {
|
| 811 |
+
container.innerHTML = `
|
| 812 |
+
<div class="optimization-loading">
|
| 813 |
+
<div class="loading-spinner"></div>
|
| 814 |
+
<div class="loading-text">${message}</div>
|
| 815 |
+
</div>
|
| 816 |
+
<div class="optimization-placeholder">
|
| 817 |
+
<p>生成中���请稍候...</p>
|
| 818 |
+
</div>
|
| 819 |
+
`;
|
| 820 |
+
}
|
| 821 |
+
|
| 822 |
+
// 显示优化结果
|
| 823 |
+
function showOptimizationResult(container, imageUrl) {
|
| 824 |
+
container.innerHTML = `
|
| 825 |
+
<div class="optimization-result">
|
| 826 |
+
<img src="${imageUrl}" alt="优化方案" />
|
| 827 |
+
</div>
|
| 828 |
+
`;
|
| 829 |
+
}
|
| 830 |
+
|
| 831 |
+
// 润色功能
|
| 832 |
+
const generateBtn = document.getElementById('generateBtn');
|
| 833 |
+
if (generateBtn) {
|
| 834 |
+
generateBtn.addEventListener('click', function() {
|
| 835 |
+
const userInput = document.getElementById('userInput').value.trim();
|
| 836 |
+
if (!userInput) {
|
| 837 |
+
alert('请输入需要润色的内容');
|
| 838 |
+
return;
|
| 839 |
+
}
|
| 840 |
+
|
| 841 |
+
// 显示Toast提示
|
| 842 |
+
const toast = document.getElementById('toast');
|
| 843 |
+
toast.style.display = 'block';
|
| 844 |
+
|
| 845 |
+
// 调用OpenAI API
|
| 846 |
+
callOptimizeText(userInput);
|
| 847 |
+
});
|
| 848 |
+
}
|
| 849 |
+
|
| 850 |
+
// OpenAI API调用函数
|
| 851 |
+
async function callOptimizeText(inputText) {
|
| 852 |
+
try {
|
| 853 |
+
// 获取原始反馈文本
|
| 854 |
+
const originalFeedback = sessionStorage.getItem('uploadedText') || '';
|
| 855 |
+
|
| 856 |
+
const response = await fetch(`${BASE_URL}api/optimize-text`, {
|
| 857 |
+
method: 'POST',
|
| 858 |
+
headers: {
|
| 859 |
+
'Content-Type': 'application/json'
|
| 860 |
+
},
|
| 861 |
+
body: JSON.stringify({
|
| 862 |
+
original_feedback: originalFeedback,
|
| 863 |
+
user_input: inputText,
|
| 864 |
+
request_model_id: 'gpt-4.1-mini'
|
| 865 |
+
})
|
| 866 |
+
});
|
| 867 |
+
|
| 868 |
+
if (!response.ok) {
|
| 869 |
+
throw new Error('文本优化请求失败');
|
| 870 |
+
}
|
| 871 |
+
|
| 872 |
+
const data = await response.json();
|
| 873 |
+
|
| 874 |
+
// 隐藏Toast
|
| 875 |
+
const toast = document.getElementById('toast');
|
| 876 |
+
toast.style.display = 'none';
|
| 877 |
+
|
| 878 |
+
if (data.output && data.output[0].content[0].text.length > 0) {
|
| 879 |
+
// 显示API返回的结果
|
| 880 |
+
const optimizedText = data['output'][0]['content'][0]['text'];
|
| 881 |
+
const resultContent = document.getElementById('resultContent');
|
| 882 |
+
const resultCard = document.getElementById('resultCard');
|
| 883 |
+
|
| 884 |
+
resultContent.innerText = optimizedText;
|
| 885 |
+
resultCard.style.display = 'block';
|
| 886 |
+
|
| 887 |
+
// 平滑滚动到结果区域
|
| 888 |
+
resultCard.scrollIntoView({ behavior: 'smooth' });
|
| 889 |
+
} else {
|
| 890 |
+
handleAPIError('未获取到有效响应');
|
| 891 |
+
}
|
| 892 |
+
} catch (error) {
|
| 893 |
+
handleAPIError(error);
|
| 894 |
+
}
|
| 895 |
+
}
|
| 896 |
+
|
| 897 |
+
// 处理API错误
|
| 898 |
+
function handleAPIError(error) {
|
| 899 |
+
console.error('API调用失败:', error);
|
| 900 |
+
|
| 901 |
+
// 隐藏Toast
|
| 902 |
+
const toast = document.getElementById('toast');
|
| 903 |
+
toast.style.display = 'none';
|
| 904 |
+
|
| 905 |
+
// 显示错误信息,同时提供备选方案
|
| 906 |
+
const resultContent = document.getElementById('resultContent');
|
| 907 |
+
const resultCard = document.getElementById('resultCard');
|
| 908 |
+
|
| 909 |
+
// 备选文案,当API调用失败时使用
|
| 910 |
+
const userInput = document.getElementById('userInput').value.trim();
|
| 911 |
+
let fallbackText = '';
|
| 912 |
+
if (userInput.includes('不行') || userInput.includes('不能')) {
|
| 913 |
+
fallbackText = '感谢您的建议,我们团队已经考虑过这个方案,但受到一些技术限制,暂时无法实现。我们已经记录下您的想法,并会在后续版本中尝试优化解决。';
|
| 914 |
+
} else if (userInput.includes('丑') || userInput.includes('难看')) {
|
| 915 |
+
fallbackText = '非常感谢您的审美建议!我们设计团队正在不断优化视觉体验,您提出的这些意见非常有价值,我们会在下一版本中优先考虑调整。';
|
| 916 |
+
} else {
|
| 917 |
+
fallbackText = '您的意见我们已经记录,团队会认真研究并在后续迭代中考虑采纳。感谢您的宝贵反馈,这对我们产品的完善非常重要!';
|
| 918 |
+
}
|
| 919 |
+
|
| 920 |
+
resultContent.innerText = fallbackText;
|
| 921 |
+
resultCard.style.display = 'block';
|
| 922 |
+
|
| 923 |
+
// 平滑滚动到结果区域
|
| 924 |
+
resultCard.scrollIntoView({ behavior: 'smooth' });
|
| 925 |
+
}
|
| 926 |
+
|
| 927 |
+
// 添加图片转base64函数
|
| 928 |
+
async function convertImageToBase64(imageUrl) {
|
| 929 |
+
try {
|
| 930 |
+
const response = await fetch(imageUrl);
|
| 931 |
+
const blob = await response.blob();
|
| 932 |
+
return new Promise((resolve, reject) => {
|
| 933 |
+
const reader = new FileReader();
|
| 934 |
+
reader.onloadend = () => {
|
| 935 |
+
// 返回完整的Data URL
|
| 936 |
+
resolve(reader.result);
|
| 937 |
+
};
|
| 938 |
+
reader.onerror = reject;
|
| 939 |
+
reader.readAsDataURL(blob);
|
| 940 |
+
});
|
| 941 |
+
} catch (error) {
|
| 942 |
+
console.error('转换图片为base64失败:', error);
|
| 943 |
+
return imageUrl; // 如果失败,返回原始URL
|
| 944 |
+
}
|
| 945 |
+
}
|
| 946 |
+
});
|
static/styles.css
ADDED
|
@@ -0,0 +1,927 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* 全局样式 */
|
| 2 |
+
* {
|
| 3 |
+
margin: 0;
|
| 4 |
+
padding: 0;
|
| 5 |
+
box-sizing: border-box;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
body {
|
| 9 |
+
font-family: 'Helvetica Neue', Arial, sans-serif;
|
| 10 |
+
line-height: 1.6;
|
| 11 |
+
color: #fff;
|
| 12 |
+
background-color: #121212;
|
| 13 |
+
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
a {
|
| 17 |
+
text-decoration: none;
|
| 18 |
+
color: inherit;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
/* 登录页样式 */
|
| 22 |
+
.landing-container {
|
| 23 |
+
min-height: 100vh;
|
| 24 |
+
width: 100%;
|
| 25 |
+
position: relative;
|
| 26 |
+
overflow: hidden;
|
| 27 |
+
background: url(/static/assets/beijing.png),
|
| 28 |
+
url(/static/assets/beijing2.png),
|
| 29 |
+
url(/static/assets/beijing3.png),
|
| 30 |
+
linear-gradient(to bottom right, #080618, #080618);
|
| 31 |
+
background-size: 50% 50%,80% 80%,80% 80%,100% 100%;
|
| 32 |
+
background-position: 0 0,90% 10%,-120% 260%, 0 0;
|
| 33 |
+
background-repeat: no-repeat,no-repeat,no-repeat;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
header {
|
| 37 |
+
padding: 20px 40px;
|
| 38 |
+
z-index: 2;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.logo {
|
| 42 |
+
font-size: 1.5rem;
|
| 43 |
+
font-weight: bold;
|
| 44 |
+
color: #fff;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.logo a {
|
| 48 |
+
color: #fff;
|
| 49 |
+
text-decoration: none;
|
| 50 |
+
transition: opacity 0.3s;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
.logo a:hover {
|
| 54 |
+
opacity: 0.8;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.landing-content {
|
| 58 |
+
position: relative;
|
| 59 |
+
min-height: calc(100vh - 80px);
|
| 60 |
+
padding: 20px;
|
| 61 |
+
display: flex;
|
| 62 |
+
flex-direction: column;
|
| 63 |
+
align-items: center;
|
| 64 |
+
z-index: 2;
|
| 65 |
+
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
/* 浮动元素 */
|
| 69 |
+
.floating-avatar {
|
| 70 |
+
position: absolute;
|
| 71 |
+
left: 24%;
|
| 72 |
+
top: 15%;
|
| 73 |
+
z-index: 10;
|
| 74 |
+
background: #fff;
|
| 75 |
+
backdrop-filter: blur(5px);
|
| 76 |
+
padding: 10px 20px;
|
| 77 |
+
border-radius: 20px;
|
| 78 |
+
display: flex;
|
| 79 |
+
align-items: center;
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
.avatar-icon {
|
| 83 |
+
width: 45px;
|
| 84 |
+
height: 45px;
|
| 85 |
+
border-radius: 50%;
|
| 86 |
+
background: #333;
|
| 87 |
+
display: flex;
|
| 88 |
+
align-items: center;
|
| 89 |
+
justify-content: center;
|
| 90 |
+
margin-right: 12px;
|
| 91 |
+
overflow: hidden;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.avatar-img {
|
| 95 |
+
width: 100%;
|
| 96 |
+
height: 100%;
|
| 97 |
+
object-fit: cover;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
.dynamic-text {
|
| 101 |
+
font-size: 18px;
|
| 102 |
+
font-weight: 500;
|
| 103 |
+
color: #333;
|
| 104 |
+
position: relative;
|
| 105 |
+
display: inline-block;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
.floating-feature {
|
| 109 |
+
position: absolute;
|
| 110 |
+
right: 24%;
|
| 111 |
+
top: 15%;
|
| 112 |
+
z-index: 10;
|
| 113 |
+
background: #fff;
|
| 114 |
+
backdrop-filter: blur(5px);
|
| 115 |
+
padding: 10px 20px;
|
| 116 |
+
border-radius: 20px;
|
| 117 |
+
display: flex;
|
| 118 |
+
align-items: center;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
.feature-icon {
|
| 122 |
+
width: 45px;
|
| 123 |
+
height: 45px;
|
| 124 |
+
border-radius: 50%;
|
| 125 |
+
display: flex;
|
| 126 |
+
align-items: center;
|
| 127 |
+
justify-content: center;
|
| 128 |
+
margin-right: 12px;
|
| 129 |
+
overflow: hidden;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
.feature-icon img {
|
| 133 |
+
width: 100%;
|
| 134 |
+
height: 100%;
|
| 135 |
+
object-fit: cover;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
.feature-text {
|
| 139 |
+
white-space: nowrap;
|
| 140 |
+
font-size: 16px;
|
| 141 |
+
color: #333;
|
| 142 |
+
font-weight: 500;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
/* 花朵容器 */
|
| 146 |
+
.flower-container {
|
| 147 |
+
position: relative;
|
| 148 |
+
margin-top: 0%;
|
| 149 |
+
z-index: 2;
|
| 150 |
+
width: 100%;
|
| 151 |
+
display: flex;
|
| 152 |
+
justify-content: center;
|
| 153 |
+
}
|
| 154 |
+
.ti-container {
|
| 155 |
+
position: relative;
|
| 156 |
+
margin-top: 4%;
|
| 157 |
+
z-index: 2;
|
| 158 |
+
width: 100%;
|
| 159 |
+
display: flex;
|
| 160 |
+
justify-content: center;
|
| 161 |
+
}
|
| 162 |
+
.biao-container {
|
| 163 |
+
position: relative;
|
| 164 |
+
margin-top: 6%;
|
| 165 |
+
z-index: 2;
|
| 166 |
+
width: 100%;
|
| 167 |
+
display: flex;
|
| 168 |
+
justify-content: center;
|
| 169 |
+
}
|
| 170 |
+
.beijing-container {
|
| 171 |
+
position: relative;
|
| 172 |
+
margin-top: 0%;
|
| 173 |
+
z-index: 5;
|
| 174 |
+
width: 100%;
|
| 175 |
+
display: flex;
|
| 176 |
+
justify-content: left;
|
| 177 |
+
}
|
| 178 |
+
.ti-img {
|
| 179 |
+
width: 600px;
|
| 180 |
+
height: auto;
|
| 181 |
+
max-width: 90%;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
.flower-img {
|
| 185 |
+
width: 700px;
|
| 186 |
+
height: auto;
|
| 187 |
+
max-width: 90%;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
.beijing-img {
|
| 191 |
+
width: 600px;
|
| 192 |
+
height: auto;
|
| 193 |
+
max-width: 90%;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
/* 标题区域 */
|
| 198 |
+
.hero-title {
|
| 199 |
+
text-align: center;
|
| 200 |
+
margin-top: -300px;
|
| 201 |
+
z-index: 3;
|
| 202 |
+
position: relative;
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
.main-title {
|
| 206 |
+
font-size: 4.5rem;
|
| 207 |
+
margin-bottom: 15px;
|
| 208 |
+
background: linear-gradient(to right, #c299fc, #7f9bff);
|
| 209 |
+
-webkit-background-clip: text;
|
| 210 |
+
background-clip: text;
|
| 211 |
+
color: transparent;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
.subtitle {
|
| 215 |
+
font-size: 8rem;
|
| 216 |
+
font-weight: bold;
|
| 217 |
+
color: rgba(255, 255, 255, 0.1);
|
| 218 |
+
letter-spacing: 4px;
|
| 219 |
+
margin-bottom: 25px;
|
| 220 |
+
margin-top: -100px;
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
.description {
|
| 224 |
+
max-width: 800px;
|
| 225 |
+
font-weight: lighter;
|
| 226 |
+
margin: 0 auto 40px;
|
| 227 |
+
color: rgba(255, 255, 255, 0.8);
|
| 228 |
+
line-height: 1.8;
|
| 229 |
+
font-size: 1.1rem;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
/* 通用按钮样式 */
|
| 233 |
+
.cta-button, .resolve-button, .generate-button {
|
| 234 |
+
display: inline-block;
|
| 235 |
+
background: linear-gradient(to right, #5E33F1, #BA9EF7);
|
| 236 |
+
color: #fff;
|
| 237 |
+
padding: 15px 70px;
|
| 238 |
+
border-radius: 20px;
|
| 239 |
+
font-size: 1.1rem;
|
| 240 |
+
font-weight: bold;
|
| 241 |
+
border: none;
|
| 242 |
+
cursor: pointer;
|
| 243 |
+
transition: transform 0.3s, box-shadow 0.3s;
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
.cta-button:hover, .resolve-button:hover, .generate-button:hover {
|
| 247 |
+
transform: translateY(-6px);
|
| 248 |
+
box-shadow: 0 10px 30px rgba(128, 49, 255, 0.5);
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
/* 设备展示区域 */
|
| 252 |
+
.showcase-section {
|
| 253 |
+
width: 100%;
|
| 254 |
+
height: 550px;
|
| 255 |
+
position: relative;
|
| 256 |
+
margin: 300px 0 100px;
|
| 257 |
+
display: flex;
|
| 258 |
+
justify-content: center;
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
.device-wrapper {
|
| 262 |
+
position: relative;
|
| 263 |
+
width: 1100px;
|
| 264 |
+
height: 100%;
|
| 265 |
+
transform-style: preserve-3d;
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
.showcase-device {
|
| 269 |
+
position: absolute;
|
| 270 |
+
border-radius: 80px;
|
| 271 |
+
overflow: hidden;
|
| 272 |
+
box-shadow: 0 0 100px rgba(0, 0, 0, 0.5);
|
| 273 |
+
transition: transform 0.4s ease-out;
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
.showcase-device img {
|
| 277 |
+
width: 100%;
|
| 278 |
+
height: 100%;
|
| 279 |
+
object-fit: cover;
|
| 280 |
+
display: block;
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
.device1 {
|
| 284 |
+
width: 350px;
|
| 285 |
+
height: 400px;
|
| 286 |
+
left: -40px;
|
| 287 |
+
top: -200px;
|
| 288 |
+
z-index: 2;
|
| 289 |
+
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
.device2 {
|
| 293 |
+
width: 740px;
|
| 294 |
+
height: 540px;
|
| 295 |
+
left: 160px;
|
| 296 |
+
top: 0;
|
| 297 |
+
z-index: 3;
|
| 298 |
+
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
.device3 {
|
| 302 |
+
width: 320px;
|
| 303 |
+
height: 300px;
|
| 304 |
+
right:-20px;
|
| 305 |
+
top: -120px;
|
| 306 |
+
z-index: 4;
|
| 307 |
+
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
.device4 {
|
| 311 |
+
width: 220px;
|
| 312 |
+
height: 220px;
|
| 313 |
+
left: 100px;
|
| 314 |
+
bottom: -60px;
|
| 315 |
+
z-index: 5;
|
| 316 |
+
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
.device1:hover, .device2:hover, .device3:hover, .device4:hover {
|
| 320 |
+
transform: translateY(-10px);
|
| 321 |
+
box-shadow: 0 0 100px rgba(77, 0, 209, 0.5);
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
/* 底部区域 */
|
| 325 |
+
.bottom-section {
|
| 326 |
+
text-align: center;
|
| 327 |
+
margin-top: -20px;
|
| 328 |
+
padding: 0px 20px;
|
| 329 |
+
position: relative;
|
| 330 |
+
z-index: 5;
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
.subtitle-text {
|
| 334 |
+
color: #999;
|
| 335 |
+
margin-bottom: 15px;
|
| 336 |
+
font-size: 1.1rem;
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
.secondary-title {
|
| 340 |
+
font-size: 3rem;
|
| 341 |
+
margin-bottom: 25px;
|
| 342 |
+
background: linear-gradient(to right, #c299fc, #7f9bff);
|
| 343 |
+
-webkit-background-clip: text;
|
| 344 |
+
background-clip: text;
|
| 345 |
+
color: transparent;
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
.description-secondary {
|
| 349 |
+
max-width: 800px;
|
| 350 |
+
font-weight: lighter;
|
| 351 |
+
margin: 0 auto 40px;
|
| 352 |
+
color: rgba(255, 255, 255, 0.8);
|
| 353 |
+
line-height: 1.8;
|
| 354 |
+
font-size: 1.1rem;
|
| 355 |
+
margin-top: 20px;
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
.secondary-button {
|
| 359 |
+
margin-top: 40px;
|
| 360 |
+
margin-bottom:200px
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
/* 深色主题容器 */
|
| 364 |
+
.dark-container {
|
| 365 |
+
min-height: 100vh;
|
| 366 |
+
width: 100%;
|
| 367 |
+
position: relative;
|
| 368 |
+
overflow: hidden;
|
| 369 |
+
background: url(./assets/beijing.png),
|
| 370 |
+
url(./assets/beijing2.png),
|
| 371 |
+
url(./assets/beijing3.png),
|
| 372 |
+
linear-gradient(to bottom right, #080618, #080618);
|
| 373 |
+
background-size: 50% 50%,80% 80%,80% 80%,100% 100%;
|
| 374 |
+
background-position: 0 0,90% 10%,-120% 260%, 0 0;
|
| 375 |
+
background-repeat: no-repeat,no-repeat,no-repeat;
|
| 376 |
+
padding: 20px;
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
/* 上传页面样式 - 新版本 */
|
| 380 |
+
.upload-page {
|
| 381 |
+
display: flex;
|
| 382 |
+
flex-direction: column;
|
| 383 |
+
align-items: center;
|
| 384 |
+
justify-content: center;
|
| 385 |
+
height: calc(100vh - 80px);
|
| 386 |
+
max-width: 900px;
|
| 387 |
+
margin: 0 auto;
|
| 388 |
+
padding: 20px;
|
| 389 |
+
gap: 30px;
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
.upload-box {
|
| 393 |
+
width: 100%;
|
| 394 |
+
height: 45vh;
|
| 395 |
+
background: rgba(255, 255, 255, 0.05);
|
| 396 |
+
border-radius: 20px;
|
| 397 |
+
display: flex;
|
| 398 |
+
align-items: center;
|
| 399 |
+
justify-content: center;
|
| 400 |
+
backdrop-filter: blur(5px);
|
| 401 |
+
overflow: hidden;
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
.upload-area {
|
| 405 |
+
width: 100%;
|
| 406 |
+
height: 100%;
|
| 407 |
+
display: flex;
|
| 408 |
+
align-items: center;
|
| 409 |
+
justify-content: center;
|
| 410 |
+
cursor: pointer;
|
| 411 |
+
transition: all 0.3s;
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
.upload-button {
|
| 415 |
+
background: #5d4bb7;
|
| 416 |
+
color: white;
|
| 417 |
+
padding: 12px 30px;
|
| 418 |
+
border-radius: 50px;
|
| 419 |
+
font-weight: 500;
|
| 420 |
+
transition: all 0.3s;
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
.upload-area:hover .upload-button {
|
| 424 |
+
background: #6b5ed8;
|
| 425 |
+
transform: translateY(-2px);
|
| 426 |
+
box-shadow: 0 5px 15px rgba(107, 94, 216, 0.3);
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
.text-input-box {
|
| 430 |
+
width: 100%;
|
| 431 |
+
height: 25vh;
|
| 432 |
+
background: rgba(255, 255, 255, 0.05);
|
| 433 |
+
border-radius: 20px;
|
| 434 |
+
backdrop-filter: blur(5px);
|
| 435 |
+
overflow: hidden;
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
textarea {
|
| 439 |
+
width: 100%;
|
| 440 |
+
height: 100%;
|
| 441 |
+
background: transparent;
|
| 442 |
+
border: none;
|
| 443 |
+
padding: 20px;
|
| 444 |
+
color: #ccc;
|
| 445 |
+
font-size: 16px;
|
| 446 |
+
resize: none;
|
| 447 |
+
outline: none;
|
| 448 |
+
}
|
| 449 |
+
|
| 450 |
+
.button-container {
|
| 451 |
+
width: 100%;
|
| 452 |
+
display: flex;
|
| 453 |
+
justify-content: center;
|
| 454 |
+
margin-top: 30px;
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
+
/* 图片预览 */
|
| 458 |
+
.upload-area.with-image {
|
| 459 |
+
padding: 0;
|
| 460 |
+
}
|
| 461 |
+
|
| 462 |
+
.upload-area img {
|
| 463 |
+
width: 100%;
|
| 464 |
+
height: 100%;
|
| 465 |
+
object-fit: contain;
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
/* 新版反馈页面样式 */
|
| 469 |
+
.feedback-page {
|
| 470 |
+
max-width: 900px;
|
| 471 |
+
margin: 0 auto;
|
| 472 |
+
padding: 20px;
|
| 473 |
+
display: flex;
|
| 474 |
+
flex-direction: column;
|
| 475 |
+
gap: 80px;
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
+
.feedback-section {
|
| 479 |
+
width: 100%;
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
.section-title {
|
| 483 |
+
font-size: 24px;
|
| 484 |
+
font-weight: 600;
|
| 485 |
+
margin-bottom: 20px;
|
| 486 |
+
color: #fff;
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
/* 情绪值卡片 */
|
| 490 |
+
.emotion-card {
|
| 491 |
+
width: 100%;
|
| 492 |
+
background: rgba(255, 255, 255, 0.05);
|
| 493 |
+
border-radius: 20px;
|
| 494 |
+
padding: 25px;
|
| 495 |
+
backdrop-filter: blur(5px);
|
| 496 |
+
display: flex;
|
| 497 |
+
flex-direction: column;
|
| 498 |
+
align-items: center;
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
.emotion-icons {
|
| 502 |
+
display: flex;
|
| 503 |
+
gap: 16px;
|
| 504 |
+
margin-bottom: 15px;
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
.emoji {
|
| 508 |
+
font-size: 50px;
|
| 509 |
+
}
|
| 510 |
+
|
| 511 |
+
.emotion-text {
|
| 512 |
+
color: #afafaf;
|
| 513 |
+
font-size: 16px;
|
| 514 |
+
text-align: center;
|
| 515 |
+
margin-top: 10px;
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
/* 修改建议卡片 */
|
| 519 |
+
.suggestions-container {
|
| 520 |
+
display: grid;
|
| 521 |
+
grid-template-columns: repeat(3, 1fr);
|
| 522 |
+
gap: 20px;
|
| 523 |
+
position: relative; /* 确保容器有定位上下文 */
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
.suggestion-card {
|
| 527 |
+
background: rgba(255, 255, 255, 0.1);
|
| 528 |
+
border-radius: 20px;
|
| 529 |
+
overflow: visible; /* 允许横条超出卡片 */
|
| 530 |
+
height: 300px;
|
| 531 |
+
display: flex;
|
| 532 |
+
flex-direction: column;
|
| 533 |
+
backdrop-filter: blur(10px);
|
| 534 |
+
position: relative;
|
| 535 |
+
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
| 536 |
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 537 |
+
z-index: 1;
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
.suggestion-card::after {
|
| 541 |
+
content: '';
|
| 542 |
+
position: absolute;
|
| 543 |
+
top: -6px; /* 向上露出10px */
|
| 544 |
+
left: 50%;
|
| 545 |
+
transform: translateX(-50%);
|
| 546 |
+
width: 100px; /* 略微减小宽度 */
|
| 547 |
+
height: 10px; /* 略微减小高度 */
|
| 548 |
+
border-radius: 8px;
|
| 549 |
+
z-index: -1;
|
| 550 |
+
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
.yellow-top::after {
|
| 554 |
+
background: linear-gradient(to right, #deb045, #ffd166);
|
| 555 |
+
/* 负值确保在卡片下方 */
|
| 556 |
+
|
| 557 |
+
}
|
| 558 |
+
|
| 559 |
+
.cyan-top::after {
|
| 560 |
+
background: linear-gradient(to right, #25b1c1, #3ec1cf);
|
| 561 |
+
|
| 562 |
+
}
|
| 563 |
+
|
| 564 |
+
.purple-top::after {
|
| 565 |
+
background: linear-gradient(to right, #9747FF, #b26bff);
|
| 566 |
+
|
| 567 |
+
}
|
| 568 |
+
|
| 569 |
+
.suggestion-title {
|
| 570 |
+
font-size: 18px;
|
| 571 |
+
font-weight: 600;
|
| 572 |
+
margin: 40px;
|
| 573 |
+
text-align: center;
|
| 574 |
+
}
|
| 575 |
+
|
| 576 |
+
.suggestion-text {
|
| 577 |
+
padding: 0 24px 24px;
|
| 578 |
+
color: #afafaf;
|
| 579 |
+
font-size: 13px;
|
| 580 |
+
line-height: 2;
|
| 581 |
+
}
|
| 582 |
+
|
| 583 |
+
/* 参考案例卡片 */
|
| 584 |
+
.examples-container {
|
| 585 |
+
display: grid;
|
| 586 |
+
grid-template-columns: repeat(2, 1fr);
|
| 587 |
+
gap: 30px;
|
| 588 |
+
}
|
| 589 |
+
|
| 590 |
+
.example-card {
|
| 591 |
+
position: relative;
|
| 592 |
+
height: 320px;
|
| 593 |
+
border-radius: 12px;
|
| 594 |
+
overflow: hidden;
|
| 595 |
+
display: flex;
|
| 596 |
+
flex-direction: column;
|
| 597 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
| 598 |
+
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
| 599 |
+
background-color: rgba(255, 255, 255, 0.05);
|
| 600 |
+
backdrop-filter: blur(10px);
|
| 601 |
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 602 |
+
}
|
| 603 |
+
|
| 604 |
+
.example-card:hover {
|
| 605 |
+
transform: translateY(-5px);
|
| 606 |
+
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
|
| 607 |
+
}
|
| 608 |
+
|
| 609 |
+
.example-image {
|
| 610 |
+
width: 100%;
|
| 611 |
+
height: 180px;
|
| 612 |
+
object-fit: cover;
|
| 613 |
+
border-top-left-radius: 12px;
|
| 614 |
+
border-top-right-radius: 12px;
|
| 615 |
+
}
|
| 616 |
+
|
| 617 |
+
.example-content {
|
| 618 |
+
padding: 15px;
|
| 619 |
+
flex-grow: 1;
|
| 620 |
+
display: flex;
|
| 621 |
+
flex-direction: column;
|
| 622 |
+
max-height: 140px;
|
| 623 |
+
overflow: hidden;
|
| 624 |
+
}
|
| 625 |
+
|
| 626 |
+
.example-desc {
|
| 627 |
+
color: #e0e0e0;
|
| 628 |
+
margin: 0 0 10px 0;
|
| 629 |
+
font-size: 14px;
|
| 630 |
+
line-height: 1.5;
|
| 631 |
+
flex-grow: 1;
|
| 632 |
+
overflow-y: auto;
|
| 633 |
+
max-height: 100px;
|
| 634 |
+
padding-right: 5px;
|
| 635 |
+
scrollbar-width: thin;
|
| 636 |
+
scrollbar-color: #9747FF rgba(255, 255, 255, 0.1);
|
| 637 |
+
}
|
| 638 |
+
|
| 639 |
+
.example-desc::-webkit-scrollbar {
|
| 640 |
+
width: 4px;
|
| 641 |
+
}
|
| 642 |
+
|
| 643 |
+
.example-desc::-webkit-scrollbar-track {
|
| 644 |
+
background: rgba(255, 255, 255, 0.1);
|
| 645 |
+
border-radius: 4px;
|
| 646 |
+
}
|
| 647 |
+
|
| 648 |
+
.example-desc::-webkit-scrollbar-thumb {
|
| 649 |
+
background-color: #9747FF;
|
| 650 |
+
border-radius: 4px;
|
| 651 |
+
}
|
| 652 |
+
|
| 653 |
+
.example-source {
|
| 654 |
+
font-size: 12px;
|
| 655 |
+
color: #888;
|
| 656 |
+
margin-top: auto;
|
| 657 |
+
text-decoration: none;
|
| 658 |
+
}
|
| 659 |
+
|
| 660 |
+
.example-source a {
|
| 661 |
+
color: #9747FF;
|
| 662 |
+
text-decoration: none;
|
| 663 |
+
}
|
| 664 |
+
|
| 665 |
+
.example-source a:hover {
|
| 666 |
+
text-decoration: underline;
|
| 667 |
+
}
|
| 668 |
+
|
| 669 |
+
.loading-examples {
|
| 670 |
+
width: 100%;
|
| 671 |
+
padding: 20px;
|
| 672 |
+
text-align: center;
|
| 673 |
+
color: #888;
|
| 674 |
+
font-size: 16px;
|
| 675 |
+
grid-column: span 2;
|
| 676 |
+
}
|
| 677 |
+
|
| 678 |
+
.loading-error {
|
| 679 |
+
width: 100%;
|
| 680 |
+
padding: 20px;
|
| 681 |
+
text-align: center;
|
| 682 |
+
color: #ff6b6b;
|
| 683 |
+
font-size: 16px;
|
| 684 |
+
grid-column: span 2;
|
| 685 |
+
}
|
| 686 |
+
|
| 687 |
+
/* 润色卡片 */
|
| 688 |
+
.polished-card {
|
| 689 |
+
width: 100%;
|
| 690 |
+
background: rgba(255, 255, 255, 0.05);
|
| 691 |
+
border-radius: 20px;
|
| 692 |
+
overflow: hidden;
|
| 693 |
+
height: 150px;
|
| 694 |
+
backdrop-filter: blur(5px);
|
| 695 |
+
margin-bottom: 20px;
|
| 696 |
+
}
|
| 697 |
+
|
| 698 |
+
/* 结果文案卡片 */
|
| 699 |
+
.result-card {
|
| 700 |
+
width: 100%;
|
| 701 |
+
background: rgba(255, 255, 255, 0.07);
|
| 702 |
+
border-radius: 20px;
|
| 703 |
+
overflow: hidden;
|
| 704 |
+
margin-top: 20px;
|
| 705 |
+
backdrop-filter: blur(5px);
|
| 706 |
+
border: 1px solid rgba(138, 43, 226, 0.2);
|
| 707 |
+
}
|
| 708 |
+
|
| 709 |
+
.result-title {
|
| 710 |
+
padding: 15px 20px;
|
| 711 |
+
font-size: 16px;
|
| 712 |
+
font-weight: 500;
|
| 713 |
+
color: #fff;
|
| 714 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
| 715 |
+
background: rgba(138, 43, 226, 0.1);
|
| 716 |
+
}
|
| 717 |
+
|
| 718 |
+
.result-content {
|
| 719 |
+
padding: 20px;
|
| 720 |
+
color: #ccc;
|
| 721 |
+
font-size: 16px;
|
| 722 |
+
line-height: 1.6;
|
| 723 |
+
min-height: 100px;
|
| 724 |
+
}
|
| 725 |
+
|
| 726 |
+
footer {
|
| 727 |
+
text-align: center;
|
| 728 |
+
margin-top: 50px;
|
| 729 |
+
padding: 20px 0;
|
| 730 |
+
color: #777;
|
| 731 |
+
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
| 732 |
+
}
|
| 733 |
+
|
| 734 |
+
/* 响应式设计 */
|
| 735 |
+
@media (max-width: 768px) {
|
| 736 |
+
.landing-content {
|
| 737 |
+
padding: 10px;
|
| 738 |
+
}
|
| 739 |
+
|
| 740 |
+
.floating-avatar, .floating-feature {
|
| 741 |
+
position: static;
|
| 742 |
+
margin: 10px auto;
|
| 743 |
+
}
|
| 744 |
+
|
| 745 |
+
.flower-img {
|
| 746 |
+
max-width: 80%;
|
| 747 |
+
}
|
| 748 |
+
|
| 749 |
+
.main-title {
|
| 750 |
+
font-size: 2.5rem;
|
| 751 |
+
}
|
| 752 |
+
|
| 753 |
+
.subtitle {
|
| 754 |
+
font-size: 2rem;
|
| 755 |
+
}
|
| 756 |
+
|
| 757 |
+
.secondary-title {
|
| 758 |
+
font-size: 1.8rem;
|
| 759 |
+
}
|
| 760 |
+
|
| 761 |
+
.upload-page {
|
| 762 |
+
padding: 10px;
|
| 763 |
+
}
|
| 764 |
+
|
| 765 |
+
.upload-box {
|
| 766 |
+
height: 35vh;
|
| 767 |
+
}
|
| 768 |
+
|
| 769 |
+
.text-input-box {
|
| 770 |
+
height: 25vh;
|
| 771 |
+
}
|
| 772 |
+
|
| 773 |
+
.suggestions-container,
|
| 774 |
+
.examples-container {
|
| 775 |
+
grid-template-columns: 1fr;
|
| 776 |
+
}
|
| 777 |
+
|
| 778 |
+
.button-container {
|
| 779 |
+
flex-direction: column;
|
| 780 |
+
gap: 15px;
|
| 781 |
+
}
|
| 782 |
+
|
| 783 |
+
.back-button, .submit-button, .save-button {
|
| 784 |
+
width: 100%;
|
| 785 |
+
}
|
| 786 |
+
}
|
| 787 |
+
|
| 788 |
+
/* Toast提示 */
|
| 789 |
+
.toast {
|
| 790 |
+
position: fixed;
|
| 791 |
+
top: 50%;
|
| 792 |
+
left: 50%;
|
| 793 |
+
transform: translate(-50%, -50%);
|
| 794 |
+
background: rgba(0, 0, 0, 0.8);
|
| 795 |
+
color: white;
|
| 796 |
+
padding: 15px 30px;
|
| 797 |
+
border-radius: 50px;
|
| 798 |
+
z-index: 9999;
|
| 799 |
+
font-size: 16px;
|
| 800 |
+
display: none;
|
| 801 |
+
animation: fadeIn 0.3s;
|
| 802 |
+
}
|
| 803 |
+
|
| 804 |
+
@keyframes fadeIn {
|
| 805 |
+
from { opacity: 0; }
|
| 806 |
+
to { opacity: 1; }
|
| 807 |
+
}
|
| 808 |
+
|
| 809 |
+
/* 优化方案卡片 */
|
| 810 |
+
.optimization-container {
|
| 811 |
+
display: grid;
|
| 812 |
+
grid-template-columns: repeat(2, 1fr);
|
| 813 |
+
gap: 20px;
|
| 814 |
+
}
|
| 815 |
+
|
| 816 |
+
.optimization-card {
|
| 817 |
+
background: rgba(255, 255, 255, 0.05);
|
| 818 |
+
border-radius: 20px;
|
| 819 |
+
overflow: hidden;
|
| 820 |
+
backdrop-filter: blur(5px);
|
| 821 |
+
display: flex;
|
| 822 |
+
flex-direction: column;
|
| 823 |
+
min-height: 320px;
|
| 824 |
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 825 |
+
}
|
| 826 |
+
|
| 827 |
+
.optimization-header {
|
| 828 |
+
display: flex;
|
| 829 |
+
justify-content: space-between;
|
| 830 |
+
align-items: center;
|
| 831 |
+
padding: 16px 20px;
|
| 832 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
| 833 |
+
background: rgba(0, 0, 0, 0.2);
|
| 834 |
+
}
|
| 835 |
+
|
| 836 |
+
.optimization-title {
|
| 837 |
+
font-size: 18px;
|
| 838 |
+
font-weight: 500;
|
| 839 |
+
margin: 0;
|
| 840 |
+
}
|
| 841 |
+
|
| 842 |
+
.generate-btn {
|
| 843 |
+
background: linear-gradient(to right, #5E33F1, #BA9EF7);
|
| 844 |
+
color: white;
|
| 845 |
+
border: none;
|
| 846 |
+
padding: 8px 16px;
|
| 847 |
+
border-radius: 20px;
|
| 848 |
+
font-size: 14px;
|
| 849 |
+
cursor: pointer;
|
| 850 |
+
transition: all 0.3s ease;
|
| 851 |
+
}
|
| 852 |
+
|
| 853 |
+
.generate-btn:hover {
|
| 854 |
+
transform: translateY(-2px);
|
| 855 |
+
box-shadow: 0 5px 15px rgba(94, 51, 241, 0.3);
|
| 856 |
+
}
|
| 857 |
+
|
| 858 |
+
.generate-btn:disabled {
|
| 859 |
+
background: #666;
|
| 860 |
+
cursor: not-allowed;
|
| 861 |
+
transform: none;
|
| 862 |
+
box-shadow: none;
|
| 863 |
+
}
|
| 864 |
+
|
| 865 |
+
.optimization-content {
|
| 866 |
+
padding: 20px;
|
| 867 |
+
flex-grow: 1;
|
| 868 |
+
display: flex;
|
| 869 |
+
align-items: center;
|
| 870 |
+
justify-content: center;
|
| 871 |
+
position: relative;
|
| 872 |
+
min-height: 260px;
|
| 873 |
+
}
|
| 874 |
+
|
| 875 |
+
.optimization-result {
|
| 876 |
+
width: 100%;
|
| 877 |
+
height: 100%;
|
| 878 |
+
display: flex;
|
| 879 |
+
align-items: center;
|
| 880 |
+
justify-content: center;
|
| 881 |
+
}
|
| 882 |
+
|
| 883 |
+
.optimization-result img {
|
| 884 |
+
max-width: 100%;
|
| 885 |
+
max-height: 250px;
|
| 886 |
+
border-radius: 8px;
|
| 887 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
| 888 |
+
}
|
| 889 |
+
|
| 890 |
+
.optimization-placeholder {
|
| 891 |
+
color: #888;
|
| 892 |
+
text-align: center;
|
| 893 |
+
padding: 20px;
|
| 894 |
+
}
|
| 895 |
+
|
| 896 |
+
.optimization-loading {
|
| 897 |
+
position: absolute;
|
| 898 |
+
top: 0;
|
| 899 |
+
left: 0;
|
| 900 |
+
width: 100%;
|
| 901 |
+
height: 100%;
|
| 902 |
+
background: rgba(0, 0, 0, 0.7);
|
| 903 |
+
display: flex;
|
| 904 |
+
flex-direction: column;
|
| 905 |
+
align-items: center;
|
| 906 |
+
justify-content: center;
|
| 907 |
+
z-index: 2;
|
| 908 |
+
}
|
| 909 |
+
|
| 910 |
+
.loading-spinner {
|
| 911 |
+
width: 40px;
|
| 912 |
+
height: 40px;
|
| 913 |
+
border: 4px solid rgba(255, 255, 255, 0.1);
|
| 914 |
+
border-left-color: #5E33F1;
|
| 915 |
+
border-radius: 50%;
|
| 916 |
+
animation: spin 1s linear infinite;
|
| 917 |
+
margin-bottom: 15px;
|
| 918 |
+
}
|
| 919 |
+
|
| 920 |
+
.loading-text {
|
| 921 |
+
color: #ccc;
|
| 922 |
+
font-size: 14px;
|
| 923 |
+
}
|
| 924 |
+
|
| 925 |
+
@keyframes spin {
|
| 926 |
+
to { transform: rotate(360deg); }
|
| 927 |
+
}
|
static/upload.html
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="zh-CN">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>内容上传 - 解语花</title>
|
| 7 |
+
<link rel="stylesheet" href="/static/styles.css">
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div class="dark-container">
|
| 11 |
+
<header>
|
| 12 |
+
<div class="logo"><a href="/static/index.html">解语花</a></div>
|
| 13 |
+
</header>
|
| 14 |
+
|
| 15 |
+
<main class="upload-page">
|
| 16 |
+
<div class="upload-box">
|
| 17 |
+
<div class="upload-area" id="image-upload">
|
| 18 |
+
<div class="upload-button">点击/拖拽设计稿</div>
|
| 19 |
+
</div>
|
| 20 |
+
<input type="file" id="image-input" accept="image/*" hidden>
|
| 21 |
+
</div>
|
| 22 |
+
|
| 23 |
+
<div class="text-input-box">
|
| 24 |
+
<textarea id="text-input" placeholder="请输入设计类型和内容描述,以及老板反馈"></textarea>
|
| 25 |
+
</div>
|
| 26 |
+
|
| 27 |
+
<div class="button-container">
|
| 28 |
+
<button type="button" class="resolve-button" id="resolve-button">一键解读</button>
|
| 29 |
+
</div>
|
| 30 |
+
</main>
|
| 31 |
+
</div>
|
| 32 |
+
|
| 33 |
+
<script src="/static/script.js"></script>
|
| 34 |
+
</body>
|
| 35 |
+
</html>
|