fix(dash): improved dashboard and some bug fixes #8

Merged
sharang merged 8 commits from feat/CAI-4-dashboard into main 2026-02-20 12:17:10 +00:00
9 changed files with 62 additions and 32 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

25
assets/favicon.svg Normal file
View File

@@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
<!-- Shield body -->
<path d="M32 4L8 16v16c0 14.4 10.24 27.2 24 32 13.76-4.8 24-17.6 24-32V16L32 4z"
fill="#4B3FE0" fill-opacity="0.12" stroke="#4B3FE0" stroke-width="2"
stroke-linejoin="round"/>
<!-- Inner shield highlight -->
<path d="M32 10L14 19v11c0 11.6 7.68 22 18 26 10.32-4 18-14.4 18-26V19L32 10z"
fill="none" stroke="#4B3FE0" stroke-width="1" stroke-opacity="0.3"
stroke-linejoin="round"/>
<!-- Neural network nodes -->
<circle cx="32" cy="24" r="3.5" fill="#38B2AC"/>
<circle cx="22" cy="36" r="3" fill="#38B2AC"/>
<circle cx="42" cy="36" r="3" fill="#38B2AC"/>
<circle cx="27" cy="48" r="2.5" fill="#38B2AC" fill-opacity="0.7"/>
<circle cx="37" cy="48" r="2.5" fill="#38B2AC" fill-opacity="0.7"/>
<!-- Neural network edges -->
<line x1="32" y1="24" x2="22" y2="36" stroke="#38B2AC" stroke-width="1.2" stroke-opacity="0.6"/>
<line x1="32" y1="24" x2="42" y2="36" stroke="#38B2AC" stroke-width="1.2" stroke-opacity="0.6"/>
<line x1="22" y1="36" x2="27" y2="48" stroke="#38B2AC" stroke-width="1" stroke-opacity="0.4"/>
<line x1="22" y1="36" x2="37" y2="48" stroke="#38B2AC" stroke-width="1" stroke-opacity="0.4"/>
<line x1="42" y1="36" x2="27" y2="48" stroke="#38B2AC" stroke-width="1" stroke-opacity="0.4"/>
<line x1="42" y1="36" x2="37" y2="48" stroke="#38B2AC" stroke-width="1" stroke-opacity="0.4"/>
<!-- Cross edge for connectivity -->
<line x1="22" y1="36" x2="42" y2="36" stroke="#38B2AC" stroke-width="0.8" stroke-opacity="0.3"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -12,7 +12,7 @@ self.addEventListener("install", (event) => {
"/",
"/dashboard",
"/assets/logo.svg",
"/assets/favicon.ico",
"/assets/favicon.svg",
])
)
);

View File

