use super::{AudioData, ImageData, Media, VideoData};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ContentPart {
#[serde(rename = "text")]
Text { text: String },
#[serde(rename = "image_url")]
Image { image_url: ImageUrl },
#[serde(rename = "input_audio")]
Audio { input_audio: AudioInput },
#[serde(rename = "video_url")]
Video { video_url: VideoUrl },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageUrl {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioInput {
pub data: String,
pub format: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoUrl {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub duration_secs: Option<f64>,
}
impl ContentPart {
pub fn text(text: impl Into<String>) -> Self {
Self::Text { text: text.into() }
}
pub fn image(image: &ImageData) -> Self {
Self::Image {
image_url: ImageUrl {
url: image.to_data_url(),
detail: image.detail.clone(),
},
}
}
pub fn audio(audio: &AudioData) -> Self {
Self::Audio {
input_audio: AudioInput {
data: audio.base64_data.clone(),
format: audio.format.clone(),
},
}
}
pub fn video(video: &VideoData) -> Self {
Self::Video {
video_url: VideoUrl {
url: video.to_data_url(),
duration_secs: video.duration_secs,
},
}
}
pub fn from_media(media: &Media) -> Self {
match media {
Media::Image(img) => Self::image(img),
Media::Audio(audio) => Self::audio(audio),
Media::Video(video) => Self::video(video),
}
}
pub fn is_text(&self) -> bool {
matches!(self, Self::Text { .. })
}
pub fn as_text(&self) -> Option<&str> {
match self {
Self::Text { text } => Some(text),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum MessageContent {
Text(String),
Parts(Vec<ContentPart>),
}
impl MessageContent {
pub fn text(text: impl Into<String>) -> Self {
Self::Text(text.into())
}
pub fn parts(parts: Vec<ContentPart>) -> Self {
Self::Parts(parts)
}
pub fn with_images(text: impl Into<String>, images: &[ImageData]) -> Self {
let mut parts = vec![ContentPart::text(text)];
parts.extend(images.iter().map(ContentPart::image));
Self::Parts(parts)
}
pub fn with_audio(text: impl Into<String>, audio: &[AudioData]) -> Self {
let mut parts = vec![ContentPart::text(text)];
parts.extend(audio.iter().map(ContentPart::audio));
Self::Parts(parts)
}
pub fn with_video(text: impl Into<String>, video: &[VideoData]) -> Self {
let mut parts = vec![ContentPart::text(text)];
parts.extend(video.iter().map(ContentPart::video));
Self::Parts(parts)
}
pub fn with_media(text: impl Into<String>, media: &[Media]) -> Self {
let mut parts = vec![ContentPart::text(text)];
parts.extend(media.iter().map(ContentPart::from_media));
Self::Parts(parts)
}
pub fn has_multimodal(&self) -> bool {
match self {
Self::Text(_) => false,
Self::Parts(parts) => parts.iter().any(|p| !p.is_text()),
}
}
pub fn get_text(&self) -> Option<&str> {
match self {
Self::Text(text) => Some(text),
Self::Parts(parts) => {
parts.iter().find_map(|p| p.as_text())
}
}
}
pub fn all_text(&self) -> String {
match self {
Self::Text(text) => text.clone(),
Self::Parts(parts) => parts
.iter()
.filter_map(|p| p.as_text())
.collect::<Vec<_>>()
.join("\n"),
}
}
pub fn to_api_format(&self) -> serde_json::Value {
match self {
Self::Text(text) => serde_json::json!(text),
Self::Parts(parts) => serde_json::json!(parts),
}
}
pub fn merge(&self, other: &MessageContent) -> MessageContent {
match (self, other) {
(Self::Text(a), Self::Text(b)) => Self::Text(format!("{}\n{}", a, b)),
(Self::Text(a), Self::Parts(b)) => {
let mut parts = vec![ContentPart::text(a)];
parts.extend(b.clone());
Self::Parts(parts)
}
(Self::Parts(a), Self::Text(b)) => {
let mut parts = a.clone();
parts.push(ContentPart::text(b));
Self::Parts(parts)
}
(Self::Parts(a), Self::Parts(b)) => {
let mut parts = a.clone();
parts.extend(b.clone());
Self::Parts(parts)
}
}
}
}
impl From<String> for MessageContent {
fn from(text: String) -> Self {
Self::Text(text)
}
}
impl From<&str> for MessageContent {
fn from(text: &str) -> Self {
Self::Text(text.to_string())
}
}
impl Default for MessageContent {
fn default() -> Self {
Self::Text(String::new())
}
}