import React, { useState, useEffect, useCallback } from 'react'; import { X, Settings, Bot, Zap, Shield, Wrench, Users, Server, ChevronDown, ChevronUp, Check, Loader2, FileText, Trash2, } from 'lucide-react'; // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- const PROFILE_OPTIONS = [ { value: 'fast', label: 'Fast', hint: 'Low latency, fewer tool calls' }, { value: 'balanced', label: 'Balanced', hint: 'Good mix of speed and depth' }, { value: 'quality', label: 'Quality', hint: 'Thorough, multi-step reasoning' }, ]; const BUILTIN_CAPABILITIES = [ { id: 'generate_images', label: 'Generate images' }, { id: 'generate_videos', label: 'Generate short videos' }, { id: 'analyze_documents', label: 'Analyze documents' }, { id: 'automate_external', label: 'Automate external services' }, ]; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- function SectionHeader({ icon: Icon, title, badge }) { return (
{title} {badge !== undefined && ( {badge} )}
); } function Toggle({ checked, onChange, label }) { return (); } function StatusDot({ ok }) { return (); } // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- export function AgentSettingsPanel({ project, backendUrl, apiKey, onClose, onSaved }) { const ag = project.agentic || {}; // --- Editable state --- const [name, setName] = useState(project.name || ''); const [description, setDescription] = useState(project.description || ''); const [instructions, setInstructions] = useState(project.instructions || ''); const [goal, setGoal] = useState(ag.goal || ''); const [capabilities, setCapabilities] = useState(ag.capabilities || []); const [profile, setProfile] = useState(ag.execution_profile || 'fast'); const [askFirst, setAskFirst] = useState(ag.ask_before_acting !== false); const [toolIds, setToolIds] = useState(ag.tool_ids || []); const [agentIds, setAgentIds] = useState(ag.a2a_agent_ids || []); const [toolSource, setToolSource] = useState(ag.tool_source || 'all'); // --- Catalog data --- const [catalogTools, setCatalogTools] = useState([]); const [catalogAgents, setCatalogAgents] = useState([]); const [catalogServers, setCatalogServers] = useState([]); const [catalogLoading, setCatalogLoading] = useState(true); // --- Documents --- const [documents, setDocuments] = useState(project.files || []); // --- UI state --- const [saving, setSaving] = useState(false); const [dirty, setDirty] = useState(false); const [showTools, setShowTools] = useState(false); const [showAgents, setShowAgents] = useState(false); // Track dirtiness useEffect(() => { setDirty(true); }, [name, description, instructions, goal, capabilities, profile, askFirst, toolIds, agentIds, toolSource]); // Reset dirty on initial load useEffect(() => { setDirty(false); }, []); // --- Fetch catalog --- useEffect(() => { const headers = {}; if (apiKey) headers['x-api-key'] = apiKey; try { if (typeof window !== 'undefined') { const tok = window.localStorage.getItem('homepilot_auth_token') || ''; if (tok) headers['authorization'] = `Bearer ${tok}`; } } catch { /* ignore */ } fetch(`${backendUrl}/v1/agentic/catalog`, { headers, credentials: 'include' }) .then((r) => r.ok ? r.json() : null) .then((data) => { if (data) { setCatalogServers(Array.isArray(data.servers) ? data.servers.map((s) => ({ id: String(s.id || s.name), name: String(s.name || s.id), description: s.description, enabled: s.enabled !== false, tool_ids: Array.isArray(s.tool_ids) ? s.tool_ids : (Array.isArray(s.associated_tools) ? s.associated_tools : []), })) : []); setCatalogTools(Array.isArray(data.tools) ? data.tools.map((t) => ({ id: t.id || t.name, name: t.name, description: t.description, enabled: t.enabled !== false })) : []); setCatalogAgents(Array.isArray(data.a2a_agents) ? data.a2a_agents.map((a) => ({ id: a.id || a.name, name: a.name, description: a.description, enabled: a.enabled !== false })) : []); } }) .catch(() => { }) .finally(() => setCatalogLoading(false)); }, [backendUrl, apiKey]); // --- Derived: effective access policy (matches wizard + backend enforcement) --- const enabledCatalogTools = catalogTools.filter((t) => t.enabled !== false); const serverToolCount = (() => { if (!toolSource.startsWith('server:')) return 0; const sid = toolSource.replace('server:', ''); const s = catalogServers.find((x) => x.id === sid); return s?.tool_ids?.length || 0; })(); const effectiveToolCount = (() => { if (toolSource === 'none') return 0; if (toolSource === 'all') return enabledCatalogTools.length; if (toolSource.startsWith('server:')) return serverToolCount; return 0; })(); const visibleTools = (() => { if (toolSource === 'none') return []; if (toolSource === 'all') return enabledCatalogTools; if (toolSource.startsWith('server:')) { const sid = toolSource.replace('server:', ''); const s = catalogServers.find((x) => x.id === sid); if (!s?.tool_ids?.length) return []; const ids = new Set(s.tool_ids); return enabledCatalogTools.filter((t) => ids.has(t.id)); } return []; })(); // --- Save --- const handleSave = useCallback(async () => { setSaving(true); try { const headers = { 'Content-Type': 'application/json' }; if (apiKey) headers['x-api-key'] = apiKey; // Build lookup from previously saved details so we can fall back if catalog is empty/stale const prevToolDetails = {}; for (const d of (ag.tool_details || [])) { if (d && typeof d === 'object' && d.id) prevToolDetails[d.id] = d; } const prevAgentDetails = {}; for (const d of (ag.agent_details || [])) { if (d && typeof d === 'object' && d.id) prevAgentDetails[d.id] = d; } // Resolve human-readable names for tools & agents (catalog → previous save → fallback) const toolDetails = toolIds.map((tid) => { const t = catalogTools.find((x) => x.id === tid); const prev = prevToolDetails[tid]; return { id: tid, name: t?.name || prev?.name || tid, description: t?.description || prev?.description || '', }; }); const agentDetailsList = agentIds.map((aid) => { const a = catalogAgents.find((x) => x.id === aid); const prev = prevAgentDetails[aid]; return { id: aid, name: a?.name || prev?.name || aid, description: a?.description || prev?.description || '', }; }); const body = { name, description, instructions, project_type: 'agent', agentic: { goal, capabilities, tool_ids: toolIds, a2a_agent_ids: agentIds, tool_details: toolDetails, agent_details: agentDetailsList, tool_source: toolSource, ask_before_acting: askFirst, execution_profile: profile, }, }; const res = await fetch(`${backendUrl}/projects/${project.id}`, { method: 'PUT', headers, body: JSON.stringify(body), }); if (res.ok) { const data = await res.json(); setDirty(false); onSaved(data.project); } else { alert('Failed to save project settings'); } } catch { alert('Failed to save project settings'); } finally { setSaving(false); } }, [name, description, instructions, goal, capabilities, profile, askFirst, toolIds, agentIds, toolSource, backendUrl, apiKey, project.id, onSaved, catalogTools, catalogAgents, ag.tool_details, ag.agent_details]); // --- Document delete --- const handleDeleteDoc = async (docName) => { if (!confirm(`Delete document "${docName}"?`)) return; try { const headers = {}; if (apiKey) headers['x-api-key'] = apiKey; const res = await fetch(`${backendUrl}/projects/${project.id}/documents/${encodeURIComponent(docName)}`, { method: 'DELETE', headers }); if (res.ok) setDocuments((prev) => prev.filter((d) => d.name !== docName)); } catch { /* silent */ } }; const toggleCap = (id) => { setCapabilities((prev) => prev.includes(id) ? prev.filter((c) => c !== id) : [...prev, id]); }; const toggleTool = (id) => { setToolIds((prev) => prev.includes(id) ? prev.filter((t) => t !== id) : [...prev, id]); }; const toggleAgent = (id) => { setAgentIds((prev) => prev.includes(id) ? prev.filter((a) => a !== id) : [...prev, id]); }; return (
e.stopPropagation()}> {/* ── Header ── */}

Agent Settings

Configure behavior, tools, and connections

{/* ── Content ── */}
{/* ─── Section: Identity ─── */}
setName(e.target.value)} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-2.5 text-sm text-white placeholder-white/30 focus:outline-none focus:border-purple-500/50 focus:ring-1 focus:ring-purple-500/30 transition-all"/>
setDescription(e.target.value)} placeholder="What does this agent do?" className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-2.5 text-sm text-white placeholder-white/30 focus:outline-none focus:border-purple-500/50 focus:ring-1 focus:ring-purple-500/30 transition-all"/>