AB
AiBoss
Tutorials

Jev

Tutorials

Jev 驱动的网站支持系统搭建教程:从访客反馈到 GitHub Issue 的完整链路

访客的反馈常常散落在邮箱、社交平台和表单里,等你动手修的时候已经缺东少西。这套方案把反馈先存进自己的数据库,再由 Jev 做有边界的分类判断,人工确认后才生成 GitHub Issue,并在 Issue 关闭时反向同步工单状态。本文讲清前置条件、各步骤的真实代码、一个能跑通的最小示例,以及部署与测试中容易踩的坑。

网站只要对外开放,就一定会收到反馈,而这些反馈的落点往往很尴尬:有人发现按钮坏了,发邮件告诉你;有人在社交平台上留言说某个页面在手机上打不开;还有人通过联系表单提了一个功能想法,它静静躺在收件箱里,夹在一封订阅邮件和一张收据之间。等你终于坐下来处理时,缺陷报告散落在三个地方,一半缺关键信息,少数被手工复制进 GitHub 的还带着访客的邮箱地址。这套支持系统的目标就是解决这个问题:给 React 网站挂一个轻量反馈组件,访客可以提问、报缺陷、提功能建议;每条报告先落进你自己的数据库,再由 Jev 分类,由代码里的明文规则决定去向,最后在私有面板里人工复核。确认是真缺陷后,才生成一条干净的 GitHub Issue,访客的私人信息不会跟着出去;之后在 GitHub 上关闭该 Issue,对应工单也会同步关闭。站内有一个相关工具页可以参考:Jev。

准备工作

要跟着部署一套自己的副本,需要先确认下面这些条件。它们大多不是 AI 相关的东西,而是普通的 Web 工程依赖。

  • React、Next.js 与 TypeScript 的实操经验:平台侧使用 Next.js App Router,反馈组件本身是一个 React 组件。不熟悉 App Router 的客户端组件边界,会在挂载组件时卡住。
  • Node.js 24 与 pnpm:本地运行项目、以及使用那条创建 GitHub App 的命令都需要它们。
  • 一个 GitHub 账号:以及你打算安装反馈组件的那个网站或应用的仓库。确认后的缺陷最终会变成这个仓库里的 Issue。
  • 一个 Vercel 账号:免费套餐就够用。PostgreSQL 数据库通过 Vercel 的应用市场接入,Neon 本身也有免费方案。
  • 一个 TypeSafe 账号:用于调用 Jev。需要先在 TypeSafe 控制台拿到 API key,否则任何报告都无法变成 GitHub Issue。
  • 一个 React 网站:能加组件即可,Next.js 站点是最省事的起点。
  • 可选:一个 Resend 账号:如果你需要密码重置这类账号邮件。

不需要是 AI 专家。Jev 通过一个小而类型化的 SDK 调用,整套系统里真正有意思的部分都是常规 Web 工程:数据库、输入校验、认证、Webhook。

这套系统适合谁

支持系统听起来像是大公司才需要的东西,但它解决的问题几乎出现在每一类网站上:

  • 作品集站点会收到招聘方的留言、关于项目的提问,以及某个浏览器上页面坏掉的报告。
  • SaaS 产品会把缺陷报告、账单问题和功能请求混在一起,每一类都需要不同的处理路径。
  • 文档站点会收到「这个示例跑不通」的报告,而它其实是产品本身的缺陷。
  • 开源项目会遇到那种不愿意自己开 GitHub Issue、但很乐意在网站上点一下按钮的用户。
  • 你替客户做的站点,反馈会先到客户手里,几天后才被转发给你,而且没有任何细节。

一套好的系统应该做到三件事:所有报告汇到一个地方;即使其他服务挂了,每条报告也安全留存;报告被分好类,让你的时间花在真正重要的那些上。AI 只负责分类这一环,而且绝不能让它做主。模型可以非常自信地给出错误结论,而公开的 GitHub Issue 不是可以靠猜测创建的东西。所以整套系统贯穿一条规则:AI 只做推荐,人来做确认,代码来执行规则。

操作步骤

第一步:在站点里挂载反馈组件

反馈组件是一个普通的 React 组件,从 npm 安装:

npm install @issuerelay/widget

然后在根布局里渲染一次,例如放在你自己的客户端组件中:

