安装反馈组件
把 FeedLog 嵌入你的产品后,用户无需离开当前应用即可提交反馈。用户打开悬浮按钮并描述需求,AI 会将消息整理成结构化反馈并提交到你的看板。
接入只需调用一个函数。SDK 是一个加载器:gzip 后约 5 KB,没有运行时依赖,以 ES 模块形式发布并提供 TypeScript 类型声明。SDK 负责渲染悬浮按钮、未读角标和面板;反馈界面由 FeedLog 托管,通过 iframe 加载。FeedLog 发布新的反馈组件功能时,你无需升级 SDK。
让 Coding Agent 完成接入
打开反馈组件接入 Prompt,复制到已经打开项目的 Coding Agent 中。Agent 会检查代码,修改前端和服务端,并验证接入结果。
安装
npm install @feedlog/widget只有 ESM
这个包包含 ES 模块和类型声明,需要配合打包器使用。它不提供 <script src="…"> 版本或全局变量,因此不支持通过 CMS 代码片段直接安装。
配置反馈组件
import { createWidget } from '@feedlog/widget'
createWidget({
baseUrl: 'https://acme.feedlog.ai',
auth: {
getToken: async () => {
const res = await fetch('/api/feedlog-token')
// A reachability problem, not a sign-out — see below.
if (!res.ok) throw new Error('token endpoint unavailable')
return (await res.json()).token ?? null
},
// Must resolve when the modal closes, not when it opens.
login: () => openYourSignInModal(),
},
theme: 'auto',
})createWidget 是这个包唯一的导出。它没有返回值、实例方法或事件。悬浮按钮用于打开面板,面板上的关闭按钮用于关闭面板。反馈组件在内部管理开合状态,因此组件行为发生变化时,接入代码无需随之修改。
选项
| 选项 | 必填 | 说明 |
|---|---|---|
baseUrl | 是 | 你的 FeedLog 地址,例如 https://acme.feedlog.ai。自定义域名一样填这里 |
auth.getToken | 是 | () => Promise<string | null>。报告当前登录状态。实现前请先阅读下一节的三种结果 |
auth.login | 否 | () => void | Promise<void>。用户在面板中选择登录时,打开应用的登录界面 |
theme | 否 | 'light'、'dark' 或 'auto'(默认)。auto 跟随系统 |
getToken 有三种结果,不是两种
getToken() 用于报告当前登录状态。FeedLog 会分别处理以下三种结果:
你的 getToken() | FeedLog 理解成 | 组件的行为 |
|---|---|---|
| 返回一个 JWT | 已登录,身份是令牌中的 email | 用它换一个会话,然后以该用户的身份打开面板 |
返回 null | 已登出 | 立刻清掉缓存的会话,显示登录提示 |
| 抛错 / reject | 临时失败 | 会话保留;给出一个可重试的提示,已经能用的面板不动 |
失败的时候不要返回 null
null 表示用户已登出。只有在确认当前没有用户登录时才返回 null。如果令牌接口返回 500、请求超时或 fetch reject,请抛出错误。
临时失败时返回 null 会清除已缓存的会话,导致用户间歇性退出登录。由于原始网络错误没有作为错误继续上报,日志中通常缺少直接的排查依据。
第一次打开面板及后续每次打开时,SDK 都会调用 getToken(),以便与应用的登录状态保持一致。因此,这个函数应尽快返回。SDK 会缓存通过 JWT 获取的 FeedLog 会话;再次打开面板时,通常只会调用该函数,不会再次发送交换请求。
打开应用的登录界面
auth.login 是可选项。当用户在面板中选择登录,并且 getToken() 返回 null 时,SDK 会调用它。你可以打开产品现有的登录界面,例如模态框、弹出窗口,或者跳转到身份提供商的登录页。对于整页跳转,SDK 会保存一个短期标记;如果用户在大约五分钟内返回当前页面,面板会自动重新打开。
login() 结束不代表登录成功。 SDK 只能确定登录交互已经结束,随后会再次调用 getToken(),并根据这次调用的结果判断登录状态。因此,用户关闭登录窗口后 resolve 或 reject,处理结果相同。login() 无需返回登录结果。
会话过期时,SDK 不会自动调用 login()。它会先在不打开界面的情况下重试 getToken(),因为用户可能已经在另一个标签页完成登录。只有用户在面板中明确选择登录时,SDK 才会打开登录界面;用户仅浏览页面时不会看到登录窗口。
在服务端签发令牌
你的后端必须使用 开发者 → 单点登录 中的密钥签发 getToken() 返回的 JWT。签名算法为 HS256,支持四个字段,exp 距当前时间不能超过 24 小时:
// Server-side only — the secret must never reach the browser.
import jwt from 'jsonwebtoken'
export default handler(async (req) => {
const user = await currentUser(req) // your own session, however you read it
if (!user) return { token: null } // becomes `null` in getToken()
return {
token: jwt.sign(
{ email: user.email, name: user.name, picture: user.avatarUrl },
process.env.FEEDLOG_SSO_SECRET,
{ algorithm: 'HS256', expiresIn: '1h' },
),
}
})SDK 会将令牌 POST 到 FeedLog,并按登录邮箱把返回的会话存入 localStorage。会话过期后,SDK 会再次交换令牌。你无需直接调用交换接口。会话通过 URL fragment 传入 iframe,因此不会出现在访问日志和 Referer 请求头中。
有关密钥的创建与轮换、完整字段清单以及交换接口的所有错误,请参阅单点登录(JWT 跳转)。如果产品已经配置浏览器跳转,可以复用相同的签名代码、密钥和令牌。
该在哪里调用
在浏览器中,每个页面调用一次 createWidget()。没有 window 时,该函数不会执行任何操作,因此可以在 Nuxt 或 Next 应用中导入。调用本身应放在仅客户端运行的生命周期钩子中,例如 onMounted、useEffect 或 .client 插件。
第二次调用会记录一条警告并直接返回,不会创建另一个反馈组件。这可以避免 React StrictMode 的重复挂载和 HMR 的模块重新执行添加第二个悬浮按钮。再次调用不会更新 baseUrl 或 theme;要更改这两个选项,请刷新页面。
页面元素与外观
SDK 会在 document.body 中添加一个带 shadow root 的 <div>,其中包含悬浮按钮、角标和面板。shadow root 可以避免宿主页面的 CSS reset 影响反馈组件,也可以避免组件样式影响宿主页面;无需额外配置 z-index。悬浮按钮位于右下角;面板在桌面端宽 400 px,视口窄于 520 px 时全屏显示。
主色来自 FeedLog 工作区,不能在 SDK 中配置。请在工作区设置中配置主色,FeedLog 会同时选择清晰可读的前景色。theme 是 SDK 中唯一的外观选项。它会在 iframe 创建时、首次绘制前传入,因此调用 createWidget() 时必须提供最终值。如果挂载后才从 store 或 media query 监听器中获取这个值,面板可能会短暂显示错误的主题。除非应用提供自己的明暗主题切换,否则建议使用 auto;它会跟随系统设置并在系统设置变化时更新。
角标显示用户尚未阅读的回复数。面板关闭时,SDK 会在页面加载和标签页重新获得焦点时刷新计数,并使用 60 秒缓存。面板打开后,iframe 会更新计数,并在用户阅读对应讨论后清除未读项。显示的最大计数为 9+。
启用反馈组件
请在 设置 → 反馈组件 中启用反馈组件。SDK 会在渲染前检查这个设置。组件关闭时,SDK 不会渲染内容或记录日志。这个设置对所有已安装 SDK 的站点生效,无需重新部署你的产品。
无需配置域名白名单。FeedLog 不维护客户域名列表:反馈组件 API 接受来自任何来源的请求,嵌入页可以被任何站点通过 iframe 加载。身份验证不依赖 cookie;会话通过显式 bearer token 提供。
反馈组件支持当前版本的 Chrome、Firefox、Safari 和 Edge。构建目标为 ES2020,使用 Shadow DOM、fetch 和 Web Storage,不包含 polyfill。如果浏览器隐私模式导致 Web Storage 抛错,SDK 会改用内存存储。此时会话缓存无法在页面刷新后保留,每次加载页面会额外交换一次令牌。
常见错误
页面上出现两个悬浮按钮。 重复调用保护只覆盖同一次页面加载中的第二次调用,触发时会打印 createWidget() was already called; ignoring this call。如果出现两个按钮但没有这条警告,页面中存在两个 document,例如你的 iframe 或微前端再次运行了接入代码。
用户间歇性退出登录。 getToken() 在临时失败时返回了 null,而不是抛出错误。请检查上面的三种结果。
createWidget requires a baseUrl 或 requires auth.getToken(TypeError)。 这些错误会在调用时同步抛出,表示选项缺失或类型错误。getToken 必须是函数,不能是 promise 或令牌字符串。baseUrl is not a valid URL 表示该值无法解析为 URL;请包含协议头。
没有渲染内容,控制台显示 could not load widget config。 SDK 无法访问 baseUrl 下的 GET /api/widget/config,因此无法判断反馈组件是否启用,也不会渲染悬浮按钮。请直接在浏览器中打开该地址。baseUrl 错误、域名未解析到工作区或 FeedLog 实例不可用都会产生相同症状。
没有渲染内容,也没有警告。 反馈组件可能已在 设置 → 反馈组件 中关闭,或者调用只在服务端执行。可以在调用旁添加 console.log,确认客户端是否执行到该位置。
面板打开后一直显示加载动画。 iframe 已加载,但没有报告就绪。请在网络面板中检查嵌入页请求。如果请求返回 404 或 500,页面会显示空白 iframe,而不是错误页,因为 FeedLog 错误页不包含允许 iframe 嵌入的响应头。
显示「Feedback could not be loaded.」和 Try again 按钮。 getToken() 抛出了错误,或者令牌交换失败。控制台中的 Widget token exchange failed with status … 会显示状态码;具体原因位于响应体 JSON 中,可以在网络面板中查看。403 表示反馈组件已关闭,400 通常表示令牌有问题。单点登录文档列出了所有错误消息。
用户已在应用中登录,但面板仍显示登录提示。 getToken() 返回了 null。常见原因是令牌接口无法读取登录 cookie,例如接口位于不同子域,或者 SameSite 设置阻止了 cookie。请在浏览器控制台中调用该接口并检查实际返回值。
角标最多延迟一分钟。 面板关闭时,计数会缓存 60 秒。打开面板后会刷新计数。