'use client'; import React, { useState, useMemo } from 'react'; import { AlertTriangle, ExternalLink, X, Square, Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent } from '@/components/ui/card'; import { Separator } from '@/components/ui/separator'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import Link from 'next/link'; import { useStopAgentMutation } from '@/hooks/react-query/threads/use-agent-run'; import { AgentRun, getAgentRuns } from '@/lib/api'; import { toast } from 'sonner'; import { useQueries, useQueryClient } from '@tanstack/react-query'; import { getThread, getProject } from '@/hooks/react-query/threads/utils'; import { threadKeys } from '@/hooks/react-query/threads/keys'; interface RunningThreadInfo { threadId: string; name: string; projectId: string | null; projectName: string; agentRun: AgentRun | null; isLoading: boolean; error?: boolean; } interface RunningThreadItemProps { threadInfo: RunningThreadInfo; onThreadStopped: () => void; } const RunningThreadItem: React.FC = ({ threadInfo, onThreadStopped, }) => { const stopAgentMutation = useStopAgentMutation(); const queryClient = useQueryClient(); const handleStop = async () => { if (!threadInfo.agentRun?.id) return; try { await stopAgentMutation.mutateAsync(threadInfo.agentRun.id); // Invalidate relevant queries to refetch updated data await Promise.all([ // Refetch agent runs for this thread to update the running status queryClient.invalidateQueries({ queryKey: threadKeys.agentRuns(threadInfo.threadId) }), // Refetch thread details in case the status affects the thread queryClient.invalidateQueries({ queryKey: threadKeys.details(threadInfo.threadId) }) ]); toast.success('Agent stopped successfully'); onThreadStopped(); } catch (error) { console.error('Failed to stop agent:', error); toast.error('Failed to stop agent'); } }; const getThreadDisplayName = () => { if (threadInfo.name && threadInfo.name.trim()) { return threadInfo.name; } return `Thread ${threadInfo.threadId.slice(0, 8)}...`; }; const getProjectDisplayName = () => { if (threadInfo.projectName && threadInfo.projectName.trim()) { return threadInfo.projectName; } return threadInfo.projectId ? `Project ${threadInfo.projectId.slice(0, 8)}...` : 'No Project'; }; return (
{getProjectDisplayName()}
{threadInfo.threadId}
{threadInfo.agentRun && ( Stop this agent )} {threadInfo.projectId && ( Open thread )}
); }; interface AgentRunLimitDialogProps { open: boolean; onOpenChange: (open: boolean) => void; runningCount: number; runningThreadIds: string[]; projectId?: string; } export const AgentRunLimitDialog: React.FC = ({ open, onOpenChange, runningCount, runningThreadIds, projectId, }) => { // Use the exact same query keys as existing hooks for perfect cache consistency with sidebar // This means data fetched by the sidebar is immediately available here without additional requests const threadQueries = useQueries({ queries: runningThreadIds.map(threadId => ({ queryKey: threadKeys.details(threadId), queryFn: () => getThread(threadId), enabled: open && !!threadId, retry: 1, })) }); const agentRunQueries = useQueries({ queries: runningThreadIds.map(threadId => ({ queryKey: threadKeys.agentRuns(threadId), // This matches useAgentRunsQuery exactly queryFn: () => getAgentRuns(threadId), enabled: open && !!threadId, retry: 1, })) }); // Use the same query keys as useProjectQuery for cache consistency const projectQueries = useQueries({ queries: runningThreadIds.map(threadId => { const threadQuery = threadQueries.find((_, index) => runningThreadIds[index] === threadId); const projectId = threadQuery?.data?.project_id; return { queryKey: threadKeys.project(projectId || ""), queryFn: () => projectId ? getProject(projectId) : null, enabled: open && !!projectId, retry: 1, }; }) }); // Process the React Query results into our thread info structure const runningThreadsInfo: RunningThreadInfo[] = useMemo(() => { return runningThreadIds.map((threadId, index) => { const threadQuery = threadQueries[index]; const agentRunQuery = agentRunQueries[index]; const projectQuery = projectQueries[index]; const isLoading = threadQuery.isLoading || agentRunQuery.isLoading || projectQuery.isLoading; const hasError = threadQuery.isError || agentRunQuery.isError || projectQuery.isError; // Find the running agent run for this thread const runningAgentRun = agentRunQuery.data?.find((run: AgentRun) => run.status === 'running') || null; // Get thread name from first user message if available let threadName = ''; if (threadQuery.data?.messages?.length > 0) { const firstUserMessage = threadQuery.data.messages.find((msg: any) => msg.type === 'user'); if (firstUserMessage?.content) { threadName = firstUserMessage.content.substring(0, 50); if (firstUserMessage.content.length > 50) threadName += '...'; } } // Get project information const projectId = threadQuery.data?.project_id || null; const projectName = projectQuery.data?.name || ''; return { threadId, name: threadName, projectId, projectName, agentRun: runningAgentRun, isLoading, error: hasError, }; }); }, [runningThreadIds, threadQueries, agentRunQueries, projectQueries]); const isLoadingThreads = threadQueries.some(q => q.isLoading) || agentRunQueries.some(q => q.isLoading) || projectQueries.some(q => q.isLoading); const handleClose = () => { onOpenChange(false); }; const handleThreadStopped = () => { // No need to close the dialog - the queries will automatically refetch // and update the UI to show the current running state }; return (
Parallel Runs Limit Reached
You've reached the maximum parallel agent runs allowed.
{(runningThreadIds.length > 0 || runningCount > 0) && (

Currently Running Agents

{isLoadingThreads ? (
Loading threads...
) : runningThreadIds.length === 0 ? (

Found {runningCount} running agents but unable to load thread details.

Thread IDs: {JSON.stringify(runningThreadIds)}

) : (
{runningThreadsInfo.slice(0, 5).map((threadInfo) => ( ))} {runningThreadsInfo.length > 5 && (
+{runningThreadsInfo.length - 5} more running
)}
)}
)}

What can you do?

  • Click the button to stop running agents
  • Wait for an agent to complete automatically
); };