v0.1.0-pre.052
This commit is contained in:
185
kb-app-demo-desktop/frontend/ts/demo_core_extraction.ts
Normal file
185
kb-app-demo-desktop/frontend/ts/demo_core_extraction.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_core_extraction.ts
|
||||
// version: 4
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
import ResizeObserver from "resize-observer-polyfill";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log.ts";
|
||||
import type { DemoCoreExtractionOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_core_extraction/DemoCoreExtractionOptionsPayload.ts";
|
||||
import type { DemoCoreExtractionProgressPayload } from "./bindings/kb_app_demo_desktop/demo_core_extraction/DemoCoreExtractionProgressPayload.ts";
|
||||
import type { DemoCoreExtractionRequest } from "./bindings/kb_app_demo_desktop/demo_core_extraction/DemoCoreExtractionRequest.ts";
|
||||
import type { DemoCoreExtractionSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_core_extraction/DemoCoreExtractionSummaryPayload.ts";
|
||||
|
||||
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
|
||||
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
|
||||
|
||||
const logLines: string[] = [];
|
||||
const maximumLogLines = 1000;
|
||||
let running = false;
|
||||
|
||||
function element<T extends HTMLElement>(selector: string): T {
|
||||
const value = document.querySelector<T>(selector);
|
||||
if (!value) {
|
||||
throw new Error(`Missing UI element: ${selector}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function inputValue(selector: string): string {
|
||||
return element<HTMLInputElement | HTMLTextAreaElement>(selector).value.trim();
|
||||
}
|
||||
|
||||
function integerValue(selector: string): number {
|
||||
const parsed = Number.parseInt(inputValue(selector), 10);
|
||||
if (!Number.isFinite(parsed)) {
|
||||
throw new Error(`Valeur numérique invalide pour ${selector}`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function nullableIntegerValue(selector: string): number | null {
|
||||
const value = inputValue(selector);
|
||||
if (value.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
if (!Number.isFinite(parsed)) {
|
||||
throw new Error(`Valeur numérique invalide pour ${selector}`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function appendLog(payload: DemoCoreExtractionProgressPayload | { timestamp: string; level: string; message: string }): void {
|
||||
const progress = "completed" in payload ? ` [${payload.completed}/${payload.total}]` : "";
|
||||
logLines.push(`${payload.timestamp} ${payload.level.toUpperCase()}${progress} ${payload.message}`);
|
||||
while (logLines.length > maximumLogLines) {
|
||||
logLines.shift();
|
||||
}
|
||||
const output = element<HTMLTextAreaElement>("#coreExtractionLogOutput");
|
||||
output.value = logLines.join("\n");
|
||||
output.scrollTop = output.scrollHeight;
|
||||
}
|
||||
|
||||
function setRunning(value: boolean): void {
|
||||
running = value;
|
||||
document.querySelectorAll<HTMLButtonElement>(".core-extraction-start-button").forEach(button => {
|
||||
button.disabled = value;
|
||||
});
|
||||
element<HTMLButtonElement>("#cancelCoreExtractionButton").disabled = !value;
|
||||
const badge = element<HTMLElement>("#coreExtractionStatusBadge");
|
||||
badge.textContent = value ? "Extraction en cours" : "Prêt";
|
||||
badge.className = value ? "badge text-bg-warning" : "badge text-bg-success";
|
||||
}
|
||||
|
||||
function buildRequest(mode: string): DemoCoreExtractionRequest {
|
||||
return {
|
||||
mode,
|
||||
signaturesText: mode === "signatures" ? element<HTMLTextAreaElement>("#coreExtractionSignaturesTextarea").value : null,
|
||||
programId: mode === "program_id" ? inputValue("#coreExtractionProgramIdInput") : null,
|
||||
minSlot: mode === "slot_range" ? nullableIntegerValue("#coreExtractionMinSlotInput") : null,
|
||||
maxSlot: mode === "slot_range" ? nullableIntegerValue("#coreExtractionMaxSlotInput") : null,
|
||||
limit: integerValue("#coreExtractionLimitInput"),
|
||||
maxConcurrentExtractions: integerValue("#coreExtractionConcurrencyInput"),
|
||||
forceReplay: element<HTMLInputElement>("#coreExtractionForceReplayInput").checked,
|
||||
};
|
||||
}
|
||||
|
||||
async function executeCoreExtraction(mode: string): Promise<void> {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const request = buildRequest(mode);
|
||||
setRunning(true);
|
||||
element<HTMLTextAreaElement>("#coreExtractionSummaryOutput").value = "Exécution en cours...";
|
||||
appendLog({ timestamp: new Date().toISOString(), level: "info", message: `Démarrage du mode ${mode}` });
|
||||
const summary = await invoke<DemoCoreExtractionSummaryPayload>("demo_core_extraction_execute", { request });
|
||||
element<HTMLTextAreaElement>("#coreExtractionSummaryOutput").value = JSON.stringify(summary, null, 2);
|
||||
appendLog({
|
||||
timestamp: new Date().toISOString(),
|
||||
level: summary.cancelled || Number(summary.failed) > 0 ? "warn" : "info",
|
||||
message: `Campagne terminée : extracted=${summary.extracted}, skipped=${summary.skipped}, failed=${summary.failed}, cancelled=${summary.cancelledCandidates}, notStarted=${summary.notStarted}`,
|
||||
});
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
element<HTMLTextAreaElement>("#coreExtractionSummaryOutput").value = JSON.stringify({ error: message }, null, 2);
|
||||
appendLog({ timestamp: new Date().toISOString(), level: "error", message });
|
||||
frontendError("kb_app_demo_desktop.frontend.demo_core_extraction", `Core extraction failed: ${message}`);
|
||||
} finally {
|
||||
setRunning(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelCoreExtraction(): Promise<void> {
|
||||
try {
|
||||
const accepted = await invoke<boolean>("demo_core_extraction_cancel");
|
||||
appendLog({
|
||||
timestamp: new Date().toISOString(),
|
||||
level: accepted ? "warn" : "info",
|
||||
message: accepted ? "Demande d'arrêt envoyée." : "Aucune campagne active.",
|
||||
});
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
frontendError("kb_app_demo_desktop.frontend.demo_core_extraction", `Cancellation failed: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function openDiagnostics(command: "open_demo_sql_pg_raw_window" | "open_demo_sql_pg_core_window" | "open_demo_sql_replay_candidates_window", label: string): Promise<void> {
|
||||
try {
|
||||
await invoke(command);
|
||||
appendLog({ timestamp: new Date().toISOString(), level: "info", message: `${label} ouvert.` });
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
appendLog({ timestamp: new Date().toISOString(), level: "error", message });
|
||||
frontendError("kb_app_demo_desktop.frontend.demo_core_extraction", `${label} opening failed: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadOptions(): Promise<void> {
|
||||
const options = await invoke<DemoCoreExtractionOptionsPayload>("demo_core_extraction_options");
|
||||
element<HTMLElement>("#coreExtractionVersionBadge").textContent = `Version ${options.processorVersion}`;
|
||||
element<HTMLInputElement>("#coreExtractionLimitInput").value = String(options.defaultLimit);
|
||||
element<HTMLInputElement>("#coreExtractionConcurrencyInput").value = String(options.defaultMaxConcurrentExtractions);
|
||||
setRunning(options.running);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
installFrontendConsoleBridge("kb_app_demo_desktop.frontend.demo_core_extraction");
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_core_extraction", "core extraction demo window loaded");
|
||||
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(item => new bootstrap.Tooltip(item));
|
||||
document.querySelectorAll<HTMLButtonElement>(".core-extraction-start-button").forEach(button => {
|
||||
button.addEventListener("click", () => {
|
||||
const mode = button.dataset.coreExtractionMode;
|
||||
if (mode) {
|
||||
void executeCoreExtraction(mode);
|
||||
}
|
||||
});
|
||||
});
|
||||
element<HTMLButtonElement>("#openRawDiagnosticsButton").addEventListener("click", () => {
|
||||
void openDiagnostics("open_demo_sql_pg_raw_window", "Diagnostic raw");
|
||||
});
|
||||
element<HTMLButtonElement>("#openCoreDiagnosticsButton").addEventListener("click", () => {
|
||||
void openDiagnostics("open_demo_sql_pg_core_window", "Diagnostic core");
|
||||
});
|
||||
element<HTMLButtonElement>("#openReplayCandidatesButton").addEventListener("click", () => {
|
||||
void openDiagnostics("open_demo_sql_replay_candidates_window", "Sélecteur de candidats replay");
|
||||
});
|
||||
element<HTMLButtonElement>("#cancelCoreExtractionButton").addEventListener("click", () => {
|
||||
void cancelCoreExtraction();
|
||||
});
|
||||
element<HTMLButtonElement>("#clearCoreExtractionLogButton").addEventListener("click", () => {
|
||||
logLines.length = 0;
|
||||
element<HTMLTextAreaElement>("#coreExtractionLogOutput").value = "";
|
||||
});
|
||||
void listen<DemoCoreExtractionProgressPayload>("demo-core-extraction-progress", event => {
|
||||
appendLog(event.payload);
|
||||
});
|
||||
void loadOptions().catch(caughtError => {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
appendLog({ timestamp: new Date().toISOString(), level: "error", message });
|
||||
frontendError("kb_app_demo_desktop.frontend.demo_core_extraction", `Options loading failed: ${message}`);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user