跳转到主要内容
/admin/prompts 显示 app 注册了哪些 prompt、每个的最新版本是几、最近一次 更新时间。它刻意做成只读——发新版本是个郑重的操作,应该走 code review 或 server action,而不是放给一个自由编辑的 admin 面板。这页是审计轨迹,编辑 在别处发生。

前置条件

  • 你的 user 记录 role = 'admin'/admin/* layout 调 requireAdmin(), 否则 403。
  • 数据库里已经注册了 prompt——通常是 app 启动时通过 prompt registry 模式 种入的(看 /docs/ai/prompts)。
  • promptprompt_version 表存在(pnpm db:push 时根据 src/db/ai.schema.ts 创建)。

一步步用

  1. 用 admin 账号在 /login 登录,然后访问 /admin/prompts
  2. 看表
    内容
    slugapp 用来取 prompt 的稳定标识符
    name + desc人类可读的标签 + 一句话描述
    latest versionjoin prompt_version 拿到的 vN
    updatedprompt.updatedAt,date-fns 格式化
  3. 排序与过滤——目前都没实现。返回的行按 updatedAt desc 排,硬编码 limit(100)。要么把页面重写成支持排序,要么加分页(看坑 1)。
  4. 要发新版本,写 SQL 或 server action 往 prompt_version 插一行, 并更新 prompt.latestVersionId。完整的 registry 模式见 /docs/ai/prompts

join 是怎么做的

页面先查 prompt 表,然后挨个去 resolve latestVersionId
const rows = await db.select().from(prompt)
  .orderBy(desc(prompt.updatedAt)).limit(100);

const versionMap = new Map<string, number>();
for (const r of rows) {
  if (!r.latestVersionId) continue;
  const v = await db.select({ version: promptVersion.version })
    .from(promptVersion)
    .where(eq(promptVersion.id, r.latestVersionId)).limit(1);
  if (v[0]) versionMap.set(r.id, v[0].version);
}
这是教科书级的 N+1——admin 100 行的体量没问题,但 limit 一旦撑爆就要立刻 改成 join。

验证生效

  1. 通过 registry 的 register() 注册一个新 prompt(或者测试时直接往 prompt 表插)。
  2. 刷新 /admin/prompts——新行应该出现在最上面(按 updatedAt desc 排)。
  3. 插一条 prompt_version 行指回这个 prompt,更新 prompt.latestVersionId, 再刷新——版本列应该显示 v1
  4. 测试完删掉。目前 UI 里没有删除按钮。

常见坑

  1. 没分页。硬编码的 limit(100) 静默截断大 registry。在过 100 条之前 加 ?page=N 或换 keyset pagination。
  2. N+1 查询在高并发下扛不住。admin 流量没事,几百条以上就疼。改写成 一条 LEFT JOIN promptVersion ON promptVersion.id = prompt.latestVersionId
  3. 最新版本显示 。说明 prompt.latestVersionId 是 null——这个 prompt 还没发布过版本。插一条 prompt_version 然后更新父行的 latestVersionId
  4. 修改不显示。改 prompt_version.text 不会 bump prompt.updatedAt。 要么在同一事务里 touch 父行,要么改成按 version 的 createdAt 排序。
  5. 没编辑 UI。这是设计——刻意只读。不要随手加自由编辑器,除非你顺手把 审计日志和审批流也加上。

官方文档