From 61d2ad5df42ab65879b844924d5b6b0a68a4ac87 Mon Sep 17 00:00:00 2001 From: Krishav Raj Singh Date: Tue, 29 Jul 2025 21:57:02 +0530 Subject: [PATCH] feat: ui for task list tool --- backend/agent/run.py | 1 + .../tool-views/task-list/TaskListToolView.tsx | 171 ++++++++++++++++++ .../thread/tool-views/task-list/_utils.ts | 66 +++++++ .../tool-views/wrapper/ToolViewRegistry.tsx | 6 + 4 files changed, 244 insertions(+) create mode 100644 frontend/src/components/thread/tool-views/task-list/TaskListToolView.tsx create mode 100644 frontend/src/components/thread/tool-views/task-list/_utils.ts diff --git a/backend/agent/run.py b/backend/agent/run.py index 4457e361..f01843e8 100644 --- a/backend/agent/run.py +++ b/backend/agent/run.py @@ -29,6 +29,7 @@ from services.langfuse import langfuse from langfuse.client import StatefulTraceClient from agent.gemini_prompt import get_gemini_system_prompt from agent.tools.mcp_tool_wrapper import MCPToolWrapper +from agent.tools.task_list_tool import TaskListTool from agentpress.tool import SchemaType load_dotenv() diff --git a/frontend/src/components/thread/tool-views/task-list/TaskListToolView.tsx b/frontend/src/components/thread/tool-views/task-list/TaskListToolView.tsx new file mode 100644 index 00000000..63268a86 --- /dev/null +++ b/frontend/src/components/thread/tool-views/task-list/TaskListToolView.tsx @@ -0,0 +1,171 @@ +import type React from "react" +import { Check, Circle, X, Clock, AlertTriangle, CircleCheck, CircleX } from "lucide-react" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { cn } from "@/lib/utils" +import { extractTaskListData, type Task, type TaskListData } from "./_utils" +import type { ToolViewProps } from "../types" +import { ScrollArea } from "@/components/ui/scroll-area" + +const TaskItem: React.FC<{ task: Task; index: number }> = ({ task, index }) => { + const isCompleted = task.status === "completed" + const isCancelled = task.status === "cancelled" + + return ( +
+ {/* Status Icon */} +
+ {isCompleted && } + {isCancelled && } + {!isCompleted && !isCancelled && } +
+ + {/* Content */} +
+

+ {task.content} +

+
+
+ ) +} + +const EmptyState: React.FC = () => ( +
+

No tasks yet

+

Tasks will appear here as they are created

+
+) + +export const TaskListToolView: React.FC = ({ + assistantContent, + toolContent, + isStreaming = false, +}) => { + const taskData = extractTaskListData(assistantContent, toolContent) + + // Show loading state while streaming and no data + if (isStreaming && !taskData) { + return ( + + +
+ + Task List +
+
+ +

Processing tasks...

+
+
+ ) + } + console.log('taskData', taskData) + // Show no data state if no task data + if (!taskData) { + return ( + + +
+ + Task List +
+
+ +

No task data available

+
+
+ ) + } + + // Show task data + const tasks = taskData.tasks || [] + const totalTasks = tasks.length + const completedTasks = tasks.filter((t) => t.status === "completed").length + const cancelledTasks = tasks.filter((t) => t.status === "cancelled").length + const completionPercentage = totalTasks > 0 ? ((completedTasks + cancelledTasks) / totalTasks) * 100 : 0 + + return ( + + +
+
+
+ +
+
+ + Task List + +
+
+ + + {completedTasks}/{totalTasks} completed + +
+
+ + + +
+
+ {tasks.length > 0 ? ( + tasks.map((task, index) => ( + + ) + ) + ) : ( + + )} + {/* Progress Bar */} + {tasks.length > 0 &&
+
0 && completionPercentage <= 25 && "bg-yellow-400", + completionPercentage > 25 && completionPercentage <= 50 && "bg-yellow-500", + completionPercentage > 50 && completionPercentage <= 75 && "bg-green-300", + completionPercentage > 75 && completionPercentage < 100 && "bg-green-400", + completionPercentage === 100 && "bg-green-600" + )} + style={{ width: `${completionPercentage}%` }} + /> +
} + +
+
+ + + +
+
+ {tasks.length > 0 && ( + + + {tasks.length} tasks + + )} +
+
+ + ) +} \ No newline at end of file diff --git a/frontend/src/components/thread/tool-views/task-list/_utils.ts b/frontend/src/components/thread/tool-views/task-list/_utils.ts new file mode 100644 index 00000000..948557e4 --- /dev/null +++ b/frontend/src/components/thread/tool-views/task-list/_utils.ts @@ -0,0 +1,66 @@ +export interface TaskListData { + tasks?: Task[] + updated_tasks?: Task[] + deleted_tasks?: Task[] + filter?: string + total?: number + [key: string]: any +} + +export interface Task { + id: string + content: string + status: "pending" | "completed" | "cancelled" + created_at?: string + updated_at?: string + completed_at?: string +} + +export function extractTaskListData( + assistantContent?: string, + toolContent?: string + ): TaskListData | null { + const parseContent = (content: any): any => { + if (typeof content === 'string') { + try { + return JSON.parse(content); + } catch (e) { + return content; + } + } + return content; + }; + + const extractFromNewFormat = (content: any): TaskListData | null => { + const parsedContent = parseContent(content); + + if (!parsedContent || typeof parsedContent !== 'object') { + return null; + } + + // Check for tool_execution format + if (parsedContent.tool_execution?.result?.output) { + const output = parsedContent.tool_execution.result.output; + const outputData = parseContent(output); + + if (outputData?.tasks && Array.isArray(outputData.tasks)) { + return outputData; + } + } + + // Check for direct tasks array + if (parsedContent.tasks && Array.isArray(parsedContent.tasks)) { + return parsedContent; + } + + // Check for nested content + if (parsedContent.content) { + return extractFromNewFormat(parsedContent.content); + } + + return null; + }; + + // Try tool content first, then assistant content + return extractFromNewFormat(toolContent) || extractFromNewFormat(assistantContent); + } \ No newline at end of file diff --git a/frontend/src/components/thread/tool-views/wrapper/ToolViewRegistry.tsx b/frontend/src/components/thread/tool-views/wrapper/ToolViewRegistry.tsx index 16122a1f..1b05064c 100644 --- a/frontend/src/components/thread/tool-views/wrapper/ToolViewRegistry.tsx +++ b/frontend/src/components/thread/tool-views/wrapper/ToolViewRegistry.tsx @@ -25,6 +25,7 @@ import { CheckProfileConnectionToolView } from '../check-profile-connection/chec import { ConfigureProfileForAgentToolView } from '../configure-profile-for-agent/configure-profile-for-agent'; import { GetCredentialProfilesToolView } from '../get-credential-profiles/get-credential-profiles'; import { GetCurrentAgentConfigToolView } from '../get-current-agent-config/get-current-agent-config'; +import { TaskListToolView } from '../task-list/TaskListToolView'; export type ToolViewComponent = React.ComponentType; @@ -76,6 +77,11 @@ const defaultRegistry: ToolViewRegistryType = { 'configure-profile-for-agent': ConfigureProfileForAgentToolView, 'get-credential-profiles': GetCredentialProfilesToolView, 'get-current-agent-config': GetCurrentAgentConfigToolView, + 'create-tasks': TaskListToolView, + 'view-tasks': TaskListToolView, + 'update-tasks': TaskListToolView, + 'delete-tasks': TaskListToolView, + 'clear-all-tasks': TaskListToolView, 'expose-port': ExposePortToolView,