Langfuse chart-view-prototype 深度解析:用「设计即构建」的方式打磨 v4 事件的「任意视图皆图表」体验
2026/9/10 0:47:00 网站建设 项目流程

Langfuse chart-view-prototype 深度解析:用「设计即构建」的方式打磨 v4 事件的「任意视图皆图表」体验

【免费下载链接】langfuse🪢 Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. 🍊YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuse

本指南以 chart-view-prototype 的 README 为骨架,系统拆解 Langfuse 前端团队如何通过一个「可丢弃的设计原型」(throwaway design prototype,阶段 EXP-CHART-PROTOTYPE / phase 0)预先打磨 v4 events/traces 视图的「table → chart」可视化体验。读完本文,你将理解原型的数据层如何用纯函数模拟未来服务端聚合接口、两种配置 UI 形态(内联条 vs 侧边面板)的设计取舍,以及原型如何为后续 tRPCevents.aggregate接真数据铺路。

一、原型的定位:为什么要在接真实数据前先做这个

按 README 的界定,web/src/features/chart-view-prototype/是一个状态明确的可丢弃设计原型:它只跑在 Storybook 里、只使用 mock 数据,当前应用的任何代码都还没有 import 它。它的存在意义是「design by building」——在把功能接入 v4 真实读路径之前,先用代码把「any view is a chart」的完整交互体验建出来,供团队(README 中提到的 Nikita)拍板方向。

这一阶段设计(phase 0)的产物不是可上线的功能,而是一份可运行、可点击、可对比的设计决议,这正是它与传统静态设计稿的本质区别。

从源码结构看(目录清单),原型由三部分组成:

web/src/features/chart-view-prototype/ ├── types.ts # 配置规格 ChartViewConfig + mock 事件行 PrototypeEvent ├── vocab.ts # 指标/维度/聚合/图表类型词汇表(重导出生产词汇) ├── lib/ │ ├── aggregate.ts # 纯函数数据层:aggregateEvents(events, config) → DataPoint[] │ ├── aggregate.clienttest.ts # 聚合器与时间分桶的单元测试 │ └── fixtures.ts # 确定性 mock 事件生成器 + 命名场景 ├── components/ │ ├── ChartViewPrototype.tsx # 根组件:持有 mode + config 状态,渲染两种 take │ └── MockEventsTable.tsx # 代表性的「表格」一侧 └── ChartViewPrototype.stories.tsx # 真正的交付物:Storybook stories

二、核心体验设计:一次「table → chart」的就地可视化

原型要还原的核心体验是:在 v4 events 视图中,把表格翻转成图表,并在原位配置可视化,配置项是四元组:

  • metric(指标):对什么做聚合,如 count、latency、totalCost、totalTokens;
  • aggregation(聚合方式):sum / avg / min / max / p50 / p95 / p99 / count;
  • breakdown(分组维度):按 model、name、level、type、environment 拆分,或不做拆分;
  • chart-type(图表类型):折线、面积、柱状、横向排行、饼图、大数字。

同时提供Ask AI入口:用自然语言描述需求,由 AI 直接产出这份配置,从而改变图表配置方式。整个配置模型刻意设计成「扁平的、枚举密集的」结构——正如 chart-view 的 types.ts 注释所说,这样既便宜地放进 URL,又方便 LLM 生成(即 phase 2 的「Ask AI → chart」),当前只覆盖 happy path:1 个指标 × 1 个维度 × 1 种标准图表类型。

Take A 与 Take B:两种配置承载形态

README 明确要求同时交付两种 UX 形态做对比:

Take形态特征
Take A — inline bar紧凑的、常驻的配置条,位于画布上方密集、快速,像是表格的「活扩展」,改动配置零跳转
Take B — side panel最大化画布 + 可折叠的侧边配置面板更干净、更有引导性,给图表留出更大空间

两个 take 共用同一个 table↔chart 切换开关、同一个纯聚合器、同一个 mock 的「Ask AI → chart」入口,差异只在于配置 UI 的呈现方式(见 ChartViewPrototype.stories.tsx 的注释)。

从 ChartViewPrototype.tsx 的实现可以看到两种 take 的分流逻辑:mode === "table"时渲染 MockEventsTable;否则按affordance属性决定渲染内联条(InlineTake,原型内自建)还是侧边面板——后者直接复用生产组件 ChartViewPanel,且 README 的组件注释明确记录了团队最终选择的方向:Take B(侧边面板)被 Nikita 采纳为共享的生产形态,Take A 仅作为设计记录保留在原型中。

