UNPKG

@voicefeedback/sdk

Version:

Modern voice feedback SDK with beautiful UI components and AI-powered analysis

250 lines (246 loc) 9.69 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); class VoiceFeedback { constructor(config) { this.mediaRecorder = null; this.stream = null; this.chunks = []; this.isRecording = false; this.startTime = 0; this.config = { language: 'en', maxDuration: 300, // 5 minutes default apiUrl: 'https://quicksass.cool.newstack.be/api', // Fixed: removed /v1 suffix debug: false, ...config }; if (!this.config.apiKey) { throw new Error('VoiceFeedback: API key is required'); } this.log('VoiceFeedback initialized with config:', this.config); } log(...args) { if (this.config.debug) { console.log('[VoiceFeedback]', ...args); } } async startRecording() { if (this.isRecording) { this.log('Recording already in progress'); return; } try { this.log('Requesting microphone access...'); // Request microphone access this.stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, sampleRate: 44100 } }); this.log('Microphone access granted'); // Create MediaRecorder with fallback mime types const mimeType = this.getSupportedMimeType(); this.log('Using mime type:', mimeType); this.mediaRecorder = new MediaRecorder(this.stream, { mimeType }); this.chunks = []; // Set up event listeners this.mediaRecorder.ondataavailable = (event) => { if (event.data.size > 0) { this.chunks.push(event.data); this.log('Audio chunk received:', event.data.size, 'bytes'); } }; this.mediaRecorder.onstop = () => { this.log('Recording stopped, processing...'); this.processRecording(); }; this.mediaRecorder.onerror = (event) => { this.log('MediaRecorder error:', event); const error = new Error('Recording failed'); this.config.onError?.(error); }; // Start recording this.mediaRecorder.start(); this.isRecording = true; this.startTime = Date.now(); this.log('Recording started'); // Auto-stop after max duration if (this.config.maxDuration) { setTimeout(() => { if (this.isRecording) { this.log('Auto-stopping recording after max duration'); this.stopRecording(); } }, this.config.maxDuration * 1000); } this.config.onStart?.(); } catch (error) { this.log('Failed to start recording:', error); const err = new Error(`Failed to start recording: ${error instanceof Error ? error.message : String(error)}`); this.config.onError?.(err); throw err; } } stopRecording() { if (!this.isRecording || !this.mediaRecorder) { this.log('No recording in progress'); return; } this.log('Stopping recording...'); this.mediaRecorder.stop(); this.isRecording = false; // Stop all tracks if (this.stream) { this.stream.getTracks().forEach(track => { track.stop(); this.log('Stopped audio track'); }); } this.config.onStop?.(); } getSupportedMimeType() { const types = [ 'audio/webm;codecs=opus', 'audio/webm', 'audio/mp4', 'audio/wav' ]; for (const type of types) { if (typeof MediaRecorder !== 'undefined' && MediaRecorder.isTypeSupported && MediaRecorder.isTypeSupported(type)) { return type; } } return 'audio/webm'; // fallback } async processRecording() { if (this.chunks.length === 0) { const error = new Error('No audio data recorded'); this.config.onError?.(error); return; } try { // Create blob from recorded chunks const audioBlob = new Blob(this.chunks, { type: this.getSupportedMimeType() }); const duration = (Date.now() - this.startTime) / 1000; this.log('Processing audio blob:', audioBlob.size, 'bytes, duration:', duration, 'seconds'); // Prepare form data to match backend expectations const formData = new FormData(); formData.append('audio', audioBlob, 'recording.webm'); formData.append('duration', duration.toString()); // Send to VoiceFeedback API - use correct endpoint const apiUrl = `${this.config.apiUrl}/process-audio`; this.log('Sending request to:', apiUrl); const response = await fetch(apiUrl, { method: 'POST', headers: { 'Authorization': `Bearer ${this.config.apiKey}`, }, body: formData }); if (!response.ok) { let errorMessage = `API request failed: ${response.status} ${response.statusText}`; try { const errorData = await response.json(); if (errorData.error && errorData.error.message) { errorMessage = errorData.error.message; } } catch (e) { // Fallback to status text if JSON parsing fails } this.log('API error response:', response.status, errorMessage); throw new Error(errorMessage); } const responseData = await response.json(); this.log('Raw API response:', responseData); // Transform backend response to match SDK interface if (responseData.success && responseData.result) { const backendResult = responseData.result; const analysis = backendResult.analysis || {}; const sentiment = analysis.sentiment || {}; const result = { id: `rec_${Date.now()}`, // Generate client-side ID transcript: backendResult.transcription || '', sentiment: sentiment.label || 'neutral', sentimentScore: sentiment.score || 0, topics: analysis.keywords || [], // Use keywords as topics for now emotions: [analysis.tone || 'neutral'], duration: duration, language: this.config.language || 'en', processingTime: backendResult.metadata?.processingTime || 0 }; this.log('Transformed result:', result); this.config.onComplete?.(result); } else { throw new Error('Invalid response format from server'); } } catch (error) { this.log('Failed to process recording:', error); const err = new Error(`Failed to process recording: ${error instanceof Error ? error.message : String(error)}`); this.config.onError?.(err); } } // Static method for quick integration static async quickStart(apiKey, options) { const instance = new VoiceFeedback({ apiKey, debug: true, // Enable debug by default for quick start ...options }); return instance; } // Get recording status getStatus() { return { isRecording: this.isRecording, duration: this.isRecording ? (Date.now() - this.startTime) / 1000 : 0 }; } // Check browser compatibility static isSupported() { return !!(typeof navigator !== 'undefined' && navigator.mediaDevices && typeof navigator.mediaDevices.getUserMedia === 'function' && typeof window !== 'undefined' && typeof window.MediaRecorder !== 'undefined'); } // Test API key validity async testApiKey() { try { const response = await fetch(`${this.config.apiUrl}/test-api-key`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.config.apiKey}`, 'Content-Type': 'application/json' } }); const responseData = await response.json(); if (response.ok && responseData.valid) { return { valid: true, message: responseData.message || 'API key is valid and working correctly!' }; } else { return { valid: false, message: responseData.error || `API key test failed: ${response.status}` }; } } catch (error) { return { valid: false, message: `API key test error: ${error instanceof Error ? error.message : String(error)}` }; } } } exports.VoiceFeedback = VoiceFeedback; exports.default = VoiceFeedback; //# sourceMappingURL=index.js.map