242 lines
11 KiB
TypeScript
242 lines
11 KiB
TypeScript
import { detailFingerprint } from "../../../../../lib/autoScanLoop";
|
|
import { dataGeneratedAt, sourceVersion } from "../../../../../lib/genshinData";
|
|
import { dataPackageStatus } from "../../../../../lib/dataPackageStatus";
|
|
import { validateLookupPackage } from "../../../../../lib/genshinLookup";
|
|
import { useCallback, useMemo, type MouseEvent } from "react";
|
|
import type { ScanDiagnosticsModalProps } from "../types";
|
|
import type { ScanDiagnosticEvent } from "../../../../../lib/scanDiagnosticsLog";
|
|
|
|
export interface ScanDiagnosticsModelProgress {
|
|
width: number;
|
|
show: boolean;
|
|
}
|
|
|
|
export interface ScanDiagnosticsModalModel {
|
|
closeDiagnostics: () => void;
|
|
openDetails: () => void;
|
|
handleSaveReviewSample: () => void;
|
|
stopPropagation: (event: MouseEvent<HTMLDivElement>) => void;
|
|
canOpenDetails: boolean;
|
|
statusTitle: string;
|
|
rightsClassName: string;
|
|
rightsValue: string;
|
|
genshinClassName: string;
|
|
genshinValue: string;
|
|
shouldShowAdminBanner: boolean;
|
|
gridSourceClass: string;
|
|
gridMainValue: string;
|
|
gridMetaValue: string;
|
|
learningRulesText: string;
|
|
learningRulesSubtext: string;
|
|
autoScanModeLabel: string;
|
|
fingerprintText: string;
|
|
runtimeRows: string[];
|
|
scanLimitText: string;
|
|
scanTipText: string;
|
|
autoScanStatsLines: Array<{ label: string; value: number }>;
|
|
playerProgress: ScanDiagnosticsModelProgress;
|
|
autoScanRunning: boolean;
|
|
showDevRows: boolean;
|
|
reviewStatus: string;
|
|
automationLogLines: string[];
|
|
diagnosticEvents: ScanDiagnosticEvent[];
|
|
canSaveReviewSample: boolean;
|
|
}
|
|
|
|
type ScanDiagnosticsController = ScanDiagnosticsModalProps["controller"];
|
|
|
|
interface UseScanDiagnosticsModalModelInput extends Pick<
|
|
ScanDiagnosticsModalProps,
|
|
| "setDetailsOpen"
|
|
| "setDiagnosticsOpen"
|
|
| "latestCapture"
|
|
| "controller"
|
|
> {
|
|
captureStatus: string;
|
|
// saveReviewSample / canSaveReviewSample live on the controller, not the modal
|
|
// props; the component wires them through from controller.* .
|
|
saveReviewSample: ScanDiagnosticsController["saveReviewSample"];
|
|
canSaveReviewSample: ScanDiagnosticsController["canSaveReviewSample"];
|
|
}
|
|
|
|
export function useScanDiagnosticsModalModel({
|
|
setDetailsOpen,
|
|
setDiagnosticsOpen,
|
|
saveReviewSample,
|
|
canSaveReviewSample,
|
|
latestCapture,
|
|
controller,
|
|
captureStatus,
|
|
}: UseScanDiagnosticsModalModelInput): ScanDiagnosticsModalModel {
|
|
const closeDiagnostics = useCallback(() => setDiagnosticsOpen(false), [setDiagnosticsOpen]);
|
|
const openDetails = useCallback(() => setDetailsOpen(true), [setDetailsOpen]);
|
|
const handleSaveReviewSample = useCallback(() => {
|
|
if (canSaveReviewSample && controller.parsedArtifact) {
|
|
void saveReviewSample();
|
|
}
|
|
}, [canSaveReviewSample, controller.parsedArtifact, saveReviewSample]);
|
|
const stopPropagation = useCallback((event: MouseEvent<HTMLDivElement>) => {
|
|
event.stopPropagation();
|
|
}, []);
|
|
|
|
const canOpenDetails = Boolean(latestCapture?.crops?.length || latestCapture?.ocr?.length);
|
|
const shouldShowAdminBanner = !controller.runtimeInfo?.isElevated;
|
|
const rightsClassName = controller.runtimeInfo?.isElevated ? "ok" : "blocked";
|
|
const rightsValue = controller.runtimeInfo?.isElevated ? "Admin" : "Standard";
|
|
const genshinClassName = controller.runtimeInfo?.genshinFound ? "ok" : "neutral";
|
|
const genshinValue = controller.runtimeInfo?.genshinFound ? "Gefunden" : "Nicht gefunden";
|
|
const statusTitle = controller.devMode ? "Dev-Ausgabe kompakt" : "Dev-Ausgabe erweitern";
|
|
const autoScanModeLabel = controller.autoScanRunning ? "Scan active" : "Last scan";
|
|
|
|
const inventoryGrid = latestCapture?.inventoryGrid;
|
|
const gridSourceClass = inventoryGrid?.source ?? "missing";
|
|
const gridMainValue = inventoryGrid
|
|
? inventoryGrid?.source === "missing"
|
|
? "not detected"
|
|
: `${inventoryGrid?.cols} x ${inventoryGrid?.rows}`
|
|
: "waiting";
|
|
const gridMetaValue = inventoryGrid
|
|
? `${inventoryGrid?.confidence}% confidence - ${inventoryGrid?.centers.length} click targets`
|
|
: "Run a capture once while the artifact inventory is visible.";
|
|
|
|
const learningRulesText = controller.learningRulesLoaded ? `${controller.learningRuleCount} local rules` : "loading";
|
|
const dataStaleness = dataPackageStatus(dataGeneratedAt, sourceVersion);
|
|
const lookupStatus = validateLookupPackage();
|
|
const lookupText = lookupStatus.valid
|
|
? `Lookup OK: ${lookupStatus.summary.artifactSets} sets / ${lookupStatus.summary.artifactPieces} pieces`
|
|
: `Lookup invalid: ${lookupStatus.errors[0] ?? "unknown error"}`;
|
|
const learningRulesSubtext = `Review samples feed deterministic OCR fixes before each new parse. ${lookupText}. Data package: ${sourceVersion}${dataStaleness.warning ? ` - ${dataStaleness.warning}` : ""}`;
|
|
|
|
const playerProgress = useMemo(() => {
|
|
const width = Math.min(
|
|
100,
|
|
controller.autoScanStats.attempted > 0
|
|
? Math.round((controller.autoScanStats.verified / Math.max(1, controller.autoScanStats.attempted)) * 100)
|
|
: 0,
|
|
);
|
|
const show = controller.autoScanRunning || controller.autoScanStats.clicked > 0 || controller.autoScanStats.parsed > 0;
|
|
return { width, show };
|
|
}, [
|
|
controller.autoScanRunning,
|
|
controller.autoScanStats.attempted,
|
|
controller.autoScanStats.clicked,
|
|
controller.autoScanStats.parsed,
|
|
controller.autoScanStats.verified,
|
|
]);
|
|
|
|
const autoScanStatsLines = useMemo(
|
|
() => [
|
|
{ label: "clicked", value: controller.autoScanStats.clicked },
|
|
{ label: "attempted", value: controller.autoScanStats.attempted },
|
|
{ label: "verified", value: controller.autoScanStats.verified },
|
|
{ label: "parsed", value: controller.autoScanStats.parsed },
|
|
{ label: "stored", value: controller.autoScanStats.stored },
|
|
{ label: "review", value: controller.autoScanStats.review },
|
|
{ label: "duplicates", value: controller.autoScanStats.duplicates },
|
|
{ label: "misses", value: controller.autoScanStats.misses },
|
|
{ label: "pages", value: controller.autoScanStats.pages },
|
|
{ label: "elapsedMs", value: controller.autoScanStats.elapsedMs },
|
|
{ label: "activeScanMs", value: controller.autoScanStats.activeScanMs },
|
|
{ label: "writeFlushMs", value: controller.autoScanStats.writeFlushMs },
|
|
{ label: "avgMs", value: controller.autoScanStats.averageMsPerParsed },
|
|
{ label: "activeAvgMs", value: controller.autoScanStats.activeAverageMsPerParsed },
|
|
{ label: "avgCaptureMs", value: controller.autoScanStats.averageCaptureMs },
|
|
{ label: "avgCaptureRoundTripMs", value: controller.autoScanStats.averageCaptureRoundTripMs },
|
|
{ label: "avgCaptureRoundTripOverheadMs", value: controller.autoScanStats.averageCaptureRoundTripOverheadMs },
|
|
{ label: "captureP50Ms", value: controller.autoScanStats.captureP50Ms },
|
|
{ label: "captureP90Ms", value: controller.autoScanStats.captureP90Ms },
|
|
{ label: "avgOcrMs", value: controller.autoScanStats.averageOcrMs },
|
|
{ label: "ocrP50Ms", value: controller.autoScanStats.ocrP50Ms },
|
|
{ label: "ocrP90Ms", value: controller.autoScanStats.ocrP90Ms },
|
|
{ label: "cardReadyAvgMs", value: controller.autoScanStats.averageCardReadyMs },
|
|
{ label: "cardReadyCount", value: controller.autoScanStats.cardReadyCount },
|
|
{ label: "scrollReadyAvgMs", value: controller.autoScanStats.averageScrollReadyMs },
|
|
{ label: "scrollReadyCount", value: controller.autoScanStats.scrollReadyCount },
|
|
{ label: "perMin x10", value: Math.round(controller.autoScanStats.artifactsPerMinute * 10) },
|
|
{ label: "activePerMin x10", value: Math.round(controller.autoScanStats.activeArtifactsPerMinute * 10) },
|
|
{ label: "projected100Ms", value: controller.autoScanStats.projectedMsFor100 },
|
|
{ label: "activeProjected100Ms", value: controller.autoScanStats.activeProjectedMsFor100 },
|
|
],
|
|
[
|
|
controller.autoScanStats.clicked,
|
|
controller.autoScanStats.attempted,
|
|
controller.autoScanStats.verified,
|
|
controller.autoScanStats.parsed,
|
|
controller.autoScanStats.stored,
|
|
controller.autoScanStats.review,
|
|
controller.autoScanStats.duplicates,
|
|
controller.autoScanStats.misses,
|
|
controller.autoScanStats.pages,
|
|
controller.autoScanStats.elapsedMs,
|
|
controller.autoScanStats.activeScanMs,
|
|
controller.autoScanStats.writeFlushMs,
|
|
controller.autoScanStats.averageMsPerParsed,
|
|
controller.autoScanStats.activeAverageMsPerParsed,
|
|
controller.autoScanStats.averageCaptureMs,
|
|
controller.autoScanStats.averageCaptureRoundTripMs,
|
|
controller.autoScanStats.averageCaptureRoundTripOverheadMs,
|
|
controller.autoScanStats.captureP50Ms,
|
|
controller.autoScanStats.captureP90Ms,
|
|
controller.autoScanStats.averageOcrMs,
|
|
controller.autoScanStats.ocrP50Ms,
|
|
controller.autoScanStats.ocrP90Ms,
|
|
controller.autoScanStats.averageCardReadyMs,
|
|
controller.autoScanStats.cardReadyCount,
|
|
controller.autoScanStats.averageScrollReadyMs,
|
|
controller.autoScanStats.scrollReadyCount,
|
|
controller.autoScanStats.artifactsPerMinute,
|
|
controller.autoScanStats.activeArtifactsPerMinute,
|
|
controller.autoScanStats.projectedMsFor100,
|
|
controller.autoScanStats.activeProjectedMsFor100,
|
|
],
|
|
);
|
|
|
|
const runtimeRows = useMemo(() => {
|
|
const selectedSourceLabel = controller.selectedSource
|
|
? `Selected: ${controller.selectedSource.name}`
|
|
: "No source selected";
|
|
const storedTotalLabel = controller.storedTotal !== null ? `DB: ${controller.storedTotal} Artifacts` : "DB: -";
|
|
const runtime = controller.runtimeInfo
|
|
? `${controller.runtimeInfo?.isElevated ? "admin" : "standard"} - target ${controller.runtimeInfo?.genshinFound ? controller.runtimeInfo?.targetProcess || "Genshin" : "missing"} - fg ${controller.runtimeInfo?.foregroundProcess || "unknown"}`
|
|
: "unknown";
|
|
|
|
return [selectedSourceLabel, storedTotalLabel, runtime, captureStatus, controller.reviewStatus].filter(Boolean);
|
|
}, [controller.selectedSource, controller.storedTotal, controller.runtimeInfo, captureStatus, controller.reviewStatus]);
|
|
|
|
const fingerprintText = latestCapture ? detailFingerprint(latestCapture) : "--";
|
|
const scanLimitText = controller.scanLimit ? `Scan-Limit: ${controller.scanLimit}` : "Keine Limitinfo.";
|
|
const scanTipText = "Tip: Die Auto-Scan Logik bleibt aktivierbar, aber Dev-Ansicht ist rein informativ.";
|
|
|
|
return {
|
|
closeDiagnostics,
|
|
openDetails,
|
|
handleSaveReviewSample,
|
|
stopPropagation,
|
|
canOpenDetails,
|
|
statusTitle,
|
|
rightsClassName,
|
|
rightsValue,
|
|
genshinClassName,
|
|
genshinValue,
|
|
shouldShowAdminBanner,
|
|
gridSourceClass,
|
|
gridMainValue,
|
|
gridMetaValue,
|
|
learningRulesText,
|
|
learningRulesSubtext,
|
|
autoScanModeLabel,
|
|
fingerprintText,
|
|
runtimeRows,
|
|
scanLimitText,
|
|
scanTipText,
|
|
autoScanStatsLines,
|
|
playerProgress,
|
|
autoScanRunning: controller.autoScanRunning,
|
|
showDevRows: controller.devMode,
|
|
reviewStatus: controller.reviewStatus,
|
|
automationLogLines: controller.automationLog,
|
|
diagnosticEvents: controller.diagnosticEvents,
|
|
canSaveReviewSample: canSaveReviewSample && Boolean(controller.parsedArtifact),
|
|
};
|
|
}
|