Junhui Ji commited on
Commit
3394804
·
1 Parent(s): e4b0841
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: green
5
- colorTo: indigo
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>