Claude commited on
Commit
af9f4e6
·
unverified ·
1 Parent(s): 0f54cd7

Add 10-min GPU idle-shutdown; make Preview/Code toggle a real button

Browse files

The simulated NVIDIA GPU badge now sleeps after 10 minutes without a
Run Test, mirroring real rented-GPU cost behavior; the next run pays
an honest, logged cold-start delay to wake it. Also replaced the
easy-to-miss vertical edge tab with a bold fixed pill button
(bottom-right) for Preview/Code, since the original was overlapping
other text and going unnoticed.

Files changed (1) hide show
  1. src/app/testbench/Testbench.tsx +66 -8
src/app/testbench/Testbench.tsx CHANGED
@@ -35,6 +35,8 @@ type SessionRun = {
35
  log: string[];
36
  };
37
 
 
 
38
  export default function Testbench() {
39
  const [targets, setTargets] = useState<Target[] | null>(null);
40
  const [targetsError, setTargetsError] = useState<string | null>(null);
@@ -69,6 +71,26 @@ export default function Testbench() {
69
  // effect of every individual run.
70
  const [sessionRuns, setSessionRuns] = useState<SessionRun[]>([]);
71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  useEffect(() => {
73
  fetch("/api/testbench")
74
  .then(async (res) => {
@@ -108,6 +130,7 @@ export default function Testbench() {
108
  if (tickRef.current) clearInterval(tickRef.current);
109
  if (lockTimeoutRef.current) clearTimeout(lockTimeoutRef.current);
110
 
 
111
  setTab("console");
112
 
113
  // Re-verify the lock right at the boundary where nodes leave the grid.
@@ -139,18 +162,43 @@ export default function Testbench() {
139
  const script = buildSimulatedRunScript(berylPipeline, label);
140
 
141
  const introLine = `Artifact lock verified — sha256:${fresh.checksum.slice(0, 16)}… · ${fresh.byteSize}B · ${fresh.loc} LOC · ${fresh.nodeCount} nodes. Checked out from the front for this run.`;
142
- setLog([{ atMs: 0, level: "ok", text: introLine }]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  setElapsedMs(0);
144
  setRunState("running");
145
  setLockStatus("checked-out");
 
146
 
147
  const start = Date.now();
148
  tickRef.current = setInterval(() => setElapsedMs(Date.now() - start), 200);
149
 
 
 
 
 
 
 
150
  script.forEach((line) => {
151
  const timer = setTimeout(() => {
152
  setLog((prev) => [...prev, line]);
153
- }, line.atMs);
154
  timers.current.push(timer);
155
  });
156
 
@@ -166,7 +214,7 @@ export default function Testbench() {
166
  ]);
167
  }, DEFAULT_LOCK_TIMEOUT_MS);
168
 
169
- const finishAt = script[script.length - 1]?.atMs ?? 0;
170
  const finishTimer = setTimeout(async () => {
171
  setRunState("done");
172
  if (tickRef.current) clearInterval(tickRef.current);
@@ -265,11 +313,14 @@ export default function Testbench() {
265
  <div className="flex-1 flex flex-col h-[calc(100dvh-var(--topnav-h))] relative">
266
  <button
267
  onClick={toggleCodeView}
268
- className="fixed right-0 top-1/2 -translate-y-1/2 z-30 flex flex-col items-center gap-1.5 px-2 py-3 rounded-l-lg border border-r-0 border-[var(--panel-border)] glass-panel text-[10px] font-semibold tracking-wide text-[var(--muted)] hover:text-white transition"
269
- style={{ writingMode: "vertical-rl" }}
 
 
 
270
  title={codeView ? "Switch to Preview" : "Switch to Code — real source from the reference targets"}
271
  >
272
- {codeView ? <Eye size={12} /> : <Code2 size={12} />}
273
  {codeView ? "PREVIEW" : "CODE"}
274
  </button>
275
 
@@ -284,8 +335,15 @@ export default function Testbench() {
284
  </span>
285
  </div>
286
  <div className="ml-auto flex items-center gap-2 shrink-0">
287
- <span className="flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-full border border-[var(--accent-teal)]/40 text-[var(--accent-teal)]">
288
- <Cpu size={12} /> NVIDIA GPU · simulated
 
 
 
 
 
 
 
289
  </span>
290
  </div>
291
  </header>
 
35
  log: string[];
36
  };
37
 
38
+ const GPU_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
39
+
40
  export default function Testbench() {
41
  const [targets, setTargets] = useState<Target[] | null>(null);
42
  const [targetsError, setTargetsError] = useState<string | null>(null);
 
71
  // effect of every individual run.
72
  const [sessionRuns, setSessionRuns] = useState<SessionRun[]>([]);
73
 
74
+ // Idle-shutdown for the simulated GPU: mirrors real rented-GPU behavior —
75
+ // no Run Test activity for GPU_IDLE_TIMEOUT_MS puts it to sleep so it isn't
76
+ // "billing" while unused; the next run pays a short cold-start cost to wake
77
+ // it back up instead of silently staying "on" forever.
78
+ const [gpuAsleep, setGpuAsleep] = useState(false);
79
+ const gpuIdleTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
80
+
81
+ function resetGpuIdleTimer() {
82
+ if (gpuIdleTimerRef.current) clearTimeout(gpuIdleTimerRef.current);
83
+ gpuIdleTimerRef.current = setTimeout(() => setGpuAsleep(true), GPU_IDLE_TIMEOUT_MS);
84
+ }
85
+
86
+ useEffect(() => {
87
+ resetGpuIdleTimer();
88
+ return () => {
89
+ if (gpuIdleTimerRef.current) clearTimeout(gpuIdleTimerRef.current);
90
+ };
91
+ // eslint-disable-next-line react-hooks/exhaustive-deps
92
+ }, []);
93
+
94
  useEffect(() => {
95
  fetch("/api/testbench")
96
  .then(async (res) => {
 
130
  if (tickRef.current) clearInterval(tickRef.current);
131
  if (lockTimeoutRef.current) clearTimeout(lockTimeoutRef.current);
132
 
133
+ resetGpuIdleTimer();
134
  setTab("console");
135
 
136
  // Re-verify the lock right at the boundary where nodes leave the grid.
 
162
  const script = buildSimulatedRunScript(berylPipeline, label);
163
 
164
  const introLine = `Artifact lock verified — sha256:${fresh.checksum.slice(0, 16)}… · ${fresh.byteSize}B · ${fresh.loc} LOC · ${fresh.nodeCount} nodes. Checked out from the front for this run.`;
165
+
166
+ // If the GPU went to sleep from inactivity, waking it back up costs a
167
+ // short, honestly-logged cold-start delay before the run actually begins.
168
+ const wasAsleep = gpuAsleep;
169
+ const preamble: SandboxLogLine[] = [];
170
+ let preambleT = 0;
171
+ if (wasAsleep) {
172
+ preamble.push({
173
+ atMs: preambleT,
174
+ level: "warn",
175
+ text: "GPU was asleep (idle >10min, shut off to avoid wasting money) — cold-starting...",
176
+ });
177
+ preambleT += 1200;
178
+ preamble.push({ atMs: preambleT, level: "ok", text: "NVIDIA GPU allocation restored (simulated)." });
179
+ preambleT += 700;
180
+ }
181
+ preamble.push({ atMs: preambleT, level: "ok", text: introLine });
182
+
183
+ setLog([]);
184
  setElapsedMs(0);
185
  setRunState("running");
186
  setLockStatus("checked-out");
187
+ if (wasAsleep) setGpuAsleep(false);
188
 
189
  const start = Date.now();
190
  tickRef.current = setInterval(() => setElapsedMs(Date.now() - start), 200);
191
 
192
+ preamble.forEach((line) => {
193
+ const timer = setTimeout(() => setLog((prev) => [...prev, line]), line.atMs);
194
+ timers.current.push(timer);
195
+ });
196
+
197
+ const scriptOffset = preambleT;
198
  script.forEach((line) => {
199
  const timer = setTimeout(() => {
200
  setLog((prev) => [...prev, line]);
201
+ }, line.atMs + scriptOffset);
202
  timers.current.push(timer);
203
  });
204
 
 
214
  ]);
215
  }, DEFAULT_LOCK_TIMEOUT_MS);
216
 
217
+ const finishAt = (script[script.length - 1]?.atMs ?? 0) + scriptOffset;
218
  const finishTimer = setTimeout(async () => {
219
  setRunState("done");
220
  if (tickRef.current) clearInterval(tickRef.current);
 
313
  <div className="flex-1 flex flex-col h-[calc(100dvh-var(--topnav-h))] relative">
314
  <button
315
  onClick={toggleCodeView}
316
+ className={`fixed right-5 bottom-6 z-30 flex items-center gap-2 px-4 py-2.5 rounded-full border-2 shadow-lg text-xs font-bold tracking-wide transition hover:scale-105 ${
317
+ codeView
318
+ ? "bg-[var(--accent-teal)] border-[var(--accent-teal)] text-black"
319
+ : "bg-[var(--panel)] border-[var(--gold)] text-[var(--gold-bright)]"
320
+ }`}
321
  title={codeView ? "Switch to Preview" : "Switch to Code — real source from the reference targets"}
322
  >
323
+ {codeView ? <Eye size={15} /> : <Code2 size={15} />}
324
  {codeView ? "PREVIEW" : "CODE"}
325
  </button>
326
 
 
335
  </span>
336
  </div>
337
  <div className="ml-auto flex items-center gap-2 shrink-0">
338
+ <span
339
+ className={`flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-full border transition ${
340
+ gpuAsleep
341
+ ? "border-[var(--panel-border)] text-[var(--muted)]"
342
+ : "border-[var(--accent-teal)]/40 text-[var(--accent-teal)]"
343
+ }`}
344
+ title={gpuAsleep ? `Idle >${GPU_IDLE_TIMEOUT_MS / 60000}min — shut off to avoid wasting money. Run Test to wake it.` : undefined}
345
+ >
346
+ <Cpu size={12} /> NVIDIA GPU · {gpuAsleep ? "sleeping (idle)" : "simulated"}
347
  </span>
348
  </div>
349
  </header>