diff --git a/public/locales/de.json b/public/locales/de.json index 5c5c1acc..2d48b16d 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -54,6 +54,7 @@ "label": "URL", "button": "Aufnahme starten" }, + "retrain": "Neu trainieren", "edit": "Bearbeiten", "delete": "Löschen", "duplicate": "Duplizieren", @@ -237,7 +238,9 @@ "confirm": "Bestätigen" }, "notifications": { - "save_success": "Roboter erfolgreich gespeichert" + "save_success": "Roboter erfolgreich gespeichert", + "retrain_success": "Roboter erfolgreich neu trainiert", + "save_error": "Fehler beim Speichern des Roboters" }, "errors": { "user_not_logged": "Benutzer nicht angemeldet. Aufnahme kann nicht gespeichert werden.", diff --git a/public/locales/en.json b/public/locales/en.json index 03ee61cb..6f7fe38d 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -60,6 +60,7 @@ "discard_and_create":"Discard & Create New", "cancel":"Cancel" }, + "retrain": "Retrain", "edit":"Edit", "delete":"Delete", "duplicate":"Duplicate", @@ -245,7 +246,9 @@ "confirm": "Confirm" }, "notifications": { - "save_success": "Robot saved successfully" + "save_success": "Robot saved successfully", + "retrain_success": "Robot retrained successfully", + "save_error": "Error saving robot" }, "errors": { "user_not_logged": "User not logged in. Cannot save recording.", diff --git a/public/locales/es.json b/public/locales/es.json index 0abb10a0..edc762a4 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -54,6 +54,7 @@ "label": "URL", "button": "Comenzar grabación" }, + "retrain": "Reentrenar", "edit": "Editar", "delete": "Eliminar", "duplicate": "Duplicar", @@ -238,7 +239,9 @@ "confirm": "Confirmar" }, "notifications": { - "save_success": "Robot guardado exitosamente" + "save_success": "Robot guardado correctamente", + "retrain_success": "Robot reentrenado correctamente", + "save_error": "Error al guardar el robot" }, "errors": { "user_not_logged": "Usuario no conectado. No se puede guardar la grabación.", diff --git a/public/locales/ja.json b/public/locales/ja.json index 58219ceb..15d8c7e0 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -54,6 +54,7 @@ "label": "URL", "button": "録画を開始" }, + "retrain": "再学習", "edit": "編集", "delete": "削除", "duplicate": "複製", @@ -238,7 +239,9 @@ "confirm": "確認" }, "notifications": { - "save_success": "ロボットが正常に保存されました" + "save_success": "ロボットの保存に成功しました", + "retrain_success": "ロボットの再トレーニングに成功しました", + "save_error": "ロボットの保存中にエラーが発生しました" }, "errors": { "user_not_logged": "ユーザーがログインしていません。録画を保存できません。", diff --git a/public/locales/zh.json b/public/locales/zh.json index 71f98c07..faaf0496 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -54,6 +54,7 @@ "label": "URL", "button": "开始录制" }, + "retrain": "重新训练", "edit": "编辑", "delete": "删除", "duplicate": "复制", @@ -238,7 +239,9 @@ "confirm": "确认" }, "notifications": { - "save_success": "机器人保存成功" + "save_success": "机器人保存成功", + "retrain_success": "机器人重新训练成功", + "save_error": "保存机器人时出错" }, "errors": { "user_not_logged": "用户未登录。无法保存录制。", diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 004126bd..dac18618 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -139,12 +139,14 @@ export class WorkflowGenerator { */ private registerEventHandlers = (socket: Socket) => { socket.on('save', (data) => { - const { fileName, userId, isLogin } = data; + const { fileName, userId, isLogin, robotId } = data; logger.log('debug', `Saving workflow ${fileName} for user ID ${userId}`); - this.saveNewWorkflow(fileName, userId, isLogin); + this.saveNewWorkflow(fileName, userId, isLogin, robotId); }); - socket.on('new-recording', () => this.workflowRecord = { - workflow: [], + socket.on('new-recording', (data) => { + this.workflowRecord = { + workflow: [], + }; }); socket.on('activeIndex', (data) => this.generatedData.lastIndex = parseInt(data)); socket.on('decision', async ({ pair, actionType, decision, userId }) => { @@ -764,38 +766,62 @@ export class WorkflowGenerator { * @param fileName The name of the file. * @returns {Promise} */ - public saveNewWorkflow = async (fileName: string, userId: number, isLogin: boolean) => { + public saveNewWorkflow = async (fileName: string, userId: number, isLogin: boolean, robotId?: string) => { const recording = this.optimizeWorkflow(this.workflowRecord); + let actionType = 'saved'; + try { - this.recordingMeta = { - name: fileName, - id: uuid(), - createdAt: this.recordingMeta.createdAt || new Date().toLocaleString(), - pairs: recording.workflow.length, - updatedAt: new Date().toLocaleString(), - params: this.getParams() || [], - isLogin: isLogin, - } - const robot = await Robot.create({ - userId, - recording_meta: this.recordingMeta, - recording: recording, - }); - capture( - 'maxun-oss-robot-created', - { - robot_meta: robot.recording_meta, - recording: robot.recording, - } - ) + if (robotId) { + const robot = await Robot.findOne({ where: { 'recording_meta.id': robotId }}); - logger.log('info', `Robot saved with id: ${robot.id}`); + if (robot) { + await robot.update({ + recording: recording, + recording_meta: { + ...robot.recording_meta, + pairs: recording.workflow.length, + params: this.getParams() || [], + updatedAt: new Date().toLocaleString(), + }, + }) + + actionType = 'retrained'; + logger.log('info', `Robot retrained with id: ${robot.id}`); + } + } else { + this.recordingMeta = { + name: fileName, + id: uuid(), + createdAt: this.recordingMeta.createdAt || new Date().toLocaleString(), + pairs: recording.workflow.length, + updatedAt: new Date().toLocaleString(), + params: this.getParams() || [], + isLogin: isLogin, + } + const robot = await Robot.create({ + userId, + recording_meta: this.recordingMeta, + recording: recording, + }); + capture( + 'maxun-oss-robot-created', + { + robot_meta: robot.recording_meta, + recording: robot.recording, + } + ) + + actionType = 'saved'; + logger.log('info', `Robot saved with id: ${robot.id}`); + } } catch (e) { const { message } = e as Error; logger.log('warn', `Cannot save the file to the local file system ${e}`) + actionType = 'error'; } - this.socket.emit('fileSaved'); + + this.socket.emit('fileSaved', { actionType }); } /** diff --git a/src/components/browser/BrowserRecordingSave.tsx b/src/components/browser/BrowserRecordingSave.tsx index d4fd54fb..2adfcd79 100644 --- a/src/components/browser/BrowserRecordingSave.tsx +++ b/src/components/browser/BrowserRecordingSave.tsx @@ -55,6 +55,11 @@ const BrowserRecordingSave = () => { type: 'recording-notification', notification: notificationData }, '*'); + + window.opener.postMessage({ + type: 'session-data-clear', + timestamp: Date.now() + }, '*'); } setBrowserId(null); diff --git a/src/components/recorder/SaveRecording.tsx b/src/components/recorder/SaveRecording.tsx index f7020b44..a85cb868 100644 --- a/src/components/recorder/SaveRecording.tsx +++ b/src/components/recorder/SaveRecording.tsx @@ -19,26 +19,32 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => { const { t } = useTranslation(); const [openModal, setOpenModal] = useState(false); const [needConfirm, setNeedConfirm] = useState(false); - const [recordingName, setRecordingName] = useState(fileName); + const [saveRecordingName, setSaveRecordingName] = useState(fileName); const [waitingForSave, setWaitingForSave] = useState(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) => { 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 (