diff --git a/assets/favicon.ico b/assets/favicon.ico
deleted file mode 100644
index eed0c09..0000000
Binary files a/assets/favicon.ico and /dev/null differ
diff --git a/assets/favicon.svg b/assets/favicon.svg
new file mode 100644
index 0000000..ac16408
--- /dev/null
+++ b/assets/favicon.svg
@@ -0,0 +1,25 @@
+
diff --git a/assets/sw.js b/assets/sw.js
index f5db1a2..dcaaab7 100644
--- a/assets/sw.js
+++ b/assets/sw.js
@@ -12,7 +12,7 @@ self.addEventListener("install", (event) => {
"/",
"/dashboard",
"/assets/logo.svg",
- "/assets/favicon.ico",
+ "/assets/favicon.svg",
])
)
);
diff --git a/assets/tailwind.css b/assets/tailwind.css
index d30c32c..7177b28 100644
--- a/assets/tailwind.css
+++ b/assets/tailwind.css
@@ -1733,6 +1733,9 @@
.text-center {
text-align: center;
}
+ .lowercase {
+ text-transform: lowercase;
+ }
.outline {
outline-style: var(--tw-outline-style);
outline-width: 1px;
diff --git a/src/app.rs b/src/app.rs
index 8c982cd..8964acc 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -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");
diff --git a/src/infrastructure/llm.rs b/src/infrastructure/llm.rs
index 46955c8..5addf44 100644
--- a/src/infrastructure/llm.rs
+++ b/src/infrastructure/llm.rs
@@ -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,
ollama_url: String,
diff --git a/src/infrastructure/ollama.rs b/src/infrastructure/ollama.rs
index 1b60964..5ef0449 100644
--- a/src/infrastructure/ollama.rs
+++ b/src/infrastructure/ollama.rs
@@ -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 {
dotenvy::dotenv().ok();
diff --git a/src/infrastructure/searxng.rs b/src/infrastructure/searxng.rs
index d5d2322..67e6274 100644
--- a/src/infrastructure/searxng.rs
+++ b/src/infrastructure/searxng.rs
@@ -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, ServerFnError> {
dotenvy::dotenv().ok();
use inner::{extract_source, rank_and_deduplicate, SearxngResponse};
@@ -122,23 +122,21 @@ pub async fn search_topic(query: String) -> Result, 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(¶ms)
.send()
.await
.map_err(|e| ServerFnError::new(format!("SearXNG request failed: {e}")))?;
@@ -198,7 +196,7 @@ pub async fn search_topic(query: String) -> Result, ServerFnError>
/// # Errors
///
/// Returns `ServerFnError` if the SearXNG search request fails
-#[server(endpoint = "/api/trending")]
+#[get("/api/trending")]
pub async fn get_trending_topics() -> Result, ServerFnError> {
dotenvy::dotenv().ok();
use inner::SearxngResponse;
@@ -207,12 +205,15 @@ pub async fn get_trending_topics() -> Result, 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, ServerFnError> {
.map_err(|e| ServerFnError::new(format!("HTTP client error: {e}")))?;
let resp = client
- .get(&search_url)
+ .post(&search_url)
+ .form(¶ms)
.send()
.await
.map_err(|e| ServerFnError::new(format!("SearXNG trending search failed: {e}")))?;
diff --git a/src/pages/dashboard.rs b/src/pages/dashboard.rs
index f3d3a1b..b6520cb 100644
--- a/src/pages/dashboard.rs
+++ b/src/pages/dashboard.rs
@@ -329,7 +329,7 @@ pub fn DashboardPage() -> Element {
.set(
format!(
"Article content:\n{snippet}\n\n\
- AI Summary:\n{text}",
+ AI Summary:\n{text}",
),
);
summary.set(Some(text));
@@ -385,9 +385,9 @@ pub fn DashboardPage() -> Element {
role: "system".into(),
content: format!(
"You are a helpful assistant. The user is reading \
- a news article. Use the following context to answer \
- their questions. Do NOT comment on the source, \
- dates, URLs, or formatting.\n\n{ctx}",
+ a news article. Use the following context to answer \
+ their questions. Do NOT comment on the source, \
+ dates, URLs, or formatting.\n\n{ctx}",
),
},
];