Checkpoint Eibenreith ink architecture

This commit is contained in:
2026-05-24 09:09:41 +02:00
parent beac5a2be3
commit d42540f29d
35 changed files with 12015 additions and 54 deletions
+25 -3
View File
@@ -30,6 +30,7 @@ class SentenceQueueModule extends BaseModule {
this.assetPreloadTimeoutMs = ASSET_PRELOAD_TIMEOUT_MS;
this.generationRequests = new Map();
this.assetPreloadRequests = new Map();
this.queueGeneration = 0;
// Bind methods
this.bindMethods([
@@ -64,6 +65,7 @@ class SentenceQueueModule extends BaseModule {
'getDropCapText',
'extractDropCapText',
'calculateAnimationTiming',
'isCurrentQueueItem',
'clear'
]);
}
@@ -170,12 +172,14 @@ class SentenceQueueModule extends BaseModule {
this.isProcessing = true;
const item = this.sentenceQueue[0];
const queueGeneration = this.queueGeneration;
try {
if (this.pauseBeforeNextReason) {
const reason = this.pauseBeforeNextReason;
this.pauseBeforeNextReason = null;
await this.waitForManualContinue(reason);
if (!this.isCurrentQueueItem(item, queueGeneration)) return;
}
document.dispatchEvent(new CustomEvent('story:process-state', {
@@ -189,10 +193,12 @@ class SentenceQueueModule extends BaseModule {
}));
const sentence = await this.getPreparedSentence(item);
if (!this.isCurrentQueueItem(item, queueGeneration)) return;
// Prefetch far enough ahead that media pauses do not block TTS
// generation for the next spoken paragraph.
this.prefetchAhead();
this.prefetchAhead(4, queueGeneration);
if (!this.isCurrentQueueItem(item, queueGeneration)) return;
// Notify display handler with complete sentence
if (this.onSentenceReadyCallback) {
@@ -201,22 +207,28 @@ class SentenceQueueModule extends BaseModule {
sentence.playbackStartedAt = performance.now();
this.onSentenceReadyCallback(sentence, resolve);
});
if (!this.isCurrentQueueItem(item, queueGeneration)) return;
}
const mediaPauseSeconds = this.getMediaPauseSeconds(sentence);
if (mediaPauseSeconds > 0) {
await this.waitForSkippableMediaPause(mediaPauseSeconds, sentence.kind, sentence.id);
if (!this.isCurrentQueueItem(item, queueGeneration)) return;
}
if (this.shouldPauseAfterSentence(sentence)) {
await this.waitForManualContinue(sentence.id);
if (!this.isCurrentQueueItem(item, queueGeneration)) return;
}
// Remove from queue and continue
this.sentenceQueue.shift();
if (this.sentenceQueue[0] === item) {
this.sentenceQueue.shift();
}
if (item.callback) item.callback({ success: true });
} catch (error) {
if (!this.isCurrentQueueItem(item, queueGeneration)) return;
console.error("SentenceQueue: Error processing sentence:", error);
const failedItem = this.sentenceQueue.shift();
console.warn('SentenceQueue: Dropped failed queue item so playback can continue', {
@@ -843,7 +855,11 @@ class SentenceQueueModule extends BaseModule {
return this.prepareSentence(item);
}
prefetchAhead(maxLookahead = 4) {
isCurrentQueueItem(item, queueGeneration = this.queueGeneration) {
return queueGeneration === this.queueGeneration && this.sentenceQueue[0] === item;
}
prefetchAhead(maxLookahead = 4, queueGeneration = this.queueGeneration) {
if (this.sentenceQueue.length <= 1) {
document.dispatchEvent(new CustomEvent('story:process-state', {
detail: { state: 'playing-ready', reason: 'no-prefetch-needed', sentenceId: this.sentenceQueue[0]?.id }
@@ -871,11 +887,13 @@ class SentenceQueueModule extends BaseModule {
console.log(`Process state: ${state}`, { reason: 'prefetch-start', sentenceId: nextItem.id, queueIndex: index });
const promise = (async () => {
if (queueGeneration !== this.queueGeneration) return null;
await this.preloadAssetsForItem(nextItem, {
sentenceId: nextItem.id,
blocking: false,
prefetch: true
});
if (queueGeneration !== this.queueGeneration) return null;
if (!this.isSpeechItem(nextItem)) {
return null;
@@ -892,6 +910,7 @@ class SentenceQueueModule extends BaseModule {
});
})()
.then(() => {
if (queueGeneration !== this.queueGeneration) return false;
console.log('SentenceQueue: Prefetched queued speech/media', { sentenceId: nextItem.id, queueIndex: index });
document.dispatchEvent(new CustomEvent('story:process-state', {
detail: { state: 'playing-ready', reason: 'prefetch-complete', sentenceId: nextItem.id, queueIndex: index }
@@ -1316,10 +1335,13 @@ class SentenceQueueModule extends BaseModule {
}
clear() {
this.queueGeneration += 1;
this.sentenceQueue = [];
this.isProcessing = false;
this.cancelGenerationRequests('sentence-queue-cleared');
this.cancelAssetPreloads('sentence-queue-cleared');
this.prefetchingSpeech.clear();
this.pauseBeforeNextReason = null;
document.dispatchEvent(new CustomEvent('tts:queue-empty', {
detail: { reason: 'sentence-queue-cleared' }
}));