import React, { useState, useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { Plus, Edit, Trash2, Play, Bot, ArrowLeft, History, Download, Upload, Globe, FileJson, ChevronDown } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardFooter } from "@/components/ui/card"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { api, type Agent, type AgentRunWithMetrics } from "@/lib/api"; import { save, open } from "@tauri-apps/plugin-dialog"; import { invoke } from "@tauri-apps/api/core"; import { cn } from "@/lib/utils"; import { Toast, ToastContainer } from "@/components/ui/toast"; import { CreateAgent } from "./CreateAgent"; import { AgentExecution } from "./AgentExecution"; import { AgentRunsList } from "./AgentRunsList"; import { GitHubAgentBrowser } from "./GitHubAgentBrowser"; import { ICON_MAP } from "./IconPicker"; import { useTranslation } from "@/hooks/useTranslation"; interface CCAgentsProps { /** * Callback to go back to the main view */ onBack: () => void; /** * Optional className for styling */ className?: string; } // Available icons for agents - now using all icons from IconPicker export const AGENT_ICONS = ICON_MAP; export type AgentIconName = keyof typeof AGENT_ICONS; /** * CCAgents component for managing Claude Code agents * * @example * setView('home')} /> */ export const CCAgents: React.FC = ({ onBack, className }) => { const { t, i18n } = useTranslation(); const [agents, setAgents] = useState([]); const [runs, setRuns] = useState([]); const [loading, setLoading] = useState(true); const [runsLoading, setRunsLoading] = useState(false); const [error, setError] = useState(null); const [toast, setToast] = useState<{ message: string; type: "success" | "error" } | null>(null); const [currentPage, setCurrentPage] = useState(1); const [view, setView] = useState<"list" | "create" | "edit" | "execute">("list"); const [selectedAgent, setSelectedAgent] = useState(null); // const [selectedRunId, setSelectedRunId] = useState(null); const [showGitHubBrowser, setShowGitHubBrowser] = useState(false); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [agentToDelete, setAgentToDelete] = useState(null); const [isDeleting, setIsDeleting] = useState(false); const AGENTS_PER_PAGE = 9; // 3x3 grid useEffect(() => { loadAgents(); loadRuns(); }, []); const loadAgents = async () => { try { setLoading(true); setError(null); const agentsList = await api.listAgents(); setAgents(agentsList); } catch (err) { console.error("Failed to load agents:", err); setError(t('agents.loadAgentsFailed')); setToast({ message: t('agents.loadAgentsFailed'), type: "error" }); } finally { setLoading(false); } }; const loadRuns = async () => { try { setRunsLoading(true); const runsList = await api.listAgentRuns(); setRuns(runsList); } catch (err) { console.error("Failed to load runs:", err); } finally { setRunsLoading(false); } }; /** * Initiates the delete agent process by showing the confirmation dialog * @param agent - The agent to be deleted */ const handleDeleteAgent = (agent: Agent) => { setAgentToDelete(agent); setShowDeleteDialog(true); }; /** * Confirms and executes the agent deletion * Only called when user explicitly confirms the deletion */ const confirmDeleteAgent = async () => { if (!agentToDelete?.id) return; try { setIsDeleting(true); await api.deleteAgent(agentToDelete.id); setToast({ message: t('agents.agentDeleted'), type: "success" }); await loadAgents(); await loadRuns(); // Reload runs as they might be affected } catch (err) { console.error("Failed to delete agent:", err); setToast({ message: t('agents.deleteFailed'), type: "error" }); } finally { setIsDeleting(false); setShowDeleteDialog(false); setAgentToDelete(null); } }; /** * Cancels the delete operation and closes the dialog */ const cancelDeleteAgent = () => { setShowDeleteDialog(false); setAgentToDelete(null); }; const handleEditAgent = (agent: Agent) => { setSelectedAgent(agent); setView("edit"); }; const handleExecuteAgent = (agent: Agent) => { setSelectedAgent(agent); setView("execute"); }; const handleAgentCreated = async () => { setView("list"); await loadAgents(); setToast({ message: t('agents.agentCreated'), type: "success" }); }; const handleAgentUpdated = async () => { setView("list"); await loadAgents(); setToast({ message: t('agents.agentUpdated'), type: "success" }); }; // const handleRunClick = (run: AgentRunWithMetrics) => { // if (run.id) { // setSelectedRunId(run.id); // setView("viewRun"); // } // }; const handleExecutionComplete = async () => { // Reload runs when returning from execution await loadRuns(); }; const handleExportAgent = async (agent: Agent) => { try { // Show native save dialog const filePath = await save({ defaultPath: `${agent.name.toLowerCase().replace(/\s+/g, '-')}.claudia.json`, filters: [{ name: 'Claudia Agent', extensions: ['claudia.json'] }] }); if (!filePath) { // User cancelled the dialog return; } // Export the agent to the selected file await invoke('export_agent_to_file', { id: agent.id!, filePath }); setToast({ message: t('agents.exportedSuccessfully', { name: agent.name }), type: "success" }); } catch (err) { console.error("Failed to export agent:", err); setToast({ message: t('agents.exportFailed'), type: "error" }); } }; const handleImportAgent = async () => { try { // Show native open dialog const filePath = await open({ multiple: false, filters: [{ name: 'Claudia Agent', extensions: ['claudia.json', 'json'] }] }); if (!filePath) { // User cancelled the dialog return; } // Import the agent from the selected file await api.importAgentFromFile(filePath as string); setToast({ message: t('agents.importedSuccessfully'), type: "success" }); await loadAgents(); } catch (err) { console.error("Failed to import agent:", err); const errorMessage = err instanceof Error ? err.message : t('agents.importFailed'); setToast({ message: errorMessage, type: "error" }); } }; // Pagination calculations const totalPages = Math.ceil(agents.length / AGENTS_PER_PAGE); const startIndex = (currentPage - 1) * AGENTS_PER_PAGE; const paginatedAgents = agents.slice(startIndex, startIndex + AGENTS_PER_PAGE); const renderIcon = (iconName: string) => { const Icon = AGENT_ICONS[iconName as AgentIconName] || AGENT_ICONS.bot; return ; }; if (view === "create") { return ( setView("list")} onAgentCreated={handleAgentCreated} /> ); } if (view === "edit" && selectedAgent) { return ( setView("list")} onAgentCreated={handleAgentUpdated} /> ); } if (view === "execute" && selectedAgent) { return ( { setView("list"); handleExecutionComplete(); }} /> ); } // Removed viewRun case - now using modal preview in AgentRunsList return (
{/* Header */}

