Co-authored-by: Quentin Torroba <quentin.torroba@mistral.ai>
Co-authored-by: Clement Sirieix <clem.sirieix@gmail.com>
Co-authored-by: Kim-Adeline Miguel <kimadeline.miguel@mistral.ai>
Co-authored-by: Simon Van de Kerckhove <simon.vandekerckhove@mistral.ai>
Co-authored-by: Vincent Guilloux <vincent.guilloux@mistral.ai>
Co-authored-by: Michel Thomazo <michel.thomazo@mistral.ai>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Mathias Gesbert 2026-03-16 17:51:47 +01:00 committed by GitHub
parent 9421fbc08e
commit 5103019b01
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
104 changed files with 7277 additions and 691 deletions

View file

@ -0,0 +1,65 @@
from __future__ import annotations
from collections.abc import AsyncGenerator, Callable
from vibe.core.audio_recorder import (
AlreadyRecordingError,
AudioRecording,
RecordingMode,
)
FAKE_WAV_DATA = b"RIFF\x00\x00\x00\x00WAVEfmt "
class FakeAudioRecorder:
def __init__(self, *, fake_wav_data: bytes = FAKE_WAV_DATA) -> None:
self._recording = False
self._peak = 0.0
self._mode = RecordingMode.BUFFER
self._fake_wav_data = fake_wav_data
self._stream_chunks: list[bytes] = []
@property
def is_recording(self) -> bool:
return self._recording
@property
def peak(self) -> float:
return self._peak
@property
def mode(self) -> RecordingMode:
return self._mode
def start(
self,
mode: RecordingMode,
*,
sample_rate: int = 48_000,
channels: int = 1,
max_duration: float = 300.0,
on_expire: Callable[[AudioRecording], object] | None = None,
) -> None:
if self._recording:
raise AlreadyRecordingError("Already recording")
self._recording = True
self._mode = mode
def stop(self, *, wait_for_queue_drained: bool = True) -> AudioRecording:
if not self._recording:
return AudioRecording(data=b"", duration=0.0)
self._recording = False
return AudioRecording(data=self._fake_wav_data, duration=1.0)
def cancel(self) -> None:
self._recording = False
async def audio_stream(self) -> AsyncGenerator[bytes, None]:
for chunk in self._stream_chunks:
yield chunk
def set_peak(self, value: float) -> None:
self._peak = value
def set_stream_chunks(self, chunks: list[bytes]) -> None:
self._stream_chunks = chunks

View file

@ -0,0 +1,22 @@
from __future__ import annotations
from collections.abc import AsyncIterator
from typing import Any
from vibe.core.transcribe.transcribe_client_port import TranscribeEvent
class FakeTranscribeClient:
def __init__(
self, *_args: Any, events: list[TranscribeEvent] | None = None, **_kwargs: Any
) -> None:
self._events: list[TranscribeEvent] = events or []
def set_events(self, events: list[TranscribeEvent]) -> None:
self._events = events
async def transcribe(
self, audio_stream: AsyncIterator[bytes]
) -> AsyncIterator[TranscribeEvent]:
for event in self._events:
yield event

View file

@ -0,0 +1,74 @@
from __future__ import annotations
from tests.stubs.fake_audio_recorder import FakeAudioRecorder
from vibe.cli.voice_manager import VoiceToggleResult
from vibe.cli.voice_manager.voice_manager_port import (
TranscribeState,
VoiceManagerListener,
)
from vibe.core.audio_recorder import AudioRecorderPort
from vibe.core.audio_recorder.audio_recorder_port import RecordingMode
class FakeVoiceManager:
def __init__(
self,
*,
is_voice_ready: bool = False,
audio_recorder: AudioRecorderPort | None = None,
) -> None:
self._enabled = is_voice_ready
self._audio_recorder: AudioRecorderPort = audio_recorder or FakeAudioRecorder()
self._transcribe_state = TranscribeState.IDLE
self._listeners: list[VoiceManagerListener] = []
@property
def is_enabled(self) -> bool:
return self._enabled
@property
def transcribe_state(self) -> TranscribeState:
return self._transcribe_state
@property
def peak(self) -> float:
return self._audio_recorder.peak
def toggle_voice_mode(self) -> VoiceToggleResult:
self._enabled = not self._enabled
if not self._enabled:
self.cancel_recording()
for listener in self._listeners:
listener.on_voice_mode_change(self._enabled)
return VoiceToggleResult(enabled=self._enabled)
def start_recording(self, mode: RecordingMode = RecordingMode.STREAM) -> None:
self._set_state(TranscribeState.RECORDING)
async def stop_recording(self) -> None:
self._set_state(TranscribeState.FLUSHING)
self._set_state(TranscribeState.IDLE)
def cancel_recording(self) -> None:
if self._transcribe_state == TranscribeState.IDLE:
return
if self._transcribe_state == TranscribeState.RECORDING:
pass # fake: no actual audio to cancel
self._set_state(TranscribeState.IDLE)
def add_listener(self, listener: VoiceManagerListener) -> None:
if listener not in self._listeners:
self._listeners.append(listener)
def remove_listener(self, listener: VoiceManagerListener) -> None:
try:
self._listeners.remove(listener)
except ValueError:
pass
def _set_state(self, state: TranscribeState) -> None:
if self._transcribe_state == state:
return
self._transcribe_state = state
for listener in self._listeners:
listener.on_transcribe_state_change(state)