diff --git a/src/components/browser/BrowserRecordingSave.tsx b/src/components/browser/BrowserRecordingSave.tsx index 5beed5d03..7172dbf95 100644 --- a/src/components/browser/BrowserRecordingSave.tsx +++ b/src/components/browser/BrowserRecordingSave.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react' -import { Grid, Button, Box, Typography, IconButton, Menu, MenuItem, ListItemText } from '@mui/material'; +import { Grid, Button, Box, Typography, IconButton, Menu, MenuItem, ListItemText, Dialog, DialogTitle, DialogActions, } from '@mui/material'; import { SaveRecording } from "../recorder/SaveRecording"; import { useGlobalInfoStore } from '../../context/globalInfo'; import { useActionContext } from '../../context/browserActions'; @@ -20,9 +20,9 @@ const BrowserRecordingSave = () => { const { socket } = useSocketStore(); - const { - stopGetText, - stopGetList, + const { + stopGetText, + stopGetList, stopGetScreenshot, stopPaginationMode, stopLimitMode, @@ -45,7 +45,7 @@ const BrowserRecordingSave = () => { timestamp: Date.now() }; window.sessionStorage.setItem('pendingNotification', JSON.stringify(notificationData)); - + if (window.opener) { window.opener.postMessage({ type: 'recording-notification', @@ -57,9 +57,9 @@ const BrowserRecordingSave = () => { timestamp: Date.now() }, '*'); } - + setBrowserId(null); - + window.close(); stopRecording(browserId).catch((error) => { @@ -74,7 +74,7 @@ const BrowserRecordingSave = () => { stopGetScreenshot(); stopPaginationMode(); stopLimitMode(); - + setShowLimitOptions(false); setShowPaginationOptions(false); setCaptureStage('initial'); @@ -97,7 +97,7 @@ const BrowserRecordingSave = () => { browserSteps.forEach(step => { deleteBrowserStep(step.id); }); - + if (socket) { socket?.emit('new-recording'); socket.emit('input:url', initialUrl); @@ -119,7 +119,7 @@ const BrowserRecordingSave = () => { }; const handleClick = (event: any) => { - setAnchorEl(event.currentTarget); + setAnchorEl(event.currentTarget); }; const handleClose = () => { @@ -183,19 +183,39 @@ const BrowserRecordingSave = () => { - setOpenDiscardModal(false)} modalStyle={modalStyle}> - - {t('browser_recording.modal.confirm_discard')} - - - - - - + setOpenDiscardModal(false)} + maxWidth="xs" + fullWidth + PaperProps={{ + sx: { + p: 0, + borderRadius: 2, + border: "none" + } + }} + > + + {t('browser_recording.modal.confirm_discard')} + + + + + + + setOpenResetModal(false)} modalStyle={modalStyle}> @@ -204,9 +224,9 @@ const BrowserRecordingSave = () => { {t('browser_recording.modal.reset_warning')} - - setOpenModal(false)} modalStyle={modalStyle}> -
- {t('save_recording.title')} - - - {waitingForSave && - - - - - + setOpenModal(false)} + maxWidth="xs" + fullWidth + PaperProps={{ + sx: { + p: 0, + borderRadius: 2 } - -
+ }} + > + + {t('save_recording.title')} + + + +
+ + + + + {waitingForSave && ( + + + + + + )} + +
+ ); } diff --git a/src/components/robot/RecordingsTable.tsx b/src/components/robot/RecordingsTable.tsx index f1e9bbe02..dfb69de00 100644 --- a/src/components/robot/RecordingsTable.tsx +++ b/src/components/robot/RecordingsTable.tsx @@ -23,6 +23,11 @@ import { CircularProgress, FormControlLabel, Checkbox, + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, } from "@mui/material"; import { Schedule, @@ -94,7 +99,7 @@ const LoadingRobotRow = memo(({ row, columns }: any) => { } else if (column.id === 'interpret') { return ( - - + - ); } else { @@ -181,7 +186,7 @@ export const RecordingsTable = ({ handleSettingsRecording, handleEditRobot, handleDuplicateRobot, - }: RecordingsTableProps) => { +}: RecordingsTableProps) => { const { t } = useTranslation(); const theme = useTheme(); const [page, setPage] = React.useState(0); @@ -227,11 +232,11 @@ export const RecordingsTable = ({ const notificationData = event.data.notification; if (notificationData) { notify(notificationData.type, notificationData.message); - - if ((notificationData.type === 'success' && - (notificationData.message.includes('saved') || notificationData.message.includes('retrained'))) || - (notificationData.type === 'warning' && - notificationData.message.includes('terminated'))) { + + if ((notificationData.type === 'success' && + (notificationData.message.includes('saved') || notificationData.message.includes('retrained'))) || + (notificationData.type === 'warning' && + notificationData.message.includes('terminated'))) { setRerenderRobots(true); } } @@ -248,9 +253,9 @@ export const RecordingsTable = ({ window.sessionStorage.removeItem('initialUrl'); } }; - + window.addEventListener('message', handleMessage); - + return () => { window.removeEventListener('message', handleMessage); }; @@ -325,7 +330,7 @@ export const RecordingsTable = ({ timestamp: Date.now() }; window.sessionStorage.setItem('recordingTabCloseMessage', JSON.stringify(closeMessage)); - + if (window.openedRecordingWindow && !window.openedRecordingWindow.closed) { try { window.openedRecordingWindow.close(); @@ -339,10 +344,10 @@ export const RecordingsTable = ({ if (activeBrowserId) { await stopRecording(activeBrowserId); notify('warning', t('browser_recording.notifications.terminated')); - + notifyRecordingTabsToClose(activeBrowserId); } - + setWarningModalOpen(false); setModalOpen(true); }; @@ -350,31 +355,31 @@ export const RecordingsTable = ({ const handleRetrainRobot = useCallback(async (id: string, name: string) => { const robot = rows.find(row => row.id === id); let targetUrl; - + if (robot?.content?.workflow && robot.content.workflow.length > 0) { const lastPair = robot.content.workflow[robot.content.workflow.length - 1]; - + if (lastPair?.what) { if (Array.isArray(lastPair.what)) { - const gotoAction = lastPair.what.find((action: any) => + const gotoAction = lastPair.what.find((action: any) => action && typeof action === 'object' && 'action' in action && action.action === "goto" ) as any; - + if (gotoAction?.args?.[0]) { targetUrl = gotoAction.args[0]; } } } } - + if (targetUrl) { setInitialUrl(targetUrl); setRecordingUrl(targetUrl); window.sessionStorage.setItem('initialUrl', targetUrl); } - + const canCreateRecording = await canCreateBrowserInState("recording"); - + if (!canCreateRecording) { const activeBrowserId = await getActiveBrowserId(); if (activeBrowserId) { @@ -384,45 +389,45 @@ export const RecordingsTable = ({ notify('warning', t('recordingtable.notifications.browser_limit_warning')); } } else { - startRetrainRecording(id, name, targetUrl); + startRetrainRecording(id, name, targetUrl); } }, [rows, setInitialUrl, setRecordingUrl]); const startRetrainRecording = (id: string, name: string, url?: string) => { setBrowserId('new-recording'); - setRecordingName(name); - setRecordingId(id); - + setRecordingName(name); + setRecordingId(id); + window.sessionStorage.setItem('browserId', 'new-recording'); window.sessionStorage.setItem('robotToRetrain', id); window.sessionStorage.setItem('robotName', name); - + window.sessionStorage.setItem('recordingUrl', url || recordingUrl); - + const sessionId = Date.now().toString(); window.sessionStorage.setItem('recordingSessionId', sessionId); - + window.openedRecordingWindow = window.open(`/recording-setup?session=${sessionId}`, '_blank'); - + window.sessionStorage.setItem('nextTabIsRecording', 'true'); }; const startRecording = () => { setModalOpen(false); - + // Set local state setBrowserId('new-recording'); setRecordingName(''); setRecordingId(''); - + window.sessionStorage.setItem('browserId', 'new-recording'); - + const sessionId = Date.now().toString(); window.sessionStorage.setItem('recordingSessionId', sessionId); window.sessionStorage.setItem('recordingUrl', recordingUrl); - + window.openedRecordingWindow = window.open(`/recording-setup?session=${sessionId}`, '_blank'); - + window.sessionStorage.setItem('nextTabIsRecording', 'true'); }; @@ -442,17 +447,17 @@ export const RecordingsTable = ({ function useDebounce(value: T, delay: number): T { const [debouncedValue, setDebouncedValue] = React.useState(value); - + useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay); - + return () => { clearTimeout(handler); }; }, [value, delay]); - + return debouncedValue; } @@ -472,9 +477,9 @@ export const RecordingsTable = ({ }, [filteredRows, page, rowsPerPage]); const openDeleteConfirm = React.useCallback((id: string) => { - setPendingDeleteId(String(id)); - setDeleteConfirmOpen(true); - }, []); + setPendingDeleteId(String(id)); + setDeleteConfirmOpen(true); + }, []); const confirmDeleteRecording = React.useCallback(async () => { if (!pendingDeleteId) return; @@ -548,13 +553,13 @@ export const RecordingsTable = ({
- + {isFetching ? ( {debouncedSearchTerm ? t('recordingtable.placeholder.search') : t('recordingtable.placeholder.title')} - {debouncedSearchTerm + {debouncedSearchTerm ? t('recordingtable.search_criteria') : t('recordingtable.placeholder.body') } @@ -587,16 +592,16 @@ export const RecordingsTable = ({ <> - - {columns.map((column) => ( - - {column.label} - - ))} - + + {columns.map((column) => ( + + {column.label} + + ))} + {visibleRows.map((row) => ( )} - setWarningModalOpen(false)} modalStyle={modalStyle}> -
- {t('recordingtable.warning_modal.title')} - + setWarningModalOpen(false)} + maxWidth="xs" + fullWidth + PaperProps={{ + sx: { + p: 0, + borderRadius: 2 + } + }} + > + + {t('recordingtable.warning_modal.title')} + + + + {t('recordingtable.warning_modal.message')} - - - - - -
-
+ + + + + + + setModalOpen(false)} modalStyle={modalStyle}>
{t('recordingtable.modal.title')} @@ -679,33 +698,50 @@ export const RecordingsTable = ({
- { setDeleteConfirmOpen(false); setPendingDeleteId(null); }} - modalStyle={{ ...modalStyle, padding: 0, backgroundColor: 'transparent', width: 'auto', maxWidth: '520px' }} + { + setDeleteConfirmOpen(false); + setPendingDeleteId(null); + }} + maxWidth="xs" + fullWidth > + + {t('recordingtable.delete_confirm.title', { + name: pendingRow?.name, + defaultValue: 'Delete {{name}}?' + })} + + + + + {t('recordingtable.delete_confirm.message', { + name: pendingRow?.name, + defaultValue: 'Are you sure you want to delete the robot "{{name}}"?' + })} + + + + + - - - {t('recordingtable.delete_confirm.title', { name: pendingRow?.name, defaultValue: 'Delete {{name}}?' })} - - - {t('recordingtable.delete_confirm.message', { - name: pendingRow?.name, - defaultValue: 'Are you sure you want to delete the robot "{{name}}"?' - })} - - - - - - - - + + + ); } @@ -838,7 +874,6 @@ const OptionsButton = ({ handleRetrain, handleEdit, handleDuplicate, handleDelet const MemoizedTableCell = memo(TableCell); -// Memoized action buttons const MemoizedInterpretButton = memo(InterpretButton); const MemoizedScheduleButton = memo(ScheduleButton); const MemoizedIntegrateButton = memo(IntegrateButton); diff --git a/src/components/robot/pages/RobotCreate.tsx b/src/components/robot/pages/RobotCreate.tsx index 992af17d7..185a40585 100644 --- a/src/components/robot/pages/RobotCreate.tsx +++ b/src/components/robot/pages/RobotCreate.tsx @@ -18,6 +18,10 @@ import { Select, MenuItem, InputLabel, + Dialog, + DialogTitle, + DialogContent, + DialogActions, Collapse, FormControlLabel } from '@mui/material'; @@ -244,7 +248,7 @@ const RobotCreate: React.FC = () => { setIsLoading(true); try { const formatsForRequest = searchMode === 'discover' ? [] : searchOutputFormats; - + const result = await createSearchRobot( searchRobotName, { @@ -368,7 +372,7 @@ const RobotCreate: React.FC = () => { } }} > - + Recorder Mode @@ -409,7 +413,7 @@ const RobotCreate: React.FC = () => { Beta - + AI Mode @@ -421,190 +425,213 @@ const RobotCreate: React.FC = () => { - {generationMode === 'agent' && ( - - - setExtractRobotName(e.target.value)} - label="Name" - /> - + {generationMode === 'agent' && ( + + + setExtractRobotName(e.target.value)} + label="Name" + /> + + + + setAiPrompt(e.target.value)} + label="Extraction Prompt" + /> + + + + setUrl(e.target.value)} + label="Website URL (Optional)" + /> + + + + + LLM Provider + + + + Model + + + + + {/* API Key for non-Ollama providers */} + {llmProvider !== 'ollama' && ( setAiPrompt(e.target.value)} - label="Extraction Prompt" + type="password" + value={llmApiKey} + onChange={(e) => setLlmApiKey(e.target.value)} + label="API Key (Optional if set in .env)" /> + )} + {llmProvider === 'ollama' && ( setUrl(e.target.value)} - label="Website URL (Optional)" + value={llmBaseUrl} + onChange={(e) => setLlmBaseUrl(e.target.value)} + label="Ollama Base URL (Optional)" /> + )} - - - LLM Provider - - - - - Model - - - + - - )} + } 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={!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' && ( <> { - )} - + )} + @@ -942,13 +975,13 @@ const RobotCreate: React.FC = () => { @@ -1102,38 +1135,38 @@ const RobotCreate: React.FC = () => { - Mode - + Mode + - Time Range - + Time Range + @@ -1195,38 +1228,50 @@ const RobotCreate: React.FC = () => { - - { - setWarningModalOpen(false); - setIsLoading(false); - }} modalStyle={modalStyle}> -
- {t('recordingtable.warning_modal.title')} - + { + setWarningModalOpen(false); + setIsLoading(false); + }} + maxWidth="xs" + fullWidth + PaperProps={{ + sx: { + p: 0, + borderRadius: 2 + } + }} + > + + {t('recordingtable.warning_modal.title')} + + + + {t('recordingtable.warning_modal.message')} + - - - - -
-
- + + + + + ); diff --git a/src/components/run/ColapsibleRow.tsx b/src/components/run/ColapsibleRow.tsx index cbb6860c0..9d1e9c7b1 100644 --- a/src/components/run/ColapsibleRow.tsx +++ b/src/components/run/ColapsibleRow.tsx @@ -2,7 +2,12 @@ import { useEffect, useRef, useState } from "react"; import * as React from "react"; import TableRow from "@mui/material/TableRow"; import TableCell from "@mui/material/TableCell"; -import { Box, Collapse, IconButton, Typography, Chip, TextField } from "@mui/material"; +import { + Box, Collapse, IconButton, Typography, Chip, TextField, Dialog, DialogTitle, + DialogContent, + DialogContentText, + DialogActions, +} from "@mui/material"; import { Button } from "@mui/material"; import { DeleteForever, KeyboardArrowDown, KeyboardArrowUp, Settings } from "@mui/icons-material"; import { deleteRunFromStorage } from "../../api/storage"; @@ -250,7 +255,7 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, onToggleExpanded, cu - setDeleteOpen(false)} modalStyle={{ ...modalStyle, padding: 0, backgroundColor: 'transparent', width: 'auto', maxWidth: '520px' }}> - - - {t('runs_table.delete_confirm.title', { - name: row.name, - defaultValue: 'Delete run "{{name}}"?' - })} - - + setDeleteOpen(false)} + maxWidth="xs" + fullWidth + PaperProps={{ + sx: { + p: 0, + backgroundColor: theme.palette.mode === 'dark' + ? theme.palette.grey[900] + : theme.palette.background.paper, + borderRadius: 2, + width: { xs: '90vw', sm: '460px', md: '420px' }, + maxWidth: '90vw', + boxSizing: 'border-box' + } + }} + > + + {t('runs_table.delete_confirm.title', { + name: row.name, + defaultValue: 'Delete run "{{name}}"?' + })} + + + + {t('runs_table.delete_confirm.message', { name: row.name, defaultValue: 'Are you sure you want to delete the run "{{name}}"?' })} - - - - - - - + + + + + + + + + ); }