From 5217eee53aa1f657a6a6e3f915aaf4d1182096e4 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Mon, 8 Jun 2026 20:44:31 +0200 Subject: [PATCH] fix: downsample audio to 16kHz to prevent ASR 500 error, add permission check handler, and render errors in compact view (v1.0.8) --- README.md | 4 +- main.js | 7 +++ package-lock.json | 4 +- package.json | 2 +- src/App.tsx | 21 +++++++++ src/services/audioService.ts | 87 ++++++++++++++++++++++++++++++++---- 6 files changed, 112 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index bb3857d..596dd6c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # 🚀 EveFlow — Compagnon de Bureau Rétro-Futuriste 3D [![Windows Version](https://img.shields.io/badge/OS-Windows-blue.svg?style=flat-square&logo=windows)](https://github.com/R0m1k3/EveFlow) -[![Version](https://img.shields.io/badge/version-1.0.7-brightgreen.svg?style=flat-square)](https://github.com/R0m1k3/EveFlow/releases/tag/v1.0.7) +[![Version](https://img.shields.io/badge/version-1.0.8-brightgreen.svg?style=flat-square)](https://github.com/R0m1k3/EveFlow/releases/tag/v1.0.8) [![License](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat-square)](LICENSE) **EveFlow** est un compagnon de bureau Windows immersif haut de gamme, combinant une esthétique cyberpunk rétro-futuriste soignée et des technologies d'intelligence artificielle avancées. Il intègre un assistant virtuel en 3D nommé **Eve**, animé en temps réel avec des expressions émotionnelles dynamiques et synchronisé avec des services de synthèse vocale (TTS/STT) locaux et des architectures multi-agents. @@ -74,7 +74,7 @@ Pour générer l'exécutable d'installation Windows (`.exe`) autonome : ```bash npm run dist ``` - *L'exécutable d'installation NSIS (`EveFlow Setup 1.0.7.exe`) et la build décompressée seront générés dans le dossier `./out/`.* + *L'exécutable d'installation NSIS (`EveFlow Setup 1.0.8.exe`) et la build décompressée seront générés dans le dossier `./out/`.* --- diff --git a/main.js b/main.js index 3d3148c..64c677d 100644 --- a/main.js +++ b/main.js @@ -275,6 +275,13 @@ app.whenReady().then(() => { callback(false); }); + session.defaultSession.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => { + if (permission === 'media') { + return true; + } + return false; + }); + startWebhookServer(); createWindow(); diff --git a/package-lock.json b/package-lock.json index bc9d947..2f2ed6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "eveflow", - "version": "1.0.7", + "version": "1.0.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "eveflow", - "version": "1.0.7", + "version": "1.0.8", "dependencies": { "@react-three/drei": "^9.105.0", "@react-three/fiber": "^8.16.0", diff --git a/package.json b/package.json index 9c59c2c..49ea6b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eveflow", - "version": "1.0.7", + "version": "1.0.8", "description": "Retro-futuristic Windows companion with 3D Eve bot, local TTS/STT, and multi-agent connectivity", "main": "main.js", "scripts": { diff --git a/src/App.tsx b/src/App.tsx index aaf291e..1e10539 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2543,6 +2543,27 @@ export const App: React.FC = () => {
+ {errorMessage && ( +
+ +
+ DÉFAILLANCE: + {errorMessage} +
+
+ )} + {/* Barre de saisie compacte */}
{ @@ -699,14 +729,19 @@ export class AudioService { // Fonctions utilitaires pour générer le conteneur WAV PCM 16-bit private exportWAV(channelData: Float32Array[], recordingLength: number, sampleRate: number): Blob { - const buffer = this.mergeBuffers(channelData, recordingLength); - const wavBuffer = new ArrayBuffer(44 + buffer.length * 2); + const originalBuffer = this.mergeBuffers(channelData, recordingLength); + + // Rééchantillonner à 16000 Hz pour la compatibilité avec l'API ASR (Whisper / Qwen3-ASR) + const targetSampleRate = 16000; + const resampledBuffer = this.downsampleBuffer(originalBuffer, sampleRate, targetSampleRate); + + const wavBuffer = new ArrayBuffer(44 + resampledBuffer.length * 2); const view = new DataView(wavBuffer); /* Identifiant RIFF */ this.writeString(view, 0, 'RIFF'); /* Taille du fichier */ - view.setUint32(4, 36 + buffer.length * 2, true); + view.setUint32(4, 36 + resampledBuffer.length * 2, true); /* Type RIFF */ this.writeString(view, 8, 'WAVE'); /* Identifiant format fmt */ @@ -718,9 +753,9 @@ export class AudioService { /* Nombre de canaux (1 = Mono) */ view.setUint16(22, 1, true); /* Fréquence d'échantillonnage */ - view.setUint32(24, sampleRate, true); - /* Débit d'octets (sampleRate * blockAlign) */ - view.setUint32(28, sampleRate * 2, true); + view.setUint32(24, targetSampleRate, true); + /* Débit d'octets (targetSampleRate * blockAlign) */ + view.setUint32(28, targetSampleRate * 2, true); /* Block align (1 canal * 2 octets par échantillon) */ view.setUint16(32, 2, true); /* Bits par échantillon (16 bits) */ @@ -728,13 +763,49 @@ export class AudioService { /* Identifiant de données data */ this.writeString(view, 36, 'data'); /* Longueur des données audio */ - view.setUint32(40, buffer.length * 2, true); + view.setUint32(40, resampledBuffer.length * 2, true); - this.floatTo16BitPCM(view, 44, buffer); + this.floatTo16BitPCM(view, 44, resampledBuffer); return new Blob([view], { type: 'audio/wav' }); } + private downsampleBuffer(buffer: Float32Array, originalSampleRate: number, targetSampleRate: number): Float32Array { + if (originalSampleRate === targetSampleRate) { + return buffer; + } + + const sampleRateRatio = originalSampleRate / targetSampleRate; + const newLength = Math.round(buffer.length / sampleRateRatio); + const result = new Float32Array(newLength); + + let offsetResult = 0; + let offsetBuffer = 0; + + while (offsetResult < result.length) { + const nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio); + + // Moyennage simple (box filter) pour éviter l'aliasing + let accum = 0; + let count = 0; + for (let i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i++) { + accum += buffer[i]; + count++; + } + + if (count > 0) { + result[offsetResult] = accum / count; + } else { + result[offsetResult] = buffer[Math.min(offsetBuffer, buffer.length - 1)]; + } + + offsetResult++; + offsetBuffer = nextOffsetBuffer; + } + + return result; + } + private mergeBuffers(channelData: Float32Array[], recordingLength: number): Float32Array { const result = new Float32Array(recordingLength); let offset = 0;