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).
351 lines
11 KiB
Rust
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>,
|
|
}
|