gemini_client_api/gemini/
utils.rs

1use super::types::request::*;
2use crate::utils::{self, MatchedFiles};
3use getset::Getters;
4use regex::Regex;
5use reqwest::header::HeaderMap;
6use std::time::Duration;
7
8const REQ_TIMEOUT: Duration = Duration::from_secs(10);
9
10pub struct MarkdownToPartsBuilder {
11    regex: Option<Regex>,
12    guess_mime_type: Option<fn(url: &str) -> mime::Mime>,
13    decide_download: Option<fn(headers: &HeaderMap) -> bool>,
14    timeout: Option<Duration>,
15}
16impl MarkdownToPartsBuilder {
17    ///# Panics
18    ///`regex` must have a Regex with only 1 capture group with file URL as first capture
19    ///group, else it PANICS when `.build()` is called.
20    pub fn regex(mut self, regex: Regex) -> Self {
21        self.regex = Some(regex);
22        self
23    }
24    /// `guess_mime_type` is used to detect mimi_type of URL pointing to file system or web resource
25    /// with no "Content-Type" header.
26    pub fn guess_mime_type(mut self, guess_mime_type: fn(url: &str) -> mime::Mime) -> Self {
27        self.guess_mime_type = Some(guess_mime_type);
28        self
29    }
30    /// `decide_download` is used to decide if to download. If it returns false, resource will not
31    /// be fetched and won't be in `parts`
32    pub fn decide_download(mut self, decide_download: fn(headers: &HeaderMap) -> bool) -> Self {
33        self.decide_download = Some(decide_download);
34        self
35    }
36    pub fn timeout(mut self, timeout: Duration) -> Self {
37        self.timeout = Some(timeout);
38        self
39    }
40    pub async fn build<'a>(self, markdown: &'a str) -> MarkdownToParts<'a> {
41        MarkdownToParts {
42            markdown,
43            base64s: utils::get_file_base64s(
44                markdown,
45                self.regex
46                    .unwrap_or(Regex::new(r"(?s)!\[.*?].?\((.*?)\)").unwrap()),
47                self.guess_mime_type.unwrap_or(|_| mime::IMAGE_PNG),
48                |_| true,
49                self.timeout.unwrap_or(REQ_TIMEOUT),
50            )
51            .await,
52        }
53    }
54}
55#[derive(Getters, Clone)]
56///Converts markdown to parts considering `![image](link)` means Gemini will be see the images too. `link` can be URL or file path.  
57pub struct MarkdownToParts<'a> {
58    markdown: &'a str,
59    #[get = "pub"]
60    base64s: Vec<MatchedFiles>,
61}
62impl<'a> MarkdownToParts<'a> {
63    pub fn builder() -> MarkdownToPartsBuilder {
64        MarkdownToPartsBuilder {
65            regex: None,
66            guess_mime_type: None,
67            decide_download: None,
68            timeout: None,
69        }
70    }
71    ///# Panics
72    ///`regex` must have a Regex with only 1 capture group with file URL as first capture
73    ///group, else it PANICS.
74    /// # Arguments
75    /// `guess_mime_type` is used to detect mimi_type of URL pointing to file system or web resource
76    /// with no "Content-Type" header.
77    /// `decide_download` is used to decide if to download. If it returns false, resource will not
78    /// be fetched and won't be in `parts`
79    /// # Example
80    /// ```ignore
81    /// from_regex("Your markdown string...", Regex::new(r"(?s)!\[.*?].?\((.*?)\)").unwrap(), |_| mime::IMAGE_PNG, |_| true)
82    /// ```
83    pub async fn from_regex_checked(
84        markdown: &'a str,
85        regex: Regex,
86        guess_mime_type: fn(url: &str) -> mime::Mime,
87        decide_download: fn(headers: &HeaderMap) -> bool,
88    ) -> Self {
89        Self {
90            base64s: utils::get_file_base64s(
91                markdown,
92                regex,
93                guess_mime_type,
94                decide_download,
95                REQ_TIMEOUT,
96            )
97            .await,
98            markdown,
99        }
100    }
101    ///# Panics
102    /// `regex` must have a Regex with only 1 capture group with file URL as first capture group, else it PANICS.
103    /// # Arguments
104    /// `guess_mime_type` is used to detect mimi_type of URL pointing to file system or web resource
105    /// with no "Content-Type" header.
106    /// # Example
107    /// ```ignore
108    /// from_regex("Your markdown string...", Regex::new(r"(?s)!\[.*?].?\((.*?)\)").unwrap(), |_|
109    /// mime::IMAGE_PNG)
110    /// ```
111    pub async fn from_regex(
112        markdown: &'a str,
113        regex: Regex,
114        guess_mime_type: fn(url: &str) -> mime::Mime,
115    ) -> Self {
116        Self::from_regex_checked(markdown, regex, guess_mime_type, |_| true).await
117    }
118    /// # Arguments
119    /// `guess_mime_type` is used to detect mimi_type of URL pointing to file system or web resource
120    /// with no "Content-Type" header.
121    /// `decide_download` is used to decide if to download. If it returns false, resource will not
122    /// be fetched and won't be in `parts`
123    /// # Example
124    /// ```ignore
125    /// new("Your markdown string...", |_| mime::IMAGE_PNG, |_| true)
126    /// ```
127    pub async fn new_checked(
128        markdown: &'a str,
129        guess_mime_type: fn(url: &str) -> mime::Mime,
130        decide_download: fn(headers: &HeaderMap) -> bool,
131    ) -> Self {
132        let image_regex = Regex::new(r"(?s)!\[.*?].?\((.*?)\)").unwrap();
133        Self::from_regex_checked(markdown, image_regex, guess_mime_type, decide_download).await
134    }
135    /// # Arguments
136    /// `guess_mime_type` is used to detect mimi_type of URL pointing to file system or web resource
137    /// with no "Content-Type" header.
138    /// # Example
139    /// ```ignore
140    /// new("Your markdown string...", |_| mime::IMAGE_PNG)
141    /// ```
142    pub async fn new(markdown: &'a str, guess_mime_type: fn(url: &str) -> mime::Mime) -> Self {
143        Self::new_checked(markdown, guess_mime_type, |_| true).await
144    }
145    pub fn process(mut self) -> Vec<Part> {
146        let mut parts: Vec<Part> = Vec::new();
147        let mut removed_length = 0;
148        for file in self.base64s {
149            if let MatchedFiles {
150                index,
151                length,
152                mime_type: Some(mime_type),
153                base64: Some(base64),
154            } = file
155            {
156                let end = index + length - removed_length;
157                let text = &self.markdown[..end];
158                parts.push(Part::text(text.to_string()));
159                parts.push(Part::inline_data(InlineData::new(mime_type, base64)));
160
161                self.markdown = &self.markdown[end..];
162                removed_length += end;
163            }
164        }
165        if self.markdown.len() != 0 {
166            parts.push(Part::text(self.markdown.to_string()));
167        }
168        parts
169    }
170}