From c8c58eef54b1b369ddc008d99a262d98628bed71 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Thu, 11 Dec 2025 02:07:15 +0530 Subject: [PATCH 01/21] feat: add ai powered robot generation --- server/src/routes/storage.ts | 89 +++++++- src/api/storage.ts | 40 +++- src/components/robot/RecordingsTable.tsx | 46 +++- src/components/robot/pages/RobotCreate.tsx | 249 ++++++++++++++++++++- 4 files changed, 417 insertions(+), 7 deletions(-) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index 3941b01f..7da96179 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -15,6 +15,7 @@ import { encrypt, decrypt } from '../utils/auth'; import { WorkflowFile } from 'maxun-core'; import { cancelScheduledWorkflow, scheduleWorkflow } from '../storage/schedule'; import { pgBossClient } from '../storage/pgboss'; +import { WorkflowEnricher } from '../sdk/workflowEnricher'; export const router = Router(); @@ -518,6 +519,92 @@ router.post('/recordings/scrape', requireSignIn, async (req: AuthenticatedReques } }); +/** + * POST endpoint for creating an LLM-powered extraction robot + */ +router.post('/recordings/llm', requireSignIn, async (req: AuthenticatedRequest, res) => { + try { + const { url, prompt, llmProvider, llmModel, llmApiKey, llmBaseUrl, robotName } = req.body; + + if (!url || !prompt) { + return res.status(400).json({ error: 'Both "url" and "prompt" fields are required.' }); + } + + if (!req.user) { + return res.status(401).send({ error: 'Unauthorized' }); + } + + try { + new URL(url); + } catch (err) { + return res.status(400).json({ error: 'Invalid URL format' }); + } + + logger.log('info', `Starting LLM workflow generation for URL: ${url}`); + + const workflowResult = await WorkflowEnricher.generateWorkflowFromPrompt(url, prompt, { + provider: llmProvider || 'ollama', + model: llmModel, + apiKey: llmApiKey, + baseUrl: llmBaseUrl + }, req.user.id); + + if (!workflowResult.success || !workflowResult.workflow) { + logger.log('error', `Failed to generate workflow: ${JSON.stringify(workflowResult.errors)}`); + return res.status(400).json({ + error: 'Failed to generate workflow from prompt', + details: workflowResult.errors + }); + } + + const robotId = uuid(); + const currentTimestamp = new Date().toISOString(); + const finalRobotName = robotName || `LLM Extract: ${prompt.substring(0, 50)}`; + + const newRobot = await Robot.create({ + id: uuid(), + userId: req.user.id, + recording_meta: { + name: finalRobotName, + id: robotId, + createdAt: currentTimestamp, + updatedAt: currentTimestamp, + pairs: workflowResult.workflow.length, + params: [], + type: 'extract', + url: workflowResult.url || url, + }, + recording: { workflow: workflowResult.workflow }, + google_sheet_email: null, + google_sheet_name: null, + google_sheet_id: null, + google_access_token: null, + google_refresh_token: null, + schedule: null, + }); + + logger.log('info', `LLM robot created with id: ${newRobot.id}`); + capture('maxun-oss-robot-created', { + robot_meta: newRobot.recording_meta, + recording: newRobot.recording, + llm_provider: llmProvider || 'ollama', + }); + + return res.status(201).json({ + message: 'LLM robot created successfully.', + robot: newRobot, + }); + } catch (error) { + if (error instanceof Error) { + logger.log('error', `Error creating LLM robot: ${error.message}`); + return res.status(500).json({ error: error.message }); + } else { + logger.log('error', 'Unknown error creating LLM robot'); + return res.status(500).json({ error: 'An unknown error occurred.' }); + } + } +}); + /** * DELETE endpoint for deleting a recording from the storage. */ @@ -894,7 +981,7 @@ router.put('/schedule/:id/', requireSignIn, async (req: AuthenticatedRequest, re logger.log('warn', `Failed to cancel existing schedule for robot ${id}: ${cancelError}`); } - const jobId = await scheduleWorkflow(id, req.user.id, cronExpression, timezone); + await scheduleWorkflow(id, req.user.id, cronExpression, timezone); const nextRunAt = computeNextRun(cronExpression, timezone); diff --git a/src/api/storage.ts b/src/api/storage.ts index d2b28d5e..4ac2f01b 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -58,12 +58,50 @@ export const createScrapeRobot = async ( } }; +export const createLLMRobot = async ( + url: string, + prompt: string, + llmProvider?: 'anthropic' | 'openai' | 'ollama', + llmModel?: string, + llmApiKey?: string, + llmBaseUrl?: string, + robotName?: string +): Promise => { + try { + const response = await axios.post( + `${apiUrl}/storage/recordings/llm`, + { + url, + prompt, + llmProvider, + llmModel, + llmApiKey, + llmBaseUrl, + robotName, + }, + { + headers: { 'Content-Type': 'application/json' }, + withCredentials: true, + timeout: 300000, + } + ); + + if (response.status === 201) { + return response.data; + } else { + throw new Error('Failed to create LLM robot'); + } + } catch (error: any) { + console.error('Error creating LLM robot:', error); + return null; + } +}; + export const updateRecording = async (id: string, data: { name?: string; limits?: Array<{pairIndex: number, actionIndex: number, argIndex: number, limit: number}>; credentials?: Credentials; targetUrl?: string; - // optional full workflow replacement (useful for action renames) workflow?: any[]; }): Promise => { try { diff --git a/src/components/robot/RecordingsTable.tsx b/src/components/robot/RecordingsTable.tsx index aed9ea74..704a7f97 100644 --- a/src/components/robot/RecordingsTable.tsx +++ b/src/components/robot/RecordingsTable.tsx @@ -81,8 +81,46 @@ interface RecordingsTableProps { handleDuplicateRobot: (id: string, name: string, params: string[]) => void; } +const LoadingRobotRow = memo(({ row, columns }: any) => { + return ( + + {columns.map((column: Column) => { + if (column.id === 'name') { + return ( + + + + + {row.name} (Creating...) + + + + ); + } else if (column.id === 'interpret') { + return ( + + + + ); + } else { + return ( + + - + + ); + } + })} + + ); +}); + // Virtualized row component for efficient rendering const TableRowMemoized = memo(({ row, columns, handlers }: any) => { + // If robot is loading, show loading row + if (row.isLoading) { + return ; + } + return ( {columns.map((column: Column) => { @@ -261,7 +299,9 @@ export const RecordingsTable = ({ id: index, ...recording.recording_meta, content: recording.recording, - parsedDate + parsedDate, + isLoading: recording.isLoading || false, + isOptimistic: recording.isOptimistic || false }; } return null; @@ -552,7 +592,7 @@ export const RecordingsTable = ({ <> - {/* */} + {columns.map((column) => ( ))} - {/* */} + {visibleRows.map((row) => ( { const [activeBrowserId, setActiveBrowserId] = useState(''); const [outputFormats, setOutputFormats] = useState([]); + // AI Extract tab state + const [aiPrompt, setAiPrompt] = useState(''); + const [llmProvider, setLlmProvider] = useState<'anthropic' | 'openai' | 'ollama'>('ollama'); + const [llmModel, setLlmModel] = useState(''); + const [llmApiKey, setLlmApiKey] = useState(''); + const [llmBaseUrl, setLlmBaseUrl] = useState(''); + const [aiRobotName, setAiRobotName] = useState(''); + const { state } = React.useContext(AuthContext); const { user } = state; + const { addOptimisticRobot, removeOptimisticRobot, invalidateRecordings } = useCacheInvalidation(); const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { setTabValue(newValue); @@ -206,6 +215,7 @@ const RobotCreate: React.FC = () => { }} > + @@ -362,6 +372,241 @@ const RobotCreate: React.FC = () => { + + + Maxun Logo + + + AI-powered extraction: Describe what you want to extract in natural language. + + + + setAiRobotName(e.target.value)} + sx={{ mb: 2 }} + label="Robot Name (Optional)" + /> + setUrl(e.target.value)} + label="Website URL" + sx={{ mb: 2 }} + /> + setAiPrompt(e.target.value)} + label="Extraction Prompt" + sx={{ mb: 2 }} + /> + + + + LLM Provider + + + + + Model (Optional) + + + + + {llmProvider !== 'ollama' && ( + setLlmApiKey(e.target.value)} + label="API Key (Optional if set in .env)" + sx={{ mb: 2 }} + /> + )} + + {llmProvider === 'ollama' && ( + setLlmBaseUrl(e.target.value)} + label="Ollama Base URL (Optional)" + sx={{ mb: 2 }} + /> + )} + + + + + + + + Date: Thu, 11 Dec 2025 13:40:49 +0530 Subject: [PATCH 02/21] feat: ui enhancements and fixes --- server/src/routes/storage.ts | 6 +- src/components/robot/pages/RobotCreate.tsx | 668 ++++++++++----------- src/components/run/RunsTable.tsx | 101 +++- src/context/globalInfo.tsx | 28 +- src/pages/MainPage.tsx | 4 + 5 files changed, 435 insertions(+), 372 deletions(-) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index 7da96179..7eb0dd79 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -542,12 +542,12 @@ router.post('/recordings/llm', requireSignIn, async (req: AuthenticatedRequest, logger.log('info', `Starting LLM workflow generation for URL: ${url}`); - const workflowResult = await WorkflowEnricher.generateWorkflowFromPrompt(url, prompt, { + const workflowResult = await WorkflowEnricher.generateWorkflowFromPrompt(url, prompt, req.user.id, { provider: llmProvider || 'ollama', model: llmModel, apiKey: llmApiKey, baseUrl: llmBaseUrl - }, req.user.id); + }); if (!workflowResult.success || !workflowResult.workflow) { logger.log('error', `Failed to generate workflow: ${JSON.stringify(workflowResult.errors)}`); @@ -1321,4 +1321,4 @@ export async function recoverOrphanedRuns() { } } -export { processQueuedRuns }; +export { processQueuedRuns }; \ No newline at end of file diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index a91a7314..4aacfc7c 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -21,7 +21,7 @@ import { MenuItem, InputLabel } from '@mui/material'; -import { ArrowBack, PlayCircleOutline, Article, Code, Description } from '@mui/icons-material'; +import { ArrowBack, PlayCircleOutline, Article, Code, Description, SmartToy } from '@mui/icons-material'; import { useGlobalInfoStore, useCacheInvalidation } from '../../../context/globalInfo'; import { canCreateBrowserInState, getActiveBrowserId, stopRecording } from '../../../api/recording'; import { createScrapeRobot, createLLMRobot, createAndRunRecording } from "../../../api/storage"; @@ -59,11 +59,13 @@ const RobotCreate: React.FC = () => { 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); // AI Extract tab state const [aiPrompt, setAiPrompt] = useState(''); @@ -75,7 +77,7 @@ const RobotCreate: React.FC = () => { const { state } = React.useContext(AuthContext); const { user } = state; - const { addOptimisticRobot, removeOptimisticRobot, invalidateRecordings } = useCacheInvalidation(); + const { addOptimisticRobot, removeOptimisticRobot, invalidateRecordings, invalidateRuns, addOptimisticRun } = useCacheInvalidation(); const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { setTabValue(newValue); @@ -215,190 +217,16 @@ const RobotCreate: React.FC = () => { }} > - - + - {/* Logo (kept as original) */} - Maxun Logo - - - Extract structured data from websites in a few clicks. - - - {/* Origin URL Input */} - - setUrl(e.target.value)} - /> - - - {/* Checkbox */} - - setNeedsLogin(e.target.checked)} - color="primary" - /> - } - label="This website needs logging in." - /> - - - {/* Button */} - - - - - - - First time creating a robot? - - - Get help and learn how to use Maxun effectively. - - - - - {/* YouTube Tutorials */} - - window.open("https://www.youtube.com/@MaxunOSS/videos", "_blank")} - > - - theme.palette.mode === 'light' ? 'rgba(0, 0, 0, 0.54)' : '', - }} - > - - - - - Video Tutorials - - - Watch step-by-step guides - - - - - - - {/* Documentation */} - - window.open("https://docs.maxun.dev", "_blank")} - > - - theme.palette.mode === 'light' ? 'rgba(0, 0, 0, 0.54)' : '', - }} - > -
- - - - Documentation - - - Explore detailed guides - - - - - - - - - - - - - Maxun Logo - - - AI-powered extraction: Describe what you want to extract in natural language. - - - - setAiRobotName(e.target.value)} - sx={{ mb: 2 }} - label="Robot Name (Optional)" - /> + {/* Website URL */} + { value={url} onChange={(e) => setUrl(e.target.value)} label="Website URL" - sx={{ mb: 2 }} /> - setAiPrompt(e.target.value)} - label="Extraction Prompt" - sx={{ mb: 2 }} - /> - - - - LLM Provider - - - - - Model (Optional) - - - - - {llmProvider !== 'ollama' && ( - setLlmApiKey(e.target.value)} - label="API Key (Optional if set in .env)" - sx={{ mb: 2 }} - /> - )} - - {llmProvider === 'ollama' && ( - setLlmBaseUrl(e.target.value)} - label="Ollama Base URL (Optional)" - sx={{ mb: 2 }} - /> - )} - + addOptimisticRobot(optimisticRobot); + + notify('info', `Robot ${robotDisplayName} creation started`); + navigate('/robots'); + + try { + const result = await createLLMRobot( + url, + aiPrompt, + llmProvider, + llmModel || undefined, + llmApiKey || undefined, + llmBaseUrl || undefined, + extractRobotName + ); + + removeOptimisticRobot(tempRobotId); + + if (!result || !result.robot) { + notify('error', 'Failed to create AI robot. Please check your LLM configuration.'); + invalidateRecordings(); + return; + } + + const robotMetaId = result.robot.recording_meta.id; + const robotName = result.robot.recording_meta.name; + + invalidateRecordings(); + notify('success', `${robotName} created successfully!`); + + const optimisticRun = { + id: robotMetaId, + runId: `temp-${Date.now()}`, + status: 'running', + name: robotName, + startedAt: new Date().toISOString(), + finishedAt: '', + robotMetaId: robotMetaId, + log: 'Starting...', + isOptimistic: true + }; + + addOptimisticRun(optimisticRun); + + const runResponse = await createAndRunRecording(robotMetaId, { + maxConcurrency: 1, + maxRepeats: 1, + debug: false + }); + + invalidateRuns(); + + if (runResponse && runResponse.runId) { + await new Promise(resolve => setTimeout(resolve, 300)); + navigate(`/runs/${robotMetaId}/run/${runResponse.runId}`); + notify('info', `Run started: ${robotName}`); + } else { + notify('warning', 'Robot created but failed to start execution.'); + navigate('/robots'); + } + } catch (error: any) { + console.error('Error in AI robot creation:', error); + removeOptimisticRobot(tempRobotId); + invalidateRecordings(); + notify('error', error?.message || 'Failed to create and run AI robot'); + } + }} + disabled={!url.trim() || !extractRobotName.trim() || !aiPrompt.trim() || isLoading} + sx={{ + bgcolor: '#ff00c3', + py: 1.4, + fontSize: '1rem', + textTransform: 'none', + borderRadius: 2 + }} + startIcon={isLoading ? : null} + > + {isLoading ? 'Creating & Running...' : 'Create & Run Robot'} + + + )} + + {generationMode === 'recorder' && ( + + + + )} - + = ({ const { notify, rerenderRuns, setRerenderRuns } = useGlobalInfoStore(); const { data: rows = [], isLoading: isFetching, error, refetch } = useCachedRuns(); + const { invalidateRuns } = useCacheInvalidation(); + + const activeSocketsRef = useRef>(new Map()); const [searchTerm, setSearchTerm] = useState(''); const [paginationStates, setPaginationStates] = useState({}); @@ -285,6 +290,98 @@ export const RunsTable: React.FC = ({ } }, [rerenderRuns, refetch, setRerenderRuns]); + useEffect(() => { + if (!rows || rows.length === 0) return; + + const activeRuns = rows.filter((row: Data) => + row.status === 'running' && row.browserId && row.browserId.trim() !== '' + ); + + activeRuns.forEach((run: Data) => { + const { browserId, runId: currentRunId, name } = run; + + if (activeSocketsRef.current.has(browserId)) { + return; + } + + console.log(`[RunsTable] Connecting to browser socket: ${browserId} for run: ${currentRunId}`); + + try { + const socket = io(`${apiUrl}/${browserId}`, { + transports: ['websocket'], + rejectUnauthorized: false + }); + + socket.on('connect', () => { + console.log(`[RunsTable] Connected to browser ${browserId}`); + }); + + socket.on('debugMessage', (msg: string) => { + console.log(`[RunsTable] Debug message for ${browserId}:`, msg); + // Optionally update logs in real-time here + }); + + socket.on('run-completed', (data: any) => { + console.log(`[RunsTable] Run completed for ${browserId}:`, data); + + // Invalidate cache to show updated run status + invalidateRuns(); + setRerenderRuns(true); + + // Show notification + if (data.status === 'success') { + notify('success', t('main_page.notifications.interpretation_success', { name: data.robotName || name })); + } else { + notify('error', t('main_page.notifications.interpretation_failed', { name: data.robotName || name })); + } + + socket.disconnect(); + activeSocketsRef.current.delete(browserId); + }); + + socket.on('urlChanged', (url: string) => { + console.log(`[RunsTable] URL changed for ${browserId}:`, url); + }); + + socket.on('dom-snapshot-loading', () => { + console.log(`[RunsTable] DOM snapshot loading for ${browserId}`); + }); + + socket.on('connect_error', (error: Error) => { + console.error(`[RunsTable] Connection error for browser ${browserId}:`, error.message); + }); + + socket.on('disconnect', (reason: string) => { + console.log(`[RunsTable] Disconnected from browser ${browserId}:`, reason); + activeSocketsRef.current.delete(browserId); + }); + + activeSocketsRef.current.set(browserId, socket); + } catch (error) { + console.error(`[RunsTable] Error connecting to browser ${browserId}:`, error); + } + }); + + // Disconnect from sockets for runs that are no longer active + const activeBrowserIds = new Set(activeRuns.map((run: Data) => run.browserId)); + activeSocketsRef.current.forEach((socket, browserId) => { + if (!activeBrowserIds.has(browserId)) { + console.log(`[RunsTable] Disconnecting from inactive browser: ${browserId}`); + socket.disconnect(); + activeSocketsRef.current.delete(browserId); + } + }); + + // Cleanup on unmount + return () => { + console.log('[RunsTable] Cleaning up all socket connections'); + activeSocketsRef.current.forEach((socket) => { + socket.disconnect(); + }); + activeSocketsRef.current.clear(); + }; + }, [rows, notify, t, invalidateRuns, setRerenderRuns]); + const handleDelete = useCallback(() => { notify('success', t('runstable.notifications.delete_success')); refetch(); diff --git a/src/context/globalInfo.tsx b/src/context/globalInfo.tsx index 3dddab1c..c5bbc044 100644 --- a/src/context/globalInfo.tsx +++ b/src/context/globalInfo.tsx @@ -204,31 +204,47 @@ export const useCachedRuns = () => { export const useCacheInvalidation = () => { const queryClient = useQueryClient(); - + const invalidateRuns = () => { queryClient.invalidateQueries({ queryKey: dataCacheKeys.runs }); }; - + const invalidateRecordings = () => { queryClient.invalidateQueries({ queryKey: dataCacheKeys.recordings }); }; - + const addOptimisticRun = (newRun: any) => { queryClient.setQueryData(dataCacheKeys.runs, (oldData: any) => { if (!oldData) return [{ id: 0, ...newRun }]; return [{ id: oldData.length, ...newRun }, ...oldData]; }); }; - + + const addOptimisticRobot = (newRobot: any) => { + queryClient.setQueryData(dataCacheKeys.recordings, (oldData: any) => { + if (!oldData) return [newRobot]; + return [newRobot, ...oldData]; + }); + }; + + const removeOptimisticRobot = (tempId: string) => { + queryClient.setQueryData(dataCacheKeys.recordings, (oldData: any) => { + if (!oldData) return []; + return oldData.filter((robot: any) => robot.id !== tempId); + }); + }; + const invalidateAllCache = () => { invalidateRuns(); invalidateRecordings(); }; - + return { invalidateRuns, - invalidateRecordings, + invalidateRecordings, addOptimisticRun, + addOptimisticRobot, + removeOptimisticRobot, invalidateAllCache }; }; diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index 4f302135..004785f7 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -53,6 +53,10 @@ export const MainPage = ({ handleEditRecording, initialContent }: MainPageProps) const { invalidateRuns, addOptimisticRun } = useCacheInvalidation(); const navigate = useNavigate(); + React.useEffect(() => { + setContent(initialContent); + }, [initialContent]); + const { state } = useContext(AuthContext); const { user } = state; From 5ba8a2889e8aa2e347ff03f85880eae564502698 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 11 Dec 2025 14:28:48 +0530 Subject: [PATCH 03/21] fix: lint --- src/components/robot/pages/RobotCreate.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index 4aacfc7c..3d663c79 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -158,11 +158,6 @@ const RobotCreate: React.FC = () => { navigate('/robots'); }; - - - - - return ( From b5add96446f230d79f982f36be7773d0a0af9706 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 11 Dec 2025 14:39:35 +0530 Subject: [PATCH 04/21] feat: bring back branding + ddescription --- src/components/robot/pages/RobotCreate.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index 3d663c79..e387a2e9 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -220,6 +220,20 @@ const RobotCreate: React.FC = () => { + Maxun Logo + + + Extract structured data from websites using AI or record your own extraction workflow. + {/* Website URL */} Date: Thu, 11 Dec 2025 14:40:25 +0530 Subject: [PATCH 05/21] feat: change card order --- src/components/robot/pages/RobotCreate.tsx | 47 +++++++++++----------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index e387a2e9..5a0a306c 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -253,30 +253,6 @@ const RobotCreate: React.FC = () => { - setGenerationMode('agent')} - sx={{ - flex: 1, - cursor: 'pointer', - border: '2px solid', - borderColor: generationMode === 'agent' ? '#ff00c3' : 'divider', - transition: 'all 0.2s', - '&:hover': { - borderColor: '#ff00c3', - } - }} - > - - - - AI Powered - - - AI will take care of everything - - - - setGenerationMode('recorder')} sx={{ @@ -300,6 +276,29 @@ const RobotCreate: React.FC = () => { + setGenerationMode('agent')} + sx={{ + flex: 1, + cursor: 'pointer', + border: '2px solid', + borderColor: generationMode === 'agent' ? '#ff00c3' : 'divider', + transition: 'all 0.2s', + '&:hover': { + borderColor: '#ff00c3', + } + }} + > + + + + AI Powered + + + AI will take care of everything + + + From 372ea7e7f226dcb4d836bde08ae66b78eceb9fa1 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 11 Dec 2025 14:43:10 +0530 Subject: [PATCH 06/21] chore: cleanup comments --- src/components/robot/pages/RobotCreate.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index 5a0a306c..0e44a023 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -234,7 +234,6 @@ const RobotCreate: React.FC = () => { Extract structured data from websites using AI or record your own extraction workflow. - {/* Website URL */} { /> - {/* Generation Mode Selection */} Generation Mode @@ -302,10 +300,8 @@ const RobotCreate: React.FC = () => { - {/* Conditional rendering based on generation mode */} {generationMode === 'agent' && ( - {/* Robot Name */} { /> - {/* Extraction Prompt */} { /> - {/* LLM Provider and Model */} LLM Provider @@ -402,7 +396,6 @@ const RobotCreate: React.FC = () => { )} - {/* Ollama Base URL */} {llmProvider === 'ollama' && ( { )} - {/* Create & Run Button */} - - )} + }} + disabled={!url.trim() || !extractRobotName.trim() || !aiPrompt.trim() || isLoading} + sx={{ + bgcolor: '#ff00c3', + py: 1.4, + fontSize: '1rem', + textTransform: 'none', + borderRadius: 2 + }} + startIcon={isLoading ? : null} + > + {isLoading ? 'Creating & Running...' : 'Create & Run Robot'} + + + )} - {generationMode === 'recorder' && ( - - - - )} - + {generationMode === 'recorder' && ( + + + + )} + From b71b210be2b75f2e40b7dfe6a87b157d145add8a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 11 Dec 2025 14:46:01 +0530 Subject: [PATCH 08/21] feat: recorder mode --- src/components/robot/pages/RobotCreate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index fa6959db..d35ab495 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -267,7 +267,7 @@ const RobotCreate: React.FC = () => { - Manual Mode + Recorder Mode Capture actions step-by-step From e20f73da1034ce04c34dc904198fddf1dbc171b1 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 11 Dec 2025 14:55:54 +0530 Subject: [PATCH 09/21] feat: choose how to build --- src/components/robot/pages/RobotCreate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index d35ab495..1bb4713f 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -247,7 +247,7 @@ const RobotCreate: React.FC = () => { - Generation Mode + Choose How to Build From de294ce3f8b18b667b787921c4b6923fe808bd7e Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 11 Dec 2025 14:58:53 +0530 Subject: [PATCH 10/21] feat: recorder mode --- src/components/robot/pages/RobotCreate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index 1bb4713f..ca1c8242 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -270,7 +270,7 @@ const RobotCreate: React.FC = () => { Recorder Mode - Capture actions step-by-step + Record your actions into a workflow. From 1e0af2d52d9a08f05362a06871918366a8a533d9 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 11 Dec 2025 15:02:52 +0530 Subject: [PATCH 11/21] feat: use HighlightAlt icon for recorder --- src/components/robot/pages/RobotCreate.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index ca1c8242..64ba9cc5 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -21,7 +21,7 @@ import { MenuItem, InputLabel } from '@mui/material'; -import { ArrowBack, PlayCircleOutline, Article, Code, Description, SmartToy } from '@mui/icons-material'; +import { ArrowBack, PlayCircleOutline, Article, Code, Description, SmartToy, HighlightAlt } from '@mui/icons-material'; import { useGlobalInfoStore, useCacheInvalidation } from '../../../context/globalInfo'; import { canCreateBrowserInState, getActiveBrowserId, stopRecording } from '../../../api/recording'; import { createScrapeRobot, createLLMRobot, createAndRunRecording } from "../../../api/storage"; @@ -265,7 +265,7 @@ const RobotCreate: React.FC = () => { }} > - + Recorder Mode From c1af8493ac5561899e0efe332455b2c4bb775991 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 11 Dec 2025 15:10:45 +0530 Subject: [PATCH 12/21] feat: use AutoAwesome icon for recorder --- src/components/robot/pages/RobotCreate.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index 64ba9cc5..cb27f14e 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -21,7 +21,7 @@ import { MenuItem, InputLabel } from '@mui/material'; -import { ArrowBack, PlayCircleOutline, Article, Code, Description, SmartToy, HighlightAlt } from '@mui/icons-material'; +import { ArrowBack, PlayCircleOutline, Article, Code, Description, AutoAwesome, HighlightAlt } from '@mui/icons-material'; import { useGlobalInfoStore, useCacheInvalidation } from '../../../context/globalInfo'; import { canCreateBrowserInState, getActiveBrowserId, stopRecording } from '../../../api/recording'; import { createScrapeRobot, createLLMRobot, createAndRunRecording } from "../../../api/storage"; @@ -308,7 +308,7 @@ const RobotCreate: React.FC = () => { - + AI Mode From 89745de0fe3fc7973afffc201820bb83e5369b86 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 11 Dec 2025 15:11:31 +0530 Subject: [PATCH 13/21] chore: cleanup unused imports --- src/components/robot/pages/RobotCreate.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index cb27f14e..a40f0e3e 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -6,10 +6,8 @@ import { Typography, TextField, Button, - FormControlLabel, Checkbox, IconButton, - Grid, Card, CircularProgress, Container, @@ -21,7 +19,7 @@ import { MenuItem, InputLabel } from '@mui/material'; -import { ArrowBack, PlayCircleOutline, Article, Code, Description, AutoAwesome, HighlightAlt } from '@mui/icons-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 } from "../../../api/storage"; @@ -216,7 +214,6 @@ const RobotCreate: React.FC = () => { - @@ -289,7 +286,6 @@ const RobotCreate: React.FC = () => { position: 'relative' }} > - {/* Beta Tag */} Date: Thu, 11 Dec 2025 15:45:52 +0530 Subject: [PATCH 14/21] fix: model selection preview --- src/components/robot/pages/RobotCreate.tsx | 45 ++++++++++------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index a40f0e3e..bbb6613b 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -65,10 +65,9 @@ const RobotCreate: React.FC = () => { const [outputFormats, setOutputFormats] = useState([]); const [generationMode, setGenerationMode] = useState<'agent' | 'recorder' | null>(null); - // AI Extract tab state const [aiPrompt, setAiPrompt] = useState(''); const [llmProvider, setLlmProvider] = useState<'anthropic' | 'openai' | 'ollama'>('ollama'); - const [llmModel, setLlmModel] = useState(''); + const [llmModel, setLlmModel] = useState('default'); const [llmApiKey, setLlmApiKey] = useState(''); const [llmBaseUrl, setLlmBaseUrl] = useState(''); const [aiRobotName, setAiRobotName] = useState(''); @@ -350,7 +349,7 @@ const RobotCreate: React.FC = () => { onChange={(e) => { const provider = e.target.value as 'anthropic' | 'openai' | 'ollama'; setLlmProvider(provider); - setLlmModel(''); + setLlmModel('default'); if (provider === 'ollama') { setLlmBaseUrl('http://localhost:11434'); } else { @@ -371,26 +370,24 @@ const RobotCreate: React.FC = () => { label="Model" onChange={(e) => setLlmModel(e.target.value)} > - {llmProvider === 'ollama' && ( - <> - Default (llama3.2-vision) - llama3.2-vision - llama3.2 - - )} - {llmProvider === 'anthropic' && ( - <> - Default (claude-3-5-sonnet) - claude-3-5-sonnet-20241022 - claude-3-opus-20240229 - - )} - {llmProvider === 'openai' && ( - <> - Default (gpt-4-vision-preview) - gpt-4-vision-preview - gpt-4o - + {llmProvider === 'ollama' ? ( + [ + Default (llama3.2-vision), + llama3.2-vision, + llama3.2 + ] + ) : llmProvider === 'anthropic' ? ( + [ + Default (claude-3-5-sonnet), + claude-3-5-sonnet-20241022, + claude-3-opus-20240229 + ] + ) : ( + [ + Default (gpt-4-vision-preview), + gpt-4-vision-preview, + gpt-4o + ] )} @@ -471,7 +468,7 @@ const RobotCreate: React.FC = () => { url, aiPrompt, llmProvider, - llmModel || undefined, + llmModel === 'default' ? undefined : llmModel, llmApiKey || undefined, llmBaseUrl || undefined, extractRobotName From 32fbe2b6884c8fc8bfe930e288366b966e6adc03 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 11 Dec 2025 16:08:18 +0530 Subject: [PATCH 15/21] fix: set color text secondary --- src/components/robot/pages/RobotCreate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index a40f0e3e..0602900d 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -263,7 +263,7 @@ const RobotCreate: React.FC = () => { > - + Recorder Mode From 3b9aae51834cf0c991df1318973bedc60ab1f0d4 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 11 Dec 2025 16:09:53 +0530 Subject: [PATCH 16/21] fix: set color text secondary for card --- src/components/robot/pages/RobotCreate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index 681be185..8345e46a 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -260,7 +260,7 @@ const RobotCreate: React.FC = () => { } }} > - + Recorder Mode From 819c9a02eadb5aeaa72ceebaae776c45bb4ad885 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 11 Dec 2025 16:10:13 +0530 Subject: [PATCH 17/21] fix: set color text secondary for card --- src/components/robot/pages/RobotCreate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index 8345e46a..f48614b3 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -302,7 +302,7 @@ const RobotCreate: React.FC = () => { Beta - + AI Mode From 98af09a1c280acd6356cfef48b14e05d22148e6d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 11 Dec 2025 16:12:02 +0530 Subject: [PATCH 18/21] fix: remove repeated styles --- src/components/robot/pages/RobotCreate.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index f48614b3..c3902cc4 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -262,10 +262,10 @@ const RobotCreate: React.FC = () => { > - + Recorder Mode - + Record your actions into a workflow. @@ -307,7 +307,7 @@ const RobotCreate: React.FC = () => { AI Mode - + Describe the task. It builds it for you. From 3bbe2b2727b8ab7e95ea19205bdad3e700da52a7 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 11 Dec 2025 16:13:09 +0530 Subject: [PATCH 19/21] feat: remove fontweight --- src/components/robot/pages/RobotCreate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index c3902cc4..b92e2561 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -242,7 +242,7 @@ const RobotCreate: React.FC = () => { - + Choose How to Build From 8a5c90c86b0db464067e3c48db30e8926e20f6a4 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 11 Dec 2025 16:14:07 +0530 Subject: [PATCH 20/21] fix: set color text secondary --- src/components/robot/pages/RobotCreate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index b92e2561..1deb55de 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -242,7 +242,7 @@ const RobotCreate: React.FC = () => { - + Choose How to Build From 1e17e18d322b2cae3aa9ed6c02e40e1d0322aad8 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 11 Dec 2025 16:15:48 +0530 Subject: [PATCH 21/21] fix: match theme --- src/components/robot/pages/RobotCreate.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index 1deb55de..a64564e6 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -294,9 +294,8 @@ const RobotCreate: React.FC = () => { color: '#fff', px: 1, py: 0.3, - borderRadius: '6px', + borderRadius: '10px', fontSize: '0.7rem', - fontWeight: 600 }} > Beta