use std::path::Path;
use langfuse_core::error::LangfuseError;
#[derive(Debug, Clone)]
pub struct LangfuseMedia {
pub content_type: String,
pub data: Vec<u8>,
}
impl LangfuseMedia {
pub fn from_data_uri(data_uri: &str) -> Result<Self, LangfuseError> {
let parts: Vec<&str> = data_uri.splitn(2, ',').collect();
if parts.len() != 2 {
return Err(LangfuseError::Media("Invalid data URI".into()));
}
let header = parts[0]; let base64_data = parts[1];
let content_type = header
.strip_prefix("data:")
.and_then(|s| s.strip_suffix(";base64"))
.ok_or_else(|| LangfuseError::Media("Invalid data URI format".into()))?;
use base64::Engine as _;
let data = base64::engine::general_purpose::STANDARD
.decode(base64_data)
.map_err(|e| LangfuseError::Media(format!("Base64 decode error: {e}")))?;
Ok(Self {
content_type: content_type.to_string(),
data,
})
}
pub fn from_bytes(content_type: &str, data: Vec<u8>) -> Self {
Self {
content_type: content_type.to_string(),
data,
}
}
pub fn from_file(content_type: &str, path: impl AsRef<Path>) -> Result<Self, LangfuseError> {
let data = std::fs::read(path.as_ref())
.map_err(|e| LangfuseError::Media(format!("File read error: {e}")))?;
Ok(Self {
content_type: content_type.to_string(),
data,
})
}
pub async fn from_file_async(
content_type: &str,
path: impl AsRef<Path>,
) -> Result<Self, LangfuseError> {
let data = tokio::fs::read(path.as_ref())
.await
.map_err(|e| LangfuseError::Media(format!("File read error: {e}")))?;
Ok(Self {
content_type: content_type.to_string(),
data,
})
}
pub fn size(&self) -> usize {
self.data.len()
}
}
pub const MEDIA_REFERENCE_PATTERN: &str =
r"@@@langfuseMedia:type=([^|]+)\|id=([^|]+)\|source=([^@]+)@@@";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedMediaReference {
pub content_type: String,
pub media_id: String,
pub source: String,
}
pub fn parse_media_references(text: &str) -> Vec<ParsedMediaReference> {
let mut refs = Vec::new();
let mut remaining = text;
while let Some(start) = remaining.find("@@@langfuseMedia:") {
let after = &remaining[start + 17..]; if let Some(end) = after.find("@@@") {
let inner = &after[..end];
let mut content_type = None;
let mut media_id = None;
let mut source = None;
for part in inner.split('|') {
if let Some(val) = part.strip_prefix("type=") {
content_type = Some(val.to_string());
} else if let Some(val) = part.strip_prefix("id=") {
media_id = Some(val.to_string());
} else if let Some(val) = part.strip_prefix("source=") {
source = Some(val.to_string());
}
}
if let (Some(ct), Some(id), Some(src)) = (content_type, media_id, source) {
refs.push(ParsedMediaReference {
content_type: ct,
media_id: id,
source: src,
});
}
remaining = &after[end + 3..];
} else {
break;
}
}
refs
}