use crate::Result;
use crate::chat::{Binary, ToolCall, ToolResponse};
use derive_more::From;
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize, From)]
pub enum ContentPart {
#[from(String, &String, &str)]
Text(String),
#[from]
Binary(Binary),
#[from]
ToolCall(ToolCall),
#[from]
ToolResponse(ToolResponse),
#[from(ignore)]
ThoughtSignature(String),
}
impl ContentPart {
pub fn from_text(text: impl Into<String>) -> ContentPart {
ContentPart::Text(text.into())
}
pub fn from_binary_base64(
content_type: impl Into<String>,
content: impl Into<Arc<str>>,
name: Option<String>,
) -> ContentPart {
ContentPart::Binary(Binary::from_base64(content_type, content, name))
}
pub fn from_binary_url(
content_type: impl Into<String>,
url: impl Into<String>,
name: Option<String>,
) -> ContentPart {
ContentPart::Binary(Binary::from_url(content_type, url, name))
}
pub fn from_binary_file(file_path: impl AsRef<Path>) -> Result<ContentPart> {
Ok(ContentPart::Binary(Binary::from_file(file_path)?))
}
}
impl ContentPart {
pub fn as_text(&self) -> Option<&str> {
if let ContentPart::Text(content) = self {
Some(content.as_str())
} else {
None
}
}
pub fn into_text(self) -> Option<String> {
if let ContentPart::Text(content) = self {
Some(content)
} else {
None
}
}
pub fn as_tool_call(&self) -> Option<&ToolCall> {
if let ContentPart::ToolCall(tool_call) = self {
Some(tool_call)
} else {
None
}
}
pub fn into_tool_call(self) -> Option<ToolCall> {
if let ContentPart::ToolCall(tool_call) = self {
Some(tool_call)
} else {
None
}
}
pub fn as_tool_response(&self) -> Option<&ToolResponse> {
if let ContentPart::ToolResponse(tool_response) = self {
Some(tool_response)
} else {
None
}
}
pub fn into_tool_response(self) -> Option<ToolResponse> {
if let ContentPart::ToolResponse(tool_response) = self {
Some(tool_response)
} else {
None
}
}
pub fn as_binary(&self) -> Option<&Binary> {
if let ContentPart::Binary(binary) = self {
Some(binary)
} else {
None
}
}
pub fn into_binary(self) -> Option<Binary> {
if let ContentPart::Binary(binary) = self {
Some(binary)
} else {
None
}
}
pub fn as_thought_signature(&self) -> Option<&str> {
if let ContentPart::ThoughtSignature(thought_signature) = self {
Some(thought_signature)
} else {
None
}
}
pub fn into_thought_signature(self) -> Option<String> {
if let ContentPart::ThoughtSignature(thought_signature) = self {
Some(thought_signature)
} else {
None
}
}
}
impl ContentPart {
pub fn size(&self) -> usize {
match self {
ContentPart::Text(text) => text.len(),
ContentPart::Binary(binary) => binary.size(),
ContentPart::ToolCall(tool_call) => tool_call.size(),
ContentPart::ToolResponse(tool_response) => tool_response.size(),
ContentPart::ThoughtSignature(thought) => thought.len(),
}
}
}
impl ContentPart {
#[allow(unused)]
pub fn is_text(&self) -> bool {
matches!(self, ContentPart::Text(_))
}
pub fn is_image(&self) -> bool {
match self {
ContentPart::Binary(binary) => binary.content_type.trim().to_ascii_lowercase().starts_with("image/"),
_ => false,
}
}
pub fn is_audio(&self) -> bool {
match self {
ContentPart::Binary(binary) => binary.content_type.trim().to_ascii_lowercase().starts_with("audio/"),
_ => false,
}
}
#[allow(unused)]
pub fn is_pdf(&self) -> bool {
match self {
ContentPart::Binary(binary) => binary.content_type.trim().eq_ignore_ascii_case("application/pdf"),
_ => false,
}
}
pub fn is_tool_call(&self) -> bool {
matches!(self, ContentPart::ToolCall(_))
}
pub fn is_tool_response(&self) -> bool {
matches!(self, ContentPart::ToolResponse(_))
}
pub fn is_thought_signature(&self) -> bool {
matches!(self, ContentPart::ThoughtSignature(_))
}
}