import { useState, useEffect, useRef, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from '@/components/ui/dialog';
import BotForm from '@/app/home/bots/components/bot-form/BotForm';
import { BotLogListComponent } from '@/app/home/bots/components/bot-log/view/BotLogListComponent';
import BotSessionMonitor from '@/app/home/bots/components/bot-session/BotSessionMonitor';
import type { BotSessionMonitorHandle } from '@/app/home/bots/components/bot-session/BotSessionMonitor';
import { httpClient } from '@/app/infra/http/HttpClient';
import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext';
import { useTranslation } from 'react-i18next';
import {
Settings,
FileText,
Users,
RefreshCw,
Trash2,
CircleCheck,
CircleAlert,
Loader2,
CircleOff,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { toast } from 'sonner';
import type { Bot, BotAdapterRuntimeStatus } from '@/app/infra/entities/api';
function getBotRuntimeStatus(bot: Bot | null): BotAdapterRuntimeStatus | null {
return bot?.adapter_runtime_values?.runtime_status ?? null;
}
function RuntimeStatusBadge({
status,
}: {
status: BotAdapterRuntimeStatus | null;
}) {
const { t } = useTranslation();
if (!status) return null;
const value = status?.connection_status ?? 'disconnected';
const config = {
connected: {
label: t('bots.runtimeConnected'),
className: 'border-emerald-500/30 bg-emerald-500/10 text-emerald-700',
icon: CircleCheck,
},
connecting: {
label: t('bots.runtimeConnecting'),
className: 'border-amber-500/30 bg-amber-500/10 text-amber-700',
icon: Loader2,
},
disconnected: {
label: t('bots.runtimeDisconnected'),
className: 'border-muted-foreground/20 bg-muted text-muted-foreground',
icon: CircleOff,
},
error: {
label: t('bots.runtimeError'),
className: 'border-destructive/30 bg-destructive/10 text-destructive',
icon: CircleAlert,
},
}[value];
const Icon = config.icon;
return (
{config.label}
{status?.connection_error && (
{status.connection_error}
)}
);
}
import { useCurrentWorkspace } from '@/app/infra/http';
export default function BotDetailContent({ id }: { id: string }) {
const isCreateMode = id === 'new';
const navigate = useNavigate();
const { t } = useTranslation();
const currentWorkspace = useCurrentWorkspace();
const canManage =
currentWorkspace?.permissions.includes('resource.manage') ?? false;
const canViewMonitoring =
currentWorkspace?.permissions.includes('resource.view') ?? false;
const { refreshBots, bots, setDetailEntityName } = useSidebarData();
// Set breadcrumb entity name
useEffect(() => {
if (isCreateMode) {
setDetailEntityName(t('bots.createBot'));
} else {
const bot = bots.find((b) => b.id === id);
setDetailEntityName(bot?.name ?? id);
}
return () => setDetailEntityName(null);
}, [id, isCreateMode, bots, setDetailEntityName, t]);
const [activeTab, setActiveTab] = useState('config');
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [isRefreshingSessions, setIsRefreshingSessions] = useState(false);
const sessionMonitorRef = useRef(null);
// Track whether the form has unsaved changes
const [formDirty, setFormDirty] = useState(false);
// Enable state managed here so the header switch works
const [botEnabled, setBotEnabled] = useState(true);
const [enableLoaded, setEnableLoaded] = useState(false);
const [botDetail, setBotDetail] = useState(null);
const fetchBotDetail = useCallback(async () => {
if (isCreateMode) return;
const res = await httpClient.getBot(id);
setBotDetail(res.bot);
setBotEnabled(res.bot.enable ?? true);
setEnableLoaded(true);
}, [id, isCreateMode]);
// Fetch bot enable state
useEffect(() => {
if (!isCreateMode) {
fetchBotDetail();
const timer = window.setInterval(fetchBotDetail, 5000);
return () => window.clearInterval(timer);
}
}, [fetchBotDetail, isCreateMode]);
const handleEnableToggle = useCallback(
async (checked: boolean) => {
const prev = botEnabled;
setBotEnabled(checked);
try {
// Fetch current bot data to send a complete update
const res = await httpClient.getBot(id);
const bot = res.bot;
await httpClient.updateBot(id, {
name: bot.name,
description: bot.description,
adapter: bot.adapter,
adapter_config: bot.adapter_config,
enable: checked,
});
refreshBots();
} catch {
setBotEnabled(prev);
toast.error(t('bots.setBotEnableError'));
}
},
[id, botEnabled, refreshBots, t],
);
function handleFormSubmit() {
// Re-sync enable state after form save (form may update enable too)
fetchBotDetail();
refreshBots();
}
function handleBotDeleted() {
refreshBots();
navigate('/home/bots');
}
function handleNewBotCreated(newBotId: string) {
refreshBots();
navigate(`/home/bots?id=${encodeURIComponent(newBotId)}`);
}
function confirmDelete() {
httpClient
.deleteBot(id)
.then(() => {
setShowDeleteConfirm(false);
toast.success(t('bots.deleteSuccess'));
handleBotDeleted();
})
.catch((err) => {
toast.error(t('bots.deleteError') + err.msg);
});
}
// ==================== Create Mode ====================
if (isCreateMode) {
return (
{/* Header */}
{t('bots.createBot')}
{canManage && (
)}
{/* Content */}
);
}
// ==================== Edit Mode ====================
return (
<>
{/* Sticky Header: title + enable switch + save button */}
{t('bots.editBot')}
{enableLoaded && (
)}
{canManage && (
)}
{/* Horizontal Tabs */}
{t('bots.configuration')}
{canViewMonitoring && (
{t('bots.logs')}
)}
{canViewMonitoring && (
{t('bots.sessionMonitor.title')}
)}
{activeTab === 'sessions' && (
)}
{/* Tab: Configuration */}
{/* Card: Danger Zone */}
{canManage && (
{t('bots.dangerZone')}
{t('bots.dangerZoneDescription')}
{t('bots.deleteBotAction')}
{t('bots.deleteBotHint')}
)}
{/* Tab: Logs */}
{canViewMonitoring && (
)}
{/* Tab: Sessions */}
{canViewMonitoring && (
)}
{/* Delete confirmation dialog */}
>
);
}