377 lines
13 KiB
TypeScript
377 lines
13 KiB
TypeScript
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
import type { Dispatch, MutableRefObject, SetStateAction } from "react";
|
|
import { runAutoReviewScan as runAutoReviewScanAction, runVisibleGridScan as runVisibleGridScanAction, type VisibleGridScanOptions } from "./scanViewScanActions";
|
|
import {
|
|
initializeLearningState,
|
|
loadReviewQueue as loadReviewQueueFromRepo,
|
|
persistParsedArtifact as persistParsedArtifactHelper,
|
|
persistParsedArtifactsBatch as persistParsedArtifactsBatchHelper,
|
|
saveReviewSample as saveReviewSampleHelper,
|
|
} from "./scanViewReviewHelpers";
|
|
import { createReviewContext, createScanActionContext } from "./scanViewControllerService";
|
|
import { useScanCommandListener } from "./useScanCommandListener";
|
|
import type { ParsedArtifactCandidate } from "../../../lib/artifactOcrParser";
|
|
import type { ScannerLearningRules } from "../../../lib/scannerLearning";
|
|
import type { BooleanResult, CaptureOptions, CaptureResult, ClickResult, ReviewSampleRecord } from "../../../types/global";
|
|
import type {
|
|
ArtifactRepositoryPort,
|
|
ReviewSampleRepositoryPort,
|
|
LearningRepositoryPort,
|
|
RuntimeRepositoryPort,
|
|
AutomationRepositoryPort,
|
|
} from "../../../infrastructure/repositories/rendererBridgeRepositoryTypes";
|
|
import type { AutoScanStats, ScanSummary } from "../../../lib/scannerSession";
|
|
import type { RuntimeInfo } from "../../../types/global";
|
|
import type { createScanDiagnosticEvent } from "../../../lib/scanDiagnosticsLog";
|
|
import { validateAutoScanEntryPreflight } from "../../../lib/autoScanEntry";
|
|
|
|
type BooleanSetter = Dispatch<SetStateAction<boolean>>;
|
|
type NumberSetter = Dispatch<SetStateAction<number>>;
|
|
type StringSetter = Dispatch<SetStateAction<string>>;
|
|
type NumberOrNullSetter = Dispatch<SetStateAction<number | null>>;
|
|
type ScannerRulesSetter = Dispatch<SetStateAction<ScannerLearningRules>>;
|
|
type ReviewSamplesSetter = Dispatch<SetStateAction<ReviewSampleRecord[]>>;
|
|
type ScanSummarySetter = Dispatch<SetStateAction<ScanSummary | null>>;
|
|
type AutoScanStatsSetter = Dispatch<SetStateAction<AutoScanStats>>;
|
|
|
|
interface ScanViewActionInput {
|
|
autoScanRunning: boolean;
|
|
setAutoScanRunning: BooleanSetter;
|
|
isScanning: boolean;
|
|
stopVisibleScanRef: MutableRefObject<boolean>;
|
|
selectedSourceId: string;
|
|
bridgeReady: boolean;
|
|
automationRepo?: AutomationRepositoryPort;
|
|
runtimeInfo?: RuntimeInfo | null;
|
|
runtimeRepo?: RuntimeRepositoryPort;
|
|
scanLimit: number;
|
|
skipRows: number;
|
|
detectedInventoryCount: number;
|
|
setScanSummary: ScanSummarySetter;
|
|
setAutoScanStats: AutoScanStatsSetter;
|
|
setReviewStatus: StringSetter;
|
|
appendAutomationLog: (line: string) => void;
|
|
appendClickDiagnostics: (result: ClickResult, prefix?: string) => void;
|
|
appendDiagnosticEvent: (event: Omit<Parameters<typeof createScanDiagnosticEvent>[0], "includeFullScreenshot">) => void;
|
|
parseArtifact: (capture: CaptureResult | null) => ParsedArtifactCandidate | null;
|
|
artifactRepo?: ArtifactRepositoryPort;
|
|
reviewSamplesRepo?: ReviewSampleRepositoryPort;
|
|
learningRepo?: LearningRepositoryPort;
|
|
onStoredArtifactsChanged?: (() => Promise<void>) | (() => void);
|
|
setReviewSampleTotal: NumberSetter;
|
|
setReviewSamples: ReviewSamplesSetter;
|
|
setLearningRulesLoaded: BooleanSetter;
|
|
setScannerLearningRules: ScannerRulesSetter;
|
|
setStoredTotal: NumberOrNullSetter;
|
|
scannerLearningRules: ScannerLearningRules;
|
|
captureSelectedSource: (delayMs?: number, focusGenshin?: boolean, options?: CaptureOptions) => Promise<CaptureResult | null>;
|
|
latestCapture: CaptureResult | null;
|
|
parsedArtifact: ParsedArtifactCandidate | null;
|
|
canCaptureSource: boolean;
|
|
setReviewQueueOpen: BooleanSetter;
|
|
}
|
|
|
|
export interface ScanViewActionResult {
|
|
requestScanStop: (reason?: string) => void;
|
|
saveReviewSample: (
|
|
capture?: CaptureResult | null,
|
|
parsed?: ParsedArtifactCandidate | null,
|
|
reason?: string,
|
|
) => Promise<BooleanResult | null>;
|
|
loadReviewQueue: () => Promise<void>;
|
|
openReviewQueue: () => Promise<void>;
|
|
runAutoReviewScan: () => Promise<void>;
|
|
runGuidedAutoScan: (options?: { scanLimit?: number; ocrEngine?: CaptureOptions["ocrEngine"] }) => Promise<void>;
|
|
runVisibleGridScan: (options?: VisibleGridScanOptions) => Promise<void>;
|
|
}
|
|
|
|
export function useScanViewActions(input: ScanViewActionInput): ScanViewActionResult {
|
|
const {
|
|
autoScanRunning,
|
|
setAutoScanRunning,
|
|
isScanning,
|
|
stopVisibleScanRef,
|
|
selectedSourceId,
|
|
bridgeReady,
|
|
automationRepo,
|
|
runtimeInfo,
|
|
runtimeRepo,
|
|
scanLimit,
|
|
skipRows,
|
|
detectedInventoryCount,
|
|
setScanSummary,
|
|
setAutoScanStats,
|
|
setReviewStatus,
|
|
appendAutomationLog,
|
|
appendClickDiagnostics,
|
|
appendDiagnosticEvent,
|
|
parseArtifact,
|
|
artifactRepo,
|
|
reviewSamplesRepo,
|
|
learningRepo,
|
|
onStoredArtifactsChanged,
|
|
setReviewSampleTotal,
|
|
setReviewSamples,
|
|
setLearningRulesLoaded,
|
|
setScannerLearningRules,
|
|
setStoredTotal,
|
|
scannerLearningRules,
|
|
captureSelectedSource,
|
|
latestCapture,
|
|
parsedArtifact,
|
|
canCaptureSource,
|
|
setReviewQueueOpen,
|
|
} = input;
|
|
const learningInitializedRef = useRef(false);
|
|
|
|
const requestScanStop = useCallback((reason = "Stop angefordert.") => {
|
|
stopVisibleScanRef.current = true;
|
|
appendAutomationLog(`stop requested: ${reason}`);
|
|
setReviewStatus(`${reason} Der aktuelle Klick/Capture-Schritt wird noch sauber beendet.`);
|
|
}, [appendAutomationLog, setReviewStatus, stopVisibleScanRef]);
|
|
|
|
const reviewContext = useMemo(
|
|
() =>
|
|
createReviewContext({
|
|
artifactRepo,
|
|
reviewSamplesRepo,
|
|
learningRepo,
|
|
onStoredArtifactsChanged,
|
|
setReviewSampleTotal,
|
|
setReviewSamples,
|
|
setLearningRulesLoaded,
|
|
setScannerLearningRules,
|
|
setReviewStatus,
|
|
setStoredTotal,
|
|
appendAutomationLog,
|
|
}),
|
|
[
|
|
artifactRepo,
|
|
reviewSamplesRepo,
|
|
learningRepo,
|
|
onStoredArtifactsChanged,
|
|
setReviewSampleTotal,
|
|
setReviewSamples,
|
|
setLearningRulesLoaded,
|
|
setScannerLearningRules,
|
|
setReviewStatus,
|
|
setStoredTotal,
|
|
appendAutomationLog,
|
|
],
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (learningInitializedRef.current) return;
|
|
if (!learningRepo && !reviewSamplesRepo && !artifactRepo) return;
|
|
learningInitializedRef.current = true;
|
|
void initializeLearningState(reviewContext);
|
|
}, [artifactRepo, learningRepo, reviewContext, reviewSamplesRepo]);
|
|
|
|
const focusDashboard = useCallback(async () => {
|
|
try {
|
|
await automationRepo?.focusMainWindow?.();
|
|
} catch {
|
|
// Best-effort fallback; state remains in renderer.
|
|
}
|
|
}, [automationRepo?.focusMainWindow]);
|
|
|
|
const parseArtifactAndPersist = useCallback(
|
|
async function parseArtifactAndPersist(
|
|
capture: CaptureResult | null,
|
|
parsed: ParsedArtifactCandidate,
|
|
source: string,
|
|
needsReview: boolean,
|
|
) {
|
|
return persistParsedArtifactHelper(capture, parsed, source, needsReview, reviewContext);
|
|
},
|
|
[reviewContext],
|
|
);
|
|
|
|
const parseArtifactsAndPersistBatch = useCallback(
|
|
async function parseArtifactsAndPersistBatch(
|
|
items: Array<{ capture: CaptureResult | null; parsed: ParsedArtifactCandidate; source: string; needsReview: boolean }>,
|
|
) {
|
|
return persistParsedArtifactsBatchHelper(items, reviewContext);
|
|
},
|
|
[reviewContext],
|
|
);
|
|
|
|
const handleSaveReviewSample = useCallback(
|
|
async function handleSaveReviewSample(
|
|
capture: CaptureResult | null = latestCapture,
|
|
parsed: ParsedArtifactCandidate | null = parsedArtifact,
|
|
reason = "manual",
|
|
): Promise<BooleanResult | null> {
|
|
const result = await saveReviewSampleHelper(
|
|
capture,
|
|
parsed,
|
|
reason,
|
|
scannerLearningRules,
|
|
reviewContext,
|
|
);
|
|
return result.result ? { ok: result.result.ok } : null;
|
|
},
|
|
[latestCapture, parsedArtifact, reviewContext, scannerLearningRules],
|
|
);
|
|
|
|
const scanActionContext = useMemo(
|
|
() =>
|
|
createScanActionContext({
|
|
autoScanRunning,
|
|
setAutoScanRunning,
|
|
isScanning,
|
|
stopVisibleScanRef,
|
|
selectedSourceId,
|
|
bridgeReady,
|
|
automationRepo,
|
|
runtimeInfo,
|
|
runtimeRepo,
|
|
scanLimit,
|
|
skipRows,
|
|
detectedInventoryCount,
|
|
setScanSummary,
|
|
setAutoScanStats,
|
|
setReviewStatus,
|
|
appendAutomationLog,
|
|
appendClickDiagnostics,
|
|
appendDiagnosticEvent,
|
|
parseArtifact,
|
|
persistParsedArtifact: parseArtifactAndPersist,
|
|
persistParsedArtifactsBatch: parseArtifactsAndPersistBatch,
|
|
saveReviewSample: handleSaveReviewSample,
|
|
focusDashboard,
|
|
captureSelectedSource: (delayMs = 0, focusGenshin = false, options) => captureSelectedSource(delayMs, focusGenshin, {
|
|
ocrMode: "artifact",
|
|
omitFullFrame: true,
|
|
omitInventoryPreview: true,
|
|
...options,
|
|
}),
|
|
captureFastSelectedSource: (delayMs = 0, focusGenshin = false, options) => captureSelectedSource(delayMs, focusGenshin, {
|
|
skipOcr: true,
|
|
omitFullFrame: true,
|
|
omitCrops: true,
|
|
omitCropImages: true,
|
|
omitLockState: true,
|
|
...options,
|
|
}),
|
|
}),
|
|
[
|
|
autoScanRunning,
|
|
setAutoScanRunning,
|
|
isScanning,
|
|
stopVisibleScanRef,
|
|
selectedSourceId,
|
|
bridgeReady,
|
|
automationRepo,
|
|
runtimeInfo,
|
|
runtimeRepo,
|
|
scanLimit,
|
|
skipRows,
|
|
detectedInventoryCount,
|
|
setScanSummary,
|
|
setAutoScanStats,
|
|
setReviewStatus,
|
|
appendAutomationLog,
|
|
appendClickDiagnostics,
|
|
appendDiagnosticEvent,
|
|
parseArtifact,
|
|
parseArtifactAndPersist,
|
|
parseArtifactsAndPersistBatch,
|
|
handleSaveReviewSample,
|
|
focusDashboard,
|
|
captureSelectedSource,
|
|
],
|
|
);
|
|
|
|
const loadReviewQueueAction = useCallback(async () => {
|
|
await loadReviewQueueFromRepo(reviewContext);
|
|
}, [reviewContext]);
|
|
|
|
const openReviewQueueModal = useCallback(async () => {
|
|
await loadReviewQueueAction();
|
|
setReviewQueueOpen(true);
|
|
}, [loadReviewQueueAction, setReviewQueueOpen]);
|
|
|
|
const runAutoReviewScan = useCallback(async () => {
|
|
if (autoScanRunning || !selectedSourceId || !canCaptureSource) return;
|
|
await runAutoReviewScanAction(scanActionContext);
|
|
}, [autoScanRunning, canCaptureSource, selectedSourceId, scanActionContext]);
|
|
|
|
const runVisibleGridScan = useCallback(async (options: VisibleGridScanOptions = {}) => {
|
|
if (autoScanRunning || !bridgeReady || !selectedSourceId || !automationRepo?.clickScreen || !automationRepo?.scrollScreen) {
|
|
return;
|
|
}
|
|
await runVisibleGridScanAction(scanActionContext, options);
|
|
}, [
|
|
autoScanRunning,
|
|
bridgeReady,
|
|
selectedSourceId,
|
|
automationRepo?.clickScreen,
|
|
automationRepo?.scrollScreen,
|
|
scanActionContext,
|
|
]);
|
|
|
|
const runGuidedAutoScan = useCallback(async (options: { scanLimit?: number; ocrEngine?: CaptureOptions["ocrEngine"] } = {}) => {
|
|
if (autoScanRunning || !bridgeReady || !selectedSourceId || !automationRepo?.clickScreen || !automationRepo?.scrollScreen) {
|
|
return;
|
|
}
|
|
setReviewStatus("Auto-Scan prueft den Startzustand ohne OCR...");
|
|
const preflightCapture = await captureSelectedSource(0, true, {
|
|
skipOcr: true,
|
|
omitFullFrame: true,
|
|
omitCrops: true,
|
|
omitCropImages: true,
|
|
omitLockState: true,
|
|
});
|
|
const visibleInventoryReady = validateAutoScanEntryPreflight(preflightCapture).ok;
|
|
appendDiagnosticEvent({
|
|
phase: "guided-start",
|
|
severity: visibleInventoryReady ? "ok" : "info",
|
|
message: visibleInventoryReady
|
|
? "Artifact inventory detail view already visible; starting scan directly."
|
|
: "Artifact detail view is not ready; guided scan waits for a visible artifact detail card instead of navigating.",
|
|
capture: preflightCapture,
|
|
});
|
|
if (!visibleInventoryReady) {
|
|
setReviewStatus("Auto-Scan wartet: Bitte Artifact-Inventar mit sichtbarer Detailkarte oeffnen und erneut starten.");
|
|
return;
|
|
}
|
|
await runVisibleGridScanAction(scanActionContext, {
|
|
scanLimit: options.scanLimit,
|
|
scanEntryMode: "visible-inventory",
|
|
processInitialSelection: true,
|
|
ocrEngine: options.ocrEngine,
|
|
});
|
|
}, [
|
|
autoScanRunning,
|
|
bridgeReady,
|
|
selectedSourceId,
|
|
automationRepo?.clickScreen,
|
|
automationRepo?.scrollScreen,
|
|
setReviewStatus,
|
|
captureSelectedSource,
|
|
appendDiagnosticEvent,
|
|
scanActionContext,
|
|
]);
|
|
|
|
useScanCommandListener({
|
|
automationRepo,
|
|
autoScanRunning,
|
|
isScanning,
|
|
selectedSourceId,
|
|
requestScanStop,
|
|
runGuidedAutoScan,
|
|
runVisibleGridScan,
|
|
});
|
|
|
|
return {
|
|
requestScanStop,
|
|
saveReviewSample: handleSaveReviewSample,
|
|
loadReviewQueue: loadReviewQueueAction,
|
|
openReviewQueue: openReviewQueueModal,
|
|
runAutoReviewScan,
|
|
runGuidedAutoScan,
|
|
runVisibleGridScan,
|
|
};
|
|
}
|