From 6acab57aeff162fd613912dcb0d9f6b462bbdfb4 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 6 Jan 2026 13:32:18 +0530 Subject: [PATCH 1/2] chore: deprecate robot duplication --- src/components/robot/Recordings.tsx | 7 ------- src/components/robot/RecordingsTable.tsx | 17 +++-------------- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/src/components/robot/Recordings.tsx b/src/components/robot/Recordings.tsx index 44dc4df6..bcdb5d7a 100644 --- a/src/components/robot/Recordings.tsx +++ b/src/components/robot/Recordings.tsx @@ -9,7 +9,6 @@ import { import { RobotIntegrationPage } from "./pages/RobotIntegrationPage"; import { RobotSettingsPage } from "./pages/RobotSettingsPage"; import { RobotEditPage } from "./pages/RobotEditPage"; -import { RobotDuplicatePage } from "./pages/RobotDuplicatePage"; import { useNavigate, useLocation, useParams } from "react-router-dom"; import { useGlobalInfoStore } from "../../context/globalInfo"; import { useTranslation } from "react-i18next"; @@ -106,8 +105,6 @@ export const Recordings = ({ return {}} />; } else if (currentPath.endsWith("/edit")) { return {}} />; - } else if (currentPath.endsWith("/duplicate")) { - return {}} />; } return null; }; @@ -118,7 +115,6 @@ export const Recordings = ({ currentPath.includes("/integrate") || currentPath.includes("/settings") || currentPath.includes("/edit") || - currentPath.includes("/duplicate") || currentPath.includes("/run"); if (isConfigPage) { @@ -146,9 +142,6 @@ export const Recordings = ({ handleEditRobot={(id, name, params) => handleNavigate(`/robots/${id}/edit`, id, name, params) } - handleDuplicateRobot={(id, name, params) => - handleNavigate(`/robots/${id}/duplicate`, id, name, params) - } /> diff --git a/src/components/robot/RecordingsTable.tsx b/src/components/robot/RecordingsTable.tsx index 1d748a4d..841dc220 100644 --- a/src/components/robot/RecordingsTable.tsx +++ b/src/components/robot/RecordingsTable.tsx @@ -77,7 +77,6 @@ interface RecordingsTableProps { handleIntegrateRecording: (id: string, fileName: string, params: string[]) => void; handleSettingsRecording: (id: string, fileName: string, params: string[]) => void; handleEditRobot: (id: string, name: string, params: string[]) => void; - handleDuplicateRobot: (id: string, name: string, params: string[]) => void; } const LoadingRobotRow = memo(({ row, columns }: any) => { @@ -155,7 +154,6 @@ const TableRowMemoized = memo(({ row, columns, handlers }: any) => { handlers.handleRetrainRobot(row.id, row.name)} handleEdit={() => handlers.handleEditRobot(row.id, row.name, row.params || [])} - handleDuplicate={() => handlers.handleDuplicateRobot(row.id, row.name, row.params || [])} handleDelete={() => handlers.handleDelete(row.id)} robotType={row.type} /> @@ -184,7 +182,7 @@ export const RecordingsTable = ({ handleIntegrateRecording, handleSettingsRecording, handleEditRobot, - handleDuplicateRobot }: RecordingsTableProps) => { + }: RecordingsTableProps) => { const { t } = useTranslation(); const theme = useTheme(); const [page, setPage] = React.useState(0); @@ -506,10 +504,9 @@ export const RecordingsTable = ({ handleIntegrateRecording, handleSettingsRecording, handleEditRobot, - handleDuplicateRobot, handleRetrainRobot, handleDelete: async (id: string) => openDeleteConfirm(id) - }), [handleRunRecording, handleScheduleRecording, handleIntegrateRecording, handleSettingsRecording, handleEditRobot, handleDuplicateRobot, handleRetrainRobot, notify, t, refetch]); + }), [handleRunRecording, handleScheduleRecording, handleIntegrateRecording, handleSettingsRecording, handleEditRobot, handleRetrainRobot, notify, t, refetch]); return ( @@ -777,11 +774,10 @@ interface OptionsButtonProps { handleRetrain: () => void; handleEdit: () => void; handleDelete: () => void; - handleDuplicate: () => void; robotType: string; } -const OptionsButton = ({ handleRetrain, handleEdit, handleDelete, handleDuplicate, robotType }: OptionsButtonProps) => { +const OptionsButton = ({ handleRetrain, handleEdit, handleDelete, robotType }: OptionsButtonProps) => { const [anchorEl, setAnchorEl] = React.useState(null); const handleClick = (event: React.MouseEvent) => { @@ -826,13 +822,6 @@ const OptionsButton = ({ handleRetrain, handleEdit, handleDelete, handleDuplicat Delete - - {robotType !== 'scrape' && ( - { handleDuplicate(); handleClose(); }}> - - Duplicate - - )} From 8428f839c102e237cdb2150fc9317f96951d944a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 6 Jan 2026 13:34:38 +0530 Subject: [PATCH 2/2] chore: deprecate robot duplication --- legacy/src/RobotDuplicate.tsx | 172 --------------- server/src/routes/storage.ts | 73 ------- src/api/storage.ts | 16 -- .../robot/pages/RobotDuplicatePage.tsx | 202 ------------------ 4 files changed, 463 deletions(-) delete mode 100644 legacy/src/RobotDuplicate.tsx delete mode 100644 src/components/robot/pages/RobotDuplicatePage.tsx diff --git a/legacy/src/RobotDuplicate.tsx b/legacy/src/RobotDuplicate.tsx deleted file mode 100644 index bee1ef5b..00000000 --- a/legacy/src/RobotDuplicate.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { GenericModal } from "../ui/GenericModal"; -import { TextField, Typography, Box, Button } from "@mui/material"; -import { modalStyle } from "../recorder/AddWhereCondModal"; -import { useGlobalInfoStore } from '../../context/globalInfo'; -import { duplicateRecording, getStoredRecording } from '../../api/storage'; -import { WhereWhatPair } from 'maxun-core'; -import { useTranslation } from 'react-i18next'; - -interface RobotMeta { - name: string; - id: string; - createdAt: string; - pairs: number; - updatedAt: string; - params: any[]; -} - -interface RobotWorkflow { - workflow: WhereWhatPair[]; -} - -interface ScheduleConfig { - runEvery: number; - runEveryUnit: 'MINUTES' | 'HOURS' | 'DAYS' | 'WEEKS' | 'MONTHS'; - startFrom: 'SUNDAY' | 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY'; - atTimeStart?: string; - atTimeEnd?: string; - timezone: string; - lastRunAt?: Date; - nextRunAt?: Date; - cronExpression?: string; -} - -export interface RobotSettings { - id: string; - userId?: number; - recording_meta: RobotMeta; - recording: RobotWorkflow; - google_sheet_email?: string | null; - google_sheet_name?: string | null; - google_sheet_id?: string | null; - google_access_token?: string | null; - google_refresh_token?: string | null; - schedule?: ScheduleConfig | null; -} - -interface RobotSettingsProps { - isOpen: boolean; - handleStart: (settings: RobotSettings) => void; - handleClose: () => void; - initialSettings?: RobotSettings | null; - -} - -export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initialSettings }: RobotSettingsProps) => { - const { t } = useTranslation(); - const [targetUrl, setTargetUrl] = useState(''); - const [robot, setRobot] = useState(null); - const { recordingId, notify, setRerenderRobots } = useGlobalInfoStore(); - - useEffect(() => { - if (isOpen) { - getRobot(); - } - }, [isOpen]); - - useEffect(() => { - if (robot) { - const lastPair = robot?.recording.workflow[robot?.recording.workflow.length - 1]; - const url = lastPair?.what.find(action => action.action === "goto")?.args?.[0]; - setTargetUrl(url); - } - }, [robot]); - - const getRobot = async () => { - if (recordingId) { - const robot = await getStoredRecording(recordingId); - setRobot(robot); - } else { - notify('error', t('robot_duplication.notifications.robot_not_found')); - } - } - - const handleTargetUrlChange = (e: React.ChangeEvent) => { - setTargetUrl(e.target.value); - }; - - const handleSave = async () => { - if (!robot || !targetUrl) { - notify('error', t('robot_duplication.notifications.url_required')); - return; - } - - try { - const success = await duplicateRecording(robot.recording_meta.id, targetUrl); - - if (success) { - setRerenderRobots(true); - - notify('success', t('robot_duplication.notifications.duplicate_success')); - handleStart(robot); - handleClose(); - } else { - notify('error', t('robot_duplication.notifications.duplicate_error')); - } - } catch (error) { - notify('error', t('robot_duplication.notifications.unknown_error')); - console.error('Error updating Target URL:', error); - } - }; - - return ( - - <> - - {t('robot_duplication.title')} - - - { - robot && ( - <> - - {t('robot_duplication.descriptions.purpose')} - -
- producthunt.com/topics/api', - url2: 'producthunt.com/topics/database' - }) - }} /> -
- - {t('robot_duplication.descriptions.warning')} - - - - - - - - ) - } -
- -
- ); -}; diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index 9a7a5f3c..53e8f917 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -355,79 +355,6 @@ router.put('/recordings/:id', requireSignIn, async (req: AuthenticatedRequest, r } }); - -/** - * POST endpoint to duplicate a robot and update its target URL. - */ -router.post('/recordings/:id/duplicate', requireSignIn, async (req: AuthenticatedRequest, res) => { - try { - const { id } = req.params; - const { targetUrl } = req.body; - - if (!targetUrl) { - return res.status(400).json({ error: 'The "targetUrl" field is required.' }); - } - - const originalRobot = await Robot.findOne({ where: { 'recording_meta.id': id } }); - - if (!originalRobot) { - return res.status(404).json({ error: 'Original robot not found.' }); - } - - const lastWord = targetUrl.split('/').filter(Boolean).pop() || 'Unnamed'; - - const workflow = originalRobot.recording.workflow.map((step) => { - if (step.where?.url && step.where.url !== "about:blank") { - step.where.url = targetUrl; - } - - step.what.forEach((action) => { - if (action.action === "goto" && action.args?.length) { - action.args[0] = targetUrl; - } - }); - - return step; - }); - - const currentTimestamp = new Date().toLocaleString(); - - const newRobot = await Robot.create({ - id: uuid(), - userId: originalRobot.userId, - recording_meta: { - ...originalRobot.recording_meta, - id: uuid(), - name: `${originalRobot.recording_meta.name} (${lastWord})`, - createdAt: currentTimestamp, - updatedAt: currentTimestamp, - }, - recording: { ...originalRobot.recording, workflow }, - google_sheet_email: null, - google_sheet_name: null, - google_sheet_id: null, - google_access_token: null, - google_refresh_token: null, - schedule: null, - }); - - logger.log('info', `Robot with ID ${id} duplicated successfully as ${newRobot.id}.`); - - return res.status(201).json({ - message: 'Robot duplicated and target URL updated successfully.', - robot: newRobot, - }); - } catch (error) { - if (error instanceof Error) { - logger.log('error', `Error duplicating robot with ID ${req.params.id}: ${error.message}`); - return res.status(500).json({ error: error.message }); - } else { - logger.log('error', `Unknown error duplicating robot with ID ${req.params.id}`); - return res.status(500).json({ error: 'An unknown error occurred.' }); - } - } -}); - /** * POST endpoint for creating a markdown robot */ diff --git a/src/api/storage.ts b/src/api/storage.ts index 75d53c48..5c281160 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -117,22 +117,6 @@ export const updateRecording = async (id: string, data: { } }; -export const duplicateRecording = async (id: string, targetUrl: string): Promise => { - try { - const response = await axios.post(`${apiUrl}/storage/recordings/${id}/duplicate`, { - targetUrl, - }); - if (response.status === 201) { - return response.data; // Returns the duplicated robot details - } else { - throw new Error(`Couldn't duplicate recording with id ${id}`); - } - } catch (error: any) { - console.error(`Error duplicating recording: ${error.message}`); - return null; - } -}; - export const getStoredRuns = async (): Promise => { try { const response = await axios.get(`${apiUrl}/storage/runs`); diff --git a/src/components/robot/pages/RobotDuplicatePage.tsx b/src/components/robot/pages/RobotDuplicatePage.tsx deleted file mode 100644 index 8607b41b..00000000 --- a/src/components/robot/pages/RobotDuplicatePage.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { - TextField, - Typography, - Box, - Button, -} from "@mui/material"; -import { useGlobalInfoStore } from "../../../context/globalInfo"; -import { - duplicateRecording, - getStoredRecording, - getStoredRecordings, -} from "../../../api/storage"; -import { WhereWhatPair } from "maxun-core"; -import { useTranslation } from "react-i18next"; -import { RobotConfigPage } from "./RobotConfigPage"; -import { useNavigate, useLocation } from "react-router-dom"; - -interface RobotMeta { - name: string; - id: string; - prebuiltId?: string; - createdAt: string; - pairs: number; - updatedAt: string; - params: any[]; - type?: 'extract' | 'scrape' | 'crawl' | 'search'; - url?: string; - formats?: ('markdown' | 'html' | 'screenshot-visible' | 'screenshot-fullpage')[]; - isLLM?: boolean; -} - -interface RobotWorkflow { - workflow: WhereWhatPair[]; -} - -interface ScheduleConfig { - runEvery: number; - runEveryUnit: "MINUTES" | "HOURS" | "DAYS" | "WEEKS" | "MONTHS"; - startFrom: - | "SUNDAY" - | "MONDAY" - | "TUESDAY" - | "WEDNESDAY" - | "THURSDAY" - | "FRIDAY" - | "SATURDAY"; - atTimeStart?: string; - atTimeEnd?: string; - timezone: string; - lastRunAt?: Date; - nextRunAt?: Date; - cronExpression?: string; -} - -export interface RobotSettings { - id: string; - userId?: number; - recording_meta: RobotMeta; - recording: RobotWorkflow; - google_sheet_email?: string | null; - google_sheet_name?: string | null; - google_sheet_id?: string | null; - google_access_token?: string | null; - google_refresh_token?: string | null; - schedule?: ScheduleConfig | null; -} - -interface RobotSettingsProps { - handleStart: (settings: RobotSettings) => void; -} - -export const RobotDuplicatePage = ({ handleStart }: RobotSettingsProps) => { - const { t } = useTranslation(); - const navigate = useNavigate(); - const location = useLocation(); - const [targetUrl, setTargetUrl] = useState(""); - const [robot, setRobot] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const { recordingId, notify, setRerenderRobots} = - useGlobalInfoStore(); - - useEffect(() => { - getRobot(); - }, []); - - useEffect(() => { - if (robot) { - let url = robot.recording_meta.url; - - if (!url) { - const lastPair = - robot?.recording.workflow[robot?.recording.workflow.length - 1]; - url = lastPair?.what.find((action) => action.action === "goto") - ?.args?.[0]; - } - - setTargetUrl(url); - } - }, [robot]); - - const getRobot = async () => { - if (recordingId) { - try { - const robot = await getStoredRecording(recordingId); - setRobot(robot); - } catch (error) { - notify("error", t("robot_duplication.notifications.robot_not_found")); - } - } else { - notify("error", t("robot_duplication.notifications.robot_not_found")); - } - }; - - const handleTargetUrlChange = (e: React.ChangeEvent) => { - setTargetUrl(e.target.value); - }; - - const handleSave = async () => { - if (!robot || !targetUrl) { - notify("error", t("robot_duplication.notifications.url_required")); - return; - } - - setIsLoading(true); - try { - const success = await duplicateRecording( - robot.recording_meta.id, - targetUrl - ); - - if (success) { - setRerenderRobots(true); - notify( - "success", - t("robot_duplication.notifications.duplicate_success") - ); - handleStart(robot); - const basePath = location.pathname.includes("/prebuilt-robots") - ? "/prebuilt-robots" - : "/robots"; - navigate(basePath); - } else { - notify("error", t("robot_duplication.notifications.duplicate_error")); - } - } catch (error) { - notify("error", t("robot_duplication.notifications.unknown_error")); - console.error("Error updating Target URL:", error); - } finally { - setIsLoading(false); - } - }; - - const handleCancel = () => { - const basePath = location.pathname.includes("/prebuilt-robots") - ? "/prebuilt-robots" - : "/robots"; - navigate(basePath); - }; - - return ( - - <> - - {robot && ( - <> - {t("robot_duplication.descriptions.purpose")} -
- producthunt.com/topics/api", - url2: "producthunt.com/topics/database", - }), - }} - /> -
- - {t("robot_duplication.descriptions.warning")} - - - - )} -
- -
- ); -}; \ No newline at end of file