Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/dojo/src/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [
agents: async () => {
return {
agentic_chat: new ADKAgent({ url: `${envVars.adkMiddlewareUrl}/chat` }),
agentic_generative_ui: new ADKAgent({
url: `${envVars.adkMiddlewareUrl}/adk-agentic-generative-ui/`,
}),
tool_based_generative_ui: new ADKAgent({
url: `${envVars.adkMiddlewareUrl}/adk-tool-based-generative-ui`,
}),
Expand Down
26 changes: 26 additions & 0 deletions apps/dojo/src/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,32 @@
"type": "file"
}
],
"adk-middleware::agentic_generative_ui": [
{
"name": "page.tsx",
"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",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"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",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"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",
"language": "markdown",
"type": "file"
},
{
"name": "agentic_generative_ui.py",
"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",
"language": "python",
"type": "file"
}
],
"adk-middleware::tool_based_generative_ui": [
{
"name": "page.tsx",
Expand Down
Loading
Loading