// SPDX-License-Identifier: AGPL-3.0-or-later use anyhow::Result; use regex::Regex; use serde_json::{Map, Value, json}; #[derive(Clone, Debug, Eq, PartialEq)] pub struct LocalizationResult { pub localized: String, pub notes: String, } impl LocalizationResult { pub fn localized(localized: impl Into) -> Self { Self { localized: localized.into(), notes: String::new(), } } } pub trait LocalizationClient: Sync { fn base_url(&self) -> &str; fn model(&self) -> &str; fn request_timeout_seconds(&self) -> f64; fn localize( &self, options: &Map, system_prompt: &str, user_prompt: &str, ) -> Result; } pub fn base_options() -> Map { let mut options = Map::new(); options.insert("temperature".to_string(), json!(0.1)); options.insert("top_p".to_string(), json!(0.9)); options.insert("num_ctx".to_string(), json!(4096)); options.insert("num_predict".to_string(), json!(800)); options } pub fn clean_translation_response(content: &str) -> String { let trimmed = trim_special_response_tokens(content.trim()); let fenced_re = Regex::new(r"(?is)^```(?:[a-z]+)?\s*([\s\S]*?)\s*```$") .expect("valid fenced response regex"); let mut raw = fenced_re .captures(trimmed) .and_then(|captures| captures.get(1)) .map(|matched| matched.as_str().trim().to_string()) .unwrap_or_else(|| trimmed.to_string()); let label_re = Regex::new(r"(?is)^(?:localized(?: string)?|translation|answer):\s*([\s\S]+)$") .expect("valid label response regex"); if let Some(captures) = label_re.captures(&raw) && let Some(value) = captures.get(1) { raw = value.as_str().trim().to_string(); } if raw.starts_with('{') && raw.ends_with('}') && let Ok(Value::Object(object)) = serde_json::from_str::(&raw) && let Some(localized) = object.get("localized").and_then(Value::as_str) { return localized.to_string(); } if raw.len() >= 2 && raw.starts_with('"') && raw.ends_with('"') && let Ok(Value::String(value)) = serde_json::from_str::(&raw) { return value; } raw } fn trim_special_response_tokens(content: &str) -> &str { let mut trimmed = content.trim(); loop { let next = trimmed .strip_suffix("<|im_end|>") .or_else(|| trimmed.strip_suffix("")) .or_else(|| trimmed.strip_suffix("")); let Some(next) = next else { return trimmed.trim(); }; trimmed = next.trim(); } } #[cfg(test)] mod tests { use super::*; #[test] fn cleans_common_llm_response_wrappers() { assert_eq!( clean_translation_response("Translation: Bonjour"), "Bonjour" ); assert_eq!(clean_translation_response("\"Bonjour\""), "Bonjour"); assert_eq!( clean_translation_response("{\"localized\":\"Bonjour\"}"), "Bonjour" ); assert_eq!( clean_translation_response("```text\nBonjour\n```"), "Bonjour" ); assert_eq!( clean_translation_response("```json\n{\"0\":\"bonjour\"}\n```<|im_end|>"), "{\"0\":\"bonjour\"}" ); } }