use std::path::PathBuf;
use chrono::Local;
use serde::Serialize;
use tauri::State;
use crate::meetings::{Meeting, MeetingDetail, Store};
use crate::openrouter::{Client, KeyInfo};
use crate::settings::{mask_key, Secrets, Settings, OPENROUTER_KEY};
pub struct AppState {
settings_path: PathBuf,
store: Store,
secrets: Box<dyn Secrets>,
}
impl AppState {
pub fn new(settings_path: PathBuf, store: Store, secrets: Box<dyn Secrets>) -> Self {
Self {
settings_path,
store,
secrets,
}
}
fn settings(&self) -> Result<Settings, String> {
Settings::load(&self.settings_path).map_err(|e| e.to_string())
}
fn client(&self) -> Result<Client, String> {
match self.secrets.get(OPENROUTER_KEY).map_err(|e| e.to_string())? {
Some(key) => Ok(Client::new(key)),
None => Err("Ключ OpenRouter не задан — добавьте его в настройках".into()),
}
}
}
#[derive(Serialize)]
pub struct SettingsView {
settings: Settings,
openrouter_key: Option<String>,
meetings_dir: String,
}
#[tauri::command]
pub fn get_settings(state: State<'_, AppState>) -> Result<SettingsView, String> {
Ok(SettingsView {
settings: state.settings()?,
openrouter_key: state
.secrets
.get(OPENROUTER_KEY)
.map_err(|e| e.to_string())?
.map(|k| mask_key(&k)),
meetings_dir: state.store.root().display().to_string(),
})
}
#[tauri::command]
pub fn save_settings(state: State<'_, AppState>, settings: Settings) -> Result<(), String> {
settings.save(&state.settings_path).map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn set_openrouter_key(state: State<'_, AppState>, key: String) -> Result<KeyInfo, String> {
let key = key.trim().to_string();
if key.is_empty() {
return Err("Вставьте ключ OpenRouter".into());
}
let info = Client::new(key.clone())
.key_info()
.await
.map_err(|e| e.to_string())?;
state
.secrets
.set(OPENROUTER_KEY, &key)
.map_err(|e| e.to_string())?;
Ok(info)
}
#[tauri::command]
pub async fn check_openrouter_key(state: State<'_, AppState>) -> Result<KeyInfo, String> {
let client = state.client()?;
client.key_info().await.map_err(|e| e.to_string())
}
#[tauri::command]
pub fn delete_openrouter_key(state: State<'_, AppState>) -> Result<(), String> {
state.secrets.delete(OPENROUTER_KEY).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn list_meetings(state: State<'_, AppState>) -> Result<Vec<Meeting>, String> {
state.store.list().map_err(|e| e.to_string())
}
#[tauri::command]
pub fn get_meeting(state: State<'_, AppState>, id: String) -> Result<MeetingDetail, String> {
state.store.get(&id).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn import_meeting(state: State<'_, AppState>, title: String, text: String) -> Result<Meeting, String> {
state
.store
.import(&title, &text, Local::now())
.map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn analyze_meeting(state: State<'_, AppState>, id: String) -> Result<Meeting, String> {
let detail = state.store.get(&id).map_err(|e| e.to_string())?;
let model = state.settings()?.analysis_model;
let client = state.client()?;
let (analysis, usage) = client
.analyze(&model, &detail.meeting.title, &detail.transcript)
.await
.map_err(|e| e.to_string())?;
state
.store
.save_analysis(&id, &analysis, &model, usage, Local::now())
.map_err(|e| e.to_string())
}