Stream job logs from current offset
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import {
|
||||
Activity,
|
||||
@@ -45,6 +45,7 @@ type Job = {
|
||||
started_at?: string;
|
||||
finished_at?: string;
|
||||
log_tail?: string;
|
||||
log_size?: number;
|
||||
params: Record<string, unknown>;
|
||||
progress?: JobProgress;
|
||||
};
|
||||
@@ -430,6 +431,11 @@ function App() {
|
||||
const [uploadFiles, setUploadFiles] = useState<FileList | null>(null);
|
||||
const [agentValidation, setAgentValidation] = useState<ValidationAgentPayload | null>(null);
|
||||
const [agentBusy, setAgentBusy] = useState(false);
|
||||
const eventSourceRef = useRef<EventSource | null>(null);
|
||||
|
||||
useEffect(() => () => {
|
||||
eventSourceRef.current?.close();
|
||||
}, []);
|
||||
|
||||
const runningCount = jobs.filter((job) => job.status === "running").length;
|
||||
const successCount = jobs.filter((job) => job.status === "success").length;
|
||||
@@ -515,10 +521,11 @@ function App() {
|
||||
async function createJob() {
|
||||
setBusy(true);
|
||||
try {
|
||||
await api<Job>("/api/jobs", {
|
||||
const job = await api<Job>("/api/jobs", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ type: taskType, params: JSON.parse(params) })
|
||||
});
|
||||
await inspectJob(job);
|
||||
await refresh();
|
||||
} finally {
|
||||
setBusy(false);
|
||||
@@ -633,13 +640,14 @@ function App() {
|
||||
try {
|
||||
const generated = await createSelectedYoloYaml();
|
||||
if (!generated) return;
|
||||
await api<Job>("/api/jobs", {
|
||||
const job = await api<Job>("/api/jobs", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
type: "yolo.train_custom",
|
||||
params: { model: "YOLO11n-seg", data: generated.path, epochs: 10, imgsz: 640, batch: 1, workers: 0, device: "cpu", project: "var/custom_yolo_runs", name: selectedDataset.name, exist_ok: true }
|
||||
})
|
||||
});
|
||||
await inspectJob(job);
|
||||
window.location.hash = "jobs";
|
||||
await refresh();
|
||||
} finally {
|
||||
@@ -651,13 +659,14 @@ function App() {
|
||||
if (!selectedDataset?.absolute_layout) return;
|
||||
setBusy(true);
|
||||
try {
|
||||
await api<Job>("/api/jobs", {
|
||||
const job = await api<Job>("/api/jobs", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
type: "yolo.predict_custom",
|
||||
params: { weights: customYoloWeightPath(selectedDataset), source: selectedDataset.absolute_layout.images, imgsz: 640, conf: 0.25, device: "cpu", project: "var/custom_yolo_runs", name: `${selectedDataset.name}_predict`, exist_ok: true }
|
||||
})
|
||||
});
|
||||
await inspectJob(job);
|
||||
window.location.hash = "jobs";
|
||||
await refresh();
|
||||
} finally {
|
||||
@@ -669,13 +678,14 @@ function App() {
|
||||
if (!selectedDataset?.absolute_layout) return;
|
||||
setBusy(true);
|
||||
try {
|
||||
await api<Job>("/api/jobs", {
|
||||
const job = await api<Job>("/api/jobs", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
type: "yolo.heatmap_custom",
|
||||
params: { weights: customYoloWeightPath(selectedDataset), source: selectedDataset.absolute_layout.images, model_key: "YOLO11n-seg", cam_method: "GradCAM", target_layers: "model.model.model[9]", limit: 3, project: "var/custom_yolo_runs", name: `${selectedDataset.name}_heatmap` }
|
||||
})
|
||||
});
|
||||
await inspectJob(job);
|
||||
window.location.hash = "jobs";
|
||||
await refresh();
|
||||
} finally {
|
||||
@@ -684,15 +694,24 @@ function App() {
|
||||
}
|
||||
|
||||
async function inspectJob(job: Job) {
|
||||
eventSourceRef.current?.close();
|
||||
const detail = await api<Job>(`/api/jobs/${job.id}`);
|
||||
setSelectedJob(detail);
|
||||
setLog(detail.log_tail ?? "");
|
||||
const source = new EventSource(`${API_BASE}/api/jobs/${job.id}/events`);
|
||||
const source = new EventSource(`${API_BASE}/api/jobs/${job.id}/events?offset=${detail.log_size ?? 0}`);
|
||||
eventSourceRef.current = source;
|
||||
source.onmessage = (event) => {
|
||||
const payload = JSON.parse(event.data);
|
||||
if (payload.chunk) setLog((prev) => `${prev}${payload.chunk}`);
|
||||
setSelectedJob(payload.job);
|
||||
if (["success", "failed", "cancelled"].includes(payload.job.status)) source.close();
|
||||
if (["success", "failed", "cancelled"].includes(payload.job.status)) {
|
||||
source.close();
|
||||
if (eventSourceRef.current === source) eventSourceRef.current = null;
|
||||
}
|
||||
};
|
||||
source.onerror = () => {
|
||||
source.close();
|
||||
if (eventSourceRef.current === source) eventSourceRef.current = null;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user