From f360761e5bd607729d3f5fc510bd25e2d5077903 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 17 Nov 2024 13:59:21 +0530 Subject: [PATCH 01/23] feat: add robot duplication modal --- src/components/molecules/RobotDuplicate.tsx | 152 ++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 src/components/molecules/RobotDuplicate.tsx diff --git a/src/components/molecules/RobotDuplicate.tsx b/src/components/molecules/RobotDuplicate.tsx new file mode 100644 index 00000000..67b97e97 --- /dev/null +++ b/src/components/molecules/RobotDuplicate.tsx @@ -0,0 +1,152 @@ +import React, { useState, useEffect } from 'react'; +import { GenericModal } from "../atoms/GenericModal"; +import { TextField, Typography, Box, Button, Chip } from "@mui/material"; +import { modalStyle } from "./AddWhereCondModal"; +import { useGlobalInfoStore } from '../../context/globalInfo'; +import { duplicateRecording, getStoredRecording } from '../../api/storage'; +import { WhereWhatPair } from 'maxun-core'; +import { getUserById } from "../../api/auth"; + +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 [robot, setRobot] = useState(null); + const [targetUrl, setTargetUrl] = useState(''); + const { recordingId, notify } = useGlobalInfoStore(); + + useEffect(() => { + if (isOpen) { + getRobot(); + } + }, [isOpen]); + + useEffect(() => { + // Update the targetUrl when the robot data is loaded + 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', 'Could not find robot details. Please try again.'); + } + } + + // const lastPair = robot?.recording.workflow[robot?.recording.workflow.length - 1]; + + // // Find the `goto` action in `what` and retrieve its arguments + // const targetUrl = lastPair?.what.find(action => action.action === "goto")?.args?.[0]; + + const handleTargetUrlChange = (e: React.ChangeEvent) => { + setTargetUrl(e.target.value); + }; + + const handleSave = async () => { + if (!robot || !targetUrl) { + notify('error', 'Target URL is required.'); + return; + } + + try { + const success = await duplicateRecording(robot.recording_meta.id, targetUrl); + + if (success) { + notify('success', 'Target URL updated successfully.'); + handleStart(robot); // Inform parent about the updated robot + handleClose(); // Close the modal + + window.location.reload(); + } else { + notify('error', 'Failed to update the Target URL. Please try again.'); + } + } catch (error) { + notify('error', 'An error occurred while updating the Target URL.'); + console.error('Error updating Target URL:', error); + } + }; + + return ( + + <> + Robot Settings + + { + robot && ( + <> + + + + + + + ) + } + + + + ); +}; From 1c225f5988e9433514d3f2d1b467e9d9cd31d50a Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 17 Nov 2024 14:02:23 +0530 Subject: [PATCH 02/23] feat: add robot duplication modal in recordings table --- src/components/molecules/RecordingsTable.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/molecules/RecordingsTable.tsx b/src/components/molecules/RecordingsTable.tsx index 84df8d0e..f309dcc8 100644 --- a/src/components/molecules/RecordingsTable.tsx +++ b/src/components/molecules/RecordingsTable.tsx @@ -91,9 +91,10 @@ 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; } -export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handleScheduleRecording, handleIntegrateRecording, handleSettingsRecording, handleEditRobot }: RecordingsTableProps) => { +export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handleScheduleRecording, handleIntegrateRecording, handleSettingsRecording, handleEditRobot, handleDuplicateRobot }: RecordingsTableProps) => { const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(10); const [rows, setRows] = React.useState([]); @@ -264,8 +265,7 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl }) }} handleDuplicate={() => { - notify('info', 'Duplicating recording...'); - // Implement duplication logic here + handleDuplicateRobot(row.id, row.name, row.params || []); }} /> From 3b210b75ba1d4b24200de99ee1e1f8b00e860896 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 17 Nov 2024 14:03:47 +0530 Subject: [PATCH 03/23] feat: add route for robot duplication --- server/src/routes/storage.ts | 74 ++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index 7a356b27..91272663 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -145,6 +145,80 @@ 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().toISOString(); + + 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.' }); + } + } +}); + + + /** * DELETE endpoint for deleting a recording from the storage. From 4ba986aaf7c2aee00c6f03c69174121c5e606d56 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 17 Nov 2024 14:05:52 +0530 Subject: [PATCH 04/23] feat: add duplicate recording api --- src/api/storage.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/api/storage.ts b/src/api/storage.ts index 0ad51e77..90ab91af 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -33,6 +33,22 @@ export const updateRecording = async (id: string, data: { name?: string; limit?: } }; +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`); From 7a0a23ad282180da9ba5d430cafc41b53d17776c Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 17 Nov 2024 14:06:26 +0530 Subject: [PATCH 05/23] feat: add robot duplication modal --- src/components/organisms/Recordings.tsx | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/components/organisms/Recordings.tsx b/src/components/organisms/Recordings.tsx index ecb2b769..495a25f8 100644 --- a/src/components/organisms/Recordings.tsx +++ b/src/components/organisms/Recordings.tsx @@ -6,6 +6,7 @@ import { ScheduleSettings, ScheduleSettingsModal } from "../molecules/ScheduleSe import { IntegrationSettings, IntegrationSettingsModal } from "../molecules/IntegrationSettings"; import { RobotSettings, RobotSettingsModal } from "../molecules/RobotSettings"; import { RobotEditModal } from '../molecules/RobotEdit'; +import { RobotDuplicationModal } from '../molecules/RobotDuplicate'; interface RecordingsProps { handleEditRecording: (id: string, fileName: string) => void; @@ -20,11 +21,13 @@ export const Recordings = ({ handleEditRecording, handleRunRecording, setRecordi const [integrateSettingsAreOpen, setIntegrateSettingsAreOpen] = useState(false); const [robotSettingsAreOpen, setRobotSettingsAreOpen] = useState(false); const [robotEditAreOpen, setRobotEditAreOpen] = useState(false); + const [robotDuplicateAreOpen, setRobotDuplicateAreOpen] = useState(false); const [params, setParams] = useState([]); const [selectedRecordingId, setSelectedRecordingId] = useState(''); const handleIntegrateRecording = (id: string, settings: IntegrationSettings) => {}; const handleSettingsRecording = (id: string, settings: RobotSettings) => {}; const handleEditRobot = (id: string, settings: RobotSettings) => {}; + const handleDuplicateRobot = (id: string, settings: RobotSettings) => {}; const handleSettingsAndIntegrate = (id: string, name: string, params: string[]) => { if (params.length === 0) { @@ -91,6 +94,19 @@ export const Recordings = ({ handleEditRecording, handleRunRecording, setRecordi } } + const handleDuplicateRobotOption = (id: string, name: string, params: string[]) => { + if (params.length === 0) { + setRobotDuplicateAreOpen(true); + setRecordingInfo(id, name); + setSelectedRecordingId(id); + } else { + setParams(params); + setRobotDuplicateAreOpen(true); + setRecordingInfo(id, name); + setSelectedRecordingId(id); + } + } + const handleClose = () => { setParams([]); setRunSettingsAreOpen(false); @@ -126,6 +142,13 @@ export const Recordings = ({ handleEditRecording, handleRunRecording, setRecordi setSelectedRecordingId(''); } + const handleRobotDuplicateClose = () => { + setParams([]); + setRobotDuplicateAreOpen(false); + setRecordingInfo('', ''); + setSelectedRecordingId(''); + } + return ( handleEditRobot(selectedRecordingId,settings)} /> + handleDuplicateRobot(selectedRecordingId, settings)} + /> From 86b4fb29ba5324044cdc7cf765b900346b101ce0 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 18 Nov 2024 21:26:23 +0530 Subject: [PATCH 06/23] fix(temp): -rm expiration logic --- src/context/auth.tsx | 69 +++++++------------------------------------- 1 file changed, 11 insertions(+), 58 deletions(-) diff --git a/src/context/auth.tsx b/src/context/auth.tsx index 94cd9fda..5c46e4de 100644 --- a/src/context/auth.tsx +++ b/src/context/auth.tsx @@ -1,7 +1,6 @@ import { useReducer, createContext, useEffect } from 'react'; import axios from 'axios'; import { useNavigate } from 'react-router-dom'; -import { jwtDecode } from 'jwt-decode'; import { apiUrl } from "../apiConfig"; interface AuthProviderProps { @@ -51,74 +50,28 @@ const AuthProvider = ({ children }: AuthProviderProps) => { const navigate = useNavigate(); axios.defaults.withCredentials = true; - const logoutUser = () => { - dispatch({ type: 'LOGOUT' }); - window.localStorage.removeItem('user'); - window.localStorage.removeItem('logoutTimeout'); - navigate('/login'); - }; - - const checkTokenExpiration = (token: string) => { - if (!token) return; - - try { - const decodedToken: any = jwtDecode(token); - const currentTime = Date.now(); - const tokenExpiryTime = decodedToken.exp * 1000; - - if (tokenExpiryTime > currentTime) { - // Calculate remaining time until token expires - const remainingTime = tokenExpiryTime - currentTime; - - // Check if a logout timeout already exists in local storage - const existingTimeout = window.localStorage.getItem('logoutTimeout'); - - if (!existingTimeout) { - // Set a timeout for auto-logout - const timeoutId = setTimeout(logoutUser, remainingTime); - window.localStorage.setItem('logoutTimeout', JSON.stringify(timeoutId)); - } - } else { - logoutUser(); // Immediately logout if token is expired - } - } catch (error) { - console.error("Error decoding token:", error); - logoutUser(); - } - }; - useEffect(() => { const storedUser = window.localStorage.getItem('user'); if (storedUser) { - const userData = JSON.parse(storedUser); - dispatch({ type: 'LOGIN', payload: userData }); - - // Run expiration check only if a token exists - if (userData.token) { - checkTokenExpiration(userData.token); - } + dispatch({ type: 'LOGIN', payload: JSON.parse(storedUser) }); } - // Clean up timeout on component unmount - return () => { - const timeoutId = window.localStorage.getItem('logoutTimeout'); - if (timeoutId) { - clearTimeout(JSON.parse(timeoutId)); - window.localStorage.removeItem('logoutTimeout'); - } - }; - }, []); // Only run this effect once on mount + }, []); axios.interceptors.response.use( - (response) => response, - (error) => { + function (response) { + return response; + }, + function (error) { const res = error.response; - if (res?.status === 401 && !res.config.__isRetryRequest) { + if (res.status === 401 && res.config && !res.config.__isRetryRequest) { return new Promise((resolve, reject) => { axios .get(`${apiUrl}/auth/logout`) .then(() => { console.log('/401 error > logout'); - logoutUser(); + dispatch({ type: 'LOGOUT' }); + window.localStorage.removeItem('user'); + navigate('/login'); }) .catch((err) => { console.error('AXIOS INTERCEPTORS ERROR:', err); @@ -129,7 +82,7 @@ const AuthProvider = ({ children }: AuthProviderProps) => { return Promise.reject(error); } ); - + return ( {children} From 042c8d447b39517793e49be44c33c61aa4792e6f Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 18 Nov 2024 21:38:13 +0530 Subject: [PATCH 07/23] feat: add duplicate recording api --- src/api/storage.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/storage.ts b/src/api/storage.ts index 90ab91af..b4d24b9f 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -35,6 +35,7 @@ export const updateRecording = async (id: string, data: { name?: string; limit?: export const duplicateRecording = async (id: string, targetUrl: string): Promise => { try { + console.log("duplicating"); const response = await axios.post(`${apiUrl}/storage/recordings/${id}/duplicate`, { targetUrl, }); From 7d34f18156b6f60ee4aaa29d3b1c3286014ad6c6 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 18 Nov 2024 21:51:56 +0530 Subject: [PATCH 08/23] chore: remove console log --- src/api/storage.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/storage.ts b/src/api/storage.ts index b4d24b9f..90ab91af 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -35,7 +35,6 @@ export const updateRecording = async (id: string, data: { name?: string; limit?: export const duplicateRecording = async (id: string, targetUrl: string): Promise => { try { - console.log("duplicating"); const response = await axios.post(`${apiUrl}/storage/recordings/${id}/duplicate`, { targetUrl, }); From cf5b2af62fea6dad8213a43e019d3524708aca4a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 18 Nov 2024 22:11:55 +0530 Subject: [PATCH 09/23] fix(temp): expiry --- server/src/routes/auth.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 88c27d27..96f4769a 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -44,9 +44,7 @@ router.post("/register", async (req, res) => { return res.status(500).send("Internal Server Error"); } - const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET as string, { - expiresIn: "12h", - }); + const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET as string); user.password = undefined as unknown as string; res.cookie("token", token, { httpOnly: true, @@ -78,9 +76,7 @@ router.post("/login", async (req, res) => { const match = await comparePassword(password, user.password); if (!match) return res.status(400).send("Invalid email or password"); - const token = jwt.sign({ id: user?.id }, process.env.JWT_SECRET as string, { - expiresIn: "12h", - }); + const token = jwt.sign({ id: user?.id }, process.env.JWT_SECRET as string); // return user and token to client, exclude hashed password if (user) { @@ -371,8 +367,7 @@ router.get( // Generate JWT token for session const jwtToken = jwt.sign( { userId: user.id }, - process.env.JWT_SECRET as string, - { expiresIn: "12h" } + process.env.JWT_SECRET as string ); res.cookie("token", jwtToken, { httpOnly: true }); From 50e3cac63d42820f6237a41302025652751286a0 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 18 Nov 2024 22:15:04 +0530 Subject: [PATCH 10/23] feat: add robot edit modal --- src/components/molecules/RobotEdit.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/RobotEdit.tsx b/src/components/molecules/RobotEdit.tsx index f35e1048..b7e12ed5 100644 --- a/src/components/molecules/RobotEdit.tsx +++ b/src/components/molecules/RobotEdit.tsx @@ -55,10 +55,11 @@ interface RobotSettingsProps { handleStart: (settings: RobotSettings) => void; handleClose: () => void; initialSettings?: RobotSettings | null; - + } export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettings }: RobotSettingsProps) => { + console.log("robot edit"); const [robot, setRobot] = useState(null); // const [settings, setSettings] = useState({ // name: '', From 458243545c17e1b2dfa17265d5c1bacc7363f483 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 18 Nov 2024 22:17:24 +0530 Subject: [PATCH 11/23] feat: add robot duplicate modal --- src/components/molecules/RobotDuplicate.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/molecules/RobotDuplicate.tsx b/src/components/molecules/RobotDuplicate.tsx index 67b97e97..d1980da3 100644 --- a/src/components/molecules/RobotDuplicate.tsx +++ b/src/components/molecules/RobotDuplicate.tsx @@ -97,6 +97,8 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia return; } + console.log("handle save"); + try { const success = await duplicateRecording(robot.recording_meta.id, targetUrl); From fd8220fe510e7dc1c9d25112b2ab6d3ee6d59974 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 19 Nov 2024 00:06:39 +0530 Subject: [PATCH 12/23] feat: rename to Duplicate Robot --- src/components/molecules/RobotDuplicate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/RobotDuplicate.tsx b/src/components/molecules/RobotDuplicate.tsx index d1980da3..ae963e6b 100644 --- a/src/components/molecules/RobotDuplicate.tsx +++ b/src/components/molecules/RobotDuplicate.tsx @@ -124,7 +124,7 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia modalStyle={modalStyle} > <> - Robot Settings + Duplicate Robot { robot && ( From 796e09c345926869fc180442206a59520ba1d61e Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 19 Nov 2024 00:08:12 +0530 Subject: [PATCH 13/23] feat: add duplicate robot instructions --- src/components/molecules/RobotDuplicate.tsx | 23 ++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/components/molecules/RobotDuplicate.tsx b/src/components/molecules/RobotDuplicate.tsx index ae963e6b..c792c3a0 100644 --- a/src/components/molecules/RobotDuplicate.tsx +++ b/src/components/molecules/RobotDuplicate.tsx @@ -96,17 +96,17 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia notify('error', 'Target URL is required.'); return; } - + console.log("handle save"); try { const success = await duplicateRecording(robot.recording_meta.id, targetUrl); - + if (success) { notify('success', 'Target URL updated successfully.'); handleStart(robot); // Inform parent about the updated robot handleClose(); // Close the modal - + window.location.reload(); } else { notify('error', 'Failed to update the Target URL. Please try again.'); @@ -116,7 +116,7 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia console.error('Error updating Target URL:', error); } }; - + return ( <> - Duplicate Robot + Duplicate Robot { robot && ( <> + + Easily duplicate your robots to handle pages with a similar structure. For instance: + + If you've created a robot for producthunt.com/topics/api, you can duplicate it to scrape similar pages like producthunt.com/topics/database without starting from scratch. + How it works: + + Select a robot you want to duplicate. + Use the duplication feature to create a copy of the robot. + Customize the robot if needed, ensuring it fits the new page structure. + ⚠️ Ensure the new page has a similar structure to the original for seamless functionality. + + This feature saves you time and effort while keeping your workflow streamlined for similar pages. + Date: Tue, 19 Nov 2024 00:08:31 +0530 Subject: [PATCH 14/23] feat: remove beta tag --- src/components/molecules/RobotDuplicate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/RobotDuplicate.tsx b/src/components/molecules/RobotDuplicate.tsx index c792c3a0..f414acf7 100644 --- a/src/components/molecules/RobotDuplicate.tsx +++ b/src/components/molecules/RobotDuplicate.tsx @@ -124,7 +124,7 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia modalStyle={modalStyle} > <> - Duplicate Robot + Duplicate Robot { robot && ( From c50f4b40c12020f39acabc7f8d786d72d240d2ad Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 19 Nov 2024 00:11:32 +0530 Subject: [PATCH 15/23] feat: remove how it works --- src/components/molecules/RobotDuplicate.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/components/molecules/RobotDuplicate.tsx b/src/components/molecules/RobotDuplicate.tsx index f414acf7..8f28ca1d 100644 --- a/src/components/molecules/RobotDuplicate.tsx +++ b/src/components/molecules/RobotDuplicate.tsx @@ -130,17 +130,15 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia robot && ( <> - Easily duplicate your robots to handle pages with a similar structure. For instance: - - If you've created a robot for producthunt.com/topics/api, you can duplicate it to scrape similar pages like producthunt.com/topics/database without starting from scratch. - How it works: - - Select a robot you want to duplicate. - Use the duplication feature to create a copy of the robot. - Customize the robot if needed, ensuring it fits the new page structure. + Easily duplicate your robots to handle pages with a similar structure. +
+ For instance: + If you've created a robot for producthunt.com/topics/api, you can duplicate it to scrape similar pages + like producthunt.com/topics/database without starting from scratch. +
+ + ⚠️ Ensure the new page has a similar structure to the original for seamless functionality. - - This feature saves you time and effort while keeping your workflow streamlined for similar pages. Date: Tue, 19 Nov 2024 00:12:38 +0530 Subject: [PATCH 16/23] feat: wrap in --- src/components/molecules/RobotDuplicate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/RobotDuplicate.tsx b/src/components/molecules/RobotDuplicate.tsx index 8f28ca1d..49c69342 100644 --- a/src/components/molecules/RobotDuplicate.tsx +++ b/src/components/molecules/RobotDuplicate.tsx @@ -138,7 +138,7 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia - ⚠️ Ensure the new page has a similar structure to the original for seamless functionality. + ⚠️ Ensure the new page has the same structure as the original page. Date: Tue, 19 Nov 2024 00:16:06 +0530 Subject: [PATCH 17/23] feat: add example --- src/components/molecules/RobotDuplicate.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/molecules/RobotDuplicate.tsx b/src/components/molecules/RobotDuplicate.tsx index 49c69342..786780ad 100644 --- a/src/components/molecules/RobotDuplicate.tsx +++ b/src/components/molecules/RobotDuplicate.tsx @@ -129,14 +129,13 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia { robot && ( <> + When to duplicate robots? +
- Easily duplicate your robots to handle pages with a similar structure. -
- For instance: - If you've created a robot for producthunt.com/topics/api, you can duplicate it to scrape similar pages - like producthunt.com/topics/database without starting from scratch. + Example: If you've created a robot for producthunt.com/topics/api, you can duplicate it to scrape similar pages + like producthunt.com/topics/database without training a robot from scratch.
- +
⚠️ Ensure the new page has the same structure as the original page. @@ -145,7 +144,7 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia key="Robot Target URL" value={targetUrl} onChange={handleTargetUrlChange} - style={{ marginBottom: '20px' }} + style={{ marginBottom: '20px', marginTop: '30px' }} />