Files
WhispAssist/src-tauri/src/models.rs
T
iamdoubz c88c04233c feat(mcp): add MCP settings fields to Settings (T10.4)
Adds mcp_enabled/mcp_transport/mcp_port/mcp_expose/mcp_expose_recordings
to Settings, all defaulting to off/none so an upgrading settings.json
gets a fully-local default (FR-MCP-1). The auth token itself never lives
here -- OS credential store only (see mcp::token, next commit).
2026-07-07 00:10:17 -05:00

351 lines
11 KiB
Rust

//! Shared data types crossing the IPC boundary and between services.
//! Mirrors `docs/03-data-model.md` and `docs/04-api-contracts.md`.
use serde::{Deserialize, Serialize};
pub type MeetingId = String; // uuid v4
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum BackendId {
Npu,
Nvidia,
Amd,
Intel,
Cpu,
}
impl BackendId {
pub fn as_str(&self) -> &'static str {
match self {
BackendId::Npu => "npu",
BackendId::Nvidia => "nvidia",
BackendId::Amd => "amd",
BackendId::Intel => "intel",
BackendId::Cpu => "cpu",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackendInfo {
pub id: BackendId,
pub name: String,
pub available: bool,
/// Lower rank = higher priority (NPU=0 … CPU=4).
pub rank: u8,
pub vram_mb: Option<u32>,
}
/// A downloadable/installed Whisper model (T3.7, FR-MODEL-1).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelInfo {
pub id: String, // e.g. "base.en-q5_1" — also the ggml filename stem
pub label: String,
pub size_mb: u32, // approximate download size
pub installed: bool,
pub active: bool,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum MeetingStatus {
Recording,
Transcribing,
Ready,
Recovering,
Error,
}
impl MeetingStatus {
pub fn as_str(&self) -> &'static str {
match self {
MeetingStatus::Recording => "recording",
MeetingStatus::Transcribing => "transcribing",
MeetingStatus::Ready => "ready",
MeetingStatus::Recovering => "recovering",
MeetingStatus::Error => "error",
}
}
/// Parses a `meetings.status` DB value. Unrecognized values map to `Error`
/// rather than panicking — a stored value should always be one written by
/// this app, but a row is not worth crashing the app over.
pub fn parse(s: &str) -> Self {
match s {
"recording" => MeetingStatus::Recording,
"transcribing" => MeetingStatus::Transcribing,
"ready" => MeetingStatus::Ready,
"recovering" => MeetingStatus::Recovering,
_ => MeetingStatus::Error,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TranscriptSegment {
pub id: u64,
pub start_ms: u64,
pub end_ms: u64,
/// Internal speaker label ("S1"…); name resolved at render time (FR-SPK-5).
pub speaker: String,
pub text: String,
pub confidence: Option<f32>,
pub interim: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpeakerInfo {
pub label: String, // "S1"
pub display_name: Option<String>,
pub participant_id: Option<String>,
}
/// A diarization result span before alignment to transcript segments.
#[derive(Debug, Clone)]
pub struct SpeakerSpan {
pub start_ms: u64,
pub end_ms: u64,
pub speaker: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MeetingListItem {
pub id: MeetingId,
pub title: String,
pub started_at: i64,
pub duration_secs: Option<i64>,
pub status: MeetingStatus,
/// Tags (Phase 8, FR-SEARCH-2), sorted.
pub tags: Vec<String>,
}
/// One full-text search hit (Phase 8, FR-SEARCH-1) — the same shape as
/// `MeetingListItem` plus a highlighted excerpt of what matched.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchHit {
pub id: MeetingId,
pub title: String,
pub started_at: i64,
pub duration_secs: Option<i64>,
pub status: MeetingStatus,
pub tags: Vec<String>,
pub snippet: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionItem {
pub id: Option<String>,
pub text: String,
pub owner: Option<String>,
pub due_at: Option<i64>,
pub confirmed: bool,
/// Schedule a local OS reminder for `due_at` (Phase 8, T8.6, FR-CAL-5).
#[serde(default)]
pub reminder_set: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CalendarEvent {
pub id: String,
pub source: String, // pst|graph|ics
pub subject: Option<String>,
pub organizer: Option<String>,
pub starts_at: Option<i64>,
pub ends_at: Option<i64>,
pub description: Option<String>,
/// The source's own stable id (e.g. an iCalendar UID) — used to dedup
/// re-imports of the same event rather than creating duplicate rows.
pub raw_uid: Option<String>,
}
/// A parsed attendee before persistence — no DB row/id exists until storage
/// upserts it (T6.1/T6.2, FR-CAL-1).
#[derive(Debug, Clone)]
pub struct AttendeeInfo {
pub name: String,
pub email: Option<String>,
pub role: Option<String>, // organizer|required|optional
}
/// An imported event with the attendees pulled from the same source record —
/// PST/ICS both list attendees inline per-appointment, so there's no
/// separate "fetch attendees for event X" round trip against the source.
#[derive(Debug, Clone)]
pub struct ImportedEvent {
pub event: CalendarEvent,
pub attendees: Vec<AttendeeInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Participant {
pub id: String,
pub name: String,
pub email: Option<String>,
pub role: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Settings {
pub theme: String, // system|light|dark
pub storage_root: String,
pub llm_provider: String, // ollama|custom|off
pub llm_endpoint: String,
pub llm_model: String,
/// Advanced Ollama tuning (T5.2): `{ system?, think?, keep_alive?, options: {…} }`.
/// Sparse — only user-overridden values are stored; `Null`/absent = all defaults.
#[serde(default)]
pub llm_advanced: serde_json::Value,
pub preferred_backend: String, // auto|npu|nvidia|amd|intel|cpu
pub whisper_model: String, // ModelInfo.id, e.g. "base.en-q5_1"
pub low_overhead: bool,
// Recording retention (ADR-0009). Default OFF.
pub default_record: bool,
pub consent_acknowledged: bool,
// Sync master switch (ADR-0010). Default OFF. Target rows live in the DB; secrets in OS keychain.
pub sync_enabled: bool,
// Storage retention policy (FR-STORE-2). None = no cap on that dimension.
pub retention_max_age_days: Option<u32>,
pub retention_max_size_gb: Option<u32>,
// Calendar / .pst (T6.2) — remembered so the user doesn't re-browse every
// launch. `pst_auto_sync` re-imports this path once at startup if set.
#[serde(default)]
pub pst_last_path: Option<String>,
#[serde(default)]
pub pst_auto_sync: bool,
// Audio capture device override (FR-CAP-1). `Device::get_id()` string;
// None = system default render device (loopback / system audio).
#[serde(default)]
pub audio_output_device: Option<String>,
// Microphone capture (FR-CAP-7): mix the user's own voice into the live
// transcript. Local-only, no egress; default ON. Turn off to transcribe just
// the system/loopback audio, as WA did before.
#[serde(default = "default_true")]
pub microphone_enabled: bool,
// Microphone device override — `Device::get_id()` string; None = system
// default capture device.
#[serde(default)]
pub audio_input_device: Option<String>,
// Local MCP server (Phase 10b, ADR-0011, FR-MCP-1). OFF by default; the
// auth token itself is NEVER stored here — only in the OS credential
// store (see `mcp::token`). `mcp_expose` is one of none|selected|all;
// `mcp_expose_recordings` gates access to meetings with retained audio
// (ADR-0009) regardless of `mcp_expose` (FR-MCP-3).
#[serde(default)]
pub mcp_enabled: bool,
#[serde(default = "default_mcp_transport")]
pub mcp_transport: String, // http|stdio
#[serde(default = "default_mcp_port")]
pub mcp_port: u16,
#[serde(default = "default_mcp_expose")]
pub mcp_expose: String, // none|selected|all
#[serde(default)]
pub mcp_expose_recordings: bool,
}
fn default_mcp_transport() -> String {
"http".into()
}
fn default_mcp_port() -> u16 {
4849
}
fn default_mcp_expose() -> String {
"none".into()
}
/// serde default for a `bool` field that should be `true` when absent from an
/// older `settings.json` (so upgrading users get the microphone, FR-CAP-7).
fn default_true() -> bool {
true
}
// ---- Sync (ADR-0010) ----
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum SyncKind {
WebDav,
OneDrive,
Dropbox,
Box,
}
/// What the UI sees about a target. NEVER contains the secret (FR-SYNC-6).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncTargetInfo {
pub id: String,
pub name: String,
pub kind: SyncKind,
pub provider_hint: Option<String>, // nextcloud|owncloud|cloudreve|seafile|synology|generic
pub base_url: Option<String>,
pub remote_base_path: String,
pub username: Option<String>,
pub enabled: bool,
pub third_party: bool,
pub host: Option<String>,
// Upload selection + options, so the UI can pre-fill an edit form (the secret
// is never included — FR-SYNC-6).
pub upload_transcript: bool,
pub upload_notes: bool,
pub upload_summary: bool,
pub upload_recording: bool,
pub trigger_on_finalize: bool,
pub allow_plaintext_lan: bool,
pub encrypt_before_upload: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncJobInfo {
pub id: String,
pub target_id: String,
pub meeting_id: MeetingId,
pub artifact: String, // transcript|notes|summary|recording
pub status: String, // pending|uploading|done|failed|skipped
pub attempts: u32,
pub bytes_sent: u64,
pub bytes_total: Option<u64>,
pub last_error: Option<String>,
}
// ---- Feature briefs + MCP (ADR-0011) ----
/// Agent-ready spec distilled from a meeting; served by the MCP `get_feature_brief` tool.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureBrief {
pub id: String,
pub meeting_id: MeetingId,
pub title: String,
pub problem: String,
pub desired_outcome: String,
pub acceptance_criteria: Vec<String>,
pub target_repo: Option<String>,
pub context_excerpts: Vec<ContextExcerpt>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextExcerpt {
pub speaker: String,
pub text: String,
}
/// Lightweight listing row (no full body).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureBriefInfo {
pub id: String,
pub meeting_id: MeetingId,
pub title: String,
pub target_repo: Option<String>,
pub exposed: bool,
}
/// One row of the MCP audit log (FR-MCP-5).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpAccessEntry {
pub at: i64,
pub tool: String,
pub meeting_id: Option<MeetingId>,
pub client: Option<String>,
}