Refactored everything into modules.

This commit is contained in:
2025-04-01 23:38:35 +00:00
parent 53f9eb9265
commit 2f7cda4b6d
8 changed files with 107 additions and 105 deletions
+2 -24
View File
@@ -66,8 +66,7 @@
window.rstack = []; window.rstack = [];
</script> </script>
<!-- TTS implementation scripts - order matters! --> <!-- Kokoro TTS library - load as module -->
<!-- 1. Kokoro TTS library - load as module -->
<script type="module"> <script type="module">
try { try {
// Import KokoroTTS class from the module // Import KokoroTTS class from the module
@@ -88,28 +87,7 @@
} }
</script> </script>
<!-- 2. TTS handlers (kokoro-handler needs to wait for KokoroTTS) --> <!-- Main application script - imports all needed modules -->
<script src="js/kokoro-handler.js"></script>
<script src="js/tts-handler.js"></script>
<!-- 3. TTS Factory for automatic selection -->
<script src="js/tts-factory.js"></script>
<!-- New Modules for Socket-based Interaction -->
<script src="js/input-handler.js"></script>
<script src="js/socket-client.js"></script>
<!-- Modular layout and animation components -->
<script type="module" src="js/animation-queue.js"></script>
<script type="module" src="js/text-processor.js"></script>
<script type="module" src="js/paragraph-layout.js"></script>
<script type="module" src="js/layout-renderer.js"></script>
<script type="module" src="js/persistence-manager.js"></script>
<script type="module" src="js/ui-controller.js"></script>
<script type="module" src="js/audio-manager.js"></script>
<script type="module" src="js/tts-player.js"></script>
<!-- Main application script - using ES modules -->
<script type="module" src="js/ai-fiction.js"></script> <script type="module" src="js/ai-fiction.js"></script>
</body> </body>
</html> </html>
+3 -10
View File
@@ -9,12 +9,10 @@ import { LayoutRenderer } from './layout-renderer.js';
import { AudioManager } from './audio-manager.js'; import { AudioManager } from './audio-manager.js';
import { TtsPlayer } from './tts-player.js'; import { TtsPlayer } from './tts-player.js';
import { PersistenceManager } from './persistence-manager.js'; import { PersistenceManager } from './persistence-manager.js';
// import { InkStoryPlayer } from './ink-story-player.js'; // Replaced by SocketClient logic import { InputHandler } from './input-handler.js';
import { SocketClient } from './socket-client.js';
import { UiController } from './ui-controller.js'; import { UiController } from './ui-controller.js';
// Assuming InputHandler and SocketClient are loaded globally via <script> tags in HTML import { ttsFactory } from './tts-factory.js';
// If using modules properly, they would need imports:
// import { InputHandler } from './input-handler.js';
// import { SocketClient } from './socket-client.js';
export class AnimatedFiction { export class AnimatedFiction {
/** /**
@@ -71,11 +69,6 @@ export class AnimatedFiction {
this.initializeTextMeasurement(); this.initializeTextMeasurement();
// 2. Input, Socket, and UI Controller // 2. Input, Socket, and UI Controller
// Ensure InputHandler and SocketClient classes are available globally
if (typeof InputHandler === 'undefined' || typeof SocketClient === 'undefined') {
console.error("InputHandler or SocketClient class not found. Ensure scripts are loaded correctly.");
return; // Stop initialization if core components are missing
}
this.inputHandler = new InputHandler('player_input', 'cursor'); this.inputHandler = new InputHandler('player_input', 'cursor');
this.socketClient = new SocketClient(this.config.serverUrl); // Pass server URL if provided this.socketClient = new SocketClient(this.config.serverUrl); // Pass server URL if provided
+1 -4
View File
@@ -2,7 +2,7 @@
* Input Handler Module * Input Handler Module
* Manages the multi-line text input field with a custom cursor. * Manages the multi-line text input field with a custom cursor.
*/ */
class InputHandler { export class InputHandler {
constructor(inputId = 'player_input', cursorId = 'cursor') { constructor(inputId = 'player_input', cursorId = 'cursor') {
this.playerInput = document.getElementById(inputId); this.playerInput = document.getElementById(inputId);
this.cursor = document.getElementById(cursorId); this.cursor = document.getElementById(cursorId);
@@ -288,6 +288,3 @@ class InputHandler {
*/ */
} }
} }
// Export the class if using modules (optional, depends on build setup)
// export default InputHandler;
+3 -3
View File
@@ -3,7 +3,7 @@
* Uses the kokoro-js library for high-quality TTS * Uses the kokoro-js library for high-quality TTS
*/ */
class KokoroHandler { export class KokoroHandler {
constructor() { constructor() {
this.enabled = false; this.enabled = false;
this.speaking = false; this.speaking = false;
@@ -593,5 +593,5 @@ class KokoroHandler {
} }
} }
// Don't create a global instance here - the factory will do this // Create and export a singleton for the factory to use
// const ttsHandler = new KokoroHandler(); export const kokoroHandler = new KokoroHandler();
+1 -4
View File
@@ -2,7 +2,7 @@
* Socket Client Module * Socket Client Module
* Manages WebSocket communication with the game server. * Manages WebSocket communication with the game server.
*/ */
class SocketClient { export class SocketClient {
constructor(serverUrl) { constructor(serverUrl) {
this.socket = null; this.socket = null;
this.serverUrl = serverUrl || window.location.origin; // Default to current origin this.serverUrl = serverUrl || window.location.origin; // Default to current origin
@@ -174,6 +174,3 @@ class SocketClient {
this.emit('loadGame'); this.emit('loadGame');
} }
} }
// Export the class if using modules
// export default SocketClient;
+54
View File
@@ -0,0 +1,54 @@
const axios = require('axios');
const fs = require('fs');
const crypto = require('crypto');
const player = require('play-sound')(opts = {});
const { ipcMain } = require('electron');
// Directory where audio files will be cached
const cacheDirectory = './speech_cache/';
// Create cache directory if it does not exist
if (!fs.existsSync(cacheDirectory)) {
fs.mkdirSync(cacheDirectory);
}
ipcMain.handle('getSpeech', async (event, text) => {
// Create a hash of the text to use as a unique filename
const filename = crypto.createHash('md5').update(text).digest('hex') + '.mp3';
// Full path of the audio file in the cache directory
const filepath = cacheDirectory + filename;
// Check if audio file already exists in the cache
if (!fs.existsSync(filepath)) {
// If audio file does not exist, make API request
try {
const response = await axios({
method: 'post',
url: 'https://api.elevenlabs.io/v1/text-to-speech/8JNqTOY3RaSYcHTVJZ0G',
headers: {
'Content-Type': 'application/json',
'xi-api-key': 'd191e27c2e5b07573b39fe70f0783f48'
},
data: {
text: text,
model_id: 'eleven_multilingual_v1',
voice_settings: {
stability: 0,
similarity_boost: 0,
style: 0.5,
use_speaker_boost: true
}
},
responseType: 'arraybuffer'
});
// Write the audio data to a file in the cache directory
fs.writeFileSync(filepath, response.data);
} catch (error) {
console.error(`Error making API request: ${error}`);
}
}
return filepath
});
+40 -57
View File
@@ -2,12 +2,12 @@
* TTS Factory for AI Interactive Fiction * TTS Factory for AI Interactive Fiction
* Attempts to use Kokoro TTS first, then falls back to browser TTS if needed * Attempts to use Kokoro TTS first, then falls back to browser TTS if needed
*/ */
import { kokoroHandler } from './kokoro-handler.js';
import { browserTtsHandler } from './tts-handler.js';
class TTSFactory { export class TTSFactory {
constructor() { constructor() {
this.activeTTSHandler = null; this.activeTTSHandler = null;
this.kokoroHandler = null;
this.browserTTSHandler = null;
this.initializationAttempted = false; this.initializationAttempted = false;
this.usingKokoro = false; this.usingKokoro = false;
this.initializationPromise = null; // Promise for the factory initialization this.initializationPromise = null; // Promise for the factory initialization
@@ -35,54 +35,36 @@ class TTSFactory {
let kokoroInitialized = false; let kokoroInitialized = false;
// Try to initialize Kokoro first (preferred option) // Try to initialize Kokoro first (preferred option)
try { try {
// Check if KokoroHandler class is defined (loaded via script tag) console.log('Attempting to initialize Kokoro TTS...');
if (typeof KokoroHandler !== 'undefined') {
console.log('Attempting to initialize Kokoro TTS...'); // --- Increase Timeout for Kokoro Initialization ---
this.kokoroHandler = new KokoroHandler(); // Wait for KokoroHandler's internal initialization promise
// Use Promise.race to add a longer timeout specifically for Kokoro init
// --- Increase Timeout for Kokoro Initialization --- const kokoroTimeoutPromise = new Promise((_, reject) =>
// Wait for KokoroHandler's internal initialization promise setTimeout(() => reject(new Error('Kokoro initialization timed out in factory')), 60000) // 60 seconds timeout
// Use Promise.race to add a longer timeout specifically for Kokoro init );
const kokoroTimeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Kokoro initialization timed out in factory')), 60000) // 60 seconds timeout
);
try { try {
kokoroInitialized = await Promise.race([ kokoroInitialized = await Promise.race([
this.kokoroHandler.initializationPromise, kokoroHandler.initializationPromise,
kokoroTimeoutPromise kokoroTimeoutPromise
]); ]);
} catch (timeoutError) { } catch (timeoutError) {
console.error(timeoutError.message); // Log the timeout error console.error(timeoutError.message); // Log the timeout error
kokoroInitialized = false; kokoroInitialized = false;
} }
// --- End Increase Timeout --- // --- End Increase Timeout ---
if (kokoroInitialized) { if (kokoroInitialized) {
console.log('Kokoro Handler reported successful initialization.'); console.log('Kokoro Handler reported successful initialization.');
} else {
console.warn('Kokoro Handler reported failed or timed out initialization.');
}
} else { } else {
console.warn('KokoroHandler class not found when factory initialized.'); console.warn('Kokoro Handler reported failed or timed out initialization.');
} }
} catch (error) { } catch (error) {
console.error('Error creating or initializing Kokoro Handler:', error); console.error('Error initializing Kokoro Handler:', error);
kokoroInitialized = false; // Ensure it's marked as failed kokoroInitialized = false; // Ensure it's marked as failed
} }
// Initialize browser TTS as fallback (can happen in parallel)
try {
if (typeof TTSHandler !== 'undefined') {
console.log('Initializing browser TTS as fallback...');
this.browserTTSHandler = new TTSHandler();
} else {
console.warn('TTSHandler class not found when factory initialized.');
}
} catch (error) {
console.error('Error initializing browser TTS:', error);
}
// Decide which handler to use based on Kokoro's success // Decide which handler to use based on Kokoro's success
this.selectActiveHandler(kokoroInitialized); this.selectActiveHandler(kokoroInitialized);
resolve(); // Resolve the factory's promise resolve(); // Resolve the factory's promise
@@ -91,23 +73,21 @@ class TTSFactory {
return this.initializationPromise; return this.initializationPromise;
} }
// Removed waitForKokoroInitialization as KokoroHandler now manages its own promise
/** /**
* Select which TTS handler to use * Select which TTS handler to use
* @param {boolean} kokoroInitialized - Whether Kokoro initialization succeeded * @param {boolean} kokoroInitialized - Whether Kokoro initialization succeeded
*/ */
selectActiveHandler(kokoroInitialized) { selectActiveHandler(kokoroInitialized) {
// First choice: Kokoro if it's available and initialized successfully // First choice: Kokoro if it's available and initialized successfully
if (kokoroInitialized && this.kokoroHandler && this.kokoroHandler.kokoroReady) { if (kokoroInitialized && kokoroHandler.kokoroReady) {
console.log('Using Kokoro TTS as primary TTS system'); console.log('Using Kokoro TTS as primary TTS system');
this.activeTTSHandler = this.kokoroHandler; this.activeTTSHandler = kokoroHandler;
this.usingKokoro = true; this.usingKokoro = true;
} }
// Fallback to browser TTS if available // Fallback to browser TTS if available
else if (this.browserTTSHandler) { else if (browserTtsHandler) {
console.log('Falling back to browser TTS.'); console.log('Falling back to browser TTS.');
this.activeTTSHandler = this.browserTTSHandler; this.activeTTSHandler = browserTtsHandler;
this.usingKokoro = false; this.usingKokoro = false;
} }
// No TTS available // No TTS available
@@ -117,7 +97,7 @@ class TTSFactory {
this.usingKokoro = false; this.usingKokoro = false;
} }
// Expose the active handler as the global ttsHandler // Expose the active handler as the global ttsHandler for compatibility
window.ttsHandler = this.activeTTSHandler; window.ttsHandler = this.activeTTSHandler;
// Log the active TTS system // Log the active TTS system
@@ -160,8 +140,8 @@ class TTSFactory {
* @param {string} type - Either 'kokoro' or 'browser' * @param {string} type - Either 'kokoro' or 'browser'
*/ */
switchTTS(type) { switchTTS(type) {
if (type === 'kokoro' && this.kokoroHandler && this.kokoroHandler.kokoroReady) { if (type === 'kokoro' && kokoroHandler && kokoroHandler.kokoroReady) {
this.activeTTSHandler = this.kokoroHandler; this.activeTTSHandler = kokoroHandler;
this.usingKokoro = true; this.usingKokoro = true;
window.ttsHandler = this.activeTTSHandler; window.ttsHandler = this.activeTTSHandler;
console.log('Switched to Kokoro TTS'); console.log('Switched to Kokoro TTS');
@@ -169,8 +149,8 @@ class TTSFactory {
const ttsReadyEvent = new CustomEvent('tts-ready', { detail: { available: true, type: 'kokoro', handler: this.activeTTSHandler } }); const ttsReadyEvent = new CustomEvent('tts-ready', { detail: { available: true, type: 'kokoro', handler: this.activeTTSHandler } });
window.dispatchEvent(ttsReadyEvent); window.dispatchEvent(ttsReadyEvent);
return true; return true;
} else if (type === 'browser' && this.browserTTSHandler) { } else if (type === 'browser' && browserTtsHandler) {
this.activeTTSHandler = this.browserTTSHandler; this.activeTTSHandler = browserTtsHandler;
this.usingKokoro = false; this.usingKokoro = false;
window.ttsHandler = this.activeTTSHandler; window.ttsHandler = this.activeTTSHandler;
console.log('Switched to browser TTS'); console.log('Switched to browser TTS');
@@ -185,5 +165,8 @@ class TTSFactory {
} }
} }
// Create the global TTS factory instance // Create and export singleton instance
window.ttsFactory = new TTSFactory(); export const ttsFactory = new TTSFactory();
// Keep a reference in window for compatibility with existing code
window.ttsFactory = ttsFactory;
+3 -3
View File
@@ -3,7 +3,7 @@
* Enhanced version with improved voice selection, caching, and playback controls * Enhanced version with improved voice selection, caching, and playback controls
*/ */
class TTSHandler { export class TTSHandler {
constructor() { constructor() {
this.enabled = false; this.enabled = false;
this.speaking = false; this.speaking = false;
@@ -410,5 +410,5 @@ class TTSHandler {
} }
} }
// Create a global instance // Create and export a singleton instance
const ttsHandler = new TTSHandler(); export const browserTtsHandler = new TTSHandler();