Skip to content

Commit 5d3d205

Browse files
author
Adrian Tompkins
committed
Poll AI sidecars without refreshing catalog
1 parent e7f2da1 commit 5d3d205

3 files changed

Lines changed: 47 additions & 19 deletions

File tree

src/lib/catalog/index.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import { unifiedCloudManager } from '$lib/util/sync/unified-cloud-manager';
77
import { generatePlaceholders } from '$lib/catalog/placeholders';
88
import { routeParams } from '$lib/util/hash-router';
99
import { getLegacyImageOnlyVolumeUuid } from '$lib/util/download-volume-repair';
10+
import type { CloudFileMetadata } from '$lib/util/sync/provider-interface';
1011

1112
const AI_REFRESH_INTERVAL_MS = 15_000;
1213
const AI_DOWNLOAD_ATTEMPTS = 3;
14+
const discoveredAiFiles = new Map<string, CloudFileMetadata>();
1315

1416
function delay(milliseconds: number): Promise<void> {
1517
return new Promise((resolve) => setTimeout(resolve, milliseconds));
@@ -47,18 +49,28 @@ async function downloadAiSidecar(
4749
throw lastError;
4850
}
4951

50-
async function loadCurrentVolumeData(volume: VolumeMetadata): Promise<VolumeData | undefined> {
52+
async function loadCurrentVolumeData(
53+
volume: VolumeMetadata,
54+
pollAi = false
55+
): Promise<VolumeData | undefined> {
5156
const provider = unifiedCloudManager.getActiveProvider();
5257
if (provider) {
5358
const cloudPath = volume.cloudPath || `${volume.series_title}/${volume.volume_title}.cbz`;
5459
const aiPath = cloudPath.replace(/\.(cbz|zip|cbr|rar|7z)$/i, '.mokuro-ai.json').toLowerCase();
55-
const remoteAi = unifiedCloudManager
56-
.getAllCloudVolumes()
57-
.find((file) => file.path.toLowerCase() === aiPath);
60+
const discoveryKey = `${provider.type}:${aiPath}`;
61+
let remoteAi =
62+
unifiedCloudManager.getAllCloudVolumes().find((file) => file.path.toLowerCase() === aiPath) ||
63+
discoveredAiFiles.get(discoveryKey);
64+
if (!remoteAi && pollAi) {
65+
remoteAi = (await provider.listCloudVolumes()).find(
66+
(file) => file.path.toLowerCase() === aiPath
67+
);
68+
if (remoteAi) discoveredAiFiles.set(discoveryKey, remoteAi);
69+
}
5870
if (remoteAi) {
5971
const existingAi = await db.volume_ai.get(volume.volume_uuid);
6072
const remoteModified = Date.parse(remoteAi.modifiedTime || '') || 0;
61-
if (!existingAi || existingAi.updated_at * 1000 < remoteModified) {
73+
if (pollAi || !existingAi || existingAi.updated_at * 1000 < remoteModified) {
6274
try {
6375
await downloadAiSidecar(provider, remoteAi, volume, existingAi);
6476
} catch (error) {
@@ -206,10 +218,9 @@ export const currentVolumeData: Readable<VolumeData | undefined> = derived(
206218
}
207219

208220
if ($currentVolume) {
209-
const refresh = async (refreshCloudFiles = false) => {
221+
const refresh = async (pollAi = false) => {
210222
try {
211-
if (refreshCloudFiles) await unifiedCloudManager.fetchAllCloudVolumes();
212-
const volumeData = await loadCurrentVolumeData($currentVolume);
223+
const volumeData = await loadCurrentVolumeData($currentVolume, pollAi);
213224
if (!cancelled && volumeData) {
214225
set(volumeData);
215226
if (

src/lib/components/Reader/TextBoxes.svelte

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -708,18 +708,20 @@
708708
openAiWord = openAiWord === wordIndex ? null : wordIndex;
709709
}}>{word.surface}</button
710710
>
711-
{#if openAiWord === wordIndex}
712-
<div class="aiWordDetail">
713-
<strong>{word.surface}</strong>{word.reading && word.reading !== word.surface
714-
? ` (${word.reading})`
715-
: ''}<br />
716-
{#if word.dictionary_form && word.dictionary_form !== word.surface}{word.dictionary_form}{word.dictionary_reading
717-
? ` (${word.dictionary_reading})`
718-
: ''}<br />{/if}
719-
{word.meaning}{word.grammar ? ` · ${word.grammar}` : ''}
720-
</div>
721-
{/if}
722711
{/each}
712+
{#if openAiWord !== null && ai.words[openAiWord]}
713+
{@const selectedWord = ai.words[openAiWord]}
714+
<div class="aiWordDetail">
715+
<strong>{selectedWord.surface}</strong>{selectedWord.reading &&
716+
selectedWord.reading !== selectedWord.surface
717+
? ` (${selectedWord.reading})`
718+
: ''}<br />
719+
{#if selectedWord.dictionary_form && selectedWord.dictionary_form !== selectedWord.surface}{selectedWord.dictionary_form}{selectedWord.dictionary_reading
720+
? ` (${selectedWord.dictionary_reading})`
721+
: ''}<br />{/if}
722+
{selectedWord.meaning}{selectedWord.grammar ? ` · ${selectedWord.grammar}` : ''}
723+
</div>
724+
{/if}
723725
</div>
724726
{/if}
725727
</div>

src/lib/components/Reader/__tests__/TextBoxes.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,14 @@ describe('TextBoxes auto mode with lines_coords', () => {
102102
dictionary_reading: 'かのう',
103103
meaning: 'possible',
104104
grammar: 'na-adjective'
105+
},
106+
{
107+
surface: '結界',
108+
reading: 'けっかい',
109+
dictionary_form: '結界',
110+
dictionary_reading: 'けっかい',
111+
meaning: 'barrier',
112+
grammar: 'noun'
105113
}
106114
]
107115
}
@@ -118,6 +126,13 @@ describe('TextBoxes auto mode with lines_coords', () => {
118126
expect(queryByText('Copy EN')).toBeNull();
119127
await fireEvent.click(getByText('可能'));
120128
expect(container.querySelector('.aiWordDetail')?.textContent).toContain('possible');
129+
const aiWords = container.querySelector('.aiWords');
130+
expect(aiWords?.lastElementChild?.classList.contains('aiWordDetail')).toBe(true);
131+
expect(
132+
Array.from(aiWords?.querySelectorAll('button') || []).map((button) => button.textContent)
133+
).toEqual(['可能', '結界']);
134+
await fireEvent.click(getByText('結界'));
135+
expect(aiWords?.lastElementChild?.textContent).toContain('barrier');
121136
await fireEvent.pointerDown(document.body);
122137
expect(queryByText('It makes entering and leaving the barrier possible.')).toBeNull();
123138
});

0 commit comments

Comments
 (0)