fix: improve fuzzy handling

This commit is contained in:
2026-05-30 19:32:01 +07:00 Unverified
parent 4bf2466b3d
commit 39f1eb6988
13 changed files with 280 additions and 264 deletions
+25 -24
View File
@@ -1,7 +1,6 @@
<!DOCTYPE html>
<!doctype html>
<html>
<head>
<head>
<meta charset="utf-8" />
<style>
body {
@@ -35,10 +34,9 @@
background: #000;
}
</style>
</head>
<body>
</head>
<body>
<div class="bar">
<button id="refresh">refresh</button>
<label>
@@ -51,11 +49,12 @@
</label>
<label>
threshold
<input id="threshold" type="range" min="0" max="120" value="85">
<input id="threshold" type="range" min="0" max="120" value="85" />
<span id="thresholdText">85</span>
</label>
<label>
time: <span id="timingText">Loading...</span><span style="color: gray;">(image load complete in browser)</span>
time: <span id="timingText">Loading...</span
><span style="color: gray">(image load complete in browser)</span>
</label>
</div>
@@ -63,10 +62,10 @@
<script>
const urlParams = new URLSearchParams(window.location.search);
const type = urlParams.get('type');
const type = urlParams.get("type");
let MINE, ORIG, W, H;
if (type === 'list') {
if (type === "list") {
MINE = "/v1/card/genshin/775956514";
ORIG = "/v1/card/genshin/diff/listoriginal.webp";
W = 1200;
@@ -91,7 +90,9 @@
const thresholdText = document.getElementById("thresholdText");
const timingText = document.getElementById("timingText");
let mineData, origData, refreshing = false;
let mineData,
origData,
refreshing = false;
function loadImage(src) {
return new Promise((resolve, reject) => {
@@ -99,7 +100,8 @@
img.crossOrigin = "anonymous";
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src + (src.includes("?") ? "&" : "?") + "cacheBust=" + Date.now();
img.src =
src + (src.includes("?") ? "&" : "?") + "cacheBust=" + Date.now();
});
}
@@ -127,7 +129,9 @@
const start = Date.now();
const [mineImg, origImg] = await Promise.all([
loadImage(MINE).finally(() => timingText.innerText = `${Date.now() - start}ms`),
loadImage(MINE).finally(
() => (timingText.innerText = `${Date.now() - start}ms`),
),
loadImage(ORIG),
]);
@@ -200,9 +204,7 @@
} else {
o[i + 3] = 0;
}
}
else if (mode.value === "diff") {
} else if (mode.value === "diff") {
const dr = Math.abs(m[i] - r[i]);
const dg = Math.abs(m[i + 1] - r[i + 1]);
const db = Math.abs(m[i + 2] - r[i + 2]);
@@ -212,9 +214,7 @@
o[i + 1] = diff;
o[i + 2] = diff;
o[i + 3] = diff > t ? 255 : 0;
}
else {
} else {
o[i] = m[i]; // mine red-ish
o[i + 1] = r[i + 1]; // original green-ish
o[i + 2] = 0;
@@ -230,14 +230,17 @@
mode.oninput = render;
threshold.oninput = render;
refresh().catch(err => {
refresh().catch((err) => {
console.error(err);
ctx.fillStyle = "red";
ctx.fillText(String(err), 20, 30);
});
window.addEventListener("keydown", (e) => {
if (e.key === "Enter" && !["INPUT", "SELECT"].includes(e.target.tagName)) {
if (
e.key === "Enter" &&
!["INPUT", "SELECT"].includes(e.target.tagName)
) {
e.preventDefault();
refresh();
}
@@ -247,7 +250,5 @@
// refresh();
// });
</script>
</body>
</body>
</html>
+6 -3
View File
@@ -55,7 +55,8 @@ export function endfieldCard(app: App) {
params: t.Object({ uid: t.String() }),
detail: {
tags: ["Endfield"],
description: "Preload data required to render an Endfield template card.",
description:
"Preload data required to render an Endfield template card.",
},
afterResponse: ({ log }) => log`Data preload complete`,
},
@@ -121,7 +122,8 @@ export function endfieldCard(app: App) {
{ http: "http", card: "card" },
{
default: "http",
description: "Type of error to return when card fails to render.",
description:
"Type of error to return when card fails to render.",
},
),
debug: t.Boolean({
@@ -134,7 +136,8 @@ export function endfieldCard(app: App) {
),
detail: {
tags: ["Endfield"],
description: "Generate a placeholder Endfield profile card image from a UID.",
description:
"Generate a placeholder Endfield profile card image from a UID.",
},
afterResponse: ({ log }) => log`Rendering completed`,
},
+8 -3
View File
@@ -61,7 +61,9 @@ export function EndfieldTemplate({
<div tw="text-[1.55em] leading-[1.15] font-semibold">
Route + Card Shell
</div>
<div tw="text-white/60 mt-[.35em]">Ready for game-specific layout work</div>
<div tw="text-white/60 mt-[.35em]">
Ready for game-specific layout work
</div>
</div>
<div tw="rounded-[1em] border border-white/10 bg-black/20 p-[1.2em]">
@@ -71,7 +73,9 @@ export function EndfieldTemplate({
<div tw="text-[1.55em] leading-[1.15] font-semibold">
Character Grid
</div>
<div tw="text-white/60 mt-[.35em]">Replace placeholder sections with Endfield data</div>
<div tw="text-white/60 mt-[.35em]">
Replace placeholder sections with Endfield data
</div>
</div>
</div>
@@ -80,7 +84,8 @@ export function EndfieldTemplate({
Signature
</div>
<div tw="text-[1.05em] leading-[1.45] text-white/82 whitespace-pre-wrap">
{signature?.trim() || "No signature available in the fetched payload yet."}
{signature?.trim() ||
"No signature available in the fetched payload yet."}
</div>
</div>
</div>
+5 -6
View File
@@ -3,7 +3,7 @@ import { mkdir, readdir, rm } from "node:fs/promises";
import { join } from "node:path";
import { file, hash, write } from "bun";
import type { ResolvedPlayerInfo} from "enka.ts";
import type { ResolvedPlayerInfo } from "enka.ts";
import { TTLCache } from "enka.ts";
import type {
@@ -13,11 +13,9 @@ import type {
EnkaNamecards,
EnkaPfps,
RelicStore,
WeaponStore} from "@/games/genshin/types/enka";
import {
ElementList
WeaponStore,
} from "@/games/genshin/types/enka";
import { ElementList } from "@/games/genshin/types/enka";
import { Deduplicator } from "@/lib/dedupe";
import { createLogger } from "@/lib/logger";
@@ -210,10 +208,11 @@ export async function getEnkaStore<K extends keyof StoreTypeMap>(
return data;
}
// DEPRECATED: No longer needed
export function getAvatar(
store: AvatarStore,
playerInfo: ResolvedPlayerInfo,
id: number,
id: string | number,
) {
return (
Object.entries(store).find(
+6 -3
View File
@@ -1,9 +1,9 @@
import type { BaseAvatar } from "enka.ts";
import { distance } from "fastest-levenshtein";
function shortAvatarId(id: number) {
function shortAvatarId(id: string | number) {
// 10000120 -> 120
return String(id).replace(/^10*/, "");
return String(id).replace(/^10*/, "").replace(/^-\d+/, "");
}
function norm(s: string) {
@@ -60,5 +60,8 @@ function fuzzyNameMatch<T extends { avatar: BaseAvatar }>(
}
function avatarAliases(name: string) {
return [name, ...name.split(" ")];
const aliases: Record<string, string[]> = {
Traveler: ["Aether", "Lumine"],
};
return [name, ...name.split(" "), ...(aliases[name] ?? [])];
}
+4 -1
View File
@@ -2,7 +2,10 @@ import type { ResolvedAvatarInfo } from "enka.ts";
import { AvatarElement } from "enka.ts";
import type { EnkaLocsLanguage } from "@/games/genshin/types/enka";
import { FightPropMapEnum, PercentageFightProp } from "@/games/genshin/types/fightPropMap";
import {
FightPropMapEnum,
PercentageFightProp,
} from "@/games/genshin/types/fightPropMap";
// Types
interface DisplayRow {
+4 -1
View File
@@ -1,5 +1,8 @@
import type { Affix } from "@/games/genshin/types/enka";
import { FightPropMapEnum, PercentageFightProp } from "@/games/genshin/types/fightPropMap";
import {
FightPropMapEnum,
PercentageFightProp,
} from "@/games/genshin/types/fightPropMap";
export type RollSummary = {
id: string;
-1
View File
@@ -19,7 +19,6 @@ import {
} from "@/games/genshin/types/enka";
import type { Logger } from "@/lib/logger";
import { arrRandom } from "@/lib/util";
import { CardResponse } from "@/v1/response";
export async function ListPage(
+1 -1
View File
@@ -1,6 +1,6 @@
import { t } from "elysia";
export type AvatarStore = Record<number, Avatar>;
export type AvatarStore = Record<string | number, Avatar>;
export interface Avatar {
Element: ElementType;
+1 -1
View File
@@ -1,3 +1,4 @@
import { cors } from "@elysia/cors";
import openapi from "@elysiajs/openapi";
import { Elysia, t } from "elysia";
@@ -5,7 +6,6 @@ import { version } from "../package.json";
import { pm } from "./lib/auth";
import { createLogger } from "./lib/logger";
import { v1routes } from "./v1";
import { cors } from '@elysia/cors'
const log = createLogger("APP");
+1 -1
View File
@@ -1,5 +1,5 @@
import { feature } from "bun:bundle";
import type { InspectColor} from "node:util";
import type { InspectColor } from "node:util";
import { styleText } from "node:util";
import { inspect } from "bun";
+1 -1
View File
@@ -1,6 +1,6 @@
import { join } from "node:path";
import type { MaybePromise} from "bun";
import type { MaybePromise } from "bun";
import { $, write } from "bun";
import { createLogger } from "@/lib/logger";