(
db: &SqlitePool,
session_id: &str,
content: &str,
provider_id: Option<&str>,
model_id: Option<&str>,
on_token: Channel<String>,
app_handle: &AppHandle,
cancel_token:
| 241 | |
| 242 | #[allow(clippy::too_many_arguments)] |
| 243 | async fn send_message_inner( |
| 244 | db: &SqlitePool, |
| 245 | session_id: &str, |
| 246 | content: &str, |
| 247 | provider_id: Option<&str>, |
| 248 | model_id: Option<&str>, |
| 249 | on_token: Channel<String>, |
| 250 | app_handle: &AppHandle, |
| 251 | cancel_token: CancellationToken, |
| 252 | ) -> AppResult<()> { |
| 253 | let normalized = content.trim(); |
| 254 | if normalized.is_empty() { |
| 255 | return Err(AppError::Validation( |
| 256 | "Message content cannot be empty".to_string(), |
| 257 | )); |
| 258 | } |
| 259 | |
| 260 | let user_message = Message { |
| 261 | id: Uuid::new_v4().to_string(), |
| 262 | session_id: session_id.to_string(), |
| 263 | role: "user".to_string(), |
| 264 | content: normalized.to_string(), |
| 265 | created_at: now_rfc3339(), |
| 266 | }; |
| 267 | |
| 268 | sqlx::query( |
| 269 | "INSERT INTO messages (id, session_id, role, content, created_at) VALUES (?1, ?2, ?3, ?4, ?5)", |
| 270 | ) |
| 271 | .bind(&user_message.id) |
| 272 | .bind(&user_message.session_id) |
| 273 | .bind(&user_message.role) |
| 274 | .bind(&user_message.content) |
| 275 | .bind(&user_message.created_at) |
| 276 | .execute(db) |
| 277 | .await?; |
| 278 | |
| 279 | let provider = provider_service::get_provider_for_chat(db, provider_id).await?; |
| 280 | |
| 281 | // Pre-flight: check API key is configured (except for local providers like Ollama) |
| 282 | if provider.provider_type != "ollama" |
| 283 | && provider |
| 284 | .api_key |
| 285 | .as_deref() |
| 286 | .is_none_or(|k| k.trim().is_empty()) |
| 287 | { |
| 288 | return Err(AppError::Validation(format!( |
| 289 | "API key not configured for '{}'. Go to Settings → Providers and enter your API key.", |
| 290 | provider.name |
| 291 | ))); |
| 292 | } |
| 293 | |
| 294 | let raw_history = get_messages(db, session_id).await?; |
| 295 | let history = trim_history_for_llm(&raw_history); |
| 296 | |
| 297 | // Debug: log context size so token bloat is easy to spot |
| 298 | let total_chars: usize = history.iter().map(|m| m.content.len()).sum(); |
| 299 | let est_tokens = total_chars / 4; // rough estimate: 1 token ≈ 4 chars |
| 300 | log::debug!( |
no test coverage detected