feat: sql migrations and daemon state endpoint

This commit is contained in:
2026-02-14 15:51:28 +02:00
parent 3ce2fa3841
commit 9920bfcdee
10 changed files with 276 additions and 219 deletions

View File

@@ -0,0 +1,115 @@
use tauri::{Emitter, Manager, State};
use feshared::{
chatmessage::{Message, MessageHistory},
daemon::DaemonState,
};
use shared::ai::{ChatHistoryRequest, ChatRequest, DaemonStatusRequest};
use crate::AppState;
#[tauri::command]
pub fn toggle_popup(app_handle: tauri::AppHandle) {
match app_handle.get_webview_window("popup") {
Some(window) => {
let is_visible = window.is_visible().unwrap_or(false);
if is_visible {
window.hide().unwrap();
} else {
window.show().unwrap();
window.set_focus().unwrap();
let _ = window.emit("window-focused", ());
}
}
None => {
println!("ERROR: Window with label 'popup' not found!");
}
}
}
#[tauri::command]
pub async fn chat(
state: State<'_, AppState>,
prompt: String,
chat_id: Option<i64>,
) -> Result<Vec<Message>, String> {
let mut client = state.grpc_client.lock().await;
let request = tonic::Request::new(ChatRequest {
chat_id: chat_id,
text: Some(prompt),
});
match client.chat(request).await {
Ok(response) => {
let r = response.into_inner();
r.messages.iter().for_each(|m| {
if m.is_user {
println!(">>> {}", m.text)
} else {
println!("<<< {}", m.text)
}
});
Ok(r.messages
.iter()
.map(|msg| Message {
id: msg.id,
text: msg.text.clone(),
is_user: msg.is_user,
})
.collect())
}
Err(e) => {
println!("gRPC error: {}", e);
Err(format!("gRPC error: {}", e))
}
}
}
#[tauri::command]
pub async fn chat_history(
state: State<'_, AppState>,
chat_id: Option<i64>,
) -> Result<MessageHistory, String> {
let mut client = state.grpc_client.lock().await;
let result = client
.chat_history(ChatHistoryRequest { chat_id: None })
.await;
match result {
Ok(response) => {
let r = response.into_inner();
Ok(MessageHistory {
chat_id: None,
history: r
.history
.iter()
.map(|m| Message {
id: m.id,
is_user: m.is_user,
text: m.text.clone(),
})
.collect(),
})
}
Err(e) => Err(format!("gRPC error: {e}")),
}
}
#[tauri::command]
pub async fn daemon_state(state: State<'_, AppState>) -> Result<DaemonState, String> {
let mut client = state.grpc_client.lock().await;
let result = client.daemon_status(DaemonStatusRequest {}).await;
match result {
Ok(status) => {
let status_inner = status.into_inner();
Ok(DaemonState {
is_ok: status_inner.is_ok,
message: status_inner.message,
error: status_inner.error,
})
}
Err(e) => Ok(DaemonState {
is_ok: false,
message: None,
error: Some(e.message().to_string()),
}),
}
}

View File

@@ -1,112 +1,22 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use feshared::chatmessage::{Message, MessageHistory};
use shared::ai::{
ai_daemon_client::AiDaemonClient, ChatHistoryRequest, ChatRequest, PromptRequest,
};
use tauri::{Emitter, Manager, State};
mod commands;
use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, Modifiers, Shortcut, ShortcutState};
use tokio::sync::Mutex;
use tonic::{client, Response};
struct AppState {
use commands::{chat, chat_history, daemon_state, toggle_popup};
use shared::ai::ai_daemon_client::AiDaemonClient;
pub struct AppState {
grpc_client: Mutex<AiDaemonClient<tonic::transport::Channel>>,
current_chat: Mutex<Option<i64>>,
}
#[tauri::command]
fn toggle_popup(app_handle: tauri::AppHandle) {
match app_handle.get_webview_window("popup") {
Some(window) => {
let is_visible = window.is_visible().unwrap_or(false);
if is_visible {
window.hide().unwrap();
} else {
window.show().unwrap();
window.set_focus().unwrap();
let _ = window.emit("window-focused", ());
}
}
None => {
println!("ERROR: Window with label 'popup' not found!");
}
}
}
#[tauri::command]
async fn chat(
state: State<'_, AppState>,
prompt: String,
chat_id: Option<i64>,
) -> Result<Vec<Message>, String> {
let mut client = state.grpc_client.lock().await;
let request = tonic::Request::new(ChatRequest {
chat_id: chat_id,
text: Some(prompt),
});
match client.chat(request).await {
Ok(response) => {
let r = response.into_inner();
r.messages.iter().for_each(|m| {
if m.is_user {
println!(">>> {}", m.text)
} else {
println!("<<< {}", m.text)
}
});
Ok(r.messages
.iter()
.map(|msg| Message {
id: msg.id,
text: msg.text.clone(),
is_user: msg.is_user,
})
.collect())
}
Err(e) => {
println!("gRPC error: {}", e);
Err(format!("gRPC error: {}", e))
}
}
}
#[tauri::command]
async fn chat_history(
state: State<'_, AppState>,
chat_id: Option<i64>,
) -> Result<MessageHistory, String> {
let mut client = state.grpc_client.lock().await;
let result = client
.chat_history(ChatHistoryRequest { chat_id: None })
.await;
match result {
Ok(response) => {
let r = response.into_inner();
Ok(MessageHistory {
chat_id: None,
history: r
.history
.iter()
.map(|m| Message {
id: m.id,
is_user: m.is_user,
text: m.text.clone(),
})
.collect(),
})
}
Err(e) => Err(format!("gRPC error: {e}")),
}
}
#[tokio::main]
async fn main() {
let channel = tonic::transport::Channel::from_static("http://[::1]:50051")
.connect()
.await
.expect("Could not connect to daemon!");
let channel = tonic::transport::Channel::from_static("http://[::1]:50051").connect_lazy();
let client = AiDaemonClient::new(channel);
tauri::Builder::default()
@@ -115,7 +25,12 @@ async fn main() {
current_chat: Mutex::new(None),
})
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
.invoke_handler(tauri::generate_handler![toggle_popup, chat_history, chat,])
.invoke_handler(tauri::generate_handler![
toggle_popup,
chat_history,
chat,
daemon_state
])
.setup(|app| {
/* Auto-hide popup when focus is lost
if let Some(window) = app.get_webview_window("popup") {