From 9962eb5c6ef979e957002cc1d104d418bb98a36f Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 27 Oct 2025 17:48:11 +0530 Subject: [PATCH 01/82] feat: set margin top --- src/components/robot/pages/RobotEditPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index 3f17560c9..0101d5dc2 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -506,7 +506,7 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { return ( <> - + {t("List Limits")} From 29146da57e884cc6228cb653e75cc2e6b1f4ba72 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Mon, 27 Oct 2025 17:48:28 +0530 Subject: [PATCH 02/82] fix: hide overflow --- src/pages/RecordingPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/RecordingPage.tsx b/src/pages/RecordingPage.tsx index 6e3624713..b5810a844 100644 --- a/src/pages/RecordingPage.tsx +++ b/src/pages/RecordingPage.tsx @@ -157,7 +157,7 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => { <> -
+
From 7e0ea386355ea6c223289334deacb8b5961d78d2 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 27 Oct 2025 17:48:55 +0530 Subject: [PATCH 03/82] feat: set margin top --- src/components/robot/pages/RobotEditPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index 0101d5dc2..e85b81fb9 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -571,7 +571,7 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { return ( <> - + {t('Actions')} {inputs} From 158347bf90be2110af2c3356d60ca09bcbe2cc4e Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 27 Oct 2025 17:58:28 +0530 Subject: [PATCH 04/82] feat: group actions by type --- src/components/robot/pages/RobotEditPage.tsx | 115 +++++++++++++------ 1 file changed, 77 insertions(+), 38 deletions(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index e85b81fb9..7d3cee74b 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -536,48 +536,87 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { }; const renderActionNameFields = () => { - if (!robot || !robot.recording || !robot.recording.workflow) return null; - - const editableActions = new Set(['screenshot', 'scrapeList', 'scrapeSchema']); - const inputs: JSX.Element[] = []; - - robot.recording.workflow.forEach((pair, pairIndex) => { - if (!pair.what) return; - - pair.what.forEach((action, actionIndex) => { - // Only show editable name inputs for meaningful action types - if (!editableActions.has(String(action.action))) return; - - // derive current name from possible fields - const currentName = - action.name || - (action.args && action.args[0] && typeof action.args[0] === 'object' && action.args[0].__name) || - ''; + if (!robot || !robot.recording || !robot.recording.workflow) return null; + + const editableActions = new Set(['screenshot', 'scrapeList', 'scrapeSchema']); + const textInputs: JSX.Element[] = []; + const screenshotInputs: JSX.Element[] = []; + const listInputs: JSX.Element[] = []; + + robot.recording.workflow.forEach((pair, pairIndex) => { + if (!pair.what) return; + + pair.what.forEach((action, actionIndex) => { + if (!editableActions.has(String(action.action))) return; + + const currentName = + action.name || + (action.args && action.args[0] && typeof action.args[0] === 'object' && action.args[0].__name) || + ''; + + const textField = ( + handleActionNameChange(pairIndex, actionIndex, e.target.value)} + style={{ marginBottom: '12px' }} + fullWidth + /> + ); - inputs.push( - handleActionNameChange(pairIndex, actionIndex, e.target.value)} - style={{ marginBottom: '12px' }} - fullWidth - /> - ); - }); + switch (action.action) { + case 'scrapeSchema': + textInputs.push(textField); + break; + case 'screenshot': + screenshotInputs.push(textField); + break; + case 'scrapeList': + listInputs.push(textField); + break; + } }); + }); - if (inputs.length === 0) return null; + const hasAnyInputs = textInputs.length > 0 || screenshotInputs.length > 0 || listInputs.length > 0; + if (!hasAnyInputs) return null; - return ( - <> - - {t('Actions')} - - {inputs} - - ); - }; + return ( + <> + + {t('Actions')} + + + {textInputs.length > 0 && ( + <> + + Texts + + {textInputs} + + )} + + {screenshotInputs.length > 0 && ( + <> + 0 ? '16px' : '0' }}> + Screenshots + + {screenshotInputs} + + )} + + {listInputs.length > 0 && ( + <> + 0 || screenshotInputs.length > 0) ? '16px' : '0' }}> + Lists + + {listInputs} + + )} + + ); +}; const renderCredentialFields = ( selectors: string[], From e6d3323236c59ceb91d568646e5068ff9c896e10 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 27 Oct 2025 17:58:40 +0530 Subject: [PATCH 05/82] chore: lint --- src/components/robot/pages/RobotEditPage.tsx | 154 +++++++++---------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index 7d3cee74b..6b323c6ec 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -536,87 +536,87 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { }; const renderActionNameFields = () => { - if (!robot || !robot.recording || !robot.recording.workflow) return null; - - const editableActions = new Set(['screenshot', 'scrapeList', 'scrapeSchema']); - const textInputs: JSX.Element[] = []; - const screenshotInputs: JSX.Element[] = []; - const listInputs: JSX.Element[] = []; - - robot.recording.workflow.forEach((pair, pairIndex) => { - if (!pair.what) return; - - pair.what.forEach((action, actionIndex) => { - if (!editableActions.has(String(action.action))) return; - - const currentName = - action.name || - (action.args && action.args[0] && typeof action.args[0] === 'object' && action.args[0].__name) || - ''; - - const textField = ( - handleActionNameChange(pairIndex, actionIndex, e.target.value)} - style={{ marginBottom: '12px' }} - fullWidth - /> - ); + if (!robot || !robot.recording || !robot.recording.workflow) return null; - switch (action.action) { - case 'scrapeSchema': - textInputs.push(textField); - break; - case 'screenshot': - screenshotInputs.push(textField); - break; - case 'scrapeList': - listInputs.push(textField); - break; - } + const editableActions = new Set(['screenshot', 'scrapeList', 'scrapeSchema']); + const textInputs: JSX.Element[] = []; + const screenshotInputs: JSX.Element[] = []; + const listInputs: JSX.Element[] = []; + + robot.recording.workflow.forEach((pair, pairIndex) => { + if (!pair.what) return; + + pair.what.forEach((action, actionIndex) => { + if (!editableActions.has(String(action.action))) return; + + const currentName = + action.name || + (action.args && action.args[0] && typeof action.args[0] === 'object' && action.args[0].__name) || + ''; + + const textField = ( + handleActionNameChange(pairIndex, actionIndex, e.target.value)} + style={{ marginBottom: '12px' }} + fullWidth + /> + ); + + switch (action.action) { + case 'scrapeSchema': + textInputs.push(textField); + break; + case 'screenshot': + screenshotInputs.push(textField); + break; + case 'scrapeList': + listInputs.push(textField); + break; + } + }); }); - }); - const hasAnyInputs = textInputs.length > 0 || screenshotInputs.length > 0 || listInputs.length > 0; - if (!hasAnyInputs) return null; + const hasAnyInputs = textInputs.length > 0 || screenshotInputs.length > 0 || listInputs.length > 0; + if (!hasAnyInputs) return null; - return ( - <> - - {t('Actions')} - - - {textInputs.length > 0 && ( - <> - - Texts - - {textInputs} - - )} - - {screenshotInputs.length > 0 && ( - <> - 0 ? '16px' : '0' }}> - Screenshots - - {screenshotInputs} - - )} - - {listInputs.length > 0 && ( - <> - 0 || screenshotInputs.length > 0) ? '16px' : '0' }}> - Lists - - {listInputs} - - )} - - ); -}; + return ( + <> + + {t('Actions')} + + + {textInputs.length > 0 && ( + <> + + Texts + + {textInputs} + + )} + + {screenshotInputs.length > 0 && ( + <> + 0 ? '16px' : '0' }}> + Screenshots + + {screenshotInputs} + + )} + + {listInputs.length > 0 && ( + <> + 0 || screenshotInputs.length > 0) ? '16px' : '0' }}> + Lists + + {listInputs} + + )} + + ); + }; const renderCredentialFields = ( selectors: string[], From 6692de2b130fc1523870b38ceb0f84ef6f154805 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 27 Oct 2025 17:59:58 +0530 Subject: [PATCH 06/82] fix: use h6 typography --- src/components/robot/pages/RobotEditPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index 6b323c6ec..1eabc5d43 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -584,7 +584,7 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { return ( <> - + {t('Actions')} From d5e250001788b614ebfb22549ff1ae2aa697335a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 27 Oct 2025 18:09:21 +0530 Subject: [PATCH 07/82] fix: use h6 typography --- src/components/robot/pages/RobotEditPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index 1eabc5d43..96f7c9d3f 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -506,7 +506,7 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { return ( <> - + {t("List Limits")} From f5e9cec74f07e0e39c879efa567d36b30505ef9d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 27 Oct 2025 18:09:38 +0530 Subject: [PATCH 08/82] fix: remove maxHeight --- src/components/robot/pages/RobotConfigPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/robot/pages/RobotConfigPage.tsx b/src/components/robot/pages/RobotConfigPage.tsx index 53ae90abe..d52b985e5 100644 --- a/src/components/robot/pages/RobotConfigPage.tsx +++ b/src/components/robot/pages/RobotConfigPage.tsx @@ -62,7 +62,6 @@ export const RobotConfigPage: React.FC = ({ Date: Mon, 27 Oct 2025 18:40:44 +0530 Subject: [PATCH 09/82] fix: add default names for captured action data --- src/components/robot/pages/RobotEditPage.tsx | 63 +++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index 96f7c9d3f..5aa04b7ab 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -543,17 +543,50 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { const screenshotInputs: JSX.Element[] = []; const listInputs: JSX.Element[] = []; + let textCount = 0; + let screenshotCount = 0; + let listCount = 0; + robot.recording.workflow.forEach((pair, pairIndex) => { if (!pair.what) return; pair.what.forEach((action, actionIndex) => { if (!editableActions.has(String(action.action))) return; - const currentName = + let currentName = action.name || (action.args && action.args[0] && typeof action.args[0] === 'object' && action.args[0].__name) || ''; + if (!currentName) { + switch (action.action) { + case 'scrapeSchema': + textCount++; + currentName = `Text ${textCount}`; + break; + case 'screenshot': + screenshotCount++; + currentName = `Screenshot ${screenshotCount}`; + break; + case 'scrapeList': + listCount++; + currentName = `List ${listCount}`; + break; + } + } else { + switch (action.action) { + case 'scrapeSchema': + textCount++; + break; + case 'screenshot': + screenshotCount++; + break; + case 'scrapeList': + listCount++; + break; + } + } + const textField = ( { }); }); + if (textInputs.length === 1 && textCount === 1) { + robot.recording.workflow.forEach((pair, pairIndex) => { + if (!pair.what) return; + + pair.what.forEach((action, actionIndex) => { + if (action.action === 'scrapeSchema') { + const existingName = + action.name || + (action.args && action.args[0] && typeof action.args[0] === 'object' && action.args[0].__name) || + ''; + + const currentName = !existingName ? 'Texts' : existingName; + + textInputs[0] = ( + handleActionNameChange(pairIndex, actionIndex, e.target.value)} + style={{ marginBottom: '12px' }} + fullWidth + /> + ); + } + }); + }); + } + const hasAnyInputs = textInputs.length > 0 || screenshotInputs.length > 0 || listInputs.length > 0; if (!hasAnyInputs) return null; From b9ab9bd7c99e8436bc1119770afc323075618800 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Mon, 27 Oct 2025 18:59:11 +0530 Subject: [PATCH 10/82] fix: add null checks legacy data --- src/components/run/RunContent.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index ee397bf42..4cc787d14 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -87,8 +87,8 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const hasOldFormat = !row.serializableOutput.scrapeSchema && !row.serializableOutput.scrapeList && Object.keys(row.serializableOutput).length > 0; if (hasLegacySchema || hasLegacyList || hasOldFormat) { - setIsLegacyData(true); processLegacyData(row.serializableOutput); + setIsLegacyData(false); return; } @@ -154,11 +154,12 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const data = legacyOutput[key]; if (Array.isArray(data)) { - const isNestedArray = data.length > 0 && Array.isArray(data[0]); + const firstNonNullElement = data.find(item => item !== null && item !== undefined); + const isNestedArray = firstNonNullElement && Array.isArray(firstNonNullElement); if (isNestedArray) { data.forEach((subArray, index) => { - if (Array.isArray(subArray) && subArray.length > 0) { + if (subArray !== null && subArray !== undefined && Array.isArray(subArray) && subArray.length > 0) { const filteredData = subArray.filter(row => row && typeof row === 'object' && Object.values(row).some(value => value !== undefined && value !== "") ); @@ -171,7 +172,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe }); } else { const filteredData = data.filter(row => - row && typeof row === 'object' && Object.values(row).some(value => value !== undefined && value !== "") + row && typeof row === 'object' && !Array.isArray(row) && Object.values(row).some(value => value !== undefined && value !== "") ); if (filteredData.length > 0) { @@ -208,7 +209,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe if (Array.isArray(schemaOutput)) { const filteredData = schemaOutput.filter(row => - row && Object.values(row).some(value => value !== undefined && value !== "") + row && typeof row === 'object' && Object.values(row).some(value => value !== undefined && value !== "") ); if (filteredData.length > 0) { @@ -231,7 +232,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const data = schemaOutput[key]; if (Array.isArray(data)) { const filteredData = data.filter(row => - Object.values(row).some(value => value !== undefined && value !== "") + row && typeof row === 'object' && Object.values(row).some(value => value !== undefined && value !== "") ); dataByKey[key] = filteredData; @@ -272,7 +273,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const tableData = scrapeListData[key]; if (Array.isArray(tableData) && tableData.length > 0) { const filteredData = tableData.filter(row => - Object.values(row).some(value => value !== undefined && value !== "") + row && typeof row === 'object' && Object.values(row).some(value => value !== undefined && value !== "") ); if (filteredData.length > 0) { tablesList.push(filteredData); From f532a6ae2e8fef5f6cc0d3c88a0bdca99a9f54ee Mon Sep 17 00:00:00 2001 From: saniyafatima07 Date: Wed, 29 Oct 2025 12:35:12 +0530 Subject: [PATCH 11/82] Feat: Implemented delete confirmation modal --- src/components/robot/RecordingsTable.tsx | 71 +++++++++++++++++++----- src/components/run/ColapsibleRow.tsx | 49 +++++++++++++--- 2 files changed, 100 insertions(+), 20 deletions(-) diff --git a/src/components/robot/RecordingsTable.tsx b/src/components/robot/RecordingsTable.tsx index 504cfa434..f06270ed3 100644 --- a/src/components/robot/RecordingsTable.tsx +++ b/src/components/robot/RecordingsTable.tsx @@ -42,6 +42,7 @@ import { Add } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { canCreateBrowserInState, getActiveBrowserId, stopRecording } from "../../api/recording"; import { GenericModal } from '../ui/GenericModal'; +import { useTheme } from '@mui/material/styles'; declare global { interface Window { @@ -148,12 +149,15 @@ export const RecordingsTable = ({ handleEditRobot, handleDuplicateRobot }: RecordingsTableProps) => { const { t } = useTranslation(); + const theme = useTheme(); const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(10); const { data: recordingsData = [], isLoading: isFetching, error, refetch } = useCachedRecordings(); const [isModalOpen, setModalOpen] = React.useState(false); const [searchTerm, setSearchTerm] = React.useState(''); const [isWarningModalOpen, setWarningModalOpen] = React.useState(false); + const [isDeleteConfirmOpen, setDeleteConfirmOpen] = React.useState(false); + const [pendingDeleteId, setPendingDeleteId] = React.useState(null); const [activeBrowserId, setActiveBrowserId] = React.useState(''); const columns = useMemo(() => [ @@ -431,6 +435,32 @@ export const RecordingsTable = ({ return filteredRows.slice(start, start + rowsPerPage); }, [filteredRows, page, rowsPerPage]); + const openDeleteConfirm = React.useCallback((id: string) => { + setPendingDeleteId(String(id)); + setDeleteConfirmOpen(true); + }, []); + + const confirmDeleteRecording = React.useCallback(async () => { + if (!pendingDeleteId) return; + const hasRuns = await checkRunsForRecording(pendingDeleteId); + if (hasRuns) { + notify('warning', t('recordingtable.notifications.delete_warning')); + setDeleteConfirmOpen(false); + setPendingDeleteId(null); + return; + } + + const success = await deleteRecordingFromStorage(pendingDeleteId); + if (success) { + notify('success', t('recordingtable.notifications.delete_success')); + refetch(); + } + setDeleteConfirmOpen(false); + setPendingDeleteId(null); + }, [pendingDeleteId, notify, t, refetch]); + + const pendingRow = pendingDeleteId ? rows.find(r => String(r.id) === pendingDeleteId) : null; + const handlers = useMemo(() => ({ handleRunRecording, handleScheduleRecording, @@ -439,19 +469,7 @@ export const RecordingsTable = ({ handleEditRobot, handleDuplicateRobot, handleRetrainRobot, - handleDelete: async (id: string) => { - const hasRuns = await checkRunsForRecording(id); - if (hasRuns) { - notify('warning', t('recordingtable.notifications.delete_warning')); - return; - } - - const success = await deleteRecordingFromStorage(id); - if (success) { - notify('success', t('recordingtable.notifications.delete_success')); - refetch(); - } - } + handleDelete: async (id: string) => openDeleteConfirm(id) }), [handleRunRecording, handleScheduleRecording, handleIntegrateRecording, handleSettingsRecording, handleEditRobot, handleDuplicateRobot, handleRetrainRobot, notify, t, refetch]); return ( @@ -628,6 +646,33 @@ export const RecordingsTable = ({
+ { setDeleteConfirmOpen(false); setPendingDeleteId(null); }} + modalStyle={{ ...modalStyle, padding: 0, backgroundColor: 'transparent', width: 'auto', maxWidth: '520px' }} + > + + + + {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}}"?' + })} + + + + + + + + ); } diff --git a/src/components/run/ColapsibleRow.tsx b/src/components/run/ColapsibleRow.tsx index bf6e9c405..63025a465 100644 --- a/src/components/run/ColapsibleRow.tsx +++ b/src/components/run/ColapsibleRow.tsx @@ -3,6 +3,7 @@ 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 { Button } from "@mui/material"; import { DeleteForever, KeyboardArrowDown, KeyboardArrowUp, Settings } from "@mui/icons-material"; import { deleteRunFromStorage } from "../../api/storage"; import { columns, Data } from "./RunsTable"; @@ -11,6 +12,7 @@ import { GenericModal } from "../ui/GenericModal"; import { modalStyle } from "../recorder/AddWhereCondModal"; import { getUserById } from "../../api/auth"; import { useTranslation } from "react-i18next"; +import { useTheme } from "@mui/material/styles"; interface RunTypeChipProps { runByUserId?: string; @@ -39,6 +41,8 @@ interface CollapsibleRowProps { } export const CollapsibleRow = ({ row, handleDelete, isOpen, onToggleExpanded, currentLog, abortRunHandler, runningRecordingName, urlRunId }: CollapsibleRowProps) => { const { t } = useTranslation(); + const theme = useTheme(); + const [isDeleteOpen, setDeleteOpen] = useState(false); const [openSettingsModal, setOpenSettingsModal] = useState(false); const [userEmail, setUserEmail] = useState(null); const runByLabel = row.runByScheduleId @@ -83,6 +87,17 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, onToggleExpanded, cu fetchUserEmail(); }, [row.runByUserId]); + const handleConfirmDelete = async () => { + try { + const res = await deleteRunFromStorage(`${row.runId}`); + if (res) { + handleDelete(); + } + } finally { + setDeleteOpen(false); + } + }; + return ( *': { borderBottom: 'unset' } }} hover role="checkbox" tabIndex={-1} key={row.id}> @@ -120,13 +135,7 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, onToggleExpanded, cu case 'delete': return ( - { - deleteRunFromStorage(`${row.runId}`).then((result: boolean) => { - if (result) { - handleDelete(); - } - }) - }}> + setDeleteOpen(true)}> @@ -192,6 +201,32 @@ 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}}"?' + })} + + + {t('runs_table.delete_confirm.message', { + name: row.name, + defaultValue: 'Are you sure you want to delete the run "{{name}}"?' + })} + + + + + + + ); } From f39195a11c7f5e051139ab8a2d0065fc3f8b3092 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Wed, 29 Oct 2025 15:06:06 +0530 Subject: [PATCH 12/82] fix: check text content for meaningful logic --- src/helpers/clientSelectorGenerator.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/helpers/clientSelectorGenerator.ts b/src/helpers/clientSelectorGenerator.ts index 03ad67a23..3189a4303 100644 --- a/src/helpers/clientSelectorGenerator.ts +++ b/src/helpers/clientSelectorGenerator.ts @@ -564,16 +564,17 @@ class ClientSelectorGenerator { return true; } - if (element.children.length > 0) { - return false; - } - const text = (element.textContent || "").trim(); + const hasVisibleText = text.length > 0; - if (text.length > 0) { + if (hasVisibleText || element.querySelector("svg")) { return true; } + if (element.children.length > 0) { + return false; + } + return false; } From 377dde56edab6b3de9504aafdea9b86b65c9f6b6 Mon Sep 17 00:00:00 2001 From: Karishma Date: Thu, 30 Oct 2025 01:28:59 +0530 Subject: [PATCH 13/82] chore: create setup.md Added detailed setup instructions for local installation, including Docker and non-Docker methods, as well as environment variable configurations. --- SETUP.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 SETUP.md diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 000000000..da89e75a0 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,76 @@ +# Local Installation +1. Create a root folder for your project (e.g. 'maxun') +2. Create a file named `.env` in the root folder of the project +3. Example env file can be viewed [here](https://github.com/getmaxun/maxun/blob/master/ENVEXAMPLE). Copy all content of example env to your `.env` file. +4. Choose your installation method below + +### Docker Compose +1. Copy paste the [docker-compose.yml file](https://github.com/getmaxun/maxun/blob/master/docker-compose.yml) into your root folder +2. Ensure you have setup the `.env` file in that same folder +3. Run the command below from a terminal +``` +docker-compose up -d +``` +You can access the frontend at http://localhost:5173/ and backend at http://localhost:8080/ + +### Without Docker +1. Ensure you have Node.js, PostgreSQL, MinIO and Redis installed on your system. +2. Run the commands below +``` +git clone https://github.com/getmaxun/maxun + +# change directory to the project root +cd maxun + +# install dependencies +npm install + +# change directory to maxun-core to install dependencies +cd maxun-core +npm install + +# get back to the root directory +cd .. + +# install chromium and its dependencies +npx playwright install --with-deps chromium + +# get back to the root directory +cd .. + +# start frontend and backend together +npm run start +``` +You can access the frontend at http://localhost:5173/ and backend at http://localhost:8080/ + + +# Environment Variables +1. Create a file named `.env` in the root folder of the project +2. Example env file can be viewed [here](https://github.com/getmaxun/maxun/blob/master/ENVEXAMPLE). + +| Variable | Mandatory | Description | If Not Set | +|-----------------------|-----------|----------------------------------------------------------------------------------------------|--------------------------------------------------------------| +| `BACKEND_PORT` | Yes | Port to run backend on. Needed for Docker setup | Default value: 8080 | +| `FRONTEND_PORT` | Yes | Port to run frontend on. Needed for Docker setup | Default value: 5173 | +| `BACKEND_URL` | Yes | URL to run backend on. | Default value: http://localhost:8080 | +| `VITE_BACKEND_URL` | Yes | URL used by frontend to connect to backend | Default value: http://localhost:8080 | +| `PUBLIC_URL` | Yes | URL to run frontend on. | Default value: http://localhost:5173 | +| `VITE_PUBLIC_URL` | Yes | URL used by backend to connect to frontend | Default value: http://localhost:5173 | +| `JWT_SECRET` | Yes | Secret key used to sign and verify JSON Web Tokens (JWTs) for authentication. | JWT authentication will not work. | +| `DB_NAME` | Yes | Name of the Postgres database to connect to. | Database connection will fail. | +| `DB_USER` | Yes | Username for Postgres database authentication. | Database connection will fail. | +| `DB_PASSWORD` | Yes | Password for Postgres database authentication. | Database connection will fail. | +| `DB_HOST` | Yes | Host address where the Postgres database server is running. | Database connection will fail. | +| `DB_PORT` | Yes | Port number used to connect to the Postgres database server. | Database connection will fail. | +| `ENCRYPTION_KEY` | Yes | Key used for encrypting sensitive data (proxies, passwords). | Encryption functionality will not work. | +| `SESSION_SECRET` | No | A strong, random string used to sign session cookies | Uses default secret. Recommended to define your own session secret to avoid session hijacking. | +| `MINIO_ENDPOINT` | Yes | Endpoint URL for MinIO, to store Robot Run Screenshots. | Connection to MinIO storage will fail. | +| `MINIO_PORT` | Yes | Port number for MinIO service. | Connection to MinIO storage will fail. | +| `MINIO_CONSOLE_PORT` | No | Port number for MinIO WebUI service. Needed for Docker setup. | Cannot access MinIO Web UI. | +| `MINIO_ACCESS_KEY` | Yes | Access key for authenticating with MinIO. | MinIO authentication will fail. | +| `GOOGLE_CLIENT_ID` | No | Client ID for Google OAuth. Used for Google Sheet integration authentication. | Google login will not work. | +| `GOOGLE_CLIENT_SECRET`| No | Client Secret for Google OAuth. Used for Google Sheet integration authentication. | Google login will not work. | +| `GOOGLE_REDIRECT_URI` | No | Redirect URI for handling Google OAuth responses. | Google login will not work. | +| `AIRTABLE_CLIENT_ID` | No | Client ID for Airtable, used for Airtable integration authentication. | Airtable login will not work. | +| `AIRTABLE_REDIRECT_URI` | No | Redirect URI for handling Airtable OAuth responses. | Airtable login will not work. | +| `MAXUN_TELEMETRY` | No | Disables telemetry to stop sending anonymous usage data. Keeping it enabled helps us understand how the product is used and assess the impact of any new changes. Please keep it enabled. | Telemetry data will not be collected. | From b17159f083a8075905650661a34974e8fc92fba5 Mon Sep 17 00:00:00 2001 From: Karishma Date: Thu, 30 Oct 2025 02:44:16 +0530 Subject: [PATCH 14/82] chore: cleaner setup instructions + demo video --- README.md | 154 +++++++++++++++++------------------------------------- 1 file changed, 47 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index 72d4eccb0..49fe260b0 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -

+

- Open-Source No-Code Web Data Extraction Platform
-

+ The Easiest Way To Extract Web Data With No Code
+

Maxun lets you train a robot in 2 minutes and scrape the web on auto-pilot. Web data extraction doesn't get easier than this! @@ -15,115 +15,55 @@ Maxun lets you train a robot in 2 minutes and scrape the web on auto-pilot. Web

- Go To App | - Documentation | - Website | - Discord | - Twitter | + Go To App • + Documentation • + Website • + DiscordWatch Tutorials

getmaxun%2Fmaxun | Trendshift

-![maxun_gif](https://github.com/user-attachments/assets/3e0b0cf8-9e52-44d2-a140-b26b7b481477) +https://github.com/user-attachments/assets/c6baa75f-b950-482c-8d26-8a8b6c5382c3 -# Getting Started -The simplest & fastest way to get started is to use the hosted version: https://app.maxun.dev. Maxun Cloud deals with anti-bot detection, huge proxy network with automatic proxy rotation, and CAPTCHA solving. - -# Local Installation -1. Create a root folder for your project (e.g. 'maxun') -2. Create a file named `.env` in the root folder of the project -3. Example env file can be viewed [here](https://github.com/getmaxun/maxun/blob/master/ENVEXAMPLE). Copy all content of example env to your `.env` file. -4. Choose your installation method below - -### Docker Compose -1. Copy paste the [docker-compose.yml file](https://github.com/getmaxun/maxun/blob/master/docker-compose.yml) into your root folder -2. Ensure you have setup the `.env` file in that same folder -3. Run the command below from a terminal -``` -docker-compose up -d -``` -You can access the frontend at http://localhost:5173/ and backend at http://localhost:8080/ - -### Without Docker -1. Ensure you have Node.js, PostgreSQL, MinIO and Redis installed on your system. -2. Run the commands below -``` -git clone https://github.com/getmaxun/maxun - -# change directory to the project root -cd maxun - -# install dependencies -npm install - -# change directory to maxun-core to install dependencies -cd maxun-core -npm install - -# get back to the root directory -cd .. - -# install chromium and its dependencies -npx playwright install --with-deps chromium - -# get back to the root directory -cd .. - -# start frontend and backend together -npm run start -``` -You can access the frontend at http://localhost:5173/ and backend at http://localhost:8080/ - - -# Environment Variables -1. Create a file named `.env` in the root folder of the project -2. Example env file can be viewed [here](https://github.com/getmaxun/maxun/blob/master/ENVEXAMPLE). - -| Variable | Mandatory | Description | If Not Set | -|-----------------------|-----------|----------------------------------------------------------------------------------------------|--------------------------------------------------------------| -| `BACKEND_PORT` | Yes | Port to run backend on. Needed for Docker setup | Default value: 8080 | -| `FRONTEND_PORT` | Yes | Port to run frontend on. Needed for Docker setup | Default value: 5173 | -| `BACKEND_URL` | Yes | URL to run backend on. | Default value: http://localhost:8080 | -| `VITE_BACKEND_URL` | Yes | URL used by frontend to connect to backend | Default value: http://localhost:8080 | -| `PUBLIC_URL` | Yes | URL to run frontend on. | Default value: http://localhost:5173 | -| `VITE_PUBLIC_URL` | Yes | URL used by backend to connect to frontend | Default value: http://localhost:5173 | -| `JWT_SECRET` | Yes | Secret key used to sign and verify JSON Web Tokens (JWTs) for authentication. | JWT authentication will not work. | -| `DB_NAME` | Yes | Name of the Postgres database to connect to. | Database connection will fail. | -| `DB_USER` | Yes | Username for Postgres database authentication. | Database connection will fail. | -| `DB_PASSWORD` | Yes | Password for Postgres database authentication. | Database connection will fail. | -| `DB_HOST` | Yes | Host address where the Postgres database server is running. | Database connection will fail. | -| `DB_PORT` | Yes | Port number used to connect to the Postgres database server. | Database connection will fail. | -| `ENCRYPTION_KEY` | Yes | Key used for encrypting sensitive data (proxies, passwords). | Encryption functionality will not work. | -| `SESSION_SECRET` | No | A strong, random string used to sign session cookies | Uses default secret. Recommended to define your own session secret to avoid session hijacking. | -| `MINIO_ENDPOINT` | Yes | Endpoint URL for MinIO, to store Robot Run Screenshots. | Connection to MinIO storage will fail. | -| `MINIO_PORT` | Yes | Port number for MinIO service. | Connection to MinIO storage will fail. | -| `MINIO_CONSOLE_PORT` | No | Port number for MinIO WebUI service. Needed for Docker setup. | Cannot access MinIO Web UI. | -| `MINIO_ACCESS_KEY` | Yes | Access key for authenticating with MinIO. | MinIO authentication will fail. | -| `GOOGLE_CLIENT_ID` | No | Client ID for Google OAuth. Used for Google Sheet integration authentication. | Google login will not work. | -| `GOOGLE_CLIENT_SECRET`| No | Client Secret for Google OAuth. Used for Google Sheet integration authentication. | Google login will not work. | -| `GOOGLE_REDIRECT_URI` | No | Redirect URI for handling Google OAuth responses. | Google login will not work. | -| `AIRTABLE_CLIENT_ID` | No | Client ID for Airtable, used for Airtable integration authentication. | Airtable login will not work. | -| `AIRTABLE_REDIRECT_URI` | No | Redirect URI for handling Airtable OAuth responses. | Airtable login will not work. | -| `MAXUN_TELEMETRY` | No | Disables telemetry to stop sending anonymous usage data. Keeping it enabled helps us understand how the product is used and assess the impact of any new changes. Please keep it enabled. | Telemetry data will not be collected. | - -# How Do I Self-Host? -Checkout community self hosting guide: https://docs.maxun.dev/self-host - -# How Does It Work? -Maxun lets you create custom robots which emulate user actions and extract data. A robot can perform any of the actions: Capture List, Capture Text or Capture Screenshot. Once a robot is created, it will keep extracting data for you without manual intervention - -![Screenshot 2024-10-23 222138](https://github.com/user-attachments/assets/53573c98-769e-490d-829e-ada9fac0764f) - -## 1. Robot Actions -1. Capture List: Useful to extract structured and bulk items from the website. Example: Scrape products from Amazon etc. +### Getting Started +The simplest & fastest way to get started is to use the hosted version: https://app.maxun.dev. You can self-host if you like! + +### Installation +Maxun can run locally with or without Docker +1. [Setup with Docker Compose](https://docs.maxun.dev/installation/docker) +2. [Setup without Docker](https://docs.maxun.dev/installation/local) +3. [Environment Variables](https://docs.maxun.dev/installation/environment_variables) + +### Upgrading & Self Hosting +1. [Self Host Maxun With Docker & Portainer](https://docs.maxun.dev/self-host) +2. [Upgrade Maxun With Docker Compose Setup](https://docs.maxun.dev/installation/upgrade#upgrading-with-docker-compose) +3. [Upgrade Maxun Without Docker Compose Setup](https://docs.maxun.dev/installation/upgrade#upgrading-with-local-setup) + +### How Does It Work? +Maxun lets you create custom robots which emulate user actions and extract data. A robot can perform any of the actions: Capture List, Capture Text or Capture Screenshot. Once a robot is created, it will keep extracting data for you without manual intervention. +1. Capture List: Useful to extract structured and bulk items from the website. 2. Capture Text: Useful to extract individual text content from the website. 3. Capture Screenshot: Get fullpage or visible section screenshots of the website. -# Features +### Sponsors + + + + +
+ +
+ CyberYozh App +
+
+ Infrastructure for developers working with multi‑accounting & automation in one place. +
+ +### Features - ✨ Extract Data With No-Code - ✨ Handle Pagination & Scrolling - ✨ Run Robots On A Specific Schedule @@ -134,11 +74,11 @@ Maxun lets you create custom robots which emulate user actions and extract data. - ✨ Integrations - ✨ MCP -# Use Cases +### Use Cases Maxun can be used for various use-cases, including lead generation, market research, content aggregation and more. View use-cases in detail here: https://www.maxun.dev/#usecases -# Screenshots +### Screenshots ![Maxun PH Launch (1)-1-1](https://github.com/user-attachments/assets/d7c75fa2-2bbc-47bb-a5f6-0ee6c162f391) ![Maxun PH Launch (1)-2-1](https://github.com/user-attachments/assets/d85a3ec7-8ce8-4daa-89aa-52d9617e227a) ![Maxun PH Launch (1)-3-1](https://github.com/user-attachments/assets/4bd5a0b4-485d-44f4-a487-edd9afc18b11) @@ -149,18 +89,18 @@ View use-cases in detail here: https://www.maxun.dev/#usecases ![Maxun PH Launch (1)-8-1](https://github.com/user-attachments/assets/16ee4a71-772a-49ae-a0e5-cb0529519bda) ![Maxun PH Launch (1)-9-1](https://github.com/user-attachments/assets/160f46fa-0357-4c1b-ba50-b4fe64453bb7) -# Note +### Note This project is in early stages of development. Your feedback is very important for us - we're actively working on improvements. -# License +### License

This project is licensed under AGPLv3.

-# Support Us +### Support Us Star the repository, contribute if you love what we’re building, or [sponsor us](https://github.com/sponsors/amhsirak). -# Contributors +### Contributors Thank you to the combined efforts of everyone who contributes! From 0bb2d9ec06e2400ec0a2de73ab06a7eef88295c3 Mon Sep 17 00:00:00 2001 From: Karishma Date: Thu, 30 Oct 2025 02:46:51 +0530 Subject: [PATCH 15/82] fix: adjust table cell width --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 49fe260b0..f0959209c 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Maxun lets you create custom robots which emulate user actions and extract data. ### Sponsors -
+
CyberYozh App From b39db8bc82f9f6df6cb9f6f32775d0ac0d7a0f3b Mon Sep 17 00:00:00 2001 From: Karishma Date: Thu, 30 Oct 2025 02:59:32 +0530 Subject: [PATCH 16/82] fix: width table cell --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f0959209c..0d0390051 100644 --- a/README.md +++ b/README.md @@ -52,9 +52,9 @@ Maxun lets you create custom robots which emulate user actions and extract data. ### Sponsors -
+ -
+
CyberYozh App

From 1d2250275c135262a8687b627a1dc2f4a1242c12 Mon Sep 17 00:00:00 2001 From: Karishma Date: Thu, 30 Oct 2025 03:43:04 +0530 Subject: [PATCH 17/82] fix: remove unused img Removed an image from the README file. --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 0d0390051..acff02e97 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,6 @@ Maxun lets you train a robot in 2 minutes and scrape the web on auto-pilot. Web https://github.com/user-attachments/assets/c6baa75f-b950-482c-8d26-8a8b6c5382c3 - - ### Getting Started The simplest & fastest way to get started is to use the hosted version: https://app.maxun.dev. You can self-host if you like! From 15135e355b71ac999b06c604f21a8e10ce10b63c Mon Sep 17 00:00:00 2001 From: Karishma Date: Thu, 30 Oct 2025 16:05:46 +0530 Subject: [PATCH 18/82] chore: add LambdaTest to sponsors Added LambdaTest sponsorship information to README. --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index acff02e97..6fc328df8 100644 --- a/README.md +++ b/README.md @@ -50,9 +50,18 @@ Maxun lets you create custom robots which emulate user actions and extract data. ### Sponsors +
+
+ +

+ LambdaTest +
+
+ GenAI-powered Quality Engineering Platform that empowers teams to test intelligently, smarter, and ship faster. +
-
+
CyberYozh App

From cde59e77eae64a7a4e1d69fa110f070d89f86ac9 Mon Sep 17 00:00:00 2001 From: Karishma Date: Thu, 30 Oct 2025 16:11:37 +0530 Subject: [PATCH 19/82] fix: table width --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6fc328df8..d2cceb9ac 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Maxun lets you create custom robots which emulate user actions and extract data. ### Sponsors - -
+


@@ -59,7 +59,7 @@ Maxun lets you create custom robots which emulate user actions and extract data.
GenAI-powered Quality Engineering Platform that empowers teams to test intelligently, smarter, and ship faster.
+
CyberYozh App From bbb0508444c51e7341b522e7340e31f4c68ec6fe Mon Sep 17 00:00:00 2001 From: Karishma Date: Thu, 30 Oct 2025 16:14:35 +0530 Subject: [PATCH 20/82] fix: td width --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d2cceb9ac..f56e45640 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Maxun lets you create custom robots which emulate user actions and extract data. ### Sponsors -
+


From a94b2c5b9bf5ed8c9ca977ae1d065b7559508850 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 1 Nov 2025 19:07:13 +0530 Subject: [PATCH 21/82] feat: // Get the corresponding scrapeList action to extract its name --- src/components/robot/pages/RobotEditPage.tsx | 51 ++++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index 96f7c9d3f..30f5f2e19 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -510,27 +510,36 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { {t("List Limits")} - {scrapeListLimits.map((limitInfo, index) => ( - { - const value = parseInt(e.target.value, 10); - if (value >= 1) { - handleLimitChange( - limitInfo.pairIndex, - limitInfo.actionIndex, - limitInfo.argIndex, - value - ); - } - }} - inputProps={{ min: 1 }} - style={{ marginBottom: "20px" }} - /> - ))} + {scrapeListLimits.map((limitInfo, index) => { + // Get the corresponding scrapeList action to extract its name + const scrapeListAction = robot?.recording?.workflow?.[limitInfo.pairIndex]?.what?.[limitInfo.actionIndex]; + const actionName = + scrapeListAction?.name || + (scrapeListAction?.args?.[0]?.__name) || + `List Limit ${index + 1}`; + + return ( + { + const value = parseInt(e.target.value, 10); + if (value >= 1) { + handleLimitChange( + limitInfo.pairIndex, + limitInfo.actionIndex, + limitInfo.argIndex, + value + ); + } + }} + inputProps={{ min: 1 }} + style={{ marginBottom: "20px" }} + /> + ); + })} ); }; From 1f99295c0f2c67cf1b5b80c0ee2daeebe74a30a1 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Mon, 3 Nov 2025 16:00:46 +0530 Subject: [PATCH 22/82] feat: rm canvas logic, add in-browser loader --- src/components/browser/BrowserContent.tsx | 4 +- src/components/browser/BrowserWindow.tsx | 146 ++++------------------ src/pages/RecordingPage.tsx | 43 ++++--- 3 files changed, 46 insertions(+), 147 deletions(-) diff --git a/src/components/browser/BrowserContent.tsx b/src/components/browser/BrowserContent.tsx index 46a8886d9..f592f846b 100644 --- a/src/components/browser/BrowserContent.tsx +++ b/src/components/browser/BrowserContent.tsx @@ -13,7 +13,7 @@ import { export const BrowserContent = () => { const { socket } = useSocketStore(); - const [tabs, setTabs] = useState(["current"]); + const [tabs, setTabs] = useState(["Loading..."]); const [tabIndex, setTabIndex] = React.useState(0); const [showOutputData, setShowOutputData] = useState(false); const { browserWidth } = useBrowserDimensionsStore(); @@ -125,7 +125,7 @@ export const BrowserContent = () => { useEffect(() => { getCurrentTabs() .then((response) => { - if (response) { + if (response && response.length > 0) { setTabs(response); } }) diff --git a/src/components/browser/BrowserWindow.tsx b/src/components/browser/BrowserWindow.tsx index 98642f8c5..207e8b56b 100644 --- a/src/components/browser/BrowserWindow.tsx +++ b/src/components/browser/BrowserWindow.tsx @@ -1,8 +1,6 @@ import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; import { useSocketStore } from '../../context/socket'; import { Button } from '@mui/material'; -import Canvas from "../recorder/Canvas"; -import { Highlighter } from "../recorder/Highlighter"; import { GenericModal } from '../ui/GenericModal'; import { useActionContext } from '../../context/browserActions'; import { useBrowserSteps, TextStep, ListStep } from '../../context/browserSteps'; @@ -38,12 +36,6 @@ interface AttributeOption { value: string; } -interface ScreencastData { - image: string; - userId: string; - viewport?: ViewportInfo | null; -} - interface ViewportInfo { width: number; height: number; @@ -146,8 +138,6 @@ const getAttributeOptions = (tagName: string, elementInfo: ElementInfo | null): export const BrowserWindow = () => { const { t } = useTranslation(); const { browserWidth, browserHeight } = useBrowserDimensionsStore(); - const [canvasRef, setCanvasReference] = useState | undefined>(undefined); - const [screenShot, setScreenShot] = useState(""); const [highlighterData, setHighlighterData] = useState<{ rect: DOMRect; selector: string; @@ -1303,17 +1293,6 @@ export const BrowserWindow = () => { }, []); const onMouseMove = (e: MouseEvent) => { - if (canvasRef && canvasRef.current && highlighterData) { - const canvasRect = canvasRef.current.getBoundingClientRect(); - if ( - e.pageX < canvasRect.left - || e.pageX > canvasRect.right - || e.pageY < canvasRect.top - || e.pageY > canvasRect.bottom - ) { - setHighlighterData(null); - } - } }; const resetListState = useCallback(() => { @@ -1331,35 +1310,15 @@ export const BrowserWindow = () => { } }, [getList, resetListState]); - const screencastHandler = useCallback((data: string | ScreencastData) => { - if (typeof data === 'string') { - setScreenShot(data); - } else if (data && typeof data === 'object' && 'image' in data) { - if (!data.userId || data.userId === user?.id) { - setScreenShot(data.image); - - if (data.viewport) { - setViewportInfo(data.viewport); - } - } - } - }, [user?.id]); - useEffect(() => { if (socket) { - socket.on("screencast", screencastHandler); socket.on("domcast", rrwebSnapshotHandler); socket.on("dom-mode-enabled", domModeHandler); socket.on("dom-mode-error", domModeErrorHandler); } - if (canvasRef?.current && !isDOMMode && screenShot) { - drawImage(screenShot, canvasRef.current); - } - return () => { if (socket) { - socket.off("screencast", screencastHandler); socket.off("domcast", rrwebSnapshotHandler); socket.off("dom-mode-enabled", domModeHandler); socket.off("dom-mode-error", domModeErrorHandler); @@ -1367,10 +1326,6 @@ export const BrowserWindow = () => { }; }, [ socket, - screenShot, - canvasRef, - isDOMMode, - screencastHandler, rrwebSnapshotHandler, domModeHandler, domModeErrorHandler, @@ -1847,24 +1802,7 @@ export const BrowserWindow = () => { const handleClick = (e: React.MouseEvent) => { if (highlighterData) { - let shouldProcessClick = false; - - if (!isDOMMode && canvasRef?.current) { - const canvasRect = canvasRef.current.getBoundingClientRect(); - const clickX = e.clientX - canvasRect.left; - const clickY = e.clientY - canvasRect.top; - const highlightRect = highlighterData.rect; - const mappedRect = - coordinateMapper.mapBrowserRectToCanvas(highlightRect); - - shouldProcessClick = - clickX >= mappedRect.left && - clickX <= mappedRect.right && - clickY >= mappedRect.top && - clickY <= mappedRect.bottom; - } else { - shouldProcessClick = true; - } + const shouldProcessClick = true; if (shouldProcessClick) { const options = getAttributeOptions( @@ -2209,17 +2147,7 @@ export const BrowserWindow = () => { !showAttributeModal && highlighterData?.rect != null && ( <> - {!isDOMMode && canvasRef?.current && ( - - )} - - {isDOMMode && highlighterData && ( + {highlighterData && (
{ borderRadius: "0px 0px 5px 5px", }} > - {isDOMMode ? ( + {currentSnapshot ? ( <> - {currentSnapshot ? ( - - ) : ( - - )} + {/* --- Loading overlay --- */} {isCachingChildSelectors && ( @@ -2492,11 +2416,7 @@ export const BrowserWindow = () => { )} ) : ( - + )}
@@ -2591,26 +2511,6 @@ const DOMLoadingIndicator: React.FC = () => { ); }; - -const drawImage = (image: string, canvas: HTMLCanvasElement): void => { - const ctx = canvas.getContext('2d'); - if (!ctx) return; - - const img = new Image(); - img.onload = () => { - requestAnimationFrame(() => { - ctx.drawImage(img, 0, 0, canvas.width, canvas.height); - }); - if (image.startsWith('blob:')) { - URL.revokeObjectURL(image); - } - }; - img.onerror = () => { - console.warn('Failed to load image'); - }; - img.src = image; -}; - const modalStyle = { top: '50%', left: '50%', diff --git a/src/pages/RecordingPage.tsx b/src/pages/RecordingPage.tsx index 6e3624713..d53ce32d7 100644 --- a/src/pages/RecordingPage.tsx +++ b/src/pages/RecordingPage.tsx @@ -43,7 +43,7 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => { const { setId, socket } = useSocketStore(); const { setWidth } = useBrowserDimensionsStore(); - const { browserId, setBrowserId, recordingId, recordingUrl, setRecordingUrl, setRecordingName, setRetrainRobotId } = useGlobalInfoStore(); + const { browserId, setBrowserId, recordingId, recordingUrl, setRecordingUrl, setRecordingName, setRetrainRobotId, setIsDOMMode } = useGlobalInfoStore(); const handleShowOutputData = useCallback(() => { setShowOutputData(true); @@ -77,6 +77,8 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => { useEffect(() => { let isCancelled = false; const handleRecording = async () => { + setIsDOMMode(true); + const storedUrl = window.sessionStorage.getItem('recordingUrl'); if (storedUrl && !recordingUrl) { setRecordingUrl(storedUrl); @@ -137,9 +139,12 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => { if (browserId === 'new-recording') { socket?.emit('new-recording'); } + if (recordingUrl && socket) { + socket.emit('input:url', recordingUrl); + } setIsLoaded(true); } - }, [socket, browserId, recordingName, recordingId, isLoaded]); + }, [socket, browserId, recordingName, recordingId, recordingUrl, isLoaded]); useEffect(() => { socket?.on('loaded', handleLoaded); @@ -153,26 +158,20 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => {
- {isLoaded ? ( - <> - - -
- - -
-
- -
- - -
-
-
- - ) : ( - - )} + + +
+ + +
+
+ +
+ + +
+
+
From e0b5103374840195d7777bb028b5eb602a6edd25 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Mon, 3 Nov 2025 22:26:53 +0530 Subject: [PATCH 23/82] fix: hide right side panel scrollbar --- src/components/recorder/RightSidePanel.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/components/recorder/RightSidePanel.tsx b/src/components/recorder/RightSidePanel.tsx index e22ab08bb..de0dfcb02 100644 --- a/src/components/recorder/RightSidePanel.tsx +++ b/src/components/recorder/RightSidePanel.tsx @@ -534,9 +534,24 @@ export const RightSidePanel: React.FC = ({ onFinishCapture const isDarkMode = theme.darkMode; return ( - + - + {!isAnyActionActive && ( <> {showCaptureList && ( From 58aa5f5c563df7b560de5061ed3ba2f751fde9e4 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Mon, 3 Nov 2025 22:55:14 +0530 Subject: [PATCH 24/82] feat: speed up snapshot emission --- .../classes/RemoteBrowser.ts | 78 +++++++++++++++---- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index a3796cbeb..c88ba068b 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -201,6 +201,11 @@ export class RemoteBrowser { private networkRequestTimeout: NodeJS.Timeout | null = null; private pendingNetworkRequests: string[] = []; private readonly NETWORK_QUIET_PERIOD = 8000; + private readonly INITIAL_LOAD_QUIET_PERIOD = 3000; + private networkWaitStartTime: number = 0; + private progressInterval: NodeJS.Timeout | null = null; + private hasShownInitialLoader: boolean = false; + private isInitialLoadInProgress: boolean = false; /** * Initializes a new instances of the {@link Generator} and {@link WorkflowInterpreter} classes and @@ -432,17 +437,19 @@ export class RemoteBrowser { if (!this.currentPage) return; this.currentPage.on("domcontentloaded", async () => { - logger.info("DOM content loaded - triggering snapshot"); - await this.makeAndEmitDOMSnapshot(); + if (!this.isInitialLoadInProgress) { + logger.info("DOM content loaded - triggering snapshot"); + await this.makeAndEmitDOMSnapshot(); + } }); this.currentPage.on("response", async (response) => { const url = response.url(); - if ( - response.request().resourceType() === "document" || - url.includes("api/") || - url.includes("ajax") - ) { + const isDocumentRequest = response.request().resourceType() === "document"; + + if (!this.hasShownInitialLoader && isDocumentRequest && !url.includes("about:blank")) { + this.hasShownInitialLoader = true; + this.isInitialLoadInProgress = true; this.pendingNetworkRequests.push(url); if (this.networkRequestTimeout) { @@ -450,24 +457,54 @@ export class RemoteBrowser { this.networkRequestTimeout = null; } + if (this.progressInterval) { + clearInterval(this.progressInterval); + this.progressInterval = null; + } + + this.networkWaitStartTime = Date.now(); + this.progressInterval = setInterval(() => { + const elapsed = Date.now() - this.networkWaitStartTime; + const navigationProgress = Math.min((elapsed / this.INITIAL_LOAD_QUIET_PERIOD) * 40, 35); + const totalProgress = 60 + navigationProgress; + this.emitLoadingProgress(totalProgress, this.pendingNetworkRequests.length); + }, 500); + logger.debug( - `Network request received: ${url}. Total pending: ${this.pendingNetworkRequests.length}` + `Initial load network request received: ${url}. Using ${this.INITIAL_LOAD_QUIET_PERIOD}ms quiet period` ); this.networkRequestTimeout = setTimeout(async () => { logger.info( - `Network quiet period reached. Processing ${this.pendingNetworkRequests.length} requests` + `Initial load network quiet period reached (${this.INITIAL_LOAD_QUIET_PERIOD}ms)` ); + if (this.progressInterval) { + clearInterval(this.progressInterval); + this.progressInterval = null; + } + + this.emitLoadingProgress(100, this.pendingNetworkRequests.length); + this.pendingNetworkRequests = []; this.networkRequestTimeout = null; + this.isInitialLoadInProgress = false; await this.makeAndEmitDOMSnapshot(); - }, this.NETWORK_QUIET_PERIOD); + }, this.INITIAL_LOAD_QUIET_PERIOD); } }); } + private emitLoadingProgress(progress: number, pendingRequests: number): void { + this.socket.emit("domLoadingProgress", { + progress: Math.round(progress), + pendingRequests, + userId: this.userId, + timestamp: Date.now(), + }); + } + private async setupPageEventListeners(page: Page) { page.on('framenavigated', async (frame) => { if (frame === page.mainFrame()) { @@ -521,7 +558,13 @@ export class RemoteBrowser { const MAX_RETRIES = 3; let retryCount = 0; let success = false; - + + this.socket.emit("dom-snapshot-loading", { + userId: this.userId, + timestamp: Date.now(), + }); + this.emitLoadingProgress(0, 0); + while (!success && retryCount < MAX_RETRIES) { try { this.browser = (await chromium.launch({ @@ -545,7 +588,9 @@ export class RemoteBrowser { if (!this.browser || this.browser.isConnected() === false) { throw new Error('Browser failed to launch or is not connected'); } - + + this.emitLoadingProgress(20, 0); + const proxyConfig = await getDecryptedProxyConfig(userId); let proxyOptions: { server: string, username?: string, password?: string } = { server: '' }; @@ -623,6 +668,8 @@ export class RemoteBrowser { this.currentPage = await this.context.newPage(); + this.emitLoadingProgress(40, 0); + await this.setupPageEventListeners(this.currentPage); const viewportSize = await this.currentPage.viewportSize(); @@ -645,7 +692,9 @@ export class RemoteBrowser { // Still need to set up the CDP session even if blocker fails this.client = await this.currentPage.context().newCDPSession(this.currentPage); } - + + this.emitLoadingProgress(60, 0); + success = true; logger.log('debug', `Browser initialized successfully for user ${userId}`); } catch (error: any) { @@ -1521,9 +1570,6 @@ export class RemoteBrowser { this.isDOMStreamingActive = true; logger.info("DOM streaming started successfully"); - // Initial DOM snapshot - await this.makeAndEmitDOMSnapshot(); - this.setupScrollEventListener(); this.setupPageChangeListeners(); } catch (error) { From d7da903ceb1725fb4ed59dc0e2f5ed827c5a7a75 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Tue, 4 Nov 2025 13:10:41 +0530 Subject: [PATCH 25/82] fix: add recording setup route --- src/pages/PageWrapper.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pages/PageWrapper.tsx b/src/pages/PageWrapper.tsx index e257367bf..85bf311f9 100644 --- a/src/pages/PageWrapper.tsx +++ b/src/pages/PageWrapper.tsx @@ -116,6 +116,10 @@ export const PageWrapper = () => { path="/register" element={} /> + } + /> } /> From 3717d7d4581dae59db1da4a258cd37f715127b1a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 4 Nov 2025 21:18:18 +0530 Subject: [PATCH 26/82] fix: switch to active action data tab on capture --- src/components/run/InterpretationLog.tsx | 41 ++++++++++++++++-------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/components/run/InterpretationLog.tsx b/src/components/run/InterpretationLog.tsx index 74c46f4ef..056777246 100644 --- a/src/components/run/InterpretationLog.tsx +++ b/src/components/run/InterpretationLog.tsx @@ -354,6 +354,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se useEffect(() => { let shouldOpenDrawer = false; + let switchToListTab = false; let switchToTextTab = false; let switchToScreenshotTab = false; @@ -362,6 +363,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se if (captureListData.length > lastListDataLength.current) { userClosedDrawer.current = false; shouldOpenDrawer = true; + switchToListTab = true; } lastListDataLength.current = captureListData.length; } @@ -386,18 +388,31 @@ export const InterpretationLog: React.FC = ({ isOpen, se lastScreenshotDataLength.current = screenshotData.length; } + const getLatestCaptureType = () => { + for (let i = browserSteps.length - 1; i >= 0; i--) { + const type = browserSteps[i].type; + if (type === "list" || type === "text" || type === "screenshot") { + return type; + } + } + return null; + }; + if (shouldOpenDrawer) { setIsOpen(true); - if (switchToTextTab) { - setTimeout(() => { - const textTabIndex = getAvailableTabs().findIndex(tab => tab.id === 'captureText'); - if (textTabIndex !== -1) { - setActiveTab(textTabIndex); - } - }, 100); - } else if (switchToScreenshotTab) { - setTimeout(() => { - const screenshotTabIndex = getAvailableTabs().findIndex(tab => tab.id === 'captureScreenshot'); + const latestType = getLatestCaptureType(); + + setTimeout(() => { + if (latestType === "text") { + const idx = getAvailableTabs().findIndex(t => t.id === "captureText"); + if (idx !== -1) setActiveTab(idx); + + } else if (latestType === "list") { + const idx = getAvailableTabs().findIndex(t => t.id === "captureList"); + if (idx !== -1) setActiveTab(idx); + + } else if (latestType === "screenshot") { + const screenshotTabIndex = getAvailableTabs().findIndex(tab => tab.id === "captureScreenshot"); if (screenshotTabIndex !== -1) { setActiveTab(screenshotTabIndex); const latestIndex = screenshotData.length - 1; @@ -406,7 +421,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se if (!autoFocusedScreenshotIndices.current.has(latestIndex)) { autoFocusedScreenshotIndices.current.add(latestIndex); setTimeout(() => { - const screenshotSteps = browserSteps.filter(step => step.type === 'screenshot') as Array<{ id: number; name?: string; type: 'screenshot' }>; + const screenshotSteps = browserSteps.filter(step => step.type === "screenshot"); const latestScreenshotStep = screenshotSteps[latestIndex]; if (latestScreenshotStep) { const screenshotName = latestScreenshotStep.name || `Screenshot ${latestIndex + 1}`; @@ -415,8 +430,8 @@ export const InterpretationLog: React.FC = ({ isOpen, se }, 300); } } - }, 100); - } + } + }, 100); } }, [hasScrapeListAction, hasScrapeSchemaAction, hasScreenshotAction, captureListData, captureTextData, screenshotData, setIsOpen, getText]); From 6b0fac6491ed4dfe33d819b5c635e00c366e91d5 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Wed, 5 Nov 2025 13:04:47 +0530 Subject: [PATCH 27/82] feat: centralize tab and field editing --- src/components/run/InterpretationLog.tsx | 185 +++++++---------------- 1 file changed, 56 insertions(+), 129 deletions(-) diff --git a/src/components/run/InterpretationLog.tsx b/src/components/run/InterpretationLog.tsx index 056777246..a00426948 100644 --- a/src/components/run/InterpretationLog.tsx +++ b/src/components/run/InterpretationLog.tsx @@ -41,17 +41,14 @@ export const InterpretationLog: React.FC = ({ isOpen, se const [editingField, setEditingField] = useState<{listId: number, fieldKey: string} | null>(null); const [editingValue, setEditingValue] = useState(''); - const [editingListName, setEditingListName] = useState(null); - const [editingListNameValue, setEditingListNameValue] = useState(''); - const [editingTextGroupName, setEditingTextGroupName] = useState(false); const [editingTextGroupNameValue, setEditingTextGroupNameValue] = useState('Text Data'); - const [editingTextLabel, setEditingTextLabel] = useState(null); - const [editingTextLabelValue, setEditingTextLabelValue] = useState(''); - - const [editingScreenshotName, setEditingScreenshotName] = useState(null); - const [editingScreenshotNameValue, setEditingScreenshotNameValue] = useState(''); + const [editing, setEditing] = useState<{ + stepId: number | null; + type: 'list' | 'text' | 'screenshot' | null; + value: string; + }>({ stepId: null, type: null, value: '' }); const logEndRef = useRef(null); const autoFocusedListIds = useRef>(new Set()); @@ -125,30 +122,6 @@ export const InterpretationLog: React.FC = ({ isOpen, se } }; - const handleStartEditListName = (listId: number, currentName: string) => { - setEditingListName(listId); - setEditingListNameValue(currentName); - }; - - const handleSaveListName = () => { - if (editingListName !== null) { - const trimmedName = editingListNameValue.trim(); - const finalName = trimmedName || `List Data ${captureListData.findIndex(l => l.id === editingListName) + 1}`; - - updateListStepName(editingListName, finalName); - - // Use ref-synced version of browserSteps via emitForStepId - const listStep = browserSteps.find(step => step.id === editingListName); - if (listStep?.actionId) { - // small async delay ensures React state commit - setTimeout(() => emitForStepId(listStep.actionId!), 0); - } - - setEditingListName(null); - setEditingListNameValue(''); - } - }; - const handleStartEditTextGroupName = () => { setEditingTextGroupName(true); setEditingTextGroupNameValue(currentTextGroupName); @@ -158,7 +131,6 @@ export const InterpretationLog: React.FC = ({ isOpen, se const trimmedName = editingTextGroupNameValue.trim(); const finalName = trimmedName || 'Text Data'; - console.log("SAVING TEXT GROUP NAME:", finalName); setCurrentTextGroupName(finalName); setEditingTextGroupName(false); @@ -169,34 +141,6 @@ export const InterpretationLog: React.FC = ({ isOpen, se }, 0); }; - - const handleStartEditTextLabel = (textId: number, currentLabel: string) => { - setEditingTextLabel(textId); - setEditingTextLabelValue(currentLabel); - }; - - const handleSaveTextLabel = () => { - if (editingTextLabel !== null && editingTextLabelValue.trim()) { - const textStep = browserSteps.find(step => step.id === editingTextLabel); - const actionId = textStep?.actionId; - - updateBrowserTextStepLabel(editingTextLabel, editingTextLabelValue.trim()); - - // Emit updated action to backend after state update completes - if (actionId) { - setTimeout(() => emitForStepId(actionId), 0); - } - - setEditingTextLabel(null); - setEditingTextLabelValue(''); - } - }; - - const handleCancelTextLabel = () => { - setEditingTextLabel(null); - setEditingTextLabelValue(''); - }; - const handleDeleteTextStep = (textId: number) => { const textStep = browserSteps.find(step => step.id === textId); const actionId = textStep?.actionId; @@ -210,36 +154,36 @@ export const InterpretationLog: React.FC = ({ isOpen, se } }; - const handleStartEditScreenshotName = (screenshotStepId: number, currentName: string) => { - setEditingScreenshotName(screenshotStepId); - setEditingScreenshotNameValue(currentName); + const startEdit = (stepId: number, type: 'list' | 'text' | 'screenshot', currentValue: string) => { + setEditing({ stepId, type, value: currentValue }); }; - const handleSaveScreenshotName = () => { - if (editingScreenshotName !== null) { - const trimmedName = editingScreenshotNameValue.trim(); - const screenshotSteps = browserSteps.filter(step => step.type === 'screenshot'); - const screenshotIndex = screenshotSteps.findIndex(s => s.id === editingScreenshotName); - const finalName = trimmedName || `Screenshot ${screenshotIndex + 1}`; - - updateScreenshotStepName(editingScreenshotName, finalName); - - const screenshotStep = browserSteps.find(step => step.id === editingScreenshotName); - if (screenshotStep?.actionId) { - const originalName = screenshotStep.name?.trim() || ""; - const trimmedName = editingScreenshotNameValue.trim(); - - // 🚫 Only emit if name actually changed - if (trimmedName && trimmedName !== originalName) { - setTimeout(() => emitForStepId(screenshotStep.actionId!), 500); - } else { - console.log("🧠 Skipping emit — screenshot name unchanged."); - } - } + const saveEdit = () => { + const { stepId, type, value } = editing; + if (stepId == null || !type) return; - setEditingScreenshotName(null); - setEditingScreenshotNameValue(''); + const finalValue = value.trim(); + if (!finalValue) { + setEditing({ stepId: null, type: null, value: '' }); + return; } + + if (type === 'list') { + updateListStepName(stepId, finalValue); + } else if (type === 'text') { + updateBrowserTextStepLabel(stepId, finalValue); + } else if (type === 'screenshot') { + updateScreenshotStepName(stepId, finalValue); + } + + const step = browserSteps.find(s => s.id === stepId); + if (step?.actionId) setTimeout(() => emitForStepId(step.actionId!), 0); + + setEditing({ stepId: null, type: null, value: '' }); + }; + + const cancelEdit = () => { + setEditing({ stepId: null, type: null, value: '' }); }; @@ -354,16 +298,12 @@ export const InterpretationLog: React.FC = ({ isOpen, se useEffect(() => { let shouldOpenDrawer = false; - let switchToListTab = false; - let switchToTextTab = false; - let switchToScreenshotTab = false; if (hasScrapeListAction && captureListData.length > 0 && captureListData[0]?.data?.length > 0) { setShowPreviewData(true); if (captureListData.length > lastListDataLength.current) { userClosedDrawer.current = false; shouldOpenDrawer = true; - switchToListTab = true; } lastListDataLength.current = captureListData.length; } @@ -373,7 +313,6 @@ export const InterpretationLog: React.FC = ({ isOpen, se if (captureTextData.length > lastTextDataLength.current) { userClosedDrawer.current = false; shouldOpenDrawer = true; - switchToTextTab = true; } lastTextDataLength.current = captureTextData.length; } @@ -383,7 +322,6 @@ export const InterpretationLog: React.FC = ({ isOpen, se if (screenshotData.length > lastScreenshotDataLength.current) { userClosedDrawer.current = false; shouldOpenDrawer = true; - switchToScreenshotTab = true; } lastScreenshotDataLength.current = screenshotData.length; } @@ -425,7 +363,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se const latestScreenshotStep = screenshotSteps[latestIndex]; if (latestScreenshotStep) { const screenshotName = latestScreenshotStep.name || `Screenshot ${latestIndex + 1}`; - handleStartEditScreenshotName(latestScreenshotStep.id, screenshotName); + startEdit(latestScreenshotStep.id, 'screenshot', screenshotName); } }, 300); } @@ -439,7 +377,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se if (captureListData.length > 0 && isOpen && captureStage === 'initial') { const latestListIndex = captureListData.length - 1; const latestList = captureListData[latestListIndex]; - if (latestList && latestList.data && latestList.data.length > 0 && !editingListName) { + if (latestList && latestList.data && latestList.data.length > 0 && editing.type !== 'list') { const previousLength = previousDataLengths.current.get(latestList.id) || 0; const currentLength = latestList.data.length; @@ -448,7 +386,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se autoFocusedListIds.current.add(latestList.id); setActiveListTab(latestListIndex); setTimeout(() => { - handleStartEditListName(latestList.id, latestList.name || `List Data ${latestListIndex + 1}`); + startEdit(latestList.id, 'list', latestList.name || `List Data ${latestListIndex + 1}`); }, 300); } } @@ -594,7 +532,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se }} > {captureListData.map((listItem, index) => { - const isEditing = editingListName === listItem.id; + const isEditing = editing.stepId === listItem.id && editing.type === 'list'; const isActive = activeListTab === index; return ( @@ -612,10 +550,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se } }} onDoubleClick={() => { - handleStartEditListName( - listItem.id, - listItem.name || `List Data ${index + 1}` - ); + startEdit(listItem.id, 'list', listItem.name || `List Data ${index + 1}`) }} sx={{ px: 3, @@ -653,15 +588,12 @@ export const InterpretationLog: React.FC = ({ isOpen, se > {isEditing ? ( setEditingListNameValue(e.target.value)} - onBlur={handleSaveListName} + value={editing.value} + onChange={(e) => setEditing({ ...editing, value: e.target.value })} + onBlur={saveEdit} onKeyDown={(e) => { - if (e.key === 'Enter') handleSaveListName(); - if (e.key === 'Escape') { - setEditingListName(null); - setEditingListNameValue(''); - } + if (e.key === 'Enter') saveEdit(); + if (e.key === 'Escape') cancelEdit(); }} autoFocus size="small" @@ -857,7 +789,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se if (!screenshotStep) return null; const isActive = activeScreenshotTab === index; - const isEditing = editingScreenshotName === screenshotStep.id; + const isEditing = editing.stepId === screenshotStep.id && editing.type === 'screenshot'; const screenshotName = screenshotStep.name || `Screenshot ${index + 1}`; return ( @@ -873,9 +805,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se setActiveScreenshotTab(index); } }} - onDoubleClick={() => { - handleStartEditScreenshotName(screenshotStep.id, screenshotName); - }} + onDoubleClick={() => startEdit(screenshotStep.id, 'screenshot', screenshotName)} sx={{ px: 3, py: 1.25, @@ -910,15 +840,12 @@ export const InterpretationLog: React.FC = ({ isOpen, se > {isEditing ? ( setEditingScreenshotNameValue(e.target.value)} - onBlur={handleSaveScreenshotName} + value={editing.value} + onChange={(e) => setEditing({ ...editing, value: e.target.value })} + onBlur={saveEdit} onKeyDown={(e) => { - if (e.key === 'Enter') handleSaveScreenshotName(); - if (e.key === 'Escape') { - setEditingScreenshotName(null); - setEditingScreenshotNameValue(''); - } + if (e.key === 'Enter') saveEdit(); + if (e.key === 'Escape') cancelEdit(); }} autoFocus size="small" @@ -1074,7 +1001,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se {captureTextData.map((textStep: any, index) => { - const isEditing = editingTextLabel === textStep.id; + const isEditing = editing.stepId === textStep.id && editing.type === 'text'; return ( = ({ isOpen, se {isEditing ? ( setEditingTextLabelValue(e.target.value)} - onBlur={handleSaveTextLabel} + value={editing.value} + onChange={(e) => setEditing({ ...editing, value: e.target.value })} + onBlur={saveEdit} onKeyDown={(e) => { - if (e.key === 'Enter') handleSaveTextLabel(); - if (e.key === 'Escape') handleCancelTextLabel(); + if (e.key === 'Enter') saveEdit(); + if (e.key === 'Escape') cancelEdit(); }} autoFocus size="small" @@ -1117,7 +1044,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se /> = ({ isOpen, se textDecoration: 'underline' } }} - onClick={() => handleStartEditTextLabel(textStep.id, textStep.label)} + onClick={() => startEdit(textStep.id, 'text', textStep.label)} > {textStep.label} From ab48f3e7b8f16374f6d7572a86e605e8510a39c2 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Wed, 5 Nov 2025 15:07:14 +0530 Subject: [PATCH 28/82] fix: rm __name, limit to single text name --- src/components/robot/pages/RobotEditPage.tsx | 84 ++++++++++---------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index ba030e76f..fa745160f 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -436,15 +436,6 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { // update the standard name field action.name = newName; - // also update legacy __name location if present (args[0].__name) - if (action.args && action.args.length > 0 && typeof action.args[0] === 'object' && action.args[0] !== null && '__name' in action.args[0]) { - try { - action.args[0] = { ...action.args[0], __name: newName }; - } catch (e) { - console.error('Failed to update legacy __name field:', e); - } - } - updatedWorkflow[pairIndex].what[actionIndex] = action; } @@ -515,7 +506,6 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { const scrapeListAction = robot?.recording?.workflow?.[limitInfo.pairIndex]?.what?.[limitInfo.actionIndex]; const actionName = scrapeListAction?.name || - (scrapeListAction?.args?.[0]?.__name) || `List Limit ${index + 1}`; return ( @@ -564,7 +554,7 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { let currentName = action.name || - (action.args && action.args[0] && typeof action.args[0] === 'object' && action.args[0].__name) || + (action.args && action.args[0] && typeof action.args[0] === 'object') || ''; if (!currentName) { @@ -608,9 +598,49 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { ); switch (action.action) { - case 'scrapeSchema': - textInputs.push(textField); + case 'scrapeSchema': { + const existingName = + currentName || + (action.args && action.args[0] && typeof action.args[0] === "object") || + "Texts"; + + if (!textInputs.length) { + textInputs.push( + { + const newName = e.target.value; + + setRobot((prev) => { + if (!prev?.recording?.workflow) return prev; + + const updated = { ...prev }; + updated.recording = { ...prev.recording }; + updated.recording.workflow = prev.recording.workflow.map((p) => ({ + ...p, + what: p.what?.map((a) => { + if (a.action === "scrapeSchema") { + const updatedAction = { ...a }; + updatedAction.name = newName; + return updatedAction; + } + return a; + }), + })); + + return updated; + }); + }} + style={{ marginBottom: "12px" }} + fullWidth + /> + ); + } + break; + } case 'screenshot': screenshotInputs.push(textField); break; @@ -621,34 +651,6 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { }); }); - if (textInputs.length === 1 && textCount === 1) { - robot.recording.workflow.forEach((pair, pairIndex) => { - if (!pair.what) return; - - pair.what.forEach((action, actionIndex) => { - if (action.action === 'scrapeSchema') { - const existingName = - action.name || - (action.args && action.args[0] && typeof action.args[0] === 'object' && action.args[0].__name) || - ''; - - const currentName = !existingName ? 'Texts' : existingName; - - textInputs[0] = ( - handleActionNameChange(pairIndex, actionIndex, e.target.value)} - style={{ marginBottom: '12px' }} - fullWidth - /> - ); - } - }); - }); - } - const hasAnyInputs = textInputs.length > 0 || screenshotInputs.length > 0 || listInputs.length > 0; if (!hasAnyInputs) return null; From e37512c016f12c8705d7a5f5ccd3de290c30a1b0 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 5 Nov 2025 16:35:51 +0530 Subject: [PATCH 29/82] fix: import path for ScheduleSettings --- src/pages/MainPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index 861781172..bf628fa67 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -11,7 +11,7 @@ import { createAndRunRecording, createRunForStoredRecording, CreateRunResponseWi import { io, Socket } from "socket.io-client"; import { stopRecording } from "../api/recording"; import { RunSettings } from "../components/run/RunSettings"; -import { ScheduleSettings } from "../components/robot/ScheduleSettings"; +import { ScheduleSettings } from "../components/robot/pages/ScheduleSettingsPage"; import { apiUrl } from "../apiConfig"; import { useNavigate } from 'react-router-dom'; import { AuthContext } from '../context/auth'; From 75b42b95e2c7c132a205418cc5b144bcc7932ebb Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 5 Nov 2025 16:36:26 +0530 Subject: [PATCH 30/82] fix: import path for ScheduleSettings --- src/api/storage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/storage.ts b/src/api/storage.ts index e584c36fc..b5dc32ded 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -1,7 +1,7 @@ import { default as axios } from "axios"; import { WorkflowFile } from "maxun-core"; import { RunSettings } from "../components/run/RunSettings"; -import { ScheduleSettings } from "../components/robot/ScheduleSettings"; +import { ScheduleSettings } from "../components/robot/pages/ScheduleSettingsPage"; import { CreateRunResponse, ScheduleRunResponse } from "../pages/MainPage"; import { apiUrl } from "../apiConfig"; From be90cc75d595dad955ad582bed00e8c8ec2efbff Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 5 Nov 2025 20:25:59 +0530 Subject: [PATCH 31/82] fix: limit run output panel width --- src/components/run/RunContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 4cc787d14..197f4b1fa 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -615,7 +615,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe return ( - + {row.status === 'running' || row.status === 'queued' ? ( From 3526a1a49a73285fe9805bae48e57632fcb8a131 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 5 Nov 2025 20:30:10 +0530 Subject: [PATCH 32/82] feat: decouple menu components --- src/pages/PageWrapper.tsx | 71 ++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/src/pages/PageWrapper.tsx b/src/pages/PageWrapper.tsx index e257367bf..83004fcc4 100644 --- a/src/pages/PageWrapper.tsx +++ b/src/pages/PageWrapper.tsx @@ -13,6 +13,7 @@ import UserRoute from '../routes/userRoute'; import { Routes, Route, useNavigate, Navigate } from 'react-router-dom'; import { NotFoundPage } from '../components/dashboard/NotFound'; import RobotCreate from '../components/robot/pages/RobotCreate'; +import { Box } from '@mui/material'; export const PageWrapper = () => { const [open, setOpen] = useState(false); @@ -90,34 +91,48 @@ export const PageWrapper = () => { - {/* {!browserId && location.pathname !== '/recording' && } */} - {location.pathname !== '/recording' && } - - }> - } /> - } /> - } /> - } /> - } /> - } /> - - }> - - - - } /> - - } - /> - } - /> - } /> - + {/* Sticky NavBar - only show on non-recording pages */} + {location.pathname !== '/recording' && ( + + + + )} + + + }> + } /> + } /> + } /> + } /> + } /> + } /> + + }> + + + + } /> + + } + /> + } + /> + } /> + + From a515c9046a529f69a299db1975bfc47e82ef1241 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 5 Nov 2025 20:30:24 +0530 Subject: [PATCH 33/82] feat: decouple menu components --- src/pages/MainPage.tsx | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index bf628fa67..65647182a 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useContext, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { MainMenu } from "../components/dashboard/MainMenu"; -import { Stack } from "@mui/material"; +import { Stack, Box } from "@mui/material"; import { Recordings } from "../components/robot/Recordings"; import { Runs } from "../components/run/Runs"; import ProxyForm from '../components/proxy/ProxyForm'; @@ -318,12 +318,31 @@ export const MainPage = ({ handleEditRecording, initialContent }: MainPageProps) } } - return ( - - +return ( + + {/* Sticky Sidebar */} + - + + + {/* Scrollable Content Area - Fills remaining space */} + {DisplayContent()} - - ); -}; + + +) +} From fb7c37dab963141c9c7429dd6519259100b9c468 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 5 Nov 2025 20:39:35 +0530 Subject: [PATCH 34/82] feat: match proxy layout w new menu layout --- src/components/proxy/ProxyForm.tsx | 88 ++++++++++++++++++------------ 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/src/components/proxy/ProxyForm.tsx b/src/components/proxy/ProxyForm.tsx index 9b1e8ba56..c0902f413 100644 --- a/src/components/proxy/ProxyForm.tsx +++ b/src/components/proxy/ProxyForm.tsx @@ -156,19 +156,30 @@ const ProxyForm: React.FC = () => { }, []); return ( - <> - - + + + {t('proxy.title')} - + {tabIndex === 0 && ( isProxyConfigured ? ( - - + + @@ -187,13 +198,13 @@ const ProxyForm: React.FC = () => { - ) : ( - - + + { } /> - - + + } label={t('proxy.requires_auth')} /> - + {requiresAuth && ( <> - + { error={!!errors.username} helperText={errors.username || ''} /> - - + + { error={!!errors.password} helperText={errors.password || ''} /> - + )} ))} - + - - {t('proxy.alert.title')} -
- {t('proxy.alert.right_way')} -
- {t('proxy.alert.proxy_url')} http://proxy.com:1337 -
- {t('proxy.alert.username')} myusername -
- {t('proxy.alert.password')} mypassword -
-
- {t('proxy.alert.wrong_way')} -
- - {t('proxy.alert.proxy_url')} http://myusername:mypassword@proxy.com:1337 -
- + {/* Instructions Section */} + + + {t('proxy.alert.title')} +
+ {t('proxy.alert.right_way')} +
+ {t('proxy.alert.proxy_url')} http://proxy.com:1337 +
+ {t('proxy.alert.username')} myusername +
+ {t('proxy.alert.password')} mypassword +
+
+ {t('proxy.alert.wrong_way')} +
+ {t('proxy.alert.proxy_url')} http://myusername:mypassword@proxy.com:1337 +
+
+ ); }; + export default ProxyForm; \ No newline at end of file From e1d10042d75c4f552e537fcb7e6b168dd9f233d3 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 5 Nov 2025 20:39:48 +0530 Subject: [PATCH 35/82] fix: format --- src/components/proxy/ProxyForm.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/proxy/ProxyForm.tsx b/src/components/proxy/ProxyForm.tsx index c0902f413..b9e60b869 100644 --- a/src/components/proxy/ProxyForm.tsx +++ b/src/components/proxy/ProxyForm.tsx @@ -156,17 +156,17 @@ const ProxyForm: React.FC = () => { }, []); return ( - - @@ -269,9 +269,9 @@ const ProxyForm: React.FC = () => { {/* Instructions Section */} - From 2aa4aff6089d43075ce47d95cba4c004d857eb74 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 5 Nov 2025 20:54:35 +0530 Subject: [PATCH 36/82] feat: match api layout w new menu layout --- src/components/api/ApiKey.tsx | 137 +++++++++++++++++----------------- 1 file changed, 68 insertions(+), 69 deletions(-) diff --git a/src/components/api/ApiKey.tsx b/src/components/api/ApiKey.tsx index 9feb9551e..2da3502f3 100644 --- a/src/components/api/ApiKey.tsx +++ b/src/components/api/ApiKey.tsx @@ -107,73 +107,72 @@ const ApiKeyManager = () => { } return ( - - - Start by creating an API key below. Then, - - test your API - - or read the - API documentation - for setup instructions. - - - {t('apikey.title')} - - {apiKey ? ( - -
- - - {t('apikey.table.name')} - {t('apikey.table.key')} - {t('apikey.table.actions')} - - - - - {apiKeyName} - - - {showKey ? `${apiKey?.substring(0, 10)}...` : '**********'} - - - - - - - - - - setShowKey(!showKey)}> - {showKey ? : } - - - - - - - - - - -
-
- ) : ( - <> - {t('apikey.no_key_message')} - - - )} - - ); -}; - + + + Start by creating an API key below. Then, + + test your API + + or read the + API documentation + for setup instructions. + + + {t('apikey.title')} + + {apiKey ? ( + + + + + {t('apikey.table.name')} + {t('apikey.table.key')} + {t('apikey.table.actions')} + + + + + {apiKeyName} + + + {showKey ? `${apiKey?.substring(0, 10)}...` : '**********'} + + + + + + + + + + setShowKey(!showKey)}> + {showKey ? : } + + + + + + + + + + +
+
+ ) : ( + <> + {t('apikey.no_key_message')} + + + )} +
+); +} export default ApiKeyManager; \ No newline at end of file From 95ac9ce91343dd20035d39dfc39a66e41862066b Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 5 Nov 2025 20:54:48 +0530 Subject: [PATCH 37/82] chore: lint --- src/components/api/ApiKey.tsx | 134 +++++++++++++++++----------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/src/components/api/ApiKey.tsx b/src/components/api/ApiKey.tsx index 2da3502f3..c29f4ec81 100644 --- a/src/components/api/ApiKey.tsx +++ b/src/components/api/ApiKey.tsx @@ -107,72 +107,72 @@ const ApiKeyManager = () => { } return ( - - - Start by creating an API key below. Then, - - test your API - - or read the - API documentation - for setup instructions. - - - {t('apikey.title')} - - {apiKey ? ( - - - - - {t('apikey.table.name')} - {t('apikey.table.key')} - {t('apikey.table.actions')} - - - - - {apiKeyName} - - - {showKey ? `${apiKey?.substring(0, 10)}...` : '**********'} - - - - - - - - - - setShowKey(!showKey)}> - {showKey ? : } - - - - - - - - - - -
-
- ) : ( - <> - {t('apikey.no_key_message')} - - - )} -
-); + + + Start by creating an API key below. Then, + + test your API + + or read the + API documentation + for setup instructions. + + + {t('apikey.title')} + + {apiKey ? ( + + + + + {t('apikey.table.name')} + {t('apikey.table.key')} + {t('apikey.table.actions')} + + + + + {apiKeyName} + + + {showKey ? `${apiKey?.substring(0, 10)}...` : '**********'} + + + + + + + + + + setShowKey(!showKey)}> + {showKey ? : } + + + + + + + + + + +
+
+ ) : ( + <> + {t('apikey.no_key_message')} + + + )} +
+ ); } export default ApiKeyManager; \ No newline at end of file From 11fa50f727f1fbb272dd8034e2770849819acdad Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 5 Nov 2025 20:55:51 +0530 Subject: [PATCH 38/82] chore: hidden api key --- src/components/api/ApiKey.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/api/ApiKey.tsx b/src/components/api/ApiKey.tsx index c29f4ec81..1e0386c90 100644 --- a/src/components/api/ApiKey.tsx +++ b/src/components/api/ApiKey.tsx @@ -140,7 +140,7 @@ const ApiKeyManager = () => { {apiKeyName} - {showKey ? `${apiKey?.substring(0, 10)}...` : '**********'} + {showKey ? `${apiKey?.substring(0, 20)}...` : '***************'} From c394198321c38829ff57f5f7d825fd021b6c7cd1 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 5 Nov 2025 21:09:30 +0530 Subject: [PATCH 39/82] feat: match robot config layout w new menu layout --- .../robot/pages/RobotConfigPage.tsx | 238 +++++++++--------- 1 file changed, 117 insertions(+), 121 deletions(-) diff --git a/src/components/robot/pages/RobotConfigPage.tsx b/src/components/robot/pages/RobotConfigPage.tsx index d52b985e5..ac841f0bf 100644 --- a/src/components/robot/pages/RobotConfigPage.tsx +++ b/src/components/robot/pages/RobotConfigPage.tsx @@ -59,142 +59,138 @@ export const RobotConfigPage: React.FC = ({ }; return ( + - {/* Header Section - Fixed Position */} - - - - - {icon && ( - - {icon} - - )} - - {title} - - - + }, + '&:active': { + backgroundColor: 'transparent !important', + }, + '&:focus': { + backgroundColor: 'transparent !important', + }, + '&:focus-visible': { + backgroundColor: 'transparent !important', + }, + }} + disableRipple + > + +
+ {icon && ( + + {icon} + + )} + + {title} + +
+ - {/* Content Section */} - - {children} - + + {children} + + + {(showSaveButton || showCancelButton || onBackToSelection) && ( + + {onBackToSelection && ( + + )} - {/* Action Buttons */} - {(showSaveButton || showCancelButton || onBackToSelection) && ( - - {/* Left side - Back to Selection button */} - {onBackToSelection && ( + + {showCancelButton && ( )} - - {/* Right side - Save/Cancel buttons */} - - {showCancelButton && ( - - )} - {showSaveButton && onSave && ( - - )} - + }, + textTransform: 'none', + fontWeight: 500, + px: 3, + boxShadow: 'none', + }} + > + {isLoading ? t("buttons.saving") : (saveButtonText || t("buttons.save"))} + + )} - )} - - ); -}; + + )} +
+); +} From 75bb63d19423eaea908063e337d02542c844e158 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 5 Nov 2025 21:09:38 +0530 Subject: [PATCH 40/82] chore: lint --- .../robot/pages/RobotConfigPage.tsx | 232 +++++++++--------- 1 file changed, 116 insertions(+), 116 deletions(-) diff --git a/src/components/robot/pages/RobotConfigPage.tsx b/src/components/robot/pages/RobotConfigPage.tsx index ac841f0bf..ccf6452bb 100644 --- a/src/components/robot/pages/RobotConfigPage.tsx +++ b/src/components/robot/pages/RobotConfigPage.tsx @@ -59,138 +59,138 @@ export const RobotConfigPage: React.FC = ({ }; return ( - - - - - {icon && ( - - {icon} - - )} - - {title} - - - - - - {children} - - - {(showSaveButton || showCancelButton || onBackToSelection) && ( - - {onBackToSelection && ( - + + + + + {icon && ( + + {icon} + )} + + {title} + + + + + + {children} + - - {showCancelButton && ( + {(showSaveButton || showCancelButton || onBackToSelection) && ( + + {onBackToSelection && ( )} - {showSaveButton && onSave && ( - + )} + {showSaveButton && onSave && ( + - )} + }} + > + {isLoading ? t("buttons.saving") : (saveButtonText || t("buttons.save"))} + + )} + - - )} - -); + )} +
+ ); } From fbaf588169b3a1abbc91e80e390a4c8fa030374d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 5 Nov 2025 21:53:51 +0530 Subject: [PATCH 41/82] fix: remove minHeight --- src/components/robot/pages/RobotConfigPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/robot/pages/RobotConfigPage.tsx b/src/components/robot/pages/RobotConfigPage.tsx index ccf6452bb..e87c552c1 100644 --- a/src/components/robot/pages/RobotConfigPage.tsx +++ b/src/components/robot/pages/RobotConfigPage.tsx @@ -66,7 +66,6 @@ export const RobotConfigPage: React.FC = ({ flexDirection: 'column', width: '100%', height: 'auto', - minHeight: 'calc(100vh - 64px)', boxSizing: 'border-box' }}> Date: Wed, 5 Nov 2025 21:57:14 +0530 Subject: [PATCH 42/82] fix: margins --- src/components/robot/pages/RobotConfigPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotConfigPage.tsx b/src/components/robot/pages/RobotConfigPage.tsx index e87c552c1..a5435ca30 100644 --- a/src/components/robot/pages/RobotConfigPage.tsx +++ b/src/components/robot/pages/RobotConfigPage.tsx @@ -122,7 +122,7 @@ export const RobotConfigPage: React.FC = ({ flexDirection: 'column', minHeight: 0, mt: 2, - mb: 3, + mb: 5, }}> {children} From c235ac240e55bd1e81d110105edd59b8cb258a52 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 5 Nov 2025 22:01:34 +0530 Subject: [PATCH 43/82] fix: inherit bg for cancel button --- src/components/robot/pages/RobotConfigPage.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/robot/pages/RobotConfigPage.tsx b/src/components/robot/pages/RobotConfigPage.tsx index a5435ca30..6675aa3dc 100644 --- a/src/components/robot/pages/RobotConfigPage.tsx +++ b/src/components/robot/pages/RobotConfigPage.tsx @@ -121,7 +121,7 @@ export const RobotConfigPage: React.FC = ({ display: 'flex', flexDirection: 'column', minHeight: 0, - mt: 2, + mt: 1.8, mb: 5, }}> {children} @@ -160,9 +160,7 @@ export const RobotConfigPage: React.FC = ({ onClick={handleBack} disabled={isLoading} sx={{ - color: '#ff00c3 !important', - borderColor: '#ff00c3 !important', - backgroundColor: 'white !important', + backgroundColor: 'inherit !important', }} > {cancelButtonText || t("buttons.cancel")} From 56467d5560c8a1632d0b9a1801f5dd6c0822aa75 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 5 Nov 2025 22:01:58 +0530 Subject: [PATCH 44/82] feat: cleaner UI --- .../robot/pages/ScheduleSettingsPage.tsx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/robot/pages/ScheduleSettingsPage.tsx b/src/components/robot/pages/ScheduleSettingsPage.tsx index e386cfec2..9f3084876 100644 --- a/src/components/robot/pages/ScheduleSettingsPage.tsx +++ b/src/components/robot/pages/ScheduleSettingsPage.tsx @@ -188,8 +188,8 @@ export const ScheduleSettingsPage = ({ display: "flex", flexDirection: "column", alignItems: "flex-start", - "& > *": { marginBottom: "20px" }, - marginTop: "-20px", + gap: 3, + width: "100%", }} > <> @@ -215,7 +215,7 @@ export const ScheduleSettingsPage = ({ {t("schedule_settings.at_around")}: {schedule.atTimeStart},{" "} {schedule.timezone} {t("schedule_settings.timezone")} - + )} From ea1114693b4b811735ae3a1d773b53b75e34d3d2 Mon Sep 17 00:00:00 2001 From: Karishma Date: Wed, 5 Nov 2025 23:44:30 +0530 Subject: [PATCH 58/82] Revert "fix: recorder ui bug " --- src/components/recorder/RightSidePanel.tsx | 19 ++----------------- src/pages/RecordingPage.tsx | 2 +- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/components/recorder/RightSidePanel.tsx b/src/components/recorder/RightSidePanel.tsx index de0dfcb02..e22ab08bb 100644 --- a/src/components/recorder/RightSidePanel.tsx +++ b/src/components/recorder/RightSidePanel.tsx @@ -534,24 +534,9 @@ export const RightSidePanel: React.FC = ({ onFinishCapture const isDarkMode = theme.darkMode; return ( - + - + {!isAnyActionActive && ( <> {showCaptureList && ( diff --git a/src/pages/RecordingPage.tsx b/src/pages/RecordingPage.tsx index b5810a844..6e3624713 100644 --- a/src/pages/RecordingPage.tsx +++ b/src/pages/RecordingPage.tsx @@ -157,7 +157,7 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => { <> -
+
From 342c5a97b6457101e75ebb8a31ba0af487b83c7f Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 6 Nov 2025 00:00:54 +0530 Subject: [PATCH 59/82] feat: use outputPreviewHeight --- src/components/run/InterpretationLog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/run/InterpretationLog.tsx b/src/components/run/InterpretationLog.tsx index a00426948..c269add1c 100644 --- a/src/components/run/InterpretationLog.tsx +++ b/src/components/run/InterpretationLog.tsx @@ -453,7 +453,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se background: `${darkMode ? '#1d1c1cff' : 'white'}`, color: `${darkMode ? 'white' : 'black'}`, padding: '10px', - height: "calc(100% - 140px)", + height: outputPreviewHeight, width: outputPreviewWidth, display: 'flex', flexDirection: 'column', From 4f5b248c1a5e1eaff083267bdc3545fc00a19a68 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 6 Nov 2025 00:09:01 +0530 Subject: [PATCH 60/82] feat: use outputPreviewHeight --- src/components/run/InterpretationLog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/run/InterpretationLog.tsx b/src/components/run/InterpretationLog.tsx index a00426948..c269add1c 100644 --- a/src/components/run/InterpretationLog.tsx +++ b/src/components/run/InterpretationLog.tsx @@ -453,7 +453,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se background: `${darkMode ? '#1d1c1cff' : 'white'}`, color: `${darkMode ? 'white' : 'black'}`, padding: '10px', - height: "calc(100% - 140px)", + height: outputPreviewHeight, width: outputPreviewWidth, display: 'flex', flexDirection: 'column', From 596d2781c9bde785cd0681dfea723d4f90a12346 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 6 Nov 2025 00:25:38 +0530 Subject: [PATCH 61/82] fix: set position relative --- src/components/recorder/RightSidePanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/recorder/RightSidePanel.tsx b/src/components/recorder/RightSidePanel.tsx index e22ab08bb..4faa90817 100644 --- a/src/components/recorder/RightSidePanel.tsx +++ b/src/components/recorder/RightSidePanel.tsx @@ -534,7 +534,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture const isDarkMode = theme.darkMode; return ( - + {!isAnyActionActive && ( From 16f7bb12bcac4d835410f50c5f432668dd951d11 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 6 Nov 2025 00:26:04 +0530 Subject: [PATCH 62/82] fix: set border none --- src/components/recorder/RightSidePanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/recorder/RightSidePanel.tsx b/src/components/recorder/RightSidePanel.tsx index 4faa90817..d5a7c29c0 100644 --- a/src/components/recorder/RightSidePanel.tsx +++ b/src/components/recorder/RightSidePanel.tsx @@ -534,7 +534,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture const isDarkMode = theme.darkMode; return ( - + {!isAnyActionActive && ( From f8b54b98b00876442299c815919ac834b969b86b Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 6 Nov 2025 00:32:17 +0530 Subject: [PATCH 63/82] chore: cloud sync --- src/pages/RecordingPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/RecordingPage.tsx b/src/pages/RecordingPage.tsx index 6e3624713..02a1d6262 100644 --- a/src/pages/RecordingPage.tsx +++ b/src/pages/RecordingPage.tsx @@ -157,7 +157,7 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => { <> -
+
From 49598518dd06e257596ecea9e3ebc42b2dad9135 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 6 Nov 2025 00:42:28 +0530 Subject: [PATCH 64/82] feat: increase outputPreviweHeight --- src/helpers/dimensionUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/dimensionUtils.ts b/src/helpers/dimensionUtils.ts index ab9dd209a..2533ba625 100644 --- a/src/helpers/dimensionUtils.ts +++ b/src/helpers/dimensionUtils.ts @@ -73,7 +73,7 @@ export const getResponsiveDimensions = (): AppDimensions => { browserWidth, browserHeight, panelHeight: browserHeight + 137, - outputPreviewHeight: windowHeight * 0.7, + outputPreviewHeight: windowHeight * 0.9, outputPreviewWidth, canvasWidth: browserWidth, canvasHeight: browserHeight From c87893996ff47f174fc1097765c1c67ce1d98866 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 6 Nov 2025 01:08:27 +0530 Subject: [PATCH 65/82] chore: cloud sync --- src/pages/RecordingPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/RecordingPage.tsx b/src/pages/RecordingPage.tsx index 02a1d6262..8d3a53af9 100644 --- a/src/pages/RecordingPage.tsx +++ b/src/pages/RecordingPage.tsx @@ -156,8 +156,8 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => { {isLoaded ? ( <> - -
+ +
From 725492bff2e310186ba2432fe05b3c9a248c0e60 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 6 Nov 2025 01:28:52 +0530 Subject: [PATCH 66/82] fix: remove isRecording condition --- src/pages/PageWrapper.tsx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/pages/PageWrapper.tsx b/src/pages/PageWrapper.tsx index 1af536811..07ef8cfb8 100644 --- a/src/pages/PageWrapper.tsx +++ b/src/pages/PageWrapper.tsx @@ -55,7 +55,7 @@ export const PageWrapper = () => { const sessionParam = urlParams.get('session'); const storedSessionId = getTabState('recordingSessionId'); const storedRecordingUrl = getTabState('recordingUrl'); - + if (location.pathname === '/recording-setup' && sessionParam && sessionParam === storedSessionId) { setBrowserId('new-recording'); setRecordingName(''); @@ -67,14 +67,14 @@ export const PageWrapper = () => { navigate('/recording'); } - else if (location.pathname === '/recording' || - (getTabState('nextTabIsRecording') === 'true' && sessionParam === storedSessionId)) { + else if (location.pathname === '/recording' || + (getTabState('nextTabIsRecording') === 'true' && sessionParam === storedSessionId)) { setIsRecordingMode(true); - + if (location.pathname !== '/recording') { navigate('/recording'); } - + window.sessionStorage.removeItem('nextTabIsRecording'); } else if (tabMode === 'main') { console.log('Tab is in main application mode'); @@ -88,7 +88,7 @@ export const PageWrapper = () => { const isAuthPage = location.pathname === '/login' || location.pathname === '/register'; const isRecordingPage = location.pathname === '/recording'; - + return (
@@ -96,18 +96,18 @@ export const PageWrapper = () => { {/* Show NavBar only for main app pages, not for recording pages */} {!isRecordingPage && ( - )} - }> From 0ba7fa0d445c9f308a58951d5cf72e60451faf92 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 6 Nov 2025 01:29:21 +0530 Subject: [PATCH 67/82] chore: cloud sync --- src/pages/RecordingPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/RecordingPage.tsx b/src/pages/RecordingPage.tsx index 8d3a53af9..bd23012a5 100644 --- a/src/pages/RecordingPage.tsx +++ b/src/pages/RecordingPage.tsx @@ -157,7 +157,7 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => { <> -
+
From cdee69a9cb0c95a9746f23769d4921a702e64a9f Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 6 Nov 2025 01:30:23 +0530 Subject: [PATCH 68/82] fix: set margin left --- src/components/browser/BrowserRecordingSave.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/browser/BrowserRecordingSave.tsx b/src/components/browser/BrowserRecordingSave.tsx index 32d0fabad..d5e484063 100644 --- a/src/components/browser/BrowserRecordingSave.tsx +++ b/src/components/browser/BrowserRecordingSave.tsx @@ -143,7 +143,8 @@ const BrowserRecordingSave = () => { overflow: 'hidden', display: 'flex', justifyContent: 'space-between', - height: "48px" + height: "48px", + marginLeft: '10px' }}> + ) : (!hasData && !hasScreenshots ? {t('run_content.empty_output')} : null)} From deb2ae674b38a50b5ffa3fdc469fb0bd70ed334b Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Thu, 6 Nov 2025 18:56:24 +0530 Subject: [PATCH 70/82] fix: add max no new items check --- maxun-core/src/interpret.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index 08efb1205..fdc31cdcd 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -1246,9 +1246,9 @@ export default class Interpreter extends EventEmitter { if (checkLimit()) return allResults; let loadMoreCounter = 0; - // let previousResultCount = allResults.length; - // let noNewItemsCounter = 0; - // const MAX_NO_NEW_ITEMS = 2; + let previousResultCount = allResults.length; + let noNewItemsCounter = 0; + const MAX_NO_NEW_ITEMS = 5; while (true) { if (this.isAborted) { @@ -1332,21 +1332,21 @@ export default class Interpreter extends EventEmitter { await scrapeCurrentPage(); - // const currentResultCount = allResults.length; - // const newItemsAdded = currentResultCount > previousResultCount; + const currentResultCount = allResults.length; + const newItemsAdded = currentResultCount > previousResultCount; - // if (!newItemsAdded) { - // noNewItemsCounter++; - // debugLog(`No new items added after click (${noNewItemsCounter}/${MAX_NO_NEW_ITEMS})`); + if (!newItemsAdded) { + noNewItemsCounter++; + debugLog(`No new items added after click (${noNewItemsCounter}/${MAX_NO_NEW_ITEMS})`); - // if (noNewItemsCounter >= MAX_NO_NEW_ITEMS) { - // debugLog(`Stopping after ${MAX_NO_NEW_ITEMS} clicks with no new items`); - // return allResults; - // } - // } else { - // noNewItemsCounter = 0; - // previousResultCount = currentResultCount; - // } + if (noNewItemsCounter >= MAX_NO_NEW_ITEMS) { + debugLog(`Stopping after ${MAX_NO_NEW_ITEMS} clicks with no new items`); + return allResults; + } + } else { + noNewItemsCounter = 0; + previousResultCount = currentResultCount; + } if (checkLimit()) return allResults; From 5e05b08434d5388b46dcb44ec4229579719a0de9 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Thu, 6 Nov 2025 23:17:26 +0530 Subject: [PATCH 71/82] feat: add list validity check --- src/components/browser/BrowserWindow.tsx | 36 +++++++++++++++++------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/components/browser/BrowserWindow.tsx b/src/components/browser/BrowserWindow.tsx index 98642f8c5..deff7f0f2 100644 --- a/src/components/browser/BrowserWindow.tsx +++ b/src/components/browser/BrowserWindow.tsx @@ -1174,16 +1174,31 @@ export const BrowserWindow = () => { undefined, false ); + + if (pendingNotification) { + notify(pendingNotification.type, pendingNotification.message); + setPendingNotification(null); + } + } else { + console.warn(`Failed to extract any fields from list selector: ${listSelector}`); + + setListSelector(null); + setFields({}); + setCachedListSelector(null); + setCachedChildSelectors([]); + setCurrentListId(null); + setInitialAutoFieldIds(new Set()); + setPendingNotification(null); + + notify( + "error", + "The list you have selected is not valid. Please reselect it." + ); } } catch (error) { console.error("Error during child selector caching:", error); } finally { setIsCachingChildSelectors(false); - - if (pendingNotification) { - notify(pendingNotification.type, pendingNotification.message); - setPendingNotification(null); - } } }, 100); } else { @@ -1710,16 +1725,17 @@ export const BrowserWindow = () => { let cleanedSelector = highlighterData.selector; setListSelector(cleanedSelector); - notify( - `info`, - t( + setPendingNotification({ + type: `info`, + message: t( "browser_window.attribute_modal.notifications.list_select_success", { count: highlighterData.groupInfo.groupSize, } ) || - `Selected group with ${highlighterData.groupInfo.groupSize} similar elements` - ); + `Selected group with ${highlighterData.groupInfo.groupSize} similar elements`, + count: highlighterData.groupInfo.groupSize, + }); setCurrentListId(Date.now()); setFields({}); From 2e9125f2b50c227ccd8278f2fcad3cabf319fed7 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Fri, 7 Nov 2025 13:23:34 +0530 Subject: [PATCH 72/82] fix: legacy cap text action naming --- src/components/robot/pages/RobotEditPage.tsx | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index fa745160f..80671c1fb 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -552,16 +552,12 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { pair.what.forEach((action, actionIndex) => { if (!editableActions.has(String(action.action))) return; - let currentName = - action.name || - (action.args && action.args[0] && typeof action.args[0] === 'object') || - ''; + let currentName = action.name || ''; if (!currentName) { switch (action.action) { case 'scrapeSchema': - textCount++; - currentName = `Text ${textCount}`; + currentName = 'Texts'; break; case 'screenshot': screenshotCount++; @@ -574,9 +570,6 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { } } else { switch (action.action) { - case 'scrapeSchema': - textCount++; - break; case 'screenshot': screenshotCount++; break; @@ -599,10 +592,7 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { switch (action.action) { case 'scrapeSchema': { - const existingName = - currentName || - (action.args && action.args[0] && typeof action.args[0] === "object") || - "Texts"; + const existingName = currentName || "Texts"; if (!textInputs.length) { textInputs.push( From 25fb8290ebbda5a41323906b03a50012e83c019c Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Fri, 7 Nov 2025 13:56:42 +0530 Subject: [PATCH 73/82] feat: gen unique action name keys --- .../classes/Interpreter.ts | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/server/src/workflow-management/classes/Interpreter.ts b/server/src/workflow-management/classes/Interpreter.ts index 257e478c6..71cac8b70 100644 --- a/server/src/workflow-management/classes/Interpreter.ts +++ b/server/src/workflow-management/classes/Interpreter.ts @@ -116,6 +116,16 @@ export class WorkflowInterpreter { */ private currentScrapeListIndex: number = 0; + /** + * Track action counts to generate unique names + */ + private actionCounts: Record = {}; + + /** + * Track used action names to prevent duplicates + */ + private usedActionNames: Set = new Set(); + /** * Current run ID for real-time persistence */ @@ -379,6 +389,8 @@ export class WorkflowInterpreter { }; this.binaryData = []; this.currentScrapeListIndex = 0; + this.actionCounts = {}; + this.usedActionNames = new Set(); this.currentRunId = null; this.persistenceBuffer = []; this.persistenceInProgress = false; @@ -394,6 +406,43 @@ export class WorkflowInterpreter { logger.log('debug', `Set run ID for real-time persistence: ${runId}`); }; + /** + * Generates a unique action name for data storage + * @param actionType The type of action (scrapeList, scrapeSchema, etc.) + * @param providedName Optional name provided by the action + * @returns A unique action name + */ + private getUniqueActionName = (actionType: string, providedName?: string | null): string => { + if (providedName && providedName.trim() !== '' && !this.usedActionNames.has(providedName)) { + this.usedActionNames.add(providedName); + return providedName; + } + + if (!this.actionCounts[actionType]) { + this.actionCounts[actionType] = 0; + } + + let uniqueName: string; + let counter = this.actionCounts[actionType]; + + do { + counter++; + if (actionType === 'scrapeList') { + uniqueName = `List ${counter}`; + } else if (actionType === 'scrapeSchema') { + uniqueName = `Text ${counter}`; + } else if (actionType === 'screenshot') { + uniqueName = `Screenshot ${counter}`; + } else { + uniqueName = `${actionType} ${counter}`; + } + } while (this.usedActionNames.has(uniqueName)); + + this.actionCounts[actionType] = counter; + this.usedActionNames.add(uniqueName); + return uniqueName; + }; + /** * Persists extracted data to database with intelligent batching for performance * Falls back to immediate persistence for critical operations @@ -525,6 +574,9 @@ export class WorkflowInterpreter { } let actionName = this.currentActionName || ""; + if (typeKey === "scrapeList") { + actionName = this.getUniqueActionName(typeKey, this.currentActionName); + } const flattened = Array.isArray(data) ? data @@ -555,9 +607,10 @@ export class WorkflowInterpreter { const { name, data, mimeType } = payload; const base64Data = data.toString("base64"); + const uniqueName = this.getUniqueActionName('screenshot', name); const binaryItem = { - name, + name: uniqueName, mimeType, data: base64Data }; @@ -567,7 +620,7 @@ export class WorkflowInterpreter { await this.persistBinaryDataToDatabase(binaryItem); this.socket.emit("binaryCallback", { - name, + name: uniqueName, data: base64Data, mimeType }); From d89e78c713e4972312dfa2c1be7223a9e630d8af Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Fri, 7 Nov 2025 14:18:46 +0530 Subject: [PATCH 74/82] feat: add duplicate tab name check --- src/components/run/InterpretationLog.tsx | 28 +++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/components/run/InterpretationLog.tsx b/src/components/run/InterpretationLog.tsx index a00426948..bdc31b763 100644 --- a/src/components/run/InterpretationLog.tsx +++ b/src/components/run/InterpretationLog.tsx @@ -61,7 +61,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se const { captureStage, getText } = useActionContext(); const { browserWidth, outputPreviewHeight, outputPreviewWidth } = useBrowserDimensionsStore(); - const { currentWorkflowActionsState, shouldResetInterpretationLog, currentTextGroupName, setCurrentTextGroupName } = useGlobalInfoStore(); + const { currentWorkflowActionsState, shouldResetInterpretationLog, currentTextGroupName, setCurrentTextGroupName, notify } = useGlobalInfoStore(); const [showPreviewData, setShowPreviewData] = useState(false); const userClosedDrawer = useRef(false); @@ -154,6 +154,28 @@ export const InterpretationLog: React.FC = ({ isOpen, se } }; + const checkForDuplicateName = (stepId: number, type: 'list' | 'text' | 'screenshot', newName: string): boolean => { + const trimmedName = newName.trim(); + + if (type === 'list') { + const listSteps = browserSteps.filter(step => step.type === 'list' && step.id !== stepId); + const duplicate = listSteps.find(step => step.name === trimmedName); + if (duplicate) { + notify('error', `A list with the name "${trimmedName}" already exists. Please choose a different name.`); + return true; + } + } else if (type === 'screenshot') { + const screenshotSteps = browserSteps.filter(step => step.type === 'screenshot' && step.id !== stepId); + const duplicate = screenshotSteps.find(step => step.name === trimmedName); + if (duplicate) { + notify('error', `A screenshot with the name "${trimmedName}" already exists. Please choose a different name.`); + return true; + } + } + + return false; + }; + const startEdit = (stepId: number, type: 'list' | 'text' | 'screenshot', currentValue: string) => { setEditing({ stepId, type, value: currentValue }); }; @@ -168,6 +190,10 @@ export const InterpretationLog: React.FC = ({ isOpen, se return; } + if (checkForDuplicateName(stepId, type, finalValue)) { + return; + } + if (type === 'list') { updateListStepName(stepId, finalValue); } else if (type === 'text') { From ca6615f3c0ce06b587b34e3365f240771ca0605d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 7 Nov 2025 19:05:39 +0530 Subject: [PATCH 75/82] fix: inherit bg on hover --- src/components/dashboard/NavBar.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/dashboard/NavBar.tsx b/src/components/dashboard/NavBar.tsx index 33a52d656..e0db15cc6 100644 --- a/src/components/dashboard/NavBar.tsx +++ b/src/components/dashboard/NavBar.tsx @@ -253,6 +253,9 @@ export const NavBar: React.FC = ({ borderRadius: '5px', padding: '8px', marginRight: '20px', + '&:hover': { + background: 'inherit' + } }}> {t('navbar.upgrade.button')} From 343f6fdf7a7df6bac322aab63dd26dbcba1e7a96 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 7 Nov 2025 19:07:00 +0530 Subject: [PATCH 76/82] fix: inherit bg on hover --- src/components/dashboard/NavBar.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/dashboard/NavBar.tsx b/src/components/dashboard/NavBar.tsx index e0db15cc6..437b1019c 100644 --- a/src/components/dashboard/NavBar.tsx +++ b/src/components/dashboard/NavBar.tsx @@ -163,6 +163,9 @@ export const NavBar: React.FC = ({ onClick={toggleTheme} sx={{ color: darkMode ? '#ffffff' : '#0000008A', + '&:hover': { + background: 'inherit' + } }} > {darkMode ? : } From 5a6e89a9c48b004bd07f30e0b88bfbb9882945d0 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 7 Nov 2025 19:07:11 +0530 Subject: [PATCH 77/82] chore: lint --- src/components/dashboard/NavBar.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/dashboard/NavBar.tsx b/src/components/dashboard/NavBar.tsx index 437b1019c..4240d8af3 100644 --- a/src/components/dashboard/NavBar.tsx +++ b/src/components/dashboard/NavBar.tsx @@ -119,7 +119,7 @@ export const NavBar: React.FC = ({ } catch (error: any) { const status = error.response?.status; let errorKey = 'unknown'; - + switch (status) { case 401: errorKey = 'unauthorized'; @@ -132,7 +132,7 @@ export const NavBar: React.FC = ({ errorKey = 'network'; } } - + notify( 'error', t(`navbar.notifications.errors.logout.${errorKey}`, { @@ -164,8 +164,8 @@ export const NavBar: React.FC = ({ sx={{ color: darkMode ? '#ffffff' : '#0000008A', '&:hover': { - background: 'inherit' - } + background: 'inherit' + } }} > {darkMode ? : } @@ -257,7 +257,7 @@ export const NavBar: React.FC = ({ padding: '8px', marginRight: '20px', '&:hover': { - background: 'inherit' + background: 'inherit' } }}> @@ -338,7 +338,7 @@ export const NavBar: React.FC = ({ docker-compose down

- # Remove existing backend and frontend images + # Remove existing backend and frontend images
docker rmi getmaxun/maxun-frontend:latest getmaxun/maxun-backend:latest
@@ -373,7 +373,7 @@ export const NavBar: React.FC = ({ padding: '8px', marginRight: '10px', '&:hover': { - background: 'inherit' + background: 'inherit' } }}> From 141316d353f711ada14002527cce89ce5167481e Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Mon, 10 Nov 2025 11:30:01 +0530 Subject: [PATCH 78/82] fix: preview for capture text action --- src/components/run/InterpretationLog.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/run/InterpretationLog.tsx b/src/components/run/InterpretationLog.tsx index c269add1c..07a2ff345 100644 --- a/src/components/run/InterpretationLog.tsx +++ b/src/components/run/InterpretationLog.tsx @@ -306,6 +306,8 @@ export const InterpretationLog: React.FC = ({ isOpen, se shouldOpenDrawer = true; } lastListDataLength.current = captureListData.length; + } else if (hasScrapeListAction && captureListData.length === 0) { + lastListDataLength.current = 0; } if (hasScrapeSchemaAction && captureTextData.length > 0 && !getText) { @@ -315,6 +317,8 @@ export const InterpretationLog: React.FC = ({ isOpen, se shouldOpenDrawer = true; } lastTextDataLength.current = captureTextData.length; + } else if (hasScrapeSchemaAction && captureTextData.length === 0) { + lastTextDataLength.current = 0; } if (hasScreenshotAction && screenshotData.length > 0) { @@ -324,6 +328,8 @@ export const InterpretationLog: React.FC = ({ isOpen, se shouldOpenDrawer = true; } lastScreenshotDataLength.current = screenshotData.length; + } else if (hasScreenshotAction && screenshotData.length === 0) { + lastScreenshotDataLength.current = 0; } const getLatestCaptureType = () => { @@ -466,7 +472,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se {t('interpretation_log.titles.output_preview')} - {!(hasScrapeListAction || hasScrapeSchemaAction || hasScreenshotAction) && ( + {!(hasScrapeListAction || hasScrapeSchemaAction || hasScreenshotAction) && !showPreviewData && availableTabs.length === 0 && ( From 23e20cd253b98d72def6792fc342f882396a0c47 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Mon, 10 Nov 2025 12:49:44 +0530 Subject: [PATCH 79/82] feat: discard captured action tab data --- src/components/run/InterpretationLog.tsx | 174 ++++++++++++++++++++++- 1 file changed, 168 insertions(+), 6 deletions(-) diff --git a/src/components/run/InterpretationLog.tsx b/src/components/run/InterpretationLog.tsx index c269add1c..97738565c 100644 --- a/src/components/run/InterpretationLog.tsx +++ b/src/components/run/InterpretationLog.tsx @@ -21,6 +21,7 @@ import { useThemeMode } from '../../context/theme-provider'; import { useTranslation } from 'react-i18next'; import { useBrowserSteps } from '../../context/browserSteps'; import { useActionContext } from '../../context/browserActions'; +import { useSocketStore } from '../../context/socket'; interface InterpretationLogProps { isOpen: boolean; @@ -57,11 +58,12 @@ export const InterpretationLog: React.FC = ({ isOpen, se const previousGetText = useRef(false); const autoFocusedScreenshotIndices = useRef>(new Set()); - const { browserSteps, updateListTextFieldLabel, removeListTextField, updateListStepName, updateScreenshotStepName, updateBrowserTextStepLabel, deleteBrowserStep, emitForStepId } = useBrowserSteps(); + const { browserSteps, updateListTextFieldLabel, removeListTextField, updateListStepName, updateScreenshotStepName, updateBrowserTextStepLabel, deleteBrowserStep, deleteStepsByActionId, emitForStepId } = useBrowserSteps(); const { captureStage, getText } = useActionContext(); + const { socket } = useSocketStore(); const { browserWidth, outputPreviewHeight, outputPreviewWidth } = useBrowserDimensionsStore(); - const { currentWorkflowActionsState, shouldResetInterpretationLog, currentTextGroupName, setCurrentTextGroupName } = useGlobalInfoStore(); + const { currentWorkflowActionsState, shouldResetInterpretationLog, currentTextGroupName, setCurrentTextGroupName, notify } = useGlobalInfoStore(); const [showPreviewData, setShowPreviewData] = useState(false); const userClosedDrawer = useRef(false); @@ -154,6 +156,76 @@ export const InterpretationLog: React.FC = ({ isOpen, se } }; + const handleRemoveListAction = (listId: number, actionId: string | undefined) => { + if (!actionId) return; + + const listIndex = captureListData.findIndex(list => list.id === listId); + const listItem = captureListData[listIndex]; + const listName = listItem?.name || `List Data ${listIndex + 1}`; + const isActiveList = listIndex === activeListTab; + + deleteStepsByActionId(actionId); + + if (socket) { + socket.emit('removeAction', { actionId }); + } + + if (isActiveList && captureListData.length > 1) { + if (listIndex === captureListData.length - 1) { + setActiveListTab(listIndex - 1); + } + } else if (listIndex < activeListTab) { + setActiveListTab(activeListTab - 1); + } + + notify('error', `List "${listName}" discarded`); + }; + + const handleRemoveScreenshotAction = (screenshotId: number, actionId: string | undefined) => { + if (!actionId) return; + + const screenshotSteps = browserSteps.filter(step => step.type === 'screenshot' && step.screenshotData); + const screenshotIndex = screenshotSteps.findIndex(step => step.id === screenshotId); + const screenshotStep = screenshotSteps[screenshotIndex]; + const screenshotName = screenshotStep?.name || `Screenshot ${screenshotIndex + 1}`; + const isActiveScreenshot = screenshotIndex === activeScreenshotTab; + + deleteStepsByActionId(actionId); + + if (socket) { + socket.emit('removeAction', { actionId }); + } + + if (isActiveScreenshot && screenshotData.length > 1) { + if (screenshotIndex === screenshotData.length - 1) { + setActiveScreenshotTab(screenshotIndex - 1); + } + } else if (screenshotIndex < activeScreenshotTab) { + setActiveScreenshotTab(activeScreenshotTab - 1); + } + + notify('error', `Screenshot "${screenshotName}" discarded`); + }; + + const handleRemoveAllTextActions = () => { + const uniqueActionIds = new Set(); + captureTextData.forEach(textStep => { + if (textStep.actionId) { + uniqueActionIds.add(textStep.actionId); + } + }); + + uniqueActionIds.forEach(actionId => { + deleteStepsByActionId(actionId); + + if (socket) { + socket.emit('removeAction', { actionId }); + } + }); + + notify('error', `Text data "${currentTextGroupName}" discarded`); + }; + const startEdit = (stepId: number, type: 'list' | 'text' | 'screenshot', currentValue: string) => { setEditing({ stepId, type, value: currentValue }); }; @@ -577,6 +649,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se : '2px solid #ffffff' : '2px solid transparent', transition: 'all 0.2s ease', + position: 'relative', '&:hover': { backgroundColor: isActive ? undefined @@ -584,6 +657,9 @@ export const InterpretationLog: React.FC = ({ isOpen, se ? '#161616' : '#e9ecef', }, + '&:hover .delete-icon': { + opacity: 1 + }, }} > {isEditing ? ( @@ -612,7 +688,33 @@ export const InterpretationLog: React.FC = ({ isOpen, se }} /> ) : ( - listItem.name || `List Data ${index + 1}` + <> + {listItem.name || `List Data ${index + 1}`} + { + e.stopPropagation(); + handleRemoveListAction(listItem.id, listItem.actionId); + }} + sx={{ + position: 'absolute', + right: 4, + top: '50%', + transform: 'translateY(-50%)', + opacity: 0, + transition: 'opacity 0.2s', + color: darkMode ? '#999' : '#666', + padding: '2px', + '&:hover': { + color: '#f44336', + backgroundColor: darkMode ? 'rgba(244, 67, 54, 0.1)' : 'rgba(244, 67, 54, 0.05)' + } + }} + > + + + )} @@ -783,7 +885,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se }} > {(() => { - const screenshotSteps = browserSteps.filter(step => step.type === 'screenshot' && step.screenshotData) as Array<{ id: number; name?: string; type: 'screenshot'; screenshotData?: string }>; + const screenshotSteps = browserSteps.filter(step => step.type === 'screenshot' && step.screenshotData) as Array<{ id: number; name?: string; type: 'screenshot'; fullPage: boolean; actionId?: string; screenshotData?: string }>; return screenshotData.map((screenshot, index) => { const screenshotStep = screenshotSteps[index]; if (!screenshotStep) return null; @@ -829,6 +931,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se : '2px solid #ffffff' : '2px solid transparent', transition: 'all 0.2s ease', + position: 'relative', '&:hover': { backgroundColor: isActive ? undefined @@ -836,6 +939,9 @@ export const InterpretationLog: React.FC = ({ isOpen, se ? '#161616' : '#e9ecef', }, + '&:hover .delete-icon': { + opacity: 1 + }, }} > {isEditing ? ( @@ -864,7 +970,33 @@ export const InterpretationLog: React.FC = ({ isOpen, se }} /> ) : ( - screenshotName + <> + {screenshotName} + { + e.stopPropagation(); + handleRemoveScreenshotAction(screenshotStep.id, screenshotStep.actionId); + }} + sx={{ + position: 'absolute', + right: 4, + top: '50%', + transform: 'translateY(-50%)', + opacity: 0, + transition: 'opacity 0.2s', + color: darkMode ? '#999' : '#666', + padding: '2px', + '&:hover': { + color: '#f44336', + backgroundColor: darkMode ? 'rgba(244, 67, 54, 0.1)' : 'rgba(244, 67, 54, 0.05)' + } + }} + > + + + )} @@ -921,6 +1053,10 @@ export const InterpretationLog: React.FC = ({ isOpen, se borderColor: darkMode ? '#2a2a2a' : '#d0d0d0', borderBottom: darkMode ? '2px solid #1c1c1c' : '2px solid #ffffff', transition: 'all 0.2s ease', + position: 'relative', + '&:hover .delete-icon': { + opacity: 1 + }, }} > {editingTextGroupName ? ( @@ -952,7 +1088,33 @@ export const InterpretationLog: React.FC = ({ isOpen, se }} /> ) : ( - currentTextGroupName + <> + {currentTextGroupName} + { + e.stopPropagation(); + handleRemoveAllTextActions(); + }} + sx={{ + position: 'absolute', + right: 4, + top: '50%', + transform: 'translateY(-50%)', + opacity: 0, + transition: 'opacity 0.2s', + color: darkMode ? '#999' : '#666', + padding: '2px', + '&:hover': { + color: '#f44336', + backgroundColor: darkMode ? 'rgba(244, 67, 54, 0.1)' : 'rgba(244, 67, 54, 0.05)' + } + }} + > + + + )} From 447375c4e6ddd61b2308ad90a680a44db9edc7a8 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 12 Nov 2025 22:16:10 +0530 Subject: [PATCH 80/82] chore: v0.0.26 --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index d0e3fb6a8..7b7521b40 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "maxun", - "version": "0.0.25", + "version": "0.0.26", "author": "Maxun", "license": "AGPL-3.0-or-later", "dependencies": { @@ -51,7 +51,6 @@ "lodash": "^4.17.21", "loglevel": "^1.8.0", "loglevel-plugin-remote": "^0.6.8", - "maxun-core": "^0.0.25", "minio": "^8.0.1", "moment-timezone": "^0.5.45", "node-cron": "^3.0.3", @@ -81,7 +80,7 @@ "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "typedoc": "^0.23.8", - "typescript": "^5.0.0", + "typescript": "^5.0.0", "uuid": "^8.3.2", "uuidv4": "^6.2.12", "web-vitals": "^2.1.4", From 4fcd6022644aba6394f78a6cc2284d7601f3d2d7 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 12 Nov 2025 22:17:46 +0530 Subject: [PATCH 81/82] chore: core v0.0.26 --- maxun-core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maxun-core/package.json b/maxun-core/package.json index cd920d79c..a999f25bf 100644 --- a/maxun-core/package.json +++ b/maxun-core/package.json @@ -1,6 +1,6 @@ { "name": "maxun-core", - "version": "0.0.25", + "version": "0.0.26", "description": "Core package for Maxun, responsible for data extraction", "main": "build/index.js", "typings": "build/index.d.ts", From d84dd47d54dbf53cbcebb0a720736395e08efd59 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 12 Nov 2025 22:26:36 +0530 Subject: [PATCH 82/82] chore: install core v0.0.26 --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 7b7521b40..c70b0fef5 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "lodash": "^4.17.21", "loglevel": "^1.8.0", "loglevel-plugin-remote": "^0.6.8", + "maxun-core": "^0.0.26", "minio": "^8.0.1", "moment-timezone": "^0.5.45", "node-cron": "^3.0.3",