docs: 记录 portal 用户界面实现 (#12)

This commit is contained in:
ila
2026-08-21 11:28:07 +08:00
parent d7233d4f1e
commit 9521fbbaa0
5 changed files with 50 additions and 27 deletions
+4 -4
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Project-Profile
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Project-Profile.-
wiki_revision: 233014296f1a555f1e223cbbeeedd5a974b767ca
synchronized_at: 2026-08-21T02:32:36Z
wiki_revision: 9e3b50db59ed7ff77bc900d2dfa61c66ce97fd83
synchronized_at: 2026-08-21T03:22:44Z
<!-- gitea-wiki-mirror:end -->
# 项目档案
@@ -43,7 +43,7 @@ synchronized_at: 2026-08-21T02:32:36Z
|---|---|---|---|---|---|
| `internal/core` 核心域库 | 领域模型、provider 协议抽象、生成编排、队列状态、加密接口 | Go 1.26.5、GORM、标准库 | `go test ./internal/core/...` | 不单独发布 | **不得 import Gin、go-admin、gobreaker 或 imaging** |
| `internal/platform` 基础设施适配 | 出站 HTTP/SSRF、熔断、图片处理、存储等接口实现 | 标准库、sony/gobreaker、disintegration/imaging | `go test ./internal/platform/...` | 随服务二进制发布 | 通过接口注入 core,不反向污染领域层 |
| `portal` 用户端二进制 | 会话、页面/HTMX 片段、JSON API,MVP 阶段内嵌 worker | Gin、html/template、HTMX、Alpine、Tailwind | `go test ./portal/...` | 单二进制 | 依赖 core 与 platform;提交链路不得调用上游 |
| `portal` 用户端二进制 | 会话、页面/HTMX 片段、JSON API,MVP 阶段内嵌 worker | Gin、html/template、HTMX、Alpine、Tailwind | `pnpm --dir portal/web build`、`go test ./portal/...`、浏览器 E2E | 单二进制 | 依赖 core 与 platform;提交链路不得调用上游 |
| `admin` 管理端后端 | Provider/Model/路由池/记录/用户与点数管理 | 固定提交的 go-admin(Gin、GORM、Casbin、JWT) | `go test ./admin/...` | 独立二进制 | 依赖 core;不得复制生成逻辑 |
| `admin-ui` 管理端前端 | go-admin-ui CRUD 与少量定制页 | Vue 3.5.41、Element Plus 2.14.4、Vue CLI 5.0.9 | `pnpm install --frozen-lockfile`、`pnpm build:prod` | 静态产物 | 代码生成器只在隔离开发环境使用 |
| `migrations` 数据库迁移 | 业务表、`sys_*` 基线和菜单/API 配置的全部生产演进 | golang-migrate SQL | up/down 隔离库验证 | 随版本发布 | **生产数据库结构的唯一事实来源** |
@@ -76,7 +76,7 @@ synchronized_at: 2026-08-21T02:32:36Z
| 平台适配 | gobreaker、imaging | 只能位于 `internal/platform` |
| 限流 | ulule/limiter | MVP-2 用户维度令牌桶 |
| 上游调用 | `net/http` 适配器 | 支持多图、`extra_body`、URL/Base64 差异,并实施 SSRF 钩子 |
| 用户端 UI | html/template + HTMX 2.x + Alpine 3.x + Tailwind 4.x standalone | portal 不需要 Node;admin-ui 构建需要 Node/pnpm |
| 用户端 UI | html/template + HTMX 2.x + Alpine 3.x + Tailwind 4.x | portal 运行时不需要 Node;重建 portal/web 与 admin-ui 静态资源需要 Node/pnpm |
| 开发环境 | Windows + PowerShell + Git;本机隔离 MySQL 8 | Harness 使用 Python 3 标准库;Chorus 专用实例使用回环地址和独立端口/数据目录 |
### 已验证的 MVP-0 本机基线(2026-08-20)
+12 -5
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Architecture-and-Code-Map
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Architecture-and-Code-Map.-
wiki_revision: 04391bbce2fcc59da2078265c020a8a845ca4c16
synchronized_at: 2026-08-20T16:52:38Z
wiki_revision: 08b7352c995a45a8254943958da8ada72c210b13
synchronized_at: 2026-08-21T03:22:57Z
<!-- gitea-wiki-mirror:end -->
# 架构与代码地图
@@ -56,7 +56,7 @@ MVP-0 第一张上传图自动作为 `primary`,其余为 `reference`;不提
- `migrations/000001_mvp0_base` 建立 `users`、`providers`、`provider_models`、`prompt_templates`、`generations`、`generation_inputs`、`generation_outputs`,生产启动不调用 AutoMigrate。
- 迁移显式定义用户幂等唯一键、队列/历史索引、5 个外键和删除规则,以及状态、租约、错误、attempt 数组和输出内容检查约束。
- `internal/seed/defaults.json` 保存已确认 Prompt 和 mock ProviderModel 数据;`cmd/chorus-seed` 在事务中幂等 upsert 构造用户、mock Provider/Model 和 Prompt,不保存或输出明文凭据。
- `portal/handler`、`service`、`auth` 和 `session` 已在 #11 落地用户 API 与安全边界;最终页面与静态资源仍属于 #12。`portal/worker` 负责异步上游调用,提交 handler 没有 Provider 依赖。
- `portal/handler`、`service`、`auth` 和 `session` 在 #11 落地用户 API 与安全边界;`portal/web` 在 #12 落地模板、静态资源和浏览器行为。`portal/worker` 负责异步上游调用,提交 handler 没有 Provider 依赖。
### #7 已落地的核心契约
@@ -106,13 +106,20 @@ MVP-0 第一张上传图自动作为 `primary`,其余为 `reference`;不提
- 同步提交只创建 pending、inputs 和 rendered_prompt,不 import 或调用 Provider/worker,也不读取点数。JSON 与 HTMX 查询共享同一用户作用域服务,认证过期返回 401;HTMX 额外返回登录跳转提示,不修改 generation 状态。
- 任务、原图、生成图和缩略图查询先用 generation.user_id 过滤,再核对文件 metadata 的 owner/generation;响应只给受控 URL、内容和安全文件名,不暴露 storage key 或文件系统路径。
- Gin 1.12.0 与用户指定 go-admin 基线一致。portal 不注册公开文件目录、注册、找回密码、管理员登录、点数或同步上游路由。
### #12 已落地的 portal 用户界面
- `portal/web/templates` 负责登录页、工作区和结果片段;`portal/web/static` 负责本地 CSS、交互脚本及固定版本的 HTMX、Alpine CSP 和 Lucide 产物,运行时不访问 CDN。
- 首屏和状态片段由 `html/template` 输出;HTMX 只轮询和替换 `#result-section`,终态不再带轮询属性。Alpine 仅管理文本/图片模式,原生 JavaScript 负责提交、上传列表、复制、重试、历史搜索和认证失效。
- 网络传输错误和非 401 HTTP 错误保留当前结果并显示恢复提示;401 停止当前交互、显示登录失效对话框,并保留任务路径作为登录返回地址。
- 响应式断点为 375、768、1024 和 1440:手机按结果、输入、历史顺序单列;平板为历史加单列工作区;宽桌面为历史、结果、输入三列。交互目标至少 44px,支持键盘焦点、aria-live 和 reduced-motion。
- `portal/web/package.json` 与 `pnpm-lock.yaml` 固定前端构建依赖;构建生成的静态文件嵌入 Go 二进制。MySQL handler 测试覆盖真实状态片段,Playwright 覆盖四视口、输入错误、上传错误、断网提示和认证失效。
## 代码地图
| 想改什么 | 从哪里开始读 | 必要验证 |
|---|---|---|
| 页面、状态和响应式 | `portal/web/templates/`、`portal/web/static/` | 浏览器 E2E + 已确认原型 |
| 提交校验/幂等 | `portal/handler/generate.go` | handler 测试、跨用户测试 |
| HTMX 卡片轮询 | `portal/handler/card.go` | 终态停止、网络错误、认证失效 |
| 提交校验/幂等 | `portal/handler/router.go`、`portal/service/service.go` | handler 测试、跨用户测试 |
| HTMX 结果轮询 | `portal/handler/router.go`、`portal/web/templates/result.html`、`portal/web/static/app.js` | 终态停止、网络错误、认证失效 |
| prompt 合成 | `internal/core/generate/prompt.go` | 默认模板、图片顺序与落库 |
| 上游协议 | `internal/core/provider/` | mock 429/5xx/超时/连接/400/401/策略拒绝 |
| SSRF/重定向/结果 URL | `internal/platform/http/` | DNS、IPv4/IPv6、redirect、proxy 测试 |
+21 -7
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Local-Development-and-Verification
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Local-Development-and-Verification.-
wiki_revision: bf8347d2ea78ebb91e0e1a9db53b80e53b9065a0
synchronized_at: 2026-08-21T02:32:51Z
wiki_revision: 357def01026f5c12859461d54739c52a0896da4e
synchronized_at: 2026-08-21T03:27:24Z
<!-- gitea-wiki-mirror:end -->
# 本地开发与验证
@@ -19,9 +19,9 @@ synchronized_at: 2026-08-21T02:32:51Z
| Go | **1.26.5** | `go version` |
| MySQL | 8.0,支持 SKIP LOCKED | `mysql --version` |
| golang-migrate | 可执行文件 | `migrate -version` |
| Node | >=22,仅 admin-ui | `node --version` |
| Node | >=22,用于重建 portal/web 与 admin-ui 静态资源 | `node --version` |
| pnpm | 9.15.1,由 packageManager/Corepack 固定 | `pnpm --version` |
| Tailwind | standalone,仅 portal 样式 | `tailwindcss --help` |
| Tailwind | 4.3.3,由 portal/web 锁文件固定 | `pnpm --dir portal/web exec tailwindcss --help` |
2026-08-20 已完成 MVP-0 环境基线:便携 Go 1.26.5、golang-migrate v4.19.1 和隔离 MySQL 8.4.8 已验证。Chorus 专用实例监听 `127.0.0.1:3308`,使用独立数据目录和测试账号;既有 `3307` 实例与 MySQL 5.7 不受影响。Node 22.22.1 可用;admin-ui 后续仍必须按 `packageManager` 使用 pnpm 9.15.1,不能直接采用全局 pnpm 11。
@@ -217,6 +217,8 @@ $env:CHORUS_TEST_DSN = $env:CHORUS_DSN
go test -v -count=1 ./internal/core/queue/...
go test -race -count=1 ./internal/core/queue/...
$env:CHORUS_TEST_DSN = $null
```
Provider/worker 的 mock 协议、真实 MySQL 8 attempt/output 与优雅退出验证:
```powershell
@@ -225,10 +227,9 @@ go test -v -count=1 ./internal/core/provider/... ./internal/platform/http/... ./
go test -race -count=1 ./...
$env:CHORUS_TEST_DSN = $null
```
```
- Provider:全部 retryable 类别使用 mock,不消耗真实额度。
- Portal:真实 MySQL 8 覆盖登录/退出、session/CSRF 轮换、用户作用域幂等、pending 提交、上传边界、任务与文件跨用户授权、终态和 HTMX 401;测试结束 generation/input/output 和构造用户必须为 0。
- Portal handler:真实 MySQL 8 覆盖登录/退出、session/CSRF 轮换、用户作用域幂等、pending 提交、上传边界、任务与文件跨用户授权、终态和 HTMX 401;Go 集成测试结束后清理本次构造数据。浏览器 E2E 使用固定的合成幂等任务,重复运行不持续新增记录。
```powershell
$env:CHORUS_TEST_DSN = $env:CHORUS_DSN
@@ -236,9 +237,19 @@ go test -v -count=1 ./portal/handler/... ./portal/service/... ./portal/session/.
$env:CHORUS_TEST_DSN = $null
```
portal 静态资源和浏览器 E2E 使用固定锁文件。E2E 账号、密码和地址只通过本机环境变量提供,不写入命令示例、仓库或测试报告:
```powershell
corepack pnpm --dir portal/web install --frozen-lockfile
corepack pnpm --dir portal/web build
$env:CHORUS_E2E_BASE_URL = "http://127.0.0.1:8080"
# 在当前进程安全设置 CHORUS_E2E_EMAIL 与 CHORUS_E2E_PASSWORD
corepack pnpm --dir portal/web test:e2e
```
- SSRF:IPv4/IPv6 私网、DNS rebinding、redirect 链、环境代理、结果 URL。
- 安全:密码/session/CSRF、登录节流、跨用户任务与文件、错误脱敏、密钥轮换。
- 浏览器:HTMX 动态状态、终态停止、认证过期、网络错误;375/768/1024,无主区域横向滚动,键盘和 reduced-motion。
- 浏览器:HTMX 动态状态、终态停止、认证过期、网络错误;375/768/1024/1440 无主区域横向滚动,并检查键盘、44px 触控目标和 reduced-motion。
- 供应链:Go/Node 依赖锁定、漏洞和许可证检查按实现工单确定命令。
## 完成修改前
@@ -248,6 +259,9 @@ git status --short --branch
go build ./...
go vet ./...
go test ./...
corepack pnpm --dir portal/web install --frozen-lockfile
corepack pnpm --dir portal/web build
corepack pnpm --dir portal/web test:e2e
pnpm --dir admin-ui install --frozen-lockfile
pnpm --dir admin-ui build:prod
python dev_scripts/harness.py check --strict
+7 -7
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Product-Requirements-Overview
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Product-Requirements-Overview.-
wiki_revision: 54272b7b209f07a314db23be1de81f16aca63f3b
synchronized_at: 2026-08-20T10:02:44Z
wiki_revision: f7337354be2c9e858e214668485e9357a51f6b04
synchronized_at: 2026-08-21T03:27:40Z
<!-- gitea-wiki-mirror:end -->
# 产品需求总览
@@ -29,13 +29,13 @@ synchronized_at: 2026-08-20T10:02:44Z
## 当前需求索引
工单 [#1](https://git.ilapage.cn/OPC/chorus/issues/1) 已完成长期技术基线整理和验收。项目由 [Epic #3](https://git.ilapage.cn/OPC/chorus/issues/3) 统一跟踪,当前阶段由 [MVP-0 #4](https://git.ilapage.cn/OPC/chorus/issues/4) 汇总。MVP-0 生产闭环已拆分为环境 [#5](https://git.ilapage.cn/OPC/chorus/issues/5)、实现 [#6–#12](https://git.ilapage.cn/OPC/chorus/issues/6) 和集成验收 [#13](https://git.ilapage.cn/OPC/chorus/issues/13);尚未开始生产编码,必须按工单前置依赖执行。
工单 [#1](https://git.ilapage.cn/OPC/chorus/issues/1) 已完成长期技术基线整理和验收。项目由 [Epic #3](https://git.ilapage.cn/OPC/chorus/issues/3) 统一跟踪,当前阶段由 [MVP-0 #4](https://git.ilapage.cn/OPC/chorus/issues/4) 汇总。MVP-0 生产闭环已拆分为环境 [#5](https://git.ilapage.cn/OPC/chorus/issues/5)、实现 [#6–#12](https://git.ilapage.cn/OPC/chorus/issues/6) 和集成验收 [#13](https://git.ilapage.cn/OPC/chorus/issues/13)。截至 2026-08-21,生产单元实现已提交并处于逐项验收阶段;#13 仍须等待全部前置单元工单验收后执行。
| 需求领域 | 用户与场景 | 需求状态 | MVP | 详细说明 | 实施工单 | 设计证据 |
|---|---|---|---|---|---|---|
| 核心生成域与单上游 | 用户提交提示词/原图得到结果 | 已确认(待实施) | [MVP-0 #4](https://git.ilapage.cn/OPC/chorus/issues/4) | [架构](Architecture-and-Code-Map.-)、[业务规则](Business-Rules-and-Glossary.-) | [#6 迁移](https://git.ilapage.cn/OPC/chorus/issues/6)、[#7 core](https://git.ilapage.cn/OPC/chorus/issues/7)、[#8 platform](https://git.ilapage.cn/OPC/chorus/issues/8)、[#9 queue](https://git.ilapage.cn/OPC/chorus/issues/9)、[#10 worker](https://git.ilapage.cn/OPC/chorus/issues/10) | 架构/数据/状态设计已确认 |
| 用户端生成页 | 种子用户登录并完成一次异步生成 | 已确认(原型已确认,待实施) | [MVP-0 #4](https://git.ilapage.cn/OPC/chorus/issues/4) | 本页“用户端布局与状态” | [#11 portal 后端](https://git.ilapage.cn/OPC/chorus/issues/11)、[#12 用户界面](https://git.ilapage.cn/OPC/chorus/issues/12) | [原型设计 #2](https://git.ilapage.cn/OPC/chorus/issues/2);`prototypes/2/v1/index.html`,2026-08-20 用户已确认 |
| 默认 prompt template | 系统以数据配置而非硬编码合成提示词 | 已确认(待实施) | [MVP-0 #4](https://git.ilapage.cn/OPC/chorus/issues/4) | [业务规则](Business-Rules-and-Glossary.-) | [#7](https://git.ilapage.cn/OPC/chorus/issues/7) | 2026-08-20 已确认中性模板与 `{{.UserPrompt}}` |
| 核心生成域与单上游 | 用户提交提示词/原图得到结果 | 已实现(待单元验收与 #13 集成验收) | [MVP-0 #4](https://git.ilapage.cn/OPC/chorus/issues/4) | [架构](Architecture-and-Code-Map.-)、[业务规则](Business-Rules-and-Glossary.-) | [#6 迁移](https://git.ilapage.cn/OPC/chorus/issues/6)、[#7 core](https://git.ilapage.cn/OPC/chorus/issues/7)、[#8 platform](https://git.ilapage.cn/OPC/chorus/issues/8)、[#9 queue](https://git.ilapage.cn/OPC/chorus/issues/9)、[#10 worker](https://git.ilapage.cn/OPC/chorus/issues/10) | 架构/数据/状态设计已确认 |
| 用户端生成页 | 种子用户登录并完成一次异步生成 | 已实现(#12 待验收) | [MVP-0 #4](https://git.ilapage.cn/OPC/chorus/issues/4) | 本页“用户端布局与状态” | [#11 portal 后端](https://git.ilapage.cn/OPC/chorus/issues/11)、[#12 用户界面](https://git.ilapage.cn/OPC/chorus/issues/12) | [原型设计 #2](https://git.ilapage.cn/OPC/chorus/issues/2);`prototypes/2/v1/index.html`,2026-08-20 用户已确认 |
| 默认 prompt template | 系统以数据配置而非硬编码合成提示词 | 已实现(待单元验收) | [MVP-0 #4](https://git.ilapage.cn/OPC/chorus/issues/4) | [业务规则](Business-Rules-and-Glossary.-) | [#7](https://git.ilapage.cn/OPC/chorus/issues/7) | 2026-08-20 已确认中性模板与 `{{.UserPrompt}}` |
| 多 Provider 路由与故障转移 | 单上游故障时继续服务 | 已确认 | MVP-1 | 业务规则“选路与上游” | 待建 | 架构/状态设计 |
| 管理端配置与记录 | 运营配置模型、路由池并排障 | 已确认 | MVP-1 | 本页“管理端页面” | 待建 | 复用型/定制页原型待确认 |
| 图片角色编辑 | 用户编辑 role_rule、角色、备注与顺序 | 已确认 | MVP-1 | 业务规则“提示词与上传” | 待建 | 组件状态与键盘交互原型 |
@@ -77,7 +77,7 @@ MVP-0 非目标:
- [ ] 私网、IPv6、redirect、代理和恶意结果 URL 被 SSRF 策略覆盖。
- [ ] API Key 密文含 key_id,日志/响应无明文;浏览器写请求有 CSRF,会话安全。
- [ ] 图片输出通过鉴权访问且有缩略图;文件原子落位。
- [ ] 375/768/1024 无主区域横向滚动,所有主要操作可用键盘和 44px 触控目标完成。
- [ ] 375/768/1024/1440 无主区域横向滚动,所有主要操作可用键盘和 44px 触控目标完成。
- [ ] 迁移在空 MySQL 8 完成 up/down/up;Go 构建、vet、测试和浏览器 E2E 通过。
#### MVP-1:可用性与运营
+6 -4
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Deployment-and-Operations
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Deployment-and-Operations.-
wiki_revision: efd73fb39c69cb94a9da7e2157b765b4f8da2431
synchronized_at: 2026-08-20T16:42:26Z
wiki_revision: 23cabbc250817324c8f00dacc8e3d15c512141d4
synchronized_at: 2026-08-21T03:23:42Z
<!-- gitea-wiki-mirror:end -->
# 部署与运维
@@ -42,7 +42,7 @@ portal → /var/lib/chorus/storage(或后续对象存储适配)
| 日志 | journald 或平台日志;结构化且脱敏 |
| 进程托管 | systemd:`chorus-portal.service`、`chorus-admin.service` |
| 数据库 | MySQL 8.0,独立最小权限账号 |
| 构建 | CI 使用 Go 1.26.5;admin-ui 使用 Node >=22 + pnpm 9.15.1 |
| 构建 | CI 使用 Go 1.26.5;portal/web 与 admin-ui 使用 Node >=22 + pnpm 9.15.1 |
## 配置与凭据
@@ -69,6 +69,8 @@ portal → /var/lib/chorus/storage(或后续对象存储适配)
```bash
go version
corepack pnpm --dir portal/web install --frozen-lockfile
corepack pnpm --dir portal/web build
go test ./...
go vet ./...
go build -trimpath -o dist/chorus-portal ./portal
@@ -77,7 +79,7 @@ corepack pnpm --dir admin-ui install --frozen-lockfile
corepack pnpm --dir admin-ui build:prod
```
MVP-0 没有 admin/admin-ui 时跳过对应命令并在发布记录说明。制品记录 chorus commit、Go module 校验、admin-ui lockfile 和固定 go-admin 来源提交。
portal 的模板和静态资源嵌入 `chorus-portal` 二进制,运行环境不需要 Node,也不从 CDN 加载资源;CI 必须先按 `portal/web/pnpm-lock.yaml` 重建并确认工作区无差异。MVP-0 没有 admin/admin-ui 时跳过对应命令并在发布记录说明。制品记录 chorus commit、Go module 校验、portal/web 与 admin-ui lockfile 和固定 go-admin 来源提交。
## 首次部署与升级顺序