mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-27 18:12:05 -07:00
refactor: split shared type entrypoints
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { AiConfig } from '../types';
|
||||
import type { AiConfig } from '../types/integrations';
|
||||
import { requestAiChatCompletion } from '../ai/client';
|
||||
|
||||
const DEFAULT_AI_SYSTEM_PROMPT =
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
|
||||
import { DEFAULT_ANKI_CONNECT_CONFIG } from '../config';
|
||||
import type { AnkiConnectConfig } from '../types';
|
||||
import type { AnkiConnectConfig } from '../types/anki';
|
||||
|
||||
type NoteInfoLike = {
|
||||
noteId: number;
|
||||
@@ -36,9 +36,7 @@ export function extractSoundFilenames(value: string): string[] {
|
||||
}
|
||||
|
||||
function shouldSyncAnimatedImageToWordAudio(config: Pick<AnkiConnectConfig, 'media'>): boolean {
|
||||
return (
|
||||
config.media?.imageType === 'avif' && config.media?.syncAnimatedImageToWordAudio !== false
|
||||
);
|
||||
return config.media?.imageType === 'avif' && config.media?.syncAnimatedImageToWordAudio !== false;
|
||||
}
|
||||
|
||||
export async function probeAudioDurationSeconds(
|
||||
|
||||
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import { CardCreationService } from './card-creation';
|
||||
import type { AnkiConnectConfig } from '../types';
|
||||
import type { AnkiConnectConfig } from '../types/anki';
|
||||
|
||||
test('CardCreationService counts locally created sentence cards', async () => {
|
||||
const minedCards: Array<{ count: number; noteIds?: number[] }> = [];
|
||||
|
||||
@@ -3,10 +3,11 @@ import {
|
||||
getConfiguredWordFieldName,
|
||||
getPreferredWordValueFromExtractedFields,
|
||||
} from '../anki-field-config';
|
||||
import { AiConfig, AnkiConnectConfig } from '../types';
|
||||
import { AnkiConnectConfig } from '../types/anki';
|
||||
import { createLogger } from '../logger';
|
||||
import { SubtitleTimingTracker } from '../subtitle-timing-tracker';
|
||||
import { MpvClient } from '../types';
|
||||
import { AiConfig } from '../types/integrations';
|
||||
import { MpvClient } from '../types/runtime';
|
||||
import { resolveSentenceBackText } from './ai';
|
||||
import { resolveMediaGenerationInputPath } from './media-source';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AnkiConnectConfig } from '../types';
|
||||
import { AnkiConnectConfig } from '../types/anki';
|
||||
import { getConfiguredWordFieldName } from '../anki-field-config';
|
||||
|
||||
interface FieldGroupingMergeMedia {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { FieldGroupingWorkflow } from './field-grouping-workflow';
|
||||
import type { KikuDuplicateCardInfo, KikuFieldGroupingChoice } from '../types';
|
||||
import type { KikuDuplicateCardInfo, KikuFieldGroupingChoice } from '../types/anki';
|
||||
|
||||
type NoteInfo = {
|
||||
noteId: number;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { KikuDuplicateCardInfo, KikuFieldGroupingChoice } from '../types';
|
||||
import { KikuDuplicateCardInfo, KikuFieldGroupingChoice } from '../types/anki';
|
||||
import { getPreferredWordValueFromExtractedFields } from '../anki-field-config';
|
||||
|
||||
export interface FieldGroupingWorkflowNoteInfo {
|
||||
@@ -181,7 +181,8 @@ export class FieldGroupingWorkflow {
|
||||
return {
|
||||
noteId: noteInfo.noteId,
|
||||
expression:
|
||||
getPreferredWordValueFromExtractedFields(fields, this.deps.getConfig()) || fallbackExpression,
|
||||
getPreferredWordValueFromExtractedFields(fields, this.deps.getConfig()) ||
|
||||
fallbackExpression,
|
||||
sentencePreview: this.deps.truncateSentence(
|
||||
fields[(sentenceCardConfig.sentenceField || 'sentence').toLowerCase()] ||
|
||||
(isOriginal ? '' : this.deps.getCurrentSubtitleText() || ''),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { KikuMergePreviewResponse } from '../types';
|
||||
import { KikuMergePreviewResponse } from '../types/anki';
|
||||
import { createLogger } from '../logger';
|
||||
import { getPreferredWordValueFromExtractedFields } from '../anki-field-config';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
import type { AnkiConnectConfig } from '../types';
|
||||
import type { AnkiConnectConfig } from '../types/anki';
|
||||
import { KnownWordCacheManager } from './known-word-cache';
|
||||
|
||||
async function waitForCondition(
|
||||
@@ -351,10 +351,7 @@ test('KnownWordCacheManager preserves cache state key captured before refresh wo
|
||||
scope: string;
|
||||
words: string[];
|
||||
};
|
||||
assert.equal(
|
||||
persisted.scope,
|
||||
'{"refreshMinutes":1,"scope":"is:note","fieldsWord":"Word"}',
|
||||
);
|
||||
assert.equal(persisted.scope, '{"refreshMinutes":1,"scope":"is:note","fieldsWord":"Word"}');
|
||||
assert.deepEqual(persisted.words, ['猫']);
|
||||
} finally {
|
||||
fs.rmSync(stateDir, { recursive: true, force: true });
|
||||
|
||||
@@ -3,7 +3,7 @@ import path from 'path';
|
||||
|
||||
import { DEFAULT_ANKI_CONNECT_CONFIG } from '../config';
|
||||
import { getConfiguredWordFieldName } from '../anki-field-config';
|
||||
import { AnkiConnectConfig } from '../types';
|
||||
import { AnkiConnectConfig } from '../types/anki';
|
||||
import { createLogger } from '../logger';
|
||||
|
||||
const log = createLogger('anki').child('integration.known-word-cache');
|
||||
@@ -316,9 +316,9 @@ export class KnownWordCacheManager {
|
||||
const currentDeck = this.deps.getConfig().deck?.trim();
|
||||
const selectedDeckEntry =
|
||||
currentDeck !== undefined && currentDeck.length > 0
|
||||
? trimmedDeckEntries.find(([deckName]) => deckName === currentDeck) ?? null
|
||||
? (trimmedDeckEntries.find(([deckName]) => deckName === currentDeck) ?? null)
|
||||
: trimmedDeckEntries.length === 1
|
||||
? trimmedDeckEntries[0] ?? null
|
||||
? (trimmedDeckEntries[0] ?? null)
|
||||
: null;
|
||||
|
||||
if (!selectedDeckEntry) {
|
||||
@@ -329,7 +329,10 @@ export class KnownWordCacheManager {
|
||||
if (Array.isArray(deckFields)) {
|
||||
const normalizedFields = [
|
||||
...new Set(
|
||||
deckFields.map(String).map((field) => field.trim()).filter((field) => field.length > 0),
|
||||
deckFields
|
||||
.map(String)
|
||||
.map((field) => field.trim())
|
||||
.filter((field) => field.length > 0),
|
||||
),
|
||||
];
|
||||
if (normalizedFields.length > 0) {
|
||||
@@ -353,7 +356,14 @@ export class KnownWordCacheManager {
|
||||
continue;
|
||||
}
|
||||
const normalizedFields = Array.isArray(fields)
|
||||
? [...new Set(fields.map(String).map((field) => field.trim()).filter(Boolean))]
|
||||
? [
|
||||
...new Set(
|
||||
fields
|
||||
.map(String)
|
||||
.map((field) => field.trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
]
|
||||
: [];
|
||||
scopes.push({
|
||||
query: `deck:"${escapeAnkiSearchValue(trimmedDeckName)}"`,
|
||||
@@ -402,7 +412,10 @@ export class KnownWordCacheManager {
|
||||
private async fetchKnownWordNoteFieldsById(): Promise<Map<number, string[]>> {
|
||||
const scopes = this.getKnownWordQueryScopes();
|
||||
const noteFieldsById = new Map<number, string[]>();
|
||||
log.debug('Refreshing known-word cache', `queries=${scopes.map((scope) => scope.query).join(' | ')}`);
|
||||
log.debug(
|
||||
'Refreshing known-word cache',
|
||||
`queries=${scopes.map((scope) => scope.query).join(' | ')}`,
|
||||
);
|
||||
|
||||
for (const scope of scopes) {
|
||||
const noteIds = (await this.deps.client.findNotes(scope.query, {
|
||||
@@ -414,10 +427,7 @@ export class KnownWordCacheManager {
|
||||
continue;
|
||||
}
|
||||
const existingFields = noteFieldsById.get(noteId) ?? [];
|
||||
noteFieldsById.set(
|
||||
noteId,
|
||||
[...new Set([...existingFields, ...scope.fields])],
|
||||
);
|
||||
noteFieldsById.set(noteId, [...new Set([...existingFields, ...scope.fields])]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { isRemoteMediaPath } from '../jimaku/utils';
|
||||
import type { MpvClient } from '../types';
|
||||
import type { MpvClient } from '../types/runtime';
|
||||
|
||||
export type MediaGenerationKind = 'audio' | 'video';
|
||||
|
||||
@@ -50,7 +50,7 @@ function resolvePreferredUrlFromMpvEdlSource(
|
||||
|
||||
// mpv EDL sources usually list audio streams first and video streams last, so
|
||||
// when classifyMediaUrl cannot identify a typed URL we fall back to stream order.
|
||||
return kind === 'audio' ? urls[0] ?? null : urls[urls.length - 1] ?? null;
|
||||
return kind === 'audio' ? (urls[0] ?? null) : (urls[urls.length - 1] ?? null);
|
||||
}
|
||||
|
||||
export async function resolveMediaGenerationInputPath(
|
||||
|
||||
@@ -2,7 +2,7 @@ import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { DEFAULT_ANKI_CONNECT_CONFIG } from '../config';
|
||||
import type { AnkiConnectConfig } from '../types';
|
||||
import type { AnkiConnectConfig } from '../types/anki';
|
||||
import { AnkiIntegrationRuntime } from './runtime';
|
||||
|
||||
function createRuntime(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DEFAULT_ANKI_CONNECT_CONFIG } from '../config';
|
||||
import type { AnkiConnectConfig } from '../types';
|
||||
import type { AnkiConnectConfig } from '../types/anki';
|
||||
import {
|
||||
getKnownWordCacheLifecycleConfig,
|
||||
getKnownWordCacheRefreshIntervalMinutes,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NotificationOptions } from '../types';
|
||||
import { NotificationOptions } from '../types/anki';
|
||||
|
||||
export interface UiFeedbackState {
|
||||
progressDepth: number;
|
||||
|
||||
Reference in New Issue
Block a user