Skip to content

feat(canvas): add AI gateway built-in template#2773

Draft
richardsolomou wants to merge 1 commit into
mainfrom
posthog-code/ai-gateway-canvas-template
Draft

feat(canvas): add AI gateway built-in template#2773
richardsolomou wants to merge 1 commit into
mainfrom
posthog-code/ai-gateway-canvas-template

Conversation

@richardsolomou

@richardsolomou richardsolomou commented Jun 19, 2026

Copy link
Copy Markdown
Member

Problem

New agents-platform frontend work belongs in PostHog Code, not the Cloud frontend. The AI gateway usage page shipped as a Cloud kea scene (PostHog/posthog#64511); this is the canvas-native equivalent.

Changes

  • New ai-gateway declarative built-in (KPI Stats, spend-per-day chart, by-model table, connect-snippet switch, empty-state Hero), mirroring the web-analytics template.
  • Enrols ai-gateway in DATA_TEMPLATES so the board gets the date picker and toolbar refresh.
  • Gateway base URL is a <placeholder> (Code has no preflight to source it); rationale and open question in AI_GATEWAY.md.

How did you test this?

vitest run src/canvas/ (6 files, 53 tests), core typecheck, biome lint, all green. New canvasTemplates.test.ts asserts the prompt bakes the exact filter, formulas, and date placeholders. Have not driven the live agent.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Created with PostHog Code

Ports the Cloud AI gateway usage page (PostHog/posthog#64511) to a canvas
as a declarative built-in, mirroring the web-analytics template. The board
reuses existing catalog components and the state.queries refresh machinery,
so the only host wiring is enrolling "ai-gateway" in DATA_TEMPLATES for the
date picker and toolbar refresh.

Generated-By: PostHog Code
Task-Id: 1e07b596-b3fb-4d08-a836-282ef28620f0
@github-actions

Copy link
Copy Markdown

React Doctor found no issues in the changed files. 🎉

Reviewed by React Doctor for commit 9d72727.

@greptile-apps

greptile-apps Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
packages/ui/src/features/canvas/components/WebsiteLayout.tsx:45
**Parallel registry that can drift from `BUILT_INS`**

`DATA_TEMPLATES` is a second, hand-maintained list that must stay in sync with `canvasTemplates.ts`'s `BUILT_INS`. The PR's own `AI_GATEWAY.md` notes this: "That's a parallel list to the core template registry — worth collapsing into a `dataTemplate` flag on the template record so the two can't drift." A future data-driven template will require the same two-file update in different packages, and there is nothing to catch it if one side is missed. Adding a `dataTemplate?: boolean` field to `BuiltInTemplate` and deriving this list from the registry would remove the duplication.

### Issue 2 of 3
packages/core/src/canvas/canvasTemplates.test.ts:44-50
The "connect snippets" test bundles four independent `toContain` assertions in one non-parameterised `it`. The formula test just above it already uses `it.each` for exactly this pattern, and the team preference is parameterised tests throughout. Applying the same shape here keeps the test suite consistent and gives a descriptive failure message per assertion.

```suggestion
  it.each([
    ["OpenAI base URL", "baseURL: '<gateway base URL>/v1'"],
    ["Anthropic SDK import", "@anthropic-ai/sdk"],
    ["provider state path", '"$state": "/provider"'],
    ["language state path", '"$state": "/language"'],
  ])("bakes the %s into the connect section", (_name, snippet) => {
    expect(aiGateway?.systemPrompt).toContain(snippet);
  });
```

### Issue 3 of 3
packages/core/src/canvas/canvasTemplates.ts:150-155
**Fragile string surgery on `GATEWAY_WHERE`**

The empty-state probe query is constructed by calling `.replace()` twice on `GATEWAY_WHERE`. If the constant is ever restructured (e.g. the placeholder renamed, or the `AND timestamp < {date_to}` clause moved), the replacement silently produces wrong SQL with no compile-time or test-time signal. No existing test asserts the assembled empty-state query text. A named constant — `const GATEWAY_EMPTY_STATE_WHERE = \`${GATEWAY_BASE_FILTER} AND timestamp >= now() - INTERVAL 30 DAY\`` — next to `GATEWAY_WHERE` would be self-documenting and immune to structural drift.

Reviews (1): Last reviewed commit: "feat(canvas): add AI gateway built-in te..." | Re-trigger Greptile

// Templates whose canvases carry the data toolbar (Filter + date range +
// refresh) — the ones with refreshable, time-scoped queries.
const DATA_TEMPLATES = ["dashboard", "web-analytics"];
const DATA_TEMPLATES = ["dashboard", "web-analytics", "ai-gateway"];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Parallel registry that can drift from BUILT_INS

DATA_TEMPLATES is a second, hand-maintained list that must stay in sync with canvasTemplates.ts's BUILT_INS. The PR's own AI_GATEWAY.md notes this: "That's a parallel list to the core template registry — worth collapsing into a dataTemplate flag on the template record so the two can't drift." A future data-driven template will require the same two-file update in different packages, and there is nothing to catch it if one side is missed. Adding a dataTemplate?: boolean field to BuiltInTemplate and deriving this list from the registry would remove the duplication.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ui/src/features/canvas/components/WebsiteLayout.tsx
Line: 45

Comment:
**Parallel registry that can drift from `BUILT_INS`**

`DATA_TEMPLATES` is a second, hand-maintained list that must stay in sync with `canvasTemplates.ts`'s `BUILT_INS`. The PR's own `AI_GATEWAY.md` notes this: "That's a parallel list to the core template registry — worth collapsing into a `dataTemplate` flag on the template record so the two can't drift." A future data-driven template will require the same two-file update in different packages, and there is nothing to catch it if one side is missed. Adding a `dataTemplate?: boolean` field to `BuiltInTemplate` and deriving this list from the registry would remove the duplication.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +44 to +50
it("includes the connect snippets and the declarative provider/language switch", () => {
const prompt = aiGateway?.systemPrompt ?? "";
expect(prompt).toContain("baseURL: '<gateway base URL>/v1'"); // OpenAI
expect(prompt).toContain("@anthropic-ai/sdk"); // Anthropic
expect(prompt).toContain('"$state": "/provider"');
expect(prompt).toContain('"$state": "/language"');
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The "connect snippets" test bundles four independent toContain assertions in one non-parameterised it. The formula test just above it already uses it.each for exactly this pattern, and the team preference is parameterised tests throughout. Applying the same shape here keeps the test suite consistent and gives a descriptive failure message per assertion.

Suggested change
it("includes the connect snippets and the declarative provider/language switch", () => {
const prompt = aiGateway?.systemPrompt ?? "";
expect(prompt).toContain("baseURL: '<gateway base URL>/v1'"); // OpenAI
expect(prompt).toContain("@anthropic-ai/sdk"); // Anthropic
expect(prompt).toContain('"$state": "/provider"');
expect(prompt).toContain('"$state": "/language"');
});
it.each([
["OpenAI base URL", "baseURL: '<gateway base URL>/v1'"],
["Anthropic SDK import", "@anthropic-ai/sdk"],
["provider state path", '"$state": "/provider"'],
["language state path", '"$state": "/language"'],
])("bakes the %s into the connect section", (_name, snippet) => {
expect(aiGateway?.systemPrompt).toContain(snippet);
});
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/core/src/canvas/canvasTemplates.test.ts
Line: 44-50

Comment:
The "connect snippets" test bundles four independent `toContain` assertions in one non-parameterised `it`. The formula test just above it already uses `it.each` for exactly this pattern, and the team preference is parameterised tests throughout. Applying the same shape here keeps the test suite consistent and gives a descriptive failure message per assertion.

```suggestion
  it.each([
    ["OpenAI base URL", "baseURL: '<gateway base URL>/v1'"],
    ["Anthropic SDK import", "@anthropic-ai/sdk"],
    ["provider state path", '"$state": "/provider"'],
    ["language state path", '"$state": "/language"'],
  ])("bakes the %s into the connect section", (_name, snippet) => {
    expect(aiGateway?.systemPrompt).toContain(snippet);
  });
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +150 to +155
"EMPTY STATE: before building, run `SELECT count() FROM events WHERE " +
GATEWAY_WHERE.replace("{date_from}", "now() - INTERVAL 30 DAY").replace(
" AND timestamp < {date_to}",
"",
) +
'` via the MCP tools. If it returns 0 (no gateway usage in the window), do NOT build the Usage or By model sections (a zeroed-out board reads as broken). Instead emit ONLY: the h1 title + muted subtitle, a `Hero` (tone accent) titled "No gateway usage yet" whose subtitle is "One endpoint for every major LLM, billed at cost — no markup on tokens. Point your app at the gateway and PostHog tracks its usage, cost, and spend for you. Any project secret key with the llm_gateway:read scope can call it.", and the full Connect your app section.',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Fragile string surgery on GATEWAY_WHERE

The empty-state probe query is constructed by calling .replace() twice on GATEWAY_WHERE. If the constant is ever restructured (e.g. the placeholder renamed, or the AND timestamp < {date_to} clause moved), the replacement silently produces wrong SQL with no compile-time or test-time signal. No existing test asserts the assembled empty-state query text. A named constant — const GATEWAY_EMPTY_STATE_WHERE = \${GATEWAY_BASE_FILTER} AND timestamp >= now() - INTERVAL 30 DAY`— next toGATEWAY_WHERE` would be self-documenting and immune to structural drift.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/core/src/canvas/canvasTemplates.ts
Line: 150-155

Comment:
**Fragile string surgery on `GATEWAY_WHERE`**

The empty-state probe query is constructed by calling `.replace()` twice on `GATEWAY_WHERE`. If the constant is ever restructured (e.g. the placeholder renamed, or the `AND timestamp < {date_to}` clause moved), the replacement silently produces wrong SQL with no compile-time or test-time signal. No existing test asserts the assembled empty-state query text. A named constant — `const GATEWAY_EMPTY_STATE_WHERE = \`${GATEWAY_BASE_FILTER} AND timestamp >= now() - INTERVAL 30 DAY\`` — next to `GATEWAY_WHERE` would be self-documenting and immune to structural drift.

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant