use dioxus::prelude::*; use serde::{Deserialize, Serialize}; // ── Response types ── #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ChatApiResponse { pub data: ChatResponseData, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ChatResponseData { pub message: String, #[serde(default)] pub sources: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SourceRef { pub file_path: String, pub qualified_name: String, pub start_line: u32, pub end_line: u32, pub language: String, pub snippet: String, pub score: f64, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct EmbeddingStatusResponse { pub data: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct EmbeddingBuildData { pub repo_id: String, pub status: String, pub total_chunks: u32, pub embedded_chunks: u32, pub embedding_model: String, pub error_message: Option, #[serde(default)] pub started_at: Option, #[serde(default)] pub completed_at: Option, } // ── Chat message history type ── #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChatHistoryMessage { pub role: String, pub content: String, } // ── Server functions ── #[server] pub async fn send_chat_message( repo_id: String, message: String, history: Vec, ) -> Result { // Chat uses a longer timeout because the LLM round-trip can be slow; // agent_request doesn't expose a per-call timeout so we layer one on. let resp = super::agent_client::agent_request( reqwest::Method::POST, &format!("/api/v1/chat/{repo_id}"), ) .await? .timeout(std::time::Duration::from_secs(120)) .json(&serde_json::json!({ "message": message, "history": history, })) .send() .await .map_err(|e| ServerFnError::new(format!("Request failed: {e}")))?; let text = resp .text() .await .map_err(|e| ServerFnError::new(format!("Failed to read response: {e}")))?; let body: ChatApiResponse = serde_json::from_str(&text) .map_err(|e| ServerFnError::new(format!("Failed to parse response: {e} — body: {text}")))?; Ok(body) } #[server] pub async fn trigger_embedding_build(repo_id: String) -> Result<(), ServerFnError> { super::agent_client::agent_request( reqwest::Method::POST, &format!("/api/v1/chat/{repo_id}/build-embeddings"), ) .await? .send() .await .map_err(|e| ServerFnError::new(e.to_string()))?; Ok(()) } #[server] pub async fn fetch_embedding_status( repo_id: String, ) -> Result { let resp = super::agent_client::agent_get(&format!("/api/v1/chat/{repo_id}/status")) .await? .send() .await .map_err(|e| ServerFnError::new(e.to_string()))?; let body: EmbeddingStatusResponse = resp .json() .await .map_err(|e| ServerFnError::new(e.to_string()))?; Ok(body) }