"use client";

import {
  HttpSupportSubmissionClient,
  SupportWidget,
} from "@issuerelay/widget";

const submissionClient = new HttpSupportSubmissionClient({
  apiBaseUrl: "",
});

export function Support() {
  return (
    <SupportWidget
      projectKey="pk_your_project_key"
      submissionClient={submissionClient}
      theme="system"
      position="bottom-right"
    />
  );
}

这里有两个角色要分清。HttpSupportSubmissionClient 负责和你的平台通信:它把每条报告 POST 到你的接口,不带 cookie、不带凭据,并且给每条报告一个提交 ID,这样网络出错后重试不会产生第二张工单。SupportWidget 是访客看到的按钮和面板。projectKey 告诉平台这条报告属于哪个项目,它是公开标识而不是密码,所以放在站点代码里是安全的;真正的防护在服务端,服务端只接受来自该项目已登记站点地址的报告。

"use client" 这一行是必需的,因为提交客户端在浏览器里创建。在 Next.js App Router 中,做法就是像上面这样把组件包进你自己的小客户端组件,再从布局里渲染它。组件内部渲染在 Shadow DOM 中并自带样式,所以你的站点不需要 Tailwind,也不需要引入 CSS,你的样式也不会意外改到它。

这段代码不必手写。项目设置页会直接给出填好平台地址和项目 key 的同一段代码,复制即可。

第二步:先落库,再思考

报告到达接口后,第一件事是保存,不是分类,也不是转发,而是在一个事务里写进 PostgreSQL。这是整套系统里最重要的设计决定。

AI 服务会宕机,GitHub 也会宕机。如果平台在保存之前先调用 Jev,而 Jev 超时了,访客的消息就丢了,而且他们永远不会知道。所以规则很简单:只有安全存储之后,报告才算被接受,后续任何一步失败都不能把它抹掉。如果 Jev 不可用,工单就在面板里等着,等你重新跑一次分类。

保存之前,接口会检查几件事:

  • 请求体符合共享的 Zod 契约,非法输入会被明确报错拒绝。
  • 项目 key 存在,且请求来源属于该项目登记的允许站点地址之一。
  • 项目未超出配额限制。

此外还有针对重复提交的防护和按项目维度的速率限制。这些检查都发生在写库之前,所以被拒绝的请求不会留下垃圾数据。

第三步:让 Jev 做有边界的分类

Jev 是 TypeSafe 提供的模型,面向的是所谓「System One」类任务:快速、有边界的判断,而不是长篇开放式写作。用法不是让模型写一段话再去解析,而是给它一些状态和一组问题,每个问题都有固定的候选答案。Jev 为每个问题挑一个答案,并返回它给每个选项分配的概率。

这个形状恰好就是支持工单分类需要的:一张工单是缺陷、提问、功能请求、账单问题还是垃圾信息;严重程度是低、中、高还是紧急。没有文本可供模型自由发挥,没有提示注入能让它去写 Issue 标题,也没有自由文本需要事后清理。输出就是一个标签加一个数字,代码可以同时校验两者。

它同时也便宜且快。按撰写时的公开信息,TypeSafe 列出 Jev 的价格是每十亿输入 token 42 美元,而一条支持消息只有几十个 token。价格与配额随时可能调整,请以官网当前信息为准。

集成时只把访客的消息和他们选择的主题发给 Jev,绝不发送姓名、邮箱、工单 ID 或任何可以识别到人的内容。这一点是硬约束,不是可选项。

第四步:人工复核与 GitHub 升级

分类结果进入私有面板,面板提供筛选、分类历史,以及和 AI 输出分开存储的人工复核决定。分开存储很关键:AI 的判断和人的判断是两条记录,事后可以对比、可以追责,也不会因为重新跑一次分类就把人的结论覆盖掉。

只有当所有者确认了预览之后,GitHub App 才会创建 Issue。创建前会经过一道隐私闸门,访客的联系方式不会离开平台。生成的 Issue 是干净的:标题、正文、标签都来自模板和已确认的内容,不包含私人信息。

第五步:双向同步

在 GitHub 上关闭或重新打开 Issue,会通过带签名的 Webhook 更新工单状态。签名校验是必须的,否则任何人都能伪造一个请求来改你的工单。Webhook 载荷同样要过 Zod 校验,因为它也是一条跨越信任边界的输入。

