From 7fe982a55970c5d047e32532a3134c1a1c970c2f Mon Sep 17 00:00:00 2001 From: Rohit Date: Sun, 27 Apr 2025 15:14:48 +0530 Subject: [PATCH 01/55] feat: allow multiple capture action execution --- src/components/recorder/RightSidePanel.tsx | 367 ++++++++++++--------- 1 file changed, 208 insertions(+), 159 deletions(-) diff --git a/src/components/recorder/RightSidePanel.tsx b/src/components/recorder/RightSidePanel.tsx index 6027e26d3..54bcc5064 100644 --- a/src/components/recorder/RightSidePanel.tsx +++ b/src/components/recorder/RightSidePanel.tsx @@ -34,9 +34,6 @@ const fetchWorkflow = (id: string, callback: (response: WorkflowFile) => void) = ).catch((error) => { console.log(error.message) }) }; -// TODO: -// 1. Add description for each browser step -// 2. Handle non custom action steps interface RightSidePanelProps { onFinishCapture: () => void; } @@ -46,8 +43,6 @@ export const RightSidePanel: React.FC = ({ onFinishCapture const [errors, setErrors] = useState<{ [id: string]: string }>({}); const [confirmedTextSteps, setConfirmedTextSteps] = useState<{ [id: string]: boolean }>({}); const [confirmedListTextFields, setConfirmedListTextFields] = useState<{ [listId: string]: { [fieldKey: string]: boolean } }>({}); - // const [showPaginationOptions, setShowPaginationOptions] = useState(false); - // const [showLimitOptions, setShowLimitOptions] = useState(false); const [showCaptureList, setShowCaptureList] = useState(true); const [showCaptureScreenshot, setShowCaptureScreenshot] = useState(true); const [showCaptureText, setShowCaptureText] = useState(true); @@ -58,15 +53,31 @@ export const RightSidePanel: React.FC = ({ onFinishCapture const { panelHeight } = useBrowserDimensionsStore(); const { lastAction, notify, currentWorkflowActionsState, setCurrentWorkflowActionsState, resetInterpretationLog } = useGlobalInfoStore(); - const { getText, startGetText, stopGetText, getScreenshot, startGetScreenshot, stopGetScreenshot, getList, startGetList, stopGetList, startPaginationMode, stopPaginationMode, paginationType, updatePaginationType, limitType, customLimit, updateLimitType, updateCustomLimit, stopLimitMode, startLimitMode, captureStage, setCaptureStage, showPaginationOptions, setShowPaginationOptions, showLimitOptions, setShowLimitOptions, workflow, setWorkflow } = useActionContext(); + const { + getText, startGetText, stopGetText, + getList, startGetList, stopGetList, + getScreenshot, startGetScreenshot, stopGetScreenshot, + startPaginationMode, stopPaginationMode, + paginationType, updatePaginationType, + limitType, customLimit, updateLimitType, updateCustomLimit, + stopLimitMode, startLimitMode, + captureStage, setCaptureStage, + showPaginationOptions, setShowPaginationOptions, + showLimitOptions, setShowLimitOptions, + workflow, setWorkflow, + activeAction, setActiveAction, + startAction, finishAction + } = useActionContext(); + const { browserSteps, updateBrowserTextStepLabel, deleteBrowserStep, addScreenshotStep, updateListTextFieldLabel, removeListTextField } = useBrowserSteps(); const { id, socket } = useSocketStore(); const { t } = useTranslation(); + const isAnyActionActive = activeAction !== 'none'; + const workflowHandler = useCallback((data: WorkflowFile) => { setWorkflow(data); - //setRecordingLength(data.workflow.length); - }, []) + }, [setWorkflow]); useEffect(() => { if (socket) { @@ -113,12 +124,10 @@ export const RightSidePanel: React.FC = ({ onFinishCapture hasScrapeSchemaAction, }); - const shouldHideActions = hasScrapeListAction || hasScrapeSchemaAction || hasScreenshotAction; - - setShowCaptureList(!shouldHideActions); - setShowCaptureScreenshot(!shouldHideActions); - setShowCaptureText(!(hasScrapeListAction || hasScreenshotAction)); - }, [workflow]); + setShowCaptureList(true); + setShowCaptureScreenshot(true); + setShowCaptureText(true); + }, [workflow, setCurrentWorkflowActionsState]); const handleMouseEnter = (id: number) => { setHoverStates(prev => ({ ...prev, [id]: true })); @@ -128,8 +137,6 @@ export const RightSidePanel: React.FC = ({ onFinishCapture setHoverStates(prev => ({ ...prev, [id]: false })); }; - const handlePairDelete = () => { } - const handleStartGetText = () => { setIsCaptureTextConfirmed(false); startGetText(); @@ -140,6 +147,10 @@ export const RightSidePanel: React.FC = ({ onFinishCapture startGetList(); } + const handleStartGetScreenshot = () => { + startGetScreenshot(); + }; + const handleTextLabelChange = (id: number, label: string, listId?: number, fieldKey?: string) => { if (listId !== undefined && fieldKey !== undefined) { // Prevent editing if the field is confirmed @@ -253,7 +264,6 @@ export const RightSidePanel: React.FC = ({ onFinishCapture return settings; }, [browserSteps, browserStepIdList]); - const stopCaptureAndEmitGetTextSettings = useCallback(() => { const hasUnconfirmedTextSteps = browserSteps.some(step => step.type === 'text' && !confirmedTextSteps[step.id]); if (hasUnconfirmedTextSteps) { @@ -268,8 +278,9 @@ export const RightSidePanel: React.FC = ({ onFinishCapture } setIsCaptureTextConfirmed(true); resetInterpretationLog(); + finishAction('text'); onFinishCapture(); - }, [stopGetText, getTextSettingsObject, socket, browserSteps, confirmedTextSteps, resetInterpretationLog]); + }, [stopGetText, getTextSettingsObject, socket, browserSteps, confirmedTextSteps, resetInterpretationLog, finishAction, notify, onFinishCapture, t]); const getListSettingsObject = useCallback(() => { let settings: { @@ -311,7 +322,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture setShowLimitOptions(false); updateLimitType(''); updateCustomLimit(''); - }, [updatePaginationType, updateLimitType, updateCustomLimit]); + }, [setShowPaginationOptions, updatePaginationType, setShowLimitOptions, updateLimitType, updateCustomLimit]); const handleStopGetList = useCallback(() => { stopGetList(); @@ -326,10 +337,15 @@ export const RightSidePanel: React.FC = ({ onFinishCapture notify('error', t('right_panel.errors.unable_create_settings')); } handleStopGetList(); + finishAction('list'); onFinishCapture(); - }, [stopGetList, getListSettingsObject, socket, notify, handleStopGetList]); + }, [getListSettingsObject, socket, notify, handleStopGetList, finishAction, onFinishCapture, t]); - const hasUnconfirmedListTextFields = browserSteps.some(step => step.type === 'list' && Object.values(step.fields).some(field => !confirmedListTextFields[step.id]?.[field.id])); + const hasUnconfirmedListTextFields = browserSteps.some(step => + step.type === 'list' && Object.values(step.fields).some(field => + !confirmedListTextFields[step.id]?.[field.id] + ) + ); const handleConfirmListCapture = useCallback(() => { switch (captureStage) { @@ -378,7 +394,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture setCaptureStage('initial'); break; } - }, [captureStage, paginationType, limitType, customLimit, startPaginationMode, stopPaginationMode, startLimitMode, stopLimitMode, notify, stopCaptureAndEmitGetListSettings, getListSettingsObject]); + }, [captureStage, paginationType, limitType, customLimit, startPaginationMode, setShowPaginationOptions, setCaptureStage, getListSettingsObject, notify, stopPaginationMode, startLimitMode, setShowLimitOptions, stopLimitMode, setIsCaptureListConfirmed, stopCaptureAndEmitGetListSettings, t]); const handleBackCaptureList = useCallback(() => { switch (captureStage) { @@ -395,7 +411,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture setCaptureStage('initial'); break; } - }, [captureStage, stopLimitMode, startPaginationMode, stopPaginationMode]); + }, [captureStage, stopLimitMode, setShowLimitOptions, startPaginationMode, setShowPaginationOptions, setCaptureStage, stopPaginationMode]); const handlePaginationSettingSelect = (option: PaginationType) => { updatePaginationType(option); @@ -413,7 +429,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture setConfirmedTextSteps({}); setIsCaptureTextConfirmed(false); notify('error', t('right_panel.errors.capture_text_discarded')); - }, [browserSteps, stopGetText, deleteBrowserStep]); + }, [browserSteps, stopGetText, deleteBrowserStep, notify, t]); const discardGetList = useCallback(() => { stopGetList(); @@ -429,8 +445,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture setConfirmedListTextFields({}); setIsCaptureListConfirmed(false); notify('error', t('right_panel.errors.capture_list_discarded')); - }, [browserSteps, stopGetList, deleteBrowserStep, resetListState]); - + }, [browserSteps, stopGetList, deleteBrowserStep, resetListState, setShowPaginationOptions, setShowLimitOptions, setCaptureStage, notify, t]); const captureScreenshot = (fullPage: boolean) => { const screenshotSettings: ScreenshotSettings = { @@ -444,10 +459,11 @@ export const RightSidePanel: React.FC = ({ onFinishCapture socket?.emit('action', { action: 'screenshot', settings: screenshotSettings }); addScreenshotStep(fullPage); stopGetScreenshot(); + finishAction('screenshot'); + onFinishCapture(); }; const isConfirmCaptureDisabled = useMemo(() => { - // Check if we are in the initial stage and if there are no browser steps or no valid list selectors with fields if (captureStage !== 'initial') return false; const hasValidListSelector = browserSteps.some(step => @@ -456,7 +472,6 @@ export const RightSidePanel: React.FC = ({ onFinishCapture Object.keys(step.fields).length > 0 ); - // Disable the button if there are no valid list selectors or if there are unconfirmed list text fields return !hasValidListSelector || hasUnconfirmedListTextFields; }, [captureStage, browserSteps, hasUnconfirmedListTextFields]); @@ -465,15 +480,41 @@ export const RightSidePanel: React.FC = ({ onFinishCapture return ( - {/* - Last action: {` ${lastAction}`} - */} - {!getText && !getScreenshot && !getList && showCaptureList && } + {!isAnyActionActive && ( + <> + {showCaptureList && ( + + )} + + {showCaptureText && ( + + )} + + {showCaptureScreenshot && ( + + )} + + )} {getList && ( - <> + {(captureStage === 'pagination' || captureStage === 'limit') && ( - - )} - {showPaginationOptions && ( - - {t('right_panel.pagination.title')} - - - - - + + {showPaginationOptions && ( + + {t('right_panel.pagination.title')} + + + + + + + )} + + {showLimitOptions && ( + + +

{t('right_panel.limit.title')}