@@ -1733,6 +1733,9 @@
.text-center {
text-align: center;
}
.lowercase {
text-transform: lowercase;
}
.outline {
outline-style: var(--tw-outline-style);
outline-width: 1px;

View File

@@ -49,7 +49,7 @@ pub enum Route {
Login { redirect_url: String },
}
const FAVICON: Asset = asset!("/assets/favicon.ico");
const FAVICON: Asset = asset!("/assets/favicon.svg");
const MAIN_CSS: Asset = asset!("/assets/main.css");
const TAILWIND_CSS: Asset = asset!("/assets/tailwind.css");
const MANIFEST: Asset = asset!("/assets/manifest.json");

View File

@@ -159,7 +159,7 @@ mod inner {
/// # Errors
///
/// Returns `ServerFnError` if the Ollama request fails or response parsing fails
#[server(endpoint = "/api/summarize")]
#[post("/api/summarize")]
pub async fn summarize_article(
snippet: String,
article_url: String,
@@ -258,7 +258,7 @@ pub struct FollowUpMessage {
/// # Errors
///
/// Returns `ServerFnError` if the Ollama request fails or response parsing fails
#[server(endpoint = "/api/chat")]
#[post("/api/chat")]
pub async fn chat_followup(
messages: Vec<FollowUpMessage>,
ollama_url: String,

View File

@@ -45,7 +45,7 @@ struct OllamaModel {
///
/// Returns `ServerFnError` only on serialization issues; network failures
/// are caught and returned as `online: false`
#[server(endpoint = "/api/ollama-status")]
#[post("/api/ollama-status")]
pub async fn get_ollama_status(ollama_url: String) -> Result<OllamaStatus, ServerFnError> {
dotenvy::dotenv().ok();

View File

@@ -110,7 +110,7 @@ mod inner {
/// # Errors
///
/// Returns `ServerFnError` if the SearXNG request fails or response parsing fails
#[server(endpoint = "/api/search")]
#[post("/api/search")]
pub async fn search_topic(query: String) -> Result<Vec<NewsCard>, ServerFnError> {
dotenvy::dotenv().ok();
use inner::{extract_source, rank_and_deduplicate, SearxngResponse};
@@ -122,23 +122,21 @@ pub async fn search_topic(query: String) -> Result<Vec<NewsCard>, ServerFnError>
// similar to how Perplexity reformulates queries before searching.
let enriched_query = format!("{query} latest news");
// Build URL with query parameters using the url crate's encoder
// to avoid reqwest version conflicts between our dep and dioxus's.
// Key SearXNG params:
// categories=news,general - prioritize news sources + supplement with general
// time_range=month - only recent results (last 30 days)
// language=en - English results
// format=json - machine-readable output
let encoded_query: String =
url::form_urlencoded::byte_serialize(enriched_query.as_bytes()).collect();
let search_url = format!(
"{searxng_url}/search?q={encoded_query}&format=json&language=en\
&categories=news,general&time_range=month"
);
// Use POST with form-encoded body because SearXNG's default config
// sets `method: "POST"` which rejects GET requests with 405.
let search_url = format!("{searxng_url}/search");
let params = [
("q", enriched_query.as_str()),
("format", "json"),
("language", "en"),
("categories", "news,general"),
("time_range", "month"),
];
let client = reqwest::Client::new();
let resp = client
.get(&search_url)
.post(&search_url)
.form(&params)
.send()
.await
.map_err(|e| ServerFnError::new(format!("SearXNG request failed: {e}")))?;
@@ -198,7 +196,7 @@ pub async fn search_topic(query: String) -> Result<Vec<NewsCard>, ServerFnError>
/// # Errors
///
/// Returns `ServerFnError` if the SearXNG search request fails
#[server(endpoint = "/api/trending")]
#[get("/api/trending")]
pub async fn get_trending_topics() -> Result<Vec<String>, ServerFnError> {
dotenvy::dotenv().ok();
use inner::SearxngResponse;
@@ -207,12 +205,15 @@ pub async fn get_trending_topics() -> Result<Vec<String>, ServerFnError> {
let searxng_url =
std::env::var("SEARXNG_URL").unwrap_or_else(|_| "http://localhost:8888".into());
let encoded_query: String =
url::form_urlencoded::byte_serialize(b"trending technology AI").collect();
let search_url = format!(
"{searxng_url}/search?q={encoded_query}&format=json&language=en\
&categories=news&time_range=week"
);
// Use POST to match SearXNG's default `method: "POST"` setting
let search_url = format!("{searxng_url}/search");
let params = [
("q", "trending technology AI"),
("format", "json"),
("language", "en"),
("categories", "news"),
("time_range", "week"),
];
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
@@ -220,7 +221,8 @@ pub async fn get_trending_topics() -> Result<Vec<String>, ServerFnError> {
.map_err(|e| ServerFnError::new(format!("HTTP client error: {e}")))?;
let resp = client
.get(&search_url)
.post(&search_url)
.form(&params)
.send()
.await
.map_err(|e| ServerFnError::new(format!("SearXNG trending search failed: {e}")))?;