Files
Pre_Seg_Server/AGENTS.md
admin c8c59f7ede feat: 完善分割工作区传播与交互闭环
功能增加:新增后端传播任务执行器,支持异步自动传播、传播进度、结果统计、取消/重试状态同步。

功能增加:传播请求支持指定 SAM2.1 tiny/small/base+/large 权重,并记录 seed mask、source annotation 和传播范围。

功能增加:传播逻辑增加 seed 签名,未变化的 mask 二次传播会跳过,已变化的 mask 会先清理旧自动传播结果再重新生成,避免重复重叠。

功能增加:工作区增加传播范围二次选择、传播进度提示、人工/AI 标注帧红色标识、自动传播帧蓝色标识和当前帧双层边框。

功能增加:新增临时提示组件,让工具操作提示自动消失且不阻塞后续操作。

功能增加:补充项目删除、模板删除、任务失败详情、任务取消/重试等前后端联动状态。

功能增加:新增安装部署文档,补充当前需求冻结、设计冻结、接口契约、测试计划和 AGENTS/README 项目说明。

Bugfix:修复自动传播接口 404、传播后看不到任务进度、传播结果重复堆叠和已编辑帧提示不清晰的问题。

Bugfix:修复 AI 分割框选/点选交互、单候选 mask、删除选点、工作区保存与候选 mask 推送相关问题。

Bugfix:修复 Canvas 多边形顶点拖动告警、工具栏提示缺失、项目库 FPS 展示和若干 UI 文案/可用性问题。

测试:补充 AI 分割、Canvas、Dashboard、FrameTimeline、ProjectLibrary、TemplateRegistry、ToolsPalette、VideoWorkspace、API 和后端任务/AI/dashboard 测试。

验证:npm run lint;npm run test:run;python -m pytest backend/tests -q。
2026-05-02 05:17:18 +08:00

