feat: generate title

This commit is contained in:
2026-05-11 18:28:04 +02:00
parent 1bf4cf2ca3
commit b5ef7e4881
3 changed files with 102 additions and 6 deletions
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE chat.conversation SET title = $1 WHERE id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Uuid"
]
},
"nullable": []
},
"hash": "ea0755640e64de9eb034de9c1d94bf0f09143b2ddf200757b5e8ac53e1e8a993"
}
+35 -3
View File
@@ -36,11 +36,17 @@ where
Ok(rec.id) Ok(rec.id)
} }
#[derive(Debug)]
pub enum ConversationState {
Existing(Uuid),
Created(Uuid),
}
pub async fn get_or_create_conversation( pub async fn get_or_create_conversation(
pool: &PgPool, pool: &PgPool,
conversation_id: Option<Uuid>, conversation_id: Option<Uuid>,
user_id: Uuid, user_id: Uuid,
) -> Result<Uuid, DbError> { ) -> Result<ConversationState, DbError> {
tracing::debug!("Testing conversation"); tracing::debug!("Testing conversation");
let mut tx = pool.begin().await?; let mut tx = pool.begin().await?;
@@ -56,12 +62,38 @@ pub async fn get_or_create_conversation(
match conversation_id { match conversation_id {
Some(id) => { Some(id) => {
validate_conversation(&mut *conn, id).await?; validate_conversation(&mut *conn, id).await?;
id ConversationState::Existing(id)
} }
None => create_conversation(&mut *conn, user_id).await?, None => ConversationState::Created(create_conversation(&mut *conn, user_id).await?),
} }
}; };
tx.commit().await?; tx.commit().await?;
Ok(result) Ok(result)
} }
pub async fn set_conversation_title(
pool: &PgPool,
conversation_id: Uuid,
user_id: Uuid,
title: &str,
) -> Result<(), DbError> {
let mut tx = pool.begin().await?;
let conn = tx.acquire().await?;
sqlx::query(r#"SELECT set_config('app.current_user_id', $1::text, true)"#)
.bind(user_id.to_string())
.execute(&mut *conn)
.await?;
sqlx::query!(
r#"UPDATE chat.conversation SET title = $1 WHERE id = $2"#,
title,
conversation_id
)
.execute(&mut *conn)
.await?;
tx.commit().await?;
Ok(())
}
+52 -3
View File
@@ -1,4 +1,7 @@
use crate::middlewares::auth::middleware::Auth; use crate::{
databases::postgres::chat::ConversationState, dto::api::BaseLLMRequest,
middlewares::auth::middleware::Auth, providers::ollama::client::OllamaProvider,
};
use axum::{ use axum::{
Json, Json,
extract::{Extension, State}, extract::{Extension, State},
@@ -8,8 +11,9 @@ use axum::{
}, },
}; };
use crate::databases::postgres::chat::get_or_create_conversation; use crate::databases::postgres::chat::{get_or_create_conversation, set_conversation_title};
use crate::dto::api; use crate::dto::api;
use crate::dto::api::CompletionRequest;
use crate::providers::ollama::errors::into_http_response; use crate::providers::ollama::errors::into_http_response;
use crate::state::app_state::AppState; use crate::state::app_state::AppState;
@@ -128,11 +132,35 @@ pub async fn chat_completions(
body.conversation_id.is_some() body.conversation_id.is_some()
); );
let conversation_id = let conversation_state =
get_or_create_conversation(&state.postgres, body.conversation_id, auth.user_id()) get_or_create_conversation(&state.postgres, body.conversation_id, auth.user_id())
.await .await
.map_err(crate::databases::postgres::errors::into_http_response)?; .map_err(crate::databases::postgres::errors::into_http_response)?;
let conversation_id = match &conversation_state {
ConversationState::Existing(uuid) => *uuid,
ConversationState::Created(uuid) => {
let conversation_id = *uuid;
let title =
generate_conversation_title(&state.ollama, body.messages[0].content.as_str())
.await;
if let Err(e) =
set_conversation_title(&state.postgres, conversation_id, auth.user_id(), &title)
.await
{
tracing::warn!(
conversation_id = %conversation_id,
error = %e,
"Failed to set conversation title, continuing with no title"
);
}
tracing::debug!(conversation_id = %conversation_id, %title, "generated conversation title");
conversation_id
}
};
tracing::debug!("Using conversation_id: {:?}", conversation_id); tracing::debug!("Using conversation_id: {:?}", conversation_id);
} }
@@ -156,3 +184,24 @@ pub async fn chat_completions(
Ok(Json(response).into_response()) Ok(Json(response).into_response())
} }
} }
async fn generate_conversation_title(ollama: &OllamaProvider, first_message: &str) -> String {
let request = CompletionRequest {
base: BaseLLMRequest {
model: "llama3:latest".to_string(),
..Default::default()
},
prompt: format!(
"Generate a short title (max 6 words) for the following chat conversation: {}",
first_message
),
};
ollama
.completions(&request)
.await
.ok()
.and_then(|r| r.choices.first().map(|c| c.text.trim().to_string()))
.filter(|t| !t.is_empty())
.unwrap_or_else(|| "New Conversation".to_string()) // ← default on any error
}