feat(generate-transcription): opt-in verbose mode (#159)

* feat(generate-transcription): opt-in verbose mode

* chore(generate-transcription): update test

* chore(generate-transcription): clean snapshot
This commit is contained in:
藍+85CD authored and GitHub committed 2025-05-28 17:54:35 +08:00
1 parent 341d2b098a
commit 942de6be35
3 files changed
+59 -17

No files matched your search

+36 -11
View File
@@ -2,24 +2,42 @@ import type { CommonRequestOptions } from '@xsai/shared'
import { requestHeaders, requestURL, responseJSON } from '@xsai/shared'
export interface GenerateTranscriptionOptions<T extends GenerateTranscriptionOptionsTimeStampGranularities = undefined> extends CommonRequestOptions {
export interface GenerateTranscriptionOptions<
T1 extends GenerateTranscriptionOptionsResponseFormat,
T2 extends GenerateTranscriptionOptionsTimeStampGranularities,
> extends CommonRequestOptions {
file: Blob
fileName?: string
language?: string
prompt?: string
/** @default `json` */
responseFormat?: T1
temperature?: string
/** @default `segment` */
timestampGranularities?: T
timestampGranularities?: T2
}
export type GenerateTranscriptionOptionsResponseFormat = 'json' | 'verbose_json' | undefined
export type GenerateTranscriptionOptionsTimeStampGranularities = 'segment' | 'word' | undefined
export interface GenerateTranscriptionResult<T extends GenerateTranscriptionOptionsTimeStampGranularities = undefined> {
duration: number
language: string
segments: T extends 'word' ? never : GenerateTranscriptionResultSegment[]
export interface GenerateTranscriptionResult<
T1 extends GenerateTranscriptionOptionsResponseFormat,
T2 extends GenerateTranscriptionOptionsTimeStampGranularities,
> {
duration: T1 extends 'verbose_json' ? number : never
language: T1 extends 'verbose_json' ? string : never
segments: T1 extends 'verbose_json'
? T2 extends 'word'
? never
: GenerateTranscriptionResultSegment[]
: never
text: string
words: T extends 'word' ? GenerateTranscriptionResultWord[] : never
words: T1 extends 'verbose_json'
? T2 extends 'word'
? GenerateTranscriptionResultWord[]
: never
: never
}
/** @see {@link https://platform.openai.com/docs/api-reference/audio/verbose-json-object#audio/verbose-json-object-segments} */
@@ -43,13 +61,20 @@ export interface GenerateTranscriptionResultWord {
word: string
}
export const generateTranscription = async <T extends GenerateTranscriptionOptionsTimeStampGranularities = undefined>(options: GenerateTranscriptionOptions<T>): Promise<GenerateTranscriptionResult<T>> => {
export const generateTranscription = async <
T1 extends GenerateTranscriptionOptionsResponseFormat = undefined,
T2 extends GenerateTranscriptionOptionsTimeStampGranularities = undefined,
>(options: GenerateTranscriptionOptions<T1, T2>): Promise<GenerateTranscriptionResult<T1, T2>> => {
const body = new FormData()
body.append('model', options.model)
body.append('file', options.file, options.fileName)
body.append('response_format', 'verbose_json')
body.append('timestamp_granularities[]', options.timestampGranularities ?? 'segment')
body.append('response_format', options.responseFormat ?? 'json')
// make ts happy
// eslint-disable-next-line ts/no-unnecessary-type-assertion
if (options.responseFormat as GenerateTranscriptionOptionsResponseFormat === 'verbose_json')
body.append('timestamp_granularities[]', options.timestampGranularities ?? 'segment')
if (options.language != null)
body.append('language', options.language)
@@ -66,5 +91,5 @@ export const generateTranscription = async <T extends GenerateTranscriptionOptio
method: 'POST',
signal: options.abortSignal,
})
.then(responseJSON<GenerateTranscriptionResult<T>>)
.then(responseJSON<GenerateTranscriptionResult<T1, T2>>)
}
@@ -1,6 +1,6 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`@xsai/generate-transcription > segment 1`] = `
exports[`@xsai/generate-transcription > verbose_json + segment 1`] = `
[
{
"avg_logprob": -0.084375,
@@ -43,7 +43,7 @@ exports[`@xsai/generate-transcription > segment 1`] = `
]
`;
exports[`@xsai/generate-transcription > word 1`] = `
exports[`@xsai/generate-transcription > verbose_json + word 1`] = `
[
{
"end": 0.4,
@@ -4,7 +4,22 @@ import { describe, expect, it } from 'vitest'
import { generateTranscription } from '../src'
describe('@xsai/generate-transcription', () => {
it('segment', async () => {
const expectText = 'Hello, I am your AI assistant. Just let me know how I can help bring your ideas to life.'
it('json', async () => {
const { text } = await generateTranscription({
apiKey: 'a',
baseURL: new URL('http://localhost:8000/v1/'),
file: await openAsBlob('./test/fixtures/basic.wav', { type: 'audio/wav' }),
fileName: 'basic.wav',
language: 'en',
model: 'deepdml/faster-whisper-large-v3-turbo-ct2',
})
expect(text).toBe(expectText)
})
it('verbose_json + segment', async () => {
const { duration, language, segments, text } = await generateTranscription({
apiKey: 'a',
baseURL: new URL('http://localhost:8000/v1/'),
@@ -12,15 +27,16 @@ describe('@xsai/generate-transcription', () => {
fileName: 'basic.wav',
language: 'en',
model: 'deepdml/faster-whisper-large-v3-turbo-ct2',
responseFormat: 'verbose_json',
})
expect(duration).toBe(5.472)
expect(language).toBe('en')
expect(text).toBe('Hello, I am your AI assistant. Just let me know how I can help bring your ideas to life.')
expect(text).toBe(expectText)
expect(segments).toMatchSnapshot()
}, 30000)
it('word', async () => {
it('verbose_json + word', async () => {
const { duration, language, text, words } = await generateTranscription({
apiKey: 'a',
baseURL: new URL('http://localhost:8000/v1/'),
@@ -28,12 +44,13 @@ describe('@xsai/generate-transcription', () => {
fileName: 'basic.wav',
language: 'en',
model: 'deepdml/faster-whisper-large-v3-turbo-ct2',
responseFormat: 'verbose_json',
timestampGranularities: 'word',
})
expect(duration).toBe(5.472)
expect(language).toBe('en')
expect(text).toBe('Hello, I am your AI assistant. Just let me know how I can help bring your ideas to life.')
expect(text).toBe(expectText)
expect(words).toMatchSnapshot()
}, 30000)
})