use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::entities::sampling::SamplingConfig;
use crate::shared::i18n::Lang;
pub type ToolId = String;
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct CharacterNames {
pub user: String,
pub assistant: String,
pub system: String,
}
impl CharacterNames {
pub fn user_name(&self) -> Option<&str> {
non_empty(&self.user)
}
pub fn assistant_name(&self) -> Option<&str> {
non_empty(&self.assistant)
}
pub fn system_name(&self) -> Option<&str> {
non_empty(&self.system)
}
}
fn non_empty(s: &str) -> Option<&str> {
let s = s.trim();
(!s.is_empty()).then_some(s)
}
#[derive(Debug, Clone, PartialEq)]
pub struct LlmChange {
pub changed_at: chrono::DateTime<chrono::Utc>,
pub model: String,
pub mode: crate::shared::config::ServerMode,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Profile {
pub id: Uuid,
pub name: String,
pub default_system_message: String,
#[serde(default)]
pub language: Lang,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub impersonation_profile_id: Option<Uuid>,
#[serde(default)]
pub impersonation_system_message: String,
#[serde(default)]
pub character_names: CharacterNames,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub greeting: Option<String>,
#[serde(default)]
pub enabled_tools: Vec<ToolId>,
#[serde(default)]
pub known_tools: Vec<ToolId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_sampling: Option<SamplingConfig>,
#[serde(default)]
pub is_hidden: bool,
}
impl Profile {
pub fn new(name: impl Into<String>, system_message: impl Into<String>) -> Self {
Self {
id: Uuid::new_v4(),
name: name.into(),
default_system_message: system_message.into(),
language: Lang::default(),
impersonation_profile_id: None,
impersonation_system_message: String::new(),
character_names: CharacterNames::default(),
greeting: None,
enabled_tools: Vec::new(),
known_tools: Vec::new(),
default_sampling: None,
is_hidden: false,
}
}
pub fn summary(&self) -> ProfileSummary {
ProfileSummary {
id: self.id,
name: self.name.clone(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProfileSummary {
pub id: Uuid,
pub name: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde_roundtrip() {
let p = Profile::new("Joyce", "Ты — Джойс.");
let json = serde_json::to_string(&p).unwrap();
let back: Profile = serde_json::from_str(&json).unwrap();
assert_eq!(p, back);
}
#[test]
fn defaults_applied() {
let raw = format!(
r#"{{"id":"{}","name":"X","default_system_message":"s"}}"#,
Uuid::nil()
);
let p: Profile = serde_json::from_str(&raw).unwrap();
assert_eq!(p.character_names, CharacterNames::default());
assert!(!p.is_hidden);
assert!(p.enabled_tools.is_empty());
}
#[test]
fn character_names_default_to_unset() {
let names = CharacterNames::default();
assert_eq!(names.user_name(), None);
assert_eq!(names.assistant_name(), None);
assert_eq!(Profile::new("X", "s").character_names, names);
}
#[test]
fn character_names_trim_and_treat_blank_as_unset() {
let names = CharacterNames {
user: " Гайя ".into(),
assistant: " ".into(),
system: String::new(),
};
assert_eq!(names.user_name(), Some("Гайя"));
assert_eq!(names.assistant_name(), None);
}
}