import { useState, useCallback, useEffect, memo } from 'react'; import { Button } from '@/components/ui/button'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Textarea } from '@/components/ui/textarea'; import { Badge } from '@/components/ui/badge'; import { Switch } from '@/components/ui/switch'; import { Timer, X, Plus, Loader2, Bot } from 'lucide-react'; import { client } from '@/client/client.gen'; interface AiConfig { id: string; name: string; enabled: boolean; system_prompt?: string; } interface RoomAiTasksPanelProps { roomId: string; onClose: () => void; } export const RoomAiTasksPanel = memo(function RoomAiTasksPanel({ roomId, onClose }: RoomAiTasksPanelProps) { const [aiConfigs, setAiConfigs] = useState([]); const [isLoading, setIsLoading] = useState(true); const [showAddForm, setShowAddForm] = useState(false); const loadAiConfigs = useCallback(async () => { setIsLoading(true); try { const resp = await client.get({ url: `/api/rooms/${roomId}/ai`, }); const data = (resp.data as any)?.data as any[] | undefined; if (data) { // Only include configs that have a valid model_name — never expose UID setAiConfigs(data .filter((cfg: any) => !!cfg.model_name) .map((cfg: any) => ({ id: cfg.model, name: cfg.model_name, enabled: cfg.enabled !== false, system_prompt: cfg.system_prompt, }))); } } catch (err) { console.error('Failed to load AI configs:', err); } finally { setIsLoading(false); } }, [roomId]); useEffect(() => { loadAiConfigs(); }, [loadAiConfigs]); const handleAddConfig = useCallback(async (modelName: string, systemPrompt: string) => { try { await client.put({ url: `/api/rooms/${roomId}/ai`, body: { name: modelName, system_prompt: systemPrompt, }, }); await loadAiConfigs(); setShowAddForm(false); } catch (err) { console.error('Failed to add AI config:', err); } }, [roomId, loadAiConfigs]); const handleDeleteConfig = useCallback(async (configId: string) => { try { await client.delete({ url: `/api/rooms/${roomId}/ai/${configId}`, }); await loadAiConfigs(); } catch (err) { console.error('Failed to delete AI config:', err); } }, [roomId, loadAiConfigs]); const handleToggleEnabled = useCallback(async (configId: string, enabled: boolean) => { try { const config = aiConfigs.find((c) => c.id === configId); if (!config) return; await client.put({ url: `/api/rooms/${roomId}/ai`, body: { id: configId, name: config.name, system_prompt: config.system_prompt, enabled, }, }); await loadAiConfigs(); } catch (err) { console.error('Failed to toggle AI enabled:', err); } }, [roomId, aiConfigs, loadAiConfigs]); return (
{/* Header */}

AI Tasks

{aiConfigs.length > 0 && ( {aiConfigs.length} )}
{/* Add AI Form */} {showAddForm && ( setShowAddForm(false)} /> )} {/* AI Configs List */} {isLoading ? (
) : aiConfigs.length === 0 ? (

No AI models configured

) : (
{aiConfigs.map((config) => ( handleToggleEnabled(config.id, enabled)} onDelete={() => handleDeleteConfig(config.id)} /> ))}
)}
); }); // AI Config Card Component interface AiConfigCardProps { config: AiConfig; onToggle: (enabled: boolean) => void; onDelete: () => void; } const AiConfigCard = memo(function AiConfigCard({ config, onToggle, onDelete }: AiConfigCardProps) { return (

{config.name}

{config.enabled ? 'On' : 'Off'}
{config.system_prompt && (

{config.system_prompt}

)}
); }); // Add AI Config Form interface AddAiConfigFormProps { onSubmit: (modelName: string, systemPrompt: string) => void; onCancel: () => void; } const AddAiConfigForm = memo(function AddAiConfigForm({ onSubmit, onCancel }: AddAiConfigFormProps) { const [modelName, setModelName] = useState(''); const [systemPrompt, setSystemPrompt] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!modelName.trim()) return; setIsSubmitting(true); onSubmit(modelName.trim(), systemPrompt.trim()); setIsSubmitting(false); }; return (
setModelName(e.target.value)} placeholder="Model name (e.g., gpt-4o)" className="w-full rounded-md border border-border bg-background px-2 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-ring" />