Skip to content

Commit de21d7d

Browse files
fix: add agentic_generative_ui agent to ADK (#751)
* fix: add `agentic_generative_ui` agent to ADK * chore: generate files.json * feat: add `agentic_generative_ui` agent * chore: generate files.json * fix: correct ADK agentic_generative_ui agent * fix: add trailing slash
1 parent 4b0cc40 commit de21d7d

File tree

5 files changed

+170
-0
lines changed

5 files changed

+170
-0
lines changed

apps/dojo/src/agents.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [
7474
agents: async () => {
7575
return {
7676
agentic_chat: new ADKAgent({ url: `${envVars.adkMiddlewareUrl}/chat` }),
77+
agentic_generative_ui: new ADKAgent({
78+
url: `${envVars.adkMiddlewareUrl}/adk-agentic-generative-ui/`,
79+
}),
7780
tool_based_generative_ui: new ADKAgent({
7881
url: `${envVars.adkMiddlewareUrl}/adk-tool-based-generative-ui`,
7982
}),

apps/dojo/src/files.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,32 @@
233233
"type": "file"
234234
}
235235
],
236+
"adk-middleware::agentic_generative_ui": [
237+
{
238+
"name": "page.tsx",
239+
"content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgentStateRender } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface AgenticGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticGenerativeUI: React.FC<AgenticGenerativeUIProps> = ({ params }) => {\n const { integrationId } = React.use(params);\n return (\n <CopilotKit\n runtimeUrl={`/api/copilotkit/${integrationId}`}\n showDevConsole={false}\n // agent lock to the relevant agent\n agent=\"agentic_generative_ui\"\n >\n <Chat />\n </CopilotKit>\n );\n};\n\ninterface AgentState {\n steps: {\n description: string;\n status: \"pending\" | \"completed\";\n }[];\n}\n\nconst Chat = () => {\n const { theme } = useTheme();\n useCoAgentStateRender<AgentState>({\n name: \"agentic_generative_ui\",\n render: ({ state }) => {\n if (!state.steps || state.steps.length === 0) {\n return null;\n }\n\n const completedCount = state.steps.filter((step) => step.status === \"completed\").length;\n const progressPercentage = (completedCount / state.steps.length) * 100;\n\n return (\n <div className=\"flex\">\n <div\n data-testid=\"task-progress\"\n className={`relative rounded-xl w-[700px] p-6 shadow-lg backdrop-blur-sm ${\n theme === \"dark\"\n ? \"bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-white border border-slate-700/50 shadow-2xl\"\n : \"bg-gradient-to-br from-white via-gray-50 to-white text-gray-800 border border-gray-200/80\"\n }`}\n >\n {/* Header */}\n <div className=\"mb-5\">\n <div className=\"flex items-center justify-between mb-3\">\n <h3 className=\"text-xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent\">\n Task Progress\n </h3>\n <div className={`text-sm ${theme === \"dark\" ? \"text-slate-400\" : \"text-gray-500\"}`}>\n {completedCount}/{state.steps.length} Complete\n </div>\n </div>\n\n {/* Progress Bar */}\n <div\n className={`relative h-2 rounded-full overflow-hidden ${theme === \"dark\" ? \"bg-slate-700\" : \"bg-gray-200\"}`}\n >\n <div\n className=\"absolute top-0 left-0 h-full bg-gradient-to-r from-blue-500 to-purple-500 rounded-full transition-all duration-1000 ease-out\"\n style={{ width: `${progressPercentage}%` }}\n />\n <div\n className={`absolute top-0 left-0 h-full w-full bg-gradient-to-r from-transparent to-transparent animate-pulse ${\n theme === \"dark\" ? \"via-white/20\" : \"via-white/40\"\n }`}\n />\n </div>\n </div>\n\n {/* Steps */}\n <div className=\"space-y-2\">\n {state.steps.map((step, index) => {\n const isCompleted = step.status === \"completed\";\n const isCurrentPending =\n step.status === \"pending\" &&\n index === state.steps.findIndex((s) => s.status === \"pending\");\n const isFuturePending = step.status === \"pending\" && !isCurrentPending;\n\n return (\n <div\n key={index}\n className={`relative flex items-center p-2.5 rounded-lg transition-all duration-500 ${\n isCompleted\n ? theme === \"dark\"\n ? \"bg-gradient-to-r from-green-900/30 to-emerald-900/20 border border-green-500/30\"\n : \"bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200/60\"\n : isCurrentPending\n ? theme === \"dark\"\n ? \"bg-gradient-to-r from-blue-900/40 to-purple-900/30 border border-blue-500/50 shadow-lg shadow-blue-500/20\"\n : \"bg-gradient-to-r from-blue-50 to-purple-50 border border-blue-200/60 shadow-md shadow-blue-200/50\"\n : theme === \"dark\"\n ? \"bg-slate-800/50 border border-slate-600/30\"\n : \"bg-gray-50/50 border border-gray-200/60\"\n }`}\n >\n {/* Connector Line */}\n {index < state.steps.length - 1 && (\n <div\n className={`absolute left-5 top-full w-0.5 h-2 bg-gradient-to-b ${\n theme === \"dark\"\n ? \"from-slate-500 to-slate-600\"\n : \"from-gray-300 to-gray-400\"\n }`}\n />\n )}\n\n {/* Status Icon */}\n <div\n className={`flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center mr-2 ${\n isCompleted\n ? theme === \"dark\"\n ? \"bg-gradient-to-br from-green-500 to-emerald-600 shadow-lg shadow-green-500/30\"\n : \"bg-gradient-to-br from-green-500 to-emerald-600 shadow-md shadow-green-200\"\n : isCurrentPending\n ? theme === \"dark\"\n ? \"bg-gradient-to-br from-blue-500 to-purple-600 shadow-lg shadow-blue-500/30\"\n : \"bg-gradient-to-br from-blue-500 to-purple-600 shadow-md shadow-blue-200\"\n : theme === \"dark\"\n ? \"bg-slate-700 border border-slate-600\"\n : \"bg-gray-300 border border-gray-400\"\n }`}\n >\n {isCompleted ? (\n <CheckIcon />\n ) : isCurrentPending ? (\n <SpinnerIcon />\n ) : (\n <ClockIcon theme={theme} />\n )}\n </div>\n\n {/* Step Content */}\n <div className=\"flex-1 min-w-0\">\n <div\n data-testid=\"task-step-text\"\n className={`font-semibold transition-all duration-300 text-sm ${\n isCompleted\n ? theme === \"dark\"\n ? \"text-green-300\"\n : \"text-green-700\"\n : isCurrentPending\n ? theme === \"dark\"\n ? \"text-blue-300 text-base\"\n : \"text-blue-700 text-base\"\n : theme === \"dark\"\n ? \"text-slate-400\"\n : \"text-gray-500\"\n }`}\n >\n {step.description}\n </div>\n {isCurrentPending && (\n <div\n className={`text-sm mt-1 animate-pulse ${\n theme === \"dark\" ? \"text-blue-400\" : \"text-blue-600\"\n }`}\n >\n Processing...\n </div>\n )}\n </div>\n\n {/* Animated Background for Current Step */}\n {isCurrentPending && (\n <div\n className={`absolute inset-0 rounded-lg bg-gradient-to-r animate-pulse ${\n theme === \"dark\"\n ? \"from-blue-500/10 to-purple-500/10\"\n : \"from-blue-100/50 to-purple-100/50\"\n }`}\n />\n )}\n </div>\n );\n })}\n </div>\n\n {/* Decorative Elements */}\n <div\n className={`absolute top-3 right-3 w-16 h-16 rounded-full blur-xl ${\n theme === \"dark\"\n ? \"bg-gradient-to-br from-blue-500/10 to-purple-500/10\"\n : \"bg-gradient-to-br from-blue-200/30 to-purple-200/30\"\n }`}\n />\n <div\n className={`absolute bottom-3 left-3 w-12 h-12 rounded-full blur-xl ${\n theme === \"dark\"\n ? \"bg-gradient-to-br from-green-500/10 to-emerald-500/10\"\n : \"bg-gradient-to-br from-green-200/30 to-emerald-200/30\"\n }`}\n />\n </div>\n </div>\n );\n },\n });\n\n return (\n <div className=\"flex justify-center items-center h-full w-full\">\n <div className=\"h-full w-full md:w-8/10 md:h-8/10 rounded-lg\">\n <CopilotChat\n className=\"h-full rounded-2xl max-w-6xl mx-auto\"\n labels={{\n initial:\n \"Hi, I'm an agent! I can help you with anything you need and will show you progress as I work. What can I do for you?\",\n }}\n suggestions={[\n {\n title: \"Simple plan\",\n message: \"Please build a plan to go to mars in 5 steps.\",\n },\n {\n title: \"Complex plan\",\n message: \"Please build a plan to go to make pizza in 10 steps.\",\n },\n ]}\n />\n </div>\n </div>\n );\n};\n\n// Enhanced Icons\nfunction CheckIcon() {\n return (\n <svg className=\"w-4 h-4 text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={3} d=\"M5 13l4 4L19 7\" />\n </svg>\n );\n}\n\nfunction SpinnerIcon() {\n return (\n <svg\n className=\"w-4 h-4 animate-spin text-white\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\" />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n );\n}\n\nfunction ClockIcon({ theme }: { theme?: string }) {\n return (\n <svg\n className={`w-3 h-3 ${theme === \"dark\" ? \"text-slate-400\" : \"text-gray-600\"}`}\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" strokeWidth=\"2\" />\n <polyline points=\"12,6 12,12 16,14\" strokeWidth=\"2\" />\n </svg>\n );\n}\n\nexport default AgenticGenerativeUI;\n",
240+
"language": "typescript",
241+
"type": "file"
242+
},
243+
{
244+
"name": "style.css",
245+
"content": ".copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n}\n\n.copilotKitChat {\n background-color: #fff !important;\n}\n",
246+
"language": "css",
247+
"type": "file"
248+
},
249+
{
250+
"name": "README.mdx",
251+
"content": "# 🚀 Agentic Generative UI Task Executor\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **agentic generative UI** capabilities:\n\n1. **Real-time Status Updates**: The Copilot provides live feedback as it works\n through complex tasks\n2. **Long-running Task Execution**: See how agents can handle extended processes\n with continuous feedback\n3. **Dynamic UI Generation**: The interface updates in real-time to reflect the\n agent's progress\n\n## How to Interact\n\nSimply ask your Copilot to perform any moderately complex task:\n\n- \"Make me a sandwich\"\n- \"Plan a vacation to Japan\"\n- \"Create a weekly workout routine\"\n\nThe Copilot will break down the task into steps and begin \"executing\" them,\nproviding real-time status updates as it progresses.\n\n## ✨ Agentic Generative UI in Action\n\n**What's happening technically:**\n\n- The agent analyzes your request and creates a detailed execution plan\n- Each step is processed sequentially with realistic timing\n- Status updates are streamed to the frontend using CopilotKit's streaming\n capabilities\n- The UI dynamically renders these updates without page refreshes\n- The entire flow is managed by the agent, requiring no manual intervention\n\n**What you'll see in this demo:**\n\n- The Copilot breaks your task into logical steps\n- A status indicator shows the current progress\n- Each step is highlighted as it's being executed\n- Detailed status messages explain what's happening at each moment\n- Upon completion, you receive a summary of the task execution\n\nThis pattern of providing real-time progress for long-running tasks is perfect\nfor scenarios where users benefit from transparency into complex processes -\nfrom data analysis to content creation, system configurations, or multi-stage\nworkflows!\n",
252+
"language": "markdown",
253+
"type": "file"
254+
},
255+
{
256+
"name": "agentic_generative_ui.py",
257+
"content": "\"\"\"Agentic Generative UI feature.\"\"\"\n\nfrom __future__ import annotations\n\nfrom textwrap import dedent\nfrom typing import Any, Literal\n\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel, Field\n\nfrom ag_ui.core import EventType, StateDeltaEvent, StateSnapshotEvent\nfrom ag_ui_adk import ADKAgent, add_adk_fastapi_endpoint\nfrom google.adk.agents import LlmAgent\n\nStepStatus = Literal['pending', 'completed']\n\n\nclass Step(BaseModel):\n \"\"\"Represents a step in a plan.\"\"\"\n\n description: str = Field(description='The description of the step')\n status: StepStatus = Field(\n default='pending',\n description='The status of the step (e.g., pending, completed)',\n )\n\n\nclass Plan(BaseModel):\n \"\"\"Represents a plan with multiple steps.\"\"\"\n\n steps: list[Step] = Field(default_factory=list, description='The steps in the plan')\n\n\nclass JSONPatchOp(BaseModel):\n \"\"\"A class representing a JSON Patch operation (RFC 6902).\"\"\"\n\n op: Literal['add', 'remove', 'replace', 'move', 'copy', 'test'] = Field(\n description='The operation to perform: add, remove, replace, move, copy, or test',\n )\n path: str = Field(description='JSON Pointer (RFC 6901) to the target location')\n value: Any = Field(\n default=None,\n description='The value to apply (for add, replace operations)',\n )\n from_: str | None = Field(\n default=None,\n alias='from',\n description='Source path (for move, copy operations)',\n )\n\n\nasync def create_plan(steps: list[str]) -> StateSnapshotEvent:\n \"\"\"Create a plan with multiple steps.\n\n Args:\n steps (list[str]): List of step descriptions to create the plan.\n\n Returns:\n StateSnapshotEvent: Event containing the initial state of the steps.\n \"\"\"\n plan: Plan = Plan(\n steps=[Step(description=step) for step in steps],\n )\n return StateSnapshotEvent(\n type=EventType.STATE_SNAPSHOT,\n snapshot=plan.model_dump(),\n )\n\n\nasync def update_plan_step(\n index: int, description: str | None = None, status: StepStatus | None = None\n) -> StateDeltaEvent:\n \"\"\"Update the plan with new steps or changes.\n\n Args:\n index (int): The index of the step to update.\n description (str | None): The new description for the step.\n status (StepStatus | None): The new status for the step.\n\n Returns:\n StateDeltaEvent: Event containing the changes made to the plan.\n \"\"\"\n changes: list[JSONPatchOp] = []\n if description is not None:\n changes.append(\n JSONPatchOp(\n op='replace', path=f'/steps/{index}/description', value=description\n )\n )\n if status is not None:\n changes.append(\n JSONPatchOp(op='replace', path=f'/steps/{index}/status', value=status)\n )\n return StateDeltaEvent(\n type=EventType.STATE_DELTA,\n delta=changes,\n )\n\n\n# Create the ADK agent\nagent = LlmAgent(\n name=\"planner\",\n model=\"gemini-2.0-flash\",\n instruction=dedent(\n \"\"\"\n When planning use tools only, without any other messages.\n IMPORTANT:\n - Use the `create_plan` tool to set the initial state of the steps\n - Use the `update_plan_step` tool to update the status of each step\n - Do NOT repeat the plan or summarise it in a message\n - Do NOT confirm the creation or updates in a message\n - Do NOT ask the user for additional information or next steps\n - Do NOT leave a plan hanging, always complete the plan via `update_plan_step` if one is ongoing.\n\n Only one plan can be active at a time, so do not call the `create_plan` tool\n again until all the steps in current plan are completed.\n \"\"\"\n ),\n tools=[create_plan, update_plan_step],\n)\n\n# Create ADK middleware agent instance\nadk_agent = ADKAgent(\n adk_agent=agent,\n app_name=\"demo_app\",\n user_id=\"demo_user\",\n session_timeout_seconds=3600,\n use_in_memory_services=True,\n)\n\n# Create FastAPI app\napp = FastAPI(title=\"ADK Middleware Agentic Generative UI\")\n\n# Add the ADK endpoint\nadd_adk_fastapi_endpoint(app, adk_agent, path=\"/\")\n",
258+
"language": "python",
259+
"type": "file"
260+
}
261+
],
236262
"adk-middleware::tool_based_generative_ui": [
237263
{
238264
"name": "page.tsx",

0 commit comments

Comments
 (0)