Skip to content

Commit 4ba7974

Browse files
committed
Merge branch 'feat/wave4b-byoc-json-render-demo' into consolidation
2 parents 4ef8df8 + 3622251 commit 4ba7974

23 files changed

Lines changed: 1378 additions & 3 deletions

File tree

scripts/hooks/check-binaries.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ while IFS= read -r f; do
3939
case "$f" in
4040
pnpm-lock.yaml|*/package-lock.json) continue ;;
4141
showcase/shell/src/data/demo-content.json) continue ;;
42-
showcase/shell-dojo/src/data/demo-content.json) continue ;;
4342
showcase/shell-docs/src/data/demo-content.json) continue ;;
43+
showcase/shell-dojo/src/data/demo-content.json) continue ;;
4444
showcase/shell/src/data/search-index.json) continue ;;
4545
showcase/shell/src/data/starter-content.json) continue ;;
4646
# shell-docs and shell-dojo mirror the same generated demo-content

showcase/packages/langgraph-python/docs-links.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@
148148
"byoc-hashbrown": {
149149
"og_docs_url": "https://docs.copilotkit.ai/langgraph/byoc-hashbrown",
150150
"shell_docs_path": "/byoc-hashbrown"
151+
},
152+
"byoc-json-render": {
153+
"og_docs_url": null,
154+
"shell_docs_path": null
151155
}
152156
}
153157
}

showcase/packages/langgraph-python/langgraph.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"beautiful_chat": "./src/agents/beautiful_chat.py:graph",
2727
"multimodal": "./src/agents/multimodal_agent.py:graph",
2828
"agent_config_agent": "./src/agents/agent_config_agent.py:graph",
29-
"byoc_hashbrown": "./src/agents/byoc_hashbrown_agent.py:graph"
29+
"byoc_hashbrown": "./src/agents/byoc_hashbrown_agent.py:graph",
30+
"byoc_json_render": "./src/agents/byoc_json_render_agent.py:graph"
3031
}
3132
}

showcase/packages/langgraph-python/manifest.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ features:
6666
- multimodal
6767
- auth
6868
- byoc-hashbrown
69+
- byoc-json-render
6970
- open-gen-ui
7071
- open-gen-ui-advanced
7172
- voice
@@ -413,6 +414,25 @@ demos:
413414
- src/app/demos/tool-rendering-reasoning-chain/weather-card.tsx
414415
- src/app/demos/tool-rendering-reasoning-chain/flight-list-card.tsx
415416
- src/app/api/copilotkit/route.ts
417+
- id: byoc-json-render
418+
name: BYOC json-render
419+
description: Streaming hierarchical JSON UI spec rendered via @json-render/react, with a Zod-validated catalog (MetricCard + PieChart + BarChart)
420+
tags:
421+
- generative-ui
422+
route: /demos/byoc-json-render
423+
animated_preview_url:
424+
highlight:
425+
- src/agents/byoc_json_render_agent.py
426+
- src/app/demos/byoc-json-render/page.tsx
427+
- src/app/demos/byoc-json-render/json-render-renderer.tsx
428+
- src/app/demos/byoc-json-render/registry.tsx
429+
- src/app/demos/byoc-json-render/catalog.ts
430+
- src/app/demos/byoc-json-render/metric-card.tsx
431+
- src/app/demos/byoc-json-render/charts/bar-chart.tsx
432+
- src/app/demos/byoc-json-render/charts/pie-chart.tsx
433+
- src/app/demos/byoc-json-render/types.ts
434+
- src/app/demos/byoc-json-render/suggestions.ts
435+
- src/app/api/copilotkit-byoc-json-render/route.ts
416436
- id: beautiful-chat
417437
name: Beautiful Chat
418438
description: Canonical polished starter chat — brand fonts, theme tokens, suggestion pills

showcase/packages/langgraph-python/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
"@copilotkit/voice": "next",
1717
"@hashbrownai/core": "0.5.0-beta.4",
1818
"@hashbrownai/react": "0.5.0-beta.4",
19+
"@json-render/core": "0.18.0",
20+
"@json-render/react": "0.18.0",
1921
"@radix-ui/react-checkbox": "^1.3.3",
2022
"@radix-ui/react-separator": "^1.1.8",
2123
"next": "^15.0.0",

