import React, { useState, useEffect, useRef, useCallback } from 'react'; import './App.css'; const App = () => { const [tasks, setTasks] = useState([]); const [selectedTask, setSelectedTask] = useState(null); const [showForm, setShowForm] = useState(false); const [formData, setFormData] = useState({ name: '', description: '', prompt: '', schedule_type: 'manual', schedule_value: '', enabled: true, agent_model: '', agent_tools: 'Bash,Read,Write,Edit', agent_system_prompt: '', agent_permission_mode: 'auto', agent_timeout: 300 }); const [systemInfo, setSystemInfo] = useState(null); const [usageStats, setUsageStats] = useState(null); const [authStatus, setAuthStatus] = useState(null); const [mcpServers, setMcpServers] = useState(null); const [showAddMcp, setShowAddMcp] = useState(false); const [mcpForm, setMcpForm] = useState({ name: '', server_type: 'sse', url: '', command: '' }); const [loading, setLoading] = useState(false); const [activeView, setActiveView] = useState('chat'); // 'chat' | 'tasks' | 'dashboard' // Auth const [authToken, setAuthToken] = useState(''); const [tokenType, setTokenType] = useState('oauth_token'); const [tokenSubmitting, setTokenSubmitting] = useState(false); // Chat const [chatMessages, setChatMessages] = useState([]); const [chatInput, setChatInput] = useState(''); const [chatSending, setChatSending] = useState(false); const [chatSessionId, setChatSessionId] = useState(() => crypto.randomUUID ? crypto.randomUUID() : Date.now().toString()); const [chatSessions, setChatSessions] = useState([]); const [chatModel, setChatModel] = useState(''); const chatEndRef = useRef(null); const wsRef = useRef(null); const [wsConnected, setWsConnected] = useState(false); useEffect(() => { fetchTasks(); fetchSystemInfo(); fetchUsageStats(); fetchAuthStatus(); fetchMcpServers(); const interval = setInterval(() => { fetchTasks(); fetchSystemInfo(); fetchAuthStatus(); }, 10000); return () => clearInterval(interval); }, []); useEffect(() => { if (chatEndRef.current) chatEndRef.current.scrollIntoView({ behavior: 'smooth' }); }, [chatMessages]); // WebSocket for chat const connectWs = useCallback(() => { if (wsRef.current && wsRef.current.readyState <= 1) return; const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const ws = new WebSocket(`${proto}//${window.location.host}/api/chat/ws/${chatSessionId}`); ws.onopen = () => setWsConnected(true); ws.onclose = () => setWsConnected(false); ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === 'chunk') { setChatMessages(prev => { const last = prev[prev.length - 1]; if (last && last.role === 'assistant' && last.streaming) { return [...prev.slice(0, -1), { ...last, content: last.content + data.content }]; } return [...prev, { role: 'assistant', content: data.content, streaming: true }]; }); } else if (data.type === 'done') { setChatMessages(prev => { const last = prev[prev.length - 1]; if (last && last.role === 'assistant' && last.streaming) { return [...prev.slice(0, -1), { ...last, streaming: false }]; } return prev; }); setChatSending(false); } else if (data.type === 'error') { setChatMessages(prev => [...prev, { role: 'error', content: data.content }]); setChatSending(false); } else if (data.type === 'status' && data.content === 'thinking') { setChatMessages(prev => [...prev, { role: 'assistant', content: '', streaming: true }]); } }; wsRef.current = ws; }, [chatSessionId]); useEffect(() => { if (activeView === 'chat') connectWs(); return () => { if (wsRef.current) { wsRef.current.close(); wsRef.current = null; } }; }, [activeView, chatSessionId, connectWs]); const sendChatMessage = async () => { if (!chatInput.trim() || chatSending) return; const msg = chatInput.trim(); setChatInput(''); setChatMessages(prev => [...prev, { role: 'user', content: msg }]); setChatSending(true); if (wsRef.current && wsRef.current.readyState === 1) { wsRef.current.send(JSON.stringify({ message: msg, model: chatModel || undefined })); } else { // Fallback to HTTP try { const response = await fetch('/api/chat/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: msg, session_id: chatSessionId, model: chatModel || undefined }) }); const data = await response.json(); setChatMessages(prev => [...prev, { role: data.status === 'ok' ? 'assistant' : 'error', content: data.response }]); } catch (error) { setChatMessages(prev => [...prev, { role: 'error', content: 'Failed to send message' }]); } setChatSending(false); } }; const startNewChat = () => { setChatMessages([]); const newId = crypto.randomUUID ? crypto.randomUUID() : Date.now().toString(); setChatSessionId(newId); if (wsRef.current) { wsRef.current.close(); wsRef.current = null; } }; const loadChatSession = async (sid) => { setChatSessionId(sid); if (wsRef.current) { wsRef.current.close(); wsRef.current = null; } try { const response = await fetch(`/api/chat/history/${sid}`); const data = await response.json(); setChatMessages(data.map(m => ({ role: m.role, content: m.content }))); } catch (e) { console.error('Error loading chat session:', e); } }; // Fetchers const fetchTasks = async () => { try { const r = await fetch('/api/tasks'); setTasks(await r.json()); } catch(e) {} }; const fetchSystemInfo = async () => { try { const r = await fetch('/api/system/info'); setSystemInfo(await r.json()); } catch(e) {} }; const fetchUsageStats = async () => { try { const r = await fetch('/api/system/usage'); setUsageStats(await r.json()); } catch(e) {} }; const fetchAuthStatus = async () => { try { const r = await fetch('/api/auth/status'); setAuthStatus(await r.json()); } catch(e) {} }; const fetchMcpServers = async () => { try { const r = await fetch('/api/mcp/servers'); setMcpServers(await r.json()); } catch(e) {} }; const fetchChatSessions = async () => { try { const r = await fetch('/api/chat/sessions'); setChatSessions(await r.json()); } catch(e) {} }; const handleSubmitToken = async (e) => { e.preventDefault(); if (!authToken.trim()) return; setTokenSubmitting(true); try { const r = await fetch('/api/auth/token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: authToken.trim(), token_type: tokenType }) }); const data = await r.json(); setAuthToken(''); fetchAuthStatus(); if (data.status !== 'logged_in') alert(data.message); } catch(e) { alert('Failed to submit token'); } setTokenSubmitting(false); }; const handleLogout = async () => { await fetch('/api/auth/logout', { method: 'POST' }); fetchAuthStatus(); }; const handleAddMcp = async (e) => { e.preventDefault(); const r = await fetch('/api/mcp/servers', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(mcpForm) }); const data = await r.json(); if (data.status === 'ok') { setShowAddMcp(false); setMcpForm({ name: '', server_type: 'sse', url: '', command: '' }); fetchMcpServers(); } else alert(data.message || 'Failed'); }; const handleRemoveMcp = async (name) => { if (!confirm(`Remove "${name}"?`)) return; await fetch(`/api/mcp/servers/${name}`, { method: 'DELETE' }); fetchMcpServers(); }; const handleCreateTask = async (e) => { e.preventDefault(); setLoading(true); try { const r = await fetch('/api/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); if (r.ok) { await fetchTasks(); setShowForm(false); setFormData({ name: '', description: '', prompt: '', schedule_type: 'manual', schedule_value: '', enabled: true, agent_model: '', agent_tools: 'Bash,Read,Write,Edit', agent_system_prompt: '', agent_permission_mode: 'auto', agent_timeout: 300 }); } } catch(e) {} setLoading(false); }; const handleRunTask = async (taskId) => { await fetch(`/api/tasks/${taskId}/run`, { method: 'POST' }); await fetchTasks(); }; const handleDeleteTask = async (taskId) => { if (confirm('Delete this task?')) { await fetch(`/api/tasks/${taskId}`, { method: 'DELETE' }); await fetchTasks(); setSelectedTask(null); } }; const getStatusColor = (s) => ({ running: '#3498db', completed: '#27ae60', failed: '#e74c3c' }[s] || '#95a5a6'); return (

Claude Persistent Agent

Chat, schedule tasks & orchestrate agents

{authStatus?.status === 'logged_in' ? ( ● {authStatus.account || 'Authenticated'} ) : authStatus?.has_saved_token ? ( ● Token saved ) : ( setActiveView('dashboard')} style={{cursor:'pointer'}}>⚠ Not authenticated )}
{systemInfo && (
{systemInfo.task_count} agents · {systemInfo.total_runs || 0} runs
)}
{/* ===== CHAT VIEW ===== */} {activeView === 'chat' && (
{chatSessions.map(s => (
loadChatSession(s.session_id)}>
{(s.first_message || 'Chat').substring(0, 40)}
{s.message_count} msgs · {new Date(s.last_message).toLocaleDateString()}
))}
{wsConnected ? 'Connected' : 'Disconnected'}
{chatMessages.length === 0 && (
💬

Chat with Claude

Send a message to start a conversation with the Claude Code instance running on your server.

Claude has access to Bash, file system, and any MCP servers you've configured.

)} {chatMessages.map((msg, i) => (
{msg.role === 'user' ? '👤' : msg.role === 'error' ? '⚠️' : '🤖'}
{msg.content}{msg.streaming ? '▊' : ''}
))}