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 (
{value === index && {children}}
);
}
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
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' && (
: null}
>
{isLoading ? 'Starting...' : 'Start Recording'}
)}
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 *
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"
/>
: null}
>
{isLoading ? 'Creating...' : 'Create Robot'}
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
: null}
>
{isLoading ? 'Creating...' : 'Create Robot'}
{
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',
};