showcase/packages/langgraph-python/pnpm-lock.yaml

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# QA: BYOC json-render — LangGraph (Python)
2+
3+
## Prerequisites
4+
5+
- Demo deployed at `/demos/byoc-json-render`.
6+
- Railway backend healthy (`showcase-langgraph-python-production.up.railway.app`).
7+
- `OPENAI_API_KEY` and `LANGGRAPH_DEPLOYMENT_URL` configured on the Next.js app.
8+
- `@json-render/core` + `@json-render/react` present in `package.json` (pinned to `0.18.0`).
9+
- `byoc_json_render` graph registered in `langgraph.json`.
10+
11+
## Test Steps
12+
13+
### 1. Page load
14+
15+
- [ ] Navigate to `/demos/byoc-json-render`.
16+
- [ ] Chat composer is visible.
17+
- [ ] Three suggestion pills appear with titles: "Sales dashboard", "Revenue by category", "Expense trend".
18+
- [ ] No console errors.
19+
20+
### 2. Sales dashboard suggestion
21+
22+
- [ ] Click the "Sales dashboard" suggestion.
23+
- [ ] Within 60 seconds, a `data-testid="json-render-root"` wrapper appears in the assistant bubble.
24+
- [ ] A `data-testid="metric-card"` renders inside the wrapper.
25+
- [ ] A chart (`data-testid="bar-chart"` or `data-testid="pie-chart"`) renders inside the wrapper.
26+
- [ ] No raw JSON text is shown once rendering finishes — the streaming JSON is replaced by components.
27+
28+
### 3. Revenue by category
29+
30+
- [ ] Click the "Revenue by category" suggestion.
31+
- [ ] Within 60 seconds, a `data-testid="pie-chart"` renders with multiple category slices + legend.
32+
33+
### 4. Expense trend
34+
35+
- [ ] Click the "Expense trend" suggestion.
36+
- [ ] Within 60 seconds, a `data-testid="bar-chart"` renders with month labels.
37+
38+
### 5. Free-form prompt
39+
40+
- [ ] Type "Show me a metric for quarterly revenue" and send.
41+
- [ ] Verify at least one `metric-card` renders; no console errors.
42+
43+
### 6. Multi-turn
44+
45+
- [ ] After a previous render is visible, send a follow-up prompt ("Now break that down by region").
46+
- [ ] A new assistant message appears with a new json-render rendering — prior renders stay in the transcript.
47+
48+
### 7. Malformed output handling
49+
50+
- [ ] If the agent ever replies with non-JSON text (force it by asking "tell me a joke"), the chat falls back to rendering that raw text via the default assistant bubble. No crash, no stuck spinner.
51+
52+
## Expected Results
53+
54+
- Suggestion renders land within 60 seconds. Budget is slightly higher than Wave 4a because a JSON `{ root, elements }` spec is more verbose than hashbrown's token stream.
55+
- No uncaught errors in the console.
56+
- Streaming falls back to plain text until the JSON parses, then swaps to rendered components.
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"""LangGraph agent backing the BYOC json-render demo.
2+
3+
Emits a single JSON object shaped like `@json-render/react`'s flat spec
4+
format (`{ root, elements }`) so the frontend can feed it directly into
5+
`<Renderer />` against a Zod-validated catalog of three components —
6+
MetricCard, BarChart, PieChart.
7+
8+
The scenario mirrors Wave 4a (hashbrown) so the two BYOC rows on the
9+
dashboard are directly comparable. The only difference is the rendering
10+
technology; the catalog shape and suggestion prompts are identical.
11+
"""
12+
13+
from langchain.agents import create_agent
14+
from langchain_openai import ChatOpenAI
15+
from copilotkit import CopilotKitMiddleware
16+
17+
18+
SYSTEM_PROMPT = """
19+
You are a sales-dashboard UI generator for a BYOC json-render demo.
20+
21+
When the user asks for a UI, respond with **exactly one JSON object** and
22+
nothing else — no prose, no markdown fences, no leading explanation. The
23+
object must match this schema (the "flat element map" format consumed by
24+
`@json-render/react`):
25+
26+
{
27+
"root": "<id of the root element>",
28+
"elements": {
29+
"<id>": {
30+
"type": "<component name>",
31+
"props": { ... component-specific props ... },
32+
"children": [ "<id>", ... ]
33+
},
34+
...
35+
}
36+
}
37+
38+
Available components (use each name verbatim as "type"):
39+
40+
- MetricCard
41+
props: { "label": string, "value": string, "trend": string | null }
42+
Example trend strings: "+12% vs last quarter", "-3% vs last month", null.
43+
44+
- BarChart
45+
props: {
46+
"title": string,
47+
"description": string | null,
48+
"data": [ { "label": string, "value": number }, ... ]
49+
}
50+
51+
- PieChart
52+
props: {
53+
"title": string,
54+
"description": string | null,
55+
"data": [ { "label": string, "value": number }, ... ]
56+
}
57+
58+
Rules:
59+
60+
1. Output **only** valid JSON. No markdown code fences. No text outside
61+
the object.
62+
2. Every id referenced in `root` or any `children` array must be a key
63+
in `elements`.
64+
3. For a multi-component dashboard, use a root MetricCard and list the
65+
charts in its `children` array, OR pick any element as root and list
66+
the others as its children. Do not emit orphan elements.
67+
4. Use realistic sales-domain values (revenue, pipeline, conversion,
68+
categories, months) — the demo is a sales dashboard.
69+
5. `children` is optional but when present must be an array of strings.
70+
6. Never invent component types outside the three listed above.
71+
72+
### Worked example — "Show me the sales dashboard with metrics and a revenue chart"
73+
74+
{
75+
"root": "revenue-metric",
76+
"elements": {
77+
"revenue-metric": {
78+
"type": "MetricCard",
79+
"props": {
80+
"label": "Revenue (Q3)",
81+
"value": "$1.24M",
82+
"trend": "+18% vs Q2"
83+
},
84+
"children": ["revenue-bar"]
85+
},
86+
"revenue-bar": {
87+
"type": "BarChart",
88+
"props": {
89+
"title": "Monthly revenue",
90+
"description": "Revenue by month across Q3",
91+
"data": [
92+
{ "label": "Jul", "value": 380000 },
93+
{ "label": "Aug", "value": 410000 },
94+
{ "label": "Sep", "value": 450000 }
95+
]
96+
}
97+
}
98+
}
99+
}
100+
101+
### Worked example — "Break down revenue by category as a pie chart"
102+
103+
{
104+
"root": "category-pie",
105+
"elements": {
106+
"category-pie": {
107+
"type": "PieChart",
108+
"props": {
109+
"title": "Revenue by category",
110+
"description": "Share of total revenue by product category",
111+
"data": [
112+
{ "label": "Enterprise", "value": 540000 },
113+
{ "label": "SMB", "value": 310000 },
114+
{ "label": "Self-serve", "value": 220000 },
115+
{ "label": "Partner", "value": 170000 }
116+
]
117+
}
118+
}
119+
}
120+
}
121+
122+
### Worked example — "Show me monthly expenses as a bar chart"
123+
124+
{
125+
"root": "expense-bar",
126+
"elements": {
127+
"expense-bar": {
128+
"type": "BarChart",
129+
"props": {
130+
"title": "Monthly expenses",
131+
"description": "Operating expenses by month",
132+
"data": [
133+
{ "label": "Jul", "value": 210000 },
134+
{ "label": "Aug", "value": 225000 },
135+
{ "label": "Sep", "value": 240000 }
136+
]
137+
}
138+
}
139+
}
140+
}
141+
142+
Respond with the JSON object only.
143+
"""
144+
145+
146+
graph = create_agent(
147+
model=ChatOpenAI(model="gpt-4o-mini", temperature=0.2),
148+
tools=[],
149+
middleware=[CopilotKitMiddleware()],
150+
system_prompt=SYSTEM_PROMPT.strip(),
151+
)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Dedicated runtime for the BYOC json-render demo.
3+
*
4+
* Splitting into its own endpoint (mirroring beautiful-chat +
5+
* declarative-gen-ui) keeps the `byoc_json_render` agent isolated from the
6+
* default multi-agent `/api/copilotkit` runtime. The frontend's demo page
7+
* (src/app/demos/byoc-json-render/page.tsx) points `<CopilotKit
8+
* runtimeUrl>` here.
9+
*/
10+
11+
import { NextRequest, NextResponse } from "next/server";
12+
import {
13+
CopilotRuntime,
14+
ExperimentalEmptyAdapter,
15+
copilotRuntimeNextJSAppRouterEndpoint,
16+
} from "@copilotkit/runtime";
17+
import { LangGraphAgent } from "@copilotkit/runtime/langgraph";
18+
19+
const LANGGRAPH_URL =
20+
process.env.LANGGRAPH_DEPLOYMENT_URL || "http://localhost:8123";
21+
22+
const byocJsonRenderAgent = new LangGraphAgent({
23+
deploymentUrl: LANGGRAPH_URL,
24+
graphId: "byoc_json_render",
25+
langsmithApiKey: process.env.LANGSMITH_API_KEY || "",
26+
});
27+
28+
const runtime = new CopilotRuntime({
29+
// @ts-ignore -- same-shape mismatch as the other dedicated routes in this
30+
// package; the LangGraphAgent satisfies the runtime's agent interface at
31+
// runtime but the generics don't line up across the v1/v2 boundary.
32+
agents: { byoc_json_render: byocJsonRenderAgent },
33+
});
34+
35+
export const POST = async (req: NextRequest) => {
36+
try {
37+
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
38+
endpoint: "/api/copilotkit-byoc-json-render",
39+
serviceAdapter: new ExperimentalEmptyAdapter(),
40+
runtime,
41+
});
42+
return await handleRequest(req);
43+
} catch (error: unknown) {
44+
const e = error as { message?: string; stack?: string };
45+
return NextResponse.json(
46+
{ error: e.message, stack: e.stack },
47+
{ status: 500 },
48+
);
49+
}
50+
};

0 commit comments

Comments
 (0)