Files
parcer/src/components/robot/pages/RobotEditPage.tsx
2026-01-19 13:09:00 +05:30

1125 lines
32 KiB
TypeScript

import { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import {
TextField,
Typography,
Box,
Button,
IconButton,
InputAdornment,
FormControl,
InputLabel,
Select,
MenuItem,
FormControlLabel,
Checkbox,
Collapse
} from "@mui/material";
import { Visibility, VisibilityOff } from "@mui/icons-material";
import { useGlobalInfoStore } from "../../../context/globalInfo";
import { getStoredRecording, updateRecording } from "../../../api/storage";
import { WhereWhatPair } from "maxun-core";
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;
}
interface CredentialInfo {
value: string;
type: string;
}
interface Credentials {
[key: string]: CredentialInfo;
}
interface CredentialVisibility {
[key: string]: boolean;
}
interface GroupedCredentials {
passwords: string[];
emails: string[];
usernames: string[];
others: string[];
}
interface ScrapeListLimit {
pairIndex: number;
actionIndex: number;
argIndex: number;
currentLimit: number;
}
interface CrawlConfig {
mode?: string;
limit?: number;
maxDepth?: number;
useSitemap?: boolean;
followLinks?: boolean;
excludePaths?: string[];
includePaths?: string[];
respectRobots?: boolean;
}
interface SearchConfig {
mode?: 'discover' | 'scrape';
limit?: number;
query?: string;
filters?: Record<string, any>;
provider?: string;
}
export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => {
const { t } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
const [credentials, setCredentials] = useState<Credentials>({});
const { recordingId, notify, setRerenderRobots } = useGlobalInfoStore();
const [robot, setRobot] = useState<RobotSettings | null>(null);
const [credentialGroups, setCredentialGroups] = useState<GroupedCredentials>({
passwords: [],
emails: [],
usernames: [],
others: [],
});
const [showPasswords, setShowPasswords] = useState<CredentialVisibility>({});
const [scrapeListLimits, setScrapeListLimits] = useState<ScrapeListLimit[]>(
[]
);
const [isLoading, setIsLoading] = useState(false);
const [crawlConfig, setCrawlConfig] = useState<CrawlConfig>({});
const [searchConfig, setSearchConfig] = useState<SearchConfig>({});
const [showCrawlAdvanced, setShowCrawlAdvanced] = useState(false);
const isEmailPattern = (value: string): boolean => {
return value.includes("@");
};
const isUsernameSelector = (selector: string): boolean => {
return (
selector.toLowerCase().includes("username") ||
selector.toLowerCase().includes("user") ||
selector.toLowerCase().includes("email")
);
};
const determineCredentialType = (
selector: string,
info: CredentialInfo
): "password" | "email" | "username" | "other" => {
if (
info.type === "password" ||
selector.toLowerCase().includes("password")
) {
return "password";
}
if (
isEmailPattern(info.value) ||
selector.toLowerCase().includes("email")
) {
return "email";
}
if (isUsernameSelector(selector)) {
return "username";
}
return "other";
};
useEffect(() => {
getRobot();
}, []);
useEffect(() => {
if (robot?.recording?.workflow) {
const extractedCredentials = extractInitialCredentials(
robot.recording.workflow
);
setCredentials(extractedCredentials);
setCredentialGroups(groupCredentialsByType(extractedCredentials));
findScrapeListLimits(robot.recording.workflow);
extractCrawlConfig(robot.recording.workflow);
extractSearchConfig(robot.recording.workflow);
}
}, [robot]);
const findScrapeListLimits = (workflow: WhereWhatPair[]) => {
const limits: ScrapeListLimit[] = [];
workflow.forEach((pair, pairIndex) => {
if (!pair.what) return;
pair.what.forEach((action, actionIndex) => {
if (
action.action === "scrapeList" &&
action.args &&
action.args.length > 0
) {
// Check if first argument has a limit property
const arg = action.args[0];
if (arg && typeof arg === "object" && "limit" in arg) {
limits.push({
pairIndex,
actionIndex,
argIndex: 0,
currentLimit: arg.limit,
});
}
}
});
});
setScrapeListLimits(limits);
};
const extractCrawlConfig = (workflow: WhereWhatPair[]) => {
workflow.forEach((pair) => {
if (!pair.what) return;
pair.what.forEach((action: any) => {
if (action.action === "crawl" && action.args && action.args.length > 0) {
const config = action.args[0];
if (config && typeof config === "object") {
setCrawlConfig(config as CrawlConfig);
}
}
});
});
};
const extractSearchConfig = (workflow: WhereWhatPair[]) => {
workflow.forEach((pair) => {
if (!pair.what) return;
pair.what.forEach((action: any) => {
if (action.action === "search" && action.args && action.args.length > 0) {
const config = action.args[0];
if (config && typeof config === "object") {
setSearchConfig(config as SearchConfig);
}
}
});
});
};
function extractInitialCredentials(workflow: any[]): Credentials {
const credentials: Credentials = {};
const isPrintableCharacter = (char: string): boolean => {
return char.length === 1 && !!char.match(/^[\x20-\x7E]$/);
};
workflow.forEach((step) => {
if (!step.what) return;
let currentSelector = "";
let currentValue = "";
let currentType = "";
let i = 0;
while (i < step.what.length) {
const action = step.what[i];
if (!action.action || !action.args?.[0]) {
i++;
continue;
}
const selector = action.args[0];
// Handle full word type actions first
if (
action.action === "type" &&
action.args?.length >= 2 &&
typeof action.args[1] === "string" &&
action.args[1].length > 1
) {
if (!credentials[selector]) {
credentials[selector] = {
value: action.args[1],
type: action.args[2] || "text",
};
}
i++;
continue;
}
// Handle character-by-character sequences (both type and press)
if (
(action.action === "type" || action.action === "press") &&
action.args?.length >= 2 &&
typeof action.args[1] === "string"
) {
if (selector !== currentSelector) {
if (currentSelector && currentValue) {
credentials[currentSelector] = {
value: currentValue,
type: currentType || "text",
};
}
currentSelector = selector;
currentValue = credentials[selector]?.value || "";
currentType =
action.args[2] || credentials[selector]?.type || "text";
}
const character = action.args[1];
if (isPrintableCharacter(character)) {
currentValue += character;
} else if (character === "Backspace") {
currentValue = currentValue.slice(0, -1);
}
if (!currentType && action.args[2]?.toLowerCase() === "password") {
currentType = "password";
}
let j = i + 1;
while (j < step.what.length) {
const nextAction = step.what[j];
if (
!nextAction.action ||
!nextAction.args?.[0] ||
nextAction.args[0] !== selector ||
(nextAction.action !== "type" && nextAction.action !== "press")
) {
break;
}
if (nextAction.args[1] === "Backspace") {
currentValue = currentValue.slice(0, -1);
} else if (isPrintableCharacter(nextAction.args[1])) {
currentValue += nextAction.args[1];
}
j++;
}
credentials[currentSelector] = {
value: currentValue,
type: currentType,
};
i = j;
} else {
i++;
}
}
if (currentSelector && currentValue) {
credentials[currentSelector] = {
value: currentValue,
type: currentType || "text",
};
}
});
return credentials;
}
const groupCredentialsByType = (
credentials: Credentials
): GroupedCredentials => {
return Object.entries(credentials).reduce(
(acc: GroupedCredentials, [selector, info]) => {
const credentialType = determineCredentialType(selector, info);
switch (credentialType) {
case "password":
acc.passwords.push(selector);
break;
case "email":
acc.emails.push(selector);
break;
case "username":
acc.usernames.push(selector);
break;
default:
acc.others.push(selector);
}
return acc;
},
{ passwords: [], emails: [], usernames: [], others: [] }
);
};
const getRobot = async () => {
if (recordingId) {
try {
const robot = await getStoredRecording(recordingId);
setRobot(robot);
} catch (error) {
notify("error", t("robot_edit.notifications.update_failed"));
}
} else {
notify("error", t("robot_edit.notifications.update_failed"));
}
};
const handleClickShowPassword = (selector: string) => {
setShowPasswords((prev) => ({
...prev,
[selector]: !prev[selector],
}));
};
const handleRobotNameChange = (newName: string) => {
setRobot((prev) =>
prev
? { ...prev, recording_meta: { ...prev.recording_meta, name: newName } }
: prev
);
};
const handleCredentialChange = (selector: string, value: string) => {
setCredentials((prev) => ({
...prev,
[selector]: {
...prev[selector],
value,
},
}));
};
const handleLimitChange = (
pairIndex: number,
actionIndex: number,
argIndex: number,
newLimit: number
) => {
setRobot((prev) => {
if (!prev) return prev;
const updatedWorkflow = [...prev.recording.workflow];
const pair = updatedWorkflow[pairIndex];
const action = pair?.what?.[actionIndex];
if (
updatedWorkflow.length > pairIndex &&
pair?.what &&
pair.what.length > actionIndex &&
action?.args &&
action.args.length > argIndex
) {
if (action.args[argIndex]) {
action.args[argIndex].limit = newLimit;
}
setScrapeListLimits((prev) => {
return prev.map((item) => {
if (
item.pairIndex === pairIndex &&
item.actionIndex === actionIndex &&
item.argIndex === argIndex
) {
return { ...item, currentLimit: newLimit };
}
return item;
});
});
}
return {
...prev,
recording: { ...prev.recording, workflow: updatedWorkflow },
};
});
};
const handleActionNameChange = (
pairIndex: number,
actionIndex: number,
newName: string
) => {
setRobot((prev) => {
if (!prev) return prev;
const updatedWorkflow = [...prev.recording.workflow];
if (
updatedWorkflow.length > pairIndex &&
updatedWorkflow[pairIndex]?.what &&
updatedWorkflow[pairIndex].what.length > actionIndex
) {
const action = { ...updatedWorkflow[pairIndex].what[actionIndex] };
// update the standard name field
action.name = newName;
updatedWorkflow[pairIndex].what[actionIndex] = action;
}
return {
...prev,
recording: { ...prev.recording, workflow: updatedWorkflow },
};
});
};
const handleTargetUrlChange = (newUrl: string) => {
setRobot((prev) => {
if (!prev) return prev;
const updatedWorkflow = [...prev.recording.workflow];
const lastPairIndex = updatedWorkflow.length - 1;
if (lastPairIndex >= 0) {
const gotoAction = updatedWorkflow[lastPairIndex]?.what?.find(
(action) => action.action === "goto"
);
if (gotoAction && gotoAction.args && gotoAction.args.length > 0) {
gotoAction.args[0] = newUrl;
}
}
return {
...prev,
recording_meta: { ...prev.recording_meta, url: newUrl },
recording: { ...prev.recording, workflow: updatedWorkflow },
};
});
};
const renderAllCredentialFields = () => {
return (
<>
{renderCredentialFields(
credentialGroups.usernames,
t("Username")
)}
{renderCredentialFields(credentialGroups.emails, t("Email"))}
{renderCredentialFields(
credentialGroups.passwords,
t("Password")
)}
{renderCredentialFields(credentialGroups.others, t("Other"))}
</>
);
};
const renderScrapeListLimitFields = () => {
if (scrapeListLimits.length === 0) return null;
return (
<>
<Typography variant="h6" style={{ marginBottom: "20px", marginTop: "20px" }}>
{t("List Limits")}
</Typography>
{scrapeListLimits.map((limitInfo, index) => {
const scrapeListAction = robot?.recording?.workflow?.[limitInfo.pairIndex]?.what?.[limitInfo.actionIndex];
const actionName =
scrapeListAction?.name ||
`List Limit ${index + 1}`;
return (
<TextField
key={`limit-${limitInfo.pairIndex}-${limitInfo.actionIndex}`}
label={actionName}
type="number"
value={limitInfo.currentLimit || ""}
onChange={(e) => {
const value = parseInt(e.target.value, 10);
if (value >= 1) {
handleLimitChange(
limitInfo.pairIndex,
limitInfo.actionIndex,
limitInfo.argIndex,
value
);
}
}}
inputProps={{ min: 1 }}
style={{ marginBottom: "20px" }}
/>
);
})}
</>
);
};
const renderActionNameFields = () => {
if (!robot || !robot.recording || !robot.recording.workflow) return null;
const editableActions = new Set(['screenshot', 'scrapeList', 'scrapeSchema']);
const textInputs: JSX.Element[] = [];
const screenshotInputs: JSX.Element[] = [];
const listInputs: JSX.Element[] = [];
let screenshotCount = 0;
let listCount = 0;
robot.recording.workflow.forEach((pair, pairIndex) => {
if (!pair.what) return;
pair.what.forEach((action, actionIndex) => {
if (!editableActions.has(String(action.action))) return;
let currentName = action.name || '';
if (!currentName) {
switch (action.action) {
case 'scrapeSchema':
currentName = 'Texts';
break;
case 'screenshot':
screenshotCount++;
currentName = `Screenshot ${screenshotCount}`;
break;
case 'scrapeList':
listCount++;
currentName = `List ${listCount}`;
break;
}
} else {
switch (action.action) {
case 'screenshot':
screenshotCount++;
break;
case 'scrapeList':
listCount++;
break;
}
}
const textField = (
<TextField
key={`action-name-${pairIndex}-${actionIndex}`}
type="text"
value={currentName}
onChange={(e) => handleActionNameChange(pairIndex, actionIndex, e.target.value)}
style={{ marginBottom: '12px' }}
fullWidth
/>
);
switch (action.action) {
case 'scrapeSchema': {
const existingName = currentName || "Texts";
if (!textInputs.length) {
textInputs.push(
<TextField
key={`schema-${pairIndex}-${actionIndex}`}
type="text"
value={existingName}
onChange={(e) => {
const newName = e.target.value;
setRobot((prev) => {
if (!prev?.recording?.workflow) return prev;
const updated = { ...prev };
updated.recording = { ...prev.recording };
updated.recording.workflow = prev.recording.workflow.map((p) => ({
...p,
what: p.what?.map((a) => {
if (a.action === "scrapeSchema") {
const updatedAction = { ...a };
updatedAction.name = newName;
return updatedAction;
}
return a;
}),
}));
return updated;
});
}}
style={{ marginBottom: "12px" }}
fullWidth
/>
);
}
break;
}
case 'screenshot':
screenshotInputs.push(textField);
break;
case 'scrapeList':
listInputs.push(textField);
break;
}
});
});
const hasAnyInputs = textInputs.length > 0 || screenshotInputs.length > 0 || listInputs.length > 0;
if (!hasAnyInputs) return null;
return (
<>
<Typography variant="h6" style={{ marginBottom: '20px', marginTop: '20px' }}>
{t('Actions')}
</Typography>
{textInputs.length > 0 && (
<>
<Typography variant="subtitle1" style={{ marginBottom: '8px' }}>
Texts
</Typography>
{textInputs}
</>
)}
{screenshotInputs.length > 0 && (
<>
<Typography variant="subtitle1" style={{ marginBottom: '8px', marginTop: textInputs.length > 0 ? '16px' : '0' }}>
Screenshots
</Typography>
{screenshotInputs}
</>
)}
{listInputs.length > 0 && (
<>
<Typography variant="subtitle1" style={{ marginBottom: '8px', marginTop: (textInputs.length > 0 || screenshotInputs.length > 0) ? '16px' : '0' }}>
Lists
</Typography>
{listInputs}
</>
)}
</>
);
};
const renderCredentialFields = (
selectors: string[],
headerText: string,
) => {
if (selectors.length === 0) return null;
return (
<>
{selectors.map((selector, index) => {
const isVisible = showPasswords[selector];
return (
<TextField
key={selector}
type={isVisible ? "text" : "password"}
label={
headerText === "Other" ? `${`Input`} ${index + 1}` : headerText
}
value={credentials[selector]?.value || ""}
onChange={(e) => handleCredentialChange(selector, e.target.value)}
fullWidth
style={{ marginBottom: "20px" }}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="Show input"
onClick={() => handleClickShowPassword(selector)}
edge="end"
disabled={!credentials[selector]?.value}
>
{isVisible ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
),
}}
/>
);
})}
</>
);
};
const getTargetUrl = () => {
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];
}
return url;
};
const renderCrawlConfigFields = () => {
if (robot?.recording_meta.type !== 'crawl') return null;
return (
<>
<TextField
label="Max Pages to Crawl"
type="number"
fullWidth
value={crawlConfig.limit || 10}
onChange={(e) => {
const value = parseInt(e.target.value, 10);
if (value >= 1) {
setCrawlConfig((prev) => ({ ...prev, limit: value }));
}
}}
inputProps={{ min: 1 }}
style={{ marginBottom: "20px" }}
/>
<Button
onClick={() => setShowCrawlAdvanced(!showCrawlAdvanced)}
sx={{
mb: 2,
textTransform: 'none',
color: '#ff00c3'
}}
>
{showCrawlAdvanced ? 'Hide Advanced Options' : 'Advanced Options'}
</Button>
<Collapse in={showCrawlAdvanced}>
<Box sx={{ mb: 2 }}>
<FormControl fullWidth sx={{ mb: 2 }}>
<InputLabel>Crawl Scope</InputLabel>
<Select
value={crawlConfig.mode || 'domain'}
label="Crawl Scope"
onChange={(e) => setCrawlConfig((prev) => ({ ...prev, mode: e.target.value }))}
>
<MenuItem value="domain">Same Domain Only</MenuItem>
<MenuItem value="subdomain">Include Subdomains</MenuItem>
<MenuItem value="path">Specific Path Only</MenuItem>
</Select>
</FormControl>
<TextField
label="Max Depth"
type="number"
fullWidth
value={crawlConfig.maxDepth || 3}
onChange={(e) => {
const value = parseInt(e.target.value, 10);
if (value >= 1) {
setCrawlConfig((prev) => ({ ...prev, maxDepth: value }));
}
}}
inputProps={{ min: 1 }}
sx={{ mb: 2 }}
helperText="How many links deep to follow (default: 3)"
/>
<TextField
label="Include Paths"
placeholder="Example: /products, /blog"
fullWidth
value={crawlConfig.includePaths?.join(', ') || ''}
onChange={(e) => {
const paths = e.target.value ? e.target.value.split(',').map(p => p.trim()) : [];
setCrawlConfig((prev) => ({ ...prev, includePaths: paths }));
}}
sx={{ mb: 2 }}
helperText="Only crawl URLs matching these paths (comma-separated)"
/>
<TextField
label="Exclude Paths"
placeholder="Example: /admin, /login"
fullWidth
value={crawlConfig.excludePaths?.join(', ') || ''}
onChange={(e) => {
const paths = e.target.value ? e.target.value.split(',').map(p => p.trim()) : [];
setCrawlConfig((prev) => ({ ...prev, excludePaths: paths }));
}}
sx={{ mb: 2 }}
helperText="Skip URLs matching these paths (comma-separated)"
/>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<FormControlLabel
control={
<Checkbox
checked={crawlConfig.useSitemap ?? true}
onChange={(e) => setCrawlConfig((prev) => ({ ...prev, useSitemap: e.target.checked }))}
/>
}
label="Use sitemap.xml for URL discovery"
/>
<FormControlLabel
control={
<Checkbox
checked={crawlConfig.followLinks ?? true}
onChange={(e) => setCrawlConfig((prev) => ({ ...prev, followLinks: e.target.checked }))}
/>
}
label="Follow links on pages"
/>
<FormControlLabel
control={
<Checkbox
checked={crawlConfig.respectRobots ?? true}
onChange={(e) => setCrawlConfig((prev) => ({ ...prev, respectRobots: e.target.checked }))}
/>
}
label="Respect robots.txt"
/>
</Box>
</Box>
</Collapse>
</>
);
};
const renderSearchConfigFields = () => {
if (robot?.recording_meta.type !== 'search') return null;
return (
<>
<TextField
label="Search Query"
placeholder="Example: latest AI breakthroughs 2025"
fullWidth
value={searchConfig.query || ''}
onChange={(e) => {
setSearchConfig((prev) => ({ ...prev, query: e.target.value }));
}}
sx={{ mb: 2 }}
/>
<TextField
label="Number of Results"
type="number"
fullWidth
value={searchConfig.limit || 10}
onChange={(e) => {
const value = parseInt(e.target.value, 10);
if (value >= 1) {
setSearchConfig((prev) => ({ ...prev, limit: value }));
}
}}
inputProps={{ min: 1 }}
sx={{ mb: 2 }}
/>
<FormControl fullWidth sx={{ mb: 2 }}>
<InputLabel>Mode</InputLabel>
<Select
value={searchConfig.mode || 'discover'}
label="Mode"
onChange={(e) => setSearchConfig((prev) => ({ ...prev, mode: e.target.value as 'discover' | 'scrape' }))}
>
<MenuItem value="discover">Discover URLs Only</MenuItem>
<MenuItem value="scrape">Extract Data from Results</MenuItem>
</Select>
</FormControl>
<FormControl fullWidth sx={{ mb: 2 }}>
<InputLabel>Time Range</InputLabel>
<Select
value={searchConfig.filters?.timeRange || ''}
label="Time Range"
onChange={(e) => setSearchConfig((prev) => ({
...prev,
filters: { ...prev.filters, timeRange: e.target.value as '' | 'day' | 'week' | 'month' | 'year' || undefined }
}))}
>
<MenuItem value="">No Filter</MenuItem>
<MenuItem value="day">Past 24 Hours</MenuItem>
<MenuItem value="week">Past Week</MenuItem>
<MenuItem value="month">Past Month</MenuItem>
<MenuItem value="year">Past Year</MenuItem>
</Select>
</FormControl>
</>
);
};
const handleSave = async () => {
if (!robot) return;
setIsLoading(true);
try {
const credentialsForPayload = Object.entries(credentials).reduce(
(acc, [selector, info]) => {
const enforceType = info.type === "password" ? "password" : "text";
acc[selector] = {
value: info.value,
type: enforceType,
};
return acc;
},
{} as Record<string, CredentialInfo>
);
const targetUrl = getTargetUrl();
let updatedWorkflow = robot.recording.workflow;
if (robot.recording_meta.type === 'crawl') {
updatedWorkflow = updatedWorkflow.map((pair: any) => {
if (!pair.what) return pair;
return {
...pair,
what: pair.what.map((action: any) => {
if (action.action === 'crawl') {
return {
...action,
args: [{ ...crawlConfig }]
};
}
return action;
})
};
});
}
if (robot.recording_meta.type === 'search') {
updatedWorkflow = updatedWorkflow.map((pair: any) => {
if (!pair.what) return pair;
return {
...pair,
what: pair.what.map((action: any) => {
if (action.action === 'search') {
return {
...action,
args: [{
...searchConfig,
provider: 'duckduckgo'
}]
};
}
return action;
})
};
});
}
const payload: any = {
name: robot.recording_meta.name,
limits: scrapeListLimits.map((limit) => ({
pairIndex: limit.pairIndex,
actionIndex: limit.actionIndex,
argIndex: limit.argIndex,
limit: limit.currentLimit,
})),
credentials: credentialsForPayload,
targetUrl: targetUrl,
workflow: updatedWorkflow,
};
const success = await updateRecording(robot.recording_meta.id, payload);
if (success) {
setRerenderRobots(true);
notify("success", t("robot_edit.notifications.update_success"));
handleStart(robot);
const basePath = "/robots";
navigate(basePath);
} else {
notify("error", t("robot_edit.notifications.update_failed"));
}
} catch (error) {
notify("error", t("robot_edit.notifications.update_error"));
console.error("Error updating robot:", error);
} finally {
setIsLoading(false);
}
};
const handleCancel = () => {
const basePath = "/robots";
navigate(basePath);
};
return (
<RobotConfigPage
title={t("robot_edit.title")}
onSave={handleSave}
onCancel={handleCancel}
saveButtonText={t("robot_edit.save")}
cancelButtonText={t("robot_edit.cancel")}
showCancelButton={false}
isLoading={isLoading}
>
<>
<Box style={{ display: "flex", flexDirection: "column" }}>
{robot && (
<>
<TextField
label={t("robot_edit.change_name")}
key="Name"
type="text"
value={robot.recording_meta.name}
onChange={(e) => handleRobotNameChange(e.target.value)}
style={{ marginBottom: "20px" }}
/>
{robot.recording_meta.type !== 'search' && (
<TextField
label={t("robot_duplication.fields.target_url")}
key={t("robot_duplication.fields.target_url")}
value={getTargetUrl() || ""}
onChange={(e) => handleTargetUrlChange(e.target.value)}
style={{ marginBottom: "20px" }}
/>
)}
{renderCrawlConfigFields()}
{renderSearchConfigFields()}
{renderScrapeListLimitFields()}
{renderActionNameFields()}
</>
)}
</Box>
</>
</RobotConfigPage>
);
};