Merge pull request #530 from getmaxun/retrain-robot

feat: retrain robot
This commit is contained in:
Karishma Shukla
2025-04-22 00:52:19 +05:30
committed by GitHub
11 changed files with 228 additions and 52 deletions

View File

@@ -55,6 +55,11 @@ const BrowserRecordingSave = () => {
type: 'recording-notification',
notification: notificationData
}, '*');
window.opener.postMessage({
type: 'session-data-clear',
timestamp: Date.now()
}, '*');
}
setBrowserId(null);

View File

@@ -19,26 +19,32 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => {
const { t } = useTranslation();
const [openModal, setOpenModal] = useState<boolean>(false);
const [needConfirm, setNeedConfirm] = useState<boolean>(false);
const [recordingName, setRecordingName] = useState<string>(fileName);
const [saveRecordingName, setSaveRecordingName] = useState<string>(fileName);
const [waitingForSave, setWaitingForSave] = useState<boolean>(false);
const { browserId, setBrowserId, notify, recordings, isLogin } = useGlobalInfoStore();
const { browserId, setBrowserId, notify, recordings, isLogin, recordingName, retrainRobotId } = useGlobalInfoStore();
const { socket } = useSocketStore();
const { state, dispatch } = useContext(AuthContext);
const { user } = state;
const navigate = useNavigate();
useEffect(() => {
if (recordingName) {
setSaveRecordingName(recordingName);
}
}, [recordingName]);
const handleChangeOfTitle = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.target;
if (needConfirm) {
setNeedConfirm(false);
}
setRecordingName(value);
setSaveRecordingName(value);
}
const handleSaveRecording = async (event: React.SyntheticEvent) => {
event.preventDefault();
if (recordings.includes(recordingName)) {
if (recordings.includes(saveRecordingName)) {
if (needConfirm) { return; }
setNeedConfirm(true);
} else {
@@ -46,19 +52,43 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => {
}
};
const exitRecording = useCallback(async () => {
const handleFinishClick = () => {
if (recordingName && !recordings.includes(recordingName)) {
saveRecording();
} else {
setOpenModal(true);
}
};
const exitRecording = useCallback(async (data?: { actionType: string }) => {
let successMessage = t('save_recording.notifications.save_success');
if (data && data.actionType) {
if (data.actionType === 'retrained') {
successMessage = t('save_recording.notifications.retrain_success');
} else if (data.actionType === 'saved') {
successMessage = t('save_recording.notifications.save_success');
} else if (data.actionType === 'error') {
successMessage = t('save_recording.notifications.save_error');
}
}
const notificationData = {
type: 'success',
message: t('save_recording.notifications.save_success'),
message: successMessage,
timestamp: Date.now()
};
window.sessionStorage.setItem('pendingNotification', JSON.stringify(notificationData));
if (window.opener) {
window.opener.postMessage({
type: 'recording-notification',
notification: notificationData
}, '*');
window.opener.postMessage({
type: 'session-data-clear',
timestamp: Date.now()
}, '*');
}
if (browserId) {
@@ -67,16 +97,21 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => {
setBrowserId(null);
window.close();
}, [setBrowserId, browserId]);
}, [setBrowserId, browserId, t]);
// notifies backed to save the recording in progress,
// releases resources and changes the view for main page by clearing the global browserId
const saveRecording = async () => {
if (user) {
const payload = { fileName: recordingName, userId: user.id, isLogin: isLogin };
const payload = {
fileName: saveRecordingName || recordingName,
userId: user.id,
isLogin: isLogin,
robotId: retrainRobotId,
};
socket?.emit('save', payload);
setWaitingForSave(true);
console.log(`Saving the recording as ${recordingName} for userId ${user.id}`);
console.log(`Saving the recording as ${saveRecordingName || recordingName} for userId ${user.id}`);
} else {
console.error(t('save_recording.notifications.user_not_logged'));
}
@@ -92,7 +127,7 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => {
return (
<div>
<Button
onClick={() => setOpenModal(true)}
onClick={handleFinishClick}
variant="outlined"
color="success"
sx={{
@@ -116,7 +151,7 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => {
id="title"
label={t('save_recording.robot_name')}
variant="outlined"
defaultValue={recordingName ? recordingName : null}
value={saveRecordingName}
/>
{needConfirm
?

View File

@@ -35,7 +35,8 @@ import {
Settings,
Power,
ContentCopy,
MoreHoriz
MoreHoriz,
Refresh
} from "@mui/icons-material";
import { useGlobalInfoStore } from "../../context/globalInfo";
import { checkRunsForRecording, deleteRecordingFromStorage, getStoredRecordings } from "../../api/storage";
@@ -117,6 +118,7 @@ const TableRowMemoized = memo(({ row, columns, handlers }: any) => {
return (
<MemoizedTableCell key={column.id} align={column.align}>
<MemoizedOptionsButton
handleRetrain={() =>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)}
@@ -185,7 +187,7 @@ export const RecordingsTable = ({
useEffect(() => {
const handleMessage = (event: any) => {
if (event.data && event.data.type === 'recording-notification') {
if (event.origin === window.location.origin && event.data && event.data.type === 'recording-notification') {
const notificationData = event.data.notification;
if (notificationData) {
notify(notificationData.type, notificationData.message);
@@ -198,6 +200,17 @@ export const RecordingsTable = ({
}
}
}
if (event.origin === window.location.origin && event.data && event.data.type === 'session-data-clear') {
window.sessionStorage.removeItem('browserId');
window.sessionStorage.removeItem('robotToRetrain');
window.sessionStorage.removeItem('robotName');
window.sessionStorage.removeItem('recordingUrl');
window.sessionStorage.removeItem('recordingSessionId');
window.sessionStorage.removeItem('pendingSessionData');
window.sessionStorage.removeItem('nextTabIsRecording');
window.sessionStorage.removeItem('initialUrl');
}
};
window.addEventListener('message', handleMessage);
@@ -303,6 +316,60 @@ export const RecordingsTable = ({
setModalOpen(true);
};
const handleRetrainRobot = useCallback(async (id: string, name: string) => {
const activeBrowserId = await getActiveBrowserId();
const robot = rows.find(row => row.id === id);
let targetUrl;
if (robot?.content?.workflow && robot.content.workflow.length > 0) {
const lastPair = robot.content.workflow[robot.content.workflow.length - 1];
if (lastPair?.what) {
if (Array.isArray(lastPair.what)) {
const gotoAction = lastPair.what.find(action =>
action && typeof action === 'object' && 'action' in action && action.action === "goto"
) as any;
if (gotoAction?.args?.[0]) {
targetUrl = gotoAction.args[0];
}
}
}
}
if (targetUrl) {
setInitialUrl(targetUrl);
setRecordingUrl(targetUrl);
window.sessionStorage.setItem('initialUrl', targetUrl);
}
if (activeBrowserId) {
setActiveBrowserId(activeBrowserId);
setWarningModalOpen(true);
} else {
startRetrainRecording(id, name, targetUrl);
}
}, [rows, setInitialUrl, setRecordingUrl]);
const startRetrainRecording = (id: string, name: string, url?: string) => {
setBrowserId('new-recording');
setRecordingName(name);
setRecordingId(id);
window.sessionStorage.setItem('browserId', 'new-recording');
window.sessionStorage.setItem('robotToRetrain', id);
window.sessionStorage.setItem('robotName', name);
window.sessionStorage.setItem('recordingUrl', url || recordingUrl);
const sessionId = Date.now().toString();
window.sessionStorage.setItem('recordingSessionId', sessionId);
window.openedRecordingWindow = window.open(`/recording-setup?session=${sessionId}`, '_blank');
window.sessionStorage.setItem('nextTabIsRecording', 'true');
};
const startRecording = () => {
setModalOpen(false);
@@ -381,6 +448,7 @@ export const RecordingsTable = ({
handleSettingsRecording,
handleEditRobot,
handleDuplicateRobot,
handleRetrainRobot,
handleDelete: async (id: string) => {
const hasRuns = await checkRunsForRecording(id);
if (hasRuns) {
@@ -395,7 +463,7 @@ export const RecordingsTable = ({
fetchRecordings();
}
}
}), [handleRunRecording, handleScheduleRecording, handleIntegrateRecording, handleSettingsRecording, handleEditRobot, handleDuplicateRobot, notify, t]);
}), [handleRunRecording, handleScheduleRecording, handleIntegrateRecording, handleSettingsRecording, handleEditRobot, handleDuplicateRobot, handleRetrainRobot, notify, t]);
return (
<React.Fragment>
@@ -597,12 +665,13 @@ const SettingsButton = ({ handleSettings }: SettingsButtonProps) => {
}
interface OptionsButtonProps {
handleRetrain: () => void;
handleEdit: () => void;
handleDelete: () => void;
handleDuplicate: () => void;
}
const OptionsButton = ({ handleEdit, handleDelete, handleDuplicate }: OptionsButtonProps) => {
const OptionsButton = ({ handleRetrain, handleEdit, handleDelete, handleDuplicate }: OptionsButtonProps) => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
@@ -629,6 +698,13 @@ const OptionsButton = ({ handleEdit, handleDelete, handleDuplicate }: OptionsBut
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem onClick={() => { handleRetrain(); handleClose(); }}>
<ListItemIcon>
<Refresh fontSize="small" />
</ListItemIcon>
<ListItemText>{t('recordingtable.retrain')}</ListItemText>
</MenuItem>
<MenuItem onClick={() => { handleEdit(); handleClose(); }}>
<ListItemIcon>
<Edit fontSize="small" />

View File

@@ -60,6 +60,8 @@ interface GlobalInfo {
setRecordingLength: (recordingLength: number) => void;
recordingId: string | null;
setRecordingId: (newId: string | null) => void;
retrainRobotId: string | null;
setRetrainRobotId: (newId: string | null) => void;
recordingName: string;
setRecordingName: (recordingName: string) => void;
initialUrl: string;
@@ -90,6 +92,7 @@ class GlobalInfoStore implements Partial<GlobalInfo> {
isOpen: false,
};
recordingId = null;
retrainRobotId = null;
recordings: string[] = [];
rerenderRuns = false;
rerenderRobots = false;
@@ -119,6 +122,7 @@ export const GlobalInfoProvider = ({ children }: { children: JSX.Element }) => {
const [rerenderRobots, setRerenderRobots] = useState<boolean>(globalInfoStore.rerenderRobots);
const [recordingLength, setRecordingLength] = useState<number>(globalInfoStore.recordingLength);
const [recordingId, setRecordingId] = useState<string | null>(globalInfoStore.recordingId);
const [retrainRobotId, setRetrainRobotId] = useState<string | null>(globalInfoStore.retrainRobotId);
const [recordingName, setRecordingName] = useState<string>(globalInfoStore.recordingName);
const [isLogin, setIsLogin] = useState<boolean>(globalInfoStore.isLogin);
const [initialUrl, setInitialUrl] = useState<string>(globalInfoStore.initialUrl);
@@ -169,6 +173,8 @@ export const GlobalInfoProvider = ({ children }: { children: JSX.Element }) => {
setRecordingLength,
recordingId,
setRecordingId,
retrainRobotId,
setRetrainRobotId,
recordingName,
setRecordingName,
initialUrl,

View File

@@ -43,7 +43,7 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => {
const { setId, socket } = useSocketStore();
const { setWidth } = useBrowserDimensionsStore();
const { browserId, setBrowserId, recordingId, recordingUrl, setRecordingUrl } = useGlobalInfoStore();
const { browserId, setBrowserId, recordingId, recordingUrl, setRecordingUrl, setRecordingName, setRetrainRobotId } = useGlobalInfoStore();
const handleShowOutputData = useCallback(() => {
setShowOutputData(true);
@@ -80,6 +80,19 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => {
const storedUrl = window.sessionStorage.getItem('recordingUrl');
if (storedUrl && !recordingUrl) {
setRecordingUrl(storedUrl);
window.sessionStorage.removeItem('recordingUrl');
}
const robotName = window.sessionStorage.getItem('robotName');
if (robotName) {
setRecordingName(robotName);
window.sessionStorage.removeItem('robotName');
}
const recordingId = window.sessionStorage.getItem('robotToRetrain');
if (recordingId) {
setRetrainRobotId(recordingId);
window.sessionStorage.removeItem('robotToRetrain');
}
const id = await getActiveBrowserId();
@@ -101,7 +114,7 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => {
return () => {
isCancelled = true;
}
}, [setId, recordingUrl, setRecordingUrl]);
}, [setId, recordingUrl, setRecordingUrl, setRecordingName, setRetrainRobotId]);
const changeBrowserDimensions = useCallback(() => {
if (browserContentRef.current) {
@@ -126,7 +139,7 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => {
}
setIsLoaded(true);
}
}, [socket, browserId, recordingName, recordingId, isLoaded])
}, [socket, browserId, recordingName, recordingId, isLoaded]);
useEffect(() => {
socket?.on('loaded', handleLoaded);