feat(stats): speed up session maintenance and improve stats UI (#111)

This commit is contained in:
2026-06-08 02:20:52 -07:00
committed by GitHub
parent e6a16a069b
commit 311f1e8ee5
108 changed files with 7441 additions and 729 deletions
+57 -18
View File
@@ -1833,6 +1833,31 @@ function getCurrentAutoplaySubtitlePayload(): SubtitleData | null {
return payload;
}
async function resolveSentenceSearchHeadwords(term: string): Promise<string[]> {
const fallback = term.trim() ? [term.trim()] : [];
try {
const tokenized = tokenizeSubtitleDeferred ? await tokenizeSubtitleDeferred(term) : null;
const tokens = tokenized?.tokens ?? [];
if (tokens.length === 0) return fallback;
const seen = new Set<string>();
const headwords: string[] = [];
for (const token of tokens) {
const headword = (token.headword || token.surface).trim();
if (!headword || seen.has(headword)) continue;
seen.add(headword);
headwords.push(headword);
}
return headwords.length > 0 ? headwords : fallback;
} catch (error) {
logger.debug(
'Failed to resolve sentence-search headwords:',
error instanceof Error ? error.message : String(error),
);
return fallback;
}
}
function signalCurrentSubtitleAutoplayReady(): void {
autoplayReadyGate.flushPendingAutoplayReadySignal();
const payload = getCurrentAutoplaySubtitlePayload();
@@ -2240,6 +2265,18 @@ const configHotReloadRuntime = createConfigHotReloadRuntime(
buildConfigHotReloadRuntimeMainDepsHandler(),
);
async function getCurrentYomitanAnkiDeckNameForRuntime(): Promise<string> {
await yomitanExtensionRuntime.ensureYomitanExtensionLoaded();
return getYomitanCurrentAnkiDeckNameCore(getYomitanParserRuntimeDeps(), {
error: (message, ...args) => {
logger.error(message, ...args);
},
info: (message, ...args) => {
logger.info(message, ...args);
},
});
}
const configSettingsRuntime = createConfigSettingsRuntime({
fields: configSettingsFields,
getConfigPath: () => configService.getConfigPath(),
@@ -2250,17 +2287,7 @@ const configSettingsRuntime = createConfigSettingsRuntime({
onHotReloadApplied: applyConfigHotReloadDiff,
defaultAnkiConnectUrl: DEFAULT_CONFIG.ankiConnect.url,
createAnkiClient: (url) => new AnkiConnectClient(url),
getYomitanAnkiDeckName: async () => {
await yomitanExtensionRuntime.ensureYomitanExtensionLoaded();
return getYomitanCurrentAnkiDeckNameCore(getYomitanParserRuntimeDeps(), {
error: (message, ...args) => {
logger.error(message, ...args);
},
info: (message, ...args) => {
logger.info(message, ...args);
},
});
},
getYomitanAnkiDeckName: getCurrentYomitanAnkiDeckNameForRuntime,
getSettingsWindow: () => appState.configSettingsWindow,
setSettingsWindow: (window) => {
appState.configSettingsWindow = window as BrowserWindow | null;
@@ -4441,14 +4468,20 @@ const startLocalStatsServer = (): void => {
tracker,
knownWordCachePath: path.join(USER_DATA_PATH, 'known-words-cache.json'),
mpvSocketPath: appState.mpvSocketPath,
ankiConnectConfig: getResolvedConfig().ankiConnect,
getAnkiConnectConfig: () => getResolvedConfig().ankiConnect,
getYomitanAnkiDeckName: getCurrentYomitanAnkiDeckNameForRuntime,
getSecondarySubtitleLanguages: () => getResolvedConfig().secondarySub.secondarySubLanguages,
getStatsMiningAlassPath: () => getResolvedConfig().subsync.alass_path,
anilistRateLimiter,
resolveAnkiNoteId: (noteId: number) =>
appState.ankiIntegration?.resolveCurrentNoteId(noteId) ?? noteId,
resolveSentenceSearchHeadwords,
addYomitanNote: async (word: string) => {
const ankiUrl = getResolvedConfig().ankiConnect.url || 'http://127.0.0.1:8765';
const ankiConnectConfig = getResolvedConfig().ankiConnect;
const ankiUrl = ankiConnectConfig.url || 'http://127.0.0.1:8765';
await syncYomitanDefaultAnkiServerCore(ankiUrl, yomitanDeps, yomitanLogger, {
forceOverride: true,
forceOverride: shouldForceOverrideYomitanAnkiServer(ankiConnectConfig),
deck: ankiConnectConfig.deck,
});
const result = await addYomitanNoteViaSearch(word, yomitanDeps, yomitanLogger);
if (result.noteId && result.duplicateNoteIds.length > 0) {
@@ -5640,7 +5673,7 @@ async function ensureYomitanExtensionLoaded(): Promise<Extension | null> {
return extension;
}
let lastSyncedYomitanAnkiServer: string | null = null;
let lastSyncedYomitanAnkiSettingsKey: string | null = null;
function getPreferredYomitanAnkiServerUrl(): string {
return getPreferredYomitanAnkiServerUrlRuntime(getResolvedConfig().ankiConnect);
@@ -5671,7 +5704,10 @@ async function syncYomitanDefaultProfileAnkiServer(): Promise<void> {
}
const targetUrl = getPreferredYomitanAnkiServerUrl().trim();
if (!targetUrl || targetUrl === lastSyncedYomitanAnkiServer) {
const ankiConnectConfig = getResolvedConfig().ankiConnect;
const targetDeck = ankiConnectConfig?.deck?.trim() ?? '';
const targetSettingsKey = `${targetUrl}\n${targetDeck}`;
if (!targetUrl || targetSettingsKey === lastSyncedYomitanAnkiSettingsKey) {
return;
}
@@ -5687,12 +5723,15 @@ async function syncYomitanDefaultProfileAnkiServer(): Promise<void> {
},
},
{
forceOverride: shouldForceOverrideYomitanAnkiServer(getResolvedConfig().ankiConnect),
forceOverride: ankiConnectConfig
? shouldForceOverrideYomitanAnkiServer(ankiConnectConfig)
: false,
deck: targetDeck,
},
);
if (synced) {
lastSyncedYomitanAnkiServer = targetUrl;
lastSyncedYomitanAnkiSettingsKey = targetSettingsKey;
}
}