use crate::{
client::{Config, OpenAI},
error::OpenAIError,
};
use aither_core::audio::{AudioGenerator, AudioTranscriber, Data};
use futures_lite::StreamExt;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use zenwave::{
Client, client, header,
multipart::{MultipartPart, encode as encode_multipart},
};
impl AudioGenerator for OpenAI {
type Error = OpenAIError;
fn generate(
&self,
prompt: &str,
) -> impl futures_core::Stream<Item = Result<Data, Self::Error>> + Send {
let cfg = self.config();
let text = prompt.to_owned();
futures_lite::stream::iter(vec![synthesize(cfg, text)]).then(|fut| fut)
}
}
impl AudioTranscriber for OpenAI {
type Error = OpenAIError;
fn transcribe(
&self,
audio: &[u8],
) -> impl futures_core::Stream<Item = Result<String, Self::Error>> + Send {
let cfg = self.config();
let payload = audio.to_vec();
futures_lite::stream::iter(vec![transcribe_once(cfg, payload)]).then(|fut| fut)
}
}
async fn synthesize(cfg: Arc<Config>, text: String) -> Result<Vec<u8>, OpenAIError> {
let endpoint = cfg.request_url("/audio/speech");
let mut backend = client();
let mut builder = backend
.post(endpoint)
.map_err(OpenAIError::Http)?
.header(header::AUTHORIZATION.as_str(), cfg.request_auth())
.map_err(OpenAIError::Http)?
.header(header::USER_AGENT.as_str(), "aither-openai/0.1")
.map_err(OpenAIError::Http)?;
if let Some(org) = &cfg.organization {
builder = builder
.header("OpenAI-Organization", org.clone())
.map_err(OpenAIError::Http)?;
}
let request = SpeechRequest {
model: &cfg.audio_model,
input: &text,
voice: &cfg.audio_voice,
format: &cfg.audio_format,
};
let bytes = builder
.json_body(&request)
.map_err(OpenAIError::Http)?
.bytes()
.await
.map_err(OpenAIError::Http)?;
Ok(bytes.to_vec())
}
async fn transcribe_once(cfg: Arc<Config>, audio: Vec<u8>) -> Result<String, OpenAIError> {
let endpoint = cfg.request_url("/audio/transcriptions");
let mut backend = client();
let mut builder = backend
.post(endpoint)
.map_err(OpenAIError::Http)?
.header(header::AUTHORIZATION.as_str(), cfg.request_auth())
.map_err(OpenAIError::Http)?
.header(header::USER_AGENT.as_str(), "aither-openai/0.1")
.map_err(OpenAIError::Http)?;
if let Some(org) = &cfg.organization {
builder = builder
.header("OpenAI-Organization", org.clone())
.map_err(OpenAIError::Http)?;
}
let parts = vec![
MultipartPart::text("model", cfg.transcription_model.clone()),
MultipartPart::text("response_format", "json"),
MultipartPart::binary("file", "audio.wav", "application/octet-stream", audio),
];
let (boundary, body) = encode_multipart(parts);
let response: TranscriptionResponse = builder
.header(
header::CONTENT_TYPE.as_str(),
format!("multipart/form-data; boundary={boundary}"),
)
.map_err(OpenAIError::Http)?
.bytes_body(body)
.json()
.await
.map_err(OpenAIError::Http)?;
Ok(response.text)
}
#[derive(Debug, Serialize)]
struct SpeechRequest<'a> {
model: &'a str,
input: &'a str,
voice: &'a str,
#[serde(rename = "response_format")]
format: &'a str,
}
#[derive(Debug, Deserialize)]
struct TranscriptionResponse {
text: String,
}