test(desktop): cover Voxtral JSON unwrap on client-direct STT
Pin the Groq plain-text path and the Mistral envelope so dictation keeps spoken words, not the raw transcription object, in the composer.
This commit is contained in:
@@ -8,7 +8,8 @@ import {
|
||||
type DirectTtsConfig,
|
||||
fetchVoiceClientConfig,
|
||||
synthesizeSpeechClientDirect,
|
||||
transcribeAudioClientDirect
|
||||
transcribeAudioClientDirect,
|
||||
transcriptFromOpenAiMultipartBody
|
||||
} from './voice-client-direct'
|
||||
|
||||
const directStt = {
|
||||
@@ -117,6 +118,35 @@ describe('transcribeAudioClientDirect', () => {
|
||||
expect(form.get('response_format')).toBe('text')
|
||||
})
|
||||
|
||||
it('unwraps Mistral Voxtral JSON instead of dumping it into the composer', async () => {
|
||||
mockDesktopApi({
|
||||
ok: true,
|
||||
stt: {
|
||||
...directStt,
|
||||
provider: 'mistral',
|
||||
base_url: 'https://api.mistral.ai/v1',
|
||||
model: 'voxtral-mini-latest',
|
||||
language: null
|
||||
},
|
||||
tts: relay
|
||||
})
|
||||
|
||||
const voxtral = {
|
||||
model: 'voxtral-mini-latest',
|
||||
text: 'Hallo, bist du da?',
|
||||
language: null,
|
||||
segments: [],
|
||||
usage: { prompt_audio_seconds: 4, total_tokens: 388 },
|
||||
finish_reason: null
|
||||
}
|
||||
const fetchMock = vi.fn(async () => new Response(JSON.stringify(voxtral), { status: 200 }))
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
|
||||
expect(await transcribeAudioClientDirect(new Blob(['x'], { type: 'audio/webm' }))).toBe(
|
||||
'Hallo, bist du da?'
|
||||
)
|
||||
})
|
||||
|
||||
it('returns null (relay) when the provider is not client-callable', async () => {
|
||||
mockDesktopApi({ ok: true, stt: relay, tts: relay })
|
||||
const fetchMock = vi.fn()
|
||||
@@ -246,6 +276,36 @@ describe('synthesizeSpeechClientDirect', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('transcriptFromOpenAiMultipartBody', () => {
|
||||
it('keeps Groq/OpenAI plain-text bodies', () => {
|
||||
expect(transcriptFromOpenAiMultipartBody(' hello world ')).toBe('hello world')
|
||||
})
|
||||
|
||||
it('extracts .text from a Voxtral JSON envelope', () => {
|
||||
expect(
|
||||
transcriptFromOpenAiMultipartBody(
|
||||
JSON.stringify({
|
||||
model: 'voxtral-mini-latest',
|
||||
text: 'Hallo, bist du da?',
|
||||
language: null,
|
||||
segments: [],
|
||||
usage: { prompt_audio_seconds: 4 }
|
||||
})
|
||||
)
|
||||
).toBe('Hallo, bist du da?')
|
||||
})
|
||||
|
||||
it('treats a JSON envelope with empty text as silence', () => {
|
||||
expect(transcriptFromOpenAiMultipartBody(JSON.stringify({ text: ' ', model: 'voxtral-mini-latest' }))).toBe(
|
||||
''
|
||||
)
|
||||
})
|
||||
|
||||
it('leaves a non-envelope JSON object intact', () => {
|
||||
expect(transcriptFromOpenAiMultipartBody('{"error":"nope"}')).toBe('{"error":"nope"}')
|
||||
})
|
||||
})
|
||||
|
||||
describe('cutSentences', () => {
|
||||
it('emits complete sentences and holds the incomplete tail', () => {
|
||||
const { sentences, rest } = cutSentences('This is the first full sentence. And then it keeps goi', false)
|
||||
|
||||
Reference in New Issue
Block a user