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,5 @@
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
is_user BOOL NOT NULL
);

View File

@@ -1,6 +1,5 @@
use anyhow::Result;
use directories::ProjectDirs;
use shared::ai::ChatMessage as CMessage;
use sqlx::sqlite::SqliteConnectOptions;
use sqlx::Row;
use sqlx::SqlitePool;
@@ -20,14 +19,6 @@ pub trait ChatRepository {
async fn get_latest_messages(&self) -> Result<Vec<ChatMessage>>;
}
pub fn message_to_dto(msg: &ChatMessage) -> CMessage {
CMessage {
id: msg.id,
text: msg.text.clone(),
is_user: msg.is_user,
}
}
pub struct SqliteChatRepository {
pool: SqlitePool,
}
@@ -49,16 +40,10 @@ impl SqliteChatRepository {
)
.await?;
sqlx::query(
"CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
is_user BOOL NOT NULL
)",
)
.execute(&pool)
.await
.inspect_err(|e| println!("sql error: {}", e))?;
sqlx::migrate!("./migrations")
.run(&pool)
.await
.inspect_err(|e| eprintln!("Migration failed! {}", e))?;
Ok(Self { pool })
}

View File

@@ -0,0 +1,82 @@
use crate::chatpersistence::{ChatMessage, ChatRepository};
use shared::ai::ai_daemon_server::AiDaemon;
use shared::ai::{
ChatHistoryRequest, ChatHistoryResponse, ChatMessage as CMessage, ChatRequest as CRequest,
ChatResponse as CResponse, DaemonStatusRequest, DaemonStatusResponse,
};
use std::sync::Arc;
use tonic::{Code, Request, Response, Status};
pub struct DaemonServer {
repo: Arc<dyn ChatRepository + Send + Sync>,
}
impl DaemonServer {
pub fn new(repo: Arc<dyn ChatRepository + Send + Sync>) -> Self {
Self { repo }
}
}
#[tonic::async_trait]
impl AiDaemon for DaemonServer {
async fn chat(&self, request: Request<CRequest>) -> Result<Response<CResponse>, Status> {
let r = request.into_inner();
let user_message = message_to_dto(
&self
.repo
.save_message(r.text(), &true)
.await
.map_err(|e| Status::new(Code::Internal, e.to_string()))?,
);
let response_text = format!("Pong: {}", r.text());
let ai_message = message_to_dto(
&self
.repo
.save_message(response_text.as_str(), &false)
.await
.map_err(|e| Status::new(Code::Internal, e.to_string()))?,
);
let response = CResponse {
chat_id: 1,
messages: vec![user_message, ai_message],
};
return Ok(Response::new(response));
}
async fn chat_history(
&self,
_: Request<ChatHistoryRequest>,
) -> Result<Response<ChatHistoryResponse>, Status> {
let messages = self
.repo
.get_latest_messages()
.await
.map_err(|e| Status::new(Code::Internal, e.to_string()))?;
let response = ChatHistoryResponse {
chat_id: 1,
history: messages.iter().map(|m| message_to_dto(m)).collect(),
};
Ok(Response::new(response))
}
async fn daemon_status(
&self,
_: Request<DaemonStatusRequest>,
) -> Result<Response<DaemonStatusResponse>, Status> {
let status = DaemonStatusResponse {
is_ok: true,
message: None,
error: None,
};
Ok(Response::new(status))
}
}
pub fn message_to_dto(msg: &ChatMessage) -> CMessage {
CMessage {
id: msg.id,
text: msg.text.clone(),
is_user: msg.is_user,
}
}

View File

@@ -1,94 +1,15 @@
mod chatpersistence;
mod daemongrpc;
use std::sync::atomic::AtomicI64;
use std::sync::Arc;
use genai::chat::{ChatMessage, ChatRequest};
use genai::Client;
use shared::ai::ai_daemon_server::{AiDaemon, AiDaemonServer};
use shared::ai::{
ChatHistoryRequest, ChatHistoryResponse, ChatMessage as CMessage, ChatRequest as CRequest,
ChatResponse as CResponse, PromptRequest, PromptResponse,
};
use tonic::{transport::Server, Code, Request, Response, Status};
use shared::ai::ai_daemon_server::AiDaemonServer;
use tonic::transport::Server;
use chatpersistence::SqliteChatRepository;
use crate::chatpersistence::{message_to_dto, ChatRepository};
pub struct DaemonServer {
message_counter: AtomicI64,
repo: Arc<dyn ChatRepository + Send + Sync>,
}
impl DaemonServer {
pub fn new(repo: Arc<dyn ChatRepository + Send + Sync>) -> Self {
Self {
message_counter: AtomicI64::new(0),
repo,
}
}
}
#[tonic::async_trait]
impl AiDaemon for DaemonServer {
async fn prompt(
&self,
request: Request<PromptRequest>,
) -> Result<Response<PromptResponse>, Status> {
let remote_a = request.remote_addr();
let prompt_value = request.into_inner().prompt;
println!("Request from {:?}: {:?}", remote_a, prompt_value);
let client = Client::default();
let response = prompt_ollama(&client, "llama3.2", prompt_value.as_str())
.await
.unwrap_or_else(|err| format!("Prompt error: {}", err));
println!("Respone: {}", response);
let reply = PromptResponse { response: response };
Ok(Response::new(reply))
}
async fn chat(&self, request: Request<CRequest>) -> Result<Response<CResponse>, Status> {
let r = request.into_inner();
let user_message = message_to_dto(
&self
.repo
.save_message(r.text(), &true)
.await
.map_err(|e| Status::new(Code::Internal, e.to_string()))?,
);
let response_text = format!("Pong: {}", r.text());
let ai_message = message_to_dto(
&self
.repo
.save_message(response_text.as_str(), &false)
.await
.map_err(|e| Status::new(Code::Internal, e.to_string()))?,
);
let response = CResponse {
chat_id: 1,
messages: vec![user_message, ai_message],
};
return Ok(Response::new(response));
}
async fn chat_history(
&self,
_: Request<ChatHistoryRequest>,
) -> Result<Response<ChatHistoryResponse>, Status> {
let messages = self
.repo
.get_latest_messages()
.await
.map_err(|e| Status::new(Code::Internal, e.to_string()))?;
let response = ChatHistoryResponse {
chat_id: 1,
history: messages.iter().map(|m| message_to_dto(m)).collect(),
};
Ok(Response::new(response))
}
}
use daemongrpc::DaemonServer;
async fn prompt_ollama(
client: &Client,