diff --git a/tavro_app/src/components/AgentHeader.tsx b/tavro_app/src/components/AgentHeader.tsx index 0f93252..fca5bfe 100644 --- a/tavro_app/src/components/AgentHeader.tsx +++ b/tavro_app/src/components/AgentHeader.tsx @@ -1,9 +1,22 @@ import React from 'react'; import { AgentData } from '../types/agent'; -import { Bot, ExternalLink, Globe, BookOpen, ShieldAlert, CheckCircle2 } from 'lucide-react'; +import { Bot, ExternalLink, Globe, BookOpen, ShieldAlert, CheckCircle2, Loader2 } from 'lucide-react'; import { getAgentRiskLevel } from '../utils/agentRisk'; -interface AgentHeaderProps { agent: AgentData; } +type AgentInlineField = 'name' | 'description' | 'instruction'; + +interface AgentHeaderProps { + agent: AgentData; + isEditing?: boolean; + editName?: string; + onEditNameChange?: (v: string) => void; + inlineEdit?: { field: AgentInlineField; value: string } | null; + inlineSaving?: AgentInlineField | null; + onStartInlineEdit?: (field: AgentInlineField) => void; + onInlineValueChange?: (value: string) => void; + onSaveInlineEdit?: () => void; + onCancelInlineEdit?: () => void; +} const Badge: React.FC<{ text: string; color?: 'blue' | 'emerald' | 'amber' | 'rose' | 'slate' }> = ({ text, color = 'slate' }) => { const cls = { @@ -16,7 +29,18 @@ const Badge: React.FC<{ text: string; color?: 'blue' | 'emerald' | 'amber' | 'ro return {text}; }; -const AgentHeader: React.FC = ({ agent }) => { +const AgentHeader: React.FC = ({ + agent, + isEditing, + editName, + onEditNameChange, + inlineEdit, + inlineSaving, + onStartInlineEdit, + onInlineValueChange, + onSaveInlineEdit, + onCancelInlineEdit, +}) => { const id = agent.identification; const caps = agent.capabilities; @@ -43,6 +67,9 @@ const AgentHeader: React.FC = ({ agent }) => { : riskLevel === 'medium' ? 'text-amber-600' : 'text-emerald-600'; + const isInlineName = inlineEdit?.field === 'name'; + const isSavingName = inlineSaving === 'name'; + const nameSaveDisabled = isSavingName || !inlineEdit?.value.trim(); return (
@@ -50,8 +77,51 @@ const AgentHeader: React.FC = ({ agent }) => {
-
-

{agent.name}

+
+ {isEditing ? ( + onEditNameChange?.(e.target.value)} + className="text-2xl font-bold text-slate-800 tracking-tight w-full border-b-2 border-blue-400 bg-transparent outline-none pb-0.5" + /> + ) : isInlineName && inlineEdit ? ( +
+ onInlineValueChange?.(e.target.value)} + className="text-2xl font-bold text-slate-800 tracking-tight flex-1 border-b-2 border-blue-400 bg-transparent outline-none pb-0.5" + autoFocus + /> + + +
+ ) : ( +

onStartInlineEdit?.('name')} + title="Double-click to edit" + className="text-2xl font-bold text-slate-800 tracking-tight break-words cursor-text rounded-lg hover:bg-blue-50/50 transition-colors" + > + {agent.name} +

+ )}
{id?.agent_id || 'N/A'} diff --git a/tavro_app/src/components/AgentIdentificationTab.tsx b/tavro_app/src/components/AgentIdentificationTab.tsx index d6b9670..feaa903 100644 --- a/tavro_app/src/components/AgentIdentificationTab.tsx +++ b/tavro_app/src/components/AgentIdentificationTab.tsx @@ -1,15 +1,67 @@ import React, { useEffect, useRef, useState } from 'react'; import { AgentData } from '../types/agent'; -import { User, Tag, ChevronDown, ChevronUp } from 'lucide-react'; +import { User, Tag, ChevronDown, ChevronUp, Loader2 } from 'lucide-react'; -interface AgentIdentificationTabProps { agent: AgentData; } +type AgentInlineField = 'name' | 'description' | 'instruction'; -export const AgentIdentificationTab: React.FC = ({ agent }) => { +interface AgentIdentificationTabProps { + agent: AgentData; + isEditing?: boolean; + editDescription?: string; + onEditDescriptionChange?: (v: string) => void; + editInstruction?: string; + onEditInstructionChange?: (v: string) => void; + inlineEdit?: { field: AgentInlineField; value: string } | null; + inlineSaving?: AgentInlineField | null; + onStartInlineEdit?: (field: AgentInlineField) => void; + onInlineValueChange?: (value: string) => void; + onSaveInlineEdit?: () => void; + onCancelInlineEdit?: () => void; +} + +export const AgentIdentificationTab: React.FC = ({ + agent, isEditing, + editDescription, onEditDescriptionChange, + editInstruction, onEditInstructionChange, + inlineEdit, inlineSaving, onStartInlineEdit, + onInlineValueChange, onSaveInlineEdit, onCancelInlineEdit, +}) => { const [instrOpen, setInstrOpen] = useState(false); const [instrOverflow, setInstrOverflow] = useState(false); const instructionContainerRef = useRef(null); const id = agent.identification; - const COLLAPSED_MAX_HEIGHT_PX = 256; // max-h-64 + const COLLAPSED_MAX_HEIGHT_PX = 128; // max-h-32 + const isInlineDescription = inlineEdit?.field === 'description'; + const isInlineInstruction = inlineEdit?.field === 'instruction'; + + const renderInlineActions = (field: AgentInlineField) => { + const isSaving = inlineSaving === field; + const isBlank = !inlineEdit?.value.trim(); + const saveDisabled = isSaving || isBlank; + + return ( +
+ + +
+ ); + }; useEffect(() => { const node = instructionContainerRef.current; @@ -26,9 +78,33 @@ export const AgentIdentificationTab: React.FC = ({ Identification & Role -

- {agent.description} -

+ {isEditing ? ( +