1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3
4use crate::v0::Meta;
5use crate::{KagiClient, KagiError, KagiResult};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Summary {
9 pub meta: Meta,
10 pub data: Data,
11}
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct Data {
15 pub output: String,
16 pub tokens: usize,
17}
18
19#[derive(Debug, Clone)]
23pub enum Subject {
24 Url(url::Url),
26
27 Text(String),
29}
30
31impl Subject {
32 pub fn url_or_text(input: String) -> Self {
33 url::Url::parse(&input)
34 .map(Subject::Url)
35 .unwrap_or_else(|_| Subject::Text(input))
36 }
37}
38
39#[derive(Debug, Default)]
46pub struct SummaryOptions {
47 pub summary_type: Option<SummaryType>,
49
50 pub engine: Option<Engine>,
52
53 pub target_language: Option<Language>,
55
56 pub cache: Option<bool>,
58}
59
60#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
62#[serde(rename_all = "lowercase")]
63pub enum SummaryType {
64 #[default]
66 Summary,
67
68 Takeaway,
70}
71
72#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
75#[serde(rename_all = "lowercase")]
76pub enum Engine {
77 #[default]
79 Cecil,
80
81 Agnes,
83
84 Daphne,
86
87 Muriel,
89}
90
91#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
102pub enum Language {
103 BG,
105 CS,
107 DA,
109 DE,
111 EL,
113 #[default]
115 EN,
116 ES,
118 ET,
120 FI,
122 FR,
124 HU,
126 ID,
128 IT,
130 JA,
132 KO,
134 LT,
136 LV,
138 NB,
140 NL,
142 PL,
144 PT,
146 RO,
148 RU,
150 SK,
152 SL,
154 SV,
156 TR,
158 UK,
160 ZH,
162}
163
164#[async_trait]
167pub trait UniversalSummarizer {
168 async fn summarize(&self, subject: Subject, options: SummaryOptions) -> KagiResult<Summary>;
169
170 async fn summarize_url(&self, url: url::Url, options: SummaryOptions) -> KagiResult<Summary> {
171 self.summarize(Subject::Url(url), options).await
172 }
173
174 async fn summarize_text(&self, text: String, options: SummaryOptions) -> KagiResult<Summary> {
175 self.summarize(Subject::Text(text), options).await
176 }
177}
178
179#[async_trait]
180impl UniversalSummarizer for KagiClient {
181 async fn summarize(&self, subject: Subject, options: SummaryOptions) -> KagiResult<Summary> {
182 let request_body = build_request_body(subject, options);
183 let response = self.query("/v0/summarize", request_body).await?;
184 response
185 .json::<Summary>()
186 .await
187 .map_err(KagiError::ReqwestError)
188 }
189}
190
191fn build_request_body(subject: Subject, options: SummaryOptions) -> serde_json::Value {
192 let mut request_body = match subject {
193 Subject::Url(url) => serde_json::json!({ "url": url }),
194 Subject::Text(text) => serde_json::json!({ "text": text }),
195 };
196
197 if let Some(summary_type) = options.summary_type {
198 request_body["summary_type"] = serde_json::to_value(summary_type).unwrap();
199 }
200
201 if let Some(engine) = options.engine {
202 request_body["engine"] = serde_json::to_value(engine).unwrap();
203 }
204
205 if let Some(target_language) = options.target_language {
206 request_body["target_language"] = serde_json::to_value(target_language).unwrap();
207 }
208
209 if let Some(cache) = options.cache {
210 request_body["cache"] = serde_json::to_value(cache).unwrap();
211 }
212
213 request_body
214}
215
216impl std::fmt::Display for SummaryType {
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218 match self {
219 SummaryType::Summary => write!(f, "summary"),
220 SummaryType::Takeaway => write!(f, "takeaway"),
221 }
222 }
223}
224
225impl std::str::FromStr for SummaryType {
226 type Err = String;
227
228 fn from_str(s: &str) -> Result<Self, Self::Err> {
229 match s {
230 "summary" => Ok(SummaryType::Summary),
231 "takeaway" => Ok(SummaryType::Takeaway),
232 _ => Err(format!("Unknown summary type '{s}'")),
233 }
234 }
235}
236
237impl std::fmt::Display for Language {
238 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239 let language = match self {
240 Language::BG => "bg",
241 Language::CS => "cs",
242 Language::DA => "da",
243 Language::DE => "de",
244 Language::EL => "el",
245 Language::EN => "en",
246 Language::ES => "es",
247 Language::ET => "et",
248 Language::FI => "fi",
249 Language::FR => "fr",
250 Language::HU => "hu",
251 Language::ID => "id",
252 Language::IT => "it",
253 Language::JA => "ja",
254 Language::KO => "ko",
255 Language::LT => "lt",
256 Language::LV => "lv",
257 Language::NB => "nb",
258 Language::NL => "nl",
259 Language::PL => "pl",
260 Language::PT => "pt",
261 Language::RO => "ro",
262 Language::RU => "ru",
263 Language::SK => "sk",
264 Language::SL => "sl",
265 Language::SV => "sv",
266 Language::TR => "tr",
267 Language::UK => "uk",
268 Language::ZH => "zh",
269 };
270
271 write!(f, "{}", language)
272 }
273}
274
275impl std::str::FromStr for Language {
276 type Err = String;
277
278 fn from_str(s: &str) -> Result<Self, Self::Err> {
279 let language = match s.to_lowercase().as_ref() {
280 "bg" => Language::BG,
281 "cs" => Language::CS,
282 "da" => Language::DA,
283 "de" => Language::DE,
284 "el" => Language::EL,
285 "en" => Language::EN,
286 "es" => Language::ES,
287 "et" => Language::ET,
288 "fi" => Language::FI,
289 "fr" => Language::FR,
290 "hu" => Language::HU,
291 "id" => Language::ID,
292 "it" => Language::IT,
293 "ja" => Language::JA,
294 "ko" => Language::KO,
295 "lt" => Language::LT,
296 "lv" => Language::LV,
297 "nb" => Language::NB,
298 "nl" => Language::NL,
299 "pl" => Language::PL,
300 "pt" => Language::PT,
301 "ro" => Language::RO,
302 "ru" => Language::RU,
303 "sk" => Language::SK,
304 "sl" => Language::SL,
305 "sv" => Language::SV,
306 "tr" => Language::TR,
307 "uk" => Language::UK,
308 "zh" => Language::ZH,
309 _ => return Err(format!("Unknown language '{s}'")),
310 };
311
312 Ok(language)
313 }
314}
315
316impl std::fmt::Display for Engine {
317 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318 let engine = match self {
319 Engine::Cecil => "cecil",
320 Engine::Agnes => "agnes",
321 Engine::Daphne => "daphne",
322 Engine::Muriel => "muriel",
323 };
324
325 write!(f, "{}", engine)
326 }
327}
328
329impl std::str::FromStr for Engine {
330 type Err = String;
331
332 fn from_str(s: &str) -> Result<Self, Self::Err> {
333 let engine = match s.to_lowercase().as_ref() {
334 "cecil" => Engine::Cecil,
335 "agnes" => Engine::Agnes,
336 "daphne" => Engine::Daphne,
337 "muriel" => Engine::Muriel,
338 _ => return Err(format!("Unknown engine '{s}")),
339 };
340
341 Ok(engine)
342 }
343}