Refactored everything into modules.
This commit is contained in:
+2
-24
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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,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();
|
||||||
@@ -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;
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
});
|
||||||
+19
-36
@@ -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,10 +35,7 @@ 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)
|
|
||||||
if (typeof KokoroHandler !== 'undefined') {
|
|
||||||
console.log('Attempting to initialize Kokoro TTS...');
|
console.log('Attempting to initialize Kokoro TTS...');
|
||||||
this.kokoroHandler = new KokoroHandler();
|
|
||||||
|
|
||||||
// --- Increase Timeout for Kokoro Initialization ---
|
// --- Increase Timeout for Kokoro Initialization ---
|
||||||
// Wait for KokoroHandler's internal initialization promise
|
// Wait for KokoroHandler's internal initialization promise
|
||||||
@@ -49,7 +46,7 @@ class TTSFactory {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
kokoroInitialized = await Promise.race([
|
kokoroInitialized = await Promise.race([
|
||||||
this.kokoroHandler.initializationPromise,
|
kokoroHandler.initializationPromise,
|
||||||
kokoroTimeoutPromise
|
kokoroTimeoutPromise
|
||||||
]);
|
]);
|
||||||
} catch (timeoutError) {
|
} catch (timeoutError) {
|
||||||
@@ -63,26 +60,11 @@ class TTSFactory {
|
|||||||
} else {
|
} else {
|
||||||
console.warn('Kokoro Handler reported failed or timed out initialization.');
|
console.warn('Kokoro Handler reported failed or timed out initialization.');
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
console.warn('KokoroHandler class not found when factory initialized.');
|
|
||||||
}
|
|
||||||
} 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,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();
|
||||||
Reference in New Issue
Block a user