import React, { useState, useEffect } from 'react';
import ChatWindow from '../../chat/components/ChatWindow';
import FileSystemNavigator from '../../../shared/components/FileSystemNavigator';
import { getAgents, getSessionMessages, fetchWithAuth, updateAgentConfig, getUserConfig, clearSessionHistory, getSessionTokenStatus, getAgentTriggers, createAgentTrigger, deleteAgentTrigger, getUserAccessibleNodes, getSkills, resetAgentMetrics } from '../../../services/apiService';
export default function AgentDrillDown({ agentId, onNavigate }) {
const [agent, setAgent] = useState(null);
const [chatHistory, setChatHistory] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [overrideText, setOverrideText] = useState("");
// Day 2 Configurations
const [activeTab, setActiveTab] = useState('config'); // workspace or config
const [editConfig, setEditConfig] = useState(null);
const [saving, setSaving] = useState(false);
const [userConfig, setUserConfig] = useState(null);
const [tokenUsage, setTokenUsage] = useState({ token_count: 0, token_limit: 0, percentage: 0 });
const [clearing, setClearing] = useState(false);
const [triggers, setTriggers] = useState([]);
const [newTriggerType, setNewTriggerType] = useState('cron');
const [newCronValue, setNewCronValue] = useState('0 * * * *');
const [newIntervalValue, setNewIntervalValue] = useState(600);
const [newDefaultPrompt, setNewDefaultPrompt] = useState('');
const [creatingTrigger, setCreatingTrigger] = useState(false);
const [modalConfig, setModalConfig] = useState(null);
const [nodes, setNodes] = useState([]);
const [allSkills, setAllSkills] = useState([]);
const [flippedCards, setFlippedCards] = useState({ runtime: false, tokens: false });
// Helper: Convert cron expression to human-readable text
const describeCron = (expr) => {
if (!expr) return '';
if (/^\d+$/.test(expr)) {
const secs = parseInt(expr);
if (secs < 60) return `Every ${secs} seconds`;
if (secs < 3600) return `Every ${Math.round(secs/60)} minute${secs >= 120 ? 's' : ''}`;
return `Every ${Math.round(secs/3600)} hour${secs >= 7200 ? 's' : ''}`;
}
// Standard cron expressions
const parts = expr.split(' ');
if (parts.length >= 5) {
if (expr === '* * * * *') return 'Every minute';
if (expr === '0 * * * *') return 'Every hour';
if (expr === '0 0 * * *') return 'Every day at midnight';
if (parts[0].startsWith('*/')) return `Every ${parts[0].slice(2)} minute${parts[0].slice(2) !== '1' ? 's' : ''}`;
}
return expr;
};
const formatTimeLocal = (utcString) => {
if (!utcString) return 'Never';
const dateStr = utcString.endsWith('Z') || utcString.includes('+') ? utcString : utcString + 'Z';
return new Date(dateStr).toLocaleString(undefined, {
month: 'short', day: 'numeric',
hour: '2-digit', minute: '2-digit', second: '2-digit'
});
};
useEffect(() => {
const loadConf = async () => {
try {
const conf = await getUserConfig();
setUserConfig(conf);
} catch (e) {}
try {
const nList = await getUserAccessibleNodes();
setNodes(nList);
} catch (e) {}
try {
const sList = await getSkills();
setAllSkills(sList);
} catch (e) {}
};
loadConf();
}, []);
const fetchData = async () => {
try {
// Find agent
const allAgents = await getAgents();
const found = allAgents.find(a => a.id === agentId);
if (!found) throw new Error("Agent not found");
setAgent(found);
// Populate form only on first load using the agent context
setEditConfig(prev => prev || {
name: found.template?.name || "",
system_prompt: found.template?.system_prompt_content || found.template?.system_prompt_path || "",
max_loop_iterations: found.template?.max_loop_iterations || 20,
mesh_node_id: found.mesh_node_id || "",
provider_name: found.session?.provider_name || "",
restrict_skills: found.session?.restrict_skills || false,
allowed_skill_ids: found.session?.skills ? found.session.skills.map(s => s.id) : [],
is_locked: found.session?.is_locked || false,
auto_clear_history: found.session?.auto_clear_history || false
});
// Fetch chat history if session exists
if (found.session_id) {
const historyResp = await getSessionMessages(found.session_id);
const formatted = (historyResp.messages || []).map(m => ({
text: m.content,
isUser: m.sender === 'user',
reasoning: m.reasoning_content,
status: null,
sender: m.sender,
timestamp: m.created_at,
id: m.id,
tool_calls: m.tool_calls
}));
setChatHistory(formatted);
try {
const usage = await getSessionTokenStatus(found.session_id);
setTokenUsage(usage);
} catch(e) {}
}
try {
const tList = await getAgentTriggers(agentId);
setTriggers(tList);
} catch(e) {}
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
const interval = setInterval(fetchData, 3000);
return () => clearInterval(interval);
}, [agentId]);
const handleInjectOverride = async (e) => {
e.preventDefault();
if (!overrideText.trim() || !agent?.session_id) return;
try {
await fetchWithAuth(`/agents/${agentId}/webhook`, {
method: "POST",
body: { override_prompt: overrideText }
});
setOverrideText("");
fetchData();
} catch (err) {
setModalConfig({ title: 'Injection Failed', message: err.message, type: 'error' });
}
};
const handleClearHistory = () => {
if (!agent?.session_id) return;
setModalConfig({
title: 'Confirm Memory Wipe',
message: "Are you sure you want to clear the agent's memory? This cannot be undone.",
type: 'error',
confirmText: 'Clear Memory',
confirmAction: async () => {
try {
setClearing(true);
if (agent?.session?.is_locked && editConfig?.is_locked === false) {
await updateAgentConfig(agent.id, {
is_locked: false,
mesh_node_id: agent.mesh_node_id || "hub"
});
}
await clearSessionHistory(agent.session_id);
setChatHistory([]);
fetchData();
} catch (err) {
setModalConfig({ title: 'Clear Failed', message: err.message, type: 'error' });
} finally {
setClearing(false);
}
}
});
};
const handleResetMetrics = () => {
if (!agent?.id) return;
setModalConfig({
title: 'Confirm Reset Metrics',
message: "Are you sure you want to reset all execution metrics for this agent? This cannot be undone.",
type: 'error',
confirmText: 'Reset Metrics',
confirmAction: async () => {
try {
setClearing(true); // Re-use the clearing state to block duplicate clicks
await resetAgentMetrics(agent.id);
fetchData();
} catch (err) {
setModalConfig({ title: 'Reset Failed', message: err.message, type: 'error' });
} finally {
setClearing(false);
}
}
});
};
const handleSaveConfig = async () => {
try {
setSaving(true);
const payload = {
name: editConfig.name,
system_prompt: editConfig.system_prompt,
max_loop_iterations: parseInt(editConfig.max_loop_iterations, 10) || 20,
mesh_node_id: editConfig.mesh_node_id
};
if (editConfig.provider_name) {
payload.provider_name = editConfig.provider_name;
}
if (editConfig.restrict_skills !== undefined) {
payload.restrict_skills = editConfig.restrict_skills;
}
if (editConfig.allowed_skill_ids !== undefined) {
payload.allowed_skill_ids = editConfig.allowed_skill_ids;
}
if (editConfig.is_locked !== undefined) {
payload.is_locked = editConfig.is_locked;
}
if (editConfig.auto_clear_history !== undefined) {
payload.auto_clear_history = editConfig.auto_clear_history;
}
// Explicitly pause the agent loop during update as requested by the user
try {
await fetchWithAuth(`/agents/${agentId}/status`, { method: "PATCH", body: { status: "idle" } });
} catch (e) {}
await updateAgentConfig(agentId, payload);
fetchData();
setModalConfig({ title: 'Success', message: 'Configuration Saved Successfully!', type: 'success' });
} catch (err) {
setModalConfig({ title: 'Save Failed', message: err.message, type: 'error' });
} finally {
setSaving(false);
}
};
const handleAddTrigger = async () => {
try {
setCreatingTrigger(true);
const payload = {
trigger_type: newTriggerType,
default_prompt: newDefaultPrompt
};
if (newTriggerType === 'cron') payload.cron_expression = newCronValue;
if (newTriggerType === 'interval') payload.interval_seconds = parseInt(newIntervalValue) || 600;
await createAgentTrigger(agentId, payload);
setNewDefaultPrompt('');
setNewCronValue('0 * * * *');
setNewIntervalValue(600);
fetchData();
} catch (err) {
setModalConfig({ title: 'Trigger Failed', message: err.message, type: 'error' });
} finally {
setCreatingTrigger(false);
}
};
const handleDeleteTrigger = async (triggerId) => {
try {
await deleteAgentTrigger(triggerId);
fetchData();
} catch (err) {
setModalConfig({ title: 'Delete Failed', message: err.message, type: 'error' });
}
};
const handleFireTrigger = async (triggerPrompt) => {
try {
await fetchWithAuth(`/agents/${agentId}/run`, {
method: 'POST',
body: { prompt: triggerPrompt }
});
setModalConfig({ title: 'Success', message: 'Agent manual execution started successfully!', type: 'success' });
fetchData();
} catch (err) {
setModalConfig({ title: 'Execution Failed', message: err.message, type: 'error' });
}
};
if (loading && !agent) return (
<div className="flex h-screen items-center justify-center bg-[#070b14] text-white">
<div className="animate-spin w-8 h-8 border-4 border-indigo-500 border-t-transparent rounded-full font-mono text-sm ml-4"></div>
</div>
);
if (error) return (
<div className="flex h-screen items-center justify-center bg-gray-950 text-red-500">
Error loading Agent Drilldown: {error}
</div>
);
return (
<div className="flex flex-col flex-grow h-full bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 font-sans overflow-hidden">
{/* Minimal Header */}
<div className="border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-800 px-6 py-4 flex items-center justify-between z-10 shrink-0 shadow-sm">
<div className="flex items-center space-x-4">
<button onClick={() => onNavigate('/agents')} className="text-gray-500 hover:text-gray-900 dark:hover:text-white transition-colors p-2 -ml-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" /></svg>
</button>
<div>
<h2 className="text-xl font-bold tracking-tight">{agent?.id?.split('-')[0]} Dashboard</h2>
<div className="flex items-center space-x-2 mt-1">
<span className="flex h-2 w-2 relative">
{agent?.status === 'active' && <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>}
<span className={`relative inline-flex rounded-full h-2 w-2 ${agent?.status === 'active' ? 'bg-emerald-500' : 'bg-amber-500'}`}></span>
</span>
<span className="text-[10px] text-gray-500 dark:text-gray-400 uppercase tracking-widest font-mono">Status: {agent?.status}</span>
</div>
</div>
</div>
<div className="flex items-center space-x-4 font-mono text-xs text-gray-500 dark:text-gray-400">
<span>Node: <span className="text-indigo-600 dark:text-indigo-400 font-bold">{nodes.find(n => n.id === agent?.mesh_node_id)?.name || agent?.mesh_node_id || 'unassigned'}</span></span>
<span>Jail Path: <span className="text-pink-600 dark:text-pink-400 font-bold">{agent?.current_workspace_jail || '/tmp'}</span></span>
<span>Synced Workspace: <span className="text-emerald-600 dark:text-emerald-400 font-bold">{agent?.session?.sync_workspace_id || agent?.session_id || 'not-bound'}</span></span>
</div>
</div>
{/* Main Content Area - 50/50 Split */}
<div className="flex-1 grid grid-cols-2 overflow-hidden gap-1 p-1 bg-gray-200 dark:bg-gray-950">
{/* Left Pane: Chat Tracker */}
<div className="flex flex-col bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800/80 rounded-xl overflow-hidden relative shadow-lg">
<div className="px-4 py-2 border-b border-gray-200 dark:border-gray-800/80 bg-gray-50 dark:bg-gray-900/50 backdrop-blur-md flex justify-between items-center shrink-0">
<span className="text-[10px] uppercase tracking-[0.2em] font-black text-indigo-400 flex items-center gap-2">
<div className="w-1.5 h-1.5 bg-indigo-500 rounded-full animate-pulse shadow-[0_0_8px_rgba(99,102,241,0.5)]"></div>
Live Thought Process
</span>
{agent?.session_id && (
<button
onClick={handleClearHistory}
disabled={clearing || editConfig?.is_locked}
className="text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded border border-red-500/30 text-red-500 hover:bg-red-500/10 transition-colors disabled:opacity-50"
>
{clearing ? 'Clearing...' : editConfig?.is_locked ? "🔒 Locked" : "Clear History"}
</button>
)}
</div>
<div className="flex-1 overflow-hidden relative">
{agent?.session_id ? (
<ChatWindow
chatHistory={chatHistory}
maxHeight="100%"
autoCollapse={true}
/>
) : (
<div className="flex h-full items-center justify-center text-gray-500 italic text-sm">
No session bounds established for this agent.
</div>
)}
</div>
{/* Inject Prompt Override */}
<div className="p-4 border-t border-gray-200 dark:border-gray-800 bg-gray-50 dark:bg-gray-900/90 shrink-0">
<form onSubmit={handleInjectOverride} className="relative flex items-center">
<input
type="text"
value={overrideText}
onChange={(e) => setOverrideText(e.target.value)}
placeholder="Steer agent execution loop..."
className="w-full bg-white dark:bg-gray-950 border border-gray-300 dark:border-gray-700 text-sm rounded-xl py-3 pl-4 pr-12 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 text-gray-900 dark:text-gray-100 transition-all font-mono"
/>
<button
type="submit"
disabled={!overrideText.trim()}
className="absolute right-2 px-3 py-1.5 bg-indigo-600/20 text-indigo-400 rounded-lg text-xs font-bold hover:bg-indigo-600 hover:text-white transition-colors disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:text-indigo-400 uppercase tracking-wider"
>
Inject
</button>
</form>
</div>
</div>
{/* Right Pane: Multi-Tab Container */}
<div className="flex flex-col bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800/80 rounded-xl overflow-hidden shadow-lg">
{/* Tab Header */}
<div className="flex border-b border-gray-200 dark:border-gray-800/80 bg-gray-50 dark:bg-gray-900/50 px-2 pt-2 shrink-0">
<button
onClick={() => setActiveTab('config')}
className={`px-4 py-2 text-xs font-bold uppercase tracking-widest rounded-t-lg transition-colors ${activeTab === 'config' ? 'bg-white dark:bg-gray-900 text-indigo-500 border-t border-l border-r border-gray-200 dark:border-gray-800/80 -mb-px' : 'text-gray-500 hover:text-gray-400'}`}
>
Metadata & System
</button>
<button
onClick={() => setActiveTab('workspace')}
className={`px-4 py-2 text-xs font-bold uppercase tracking-widest rounded-t-lg transition-colors ${activeTab === 'workspace' ? 'bg-white dark:bg-gray-900 text-indigo-500 border-t border-l border-r border-gray-200 dark:border-gray-800/80 -mb-px' : 'text-gray-500 hover:text-gray-400'}`}
>
Mesh Workspace
</button>
<button
onClick={() => setActiveTab('metrics')}
className={`px-4 py-2 text-xs font-bold uppercase tracking-widest rounded-t-lg transition-colors ${activeTab === 'metrics' ? 'bg-white dark:bg-gray-900 text-indigo-500 border-t border-l border-r border-gray-200 dark:border-gray-800/80 -mb-px' : 'text-gray-500 hover:text-gray-400'}`}
>
Metrics
</button>
</div>
{/* Tab Body */}
<div className="flex-1 overflow-auto relative">
{activeTab === 'workspace' && (
agent ? (
<FileSystemNavigator
nodeId={agent.mesh_node_id || "hub"}
initialPath="."
sessionId={agent.session?.sync_workspace_id || agent.session_id}
showSyncStatus={true}
/>
) : (
<div className="flex h-full items-center justify-center text-gray-500">
Initializing workspace bridge...
</div>
)
)}
{activeTab === 'config' && (
<div className="p-6 flex flex-col gap-6">
{/* Token & History Utilities */}
<div className="p-4 rounded-xl border border-gray-200 dark:border-gray-700/50 bg-gray-50 dark:bg-gray-800/30 flex justify-between items-center">
<div className="flex flex-col">
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold mb-1">Session Context Window</span>
<div className="flex items-center gap-3 w-48">
<div className="flex-1 h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-emerald-500 to-indigo-500"
style={{ width: `${Math.min(tokenUsage.percentage, 100)}%` }}
/>
</div>
<span className="text-xs font-mono whitespace-nowrap text-gray-500">{Math.round(tokenUsage.percentage)}%</span>
</div>
</div>
</div>
<div className={`grid grid-cols-1 md:grid-cols-2 gap-6 ${editConfig?.is_locked ? 'opacity-50 pointer-events-none transition-opacity' : ''}`}>
<div>
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-1">Agent Name</span>
<input type="text" value={editConfig?.name || ""} onChange={(e) => setEditConfig({...editConfig, name: e.target.value})} className="w-full bg-white dark:bg-gray-950 border border-gray-300 dark:border-gray-700 text-sm rounded-md py-2.5 px-3 focus:outline-none focus:ring-1 focus:ring-indigo-500 text-gray-900 dark:text-gray-100 shadow-sm" placeholder="e.g. Documentation Assistant" disabled={editConfig?.is_locked} />
</div>
<div>
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-1">Active LLM Provider</span>
<select
value={editConfig?.provider_name || userConfig?.effective?.llm?.active_provider || ""}
onChange={e => setEditConfig({...editConfig, provider_name: e.target.value})}
className="w-full bg-white dark:bg-gray-950 border border-gray-300 dark:border-gray-700 text-sm rounded-md py-2.5 px-3 focus:outline-none focus:ring-1 focus:ring-indigo-500 text-gray-900 dark:text-gray-100 shadow-sm"
disabled={editConfig?.is_locked}
>
{userConfig?.effective?.llm?.providers && Object.keys(userConfig.effective.llm.providers).map(pid => (
<option key={pid} value={pid}>{pid} {userConfig.effective.llm.providers[pid].model ? `(${userConfig.effective.llm.providers[pid].model})` : ''}</option>
))}
</select>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-1">Target Mesh Node</span>
<select
value={editConfig?.mesh_node_id || ""}
onChange={(e) => setEditConfig({...editConfig, mesh_node_id: e.target.value})}
className="w-full bg-white dark:bg-gray-950 border border-gray-300 dark:border-gray-700 text-sm rounded-md py-2.5 px-3 focus:outline-none focus:ring-1 focus:ring-indigo-500 text-gray-900 dark:text-gray-100 shadow-sm"
required
disabled={editConfig?.is_locked}
>
{nodes.length === 0 && <option value="" disabled>No nodes available</option>}
{nodes.map(n => <option key={n.node_id} value={n.node_id}>{n.display_name || n.node_id} ({n.last_status || 'unknown'})</option>)}
</select>
</div>
<div>
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-1">Max Iterations</span>
<input type="number" min="1" max="100" value={editConfig?.max_loop_iterations || 20} onChange={(e) => setEditConfig({...editConfig, max_loop_iterations: e.target.value})} className="w-full bg-white dark:bg-gray-950 border border-gray-300 dark:border-gray-700 text-sm rounded-md py-2.5 px-3 focus:outline-none focus:ring-1 focus:ring-indigo-500 text-gray-900 dark:text-gray-100 shadow-sm" disabled={editConfig?.is_locked} />
</div>
</div>
</div>
{/* Skills and Lock Controls */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 pb-2 border-b border-gray-200 dark:border-gray-800">
<div className={editConfig?.is_locked ? 'opacity-50 pointer-events-none transition-opacity' : ''}>
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-2">Enabled Skills</span>
<div className="bg-white dark:bg-gray-950 border border-gray-300 dark:border-gray-700 rounded-lg p-3 max-h-56 overflow-y-auto w-full shadow-sm">
<label className="flex items-center gap-2 cursor-pointer pb-3 mb-3 border-b border-gray-100 dark:border-gray-800">
<div className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
className="sr-only peer"
checked={editConfig?.restrict_skills === false}
onChange={(e) => setEditConfig({...editConfig, restrict_skills: !e.target.checked})}
/>
<div className="w-8 h-4.5 bg-gray-200 peer-focus:outline-none rounded-full peer dark:bg-gray-800 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-3.5 after:w-3.5 after:transition-all dark:border-gray-600 peer-checked:bg-indigo-500"></div>
</div>
<span className="text-sm font-bold text-gray-900 dark:text-gray-100">All Skills (Unrestricted)</span>
</label>
<div className="mb-2">
<span className="text-[10px] uppercase font-bold text-gray-400 block mb-1.5">System Core Skills</span>
<div className="flex flex-wrap gap-1">
{allSkills.filter(s => s.is_system).map(skill => (
<span key={skill.id} className="text-[10px] font-bold bg-indigo-50 dark:bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 border border-indigo-200 dark:border-indigo-500/20 px-2.5 py-0.5 rounded-full whitespace-nowrap">
✓ {skill.name}
</span>
))}
</div>
</div>
{editConfig?.restrict_skills && (
<div className="mt-3 pt-3 border-t border-gray-100 dark:border-gray-800">
<span className="text-[10px] uppercase font-bold text-gray-400 block mb-2">User Context Skills</span>
<div className="flex flex-col gap-2">
{allSkills.filter(s => !s.is_system).map(skill => (
<label key={skill.id} className="flex items-center gap-2.5 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-900 -mx-1 px-1 py-1 rounded">
<input
type="checkbox"
className="w-4 h-4 rounded text-indigo-500 focus:ring-indigo-500 bg-white dark:bg-gray-900 border-gray-300 dark:border-gray-700"
checked={editConfig?.allowed_skill_ids?.includes(skill.id) || false}
onChange={(e) => {
const ids = editConfig.allowed_skill_ids || [];
const nextIds = e.target.checked ? [...ids, skill.id] : ids.filter(i => i !== skill.id);
setEditConfig({...editConfig, allowed_skill_ids: nextIds});
}}
/>
<span className="text-xs font-mono font-medium text-gray-700 dark:text-gray-300 truncate">
{skill.name}
</span>
</label>
))}
{allSkills.filter(s => !s.is_system).length === 0 && (
<span className="text-xs text-gray-400 italic py-1">No user skills available</span>
)}
</div>
</div>
)}
</div>
</div>
<div>
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-2">Memory Handling</span>
<div className="bg-white dark:bg-gray-950 border border-gray-300 dark:border-gray-700 rounded-lg p-4 shadow-sm flex flex-col gap-4">
<label className="flex items-center gap-3 cursor-pointer w-full">
<div className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
className="sr-only peer"
checked={editConfig?.is_locked || false}
onChange={(e) => setEditConfig({...editConfig, is_locked: e.target.checked})}
/>
<div className="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-indigo-300 dark:peer-focus:ring-indigo-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-amber-500"></div>
</div>
<div className="flex flex-col flex-1">
<span className={`text-sm font-bold ${editConfig?.is_locked ? "text-amber-600 dark:text-amber-500" : "text-gray-900 dark:text-gray-100"}`}>
{editConfig?.is_locked ? 'Lock Session Memory' : 'Unlocked'}
</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
When locked, the agent's memory (chat history) cannot be cleared or deleted. Unlock to clear history.
</span>
</div>
</label>
<div className="w-full h-px bg-gray-200 dark:bg-gray-800 my-1"></div>
<label className="flex items-center gap-3 cursor-pointer w-full">
<div className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
className="sr-only peer"
checked={editConfig?.auto_clear_history || false}
onChange={(e) => setEditConfig({...editConfig, auto_clear_history: e.target.checked})}
/>
<div className="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-indigo-300 dark:peer-focus:ring-indigo-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-pink-500"></div>
</div>
<div className="flex flex-col flex-1">
<span className={`text-sm font-bold ${editConfig?.auto_clear_history ? "text-pink-600 dark:text-pink-500" : "text-gray-900 dark:text-gray-100"}`}>
{editConfig?.auto_clear_history ? 'Auto-Wipe Between Runs' : 'Preserve History Continuously'}
</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
When enabled, the chat history will be completely wiped prior to every new background execution (e.g. cron/webhook).
</span>
</div>
</label>
</div>
</div>
</div>
{/* Execution Triggers Box */}
<div>
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-2">Execution Triggers</span>
<div className="bg-white dark:bg-gray-950 border border-gray-300 dark:border-gray-700 rounded-lg p-4 shadow-sm">
{/* List existing triggers */}
{triggers.length > 0 && (
<div className="mb-4 space-y-2">
{triggers.map(t => (
<div key={t.id} className="flex items-center justify-between bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded px-3 py-2 text-sm">
<div className="flex flex-col">
<span className="font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider text-[10px]">
{({'manual': '🖐️ MANUAL', 'cron': '⏰ CRON', 'interval': '🔄 INTERVAL', 'webhook': '🔗 WEBHOOK'})[t.trigger_type] || t.trigger_type} · {t.id.split('-')[0]}
</span>
<span className="font-mono text-gray-500 dark:text-gray-400 text-xs">
{t.trigger_type === 'cron' && `Schedule: ${t.cron_expression} (${describeCron(t.cron_expression)})`}
{t.trigger_type === 'interval' && `Every ${t.interval_seconds >= 3600 ? Math.round(t.interval_seconds/3600) + 'h' : t.interval_seconds >= 60 ? Math.round(t.interval_seconds/60) + 'min' : t.interval_seconds + 's'} after completion`}
{t.trigger_type === 'webhook' && `Secret: ${t.webhook_secret}`}
{t.trigger_type === 'manual' && `On-demand — Ready for requests`}
</span>
{t.default_prompt && (
<span className="text-[10px] text-gray-400 mt-1 italic">Prompt: "{t.default_prompt.substring(0, 40)}{t.default_prompt.length > 40 ? '...' : ''}"</span>
)}
</div>
<div className="flex items-center gap-2">
<button
onClick={e => { e.preventDefault(); handleFireTrigger(t.default_prompt); }}
className="text-emerald-600 dark:text-emerald-400 hover:text-emerald-700 font-bold text-xs bg-emerald-50 dark:bg-emerald-500/10 hover:bg-emerald-100 dark:hover:bg-emerald-500/20 px-3 py-1.5 rounded transition-colors"
>
Fire
</button>
<button
onClick={e => { e.preventDefault(); handleDeleteTrigger(t.id); }}
className="text-red-500 hover:text-red-700 font-bold text-xs bg-red-50 dark:bg-red-900/20 hover:bg-red-100 dark:hover:bg-red-900/40 px-3 py-1.5 rounded transition-colors"
>
Delete
</button>
</div>
</div>
))}
</div>
)}
{/* Add new trigger form */}
<div className="flex items-end gap-3 border-t border-gray-200 dark:border-gray-800 pt-4 mt-2">
<div className="w-1/3">
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-1">Type</span>
<select
value={newTriggerType}
onChange={e => setNewTriggerType(e.target.value)}
className="w-full bg-gray-50 dark:bg-gray-900 border border-gray-300 dark:border-gray-700 text-sm rounded-md py-2 px-3 focus:outline-none"
>
<option value="manual">Manual Request</option>
<option value="cron">CRON Schedule</option>
<option value="interval">Recurrent Wait</option>
<option value="webhook">Webhook Link</option>
</select>
</div>
{newTriggerType === 'cron' && (
<div className="flex-1">
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-1">CRON Expr</span>
<input
type="text"
value={newCronValue}
onChange={e => setNewCronValue(e.target.value)}
className="w-full bg-gray-50 dark:bg-gray-900 border border-gray-300 dark:border-gray-700 text-sm rounded-md py-2 px-3 font-mono focus:outline-none"
/>
</div>
)}
{newTriggerType === 'interval' && (
<div className="flex-1">
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-1">Wait Seconds</span>
<input
type="number"
value={newIntervalValue}
onChange={e => setNewIntervalValue(e.target.value)}
className="w-full bg-gray-50 dark:bg-gray-900 border border-gray-300 dark:border-gray-700 text-sm rounded-md py-2 px-3 font-mono focus:outline-none"
/>
</div>
)}
</div>
<div className="mt-4 flex flex-col">
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-1">
{newTriggerType === 'cron' || newTriggerType === 'interval' ? 'Fixed Automation Prompt' : 'Default/Overridable Prompt'}
</span>
<div className="flex gap-3 items-end">
<input
type="text"
value={newDefaultPrompt}
onChange={e => setNewDefaultPrompt(e.target.value)}
placeholder="Trigger instruction..."
className="flex-1 bg-gray-50 dark:bg-gray-900 border border-gray-300 dark:border-gray-700 text-sm rounded-md py-2 px-3 focus:outline-none"
/>
<button
onClick={e => { e.preventDefault(); handleAddTrigger(); }}
disabled={creatingTrigger}
className="px-4 py-2 text-xs font-bold bg-indigo-100 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-400 border border-indigo-200 dark:border-indigo-800 hover:bg-indigo-200 dark:hover:bg-indigo-800/50 rounded-md transition-colors whitespace-nowrap"
>
+ Add Trigger
</button>
</div>
</div>
</div>
</div>
<div className={`flex-1 flex flex-col min-h-[300px] ${editConfig?.is_locked ? 'opacity-50 pointer-events-none transition-opacity' : ''}`}>
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-2">Core System Instruction Prompt (Modifiable Live)</span>
<textarea value={editConfig?.system_prompt || ""} onChange={(e) => setEditConfig({...editConfig, system_prompt: e.target.value})} className="flex-1 resize-none w-full bg-gray-900 text-emerald-400 p-4 rounded-lg text-sm leading-relaxed font-mono shadow-inner border border-gray-700 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500" placeholder="You are an expert AI agent..."></textarea>
<p className="text-xs text-gray-500 mt-2 font-mono">Changes to the system prompt will be immediately picked up by the agent loop on its next invocation turn.</p>
</div>
<div className="flex justify-end pt-4">
<button onClick={handleSaveConfig} disabled={saving} className="px-6 py-2.5 text-sm font-bold bg-indigo-600 hover:bg-indigo-700 text-white shadow-md rounded-lg transition-all active:scale-95 flex items-center gap-2">
{saving ? "Saving Configuration..." : "Apply Live Configuration"}
</button>
</div>
</div>
)}
{activeTab === 'metrics' && (
<div className="p-6 flex flex-col gap-6 font-mono text-sm">
<div className="flex justify-between items-center mb-2 border-b border-gray-200 dark:border-gray-800 pb-2">
<h3 className="text-xl font-bold tracking-tight text-gray-800 dark:text-gray-200">Execution Metrics</h3>
<button
onClick={handleResetMetrics}
disabled={clearing}
className="px-3 py-1.5 text-xs font-bold text-red-600 dark:text-red-400 hover:text-red-700 bg-red-50 dark:bg-red-900/20 hover:bg-red-100 dark:hover:bg-red-900/40 rounded border border-red-200 dark:border-red-800 transition-colors"
>
{clearing ? "Resetting..." : "Reset Metrics"}
</button>
</div>
<div className="grid grid-cols-2 md:grid-cols-6 gap-4">
<div className="bg-gray-50 dark:bg-gray-800/50 p-4 rounded-xl border border-gray-200 dark:border-gray-800">
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-1">Total Runs</span>
<span className="text-2xl font-black text-indigo-500">{agent?.total_runs || 0}</span>
</div>
<div className="bg-gray-50 dark:bg-gray-800/50 p-4 rounded-xl border border-gray-200 dark:border-gray-800">
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-1">Last Run</span>
<span className="text-sm font-black text-blue-500 mt-2 block">
{formatTimeLocal(agent?.last_heartbeat)}
</span>
</div>
<div
className="bg-gray-50 dark:bg-gray-800/50 p-4 rounded-xl border border-gray-200 dark:border-gray-800 cursor-help"
title={`${agent?.successful_runs || 0} / ${agent?.total_runs || 0} (${(agent?.total_runs || 0) - (agent?.successful_runs || 0)})`}
>
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-1">Success Rate</span>
<span className="text-2xl font-black text-emerald-500">
{agent?.total_runs ? Math.round(((agent?.successful_runs || 0) / agent.total_runs) * 100) : 0}%
</span>
</div>
<div
className="relative w-full min-h-[5.5rem] cursor-pointer group col-span-1"
style={{ perspective: "1000px" }}
onClick={() => setFlippedCards(prev => ({...prev, runtime: !prev.runtime}))}
>
<div
className="relative w-full h-full transition-transform duration-500"
style={{ transformStyle: "preserve-3d", transform: flippedCards.runtime ? "rotateX(180deg)" : "rotateX(0deg)" }}
>
<div
className="absolute inset-0 bg-gray-50 dark:bg-gray-800/50 p-4 rounded-xl border border-gray-200 dark:border-gray-800"
style={{ backfaceVisibility: "hidden" }}
>
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-1">Total AI Run Time <span className="opacity-0 group-hover:opacity-100 float-right text-indigo-400 transition-opacity">↻</span></span>
<span className="text-2xl font-black text-pink-500">{agent?.total_running_time_seconds ? agent.total_running_time_seconds.toLocaleString() + 's' : '0s'}</span>
</div>
<div
className="absolute inset-0 bg-gray-50 dark:bg-gray-800/50 p-4 rounded-xl border border-gray-200 dark:border-gray-800"
style={{ backfaceVisibility: "hidden", transform: "rotateX(180deg)" }}
>
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-1">Avg Request Time <span className="opacity-0 group-hover:opacity-100 float-right text-indigo-400 transition-opacity">↻</span></span>
<span className="text-2xl font-black text-pink-400">
{agent?.total_runs ? (agent.total_running_time_seconds / agent.total_runs).toFixed(1) : 0}s
</span>
</div>
</div>
</div>
<div
className="relative w-full min-h-[5.5rem] cursor-pointer group col-span-2"
style={{ perspective: "1000px" }}
onClick={() => setFlippedCards(prev => ({...prev, tokens: !prev.tokens}))}
>
<div
className="relative w-full h-full transition-transform duration-500"
style={{ transformStyle: "preserve-3d", transform: flippedCards.tokens ? "rotateX(180deg)" : "rotateX(0deg)" }}
>
<div
className="absolute inset-0 bg-gray-50 dark:bg-gray-800/50 p-4 rounded-xl border border-gray-200 dark:border-gray-800"
style={{ backfaceVisibility: "hidden" }}
>
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-1">Total Tokens (In / Out) <span className="opacity-0 group-hover:opacity-100 float-right text-indigo-400 transition-opacity">↻</span></span>
<span className="text-2xl font-black text-blue-400 tabular-nums">
{agent?.total_input_tokens?.toLocaleString() || 0}
<span className="text-gray-400 text-sm mx-2 font-normal">/</span>
<span className="text-emerald-500">{agent?.total_output_tokens?.toLocaleString() || 0}</span>
</span>
</div>
<div
className="absolute inset-0 bg-gray-50 dark:bg-gray-800/50 p-4 rounded-xl border border-gray-200 dark:border-gray-800"
style={{ backfaceVisibility: "hidden", transform: "rotateX(180deg)" }}
>
<span className="uppercase text-[10px] tracking-widest text-gray-500 font-bold block mb-1">Avg Tokens (In / Out) <span className="opacity-0 group-hover:opacity-100 float-right text-indigo-400 transition-opacity">↻</span></span>
<span className="text-2xl font-black text-blue-400 tabular-nums">
{agent?.total_runs ? Math.round(agent.total_input_tokens / agent.total_runs).toLocaleString() : 0}
<span className="text-gray-400 text-sm mx-2 font-normal">/</span>
<span className="text-emerald-500">{agent?.total_runs ? Math.round(agent.total_output_tokens / agent.total_runs).toLocaleString() : 0}</span>
</span>
</div>
</div>
</div>
</div>
<div className="mt-4">
<h4 className="text-sm font-bold tracking-widest text-gray-500 uppercase mb-3">Tool Usage breakdown</h4>
<div className="bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-800 rounded-lg overflow-hidden">
<table className="w-full text-left">
<thead className="bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 text-[10px] uppercase text-gray-500 tracking-wider">
<tr>
<th className="px-4 py-3 font-bold">Tool Name</th>
<th className="px-4 py-3 font-bold text-right">Calls</th>
<th className="px-4 py-3 font-bold text-center">Success</th>
<th className="px-4 py-3 font-bold text-center">Failed</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-gray-800">
{agent?.tool_call_counts && Object.keys(agent.tool_call_counts).length > 0 ? (
Object.entries(agent.tool_call_counts).sort((a,b) => {
const bVal = typeof b[1] === 'object' ? b[1].calls : b[1];
const aVal = typeof a[1] === 'object' ? a[1].calls : a[1];
return bVal - aVal;
}).map(([tool, data]) => {
const calls = typeof data === 'object' ? data.calls : data;
const successes = typeof data === 'object' ? data.successes : data;
const failures = typeof data === 'object' ? data.failures : 0;
return (
<tr key={tool} className="hover:bg-gray-50 dark:hover:bg-gray-900/50 transition-colors text-sm">
<td className="px-4 py-3 text-indigo-600 dark:text-indigo-400 font-bold">{tool}</td>
<td className="px-4 py-3 text-right tabular-nums">{calls}</td>
<td className="px-4 py-3 text-center text-emerald-500 font-medium">{successes}</td>
<td className="px-4 py-3 text-center text-red-500 font-medium">{failures > 0 ? failures : '-'}</td>
</tr>
);
})
) : (
<tr>
<td colSpan="4" className="px-4 py-6 text-center text-gray-500 italic text-xs">No tool calls recorded yet</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
)}
</div>
</div>
</div>
{/* Modal Overlay Component */}
{modalConfig && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/40 backdrop-blur-sm p-4">
<div className="bg-white dark:bg-gray-800 shadow-2xl rounded-2xl w-full max-w-sm overflow-hidden border border-gray-100 dark:border-gray-700 transform transition-all p-6 relative">
<div className={`absolute top-0 right-0 w-64 h-64 ${modalConfig.type === 'error' ? 'bg-red-500/5' : 'bg-emerald-500/5'} blur-[80px] rounded-full pointer-events-none -z-10`} />
<h3 className={`text-xl font-bold mb-2 ${modalConfig.type === 'error' ? 'text-red-500' : 'text-emerald-500'}`}>{modalConfig.title}</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm mb-6 font-mono">{modalConfig.message}</p>
<div className="flex gap-3 justify-end mt-2">
<button onClick={() => setModalConfig(null)} className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors">
{modalConfig.confirmAction ? 'Cancel' : 'Close'}
</button>
{modalConfig.confirmAction && (
<button
onClick={() => {
modalConfig.confirmAction();
setModalConfig(null);
}}
className={`px-4 py-2 text-sm font-bold text-white rounded-lg shadow transition-colors ${modalConfig.type === 'error' ? 'bg-red-600 hover:bg-red-500' : 'bg-indigo-600 hover:bg-indigo-500'}`}
>
{modalConfig.confirmText || 'Confirm'}
</button>
)}
</div>
</div>
</div>
)}
</div>
);
}