Hermes Agent 23fd8db77d
Some checks failed
CI / Build & Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Release (push) Has been cancelled
refactor(portal): move .hint info-banner ABOVE its <input> + upgrade to teal-accented banner style
Two-part UX upgrade:

1. CSS — upgrade .hint from a generic "card with slate background" to a
   proper info-banner:
   - background: var(--color-primary-soft) (translucent teal 12%) instead
     of var(--bg-elev-2) (slate card) — visually distinct from any
     <input> card, so it can never be confused with one
   - border-left: 2px solid var(--color-primary) — clear "this is a hint"
     teal accent
   - padding: 8px 12px 8px 14px (smaller, lighter)
   - font-size: 12.5px (slightly larger for readability)
   - margin: 4px 0 8px 0 (sits between field label and input)
   - .hint code { monospace, teal-300, teal-tinted background } for inline
     <code> tokens
   - .hint strong { text-default color } for emphasis

   Also: label > input/select/textarea forced to display:block width:100%
   margin-top:6px (after the hint, hint + input collapse to margin-top:0)

2. HTML — reorder 11 labels across providers.html (7) and
   admin-batch-import.html (4) so the .hint span sits BEFORE the
   <input>/<select>/<textarea> it describes. Datalist stays adjacent to its
   owning input.

   Pattern before: <label>Field
  <input>
  <span class="hint">desc</span>
</label>
   Pattern after:  <label>Field
  <span class="hint">desc</span>
  <input>
</label>

Why: Linear/Vercel canonical form pattern is label + info banner above +
clean input below. The previous "input then hint" layout was just an
artifact of how the inline-script-dedup pass emitted the fields, not a
deliberate UX choice.

Verification (chrome remote-debugging, 7 pages, all .hint elements):
  Page                                            n_hints  covered
  https://sub.tksea.top/portal/                       2        0
  https://sub.tksea.top/portal/admin/                 0        0
  https://sub.tksea.top/portal/admin/providers.html   8        0
  https://sub.tksea.top/portal/admin/accounts.html    0        0
  https://sub.tksea.top/portal/admin/logical-groups   1        0
  https://sub.tksea.top/portal/admin/route-health     0        0
  https://sub.tksea.top/portal/admin-batch-import     4        0
  Total: 7/7 pages, 0 hint covered by any input

Local tests still PASS:
  - test_tksea_portal_assets.sh
  - verify_frontend_smoke.sh
2026-06-03 20:01:44 +08:00
2026-05-12 21:46:19 +08:00

sub2api-cn-relay-manager

sub2api-cn-relay-manager 是一个独立于 sub2api 宿主仓库的外部伴生安装器 / 控制面项目。

目标不是修改 sub2api,也不是把国产模型能力硬塞进宿主源码,而是通过 sub2api 已有的管理 API把“国产模型 OpenAI 兼容中转能力”以独立交付物的形式安装到任意一台已部署的 sub2api 实例上。

项目定位

  • 宿主:第三方开源系统 sub2api
  • 约束:不修改宿主代码,不 fork 宿主,不要求宿主内置原生插件运行时
  • 交付:一个独立控制面项目 + 一个或多个 model_pack
  • 结果:管理员可一键导入多个国产模型 key普通用户继续只用 sub2api 标准 API

这个项目解决的问题

  • 不同机器部署了 sub2api 后,如何用同一套交付物补齐国产模型中转能力
  • 如何把多个国产模型 key 批量导入为可用的宿主资源
  • 如何在不改宿主的前提下实现热生效、探测、对账和漂移发现
  • 如何让普通用户完全无感,继续走 sub2api 标准接口

非目标

  • 不做宿主原生插件系统
  • 不做任意第三方 Go 代码动态加载
  • 不要求宿主识别“插件”概念
  • 不接管宿主网关流量

目录结构

sub2api-cn-relay-manager/
  cmd/                         # CLI / server 入口
  internal/                    # 控制面核心实现
  packs/                       # provider packs / overlay metadata
  deploy/                      # 部署资产portal 静态页、Nginx 模板)
  scripts/                     # deploy / acceptance / test 三层脚本
  docs/                        # 真相入口、执行板、runbook、目录说明
  tests/                       # 集成测试
  artifacts/                   # 真实验收证据与归档
  web/                         # 预留给未来管理端前端

如果你想看更细的目录职责,而不是只看一级树结构,读:

交付模型

本项目最终会拆成两个可独立发布的产物:

  1. sub2api-cn-relay-manager

    • 外部控制面 / 安装器
    • 连接已有 sub2api
    • 安装模型包
    • 调用宿主管理 API 创建资源
    • 做 smoke test、对账和状态展示
  2. model_pack

    • 只包含 provider 定义、默认模板、导入规则和校验信息
    • 不携带宿主执行代码
    • 可以跨机器复用

当前文档

先读这些:

背景/设计文档:

当前 MVP 能力

当前仓库已经具备一个最小可运行闭环:

  • packs/openai-cn-pack/ 提供真实 pack.json + provider + checksums
  • internal/pack 负责 pack 装载、checksum 校验、provider schema 校验
  • internal/provision 负责多 key 导入编排、账号探测和访问闭环判定
  • cmd/cli import-provider 提供一键导入入口

示例:

go run ./cmd/cli import-provider \
  --host-base-url https://sub2api.example.com \
  --host-api-key <admin-api-key> \
  --pack-dir ./packs/openai-cn-pack \
  --provider-id deepseek \
  --keys sk-a,sk-b \
  --access-mode self_service \
  --access-api-key <user-api-key>

运行方式

服务端:

SUB2API_CRM_ADMIN_TOKEN=replace-me go run ./cmd/server

Docker Compose

cp .env.example .env
# 编辑 .env 中的 SUB2API_CRM_ADMIN_TOKEN
scripts/deploy/build_local_image.sh
docker run --rm -p 8080:8080 \
  --env-file .env \
  -v "$(pwd)/data:/data" \
  sub2api-cn-relay-manager:local
curl -fsS http://127.0.0.1:8080/healthz

真实宿主验收:

CRM_BASE_URL=http://127.0.0.1:8080 \
CRM_ADMIN_TOKEN='<crm-admin-token>' \
HOST_NAME=prod-sub2api \
HOST_BASE_URL=https://sub2api.example.com \
HOST_API_KEY='<sub2api-admin-key>' \
PACK_PATH=/app/packs/openai-cn-pack \
PROVIDER_ID=deepseek \
KEYS=sk-live-1 \
ACCESS_MODE=self_service \
ACCESS_API_KEY='<user-gateway-key>' \
DRY_RUN=1 \
scripts/acceptance/real_host_acceptance.sh
Description
No description provided
Readme 57 MiB
Languages
Go 74%
Shell 10.7%
HTML 10.6%
CSS 1.9%
PLpgSQL 1%
Other 1.7%