mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 12:11:28 -07:00
feat(ai): split shared provider config from Anki runtime
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { AnkiIntegration } from '../../anki-integration';
|
||||
import { mergeAiConfig } from '../../ai/config';
|
||||
import {
|
||||
AiConfig,
|
||||
AnkiConnectConfig,
|
||||
JimakuApiResponse,
|
||||
JimakuEntry,
|
||||
@@ -30,7 +32,7 @@ interface SubtitleTimingTrackerLike {
|
||||
|
||||
export interface AnkiJimakuIpcRuntimeOptions {
|
||||
patchAnkiConnectEnabled: (enabled: boolean) => void;
|
||||
getResolvedConfig: () => { ankiConnect?: AnkiConnectConfig };
|
||||
getResolvedConfig: () => { ankiConnect?: AnkiConnectConfig; ai?: AiConfig };
|
||||
getRuntimeOptionsManager: () => RuntimeOptionsManagerLike | null;
|
||||
getSubtitleTimingTracker: () => SubtitleTimingTrackerLike | null;
|
||||
getMpvClient: () => MpvClientLike | null;
|
||||
@@ -100,6 +102,7 @@ export function registerAnkiJimakuIpcRuntime(
|
||||
options.showDesktopNotification,
|
||||
options.createFieldGroupingCallback(),
|
||||
options.getKnownWordCacheStatePath(),
|
||||
mergeAiConfig(config.ai, config.ankiConnect?.ai) as AiConfig,
|
||||
);
|
||||
integration.start();
|
||||
options.setAnkiIntegration(integration);
|
||||
|
||||
@@ -125,10 +125,10 @@ test('config hot reload runtime reports validation warnings from reload', () =>
|
||||
config: deepCloneConfig(DEFAULT_CONFIG),
|
||||
warnings: [
|
||||
{
|
||||
path: 'ankiConnect.openRouter',
|
||||
message: 'Deprecated key; use ankiConnect.ai instead.',
|
||||
path: 'ankiConnect.ai',
|
||||
message: 'Expected boolean.',
|
||||
value: { enabled: true },
|
||||
fallback: {},
|
||||
fallback: false,
|
||||
},
|
||||
],
|
||||
path: '/tmp/config.jsonc',
|
||||
|
||||
@@ -73,7 +73,11 @@ function classifyDiff(prev: ResolvedConfig, next: ResolvedConfig): ConfigHotRelo
|
||||
if (key === 'ankiConnect') {
|
||||
const normalizedPrev = {
|
||||
...prev.ankiConnect,
|
||||
ai: next.ankiConnect.ai,
|
||||
ai: {
|
||||
enabled: next.ankiConnect.ai.enabled,
|
||||
model: prev.ankiConnect.ai.model,
|
||||
systemPrompt: prev.ankiConnect.ai.systemPrompt,
|
||||
},
|
||||
};
|
||||
if (!isEqual(normalizedPrev, next.ankiConnect)) {
|
||||
restartRequiredFields.push('ankiConnect');
|
||||
|
||||
@@ -109,6 +109,65 @@ test('initializeOverlayRuntime starts Anki integration when ankiConnect.enabled
|
||||
assert.equal(setIntegrationCalls, 1);
|
||||
});
|
||||
|
||||
test('initializeOverlayRuntime merges shared ai config with Anki overrides', () => {
|
||||
initializeOverlayRuntime({
|
||||
backendOverride: null,
|
||||
createMainWindow: () => {},
|
||||
registerGlobalShortcuts: () => {},
|
||||
updateVisibleOverlayBounds: () => {},
|
||||
isVisibleOverlayVisible: () => false,
|
||||
updateVisibleOverlayVisibility: () => {},
|
||||
getOverlayWindows: () => [],
|
||||
syncOverlayShortcuts: () => {},
|
||||
setWindowTracker: () => {},
|
||||
getMpvSocketPath: () => '/tmp/mpv.sock',
|
||||
createWindowTracker: () => null,
|
||||
getResolvedConfig: () => ({
|
||||
ankiConnect: {
|
||||
enabled: true,
|
||||
ai: {
|
||||
enabled: true,
|
||||
model: 'openrouter/anki-model',
|
||||
systemPrompt: 'Translate mined sentence text.',
|
||||
},
|
||||
} as never,
|
||||
ai: {
|
||||
enabled: true,
|
||||
apiKey: 'shared-key',
|
||||
baseUrl: 'https://openrouter.ai/api',
|
||||
model: 'openrouter/shared-model',
|
||||
systemPrompt: 'Legacy shared prompt.',
|
||||
requestTimeoutMs: 15000,
|
||||
},
|
||||
}),
|
||||
getSubtitleTimingTracker: () => ({}),
|
||||
getMpvClient: () => ({
|
||||
send: () => {},
|
||||
}),
|
||||
getRuntimeOptionsManager: () => ({
|
||||
getEffectiveAnkiConnectConfig: (config) => config as never,
|
||||
}),
|
||||
createAnkiIntegration: (args) => {
|
||||
assert.equal(args.aiConfig.apiKey, 'shared-key');
|
||||
assert.equal(args.aiConfig.baseUrl, 'https://openrouter.ai/api');
|
||||
assert.equal(args.aiConfig.model, 'openrouter/anki-model');
|
||||
assert.equal(args.aiConfig.systemPrompt, 'Translate mined sentence text.');
|
||||
return {
|
||||
start: () => {},
|
||||
};
|
||||
},
|
||||
setAnkiIntegration: () => {},
|
||||
showDesktopNotification: () => {},
|
||||
createFieldGroupingCallback: () => async () => ({
|
||||
keepNoteId: 5,
|
||||
deleteNoteId: 6,
|
||||
deleteDuplicate: false,
|
||||
cancelled: false,
|
||||
}),
|
||||
getKnownWordCacheStatePath: () => '/tmp/known-words-cache.json',
|
||||
});
|
||||
});
|
||||
|
||||
test('initializeOverlayRuntime re-syncs overlay shortcuts when tracker focus changes', () => {
|
||||
let syncCalls = 0;
|
||||
const tracker = {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { BrowserWindow } from 'electron';
|
||||
import { BaseWindowTracker, createWindowTracker } from '../../window-trackers';
|
||||
import { mergeAiConfig } from '../../ai/config';
|
||||
import {
|
||||
AiConfig,
|
||||
AnkiConnectConfig,
|
||||
@@ -124,7 +125,7 @@ export function initializeOverlayRuntime(options: {
|
||||
const createAnkiIntegration = options.createAnkiIntegration ?? createDefaultAnkiIntegration;
|
||||
const integration = createAnkiIntegration({
|
||||
config: effectiveAnkiConfig,
|
||||
aiConfig: config.ai ?? {},
|
||||
aiConfig: mergeAiConfig(config.ai, config.ankiConnect?.ai),
|
||||
subtitleTimingTracker,
|
||||
mpvClient,
|
||||
showDesktopNotification: options.showDesktopNotification,
|
||||
|
||||
Reference in New Issue
Block a user