Generate a short title for a conversation using the same provider/model.
(
db: &SqlitePool,
session_id: &str,
provider_id: Option<&str>,
model_id: Option<&str>,
)
| 863 | |
| 864 | /// Generate a short title for a conversation using the same provider/model. |
| 865 | pub async fn generate_title( |
| 866 | db: &SqlitePool, |
| 867 | session_id: &str, |
| 868 | provider_id: Option<&str>, |
| 869 | model_id: Option<&str>, |
| 870 | ) -> AppResult<String> { |
| 871 | let provider = provider_service::get_provider_for_chat(db, provider_id).await?; |
| 872 | let model = model_id.unwrap_or(&provider.model); |
| 873 | let history = get_messages(db, session_id).await?; |
| 874 | |
| 875 | if history.is_empty() { |
| 876 | return Ok("New Chat".to_string()); |
| 877 | } |
| 878 | |
| 879 | // Build a compact summary of the conversation (max first 2 exchanges) |
| 880 | let snippet: Vec<Value> = history |
| 881 | .iter() |
| 882 | .filter(|m| m.role != "system") |
| 883 | .take(4) |
| 884 | .map(|m| { |
| 885 | let content = if m.content.len() > 200 { |
| 886 | format!("{}…", &m.content[..200]) |
| 887 | } else { |
| 888 | m.content.clone() |
| 889 | }; |
| 890 | serde_json::json!({ "role": m.role, "content": content }) |
| 891 | }) |
| 892 | .collect(); |
| 893 | |
| 894 | let mut messages = vec![serde_json::json!({ |
| 895 | "role": "system", |
| 896 | "content": "Generate a short title (2-5 words) for this conversation. Reply with ONLY the title, nothing else. No quotes, no punctuation at the end. Examples: 'React Auth Setup', 'Greeting', 'Python Bug Fix', 'Database Migration Help'." |
| 897 | })]; |
| 898 | messages.extend(snippet); |
| 899 | messages.push(serde_json::json!({ |
| 900 | "role": "user", |
| 901 | "content": "Generate a short title for the conversation above." |
| 902 | })); |
| 903 | |
| 904 | let title = if provider.uses_anthropic_format() { |
| 905 | generate_title_anthropic(&provider, model, &messages).await? |
| 906 | } else { |
| 907 | generate_title_openai(&provider, model, &messages).await? |
| 908 | }; |
| 909 | |
| 910 | // Clean up: remove quotes, trim, cap length |
| 911 | let cleaned = title |
| 912 | .trim() |
| 913 | .trim_matches('"') |
| 914 | .trim_matches('\'') |
| 915 | .trim_end_matches('.') |
| 916 | .trim(); |
| 917 | |
| 918 | let final_title = if cleaned.is_empty() { |
| 919 | "New Chat".to_string() |
| 920 | } else if cleaned.len() > 50 { |
| 921 | let cut = &cleaned[..50]; |
| 922 | let last_space = cut.rfind(' ').unwrap_or(50); |
nothing calls this directly
no test coverage detected