1use std::path::Path;
4
5use langfuse_core::error::LangfuseError;
6
7#[derive(Debug, Clone)]
9pub struct LangfuseMedia {
10 pub content_type: String,
12 pub data: Vec<u8>,
14}
15
16impl LangfuseMedia {
17 pub fn from_data_uri(data_uri: &str) -> Result<Self, LangfuseError> {
19 let parts: Vec<&str> = data_uri.splitn(2, ',').collect();
21 if parts.len() != 2 {
22 return Err(LangfuseError::Media("Invalid data URI".into()));
23 }
24 let header = parts[0]; let base64_data = parts[1];
26
27 let content_type = header
28 .strip_prefix("data:")
29 .and_then(|s| s.strip_suffix(";base64"))
30 .ok_or_else(|| LangfuseError::Media("Invalid data URI format".into()))?;
31
32 use base64::Engine as _;
33 let data = base64::engine::general_purpose::STANDARD
34 .decode(base64_data)
35 .map_err(|e| LangfuseError::Media(format!("Base64 decode error: {e}")))?;
36
37 Ok(Self {
38 content_type: content_type.to_string(),
39 data,
40 })
41 }
42
43 pub fn from_bytes(content_type: &str, data: Vec<u8>) -> Self {
45 Self {
46 content_type: content_type.to_string(),
47 data,
48 }
49 }
50
51 pub fn from_file(content_type: &str, path: impl AsRef<Path>) -> Result<Self, LangfuseError> {
53 let data = std::fs::read(path.as_ref())
54 .map_err(|e| LangfuseError::Media(format!("File read error: {e}")))?;
55 Ok(Self {
56 content_type: content_type.to_string(),
57 data,
58 })
59 }
60
61 pub async fn from_file_async(
67 content_type: &str,
68 path: impl AsRef<Path>,
69 ) -> Result<Self, LangfuseError> {
70 let data = tokio::fs::read(path.as_ref())
71 .await
72 .map_err(|e| LangfuseError::Media(format!("File read error: {e}")))?;
73 Ok(Self {
74 content_type: content_type.to_string(),
75 data,
76 })
77 }
78
79 pub fn size(&self) -> usize {
81 self.data.len()
82 }
83}
84
85pub const MEDIA_REFERENCE_PATTERN: &str =
88 r"@@@langfuseMedia:type=([^|]+)\|id=([^|]+)\|source=([^@]+)@@@";
89
90#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct ParsedMediaReference {
93 pub content_type: String,
95 pub media_id: String,
97 pub source: String,
99}
100
101pub fn parse_media_references(text: &str) -> Vec<ParsedMediaReference> {
106 let mut refs = Vec::new();
107 let mut remaining = text;
108 while let Some(start) = remaining.find("@@@langfuseMedia:") {
109 let after = &remaining[start + 17..]; if let Some(end) = after.find("@@@") {
111 let inner = &after[..end];
112 let mut content_type = None;
114 let mut media_id = None;
115 let mut source = None;
116 for part in inner.split('|') {
117 if let Some(val) = part.strip_prefix("type=") {
118 content_type = Some(val.to_string());
119 } else if let Some(val) = part.strip_prefix("id=") {
120 media_id = Some(val.to_string());
121 } else if let Some(val) = part.strip_prefix("source=") {
122 source = Some(val.to_string());
123 }
124 }
125 if let (Some(ct), Some(id), Some(src)) = (content_type, media_id, source) {
126 refs.push(ParsedMediaReference {
127 content_type: ct,
128 media_id: id,
129 source: src,
130 });
131 }
132 remaining = &after[end + 3..];
133 } else {
134 break;
135 }
136 }
137 refs
138}