三、运行方式:在 Storybook 里体验这套设计

README 给出的运行方式非常简单,在仓库根目录执行:

pnpm --filter web run storybook

启动后进入Charts / Chart View Prototype分组即可查看全部 stories。原型的故事集(ChartViewPrototype.stories.tsx)本身就是设计交付物,包含:

  • TakeA_InlineBar:旗舰场景,默认配置是「按模型拆分的、随时间变化的事件计数折线图」;
  • TakeB_SidePanel:同样体验但配置收入可折叠面板;
  • TableView:熟悉的 events 表格起始态,右上角翻转开关切到图表;
  • AskAI:针对带 error-spike 的数据集,尝试「errors over time by level」这类自然语言问法;
  • LatencyP95ByModelCostByModelRankedEventsByLevelPie:预设配置直接打开对应图表;
  • EmptyState:空数据状态;
  • Comparison:两种 take 上下堆叠、直接并排对比。

stories 的 args 支持affordanceinline/panel)和initialModetable/chart)两个切换,方便在 Storybook 画布里快速对比。原型内部还通过initialConfig支持注入预设配置(见 stories 中的PRESETS)。

四、Owner map:每个文件各司其职

README 给出了清晰的职责划分表,这里完整继承并补充源码依据:

文件职责源码要点
types.ts配置规格ChartViewConfig+ mock 事件行PrototypeEventChartViewConfig从生产 chart-view/types.ts 重导出;PrototypeEvent是原型的 v4 事件行替身,字段名镜像 observations 视图声明(startTimetypenamemodellevelenvironmentlatencyMstotalCosttotalTokens
vocab.ts指标/维度/聚合/图表类型词汇表(真实 widget 词汇表的忠实子集)+ 取值器 + 配置强制转换;纯逻辑、无 React第一行即export * from "@/src/features/chart-view/vocab",确保 harness 与真实视图共用同一份事实来源;另定义 mock 专用的METRIC_EXTRACTORS/DIMENSION_EXTRACTORS
lib/aggregate.ts数据层。纯函数aggregateEvents(events, config) → DataPoint[],镜像未来 v4 聚合端点的返回形状内部按图表类型分派:NUMBER→ 单点聚合;时间序列 → 时间分桶 × 系列;分类 → 分组聚合后按指标降序排序;含floorToGranularity时间分桶与线性插值分位数
lib/fixtures.ts确定性(种子化)mock 事件生成器 + 命名场景基于 mulberry32 微型 PRNG,时间窗锚定固定时刻(2026-06-25T18:00:00Z),输出default/errorSpike/sparse/empty四个场景
components/ChartViewPrototype.tsx根组件:持有mode+config状态,渲染两种 take 之一;其下全部为纯展示状态用useState+coerceConfig归一化;patchConfiguseCallback保持稳定;datauseMemo派生
components/ChartCanvas.tsxReact.memo图表渲染边界(派生数据并渲染chart-library原型实际复用生产 chart-view/components/ChartCanvas.tsx
components/ConfigControls.tsx共享、纯展示的配置选择器(指标/聚合/分组/粒度/图表类型)原型直接复用生产 chart-view/components/ConfigControls.tsx 中的MetricSelectAggregationSelectBreakdownSelectGranularitySelectChartTypePicker
components/MockEventsTable.tsx切换开关中代表「表格」的一侧刻意做成轻量、纯展示的表格(非完整虚拟化DataTable),只显示前 14 行,让切换「诚实」而不重造轮子
components/ViewModeToggle.tsxtable↔chart 切换开关复用生产 chart-view/components/ViewModeToggle.tsx
ChartViewPrototype.stories.tsxstories(实际交付物)见上文第三节

五、数据层原理:纯函数模拟未来聚合端点

原型最关键的抽象在 lib/aggregate.ts:aggregateEvents(events, config)是一个纯函数,输入 mock 事件数组 + 配置,输出chart-libraryDataPoint[]。它的设计目标是镜像生产环境dashboard.executeQuery对 observations 视图的返回形状,这样 Storybook 里渲染的视图组件与真实EventsChartView完全一致,只差数据来源(见 aggregate.ts 注释)。

五.1 取值器(extractor):mock 世界的「列映射」

vocab.ts 中,METRIC_EXTRACTORS定义了每个指标如何从PrototypeEvent取出数值:

export const METRIC_EXTRACTORS: Record<MetricKey, ((e: PrototypeEvent) => number) | null> = { count: null, // null = 行数(count 度量) latency: (e) => e.latencyMs, totalCost: (e) => e.totalCost, totalTokens: (e) => e.totalTokens, }; export const DIMENSION_EXTRACTORS: Record<DimensionKey, ((e: PrototypeEvent) => string) | null> = { none: null, // null = 不做分组 model: (e) => e.model ?? "unknown", name: (e) => e.name, level: (e) => e.level, type: (e) => e.type, environment: (e) => e.environment, };

生产环境从 ClickHouse 的 observations 视图读取这些字段;harness 则在客户端取值,null的语义分别是「行数度量」和「不分组」。

五.2 聚合分派:三种图表家族的三种算法

aggregateEvents按图表类型走三条路径(aggregate.ts):

  1. NUMBER(大数字):忽略分组,全量聚合成单个DataPointtime_dimensiondimension均为undefined
  2. 时间序列(折线/面积/柱状):先按timeGranularitystartTimefloorToGranularity分桶(aggregate.ts,UTC 下向下取整到分钟/小时/天),再按系列二次分组,最后按时间桶排序输出,保证时间轴有序;
  3. 分类(横向排行/饼图):按分组维度聚合后,按指标值降序排序b.metric - a.metric),得到「排名」语义。

底层aggregate函数(aggregate.ts)实现了全部聚合算子:count直接返回行数;sum/avg/min/max遍历取值并过滤非有限数;p50/p95/p99线性插值分位数算法(percentile):排序后按rank = (p/100) * (n-1)在相邻两个值之间插值,与常见统计库的分位数口径一致。

五.3 测试验证:聚合器的行为契约

aggregate.clienttest.ts 用 Vitest 锁定了上述行为,关键断言包括:

  • 空事件集返回空数组;
  • count 指标按「时间桶 × 分组系列」计数(gpt-4o 在 10:00 桶计 2,claude-opus-4 在 11:00 计 1);
  • 时间桶无论输入顺序如何都按时间序输出
  • 组内avg计算正确(100/200/300 → 200);
  • 线性插值 p95 精确到95.05(对 1..100 序列,rank = 0.95 × 99 = 94.05);
  • 分类结果按指标降序排名;
  • NUMBER图表类型产出单一聚合点;
  • floorToGranularity对 minute/hour/day 的向下取整(如2026-06-25T10:45:12.345Z按 hour 取整为10:00:00.000Z)。

这套测试让「mock 数据层」本身也有行为契约,为 phase 1 替换成真实 tRPC 调用提供了等价的参照基准。

六、Mock 数据:种子化的确定性事件流

lib/fixtures.ts 的目标是确定性:stories 和聚合器测试跨运行完全稳定,不依赖Math.random和墙钟时间。实现手段是:

  • mulberry32 微型 PRNG(fixtures.ts):32 位种子驱动的确定性伪随机数生成器;
  • 固定时间窗锚点WINDOW_END = Date.parse("2026-06-25T18:00:00.000Z")
  • 加权随机采样weightedPick让模型/环境出现频率符合真实分布。

生成器刻意模拟真实 trace 流的形态(fixtures.ts):

  • 5 个模型规格(gpt-4ogpt-4o-miniclaude-opus-4claude-haiku-4gemini-2.5-pro),各带权重、基础延迟、延迟散布与每 token 成本;
  • 5 个典型操作名(generate-answersummarizeclassify-intentembed-docsrerank-results);
  • 80% 为GENERATION,其余为SPAN/EVENT;级别以DEFAULT为主、薄尾分布 WARNING/ERROR/DEBUG;
  • 成本随 token 数线性变化,延迟随模型变化,且非生成事件延迟和成本都显著更低;
  • 可选errorSpike开关:把 ERROR 集中到最近 4 小时窗口,让「errors over time」在图上呈现真实的凸起。

导出的四个命名场景(SCENARIOS)对应不同故事:default(640 条、24h)、errorSpike(种子 7,ERROR 聚集)、sparse(28 条、6h)、empty(空数组)。

七、架构原则:frontend-large-feature-architecture 的实践样本

README 明确标注原型遵循frontend-large-feature-architecture,三条原则在源码中逐一对得上:

  1. 纯派生(Pure derivation):所有数据变换集中在lib/aggregate.ts;组件纯展示、只渲染派生数据。「同样的 events + config → 同样的图表」,可预测、可测试。这体现在 ChartViewPrototype.tsx 的useMemo(() => aggregateEvents(events, config), [events, config])

  2. 单向数据流(One-way data flow):根组件持有mode+config,子组件只接收值 + 稳定的(memoized)onChange回调,除回调外没有任何向上回传的通道。patchConfiguseCallback包装且依赖为空数组(ChartViewPrototype.tsx),保证子组件引用稳定。

  3. 渲染边界(Render boundaries)ChartCanvas和选择器都做了React.memo单次配置变更不会触发重新聚合或无关 UI 重渲染MockEventsTable同样React.memo化(MockEventsTable.tsx)。当图表类型切换时,isTimeSeries计算结果用于禁用/启用粒度选择器,粒度选择只在时间序列图表下有意义。

配置的强制归一化(coercion)

vocab.ts 的coerceConfig是配置安全的最后防线:把配置的每个字段钳制到已知枚举成员(URL 参数不可信),并在指标改变时把聚合方式重置为该指标支持的默认聚合。视图组件和 URL 状态都经过它往返,因此两者都不可能产出非法查询。根组件初始化和每次patchConfig都调用它:

const [config, setConfig] = useState<ChartViewConfig>(() => coerceConfig({ ...DEFAULT_CONFIG, ...initialConfig }), ); const patchConfig = useCallback( (patch: Partial<ChartViewConfig>) => setConfig((prev) => coerceConfig({ ...prev, ...patch })), [], );

默认配置(DEFAULT_CONFIG)是「count 指标 × count 聚合 × 按 model 分组 × 折线时间序列 × 小时粒度」,即旗舰 story 的打开状态。

八、与生产 feature 的共享:词汇表是唯一事实来源

原型并非从零造轮子。它重导出并复用生产 features/chart-view 的能力:

  • 类型:chart-view/types.ts 定义了ViewModeMetricKeyDimensionKeyAggregationFnTimeGranularity和扁平枚举密集的ChartViewConfig
  • 词汇表:chart-view/vocab.ts 中,METRICS的每个指标都携带真实查询标识measurecount/latency/totalCost/totalTokens)和单位(millisecond/USD),DIMENSIONS的每个维度都携带 observations 视图字段名(如providedModelNamelevelenvironment),使后续构建聚合查询是「直接映射」;图表类型则从DashboardWidgetChartType(位于 packages/shared/src/db 的@langfuse/shared/src/db导入)中选取折线/面积/柱状时间序列、横向排行、饼图、大数字六种,刻意排除透视表(与表格侧重叠)和直方图(happy path 外);
  • 配置语义describeConfig把配置渲染成人类可读的句子,既是图表副标题,也是 Ask-AI 流程中「我为你生成了什么」的确认文案——例如Count of events by model over time(count 指标省略聚合前缀,NUMBER图表不声称分组,见 vocab.ts)。

原型之所以能做到「harness 与真实视图共用同一份事实来源」,核心在于 prototype/vocab.ts 的第一行export * from "@/src/features/chart-view/vocab"

九、接入路径:后续阶段的既定计划

README 明确指出原型文件夹尚未包含生产接入代码,并给出清晰的后续路线:

  1. Phase 1:把 lib/aggregate.ts 的客户端聚合函数替换为tRPCevents.aggregate调用,返回同样的DataPoint[];把mode+config移入URL 状态(实现可逆的切换);挂载进features/events/components/EventsTable.tsx,并以 v4 读路径做特性门控(gated)。

后续的「Ask AI → chart」(phase 2 方向)则建立在配置模型的 LLM 友好性之上:扁平、枚举密集的ChartViewConfig让自然语言到配置的转换对 LLM 而言成本极低。

对应地,生产侧 EventsChartView.tsx 与 lib/buildChartQuery.ts、lib/chartConfigToWidget.ts 已经搭起了从配置到查询、到 widget 的桥,原型正是这套生产代码在接真实数据前的「设计验证床」。

十、小结:一次可运行的 UI 设计决议

chart-view-prototype 的价值在于把「表格视图内嵌可配置图表」这一体验从想法变成了可点击、可对比、可测试的 Storybook 原型:纯函数数据层(aggregateEvents)提前锁定了未来聚合端点的数据契约,种子化 fixtures 保证了演示与测试的确定性,而 Take A / Take B 两种配置形态则把交互方向的取舍摆到桌面上供决策。对于想理解 Langfuse 前端如何「design by building」、以及 v4 chart view 背后配置模型与数据流的开发者,这个原型文件夹连同其测试、stories 和生产 chart-view 模块,是一份完整可循的参考路径。

【免费下载链接】langfuse🪢 Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. 🍊YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuse

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询