use serde::{Deserialize, Serialize};
use uuid::Uuid;
pub mod kinds {
pub const BUND_PRINT: &str = "bund_print";
pub const BUND_LOG: &str = "bund_log";
pub const TRANSLATION_RESULT: &str = "translation_result";
pub const TRANSLATION_MEMORY_LISTING: &str = "translation_memory_listing";
pub const TRANSLATION_CORPUS_PROGRESS: &str = "translation_corpus_progress";
pub const TRANSLATION_EVAL_RESULT: &str = "translation_eval_result";
pub const TRANSLATION_EXPORT_RESULT: &str = "translation_export_result";
pub const TRANSLATION_UNCOVERED_WORD_REPORT: &str = "translation_uncovered_word_report";
pub const LEXICON_PROPOSAL: &str = "lexicon_proposal";
pub const VARIETY_RENDERING: &str = "variety_rendering";
pub const AI_TASK_COMPLETE: &str = "ai_task_complete";
pub const FACT_CHECK_WARNING: &str = "fact_check_warning";
pub const WORLD_COMPILER_PROPOSAL: &str = "world_compiler_proposal";
pub const SOCRATIC_INQUIRY: &str = "socratic_inquiry";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Severity {
Info,
Warning,
Contradiction,
Progress,
}
impl Severity {
pub fn as_str(self) -> &'static str {
match self {
Severity::Info => "info",
Severity::Warning => "warning",
Severity::Contradiction => "contradiction",
Severity::Progress => "progress",
}
}
pub fn parse(s: &str) -> Self {
match s {
"warning" => Severity::Warning,
"contradiction" => Severity::Contradiction,
"progress" => Severity::Progress,
_ => Severity::Info,
}
}
pub fn icon(self) -> char {
match self {
Severity::Info => '●',
Severity::Warning => '⚠',
Severity::Contradiction => '⊗',
Severity::Progress => '↻',
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Lifetime {
Session(usize),
Hours(f32),
UntilActedOn,
UntilParagraphEdited(Uuid),
Never,
}
impl Lifetime {
pub fn to_columns(&self) -> (&'static str, Option<String>) {
match self {
Lifetime::Session(n) => ("session", Some(n.to_string())),
Lifetime::Hours(h) => ("hours", Some(h.to_string())),
Lifetime::UntilActedOn => ("until_acted", None),
Lifetime::UntilParagraphEdited(p) => ("until_paragraph_edit", Some(p.to_string())),
Lifetime::Never => ("never", None),
}
}
pub fn from_columns(kind: &str, value: Option<&str>) -> Self {
match kind {
"session" => Lifetime::Session(value.and_then(|v| v.parse().ok()).unwrap_or(100)),
"hours" => Lifetime::Hours(value.and_then(|v| v.parse().ok()).unwrap_or(1.0)),
"until_paragraph_edit" => value
.and_then(|v| Uuid::parse_str(v).ok())
.map(Lifetime::UntilParagraphEdited)
.unwrap_or(Lifetime::UntilActedOn),
"never" => Lifetime::Never,
_ => Lifetime::UntilActedOn,
}
}
pub fn expires_at(&self, created_at: i64) -> Option<i64> {
match self {
Lifetime::Hours(h) => Some(created_at + (h * 3600.0) as i64),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ActionId {
Primary,
Dismiss,
Pin,
AskAi,
Promote,
Snooze,
Expand,
}
impl ActionId {
pub fn as_str(self) -> &'static str {
match self {
ActionId::Primary => "primary",
ActionId::Dismiss => "dismiss",
ActionId::Pin => "pin",
ActionId::AskAi => "ask_ai",
ActionId::Promote => "promote",
ActionId::Snooze => "snooze",
ActionId::Expand => "expand",
}
}
pub fn parse(s: &str) -> Option<Self> {
Some(match s {
"primary" => ActionId::Primary,
"dismiss" => ActionId::Dismiss,
"pin" => ActionId::Pin,
"ask_ai" => ActionId::AskAi,
"promote" => ActionId::Promote,
"snooze" => ActionId::Snooze,
"expand" => ActionId::Expand,
_ => return None,
})
}
}
pub fn now_secs() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0)
}
#[derive(Debug, Clone)]
pub struct Message {
pub id: Uuid,
pub kind: String,
pub timestamp: i64,
pub metadata: serde_json::Value,
pub actions: Vec<ActionId>,
pub severity: Severity,
pub lifetime: Lifetime,
pub group_key: Option<String>,
pub pinned: bool,
pub snoozed_until: Option<i64>,
pub dismissed: bool,
pub dismissed_at: Option<i64>,
pub expires_at: Option<i64>,
pub source_paragraph_id: Option<Uuid>,
pub source_language_id: Option<String>,
pub trace_id: Option<Uuid>,
}
impl Message {
pub fn new(
kind: impl Into<String>,
severity: Severity,
lifetime: Lifetime,
metadata: serde_json::Value,
) -> Self {
let timestamp = now_secs();
let expires_at = lifetime.expires_at(timestamp);
let mut actions = vec![ActionId::Dismiss, ActionId::Expand, ActionId::Pin];
if severity != Severity::Progress {
actions.push(ActionId::AskAi);
}
Message {
id: Uuid::now_v7(),
kind: kind.into(),
timestamp,
metadata,
actions,
severity,
lifetime,
group_key: None,
pinned: false,
snoozed_until: None,
dismissed: false,
dismissed_at: None,
expires_at,
source_paragraph_id: None,
source_language_id: None,
trace_id: None,
}
}
pub fn with_actions(mut self, actions: Vec<ActionId>) -> Self {
self.actions = actions;
self
}
pub fn with_group_key(mut self, key: impl Into<String>) -> Self {
self.group_key = Some(key.into());
self
}
pub fn with_source_paragraph(mut self, id: Uuid) -> Self {
self.source_paragraph_id = Some(id);
self
}
pub fn with_source_language(mut self, lang: impl Into<String>) -> Self {
self.source_language_id = Some(lang.into());
self
}
pub fn with_trace(mut self, id: Uuid) -> Self {
self.trace_id = Some(id);
self
}
pub fn is_active(&self, now: i64) -> bool {
!self.dismissed
&& self.snoozed_until.map(|t| t <= now).unwrap_or(true)
&& self.expires_at.map(|t| t > now).unwrap_or(true)
}
}