Nicolas Rabault commited on
Commit
4f54bfc
·
unverified ·
2 Parent(s): a3ea9bd df04e9d

Merge pull request #1 from DavidLMS/save-port-on-every-change

Browse files
src/components/landing/DirectFollowerModal.tsx CHANGED
@@ -20,6 +20,8 @@ import {
20
  import { Settings } from "lucide-react";
21
  import PortDetectionModal from "@/components/ui/PortDetectionModal";
22
  import PortDetectionButton from "@/components/ui/PortDetectionButton";
 
 
23
 
24
  interface DirectFollowerModalProps {
25
  open: boolean;
@@ -44,28 +46,40 @@ const DirectFollowerModal: React.FC<DirectFollowerModalProps> = ({
44
  isLoadingConfigs,
45
  onStart,
46
  }) => {
 
 
47
  const [showPortDetection, setShowPortDetection] = useState(false);
48
 
49
- // Load saved follower port on component mount
50
  useEffect(() => {
51
- const loadSavedPort = async () => {
52
  try {
53
- const followerResponse = await fetch(
54
- "http://localhost:8000/robot-port/follower"
 
55
  );
56
  const followerData = await followerResponse.json();
57
  if (followerData.status === "success" && followerData.default_port) {
58
  setFollowerPort(followerData.default_port);
59
  }
 
 
 
 
 
 
 
 
 
60
  } catch (error) {
61
- console.error("Error loading saved follower port:", error);
62
  }
63
  };
64
 
65
- if (open) {
66
- loadSavedPort();
67
  }
68
- }, [open, setFollowerPort]);
69
 
70
  const handlePortDetection = () => {
71
  setShowPortDetection(true);
@@ -75,6 +89,20 @@ const DirectFollowerModal: React.FC<DirectFollowerModalProps> = ({
75
  setFollowerPort(port);
76
  };
77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  return (
79
  <Dialog open={open} onOpenChange={onOpenChange}>
80
  <DialogContent className="bg-gray-900 border-gray-800 text-white sm:max-w-[500px] p-8">
@@ -103,7 +131,7 @@ const DirectFollowerModal: React.FC<DirectFollowerModalProps> = ({
103
  <Input
104
  id="followerPort"
105
  value={followerPort}
106
- onChange={(e) => setFollowerPort(e.target.value)}
107
  placeholder="/dev/tty.usbmodem5A460816621"
108
  className="bg-gray-800 border-gray-700 text-white flex-1"
109
  />
@@ -121,7 +149,7 @@ const DirectFollowerModal: React.FC<DirectFollowerModalProps> = ({
121
  >
122
  Follower Calibration Config
123
  </Label>
124
- <Select value={followerConfig} onValueChange={setFollowerConfig}>
125
  <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
126
  <SelectValue
127
  placeholder={
 
20
  import { Settings } from "lucide-react";
21
  import PortDetectionModal from "@/components/ui/PortDetectionModal";
22
  import PortDetectionButton from "@/components/ui/PortDetectionButton";
23
+ import { useApi } from "@/contexts/ApiContext";
24
+ import { useAutoSave } from "@/hooks/useAutoSave";
25
 
26
  interface DirectFollowerModalProps {
27
  open: boolean;
 
46
  isLoadingConfigs,
47
  onStart,
48
  }) => {
49
+ const { baseUrl, fetchWithHeaders } = useApi();
50
+ const { debouncedSavePort, debouncedSaveConfig } = useAutoSave();
51
  const [showPortDetection, setShowPortDetection] = useState(false);
52
 
53
+ // Load saved follower port and configuration on component mount
54
  useEffect(() => {
55
+ const loadSavedData = async () => {
56
  try {
57
+ // Load follower port
58
+ const followerResponse = await fetchWithHeaders(
59
+ `${baseUrl}/robot-port/follower`
60
  );
61
  const followerData = await followerResponse.json();
62
  if (followerData.status === "success" && followerData.default_port) {
63
  setFollowerPort(followerData.default_port);
64
  }
65
+
66
+ // Load follower configuration
67
+ const followerConfigResponse = await fetchWithHeaders(
68
+ `${baseUrl}/robot-config/follower?available_configs=${followerConfigs.join(',')}`
69
+ );
70
+ const followerConfigData = await followerConfigResponse.json();
71
+ if (followerConfigData.status === "success" && followerConfigData.default_config) {
72
+ setFollowerConfig(followerConfigData.default_config);
73
+ }
74
  } catch (error) {
75
+ console.error("Error loading saved data:", error);
76
  }
77
  };
78
 
79
+ if (open && followerConfigs.length > 0) {
80
+ loadSavedData();
81
  }
82
+ }, [open, setFollowerPort, setFollowerConfig, followerConfigs, baseUrl, fetchWithHeaders]);
83
 
84
  const handlePortDetection = () => {
85
  setShowPortDetection(true);
 
89
  setFollowerPort(port);
90
  };
91
 
92
+ // Enhanced port change handler that saves automatically
93
+ const handleFollowerPortChange = (value: string) => {
94
+ setFollowerPort(value);
95
+ // Auto-save with debouncing to avoid excessive API calls
96
+ debouncedSavePort("follower", value);
97
+ };
98
+
99
+ // Enhanced config change handler that saves automatically
100
+ const handleFollowerConfigChange = (value: string) => {
101
+ setFollowerConfig(value);
102
+ // Auto-save with debouncing to avoid excessive API calls
103
+ debouncedSaveConfig("follower", value);
104
+ };
105
+
106
  return (
107
  <Dialog open={open} onOpenChange={onOpenChange}>
108
  <DialogContent className="bg-gray-900 border-gray-800 text-white sm:max-w-[500px] p-8">
 
131
  <Input
132
  id="followerPort"
133
  value={followerPort}
134
+ onChange={(e) => handleFollowerPortChange(e.target.value)}
135
  placeholder="/dev/tty.usbmodem5A460816621"
136
  className="bg-gray-800 border-gray-700 text-white flex-1"
137
  />
 
149
  >
150
  Follower Calibration Config
151
  </Label>
152
+ <Select value={followerConfig} onValueChange={handleFollowerConfigChange}>
153
  <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
154
  <SelectValue
155
  placeholder={
src/components/landing/RecordingModal.tsx CHANGED
@@ -21,6 +21,7 @@ import PortDetectionModal from "@/components/ui/PortDetectionModal";
21
  import PortDetectionButton from "@/components/ui/PortDetectionButton";
22
  import QrCodeModal from "@/components/recording/QrCodeModal";
23
  import { useApi } from "@/contexts/ApiContext";
 
24
  interface RecordingModalProps {
25
  open: boolean;
26
  onOpenChange: (open: boolean) => void;
@@ -66,6 +67,7 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
66
  onStart,
67
  }) => {
68
  const { baseUrl, fetchWithHeaders } = useApi();
 
69
  const [showPortDetection, setShowPortDetection] = useState(false);
70
  const [detectionRobotType, setDetectionRobotType] = useState<
71
  "leader" | "follower"
@@ -73,9 +75,46 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
73
  const [showQrCodeModal, setShowQrCodeModal] = useState(false);
74
  const [sessionId, setSessionId] = useState("");
75
 
76
- // Load saved ports on component mount
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  useEffect(() => {
78
- const loadSavedPorts = async () => {
79
  try {
80
  // Load leader port
81
  const leaderResponse = await fetchWithHeaders(
@@ -94,25 +133,34 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
94
  if (followerData.status === "success" && followerData.default_port) {
95
  setFollowerPort(followerData.default_port);
96
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  } catch (error) {
98
- console.error("Error loading saved ports:", error);
99
  }
100
  };
101
- if (open) {
102
- loadSavedPorts();
103
- }
104
- }, [open, setLeaderPort, setFollowerPort]);
105
- const handlePortDetection = (robotType: "leader" | "follower") => {
106
- setDetectionRobotType(robotType);
107
- setShowPortDetection(true);
108
- };
109
- const handlePortDetected = (port: string) => {
110
- if (detectionRobotType === "leader") {
111
- setLeaderPort(port);
112
- } else {
113
- setFollowerPort(port);
114
  }
115
- };
 
116
  const handleQrCodeClick = () => {
117
  // Generate a session ID for this recording session
118
  const newSessionId = `recording_${Date.now()}_${Math.random()
@@ -175,7 +223,7 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
175
  <Input
176
  id="recordLeaderPort"
177
  value={leaderPort}
178
- onChange={(e) => setLeaderPort(e.target.value)}
179
  placeholder="/dev/tty.usbmodem5A460816421"
180
  className="bg-gray-800 border-gray-700 text-white flex-1"
181
  />
@@ -194,7 +242,7 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
194
  </Label>
195
  <Select
196
  value={leaderConfig}
197
- onValueChange={setLeaderConfig}
198
  >
199
  <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
200
  <SelectValue
@@ -229,7 +277,7 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
229
  <Input
230
  id="recordFollowerPort"
231
  value={followerPort}
232
- onChange={(e) => setFollowerPort(e.target.value)}
233
  placeholder="/dev/tty.usbmodem5A460816621"
234
  className="bg-gray-800 border-gray-700 text-white flex-1"
235
  />
@@ -248,7 +296,7 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
248
  </Label>
249
  <Select
250
  value={followerConfig}
251
- onValueChange={setFollowerConfig}
252
  >
253
  <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
254
  <SelectValue
 
21
  import PortDetectionButton from "@/components/ui/PortDetectionButton";
22
  import QrCodeModal from "@/components/recording/QrCodeModal";
23
  import { useApi } from "@/contexts/ApiContext";
24
+ import { useAutoSave } from "@/hooks/useAutoSave";
25
  interface RecordingModalProps {
26
  open: boolean;
27
  onOpenChange: (open: boolean) => void;
 
67
  onStart,
68
  }) => {
69
  const { baseUrl, fetchWithHeaders } = useApi();
70
+ const { debouncedSavePort, debouncedSaveConfig } = useAutoSave();
71
  const [showPortDetection, setShowPortDetection] = useState(false);
72
  const [detectionRobotType, setDetectionRobotType] = useState<
73
  "leader" | "follower"
 
75
  const [showQrCodeModal, setShowQrCodeModal] = useState(false);
76
  const [sessionId, setSessionId] = useState("");
77
 
78
+ const handlePortDetection = (robotType: "leader" | "follower") => {
79
+ setDetectionRobotType(robotType);
80
+ setShowPortDetection(true);
81
+ };
82
+ const handlePortDetected = (port: string) => {
83
+ if (detectionRobotType === "leader") {
84
+ setLeaderPort(port);
85
+ } else {
86
+ setFollowerPort(port);
87
+ }
88
+ };
89
+
90
+ // Enhanced port change handlers that save automatically
91
+ const handleLeaderPortChange = (value: string) => {
92
+ setLeaderPort(value);
93
+ // Auto-save with debouncing to avoid excessive API calls
94
+ debouncedSavePort("leader", value);
95
+ };
96
+
97
+ const handleFollowerPortChange = (value: string) => {
98
+ setFollowerPort(value);
99
+ // Auto-save with debouncing to avoid excessive API calls
100
+ debouncedSavePort("follower", value);
101
+ };
102
+
103
+ // Enhanced config change handlers that save automatically
104
+ const handleLeaderConfigChange = (value: string) => {
105
+ setLeaderConfig(value);
106
+ // Auto-save with debouncing to avoid excessive API calls
107
+ debouncedSaveConfig("leader", value);
108
+ };
109
+
110
+ const handleFollowerConfigChange = (value: string) => {
111
+ setFollowerConfig(value);
112
+ // Auto-save with debouncing to avoid excessive API calls
113
+ debouncedSaveConfig("follower", value);
114
+ };
115
+ // Load saved ports and configurations on component mount
116
  useEffect(() => {
117
+ const loadSavedData = async () => {
118
  try {
119
  // Load leader port
120
  const leaderResponse = await fetchWithHeaders(
 
133
  if (followerData.status === "success" && followerData.default_port) {
134
  setFollowerPort(followerData.default_port);
135
  }
136
+
137
+ // Load leader configuration
138
+ const leaderConfigResponse = await fetchWithHeaders(
139
+ `${baseUrl}/robot-config/leader?available_configs=${leaderConfigs.join(',')}`
140
+ );
141
+ const leaderConfigData = await leaderConfigResponse.json();
142
+ if (leaderConfigData.status === "success" && leaderConfigData.default_config) {
143
+ setLeaderConfig(leaderConfigData.default_config);
144
+ }
145
+
146
+ // Load follower configuration
147
+ const followerConfigResponse = await fetchWithHeaders(
148
+ `${baseUrl}/robot-config/follower?available_configs=${followerConfigs.join(',')}`
149
+ );
150
+ const followerConfigData = await followerConfigResponse.json();
151
+ if (followerConfigData.status === "success" && followerConfigData.default_config) {
152
+ setFollowerConfig(followerConfigData.default_config);
153
+ }
154
  } catch (error) {
155
+ console.error("Error loading saved data:", error);
156
  }
157
  };
158
+
159
+ if (open && leaderConfigs.length > 0 && followerConfigs.length > 0) {
160
+ loadSavedData();
 
 
 
 
 
 
 
 
 
 
161
  }
162
+ }, [open, setLeaderPort, setFollowerPort, setLeaderConfig, setFollowerConfig, leaderConfigs, followerConfigs, baseUrl, fetchWithHeaders]);
163
+
164
  const handleQrCodeClick = () => {
165
  // Generate a session ID for this recording session
166
  const newSessionId = `recording_${Date.now()}_${Math.random()
 
223
  <Input
224
  id="recordLeaderPort"
225
  value={leaderPort}
226
+ onChange={(e) => handleLeaderPortChange(e.target.value)}
227
  placeholder="/dev/tty.usbmodem5A460816421"
228
  className="bg-gray-800 border-gray-700 text-white flex-1"
229
  />
 
242
  </Label>
243
  <Select
244
  value={leaderConfig}
245
+ onValueChange={handleLeaderConfigChange}
246
  >
247
  <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
248
  <SelectValue
 
277
  <Input
278
  id="recordFollowerPort"
279
  value={followerPort}
280
+ onChange={(e) => handleFollowerPortChange(e.target.value)}
281
  placeholder="/dev/tty.usbmodem5A460816621"
282
  className="bg-gray-800 border-gray-700 text-white flex-1"
283
  />
 
296
  </Label>
297
  <Select
298
  value={followerConfig}
299
+ onValueChange={handleFollowerConfigChange}
300
  >
301
  <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
302
  <SelectValue
src/components/landing/TeleoperationModal.tsx CHANGED
@@ -20,6 +20,7 @@ import { Settings } from "lucide-react";
20
  import PortDetectionModal from "@/components/ui/PortDetectionModal";
21
  import PortDetectionButton from "@/components/ui/PortDetectionButton";
22
  import { useApi } from "@/contexts/ApiContext";
 
23
 
24
  interface TeleoperationModalProps {
25
  open: boolean;
@@ -55,14 +56,15 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
55
  onStart,
56
  }) => {
57
  const { baseUrl, fetchWithHeaders } = useApi();
 
58
  const [showPortDetection, setShowPortDetection] = useState(false);
59
  const [detectionRobotType, setDetectionRobotType] = useState<
60
  "leader" | "follower"
61
  >("leader");
62
 
63
- // Load saved ports on component mount
64
  useEffect(() => {
65
- const loadSavedPorts = async () => {
66
  try {
67
  // Load leader port
68
  const leaderResponse = await fetchWithHeaders(
@@ -81,15 +83,33 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
81
  if (followerData.status === "success" && followerData.default_port) {
82
  setFollowerPort(followerData.default_port);
83
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  } catch (error) {
85
- console.error("Error loading saved ports:", error);
86
  }
87
  };
88
 
89
- if (open) {
90
- loadSavedPorts();
91
  }
92
- }, [open, setLeaderPort, setFollowerPort]);
93
 
94
  const handlePortDetection = (robotType: "leader" | "follower") => {
95
  setDetectionRobotType(robotType);
@@ -103,6 +123,32 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
103
  setFollowerPort(port);
104
  }
105
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  return (
107
  <Dialog open={open} onOpenChange={onOpenChange}>
108
  <DialogContent className="bg-gray-900 border-gray-800 text-white sm:max-w-[600px] p-8">
@@ -132,7 +178,7 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
132
  <Input
133
  id="leaderPort"
134
  value={leaderPort}
135
- onChange={(e) => setLeaderPort(e.target.value)}
136
  placeholder="/dev/tty.usbmodem5A460816421"
137
  className="bg-gray-800 border-gray-700 text-white flex-1"
138
  />
@@ -150,7 +196,7 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
150
  >
151
  Leader Calibration Config
152
  </Label>
153
- <Select value={leaderConfig} onValueChange={setLeaderConfig}>
154
  <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
155
  <SelectValue
156
  placeholder={
@@ -185,7 +231,7 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
185
  <Input
186
  id="followerPort"
187
  value={followerPort}
188
- onChange={(e) => setFollowerPort(e.target.value)}
189
  placeholder="/dev/tty.usbmodem5A460816621"
190
  className="bg-gray-800 border-gray-700 text-white flex-1"
191
  />
@@ -203,7 +249,7 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
203
  >
204
  Follower Calibration Config
205
  </Label>
206
- <Select value={followerConfig} onValueChange={setFollowerConfig}>
207
  <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
208
  <SelectValue
209
  placeholder={
 
20
  import PortDetectionModal from "@/components/ui/PortDetectionModal";
21
  import PortDetectionButton from "@/components/ui/PortDetectionButton";
22
  import { useApi } from "@/contexts/ApiContext";
23
+ import { useAutoSave } from "@/hooks/useAutoSave";
24
 
25
  interface TeleoperationModalProps {
26
  open: boolean;
 
56
  onStart,
57
  }) => {
58
  const { baseUrl, fetchWithHeaders } = useApi();
59
+ const { debouncedSavePort, debouncedSaveConfig } = useAutoSave();
60
  const [showPortDetection, setShowPortDetection] = useState(false);
61
  const [detectionRobotType, setDetectionRobotType] = useState<
62
  "leader" | "follower"
63
  >("leader");
64
 
65
+ // Load saved ports and configurations on component mount
66
  useEffect(() => {
67
+ const loadSavedData = async () => {
68
  try {
69
  // Load leader port
70
  const leaderResponse = await fetchWithHeaders(
 
83
  if (followerData.status === "success" && followerData.default_port) {
84
  setFollowerPort(followerData.default_port);
85
  }
86
+
87
+ // Load leader configuration
88
+ const leaderConfigResponse = await fetchWithHeaders(
89
+ `${baseUrl}/robot-config/leader?available_configs=${leaderConfigs.join(',')}`
90
+ );
91
+ const leaderConfigData = await leaderConfigResponse.json();
92
+ if (leaderConfigData.status === "success" && leaderConfigData.default_config) {
93
+ setLeaderConfig(leaderConfigData.default_config);
94
+ }
95
+
96
+ // Load follower configuration
97
+ const followerConfigResponse = await fetchWithHeaders(
98
+ `${baseUrl}/robot-config/follower?available_configs=${followerConfigs.join(',')}`
99
+ );
100
+ const followerConfigData = await followerConfigResponse.json();
101
+ if (followerConfigData.status === "success" && followerConfigData.default_config) {
102
+ setFollowerConfig(followerConfigData.default_config);
103
+ }
104
  } catch (error) {
105
+ console.error("Error loading saved data:", error);
106
  }
107
  };
108
 
109
+ if (open && leaderConfigs.length > 0 && followerConfigs.length > 0) {
110
+ loadSavedData();
111
  }
112
+ }, [open, setLeaderPort, setFollowerPort, setLeaderConfig, setFollowerConfig, leaderConfigs, followerConfigs, baseUrl, fetchWithHeaders]);
113
 
114
  const handlePortDetection = (robotType: "leader" | "follower") => {
115
  setDetectionRobotType(robotType);
 
123
  setFollowerPort(port);
124
  }
125
  };
126
+
127
+ // Enhanced port change handlers that save automatically
128
+ const handleLeaderPortChange = (value: string) => {
129
+ setLeaderPort(value);
130
+ // Auto-save with debouncing to avoid excessive API calls
131
+ debouncedSavePort("leader", value);
132
+ };
133
+
134
+ const handleFollowerPortChange = (value: string) => {
135
+ setFollowerPort(value);
136
+ // Auto-save with debouncing to avoid excessive API calls
137
+ debouncedSavePort("follower", value);
138
+ };
139
+
140
+ // Enhanced config change handlers that save automatically
141
+ const handleLeaderConfigChange = (value: string) => {
142
+ setLeaderConfig(value);
143
+ // Auto-save with debouncing to avoid excessive API calls
144
+ debouncedSaveConfig("leader", value);
145
+ };
146
+
147
+ const handleFollowerConfigChange = (value: string) => {
148
+ setFollowerConfig(value);
149
+ // Auto-save with debouncing to avoid excessive API calls
150
+ debouncedSaveConfig("follower", value);
151
+ };
152
  return (
153
  <Dialog open={open} onOpenChange={onOpenChange}>
154
  <DialogContent className="bg-gray-900 border-gray-800 text-white sm:max-w-[600px] p-8">
 
178
  <Input
179
  id="leaderPort"
180
  value={leaderPort}
181
+ onChange={(e) => handleLeaderPortChange(e.target.value)}
182
  placeholder="/dev/tty.usbmodem5A460816421"
183
  className="bg-gray-800 border-gray-700 text-white flex-1"
184
  />
 
196
  >
197
  Leader Calibration Config
198
  </Label>
199
+ <Select value={leaderConfig} onValueChange={handleLeaderConfigChange}>
200
  <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
201
  <SelectValue
202
  placeholder={
 
231
  <Input
232
  id="followerPort"
233
  value={followerPort}
234
+ onChange={(e) => handleFollowerPortChange(e.target.value)}
235
  placeholder="/dev/tty.usbmodem5A460816621"
236
  className="bg-gray-800 border-gray-700 text-white flex-1"
237
  />
 
249
  >
250
  Follower Calibration Config
251
  </Label>
252
+ <Select value={followerConfig} onValueChange={handleFollowerConfigChange}>
253
  <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
254
  <SelectValue
255
  placeholder={
src/hooks/useAutoSave.ts ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useCallback, useRef } from 'react';
2
+ import { useApi } from '@/contexts/ApiContext';
3
+
4
+ export const useAutoSave = () => {
5
+ const { baseUrl, fetchWithHeaders } = useApi();
6
+ const timeoutRefs = useRef<{ [key: string]: NodeJS.Timeout }>({});
7
+ const configTimeoutRefs = useRef<{ [key: string]: NodeJS.Timeout }>({});
8
+
9
+ const savePortAutomatically = useCallback(async (robotType: 'leader' | 'follower', port: string) => {
10
+ if (!port.trim()) return;
11
+
12
+ try {
13
+ await fetchWithHeaders(`${baseUrl}/save-robot-port`, {
14
+ method: 'POST',
15
+ headers: {
16
+ 'Content-Type': 'application/json',
17
+ },
18
+ body: JSON.stringify({
19
+ robot_type: robotType,
20
+ port: port.trim(),
21
+ }),
22
+ });
23
+ console.log(`Auto-saved ${robotType} port: ${port}`);
24
+ } catch (error) {
25
+ console.error(`Error saving ${robotType} port:`, error);
26
+ }
27
+ }, [baseUrl, fetchWithHeaders]);
28
+
29
+ const saveConfigAutomatically = useCallback(async (robotType: 'leader' | 'follower', configName: string) => {
30
+ if (!configName.trim()) return;
31
+
32
+ try {
33
+ await fetchWithHeaders(`${baseUrl}/save-robot-config`, {
34
+ method: 'POST',
35
+ headers: {
36
+ 'Content-Type': 'application/json',
37
+ },
38
+ body: JSON.stringify({
39
+ robot_type: robotType,
40
+ config_name: configName.trim(),
41
+ }),
42
+ });
43
+ console.log(`Auto-saved ${robotType} config: ${configName}`);
44
+ } catch (error) {
45
+ console.error(`Error saving ${robotType} config:`, error);
46
+ }
47
+ }, [baseUrl, fetchWithHeaders]);
48
+
49
+ const debouncedSavePort = useCallback((robotType: 'leader' | 'follower', port: string, delay: number = 1500) => {
50
+ // Clear existing timeout for this robotType
51
+ if (timeoutRefs.current[robotType]) {
52
+ clearTimeout(timeoutRefs.current[robotType]);
53
+ }
54
+
55
+ // Set new timeout
56
+ timeoutRefs.current[robotType] = setTimeout(() => {
57
+ savePortAutomatically(robotType, port);
58
+ delete timeoutRefs.current[robotType];
59
+ }, delay);
60
+ }, [savePortAutomatically]);
61
+
62
+ const debouncedSaveConfig = useCallback((robotType: 'leader' | 'follower', configName: string, delay: number = 1000) => {
63
+ const key = `${robotType}_config`;
64
+
65
+ // Clear existing timeout for this robotType config
66
+ if (configTimeoutRefs.current[key]) {
67
+ clearTimeout(configTimeoutRefs.current[key]);
68
+ }
69
+
70
+ // Set new timeout
71
+ configTimeoutRefs.current[key] = setTimeout(() => {
72
+ saveConfigAutomatically(robotType, configName);
73
+ delete configTimeoutRefs.current[key];
74
+ }, delay);
75
+ }, [saveConfigAutomatically]);
76
+
77
+ return { debouncedSavePort, debouncedSaveConfig };
78
+ };