命令行适合「每天自动跑」,但不适合「我现在就想看看茅台」。Web 工作台把同一套能力装进浏览器:手动分析、实时进度、历史报告、回测、持仓、告警、选股、配置与 Token 用量,都在一个界面里。
先分清「后端」和「前端」
后端是 FastAPI(Python),负责分析、API、任务队列与静态托管;前端是 React 单页应用(SPA),构建后放进 static/,由后端同源提供。只启动后端、没构建前端时,页面会提示 Frontend Not Built,但 API 仍然可用。
页面地图:12 个路由,各管一件事
工作台布局示意,非实际截图
| 页面 | 你在这里做什么 | 关键能力 |
|---|---|---|
工作台 / | 看今日概览、自选股状态、快速发起分析 | 手动分析、任务进度、历史入口 |
策略问股 /chat | 多轮追问一只股票,选择策略 | 流式输出、工具调用可视化、导出 |
持仓 /portfolio | 管理成本、数量、风险暴露 | 市场/币种暴露、风险旗标、导入 |
决策信号 /decision-signals | 追踪历史建议是否兑现 | 入场/止损/目标、状态、后验评估 |
选股 /screening | 按条件扫描候选池 | 解释「为什么选中 / 为什么是现在」 |
数据中心 /data | 看数据源能力与质量 | 市场 × 数据集矩阵、路由优先级、警告 |
回测 /backtest | 验证策略/建议的历史表现 | 命中率、收益、回撤、样本量 |
告警 /alerts | 设置价格/指标/信号规则 | 冷却、去重、通知联动 |
用量 /usage | 查看 Token 与调用成本 | 按模型/渠道/时间聚合 |
设置 /settings | 改模型、通知、数据源、调度 | 配置校验、测试渠道、草稿保护 |
登录 /login | 单管理员认证 | 首次设密码、会话 Cookie |
404 兜底 * | 未知路径 | 兜底提示,不暴露内部路由 |
手动分析一次,发生什么
前端调用 POST /api/v1/analysis/analyze,可单股或批量。同步模式直接返回 200;异步模式返回 202,并给一个任务 ID;重复提交可能返回 409。
任务进入队列,由服务层调用与 CLI 相同的 pipeline.process_single_stock()。不会存在第二套分析逻辑。
前端共享一个 SSE 连接 GET /api/v1/analysis/tasks/stream,接收创建、开始、进度、完成、失败与心跳事件。
完成后从历史接口读取结构化结果与 Markdown;支持复制、下载、分享图片。
POST /api/v1/analysis/analyze # 单股/批量分析
GET /api/v1/analysis/tasks # 任务列表
GET /api/v1/analysis/tasks/{id}/flow # 四段式运行诊断
GET /api/v1/analysis/status/{id} # 状态
GET /api/v1/history # 历史分页
GET /api/v1/history/{id}/markdown # 完整 Markdown
GET /api/v1/history/{id}/share-image # 分享海报
前端:React 19 + Vite + Tailwind
前端是一个典型的现代 SPA:React 19、React Router 7、Vite 7、TypeScript、Tailwind 4、Zustand、axios、Recharts。状态管理很克制,只有少数几个 store:分析任务、Agent 会话、股票池。没有 Redux,也没有 React Query。
- API 层:axios 同源请求,带 Cookie;响应统一从 snake_case 转 camelCase;401 自动跳登录。
- 主题:默认深色,首屏脚本恢复上次选择,避免闪烁。
- 国际化:自研 key 字典(中文/英文),不是 i18next;切换时同步
<html lang>。 - 构建:
npm run build输出到仓库根static/,由 FastAPI 托管;npm run dev可独立开发。
cd apps/dsa-web
npm ci
npm run dev # 本地开发
npm run build # 产出到 ../../static
npm run lint
npm run test # Vitest(CI 默认不跑)
npm run test:smoke # Playwright
后端:FastAPI + SSE + 静态托管
api/app.py 创建 FastAPI 应用,挂载 /api/v1、认证中间件、静态资源与 SPA fallback。lifespan 启动时会恢复 Web 内配置的定时任务、预热股票索引;关闭时停止调度。
| 能力 | 实现 | 注意 |
|---|---|---|
| 任务进度 | SSE,30 秒心跳 | 前端共享一个 EventSource,别为每个任务各开一条 |
| Agent 流式 | POST + 流式响应 | 可取消;不是 WebSocket |
| 静态资源 | static/ + SPA fallback | /api/* 不做 fallback |
| 健康检查 | /api/health | Docker healthcheck 使用 |
| 自动构建 | WEBUI_AUTO_BUILD | 启动时尝试 npm 安装/构建,生产环境更推荐预构建 |
API:15 组、约 131 个端点
所有路由挂在 /api/v1。它不是「内部接口随便开」,而是有认证、错误封装、分页与限流的正式 API,也可以被 Bot、桌面端与外部集成复用。
| 前缀 | 负责 | 代表端点 |
|---|---|---|
/analysis | 分析任务 | analyze、tasks、market-review |
/agent | 问股 | chat、chat/stream、research |
/history | 历史报告 | list、detail、markdown、share-image |
/portfolio | 持仓 | 导入、快照、风险暴露 |
/alerts | 告警 | 规则、冷却、测试 |
/screening | 选股 | 运行、结果、解释 |
/decision-signals | 决策信号 | 列表、后验评估、反馈 |
/data | 数据中心 | overview、能力矩阵 |
/system | 配置与调度 | config、scheduler/status、test-channel |
/usage | 用量 | summary |
/auth | 登录 | 状态、登录、登出、改密 |
/health | 健康 | 存活与版本 |
认证:默认关闭,公网必须打开
DSA 是单管理员模型:ADMIN_AUTH_ENABLED=true 后,首次访问会引导设置密码。密码用 PBKDF2-SHA256(10 万次迭代 + 随机盐)存哈希;会话是 HttpOnly + SameSite=Lax 的 dsa_session Cookie,默认 24 小时;失败登录 5 次/300 秒限流;登出会轮换密钥,使所有旧会话失效。
三个安全底线
① ADMIN_AUTH_ENABLED 只读取 .env,仅设置系统环境变量不会生效;② 公网部署必须加 HTTPS 反向代理;③ 不要把 .env、data/、reports/ 暴露到公网或提交到仓库。
认证中间件只保护 /api/v1/*,放行登录、状态、健康检查与文档。未登录返回 401 JSON,前端据此跳转登录页。
持仓、告警、选股与用量
持仓
记录成本、数量、币种与市场,过滤已清仓标的,展示市场暴露、币种暴露与风险旗标。支持图片/CSV/Excel 导入与 Futu 真实持仓。
告警
价格、指标、决策信号都可设规则;触发后经过降噪与冷却,再走统一通知服务。告警不是交易指令,只是提醒。
选股
按条件扫描候选池,输出「为什么选中 / 为什么是现在」的确定性解释,并标注数据质量缺口。
Token 用量
按模型、渠道、时间统计调用与 token,帮助判断「成本花在哪」。用量只记录归一化数字与 HMAC 指纹,不存 Prompt 明文。
桌面端:把 Web 装进一个窗口
apps/dsa-desktop 用 Electron 封装 Web 与后端,打包成可双击安装的客户端,适合不想开浏览器的用户。桌面端复用同一套 API,分享海报可以由本地 Chromium 截图生成。具体打包流程见 docs/desktop-package.md。
常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 页面显示 Frontend Not Built | 前端未构建到 static/ | 构建前端或用 Docker 镜像 |
| API 能用但页面空白 | 静态资源路径/构建产物缺失 | 检查 static/index.html 与 /assets |
| 设置了环境变量仍无认证 | 认证只读 .env | 写进 .env 并重启 |
| GitHub Actions 里访问不到 Web | Actions 只跑 CLI 分析 | 用本地、Docker 或云服务器 |
| 任务一直转圈 | SSE 被代理缓冲/超时 | 关闭反代缓冲,或看任务状态接口 |
为什么 ADMIN_AUTH_ENABLED=true 写在 shell 环境变量里可能不生效?
src/auth.py),它用 dotenv_values 直接读取 .env。这是一种有意的隔离,但也意味着「export 一下」不会生效。部署时请把认证配置写进 .env,并重启服务。