Coze Studio 前端埋点实践:深入解析 @coze-arch/bot-tea 事件追踪封装包
【免费下载链接】coze-studioAn AI agent development platform with all-in-one visual tools, simplifying agent creation, debugging, and deployment like never before. Coze your way to AI Agent creation.项目地址: https://gitcode.com/GitHub_Trending/co/coze-studio
Coze Studio 前端基于 Rush monorepo 组织数百个业务包,其中埋点(数据上报)逻辑分散在各业务模块中难以统一治理。本文以 frontend/packages/arch/bot-tea/README.md 为主线,结合包内 src/index.ts、src/utils.ts 及其单元测试,完整剖析@coze-arch/bot-tea这个埋点封装包:它如何统一事件类型导出、如何持久化"落地页 URL"用于用户增长归因、如何通过全局 Feature Flag 动态拼接 User Growth(UG)参数,以及utils子路径如何为模板(Template)相关行为事件提取标准化公参。读完后,你可以在 Coze Studio 前端任意业务包中正确接入统一埋点,并理解其参数拼装与测试验证机制。
一、包定位:bot tea wrapper
frontend/packages/arch/bot-tea/README.md 将@coze-arch/bot-tea定位为 "bot tea wrapper",即对底层 Tea 埋点 SDK 的业务封装层,属于 Coze Studio monorepo 中frontend/packages/arch(架构层)下的基础包之一。
从 frontend/packages/arch/bot-tea/package.json 可以看到它依赖的三个 workspace 内部包:
@coze-arch/tea:Tea 埋点 SDK 封装,实际再向下适配到@coze-studio/tea-adapter,并透传@coze-studio/tea-interface/events中定义的事件名与事件参数类型(见 frontend/packages/arch/tea/src/index.ts);@coze-arch/logger:统一日志器,sendTeaEvent在真正上报前会先落一条 info 日志;@coze-arch/bot-api:提供product_api中的ProductEntityType、ProductInfo等模板领域类型,供utils子路径做参数提取。
README 中"Features"一节提到的 Store / Plugin / Logger 等条目,从当前源码结构看更偏历史描述:实际对外能力集中在事件类型再导出 + 落地页 URL 管理 + 统一事件发送这三块,核心实现仅有两个源文件。
二、安装与依赖引入
在 monorepo 内消费该包的方式,如 README 所述,是在目标包的package.json中声明 workspace 依赖:
{ "dependencies": { "@coze-arch/bot-tea": "workspace:*" } }然后执行:
rush updatepackage.json中的exports字段揭示了该包的两条导入路径:
"exports": { ".": "./src/index.ts", "./utils": "./src/utils.ts" }也就是说:
import { sendTeaEvent, ... } from '@coze-arch/bot-tea'拿到事件发送与落地页 URL 工具;import { extractTemplateActionCommonParams, ... } from '@coze-arch/bot-tea/utils'拿到模板行为事件的公参提取函数(typesVersions字段同时保证了utils子路径的类型解析)。
值得注意的是该包的build脚本为"build": "exit 0",即直接以 TypeScript 源码作为产物供其他包消费,由上层应用(如frontend/apps/coze-studio的 rsbuild)统一编译,这是该 monorepo 内部包的通用策略。
三、事件类型导出:一套完整的事件字典
frontend/packages/arch/bot-tea/src/index.ts 从@coze-arch/tea再导出了一组事件名常量与事件参数类型,这正是 README "Exports" 一节列出的清单。它按业务域大致可分为四类:
| 分类 | 导出成员 | 用途 |
|---|---|---|
| 商店/资源页事件 | AddBotToStoreEntry、AddPluginToStoreEntry、AddWorkflowToStoreEntry、FlowStoreType、FlowResourceFrom、FlowDuplicateType | 商店卡片曝光/点击、复制资源来源等 |
| 发布/详情页行为 | PublishAction、BotDetailPageAction、BotShareConversationClick | Bot 发布、详情页操作、分享会话点击 |
| 插件调试/隐私 | PluginPrivacyAction、PluginMockSetCommonParams、PluginMockDataGenerateMode | 插件隐私设置、Mock 数据生成方式切换 |
| 模板/产品事件 | ProductEventSource、ProductEventFilterTag、ProductEventEntityType、ProductShowFrontParams、DocClickCommonParams、ExploreBotCardCommonParams | 模板市场的展示、筛选、文档点击等 |
此外还导出事件名枚举EVENT_NAMES与参数映射类型ParamsTypeDefine。这一层"再导出"的意义在于:业务包(如 agent-ide 系列)只需依赖@coze-arch/bot-tea一个包,即可获得类型安全的事件名 + 参数联合类型——发送事件时事件名与参数由ParamsTypeDefine[TEventName]约束,避免手写字符串导致上报字段错漏。
EVENT_NAMES与ParamsTypeDefine的最终定义位于@coze-studio/tea-interface(经由 frontend/packages/arch/tea/src/index.ts 的类型 re-export 引入),本文不展开其完整字典,仅说明 bot-tea 承担的是"业务侧统一出口"角色。
四、落地页 URL 机制:用户增长归因的关键
4.1 常量与函数
src/index.ts 定义了全包的存储键与两个函数:
export const LANDING_PAGE_URL_KEY = 'coze_landing_page_url'; /** * The LandingPageUrl that UG expects to report is "the full URL of the page * that netizens initially clicked on." Even if you open a new page, you * should report the URL of the landing page you opened for the first time. */ export const initBotLandingPageUrl = () => { const saved = window.sessionStorage.getItem(LANDING_PAGE_URL_KEY); if (!saved) { window.sessionStorage.setItem(LANDING_PAGE_URL_KEY, location.href); } }; export const getBotLandingPageUrl = () => { const saved = window.sessionStorage.getItem(LANDING_PAGE_URL_KEY); return saved ?? location.href; };其设计语义(源码注释已明确):UG(用户增长)侧期望上报的LandingPageUrl是用户最初点击进来的落地页完整 URL。即使之后用户在站点内跳转、打开新页面,后续所有埋点仍应携带"首次落地页"URL,而不是当前页 URL。实现上:
initBotLandingPageUrl()在应用入口处调用一次,用sessionStorage(而非localStorage)记录location.href——会话级生命周期正好匹配"一次访问归因"的语义,关闭标签页后自动失效,不会跨会话污染;- 写入前做存在性检查,保证"first write wins",后续页面跳转中的重复调用不会覆盖首次值;
getBotLandingPageUrl()读取时带兜底:sessionStorage无值(如用户以深链直接命中、入口尚未初始化)则回退到当前location.href。
4.2 测试佐证
frontend/packages/arch/bot-tea/tests/index.test.ts 用 Vitest 对上述三条路径做了显式断言:
- 无存储时调用
initBotLandingPageUrl()→setItem(LANDING_PAGE_URL_KEY, mockLocation)被调用; - 已有存储时再次调用 →
setItem不应被调用(防止覆盖); getBotLandingPageUrl()有存储返回存储值,无存储回退location.href。
测试通过Object.defineProperty(window, 'sessionStorage', { value: mockSessionStorage })注入 mock 存储,隔离了浏览器环境依赖,可作为编写类似存储逻辑测试的参考。
五、sendTeaEvent:统一事件发送与 UG 参数注入
5.1 函数签名与调用链
src/index.ts 中的核心函数:
export const sendTeaEvent = <TEventName extends EVENT_NAMES>( event: TEventName, rawParams?: ParamsTypeDefine[TEventName], ) => { let params = rawParams; if (FEATURE_ENABLE_TEA_UG) { const ugParams: UserGrowthEventParams = { LandingPageUrl: getBotLandingPageUrl(), // AppId agreed with UG, fixed value AppId: 510023, EventName: event, // timestamp EventTs: Math.floor(Date.now() / 1000), growth_deepevent: '4', }; // @ts-expect-error -- UG extra parameters params = { ...ugParams, ...(rawParams ?? {}) }; } logger.info({ message: 'send-tea-event', meta: { event, params }, }); TeaNew.sendEvent(event, params); };调用链为:sendTeaEvent→ (可选)合并 UG 参数 →logger.info记录一条send-tea-event日志(便于前端排障时核对实际上报内容)→ 委托给TeaNew(即@coze-arch/tea的 default 导出,其内部再适配@coze-studio/tea-adapter)完成真正上报。
5.2 五个 UG 参数逐一解读
当全局开关FEATURE_ENABLE_TEA_UG为真时,事件参数会被前置合并 5 个用户增长专用字段:
| 字段 | 取值 | 说明 |
|---|---|---|
LandingPageUrl | getBotLandingPageUrl() | 首次落地页 URL,见第四节 |
AppId | 510023 | 源码注释标明是"与 UG 约定的固定值" |
EventName | 事件名本身 | 将事件名冗余到 UG 参数字段中 |
EventTs | Math.floor(Date.now() / 1000) | 秒级时间戳 |
growth_deepevent | '4' | 深度事件标识,固定字符串 |
合并顺序为{ ...ugParams, ...rawParams },即业务自定义参数可覆盖 UG 参数;随后整体透传给TeaNew.sendEvent。
5.3 FEATURE_ENABLE_TEA_UG 从哪里来
这个标识符在 frontend/packages/arch/bot-tea/src/index.ts 中作为全局变量直接引用(无 import),其声明与赋值位于环境适配层:
- 类型声明:frontend/packages/arch/bot-env-adapter/src/typings.d.ts 中
declare const FEATURE_ENABLE_TEA_UG: boolean;; - 实际取值:frontend/packages/arch/bot-env-adapter/src/features.ts 中
FEATURE_ENABLE_TEA_UG: IS_RELEASE_VERSION && !IS_OVERSEA,即仅在生产发布版本且非海外环境下启用 UG 参数注入。
因此行为被环境驱动:开发/海外构建中sendTeaEvent只上报原始参数;国内生产构建中自动附加 UG 五元组。这正是"wrapper 层"的价值——业务调用方完全无感知该差异,只需调用同一个sendTeaEvent。
5.4 单元测试对两条分支的覆盖
tests/index.test.ts 通过vi.mock('@coze-arch/tea')与vi.mock('@coze-arch/logger')隔离外部依赖,验证了:
- 开关为
true时,logger.info收到的meta.params同时包含 UG 五字段(EventTs断言为expect.any(Number))与业务参数foo: 'bar'; - 开关为
false时,上报参数就是原始mockParams,不注入任何 UG 字段; rawParams为undefined时也能安全通过(params: undefined)。
六、utils 子路径:模板行为事件的公参提取
除主入口外,frontend/packages/arch/bot-tea/src/utils.ts 提供了面向模板市场行为埋点的两个工具函数,供模板卡片点击、模板应用等场景复用,保证所有template_action_front类事件上报同构的公参。
6.1 convertTemplateType:实体类型到埋点字段的映射
export function convertTemplateType( entityType?: ProductEntityType, ): ParamsTypeDefine[EVENT_NAMES.template_action_front]['template_type'] { switch (entityType) { case ProductEntityType.WorkflowTemplateV2: return 'workflow'; case ProductEntityType.ImageflowTemplateV2: return 'imageflow'; case ProductEntityType.BotTemplate: return 'bot'; case ProductEntityType.ProjectTemplate: return 'project'; default: return 'unknown'; } }将后端ProductEntityType(来自@coze-arch/bot-api/product_api)映射为埋点侧的小写字符串标签,未识别类型统一降级为'unknown'。utils.test.ts 对四个分支及undefined、非法值均断言了预期输出。
6.2 extractTemplateActionCommonParams:从 ProductInfo 提取公参
export function extractTemplateActionCommonParams(detail?: ProductInfo) { const queryParams = queryString.parse(location.search); const from = (queryParams?.from ?? '') as string; return { template_id: detail?.meta_info.id || '', entity_id: detail?.meta_info.entity_id || '', template_name: detail?.meta_info.name || '', template_type: convertTemplateType(detail?.meta_info.entity_type), ...(detail?.meta_info.entity_type === ProductEntityType.ProjectTemplate && { entity_copy_id: detail?.project_extra?.template_project_id, }), template_tag_professional: detail?.meta_info.is_professional ? 'professional' : 'basic', ...(detail?.meta_info?.is_free ? ({ template_tag_prize: 'free' } as const) : ({ template_tag_prize: 'paid', template_prize_detail: Number(detail?.meta_info?.price?.amount) || 0, } as const)), from, } as const; }逐字段说明:
template_id/entity_id/template_name:直接取meta_info对应字段,缺失时回退空串,保证detail整体为undefined时函数仍可安全调用;template_type:经convertTemplateType转换;entity_copy_id:仅当实体类型为ProjectTemplate时才附加,取值project_extra.template_project_id(项目模板复制后关联的实例 ID);template_tag_professional:is_professional→'professional'/'basic';- 价格标签三元组:免费模板仅上报
template_tag_prize: 'free';付费模板额外上报template_prize_detail(price.amount转 Number,NaN时回退0)。Number(...) || 0的写法同时兜住了字段缺失与非法值两种情况; from:用query-string解析当前 URL 查询串中的from参数,标识流量来源渠道,缺失时为''。
utils.test.ts 覆盖了五类场景:免费 workflow 模板、付费 bot 模板(amount: '100'→template_prize_detail: 100)、含entity_copy_id的 project 模板、detail为undefined的全默认输出、以及price.amount缺失时回退0,与上文逐字段说明一一对应。
七、真实消费场景:谁在调用 bot-tea
在frontend/packages/agent-ide下检索@coze-arch/bot-tea的引用,可以看到它被大量业务包消费,例如:
- frontend/packages/agent-ide/layout/src/components/header/deploy-button/hooks/service.tsx(部署按钮的上报);
- frontend/packages/agent-ide/agent-publish/src/components/bot-publish/publish-table/index.tsx(发布流程事件);
- frontend/packages/agent-ide/space-bot/src/component/bot-debug-panel/index.tsx 与 button.tsx(调试面板操作);
- frontend/packages/agent-ide/commons/src/hooks/use-send-diff-event.ts(差异事件 hook)等。
这说明 bot-tea 在架构中的位置:底层 SDK(tea-adapter/tea-interface)→ 薄封装(@coze-arch/tea)→ 业务出口(@coze-arch/bot-tea)→ 各业务组件,形成单向依赖,业务组件不直接触碰底层 SDK。
八、开发与测试
依据 package.json 的 scripts 与 devDependencies:
rushx test:以 Vitest 运行__tests__/下的用例(--passWithNoTests允许无测试时通过);rushx test:cov:附带 v8 覆盖率,产物目录由 config/rush-project.json 中operationSettings声明为coverage;rushx lint:使用 monorepo 统一的@coze-arch/eslint-config;- 类型检查基线来自
@coze-arch/ts-config,全局类型引用见 src/global.d.ts(/// <reference types='@coze-arch/bot-typings' />,使window.FEATURE_ENABLE_TEA_UG等全局变量获得类型)。
九、小结
@coze-arch/bot-tea体量虽小(两个源文件),却集中体现了 Coze Studio 前端埋点体系的治理思路:
- 单一出口:业务包只依赖 bot-tea,事件名与参数由
EVENT_NAMES/ParamsTypeDefine静态约束; - 归因一致:
sessionStorage首次写入策略保证所有事件携带同一落地页 URL,支撑 UG 渠道归因; - 环境自适应:
FEATURE_ENABLE_TEA_UG(IS_RELEASE_VERSION && !IS_OVERSEA)让 UG 参数注入只发生在需要的构建环境,业务代码零感知; - 公参收敛:
utils子路径将模板事件的易错字段(类型映射、价格标签、来源渠道)收敛为纯函数,并由完整的 Vitest 用例固化行为契约。
若要在自己的业务组件中新增一个埋点,推荐路径是:从@coze-arch/bot-tea导入对应EVENT_NAMES常量与sendTeaEvent,事件参数按ParamsTypeDefine类型提示补齐;涉及模板行为时优先复用@coze-arch/bot-tea/utils的extractTemplateActionCommonParams,避免各组件自行拼装字段造成口径不一致。
【免费下载链接】coze-studioAn AI agent development platform with all-in-one visual tools, simplifying agent creation, debugging, and deployment like never before. Coze your way to AI Agent creation.项目地址: https://gitcode.com/GitHub_Trending/co/coze-studio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考