feat(scanner): complete localized artifact quality checkpoint
This commit is contained in:
+186
-85
@@ -17,6 +17,10 @@ import { registerIpcHandlers } from "./bootstrap/ipcBootstrap.js";
|
||||
import { createDevControlServer } from "./devControlServer.js";
|
||||
import { createAppWindowManager, type AppWindowManager } from "./appWindowManager.js";
|
||||
import { createGoodFileService, type GoodFileService } from "./services/goodFileService.js";
|
||||
import { resolveNativeScannerRunDirectory } from "./services/nativeScannerRunDirectory.js";
|
||||
import { readNativeScannerRunTiming, recordNativeScannerRunTiming } from "./services/nativeScannerRunTiming.js";
|
||||
import { isLikelyGenshinSourceName } from "./services/captureSourceClassifier.js";
|
||||
import { ikInventoryListsPathCandidates, inputHelperPathCandidates } from "./runtimePaths.js";
|
||||
import type { IkArtifactCatalog } from "../src/lib/ikArtifactMatcher.js";
|
||||
import type { AppSnapshot } from "../src/types/domain.js";
|
||||
import type {
|
||||
@@ -25,12 +29,16 @@ import type {
|
||||
GoodDatabase,
|
||||
NativeScannerCatalogStatus,
|
||||
NativeScannerDataStatus,
|
||||
NativeScannerDeleteResultStatus,
|
||||
NativeScannerImageLoadStatus,
|
||||
NativeScannerPreflightStatus,
|
||||
NativeScannerProcessOptions,
|
||||
NativeScannerProcessStatus,
|
||||
NativeScannerProcessingProgressStatus,
|
||||
NativeScannerPromotionStatus,
|
||||
NativeScannerReviewArtifactInput,
|
||||
NativeScannerReviewStatus,
|
||||
NativeScannerResultsLoadOptions,
|
||||
NativeScannerResultsLoadStatus,
|
||||
NativeScannerRunStatus,
|
||||
OcrResult,
|
||||
@@ -70,7 +78,7 @@ app.commandLine.appendSwitch("disable-gpu-sandbox");
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const isDev = Boolean(process.env.VITE_DEV_SERVER_URL);
|
||||
const APP_RUNTIME_STARTED_AT = new Date().toISOString();
|
||||
const APP_RUNTIME_SIGNATURE = "2026-07-09-native-artifact-pipeline";
|
||||
const APP_RUNTIME_SIGNATURE = "2026-07-10-packaged-runtime-acceptance";
|
||||
|
||||
let registeredHotkeys: Record<string, boolean> = {};
|
||||
let devControlServer: Server | null = null;
|
||||
@@ -93,13 +101,26 @@ let scannerDevStatus: ScannerStatusPayload = {
|
||||
learningRuleCount: 0,
|
||||
updatedAt: null,
|
||||
};
|
||||
let nativeProcessingProgress: NativeScannerProcessingProgressStatus = {
|
||||
running: false,
|
||||
runDir: "",
|
||||
total: 0,
|
||||
processed: 0,
|
||||
parsed: 0,
|
||||
review: 0,
|
||||
stored: 0,
|
||||
errors: 0,
|
||||
elapsedMs: 0,
|
||||
};
|
||||
let repositoryContext: RepositoryContext | null = null;
|
||||
let artifactStoreRepository: ArtifactStoreRepositoryPort | null = null;
|
||||
let reviewSamplesRepository: ReviewSamplesRepositoryPort | null = null;
|
||||
let scannerLearningRepository: ScannerLearningRepositoryPort | null = null;
|
||||
let nativeScannerProcessingService: ReturnType<typeof createNativeScannerProcessingService> | null = null;
|
||||
let inputHelperService: InputHelperService | null = null;
|
||||
let appWindowManager: AppWindowManager | null = null;
|
||||
let goodFileService: GoodFileService | null = null;
|
||||
const nativeScannerRequestStartedAtByRunId = new Map<string, string>();
|
||||
|
||||
function getInputHelperService() {
|
||||
if (!inputHelperService) {
|
||||
@@ -111,12 +132,13 @@ function getInputHelperService() {
|
||||
// Locate the compiled C# input/capture sidecar (ADR-008). Falls back to null so
|
||||
// the service uses the embedded PowerShell helper when the exe was never built.
|
||||
function resolveInputHelperExePath(): string | null {
|
||||
const candidates = [
|
||||
process.env.INPUT_HELPER_EXE,
|
||||
path.join(process.resourcesPath, "input-helper", "InputHelper.exe"),
|
||||
path.join(process.cwd(), "native", "input-helper", "bin", "publish", "InputHelper.exe"),
|
||||
path.join(app.getAppPath(), "native", "input-helper", "bin", "publish", "InputHelper.exe"),
|
||||
].filter((candidate): candidate is string => Boolean(candidate));
|
||||
const candidates = inputHelperPathCandidates({
|
||||
isPackaged: app.isPackaged,
|
||||
resourcesPath: process.resourcesPath,
|
||||
currentWorkingDirectory: process.cwd(),
|
||||
appPath: app.getAppPath(),
|
||||
overridePath: process.env.INPUT_HELPER_EXE,
|
||||
});
|
||||
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
@@ -231,12 +253,13 @@ async function publishScannerStatus(status: ScannerStatusPayload) {
|
||||
}
|
||||
|
||||
function nativeScannerDataDir() {
|
||||
const candidates = [
|
||||
process.env.IK_INVENTORYLISTS_DIR,
|
||||
path.join(process.cwd(), "data", "ik-inventorylists"),
|
||||
path.join(app.getAppPath(), "data", "ik-inventorylists"),
|
||||
path.join(process.resourcesPath, "ik-inventorylists"),
|
||||
].filter((candidate): candidate is string => Boolean(candidate));
|
||||
const candidates = ikInventoryListsPathCandidates({
|
||||
isPackaged: app.isPackaged,
|
||||
resourcesPath: process.resourcesPath,
|
||||
currentWorkingDirectory: process.cwd(),
|
||||
appPath: app.getAppPath(),
|
||||
overridePath: process.env.IK_INVENTORYLISTS_DIR,
|
||||
});
|
||||
return candidates.find((candidate) => existsSync(path.join(candidate, "version.txt"))) ?? candidates[0];
|
||||
}
|
||||
|
||||
@@ -319,37 +342,64 @@ async function nativeScannerPreflight(options: { category?: string } = {}): Prom
|
||||
}
|
||||
|
||||
async function nativeScannerStart(options: { limit?: number; category?: string } = {}): Promise<NativeScannerRunStatus> {
|
||||
const status = await getInputHelperService().nativeScannerStart({
|
||||
const requestStartedAt = new Date().toISOString();
|
||||
const rawStatus = await getInputHelperService().nativeScannerStart({
|
||||
dataDir: nativeScannerDataDir(),
|
||||
outputRoot: nativeScannerOutputRoot(),
|
||||
limit: options.limit ?? 100,
|
||||
category: options.category ?? "artifacts",
|
||||
});
|
||||
const runId = rawStatus.runId?.trim();
|
||||
if (runId && !nativeScannerRequestStartedAtByRunId.has(runId)) {
|
||||
nativeScannerRequestStartedAtByRunId.set(runId, requestStartedAt);
|
||||
}
|
||||
const status = await attachNativeScannerTiming(rawStatus);
|
||||
publishNativeScannerStatus(status, { lookupStatus: await nativeScannerDataStatus().catch(() => undefined) });
|
||||
return status;
|
||||
}
|
||||
|
||||
async function nativeScannerStop(): Promise<NativeScannerRunStatus> {
|
||||
const status = await getInputHelperService().nativeScannerStop();
|
||||
const status = await attachNativeScannerTiming(await getInputHelperService().nativeScannerStop());
|
||||
publishNativeScannerStatus(status);
|
||||
return status;
|
||||
}
|
||||
|
||||
async function nativeScannerStatus(): Promise<NativeScannerRunStatus> {
|
||||
const status = await getInputHelperService().nativeScannerStatus();
|
||||
const status = await attachNativeScannerTiming(await getInputHelperService().nativeScannerStatus());
|
||||
publishNativeScannerStatus(status);
|
||||
return status;
|
||||
}
|
||||
|
||||
async function attachNativeScannerTiming(status: NativeScannerRunStatus): Promise<NativeScannerRunStatus> {
|
||||
const runDir = status.runDir?.trim();
|
||||
const runId = status.runId?.trim();
|
||||
if (!runDir) return status;
|
||||
const timing = await recordNativeScannerRunTiming(runDir, {
|
||||
...(runId && nativeScannerRequestStartedAtByRunId.has(runId)
|
||||
? { requestStartedAt: nativeScannerRequestStartedAtByRunId.get(runId) }
|
||||
: {}),
|
||||
...(usableRunTimingTimestamp(status.startedAt) ? { scannerStartedAt: status.startedAt } : {}),
|
||||
...(usableRunTimingTimestamp(status.captureStartedAt) ? { captureStartedAt: status.captureStartedAt } : {}),
|
||||
...(usableRunTimingTimestamp(status.captureCompletedAt) ? { captureCompletedAt: status.captureCompletedAt } : {}),
|
||||
}).catch(() => null);
|
||||
if (!status.running && runId) nativeScannerRequestStartedAtByRunId.delete(runId);
|
||||
return timing ? { ...status, timing } : status;
|
||||
}
|
||||
|
||||
function usableRunTimingTimestamp(value: unknown): value is string {
|
||||
return typeof value === "string"
|
||||
&& value.length > 0
|
||||
&& !value.startsWith("0001-")
|
||||
&& Number.isFinite(Date.parse(value));
|
||||
}
|
||||
|
||||
function resolveNativeProcessRunDir(runDir?: string) {
|
||||
const native = scannerDevStatus.nativeScanner as NativeScannerRunStatus | undefined;
|
||||
const candidate = runDir?.trim() || native?.runDir || "";
|
||||
if (!candidate) return "";
|
||||
const root = path.resolve(nativeScannerOutputRoot());
|
||||
const resolved = path.resolve(candidate);
|
||||
const relative = path.relative(root, resolved);
|
||||
if (relative.startsWith("..") || path.isAbsolute(relative)) return "";
|
||||
return resolved;
|
||||
return resolveNativeScannerRunDirectory({
|
||||
outputRoot: nativeScannerOutputRoot(),
|
||||
requestedRunDir: runDir,
|
||||
activeRunDir: native?.runDir,
|
||||
});
|
||||
}
|
||||
|
||||
async function buildNativeCardCaptureResult(
|
||||
@@ -436,39 +486,71 @@ async function buildNativeCardCaptureResult(
|
||||
};
|
||||
}
|
||||
|
||||
function createNativeScannerService() {
|
||||
return createNativeScannerProcessingService({
|
||||
function getNativeScannerService() {
|
||||
if (nativeScannerProcessingService) return nativeScannerProcessingService;
|
||||
nativeScannerProcessingService = createNativeScannerProcessingService({
|
||||
resolveRunDir: resolveNativeProcessRunDir,
|
||||
buildCaptureResult: buildNativeCardCaptureResult,
|
||||
loadArtifacts: () => getArtifactStoreRepository().loadAll(),
|
||||
saveArtifacts: (records) => getArtifactStoreRepository().saveMany(records),
|
||||
removeArtifacts: (ids) => getArtifactStoreRepository().removeByIds(ids),
|
||||
loadIkArtifactCatalog: loadNativeIkArtifactCatalog,
|
||||
saveReviewSample: (sample) => getReviewSamplesRepository().append(sample),
|
||||
});
|
||||
}
|
||||
|
||||
async function nativeScannerProcessRun(options: { runDir?: string; persist?: boolean; limit?: number } = {}): Promise<NativeScannerProcessStatus> {
|
||||
const service = createNativeScannerService();
|
||||
const status = await service.processRun(options);
|
||||
scannerDevStatus = {
|
||||
...scannerDevStatus,
|
||||
reviewStatus: `Native post-processing: ${status.parsed}/${status.processed} parsed, ${status.review} review, ${status.stored} stored.`,
|
||||
stats: {
|
||||
...(scannerDevStatus.stats ?? {}),
|
||||
...nativeScannerProcessStats(status),
|
||||
recordRunTiming: recordNativeScannerRunTiming,
|
||||
loadRunTiming: readNativeScannerRunTiming,
|
||||
onProgress: (progress) => {
|
||||
nativeProcessingProgress = { ...progress };
|
||||
},
|
||||
nativeScannerProcessing: status,
|
||||
automationLog: [
|
||||
...(scannerDevStatus.automationLog ?? []).slice(-10),
|
||||
`native process: ${status.parsed}/${status.processed} parsed, report ${status.reportPath}`,
|
||||
],
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
return status;
|
||||
});
|
||||
return nativeScannerProcessingService;
|
||||
}
|
||||
|
||||
async function nativeScannerLoadResults(options: { runDir?: string; limit?: number } = {}): Promise<NativeScannerResultsLoadStatus> {
|
||||
const service = createNativeScannerService();
|
||||
async function nativeScannerProcessingStatus() {
|
||||
return { ...nativeProcessingProgress };
|
||||
}
|
||||
|
||||
async function nativeScannerProcessRun(options: NativeScannerProcessOptions = {}): Promise<NativeScannerProcessStatus> {
|
||||
const service = getNativeScannerService();
|
||||
nativeProcessingProgress = {
|
||||
running: true,
|
||||
runDir: options.runDir ?? "",
|
||||
total: Math.max(0, Math.round(options.expectedTotal ?? 0)),
|
||||
processed: 0,
|
||||
parsed: 0,
|
||||
review: 0,
|
||||
stored: 0,
|
||||
errors: 0,
|
||||
elapsedMs: 0,
|
||||
};
|
||||
try {
|
||||
const status = await service.processRun(options);
|
||||
scannerDevStatus = {
|
||||
...scannerDevStatus,
|
||||
reviewStatus: `Native post-processing: ${status.parsed}/${status.processed} parsed, ${status.review} review, ${status.stored} stored.`,
|
||||
stats: {
|
||||
...(scannerDevStatus.stats ?? {}),
|
||||
...nativeScannerProcessStats(status),
|
||||
},
|
||||
nativeScannerProcessing: status,
|
||||
automationLog: [
|
||||
...(scannerDevStatus.automationLog ?? []).slice(-10),
|
||||
`native process: ${status.parsed}/${status.processed} parsed, report ${status.reportPath}`,
|
||||
],
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
return status;
|
||||
} catch (error) {
|
||||
nativeProcessingProgress = {
|
||||
...nativeProcessingProgress,
|
||||
running: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function nativeScannerLoadResults(options: NativeScannerResultsLoadOptions = {}): Promise<NativeScannerResultsLoadStatus> {
|
||||
const service = getNativeScannerService();
|
||||
const loaded = await service.loadResults(options);
|
||||
scannerDevStatus = {
|
||||
...scannerDevStatus,
|
||||
@@ -483,7 +565,7 @@ async function nativeScannerLoadResults(options: { runDir?: string; limit?: numb
|
||||
}
|
||||
|
||||
async function nativeScannerPromoteResults(options: { runDir?: string; resultIds: string[] }): Promise<NativeScannerPromotionStatus> {
|
||||
const service = createNativeScannerService();
|
||||
const service = getNativeScannerService();
|
||||
const status = await service.promoteResults(options);
|
||||
scannerDevStatus = {
|
||||
...scannerDevStatus,
|
||||
@@ -507,7 +589,7 @@ async function nativeScannerReviewResult(options: {
|
||||
artifact?: NativeScannerReviewArtifactInput;
|
||||
note?: string;
|
||||
}): Promise<NativeScannerReviewStatus> {
|
||||
const service = createNativeScannerService();
|
||||
const service = getNativeScannerService();
|
||||
const status = await service.reviewResult(options);
|
||||
scannerDevStatus = {
|
||||
...scannerDevStatus,
|
||||
@@ -524,8 +606,30 @@ async function nativeScannerReviewResult(options: {
|
||||
return status;
|
||||
}
|
||||
|
||||
async function nativeScannerDeleteResult(options: {
|
||||
runDir?: string;
|
||||
resultId: string;
|
||||
removeLinkedStoreRecord?: boolean;
|
||||
}): Promise<NativeScannerDeleteResultStatus> {
|
||||
const service = getNativeScannerService();
|
||||
const status = await service.deleteResult(options);
|
||||
scannerDevStatus = {
|
||||
...scannerDevStatus,
|
||||
reviewStatus: status.ok
|
||||
? `Local native result removed: ${status.resultId}${status.deletedStoreRecord ? " and linked store record" : ""}.`
|
||||
: `Local native result removal blocked: ${status.error ?? "unknown error"}`,
|
||||
nativeScannerDeletion: status,
|
||||
automationLog: [
|
||||
...(scannerDevStatus.automationLog ?? []).slice(-10),
|
||||
`native local deletion: ${status.resultId || "unknown"} ${status.ok ? "ok" : status.error ?? "blocked"}`,
|
||||
],
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
return status;
|
||||
}
|
||||
|
||||
async function nativeScannerLoadImage(options: { runDir?: string; imagePath: string }): Promise<NativeScannerImageLoadStatus> {
|
||||
const service = createNativeScannerService();
|
||||
const service = getNativeScannerService();
|
||||
return service.loadImage(options);
|
||||
}
|
||||
|
||||
@@ -701,16 +805,6 @@ async function getAutomationGuardCommand() {
|
||||
};
|
||||
}
|
||||
|
||||
function isLikelyGenshinSourceName(sourceName: string) {
|
||||
const lowered = sourceName.toLowerCase();
|
||||
return (
|
||||
lowered.includes("genshin")
|
||||
|| lowered.includes("genshinimpact")
|
||||
|| lowered.includes("yuanshen")
|
||||
|| sourceName.includes("\u539f\u795e")
|
||||
);
|
||||
}
|
||||
|
||||
async function capturePrimaryScreenViaGdi() {
|
||||
return getInputHelperService().capturePrimaryScreenViaGdi();
|
||||
}
|
||||
@@ -734,6 +828,7 @@ function nativeImageFromGdiCapture(gdi: Awaited<ReturnType<InputHelperService["c
|
||||
function shouldUseDirectGdiHotPath(options: CaptureOptions = {}) {
|
||||
return Boolean(
|
||||
options.ocrMode === "artifact" ||
|
||||
options.ocrMode === "inventory-count" ||
|
||||
options.skipOcrUnlessArtifactDetail ||
|
||||
options.skipOcr ||
|
||||
options.omitCrops,
|
||||
@@ -1398,7 +1493,7 @@ function imageCropDataUrl(sourceImage: NativeImage, rect: Electron.Rectangle, im
|
||||
|
||||
function imageCropFingerprint(sourceImage: NativeImage, rect: Electron.Rectangle, imageSize: { width: number; height: number }) {
|
||||
const safeRect = clampCaptureRect(rect, imageSize);
|
||||
const bitmap = sourceImage.crop(safeRect).getBitmap();
|
||||
const bitmap = sourceImage.crop(safeRect).toBitmap();
|
||||
let hash = 2166136261;
|
||||
const stride = Math.max(4, Math.floor(bitmap.length / 4096) * 4);
|
||||
for (let index = 0; index < bitmap.length; index += stride) {
|
||||
@@ -1424,7 +1519,7 @@ function preprocessedCropPngBuffer(sourceImage: NativeImage, rect: Electron.Rect
|
||||
const size = upscaled.getSize();
|
||||
if (!size.width || !size.height) return upscaled.toPNG();
|
||||
const binarized = binarizeForOcr(
|
||||
{ data: upscaled.getBitmap(), width: size.width, height: size.height },
|
||||
{ data: upscaled.toBitmap(), width: size.width, height: size.height },
|
||||
cropId === "artifact-level" ? { contrast: 80 } : {},
|
||||
);
|
||||
return nativeImage
|
||||
@@ -1442,19 +1537,22 @@ function createCrops(
|
||||
cropOptions: { sanctified?: boolean; skipOcr?: boolean } = {},
|
||||
) {
|
||||
const isArtifactScanMode = options.ocrMode === "artifact";
|
||||
const isInventoryCountMode = options.ocrMode === "inventory-count";
|
||||
const fastArtifactProfile = isArtifactScanMode && options.ocrProfile === "fast";
|
||||
const omitCropImages = Boolean(options.omitCropImages);
|
||||
const skipCropOcr = Boolean(cropOptions.skipOcr);
|
||||
const templates: CropTemplate[] = detailCropRects(detailRect, imageSize, { ...cropOptions, fastProfile: fastArtifactProfile })
|
||||
.filter((template) => {
|
||||
if (fastArtifactProfile && (
|
||||
template.id === "artifact-set-effects" ||
|
||||
template.id === "artifact-main-stat-value"
|
||||
)) return false;
|
||||
if (template.id === "artifact-footer" && options.omitEquippedOcr) return false;
|
||||
if (!isArtifactScanMode || template.id !== "artifact-footer" || !bitmap) return true;
|
||||
return hasEquippedFooterMarker(bitmap, imageSize, template.rect);
|
||||
});
|
||||
const templates: CropTemplate[] = isInventoryCountMode
|
||||
? []
|
||||
: detailCropRects(detailRect, imageSize, { ...cropOptions, fastProfile: fastArtifactProfile })
|
||||
.filter((template) => {
|
||||
if (fastArtifactProfile && (
|
||||
template.id === "artifact-set-effects" ||
|
||||
template.id === "artifact-main-stat-value"
|
||||
)) return false;
|
||||
if (template.id === "artifact-footer" && options.omitEquippedOcr) return false;
|
||||
if (!isArtifactScanMode || template.id !== "artifact-footer" || !bitmap) return true;
|
||||
return hasEquippedFooterMarker(bitmap, imageSize, template.rect);
|
||||
});
|
||||
|
||||
if (!isArtifactScanMode && inventoryRect.width > 120 && inventoryRect.height > 80) {
|
||||
templates.push({
|
||||
@@ -1544,24 +1642,25 @@ async function buildCaptureResult(
|
||||
options: CaptureOptions = {},
|
||||
) {
|
||||
const buildStartedAt = Date.now();
|
||||
const isInventoryCountMode = options.ocrMode === "inventory-count";
|
||||
const size = sourceImage.getSize();
|
||||
if (!size.width || !size.height) {
|
||||
throw new Error("Capture produced an empty image.");
|
||||
}
|
||||
|
||||
const bitmap = sourceImage.getBitmap();
|
||||
const bitmap = sourceImage.toBitmap();
|
||||
const detailRect = inferDetailRect(bitmap, size);
|
||||
const artifactDetail = analyzeArtifactDetailPanel(bitmap, size, detailRect);
|
||||
const paimonMenu = analyzePaimonMenu(bitmap, size);
|
||||
const sanctified = detectSanctifiedArtifactDetail(bitmap, size, detailRect);
|
||||
const skipOcrForMissingDetail = Boolean(options.skipOcrUnlessArtifactDetail && !artifactDetail.present);
|
||||
const paimonMenu = isInventoryCountMode ? undefined : analyzePaimonMenu(bitmap, size);
|
||||
const sanctified = isInventoryCountMode ? undefined : detectSanctifiedArtifactDetail(bitmap, size, detailRect);
|
||||
const skipOcrForMissingDetail = Boolean(options.skipOcrUnlessArtifactDetail && !artifactDetail?.present);
|
||||
const shouldSkipOcr = Boolean(options.skipOcr || skipOcrForMissingDetail);
|
||||
const inventoryRect = inferInventoryRect(size, detailRect);
|
||||
const omitCrops = Boolean(options.omitCrops);
|
||||
const crops = omitCrops
|
||||
? []
|
||||
: createCrops(sourceImage, size, detailRect, inventoryRect, options, bitmap, { sanctified, skipOcr: shouldSkipOcr });
|
||||
const lockSignal = options.omitLockState
|
||||
const lockSignal = options.omitLockState || isInventoryCountMode
|
||||
? undefined
|
||||
: (() => {
|
||||
const lockRect = clampCaptureRect(lockIconCropRect(detailRect, size), size);
|
||||
@@ -1572,7 +1671,7 @@ async function buildCaptureResult(
|
||||
try {
|
||||
return lockSignalRatio(pngBufferToBitmap(lockImage.toPNG()));
|
||||
} catch {
|
||||
return lockSignalRatio({ data: lockImage.getBitmap(), width: lockSize.width, height: lockSize.height });
|
||||
return lockSignalRatio({ data: lockImage.toBitmap(), width: lockSize.width, height: lockSize.height });
|
||||
}
|
||||
})()
|
||||
: undefined;
|
||||
@@ -1590,11 +1689,11 @@ async function buildCaptureResult(
|
||||
: undefined;
|
||||
})();
|
||||
const locked = lockSignal ? isLocked(lockSignal.ratio, lockSignal.threshold) : undefined;
|
||||
const omitFullFrame = Boolean(options.omitFullFrame || options.ocrMode === "artifact");
|
||||
const omitDetailPreview = Boolean(options.omitDetailPreview);
|
||||
const omitInventoryPreview = Boolean(options.omitInventoryPreview || options.ocrMode === "artifact");
|
||||
const detailFingerprint = imageCropFingerprint(sourceImage, detailRect, size);
|
||||
const inventoryFingerprint = imageCropFingerprint(sourceImage, inventoryRect, size);
|
||||
const omitFullFrame = Boolean(options.omitFullFrame || options.ocrMode === "artifact" || isInventoryCountMode);
|
||||
const omitDetailPreview = Boolean(options.omitDetailPreview || isInventoryCountMode);
|
||||
const omitInventoryPreview = Boolean(options.omitInventoryPreview || options.ocrMode === "artifact" || isInventoryCountMode);
|
||||
const detailFingerprint = isInventoryCountMode ? undefined : imageCropFingerprint(sourceImage, detailRect, size);
|
||||
const inventoryFingerprint = isInventoryCountMode ? undefined : imageCropFingerprint(sourceImage, inventoryRect, size);
|
||||
const fastOcrPriority: Record<string, number> = {
|
||||
"artifact-substats": 0,
|
||||
"artifact-name": 1,
|
||||
@@ -1757,8 +1856,9 @@ function initializeAppLifecycle() {
|
||||
nativeScannerStart: (options?: { limit?: number; category?: string }) => nativeScannerStart(options),
|
||||
nativeScannerStop: () => nativeScannerStop(),
|
||||
nativeScannerStatus: () => nativeScannerStatus(),
|
||||
nativeScannerProcessRun: (options?: { runDir?: string; persist?: boolean; limit?: number }) => nativeScannerProcessRun(options),
|
||||
nativeScannerLoadResults: (options?: { runDir?: string; limit?: number }) => nativeScannerLoadResults(options),
|
||||
nativeScannerProcessRun: (options?: NativeScannerProcessOptions) => nativeScannerProcessRun(options),
|
||||
nativeScannerProcessingStatus: () => nativeScannerProcessingStatus(),
|
||||
nativeScannerLoadResults: (options?: NativeScannerResultsLoadOptions) => nativeScannerLoadResults(options),
|
||||
nativeScannerPromoteResults: (options: { runDir?: string; resultIds: string[] }) => nativeScannerPromoteResults(options),
|
||||
nativeScannerReviewResult: (options: {
|
||||
runDir?: string;
|
||||
@@ -1767,6 +1867,7 @@ function initializeAppLifecycle() {
|
||||
artifact?: NativeScannerReviewArtifactInput;
|
||||
note?: string;
|
||||
}) => nativeScannerReviewResult(options),
|
||||
nativeScannerDeleteResult: (options: { runDir?: string; resultId: string; removeLinkedStoreRecord?: boolean }) => nativeScannerDeleteResult(options),
|
||||
nativeScannerLoadImage: (options: { runDir?: string; imagePath: string }) => nativeScannerLoadImage(options),
|
||||
readRuntimeInfo: () => readRuntimeInfo(),
|
||||
loadSnapshotFromDisk: () => loadSnapshotFromDisk(),
|
||||
|
||||
Reference in New Issue
Block a user