import React, { useState, useEffect, useRef } from 'react'; import { Box, Typography, Button, Container, Paper, TextField, IconButton, Select, MenuItem, FormControl, InputLabel, CircularProgress } from '@mui/material'; import ShuffleIcon from '@mui/icons-material/Shuffle'; import SendIcon from '@mui/icons-material/Send'; import LightModeOutlined from '@mui/icons-material/LightModeOutlined'; import DarkModeOutlined from '@mui/icons-material/DarkModeOutlined'; import SmartToyIcon from '@mui/icons-material/SmartToy'; import { useAgentStore, selectSelectedModelId, selectIsDarkMode, selectAvailableModels, selectIsLoadingModels } from '@/stores/agentStore'; import { fetchAvailableModels, generateRandomQuestion } from '@/services/api'; interface WelcomeScreenProps { onStartTask: (instruction: string, modelId: string) => void; isConnected: boolean; } export const WelcomeScreen: React.FC = ({ onStartTask, isConnected }) => { const [customTask, setCustomTask] = useState(''); const [isTyping, setIsTyping] = useState(false); const [isGeneratingQuestion, setIsGeneratingQuestion] = useState(false); const typingIntervalRef = useRef(null); const isDarkMode = useAgentStore(selectIsDarkMode); const toggleDarkMode = useAgentStore((state) => state.toggleDarkMode); const selectedModelId = useAgentStore(selectSelectedModelId); const setSelectedModelId = useAgentStore((state) => state.setSelectedModelId); const availableModels = useAgentStore(selectAvailableModels); const isLoadingModels = useAgentStore(selectIsLoadingModels); const setAvailableModels = useAgentStore((state) => state.setAvailableModels); const setIsLoadingModels = useAgentStore((state) => state.setIsLoadingModels); // Load available models on mount useEffect(() => { const loadModels = async () => { setIsLoadingModels(true); try { const models = await fetchAvailableModels(); setAvailableModels(models); // Set first model as default if current selection is not in the list if (models.length > 0 && !models.includes(selectedModelId)) { setSelectedModelId(models[0]); } } catch (error) { console.error('Failed to load models:', error); // Fallback to empty array on error setAvailableModels([]); } finally { setIsLoadingModels(false); } }; loadModels(); }, []); // eslint-disable-line react-hooks/exhaustive-deps // Clean up typing interval on unmount useEffect(() => { return () => { if (typingIntervalRef.current) { clearInterval(typingIntervalRef.current); } }; }, []); const handleWriteRandomTask = async () => { // Clear any existing typing interval if (typingIntervalRef.current) { clearInterval(typingIntervalRef.current); typingIntervalRef.current = null; } setIsGeneratingQuestion(true); try { const randomTask = await generateRandomQuestion(selectedModelId); // Clear current text setCustomTask(''); setIsTyping(true); // Type effect let currentIndex = 0; typingIntervalRef.current = setInterval(() => { if (currentIndex < randomTask.length) { setCustomTask(randomTask.substring(0, currentIndex + 1)); currentIndex++; } else { if (typingIntervalRef.current) { clearInterval(typingIntervalRef.current); typingIntervalRef.current = null; } setIsTyping(false); } }, 30); // 30ms per character } catch (error) { console.error('Failed to generate question:', error); setIsTyping(false); } finally { setIsGeneratingQuestion(false); } }; const handleCustomTask = () => { if (customTask.trim() && !isTyping) { onStartTask(customTask.trim(), selectedModelId); } }; return ( <> {/* Dark Mode Toggle - Top Right (Absolute to viewport) */} {isDarkMode ? : } {/* Title */} CUA2 Agent {/* Powered by smolagents */} Powered by {/* Hugging Face Official Logo */} smolagents {/* GitHub stars badge */} theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.08)' : 'rgba(25, 118, 210, 0.08)', borderRadius: 1, border: '1px solid', borderColor: 'primary.main', }} > 23.7k {/* Subtitle */} AI-Powered Computer Use Automation {/* Description */} Watch in real-time as AI agents write and execute Python code to complete tasks. Built by Hugging Face, smolagents is LLM-agnostic and uses 30% fewer steps than traditional agents. {/* Task Input Section */} `0 4px 16px ${theme.palette.mode === 'dark' ? 'rgba(79, 134, 198, 0.3)' : 'rgba(79, 134, 198, 0.15)'}`, } : {}, }} > {/* Input Field */} setCustomTask(e.target.value)} onKeyPress={(e) => { if (e.key === 'Enter' && !e.shiftKey && isConnected && customTask.trim() && !isTyping) { handleCustomTask(); } }} disabled={!isConnected || isTyping} multiline rows={3} sx={{ mb: 2, '& .MuiOutlinedInput-root': { borderRadius: 1.5, backgroundColor: 'action.hover', color: 'text.primary', '& fieldset': { borderColor: 'divider', }, '&:hover fieldset': { borderColor: 'text.secondary', }, '&.Mui-focused fieldset': { borderColor: 'primary.main', borderWidth: '2px', }, }, '& .MuiInputBase-input': { color: (theme) => theme.palette.mode === 'dark' ? '#FFFFFF !important' : '#000000 !important', fontWeight: 500, WebkitTextFillColor: (theme) => theme.palette.mode === 'dark' ? '#FFFFFF !important' : '#000000 !important', }, '& .MuiInputBase-input.Mui-disabled': { color: (theme) => theme.palette.mode === 'dark' ? '#FFFFFF !important' : '#000000 !important', WebkitTextFillColor: (theme) => theme.palette.mode === 'dark' ? '#FFFFFF !important' : '#000000 !important', }, '& .MuiInputBase-input::placeholder': { color: 'text.secondary', opacity: 0.7, }, }} /> {/* Model Selection + Buttons Row */} {/* Model Select */} Model {/* Buttons on the right */} {/* Connection status hint */} {!isConnected && ( Make sure the backend is running on port 8000 )} ); };