diff --git a/backend/main.py b/backend/main.py index 38f8db8..402420d 100644 --- a/backend/main.py +++ b/backend/main.py @@ -8,7 +8,7 @@ from typing import Any import cv2 from fastapi import FastAPI, File, Form, HTTPException, UploadFile from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import FileResponse +from fastapi.responses import FileResponse, Response from fastapi.staticfiles import StaticFiles from backend.segmentation import METHOD_DESCRIPTIONS, compare_frame, segment_frame @@ -70,6 +70,15 @@ def samples() -> dict[str, Any]: return {"samples": items} +@app.get("/favicon.ico", include_in_schema=False) +def favicon() -> Response: + svg = """ + + +""" + return Response(content=svg, media_type="image/svg+xml") + + def _public(path: Path) -> str: return "/" + path.relative_to(ROOT).as_posix() diff --git a/frontend/app.js b/frontend/app.js index 2c1dc5a..912c5b6 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -46,9 +46,11 @@ function setBusy(isBusy, text = "运行分割") { function setFile(file) { selectedFile = file; - const transfer = new DataTransfer(); - transfer.items.add(file); - fileInput.files = transfer.files; + if (typeof DataTransfer !== "undefined") { + const transfer = new DataTransfer(); + transfer.items.add(file); + fileInput.files = transfer.files; + } fileName.textContent = `${file.name} · ${(file.size / 1024 / 1024).toFixed(2)} MB`; renderPreview(file); } @@ -180,15 +182,22 @@ function renderResults(data) { async function loadSample() { sampleButton.disabled = true; sampleButton.textContent = "加载中"; + emptyState.hidden = false; + emptyState.textContent = "正在加载内置样例视频。"; try { const response = await fetch("/api/samples"); + if (!response.ok) throw new Error("样例清单读取失败"); const data = await response.json(); const sample = data.samples.find((item) => item.kind === "video") || data.samples[0]; if (!sample) throw new Error("未找到样例文件"); const blobResponse = await fetch(sample.url); + if (!blobResponse.ok) throw new Error("样例文件下载失败"); const blob = await blobResponse.blob(); const file = new File([blob], sample.name, { type: sample.kind === "video" ? "video/mp4" : "image/png" }); setFile(file); + emptyState.textContent = "样例已加载。点击左侧“运行分割”即可生成导丝掩膜和叠加结果。"; + } catch (error) { + emptyState.textContent = error.message || "样例加载失败"; } finally { sampleButton.disabled = false; sampleButton.textContent = "加载样例"; diff --git a/工程分析/实现方案-2026-05-18-19-19-36.md b/工程分析/实现方案-2026-05-18-19-19-36.md new file mode 100644 index 0000000..8e0d89e --- /dev/null +++ b/工程分析/实现方案-2026-05-18-19-19-36.md @@ -0,0 +1,22 @@ +# 实现方案 + +开始时间:2026-05-18-19-19-36 + +## 修改内容 + +1. 后端新增 `/favicon.ico` + - 返回一个轻量 SVG 图标响应。 + - 避免浏览器默认请求 favicon 时产生 404 控制台噪声。 + +2. 前端增强 `loadSample` + - 加载开始时显示“正在加载内置样例视频”。 + - 成功后显示“样例已加载,点击运行分割”。 + - 接口或文件下载失败时在页面中显示错误。 + +3. 前端增强 `setFile` + - 仅在浏览器支持 `DataTransfer` 时同步设置 `fileInput.files`。 + - 保留 `selectedFile` 作为真实提交来源,兼容性更稳。 + +## 说明 + +`Unchecked runtime.lastError` 一般不是本项目代码产生,多见于浏览器扩展。若无痕模式或禁用扩展后消失,即可确认与系统无关。 diff --git a/工程分析/测试方案-2026-05-18-19-19-36.md b/工程分析/测试方案-2026-05-18-19-19-36.md new file mode 100644 index 0000000..a3f02da --- /dev/null +++ b/工程分析/测试方案-2026-05-18-19-19-36.md @@ -0,0 +1,24 @@ +# 测试方案 + +开始时间:2026-05-18-19-19-36 + +## 测试项 + +- `curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8001/favicon.ico` +- `curl -s http://127.0.0.1:8001/api/samples` +- `pytest -q` +- Chrome headless 首页截图确认页面仍可渲染。 + +## 验收标准 + +- `/favicon.ico` 不再返回 404。 +- `/api/samples` 返回样例视频和样例图像。 +- 自动化测试通过。 +- 网页端加载样例有明确页面提示。 + +## 执行结果 + +- `/favicon.ico`:通过,返回 `200 image/svg+xml`。 +- `/api/samples`:通过,返回 `synthetic_guidewire.mp4` 和 `synthetic_guidewire.png`。 +- `pytest -q`:通过,4 个测试全部通过。 +- Chrome headless 首页截图:通过,页面可正常渲染。 diff --git a/工程分析/经验记录.md b/工程分析/经验记录.md index 480c434..574d5cc 100644 --- a/工程分析/经验记录.md +++ b/工程分析/经验记录.md @@ -57,3 +57,15 @@ B. 产生问题原因:CSS 中对 `#videoPreview`、`#imagePreview` 设置了 ` C. 解决问题方案:在全局 CSS 中增加 `[hidden] { display: none !important; }`,确保所有使用 `hidden` 的状态元素都能可靠隐藏。 D. 后续如何避免问题:前端页面涉及显隐状态时,必须用 headless 截图检查初始状态;如果自定义了 display 样式,需要显式保护 `[hidden]`。 + +## 2026-05-18-19-19-36 加载样例控制台提示处理 + +### 1. favicon.ico 404 干扰问题判断 + +A. 具体问题:点击“加载样例”后,浏览器控制台出现 `/favicon.ico` 404,同时还有 `Unchecked runtime.lastError`。 + +B. 产生问题原因:项目没有提供 `/favicon.ico`,浏览器会默认请求该资源并记录 404;`runtime.lastError` 更常见于浏览器扩展消息通道关闭,通常不是普通网页代码产生。 + +C. 解决问题方案:后端增加 `/favicon.ico` 响应,前端为“加载样例”增加明确的成功/失败状态提示和异常捕获。 + +D. 后续如何避免问题:网页端交付时补齐 favicon、状态提示和错误处理;遇到 `runtime.lastError` 时优先用无痕模式或禁用扩展排查来源。 diff --git a/工程分析/需求分析-2026-05-18-19-19-36.md b/工程分析/需求分析-2026-05-18-19-19-36.md new file mode 100644 index 0000000..96a5999 --- /dev/null +++ b/工程分析/需求分析-2026-05-18-19-19-36.md @@ -0,0 +1,23 @@ +# 需求分析 + +开始时间:2026-05-18-19-19-36 + +## 用户反馈 + +点击网页端“加载样例”后,浏览器控制台出现: + +- `Unchecked runtime.lastError: The message port closed before a response was received.` +- `:8001/favicon.ico:1 Failed to load resource: the server responded with a status of 404 (Not Found)` + +## 判断 + +- `runtime.lastError` 通常来自浏览器扩展消息通道,页面自身代码未直接使用扩展消息接口。 +- `favicon.ico 404` 是项目没有提供站点图标,确实会在控制台产生干扰。 +- “加载样例”需要更明确的页面状态提示,避免用户只看到控制台信息却不知道样例是否加载成功。 + +## 本轮目标 + +- 修复 `/favicon.ico` 404。 +- 为“加载样例”增加成功和失败提示。 +- 增强样例加载异常捕获。 +- 验证服务和样例接口可用。