use serde::{Deserialize, Serialize}; use std::sync::Mutex; // ─── LLM Event (for streaming) ─────────────────────────────── #[derive(Clone, Serialize, Deserialize)] #[serde(tag = "type", content = "data", rename_all = "camelCase")] pub enum LlmEvent { Token(String), Done(String), // full text Error(String), } // ─── App State ──────────────────────────────────────────────── pub struct AppState { pub config: Mutex, pub rag: crate::rag::RagStore, pub gen: crate::generations::GenerationStore, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LlmConfig { pub api_url: String, pub api_key: String, pub model: String, pub temperature: f32, pub max_tokens: u32, pub top_p: f32, /// Ollama image-generation model (e.g. `x/flux2-klein:4b`). macOS-only via Ollama. pub image_model: String, /// Ollama embedding model for lore RAG (e.g. `nomic-embed-text`). pub embed_model: String, } impl Default for LlmConfig { fn default() -> Self { Self { // Default to Ollama local server; also works with LM Studio, llama.cpp server, or OpenAI api_url: "http://localhost:11434".to_string(), api_key: String::new(), model: "llama3.2".to_string(), temperature: 0.7, max_tokens: 512, top_p: 0.9, image_model: "x/flux2-klein:4b".to_string(), embed_model: "nomic-embed-text".to_string(), } } } // ─── Generation Request ─────────────────────────────────────── #[derive(Debug, Deserialize)] pub struct GenerateRequest { pub prompt: String, pub system: Option, pub temperature: Option, pub max_tokens: Option, /// Optional RAG query: when set, the top lore chunks for this query are /// retrieved and prepended to the system prompt so generation stays /// consistent with the user's world bible. pub rag_query: Option, } // ─── OpenAI-Compatible Chat Response ───────────────────────── #[derive(Debug, Deserialize)] pub struct ChatResponse { pub choices: Vec, } #[derive(Debug, Deserialize)] pub struct ChatChoice { pub message: ChatMessage, } #[derive(Debug, Serialize, Deserialize)] pub struct ChatMessage { pub role: String, pub content: String, } // ─── Ollama Generate Response ───────────────────────────────── #[derive(Debug, Deserialize)] pub struct OllamaGenerateResponse { pub response: String, pub done: bool, } #[derive(Debug, Deserialize)] pub struct OllamaChatResponse { pub message: ChatMessage, pub done: bool, } // ─── Helper: detect if we're talking to Ollama ─────────────── pub fn is_ollama(url: &str) -> bool { url.contains("localhost:11434") || url.contains("127.0.0.1:11434") } // ─── Build system + user messages from a prompt ────────────── pub fn build_messages(system: Option<&str>, prompt: &str) -> Vec { let mut messages = Vec::new(); if let Some(sys) = system { messages.push(ChatMessage { role: "system".to_string(), content: sys.to_string(), }); } messages.push(ChatMessage { role: "user".to_string(), content: prompt.to_string(), }); messages }