v0.2.0-llm: LLM integration, core utilities, and persistence

- Rust backend: LLM abstraction with OpenAI-compatible + Ollama API clients
- Tauri commands: generate, generate_stream, get/set LLM config
- LLM config: api_url, api_key, model, temperature, max_tokens, top_p
- Works with Ollama (localhost:11434), LM Studio, any OpenAI-compatible API
- Streaming infrastructure via Tauri Channels (LlmEvent)
- tauri-plugin-store, tauri-plugin-fs added
- Frontend: NPC Generator with race/class/alignment selection + AI generation
- Frontend: Dice Roller with notation parsing (2d6+3), presets, history
- Frontend: Session Logger with note-taking + AI summarization
- Frontend: Settings panel for LLM configuration (URL, key, model, temperature)
- App shell: left rail nav with active states, settings toggle
- All glassmorphism cards with gold glow hover states
- Builds clean: tsc, vite build, cargo check, tauri build
This commit is contained in:
itsamejms
2026-06-28 22:36:30 +01:00
parent f30a92ddeb
commit 9675e05b07
13 changed files with 1358 additions and 56 deletions
+108
View File
@@ -0,0 +1,108 @@
use serde::{Deserialize, Serialize};
use std::sync::Mutex;
use tauri::ipc::Channel;
// ─── 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<LlmConfig>,
}
#[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,
}
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,
}
}
}
// ─── Generation Request ───────────────────────────────────────
#[derive(Debug, Deserialize)]
pub struct GenerateRequest {
pub prompt: String,
pub system: Option<String>,
pub temperature: Option<f32>,
pub max_tokens: Option<u32>,
}
// ─── OpenAI-Compatible Chat Response ─────────────────────────
#[derive(Debug, Deserialize)]
pub struct ChatResponse {
pub choices: Vec<ChatChoice>,
}
#[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<ChatMessage> {
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
}