feat: Add analyze module for LLM integration
This commit introduces the `analyze` module, which orchestrates communication with Large Language Models (LLMs) for text summarization and analysis. Key components include: - `llm.rs`: Contains the `LlmError` enum and the `chat_once` function for interacting with OpenAI-compatible LLM APIs. - `prompt.rs`: Defines the system prompt and logic for rendering user content from analysis inputs. - `recovery.rs`: Implements logic to scan for and re-enqueue pending analysis jobs upon server startup. - `worker.rs`: The core worker that consumes analysis jobs, processes them with the LLM, and writes the output. - `mod.rs`: Defines `AnalyzeJob` and associated channel types for inter-component communication. This module enables the server to process dictated recordings, summarize them using an LLM, and store the results.
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
pub mod llm;
|
||||
pub mod prompt;
|
||||
pub mod recovery;
|
||||
pub mod worker;
|
||||
|
||||
/// A single request to analyze a case. Payload is tiny (path + u32), so the
|
||||
/// channel is unbounded — persistence lives in `analysis_input_v{N}.json` on
|
||||
/// disk, not in the queue. Recovery re-enqueues any inputs the server may
|
||||
/// have held at crash time.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AnalyzeJob {
|
||||
pub case_dir: PathBuf,
|
||||
pub version: u32,
|
||||
}
|
||||
|
||||
pub type AnalyzeSender = mpsc::UnboundedSender<AnalyzeJob>;
|
||||
pub type AnalyzeReceiver = mpsc::UnboundedReceiver<AnalyzeJob>;
|
||||
|
||||
pub fn channel() -> (AnalyzeSender, AnalyzeReceiver) {
|
||||
mpsc::unbounded_channel()
|
||||
}
|
||||
|
||||
/// Persistent input for a single analysis run. Written by the close-case
|
||||
/// handler, consumed by the analyze worker, and left on disk indefinitely as
|
||||
/// an audit trail of what was sent to the LLM.
|
||||
///
|
||||
/// Timestamps are stored as ISO-8601 strings (lexicographic order matches
|
||||
/// chronological order, so string comparison is sufficient for the
|
||||
/// "Nachtrag" detection the UI needs later).
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AnalysisInput {
|
||||
pub version: u32,
|
||||
/// Latest filesystem mtime (server clock) of any `.m4a` in the case at
|
||||
/// the moment the close button was clicked. Used later to detect late
|
||||
/// arrivals that should trigger a re-analysis prompt.
|
||||
pub last_recording_mtime: String,
|
||||
pub recordings: Vec<RecordingInput>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RecordingInput {
|
||||
/// Recording timestamp from the watch (filename-derived, ISO-8601).
|
||||
pub recorded_at: String,
|
||||
pub text: String,
|
||||
}
|
||||
Reference in New Issue
Block a user