use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use crate::v0::Meta;
use crate::{KagiClient, KagiError, KagiResult};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Summary {
pub meta: Meta,
pub data: Data,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Data {
pub output: String,
pub tokens: usize,
}
#[derive(Debug, Clone)]
pub enum Subject {
Url(url::Url),
Text(String),
}
impl Subject {
pub fn url_or_text(input: String) -> Self {
url::Url::parse(&input)
.map(Subject::Url)
.unwrap_or_else(|_| Subject::Text(input))
}
}
#[derive(Debug, Default)]
pub struct SummaryOptions {
pub summary_type: Option<SummaryType>,
pub engine: Option<Engine>,
pub target_language: Option<Language>,
pub cache: Option<bool>,
}
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SummaryType {
#[default]
Summary,
Takeaway,
}
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Engine {
#[default]
Cecil,
Agnes,
Daphne,
Muriel,
}
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
pub enum Language {
BG,
CS,
DA,
DE,
EL,
#[default]
EN,
ES,
ET,
FI,
FR,
HU,
ID,
IT,
JA,
KO,
LT,
LV,
NB,
NL,
PL,
PT,
RO,
RU,
SK,
SL,
SV,
TR,
UK,
ZH,
}
#[async_trait]
pub trait UniversalSummarizer {
async fn summarize(&self, subject: Subject, options: SummaryOptions) -> KagiResult<Summary>;
async fn summarize_url(&self, url: url::Url, options: SummaryOptions) -> KagiResult<Summary> {
self.summarize(Subject::Url(url), options).await
}
async fn summarize_text(&self, text: String, options: SummaryOptions) -> KagiResult<Summary> {
self.summarize(Subject::Text(text), options).await
}
}
#[async_trait]
impl UniversalSummarizer for KagiClient {
async fn summarize(&self, subject: Subject, options: SummaryOptions) -> KagiResult<Summary> {
let request_body = build_request_body(subject, options);
let response = self.query("/v0/summarize", request_body).await?;
response
.json::<Summary>()
.await
.map_err(KagiError::ReqwestError)
}
}
fn build_request_body(subject: Subject, options: SummaryOptions) -> serde_json::Value {
let mut request_body = match subject {
Subject::Url(url) => serde_json::json!({ "url": url }),
Subject::Text(text) => serde_json::json!({ "text": text }),
};
if let Some(summary_type) = options.summary_type {
request_body["summary_type"] = serde_json::to_value(summary_type).unwrap();
}
if let Some(engine) = options.engine {
request_body["engine"] = serde_json::to_value(engine).unwrap();
}
if let Some(target_language) = options.target_language {
request_body["target_language"] = serde_json::to_value(target_language).unwrap();
}
if let Some(cache) = options.cache {
request_body["cache"] = serde_json::to_value(cache).unwrap();
}
request_body
}
impl std::fmt::Display for SummaryType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SummaryType::Summary => write!(f, "summary"),
SummaryType::Takeaway => write!(f, "takeaway"),
}
}
}
impl std::str::FromStr for SummaryType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"summary" => Ok(SummaryType::Summary),
"takeaway" => Ok(SummaryType::Takeaway),
_ => Err(format!("Unknown summary type '{s}'")),
}
}
}
impl std::fmt::Display for Language {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let language = match self {
Language::BG => "bg",
Language::CS => "cs",
Language::DA => "da",
Language::DE => "de",
Language::EL => "el",
Language::EN => "en",
Language::ES => "es",
Language::ET => "et",
Language::FI => "fi",
Language::FR => "fr",
Language::HU => "hu",
Language::ID => "id",
Language::IT => "it",
Language::JA => "ja",
Language::KO => "ko",
Language::LT => "lt",
Language::LV => "lv",
Language::NB => "nb",
Language::NL => "nl",
Language::PL => "pl",
Language::PT => "pt",
Language::RO => "ro",
Language::RU => "ru",
Language::SK => "sk",
Language::SL => "sl",
Language::SV => "sv",
Language::TR => "tr",
Language::UK => "uk",
Language::ZH => "zh",
};
write!(f, "{}", language)
}
}
impl std::str::FromStr for Language {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let language = match s.to_lowercase().as_ref() {
"bg" => Language::BG,
"cs" => Language::CS,
"da" => Language::DA,
"de" => Language::DE,
"el" => Language::EL,
"en" => Language::EN,
"es" => Language::ES,
"et" => Language::ET,
"fi" => Language::FI,
"fr" => Language::FR,
"hu" => Language::HU,
"id" => Language::ID,
"it" => Language::IT,
"ja" => Language::JA,
"ko" => Language::KO,
"lt" => Language::LT,
"lv" => Language::LV,
"nb" => Language::NB,
"nl" => Language::NL,
"pl" => Language::PL,
"pt" => Language::PT,
"ro" => Language::RO,
"ru" => Language::RU,
"sk" => Language::SK,
"sl" => Language::SL,
"sv" => Language::SV,
"tr" => Language::TR,
"uk" => Language::UK,
"zh" => Language::ZH,
_ => return Err(format!("Unknown language '{s}'")),
};
Ok(language)
}
}
impl std::fmt::Display for Engine {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let engine = match self {
Engine::Cecil => "cecil",
Engine::Agnes => "agnes",
Engine::Daphne => "daphne",
Engine::Muriel => "muriel",
};
write!(f, "{}", engine)
}
}
impl std::str::FromStr for Engine {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let engine = match s.to_lowercase().as_ref() {
"cecil" => Engine::Cecil,
"agnes" => Engine::Agnes,
"daphne" => Engine::Daphne,
"muriel" => Engine::Muriel,
_ => return Err(format!("Unknown engine '{s}")),
};
Ok(engine)
}
}