{t('navigation.agents')}

{t('agents.manageAgents')}

{t('agents.importFromFile')} setShowGitHubBrowser(true)}> {t('agents.importFromGitHub')}
{/* Error display */} {error && ( {error} )} {/* Main Content */}
{/* Agents Grid */}
{loading ? (
) : agents.length === 0 ? (

{t('agents.noAgentsYet')}

{t('agents.createFirstAgent')}

) : ( <>
{paginatedAgents.map((agent, index) => (
{renderIcon(agent.icon)}

{agent.name}

{t('agents.created')}: {new Date(agent.created_at).toLocaleDateString(i18n.language)}

))}
{/* Pagination */} {totalPages > 1 && (
{t('app.page')} {currentPage} {t('app.of')} {totalPages}
)} )}
{/* Execution History */} {!loading && agents.length > 0 && (

{t('agents.recentExecutions')}

{runsLoading ? (
) : ( )}
)}
{/* Toast Notification */} {toast && ( setToast(null)} /> )} {/* GitHub Agent Browser */} setShowGitHubBrowser(false)} onImportSuccess={async () => { setShowGitHubBrowser(false); await loadAgents(); setToast({ message: t('agents.importFromGitHubSuccess'), type: "success" }); }} /> {/* Delete Confirmation Dialog */} {t('agents.deleteAgentTitle')} {t('agents.deleteConfirmation', { name: agentToDelete?.name })}
); };