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:
Vendored
+250
-3
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user