Latest state before reworking with cluade 4.6.
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* SentenceQueueModule
|
||||
* Manages the preparation pipeline for sentences, including TTS generation
|
||||
*/
|
||||
import { BaseModule } from './base-module.js';
|
||||
|
||||
class SentenceQueueModule extends BaseModule {
|
||||
constructor() {
|
||||
super('sentence-queue', 'Sentence Queue');
|
||||
|
||||
// Dependencies
|
||||
this.dependencies = ['text-buffer', 'tts-factory', 'tts-player'];
|
||||
|
||||
// Queue state
|
||||
this.sentenceQueue = [];
|
||||
this.isProcessing = false;
|
||||
this.onSentenceReadyCallback = null;
|
||||
|
||||
// Bind methods
|
||||
this.bindMethods([
|
||||
'initialize',
|
||||
'addSentence',
|
||||
'processNextSentence',
|
||||
'setOnSentenceReady',
|
||||
'completeSentence'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the module
|
||||
* @returns {Promise<boolean>} - Resolves with success status
|
||||
*/
|
||||
async initialize() {
|
||||
try {
|
||||
// Get dependencies
|
||||
const textBuffer = this.getModule('text-buffer');
|
||||
|
||||
if (!textBuffer) {
|
||||
console.error("SentenceQueue: TextBuffer dependency not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up the text buffer to send sentences to this queue
|
||||
textBuffer.setOnSentenceReady((sentence, callback) => {
|
||||
this.addSentence(sentence, callback);
|
||||
});
|
||||
|
||||
this.reportProgress(100, "Sentence queue ready");
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error initializing Sentence Queue:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set callback for when a sentence is ready for display
|
||||
* @param {Function} callback - Function to call with prepared sentence
|
||||
*/
|
||||
setOnSentenceReady(callback) {
|
||||
if (typeof callback === 'function') {
|
||||
this.onSentenceReadyCallback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a sentence to the queue
|
||||
* @param {string} sentence - Sentence to add
|
||||
* @param {Function} callback - Callback to call when sentence is processed
|
||||
*/
|
||||
addSentence(sentence, callback) {
|
||||
this.sentenceQueue.push({
|
||||
text: sentence,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
// Process the queue if not already processing
|
||||
if (!this.isProcessing) {
|
||||
this.processNextSentence();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the next sentence in the queue
|
||||
*/
|
||||
async processNextSentence() {
|
||||
if (this.sentenceQueue.length === 0 || this.isProcessing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isProcessing = true;
|
||||
const item = this.sentenceQueue[0]; // Don't remove yet
|
||||
|
||||
try {
|
||||
// Get TTS Factory
|
||||
const ttsFactory = this.getModule('tts-factory');
|
||||
|
||||
if (!ttsFactory) {
|
||||
console.error("SentenceQueue: TTSFactory dependency not found");
|
||||
this.completeSentence(item, { success: false, reason: 'no_tts_factory' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a speech metadata object
|
||||
const speechMetadata = await this.prepareSpeechMetadata(item.text);
|
||||
|
||||
// If we have a callback for ready sentences, call it with the metadata
|
||||
if (this.onSentenceReadyCallback) {
|
||||
this.onSentenceReadyCallback(item.text, speechMetadata, () => {
|
||||
// Remove from queue and process next
|
||||
this.completeSentence(item, { success: true });
|
||||
});
|
||||
} else {
|
||||
// No callback, just complete
|
||||
this.completeSentence(item, { success: true });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error processing sentence:", error);
|
||||
this.completeSentence(item, { success: false, reason: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare speech metadata for a sentence
|
||||
* @param {string} text - Text to prepare speech for
|
||||
* @returns {Promise<Object>} - Speech metadata object
|
||||
*/
|
||||
async prepareSpeechMetadata(text) {
|
||||
const ttsFactory = this.getModule('tts-factory');
|
||||
const ttsPlayer = this.getModule('tts-player');
|
||||
|
||||
if (!ttsFactory || !ttsPlayer) {
|
||||
throw new Error("TTS dependencies not found");
|
||||
}
|
||||
|
||||
// Check if TTS is enabled
|
||||
const isTtsEnabled = ttsPlayer.isEnabled();
|
||||
|
||||
// If TTS is disabled, estimate duration based on character count
|
||||
if (!isTtsEnabled) {
|
||||
return this.estimateSpeechDuration(text);
|
||||
}
|
||||
|
||||
try {
|
||||
// Preload the speech to get metadata
|
||||
const result = await ttsFactory.preloadSpeech(text);
|
||||
|
||||
if (!result.success) {
|
||||
console.warn("SentenceQueue: Speech preload failed, using estimated duration");
|
||||
return this.estimateSpeechDuration(text);
|
||||
}
|
||||
|
||||
// Create a speech metadata object
|
||||
return {
|
||||
text: text,
|
||||
duration: result.duration || this.estimateSpeechDuration(text).duration,
|
||||
handler: ttsFactory.getActiveHandler() ? ttsFactory.getActiveHandler().id : null,
|
||||
play: async () => {
|
||||
return ttsFactory.speak(text);
|
||||
},
|
||||
stop: () => {
|
||||
return ttsFactory.stop();
|
||||
},
|
||||
isTtsEnabled: isTtsEnabled
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error preparing speech metadata:", error);
|
||||
return this.estimateSpeechDuration(text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate speech duration based on character count
|
||||
* @param {string} text - Text to estimate duration for
|
||||
* @returns {Object} - Speech metadata object with estimated duration
|
||||
*/
|
||||
estimateSpeechDuration(text) {
|
||||
// Average reading speed is about 14-15 characters per second
|
||||
// We'll use a slightly slower rate for TTS
|
||||
const charactersPerSecond = 12;
|
||||
const ttsPlayer = this.getModule('tts-player');
|
||||
|
||||
// Get the current speed setting if available
|
||||
let speedMultiplier = 1.0;
|
||||
if (ttsPlayer) {
|
||||
const ttsFactory = this.getModule('tts-factory');
|
||||
if (ttsFactory) {
|
||||
// Get the current speed setting (typically 0.5-2.0)
|
||||
const speed = ttsFactory.speed || 1.0;
|
||||
speedMultiplier = speed;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate estimated duration in milliseconds
|
||||
const charCount = text.length;
|
||||
const durationSeconds = charCount / (charactersPerSecond * speedMultiplier);
|
||||
const durationMs = Math.max(durationSeconds * 1000, 500); // Minimum 500ms
|
||||
|
||||
return {
|
||||
text: text,
|
||||
duration: durationMs,
|
||||
handler: null,
|
||||
play: async () => ({ success: false, reason: 'tts_disabled' }),
|
||||
stop: () => true,
|
||||
isTtsEnabled: false,
|
||||
isEstimated: true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete processing of a sentence
|
||||
* @param {Object} item - Queue item
|
||||
* @param {Object} result - Processing result
|
||||
*/
|
||||
completeSentence(item, result) {
|
||||
// Remove from queue
|
||||
this.sentenceQueue.shift();
|
||||
|
||||
// Call the original callback
|
||||
if (item.callback) {
|
||||
item.callback(result);
|
||||
}
|
||||
|
||||
// Reset processing flag
|
||||
this.isProcessing = false;
|
||||
|
||||
// Process next sentence if any
|
||||
if (this.sentenceQueue.length > 0) {
|
||||
this.processNextSentence();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the singleton instance
|
||||
const SentenceQueue = new SentenceQueueModule();
|
||||
|
||||
// Export the module
|
||||
export { SentenceQueue };
|
||||
|
||||
// Register with the module registry
|
||||
if (window.moduleRegistry) {
|
||||
window.moduleRegistry.register(SentenceQueue);
|
||||
}
|
||||
|
||||
// Keep a reference in window for loader system
|
||||
window.SentenceQueue = SentenceQueue;
|
||||
Reference in New Issue
Block a user