//! 画布Agent对话领域模型。 //! //! 本 crate 只描述会话元数据、消息与附件的纯领域事实与规则; //! LLM、SSE、OSS 读写和 SpacetimeDB 写表均留在外层 adapter。 use serde::{Deserialize, Serialize}; #[cfg(feature = "spacetime-types")] use spacetimedb::SpacetimeType; pub const EDITOR_AGENT_CONVERSATION_ID_PREFIX: &str = "editor-agent-conv-"; pub const EDITOR_AGENT_MESSAGE_ID_PREFIX: &str = "editor-agent-message-"; /// 会话消息 OSS 对象目录前缀,完整键见 [`editor_agent_messages_object_key`]。 pub const EDITOR_AGENT_MESSAGES_OBJECT_PREFIX: &str = "editor-agent/"; /// 单条消息附件上限(超出由前端拦截、后端兜底拒绝)。 pub const EDITOR_AGENT_MAX_ATTACHMENTS: usize = 9; /// 会话标题取首条用户消息的前 N 个字符。 pub const EDITOR_AGENT_TITLE_MAX_CHARS: usize = 20; /// 新会话默认标题。 pub const EDITOR_AGENT_DEFAULT_CONVERSATION_TITLE: &str = "新对话"; /// 会话元数据快照:SpacetimeDB 表只存这些字段,消息正文整体存 OSS。 #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct EditorAgentConversationSnapshot { pub conversation_id: String, pub project_id: String, pub owner_user_id: String, pub title: String, pub messages_object_key: String, pub deleted: bool, pub created_at_micros: i64, pub updated_at_micros: i64, } /// 会话消息 OSS 对象键:`editor-agent/{conversationId}.json`,会话粒度整体读写。 pub fn editor_agent_messages_object_key(conversation_id: &str) -> String { format!("{EDITOR_AGENT_MESSAGES_OBJECT_PREFIX}{conversation_id}.json") } /// 从首条用户消息推导会话标题:去掉首尾空白与换行后截取前 N 个字符; /// 空文本退回默认标题,供尚未发送消息的新会话使用。 pub fn derive_conversation_title(first_message_text: &str) -> String { let normalized: String = first_message_text .chars() .map(|c| if c == '\n' || c == '\r' { ' ' } else { c }) .collect::() .split_whitespace() .collect::>() .join(" "); if normalized.is_empty() { return EDITOR_AGENT_DEFAULT_CONVERSATION_TITLE.to_string(); } normalized .chars() .take(EDITOR_AGENT_TITLE_MAX_CHARS) .collect() }