chore: initialize repository baseline
Import the existing Electron + React + TypeScript app as the version-control baseline before the scanner rework (C# input/capture sidecar, resolution-anchored layout profiles, OCR preprocessing, eval harness, rescan-merge, GOOD interop). Housekeeping in this commit: - Remove orphaned temp_inputhelper_block.ts (duplicate of the input-helper script). - Ignore .claude/scheduled_tasks.lock local session state. - Add .gitattributes to normalize line endings (LF in repo). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
export type ArtifactSlot = "flower" | "plume" | "sands" | "goblet" | "circlet";
|
||||
|
||||
export type ArtifactVerdict =
|
||||
| "keep"
|
||||
| "maybe_level"
|
||||
| "character_specific"
|
||||
| "trash_candidate"
|
||||
| "needs_review";
|
||||
|
||||
export type ScanSource = "mock" | "screen" | "overlay" | "good_import" | "manual";
|
||||
|
||||
export interface ArtifactSubstat {
|
||||
key: string;
|
||||
value: number;
|
||||
unit: "%" | "flat";
|
||||
}
|
||||
|
||||
export interface Artifact {
|
||||
id: string;
|
||||
setKey: string;
|
||||
setName: string;
|
||||
slot: ArtifactSlot;
|
||||
rarity: 4 | 5;
|
||||
level: number;
|
||||
mainStat: string;
|
||||
substats: ArtifactSubstat[];
|
||||
equipped?: string;
|
||||
locked: boolean;
|
||||
source: ScanSource;
|
||||
confidence: number;
|
||||
lastSeenAt: string;
|
||||
}
|
||||
|
||||
export interface Character {
|
||||
id: string;
|
||||
name: string;
|
||||
owned: boolean;
|
||||
level: number;
|
||||
constellation: number;
|
||||
rolePreference: CharacterRole;
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
export type CharacterRole =
|
||||
| "main_dps"
|
||||
| "sub_dps"
|
||||
| "support"
|
||||
| "healer"
|
||||
| "reaction";
|
||||
|
||||
export interface CharacterPreset {
|
||||
characterId: string;
|
||||
role: CharacterRole;
|
||||
recommendedSets: string[];
|
||||
alternativeSets: string[];
|
||||
mainStats: Partial<Record<ArtifactSlot, string[]>>;
|
||||
substatWeights: Record<string, number>;
|
||||
erTarget?: number;
|
||||
explanation: string;
|
||||
}
|
||||
|
||||
export interface Recommendation {
|
||||
artifactId: string;
|
||||
verdict: ArtifactVerdict;
|
||||
score: number;
|
||||
bestCharacters: string[];
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface BuildSuggestion {
|
||||
id: string;
|
||||
characterId: string;
|
||||
label: string;
|
||||
quality: "recommended_set" | "alternative_set" | "rainbow";
|
||||
artifactIds: string[];
|
||||
score: number;
|
||||
warnings: string[];
|
||||
explanation: string;
|
||||
}
|
||||
|
||||
export interface ScanEvent {
|
||||
id: string;
|
||||
type: "environment" | "character" | "artifact" | "review" | "complete";
|
||||
label: string;
|
||||
detail: string;
|
||||
confidence?: number;
|
||||
}
|
||||
|
||||
export interface AppSnapshot {
|
||||
artifacts: Artifact[];
|
||||
characters: Character[];
|
||||
recommendations: Recommendation[];
|
||||
builds: BuildSuggestion[];
|
||||
scanEvents: ScanEvent[];
|
||||
}
|
||||
Vendored
+305
@@ -0,0 +1,305 @@
|
||||
import type { AppSnapshot } from "./domain";
|
||||
import type { StoredArtifactRecord } from "./storage";
|
||||
|
||||
export interface CaptureOptions {
|
||||
skipOcr?: boolean;
|
||||
}
|
||||
|
||||
export interface CaptureSourceInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
isGenshinCandidate: boolean;
|
||||
thumbnailDataUrl: string;
|
||||
}
|
||||
|
||||
export interface CaptureCrop {
|
||||
id: string;
|
||||
label: string;
|
||||
rect: { x: number; y: number; width: number; height: number };
|
||||
dataUrl: string;
|
||||
}
|
||||
|
||||
export interface OcrResult {
|
||||
id: string;
|
||||
label: string;
|
||||
text: string;
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
export type CaptureTarget = "genshin-client" | "primary-screen" | "desktop-source";
|
||||
|
||||
export interface CaptureResult {
|
||||
id: string;
|
||||
name: string;
|
||||
width: number;
|
||||
height: number;
|
||||
dataUrl: string;
|
||||
capturedAt: string;
|
||||
captureTarget?: CaptureTarget;
|
||||
detailDataUrl?: string;
|
||||
inventoryDataUrl?: string;
|
||||
ocrSkipped?: boolean;
|
||||
ocrTimedOut?: boolean;
|
||||
crops?: CaptureCrop[];
|
||||
ocr?: OcrResult[];
|
||||
inventoryGrid?: {
|
||||
centers: Array<{ x: number; y: number; row: number; col: number }>;
|
||||
rows: number;
|
||||
cols: number;
|
||||
confidence: number;
|
||||
source: "detected" | "fallback" | "missing";
|
||||
};
|
||||
inventoryCount?: {
|
||||
current: number;
|
||||
total: number;
|
||||
confidence: number;
|
||||
source: "ocr" | "missing";
|
||||
text: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface WindowBounds {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface ClickResult {
|
||||
ok: boolean;
|
||||
x: number;
|
||||
y: number;
|
||||
cursorX?: number;
|
||||
cursorY?: number;
|
||||
escapePressed?: boolean;
|
||||
enterPressed?: boolean;
|
||||
f9Pressed?: boolean;
|
||||
/** Cursor verifiably reached the target position before clicking. */
|
||||
moved?: boolean;
|
||||
/** Mouse down+up were injected (only attempted when moved is true). */
|
||||
clicked?: boolean;
|
||||
/** SendInput injected zero events, e.g. blocked by UIPI (elevated game). */
|
||||
inputBlocked?: boolean;
|
||||
focused?: boolean;
|
||||
alreadyForeground?: boolean;
|
||||
foregroundProcess?: string;
|
||||
targetProcess?: string;
|
||||
isElevated?: boolean;
|
||||
}
|
||||
|
||||
export interface RuntimeInfo {
|
||||
ok: boolean;
|
||||
isElevated: boolean;
|
||||
platform: string;
|
||||
hotkeys?: Record<string, boolean>;
|
||||
genshinFound?: boolean;
|
||||
genshinHwnd?: number;
|
||||
targetProcess?: string;
|
||||
foregroundProcess?: string;
|
||||
foregroundHwnd?: number;
|
||||
helperPid?: number;
|
||||
}
|
||||
|
||||
export interface FocusGenshinResult {
|
||||
focused: boolean;
|
||||
alreadyForeground: boolean;
|
||||
foregroundProcess?: string;
|
||||
targetProcess?: string;
|
||||
genshinFound?: boolean;
|
||||
setForegroundResult?: boolean;
|
||||
}
|
||||
|
||||
export interface BooleanResult {
|
||||
ok: boolean;
|
||||
}
|
||||
|
||||
export interface SaveResultWithPath {
|
||||
ok: boolean;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export type SaveSnapshotResult = SaveResultWithPath;
|
||||
|
||||
export interface ScannerLearningRulePayload {
|
||||
textReplacements?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface LoadScannerLearningRulesResult {
|
||||
ok: boolean;
|
||||
path: string;
|
||||
rules: ScannerLearningRulePayload;
|
||||
}
|
||||
|
||||
export interface SaveScannerLearningRulesResult {
|
||||
ok: boolean;
|
||||
path: string;
|
||||
rules: ScannerLearningRulePayload;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface AutomationGuard {
|
||||
ok: boolean;
|
||||
cursorX?: number;
|
||||
cursorY?: number;
|
||||
escapePressed: boolean;
|
||||
enterPressed?: boolean;
|
||||
f9Pressed?: boolean;
|
||||
isElevated?: boolean;
|
||||
}
|
||||
|
||||
export interface GdiCaptureResult {
|
||||
dataUrl: string;
|
||||
width: number;
|
||||
height: number;
|
||||
originX: number;
|
||||
originY: number;
|
||||
captureTarget: Extract<CaptureTarget, "primary-screen" | "genshin-client">;
|
||||
}
|
||||
|
||||
export interface HelperOperationResponse {
|
||||
id?: string;
|
||||
ok: boolean;
|
||||
error?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface ScrollResult {
|
||||
ok: boolean;
|
||||
notchesSent: number;
|
||||
inputBlocked?: boolean;
|
||||
isElevated?: boolean;
|
||||
}
|
||||
|
||||
export type ArtifactSaveResult = ArtifactStoreSaveResult;
|
||||
|
||||
export interface ArtifactStoreLoadResult {
|
||||
ok: boolean;
|
||||
artifacts: StoredArtifactRecord[];
|
||||
total: number;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface ArtifactStoreSaveResult {
|
||||
ok: boolean;
|
||||
added: number;
|
||||
updated: number;
|
||||
total: number;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface ReviewSampleRecord {
|
||||
savedAt: string;
|
||||
sample?: {
|
||||
reason?: string;
|
||||
parsed?: unknown;
|
||||
capture?: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
dataUrl?: string;
|
||||
detailDataUrl?: string;
|
||||
inventoryDataUrl?: string;
|
||||
captureTarget?: CaptureResult["captureTarget"];
|
||||
capturedAt?: string;
|
||||
crops?: Array<{ id: string; label: string; rect: { x: number; y: number; width: number; height: number }; dataUrl?: string }>;
|
||||
ocr?: OcrResult[];
|
||||
inventoryGrid?: CaptureResult["inventoryGrid"];
|
||||
inventoryCount?: CaptureResult["inventoryCount"];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface ReviewSampleListResult {
|
||||
ok: boolean;
|
||||
samples: ReviewSampleRecord[];
|
||||
total: number;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface ReviewSamplePayload {
|
||||
reason?: string;
|
||||
parsed?: unknown;
|
||||
capture?: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
dataUrl?: string;
|
||||
detailDataUrl?: string;
|
||||
inventoryDataUrl?: string;
|
||||
captureTarget?: CaptureResult["captureTarget"];
|
||||
capturedAt?: string;
|
||||
crops?: Array<{ id: string; label: string; rect: { x: number; y: number; width: number; height: number }; dataUrl?: string }>;
|
||||
ocr?: OcrResult[];
|
||||
inventoryGrid?: CaptureResult["inventoryGrid"];
|
||||
inventoryCount?: CaptureResult["inventoryCount"];
|
||||
};
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface ScannerStatusPayload {
|
||||
running: boolean;
|
||||
reviewStatus: string;
|
||||
captureStatus: string;
|
||||
selectedSource: string | null;
|
||||
stats: Record<string, number>;
|
||||
summary: unknown;
|
||||
snapshotArtifacts: number;
|
||||
snapshotCharacters: number;
|
||||
snapshotRecommendations: number;
|
||||
snapshotBuilds: number;
|
||||
grid: CaptureResult["inventoryGrid"] | null;
|
||||
automationLog: string[];
|
||||
runtimeInfo: RuntimeInfo | null;
|
||||
storedTotal: number | null;
|
||||
learningRuleCount: number;
|
||||
updatedAt: string | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface GoodExportArtifact {
|
||||
setKey: string;
|
||||
slotKey: string;
|
||||
rarity: number;
|
||||
level: number;
|
||||
mainStatKey: string;
|
||||
substats: Array<{ key: string; value: number }>;
|
||||
lock: boolean;
|
||||
}
|
||||
|
||||
export interface GoodDatabase {
|
||||
format: "GOOD";
|
||||
version: number;
|
||||
source: string;
|
||||
artifacts: GoodExportArtifact[];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
assistantApi?: {
|
||||
loadSnapshot: () => Promise<AppSnapshot | null>;
|
||||
saveSnapshot: (snapshot: AppSnapshot) => Promise<SaveSnapshotResult>;
|
||||
runMockScan: () => Promise<AppSnapshot | null>;
|
||||
listCaptureSources: () => Promise<CaptureSourceInfo[]>;
|
||||
captureSource: (sourceId: string, delayMs?: number, focusGenshin?: boolean, options?: CaptureOptions) => Promise<CaptureResult>;
|
||||
clickScreen: (x: number, y: number) => Promise<ClickResult>;
|
||||
scrollScreen: (notches: number, anchorX?: number, anchorY?: number) => Promise<ScrollResult>;
|
||||
getAutomationGuard: () => Promise<AutomationGuard>;
|
||||
focusMainWindow: () => Promise<BooleanResult>;
|
||||
focusGenshin: () => Promise<FocusGenshinResult>;
|
||||
getRuntimeInfo: () => Promise<RuntimeInfo>;
|
||||
saveReviewSample: (sample: ReviewSamplePayload) => Promise<SaveResultWithPath>;
|
||||
loadReviewSamples: (limit?: number) => Promise<ReviewSampleListResult>;
|
||||
loadScannerLearningRules: () => Promise<LoadScannerLearningRulesResult>;
|
||||
saveScannerLearningRules: (rules: ScannerLearningRulePayload) => Promise<SaveScannerLearningRulesResult>;
|
||||
loadArtifacts: () => Promise<ArtifactStoreLoadResult>;
|
||||
saveArtifacts: (records: StoredArtifactRecord[]) => Promise<ArtifactStoreSaveResult>;
|
||||
exportGood: (payload: GoodDatabase) => Promise<SaveResultWithPath>;
|
||||
publishScannerStatus: (status: ScannerStatusPayload) => Promise<BooleanResult>;
|
||||
showOverlay: () => Promise<BooleanResult>;
|
||||
hideOverlay: () => Promise<BooleanResult>;
|
||||
onScannerCommand: (callback: (command: "start-auto" | "stop") => void) => () => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export interface StoredArtifactRecord {
|
||||
id: string;
|
||||
name: string;
|
||||
slot: string;
|
||||
level?: number;
|
||||
setName: string;
|
||||
mainStat: string;
|
||||
mainValue: string;
|
||||
substats: string[];
|
||||
equipped: string;
|
||||
confidence: number;
|
||||
needsReview: boolean;
|
||||
source: string;
|
||||
firstSeenAt?: string;
|
||||
lastSeenAt?: string;
|
||||
timesSeen?: number;
|
||||
}
|
||||
Reference in New Issue
Block a user