安装反馈组件
反馈组件将 FeedLog Agent 接入你的产品。用户可以提问、阅读帮助文章、提交反馈并查看进展。用户侧体验见FeedLog Agent 介绍,工作区配置见配置并上线 Agent。
安装
npm install @feedlog/widget这个包提供 ES 模块和 TypeScript 类型声明,适用于使用打包器的应用,没有运行时依赖。SDK 显示反馈按钮,并从你的 FeedLog 工作区加载反馈界面。
使用 Coding Agent 接入
将反馈组件接入 Prompt复制到已打开项目的 Coding Agent 中。Agent 会检查代码、完成接入并验证结果。
配置反馈组件
在设置 → 反馈组件中启用组件,然后选择用户的使用方式。
仅使用访客模式
在工作区开启访客提交,并在客户端初始化:
import { createWidget } from '@feedlog/widget'
createWidget({
baseUrl: 'https://acme.feedlog.ai',
theme: 'auto',
})填写你的工作区地址;配置了自定义域名时,也可以使用该域名。访客投票和评论分别由工作区权限控制。仅使用访客模式时,可以跳过下方登录与令牌相关章节。
接入产品已有账号
提供 auth,让用户使用产品里的账号身份。下面的示例需要配合服务端令牌接口:
import { createWidget } from '@feedlog/widget'
createWidget({
baseUrl: 'https://acme.feedlog.ai',
auth: {
getToken: async () => {
const res = await fetch('/api/feedlog-token')
if (!res.ok) throw new Error('Token endpoint unavailable')
const { token } = await res.json()
if (token === null || (typeof token === 'string' && token.length > 0)) return token
throw new Error('Invalid token response')
},
login: () => new Promise<void>((resolve) => {
openYourSignInModal({ onClose: resolve })
}),
},
theme: 'auto',
})openYourSignInModal 代表应用自己的登录方法。用户完成登录或取消时,都需要触发关闭回调。未登录用户能否使用访客模式,由工作区权限决定。
该在哪里调用
在浏览器中,每个页面初始化一次 createWidget()。放在仅客户端运行的入口,例如 Nuxt 的 .client 插件或 React 的 useEffect。服务端渲染时可以导入这个包,初始化只在浏览器中执行。
重复调用会被忽略。后续显示设置通过 updateWidget() 修改。调用 createWidget() 后即可使用控制方法,无需等待组件加载完成。
报告当前登录状态
auth.getToken() 用于报告当前用户。SDK 在打开面板及刷新身份时会调用它,因此这个函数应尽快返回。
| 返回结果 | 含义 | 组件行为 |
|---|---|---|
| JWT 字符串 | 已登录,身份由令牌中的 email 确定 | 以该身份建立 FeedLog 会话 |
null | 确认未登录 | 清除登录会话,根据工作区权限提供访客访问或登录入口 |
| 抛错或 reject | 临时失败 | 保留会话,在需要时提供重试入口 |
只有确认未登录时才返回 null
网络错误、超时和异常响应都应抛出错误。这些情况下返回 null 会清除用户缓存的登录会话。
打开应用的登录界面
当用户在组件内选择登录,且 getToken() 确认未登录时,SDK 会调用 auth.login。将它连接到应用已有的登录弹窗、独立窗口或登录路由。
使用弹窗或独立窗口时,返回一个 Promise,在登录交互结束时完成,包括用户取消的情况。等待期间,组件暂时隐藏面板和按钮;交互结束后重新调用 getToken(),再恢复显示。resolve 或 reject 都会结束等待,是否登录成功由获取到的令牌决定。
回调返回 void 时,组件保持显示,并立即重新检查身份。整页跳转时沿用应用自己的返回地址处理;用户在约五分钟内回到页面,反馈面板会重新打开。
会话刷新会静默调用 getToken()。只有用户明确选择登录时,才会打开登录界面。
在服务端签发令牌
在开发者 → 单点登录中创建 SSO 密钥。后端从应用已验证的会话读取用户信息,使用 HS256 签名 JWT。email 和 exp 必填,name 和 picture 可选;有效期不能超过 24 小时。
import jwt from 'jsonwebtoken'
export default handler(async (req) => {
const user = await currentUser(req)
if (!user) return { token: null }
return {
token: jwt.sign(
{ email: user.email, name: user.name, picture: user.avatarUrl },
process.env.FEEDLOG_SSO_SECRET,
{ algorithm: 'HS256', expiresIn: '1h' },
),
}
})请将 handler 和 currentUser 替换为你的服务端框架方法。SSO 密钥保留在服务端;签名中的身份字段必须来自已验证的会话,不能直接信任浏览器提交的身份信息。
SDK 会用 JWT 换取 FeedLog 会话并缓存。密钥管理、字段要求和交换错误见单点登录。浏览器单点登录和反馈组件可以复用相同的签名代码与密钥。
自定义入口与显示
反馈面板和悬浮按钮分别控制。打开或关闭面板会保留按钮当前的状态;修改按钮状态,也不会改变面板的开合状态。
用自己的按钮打开反馈
将网站已有的帮助或反馈入口连接到 openWidget():
import { openWidget } from '@feedlog/widget'
document.querySelector('#help-entry')?.addEventListener('click', openWidget)在入口渲染完成后绑定事件,或使用框架的点击事件处理。如果只想保留自己的入口,在首次 createWidget() 时设置 launcher: { state: 'hidden' }。悬浮按钮隐藏时,openWidget() 仍能打开面板。
业务弹窗打开时关闭面板
应用打开需要占用这块空间的业务弹窗时,调用 closeWidget():
import { closeWidget } from '@feedlog/widget'
function showCheckout() {
closeWidget()
openCheckoutDialog()
}关闭会保留对话和草稿。用户可以通过悬浮按钮或自定义入口再次打开反馈。
跟随应用切换主题
auto 跟随操作系统。应用有自己的主题开关时,在初始化时传入当前的 light 或 dark,并在应用切换主题的回调中更新:
import { updateWidget, type WidgetTheme } from '@feedlog/widget'
function onThemeChange(theme: WidgetTheme) {
updateWidget({ theme })
}主题切换不会打断当前对话。组件的品牌色来自 FeedLog 工作区设置。
调整按钮位置和状态
在初始化或调用 updateWidget() 时,通过 launcher 配置:
import { updateWidget } from '@feedlog/widget'
updateWidget({
launcher: { alignment: 'left', bottomOffset: 80, sideOffset: 20 },
})将 launcher.state 设为 button、tab 或 hidden,分别显示悬浮按钮、显示贴边的 Help 标签或隐藏入口。例如:
updateWidget({ launcher: { state: 'tab' } })初始化时,优先使用用户保存的拖动位置,其次是 SDK 中指定的位置字段,再次是工作区默认值。通过 updateWidget() 明确修改位置,会覆盖保存的拖动位置,未传入的位置字段保持当前值。只修改状态、主题或层级时,会保留按钮位置。
同一个浏览器标签页内,拖动位置和按钮/标签形态在重新加载后保留。隐藏只对当前页面有效,重新加载后恢复按钮。明确传入的 launcher.state 优先于用户保存的状态,因此每次初始化都传入 hidden 时,按钮每次都会隐藏。后台关闭方式控制用户能否关闭按钮及关闭后的行为;应用仍可通过 SDK 明确设置按钮状态。
设置显示层级
按钮和面板共用 zIndex,默认值为 40。可以在初始化时设置,也可以按应用导航栏和弹窗的层级动态调整:
updateWidget({ zIndex: 100 })具体值根据应用已有的层级安排选择。
在自定义入口显示未读数
在首次 createWidget() 时提供 onUnreadChange,接收组件角标使用的未读数量:
createWidget({
baseUrl: 'https://acme.feedlog.ai',
onUnreadChange: (count) => {
const badge = document.querySelector<HTMLElement>('#help-unread')
if (!badge) return
badge.textContent = String(count)
badge.hidden = count === 0
},
})将回调加入已有的那一次初始化;接入产品账号时,同时保留 auth。自己的角标初始设为隐藏,在 SDK 报告数量后显示。回调提供数字,入口自行决定显示格式。
API 与配置参考
四个方法均返回 void。
| 方法 | 用途 |
|---|---|
createWidget(options) | 每个页面初始化一次 |
openWidget() | 打开反馈面板 |
closeWidget() | 关闭面板,保留对话和草稿 |
updateWidget(options) | 更新 theme、zIndex 和 launcher 字段 |
初始化选项
| 选项 | 说明 |
|---|---|
baseUrl | 必填,包含协议头的工作区地址 |
auth.getToken | 提供 auth 时必填,类型为 () => Promise<string | null> |
auth.login | 可选,类型为 () => void | Promise<void>,打开应用登录界面 |
theme | light、dark 或 auto,默认 auto |
zIndex | 0 到 2147483647 的整数,默认 40 |
launcher | 下表中的位置和状态字段 |
onUnreadChange | 可选的 (count: number) => void,用于自定义未读提示 |
updateWidget() 按字段合并显示设置,包括 launcher 内的各个字段,未传入的字段保持不变。baseUrl、auth 和 onUnreadChange 在初始化时配置。显示更新不修改后台保存的工作区设置。
按钮选项
| 字段 | 说明 |
|---|---|
alignment | left 或 right,省略时使用工作区设置 |
bottomOffset | 非负的 JavaScript 安全整数,单位为 CSS 像素,省略时使用工作区设置 |
sideOffset | 非负的 JavaScript 安全整数,单位为 CSS 像素,省略时使用工作区设置 |
state | button、tab 或 hidden;省略时,在后台关闭方式允许的情况下恢复用户保存的状态,否则显示按钮 |
工作区的初始位置为右下角,距底部和侧边各 20 px。桌面端与手机端共用位置设置。SDK 会处理设备安全区域,并将入口保持在可见范围内。Help 标签会紧贴屏幕边缘。
自定义嵌入页面
部署了自定义组件页面时,可以在 createWidget() 中传入 embedPath。默认值为 /widget/embed,需要填写与 baseUrl 同源的绝对路径,例如 /custom-widget。
后台设置与浏览器支持
设置 → 反馈组件控制组件是否启用、默认位置和用户关闭按钮后的行为。SDK 在页面加载时读取这些设置。预览与保存步骤见设置反馈按钮。
组件支持当前版本的 Chrome、Firefox、Safari 和 Edge,样式与宿主页面隔离。桌面端面板根据按钮周围的可用空间放置;屏幕宽度不超过 520 CSS 像素时使用全屏面板。浏览器限制存储时,位置、状态和会话缓存仅在当前页面有效。
Agent 页面上下文
用户发送消息时,组件会提供当前页面路径、标题和 meta description,帮助 Agent 理解所在页面。提供的信息不包含查询参数、URL 片段或页面正文。SPA 切换页面后,下一条消息会使用新的页面信息,无需额外配置。
常见错误
反馈按钮不出现。 检查组件是否启用、初始化是否在客户端执行,以及是否明确设置了 launcher.state: 'hidden'。用户也可能在当前页面中暂时隐藏了按钮。如果控制台出现 could not load widget config,检查 baseUrl 是否指向你的工作区,以及其 /api/widget/config 接口能否访问。
用户间歇性退出登录。 检查令牌接口临时失败时,getToken() 是否抛出了错误,而不是返回 null。
应用已登录,但组件仍提示登录。 查看令牌接口的响应,检查它能否读取应用会话。返回 null 会让组件认为用户未登录。
出现“Feedback could not be loaded.”。 在浏览器网络面板中检查令牌接口和 FeedLog 令牌交换接口的响应。单点登录文档列出了令牌要求和交换错误。
修改默认位置后,按钮没有移动。 用户保存的拖动位置优先于初始配置。应用需要主动移动按钮时,通过 updateWidget() 明确更新位置。
导航栏遮住了反馈组件。 对照应用导航栏和弹窗的层级,调整组件的 zIndex。