// src/test.ts import 'dotenv/config'; import { Space } from './core/Space'; import { Scraper } from '../scraper'; import { SpaceConfig } from './types'; import { RecordToDiskPlugin } from './plugins/RecordToDiskPlugin'; import { SttTtsPlugin } from './plugins/SttTtsPlugin'; import { IdleMonitorPlugin } from './plugins/IdleMonitorPlugin'; /** * Main test entry point */ async function main() { console.log('[Test] Starting...'); // 1) Twitter login with your scraper const scraper = new Scraper(); await scraper.login( process.env.TWITTER_USERNAME!, process.env.TWITTER_PASSWORD!, ); // 2) Create the Space instance const space = new Space(scraper); // Add a plugin to record audio const recordPlugin = new RecordToDiskPlugin(); space.use(recordPlugin); // Create our TTS/STT plugin instance const sttTtsPlugin = new SttTtsPlugin(); space.use(sttTtsPlugin, { openAiApiKey: process.env.OPENAI_API_KEY, elevenLabsApiKey: process.env.ELEVENLABS_API_KEY, voiceId: 'D38z5RcWu1voky8WS1ja', // example // You can also initialize systemPrompt, chatContext, etc. here if you wish // systemPrompt: "You are a calm and friendly AI assistant." }); // Create an IdleMonitorPlugin to stop after 60s of silence const idlePlugin = new IdleMonitorPlugin(60_000, 10_000); space.use(idlePlugin); // If idle occurs, say goodbye and end the Space space.on('idleTimeout', async (info) => { console.log(`[Test] idleTimeout => no audio for ${info.idleMs}ms.`); await sttTtsPlugin.speakText('Ending Space due to inactivity. Goodbye!'); await new Promise((r) => setTimeout(r, 10_000)); await space.stop(); console.log('[Test] Space stopped due to silence.'); process.exit(0); }); // 3) Initialize the Space const config: SpaceConfig = { mode: 'INTERACTIVE', title: 'AI Chat - Dynamic GPT Config', description: 'Space that demonstrates dynamic GPT personalities.', languages: ['en'], }; const broadcastInfo = await space.initialize(config); const spaceUrl = broadcastInfo.share_url.replace('broadcasts', 'spaces'); console.log('[Test] Space created =>', spaceUrl); // (Optional) Tweet out the Space link await scraper.sendTweet(`${config.title} ${spaceUrl}`); console.log('[Test] Tweet sent'); // --------------------------------------- // Example of dynamic GPT usage: // You can change the system prompt at runtime setTimeout(() => { console.log('[Test] Changing system prompt to a new persona...'); sttTtsPlugin.setSystemPrompt( 'You are a very sarcastic AI who uses short answers.', ); }, 45_000); // Another example: after some time, switch to GPT-4 setTimeout(() => { console.log('[Test] Switching GPT model to "gpt-4" (if available)...'); sttTtsPlugin.setGptModel('gpt-4'); }, 60_000); // Also, demonstrate how to manually call askChatGPT and speak the result setTimeout(async () => { console.log('[Test] Asking GPT for an introduction...'); try { const response = await sttTtsPlugin['askChatGPT']('Introduce yourself'); console.log('[Test] ChatGPT introduction =>', response); // Then speak it await sttTtsPlugin.speakText(response); } catch (err) { console.error('[Test] askChatGPT error =>', err); } }, 75_000); // Example: periodically speak a greeting every 60s setInterval(() => { sttTtsPlugin .speakText('Hello everyone, this is an automated greeting.') .catch((err) => console.error('[Test] speakText() =>', err)); }, 20_000); // 4) Some event listeners space.on('speakerRequest', async (req) => { console.log('[Test] Speaker request =>', req); await space.approveSpeaker(req.userId, req.sessionUUID); // Remove the speaker after 10 seconds (testing only) setTimeout(() => { console.log( `[Test] Removing speaker => userId=${req.userId} (after 10s)`, ); space.removeSpeaker(req.userId).catch((err) => { console.error('[Test] removeSpeaker error =>', err); }); }, 10_000); }); // When a user react, reply with some reactions to test the flow space.on('guestReaction', (evt) => { // Pick a random emoji from the list const emojis = ['💯', '✨', '🙏', '🎮']; const emoji = emojis[Math.floor(Math.random() * emojis.length)]; space.reactWithEmoji(emoji); }); space.on('error', (err) => { console.error('[Test] Space Error =>', err); }); // ================================================== // BEEP GENERATION (500 ms) @16kHz => 8000 samples // ================================================== const beepDurationMs = 500; const sampleRate = 16000; const totalSamples = (sampleRate * beepDurationMs) / 1000; // 8000 const beepFull = new Int16Array(totalSamples); // Sine wave: 440Hz, amplitude ~12000 const freq = 440; const amplitude = 12000; for (let i = 0; i < beepFull.length; i++) { const t = i / sampleRate; beepFull[i] = amplitude * Math.sin(2 * Math.PI * freq * t); } const FRAME_SIZE = 160; /** * Send a beep by slicing beepFull into frames of 160 samples */ async function sendBeep() { console.log('[Test] Starting beep...'); for (let offset = 0; offset < beepFull.length; offset += FRAME_SIZE) { // subarray => simple "view" const portion = beepFull.subarray(offset, offset + FRAME_SIZE); // Make a real copy const frame = new Int16Array(FRAME_SIZE); frame.set(portion); // Now frame.length = 160, and frame.byteLength = 320 space.pushAudio(frame, sampleRate); await new Promise((r) => setTimeout(r, 10)); } console.log('[Test] Finished beep'); } // 5) Send beep every 5s //setInterval(() => { // sendBeep().catch((err) => console.error('[Test] beep error =>', err)); //}, 5000); console.log('[Test] Space is running... press Ctrl+C to exit.'); // Graceful shutdown process.on('SIGINT', async () => { console.log('\n[Test] Caught interrupt signal, stopping...'); await space.stop(); console.log('[Test] Space stopped. Bye!'); process.exit(0); }); } main().catch((err) => { console.error('[Test] Unhandled main error =>', err); process.exit(1); });