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 { let state: super::server_state::ServerState = dioxus_fullstack::FullstackContext::extract().await?; let url = format!("{}/api/v1/chat/{repo_id}", state.agent_api_url); let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(120)) .build() .map_err(|e| ServerFnError::new(e.to_string()))?; let resp = client .post(&url) .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> { let state: super::server_state::ServerState = dioxus_fullstack::FullstackContext::extract().await?; let url = format!( "{}/api/v1/chat/{repo_id}/build-embeddings", state.agent_api_url ); let client = reqwest::Client::new(); client .post(&url) .send() .await .map_err(|e| ServerFnError::new(e.to_string()))?; Ok(()) } #[server] pub async fn fetch_embedding_status( repo_id: String, ) -> Result { let state: super::server_state::ServerState = dioxus_fullstack::FullstackContext::extract().await?; let url = format!("{}/api/v1/chat/{repo_id}/status", state.agent_api_url); let resp = reqwest::get(&url) .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) }