313 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AGENTS.md — AI 编码助手项目指南
> 本文件面向 AI 编码助手。阅读者应假设对该项目一无所知。以下信息基于当前仓库实际文件、脚本和源码不要把早期设计目标当作已实现事实。任何代码和功能修改都要落实到文档和测试上如果生成git commit信息要逐个列点把所有修改都列上重要的、大的修改放前面不重要的、小的修改列在后面。
---
## 项目概述
本项目是一个**语义分割系统**Semantic Segmentation System当前形态是 React 前端 + FastAPI 后端的全栈 Web 应用,用于视频/DICOM 医学影像上传、显式视频生成帧、交互式 Canvas 标注、视频片段传播、GT mask 导入、可选 SAM 2.1 tiny/small/base+/large 辅助分割、模板分类管理和标注导出。SAM 3 相关源码和安装脚本保留在仓库中,但由于当前产品不提供文本提示,前端入口已隐藏,后端 registry 也不暴露 `sam3` 模型。
- **项目名称**: `react-example``package.json` 中的 `name`
- **前端入口**: `src/main.tsx``src/App.tsx`
- **前端服务入口**: `server.ts`Express + Vite 中间件 / 生产静态服务;旧版 mock API 已清理)
- **后端入口**: `backend/main.py`FastAPI
- **默认前端地址**: `http://localhost:3000`
- **默认后端地址**: `http://localhost:8000`
- **前端 API 配置**: `src/lib/config.ts`,优先读取 `VITE_API_BASE_URL`,未配置时按当前浏览器 hostname 推导 `http://<host>:8000`
- **业务文档**: `语义分割系统构建方案.docx`(项目根目录)
---
## 技术栈
| 层级 | 技术 |
|------|------|
| 前端框架 | React 19 + TypeScript 5.8 |
| 构建工具 | Vite 6 |
| 前端样式 | TailwindCSS 4 + 自定义深色主题 |
| 前端状态 | Zustand`src/store/useStore.ts` |
| 前端请求 | Axios`src/lib/api.ts` |
| 实时通信 | WebSocket 客户端(`src/lib/websocket.ts` |
| Canvas 渲染 | Konva + react-konva + use-image |
| 几何布尔运算 | polygon-clipping |
| 图标库 | lucide-react |
| 动画依赖 | motion`package.json` 中声明) |
| AI SDK 依赖 | `@google/genai`(在 `package.json` 中声明;当前业务源码未直接调用) |
| 后端框架 | FastAPI + Uvicorn |
| ORM / 数据库 | SQLAlchemy + PostgreSQL |
| 缓存 / 队列 Broker | Redis |
| 后台任务 | Celery worker |
| 对象存储 | MinIO |
| AI 推理 | 当前启用 SAM 2.1 + PyTorch可选 tiny/small/base+/large`GET /api/ai/models/status` 返回真实 GPU 和各 SAM 2.1 变体状态SAM 3 源码保留但产品入口禁用 |
| 视频 / 影像处理 | FFmpeg / OpenCV / pydicom |
| 运行时 | Node.js ES ModulesPython 3.11 后端环境;历史保留的 `sam3` Python 3.12 conda 环境不是当前必需运行条件 |
---
## 项目结构
```
Seg_Server/
├── server.ts # Express + Vite 前端入口;不再提供旧版 /api mock
├── index.html # SPA HTML 入口
├── vite.config.ts # Vite 配置;含 @/* 路径别名与 DISABLE_HMR 逻辑
├── tsconfig.json # TypeScript 配置;@/* 映射到项目根目录
├── package.json # npm 依赖与脚本
├── .env.example # AI Studio/Gemini 前端环境变量模板
├── metadata.json # AI Studio 元数据
├── public/
│ └── logo.png # Sidebar 使用的 /logo.png
├── doc/ # 当前实现审计、接口契约和后续实施文档
├── start_services.sh # 本地一键启动 PostgreSQL/Redis/MinIO/FastAPI/Celery/前端
├── backend/ # FastAPI 后端
│ ├── main.py # 应用入口、lifespan、CORS、路由注册、WebSocket
│ ├── config.py # Pydantic Settings读取 backend/.env
│ ├── database.py # SQLAlchemy Engine / Session
│ ├── models.py # Project/Frame/Template/Annotation/Mask/ProcessingTask ORM
│ ├── schemas.py # Pydantic 请求/响应模型
│ ├── minio_client.py # MinIO 上传、下载、预签名 URL
│ ├── redis_client.py # Redis 连接封装
│ ├── celery_app.py # Celery app 配置
│ ├── worker_tasks.py # Celery 任务入口
│ ├── download_sam2.py # SAM 2 权重下载脚本
│ ├── setup_sam3_env.sh # 历史保留的 SAM 3 独立 Python 3.12 环境安装脚本;当前产品入口禁用
│ ├── requirements.txt # Python 依赖
│ ├── routers/
│ │ ├── auth.py # /api/auth/login
│ │ ├── projects.py # /api/projects 与 /api/projects/{id}/frames
│ │ ├── templates.py # /api/templates
│ │ ├── media.py # /api/media/upload、/upload/dicom、/parse
│ │ ├── ai.py # /api/ai/predict、/propagate、/propagate/task、/models/status、/auto、/annotate
│ │ └── export.py # /api/export/{project_id}/coco、/masks
│ └── services/
│ ├── frame_parser.py # FFmpeg/OpenCV 拆帧、pydicom 读片、帧上传
│ ├── propagation_task_runner.py # Celery 自动传播任务 runner
│ ├── sam2_engine.py # SAM 2.1 变体选择、单帧推理和 video predictor 传播封装
│ ├── sam3_engine.py # 历史保留的 SAM 3 桥接实现;当前未接入 registry
│ ├── sam3_external_worker.py # 历史保留的独立 sam3 helper当前未被产品入口调用
│ └── sam_registry.py # 当前暴露 SAM 2.1 变体、GPU 状态与推理分发
└── src/ # React 前端
├── main.tsx # React StrictMode 挂载
├── App.tsx # 登录拦截 + 模块切换
├── index.css # TailwindCSS 导入 + 全局样式
├── store/useStore.ts # Zustand 全局状态
├── lib/api.ts # Axios API 封装
├── lib/websocket.ts # 解析进度 WebSocket 客户端
├── lib/utils.ts # cn() 工具函数
└── components/ # 扁平化组件目录
├── Login.tsx
├── Sidebar.tsx
├── Dashboard.tsx
├── ProjectLibrary.tsx
├── VideoWorkspace.tsx
├── CanvasArea.tsx
├── ToolsPalette.tsx
├── OntologyInspector.tsx
├── FrameTimeline.tsx
├── AISegmentation.tsx
├── TransientNotice.tsx
└── TemplateRegistry.tsx
```
以下目录/文件通常是运行产物或本地数据,已在 `.gitignore` 中忽略:`node_modules/``dist/``models/``uploads/``frames/``Data_*/``*.mp4``*.dcm``*.7z``backend/.env`、日志文件等。
`doc/` 目录是当前项目的事实文档入口。修改功能前优先查看:
- `doc/03-frontend-element-audit.md`:哪些前端元素是真功能,哪些是 Mock/UI-only。
- `doc/04-api-contracts.md`:前后端接口契约,以及当前不一致点。
- `doc/05-implementation-plan.md`:建议的后续实施顺序。
- `doc/10-installation.md`:完整安装部署流程,覆盖 PostgreSQL、Redis、MinIO、FastAPI、Celery、前端和 SAM 2.1 权重。
---
## 构建与运行命令
### 前端 / Node 入口
```bash
npm install
# 开发模式:运行 tsx server.tsExpress 集成 Vite middleware端口 3000
npm run dev
# 生产构建:输出 dist/
npm run build
# Vite 预览
npm run preview
# 生产模式运行 server.ts服务 dist/
npm start
# TypeScript 类型检查
npm run lint
# 删除 dist/
npm run clean
```
### FastAPI 后端
```bash
cd backend
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
```
### 一键启动
```bash
./start_services.sh
```
该脚本会依次检查/启动 PostgreSQL、Redis、MinIO、FastAPI 后端、Celery worker 和前端。
---
## 运行时架构
### 前端
- 单页应用,无路由库;模块切换由 `useStore().activeModule` 控制。
- 模块值包括:`dashboard``projects``ai``workspace``templates`
- 默认模块是 `workspace`
- 未登录时渲染 `Login`
- 登录成功后 token 写入 `localStorage`Axios request interceptor 会附加 `Authorization: Bearer <token>`
- `App.tsx` 在登录后调用 `getProjects()` 初始化项目列表。
### 后端
- 主后端是 `backend/main.py` 的 FastAPI 服务。
- `lifespan` 启动时会:
- 创建数据库表;
- 检查/创建 MinIO bucket `seg-media`
- 测试 Redis 连接;
- 后台 seed 默认模板;
- 如果本地存在 `Data_MyVideo_1.mp4`,后台 seed 默认演示项目并拆前 100 帧。
- API 路由包括:
- `POST /api/auth/login`
- `GET/POST/PATCH/DELETE /api/projects`
- `GET/POST /api/projects/{project_id}/frames`
- `GET/POST/PATCH/DELETE /api/templates`
- `POST /api/media/upload`
- `POST /api/media/upload/dicom`
- `POST /api/media/parse`
- `GET /api/tasks`
- `GET /api/tasks/{task_id}`
- `POST /api/tasks/{task_id}/cancel`
- `POST /api/tasks/{task_id}/retry`
- `POST /api/ai/predict`
- `POST /api/ai/propagate`
- `POST /api/ai/propagate/task`
- `GET /api/ai/models/status`
- `POST /api/ai/auto`
- `POST /api/ai/annotate`
- `POST /api/ai/import-gt-mask`
- `GET /api/ai/annotations`
- `PATCH/DELETE /api/ai/annotations/{annotation_id}`
- `GET /api/dashboard/overview`
- `GET /api/export/{project_id}/coco`
- `GET /api/export/{project_id}/masks`
- `GET /health`
- `WS /ws/progress`
### 存储
- PostgreSQL 存储项目、帧、模板、标注、mask 和后台任务元数据。
- MinIO 存储上传视频、DICOM、拆出的帧、缩略图等对象前端展示使用预签名 URL。
- Redis 当前作为 Celery broker/result backend并用于连接检查。
---
## 主要业务流程
1. 登录:`Login.tsx` 调用 `POST /api/auth/login`,默认开发凭证为 `admin / 123456`
2. 项目管理:`ProjectLibrary.tsx` 调用项目 API 创建项目、拉取列表、删除项目删除当前项目后会清空工作区当前项目、帧、mask 和选区。
3. 上传资源:视频走 `/api/media/upload`只上传源文件并关联项目不自动拆帧DICOM 批量走 `/api/media/upload/dicom`
4. 生成帧入队:用户在项目库点击“生成帧”,选择目标 FPS 后前端调用 `/api/media/parse`;后端创建 `ProcessingTask` 并投递 Celery接口支持 `parse_fps``max_frames``target_width` 标准帧序列参数;项目库和模板库的成功/失败短反馈使用非阻塞 `TransientNotice`,会自动消失。
5. worker 执行Celery worker 用 FFmpeg 优先拆视频帧,失败后用 OpenCV fallbackDICOM 使用 pydicom视频帧按 `frame_%06d.jpg` 连续命名并记录 `timestamp_ms``source_frame_number` 和任务 `frame_sequence` 元数据。
6. 帧展示:`VideoWorkspace.tsx` 调用 `/api/projects/{id}/frames``CanvasArea.tsx``FrameTimeline.tsx` 显示当前帧与时间轴缩略图;`CanvasArea` 会按容器和帧尺寸默认居中放大底图并保留边距;`FrameTimeline` 会根据已保存标注回显到 `Mask.metadata` 的传播来源,把自动传播生成的帧在视频处理进度条显示为蓝色区段,人工/AI 标注帧显示红色竖线;视频处理进度条和红/蓝标识可点击跳转到对应帧;底部缩略图中人工/AI 标注帧用红色边框、自动传播/推理帧用蓝色边框,当前帧仍以青色外框高亮优先;若当前帧同时是人工/AI 标注帧,则在青色外框内增加红色内描边;只有进入自动传播范围选择模式时,播放进度条和视频处理进度条才显示黄色范围框,并可点击/拖拽选择传播起止帧;前端 `Frame` 会保留后端返回的帧序列时间戳和源帧号。
7. 手工标注:`CanvasArea.tsx` 支持多边形、矩形、圆、点区域和线段生成 polygon mask多边形可按 Enter 或点击首节点闭合;绘制工具可在已有 mask 上继续落点;工具栏有“调整多边形”入口,左侧 `ToolsPalette` 使用紧凑垂直布局并在高度不足时自身滚动;点击 mask 后可按住顶点直接拖动并实时更新 polygon也可删除 polygon 顶点、通过边中点或双击边界插入新顶点,并能选择编辑多 polygon mask 的单个子区域;选中整块 mask 可用 Delete/Backspace 删除,已保存 mask 会同步后端删除;区域合并/去除会隐藏编辑手柄并显示已选数量,第一个选中的主区域用黄色实线轮廓,后续参与合并/扣除的区域用红色虚线轮廓,使用 `polygon-clipping` 做 union/difference内含去除结果用 even-odd 规则渲染 holeZustand 维护 `maskHistory/maskFuture` 支持撤销/重做。
8. AI 分割:前端工具包括 SAM 2.1 变体选择、正向点、反向点和框选AI 画布会按容器和当前帧尺寸默认居中放大底图并保留边距;工作区和 AI 页面都可点击已有提示点删除单点AI 页面也可删除最近锚点、删除选中候选或清空本页锚点;这些删除入口会限制在当前提示点/本页 AI 候选范围内,避免误删工作区已有 mask。SAM 2.1 框选会建立候选 mask后续正/反点通过 `interactive` prompt 携带原始框和累计点细化同一个候选 maskAI 页面框选会先固化 `promptBox`,执行分割时只框选发送 `box` prompt框选后继续加正/反点发送 `interactive` prompt重复执行高精度分割会替换上一次 AI 页候选,只保留最新一个候选。包含反向点时工作区会传 `options.auto_filter_background=true``min_score=0.05`,如果后端过滤为空则移除旧候选 mask。后端 `ai.py` 期望按 `image_id``prompt_type``prompt_data``model` 和可选 `options` 调用 SAM registry。当前 registry 暴露 `sam2.1_hiera_tiny``sam2.1_hiera_small``sam2.1_hiera_base_plus``sam2.1_hiera_large`,并兼容 `sam2` 作为 tiny 别名;`model=sam3` 会被拒绝,`semantic` 文本提示也被禁用。SAM 2.1 支持点/框/interactive/自动分割和 video predictor 传播多候选默认只采用最高分区域避免重叠候选同时显示AI 页面只渲染本页最新生成的候选 mask不会把工作区已有 mask 带入 AI 画布AI 页面生成的 mask 会写入全局 `masks` 并自动选中,右侧分类树可直接改标签,推送到工作区会切到“调整多边形”并保留选择。`options.crop_to_prompt` 可对点/框/interactive prompt 做局部裁剪推理并回映射,`options.auto_filter_background` 可按分数和负向点过滤结果。
9. 视频片段传播:工作区以当前打开帧作为参考帧,使用该帧全部 mask 作为 seed并用传播起始帧和传播结束帧指定追踪范围用户可直接修改数字框也可点击“自动传播”进入时间轴范围选择模式在播放进度条或视频处理进度条上点击/拖拽选择范围,再点击“开始传播”。工作区顶栏有独立“传播权重”选择器,可为本次传播二次选择 SAM 2.1 tiny/small/base+/large 权重,不提供 SAM2/SAM3 家族切换,也不影响 AI 单帧分割权重;前端会按传播权重 id、seed mask、seed 来源 id 和前/后方向组装 `steps` 并调用 `POST /api/ai/propagate/task` 创建 `propagate_masks` 后台任务;后端入队时会规范化/校验权重 id 并把规范化后的 id 写入任务 payload/resultCelery worker 顺序执行各 step避免多个视频 tracker 并发抢占 GPU每个 step 会根据 seed 来源 id、权重、方向和 seed 签名做幂等判断,未改变的 seed 直接跳过,已改变的 seed 会先删除同源旧自动传播标注再重传;后端按项目帧序列下载片段帧,当前使用所选 SAM 2.1 权重变体的 `SAM2VideoPredictor.add_new_mask()` + `propagate_in_video()`,并把后续帧结果保存为 `Annotation`。工作区轮询 `GET /api/tasks/{task_id}` 展示进度并刷新标注Dashboard 也能显示/取消/重试传播任务。
10. GT 导入:工作区“导入 GT Mask”调用 `/api/ai/import-gt-mask`;后端按非零像素值和连通域生成 polygon 标注,并用 distance transform 生成 seed point前端回显 seed point拖动后可归档更新。
11. 模板管理:`TemplateRegistry.tsx` 管理分类、颜色和 z-index`OntologyInspector.tsx` 在工作区显示当前模板分类树。
12. 导出:后端支持 COCO JSON 和 PNG mask ZIP 导出PNG ZIP 包含单标注 mask、按 zIndex 融合的语义 mask 和 `semantic_classes.json`
---
## 当前实现注意事项
- `src/lib/config.ts` 会优先读取 `VITE_API_BASE_URL``VITE_WS_PROGRESS_URL`;未配置时按当前浏览器 hostname 推导后端 `:8000` 地址。
- 前端 `predictMask()` 已按后端 `PredictRequest` 发送 `image_id``prompt_type``prompt_data``model`,并将后端 `polygons` 转成 Konva 可渲染的 `pathData`
- 手工绘制工具会生成可保存的 `Mask.segmentation`;撤销/重做通过 `maskHistory/maskFuture` 工作。
- Polygon 顶点编辑和新增顶点会重算 `pathData/segmentation/bbox/area`;已保存 mask 进入 dirty 状态后复用归档 PATCH 链路。
- 区域合并/去除会重算主 mask 的几何;合并已保存的次级 mask 时会通过工作区回调删除对应后端标注。
- 前端 `importGtMask()` 已对齐后端 `/api/ai/import-gt-mask`;工作区“导入 GT Mask”会导入后端生成的多类别标注和 seed point 并回显。
- 前端 `exportCoco()` 已对齐后端 `/api/export/{project_id}/coco`;前端 `exportMasks()` 已对齐后端 `/api/export/{project_id}/masks`;工作区导出按钮会先保存当前待归档 mask。
- 工作区“结构化归档保存”按钮已接入 `POST /api/ai/annotate``PATCH /api/ai/annotations/{id}`;加载工作区时会通过 `GET /api/ai/annotations` 回显已保存标注。
- 工作区“自动传播”按钮已接入 `POST /api/ai/propagate/task`;若用户尚未显式设置范围,第一次点击会进入时间轴范围选择模式,第二次点击“开始传播”才提交后台任务;当前启用所选 SAM 2.1 变体的视频 predictor 后台任务,运行中轮询任务进度,完成后刷新后端已保存标注;同步 `POST /api/ai/propagate` 仍作为单 seed 兼容接口保留。
- 工作区顶栏短状态会自动消失;保存、导出、导入 GT、传播进行中和无帧项目提示会保留到状态变化。
- 工作区“清空遮罩”会调用 `DELETE /api/ai/annotations/{id}` 删除当前帧已保存标注,并清空当前帧本地 mask。
- 项目状态已统一为 `pending``parsing``ready``error`;前端 `src/lib/api.ts` 会兼容归一化旧库中可能存在的 `Ready``Parsing``Error`
- 项目库的视频导入与生成帧是两个独立动作:导入视频只上传源文件,生成帧按钮才会带 `parse_fps` 调用 `/api/media/parse`;工作区不会再因“有视频但无帧”自动创建拆帧任务。
- `server.ts` 不再提供旧版 `/api/login``/api/projects``/api/templates` mock当前前端真实 API 调用走 FastAPI 的 `/api/auth/*``/api/projects``/api/templates` 等接口。
- `Dashboard.tsx` 初始统计、任务进度和活动日志来自 `GET /api/dashboard/overview`;任务进度来自 `processing_tasks` queued/running/success/failed/cancelled处理中统计只计算 queued/running支持取消 queued/running 任务、重试 failed/cancelled 任务和查看失败详情。Celery worker 通过 Redis pub/sub 的 `seg:progress` 频道推送细粒度进度,再由 FastAPI 广播到 `/ws/progress`;前端 WebSocket 客户端通过 `onopen/onclose/onerror` 更新连接状态,并定时发送 `ping` 心跳。
---
## 代码风格与约定
### 样式规范
- 深色主题为主,常见背景色包括 `#0a0a0a``#111``#0d0d0d``#151515``#1e1e1e`
- 青色(如 `cyan-400` / `cyan-500`)用于激活状态、主按钮和关键指标。
- 前端样式主要使用 TailwindCSS 工具类,通过 `cn()` 合并条件类名。
- `src/index.css` 使用 TailwindCSS 4 的 `@import "tailwindcss";`
### 组件规范
- 组件使用函数组件 + Hooks。
- 当前组件目录是扁平结构:`src/components/*.tsx`,不是按模块子目录分层。
- Props 类型优先使用 TypeScript `interface`
- UI 文本保持中文。
- 代码与注释优先使用英文。
### 命名规范
- 组件文件使用 PascalCase例如 `AISegmentation.tsx`
- 工具文件使用 camelCase例如 `utils.ts`
- 类型和接口使用 PascalCase。
---
## 测试策略
当前仓库已配置前端 Vitest 测试和后端 pytest 测试。测试依据 `doc/07-current-requirements-freeze.md``doc/08-current-design-freeze.md``doc/09-test-plan.md`
- 前端测试配置:`vitest.config.ts`,共享 setup 在 `src/test/setup.tsx`
- 前端测试命令:`npm run test:run`
- 后端测试依赖:`backend/requirements-dev.txt`
- 后端测试命令:`pytest backend/tests`,或在 `backend/` 目录执行 `pytest tests`
- 基础静态校验:`npm run lint``npm run build``python -m py_compile backend/routers/ai.py backend/routers/templates.py backend/schemas.py`
- 后端测试使用内存 SQLite、fake MinIO 和 fake SAM registry不依赖真实 PostgreSQL、MinIO、Redis 或模型权重。
---
## 安全注意事项
- FastAPI 登录是开发用硬编码凭证:`admin / 123456`
- 登录成功返回固定 token`fake-jwt-token-for-admin`,没有真实 JWT 签名校验。
- Axios 会附加 Bearer token但后端大多数业务路由当前没有鉴权依赖。
- `backend/.env``.gitignore` 忽略不要提交真实数据库、MinIO、Redis、模型路径等敏感配置。
- `start_services.sh` 中包含本机路径和 sudo 启动逻辑,迁移机器时要审查。
- Express `server.ts` 只负责前端开发/静态服务,不承担业务 API 或鉴权。
---
## AI Studio / Vite 特定配置
- `.env.example` 包含 `GEMINI_API_KEY``APP_URL`,说明这些值由 AI Studio 注入。
- `vite.config.ts` 通过 `loadEnv``GEMINI_API_KEY` 注入到 `process.env.GEMINI_API_KEY`
- `vite.config.ts` 中的 `DISABLE_HMR` 逻辑用于关闭 HMR避免 AI Studio agent 编辑时闪烁。**不要随意修改该逻辑。**