Stream Whisper segments so the UI can show live transcript text.
Consume faster-whisper SSE instead of blocking on one JSON response, persist growing text and progress over Reverb, and drop the fake percent heartbeat.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Services\WhisperTranscriptionStream;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class WhisperTranscriptionStreamTest extends TestCase
|
||||
{
|
||||
private WhisperTranscriptionStream $stream;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->stream = new WhisperTranscriptionStream;
|
||||
}
|
||||
|
||||
public function test_extracts_sse_data_payloads_from_buffer(): void
|
||||
{
|
||||
$buffer = "data: {\"type\":\"transcript.text.delta\",\"delta\":\"Hello\"}\n\npartial";
|
||||
|
||||
$payloads = $this->stream->extractPayloads($buffer);
|
||||
|
||||
$this->assertSame(['{"type":"transcript.text.delta","delta":"Hello"}'], $payloads);
|
||||
$this->assertSame('partial', $buffer);
|
||||
}
|
||||
|
||||
public function test_parses_openai_delta_and_done_events(): void
|
||||
{
|
||||
$delta = $this->stream->parseEvent('{"type":"transcript.text.delta","delta":"Hello "}');
|
||||
$done = $this->stream->parseEvent('{"type":"transcript.text.done","text":"Hello world"}');
|
||||
|
||||
$this->assertSame('Hello ', $delta['append']);
|
||||
$this->assertFalse($delta['done']);
|
||||
$this->assertSame('Hello world', $done['replace']);
|
||||
$this->assertTrue($done['done']);
|
||||
}
|
||||
|
||||
public function test_parses_legacy_segment_events(): void
|
||||
{
|
||||
$event = $this->stream->parseEvent('{"text":"First segment","start":0,"end":12.5}');
|
||||
|
||||
$this->assertTrue($event['legacy']);
|
||||
$this->assertSame('First segment', $event['append']);
|
||||
$this->assertSame(12.5, $event['end']);
|
||||
}
|
||||
|
||||
public function test_applies_delta_then_done_replace(): void
|
||||
{
|
||||
$delta = $this->stream->parseEvent('{"type":"transcript.text.delta","delta":"Hel"}');
|
||||
$text = $this->stream->applyEvent('', $delta);
|
||||
$delta2 = $this->stream->parseEvent('{"type":"transcript.text.delta","delta":"lo"}');
|
||||
$text = $this->stream->applyEvent($text, $delta2);
|
||||
$done = $this->stream->parseEvent('{"type":"transcript.text.done","text":"Hello world"}');
|
||||
$text = $this->stream->applyEvent($text, $done);
|
||||
|
||||
$this->assertSame('Hello world', $text);
|
||||
}
|
||||
|
||||
public function test_joins_legacy_segments_with_spaces(): void
|
||||
{
|
||||
$first = $this->stream->parseEvent('{"text":"Hello from","end":10}');
|
||||
$second = $this->stream->parseEvent('{"text":"whisper.","end":20}');
|
||||
|
||||
$text = $this->stream->applyEvent('', $first);
|
||||
$text = $this->stream->applyEvent($text, $second);
|
||||
|
||||
$this->assertSame('Hello from whisper.', $text);
|
||||
}
|
||||
|
||||
public function test_ignores_done_sentinel_and_non_json(): void
|
||||
{
|
||||
$buffer = "data: [DONE]\n\ndata: not-json\n\n";
|
||||
|
||||
$payloads = $this->stream->extractPayloads($buffer);
|
||||
|
||||
$this->assertSame(['not-json'], $payloads);
|
||||
$this->assertNull($this->stream->parseEvent('not-json'));
|
||||
}
|
||||
|
||||
public function test_flush_buffer_emits_trailing_frame(): void
|
||||
{
|
||||
$buffer = 'data: {"text":"Last"}';
|
||||
|
||||
$payloads = $this->stream->flushBuffer($buffer);
|
||||
|
||||
$this->assertSame(['{"text":"Last"}'], $payloads);
|
||||
$this->assertSame('', $buffer);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user