Skip to content

Commit cd5eeef

Browse files
committed
feat: add planning step before visualization generation (#62)
Add a plan_visualization tool that the agent must call before any visualization tool (widgetRenderer, pieChart, barChart). This gives users transparency into the agent's approach and improves output quality by forcing structured thinking before code generation.
1 parent df96336 commit cd5eeef

File tree

4 files changed

+114
-1
lines changed

4 files changed

+114
-1
lines changed

apps/agent/main.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@
1818
from src.query import query_data
1919
from src.todos import AgentState, todo_tools
2020
from src.form import generate_form
21+
from src.plan import plan_visualization
2122
from src.templates import template_tools
2223

2324
load_dotenv()
2425

2526
agent = create_deep_agent(
2627
model=ChatOpenAI(model=os.environ.get("LLM_MODEL", "gpt-5.4-2026-03-05")),
27-
tools=[query_data, *todo_tools, generate_form, *template_tools],
28+
tools=[query_data, plan_visualization, *todo_tools, generate_form, *template_tools],
2829
middleware=[CopilotKitMiddleware()],
2930
context_schema=AgentState,
3031
skills=[str(Path(__file__).parent / "skills")],
@@ -53,6 +54,23 @@
5354
- Pre-styled form elements (buttons, inputs, sliders look native automatically)
5455
- Pre-built SVG CSS classes for color ramps (.c-purple, .c-teal, .c-blue, etc.)
5556
57+
## Visualization Workflow (MANDATORY)
58+
59+
When producing ANY visual response (widgetRenderer, pieChart, barChart), you MUST
60+
follow this exact sequence:
61+
62+
1. **Acknowledge** — Reply with 1-2 sentences of plain text acknowledging the
63+
request and setting context for what the visualization will show.
64+
2. **Plan** — Call `plan_visualization` with your approach, technology choice,
65+
and 2-4 key elements. Keep it concise.
66+
3. **Build** — Call the appropriate visualization tool (widgetRenderer, pieChart,
67+
or barChart).
68+
4. **Narrate** — After the visualization, add 2-3 sentences walking through
69+
what was built and offering to go deeper.
70+
71+
NEVER skip the plan_visualization step. NEVER call widgetRenderer, pieChart, or
72+
barChart without calling plan_visualization first.
73+
5674
## UI Templates
5775
5876
Users can save generated UIs as reusable templates and apply them later.

apps/agent/src/plan.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Planning tool for visualization generation."""
2+
3+
from langchain.tools import tool
4+
5+
6+
@tool
7+
def plan_visualization(
8+
approach: str, technology: str, key_elements: list[str]
9+
) -> str:
10+
"""Plan a visualization before building it. MUST be called before
11+
widgetRenderer, pieChart, or barChart. Outlines the approach, technology
12+
choice, and key elements.
13+
14+
Args:
15+
approach: One sentence describing the visualization strategy.
16+
technology: The primary technology (e.g. "inline SVG", "Chart.js",
17+
"HTML + Canvas", "Three.js", "Mermaid", "D3.js").
18+
key_elements: 2-4 concise bullet points describing what will be built.
19+
"""
20+
elements = "\n".join(f" - {e}" for e in key_elements)
21+
return f"Plan: {approach}\nTech: {technology}\n{elements}"
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"use client";
2+
3+
import { useEffect, useRef } from "react";
4+
5+
interface PlanCardProps {
6+
status: "executing" | "inProgress" | "complete";
7+
approach?: string;
8+
technology?: string;
9+
key_elements?: string[];
10+
}
11+
12+
export function PlanCard({ status, approach, technology, key_elements }: PlanCardProps) {
13+
const detailsRef = useRef<HTMLDetailsElement>(null);
14+
const isRunning = status === "executing" || status === "inProgress";
15+
16+
useEffect(() => {
17+
if (!detailsRef.current) return;
18+
detailsRef.current.open = isRunning;
19+
}, [isRunning]);
20+
21+
const spinner = (
22+
<span className="inline-block h-3 w-3 rounded-full border-2 border-gray-400 border-t-transparent animate-spin" />
23+
);
24+
const checkmark = <span className="text-green-500 text-xs"></span>;
25+
26+
return (
27+
<div className="my-2 text-sm">
28+
<details ref={detailsRef} open>
29+
<summary className="flex items-center gap-2 text-gray-600 dark:text-gray-400 cursor-pointer list-none">
30+
{isRunning ? spinner : checkmark}
31+
<span className="font-medium">
32+
{isRunning ? "Planning visualization…" : `Plan: ${technology || "visualization"}`}
33+
</span>
34+
<span className="text-[10px]"></span>
35+
</summary>
36+
{approach && (
37+
<div className="pl-5 mt-1.5 space-y-1.5 text-xs text-gray-500 dark:text-zinc-400">
38+
{technology && (
39+
<span className="inline-block px-1.5 py-0.5 rounded bg-gray-100 dark:bg-zinc-700 text-gray-600 dark:text-zinc-300 font-medium text-[11px]">
40+
{technology}
41+
</span>
42+
)}
43+
<p className="text-gray-600 dark:text-gray-400">{approach}</p>
44+
{key_elements && key_elements.length > 0 && (
45+
<ul className="list-disc pl-4 space-y-0.5">
46+
{key_elements.map((el, i) => (
47+
<li key={i}>{el}</li>
48+
))}
49+
</ul>
50+
)}
51+
</div>
52+
)}
53+
</details>
54+
</div>
55+
);
56+
}

apps/app/src/hooks/use-generative-ui-examples.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
useFrontendTool,
88
useHumanInTheLoop,
99
useDefaultRenderTool,
10+
useRenderTool,
1011
} from "@copilotkit/react-core/v2";
1112

1213
// Generative UI imports
@@ -15,6 +16,7 @@ import { BarChart, BarChartProps } from "@/components/generative-ui/charts/bar-c
1516
import { WidgetRenderer, WidgetRendererProps } from "@/components/generative-ui/widget-renderer";
1617
import { MeetingTimePicker } from "@/components/generative-ui/meeting-time-picker";
1718
import { ToolReasoning } from "@/components/tool-rendering";
19+
import { PlanCard } from "@/components/generative-ui/plan-card";
1820

1921
export const useGenerativeUIExamples = () => {
2022
const { theme, setTheme } = useTheme();
@@ -61,6 +63,22 @@ export const useGenerativeUIExamples = () => {
6163
render: WidgetRenderer,
6264
});
6365

66+
// --------------------------
67+
// 🪁 Plan Visualization: Custom rendering for the planning step
68+
// --------------------------
69+
const PlanVisualizationParams = z.object({
70+
approach: z.string(),
71+
technology: z.string(),
72+
key_elements: z.array(z.string()),
73+
});
74+
useRenderTool({
75+
name: "plan_visualization",
76+
parameters: PlanVisualizationParams,
77+
render: ({ status, parameters }) => (
78+
<PlanCard status={status} {...parameters} />
79+
),
80+
});
81+
6482
// --------------------------
6583
// 🪁 Default Tool Rendering: https://docs.copilotkit.ai/langgraph/generative-ui/backend-tools
6684
// --------------------------

0 commit comments

Comments
 (0)