From 806f4a2b3fe836b0ff77b36ba8803e756e31a809 Mon Sep 17 00:00:00 2001 From: dharaneesh-r Date: Wed, 30 Sep 2026 18:05:38 +0530 Subject: [PATCH] updates on the agentstudio --- .../agentStudio/AgentScopeNavigator.jsx | 383 ++++------- .../settings/agentStudio/AgentStudio.jsx | 10 +- .../settings/agentStudio/SkillCard.jsx | 261 ++++---- .../settings/agentStudio/SkillCardsGrid.jsx | 314 +++++---- .../settings/agentStudio/ToolRegistryView.jsx | 611 +++++++++++------- .../agentStudio/WiringStatusBanner.jsx | 115 ++-- 6 files changed, 844 insertions(+), 850 deletions(-) diff --git a/src/pages/doormile/settings/agentStudio/AgentScopeNavigator.jsx b/src/pages/doormile/settings/agentStudio/AgentScopeNavigator.jsx index 26ae870..71d22e6 100644 --- a/src/pages/doormile/settings/agentStudio/AgentScopeNavigator.jsx +++ b/src/pages/doormile/settings/agentStudio/AgentScopeNavigator.jsx @@ -2,16 +2,13 @@ import React, { useState } from 'react'; import { Sparkles, ChevronDown, - ChevronRight, - LayoutGrid, Zap, Wrench, - Pencil, - Users, - Package, - Route, - Layers, Check, + Bot, + LayoutGrid, + Shield, + Activity, } from 'lucide-react'; import { cn } from '@/lib/utils'; @@ -23,15 +20,15 @@ const STATUS_TONE = { indigo: 'bg-indigo-50 text-indigo-700 border-indigo-200', }; -// The registry's word for what an agent actually does today. Shown beside -// every agent, because a simulated agent that looks live is the failure the -// registry exists to end. function StatusBadge({ agent }) { - const meta = agent.statusMeta; + const meta = agent.statusMeta || { label: agent.status || 'Live', tone: 'emerald', hint: '' }; return ( {meta.label} @@ -48,279 +45,175 @@ export default function AgentScopeNavigator({ onSelectSkill, onSelectTool, }) { - const [surfacesOpen, setSurfacesOpen] = useState(false); - const [skillsOpen, setSkillsOpen] = useState(true); - const [agentSkillsOpen, setAgentSkillsOpen] = useState(true); - const [boardOpen, setBoardOpen] = useState(true); - const [toolsOpen, setToolsOpen] = useState(true); const [agentMenuOpen, setAgentMenuOpen] = useState(false); const currentAgent = agents.find((a) => a.id === activeAgentId) || agents[0]; if (!currentAgent) return null; - const agentSkills = skills.filter((s) => s.scope !== 'board'); - const boardSkills = skills.filter((s) => s.scope === 'board'); + + const activeSkillsCount = skills.filter((s) => s.enabled !== false).length; return ( -
- {/* 1. Agent Profile Header + Dropdown Switcher */} +
+ {/* 1. Agent Switcher */}
+ - {/* Agent Switcher Menu */} + {/* Dropdown Menu */} {agentMenuOpen && ( <>
setAgentMenuOpen(false)} /> -
+
- Switch Agent Scope + Select Agent
- {agents.map((agent) => ( - - ))} + {agents.map((agent) => { + const isSelected = agent.id === activeAgentId; + return ( + + ); + })}
)}
-
+ {/* 2. Agent Description */} + {currentAgent.description && ( +
+ {currentAgent.description} +
+ )} - {/* 2. Hierarchical Tree (Clean, un-cluttered style) */} -
- {/* SURFACES TREE NODE */} -
- - - {surfacesOpen && ( -
- {surfaces.map((surf) => ( -
- {surf.name} -
- ))} -
- )} + {/* 3. Summary Stats */} +
+
+ + Active Skills + +
+ {activeSkillsCount} + / {skills.length} +
- {/* SKILLS TREE NODE */} -
- - - {skillsOpen && ( -
- {/* Agent Skills Sub-node */} -
- - - {agentSkillsOpen && ( -
- {agentSkills.map((skill) => { - const getSkillIcon = (cat) => { - switch ((cat || '').toUpperCase()) { - case 'AUTHORING': - return Pencil; - case 'WORKFORCE': - return Users; - case 'LOGISTICS': - return Package; - case 'DISPATCH': - return Route; - default: - return Zap; - } - }; - const SkillIcon = getSkillIcon(skill.category); - - return ( - - ); - })} -
- )} -
- - {/* Board Skills Sub-node */} -
- - - {boardOpen && ( -
-

- None drawing on this agent's surfaces. -

-
- )} -
-
- )} -
- - {/* TOOLS TREE NODE */} -
- - - {toolsOpen && ( -
- {tools.map((tool) => ( - - ))} -
- )} +
+ + Linked Tools + +
+ {tools.length} + ready +
+ + {/* 4. Quick Shortcuts */} +
+
+ Agent Capabilities +
+ + + + +
+ + {/* 5. Surfaces / Touchpoints */} + {surfaces && surfaces.length > 0 && ( +
+
+ + Active Surfaces +
+
+ {surfaces.map((surf) => ( + + {surf.name} + + ))} +
+
+ )}
); } diff --git a/src/pages/doormile/settings/agentStudio/AgentStudio.jsx b/src/pages/doormile/settings/agentStudio/AgentStudio.jsx index d4c0804..8ab9954 100644 --- a/src/pages/doormile/settings/agentStudio/AgentStudio.jsx +++ b/src/pages/doormile/settings/agentStudio/AgentStudio.jsx @@ -219,13 +219,21 @@ export default function AgentStudio() { activeAgent={activeAgent} onTestSkill={handleTestSkill} onOpenDetails={(skill) => setDrawerSkillId(skill.id)} + onToggleStatus={handleToggleStatus} + canEdit={canEdit} onNewSkill={() => setCreateModalOpen(true)} canCreate={canCreate} createDisabledReason={createDisabledReason} /> )} - {activeTab === 'tools' && } + {activeTab === 'tools' && ( + + )} {activeTab === 'configure' && ( - {/* Top gradient accent bar */} -
- -
- {/* Header row: category badge + status */} -
-
+ {/* Top Header: Category Tag & Direct Status Toggle Switch */} +
+ - - {(skill.category || 'SKILL').toUpperCase().replace(/_/g, ' ')} -
+ {categoryKey.replace(/_/g, ' ')} + -
- {isEnabled ? ( - <> - - Active - - ) : ( - <> - - Disabled - - )} -
-
- - {/* Icon + Title */} -
-
- -
-
-

- {skill.title} -

-

- {skill.description} -

-
-
- - {/* Prompt pill */} - {skill.prompt && ( -
- - - “{skill.prompt}” + {/* User-friendly Direct On/Off Toggle */} +
+ + {isEnabled ? 'Active' : 'Disabled'} +
- )} +
- {/* Integration tag */} - {skill.integration && ( -
- + {/* Skill Title & Plain English Description */} +
+

+ {skill.title} +

+

+ {skill.description || 'No description provided for this skill.'} +

+
+ + {/* Linked Tools Counter & Integration info */} +
+ {toolsCount > 0 ? ( + + + {toolsCount} {toolsCount === 1 ? 'tool linked' : 'tools linked'} + + ) : ( + + No external tools + + )} + + {skill.scope === 'board' && ( + - {skill.integration} + Shared Board Skill -
- )} - - {/* Footer */} -
- - - + )}
+ + {/* Action Footer: Test and Configure */} +
+ + + +
); } diff --git a/src/pages/doormile/settings/agentStudio/SkillCardsGrid.jsx b/src/pages/doormile/settings/agentStudio/SkillCardsGrid.jsx index 1f4d4f4..6cdbac8 100644 --- a/src/pages/doormile/settings/agentStudio/SkillCardsGrid.jsx +++ b/src/pages/doormile/settings/agentStudio/SkillCardsGrid.jsx @@ -1,5 +1,5 @@ import React, { useState, useMemo } from 'react'; -import { Search, Plus, X, Layers, AlertCircle } from 'lucide-react'; +import { Search, Plus, X, AlertCircle, Filter, Zap } from 'lucide-react'; import { cn } from '@/lib/utils'; import SkillCard from './SkillCard'; @@ -8,218 +8,190 @@ export default function SkillCardsGrid({ activeAgent, onTestSkill, onOpenDetails, + onToggleStatus, onNewSkill, canCreate = true, + canEdit = true, createDisabledReason = '', }) { - const [scopeTab, setScopeTab] = useState('agent'); // 'agent' or 'board' const [searchQuery, setSearchQuery] = useState(''); const [categoryFilter, setCategoryFilter] = useState('ALL'); + const [statusFilter, setStatusFilter] = useState('ALL'); // 'ALL' | 'ACTIVE' | 'DISABLED' + const [scopeFilter, setScopeFilter] = useState('ALL'); // 'ALL' | 'AGENT' | 'BOARD' - // Filter by active agent and scope - const scopedSkills = useMemo(() => { - return skills.filter((s) => { - if (scopeTab === 'board') { - return s.scope === 'board'; - } - return s.scope !== 'board'; - }); - }, [skills, scopeTab]); + const hasBoardSkills = skills.some((s) => s.scope === 'board'); - // Categories present in the current scope + // Categories present in the skills const categories = useMemo(() => { - const counts = { ALL: scopedSkills.length }; - scopedSkills.forEach((s) => { - const cat = (s.category || 'GENERAL').toUpperCase(); - counts[cat] = (counts[cat] || 0) + 1; + const list = new Set(); + skills.forEach((s) => { + if (s.category) list.add(s.category.toUpperCase()); }); - return counts; - }, [scopedSkills]); + return Array.from(list); + }, [skills]); - // Filter by category and search query + // Filter skills const filteredSkills = useMemo(() => { - return scopedSkills.filter((s) => { - const matchesCat = - categoryFilter === 'ALL' || - (s.category || '').toUpperCase() === categoryFilter; + return skills.filter((s) => { + // Category filter + if (categoryFilter !== 'ALL' && (s.category || '').toUpperCase() !== categoryFilter) { + return false; + } + // Status filter + const isEnabled = s.enabled !== false; + if (statusFilter === 'ACTIVE' && !isEnabled) return false; + if (statusFilter === 'DISABLED' && isEnabled) return false; + + // Scope filter + if (scopeFilter === 'AGENT' && s.scope === 'board') return false; + if (scopeFilter === 'BOARD' && s.scope !== 'board') return false; + + // Search query const q = searchQuery.toLowerCase().trim(); - const matchesQuery = - !q || + if (!q) return true; + + return ( (s.title || '').toLowerCase().includes(q) || (s.description || '').toLowerCase().includes(q) || (s.category || '').toLowerCase().includes(q) || (s.integration || '').toLowerCase().includes(q) || - (s.prompt || '').toLowerCase().includes(q); - - return matchesCat && matchesQuery; + (s.prompt || '').toLowerCase().includes(q) + ); }); - }, [scopedSkills, categoryFilter, searchQuery]); + }, [skills, categoryFilter, statusFilter, scopeFilter, searchQuery]); - const enabledCount = filteredSkills.filter((s) => s.enabled !== false).length; - const agentCount = skills.filter((s) => s.scope !== 'board').length; - const boardCount = skills.filter((s) => s.scope === 'board').length; + const activeCount = skills.filter((s) => s.enabled !== false).length; return (
- {/* 1. Scope Sub-tabs (Agent Skills / Board Skills) */} -
- )} - > - Agent Skills - - {agentCount} - - +
- -
- {/* 2. Global Search Input */} -
- - setSearchQuery(e.target.value)} - placeholder="Search skills, capabilities or integrations..." - className="w-full h-11 bg-white border border-slate-200 rounded-2xl pl-11 pr-10 text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:border-brand focus:ring-2 focus:ring-brand/10 shadow-sm hover:border-slate-300 transition-all" - /> - {searchQuery && ( + {/* New Skill Button */} +
+
+ + {/* 2. Summary & Result Stats */} +
+
+ Showing + {filteredSkills.length} + of {skills.length} skills ( + {activeCount} active + ) +
+ + {(categoryFilter !== 'ALL' || statusFilter !== 'ALL' || scopeFilter !== 'ALL' || searchQuery) && ( + )}
- {/* 3. Category Filter Pills */} -
- {Object.entries(categories).map(([cat, count]) => { - const isSelected = categoryFilter === cat; - const displayLabel = cat === 'ALL' ? 'All' : (cat.charAt(0) + cat.slice(1).toLowerCase()).replace(/_/g, ' '); - - return ( - - ); - })} -
- - {/* 4. Stats + New Skill Row */} -
-
-
- Total - {filteredSkills.length} -
-
- - {enabledCount} active -
-
- - -
- - {/* 5. Responsive Skill Cards Grid */} - {scopeTab === 'board' && filteredSkills.length === 0 ? ( -
- -
No Board Skills Connected
-

- None drawing on this agent's surfaces. Connect board skills to link live operational boards with this agent. -

-
- ) : filteredSkills.length === 0 ? ( + {/* 3. Skill Cards Grid or Empty State */} + {filteredSkills.length === 0 ? (
- -
No matching capabilities found
-

- Try adjusting your search query or selecting a different category filter. + +

No matching skills found
+

+ {searchQuery + ? `No skills matched "${searchQuery}". Try a different keyword or reset filters.` + : 'There are no skills configured under the selected filter criteria.'}

) : ( @@ -230,6 +202,8 @@ export default function SkillCardsGrid({ skill={skill} onTest={onTestSkill} onDetails={onOpenDetails} + onToggleStatus={onToggleStatus} + canEdit={canEdit} /> ))}
diff --git a/src/pages/doormile/settings/agentStudio/ToolRegistryView.jsx b/src/pages/doormile/settings/agentStudio/ToolRegistryView.jsx index 902b308..36808c1 100644 --- a/src/pages/doormile/settings/agentStudio/ToolRegistryView.jsx +++ b/src/pages/doormile/settings/agentStudio/ToolRegistryView.jsx @@ -11,207 +11,262 @@ import { Bell, Database, X, - Hash, - MapPin, - Layers, + Bot, + Info, } from 'lucide-react'; import { cn } from '@/lib/utils'; -/* ─── Kind config ─────────────────────────────────────────────────────────── */ -const KIND = { +/* ─── Friendly Tool Dictionary ────────────────────────────────────────────── */ +const TOOL_DICTIONARY = { + alert_low_battery_rider: { + title: 'Rider Battery Warning', + desc: 'Sends an urgent notification to riders with low vehicle battery to recharge or report to a hub.', + category: 'Fleet & Riders', + }, + assign_express_batch: { + title: 'Assign Express Batch', + desc: 'Automatically assigns grouped high-priority express orders to optimal nearby riders.', + category: 'Orders & Dispatch', + }, + assign_riders: { + title: 'Assign Orders to Riders', + desc: 'Dispatches and matches unassigned customer orders with active, available delivery riders.', + category: 'Orders & Dispatch', + }, + create_single_order: { + title: 'Create Express Booking', + desc: 'Creates an on-demand express delivery booking upon operator review and confirmation.', + category: 'Orders & Dispatch', + }, + decide_assignment_failure: { + title: 'Diagnose Assignment Failure', + desc: 'Analyzes why an order failed assignment and recommends an immediate resolution.', + category: 'Analysis & Diagnostics', + }, + decide_stall_response: { + title: 'Resolve Stalled Rider', + desc: 'Detects riders stopped mid-route and initiates automatic reassignment or customer updates.', + category: 'Analysis & Diagnostics', + }, + dispatch_hub_idle_parcels: { + title: 'Dispatch Idle Hub Parcels', + desc: 'Coordinates idle riders to pick up parcels waiting at crowded hubs.', + category: 'Orders & Dispatch', + }, + enforce_cash_handoff: { + title: 'Enforce Cash Hand-off', + desc: 'Routes riders carrying high Cash-on-Delivery balances to deposit cash at the nearest hub.', + category: 'Fleet & Riders', + }, + enforce_otp_verification: { + title: 'Require Delivery OTP', + desc: 'Enforces one-time password verification before handing over high-value COD orders.', + category: 'Fleet & Riders', + }, + nearby_milers: { + title: 'Locate Nearby Riders', + desc: 'Searches real-time GPS locations to find active delivery riders within a specified radius.', + category: 'Fleet & Riders', + }, + notify_customer: { + title: 'Send Customer Update', + desc: 'Sends automated SMS or WhatsApp status notifications to the recipient about order delays or arrivals.', + category: 'Customer Alerts', + }, + publish_miler_stalled: { + title: 'Broadcast Delay Event', + desc: 'Broadcasts a stalled delivery alert to the operations team dashboard.', + category: 'Customer Alerts', + }, + reassign_booking: { + title: 'Reassign to New Rider', + desc: 'Transfers a stalled or delayed order to an alternate available rider to protect delivery SLA.', + category: 'Orders & Dispatch', + }, + rebalance_riders: { + title: 'Rebalance Fleet Zones', + desc: 'Directs idle riders from low-demand areas to high-volume delivery zones.', + category: 'Fleet & Riders', + }, + scan_bookings: { + title: 'Scan Pending Orders', + desc: 'Continuously monitors order queues for delayed or unassigned shipments.', + category: 'Orders & Dispatch', + }, + simulate_pricing_quote: { + title: 'Calculate Delivery Quote', + desc: 'Estimates accurate delivery fares and ETA based on routed distance and vehicle type.', + category: 'Pricing & Route', + }, + trigger_auto_dispatch: { + title: 'Trigger Auto-Dispatch', + desc: 'Automatically pairs waiting orders with the closest available riders during peak rush.', + category: 'Orders & Dispatch', + }, +}; + +const CATEGORIES = [ + 'Orders & Dispatch', + 'Fleet & Riders', + 'Customer Alerts', + 'Analysis & Diagnostics', +]; + +/** Convert snake_case code into clean Title Case if not in dictionary */ +function formatToolTitle(rawName) { + if (TOOL_DICTIONARY[rawName]?.title) return TOOL_DICTIONARY[rawName].title; + return rawName + .split('_') + .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) + .join(' '); +} + +/** Sanitize developer descriptions into clean operational text */ +function formatToolDescription(tool) { + if (TOOL_DICTIONARY[tool.name]?.desc) return TOOL_DICTIONARY[tool.name].desc; + let text = tool.description || 'No description provided.'; + text = text.replace(/REVIEW ONLY\.\s*/gi, ''); + text = text.replace(/No executor\.\s*/gi, ''); + text = text.replace(/\(structured output\)\s*/gi, ''); + text = text.replace(/POST\s+[^\s]+\s+accepts[^\.]+\.\s*/gi, ''); + text = text.replace(/NOT IMPLEMENTED:[^\.]+\.\s*/gi, ''); + return text.trim() || 'Executes this automated workflow action.'; +} + +const KIND_META = { read: { icon: Eye, - label: 'Read', - card: 'border-sky-200 bg-sky-50/40', - iconWrap: 'bg-sky-100 text-sky-600', - badge: 'bg-sky-100 text-sky-700 border-sky-200', - dot: 'bg-sky-500', + label: 'Lookup', + badge: 'bg-sky-50 text-sky-700 border-sky-200', }, write: { icon: Edit3, - label: 'Write', - card: 'border-violet-200 bg-violet-50/30', - iconWrap: 'bg-violet-100 text-violet-600', - badge: 'bg-violet-100 text-violet-700 border-violet-200', - dot: 'bg-violet-500', + label: 'Action', + badge: 'bg-violet-50 text-violet-700 border-violet-200', }, notify: { icon: Bell, - label: 'Notify', - card: 'border-amber-200 bg-amber-50/30', - iconWrap: 'bg-amber-100 text-amber-600', - badge: 'bg-amber-100 text-amber-700 border-amber-200', - dot: 'bg-amber-500', + label: 'Alert', + badge: 'bg-amber-50 text-amber-700 border-amber-200', }, }; -const KIND_DEFAULT = { + +const DEFAULT_KIND_META = { icon: Database, - label: 'General', - card: 'border-slate-200 bg-white', - iconWrap: 'bg-slate-100 text-slate-500', - badge: 'bg-slate-100 text-slate-600 border-slate-200', - dot: 'bg-slate-400', + label: 'Action', + badge: 'bg-slate-100 text-slate-700 border-slate-200', }; -function kindOf(tool) { - return KIND[(tool.kind || '').toLowerCase()] || KIND_DEFAULT; -} - -/* ─── Stat chip ───────────────────────────────────────────────────────────── */ -function StatChip({ label, value, tone = 'slate' }) { - const tones = { - slate: 'bg-slate-100 text-slate-700 border-slate-200', - amber: 'bg-amber-50 text-amber-700 border-amber-200', - emerald: 'bg-emerald-50 text-emerald-700 border-emerald-200', - sky: 'bg-sky-50 text-sky-700 border-sky-200', - }; - return ( -
- {value} - {label} -
- ); -} - -/* ─── Parameter pill ──────────────────────────────────────────────────────── */ -function ParamPill({ param }) { - return ( -
-
- {param.name} -
- - {param.type} - - {param.required && ( - - req - - )} -
-
- {param.description && ( -

{param.description}

- )} - {param.default !== undefined && ( -
- default: {String(param.default)} -
- )} -
- ); -} - -/* ─── Tool Card ───────────────────────────────────────────────────────────── */ -function ToolCard({ tool }) { +function ToolRow({ tool }) { const [open, setOpen] = useState(false); - const k = kindOf(tool); - const KindIcon = k.icon; + const meta = KIND_META[(tool.kind || '').toLowerCase()] || DEFAULT_KIND_META; + const KindIcon = meta.icon; + const friendlyTitle = formatToolTitle(tool.name); + const friendlyDesc = formatToolDescription(tool); const hasParams = tool.parameters && tool.parameters.length > 0; return (
- {/* Card header — always visible */} - {/* Expandable detail panel */} {open && ( -
- {/* Meta grid */} - {(tool.target || tool.implementedAt || tool.parentSkill) && ( -
- {tool.target && ( -
- -
-
Touches
-
{tool.target}
-
-
- )} - {tool.implementedAt && ( -
- -
-
Implemented at
-
{tool.implementedAt}
-
-
- )} +
+
+ Detailed Action: + {friendlyDesc} +
+ + {tool.parentSkill && ( +
+ Used by Skills: + {tool.parentSkill}
)} - {/* Parameters */} - {hasParams ? ( -
-
+ {hasParams && ( +
+
- Parameters + Required Inputs ({tool.parameters.length})
- {tool.parameters.map((p) => )} + {tool.parameters.map((p) => ( +
+
+ {p.name} + + {p.type || 'text'} + +
+ {p.description && ( +

{p.description}

+ )} +
+ ))}
- ) : ( -

No parameters — input is open-ended.

)}
)} @@ -219,131 +274,201 @@ function ToolCard({ tool }) { ); } -/* ─── Main view ───────────────────────────────────────────────────────────── */ -export default function ToolRegistryView({ tools = [] }) { +export default function ToolRegistryView({ + tools = [], + agentTools = [], + activeAgent = null, +}) { + const [scope, setScope] = useState('AGENT'); // 'AGENT' | 'ALL' const [search, setSearch] = useState(''); - const [kindFilter, setKindFilter] = useState('all'); + const [categoryFilter, setCategoryFilter] = useState('Orders & Dispatch'); - const counts = useMemo(() => { - const c = { all: tools.length }; - tools.forEach((t) => { - const k = (t.kind || 'read').toLowerCase(); - c[k] = (c[k] || 0) + 1; - }); - return c; - }, [tools]); - - const kindTabs = ['all', ...Object.keys(KIND).filter((k) => counts[k])]; + // If the agent has no linked tools, default to showing all tools + const effectiveScope = scope === 'AGENT' && agentTools.length > 0 ? 'AGENT' : 'ALL'; + const baseTools = effectiveScope === 'AGENT' ? agentTools : tools; const filtered = useMemo(() => { const q = search.toLowerCase().trim(); - return tools.filter((t) => { - const matchesKind = kindFilter === 'all' || (t.kind || 'read').toLowerCase() === kindFilter; - const matchesSearch = - !q || - t.name.toLowerCase().includes(q) || - t.description.toLowerCase().includes(q) || - (t.parentSkill || '').toLowerCase().includes(q) || - (t.target || '').toLowerCase().includes(q); - return matchesKind && matchesSearch; - }); - }, [tools, kindFilter, search]); + return baseTools.filter((t) => { + // In ALL tools mode, filter by selected category unless searching + if (effectiveScope === 'ALL' && !q) { + const cat = TOOL_DICTIONARY[t.name]?.category || 'Orders & Dispatch'; + if (cat !== categoryFilter) return false; + } - const gatedCount = tools.filter((t) => t.requiresConfirmation).length; - const openCount = tools.length - gatedCount; + if (!q) return true; + const friendlyTitle = formatToolTitle(t.name).toLowerCase(); + const friendlyDesc = formatToolDescription(t).toLowerCase(); + return ( + t.name.toLowerCase().includes(q) || + friendlyTitle.includes(q) || + friendlyDesc.includes(q) + ); + }); + }, [baseTools, effectiveScope, categoryFilter, search]); + + const agentName = activeAgent?.name || 'Agent'; return ( -
- {/* ── Stats row ─────────────────────────────────────────────────── */} -
- - - - - +
+ {/* 1. Scope Selector Tabs: Agent Tools vs All Tools */} +
+
+ - {/* Right-side header text */} -
-

- - Agent Tool Registry -

-

- Every capability a skill may call. Write & notify tools need operator confirmation unless the agent is autonomous. -

+ +
+ + {/* Helper Note */} +
+ + Tools are automated actions agents can execute in your system.
- {/* ── Search + Kind filter ───────────────────────────────────────── */} -
- {/* Search */} + {/* 2. Search & Category Filters */} +
+ {/* Search Input */}
setSearch(e.target.value)} - placeholder="Search by name, target system, or description…" - className="w-full h-10 bg-white border border-slate-200 rounded-xl pl-10 pr-9 text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:border-brand focus:ring-2 focus:ring-brand/10 shadow-sm transition-all" + placeholder="Search tools by action, rider, booking..." + className="w-full h-10 bg-white border border-slate-200 rounded-xl pl-10 pr-9 text-xs sm:text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:border-brand focus:ring-2 focus:ring-brand/10 shadow-2xs hover:border-slate-300 transition-all" /> {search && ( )}
- {/* Kind filter tabs */} -
- {kindTabs.map((k) => { - const meta = KIND[k]; - const isActive = kindFilter === k; - return ( - - ); - })} -
+ {cat} + + ); + })} +
+ )}
- {/* ── Tool Cards ────────────────────────────────────────────────── */} - {filtered.length === 0 ? ( -
- -

No tools match your filter

+ {/* 3. Result Count */} +
+
+ Showing + {filtered.length} + + {effectiveScope === 'ALL' && !search + ? ` tools in ${categoryFilter}` + : ` of ${baseTools.length} ${effectiveScope === 'AGENT' ? `${agentName} tools` : 'tools'}`} + +
+ + {search && ( + )} +
+ + {/* 4. Tool List */} + {filtered.length === 0 ? ( +
+ +
No tools found
+

+ {search + ? `No tools matched "${search}".` + : `No tools configured under ${categoryFilter}.`} +

+ {search && ( + + )}
) : ( -
- {filtered.map((tool) => )} +
+ {filtered.map((tool) => ( + + ))}
)}
diff --git a/src/pages/doormile/settings/agentStudio/WiringStatusBanner.jsx b/src/pages/doormile/settings/agentStudio/WiringStatusBanner.jsx index e276a01..596d2f9 100644 --- a/src/pages/doormile/settings/agentStudio/WiringStatusBanner.jsx +++ b/src/pages/doormile/settings/agentStudio/WiringStatusBanner.jsx @@ -1,14 +1,12 @@ -import React from 'react'; -import { AlertTriangle, CheckCircle2, Info, XCircle } from 'lucide-react'; +import React, { useState } from 'react'; +import { AlertTriangle, CheckCircle2, Info, XCircle, ChevronDown, ChevronUp, Cpu } from 'lucide-react'; import { useAiStatus } from '@/lib/doormileHooks'; +import { cn } from '@/lib/utils'; /** - * What this page's settings actually drive, from GET /admin/ai/status — not a - * fixed note. It used to say "Half wired" forever, including after AI_engine - * and the backend were deployed, because nothing checked. - * - * Green only when AI_engine has read the registry recently (the backend stamps - * every poll) and the Test tab has a model. Re-checks every 30 seconds. + * What this page's settings actually drive, from GET /admin/ai/status. + * Designed with a clean, unobtrusive status bar that can be expanded + * for detailed diagnostics when needed. */ function ago(iso) { @@ -49,11 +47,13 @@ export function wiringLines(status) { export default function WiringStatusBanner() { const { data, isLoading, isError, error } = useAiStatus(); + const [expanded, setExpanded] = useState(false); if (isLoading) { return ( -
- Checking what is connected… +
+ + Checking system connectivity…
); } @@ -61,17 +61,15 @@ export default function WiringStatusBanner() { if (isError) { const notDeployed = error?.response?.status === 404; return ( -
- -

- Connection status unavailable.{' '} - {notDeployed - ? 'This API server does not report agent status yet — deploy the current doormile_backend build. Changes here are still saved and audited.' - : 'The status check failed; it will retry shortly. Changes here are still saved and audited.'} -

+
+
+ + + {notDeployed + ? 'API status reporting is starting up. Changes made here are saved safely.' + : 'Status check retrying shortly. Changes made here are saved safely.'} + +
); } @@ -81,28 +79,63 @@ export default function WiringStatusBanner() { return (
-

- {allOk ? 'All connected.' : 'Partly connected.'} -

-
    - {lines.map((l) => { - const Icon = l.info ? Info : l.ok ? CheckCircle2 : XCircle; - const color = l.info ? 'text-slate-400' : l.ok ? 'text-emerald-600' : 'text-amber-600'; - return ( -
  • - - {l.text} -
  • - ); - })} -
+ {/* Sleek top summary bar */} +
+
+ + + {allOk ? 'AI Engine Active' : 'Agent Registry Synced'} + + • + + {allOk + ? 'All configured skills and tools are actively wired' + : 'Settings are saved to registry; agent runtimes sync automatically'} + +
+ + +
+ + {/* Collapsible diagnostic details */} + {expanded && ( +
+
+ Connection Diagnostics +
+
    + {lines.map((l) => { + const Icon = l.info ? Info : l.ok ? CheckCircle2 : XCircle; + const color = l.info ? 'text-slate-400' : l.ok ? 'text-emerald-600' : 'text-amber-500'; + return ( +
  • + + {l.text} +
  • + ); + })} +
+
+ )}
); }