Сохранять каждый запуск датасета в отдельную папку и показывать прогресс генерации.

Файлы пишутся в sonar_dataset/дата-время-модель без удаления прошлых запусков; кнопка «Генерация…» заполняется по мере записи сцен через NDJSON-стрим.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-17 15:30:07 +03:00
co-authored by Cursor
parent 643d834ba7
commit 8b248e1d54
5 changed files with 258 additions and 42 deletions
+67 -2
View File
@@ -289,7 +289,7 @@ export const api = {
});
triggerDownload(blob, safeName || suggested);
},
datasetGenerate: ({
datasetGenerate: async ({
count = 5,
seed = 42,
outputDir = "sonar_dataset",
@@ -298,6 +298,7 @@ export const api = {
beamCount = 45,
lengthCount = 45,
modelFile,
onProgress,
} = {}) => {
if (!modelFile) {
return Promise.reject(new Error("Выберите файл модели .obj"));
@@ -311,10 +312,74 @@ export const api = {
formData.append("beamCount", String(beamCount ?? 45));
formData.append("lengthCount", String(lengthCount ?? 45));
formData.append("model", modelFile, modelFile.name || "model.obj");
return request("/api/dataset/generate", {
const response = await fetch("/api/dataset/generate", {
method: "POST",
body: formData,
});
if (!response.ok) {
const payload = await response.json().catch(() => ({}));
throw new Error(formatApiError(payload.detail, `Request failed: /api/dataset/generate`));
}
if (!response.body) {
throw new Error("Пустой ответ генерации датасета");
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let result = null;
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) continue;
let event;
try {
event = JSON.parse(trimmed);
} catch {
continue;
}
if (event.type === "error") {
throw new Error(event.detail || "Ошибка генерации датасета");
}
if (event.type === "start" || event.type === "progress") {
if (typeof onProgress === "function") onProgress(event);
}
if (event.type === "done") {
result = event.result;
}
}
}
if (buffer.trim()) {
try {
const event = JSON.parse(buffer.trim());
if (event.type === "error") {
throw new Error(event.detail || "Ошибка генерации датасета");
}
if (event.type === "done") result = event.result;
if ((event.type === "start" || event.type === "progress") && typeof onProgress === "function") {
onProgress(event);
}
} catch (error) {
if (error instanceof SyntaxError) {
/* ignore trailing junk */
} else {
throw error;
}
}
}
if (!result) {
throw new Error("Генерация завершилась без результата");
}
return result;
},
datasetPreview: ({ stem, outputDir = "sonar_dataset", maxPoints = 25000 } = {}) =>
request("/api/dataset/preview", {