fix: harden AI subtitle fix response parsing

This commit is contained in:
2026-03-08 16:01:40 -07:00
parent 8e319a417d
commit 93cd688625
22 changed files with 641 additions and 55 deletions

View File

@@ -6,9 +6,10 @@ import {
import {
refreshOverlayShortcutsRuntime,
registerOverlayShortcuts,
shouldActivateOverlayShortcuts,
syncOverlayShortcutsRuntime,
unregisterOverlayShortcutsRuntime,
} from '../core/services';
} from '../core/services/overlay-shortcut';
import { runOverlayShortcutLocalFallback } from '../core/services/overlay-shortcut-handler';
export interface OverlayShortcutRuntimeServiceInput {
@@ -16,6 +17,8 @@ export interface OverlayShortcutRuntimeServiceInput {
getShortcutsRegistered: () => boolean;
setShortcutsRegistered: (registered: boolean) => void;
isOverlayRuntimeInitialized: () => boolean;
isMacOSPlatform: () => boolean;
isTrackedMpvWindowFocused: () => boolean;
showMpvOsd: (text: string) => void;
openRuntimeOptionsPalette: () => void;
openJimaku: () => void;
@@ -89,7 +92,12 @@ export function createOverlayShortcutsRuntimeService(
};
};
const shouldOverlayShortcutsBeActive = () => input.isOverlayRuntimeInitialized();
const shouldOverlayShortcutsBeActive = () =>
shouldActivateOverlayShortcuts({
overlayRuntimeInitialized: input.isOverlayRuntimeInitialized(),
isMacOSPlatform: input.isMacOSPlatform(),
trackedMpvWindowFocused: input.isTrackedMpvWindowFocused(),
});
return {
tryHandleOverlayShortcutLocalFallback: (inputEvent) =>

View File

@@ -58,6 +58,7 @@ test('media path change handler reports stop for empty path and probes media key
ensureAnilistMediaGuess: (mediaKey) => calls.push(`guess:${mediaKey}`),
syncImmersionMediaState: () => calls.push('sync'),
scheduleCharacterDictionarySync: () => calls.push('dict-sync'),
signalAutoplayReadyIfWarm: (path) => calls.push(`autoplay:${path}`),
refreshDiscordPresence: () => calls.push('presence'),
});
@@ -74,6 +75,34 @@ test('media path change handler reports stop for empty path and probes media key
]);
});
test('media path change handler signals autoplay-ready fast path for warm non-empty media', () => {
const calls: string[] = [];
const handler = createHandleMpvMediaPathChangeHandler({
updateCurrentMediaPath: (path) => calls.push(`path:${path}`),
reportJellyfinRemoteStopped: () => calls.push('stopped'),
restoreMpvSubVisibility: () => calls.push('restore-mpv-sub'),
getCurrentAnilistMediaKey: () => null,
resetAnilistMediaTracking: (mediaKey) => calls.push(`reset:${String(mediaKey)}`),
maybeProbeAnilistDuration: (mediaKey) => calls.push(`probe:${mediaKey}`),
ensureAnilistMediaGuess: (mediaKey) => calls.push(`guess:${mediaKey}`),
syncImmersionMediaState: () => calls.push('sync'),
scheduleCharacterDictionarySync: () => calls.push('dict-sync'),
signalAutoplayReadyIfWarm: (path) => calls.push(`autoplay:${path}`),
refreshDiscordPresence: () => calls.push('presence'),
});
handler({ path: '/tmp/video.mkv' });
assert.deepEqual(calls, [
'path:/tmp/video.mkv',
'reset:null',
'sync',
'dict-sync',
'autoplay:/tmp/video.mkv',
'presence',
]);
});
test('media title change handler clears guess state and syncs immersion', () => {
const calls: string[] = [];
const handler = createHandleMpvMediaTitleChangeHandler({

View File

@@ -40,6 +40,7 @@ export function createHandleMpvMediaPathChangeHandler(deps: {
ensureAnilistMediaGuess: (mediaKey: string) => void;
syncImmersionMediaState: () => void;
scheduleCharacterDictionarySync?: () => void;
signalAutoplayReadyIfWarm?: (path: string) => void;
refreshDiscordPresence: () => void;
}) {
return ({ path }: { path: string | null }): void => {
@@ -58,6 +59,7 @@ export function createHandleMpvMediaPathChangeHandler(deps: {
deps.syncImmersionMediaState();
if (normalizedPath.trim().length > 0) {
deps.scheduleCharacterDictionarySync?.();
deps.signalAutoplayReadyIfWarm?.(normalizedPath);
}
deps.refreshDiscordPresence();
};

View File

@@ -50,6 +50,7 @@ export function createBindMpvMainEventHandlersHandler(deps: {
maybeProbeAnilistDuration: (mediaKey: string) => void;
ensureAnilistMediaGuess: (mediaKey: string) => void;
syncImmersionMediaState: () => void;
signalAutoplayReadyIfWarm?: (path: string) => void;
updateCurrentMediaTitle: (title: string) => void;
resetAnilistMediaGuessState: () => void;
@@ -105,6 +106,7 @@ export function createBindMpvMainEventHandlersHandler(deps: {
maybeProbeAnilistDuration: (mediaKey) => deps.maybeProbeAnilistDuration(mediaKey),
ensureAnilistMediaGuess: (mediaKey) => deps.ensureAnilistMediaGuess(mediaKey),
syncImmersionMediaState: () => deps.syncImmersionMediaState(),
signalAutoplayReadyIfWarm: (path) => deps.signalAutoplayReadyIfWarm?.(path),
scheduleCharacterDictionarySync: () => deps.scheduleCharacterDictionarySync?.(),
refreshDiscordPresence: () => deps.refreshDiscordPresence(),
});

View File

@@ -33,6 +33,7 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
maybeProbeAnilistDuration: (mediaKey: string) => void;
ensureAnilistMediaGuess: (mediaKey: string) => void;
syncImmersionMediaState: () => void;
signalAutoplayReadyIfWarm?: (path: string) => void;
scheduleCharacterDictionarySync?: () => void;
updateCurrentMediaTitle: (title: string) => void;
resetAnilistMediaGuessState: () => void;
@@ -82,6 +83,7 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
maybeProbeAnilistDuration: (mediaKey: string) => deps.maybeProbeAnilistDuration(mediaKey),
ensureAnilistMediaGuess: (mediaKey: string) => deps.ensureAnilistMediaGuess(mediaKey),
syncImmersionMediaState: () => deps.syncImmersionMediaState(),
signalAutoplayReadyIfWarm: (path: string) => deps.signalAutoplayReadyIfWarm?.(path),
scheduleCharacterDictionarySync: () => deps.scheduleCharacterDictionarySync?.(),
updateCurrentMediaTitle: (title: string) => deps.updateCurrentMediaTitle(title),
resetAnilistMediaGuessState: () => deps.resetAnilistMediaGuessState(),

View File

@@ -13,6 +13,8 @@ test('overlay shortcuts runtime main deps builder maps lifecycle and action call
calls.push(`registered:${registered}`);
},
isOverlayRuntimeInitialized: () => true,
isMacOSPlatform: () => true,
isTrackedMpvWindowFocused: () => false,
showMpvOsd: (text) => calls.push(`osd:${text}`),
openRuntimeOptionsPalette: () => calls.push('runtime-options'),
openJimaku: () => calls.push('jimaku'),
@@ -40,6 +42,8 @@ test('overlay shortcuts runtime main deps builder maps lifecycle and action call
})();
assert.equal(deps.isOverlayRuntimeInitialized(), true);
assert.equal(deps.isMacOSPlatform(), true);
assert.equal(deps.isTrackedMpvWindowFocused(), false);
assert.equal(deps.getShortcutsRegistered(), false);
deps.setShortcutsRegistered(true);
assert.equal(shortcutsRegistered, true);

View File

@@ -8,6 +8,8 @@ export function createBuildOverlayShortcutsRuntimeMainDepsHandler(
getShortcutsRegistered: () => deps.getShortcutsRegistered(),
setShortcutsRegistered: (registered: boolean) => deps.setShortcutsRegistered(registered),
isOverlayRuntimeInitialized: () => deps.isOverlayRuntimeInitialized(),
isMacOSPlatform: () => deps.isMacOSPlatform(),
isTrackedMpvWindowFocused: () => deps.isTrackedMpvWindowFocused(),
showMpvOsd: (text: string) => deps.showMpvOsd(text),
openRuntimeOptionsPalette: () => deps.openRuntimeOptionsPalette(),
openJimaku: () => deps.openJimaku(),