Agent Skillslangwatch/langwatch › dashboard-widgets

dashboard-widgets

GitHub

将自然语言问题转化为可渲染的 React 仪表板组件,通过 LangWatchQL 查询数据。涵盖发现分析模式、编写 SQL 查询、生成组件代码并验证渲染流程,适用于构建自定义图表或复杂可视化需求。

skills/_compiled/native/dashboard-widgets/SKILL.md langwatch/langwatch

Trigger Scenarios

需要创建自定义仪表板组件 标准保存的图表无法满足展示需求 根据问题生成带查询的 React 小部件

Install

npx skills add langwatch/langwatch --skill dashboard-widgets -g -y
More Options

Non-standard path

npx skills add https://github.com/langwatch/langwatch/tree/main/skills/_compiled/native/dashboard-widgets -g -y

Use without installing

npx skills use langwatch/langwatch@dashboard-widgets

指定 Agent (Claude Code)

npx skills add langwatch/langwatch --skill dashboard-widgets -a claude-code -g -y

安装 repo 全部 skill

npx skills add langwatch/langwatch --all -g -y

预览 repo 内 skill

npx skills add langwatch/langwatch --list

SKILL.md

Frontmatter
{
    "name": "dashboard-widgets",
    "license": "MIT",
    "metadata": {
        "category": "recipe"
    },
    "description": "Author a dashboard widget — a small React component backed by named LangWatchQL queries — from a plain question. Discovers the analytics schema, writes the queries, writes the widget file, saves it, then proves it renders. Use when asked to build, prototype, or iterate on a custom chart widget, or to add a widget beyond what a standard saved chart can express.",
    "user-prompt": "Build me a custom chart widget",
    "feature-flag": "release_custom_chart_playground",
    "compatibility": "Requires the `langwatch` CLI with a valid `LANGWATCH_API_KEY`, and a project with LangWatchQL analytics enabled. Works with Claude Code and similar coding agents."
}

Author a Dashboard Widget

Turn a question ("show cost per model as a bar chart") into a saved dashboard widget: one React file rendering data pulled through named LangWatchQL queries. The loop is: discover the schema → write the queries → write the widget → save it → prove it runs.

Reach for this instead of lwql-charts when the visualization is not a plain Vega-Lite spec over one query result — custom layout, multiple queries in one widget, conditional rendering, or anything a component can do that a declarative spec cannot.

Prerequisites

LangWatchQL analytics is switched per project. If any command answers with error code lwql_not_enabled, the feature is off for this project — tell the user, do not retry.

