feat(scanner): add native artifact pipeline

Add native IK-style capture processing, Artifact Inventory, explicit promotion and single-result review. Confirm the three live OCR corrections in the eval corpus and preserve extraction/value separation.
This commit is contained in:
AzuTear
2026-07-09 23:30:42 +02:00
parent 28d60eb915
commit 639b0b7f59
92 changed files with 13606 additions and 1703 deletions
+250 -3
View File
@@ -1,12 +1,12 @@
import type { AppSnapshot } from "./domain";
import type { StoredArtifactRecord } from "./storage";
import type { ScanResultCategory, ScanResultIkMatch, StoredArtifactRecord, StoredScanResultEntry } from "./storage";
export interface CaptureOptions {
skipOcr?: boolean;
skipOcrUnlessArtifactDetail?: boolean;
ocrMode?: "full" | "artifact";
ocrProfile?: "full" | "fast";
ocrEngine?: "current" | "native-tesseract" | "benchmark" | "ik-traineddata";
ocrEngine?: "current";
scanEntryMode?: "visible-inventory" | "direct-inventory" | "paimon-menu" | "auto-entry";
omitFullFrame?: boolean;
omitDetailPreview?: boolean;
@@ -198,7 +198,6 @@ export type ScannerCommand =
type: "start-auto";
scanLimit?: number;
scanEntryMode?: CaptureOptions["scanEntryMode"];
ocrEngine?: CaptureOptions["ocrEngine"];
};
export interface ScannerLearningRulePayload {
@@ -224,6 +223,237 @@ export interface ScannerLearningRulePayload {
}>;
}
export interface NativeScannerDataStatus {
directory: string;
version: string;
artifactSets: number;
artifactPieces: number;
weapons: number;
characters: number;
materials: number;
totalEntries: number;
categories: {
artifacts: NativeScannerArtifactCategoryStatus;
weapons: NativeScannerSimpleCategoryStatus;
characters: NativeScannerSimpleCategoryStatus;
materials: NativeScannerSimpleCategoryStatus;
};
missing: string[];
valid: boolean;
source: string;
}
export type NativeScannerCategoryScanStatus = "native_capture" | "catalog_only" | "missing_data";
export type NativeScannerStartCategory = "artifacts" | "weapons" | "characters" | "materials";
export interface NativeScannerCategoryStatusBase {
file: string;
supported: boolean;
catalogAvailable?: boolean;
nativeCaptureSupported?: boolean;
scanStatus?: NativeScannerCategoryScanStatus;
}
export interface NativeScannerArtifactCategoryStatus extends NativeScannerCategoryStatusBase {
setCount: number;
pieceCount: number;
}
export interface NativeScannerSimpleCategoryStatus extends NativeScannerCategoryStatusBase {
count: number;
}
export interface NativeScannerGridStatus {
rows: number;
cols: number;
count: number;
anchorX: number;
anchorY: number;
first?: { x: number; y: number; row: number; col: number };
last?: { x: number; y: number; row: number; col: number };
}
export interface NativeScannerCatalogStatus {
data: NativeScannerDataStatus;
artifacts: Array<{
normalizedName: string;
setName: string;
good: string;
pieces: Array<{ slot: string; artifactName: string; good: string; normalizedName: string }>;
}>;
weapons: Array<{ normalizedName: string; good: string }>;
characters: Array<{
normalizedName: string;
good: string;
element: string;
weaponType: number;
constellationName: string;
}>;
materials: Array<{ normalizedName: string; good: string }>;
}
export interface NativeScannerRunStatus {
running: boolean;
status: "idle" | "running" | "done" | "blocked" | "stopped" | string;
runId: string;
category?: NativeScannerStartCategory | string;
target: number;
captured: number;
queued: number;
clicked: number;
pages: number;
activeMs: number;
message: string;
outputRoot: string;
runDir: string;
manifestPath: string;
jobsPath: string;
statusPath: string;
dataVersion: string;
lastArtifactPath: string;
supportedCategories?: NativeScannerDataStatus["categories"];
lastJob?: {
sequence: number;
category?: NativeScannerStartCategory | string;
page: number;
row: number;
col: number;
clientX: number;
clientY: number;
screenX: number;
screenY: number;
clickEventsSent: number;
relativePath: string;
absolutePath: string;
capturedAt: string;
detail: { width: number; height: number };
kind: string;
downstream: string;
};
}
export interface NativeScannerPreflightStatus {
data: NativeScannerDataStatus;
category?: NativeScannerStartCategory | string;
supportedCategories?: NativeScannerDataStatus["categories"];
categoryReady?: boolean;
genshinFound: boolean;
bounds: { left: number; top: number; width: number; height: number } | null;
isSixteenNine: boolean;
grid: NativeScannerGridStatus;
visual?: {
ready: boolean;
samples: number;
whitePct: number;
darkPct: number;
colorPct: number;
lumaStdDev: number;
blockReason?: string;
};
ready: boolean;
blockReason?: string;
}
export interface NativeScannerProcessStatus {
ok: boolean;
runDir: string;
jobsPath: string;
reportPath: string;
scanResultsPath: string;
processed: number;
parsed: number;
review: number;
stored: number;
errors: number;
elapsedMs: number;
queueConcurrency: number;
persisted: boolean;
error?: string;
results: Array<{
sequence: number;
category?: ScanResultCategory | string;
page?: number;
row?: number;
col?: number;
imagePath: string;
parsed: boolean;
artifactName?: string;
setName?: string;
slot?: string;
confidence: number;
needsReview: boolean;
persisted?: boolean;
ikMatch?: ScanResultIkMatch;
notes?: string[];
ocr: OcrResult[];
error?: string;
}>;
}
export interface NativeScannerResultsLoadStatus {
ok: boolean;
runDir: string;
path: string;
total: number;
results: StoredScanResultEntry[];
error?: string;
}
export interface NativeScannerPromotionStatus {
ok: boolean;
runDir: string;
logPath: string;
requested: number;
selected: number;
promoted: number;
alreadyStored: number;
review: number;
blocked: number;
added: number;
updated: number;
total: number;
promotedResultIds: string[];
error?: string;
}
export interface NativeScannerReviewArtifactInput {
name: string;
slot: string;
level: number;
setName: string;
mainStat: string;
mainValue: string;
substats: string[];
equipped: string;
locked?: boolean;
}
export interface NativeScannerReviewStatus {
ok: boolean;
runDir: string;
logPath: string;
resultId: string;
action: "approve" | "reject";
evalSampleSaved: boolean;
correctedFields: string[];
error?: string;
}
export interface NativeScannerImageLoadStatus {
ok: boolean;
runDir: string;
path: string;
dataUrl: string;
width: number;
height: number;
error?: string;
}
export interface NativeScannerResult {
ok: boolean;
scanner: NativeScannerRunStatus | NativeScannerPreflightStatus | NativeScannerDataStatus | NativeScannerCatalogStatus | NativeScannerProcessStatus | NativeScannerResultsLoadStatus | NativeScannerImageLoadStatus;
}
export interface LoadScannerLearningRulesResult {
ok: boolean;
path: string;
@@ -429,6 +659,23 @@ declare global {
exportGood: (payload: GoodDatabase) => Promise<SaveResultWithPath>;
importGoodFile: () => Promise<GoodImportFileResult>;
publishScannerStatus: (status: ScannerStatusPayload) => Promise<BooleanResult>;
nativeScannerDataStatus: () => Promise<NativeScannerDataStatus>;
nativeScannerCatalog: () => Promise<NativeScannerCatalogStatus>;
nativeScannerPreflight: (options?: { category?: NativeScannerStartCategory }) => Promise<NativeScannerPreflightStatus>;
nativeScannerStart: (options?: { limit?: number; category?: NativeScannerStartCategory }) => Promise<NativeScannerRunStatus>;
nativeScannerStop: () => Promise<NativeScannerRunStatus>;
nativeScannerStatus: () => Promise<NativeScannerRunStatus>;
nativeScannerProcessRun: (options?: { runDir?: string; persist?: boolean; limit?: number }) => Promise<NativeScannerProcessStatus>;
nativeScannerLoadResults: (options?: { runDir?: string; limit?: number }) => Promise<NativeScannerResultsLoadStatus>;
nativeScannerPromoteResults: (options: { runDir?: string; resultIds: string[] }) => Promise<NativeScannerPromotionStatus>;
nativeScannerReviewResult: (options: {
runDir?: string;
resultId: string;
action: "approve" | "reject";
artifact?: NativeScannerReviewArtifactInput;
note?: string;
}) => Promise<NativeScannerReviewStatus>;
nativeScannerLoadImage: (options: { runDir?: string; imagePath: string }) => Promise<NativeScannerImageLoadStatus>;
showOverlay: () => Promise<BooleanResult>;
hideOverlay: () => Promise<BooleanResult>;
onScannerCommand: (callback: (command: ScannerCommand) => void) => () => void;