import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Box, Typography, TextField, Button, Checkbox, IconButton, Card, CircularProgress, Container, CardContent, Tabs, Tab, FormControl, Select, MenuItem, InputLabel, Collapse, FormControlLabel } from '@mui/material'; import { ArrowBack, AutoAwesome, HighlightAlt } from '@mui/icons-material'; import { useGlobalInfoStore, useCacheInvalidation } from '../../../context/globalInfo'; import { canCreateBrowserInState, getActiveBrowserId, stopRecording } from '../../../api/recording'; import { createScrapeRobot, createLLMRobot, createAndRunRecording, createCrawlRobot, createSearchRobot } from "../../../api/storage"; import { AuthContext } from '../../../context/auth'; import { GenericModal } from '../../ui/GenericModal'; interface TabPanelProps { children?: React.ReactNode; index: number; value: number; } function TabPanel(props: TabPanelProps) { const { children, value, index, ...other } = props; return ( ); } const RobotCreate: React.FC = () => { const { t } = useTranslation(); const navigate = useNavigate(); const { setBrowserId, setRecordingUrl, notify, setRecordingId, setRerenderRobots } = useGlobalInfoStore(); const [tabValue, setTabValue] = useState(0); const [url, setUrl] = useState(''); const [scrapeRobotName, setScrapeRobotName] = useState(''); const [extractRobotName, setExtractRobotName] = useState(''); const [needsLogin, setNeedsLogin] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isWarningModalOpen, setWarningModalOpen] = useState(false); const [activeBrowserId, setActiveBrowserId] = useState(''); const [outputFormats, setOutputFormats] = useState([]); const [generationMode, setGenerationMode] = useState<'agent' | 'recorder' | null>(null); const [aiPrompt, setAiPrompt] = useState(''); const [llmProvider, setLlmProvider] = useState<'anthropic' | 'openai' | 'ollama'>('ollama'); const [llmModel, setLlmModel] = useState('default'); const [llmApiKey, setLlmApiKey] = useState(''); const [llmBaseUrl, setLlmBaseUrl] = useState(''); const [aiRobotName, setAiRobotName] = useState(''); const [crawlRobotName, setCrawlRobotName] = useState(''); const [crawlUrl, setCrawlUrl] = useState(''); const [crawlMode, setCrawlMode] = useState<'domain' | 'subdomain' | 'path'>('domain'); const [crawlLimit, setCrawlLimit] = useState(50); const [crawlMaxDepth, setCrawlMaxDepth] = useState(3); const [crawlIncludePaths, setCrawlIncludePaths] = useState(''); const [crawlExcludePaths, setCrawlExcludePaths] = useState(''); const [crawlUseSitemap, setCrawlUseSitemap] = useState(true); const [crawlFollowLinks, setCrawlFollowLinks] = useState(true); const [crawlRespectRobots, setCrawlRespectRobots] = useState(true); const [showCrawlAdvanced, setShowCrawlAdvanced] = useState(false); const [searchRobotName, setSearchRobotName] = useState(''); const [searchQuery, setSearchQuery] = useState(''); const [searchLimit, setSearchLimit] = useState(10); const [searchProvider] = useState<'duckduckgo'>('duckduckgo'); const [searchMode, setSearchMode] = useState<'discover' | 'scrape'>('discover'); const [searchTimeRange, setSearchTimeRange] = useState<'day' | 'week' | 'month' | 'year' | ''>(''); const { state } = React.useContext(AuthContext); const { user } = state; const { addOptimisticRobot, removeOptimisticRobot, invalidateRecordings, invalidateRuns, addOptimisticRun } = useCacheInvalidation(); const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { setTabValue(newValue); }; const handleStartRecording = async () => { if (!url.trim()) { notify('error', 'Please enter a valid URL'); return; } setIsLoading(true); try { const canCreateRecording = await canCreateBrowserInState("recording"); if (!canCreateRecording) { const activeBrowser = await getActiveBrowserId(); if (activeBrowser) { setActiveBrowserId(activeBrowser); setWarningModalOpen(true); } else { notify('warning', t('recordingtable.notifications.browser_limit_warning')); } setIsLoading(false); return; } setBrowserId('new-recording'); setRecordingUrl(url); window.sessionStorage.setItem('browserId', 'new-recording'); window.sessionStorage.setItem('recordingUrl', url); window.sessionStorage.setItem('initialUrl', url); window.sessionStorage.setItem('needsLogin', needsLogin.toString()); const sessionId = Date.now().toString(); window.sessionStorage.setItem('recordingSessionId', sessionId); window.open(`/recording-setup?session=${sessionId}`, '_blank'); window.sessionStorage.setItem('nextTabIsRecording', 'true'); // Reset loading state immediately after opening new tab setIsLoading(false); navigate('/robots'); } catch (error) { console.error('Error starting recording:', error); notify('error', 'Failed to start recording. Please try again.'); setIsLoading(false); } }; const handleDiscardAndCreate = async () => { if (activeBrowserId) { await stopRecording(activeBrowserId); notify('warning', t('browser_recording.notifications.terminated')); } setWarningModalOpen(false); setIsLoading(false); // Continue with the original Recording logic setBrowserId('new-recording'); setRecordingUrl(url); window.sessionStorage.setItem('browserId', 'new-recording'); window.sessionStorage.setItem('recordingUrl', url); window.sessionStorage.setItem('initialUrl', url); window.sessionStorage.setItem('needsLogin', needsLogin.toString()); const sessionId = Date.now().toString(); window.sessionStorage.setItem('recordingSessionId', sessionId); window.open(`/recording-setup?session=${sessionId}`, '_blank'); window.sessionStorage.setItem('nextTabIsRecording', 'true'); navigate('/robots'); }; const handleCreateCrawlRobot = async () => { if (!crawlUrl.trim()) { notify('error', 'Please enter a valid URL'); return; } if (!crawlRobotName.trim()) { notify('error', 'Please enter a robot name'); return; } setIsLoading(true); const result = await createCrawlRobot( crawlUrl, crawlRobotName, { mode: crawlMode, limit: crawlLimit, maxDepth: crawlMaxDepth, includePaths: crawlIncludePaths ? crawlIncludePaths.split(',').map(p => p.trim()) : [], excludePaths: crawlExcludePaths ? crawlExcludePaths.split(',').map(p => p.trim()) : [], useSitemap: crawlUseSitemap, followLinks: crawlFollowLinks, respectRobots: crawlRespectRobots } ); setIsLoading(false); if (result) { invalidateRecordings(); notify('success', `${crawlRobotName} created successfully!`); navigate('/robots'); } else { notify('error', 'Failed to create crawl robot'); } }; const handleCreateSearchRobot = async () => { if (!searchQuery.trim()) { notify('error', 'Please enter a search query'); return; } if (!searchRobotName.trim()) { notify('error', 'Please enter a robot name'); return; } setIsLoading(true); const result = await createSearchRobot( searchRobotName, { query: searchQuery, limit: searchLimit, provider: searchProvider, filters: { timeRange: searchTimeRange ? searchTimeRange as 'day' | 'week' | 'month' | 'year' : undefined }, mode: searchMode } ); setIsLoading(false); if (result) { invalidateRecordings(); notify('success', `${searchRobotName} created successfully!`); navigate('/robots'); } else { notify('error', 'Failed to create search robot'); } }; return ( navigate('/robots')} sx={{ ml: -1, mr: 1, color: theme => theme.palette.text.primary, backgroundColor: 'transparent !important', '&:hover': { backgroundColor: 'transparent !important', }, '&:active': { backgroundColor: 'transparent !important', }, '&:focus': { backgroundColor: 'transparent !important', }, '&:focus-visible': { backgroundColor: 'transparent !important', }, }} disableRipple aria-label="Go back" > Create New Robot Maxun Logo Extract structured data from websites using AI or record your own extraction workflow. setUrl(e.target.value)} label="Website URL" /> Choose How to Build setGenerationMode('recorder')} sx={{ flex: 1, cursor: 'pointer', border: '2px solid', borderColor: generationMode === 'recorder' ? '#ff00c3' : 'divider', transition: 'all 0.2s', '&:hover': { borderColor: '#ff00c3', } }} > Recorder Mode Record your actions into a workflow. setGenerationMode('agent')} sx={{ flex: 1, cursor: 'pointer', border: '2px solid', borderColor: generationMode === 'agent' ? '#ff00c3' : 'divider', transition: 'all 0.2s', '&:hover': { borderColor: '#ff00c3', }, position: 'relative' }} > Beta AI Mode Describe the task. It builds it for you. {generationMode === 'agent' && ( setExtractRobotName(e.target.value)} label="Name" /> setAiPrompt(e.target.value)} label="Extraction Prompt" /> LLM Provider Model {/* API Key for non-Ollama providers */} {llmProvider !== 'ollama' && ( setLlmApiKey(e.target.value)} label="API Key (Optional if set in .env)" /> )} {llmProvider === 'ollama' && ( setLlmBaseUrl(e.target.value)} label="Ollama Base URL (Optional)" /> )} )} {generationMode === 'recorder' && ( )} Maxun Logo Turn websites into LLM-ready Markdown, clean HTML, or screenshots for AI apps. setScrapeRobotName(e.target.value)} sx={{ mb: 2 }} label="Name" /> setUrl(e.target.value)} label="Website URL" sx={{ mb: 2 }} /> Output Formats * Maxun Logo Crawl entire websites and gather data from multiple pages automatically. setCrawlRobotName(e.target.value)} sx={{ mb: 2 }} /> setCrawlUrl(e.target.value)} sx={{ mb: 2 }} /> setCrawlLimit(parseInt(e.target.value) || 10)} sx={{ mb: 2 }} /> Crawl Scope setCrawlMaxDepth(parseInt(e.target.value) || 3)} sx={{ mb: 2 }} helperText="How many links deep to follow (default: 3)" FormHelperTextProps={{ sx: { ml: 0 } }} /> setCrawlIncludePaths(e.target.value)} sx={{ mb: 2 }} helperText="Only crawl URLs matching these paths (comma-separated)" FormHelperTextProps={{ sx: { ml: 0 } }} /> setCrawlExcludePaths(e.target.value)} sx={{ mb: 2 }} helperText="Skip URLs matching these paths (comma-separated)" FormHelperTextProps={{ sx: { ml: 0 } }} /> setCrawlUseSitemap(e.target.checked)} /> } label="Use sitemap.xml for URL discovery" /> setCrawlFollowLinks(e.target.checked)} /> } label="Follow links on pages" /> setCrawlRespectRobots(e.target.checked)} /> } label="Respect robots.txt" /> Maxun Logo Search the web and gather data from relevant results. setSearchRobotName(e.target.value)} sx={{ mb: 2 }} /> setSearchQuery(e.target.value)} sx={{ mb: 2 }} /> setSearchLimit(parseInt(e.target.value) || 10)} sx={{ mb: 2 }} /> Mode Time Range { setWarningModalOpen(false); setIsLoading(false); }} modalStyle={modalStyle}>
{t('recordingtable.warning_modal.title')} {t('recordingtable.warning_modal.message')}
); }; export default RobotCreate; const modalStyle = { top: '50%', left: '50%', transform: 'translate(-50%, -50%)', width: '30%', backgroundColor: 'background.paper', p: 4, height: 'fit-content', display: 'block', padding: '20px', };