Every dashboard-widget command runs against whatever project LANGWATCH_PROJECT_ID names in the environment. That variable is normally already set for you — do not invent a langwatch project ... command to look it up (there isn't one; langwatch projects list is the read command, plural). Only pass --project <slug-or-id> on a command yourself when you need to target a different project than the one already in scope. If a command fails with "No project is in scope", set LANGWATCH_PROJECT_ID or pass --project — do not retry the same call unchanged.

Step 1: Discover the schema before writing any SQL

Never guess dataset or column names. The schema command lists every dataset your credentials may query, each column's type and description, and a runnable example query per dataset:

langwatch dashboard-widget schema -f json

Read the datasets, their grain, their time column, and which columns are available to you.

Seed data is stale (newest trace 2026-08-04 as of this writing). Write SQL with a wide time window — WHERE timestamp >= subtractDays(now(), 400) — not a narrow recent one. A query that legitimately returns 0 rows because of a narrow window is not a bug; widen the window before assuming something is broken.

Step 2: Write the queries file

--queries-file is a JSON array of named queries: { name, sql, parameters? }. A widget's code calls a query by name — the two files are one unit.

  • parameters declares what a LW.query call may pass for this query: each entry is { name, type, default? }, where type is one of "string" | "number" | "boolean" (the JS type of the value, not the SQL type). Any key the widget code passes that is not declared here is rejected before any request fires — declare it or don't pass it.
  • The reserved bound names {dashboard_context_period_start:DateTime}, {dashboard_context_period_end:DateTime}, {dashboard_context_granularity_seconds:UInt32} are opt-in: use them in a query's SQL and the executor fills them from the page's own time window automatically. The dashboard_context_ prefix is reserved — supplied by the dashboard, read-only; author-declared params are separate. Do not add them to that query's parameters array, and never pass them yourself in a LW.query call — either mistake is refused with dashboard_widget_query_reserved_param / a schema validation error. On the dashboard-widgets authoring page, that window comes from a session-only range chip (1h / 24h / 7d / 30d, default 24h, resets on reload) — a query using the reserved bounds automatically re-runs when the chip changes. On a dashboard, a pinned widget instead follows that dashboard's own period selector.
  • Your own bind parameters use the same ClickHouse-style SQL placeholder syntax as lwql-charts ({since:DateTime}, {model:String}, …), declared with their JS type in parameters.
[
  {
    "name": "cost_by_model",
    "sql": "SELECT model, sum(costCents) / 100.0 AS cost FROM traces WHERE timestamp >= subtractDays(now(), 400) GROUP BY model ORDER BY cost DESC LIMIT 10",
    "parameters": []
  }
]

Step 3: Write the widget file

One React/TSX file, default-exporting a component. React and Recharts are available as globals in the render iframe (window.React, window.Recharts) — no import needed, no build step (in-browser Babel compiles the file on load). You may also import from "react", "react-dom", "react-dom/client", "recharts", or "@langwatch/charts" if you prefer that style; these resolve to the same globals, and importing anything else is refused at compile time.

Fetch data with the LW.useChartQuery(name, params) hook, where name matches an entry in the queries file. It returns the same shape as TanStack Query's useQuery — same field names, on purpose:

const { data, isLoading, isError, error } = LW.useChartQuery("cost_by_model", {});
  • data is the query's result rows, or null until the first result lands. An empty array (data.length === 0) means the query genuinely returned zero rows — not an error; against stale seed data that is expected unless the SQL uses a wide window (see Step 1).
  • isLoading is true only for the first fetch, before any data has ever landed. isFetching is true for any fetch in flight, including a background refetch — use it if you want to show a subtle "updating" state without clearing the chart.
  • isError is true on failure; error is then an Error (error.message) — it never throws into the component. An undeclared param, a reserved-param misuse, or a SQL error all surface here instead of crashing the widget. status is "pending" | "error" | "success" if you prefer a single field to branch on.
  • The hook auto-refetches when the page's time window changes, and when params changes by value — pass a plain object, not one you rebuild with a different identity every render for no reason. Call the returned refetch() to re-run it manually (e.g. from a button).

Always render all three branches (loading / error / data) — a widget that only handles the happy path shows nothing useful while data is stale-empty or a query is misconfigured.

Params are validated against the query's declared parameters before anything is forwarded to LangWatchQL — an undeclared key, or a required parameter with no default that is omitted, comes back through error rather than firing the request.

For code outside a component (or when you need a raw promise instead of hook semantics), LW.query(name, params) is the low-level escape hatch the hook is built on: it returns a promise that resolves to { rows, statistics } or rejects with { code, title, message }.

Prebuilt charts: @langwatch/charts

Ten chart primitives, prebuilt and themed, so most widgets don't need to hand-roll a Recharts layout: MetricStat, Sparkline, AreaTimeseries, StackedBars, GroupedBars, ProjectionBars, Donut, Leaderboard, Heatmap. Plus LwqlChart, an auto-picker that infers a chart type (area / bars / donut / leaderboard / table) from a query's row shape — pass it data, and optionally kind to force one.

Every component consumes LW.useChartQuery rows directly and themes itself — no color or style props needed.

export default function Widget() {
  const { data, isLoading, isError, error } = LW.useChartQuery("cost_by_model", {});

  if (isError) return <div style={{ fontSize: 11, color: "#b00" }}>{error.message}</div>;
  if (isLoading || data === null) return <div style={{ fontSize: 11, color: "#666" }}>Loading…</div>;

  return <LwqlChart data={data} />;
}

More worked examples: platform/app/scripts/north-star-widgets/.

Drill-down: LW.navigate

LW.navigate(target, params) sends the user to another LangWatch page from inside a widget — target is "traces" or "trace", and params is keyed by filter field ids (e.g. "metadata.user_id") that the host resolves into the Trace Explorer URL.

Leaderboard wraps this for you: pass navigateTo={{ target, params: (row) => ({ ... }) }} and a row click navigates. An unknown target is ignored rather than erroring.

Step 4: Save the widget, then prove it renders

langwatch dashboard-widget create \
  --name "Cost by model" \
  --code-file widget.tsx \
  --queries-file queries.json \
  -f json

Creating (and updating) validates the queries file's shape and the widget's JS/TSX syntax, but it does not execute the queries or mount the component — a wrong column name or a runtime error in the widget only surfaces when it actually runs. Open the widget's platformUrl (printed on success) and confirm it renders with real data before calling the work done.

Managing saved widgets

langwatch dashboard-widget list -f json
langwatch dashboard-widget get <id> -f json      # code, queries, platformUrl
langwatch dashboard-widget update <id> --code-file widget.tsx --queries-file queries.json
langwatch dashboard-widget delete <id>
langwatch dashboard-widget pin <id-or-name> --dashboard <id-or-name>   # add to a dashboard

pin reassigns the widget to that dashboard, at the dashboard's next free row — the widget still lives and is edited on the custom-chart-playground page. See langwatch dashboard list for ids.

update accepts --name on its own, or a full definition (--code/--code-file together with --queries-file) — passing one definition flag without the other is refused locally rather than saving half a widget. A call with nothing to change is refused too.

Worked example: bar chart of daily trace counts

widget.tsx:

import { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";

export default function Widget() {
  const { data, isLoading, isError, error } = LW.useChartQuery("daily_traces", {});

  if (isError) {
    return <div style={{ fontSize: 11, color: "#b00" }}>{error.message}</div>;
  }
  if (isLoading || data === null) {
    return <div style={{ fontSize: 11, color: "#666" }}>Loading…</div>;
  }

  const rows = data.map((row) => ({
    day: String(row.day).slice(0, 10),
    traces: Number(row.traces),
  }));

  return (
    <div>
      <div style={{ fontSize: 11, color: "#666", marginBottom: 4 }}>{rows.length} rows</div>
      <ResponsiveContainer width="100%" height={230}>
        <BarChart data={rows}>
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis dataKey="day" tick={{ fontSize: 10 }} />
          <YAxis allowDecimals={false} />
          <Tooltip />
          <Bar dataKey="traces" fill="#f97316" />
        </BarChart>
      </ResponsiveContainer>
    </div>
  );
}

queries.json:

[
  {
    "name": "daily_traces",
    "sql": "SELECT toStartOfDay(timestamp) AS day, count() AS traces FROM traces WHERE timestamp >= subtractDays(now(), 400) GROUP BY day ORDER BY day",
    "parameters": []
  }
]

Create it:

langwatch dashboard-widget create \
  --name "Daily traces" \
  --code-file widget.tsx \
  --queries-file queries.json \
  -f json

Failure modes worth knowing

  • lwql_not_enabled — the project's LangWatchQL switch is off; stop and say so.
  • custom_chart_playground_not_enabled — the custom-chart-playground feature flag is off for this project; do not retry any dashboard-widget command, and do not fall back to guessing. Use the lwql-charts skill / langwatch chart commands for a saved dashboard chart instead.
  • A save that succeeds but a blank or errored widget when opened — the SQL names a column that does not exist, or the component threw at render; re-read the schema (Step 1) and the error panel shown in the widget frame, then update.
  • dashboard_widget_query_undeclared_param (shows up in the hook's error) — the widget code passed a param the query's parameters array doesn't declare; add the declaration or stop passing it.
  • dashboard_widget_query_reserved_param (shows up in the hook's error) — the widget code passed dashboard_context_period_start, dashboard_context_period_end, or dashboard_context_granularity_seconds directly as a param; these are bound by the executor from the page window, never by the caller.
  • "Cannot import '<specifier>'" — the widget imported something other than react, react-dom, react-dom/client, recharts, or @langwatch/charts; use only those, or the corresponding global.
  • A query returning 0 rows against seed data — the seed's newest trace is 2026-08-04; widen the time window before assuming the query is wrong.

Version History

  • c106e86 Current 2026-09-22 23:39

Same Skill Collection

.claude/skills/browser-pair/SKILL.md
.claude/skills/browser-test/SKILL.md
.claude/skills/code-review/SKILL.md
.claude/skills/feature-map/SKILL.md
.claude/skills/haven-setup/SKILL.md
.claude/skills/langwatch-kanban/SKILL.md
plugins/langwatch/skills/langwatch/SKILL.md
services/langy-agent/skills/github/SKILL.md
skills/_compiled/native/agent-best-practices/SKILL.md
skills/_compiled/native/agent-performance/SKILL.md
skills/_compiled/native/code-changes/SKILL.md
skills/_compiled/native/context-sweet-spot/SKILL.md
skills/_compiled/native/datasets/SKILL.md
skills/_compiled/native/debug-instrumentation/SKILL.md
skills/_compiled/native/debug-with-langwatch/SKILL.md
skills/_compiled/native/drive-the-ui/SKILL.md
skills/_compiled/native/eval-triage/SKILL.md
skills/_compiled/native/evaluate-multimodal/SKILL.md
skills/_compiled/native/evaluations/SKILL.md
skills/_compiled/native/experiments/SKILL.md
skills/_compiled/native/find-traces/SKILL.md
skills/_compiled/native/generate-rag-dataset/SKILL.md
skills/_compiled/native/github/SKILL.md
skills/_compiled/native/guided-onboarding/SKILL.md
skills/_compiled/native/level-up/SKILL.md
skills/_compiled/native/lwql-charts/SKILL.md
skills/_compiled/native/online-evaluations/SKILL.md
skills/_compiled/native/prompt-optimization/SKILL.md
skills/_compiled/native/prompts/SKILL.md
skills/_compiled/native/provider-cost-comparison/SKILL.md
skills/_compiled/native/setup-lw/SKILL.md
skills/_compiled/native/test-cli-usability/SKILL.md
skills/_compiled/native/test-compliance/SKILL.md
skills/_compiled/native/tracing/SKILL.md
skills/_compiled/native/connect-agent/SKILL.md
skills/_compiled/native/scenarios/SKILL.md

Metadata

Files
0
Version
c106e86
Hash
4f4d3bf0
Indexed
2026-09-22 23:39

trang chủ - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-09-24 02:19
浙ICP备14020137号-1