+
+ updateLimitType(e.target.value as LimitType)} + sx={{ + display: 'flex', + flexDirection: 'column', + width: '500px' + }} + > + } label="10" /> + } label="100" /> +
+ } label={t('right_panel.limit.custom')} /> + {limitType === 'custom' && ( + ) => { + const value = parseInt(e.target.value); + if (e.target.value === '' || value >= 1) { + updateCustomLimit(e.target.value); + } + }} + inputProps={{ + min: 1, + onKeyPress: (e: React.KeyboardEvent) => { + const value = (e.target as HTMLInputElement).value + e.key; + if (parseInt(value) < 1) { + e.preventDefault(); + } + } + }} + placeholder={t('right_panel.limit.enter_number')} + sx={{ + marginLeft: '10px', + '& input': { + padding: '10px', + }, + width: '150px', + background: isDarkMode ? "#1E2124" : 'white', + color: isDarkMode ? "white" : 'black', + }} + /> + )} +
+
+
+ )}
)} - {showLimitOptions && ( - - -

{t('right_panel.limit.title')}

-
- updateLimitType(e.target.value as LimitType)} - sx={{ - display: 'flex', - flexDirection: 'column', - width: '500px' - }} - > - } label="10" /> - } label="100" /> -
- } label={t('right_panel.limit.custom')} /> - {limitType === 'custom' && ( - ) => { - const value = parseInt(e.target.value); - // Only update if the value is greater than or equal to 1 or if the field is empty - if (e.target.value === '' || value >= 1) { - updateCustomLimit(e.target.value); - } - }} - inputProps={{ - min: 1, - onKeyPress: (e: React.KeyboardEvent) => { - const value = (e.target as HTMLInputElement).value + e.key; - if (parseInt(value) < 1) { - e.preventDefault(); - } - } - }} - placeholder={t('right_panel.limit.enter_number')} - sx={{ - marginLeft: '10px', - '& input': { - padding: '10px', - - }, - width: '150px', - background: isDarkMode ? "#1E2124" : 'white', - color: isDarkMode ? "white" : 'black', // Ensure the text field does not go outside the panel - }} - /> - )} -
-
-
- )} - {/* {!getText && !getScreenshot && !getList && showCaptureText && } */} - - {!getText && !getScreenshot && !getList && showCaptureText && } - {getText && - <> + + {getText && ( + - - } - {/* {!getText && !getScreenshot && !getList && showCaptureScreenshot && } */} - {!getText && !getScreenshot && !getList && showCaptureScreenshot && } + + )} + {getScreenshot && ( - - + + )}
+ {browserSteps.map(step => ( handleMouseEnter(step.id)} onMouseLeave={() => handleMouseLeave(step.id)} sx={{ padding: '10px', margin: '11px', borderRadius: '5px', position: 'relative', background: isDarkMode ? "#1E2124" : 'white', color: isDarkMode ? "white" : 'black' }}> @@ -714,7 +764,6 @@ export const RightSidePanel: React.FC = ({ onFinishCapture ) }} - /> {!confirmedTextSteps[step.id] ? ( From c6266fd17c990c73962e64e563e00eda5004570c Mon Sep 17 00:00:00 2001 From: Rohit Date: Sun, 27 Apr 2025 15:22:06 +0530 Subject: [PATCH 02/55] feat: emit action type --- maxun-core/src/interpret.ts | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index e662683c4..4e5e2be9b 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -45,6 +45,7 @@ interface InterpreterOptions { debugChannel: Partial<{ activeId: Function, debugMessage: Function, + setActionType: Function, }> } @@ -377,12 +378,20 @@ export default class Interpreter extends EventEmitter { */ const wawActions: Record void> = { screenshot: async (params: PageScreenshotOptions) => { + if (this.options.debugChannel?.setActionType) { + this.options.debugChannel.setActionType('screenshot'); + } + const screenshotBuffer = await page.screenshot({ ...params, path: undefined, }); await this.options.binaryCallback(screenshotBuffer, 'image/png'); }, enqueueLinks: async (selector: string) => { + if (this.options.debugChannel?.setActionType) { + this.options.debugChannel.setActionType('enqueueLinks'); + } + const links: string[] = await page.locator(selector) .evaluateAll( // @ts-ignore @@ -409,6 +418,10 @@ export default class Interpreter extends EventEmitter { await page.close(); }, scrape: async (selector?: string) => { + if (this.options.debugChannel?.setActionType) { + this.options.debugChannel.setActionType('scrape'); + } + await this.ensureScriptsLoaded(page); const scrapeResults: Record[] = await page.evaluate((s) => window.scrape(s ?? null), selector); @@ -416,6 +429,10 @@ export default class Interpreter extends EventEmitter { }, scrapeSchema: async (schema: Record) => { + if (this.options.debugChannel?.setActionType) { + this.options.debugChannel.setActionType('scrapeSchema'); + } + await this.ensureScriptsLoaded(page); const scrapeResult = await page.evaluate((schemaObj) => window.scrapeSchema(schemaObj), schema); @@ -458,6 +475,10 @@ export default class Interpreter extends EventEmitter { }, scrapeList: async (config: { listSelector: string, fields: any, limit?: number, pagination: any }) => { + if (this.options.debugChannel?.setActionType) { + this.options.debugChannel.setActionType('scrapeList'); + } + await this.ensureScriptsLoaded(page); if (!config.pagination) { const scrapeResults: Record[] = await page.evaluate((cfg) => window.scrapeList(cfg), config); @@ -469,6 +490,10 @@ export default class Interpreter extends EventEmitter { }, scrapeListAuto: async (config: { listSelector: string }) => { + if (this.options.debugChannel?.setActionType) { + this.options.debugChannel.setActionType('scrapeListAuto'); + } + await this.ensureScriptsLoaded(page); const scrapeResults: { selector: string, innerText: string }[] = await page.evaluate((listSelector) => { @@ -479,6 +504,10 @@ export default class Interpreter extends EventEmitter { }, scroll: async (pages?: number) => { + if (this.options.debugChannel?.setActionType) { + this.options.debugChannel.setActionType('scroll'); + } + await page.evaluate(async (pagesInternal) => { for (let i = 1; i <= (pagesInternal ?? 1); i += 1) { // @ts-ignore @@ -488,6 +517,10 @@ export default class Interpreter extends EventEmitter { }, script: async (code: string) => { + if (this.options.debugChannel?.setActionType) { + this.options.debugChannel.setActionType('script'); + } + const AsyncFunction: FunctionConstructor = Object.getPrototypeOf( async () => { }, ).constructor; @@ -496,6 +529,10 @@ export default class Interpreter extends EventEmitter { }, flag: async () => new Promise((res) => { + if (this.options.debugChannel?.setActionType) { + this.options.debugChannel.setActionType('flag'); + } + this.emit('flag', page, res); }), }; @@ -526,6 +563,10 @@ export default class Interpreter extends EventEmitter { const params = !step.args || Array.isArray(step.args) ? step.args : [step.args]; await wawActions[step.action as CustomFunctions](...(params ?? [])); } else { + if (this.options.debugChannel?.setActionType) { + this.options.debugChannel.setActionType(String(step.action)); + } + // Implements the dot notation for the "method name" in the workflow const levels = String(step.action).split('.'); const methodName = levels[levels.length - 1]; From cd4820f818e02ea588754a76336fc532a3201ad4 Mon Sep 17 00:00:00 2001 From: Rohit Date: Sun, 27 Apr 2025 15:23:05 +0530 Subject: [PATCH 03/55] feat: serialize data by action type --- .../classes/Interpreter.ts | 58 ++++++++++++++++--- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/server/src/workflow-management/classes/Interpreter.ts b/server/src/workflow-management/classes/Interpreter.ts index c8aec13c4..c9e83f1b8 100644 --- a/server/src/workflow-management/classes/Interpreter.ts +++ b/server/src/workflow-management/classes/Interpreter.ts @@ -87,9 +87,22 @@ export class WorkflowInterpreter { public debugMessages: string[] = []; /** - * An array of all the serializable data extracted from the run. + * Storage for different types of serializable data */ - public serializableData: string[] = []; + public serializableDataByType: { + scrapeSchema: any[], + scrapeList: any[], + other: any[] + } = { + scrapeSchema: [], + scrapeList: [], + other: [] + }; + + /** + * Track the current action type being processed + */ + private currentActionType: string | null = null; /** * An array of all the binary data extracted from the run. @@ -181,6 +194,9 @@ export class WorkflowInterpreter { this.debugMessages.push(`[${new Date().toLocaleString()}] ` + msg); this.socket.emit('log', msg) }, + setActionType: (type: string) => { + this.currentActionType = type; + } }, serializableCallback: (data: any) => { this.socket.emit('serializableCallback', data); @@ -246,7 +262,12 @@ export class WorkflowInterpreter { this.interpreter = null; this.breakpoints = []; this.interpretationResume = null; - this.serializableData = []; + this.currentActionType = null; + this.serializableDataByType = { + scrapeSchema: [], + scrapeList: [], + other: [] + }; this.binaryData = []; } @@ -278,9 +299,19 @@ export class WorkflowInterpreter { this.debugMessages.push(`[${new Date().toLocaleString()}] ` + msg); this.socket.emit('debugMessage', msg) }, + setActionType: (type: string) => { + this.currentActionType = type; + } }, serializableCallback: (data: any) => { - this.serializableData.push(data); + if (this.currentActionType === 'scrapeSchema') { + this.serializableDataByType.scrapeSchema.push(data); + } else if (this.currentActionType === 'scrapeList') { + this.serializableDataByType.scrapeList.push(data); + } else { + this.serializableDataByType.other.push(data); + } + this.socket.emit('serializableCallback', data); }, binaryCallback: async (data: string, mimetype: string) => { @@ -311,14 +342,23 @@ export class WorkflowInterpreter { const status = await interpreter.run(page, params); - const lastArray = this.serializableData.length > 1 - ? [this.serializableData[this.serializableData.length - 1]] - : this.serializableData; - + // Structure the output to maintain separate data for each action type const result = { log: this.debugMessages, result: status, - serializableOutput: lastArray.reduce((reducedObject, item, index) => { + scrapeSchemaOutput: this.serializableDataByType.scrapeSchema.reduce((reducedObject, item, index) => { + return { + [`schema-${index}`]: item, + ...reducedObject, + } + }, {}), + scrapeListOutput: this.serializableDataByType.scrapeList.reduce((reducedObject, item, index) => { + return { + [`list-${index}`]: item, + ...reducedObject, + } + }, {}), + otherOutput: this.serializableDataByType.other.reduce((reducedObject, item, index) => { return { [`item-${index}`]: item, ...reducedObject, From bd5087ef6cd633cfeead74a99cf882efd49a0a7d Mon Sep 17 00:00:00 2001 From: Rohit Date: Sun, 27 Apr 2025 15:27:52 +0530 Subject: [PATCH 04/55] feat: run and abort categorize data by action type --- server/src/pgboss-worker.ts | 111 ++++++++++++++++++++++++++---------- 1 file changed, 82 insertions(+), 29 deletions(-) diff --git a/server/src/pgboss-worker.ts b/server/src/pgboss-worker.ts index 3ac993a7c..b6cb13551 100644 --- a/server/src/pgboss-worker.ts +++ b/server/src/pgboss-worker.ts @@ -255,7 +255,6 @@ async function processRunExecution(job: Job) { return { success: true }; } - // Process the results const binaryOutputService = new BinaryOutputService('maxun-run-screenshots'); const uploadedBinaryOutput = await binaryOutputService.uploadAndStoreBinaryOutput(run, interpretationInfo.binaryOutput); @@ -264,36 +263,57 @@ async function processRunExecution(job: Job) { return { success: true }; } - // Update the run record with results + const categorizedOutput = { + scrapeSchema: interpretationInfo.scrapeSchemaOutput || {}, + scrapeList: interpretationInfo.scrapeListOutput || {}, + other: interpretationInfo.otherOutput || {} + }; + await run.update({ ...run, status: 'success', finishedAt: new Date().toLocaleString(), browserId: plainRun.browserId, log: interpretationInfo.log.join('\n'), - serializableOutput: interpretationInfo.serializableOutput, + serializableOutput: { + scrapeSchema: Object.values(categorizedOutput.scrapeSchema), + scrapeList: Object.values(categorizedOutput.scrapeList), + other: Object.values(categorizedOutput.other), + }, binaryOutput: uploadedBinaryOutput, }); // Track extraction metrics - let totalRowsExtracted = 0; + let totalSchemaItemsExtracted = 0; + let totalListItemsExtracted = 0; let extractedScreenshotsCount = 0; - let extractedItemsCount = 0; - - if (run.dataValues.binaryOutput && run.dataValues.binaryOutput["item-0"]) { - extractedScreenshotsCount = 1; + + if (categorizedOutput.scrapeSchema) { + Object.values(categorizedOutput.scrapeSchema).forEach((schemaResult: any) => { + if (Array.isArray(schemaResult)) { + totalSchemaItemsExtracted += schemaResult.length; + } else if (schemaResult && typeof schemaResult === 'object') { + totalSchemaItemsExtracted += 1; + } + }); } - - if (run.dataValues.serializableOutput && run.dataValues.serializableOutput["item-0"]) { - const itemsArray = run.dataValues.serializableOutput["item-0"]; - extractedItemsCount = itemsArray.length; - - totalRowsExtracted = itemsArray.reduce((total, item) => { - return total + Object.keys(item).length; - }, 0); + + if (categorizedOutput.scrapeList) { + Object.values(categorizedOutput.scrapeList).forEach((listResult: any) => { + if (Array.isArray(listResult)) { + totalListItemsExtracted += listResult.length; + } + }); } - - console.log(`Extracted Items Count: ${extractedItemsCount}`); + + if (uploadedBinaryOutput) { + extractedScreenshotsCount = Object.keys(uploadedBinaryOutput).length; + } + + const totalRowsExtracted = totalSchemaItemsExtracted + totalListItemsExtracted; + + console.log(`Extracted Schema Items Count: ${totalSchemaItemsExtracted}`); + console.log(`Extracted List Items Count: ${totalListItemsExtracted}`); console.log(`Extracted Screenshots Count: ${extractedScreenshotsCount}`); console.log(`Total Rows Extracted: ${totalRowsExtracted}`); @@ -306,7 +326,8 @@ async function processRunExecution(job: Job) { created_at: new Date().toISOString(), status: 'success', totalRowsExtracted, - extractedItemsCount, + schemaItemsExtracted: totalSchemaItemsExtracted, + listItemsExtracted: totalListItemsExtracted, extractedScreenshotsCount, } ); @@ -339,7 +360,7 @@ async function processRunExecution(job: Job) { robotName: recording.recording_meta.name, status: 'success', finishedAt: new Date().toLocaleString() - });; + }); // Check for and process queued runs before destroying the browser const queuedRunProcessed = await checkAndProcessQueuedRun(data.userId, plainRun.browserId); @@ -458,7 +479,11 @@ async function abortRun(runId: string, userId: string): Promise { } let currentLog = 'Run aborted by user'; - let serializableOutput: Record = {}; + let categorizedOutput = { + scrapeSchema: {}, + scrapeList: {}, + other: {} + }; let binaryOutput: Record = {}; try { @@ -467,16 +492,16 @@ async function abortRun(runId: string, userId: string): Promise { currentLog = browser.interpreter.debugMessages.join('\n') || currentLog; } - if (browser.interpreter.serializableData) { - browser.interpreter.serializableData.forEach((item, index) => { - serializableOutput[`item-${index}`] = item; - }); + if (browser.interpreter.serializableDataByType) { + categorizedOutput = { + scrapeSchema: collectDataByType(browser.interpreter.serializableDataByType.scrapeSchema || []), + scrapeList: collectDataByType(browser.interpreter.serializableDataByType.scrapeList || []), + other: collectDataByType(browser.interpreter.serializableDataByType.other || []) + }; } if (browser.interpreter.binaryData) { - browser.interpreter.binaryData.forEach((item, index) => { - binaryOutput[`item-${index}`] = item; - }); + binaryOutput = collectBinaryData(browser.interpreter.binaryData); } } } catch (interpreterError) { @@ -488,7 +513,11 @@ async function abortRun(runId: string, userId: string): Promise { finishedAt: new Date().toLocaleString(), browserId: plainRun.browserId, log: currentLog, - serializableOutput, + serializableOutput: { + scrapeSchema: Object.values(categorizedOutput.scrapeSchema), + scrapeList: Object.values(categorizedOutput.scrapeList), + other: Object.values(categorizedOutput.other), + }, binaryOutput, }); @@ -529,6 +558,30 @@ async function abortRun(runId: string, userId: string): Promise { } } +/** + * Helper function to collect data from arrays into indexed objects + * @param dataArray Array of data to be transformed into an object with indexed keys + * @returns Object with indexed keys + */ +function collectDataByType(dataArray: any[]): Record { + return dataArray.reduce((result: Record, item, index) => { + result[`item-${index}`] = item; + return result; + }, {}); +} + +/** + * Helper function to collect binary data (like screenshots) + * @param binaryDataArray Array of binary data objects to be transformed + * @returns Object with indexed keys + */ +function collectBinaryData(binaryDataArray: { mimetype: string, data: string, type?: string }[]): Record { + return binaryDataArray.reduce((result: Record, item, index) => { + result[`item-${index}`] = item; + return result; + }, {}); +} + async function registerRunExecutionWorker() { try { const registeredUserQueues = new Map(); From 35e7778c32facfe6c538e13c6eb25fde880025f6 Mon Sep 17 00:00:00 2001 From: Rohit Date: Sun, 27 Apr 2025 15:28:50 +0530 Subject: [PATCH 05/55] feat: schedule categorize data by action type --- server/src/workflow-management/scheduler/index.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/server/src/workflow-management/scheduler/index.ts b/server/src/workflow-management/scheduler/index.ts index 8267fbb81..bbf845a6d 100644 --- a/server/src/workflow-management/scheduler/index.ts +++ b/server/src/workflow-management/scheduler/index.ts @@ -132,6 +132,12 @@ async function executeRun(id: string, userId: string) { const binaryOutputService = new BinaryOutputService('maxun-run-screenshots'); const uploadedBinaryOutput = await binaryOutputService.uploadAndStoreBinaryOutput(run, interpretationInfo.binaryOutput); + const categorizedOutput = { + scrapeSchema: interpretationInfo.scrapeSchemaOutput || {}, + scrapeList: interpretationInfo.scrapeListOutput || {}, + other: interpretationInfo.otherOutput || {} + }; + await destroyRemoteBrowser(plainRun.browserId, userId); await run.update({ @@ -140,7 +146,11 @@ async function executeRun(id: string, userId: string) { finishedAt: new Date().toLocaleString(), browserId: plainRun.browserId, log: interpretationInfo.log.join('\n'), - serializableOutput: interpretationInfo.serializableOutput, + serializableOutput: { + scrapeSchema: Object.values(categorizedOutput.scrapeSchema), + scrapeList: Object.values(categorizedOutput.scrapeList), + other: Object.values(categorizedOutput.other), + }, binaryOutput: uploadedBinaryOutput, }); From 00ef3baa3b2bec16be9c5da588bbfca5ac4ef59a Mon Sep 17 00:00:00 2001 From: Rohit Date: Sun, 27 Apr 2025 15:30:08 +0530 Subject: [PATCH 06/55] feat: record api categorize data by action type --- server/src/api/record.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/server/src/api/record.ts b/server/src/api/record.ts index b4014c3a8..39a4b3df9 100644 --- a/server/src/api/record.ts +++ b/server/src/api/record.ts @@ -586,6 +586,12 @@ async function executeRun(id: string, userId: string) { const binaryOutputService = new BinaryOutputService('maxun-run-screenshots'); const uploadedBinaryOutput = await binaryOutputService.uploadAndStoreBinaryOutput(run, interpretationInfo.binaryOutput); + const categorizedOutput = { + scrapeSchema: interpretationInfo.scrapeSchemaOutput || {}, + scrapeList: interpretationInfo.scrapeListOutput || {}, + other: interpretationInfo.otherOutput || {} + }; + await destroyRemoteBrowser(plainRun.browserId, userId); const updatedRun = await run.update({ @@ -594,7 +600,11 @@ async function executeRun(id: string, userId: string) { finishedAt: new Date().toLocaleString(), browserId: plainRun.browserId, log: interpretationInfo.log.join('\n'), - serializableOutput: interpretationInfo.serializableOutput, + serializableOutput: { + scrapeSchema: Object.values(categorizedOutput.scrapeSchema), + scrapeList: Object.values(categorizedOutput.scrapeList), + other: Object.values(categorizedOutput.other), + }, binaryOutput: uploadedBinaryOutput, }); From 6243563ff715215ecf45ade18678091ca7d82e3e Mon Sep 17 00:00:00 2001 From: Rohit Date: Sun, 27 Apr 2025 15:32:23 +0530 Subject: [PATCH 07/55] feat: track browser actions state --- src/context/browserActions.tsx | 97 ++++++++++++++++++++++++++++------ 1 file changed, 81 insertions(+), 16 deletions(-) diff --git a/src/context/browserActions.tsx b/src/context/browserActions.tsx index bc377bbb6..c332b8c8c 100644 --- a/src/context/browserActions.tsx +++ b/src/context/browserActions.tsx @@ -6,6 +6,7 @@ import { emptyWorkflow } from '../shared/constants'; export type PaginationType = 'scrollDown' | 'scrollUp' | 'clickNext' | 'clickLoadMore' | 'none' | ''; export type LimitType = '10' | '100' | 'custom' | ''; export type CaptureStage = 'initial' | 'pagination' | 'limit' | 'complete' | ''; +export type ActionType = 'text' | 'list' | 'screenshot'; interface ActionContextProps { getText: boolean; @@ -19,18 +20,27 @@ interface ActionContextProps { customLimit: string; captureStage: CaptureStage; showPaginationOptions: boolean; - showLimitOptions: boolean; + showLimitOptions: boolean; + actionsInWorkflow: { + text: boolean; + list: boolean; + screenshot: boolean; + }; + activeAction: 'none' | 'text' | 'list' | 'screenshot'; + setActiveAction: (action: 'none' | 'text' | 'list' | 'screenshot') => void; setWorkflow: (workflow: WorkflowFile) => void; setShowPaginationOptions: (show: boolean) => void; setShowLimitOptions: (show: boolean) => void; setCaptureStage: (stage: CaptureStage) => void; - startPaginationMode: () => void; + startAction: (action: 'text' | 'list' | 'screenshot') => void; + finishAction: (action: 'text' | 'list' | 'screenshot') => void; startGetText: () => void; stopGetText: () => void; startGetList: () => void; stopGetList: () => void; startGetScreenshot: () => void; stopGetScreenshot: () => void; + startPaginationMode: () => void; stopPaginationMode: () => void; updatePaginationType: (type: PaginationType) => void; startLimitMode: () => void; @@ -54,9 +64,54 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { const [captureStage, setCaptureStage] = useState('initial'); const [showPaginationOptions, setShowPaginationOptions] = useState(false); const [showLimitOptions, setShowLimitOptions] = useState(false); + const [actionsInWorkflow, setActionsInWorkflow] = useState({ + text: false, + list: false, + screenshot: false + }); + const [activeAction, setActiveAction] = useState<'none' | 'text' | 'list' | 'screenshot'>('none'); const { socket } = useSocketStore(); + const startAction = (action: 'text' | 'list' | 'screenshot') => { + if (activeAction !== 'none') return; + + setActiveAction(action); + + if (action === 'text') { + setGetText(true); + } else if (action === 'list') { + setGetList(true); + socket?.emit('setGetList', { getList: true }); + setCaptureStage('initial'); + } else if (action === 'screenshot') { + setGetScreenshot(true); + } + }; + + const finishAction = (action: 'text' | 'list' | 'screenshot') => { + if (activeAction !== action) return; + + setActionsInWorkflow(prev => ({ + ...prev, + [action]: true + })); + + setActiveAction('none'); + + if (action === 'text') { + setGetText(false); + } else if (action === 'list') { + setGetList(false); + setPaginationType(''); + setLimitType(''); + setCustomLimit(''); + setCaptureStage('complete'); + } else if (action === 'screenshot') { + setGetScreenshot(false); + } + }; + const updatePaginationType = (type: PaginationType) => setPaginationType(type); const updateLimitType = (type: LimitType) => setLimitType(type); const updateCustomLimit = (limit: string) => setCustomLimit(limit); @@ -77,25 +132,30 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { const stopLimitMode = () => setLimitMode(false); - const startGetText = () => setGetText(true); - const stopGetText = () => setGetText(false); - - const startGetList = () => { - setGetList(true); - socket?.emit('setGetList', { getList: true }); - setCaptureStage('initial'); - } - + const startGetText = () => startAction('text'); + + const stopGetText = () => { + setGetText(false); + setActiveAction('none'); + }; + + const startGetList = () => startAction('list'); + const stopGetList = () => { setGetList(false); setPaginationType(''); setLimitType(''); setCustomLimit(''); setCaptureStage('complete'); + setActiveAction('none'); + }; + + const startGetScreenshot = () => startAction('screenshot'); + + const stopGetScreenshot = () => { + setGetScreenshot(false); + setActiveAction('none'); }; - - const startGetScreenshot = () => setGetScreenshot(true); - const stopGetScreenshot = () => setGetScreenshot(false); return ( { captureStage, showPaginationOptions, showLimitOptions, + actionsInWorkflow, + activeAction, + setActiveAction, setWorkflow, setShowPaginationOptions, setShowLimitOptions, setCaptureStage, + startAction, + finishAction, startGetText, stopGetText, startGetList, @@ -123,9 +188,9 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { stopGetScreenshot, startPaginationMode, stopPaginationMode, + updatePaginationType, startLimitMode, stopLimitMode, - updatePaginationType, updateLimitType, updateCustomLimit }}> @@ -140,4 +205,4 @@ export const useActionContext = () => { throw new Error('useActionContext must be used within an ActionProvider'); } return context; -}; +}; \ No newline at end of file From 6376fd69935fa39a64b59ab85feee8ed2d134cdc Mon Sep 17 00:00:00 2001 From: Rohit Date: Tue, 29 Apr 2025 00:16:50 +0530 Subject: [PATCH 08/55] feat: reset interpret log list screenshot --- src/components/recorder/RightSidePanel.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/recorder/RightSidePanel.tsx b/src/components/recorder/RightSidePanel.tsx index 54bcc5064..7a16277d9 100644 --- a/src/components/recorder/RightSidePanel.tsx +++ b/src/components/recorder/RightSidePanel.tsx @@ -337,9 +337,10 @@ export const RightSidePanel: React.FC = ({ onFinishCapture notify('error', t('right_panel.errors.unable_create_settings')); } handleStopGetList(); + resetInterpretationLog(); finishAction('list'); onFinishCapture(); - }, [getListSettingsObject, socket, notify, handleStopGetList, finishAction, onFinishCapture, t]); + }, [getListSettingsObject, socket, notify, handleStopGetList, resetInterpretationLog, finishAction, onFinishCapture, t]); const hasUnconfirmedListTextFields = browserSteps.some(step => step.type === 'list' && Object.values(step.fields).some(field => @@ -459,6 +460,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture socket?.emit('action', { action: 'screenshot', settings: screenshotSettings }); addScreenshotStep(fullPage); stopGetScreenshot(); + resetInterpretationLog(); finishAction('screenshot'); onFinishCapture(); }; From b5c5ed7a61c0eb530930a4b793a95462c375b353 Mon Sep 17 00:00:00 2001 From: Rohit Date: Tue, 29 Apr 2025 00:19:27 +0530 Subject: [PATCH 09/55] feat: check action exists in workflow --- .../action/ActionDescriptionBox.tsx | 103 +++++++++++++++++- 1 file changed, 99 insertions(+), 4 deletions(-) diff --git a/src/components/action/ActionDescriptionBox.tsx b/src/components/action/ActionDescriptionBox.tsx index d36db4079..f3bfcd952 100644 --- a/src/components/action/ActionDescriptionBox.tsx +++ b/src/components/action/ActionDescriptionBox.tsx @@ -49,14 +49,24 @@ const Content = styled.div` text-align: left; `; - const ActionDescriptionBox = ({ isDarkMode }: { isDarkMode: boolean }) => { const { t } = useTranslation(); - const { getText, getScreenshot, getList, captureStage } = useActionContext() as { + const { + getText, + getScreenshot, + getList, + captureStage, + actionsInWorkflow + } = useActionContext() as { getText: boolean; getScreenshot: boolean; getList: boolean; captureStage: 'initial' | 'pagination' | 'limit' | 'complete'; + actionsInWorkflow: { + text: boolean; + list: boolean; + screenshot: boolean; + }; }; const messages = [ @@ -118,10 +128,95 @@ const ActionDescriptionBox = ({ isDarkMode }: { isDarkMode: boolean }) => { ); } else { + const actionsInWorkflowCount = Object.values(actionsInWorkflow).filter(Boolean).length; + return ( <> - {t('action_description.default.title')} - {t('action_description.default.description')} + + {actionsInWorkflowCount === 0 + ? t('action_description.default.title') + : t('action_description.workflow_progress.title')} + + + {actionsInWorkflowCount === 0 ? ( + + {t('action_description.default.description')} + + ) : ( + <> + + {t('action_description.workflow_actions.description')} + + + + {actionsInWorkflow.text && ( + + } + label={ + + {t('action_description.actions.text')} + + } + /> + )} + + {actionsInWorkflow.list && ( + + } + label={ + + {t('action_description.actions.list')} + + } + /> + )} + + {actionsInWorkflow.screenshot && ( + + } + label={ + + {t('action_description.actions.screenshot')} + + } + /> + )} + + + )} ); } From 42b56d7132f60c26b2481f7a10a08e346a1a5442 Mon Sep 17 00:00:00 2001 From: Rohit Date: Tue, 29 Apr 2025 00:22:05 +0530 Subject: [PATCH 10/55] feat: emit recording editor actions by type --- .../classes/Interpreter.ts | 72 ++++++++++++++----- 1 file changed, 54 insertions(+), 18 deletions(-) diff --git a/server/src/workflow-management/classes/Interpreter.ts b/server/src/workflow-management/classes/Interpreter.ts index c9e83f1b8..f3a5698c5 100644 --- a/server/src/workflow-management/classes/Interpreter.ts +++ b/server/src/workflow-management/classes/Interpreter.ts @@ -180,9 +180,9 @@ export class WorkflowInterpreter { ) => { const params = settings.params ? settings.params : null; delete settings.params; - + const processedWorkflow = processWorkflow(workflow, true); - + const options = { ...settings, debugChannel: { @@ -199,23 +199,49 @@ export class WorkflowInterpreter { } }, serializableCallback: (data: any) => { - this.socket.emit('serializableCallback', data); + if (this.currentActionType === 'scrapeSchema') { + if (Array.isArray(data) && data.length > 0) { + this.socket.emit('serializableCallback', { + type: 'captureText', + data + }); + } else { + this.socket.emit('serializableCallback', { + type: 'captureText', + data : [data] + }); + } + } else if (this.currentActionType === 'scrapeList') { + this.socket.emit('serializableCallback', { + type: 'captureList', + data + }); + } else { + this.socket.emit('serializableCallback', { + type: 'other', + data + }); + } }, binaryCallback: (data: string, mimetype: string) => { - this.socket.emit('binaryCallback', { data, mimetype }); + this.socket.emit('binaryCallback', { + data, + mimetype, + type: 'captureScreenshot' + }); } } - + const interpreter = new Interpreter(processedWorkflow, options); this.interpreter = interpreter; - + interpreter.on('flag', async (page, resume) => { if (this.activeId !== null && this.breakpoints[this.activeId]) { logger.log('debug', `breakpoint hit id: ${this.activeId}`); this.socket.emit('breakpointHit'); this.interpretationIsPaused = true; } - + if (this.interpretationIsPaused) { this.interpretationResume = resume; logger.log('debug', `Paused inside of flag: ${page.url()}`); @@ -225,13 +251,13 @@ export class WorkflowInterpreter { resume(); } }); - + this.socket.emit('log', '----- Starting the interpretation -----', false); - + const status = await interpreter.run(page, params); - + this.socket.emit('log', `----- The interpretation finished with status: ${status} -----`, false); - + logger.log('debug', `Interpretation finished`); this.interpreter = null; this.socket.emit('activePairId', -1); @@ -288,6 +314,8 @@ export class WorkflowInterpreter { const processedWorkflow = processWorkflow(workflow); + let mergedScrapeSchema = {}; + const options = { ...settings, debugChannel: { @@ -305,7 +333,13 @@ export class WorkflowInterpreter { }, serializableCallback: (data: any) => { if (this.currentActionType === 'scrapeSchema') { - this.serializableDataByType.scrapeSchema.push(data); + if (Array.isArray(data) && data.length > 0) { + mergedScrapeSchema = { ...mergedScrapeSchema, ...data[0] }; + this.serializableDataByType.scrapeSchema.push(data); + } else { + mergedScrapeSchema = { ...mergedScrapeSchema, ...data }; + this.serializableDataByType.scrapeSchema.push([data]); + } } else if (this.currentActionType === 'scrapeList') { this.serializableDataByType.scrapeList.push(data); } else { @@ -346,12 +380,14 @@ export class WorkflowInterpreter { const result = { log: this.debugMessages, result: status, - scrapeSchemaOutput: this.serializableDataByType.scrapeSchema.reduce((reducedObject, item, index) => { - return { - [`schema-${index}`]: item, - ...reducedObject, - } - }, {}), + scrapeSchemaOutput: Object.keys(mergedScrapeSchema).length > 0 + ? { "schema-merged": [mergedScrapeSchema] } + : this.serializableDataByType.scrapeSchema.reduce((reducedObject, item, index) => { + return { + [`schema-${index}`]: item, + ...reducedObject, + } + }, {}), scrapeListOutput: this.serializableDataByType.scrapeList.reduce((reducedObject, item, index) => { return { [`list-${index}`]: item, From 82d6f709207b60490234ea5d5cf995b2b71775d9 Mon Sep 17 00:00:00 2001 From: Rohit Date: Tue, 29 Apr 2025 00:27:35 +0530 Subject: [PATCH 11/55] feat: revamp output preview log ui --- src/components/run/InterpretationLog.tsx | 468 +++++++++++++++++++---- 1 file changed, 399 insertions(+), 69 deletions(-) diff --git a/src/components/run/InterpretationLog.tsx b/src/components/run/InterpretationLog.tsx index fa749efae..69608fb58 100644 --- a/src/components/run/InterpretationLog.tsx +++ b/src/components/run/InterpretationLog.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import SwipeableDrawer from '@mui/material/SwipeableDrawer'; import Typography from '@mui/material/Typography'; -import { Button, Grid } from '@mui/material'; +import { Button, Grid, Tabs, Tab, Box } from '@mui/material'; import { useCallback, useEffect, useRef, useState } from "react"; import { useSocketStore } from "../../context/socket"; import { Buffer } from 'buffer'; @@ -29,9 +29,18 @@ export const InterpretationLog: React.FC = ({ isOpen, se const { t } = useTranslation(); const [log, setLog] = useState(''); const [customValue, setCustomValue] = useState(''); - const [tableData, setTableData] = useState([]); - const [binaryData, setBinaryData] = useState(null); + + const [captureListData, setCaptureListData] = useState([]); + const [captureTextData, setCaptureTextData] = useState([]); + const [screenshotData, setScreenshotData] = useState([]); + const [otherData, setOtherData] = useState([]); + const [captureListPage, setCaptureListPage] = useState(0); + const [screenshotPage, setScreenshotPage] = useState(0); + const [otherPage, setOtherPage] = useState(0); + + const [activeTab, setActiveTab] = useState(0); + const logEndRef = useRef(null); const { browserWidth, outputPreviewHeight, outputPreviewWidth } = useBrowserDimensionsStore(); @@ -62,34 +71,68 @@ export const InterpretationLog: React.FC = ({ isOpen, se setLog((prevState) => prevState + '\n' + `[${new Date().toLocaleString()}] ` + msg); } scrollLogToBottom(); - }, [log, scrollLogToBottom]); + }, []); - const handleSerializableCallback = useCallback((data: any) => { + const handleSerializableCallback = useCallback(({ type, data }: { type: string, data: any }) => { setLog((prevState) => prevState + '\n' + t('interpretation_log.data_sections.serializable_received') + '\n' + JSON.stringify(data, null, 2) + '\n' + t('interpretation_log.data_sections.separator')); - - if (Array.isArray(data)) { - setTableData(data); + + if (type === 'captureList' && Array.isArray(data)) { + setCaptureListData(prev => [...prev, data]); + if (captureListData.length === 0) { + const availableTabs = getAvailableTabs(); + const tabIndex = availableTabs.findIndex(tab => tab.id === 'captureList'); + if (tabIndex !== -1) setActiveTab(tabIndex); + } + } else if (type === 'captureText') { + if (Array.isArray(data)) { + setCaptureTextData(data); + } else { + setCaptureTextData([data]); + } + if (captureTextData.length === 0) { + const availableTabs = getAvailableTabs(); + const tabIndex = availableTabs.findIndex(tab => tab.id === 'captureText'); + if (tabIndex !== -1) setActiveTab(tabIndex); + } + } else if (type === 'other') { + if (Array.isArray(data)) { + setOtherData(prev => [...prev, data]); + } else { + setOtherData(prev => [...prev, [data]]); + } + if (otherData.length === 0) { + const availableTabs = getAvailableTabs(); + const tabIndex = availableTabs.findIndex(tab => tab.id === 'other'); + if (tabIndex !== -1) setActiveTab(tabIndex); + } } - + scrollLogToBottom(); - }, [log, scrollLogToBottom, t]); - - const handleBinaryCallback = useCallback(({ data, mimetype }: any) => { + }, [captureListData.length, captureTextData.length, otherData.length, t]); + + const handleBinaryCallback = useCallback(({ data, mimetype, type }: { data: any, mimetype: string, type: string }) => { const base64String = Buffer.from(data).toString('base64'); const imageSrc = `data:${mimetype};base64,${base64String}`; - + setLog((prevState) => prevState + '\n' + t('interpretation_log.data_sections.binary_received') + '\n' + t('interpretation_log.data_sections.mimetype') + mimetype + '\n' + t('interpretation_log.data_sections.image_below') + '\n' + t('interpretation_log.data_sections.separator')); - - setBinaryData(imageSrc); + + if (type === 'captureScreenshot') { + setScreenshotData(prev => [...prev, imageSrc]); + if (screenshotData.length === 0) { + const availableTabs = getAvailableTabs(); + const tabIndex = availableTabs.findIndex(tab => tab.id === 'captureScreenshot'); + if (tabIndex !== -1) setActiveTab(tabIndex); + } + } + scrollLogToBottom(); - }, [log, scrollLogToBottom, t]); - + }, [screenshotData.length, t]); const handleCustomValueChange = (event: React.ChangeEvent) => { setCustomValue(event.target.value); @@ -98,8 +141,14 @@ export const InterpretationLog: React.FC = ({ isOpen, se useEffect(() => { if (shouldResetInterpretationLog) { setLog(''); - setTableData([]); - setBinaryData(null); + setCaptureListData([]); + setCaptureTextData([]); + setScreenshotData([]); + setOtherData([]); + setActiveTab(0); + setCaptureListPage(0); + setScreenshotPage(0); + setOtherPage(0); } }, [shouldResetInterpretationLog]); @@ -114,10 +163,37 @@ export const InterpretationLog: React.FC = ({ isOpen, se }; }, [socket, handleLog, handleSerializableCallback, handleBinaryCallback]); - // Extract columns dynamically from the first item of tableData - const columns = tableData.length > 0 ? Object.keys(tableData[0]) : []; + const getAvailableTabs = useCallback(() => { + const tabs = []; + + if (captureListData.length > 0) { + tabs.push({ id: 'captureList', label: 'Lists' }); + } + + if (captureTextData.length > 0) { + tabs.push({ id: 'captureText', label: 'Texts' }); + } + + if (screenshotData.length > 0) { + tabs.push({ id: 'captureScreenshot', label: 'Screenshots' }); + } + + if (otherData.length > 0) { + tabs.push({ id: 'other', label: 'Other' }); + } + + return tabs; + }, [captureListData.length, captureTextData.length, screenshotData.length, otherData.length]); - const { hasScrapeListAction, hasScreenshotAction, hasScrapeSchemaAction } = currentWorkflowActionsState + const availableTabs = getAvailableTabs(); + + useEffect(() => { + if (activeTab >= availableTabs.length && availableTabs.length > 0) { + setActiveTab(0); + } + }, [activeTab, availableTabs.length]); + + const { hasScrapeListAction, hasScreenshotAction, hasScrapeSchemaAction } = currentWorkflowActionsState; useEffect(() => { if (hasScrapeListAction || hasScrapeSchemaAction || hasScreenshotAction) { @@ -127,6 +203,8 @@ export const InterpretationLog: React.FC = ({ isOpen, se const { darkMode } = useThemeMode(); + const getCaptureTextColumns = captureTextData.length > 0 ? Object.keys(captureTextData[0]) : []; + return ( @@ -167,6 +245,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se height: outputPreviewHeight, width: outputPreviewWidth, display: 'flex', + flexDirection: 'column', borderRadius: '10px 10px 0 0', }, }} @@ -175,67 +254,318 @@ export const InterpretationLog: React.FC = ({ isOpen, se {t('interpretation_log.titles.output_preview')} -
- { - binaryData ? ( -
- - {t('interpretation_log.titles.screenshot')} - - {t('interpretation_log.titles.screenshot')} -
- ) : tableData.length > 0 ? ( - <> - - + + {availableTabs.length > 0 ? ( + <> + + {availableTabs.map((tab, index) => ( + setActiveTab(index)} + sx={{ + px: 4, + py: 2, + cursor: 'pointer', + borderBottom: activeTab === index ? '2px solid' : 'none', + borderColor: activeTab === index ? (darkMode ? '#ff00c3' : '#ff00c3') : 'transparent', + backgroundColor: activeTab === index ? (darkMode ? '#34404d' : '#e9ecef') : 'transparent', + color: darkMode ? 'white' : 'black', + fontWeight: activeTab === index ? 500 : 400, + textAlign: 'center', + position: 'relative', + '&:hover': { + backgroundColor: activeTab !== index ? (darkMode ? '#303b49' : '#e2e6ea') : undefined + } + }} + > + + {tab.label} + + + ))} + + + + {activeTab === availableTabs.findIndex(tab => tab.id === 'captureList') && captureListData.length > 0 && ( + + + + {`Table ${captureListPage + 1} of ${captureListData.length}`} + + + + + + + +
+ + + {captureListData[captureListPage] && captureListData[captureListPage].length > 0 && + Object.keys(captureListData[captureListPage][0]).map((column) => ( + + {column} + + )) + } + + + + {captureListData[captureListPage] && + captureListData[captureListPage].map((row: any, idx: any) => ( + + {Object.keys(row).map((column) => ( + + {row[column]} + + ))} + + ))} + +
+
+ + )} + + {activeTab === availableTabs.findIndex(tab => tab.id === 'captureScreenshot') && ( + + {screenshotData.length > 1 && ( + + + {`Screenshot ${screenshotPage + 1} of ${screenshotData.length}`} + + + + + + + )} + {screenshotData.length > 0 && ( + + + {t('interpretation_log.titles.screenshot')} {screenshotPage + 1} + + {`${t('interpretation_log.titles.screenshot')} + + )} + + )} + + {activeTab === availableTabs.findIndex(tab => tab.id === 'captureText') && ( + + - {columns.map((column) => ( - {column} + {getCaptureTextColumns.map((column) => ( + + {column} + ))} - {tableData.slice(0, Math.min(5, tableData.length)).map((row, index) => ( - - {columns.map((column) => ( - {row[column]} + {captureTextData.map((row, idx) => ( + + {getCaptureTextColumns.map((column) => ( + + {row[column]} + ))} ))}
- - {t('interpretation_log.messages.additional_rows')} - - - ) : ( - - - {hasScrapeListAction || hasScrapeSchemaAction || hasScreenshotAction ? ( - <> - - {t('interpretation_log.messages.successful_training')} + )} + + {activeTab === availableTabs.findIndex(tab => tab.id === 'other') && otherData.length > 0 && ( + + {otherData.length > 1 && ( + + + {`Dataset ${otherPage + 1} of ${otherData.length}`} - - - ) : ( - - {t('interpretation_log.messages.no_selection')} - + + + + + )} - - - )} -
-
+ + + + + {otherData[otherPage] && otherData[otherPage].length > 0 && + Object.keys(otherData[otherPage][0]).map((column) => ( + + {column} + + )) + } + + + + {otherData[otherPage] && + otherData[otherPage].map((row: any, idx: any) => ( + + {Object.keys(row).map((column) => ( + + {row[column]} + + ))} + + ))} + +
+
+ + )} + + + ) : ( + + + {hasScrapeListAction || hasScrapeSchemaAction || hasScreenshotAction ? ( + <> + + {t('interpretation_log.messages.successful_training')} + + + + ) : ( + + {t('interpretation_log.messages.no_selection')} + + )} + + + )} +
From a00e69e40af259bbf1ecb548fd2bf9da337e8ec0 Mon Sep 17 00:00:00 2001 From: Rohit Date: Tue, 29 Apr 2025 00:29:52 +0530 Subject: [PATCH 12/55] feat: change scrape schema merge logic --- maxun-core/src/interpret.ts | 54 +++++++++++++------------------------ 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index 4e5e2be9b..5c92a767f 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -432,46 +432,30 @@ export default class Interpreter extends EventEmitter { if (this.options.debugChannel?.setActionType) { this.options.debugChannel.setActionType('scrapeSchema'); } - + await this.ensureScriptsLoaded(page); const scrapeResult = await page.evaluate((schemaObj) => window.scrapeSchema(schemaObj), schema); - const newResults = Array.isArray(scrapeResult) ? scrapeResult : [scrapeResult]; - newResults.forEach((result) => { - Object.entries(result).forEach(([key, value]) => { - const keyExists = this.cumulativeResults.some( - (item) => key in item && item[key] !== undefined - ); - - if (!keyExists) { - this.cumulativeResults.push({ [key]: value }); - } - }); + if (!this.cumulativeResults || !Array.isArray(this.cumulativeResults)) { + this.cumulativeResults = []; + } + + if (this.cumulativeResults.length === 0) { + this.cumulativeResults.push({}); + } + + const mergedResult = this.cumulativeResults[0]; + const resultToProcess = Array.isArray(scrapeResult) ? scrapeResult[0] : scrapeResult; + + Object.entries(resultToProcess).forEach(([key, value]) => { + if (value !== undefined) { + mergedResult[key] = value; + } }); - - const mergedResult: Record[] = [ - Object.fromEntries( - Object.entries( - this.cumulativeResults.reduce((acc, curr) => { - Object.entries(curr).forEach(([key, value]) => { - // If the key doesn't exist or the current value is not undefined, add/update it - if (value !== undefined) { - acc[key] = value; - } - }); - return acc; - }, {}) - ) - ) - ]; - - // Log cumulative results after each action - console.log("CUMULATIVE results:", this.cumulativeResults); - console.log("MERGED results:", mergedResult); - - await this.options.serializableCallback(mergedResult); - // await this.options.serializableCallback(scrapeResult); + + console.log("Updated merged result:", mergedResult); + await this.options.serializableCallback([mergedResult]); }, scrapeList: async (config: { listSelector: string, fields: any, limit?: number, pagination: any }) => { From a7771cfa9b5d31572f038edecf543c86f8554529 Mon Sep 17 00:00:00 2001 From: Rohit Date: Tue, 29 Apr 2025 00:30:50 +0530 Subject: [PATCH 13/55] feat: memoize handle url change --- src/components/browser/BrowserContent.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/browser/BrowserContent.tsx b/src/components/browser/BrowserContent.tsx index a73ed6e72..14b9385ee 100644 --- a/src/components/browser/BrowserContent.tsx +++ b/src/components/browser/BrowserContent.tsx @@ -78,7 +78,7 @@ export const BrowserContent = () => { [socket] ); - const handleUrlChanged = (url: string) => { + const handleUrlChanged = useCallback((url: string) => { const parsedUrl = new URL(url); if (parsedUrl.hostname) { const host = parsedUrl.hostname @@ -100,7 +100,7 @@ export const BrowserContent = () => { ]); } } - }; + }, [tabs, tabIndex]); const tabHasBeenClosedHandler = useCallback( (index: number) => { @@ -132,7 +132,7 @@ export const BrowserContent = () => { .catch((error) => { console.log("Fetching current url failed"); }); - }, [handleUrlChanged]); + }, []); return (
From f975862a806645ce42eeda4d532d4a5848bfc358 Mon Sep 17 00:00:00 2001 From: Rohit Date: Tue, 29 Apr 2025 00:33:04 +0530 Subject: [PATCH 14/55] feat: revamp run content ui --- src/components/run/RunContent.tsx | 1069 ++++++++++++++++++++++++++--- 1 file changed, 965 insertions(+), 104 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 77bce5449..664163aa7 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -1,10 +1,22 @@ -import { Box, Tabs, Typography, Tab, Paper, Button, CircularProgress } from "@mui/material"; +import { Box, Tabs, Typography, Tab, Paper, Button, CircularProgress, Accordion, AccordionSummary, AccordionDetails, Divider, Card, CardHeader, CardContent, Grid, IconButton, Chip, ButtonGroup } from "@mui/material"; import Highlight from "react-highlight"; import * as React from "react"; import { Data } from "./RunsTable"; import { TabPanel, TabContext } from "@mui/lab"; import ArticleIcon from '@mui/icons-material/Article'; import ImageIcon from '@mui/icons-material/Image'; +import ListIcon from '@mui/icons-material/List'; +import SchemaIcon from '@mui/icons-material/Schema'; +import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import CloudDownloadIcon from '@mui/icons-material/CloudDownload'; +import DownloadIcon from '@mui/icons-material/Download'; +import FullscreenIcon from '@mui/icons-material/Fullscreen'; +import ViewModuleIcon from '@mui/icons-material/ViewModule'; +import ViewListIcon from '@mui/icons-material/ViewList'; +import DataObjectIcon from '@mui/icons-material/DataObject'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import { useEffect, useState } from "react"; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; @@ -26,53 +38,685 @@ interface RunContentProps { export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRef, abortRunHandler }: RunContentProps) => { const { t } = useTranslation(); const [tab, setTab] = React.useState('output'); - const [tableData, setTableData] = useState([]); - const [columns, setColumns] = useState([]); + + const [schemaData, setSchemaData] = useState([]); + const [schemaColumns, setSchemaColumns] = useState([]); + + const [listData, setListData] = useState([]); + const [listColumns, setListColumns] = useState([]); + const [currentListIndex, setCurrentListIndex] = useState(0); + + const [otherData, setOtherData] = useState([]); + const [otherColumns, setOtherColumns] = useState([]); + + const [expandedView, setExpandedView] = useState(null); + + const [viewMode, setViewMode] = useState<'horizontal' | 'vertical'>('vertical'); + + const [legacyData, setLegacyData] = useState([]); + const [legacyColumns, setLegacyColumns] = useState([]); + const [isLegacyData, setIsLegacyData] = useState(false); useEffect(() => { setTab(tab); }, [interpretationInProgress]); useEffect(() => { - if (row.serializableOutput && Object.keys(row.serializableOutput).length > 0) { - const firstKey = Object.keys(row.serializableOutput)[0]; - const data = row.serializableOutput[firstKey]; + if (!row.serializableOutput) return; + + if (!row.serializableOutput.scrapeSchema && + !row.serializableOutput.scrapeList && + !row.serializableOutput.other && + Object.keys(row.serializableOutput).length > 0) { + + setIsLegacyData(true); + processLegacyData(row.serializableOutput); + return; + } + + setIsLegacyData(false); + + if (row.serializableOutput.scrapeSchema && Object.keys(row.serializableOutput.scrapeSchema).length > 0) { + processDataCategory(row.serializableOutput.scrapeSchema, setSchemaData, setSchemaColumns); + } + + if (row.serializableOutput.scrapeList) { + processScrapeList(row.serializableOutput.scrapeList); + } + + if (row.serializableOutput.other && Object.keys(row.serializableOutput.other).length > 0) { + processDataCategory(row.serializableOutput.other, setOtherData, setOtherColumns); + } + }, [row.serializableOutput]); + + const processLegacyData = (legacyOutput: Record) => { + let allData: any[] = []; + + Object.keys(legacyOutput).forEach(key => { + const data = legacyOutput[key]; if (Array.isArray(data)) { - // Filter out completely empty rows const filteredData = data.filter(row => Object.values(row).some(value => value !== undefined && value !== "") ); - setTableData(filteredData); - if (filteredData.length > 0) { - setColumns(Object.keys(filteredData[0])); - } + allData = [...allData, ...filteredData]; } + }); + + if (allData.length > 0) { + const allColumns = new Set(); + allData.forEach(item => { + Object.keys(item).forEach(key => allColumns.add(key)); + }); + + setLegacyData(allData); + setLegacyColumns(Array.from(allColumns)); } - }, [row.serializableOutput]); + }; + const processDataCategory = ( + categoryData: Record, + setData: React.Dispatch>, + setColumns: React.Dispatch> + ) => { + let allData: any[] = []; + + Object.keys(categoryData).forEach(key => { + const data = categoryData[key]; + if (Array.isArray(data)) { + const filteredData = data.filter(row => + Object.values(row).some(value => value !== undefined && value !== "") + ); + allData = [...allData, ...filteredData]; + } + }); + + if (allData.length > 0) { + const allColumns = new Set(); + allData.forEach(item => { + Object.keys(item).forEach(key => allColumns.add(key)); + }); + + setData(allData); + setColumns(Array.from(allColumns)); + } + }; + + const processScrapeList = (scrapeListData: any) => { + const tablesList: any[][] = []; + const columnsList: string[][] = []; + + if (Array.isArray(scrapeListData)) { + scrapeListData.forEach(tableData => { + if (Array.isArray(tableData) && tableData.length > 0) { + const filteredData = tableData.filter(row => + Object.values(row).some(value => value !== undefined && value !== "") + ); + + if (filteredData.length > 0) { + tablesList.push(filteredData); + + const tableColumns = new Set(); + filteredData.forEach(item => { + Object.keys(item).forEach(key => tableColumns.add(key)); + }); + + columnsList.push(Array.from(tableColumns)); + } + } + }); + } else if (typeof scrapeListData === 'object') { + Object.keys(scrapeListData).forEach(key => { + const tableData = scrapeListData[key]; + if (Array.isArray(tableData) && tableData.length > 0) { + const filteredData = tableData.filter(row => + Object.values(row).some(value => value !== undefined && value !== "") + ); + + if (filteredData.length > 0) { + tablesList.push(filteredData); + + const tableColumns = new Set(); + filteredData.forEach(item => { + Object.keys(item).forEach(key => tableColumns.add(key)); + }); + + columnsList.push(Array.from(tableColumns)); + } + } + }); + } + + setListData(tablesList); + setListColumns(columnsList); + setCurrentListIndex(0); + }; // Function to convert table data to CSV format const convertToCSV = (data: any[], columns: string[]): string => { const header = columns.join(','); const rows = data.map(row => - columns.map(col => JSON.stringify(row[col], null, 2)).join(',') + columns.map(col => JSON.stringify(row[col] || "", null, 2)).join(',') ); return [header, ...rows].join('\n'); }; - const downloadCSV = () => { - const csvContent = convertToCSV(tableData, columns); + // Function to download a specific dataset as CSV + const downloadCSV = (data: any[], columns: string[], filename: string) => { + const csvContent = convertToCSV(data, columns); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; - link.setAttribute("download", "data.csv"); + link.setAttribute("download", filename); document.body.appendChild(link); link.click(); document.body.removeChild(link); }; + const downloadJSON = (data: any[], filename: string) => { + const jsonContent = JSON.stringify(data, null, 2); + const blob = new Blob([jsonContent], { type: 'application/json;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + + const link = document.createElement("a"); + link.href = url; + link.setAttribute("download", filename); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + setTimeout(() => { + URL.revokeObjectURL(url); + }, 100); + }; + + const downloadAllJSON = () => { + let allData; + + if (isLegacyData) { + allData = { data: legacyData }; + } else { + allData = { + schema: schemaData, + list: listData.flat(), + other: otherData + }; + } + + const blob = new Blob([JSON.stringify(allData, null, 2)], { type: 'application/json;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + + const link = document.createElement("a"); + link.href = url; + link.setAttribute("download", "all_data.json"); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + + const navigateListTable = (direction: 'next' | 'prev') => { + if (direction === 'next' && currentListIndex < listData.length - 1) { + setCurrentListIndex(currentListIndex + 1); + } else if (direction === 'prev' && currentListIndex > 0) { + setCurrentListIndex(currentListIndex - 1); + } + }; + + const renderDataTable = ( + data: any[], + columns: string[], + title: string, + icon: React.ReactNode, + csvFilename: string, + jsonFilename: string, + isPaginatedList: boolean = false + ) => { + if (!isPaginatedList && data.length === 0) return null; + if (isPaginatedList && (listData.length === 0 || currentListIndex >= listData.length)) return null; + + const currentData = isPaginatedList ? listData[currentListIndex] : data; + const currentColumns = isPaginatedList ? listColumns[currentListIndex] : columns; + + if (!currentData || currentData.length === 0) return null; + + return ( + + } + aria-controls={`${title.toLowerCase()}-content`} + id={`${title.toLowerCase()}-header`} + > + + {icon} + + {title} + + {isPaginatedList ? ( + 1 + ? `Table ${currentListIndex + 1} of ${listData.length} (${currentData.length} ${currentData.length === 1 ? 'item' : 'items'})` + : `${currentData.length} ${currentData.length === 1 ? 'item' : 'items'}` + } + size="small" + sx={{ ml: 2, backgroundColor: '#FF00C3', color: 'white' }} + /> + ) : ( + + )} + + + + + + + + + + {isPaginatedList && listData.length > 1 && ( + + + + + )} + + + + + + {(isPaginatedList ? currentColumns : columns).map((column) => ( + {column} + ))} + + + + {(isPaginatedList ? currentData : data).map((row, index) => ( + + {(isPaginatedList ? currentColumns : columns).map((column) => ( + + {row[column] === undefined || row[column] === "" ? "-" : row[column]} + + ))} + + ))} + +
+
+
+
+ ); + }; + + const renderDataCard = ( + data: any[], + columns: string[], + title: string, + icon: React.ReactNode, + dataType: string, + csvFilename: string, + jsonFilename: string, + isPaginatedList: boolean = false + ) => { + if (!isPaginatedList && data.length === 0) return null; + if (isPaginatedList && (listData.length === 0 || currentListIndex >= listData.length)) return null; + + const currentData = isPaginatedList ? listData[currentListIndex] : data; + const currentColumns = isPaginatedList ? listColumns[currentListIndex] : columns; + + if (!currentData || currentData.length === 0) return null; + + const previewData = currentData.slice(0, 1); + const previewColumns = currentColumns.slice(0, 3); + + const showMoreColumns = currentColumns.length > 3; + + return ( + + + { + if (isPaginatedList) { + downloadCSV(currentData, currentColumns, `list_table_${currentListIndex+1}.csv`); + } else { + downloadCSV(data, columns, csvFilename); + } + }} + title={t('run_content.captured_data.download_csv')} + > + + + { + if (isPaginatedList) { + downloadJSON(currentData, `list_table_${currentListIndex+1}.json`); + } else { + downloadJSON(data, jsonFilename); + } + }} + title="Download JSON" + sx={{ mx: 0.5 }} + > + + + { + if (isPaginatedList) { + setExpandedView(`list-${currentListIndex}`); + } else { + setExpandedView(dataType); + } + }} + title={t('run_content.captured_data.view_full')} + > + + + + } + sx={{ pb: 1 }} + /> + + + {isPaginatedList ? ( + 1 + ? `Table ${currentListIndex + 1} of ${listData.length} (${currentData.length} ${currentData.length === 1 ? 'item' : 'items'})` + : `${currentData.length} ${currentData.length === 1 ? 'item' : 'items'}` + } + size="small" + sx={{ backgroundColor: '#FF00C3', color: 'white' }} + /> + ) : ( + + )} + + {isPaginatedList && listData.length > 1 && ( + + + + + )} + + + + + + {previewColumns.map((column) => ( + {column} + ))} + {showMoreColumns && ...} + + + + {previewData.map((row, index) => ( + + {previewColumns.map((column) => ( + + {row[column] === undefined || row[column] === "" ? "-" : row[column]} + + ))} + {showMoreColumns && ...} + + ))} + {currentData.length > 1 && ( + + + + + + )} + +
+
+
+
+ ); + }; + + const renderExpandedView = (dataTypeWithIndex: string) => { + if (expandedView !== dataTypeWithIndex) return null; + + let data: any[] = []; + let columns: string[] = []; + let title = ""; + let csvFilename = ""; + let jsonFilename = ""; + + if (dataTypeWithIndex.startsWith('list-')) { + const indexStr = dataTypeWithIndex.split('-')[1]; + const index = parseInt(indexStr, 10); + + if (index >= 0 && index < listData.length) { + data = listData[index]; + columns = listColumns[index]; + title = `${t('run_content.captured_data.list_title')} - Table ${index+1}`; + csvFilename = `list_table_${index+1}.csv`; + jsonFilename = `list_table_${index+1}.json`; + } + } else { + switch (dataTypeWithIndex) { + case 'schema': + data = schemaData; + columns = schemaColumns; + title = t('run_content.captured_data.schema_title'); + csvFilename = 'schema_data.csv'; + jsonFilename = 'schema_data.json'; + break; + case 'list': + if (listData.length > 0 && listColumns.length > 0) { + data = listData[currentListIndex]; + columns = listColumns[currentListIndex]; + } + title = t('run_content.captured_data.list_title'); + csvFilename = 'list_data.csv'; + jsonFilename = 'list_data.json'; + break; + case 'other': + data = otherData; + columns = otherColumns; + title = t('run_content.captured_data.other_title'); + csvFilename = 'other_data.csv'; + jsonFilename = 'other_data.json'; + break; + case 'legacy': + data = legacyData; + columns = legacyColumns; + title = t('run_content.captured_data.title'); + csvFilename = 'data.csv'; + jsonFilename = 'data.json'; + break; + } + } + + return ( + + + + {title} + + + + + + + + + + + + + + {columns.map((column) => ( + {column} + ))} + + + + {data.map((row, index) => ( + + {columns.map((column) => ( + + {row[column] === undefined || row[column] === "" ? "-" : row[column]} + + ))} + + ))} + +
+
+
+
+ ); + }; + + const hasData = schemaData.length > 0 || listData.length > 0 || otherData.length > 0 || legacyData.length > 0; + const hasScreenshots = row.binaryOutput && Object.keys(row.binaryOutput).length > 0; + return ( @@ -82,11 +726,9 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe onChange={(e, newTab) => setTab(newTab)} aria-label="run-content-tabs" sx={{ - // Remove the default blue indicator '& .MuiTabs-indicator': { - backgroundColor: '#FF00C3', // Change to pink + backgroundColor: '#FF00C3', }, - // Remove default transition effects '& .MuiTab-root': { '&.Mui-selected': { color: '#FF00C3', @@ -147,103 +789,322 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe {t('run_content.buttons.stop')} : null} - + {row.status === 'running' || row.status === 'queued' ? ( {t('run_content.loading')} - ) : (!row || !row.serializableOutput || !row.binaryOutput - || (Object.keys(row.serializableOutput).length === 0 && Object.keys(row.binaryOutput).length === 0) + ) : (!hasData && !hasScreenshots ? {t('run_content.empty_output')} : null)} - {row.serializableOutput && - Object.keys(row.serializableOutput).length !== 0 && -
- - - {t('run_content.captured_data.title')} - - - - - {t('run_content.captured_data.download_json')} - + {hasData && ( + + + + + {t('run_content.captured_data.title')} - - {t('run_content.captured_data.download_csv')} - - - {tableData.length > 0 ? ( - - - - - {columns.map((column) => ( - {column} - ))} - - - - {tableData.map((row, index) => ( - - {columns.map((column) => ( - - {row[column] === undefined || row[column] === "" ? "-" : row[column]} - - ))} - - ))} - -
-
- ) : ( - -
-                    {JSON.stringify(row.serializableOutput, null, 2)}
-                  
+ + setViewMode('horizontal')} + color={viewMode === 'horizontal' ? 'primary' : 'default'} + sx={{ color: viewMode === 'horizontal' ? '#FF00C3' : 'inherit' }} + > + + + setViewMode('vertical')} + color={viewMode === 'vertical' ? 'primary' : 'default'} + sx={{ color: viewMode === 'vertical' ? '#FF00C3' : 'inherit' }} + > + + + - )} -
- } - {row.binaryOutput && Object.keys(row.binaryOutput).length !== 0 && -
- - - {t('run_content.captured_screenshot.title')} - - {Object.keys(row.binaryOutput).map((key) => { - try { - const imageUrl = row.binaryOutput[key]; - return ( - - - {t('run_content.captured_screenshot.download')} - - {key} - + + + {isLegacyData && ( + viewMode === 'vertical' ? ( + renderDataTable( + legacyData, + legacyColumns, + t('run_content.captured_data.title'), + , + 'data.csv', + 'data.json' ) - } catch (e) { - console.log(e) - return - {key}: {t('run_content.captured_screenshot.render_failed')} + ) : ( + + + {renderDataCard( + legacyData, + legacyColumns, + t('run_content.captured_data.title'), + , + 'legacy', + 'data.csv', + 'data.json' + )} + + + ) + )} + + {!isLegacyData && ( + viewMode === 'vertical' ? ( + <> + {renderDataTable( + schemaData, + schemaColumns, + t('run_content.captured_data.schema_title'), + , + 'schema_data.csv', + 'schema_data.json' + )} + + {listData.length > 0 && renderDataTable( + [], + [], + t('run_content.captured_data.list_title'), + , + 'list_data.csv', + 'list_data.json', + true + )} + + {renderDataTable( + otherData, + otherColumns, + t('run_content.captured_data.other_title'), + , + 'other_data.csv', + 'other_data.json' + )} + + ) : ( + + {(() => { + const dataCategoriesCount = [ + schemaData.length > 0, + listData.length > 0, + otherData.length > 0 + ].filter(Boolean).length; + + const columnWidth = dataCategoriesCount === 1 ? 12 : dataCategoriesCount === 2 ? 6 : 4; + + return ( + <> + {schemaData.length > 0 && ( + + {renderDataCard( + schemaData, + schemaColumns, + t('run_content.captured_data.schema_title'), + , + 'schema', + 'schema_data.csv', + 'schema_data.json' + )} + + )} + + {listData.length > 0 && ( + + {renderDataCard( + [], + [], + t('run_content.captured_data.list_title'), + , + 'list', + 'list_data.csv', + 'list_data.json', + true + )} + + )} + + {otherData.length > 0 && ( + + {renderDataCard( + otherData, + otherColumns, + t('run_content.captured_data.other_title'), + , + 'other', + 'other_data.csv', + 'other_data.json' + )} + + )} + + ); + })()} + + ) + )} + + {renderExpandedView('schema')} + {renderExpandedView('other')} + {renderExpandedView('legacy')} + + {listData.map((_, index) => renderExpandedView(`list-${index}`))} + + )} + + {hasScreenshots && ( + <> + + + + + {t('run_content.captured_screenshot.title')} + - } - })} -
- } + + setViewMode('horizontal')} + color={viewMode === 'horizontal' ? 'primary' : 'default'} + sx={{ color: viewMode === 'horizontal' ? '#FF00C3' : 'inherit' }} + > + + + setViewMode('vertical')} + color={viewMode === 'vertical' ? 'primary' : 'default'} + sx={{ color: viewMode === 'vertical' ? '#FF00C3' : 'inherit' }} + > + + + +
+ + + {viewMode === 'vertical' ? ( + <> + {Object.keys(row.binaryOutput).map((key, index) => { + try { + const imageUrl = row.binaryOutput[key]; + return ( + + } + aria-controls={`screenshot-${key}-content`} + id={`screenshot-${key}-header`} + > + + + + Screenshot {index+1} + + + + + + + + + + + {`Screenshot + + + + ); + } catch (e) { + console.log(e); + return ( + + {key}: {t('run_content.captured_screenshot.render_failed')} + + ); + } + })} + + ) : ( + + {Object.keys(row.binaryOutput).map((key) => { + try { + const imageUrl = row.binaryOutput[key]; + return ( + + + } + title={`Screenshot ${key}`} + action={ + + + + } + /> + + + {`Screenshot + + + + + ); + } catch (e) { + console.log(e); + return ( + + + {key}: {t('run_content.captured_screenshot.render_failed')} + + + ); + } + })} + + )} + + )} From 8d06146f6dba9f09e10026183ebed6f411ad6e5a Mon Sep 17 00:00:00 2001 From: Rohit Date: Tue, 29 Apr 2025 00:37:43 +0530 Subject: [PATCH 15/55] feat: add translations for run content --- public/locales/de.json | 23 ++++++++++++++--------- public/locales/en.json | 36 +++++++++++++++++++++++++++--------- public/locales/es.json | 23 ++++++++++++++--------- public/locales/ja.json | 21 +++++++++++++-------- public/locales/zh.json | 25 +++++++++++++++---------- 5 files changed, 83 insertions(+), 45 deletions(-) diff --git a/public/locales/de.json b/public/locales/de.json index 2d48b16db..6e5612205 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -535,20 +535,25 @@ "output_data": "Ausgabedaten", "log": "Protokoll" }, - "empty_output": "Die Ausgabe ist leer.", - "loading": "Ausführung läuft. Extrahierte Daten werden nach Abschluss des Durchlaufs hier angezeigt.", + "buttons": { + "stop": "Stoppen" + }, + "loading": "Daten werden geladen...", + "empty_output": "Keine Ausgabedaten verfügbar", "captured_data": { "title": "Erfasste Daten", - "download_json": "Als JSON herunterladen", - "download_csv": "Als CSV herunterladen" + "download_csv": "CSV herunterladen", + "download_all_json": "Gesamte JSON herunterladen", + "view_full": "Vollständige Daten anzeigen", + "items": "Elemente", + "schema_title": "Text erfassen", + "list_title": "Liste erfassen", + "other_title": "Andere Daten" }, "captured_screenshot": { - "title": "Erfasster Screenshot", + "title": "Erfasste Screenshots", "download": "Screenshot herunterladen", - "render_failed": "Das Bild konnte nicht gerendert werden" - }, - "buttons": { - "stop": "Stoppen" + "render_failed": "Screenshot konnte nicht gerendert werden" } }, "navbar": { diff --git a/public/locales/en.json b/public/locales/en.json index 6f7fe38d8..b8d910a16 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -177,6 +177,19 @@ "pagination": "Select how the robot can capture the rest of the list", "limit": "Choose the number of items to extract", "complete": "Capture is complete" + }, + "workflow_progress": { + "title": "Workflow Progress", + "description": "You have completed {{completed}} out of {{total}} possible actions", + "completed": "All actions completed!" + }, + "workflow_actions": { + "description": "The following actions have been added to your workflow:" + }, + "actions": { + "text": "Capture Text", + "list": "Capture List", + "screenshot": "Capture Screenshot" } }, "right_panel": { @@ -543,20 +556,25 @@ "output_data": "Output Data", "log": "Log" }, - "empty_output": "The output is empty.", - "loading": "Run in progress. Extracted data will appear here once run completes.", + "buttons": { + "stop": "Stop" + }, + "loading": "Loading data...", + "empty_output": "No output data available", "captured_data": { "title": "Captured Data", - "download_json": "Download as JSON", - "download_csv": "Download as CSV" + "download_csv": "Download CSV", + "download_all_json": "Download All JSON", + "view_full": "View Full Data", + "items": "items", + "schema_title": "Capture Text", + "list_title": "Capture List", + "other_title": "Other Data" }, "captured_screenshot": { - "title": "Captured Screenshot", + "title": "Captured Screenshots", "download": "Download Screenshot", - "render_failed": "The image failed to render" - }, - "buttons": { - "stop": "Stop" + "render_failed": "Failed to render screenshot" } }, "navbar": { diff --git a/public/locales/es.json b/public/locales/es.json index edc762a46..f0e2a9df0 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -536,20 +536,25 @@ "output_data": "Datos de Salida", "log": "Registro" }, - "empty_output": "La salida está vacía.", - "loading": "Ejecución en curso. Los datos extraídos aparecerán aquí una vez que se complete la ejecución.", + "buttons": { + "stop": "Detener" + }, + "loading": "Cargando datos...", + "empty_output": "No hay datos de salida disponibles", "captured_data": { "title": "Datos Capturados", - "download_json": "Descargar como JSON", - "download_csv": "Descargar como CSV" + "download_csv": "Descargar CSV", + "download_all_json": "Descargar Todo JSON", + "view_full": "Ver Datos Completos", + "items": "elementos", + "schema_title": "Capturar Texto", + "list_title": "Capturar Lista", + "other_title": "Otros Datos" }, "captured_screenshot": { - "title": "Captura de Pantalla", + "title": "Capturas de Pantalla", "download": "Descargar Captura", - "render_failed": "No se pudo renderizar la imagen" - }, - "buttons": { - "stop": "Detener" + "render_failed": "Error al renderizar la captura" } }, "navbar": { diff --git a/public/locales/ja.json b/public/locales/ja.json index 15d8c7e02..91021e32a 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -536,20 +536,25 @@ "output_data": "出力データ", "log": "ログ" }, - "empty_output": "出力は空です。", - "loading": "実行中です。実行が完了すると、抽出されたデータがここに表示されます。", + "buttons": { + "stop": "停止" + }, + "loading": "データを読み込み中...", + "empty_output": "出力データがありません", "captured_data": { "title": "キャプチャされたデータ", - "download_json": "JSONとしてダウンロード", - "download_csv": "CSVとしてダウンロード" + "download_csv": "CSVをダウンロード", + "download_all_json": "すべてのJSONをダウンロード", + "view_full": "すべてのデータを表示", + "items": "アイテム", + "schema_title": "テキストをキャプチャ", + "list_title": "リストをキャプチャ", + "other_title": "その他のデータ" }, "captured_screenshot": { "title": "キャプチャされたスクリーンショット", "download": "スクリーンショットをダウンロード", - "render_failed": "画像のレンダリングに失敗しました" - }, - "buttons": { - "stop": "停止" + "render_failed": "スクリーンショットのレンダリングに失敗しました" } }, "navbar": { diff --git a/public/locales/zh.json b/public/locales/zh.json index faaf04961..0d3277436 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -536,20 +536,25 @@ "output_data": "输出数据", "log": "日志" }, - "empty_output": "输出为空。", - "loading": "运行中。运行完成后,提取的数据将显示在此处。", + "buttons": { + "stop": "停止" + }, + "loading": "加载数据中...", + "empty_output": "没有可用的输出数据", "captured_data": { - "title": "捕获的数据", - "download_json": "下载为JSON", - "download_csv": "下载为CSV" + "title": "已捕获的数据", + "download_csv": "下载CSV", + "download_all_json": "下载所有JSON", + "view_full": "查看完整数据", + "items": "项目", + "schema_title": "捕获文本", + "list_title": "捕获列表", + "other_title": "其他数据" }, "captured_screenshot": { - "title": "捕获的截图", + "title": "已捕获的截图", "download": "下载截图", - "render_failed": "图像渲染失败" - }, - "buttons": { - "stop": "停止" + "render_failed": "渲染截图失败" } }, "navbar": { From 4ed3160ac0caa599e8448fec3feda0d530fe23c7 Mon Sep 17 00:00:00 2001 From: Rohit Date: Tue, 29 Apr 2025 00:38:53 +0530 Subject: [PATCH 16/55] feat: emit socket events for stopping --- src/context/browserActions.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/context/browserActions.tsx b/src/context/browserActions.tsx index c332b8c8c..c993dbcea 100644 --- a/src/context/browserActions.tsx +++ b/src/context/browserActions.tsx @@ -107,6 +107,7 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { setLimitType(''); setCustomLimit(''); setCaptureStage('complete'); + socket?.emit('setGetList', { getList: false }); } else if (action === 'screenshot') { setGetScreenshot(false); } @@ -123,7 +124,10 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { socket?.emit('setPaginationMode', { pagination: true }); }; - const stopPaginationMode = () => setPaginationMode(false); + const stopPaginationMode = () => { + setPaginationMode(false), + socket?.emit('setPaginationMode', { pagination: false }); + }; const startLimitMode = () => { setLimitMode(true); From 2ffbdc7d0ad5331da3a93b8673278612f3825ceb Mon Sep 17 00:00:00 2001 From: Rohit Date: Tue, 29 Apr 2025 20:12:11 +0530 Subject: [PATCH 17/55] feat: revamp gsheet integration multiple actions --- .../integrations/gsheet.ts | 213 ++++++++++++++---- 1 file changed, 172 insertions(+), 41 deletions(-) diff --git a/server/src/workflow-management/integrations/gsheet.ts b/server/src/workflow-management/integrations/gsheet.ts index b492a7df9..3fbe1229f 100644 --- a/server/src/workflow-management/integrations/gsheet.ts +++ b/server/src/workflow-management/integrations/gsheet.ts @@ -10,6 +10,12 @@ interface GoogleSheetUpdateTask { retries: number; } +interface SerializableOutput { + scrapeSchema?: any[]; + scrapeList?: any[]; + other?: any[]; +} + const MAX_RETRIES = 5; export let googleSheetUpdateTasks: { [runId: string]: GoogleSheetUpdateTask } = {}; @@ -25,18 +31,6 @@ export async function updateGoogleSheet(robotId: string, runId: string) { const plainRun = run.toJSON(); if (plainRun.status === 'success') { - let data: { [key: string]: any }[] = []; - if (plainRun.serializableOutput && Object.keys(plainRun.serializableOutput).length > 0) { - data = plainRun.serializableOutput['item-0'] as { [key: string]: any }[]; - - } else if (plainRun.binaryOutput && plainRun.binaryOutput['item-0']) { - // Handle binaryOutput by setting the URL as a data entry - const binaryUrl = plainRun.binaryOutput['item-0'] as string; - - // Create a placeholder object with the binary URL - data = [{ "Screenshot URL": binaryUrl }]; - } - const robot = await Robot.findOne({ where: { 'recording_meta.id': robotId } }); if (!robot) { @@ -44,35 +38,169 @@ export async function updateGoogleSheet(robotId: string, runId: string) { } const plainRobot = robot.toJSON(); - const spreadsheetId = plainRobot.google_sheet_id; - if (plainRobot.google_sheet_email && spreadsheetId) { - console.log(`Preparing to write data to Google Sheet for robot: ${robotId}, spreadsheetId: ${spreadsheetId}`); - - await writeDataToSheet(robotId, spreadsheetId, data); - console.log(`Data written to Google Sheet successfully for Robot: ${robotId} and Run: ${runId}`); - } else { + + if (!plainRobot.google_sheet_email || !spreadsheetId) { console.log('Google Sheets integration not configured.'); + return; + } + + console.log(`Preparing to write data to Google Sheet for robot: ${robotId}, spreadsheetId: ${spreadsheetId}`); + + const serializableOutput = plainRun.serializableOutput as SerializableOutput; + + if (serializableOutput) { + if (serializableOutput.scrapeSchema && serializableOutput.scrapeSchema.length > 0) { + await processOutputType( + robotId, + spreadsheetId, + 'Text', + serializableOutput.scrapeSchema, + plainRobot + ); + } + + if (serializableOutput.scrapeList && serializableOutput.scrapeList.length > 0) { + await processOutputType( + robotId, + spreadsheetId, + 'List', + serializableOutput.scrapeList, + plainRobot + ); + } + + if (serializableOutput.other && serializableOutput.other.length > 0) { + await processOutputType( + robotId, + spreadsheetId, + 'Other', + serializableOutput.other, + plainRobot + ); + } } + + if (plainRun.binaryOutput && Object.keys(plainRun.binaryOutput).length > 0) { + const screenshots = Object.entries(plainRun.binaryOutput).map(([key, url]) => ({ + "Screenshot Key": key, + "Screenshot URL": url + })); + + await processOutputType( + robotId, + spreadsheetId, + 'Screenshot', + [screenshots], + plainRobot + ); + } + + console.log(`Data written to Google Sheet successfully for Robot: ${robotId} and Run: ${runId}`); } else { console.log('Run status is not success or serializableOutput is missing.'); } } catch (error: any) { console.error(`Failed to write data to Google Sheet for Robot: ${robotId} and Run: ${runId}: ${error.message}`); + throw error; } -}; +} -export async function writeDataToSheet(robotId: string, spreadsheetId: string, data: any[]) { +async function processOutputType( + robotId: string, + spreadsheetId: string, + outputType: string, + outputData: any[], + robotConfig: any +) { + for (let i = 0; i < outputData.length; i++) { + const data = outputData[i]; + + if (!data || data.length === 0) { + console.log(`No data to write for ${outputType}-${i}. Skipping.`); + continue; + } + + const sheetName = `${outputType}-${i}`; + + await ensureSheetExists(spreadsheetId, sheetName, robotConfig); + + await writeDataToSheet(robotId, spreadsheetId, data, sheetName, robotConfig); + console.log(`Data written to ${sheetName} sheet for ${outputType} data`); + } +} + +async function ensureSheetExists(spreadsheetId: string, sheetName: string, robotConfig: any) { try { - const robot = await Robot.findOne({ where: { 'recording_meta.id': robotId } }); + const oauth2Client = getOAuth2Client(robotConfig); + const sheets = google.sheets({ version: 'v4', auth: oauth2Client }); + + const response = await sheets.spreadsheets.get({ + spreadsheetId, + fields: 'sheets.properties.title' + }); + + const existingSheets = response.data.sheets?.map(sheet => sheet.properties?.title) || []; + + if (!existingSheets.includes(sheetName)) { + await sheets.spreadsheets.batchUpdate({ + spreadsheetId, + requestBody: { + requests: [ + { + addSheet: { + properties: { + title: sheetName + } + } + } + ] + } + }); + console.log(`Created new sheet: ${sheetName}`); + } + } catch (error: any) { + logger.log('error', `Error ensuring sheet exists: ${error.message}`); + throw error; + } +} + +function getOAuth2Client(robotConfig: any) { + const oauth2Client = new google.auth.OAuth2( + process.env.GOOGLE_CLIENT_ID, + process.env.GOOGLE_CLIENT_SECRET, + process.env.GOOGLE_REDIRECT_URI + ); + + oauth2Client.setCredentials({ + access_token: robotConfig.google_access_token, + refresh_token: robotConfig.google_refresh_token, + }); + return oauth2Client; +} + +export async function writeDataToSheet( + robotId: string, + spreadsheetId: string, + data: any[], + sheetName: string = 'Sheet1', + robotConfig?: any +) { + try { + let robot = robotConfig; + if (!robot) { - throw new Error(`Robot not found for robotId: ${robotId}`); - } + robot = await Robot.findOne({ where: { 'recording_meta.id': robotId } }); - const plainRobot = robot.toJSON(); + if (!robot) { + throw new Error(`Robot not found for robotId: ${robotId}`); + } + + robot = robot.toJSON(); + } - if (!plainRobot.google_access_token || !plainRobot.google_refresh_token) { + if (!robot.google_access_token || !robot.google_refresh_token) { throw new Error('Google Sheets access not configured for user'); } @@ -83,16 +211,19 @@ export async function writeDataToSheet(robotId: string, spreadsheetId: string, d ); oauth2Client.setCredentials({ - access_token: plainRobot.google_access_token, - refresh_token: plainRobot.google_refresh_token, + access_token: robot.google_access_token, + refresh_token: robot.google_refresh_token, }); oauth2Client.on('tokens', async (tokens) => { - if (tokens.refresh_token) { - await robot.update({ google_refresh_token: tokens.refresh_token }); - } - if (tokens.access_token) { - await robot.update({ google_access_token: tokens.access_token }); + if (tokens.refresh_token || tokens.access_token) { + const robotModel = await Robot.findOne({ where: { 'recording_meta.id': robotId } }); + if (robotModel) { + const updateData: any = {}; + if (tokens.refresh_token) updateData.google_refresh_token = tokens.refresh_token; + if (tokens.access_token) updateData.google_access_token = tokens.access_token; + await robotModel.update(updateData); + } } }); @@ -100,7 +231,7 @@ export async function writeDataToSheet(robotId: string, spreadsheetId: string, d const checkResponse = await sheets.spreadsheets.values.get({ spreadsheetId, - range: 'Sheet1!1:1', + range: `${sheetName}!1:1`, }); if (!data || data.length === 0) { @@ -109,7 +240,6 @@ export async function writeDataToSheet(robotId: string, spreadsheetId: string, d } const expectedHeaders = Object.keys(data[0]); - const rows = data.map(item => Object.values(item)); const existingHeaders = @@ -129,28 +259,28 @@ export async function writeDataToSheet(robotId: string, spreadsheetId: string, d if (isSheetEmpty || !headersMatch) { resource = { values: [expectedHeaders, ...rows] }; - console.log('Including headers in the append operation.'); + console.log(`Including headers in the append operation for sheet ${sheetName}.`); } else { resource = { values: rows }; - console.log('Headers already exist and match, only appending data rows.'); + console.log(`Headers already exist and match in sheet ${sheetName}, only appending data rows.`); } - console.log('Attempting to write to spreadsheet:', spreadsheetId); + console.log(`Attempting to write to spreadsheet: ${spreadsheetId}, sheet: ${sheetName}`); const response = await sheets.spreadsheets.values.append({ spreadsheetId, - range: 'Sheet1!A1', + range: `${sheetName}!A1`, valueInputOption: 'USER_ENTERED', requestBody: resource, }); if (response.status === 200) { - console.log('Data successfully appended to Google Sheet.'); + console.log(`Data successfully appended to sheet: ${sheetName}`); } else { console.error('Google Sheets append failed:', response); } - logger.log(`info`, `Data written to Google Sheet: ${spreadsheetId}`); + logger.log(`info`, `Data written to Google Sheet: ${spreadsheetId}, sheet: ${sheetName}`); } catch (error: any) { logger.log(`error`, `Error writing data to Google Sheet: ${error.message}`); throw error; @@ -169,6 +299,7 @@ export const processGoogleSheetUpdates = async () => { try { await updateGoogleSheet(task.robotId, task.runId); console.log(`Successfully updated Google Sheet for runId: ${runId}`); + googleSheetUpdateTasks[runId].status = 'completed'; delete googleSheetUpdateTasks[runId]; } catch (error: any) { console.error(`Failed to update Google Sheets for run ${task.runId}:`, error); From 109afffb4bef6e35827c99da44addae4703b984b Mon Sep 17 00:00:00 2001 From: Rohit Date: Tue, 29 Apr 2025 20:15:48 +0530 Subject: [PATCH 18/55] feat: revamp airtable integration multiple actions --- .../integrations/airtable.ts | 321 +++++++++++++++--- 1 file changed, 277 insertions(+), 44 deletions(-) diff --git a/server/src/workflow-management/integrations/airtable.ts b/server/src/workflow-management/integrations/airtable.ts index 934747211..a78ec72d0 100644 --- a/server/src/workflow-management/integrations/airtable.ts +++ b/server/src/workflow-management/integrations/airtable.ts @@ -11,6 +11,12 @@ interface AirtableUpdateTask { retries: number; } +interface SerializableOutput { + scrapeSchema?: any[]; + scrapeList?: any[]; + other?: any[]; +} + const MAX_RETRIES = 3; const BASE_API_DELAY = 2000; @@ -39,38 +45,117 @@ async function refreshAirtableToken(refreshToken: string) { } } +function mergeRelatedData(serializableOutput: SerializableOutput, binaryOutput: Record) { + const mergedRecords: Record[] = []; + + const maxLength = Math.max( + ...(serializableOutput.scrapeSchema?.map(array => array?.length || 0) || [0]), + ...(serializableOutput.scrapeList?.map(array => array?.length || 0) || [0]), + ...(serializableOutput.other?.map(array => array?.length || 0) || [0]) + ); + + for (let i = 0; i < maxLength; i++) { + mergedRecords.push({}); + } + + if (serializableOutput.scrapeSchema) { + for (const schemaArray of serializableOutput.scrapeSchema) { + if (!Array.isArray(schemaArray)) continue; + + for (let i = 0; i < schemaArray.length; i++) { + if (i >= mergedRecords.length) break; + mergedRecords[i] = { ...mergedRecords[i], ...schemaArray[i] }; + } + } + } + + if (serializableOutput.scrapeList) { + for (const listArray of serializableOutput.scrapeList) { + if (!Array.isArray(listArray)) continue; + + for (let i = 0; i < listArray.length; i++) { + if (i >= mergedRecords.length) break; + mergedRecords[i] = { ...mergedRecords[i], ...listArray[i] }; + } + } + } + + if (serializableOutput.other) { + for (const otherArray of serializableOutput.other) { + if (!Array.isArray(otherArray)) continue; + + for (let i = 0; i < otherArray.length; i++) { + if (i >= mergedRecords.length) break; + mergedRecords[i] = { ...mergedRecords[i], ...otherArray[i] }; + } + } + } + + if (binaryOutput && Object.keys(binaryOutput).length > 0) { + for (let i = 0; i < mergedRecords.length; i++) { + const screenshotKey = `item-${i}`; + if (binaryOutput[screenshotKey]) { + mergedRecords[i].Screenshot = binaryOutput[screenshotKey]; + mergedRecords[i].Key = screenshotKey; + } + } + + for (const [key, url] of Object.entries(binaryOutput)) { + if (mergedRecords.some(record => record.Key === key)) { + continue; + } + + mergedRecords.push({ + "Key": key, + "Screenshot": url + }); + } + } + + return mergedRecords; +} + export async function updateAirtable(robotId: string, runId: string) { try { + console.log(`Starting Airtable update for run: ${runId}, robot: ${robotId}`); + const run = await Run.findOne({ where: { runId } }); if (!run) throw new Error(`Run not found for runId: ${runId}`); const plainRun = run.toJSON(); if (plainRun.status !== 'success') { - console.log('Run status is not success'); + console.log('Run status is not success, skipping Airtable update'); return; } - let data: { [key: string]: any }[] = []; - if (plainRun.serializableOutput?.['item-0']) { - data = plainRun.serializableOutput['item-0'] as { [key: string]: any }[]; - } else if (plainRun.binaryOutput?.['item-0']) { - data = [{ "File URL": plainRun.binaryOutput['item-0'] }]; - } - const robot = await Robot.findOne({ where: { 'recording_meta.id': robotId } }); if (!robot) throw new Error(`Robot not found for robotId: ${robotId}`); const plainRobot = robot.toJSON(); - if (plainRobot.airtable_base_id && plainRobot.airtable_table_name && plainRobot.airtable_table_id) { - console.log(`Writing to Airtable base ${plainRobot.airtable_base_id}`); + + if (!plainRobot.airtable_base_id || !plainRobot.airtable_table_name || !plainRobot.airtable_table_id) { + console.log('Airtable integration not configured'); + return; + } + + console.log(`Airtable configuration found - Base: ${plainRobot.airtable_base_id}, Table: ${plainRobot.airtable_table_name}`); + + const serializableOutput = plainRun.serializableOutput as SerializableOutput; + const binaryOutput = plainRun.binaryOutput || {}; + + const mergedData = mergeRelatedData(serializableOutput, binaryOutput); + + if (mergedData.length > 0) { await writeDataToAirtable( robotId, plainRobot.airtable_base_id, plainRobot.airtable_table_name, plainRobot.airtable_table_id, - data + mergedData ); - console.log(`Data written to Airtable for ${robotId}`); + console.log(`All data written to Airtable for ${robotId}`); + } else { + console.log(`No data to write to Airtable for ${robotId}`); } } catch (error: any) { console.error(`Airtable update failed: ${error.message}`); @@ -125,42 +210,142 @@ export async function writeDataToAirtable( tableId: string, data: any[] ) { + if (!data || data.length === 0) { + console.log('No data to write. Skipping.'); + return; + } + try { return await withTokenRefresh(robotId, async (accessToken: string) => { const airtable = new Airtable({ apiKey: accessToken }); const base = airtable.base(baseId); + const processedData = data.map(item => { + const cleanedItem: Record = {}; + + for (const [key, value] of Object.entries(item)) { + if (value === null || value === undefined) { + cleanedItem[key] = ''; + } else if (typeof value === 'object' && !Array.isArray(value)) { + cleanedItem[key] = JSON.stringify(value); + } else { + cleanedItem[key] = value; + } + } + + return cleanedItem; + }); + const existingFields = await getExistingFields(base, tableName); - console.log(`Found ${existingFields.length} existing fields in Airtable`); + console.log(`Found ${existingFields.length} existing fields in Airtable: ${existingFields.join(', ')}`); - const dataFields = [...new Set(data.flatMap(row => Object.keys(row)))]; + const dataFields = [...new Set(processedData.flatMap(row => Object.keys(row)))]; console.log(`Found ${dataFields.length} fields in data: ${dataFields.join(', ')}`); const missingFields = dataFields.filter(field => !existingFields.includes(field)); - console.log(`Found ${missingFields.length} missing fields: ${missingFields.join(', ')}`); + const hasNewColumns = missingFields.length > 0; + console.log(`Found ${missingFields.length} new fields: ${missingFields.join(', ')}`); for (const field of missingFields) { - const sampleRow = data.find(row => field in row); + const sampleRow = processedData.find(row => field in row); if (sampleRow) { const sampleValue = sampleRow[field]; try { await createAirtableField(baseId, tableName, field, sampleValue, accessToken, tableId); console.log(`Successfully created field: ${field}`); + + await new Promise(resolve => setTimeout(resolve, 200)); } catch (fieldError: any) { console.warn(`Warning: Could not create field "${field}": ${fieldError.message}`); } } } - - await deleteEmptyRecords(base, tableName); - const BATCH_SIZE = 10; - for (let i = 0; i < data.length; i += BATCH_SIZE) { - const batch = data.slice(i, i + BATCH_SIZE); - await retryableAirtableWrite(base, tableName, batch); + let existingRecords: Array<{ id: string, fields: Record }> = []; + + if (hasNewColumns) { + existingRecords = await fetchAllRecords(base, tableName); + console.log(`Found ${existingRecords.length} existing records in Airtable`); + } + + if (hasNewColumns && existingRecords.length > 0) { + const recordsToUpdate = []; + const recordsToCreate = []; + + const newColumnData = processedData.map(record => { + const newColumnsOnly: Record = {}; + missingFields.forEach(field => { + if (field in record) { + newColumnsOnly[field] = record[field]; + } + }); + return newColumnsOnly; + }); + + for (let i = 0; i < Math.min(existingRecords.length, newColumnData.length); i++) { + if (Object.keys(newColumnData[i]).length > 0) { + recordsToUpdate.push({ + id: existingRecords[i].id, + fields: newColumnData[i] + }); + } + } + + const existingColumnsBeingUpdated = dataFields.filter(field => + existingFields.includes(field) && !missingFields.includes(field) + ); + + if (existingColumnsBeingUpdated.length > 0) { + recordsToCreate.push(...processedData.map(record => ({ fields: record }))); + console.log(`Will append ${recordsToCreate.length} new records with all data`); + } else { + if (processedData.length > existingRecords.length) { + const additionalRecords = processedData.slice(existingRecords.length); + recordsToCreate.push(...additionalRecords.map(record => ({ fields: record }))); + console.log(`Will append ${recordsToCreate.length} additional records`); + } + } + + if (recordsToUpdate.length > 0) { + console.log(`Updating ${recordsToUpdate.length} existing records with new columns`); + const BATCH_SIZE = 10; + for (let i = 0; i < recordsToUpdate.length; i += BATCH_SIZE) { + const batch = recordsToUpdate.slice(i, i + BATCH_SIZE); + console.log(`Updating batch ${Math.floor(i/BATCH_SIZE) + 1} of ${Math.ceil(recordsToUpdate.length/BATCH_SIZE)}`); + + try { + await retryableAirtableUpdate(base, tableName, batch); + } catch (batchError: any) { + console.error(`Error updating batch: ${batchError.message}`); + throw batchError; + } + + await new Promise(resolve => setTimeout(resolve, 500)); + } + } + } else { + console.log(`Appending all ${processedData.length} records to Airtable`); + const recordsToCreate = processedData.map(record => ({ fields: record })); + + const BATCH_SIZE = 10; + for (let i = 0; i < recordsToCreate.length; i += BATCH_SIZE) { + const batch = recordsToCreate.slice(i, i + BATCH_SIZE); + console.log(`Creating batch ${Math.floor(i/BATCH_SIZE) + 1} of ${Math.ceil(recordsToCreate.length/BATCH_SIZE)}`); + + try { + await retryableAirtableCreate(base, tableName, batch); + } catch (batchError: any) { + console.error(`Error creating batch: ${batchError.message}`); + throw batchError; + } + + await new Promise(resolve => setTimeout(resolve, 500)); + } } - logger.log('info', `Successfully wrote ${data.length} records to Airtable`); + await deleteEmptyRecords(base, tableName); + + logger.log('info', `Successfully processed ${processedData.length} records in Airtable`); }); } catch (error: any) { logger.log('error', `Airtable write failed: ${error.message}`); @@ -168,6 +353,20 @@ export async function writeDataToAirtable( } } +async function fetchAllRecords(base: Airtable.Base, tableName: string): Promise }>> { + try { + console.log(`Fetching all records from ${tableName}...`); + const records = await base(tableName).select().all(); + return records.map(record => ({ + id: record.id, + fields: record.fields + })); + } catch (error: any) { + console.warn(`Warning: Could not fetch all records: ${error.message}`); + return []; + } +} + async function deleteEmptyRecords(base: Airtable.Base, tableName: string): Promise { console.log('Checking for empty records to clear...'); @@ -183,31 +382,53 @@ async function deleteEmptyRecords(base: Airtable.Base, tableName: string): Promi }); if (emptyRecords.length > 0) { + console.log(`Found ${emptyRecords.length} empty records to delete`); const BATCH_SIZE = 10; for (let i = 0; i < emptyRecords.length; i += BATCH_SIZE) { const batch = emptyRecords.slice(i, i + BATCH_SIZE); const recordIds = batch.map(record => record.id); await base(tableName).destroy(recordIds); + console.log(`Deleted batch ${Math.floor(i/BATCH_SIZE) + 1} of ${Math.ceil(emptyRecords.length/BATCH_SIZE)}`); } - } + console.log(`Successfully deleted ${emptyRecords.length} empty records`); + } else { + console.log('No empty records found to delete'); + } } catch (error: any) { console.warn(`Warning: Could not clear empty records: ${error.message}`); console.warn('Will continue without deleting empty records'); } } -async function retryableAirtableWrite( +async function retryableAirtableCreate( + base: Airtable.Base, + tableName: string, + batch: any[], + retries = MAX_RETRIES +): Promise { + try { + await base(tableName).create(batch); + } catch (error) { + if (retries > 0) { + await new Promise(resolve => setTimeout(resolve, BASE_API_DELAY)); + return retryableAirtableCreate(base, tableName, batch, retries - 1); + } + throw error; + } +} + +async function retryableAirtableUpdate( base: Airtable.Base, tableName: string, batch: any[], retries = MAX_RETRIES ): Promise { try { - await base(tableName).create(batch.map(row => ({ fields: row }))); + await base(tableName).update(batch); } catch (error) { if (retries > 0) { await new Promise(resolve => setTimeout(resolve, BASE_API_DELAY)); - return retryableAirtableWrite(base, tableName, batch, retries - 1); + return retryableAirtableUpdate(base, tableName, batch, retries - 1); } throw error; } @@ -217,18 +438,19 @@ async function retryableAirtableWrite( async function getExistingFields(base: Airtable.Base, tableName: string): Promise { try { const records = await base(tableName).select({ pageSize: 5 }).firstPage(); + const fieldNames = new Set(); + if (records.length > 0) { - const fieldNames = new Set(); records.forEach(record => { Object.keys(record.fields).forEach(field => fieldNames.add(field)); }); - - const headers = Array.from(fieldNames); - console.log(`Found ${headers.length} headers from records: ${headers.join(', ')}`); - return headers; } - return []; + + const headers = Array.from(fieldNames); + console.log(`Found ${headers.length} headers from records: ${headers.join(', ')}`); + return headers; } catch (error) { + console.warn(`Warning: Error fetching existing fields: ${error}`); return []; } } @@ -299,17 +521,27 @@ export const processAirtableUpdates = async () => { for (const runId in airtableUpdateTasks) { const task = airtableUpdateTasks[runId]; - if (task.status !== 'pending') continue; - - hasPendingTasks = true; - try { - await updateAirtable(task.robotId, task.runId); - delete airtableUpdateTasks[runId]; - } catch (error: any) { - task.retries += 1; - if (task.retries >= MAX_RETRIES) { - task.status = 'failed'; - logger.log('error', `Permanent failure for run ${runId}: ${error.message}`); + + if (task.status === 'pending') { + hasPendingTasks = true; + console.log(`Processing Airtable update for run: ${runId}`); + + try { + await updateAirtable(task.robotId, task.runId); + console.log(`Successfully updated Airtable for runId: ${runId}`); + airtableUpdateTasks[runId].status = 'completed'; + delete airtableUpdateTasks[runId]; + } catch (error: any) { + console.error(`Failed to update Airtable for run ${task.runId}:`, error); + + if (task.retries < MAX_RETRIES) { + airtableUpdateTasks[runId].retries += 1; + console.log(`Retrying task for runId: ${runId}, attempt: ${task.retries + 1}`); + } else { + airtableUpdateTasks[runId].status = 'failed'; + console.log(`Max retries reached for runId: ${runId}. Marking task as failed.`); + logger.log('error', `Permanent failure for run ${runId}: ${error.message}`); + } } } } @@ -319,6 +551,7 @@ export const processAirtableUpdates = async () => { break; } + console.log('Waiting for 5 seconds before checking again...'); await new Promise(resolve => setTimeout(resolve, 5000)); } }; \ No newline at end of file From 882b25c771c0b588985bcb9f78ddc562f924ffe9 Mon Sep 17 00:00:00 2001 From: Rohit Date: Tue, 29 Apr 2025 20:20:47 +0530 Subject: [PATCH 19/55] feat: correct key used when checking unconfirmed list fields --- src/components/recorder/RightSidePanel.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/recorder/RightSidePanel.tsx b/src/components/recorder/RightSidePanel.tsx index 7a16277d9..801808231 100644 --- a/src/components/recorder/RightSidePanel.tsx +++ b/src/components/recorder/RightSidePanel.tsx @@ -342,9 +342,10 @@ export const RightSidePanel: React.FC = ({ onFinishCapture onFinishCapture(); }, [getListSettingsObject, socket, notify, handleStopGetList, resetInterpretationLog, finishAction, onFinishCapture, t]); - const hasUnconfirmedListTextFields = browserSteps.some(step => - step.type === 'list' && Object.values(step.fields).some(field => - !confirmedListTextFields[step.id]?.[field.id] + const hasUnconfirmedListTextFields = browserSteps.some(step => + step.type === 'list' && + Object.entries(step.fields).some(([fieldKey]) => + !confirmedListTextFields[step.id]?.[fieldKey] ) ); From 01ab958b60ab549002ae72e3523a7803f5e5cf8a Mon Sep 17 00:00:00 2001 From: Rohit Date: Tue, 29 Apr 2025 20:27:22 +0530 Subject: [PATCH 20/55] feat: replace banned Function type with an explicit signature --- maxun-core/src/interpret.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index 5c92a767f..91ade8b2d 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -43,9 +43,9 @@ interface InterpreterOptions { binaryCallback: (output: any, mimeType: string) => (void | Promise); debug: boolean; debugChannel: Partial<{ - activeId: Function, - debugMessage: Function, - setActionType: Function, + activeId: (id: number) => void, + debugMessage: (msg: string) => void, + setActionType: (type: string) => void, }> } From c7e3a665116483e30d2a2a7800dcfa80a5779947 Mon Sep 17 00:00:00 2001 From: Rohit Date: Tue, 29 Apr 2025 20:49:24 +0530 Subject: [PATCH 21/55] feat: maxlen 0 if field not exist --- server/src/workflow-management/integrations/airtable.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/src/workflow-management/integrations/airtable.ts b/server/src/workflow-management/integrations/airtable.ts index a78ec72d0..8113bc47a 100644 --- a/server/src/workflow-management/integrations/airtable.ts +++ b/server/src/workflow-management/integrations/airtable.ts @@ -49,9 +49,12 @@ function mergeRelatedData(serializableOutput: SerializableOutput, binaryOutput: const mergedRecords: Record[] = []; const maxLength = Math.max( - ...(serializableOutput.scrapeSchema?.map(array => array?.length || 0) || [0]), - ...(serializableOutput.scrapeList?.map(array => array?.length || 0) || [0]), - ...(serializableOutput.other?.map(array => array?.length || 0) || [0]) + ...[ + ...(serializableOutput.scrapeSchema ?? []).map(arr => arr?.length ?? 0), + ...(serializableOutput.scrapeList ?? []).map(arr => arr?.length ?? 0), + ...(serializableOutput.other ?? []).map(arr => arr?.length ?? 0), + 0 + ] ); for (let i = 0; i < maxLength; i++) { From f1d0cbdaae50a5b5abcc9489661def616b6fcdc7 Mon Sep 17 00:00:00 2001 From: Rohit Date: Wed, 30 Apr 2025 19:24:38 +0530 Subject: [PATCH 22/55] feat: rm other actions logic --- public/locales/de.json | 3 +- public/locales/en.json | 3 +- public/locales/es.json | 3 +- public/locales/ja.json | 3 +- public/locales/zh.json | 3 +- server/src/api/record.ts | 2 - server/src/pgboss-worker.ts | 7 +- .../classes/Interpreter.ts | 20 +--- .../integrations/airtable.ts | 13 --- .../integrations/gsheet.ts | 11 --- .../workflow-management/scheduler/index.ts | 2 - src/components/run/InterpretationLog.tsx | 96 ------------------- src/components/run/RunContent.tsx | 43 +-------- 13 files changed, 9 insertions(+), 200 deletions(-) diff --git a/public/locales/de.json b/public/locales/de.json index 6e5612205..1256be22b 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -547,8 +547,7 @@ "view_full": "Vollständige Daten anzeigen", "items": "Elemente", "schema_title": "Text erfassen", - "list_title": "Liste erfassen", - "other_title": "Andere Daten" + "list_title": "Liste erfassen" }, "captured_screenshot": { "title": "Erfasste Screenshots", diff --git a/public/locales/en.json b/public/locales/en.json index b8d910a16..531aa2e7e 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -568,8 +568,7 @@ "view_full": "View Full Data", "items": "items", "schema_title": "Capture Text", - "list_title": "Capture List", - "other_title": "Other Data" + "list_title": "Capture List" }, "captured_screenshot": { "title": "Captured Screenshots", diff --git a/public/locales/es.json b/public/locales/es.json index f0e2a9df0..b28fc5cfd 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -548,8 +548,7 @@ "view_full": "Ver Datos Completos", "items": "elementos", "schema_title": "Capturar Texto", - "list_title": "Capturar Lista", - "other_title": "Otros Datos" + "list_title": "Capturar Lista" }, "captured_screenshot": { "title": "Capturas de Pantalla", diff --git a/public/locales/ja.json b/public/locales/ja.json index 91021e32a..52fed4463 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -548,8 +548,7 @@ "view_full": "すべてのデータを表示", "items": "アイテム", "schema_title": "テキストをキャプチャ", - "list_title": "リストをキャプチャ", - "other_title": "その他のデータ" + "list_title": "リストをキャプチャ" }, "captured_screenshot": { "title": "キャプチャされたスクリーンショット", diff --git a/public/locales/zh.json b/public/locales/zh.json index 0d3277436..b4512bb32 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -548,8 +548,7 @@ "view_full": "查看完整数据", "items": "项目", "schema_title": "捕获文本", - "list_title": "捕获列表", - "other_title": "其他数据" + "list_title": "捕获列表" }, "captured_screenshot": { "title": "已捕获的截图", diff --git a/server/src/api/record.ts b/server/src/api/record.ts index 39a4b3df9..2d6077a9c 100644 --- a/server/src/api/record.ts +++ b/server/src/api/record.ts @@ -589,7 +589,6 @@ async function executeRun(id: string, userId: string) { const categorizedOutput = { scrapeSchema: interpretationInfo.scrapeSchemaOutput || {}, scrapeList: interpretationInfo.scrapeListOutput || {}, - other: interpretationInfo.otherOutput || {} }; await destroyRemoteBrowser(plainRun.browserId, userId); @@ -603,7 +602,6 @@ async function executeRun(id: string, userId: string) { serializableOutput: { scrapeSchema: Object.values(categorizedOutput.scrapeSchema), scrapeList: Object.values(categorizedOutput.scrapeList), - other: Object.values(categorizedOutput.other), }, binaryOutput: uploadedBinaryOutput, }); diff --git a/server/src/pgboss-worker.ts b/server/src/pgboss-worker.ts index b6cb13551..d6960a056 100644 --- a/server/src/pgboss-worker.ts +++ b/server/src/pgboss-worker.ts @@ -265,8 +265,7 @@ async function processRunExecution(job: Job) { const categorizedOutput = { scrapeSchema: interpretationInfo.scrapeSchemaOutput || {}, - scrapeList: interpretationInfo.scrapeListOutput || {}, - other: interpretationInfo.otherOutput || {} + scrapeList: interpretationInfo.scrapeListOutput || {} }; await run.update({ @@ -278,7 +277,6 @@ async function processRunExecution(job: Job) { serializableOutput: { scrapeSchema: Object.values(categorizedOutput.scrapeSchema), scrapeList: Object.values(categorizedOutput.scrapeList), - other: Object.values(categorizedOutput.other), }, binaryOutput: uploadedBinaryOutput, }); @@ -482,7 +480,6 @@ async function abortRun(runId: string, userId: string): Promise { let categorizedOutput = { scrapeSchema: {}, scrapeList: {}, - other: {} }; let binaryOutput: Record = {}; @@ -496,7 +493,6 @@ async function abortRun(runId: string, userId: string): Promise { categorizedOutput = { scrapeSchema: collectDataByType(browser.interpreter.serializableDataByType.scrapeSchema || []), scrapeList: collectDataByType(browser.interpreter.serializableDataByType.scrapeList || []), - other: collectDataByType(browser.interpreter.serializableDataByType.other || []) }; } @@ -516,7 +512,6 @@ async function abortRun(runId: string, userId: string): Promise { serializableOutput: { scrapeSchema: Object.values(categorizedOutput.scrapeSchema), scrapeList: Object.values(categorizedOutput.scrapeList), - other: Object.values(categorizedOutput.other), }, binaryOutput, }); diff --git a/server/src/workflow-management/classes/Interpreter.ts b/server/src/workflow-management/classes/Interpreter.ts index f3a5698c5..0481687d8 100644 --- a/server/src/workflow-management/classes/Interpreter.ts +++ b/server/src/workflow-management/classes/Interpreter.ts @@ -92,11 +92,9 @@ export class WorkflowInterpreter { public serializableDataByType: { scrapeSchema: any[], scrapeList: any[], - other: any[] } = { scrapeSchema: [], scrapeList: [], - other: [] }; /** @@ -216,12 +214,7 @@ export class WorkflowInterpreter { type: 'captureList', data }); - } else { - this.socket.emit('serializableCallback', { - type: 'other', - data - }); - } + } }, binaryCallback: (data: string, mimetype: string) => { this.socket.emit('binaryCallback', { @@ -292,7 +285,6 @@ export class WorkflowInterpreter { this.serializableDataByType = { scrapeSchema: [], scrapeList: [], - other: [] }; this.binaryData = []; } @@ -342,9 +334,7 @@ export class WorkflowInterpreter { } } else if (this.currentActionType === 'scrapeList') { this.serializableDataByType.scrapeList.push(data); - } else { - this.serializableDataByType.other.push(data); - } + } this.socket.emit('serializableCallback', data); }, @@ -394,12 +384,6 @@ export class WorkflowInterpreter { ...reducedObject, } }, {}), - otherOutput: this.serializableDataByType.other.reduce((reducedObject, item, index) => { - return { - [`item-${index}`]: item, - ...reducedObject, - } - }, {}), binaryOutput: this.binaryData.reduce((reducedObject, item, index) => { return { [`item-${index}`]: item, diff --git a/server/src/workflow-management/integrations/airtable.ts b/server/src/workflow-management/integrations/airtable.ts index 8113bc47a..401bc11d4 100644 --- a/server/src/workflow-management/integrations/airtable.ts +++ b/server/src/workflow-management/integrations/airtable.ts @@ -14,7 +14,6 @@ interface AirtableUpdateTask { interface SerializableOutput { scrapeSchema?: any[]; scrapeList?: any[]; - other?: any[]; } const MAX_RETRIES = 3; @@ -52,7 +51,6 @@ function mergeRelatedData(serializableOutput: SerializableOutput, binaryOutput: ...[ ...(serializableOutput.scrapeSchema ?? []).map(arr => arr?.length ?? 0), ...(serializableOutput.scrapeList ?? []).map(arr => arr?.length ?? 0), - ...(serializableOutput.other ?? []).map(arr => arr?.length ?? 0), 0 ] ); @@ -83,17 +81,6 @@ function mergeRelatedData(serializableOutput: SerializableOutput, binaryOutput: } } - if (serializableOutput.other) { - for (const otherArray of serializableOutput.other) { - if (!Array.isArray(otherArray)) continue; - - for (let i = 0; i < otherArray.length; i++) { - if (i >= mergedRecords.length) break; - mergedRecords[i] = { ...mergedRecords[i], ...otherArray[i] }; - } - } - } - if (binaryOutput && Object.keys(binaryOutput).length > 0) { for (let i = 0; i < mergedRecords.length; i++) { const screenshotKey = `item-${i}`; diff --git a/server/src/workflow-management/integrations/gsheet.ts b/server/src/workflow-management/integrations/gsheet.ts index 3fbe1229f..402ace5d7 100644 --- a/server/src/workflow-management/integrations/gsheet.ts +++ b/server/src/workflow-management/integrations/gsheet.ts @@ -13,7 +13,6 @@ interface GoogleSheetUpdateTask { interface SerializableOutput { scrapeSchema?: any[]; scrapeList?: any[]; - other?: any[]; } const MAX_RETRIES = 5; @@ -69,16 +68,6 @@ export async function updateGoogleSheet(robotId: string, runId: string) { plainRobot ); } - - if (serializableOutput.other && serializableOutput.other.length > 0) { - await processOutputType( - robotId, - spreadsheetId, - 'Other', - serializableOutput.other, - plainRobot - ); - } } if (plainRun.binaryOutput && Object.keys(plainRun.binaryOutput).length > 0) { diff --git a/server/src/workflow-management/scheduler/index.ts b/server/src/workflow-management/scheduler/index.ts index bbf845a6d..1559e63c3 100644 --- a/server/src/workflow-management/scheduler/index.ts +++ b/server/src/workflow-management/scheduler/index.ts @@ -135,7 +135,6 @@ async function executeRun(id: string, userId: string) { const categorizedOutput = { scrapeSchema: interpretationInfo.scrapeSchemaOutput || {}, scrapeList: interpretationInfo.scrapeListOutput || {}, - other: interpretationInfo.otherOutput || {} }; await destroyRemoteBrowser(plainRun.browserId, userId); @@ -149,7 +148,6 @@ async function executeRun(id: string, userId: string) { serializableOutput: { scrapeSchema: Object.values(categorizedOutput.scrapeSchema), scrapeList: Object.values(categorizedOutput.scrapeList), - other: Object.values(categorizedOutput.other), }, binaryOutput: uploadedBinaryOutput, }); diff --git a/src/components/run/InterpretationLog.tsx b/src/components/run/InterpretationLog.tsx index 69608fb58..2695bae44 100644 --- a/src/components/run/InterpretationLog.tsx +++ b/src/components/run/InterpretationLog.tsx @@ -96,17 +96,6 @@ export const InterpretationLog: React.FC = ({ isOpen, se const tabIndex = availableTabs.findIndex(tab => tab.id === 'captureText'); if (tabIndex !== -1) setActiveTab(tabIndex); } - } else if (type === 'other') { - if (Array.isArray(data)) { - setOtherData(prev => [...prev, data]); - } else { - setOtherData(prev => [...prev, [data]]); - } - if (otherData.length === 0) { - const availableTabs = getAvailableTabs(); - const tabIndex = availableTabs.findIndex(tab => tab.id === 'other'); - if (tabIndex !== -1) setActiveTab(tabIndex); - } } scrollLogToBottom(); @@ -178,10 +167,6 @@ export const InterpretationLog: React.FC = ({ isOpen, se tabs.push({ id: 'captureScreenshot', label: 'Screenshots' }); } - if (otherData.length > 0) { - tabs.push({ id: 'other', label: 'Other' }); - } - return tabs; }, [captureListData.length, captureTextData.length, screenshotData.length, otherData.length]); @@ -464,87 +449,6 @@ export const InterpretationLog: React.FC = ({ isOpen, se )} - - {activeTab === availableTabs.findIndex(tab => tab.id === 'other') && otherData.length > 0 && ( - - {otherData.length > 1 && ( - - - {`Dataset ${otherPage + 1} of ${otherData.length}`} - - - - - - - )} - - - - - {otherData[otherPage] && otherData[otherPage].length > 0 && - Object.keys(otherData[otherPage][0]).map((column) => ( - - {column} - - )) - } - - - - {otherData[otherPage] && - otherData[otherPage].map((row: any, idx: any) => ( - - {Object.keys(row).map((column) => ( - - {row[column]} - - ))} - - ))} - -
-
-
- )} ) : ( diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 664163aa7..781fba707 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -45,9 +45,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const [listData, setListData] = useState([]); const [listColumns, setListColumns] = useState([]); const [currentListIndex, setCurrentListIndex] = useState(0); - - const [otherData, setOtherData] = useState([]); - const [otherColumns, setOtherColumns] = useState([]); const [expandedView, setExpandedView] = useState(null); @@ -66,7 +63,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe if (!row.serializableOutput.scrapeSchema && !row.serializableOutput.scrapeList && - !row.serializableOutput.other && Object.keys(row.serializableOutput).length > 0) { setIsLegacyData(true); @@ -83,10 +79,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe if (row.serializableOutput.scrapeList) { processScrapeList(row.serializableOutput.scrapeList); } - - if (row.serializableOutput.other && Object.keys(row.serializableOutput.other).length > 0) { - processDataCategory(row.serializableOutput.other, setOtherData, setOtherColumns); - } }, [row.serializableOutput]); const processLegacyData = (legacyOutput: Record) => { @@ -240,7 +232,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe allData = { schema: schemaData, list: listData.flat(), - other: otherData }; } @@ -619,13 +610,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe csvFilename = 'list_data.csv'; jsonFilename = 'list_data.json'; break; - case 'other': - data = otherData; - columns = otherColumns; - title = t('run_content.captured_data.other_title'); - csvFilename = 'other_data.csv'; - jsonFilename = 'other_data.json'; - break; case 'legacy': data = legacyData; columns = legacyColumns; @@ -714,7 +698,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe ); }; - const hasData = schemaData.length > 0 || listData.length > 0 || otherData.length > 0 || legacyData.length > 0; + const hasData = schemaData.length > 0 || listData.length > 0 || legacyData.length > 0; const hasScreenshots = row.binaryOutput && Object.keys(row.binaryOutput).length > 0; return ( @@ -881,15 +865,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe 'list_data.json', true )} - - {renderDataTable( - otherData, - otherColumns, - t('run_content.captured_data.other_title'), - , - 'other_data.csv', - 'other_data.json' - )} ) : ( @@ -897,7 +872,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const dataCategoriesCount = [ schemaData.length > 0, listData.length > 0, - otherData.length > 0 ].filter(Boolean).length; const columnWidth = dataCategoriesCount === 1 ? 12 : dataCategoriesCount === 2 ? 6 : 4; @@ -932,20 +906,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe )} )} - - {otherData.length > 0 && ( - - {renderDataCard( - otherData, - otherColumns, - t('run_content.captured_data.other_title'), - , - 'other', - 'other_data.csv', - 'other_data.json' - )} - - )} ); })()} @@ -954,7 +914,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe )} {renderExpandedView('schema')} - {renderExpandedView('other')} {renderExpandedView('legacy')} {listData.map((_, index) => renderExpandedView(`list-${index}`))} From f1c148817acf9eee0874720a7f85a775b3aa3510 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 19:57:38 +0530 Subject: [PATCH 23/55] fix: lint --- package.json | 1 - src/components/run/RunContent.tsx | 274 +++++++++++++++--------------- 2 files changed, 137 insertions(+), 138 deletions(-) diff --git a/package.json b/package.json index 75350f1fa..1cfa1894f 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,6 @@ "lodash": "^4.17.21", "loglevel": "^1.8.0", "loglevel-plugin-remote": "^0.6.8", - "maxun-core": "^0.0.15", "minio": "^8.0.1", "moment-timezone": "^0.5.45", "node-cron": "^3.0.3", diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 781fba707..55a69ba87 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -38,16 +38,16 @@ interface RunContentProps { export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRef, abortRunHandler }: RunContentProps) => { const { t } = useTranslation(); const [tab, setTab] = React.useState('output'); - + const [schemaData, setSchemaData] = useState([]); const [schemaColumns, setSchemaColumns] = useState([]); - + const [listData, setListData] = useState([]); const [listColumns, setListColumns] = useState([]); const [currentListIndex, setCurrentListIndex] = useState(0); const [expandedView, setExpandedView] = useState(null); - + const [viewMode, setViewMode] = useState<'horizontal' | 'vertical'>('vertical'); const [legacyData, setLegacyData] = useState([]); @@ -61,10 +61,10 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe useEffect(() => { if (!row.serializableOutput) return; - if (!row.serializableOutput.scrapeSchema && - !row.serializableOutput.scrapeList && - Object.keys(row.serializableOutput).length > 0) { - + if (!row.serializableOutput.scrapeSchema && + !row.serializableOutput.scrapeList && + Object.keys(row.serializableOutput).length > 0) { + setIsLegacyData(true); processLegacyData(row.serializableOutput); return; @@ -75,7 +75,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe if (row.serializableOutput.scrapeSchema && Object.keys(row.serializableOutput.scrapeSchema).length > 0) { processDataCategory(row.serializableOutput.scrapeSchema, setSchemaData, setSchemaColumns); } - + if (row.serializableOutput.scrapeList) { processScrapeList(row.serializableOutput.scrapeList); } @@ -83,7 +83,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const processLegacyData = (legacyOutput: Record) => { let allData: any[] = []; - + Object.keys(legacyOutput).forEach(key => { const data = legacyOutput[key]; if (Array.isArray(data)) { @@ -93,13 +93,13 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe allData = [...allData, ...filteredData]; } }); - + if (allData.length > 0) { const allColumns = new Set(); allData.forEach(item => { Object.keys(item).forEach(key => allColumns.add(key)); }); - + setLegacyData(allData); setLegacyColumns(Array.from(allColumns)); } @@ -111,7 +111,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe setColumns: React.Dispatch> ) => { let allData: any[] = []; - + Object.keys(categoryData).forEach(key => { const data = categoryData[key]; if (Array.isArray(data)) { @@ -121,13 +121,13 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe allData = [...allData, ...filteredData]; } }); - + if (allData.length > 0) { const allColumns = new Set(); allData.forEach(item => { Object.keys(item).forEach(key => allColumns.add(key)); }); - + setData(allData); setColumns(Array.from(allColumns)); } @@ -136,22 +136,22 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const processScrapeList = (scrapeListData: any) => { const tablesList: any[][] = []; const columnsList: string[][] = []; - + if (Array.isArray(scrapeListData)) { scrapeListData.forEach(tableData => { if (Array.isArray(tableData) && tableData.length > 0) { const filteredData = tableData.filter(row => Object.values(row).some(value => value !== undefined && value !== "") ); - + if (filteredData.length > 0) { tablesList.push(filteredData); - + const tableColumns = new Set(); filteredData.forEach(item => { Object.keys(item).forEach(key => tableColumns.add(key)); }); - + columnsList.push(Array.from(tableColumns)); } } @@ -163,21 +163,21 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const filteredData = tableData.filter(row => Object.values(row).some(value => value !== undefined && value !== "") ); - + if (filteredData.length > 0) { tablesList.push(filteredData); - + const tableColumns = new Set(); filteredData.forEach(item => { Object.keys(item).forEach(key => tableColumns.add(key)); }); - + columnsList.push(Array.from(tableColumns)); } } }); } - + setListData(tablesList); setListColumns(columnsList); setCurrentListIndex(0); @@ -217,7 +217,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe document.body.appendChild(link); link.click(); document.body.removeChild(link); - + setTimeout(() => { URL.revokeObjectURL(url); }, 100); @@ -225,7 +225,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const downloadAllJSON = () => { let allData; - + if (isLegacyData) { allData = { data: legacyData }; } else { @@ -234,7 +234,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe list: listData.flat(), }; } - + const blob = new Blob([JSON.stringify(allData, null, 2)], { type: 'application/json;charset=utf-8;' }); const url = URL.createObjectURL(blob); @@ -265,12 +265,12 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe ) => { if (!isPaginatedList && data.length === 0) return null; if (isPaginatedList && (listData.length === 0 || currentListIndex >= listData.length)) return null; - + const currentData = isPaginatedList ? listData[currentListIndex] : data; const currentColumns = isPaginatedList ? listColumns[currentListIndex] : columns; - + if (!currentData || currentData.length === 0) return null; - + return ( {isPaginatedList ? ( - 1 + 1 ? `Table ${currentListIndex + 1} of ${listData.length} (${currentData.length} ${currentData.length === 1 ? 'item' : 'items'})` : `${currentData.length} ${currentData.length === 1 ? 'item' : 'items'}` - } - size="small" - sx={{ ml: 2, backgroundColor: '#FF00C3', color: 'white' }} + } + size="small" + sx={{ ml: 2, backgroundColor: '#FF00C3', color: 'white' }} /> ) : ( - )} @@ -308,7 +308,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe startIcon={} onClick={() => { if (isPaginatedList) { - downloadCSV(currentData, currentColumns, `list_table_${currentListIndex+1}.csv`); + downloadCSV(currentData, currentColumns, `list_table_${currentListIndex + 1}.csv`); } else { downloadCSV(data, columns, csvFilename); } @@ -321,7 +321,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe startIcon={} onClick={() => { if (isPaginatedList) { - downloadJSON(currentData, `list_table_${currentListIndex+1}.json`); + downloadJSON(currentData, `list_table_${currentListIndex + 1}.json`); } else { downloadJSON(data, jsonFilename); } @@ -331,14 +331,14 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe JSON - + {isPaginatedList && listData.length > 1 && ( - - - + @@ -780,7 +780,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe {t('run_content.loading')} ) : (!hasData && !hasScreenshots - ? {t('run_content.empty_output')} + ? {t('run_content.empty_output')} : null)} {hasData && ( @@ -791,23 +791,23 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe {t('run_content.captured_data.title')} - setViewMode('horizontal')} color={viewMode === 'horizontal' ? 'primary' : 'default'} sx={{ color: viewMode === 'horizontal' ? '#FF00C3' : 'inherit' }} > - setViewMode('vertical')} color={viewMode === 'vertical' ? 'primary' : 'default'} sx={{ color: viewMode === 'vertical' ? '#FF00C3' : 'inherit' }} > - - + {isLegacyData && ( viewMode === 'vertical' ? ( renderDataTable( @@ -843,7 +843,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe ) )} - + {!isLegacyData && ( viewMode === 'vertical' ? ( <> @@ -855,27 +855,27 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe 'schema_data.csv', 'schema_data.json' )} - + {listData.length > 0 && renderDataTable( - [], - [], + [], + [], t('run_content.captured_data.list_title'), , 'list_data.csv', 'list_data.json', - true + true )} ) : ( {(() => { const dataCategoriesCount = [ - schemaData.length > 0, - listData.length > 0, + schemaData.length > 0, + listData.length > 0, ].filter(Boolean).length; - + const columnWidth = dataCategoriesCount === 1 ? 12 : dataCategoriesCount === 2 ? 6 : 4; - + return ( <> {schemaData.length > 0 && ( @@ -891,18 +891,18 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe )} )} - + {listData.length > 0 && ( {renderDataCard( - [], - [], + [], + [], t('run_content.captured_data.list_title'), , 'list', 'list_data.csv', 'list_data.json', - true + true )} )} @@ -912,14 +912,14 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe ) )} - + {renderExpandedView('schema')} {renderExpandedView('legacy')} - + {listData.map((_, index) => renderExpandedView(`list-${index}`))} )} - + {hasScreenshots && ( <> @@ -927,21 +927,21 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe {t('run_content.captured_screenshot.title')} - - setViewMode('horizontal')} color={viewMode === 'horizontal' ? 'primary' : 'default'} sx={{ color: viewMode === 'horizontal' ? '#FF00C3' : 'inherit' }} > - setViewMode('vertical')} color={viewMode === 'vertical' ? 'primary' : 'default'} sx={{ color: viewMode === 'vertical' ? '#FF00C3' : 'inherit' }} @@ -951,7 +951,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe - + {viewMode === 'vertical' ? ( <> {Object.keys(row.binaryOutput).map((key, index) => { @@ -967,7 +967,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe - Screenshot {index+1} + Screenshot {index + 1} @@ -985,15 +985,15 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe - {`Screenshot @@ -1033,16 +1033,16 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe /> - {`Screenshot From 0c5e98c37c47c99e608961e98b1faa12f916a0af Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 19:58:27 +0530 Subject: [PATCH 24/55] fix: lint --- src/components/run/RunContent.tsx | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 55a69ba87..f0e15d18f 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -1,4 +1,23 @@ -import { Box, Tabs, Typography, Tab, Paper, Button, CircularProgress, Accordion, AccordionSummary, AccordionDetails, Divider, Card, CardHeader, CardContent, Grid, IconButton, Chip, ButtonGroup } from "@mui/material"; +import { + Box, + Tabs, + Typography, + Tab, + Paper, + Button, + CircularProgress, + Accordion, + AccordionSummary, + AccordionDetails, + Divider, + Card, + CardHeader, + CardContent, + Grid, + IconButton, + Chip, + ButtonGroup +} from "@mui/material"; import Highlight from "react-highlight"; import * as React from "react"; import { Data } from "./RunsTable"; From d0f284ce9a2df5078958a87844bff27b2cf856f1 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 19:58:48 +0530 Subject: [PATCH 25/55] chore: -rm unused imports --- src/components/run/RunContent.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index f0e15d18f..1c3659166 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -9,7 +9,6 @@ import { Accordion, AccordionSummary, AccordionDetails, - Divider, Card, CardHeader, CardContent, @@ -26,7 +25,6 @@ import ArticleIcon from '@mui/icons-material/Article'; import ImageIcon from '@mui/icons-material/Image'; import ListIcon from '@mui/icons-material/List'; import SchemaIcon from '@mui/icons-material/Schema'; -import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import CloudDownloadIcon from '@mui/icons-material/CloudDownload'; import DownloadIcon from '@mui/icons-material/Download'; From f65dda0633e3f6e88c1b9c8c51c51e7a0b288191 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:02:12 +0530 Subject: [PATCH 26/55] feat: -rm download all json --- src/components/run/RunContent.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 1c3659166..6407d32c0 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -822,15 +822,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe > - From 302ec004d394af63e10dd737484f605ede8fc247 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:02:47 +0530 Subject: [PATCH 27/55] feat: -rm horizontal view --- src/components/run/RunContent.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 6407d32c0..65b82d274 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -808,13 +808,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe {t('run_content.captured_data.title')} - setViewMode('horizontal')} - color={viewMode === 'horizontal' ? 'primary' : 'default'} - sx={{ color: viewMode === 'horizontal' ? '#FF00C3' : 'inherit' }} - > - - setViewMode('vertical')} color={viewMode === 'vertical' ? 'primary' : 'default'} From e5f63be8f3d776c3c9455859acbabf323ed74cd5 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:03:51 +0530 Subject: [PATCH 28/55] feat: -rm vertical view --- src/components/run/RunContent.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 65b82d274..34721bd01 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -808,13 +808,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe {t('run_content.captured_data.title')} - setViewMode('vertical')} - color={viewMode === 'vertical' ? 'primary' : 'default'} - sx={{ color: viewMode === 'vertical' ? '#FF00C3' : 'inherit' }} - > - - + From 9eeb367d669fc762c0025ec5031f37b306be46ee Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:04:03 +0530 Subject: [PATCH 29/55] feat: -rm box --- src/components/run/RunContent.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 34721bd01..3b21cfd2c 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -807,9 +807,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe {t('run_content.captured_data.title')} - - - {isLegacyData && ( From 458392d65183a990919a914c7cbf5e5698b05559 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:05:09 +0530 Subject: [PATCH 30/55] feat: -rm horizontal view from screenshots --- src/components/run/RunContent.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 3b21cfd2c..801085449 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -926,13 +926,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe /> - setViewMode('horizontal')} - color={viewMode === 'horizontal' ? 'primary' : 'default'} - sx={{ color: viewMode === 'horizontal' ? '#FF00C3' : 'inherit' }} - > - - setViewMode('vertical')} color={viewMode === 'vertical' ? 'primary' : 'default'} From db890e041f97b12035ba58f4a61c5b8203f4e071 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:05:23 +0530 Subject: [PATCH 31/55] feat: -rm vertical view from screenshots --- src/components/run/RunContent.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 801085449..f4a096bed 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -926,13 +926,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe /> - setViewMode('vertical')} - color={viewMode === 'vertical' ? 'primary' : 'default'} - sx={{ color: viewMode === 'vertical' ? '#FF00C3' : 'inherit' }} - > - - From e6a7fdfcf2670b8a2b2b9cd95d2b5313d3efffed Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:05:36 +0530 Subject: [PATCH 32/55] feat: -rm box --- src/components/run/RunContent.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index f4a096bed..b9686b1cb 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -925,8 +925,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe sx={{ ml: 2, backgroundColor: '#FF00C3', color: 'white' }} /> - - From 2b046340f5763e076c967d866f75c332470d5589 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:10:15 +0530 Subject: [PATCH 33/55] feat: -rm unused icons --- src/components/run/RunContent.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index b9686b1cb..377014e27 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -26,11 +26,8 @@ import ImageIcon from '@mui/icons-material/Image'; import ListIcon from '@mui/icons-material/List'; import SchemaIcon from '@mui/icons-material/Schema'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import CloudDownloadIcon from '@mui/icons-material/CloudDownload'; import DownloadIcon from '@mui/icons-material/Download'; import FullscreenIcon from '@mui/icons-material/Fullscreen'; -import ViewModuleIcon from '@mui/icons-material/ViewModule'; -import ViewListIcon from '@mui/icons-material/ViewList'; import DataObjectIcon from '@mui/icons-material/DataObject'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; From c202a507cb4d9a7d7890f4930820c5e727010b82 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:14:06 +0530 Subject: [PATCH 34/55] feat: -rm icons for capture text and list --- src/components/run/RunContent.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 377014e27..65ceb1b07 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -272,7 +272,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe data: any[], columns: string[], title: string, - icon: React.ReactNode, csvFilename: string, jsonFilename: string, isPaginatedList: boolean = false @@ -293,7 +292,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe id={`${title.toLowerCase()}-header`} > - {icon} {title} @@ -812,7 +810,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe legacyData, legacyColumns, t('run_content.captured_data.title'), - , 'data.csv', 'data.json' ) @@ -840,7 +837,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe schemaData, schemaColumns, t('run_content.captured_data.schema_title'), - , 'schema_data.csv', 'schema_data.json' )} @@ -849,7 +845,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe [], [], t('run_content.captured_data.list_title'), - , 'list_data.csv', 'list_data.json', true From cf5be61b72f4b9a162bc96a23c01ed2ffad15b92 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:15:27 +0530 Subject: [PATCH 35/55] feat: -rm icons --- src/components/run/RunContent.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 65ceb1b07..8d05b2d39 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -405,7 +405,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe data: any[], columns: string[], title: string, - icon: React.ReactNode, dataType: string, csvFilename: string, jsonFilename: string, @@ -434,7 +433,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe boxShadow: 3 }}> @@ -820,7 +818,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe legacyData, legacyColumns, t('run_content.captured_data.title'), - , 'legacy', 'data.csv', 'data.json' @@ -868,7 +865,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe schemaData, schemaColumns, t('run_content.captured_data.schema_title'), - , 'schema', 'schema_data.csv', 'schema_data.json' @@ -882,7 +878,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe [], [], t('run_content.captured_data.list_title'), - , 'list', 'list_data.csv', 'list_data.json', From 47ed5cef714a1f5f74771025900a6ffddcffe969 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:15:49 +0530 Subject: [PATCH 36/55] feat: -rm icons --- src/components/run/RunContent.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 8d05b2d39..0a7acb25c 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -23,8 +23,6 @@ import { Data } from "./RunsTable"; import { TabPanel, TabContext } from "@mui/lab"; import ArticleIcon from '@mui/icons-material/Article'; import ImageIcon from '@mui/icons-material/Image'; -import ListIcon from '@mui/icons-material/List'; -import SchemaIcon from '@mui/icons-material/Schema'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import DownloadIcon from '@mui/icons-material/Download'; import FullscreenIcon from '@mui/icons-material/Fullscreen'; From 7c7116a0420f905d0d4bbd009cb44a98a12ac524 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:19:48 +0530 Subject: [PATCH 37/55] feat: -rm captured data --- src/components/run/RunContent.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 0a7acb25c..57ca7bd08 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -793,13 +793,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe {hasData && ( - - - - {t('run_content.captured_data.title')} - - - {isLegacyData && ( viewMode === 'vertical' ? ( renderDataTable( From 1f06bcd232579cc6b80d2e7af63c1676a5ab3c3a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:21:22 +0530 Subject: [PATCH 38/55] feat: -rm captured screenshots --- src/components/run/RunContent.tsx | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 57ca7bd08..5bbe3a3d1 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -892,19 +892,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe {hasScreenshots && ( <> - - - - - {t('run_content.captured_screenshot.title')} - - - - {viewMode === 'vertical' ? ( <> From 392a5fd60c780749c0a409c0a916da633903364c Mon Sep 17 00:00:00 2001 From: Rohit Date: Wed, 30 Apr 2025 20:22:37 +0530 Subject: [PATCH 39/55] feat: rm workflow in progress logic --- public/locales/en.json | 8 -- .../action/ActionDescriptionBox.tsx | 103 +----------------- src/context/browserActions.tsx | 16 --- 3 files changed, 4 insertions(+), 123 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 531aa2e7e..b06de383a 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -178,14 +178,6 @@ "limit": "Choose the number of items to extract", "complete": "Capture is complete" }, - "workflow_progress": { - "title": "Workflow Progress", - "description": "You have completed {{completed}} out of {{total}} possible actions", - "completed": "All actions completed!" - }, - "workflow_actions": { - "description": "The following actions have been added to your workflow:" - }, "actions": { "text": "Capture Text", "list": "Capture List", diff --git a/src/components/action/ActionDescriptionBox.tsx b/src/components/action/ActionDescriptionBox.tsx index f3bfcd952..d36db4079 100644 --- a/src/components/action/ActionDescriptionBox.tsx +++ b/src/components/action/ActionDescriptionBox.tsx @@ -49,24 +49,14 @@ const Content = styled.div` text-align: left; `; + const ActionDescriptionBox = ({ isDarkMode }: { isDarkMode: boolean }) => { const { t } = useTranslation(); - const { - getText, - getScreenshot, - getList, - captureStage, - actionsInWorkflow - } = useActionContext() as { + const { getText, getScreenshot, getList, captureStage } = useActionContext() as { getText: boolean; getScreenshot: boolean; getList: boolean; captureStage: 'initial' | 'pagination' | 'limit' | 'complete'; - actionsInWorkflow: { - text: boolean; - list: boolean; - screenshot: boolean; - }; }; const messages = [ @@ -128,95 +118,10 @@ const ActionDescriptionBox = ({ isDarkMode }: { isDarkMode: boolean }) => { ); } else { - const actionsInWorkflowCount = Object.values(actionsInWorkflow).filter(Boolean).length; - return ( <> - - {actionsInWorkflowCount === 0 - ? t('action_description.default.title') - : t('action_description.workflow_progress.title')} - - - {actionsInWorkflowCount === 0 ? ( - - {t('action_description.default.description')} - - ) : ( - <> - - {t('action_description.workflow_actions.description')} - - - - {actionsInWorkflow.text && ( - - } - label={ - - {t('action_description.actions.text')} - - } - /> - )} - - {actionsInWorkflow.list && ( - - } - label={ - - {t('action_description.actions.list')} - - } - /> - )} - - {actionsInWorkflow.screenshot && ( - - } - label={ - - {t('action_description.actions.screenshot')} - - } - /> - )} - - - )} + {t('action_description.default.title')} + {t('action_description.default.description')} ); } diff --git a/src/context/browserActions.tsx b/src/context/browserActions.tsx index c993dbcea..4ba5248cc 100644 --- a/src/context/browserActions.tsx +++ b/src/context/browserActions.tsx @@ -21,11 +21,6 @@ interface ActionContextProps { captureStage: CaptureStage; showPaginationOptions: boolean; showLimitOptions: boolean; - actionsInWorkflow: { - text: boolean; - list: boolean; - screenshot: boolean; - }; activeAction: 'none' | 'text' | 'list' | 'screenshot'; setActiveAction: (action: 'none' | 'text' | 'list' | 'screenshot') => void; setWorkflow: (workflow: WorkflowFile) => void; @@ -64,11 +59,6 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { const [captureStage, setCaptureStage] = useState('initial'); const [showPaginationOptions, setShowPaginationOptions] = useState(false); const [showLimitOptions, setShowLimitOptions] = useState(false); - const [actionsInWorkflow, setActionsInWorkflow] = useState({ - text: false, - list: false, - screenshot: false - }); const [activeAction, setActiveAction] = useState<'none' | 'text' | 'list' | 'screenshot'>('none'); const { socket } = useSocketStore(); @@ -92,11 +82,6 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { const finishAction = (action: 'text' | 'list' | 'screenshot') => { if (activeAction !== action) return; - setActionsInWorkflow(prev => ({ - ...prev, - [action]: true - })); - setActiveAction('none'); if (action === 'text') { @@ -175,7 +160,6 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { captureStage, showPaginationOptions, showLimitOptions, - actionsInWorkflow, activeAction, setActiveAction, setWorkflow, From 9bc981593ac525256ebdeb2c5e86892f1347a8c6 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:26:02 +0530 Subject: [PATCH 40/55] feat: -rm chips --- src/components/run/RunContent.tsx | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 5bbe3a3d1..5ed6ed1f5 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -293,22 +293,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe {title} - {isPaginatedList ? ( - 1 - ? `Table ${currentListIndex + 1} of ${listData.length} (${currentData.length} ${currentData.length === 1 ? 'item' : 'items'})` - : `${currentData.length} ${currentData.length === 1 ? 'item' : 'items'}` - } - size="small" - sx={{ ml: 2, backgroundColor: '#FF00C3', color: 'white' }} - /> - ) : ( - - )} From 94fecc131e19e5a59f3f883ed20af47c17e26f3f Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:33:14 +0530 Subject: [PATCH 41/55] feat: -rm download all json --- src/components/run/RunContent.tsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 5ed6ed1f5..2106b3eec 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -235,18 +235,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe }, 100); }; - const downloadAllJSON = () => { - let allData; - - if (isLegacyData) { - allData = { data: legacyData }; - } else { - allData = { - schema: schemaData, - list: listData.flat(), - }; - } - const blob = new Blob([JSON.stringify(allData, null, 2)], { type: 'application/json;charset=utf-8;' }); const url = URL.createObjectURL(blob); From 9c5782402216bbb499d345fe1624208c83a9064f Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:33:45 +0530 Subject: [PATCH 42/55] feat: -rm download all json --- src/components/run/RunContent.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 2106b3eec..59b91af0e 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -235,17 +235,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe }, 100); }; - const blob = new Blob([JSON.stringify(allData, null, 2)], { type: 'application/json;charset=utf-8;' }); - const url = URL.createObjectURL(blob); - - const link = document.createElement("a"); - link.href = url; - link.setAttribute("download", "all_data.json"); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - }; - const navigateListTable = (direction: 'next' | 'prev') => { if (direction === 'next' && currentListIndex < listData.length - 1) { setCurrentListIndex(currentListIndex + 1); From decf14a9ba2137a81681cc2e83524473dbbec91d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:35:21 +0530 Subject: [PATCH 43/55] fix: cleanup --- src/components/run/RunContent.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 59b91af0e..1fdf6f40f 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -853,7 +853,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe {hasScreenshots && ( <> - {viewMode === 'vertical' ? ( <> {Object.keys(row.binaryOutput).map((key, index) => { From b72d0dc264a562dfc7703d47bc72784d21d0475f Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 30 Apr 2025 20:35:43 +0530 Subject: [PATCH 44/55] chore: remove unused import --- src/components/run/RunContent.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 1fdf6f40f..ccd314dd6 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -21,7 +21,6 @@ import Highlight from "react-highlight"; import * as React from "react"; import { Data } from "./RunsTable"; import { TabPanel, TabContext } from "@mui/lab"; -import ArticleIcon from '@mui/icons-material/Article'; import ImageIcon from '@mui/icons-material/Image'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import DownloadIcon from '@mui/icons-material/Download'; From 43b7a7d463761cdd6b09f282148005eb09e943e9 Mon Sep 17 00:00:00 2001 From: Rohit Date: Wed, 30 Apr 2025 20:59:23 +0530 Subject: [PATCH 45/55] feat: rm view mode logic --- src/components/run/RunContent.tsx | 224 ++++-------------------------- 1 file changed, 27 insertions(+), 197 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index ccd314dd6..c31667205 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -59,8 +59,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const [expandedView, setExpandedView] = useState(null); - const [viewMode, setViewMode] = useState<'horizontal' | 'vertical'>('vertical'); - const [legacyData, setLegacyData] = useState([]); const [legacyColumns, setLegacyColumns] = useState([]); const [isLegacyData, setIsLegacyData] = useState(false); @@ -754,93 +752,34 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe {hasData && ( {isLegacyData && ( - viewMode === 'vertical' ? ( - renderDataTable( - legacyData, - legacyColumns, - t('run_content.captured_data.title'), - 'data.csv', - 'data.json' - ) - ) : ( - - - {renderDataCard( - legacyData, - legacyColumns, - t('run_content.captured_data.title'), - 'legacy', - 'data.csv', - 'data.json' - )} - - + renderDataTable( + legacyData, + legacyColumns, + t('run_content.captured_data.title'), + 'data.csv', + 'data.json' ) )} {!isLegacyData && ( - viewMode === 'vertical' ? ( - <> - {renderDataTable( - schemaData, - schemaColumns, - t('run_content.captured_data.schema_title'), - 'schema_data.csv', - 'schema_data.json' - )} - - {listData.length > 0 && renderDataTable( - [], - [], - t('run_content.captured_data.list_title'), - 'list_data.csv', - 'list_data.json', - true - )} - - ) : ( - - {(() => { - const dataCategoriesCount = [ - schemaData.length > 0, - listData.length > 0, - ].filter(Boolean).length; - - const columnWidth = dataCategoriesCount === 1 ? 12 : dataCategoriesCount === 2 ? 6 : 4; - - return ( - <> - {schemaData.length > 0 && ( - - {renderDataCard( - schemaData, - schemaColumns, - t('run_content.captured_data.schema_title'), - 'schema', - 'schema_data.csv', - 'schema_data.json' - )} - - )} - - {listData.length > 0 && ( - - {renderDataCard( - [], - [], - t('run_content.captured_data.list_title'), - 'list', - 'list_data.csv', - 'list_data.json', - true - )} - - )} - - ); - })()} - - ) + <> + {renderDataTable( + schemaData, + schemaColumns, + t('run_content.captured_data.schema_title'), + 'schema_data.csv', + 'schema_data.json' + )} + + {listData.length > 0 && renderDataTable( + [], + [], + t('run_content.captured_data.list_title'), + 'list_data.csv', + 'list_data.json', + true + )} + )} {renderExpandedView('schema')} @@ -850,120 +789,11 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe )} - {hasScreenshots && ( + {/* {hasScreenshots && ( <> - {viewMode === 'vertical' ? ( - <> - {Object.keys(row.binaryOutput).map((key, index) => { - try { - const imageUrl = row.binaryOutput[key]; - return ( - - } - aria-controls={`screenshot-${key}-content`} - id={`screenshot-${key}-header`} - > - - - - Screenshot {index + 1} - - - - - - - - - - - {`Screenshot - - - - ); - } catch (e) { - console.log(e); - return ( - - {key}: {t('run_content.captured_screenshot.render_failed')} - - ); - } - })} - - ) : ( - - {Object.keys(row.binaryOutput).map((key) => { - try { - const imageUrl = row.binaryOutput[key]; - return ( - - - } - title={`Screenshot ${key}`} - action={ - - - - } - /> - - - {`Screenshot - - - - - ); - } catch (e) { - console.log(e); - return ( - - - {key}: {t('run_content.captured_screenshot.render_failed')} - - - ); - } - })} - - )} + {renderPaginatedScreenshots()} - )} + )} */} From 02a150d135d962bf84b5aea60a913240ad955c64 Mon Sep 17 00:00:00 2001 From: Rohit Date: Wed, 30 Apr 2025 21:02:38 +0530 Subject: [PATCH 46/55] feat: paginate capture screenshots --- src/components/run/RunContent.tsx | 103 +++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 3 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index c31667205..f0074adb5 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -57,6 +57,9 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const [listColumns, setListColumns] = useState([]); const [currentListIndex, setCurrentListIndex] = useState(0); + const [screenshotKeys, setScreenshotKeys] = useState([]); + const [currentScreenshotIndex, setCurrentScreenshotIndex] = useState(0); + const [expandedView, setExpandedView] = useState(null); const [legacyData, setLegacyData] = useState([]); @@ -90,6 +93,13 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe } }, [row.serializableOutput]); + useEffect(() => { + if (row.binaryOutput && Object.keys(row.binaryOutput).length > 0) { + setScreenshotKeys(Object.keys(row.binaryOutput)); + setCurrentScreenshotIndex(0); + } + }, [row.binaryOutput]); + const processLegacyData = (legacyOutput: Record) => { let allData: any[] = []; @@ -240,6 +250,14 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe } }; + const navigateScreenshots = (direction: 'next' | 'prev') => { + if (direction === 'next' && currentScreenshotIndex < screenshotKeys.length - 1) { + setCurrentScreenshotIndex(currentScreenshotIndex + 1); + } else if (direction === 'prev' && currentScreenshotIndex > 0) { + setCurrentScreenshotIndex(currentScreenshotIndex - 1); + } + }; + const renderDataTable = ( data: any[], columns: string[], @@ -789,11 +807,90 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe )} - {/* {hasScreenshots && ( + {hasScreenshots && ( <> - {renderPaginatedScreenshots()} + + } + aria-controls="screenshot-content" + id="screenshot-header" + > + + + + {t('run_content.captured_screenshot.title', 'Screenshots')} + + + + + + + + {screenshotKeys.length > 1 && ( + + + + + )} + + + + {screenshotKeys.length > 1 && ( + + )} + + {`Screenshot + + + + - )} */} + )} From b83fcb27f0f77b7e75a6335f09e141a87b177eb2 Mon Sep 17 00:00:00 2001 From: Rohit Date: Wed, 30 Apr 2025 21:05:05 +0530 Subject: [PATCH 47/55] feat: rm left space --- 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 f0074adb5..350dc3bbb 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -282,7 +282,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe id={`${title.toLowerCase()}-header`} > - + {title} From db256270c5f74a530532707674269f83898ce208 Mon Sep 17 00:00:00 2001 From: Rohit Date: Wed, 30 Apr 2025 21:39:05 +0530 Subject: [PATCH 48/55] feat: change translations --- public/locales/de.json | 9 ++++----- public/locales/en.json | 7 +++---- public/locales/es.json | 15 +++++++-------- public/locales/ja.json | 13 ++++++------- public/locales/zh.json | 7 +++---- 5 files changed, 23 insertions(+), 28 deletions(-) diff --git a/public/locales/de.json b/public/locales/de.json index 1256be22b..54c009f0c 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -543,16 +543,15 @@ "captured_data": { "title": "Erfasste Daten", "download_csv": "CSV herunterladen", - "download_all_json": "Gesamte JSON herunterladen", "view_full": "Vollständige Daten anzeigen", "items": "Elemente", - "schema_title": "Text erfassen", - "list_title": "Liste erfassen" + "schema_title": "Erfasste Texte", + "list_title": "Erfasste Listen" }, "captured_screenshot": { "title": "Erfasste Screenshots", - "download": "Screenshot herunterladen", - "render_failed": "Screenshot konnte nicht gerendert werden" + "download": "Herunterladen", + "render_failed": "Fehler beim Rendern des Screenshots" } }, "navbar": { diff --git a/public/locales/en.json b/public/locales/en.json index 531aa2e7e..4f765f6eb 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -564,15 +564,14 @@ "captured_data": { "title": "Captured Data", "download_csv": "Download CSV", - "download_all_json": "Download All JSON", "view_full": "View Full Data", "items": "items", - "schema_title": "Capture Text", - "list_title": "Capture List" + "schema_title": "Captured Texts", + "list_title": "Captured Lists" }, "captured_screenshot": { "title": "Captured Screenshots", - "download": "Download Screenshot", + "download": "Download", "render_failed": "Failed to render screenshot" } }, diff --git a/public/locales/es.json b/public/locales/es.json index b28fc5cfd..d2d487c13 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -542,18 +542,17 @@ "loading": "Cargando datos...", "empty_output": "No hay datos de salida disponibles", "captured_data": { - "title": "Datos Capturados", + "title": "Datos capturados", "download_csv": "Descargar CSV", - "download_all_json": "Descargar Todo JSON", - "view_full": "Ver Datos Completos", + "view_full": "Ver datos completos", "items": "elementos", - "schema_title": "Capturar Texto", - "list_title": "Capturar Lista" + "schema_title": "Textos capturados", + "list_title": "Listas capturadas" }, "captured_screenshot": { - "title": "Capturas de Pantalla", - "download": "Descargar Captura", - "render_failed": "Error al renderizar la captura" + "title": "Capturas de pantalla", + "download": "Descargar", + "render_failed": "Error al renderizar la captura de pantalla" } }, "navbar": { diff --git a/public/locales/ja.json b/public/locales/ja.json index 52fed4463..833494ee0 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -542,17 +542,16 @@ "loading": "データを読み込み中...", "empty_output": "出力データがありません", "captured_data": { - "title": "キャプチャされたデータ", + "title": "キャプチャしたデータ", "download_csv": "CSVをダウンロード", - "download_all_json": "すべてのJSONをダウンロード", - "view_full": "すべてのデータを表示", + "view_full": "完全なデータを表示", "items": "アイテム", - "schema_title": "テキストをキャプチャ", - "list_title": "リストをキャプチャ" + "schema_title": "キャプチャしたテキスト", + "list_title": "キャプチャしたリスト" }, "captured_screenshot": { - "title": "キャプチャされたスクリーンショット", - "download": "スクリーンショットをダウンロード", + "title": "キャプチャしたスクリーンショット", + "download": "ダウンロード", "render_failed": "スクリーンショットのレンダリングに失敗しました" } }, diff --git a/public/locales/zh.json b/public/locales/zh.json index b4512bb32..27bb1e635 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -544,15 +544,14 @@ "captured_data": { "title": "已捕获的数据", "download_csv": "下载CSV", - "download_all_json": "下载所有JSON", "view_full": "查看完整数据", "items": "项目", - "schema_title": "捕获文本", - "list_title": "捕获列表" + "schema_title": "已捕获的文本", + "list_title": "已捕获的列表" }, "captured_screenshot": { "title": "已捕获的截图", - "download": "下载截图", + "download": "下载", "render_failed": "渲染截图失败" } }, From 5cd756c74238d14651b2e9eaba8e6af184a7d97a Mon Sep 17 00:00:00 2001 From: Rohit Date: Wed, 30 Apr 2025 21:39:55 +0530 Subject: [PATCH 49/55] feat: rm card componenent --- src/components/run/RunContent.tsx | 186 ------------------------------ 1 file changed, 186 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 350dc3bbb..007e7f37e 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -9,11 +9,6 @@ import { Accordion, AccordionSummary, AccordionDetails, - Card, - CardHeader, - CardContent, - Grid, - IconButton, Chip, ButtonGroup } from "@mui/material"; @@ -375,187 +370,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe ); }; - const renderDataCard = ( - data: any[], - columns: string[], - title: string, - dataType: string, - csvFilename: string, - jsonFilename: string, - isPaginatedList: boolean = false - ) => { - if (!isPaginatedList && data.length === 0) return null; - if (isPaginatedList && (listData.length === 0 || currentListIndex >= listData.length)) return null; - - const currentData = isPaginatedList ? listData[currentListIndex] : data; - const currentColumns = isPaginatedList ? listColumns[currentListIndex] : columns; - - if (!currentData || currentData.length === 0) return null; - - const previewData = currentData.slice(0, 1); - const previewColumns = currentColumns.slice(0, 3); - - const showMoreColumns = currentColumns.length > 3; - - return ( - - - { - if (isPaginatedList) { - downloadCSV(currentData, currentColumns, `list_table_${currentListIndex + 1}.csv`); - } else { - downloadCSV(data, columns, csvFilename); - } - }} - title={t('run_content.captured_data.download_csv')} - > - - - { - if (isPaginatedList) { - downloadJSON(currentData, `list_table_${currentListIndex + 1}.json`); - } else { - downloadJSON(data, jsonFilename); - } - }} - title="Download JSON" - sx={{ mx: 0.5 }} - > - - - { - if (isPaginatedList) { - setExpandedView(`list-${currentListIndex}`); - } else { - setExpandedView(dataType); - } - }} - title={t('run_content.captured_data.view_full')} - > - - - - } - sx={{ pb: 1 }} - /> - - - {isPaginatedList ? ( - 1 - ? `Table ${currentListIndex + 1} of ${listData.length} (${currentData.length} ${currentData.length === 1 ? 'item' : 'items'})` - : `${currentData.length} ${currentData.length === 1 ? 'item' : 'items'}` - } - size="small" - sx={{ backgroundColor: '#FF00C3', color: 'white' }} - /> - ) : ( - - )} - - {isPaginatedList && listData.length > 1 && ( - - - - - )} - - -
- - - {previewColumns.map((column) => ( - {column} - ))} - {showMoreColumns && ...} - - - - {previewData.map((row, index) => ( - - {previewColumns.map((column) => ( - - {row[column] === undefined || row[column] === "" ? "-" : row[column]} - - ))} - {showMoreColumns && ...} - - ))} - {currentData.length > 1 && ( - - - - - - )} - -
-
- - - ); - }; - const renderExpandedView = (dataTypeWithIndex: string) => { if (expandedView !== dataTypeWithIndex) return null; From 3b618d8d5d59997f1378fd90937203d1ab1e14c7 Mon Sep 17 00:00:00 2001 From: Rohit Date: Wed, 30 Apr 2025 21:43:24 +0530 Subject: [PATCH 50/55] feat: rm screenshot items chip --- src/components/run/RunContent.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 007e7f37e..dbdfe41e3 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -681,13 +681,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe - {screenshotKeys.length > 1 && ( - - )} Date: Wed, 30 Apr 2025 21:59:51 +0530 Subject: [PATCH 51/55] feat: buttons ui change, rm render expand --- src/components/run/RunContent.tsx | 199 +++++++----------------------- 1 file changed, 43 insertions(+), 156 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index dbdfe41e3..374d75183 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -284,34 +284,44 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
- - - - + {isPaginatedList && listData.length > 1 && ( @@ -370,132 +380,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe ); }; - const renderExpandedView = (dataTypeWithIndex: string) => { - if (expandedView !== dataTypeWithIndex) return null; - - let data: any[] = []; - let columns: string[] = []; - let title = ""; - let csvFilename = ""; - let jsonFilename = ""; - - if (dataTypeWithIndex.startsWith('list-')) { - const indexStr = dataTypeWithIndex.split('-')[1]; - const index = parseInt(indexStr, 10); - - if (index >= 0 && index < listData.length) { - data = listData[index]; - columns = listColumns[index]; - title = `${t('run_content.captured_data.list_title')} - Table ${index + 1}`; - csvFilename = `list_table_${index + 1}.csv`; - jsonFilename = `list_table_${index + 1}.json`; - } - } else { - switch (dataTypeWithIndex) { - case 'schema': - data = schemaData; - columns = schemaColumns; - title = t('run_content.captured_data.schema_title'); - csvFilename = 'schema_data.csv'; - jsonFilename = 'schema_data.json'; - break; - case 'list': - if (listData.length > 0 && listColumns.length > 0) { - data = listData[currentListIndex]; - columns = listColumns[currentListIndex]; - } - title = t('run_content.captured_data.list_title'); - csvFilename = 'list_data.csv'; - jsonFilename = 'list_data.json'; - break; - case 'legacy': - data = legacyData; - columns = legacyColumns; - title = t('run_content.captured_data.title'); - csvFilename = 'data.csv'; - jsonFilename = 'data.json'; - break; - } - } - - return ( - - - - {title} - - - - - - - - - - - - - - {columns.map((column) => ( - {column} - ))} - - - - {data.map((row, index) => ( - - {columns.map((column) => ( - - {row[column] === undefined || row[column] === "" ? "-" : row[column]} - - ))} - - ))} - -
-
-
-
- ); - }; - const hasData = schemaData.length > 0 || listData.length > 0 || legacyData.length > 0; const hasScreenshots = row.binaryOutput && Object.keys(row.binaryOutput).length > 0; @@ -613,11 +497,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe )} )} - - {renderExpandedView('schema')} - {renderExpandedView('legacy')} - - {listData.map((_, index) => renderExpandedView(`list-${index}`))} )} @@ -630,8 +509,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe id="screenshot-header" > - - + {t('run_content.captured_screenshot.title', 'Screenshots')} @@ -639,11 +517,20 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe From daa9779cb1d43454ba82212cd26a760323786535 Mon Sep 17 00:00:00 2001 From: Rohit Date: Wed, 30 Apr 2025 22:00:50 +0530 Subject: [PATCH 52/55] feat: rm unnecessray imports --- src/components/run/RunContent.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 374d75183..e554eaacd 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -9,18 +9,13 @@ import { Accordion, AccordionSummary, AccordionDetails, - Chip, ButtonGroup } from "@mui/material"; import Highlight from "react-highlight"; import * as React from "react"; import { Data } from "./RunsTable"; import { TabPanel, TabContext } from "@mui/lab"; -import ImageIcon from '@mui/icons-material/Image'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import DownloadIcon from '@mui/icons-material/Download'; -import FullscreenIcon from '@mui/icons-material/Fullscreen'; -import DataObjectIcon from '@mui/icons-material/DataObject'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import { useEffect, useState } from "react"; @@ -55,8 +50,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const [screenshotKeys, setScreenshotKeys] = useState([]); const [currentScreenshotIndex, setCurrentScreenshotIndex] = useState(0); - const [expandedView, setExpandedView] = useState(null); - const [legacyData, setLegacyData] = useState([]); const [legacyColumns, setLegacyColumns] = useState([]); const [isLegacyData, setIsLegacyData] = useState(false); From 7b6de7db8d90e91a0b7a8fc93817ce5a3b59eb01 Mon Sep 17 00:00:00 2001 From: Rohit Date: Wed, 30 Apr 2025 22:08:39 +0530 Subject: [PATCH 53/55] feat: fix download ss logic --- src/components/run/RunContent.tsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index e554eaacd..faa425d32 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -510,9 +510,22 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe