= ({ 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 ? (
+
diff --git a/tavro_app/src/components/AgentView.tsx b/tavro_app/src/components/AgentView.tsx
index f643c44..85fdd21 100644
--- a/tavro_app/src/components/AgentView.tsx
+++ b/tavro_app/src/components/AgentView.tsx
@@ -10,9 +10,24 @@ import AgentLineage from './AgentLineage';
import AgentRiskSummary from './AgentRiskSummary';
import AgentContextGraph from './AgentContextGraphRF';
+type AgentInlineField = 'name' | 'description' | 'instruction';
+
interface AgentViewProps {
agent: AgentData;
onBusinessImpactChange?: (snapshot: AgentBusinessImpactSnapshot) => void;
+ isEditing?: boolean;
+ editName?: string;
+ onEditNameChange?: (v: string) => void;
+ 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;
}
type TabType =
@@ -32,7 +47,14 @@ const BASE_TABS: { id: TabType; label: string }[] = [
{ id: 'CONTEXT', label: 'Context Graph' },
];
-const AgentView: React.FC
= ({ agent, onBusinessImpactChange }) => {
+const AgentView: React.FC = ({
+ agent, onBusinessImpactChange,
+ isEditing, editName, onEditNameChange,
+ editDescription, onEditDescriptionChange,
+ editInstruction, onEditInstructionChange,
+ inlineEdit, inlineSaving, onStartInlineEdit,
+ onInlineValueChange, onSaveInlineEdit, onCancelInlineEdit,
+}) => {
const [activeTab, setActiveTab] = useState('IDENTIFICATION');
const agentId = agent.identification?.agent_id;
@@ -46,7 +68,18 @@ const AgentView: React.FC = ({ agent, onBusinessImpactChange })
{/* Header (always visible) */}
{/* Tab Navigation */}
@@ -67,7 +100,22 @@ const AgentView: React.FC = ({ agent, onBusinessImpactChange })
{/* Tab Content */}
- {activeTab === 'IDENTIFICATION' &&
}
+ {activeTab === 'IDENTIFICATION' && (
+
+ )}
{activeTab === 'CONFIG' &&
}
diff --git a/tavro_app/src/components/ChatPanel.tsx b/tavro_app/src/components/ChatPanel.tsx
index 0c3ea8b..f0d1684 100644
--- a/tavro_app/src/components/ChatPanel.tsx
+++ b/tavro_app/src/components/ChatPanel.tsx
@@ -894,7 +894,6 @@ const ChatPanel: React.FC
= ({ onClose }) => {
-
Tavro AI Assistant
{getContextBadge(viewType, viewData) && (
{getContextBadge(viewType, viewData)}
diff --git a/tavro_app/src/components/Layout.tsx b/tavro_app/src/components/Layout.tsx
index b8d10f6..2b6437a 100644
--- a/tavro_app/src/components/Layout.tsx
+++ b/tavro_app/src/components/Layout.tsx
@@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
import {
- Home, Bot, Workflow, BarChart2, Settings,
+ Bot, Workflow, BarChart2, Settings,
LogOut, Database, RefreshCw, ClipboardList, MessageCircle, X, Terminal,
ChevronLeft, ChevronRight, FlaskConical, Scale, ShieldCheck,
AppWindow, Paperclip, Network, Zap, Plug, CircleHelp
@@ -185,17 +185,6 @@ const Layout: React.FC = () => {
{/* Nav links */}
-
-
@@ -846,9 +992,10 @@ const BusinessApplicationViewPage: React.FC = () => {
className={textAreaCls}
/>
) : (
-
- {form.vendor_description || 'N/A'}
-
+ renderInlineEditable('vendor_description', form.vendor_description || 'N/A', {
+ kind: 'textarea',
+ className: 'text-sm text-slate-700 bg-slate-50 border border-slate-200 rounded-xl px-3.5 py-2.5 min-h-[84px]',
+ })
)}
@@ -871,9 +1018,11 @@ const BusinessApplicationViewPage: React.FC = () => {
className={inputCls}
/>
) : (
-
- {form[field as keyof ApplicationFormState] || 'N/A'}
-
+ renderInlineEditable(
+ field as ApplicationInlineField,
+ form[field as keyof ApplicationFormState] || 'N/A',
+ { className: 'text-sm text-slate-700 bg-slate-50 border border-slate-200 rounded-xl px-3.5 py-2.5 break-all' },
+ )
)}
))}
@@ -890,9 +1039,10 @@ const BusinessApplicationViewPage: React.FC = () => {
{YES_NO_NONE_OPTIONS.map(opt =>
)}
) : (
-
- {labelFromOptions(form.is_current_version_supported, YES_NO_NONE_OPTIONS)}
-
+ renderInlineEditable('is_current_version_supported', labelFromOptions(form.is_current_version_supported, YES_NO_NONE_OPTIONS), {
+ kind: 'select',
+ options: YES_NO_NONE_OPTIONS,
+ })
)}
diff --git a/tavro_app/src/pages/BusinessProcessViewPage.tsx b/tavro_app/src/pages/BusinessProcessViewPage.tsx
index bd8c7c5..3bea4db 100644
--- a/tavro_app/src/pages/BusinessProcessViewPage.tsx
+++ b/tavro_app/src/pages/BusinessProcessViewPage.tsx
@@ -88,6 +88,21 @@ interface ProcessFormState {
process_health_state: string;
}
+type ProcessInlineField =
+ | 'process_number'
+ | 'process_name'
+ | 'process_description'
+ | 'parent_process_id'
+ | 'stakeholders'
+ | 'owner'
+ | 'operators'
+ | 'business_criticality'
+ | 'reputational_impact'
+ | 'financial_impact'
+ | 'regulatory_impact'
+ | 'sla'
+ | 'process_health_state';
+
const HINTS: Record
= {
business_criticality:
'Business Criticality indicates how essential a process is. Tier 1 is mission-critical and Tier 4 has minimal business impact.',
@@ -266,6 +281,8 @@ const BusinessProcessViewPage: React.FC = () => {
const [tab, setTab] = useState('overview');
const [editing, setEditing] = useState(isCreateMode);
const [saving, setSaving] = useState(false);
+ const [inlineEdit, setInlineEdit] = useState<{ field: ProcessInlineField; value: string } | null>(null);
+ const [inlineSaving, setInlineSaving] = useState(null);
const [generatingDescription, setGeneratingDescription] = useState(false);
const [deleting, setDeleting] = useState(false);
@@ -337,6 +354,7 @@ const BusinessProcessViewPage: React.FC = () => {
setProcess(null);
setForm(emptyForm());
setEditing(true);
+ setInlineEdit(null);
setAttemptedSave(false);
setLoading(false);
setTab('overview');
@@ -345,6 +363,7 @@ const BusinessProcessViewPage: React.FC = () => {
return;
}
setEditing(false);
+ setInlineEdit(null);
load();
}, [id, isCreateMode]);
@@ -424,6 +443,124 @@ const BusinessProcessViewPage: React.FC = () => {
setForm(prev => ({ ...prev, [key]: value }));
};
+ const startInlineEdit = (field: ProcessInlineField) => {
+ if (editing || isCreateMode || saving || inlineSaving) return;
+ setActionError(null);
+ setInlineEdit({ field, value: form[field] });
+ };
+
+ const cancelInlineEdit = () => {
+ setInlineEdit(null);
+ setActionError(null);
+ };
+
+ const saveInlineEdit = async () => {
+ if (!process || !inlineEdit) return;
+ const nextForm = { ...form, [inlineEdit.field]: inlineEdit.value };
+ if (!nextForm.process_name.trim()) {
+ setActionError('Process Name is required.');
+ return;
+ }
+
+ setInlineSaving(inlineEdit.field);
+ setActionError(null);
+ try {
+ const updated = await businessRelationsApi.updateProcess(
+ process.business_process_id,
+ buildProcessPayload(nextForm),
+ );
+ setProcess(updated);
+ setForm(formFromProcess(updated));
+ setInlineEdit(null);
+ setAttemptedSave(false);
+ } catch (err: any) {
+ setActionError(err.message || 'Failed to save process field');
+ } finally {
+ setInlineSaving(null);
+ }
+ };
+
+ const renderInlineEditable = (
+ field: ProcessInlineField,
+ displayValue: string,
+ config: { kind?: 'text' | 'textarea' | 'select'; options?: Option[]; className?: string; selectChildren?: React.ReactNode } = {},
+ ) => {
+ const isActive = inlineEdit?.field === field;
+ const kind = config.kind ?? 'text';
+ const valueClass = config.className ?? 'text-sm text-slate-700 bg-slate-50 border border-slate-200 rounded-xl px-3.5 py-2.5';
+ const isSavingField = inlineSaving === field;
+ const saveDisabled = isSavingField || (field === 'process_name' && !inlineEdit?.value.trim());
+
+ if (!editing && !isCreateMode && isActive) {
+ return (
+
+ );
+ }
+
+ return (
+ startInlineEdit(field)}
+ title="Double-click to edit"
+ className={`${valueClass} ${!editing && !isCreateMode ? 'cursor-text hover:border-blue-200 hover:bg-blue-50/40 transition-colors' : ''}`}
+ >
+ {displayValue}
+
+ );
+ };
+
const handleSuggestDescription = async () => {
if (!form.process_name.trim()) {
setActionError('Process Name is required before generating the description.');
@@ -483,6 +620,7 @@ const BusinessProcessViewPage: React.FC = () => {
setProcess(updated);
setForm(formFromProcess(updated));
setAttemptedSave(false);
+ setInlineEdit(null);
setEditing(false);
} catch (err: any) {
setActionError(err.message || 'Failed to save process');
@@ -493,6 +631,7 @@ const BusinessProcessViewPage: React.FC = () => {
const handleCancelEdit = () => {
setActionError(null);
+ setInlineEdit(null);
setAttemptedSave(false);
if (isCreateMode) {
if (linkUseCaseId) {
@@ -647,15 +786,15 @@ const BusinessProcessViewPage: React.FC = () => {
disabled={saving}
className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-sm font-bold border border-slate-200 bg-white text-slate-700 hover:bg-slate-50"
>
- Cancel
+ {isCreateMode ? 'Cancel' : 'Discard'}
{saving ? : }
- {isCreateMode ? 'Create Process' : 'Save Changes'}
+ {isCreateMode ? 'Create Process' : 'Save'}
>
) : (
@@ -664,6 +803,7 @@ const BusinessProcessViewPage: React.FC = () => {
onClick={() => {
setTab('overview');
setAttemptedSave(false);
+ setInlineEdit(null);
setEditing(true);
}}
className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-sm font-bold border border-slate-200 bg-white text-slate-700 hover:bg-slate-50"
@@ -811,9 +951,7 @@ const BusinessProcessViewPage: React.FC = () => {
className={inputCls}
/>
) : (
-
- {form.process_number || 'N/A'}
-
+ renderInlineEditable('process_number', form.process_number || 'N/A')
)}
@@ -836,9 +974,7 @@ const BusinessProcessViewPage: React.FC = () => {
)}
>
) : (
-
- {form.process_name || 'N/A'}
-
+ renderInlineEditable('process_name', form.process_name || 'N/A')
)}
@@ -875,9 +1011,10 @@ const BusinessProcessViewPage: React.FC = () => {
disabled={generatingDescription}
/>
) : (
-
- {form.process_description || 'N/A'}
-
+ renderInlineEditable('process_description', form.process_description || 'N/A', {
+ kind: 'textarea',
+ className: 'text-sm text-slate-700 bg-slate-50 border border-slate-200 rounded-xl px-3.5 py-2.5 min-h-[84px]',
+ })
)}
@@ -901,11 +1038,25 @@ const BusinessProcessViewPage: React.FC = () => {
))}
) : (
-
- {form.parent_process_id
+ renderInlineEditable(
+ 'parent_process_id',
+ form.parent_process_id
? `${processNameById.get(form.parent_process_id) || form.parent_process_id} (${form.parent_process_id})`
- : 'N/A'}
-
+ : 'N/A',
+ {
+ kind: 'select',
+ selectChildren: (
+ <>
+
+ {selectableParents.map(parent => (
+
+ ))}
+ >
+ ),
+ },
+ )
)}
@@ -923,9 +1074,10 @@ const BusinessProcessViewPage: React.FC = () => {
className={inputCls}
/>
) : (
-
- {form[field as keyof ProcessFormState] || 'N/A'}
-
+ renderInlineEditable(
+ field as ProcessInlineField,
+ form[field as keyof ProcessFormState] || 'N/A',
+ )
)}
))}
@@ -948,9 +1100,10 @@ const BusinessProcessViewPage: React.FC = () => {
))}
) : (
-
- {labelFromOptions(form.business_criticality, BUSINESS_CRITICALITY_OPTIONS)}
-
+ renderInlineEditable('business_criticality', labelFromOptions(form.business_criticality, BUSINESS_CRITICALITY_OPTIONS), {
+ kind: 'select',
+ options: BUSINESS_CRITICALITY_OPTIONS,
+ })
)}
@@ -968,9 +1121,10 @@ const BusinessProcessViewPage: React.FC = () => {
))}
) : (
-
- {labelFromOptions(form.reputational_impact, REPUTATIONAL_IMPACT_OPTIONS)}
-
+ renderInlineEditable('reputational_impact', labelFromOptions(form.reputational_impact, REPUTATIONAL_IMPACT_OPTIONS), {
+ kind: 'select',
+ options: REPUTATIONAL_IMPACT_OPTIONS,
+ })
)}
@@ -993,9 +1147,10 @@ const BusinessProcessViewPage: React.FC = () => {
))}
) : (
-
- {labelFromOptions(form.financial_impact, FINANCIAL_IMPACT_OPTIONS)}
-
+ renderInlineEditable('financial_impact', labelFromOptions(form.financial_impact, FINANCIAL_IMPACT_OPTIONS), {
+ kind: 'select',
+ options: FINANCIAL_IMPACT_OPTIONS,
+ })
)}
@@ -1013,9 +1168,10 @@ const BusinessProcessViewPage: React.FC = () => {
))}
) : (
-
- {labelFromOptions(form.regulatory_impact, REGULATORY_IMPACT_OPTIONS)}
-
+ renderInlineEditable('regulatory_impact', labelFromOptions(form.regulatory_impact, REGULATORY_IMPACT_OPTIONS), {
+ kind: 'select',
+ options: REGULATORY_IMPACT_OPTIONS,
+ })
)}
@@ -1037,9 +1193,7 @@ const BusinessProcessViewPage: React.FC = () => {
className={inputCls}
/>
) : (
-
- {form.sla || 'N/A'}
-
+ renderInlineEditable('sla', form.sla || 'N/A')
)}
@@ -1057,9 +1211,10 @@ const BusinessProcessViewPage: React.FC = () => {
))}
) : (
-
- {labelFromOptions(form.process_health_state, PROCESS_HEALTH_OPTIONS)}
-
+ renderInlineEditable('process_health_state', labelFromOptions(form.process_health_state, PROCESS_HEALTH_OPTIONS), {
+ kind: 'select',
+ options: PROCESS_HEALTH_OPTIONS,
+ })
)}
diff --git a/tavro_app/src/pages/IntegrationViewPage.tsx b/tavro_app/src/pages/IntegrationViewPage.tsx
index 3996bff..65895ca 100644
--- a/tavro_app/src/pages/IntegrationViewPage.tsx
+++ b/tavro_app/src/pages/IntegrationViewPage.tsx
@@ -72,6 +72,22 @@ interface IntegrationFormState {
parent_application_id: string;
}
+type IntegrationInlineField =
+ | 'integration_name'
+ | 'owner'
+ | 'integration_description'
+ | 'capabilities'
+ | 'protocol'
+ | 'authentication_method'
+ | 'endpoint_url'
+ | 'documentation_url'
+ | 'version'
+ | 'rate_limit'
+ | 'data_sensitivity'
+ | 'availability_status'
+ | 'sla'
+ | 'parent_application_id';
+
const inputCls =
'w-full text-sm border border-slate-200 rounded-xl px-3.5 py-2.5 outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all bg-white text-slate-800 placeholder:text-slate-400 disabled:bg-slate-50 disabled:text-slate-500';
const textAreaCls = `${inputCls} resize-y min-h-[84px]`;
@@ -172,6 +188,8 @@ const IntegrationViewPage: React.FC = () => {
const [editing, setEditing] = useState(isCreateMode);
const [saving, setSaving] = useState(false);
const [deleting, setDeleting] = useState(false);
+ const [inlineEdit, setInlineEdit] = useState<{ field: IntegrationInlineField; value: string } | null>(null);
+ const [inlineSaving, setInlineSaving] = useState(null);
const load = async () => {
if (!id || isCreateMode) return;
@@ -215,6 +233,124 @@ const IntegrationViewPage: React.FC = () => {
const isNameMissing = !form.integration_name.trim();
+ // ── Inline edit handlers ───────────────────────────────────────────────────
+
+ const startInlineEdit = (field: IntegrationInlineField) => {
+ if (editing || isCreateMode || saving || inlineSaving) return;
+ setActionError(null);
+ setInlineEdit({ field, value: form[field] });
+ };
+
+ const cancelInlineEdit = () => {
+ setInlineEdit(null);
+ setActionError(null);
+ };
+
+ const saveInlineEdit = async () => {
+ if (!integration || !inlineEdit) return;
+ const nextForm = { ...form, [inlineEdit.field]: inlineEdit.value };
+ if (!nextForm.integration_name.trim()) {
+ setActionError('Integration Name is required.');
+ return;
+ }
+ setInlineSaving(inlineEdit.field);
+ setActionError(null);
+ try {
+ const updated = await businessRelationsApi.updateIntegration(
+ integration.integration_id,
+ buildIntegrationPayload(nextForm),
+ activeCompany?.id,
+ );
+ setIntegration(updated);
+ setForm(formFromIntegration(updated));
+ setInlineEdit(null);
+ setAttemptedSave(false);
+ } catch (err: unknown) {
+ setActionError(err instanceof Error ? err.message : 'Failed to save integration field');
+ } finally {
+ setInlineSaving(null);
+ }
+ };
+
+ const renderInlineEditable = (
+ field: IntegrationInlineField,
+ displayValue: string,
+ config: { kind?: 'text' | 'textarea' | 'select'; options?: Option[]; className?: string } = {},
+ ) => {
+ const isActive = inlineEdit?.field === field;
+ const kind = config.kind ?? 'text';
+ const valueClass = config.className ?? 'text-sm text-slate-700 bg-slate-50 border border-slate-200 rounded-xl px-3.5 py-2.5';
+ const isSavingField = inlineSaving === field;
+ const saveDisabled = isSavingField || (field === 'integration_name' && !inlineEdit?.value.trim());
+
+ if (!editing && !isCreateMode && isActive) {
+ return (
+
+ );
+ }
+
+ return (
+ startInlineEdit(field)}
+ title={!editing && !isCreateMode ? 'Double-click to edit' : undefined}
+ className={`${valueClass} ${!editing && !isCreateMode ? 'cursor-text hover:border-blue-200 hover:bg-blue-50/40 transition-colors' : ''}`}
+ >
+ {displayValue}
+
+ );
+ };
+
+ // ── Bulk save / cancel ─────────────────────────────────────────────────────
+
const handleSave = async () => {
setAttemptedSave(true);
if (isNameMissing) {
@@ -320,15 +456,15 @@ const IntegrationViewPage: React.FC = () => {
disabled={saving}
className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-sm font-bold border border-slate-200 bg-white text-slate-700 hover:bg-slate-50"
>
- Cancel
+ {isCreateMode ? 'Cancel' : 'Discard'}
{saving ? : }
- {isCreateMode ? 'Create Integration' : 'Save Changes'}
+ {isCreateMode ? 'Create Integration' : 'Save'}
>
) : (
@@ -337,6 +473,7 @@ const IntegrationViewPage: React.FC = () => {
onClick={() => {
setTab('overview');
setAttemptedSave(false);
+ setInlineEdit(null);
setEditing(true);
}}
className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-sm font-bold border border-slate-200 bg-white text-slate-700 hover:bg-slate-50"
@@ -446,9 +583,7 @@ const IntegrationViewPage: React.FC = () => {
)}
>
) : (
-
- {form.integration_name || 'N/A'}
-
+ renderInlineEditable('integration_name', form.integration_name || 'N/A')
)}
@@ -462,9 +597,7 @@ const IntegrationViewPage: React.FC = () => {
placeholder="Owner name or team"
/>
) : (
-
- {form.owner || 'N/A'}
-
+ renderInlineEditable('owner', form.owner || 'N/A')
)}
@@ -478,9 +611,10 @@ const IntegrationViewPage: React.FC = () => {
className={textAreaCls}
/>
) : (
-
- {form.integration_description || 'N/A'}
-
+ renderInlineEditable('integration_description', form.integration_description || 'N/A', {
+ kind: 'textarea',
+ className: 'text-sm text-slate-700 bg-slate-50 border border-slate-200 rounded-xl px-3.5 py-2.5 min-h-[84px] whitespace-pre-wrap',
+ })
)}
@@ -501,9 +635,10 @@ const IntegrationViewPage: React.FC = () => {
placeholder={`e.g.\n1. Read real-time machine sensor data (OPC-UA)\n2. Push quality alerts to ERP work orders\n3. Subscribe to production schedule changes`}
/>
) : (
-
- {form.capabilities || 'No capabilities defined.'}
-
+ renderInlineEditable('capabilities', form.capabilities || 'No capabilities defined.', {
+ kind: 'textarea',
+ className: 'text-sm text-slate-700 bg-slate-50 border border-slate-200 rounded-xl px-3.5 py-2.5 min-h-[100px] whitespace-pre-wrap',
+ })
)}
@@ -524,9 +659,10 @@ const IntegrationViewPage: React.FC = () => {
))}
) : (
-
- {form.protocol || 'N/A'}
-
+ renderInlineEditable('protocol', form.protocol || 'N/A', {
+ kind: 'select',
+ options: PROTOCOL_OPTIONS,
+ })
)}
@@ -544,9 +680,10 @@ const IntegrationViewPage: React.FC = () => {
))}
) : (
-
- {form.authentication_method || 'N/A'}
-
+ renderInlineEditable('authentication_method', form.authentication_method || 'N/A', {
+ kind: 'select',
+ options: AUTH_METHOD_OPTIONS,
+ })
)}
@@ -560,9 +697,9 @@ const IntegrationViewPage: React.FC = () => {
placeholder="https://..."
/>
) : (
-
- {form.endpoint_url || 'N/A'}
-
+ renderInlineEditable('endpoint_url', form.endpoint_url || 'N/A', {
+ className: 'text-sm text-slate-700 bg-slate-50 border border-slate-200 rounded-xl px-3.5 py-2.5 break-all',
+ })
)}
@@ -576,9 +713,9 @@ const IntegrationViewPage: React.FC = () => {
placeholder="https://..."
/>
) : (
-
- {form.documentation_url || 'N/A'}
-
+ renderInlineEditable('documentation_url', form.documentation_url || 'N/A', {
+ className: 'text-sm text-slate-700 bg-slate-50 border border-slate-200 rounded-xl px-3.5 py-2.5 break-all',
+ })
)}
@@ -592,9 +729,7 @@ const IntegrationViewPage: React.FC = () => {
placeholder="e.g. v1.2.0"
/>
) : (
-
- {form.version || 'N/A'}
-
+ renderInlineEditable('version', form.version || 'N/A')
)}
@@ -608,9 +743,7 @@ const IntegrationViewPage: React.FC = () => {
placeholder="e.g. 1000 req/min"
/>
) : (
-
- {form.rate_limit || 'N/A'}
-
+ renderInlineEditable('rate_limit', form.rate_limit || 'N/A')
)}
@@ -632,9 +765,10 @@ const IntegrationViewPage: React.FC = () => {
))}
) : (
-
- {form.data_sensitivity || 'N/A'}
-
+ renderInlineEditable('data_sensitivity', form.data_sensitivity || 'N/A', {
+ kind: 'select',
+ options: DATA_SENSITIVITY_OPTIONS,
+ })
)}
@@ -652,9 +786,10 @@ const IntegrationViewPage: React.FC = () => {
))}
) : (
-
- {form.availability_status || 'N/A'}
-
+ renderInlineEditable('availability_status', form.availability_status || 'N/A', {
+ kind: 'select',
+ options: AVAILABILITY_STATUS_OPTIONS,
+ })
)}
@@ -668,9 +803,7 @@ const IntegrationViewPage: React.FC = () => {
placeholder="e.g. 99.9% uptime"
/>
) : (
-
- {form.sla || 'N/A'}
-
+ renderInlineEditable('sla', form.sla || 'N/A')
)}
@@ -684,13 +817,15 @@ const IntegrationViewPage: React.FC = () => {
placeholder="Application UUID"
/>
) : (
-
- {form.parent_application_id
+ renderInlineEditable(
+ 'parent_application_id',
+ form.parent_application_id
? form.parent_application_id
: integration?.parent_application_name
? `${integration.parent_application_name}`
- : 'N/A'}
-
+ : 'N/A',
+ { className: 'text-sm text-slate-700 bg-slate-50 border border-slate-200 rounded-xl px-3.5 py-2.5 break-all' },
+ )
)}
diff --git a/tavro_app/src/pages/UseCaseViewPage.tsx b/tavro_app/src/pages/UseCaseViewPage.tsx
index 3f3d2f1..61962e5 100644
--- a/tavro_app/src/pages/UseCaseViewPage.tsx
+++ b/tavro_app/src/pages/UseCaseViewPage.tsx
@@ -4,12 +4,11 @@ import { UseCaseDetail } from '../types/useCase';
import { AgentData } from '../types/agent';
import { mcpClient } from '../services/mcpClient';
import UseCaseView from '../components/UseCaseView';
-import { ArrowLeft, RefreshCw, AlertCircle, Search, Loader2, Unlink2, PlusCircle, ShieldCheck, Pencil, Trash2, Code2, Copy, Check, X } from 'lucide-react';
+import { ArrowLeft, RefreshCw, AlertCircle, Search, Loader2, Unlink2, PlusCircle, ShieldCheck, Pencil, Trash2, Code2, Copy, Check, X, CheckCircle2 } from 'lucide-react';
import { useCatalog } from '../context/CatalogContext';
import { useUseCases } from '../context/UseCaseContext';
import { useChatSync } from '../hooks/useChatSync';
import AuditInitModal from '../components/audit/AuditInitModal';
-import EditUseCaseModal from '../components/EditUseCaseModal';
import { useCaseApi } from '../services/useCaseApi';
import { businessRelationsApi } from '../services/businessRelationsApi';
import type { BusinessProcessRecord } from '../types/businessRelations';
@@ -671,7 +670,18 @@ const UseCaseViewPage: React.FC = () => {
const [auditModalOpen, setAuditModalOpen] = useState(false);
const [error, setError] = useState(null);
- const [editOpen, setEditOpen] = useState(false);
+ const [isEditing, setIsEditing] = useState(false);
+ const [editTitle, setEditTitle] = useState('');
+ const [editDescription, setEditDescription] = useState('');
+ const [editPriority, setEditPriority] = useState('');
+ const [editOwner, setEditOwner] = useState('');
+ const [editProblemStatement, setEditProblemStatement] = useState('');
+ const [editExpectedBenefits, setEditExpectedBenefits] = useState('');
+ const [editSolutionApproach, setEditSolutionApproach] = useState('');
+ const [editSaving, setEditSaving] = useState(false);
+ const [editError, setEditError] = useState(null);
+ const [inlineEdit, setInlineEdit] = useState<{ field: string; value: string } | null>(null);
+ const [inlineSaving, setInlineSaving] = useState(null);
const [deleteConfirm, setDeleteConfirm] = useState(false);
const [deleting, setDeleting] = useState(false);
const [jsonOpen, setJsonOpen] = useState(false);
@@ -770,6 +780,98 @@ const UseCaseViewPage: React.FC = () => {
}
}, [useCase]);
+ const handleStartEdit = () => {
+ if (!useCase) return;
+ const uc = useCase as any;
+ setEditTitle(uc.name ?? uc.title ?? '');
+ setEditDescription(uc.description ?? '');
+ setEditPriority(uc.priority ?? '3 - Moderate');
+ setEditOwner(uc.owner ?? uc.use_case_owner ?? '');
+ setEditProblemStatement(uc.problem_statement ?? uc.business_problem_statement ?? '');
+ setEditExpectedBenefits(uc.expected_benefits ?? '');
+ setEditSolutionApproach(uc.solution_approach ?? '');
+ setEditError(null);
+ setIsEditing(true);
+ };
+
+ const handleCancelEdit = () => {
+ setIsEditing(false);
+ setEditError(null);
+ };
+
+ const handleSaveEdit = async () => {
+ if (!useCase || !id) return;
+ setEditSaving(true);
+ setEditError(null);
+ try {
+ await useCaseApi.updateUseCase(id, {
+ title: editTitle.trim() || undefined,
+ description: editDescription.trim() || undefined,
+ priority: editPriority || undefined,
+ use_case_owner: editOwner.trim() || undefined,
+ business_problem_statement: editProblemStatement.trim() || undefined,
+ expected_benefits: editExpectedBenefits.trim() || undefined,
+ solution_approach: editSolutionApproach.trim() || undefined,
+ } as any);
+ handleUseCaseSaved({
+ title: editTitle.trim(),
+ description: editDescription.trim(),
+ problemStatement: editProblemStatement.trim(),
+ expectedBenefits: editExpectedBenefits.trim(),
+ priority: editPriority,
+ solutionApproach: editSolutionApproach.trim(),
+ owner: editOwner.trim(),
+ });
+ setIsEditing(false);
+ } catch (err: any) {
+ setEditError(err.message || 'Failed to update use case.');
+ } finally {
+ setEditSaving(false);
+ }
+ };
+
+ const handleStartInlineEdit = (field: string, value: string) => {
+ setInlineEdit({ field, value });
+ };
+
+ const handleCancelInlineEdit = () => setInlineEdit(null);
+
+ const handleSaveInlineEdit = async () => {
+ if (!inlineEdit || !id) return;
+ const { field, value } = inlineEdit;
+ setInlineSaving(field);
+ try {
+ const payload: any = {};
+ if (field === 'title') payload.title = value.trim();
+ else if (field === 'description') payload.description = value.trim();
+ else if (field === 'priority') payload.priority = value;
+ else if (field === 'owner') payload.use_case_owner = value.trim();
+ else if (field === 'problem_statement') payload.business_problem_statement = value.trim();
+ else if (field === 'expected_benefits') payload.expected_benefits = value.trim();
+ else if (field === 'solution_approach') payload.solution_approach = value.trim();
+ await useCaseApi.updateUseCase(id, payload);
+ setUseCase(prev => {
+ if (!prev) return prev;
+ const next = { ...prev } as any;
+ if (field === 'title') { next.name = value.trim(); next.title = value.trim(); }
+ else if (field === 'description') next.description = value.trim();
+ else if (field === 'priority') next.priority = value;
+ else if (field === 'owner') { next.owner = value.trim(); next.use_case_owner = value.trim(); }
+ else if (field === 'problem_statement') { next.problem_statement = value.trim(); next.business_problem_statement = value.trim(); }
+ else if (field === 'expected_benefits') next.expected_benefits = value.trim();
+ else if (field === 'solution_approach') next.solution_approach = value.trim();
+ return next as UseCaseDetail;
+ });
+ setInlineEdit(null);
+ mcpClient.invalidateCache();
+ refreshUseCases();
+ } catch (err: any) {
+ console.error('Failed to save inline edit:', err);
+ } finally {
+ setInlineSaving(null);
+ }
+ };
+
const handleUseCaseSaved = (updated: {
title: string;
description: string;
@@ -817,8 +919,8 @@ const UseCaseViewPage: React.FC = () => {
const useCaseName = useCase ? ((useCase as any).name ?? (useCase as any).title ?? useCase.identifier ?? '') : '';
return (
-
-
+
+
{
const page = (location.state as any)?.page;
@@ -838,31 +940,53 @@ const UseCaseViewPage: React.FC = () => {
{useCase && (
-
setAuditModalOpen(true)}
- className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-bold bg-blue-600 hover:bg-blue-700 text-white transition-all shadow-sm"
- >
- Audit
-
-
setJsonOpen(true)}
- title="AI Use Case Card"
- className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-bold bg-slate-800 text-slate-100 hover:bg-slate-700 transition-all border border-slate-700 shadow-sm disabled:opacity-50 disabled:cursor-not-allowed"
- >
- AI Use Case Card
-
-
setEditOpen(true)}
- className="flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-bold bg-white border border-slate-200 text-slate-700 hover:bg-slate-50 transition-all shadow-sm"
- >
- Edit
-
-
setDeleteConfirm(true)}
- className="flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-bold bg-white border border-red-200 text-red-600 hover:bg-red-50 transition-all shadow-sm"
- >
- Delete
-
+ {isEditing ? (
+ <>
+ {editError &&
{editError}}
+
+ Discard
+
+
+ {editSaving ? <> Saving…> : 'Save'}
+
+ >
+ ) : (
+ <>
+
setAuditModalOpen(true)}
+ className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-bold bg-blue-600 hover:bg-blue-700 text-white transition-all shadow-sm"
+ >
+ Audit
+
+
setJsonOpen(true)}
+ title="AI Use Case Card"
+ className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-bold bg-slate-800 text-slate-100 hover:bg-slate-700 transition-all border border-slate-700 shadow-sm"
+ >
+ AI Use Case Card
+
+
+ Edit
+
+
setDeleteConfirm(true)}
+ className="flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-bold bg-white border border-red-200 text-red-600 hover:bg-red-50 transition-all shadow-sm"
+ >
+ Delete
+
+ >
+ )}
)}
@@ -894,15 +1018,27 @@ const UseCaseViewPage: React.FC = () => {
useCase={useCase}
agentsComponent={
}
businessImpactComponent={
}
- />
- )}
-
- {useCase && (
-
setEditOpen(false)}
- onSaved={handleUseCaseSaved}
+ isEditing={isEditing}
+ editTitle={editTitle}
+ onEditTitleChange={setEditTitle}
+ editDescription={editDescription}
+ onEditDescriptionChange={setEditDescription}
+ editPriority={editPriority}
+ onEditPriorityChange={setEditPriority}
+ editOwner={editOwner}
+ onEditOwnerChange={setEditOwner}
+ editProblemStatement={editProblemStatement}
+ onEditProblemStatementChange={setEditProblemStatement}
+ editExpectedBenefits={editExpectedBenefits}
+ onEditExpectedBenefitsChange={setEditExpectedBenefits}
+ editSolutionApproach={editSolutionApproach}
+ onEditSolutionApproachChange={setEditSolutionApproach}
+ inlineEdit={inlineEdit}
+ inlineSaving={inlineSaving}
+ onStartInlineEdit={handleStartInlineEdit}
+ onInlineValueChange={(v) => setInlineEdit(prev => prev ? { ...prev, value: v } : null)}
+ onSaveInlineEdit={handleSaveInlineEdit}
+ onCancelInlineEdit={handleCancelInlineEdit}
/>
)}