Spaces:
Running
Running
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>
|