一个完整示例

下面把一条报告从浏览器走到 GitHub Issue 关闭的全过程串一遍,这是能跑通的最小路径。

  1. 访客打开组件,在站点右下角看到按钮,点开后选择一个主题,描述问题,可选地留下姓名和邮箱以便你跟进。
  2. 组件提交报告。提交客户端把报告 POST 到你的平台,不带凭据,并带上一个提交 ID。
  3. 平台校验并落库。请求体过 Zod 契约,项目 key 与来源地址校验通过,速率与重复检查通过,然后在事务里写入 PostgreSQL,并返回一个支持编号,访客之后可以引用它。
  4. 报告变成工单,出现在面板里。此时即使 Jev 不可用,这条记录也已经安全了。
  5. Jev 分类。平台把访客的消息和所选主题发给 Jev,拿回类型标签与严重程度,以及每个选项的概率。
  6. 你复核。在面板里看到分类结果、原始消息和概率,做出自己的判断。你的决定和 AI 的输出分开存储。
  7. 确认是真缺陷后创建 Issue。你确认预览,GitHub App 在你的仓库里创建一条 Issue,访客的联系方式被隐私闸门拦下,不会出现在 Issue 里。
  8. 你在 GitHub 上关闭 Issue。带签名的 Webhook 打到平台,校验通过后,对应工单同步关闭。

整个部署过程按素材的说法大约 15 分钟可以完成,前提是账号和密钥都已经准备好。

技术栈与代码边界

平台使用一套现代 TypeScript 栈:

  • Next.js 16(App Router)与 React 19,用于平台和面板。
  • 严格模式的 TypeScript,所有跨越信任边界的输入都用 Zod 校验:公开 API 请求、环境变量、AI 输出、GitHub Webhook 载荷。
  • PostgreSQL 搭配 Drizzle ORM,迁移脚本经过人工审阅。
  • Better Auth 负责面板账号。
  • TypeSafe 官方 SDK 调用 Jev,Octokit 处理 GitHub App。
  • Vitest、React Testing Library、Playwright 负责测试,Biome 负责 lint 与格式化。
  • pnpm workspaces 把一切放在一个 monorepo 里。
  • 生产环境使用 Vercel、Neon 与 Resend。

monorepo 被拆成若干职责单一的小包:

包职责
apps/web平台本体:公开工单接口、面板、初始化设置、GitHub Webhook
packages/widget发布到 npm 的浏览器组件,绝不引入服务端代码
packages/support-contracts组件与接口共享的请求和响应结构
packages/dbDrizzle schema、迁移,以及所有数据库查询
packages/aiJev 适配器、分类服务与路由策略
packages/githubGitHub App 客户端、Issue 草稿、隐私闸门与 Webhook 处理
packages/authBetter Auth 配置、会话与工作区成员校验

这些边界比看上去更重要:React 组件从不直接和 GitHub、Jev 或数据库对话;浏览器代码里永远不含密钥;AI 包不能引入数据库。把这些线划严之后,系统更容易测试,也更容易推理,这也是组件能发布到 npm 而不拖带任何服务端代码的原因。

注意事项

  • AI 不能做主。模型可以非常自信地给出错误结论,而公开的 GitHub Issue 不该靠猜测创建。整套流程必须保持「AI 推荐、人工确认、代码执行规则」。
  • 先落库再调用外部服务。如果先调 Jev 再保存,Jev 超时就会永久丢失访客的消息,而且访客毫无感知。任何后续步骤的失败都不能抹掉已接受的报告。
  • projectKey 不是密钥。它是公开标识,放在前端是设计如此。真正的防护是服务端只接受该项目登记的站点地址发来的报告,不要指望靠隐藏 key 来防护。
  • 只把必要内容发给模型。姓名、邮箱、工单 ID 等可识别个人的信息不应进入 AI 调用。
  • Webhook 必须验签。否则任何人都能伪造请求改动工单状态;载荷也要做结构校验。
  • 价格、配额与套餐随时可能变化。文中提到的模型定价、免费方案等信息请以各服务官网当前信息为准。
  • 版本与依赖会演进。Node.js、Next.js、React 等版本要求以项目仓库当前的说明为准。