From c8952df591013afe79e89f16b3c8a1d845ec5067 Mon Sep 17 00:00:00 2001 From: Jake Date: Sun, 19 Apr 2026 20:48:09 +0100 Subject: [PATCH] feat(tauri): expose profile + profile_term commands --- src-tauri/src/commands/mod.rs | 1 + src-tauri/src/commands/profiles.rs | 156 +++++++++++++++++++++++++++++ src-tauri/src/lib.rs | 9 ++ 3 files changed, 166 insertions(+) create mode 100644 src-tauri/src/commands/profiles.rs diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index a931b71..8e10e54 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -5,6 +5,7 @@ pub mod hardware; pub mod hotkey; pub mod live; pub mod models; +pub mod profiles; pub mod transcription; pub mod tasks; pub mod transcripts; diff --git a/src-tauri/src/commands/profiles.rs b/src-tauri/src/commands/profiles.rs new file mode 100644 index 0000000..473db3c --- /dev/null +++ b/src-tauri/src/commands/profiles.rs @@ -0,0 +1,156 @@ +// Tauri commands wrapping kon_storage profile + profile_term CRUD. +// Pattern mirrors tasks.rs — flat imports from `kon_storage` with a `db_` +// alias prefix to avoid name collisions with the command functions, plain +// snake_case parameters (Tauri 2.x auto-converts camelCase JS keys), +// `.map_err(|e| e.to_string())` for error conversion, and camelCase DTOs +// for the frontend-facing shape (the storage row types don't derive +// Serialize — same reason TaskRow → TaskDto exists). +// +// The Default profile is guarded at the storage layer (SQLite triggers + +// Rust pre-checks), so no extra guarding is needed here. + +use serde::Serialize; + +use kon_storage::{ + add_profile_term as db_add_profile_term, create_profile as db_create_profile, + delete_profile as db_delete_profile, delete_profile_term as db_delete_profile_term, + get_profile as db_get_profile, list_profile_terms as db_list_profile_terms, + list_profiles as db_list_profiles, update_profile as db_update_profile, ProfileRow, + ProfileTermRow, +}; + +use crate::AppState; + +/// Frontend-facing profile shape. Matches the object the Svelte profile +/// picker + editor will consume. +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ProfileDto { + pub id: String, + pub name: String, + pub initial_prompt: String, + pub created_at: String, +} + +impl From for ProfileDto { + fn from(r: ProfileRow) -> Self { + Self { + id: r.id, + name: r.name, + initial_prompt: r.initial_prompt, + created_at: r.created_at, + } + } +} + +/// Frontend-facing profile term (dictionary-style vocabulary hint). +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ProfileTermDto { + pub id: String, + pub profile_id: String, + pub term: String, + pub note: String, + pub created_at: String, +} + +impl From for ProfileTermDto { + fn from(r: ProfileTermRow) -> Self { + Self { + id: r.id, + profile_id: r.profile_id, + term: r.term, + note: r.note, + created_at: r.created_at, + } + } +} + +#[tauri::command] +pub async fn list_profiles_cmd( + state: tauri::State<'_, AppState>, +) -> Result, String> { + db_list_profiles(&state.db) + .await + .map(|rows| rows.into_iter().map(ProfileDto::from).collect()) + .map_err(|e| e.to_string()) +} + +#[tauri::command] +pub async fn get_profile_cmd( + state: tauri::State<'_, AppState>, + id: String, +) -> Result, String> { + db_get_profile(&state.db, &id) + .await + .map(|opt| opt.map(ProfileDto::from)) + .map_err(|e| e.to_string()) +} + +#[tauri::command] +pub async fn create_profile_cmd( + state: tauri::State<'_, AppState>, + name: String, + initial_prompt: String, +) -> Result { + db_create_profile(&state.db, &name, &initial_prompt) + .await + .map(ProfileDto::from) + .map_err(|e| e.to_string()) +} + +#[tauri::command] +pub async fn update_profile_cmd( + state: tauri::State<'_, AppState>, + id: String, + name: String, + initial_prompt: String, +) -> Result<(), String> { + db_update_profile(&state.db, &id, &name, &initial_prompt) + .await + .map_err(|e| e.to_string()) +} + +#[tauri::command] +pub async fn delete_profile_cmd( + state: tauri::State<'_, AppState>, + id: String, +) -> Result<(), String> { + db_delete_profile(&state.db, &id) + .await + .map_err(|e| e.to_string()) +} + +#[tauri::command] +pub async fn list_profile_terms_cmd( + state: tauri::State<'_, AppState>, + profile_id: String, +) -> Result, String> { + db_list_profile_terms(&state.db, &profile_id) + .await + .map(|rows| rows.into_iter().map(ProfileTermDto::from).collect()) + .map_err(|e| e.to_string()) +} + +#[tauri::command] +pub async fn add_profile_term_cmd( + state: tauri::State<'_, AppState>, + profile_id: String, + term: String, + note: String, +) -> Result { + db_add_profile_term(&state.db, &profile_id, &term, ¬e) + .await + .map(ProfileTermDto::from) + .map_err(|e| e.to_string()) +} + +#[tauri::command] +pub async fn delete_profile_term_cmd( + state: tauri::State<'_, AppState>, + id: String, +) -> Result<(), String> { + db_delete_profile_term(&state.db, &id) + .await + .map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f845b32..ced5e5c 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -265,6 +265,15 @@ pub fn run() { commands::tasks::decompose_and_store, commands::tasks::list_subtasks_cmd, commands::tasks::complete_subtask_cmd, + // Profiles + profile terms (canonical SQLite-backed profile CRUD) — Task 12 + commands::profiles::list_profiles_cmd, + commands::profiles::get_profile_cmd, + commands::profiles::create_profile_cmd, + commands::profiles::update_profile_cmd, + commands::profiles::delete_profile_cmd, + commands::profiles::list_profile_terms_cmd, + commands::profiles::add_profile_term_cmd, + commands::profiles::delete_profile_term_cmd, // Transcripts (canonical SQLite-backed history) — Day 4 commands::transcripts::add_transcript, commands::transcripts::list_transcripts,