From 1f99295c0f2c67cf1b5b80c0ee2daeebe74a30a1 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Mon, 3 Nov 2025 16:00:46 +0530 Subject: [PATCH 01/17] 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 46a8886d..f592f846 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 98642f8c..207e8b56 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 6e362471..d53ce32d 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 58aa5f5c563df7b560de5061ed3ba2f751fde9e4 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Mon, 3 Nov 2025 22:55:14 +0530 Subject: [PATCH 02/17] 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 a3796cbe..c88ba068 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 03/17] 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 e257367b..85bf311f 100644 --- a/src/pages/PageWrapper.tsx +++ b/src/pages/PageWrapper.tsx @@ -116,6 +116,10 @@ export const PageWrapper = () => { path="/register" element={} /> + } + /> } /> From f49c4b1265661c0147b877097fbe261c5e296d4a Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Wed, 5 Nov 2025 23:19:16 +0530 Subject: [PATCH 04/17] fix: rm ddefault action name setting --- .../workflow-management/classes/Interpreter.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/server/src/workflow-management/classes/Interpreter.ts b/server/src/workflow-management/classes/Interpreter.ts index f9a48921..257e478c 100644 --- a/server/src/workflow-management/classes/Interpreter.ts +++ b/server/src/workflow-management/classes/Interpreter.ts @@ -526,21 +526,6 @@ export class WorkflowInterpreter { let actionName = this.currentActionName || ""; - if (!actionName) { - if (!Array.isArray(data) && Object.keys(data).length === 1) { - const soleKey = Object.keys(data)[0]; - const soleValue = data[soleKey]; - if (Array.isArray(soleValue) || typeof soleValue === "object") { - actionName = soleKey; - data = soleValue; - } - } - } - - if (!actionName) { - actionName = "Unnamed Action"; - } - const flattened = Array.isArray(data) ? data : (data?.List ?? (data && typeof data === 'object' ? Object.values(data).flat?.() ?? data : [])); From ddce0ac783db9ec8bf744d96174d100c0ac8e911 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Wed, 5 Nov 2025 23:22:49 +0530 Subject: [PATCH 05/17] fix: normalize legacy robot names --- src/components/run/RunContent.tsx | 63 +++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 197f4b1f..b005e4e5 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -115,27 +115,29 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const rawKeys = Object.keys(row.binaryOutput); const isLegacyPattern = rawKeys.every(key => /^item-\d+-\d+$/.test(key)); + + let normalizedScreenshotKeys: string[]; if (isLegacyPattern) { - const renamedKeys = rawKeys.map((_, index) => `Screenshot ${index + 1}`); - const keyMap: Record = {}; - - renamedKeys.forEach((displayName, index) => { - keyMap[displayName] = rawKeys[index]; - }); - - setScreenshotKeys(renamedKeys); - setScreenshotKeyMap(keyMap); + // Legacy unnamed screenshots → Screenshot 1, Screenshot 2... + normalizedScreenshotKeys = rawKeys.map((_, index) => `Screenshot ${index + 1}`); } else { - const keyMap: Record = {}; - rawKeys.forEach(key => { - keyMap[key] = key; + // Same rule as captured lists: if name missing or generic, auto-label + normalizedScreenshotKeys = rawKeys.map((key, index) => { + if (!key || key.toLowerCase().includes("screenshot")) { + return `Screenshot ${index + 1}`; + } + return key; }); - - setScreenshotKeys(rawKeys); - setScreenshotKeyMap(keyMap); } + const keyMap: Record = {}; + normalizedScreenshotKeys.forEach((displayName, index) => { + keyMap[displayName] = rawKeys[index]; + }); + + setScreenshotKeys(normalizedScreenshotKeys); + setScreenshotKeyMap(keyMap); setCurrentScreenshotIndex(0); } else { setScreenshotKeys([]); @@ -202,7 +204,14 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const processSchemaData = (schemaOutput: any) => { const keys = Object.keys(schemaOutput); - setSchemaKeys(keys); + const normalizedKeys = keys.map((key, index) => { + if (!key || key.toLowerCase().includes("scrapeschema")) { + return keys.length === 1 ? "Texts" : `Text ${index + 1}`; + } + return key; + }); + + setSchemaKeys(normalizedKeys); const dataByKey: Record = {}; const columnsByKey: Record = {}; @@ -248,8 +257,17 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe } }); - setSchemaDataByKey(dataByKey); - setSchemaColumnsByKey(columnsByKey); + const remappedDataByKey: Record = {}; + const remappedColumnsByKey: Record = {}; + + normalizedKeys.forEach((newKey, idx) => { + const oldKey = keys[idx]; + remappedDataByKey[newKey] = dataByKey[oldKey]; + remappedColumnsByKey[newKey] = columnsByKey[oldKey]; + }); + + setSchemaDataByKey(remappedDataByKey); + setSchemaColumnsByKey(remappedColumnsByKey); if (allData.length > 0) { const allColumns = new Set(); @@ -290,7 +308,14 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe setListData(tablesList); setListColumns(columnsList); - setListKeys(keys); + const normalizedListKeys = keys.map((key, index) => { + if (!key || key.toLowerCase().includes("scrapelist")) { + return `List ${index + 1}`; + } + return key; + }); + + setListKeys(normalizedListKeys); setCurrentListIndex(0); }; From 85926ae7849063ccd727f42422acaf04055a82fc Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Wed, 5 Nov 2025 23:23:19 +0530 Subject: [PATCH 06/17] fix: null error --- maxun-core/src/preprocessor.ts | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/maxun-core/src/preprocessor.ts b/maxun-core/src/preprocessor.ts index 3d4307a9..f56f9d38 100644 --- a/maxun-core/src/preprocessor.ts +++ b/maxun-core/src/preprocessor.ts @@ -55,7 +55,7 @@ export default class Preprocessor { */ static getParams(workflow: WorkflowFile): string[] { const getParamsRecurse = (object: any): string[] => { - if (typeof object === 'object') { + if (typeof object === 'object' && object !== null) { // Recursion base case if (object.$param) { return [object.$param]; @@ -141,14 +141,24 @@ export default class Preprocessor { } const out = object; - // for every key (child) of the object + Object.keys(object!).forEach((key) => { - // if the field has only one key, which is `k` - if (Object.keys((object)[key]).length === 1 && (object)[key][k]) { - // process the current special tag (init param, hydrate regex...) - (out)[key] = f((object)[key][k]); - } else { - initSpecialRecurse((object)[key], k, f); + const childValue = (object)[key]; + + if (!childValue || typeof childValue !== 'object') { + return; + } + + try { + const childKeys = Object.keys(childValue); + + if (childKeys.length === 1 && childValue[k]) { + (out)[key] = f(childValue[k]); + } else { + initSpecialRecurse(childValue, k, f); + } + } catch (error) { + console.warn(`Error processing key "${key}" in initSpecialRecurse:`, error); } }); return out; From 342c5a97b6457101e75ebb8a31ba0af487b83c7f Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 6 Nov 2025 00:00:54 +0530 Subject: [PATCH 07/17] 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 a0042694..c269add1 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 ac6838ed58a21787c068d7bbf524e138bea02ae6 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Thu, 6 Nov 2025 18:06:46 +0530 Subject: [PATCH 08/17] fix: add stop abort option --- src/components/run/RunContent.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 197f4b1f..146c2dc2 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -617,10 +617,15 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe {row.status === 'running' || row.status === 'queued' ? ( - - - {t('run_content.loading')} - + <> + + + {t('run_content.loading')} + + + ) : (!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 09/17] 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 08efb120..fdc31cdc 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 10/17] 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 98642f8c..deff7f0f 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 11/17] 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 fa745160..80671c1f 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 12/17] 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 257e478c..71cac8b7 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 13/17] 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 a0042694..bdc31b76 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 14/17] 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 33a52d65..e0db15cc 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 15/17] 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 e0db15cc..437b1019 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 16/17] 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 437b1019..4240d8af 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 17/17] 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 c269add1..07a2ff34 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 && (