From bd3c655a80cde616a04dc87f5894d22944bc515d Mon Sep 17 00:00:00 2001 From: oiupoyt Date: Sun, 27 Sep 2026 18:19:25 +0530 Subject: [PATCH] feat: live auto-updating dashboard in /watch with top stealth and new models --- src/catalog.rs | 18 +++++ src/main.rs | 111 ++++++++++++++++++++++++------ src/presentation.rs | 163 ++++++++++++++++++++++++++++++++++++++++++++ src/store.rs | 4 ++ 4 files changed, 274 insertions(+), 22 deletions(-) diff --git a/src/catalog.rs b/src/catalog.rs index ee0a999..e1bf012 100644 --- a/src/catalog.rs +++ b/src/catalog.rs @@ -94,6 +94,14 @@ impl Model { } self.pricing.values().all(|value| is_decimal_zero(value)) } + + pub fn is_stealth(&self) -> bool { + let id_lower = self.id.to_lowercase(); + let name_lower = self.name.to_lowercase(); + id_lower.starts_with("stealth/") + || id_lower.contains("stealth") + || name_lower.contains("stealth") + } } pub(crate) fn validate(catalog: &BTreeMap) -> Result<(), Error> { @@ -253,6 +261,16 @@ mod tests { ); } + #[test] + fn stealth_detection_test() { + assert!(model("stealth/space-bunny", &[]).is_stealth()); + assert!(model("vendor/model-stealth-preview", &[]).is_stealth()); + let mut named = model("vendor/xyz", &[]); + named.name = "Stealth Model V1".into(); + assert!(named.is_stealth()); + assert!(!model("meta-llama/llama-3", &[]).is_stealth()); + } + #[test] fn changes_detects_new_and_paid_to_free() { let prev = map(vec![ diff --git a/src/main.rs b/src/main.rs index 72991c6..c3edc88 100644 --- a/src/main.rs +++ b/src/main.rs @@ -339,8 +339,12 @@ fn patch_subscription( min_context: 0, ping_enabled: false, compact: false, + message_id: None, }); - sub.channel_id = channel_id; + if sub.channel_id != channel_id { + sub.channel_id = channel_id; + sub.message_id = None; + } if patch.clear_role { sub.role_id = None; sub.ping_enabled = false; @@ -490,7 +494,7 @@ async fn watch( } let roles = guild.roles(ctx.serenity_context()).await?; let mut guard = ctx.data().store.lock().await; - let sub = match patch_subscription( + let mut sub = match patch_subscription( guard.state.subscriptions.get(&guild.get()), channel.id.get(), &patch, @@ -514,8 +518,40 @@ async fn watch( drop(guard); return reply(ctx, embed("Invalid role").description("The configured role is missing. Set ping_role to a valid role or @everyone, or use ping_enabled:false or clear_role:true.")).await; } - let result = embed("Alerts configured here") - .description(WATCH_HELP) + + // Post or update the public live dashboard in the channel + let dashboard_embed = presentation::live_dashboard(&guard.state, &sub, ctx.data().poll_seconds); + let mut updated_msg_id = None; + if let Some(existing_msg_id) = sub.message_id + && channel + .id + .edit_message( + ctx.serenity_context(), + serenity::MessageId::new(existing_msg_id), + serenity::EditMessage::new().embed(dashboard_embed.clone()), + ) + .await + .is_ok() + { + updated_msg_id = Some(existing_msg_id); + } + if updated_msg_id.is_none() + && let Ok(sent) = channel + .id + .send_message( + ctx.serenity_context(), + serenity::CreateMessage::new() + .embed(dashboard_embed) + .allowed_mentions(no_mentions()), + ) + .await + { + updated_msg_id = Some(sent.id.get()); + } + sub.message_id = updated_msg_id; + + let result = embed("Live Radar Dashboard & Alerts Configured") + .description("Posted the live auto-updating dashboard in this channel. It will automatically refresh at every poll interval.\n\n".to_owned() + WATCH_HELP) .field( "Complete settings", settings(&sub, guild.get(), ctx.data().poll_seconds), @@ -787,7 +823,11 @@ fn acknowledge(state: &mut State, guild: u64, snapshot: &Subscription, count: us } } -async fn deliver(store: &Mutex, http: &serenity::Http) -> Result<(), Error> { +async fn deliver( + store: &Mutex, + http: &serenity::Http, + poll_seconds: u64, +) -> Result<(), Error> { let guilds: Vec<_> = store .lock() .await @@ -797,26 +837,53 @@ async fn deliver(store: &Mutex, http: &serenity::Http) -> Result<(), Erro .copied() .collect(); for guild in guilds { - let snapshot = store.lock().await.state.subscriptions.get(&guild).cloned(); + let (snapshot, state) = { + let guard = store.lock().await; + ( + guard.state.subscriptions.get(&guild).cloned(), + guard.state.clone(), + ) + }; let Some(sub) = snapshot else { continue; }; - if sub.pending.is_empty() { - continue; + + let channel = serenity::ChannelId::new(sub.channel_id); + + // 1. Auto-update the live dashboard embed if message_id exists + if let Some(msg_id) = sub.message_id { + let dashboard = presentation::live_dashboard(&state, &sub, poll_seconds); + let edit_res = channel + .edit_message( + http, + serenity::MessageId::new(msg_id), + serenity::EditMessage::new().embed(dashboard), + ) + .await; + if edit_res.is_err() { + tracing::info!( + guild, + "Live dashboard message edit failed (may have been deleted or moved)" + ); + } } - let count = sub.pending.len().min(8); - if serenity::ChannelId::new(sub.channel_id) - .send_message(http, notification_message(&sub, guild, count)) - .await - .is_err() - { - tracing::warn!(guild, "Alert delivery failed; retained for next poll"); - continue; - } - let mut guard = store.lock().await; - let mut next = guard.state.clone(); - if acknowledge(&mut next, guild, &sub, count) { - guard.commit(next).await?; + + // 2. Deliver new model drop notification role pings if any are pending + if !sub.pending.is_empty() { + let count = sub.pending.len().min(8); + if channel + .send_message(http, notification_message(&sub, guild, count)) + .await + .is_err() + { + tracing::warn!(guild, "Alert delivery failed; retained for next poll"); + continue; + } + let mut guard = store.lock().await; + let mut next = guard.state.clone(); + if acknowledge(&mut next, guild, &sub, count) { + guard.commit(next).await?; + } } } Ok(()) @@ -973,7 +1040,7 @@ async fn main() -> Result<(), Error> { if let Err(error) = refresh(&poll_store, &http_client).await { tracing::warn!(%error, "Catalog refresh failed; preserving previous snapshot"); } - if let Err(error) = deliver(&poll_store, &http).await { + if let Err(error) = deliver(&poll_store, &http, poll_seconds).await { tracing::warn!(%error, "Saving delivery progress failed; alerts may retry"); } } diff --git a/src/presentation.rs b/src/presentation.rs index 40ef01d..28011e9 100644 --- a/src/presentation.rs +++ b/src/presentation.rs @@ -173,6 +173,112 @@ pub fn snapshot(state: &State, options: &Browse, poll_seconds: u64) -> serenity: .field("Poll interval", format!("{formatted_poll} seconds · run /status again to refresh this snapshot"), false) } +pub fn live_dashboard( + state: &State, + sub: &crate::store::Subscription, + poll_seconds: u64, +) -> serenity::CreateEmbed { + let freshness = state + .last_success + .map(|time| { + format!( + "Synced (). Auto-updates every {}s.", + format_number(poll_seconds) + ) + }) + .unwrap_or_else(|| "Waiting for initial catalog sync...".into()); + + let mut result = embed("Model Radar · Live Dashboard").description(freshness); + + // 1. 🔥 New & Recently Dropped Models (at the top) + let recent_drops: Vec<_> = sub + .pending + .iter() + .take(if sub.compact { 6 } else { 3 }) + .map(|event| { + format!( + "**{}** · {}", + if event.kind == "paid_to_free" { + "Now free" + } else { + "New in catalog" + }, + model_line(&event.model, sub.compact) + ) + }) + .collect(); + + if !recent_drops.is_empty() { + let text_block = recent_drops.join("\n\n"); + result = result.field("🔥 New & Recent Drops", text(&text_block, 1024), false); + } + + // 2. 🕵️ Stealth & Experimental Models (First page section) + let stealth_models: Vec<_> = state + .catalog + .values() + .filter(|m| m.is_stealth() && (!sub.free_only || m.is_free())) + .take(if sub.compact { 5 } else { 3 }) + .map(|m| model_line(m, sub.compact)) + .collect(); + + let stealth_text = if stealth_models.is_empty() { + "No stealth models currently active in catalog.".to_string() + } else { + stealth_models.join("\n\n") + }; + result = result.field( + "🕵️ Stealth & Experimental Models", + text(&stealth_text, 1024), + false, + ); + + // 3. ✨ Active Models Snapshot + let active_models: Vec<_> = state + .catalog + .values() + .filter(|m| sub.matches(m)) + .take(if sub.compact { 6 } else { 4 }) + .map(|m| model_line(m, sub.compact)) + .collect(); + + let active_text = if active_models.is_empty() { + "No matching models found with current filters.".to_string() + } else { + active_models.join("\n\n") + }; + result = result.field("✨ Active Models Snapshot", text(&active_text, 1024), false); + + // 4. Overview & Filters + let total_count = format_number(state.catalog.len()); + let free_count = format_number(state.catalog.values().filter(|m| m.is_free()).count()); + let stealth_count = format_number(state.catalog.values().filter(|m| m.is_stealth()).count()); + result = result.field( + "Overview", + format!("{total_count} total · {free_count} free · {stealth_count} stealth"), + true, + ); + + let scope_text = if sub.free_only { + "Free only" + } else { + "All models" + }; + let search_desc = if sub.search.is_empty() { + "None" + } else { + &sub.search + }; + let min_ctx = format_number(sub.min_context); + result = result.field( + "Active Filters", + format!("Scope: {scope_text}\nSearch: {search_desc}\nMin Context: {min_ctx} tokens"), + true, + ); + + result +} + fn format_price_usd(val: f64) -> String { if val == 0.0 { return "$0.00".into(); @@ -498,4 +604,61 @@ pub mod tests { "tiered pricing" ); } + + #[test] + fn live_dashboard_includes_stealth_and_new_models() { + let state = State { + catalog: [ + model("stealth/space-bunny", true, 32000), + model("qwen/qwen-2.5", true, 64000), + model("meta-llama/llama-3", false, 8000), + ] + .into_iter() + .map(|m| (m.id.clone(), m)) + .collect(), + last_success: Some(12345), + ..Default::default() + }; + let sub = crate::store::Subscription { + channel_id: 1, + role_id: None, + free_only: true, + search: String::new(), + min_context: 0, + ping_enabled: false, + compact: false, + message_id: None, + pending: vec![crate::catalog::Event { + model: model("qwen/qwen-2.5", true, 64000), + kind: "paid_to_free".into(), + }], + }; + let embed = live_dashboard(&state, &sub, 600); + let value = serde_json::to_value(&embed).unwrap(); + assert_embed_bounds(&value); + + // Verify "🔥 New & Recent Drops" at top + assert_eq!(value["fields"][0]["name"], "🔥 New & Recent Drops"); + assert!( + value["fields"][0]["value"] + .as_str() + .unwrap() + .contains("Now free") + ); + + // Verify "🕵️ Stealth & Experimental Models" section + assert_eq!( + value["fields"][1]["name"], + "🕵️ Stealth & Experimental Models" + ); + assert!( + value["fields"][1]["value"] + .as_str() + .unwrap() + .contains("stealth/space") + ); + + // Verify "✨ Active Models Snapshot" + assert_eq!(value["fields"][2]["name"], "✨ Active Models Snapshot"); + } } diff --git a/src/store.rs b/src/store.rs index 9bf64fc..49b3f37 100644 --- a/src/store.rs +++ b/src/store.rs @@ -32,6 +32,8 @@ pub struct Subscription { pub ping_enabled: bool, #[serde(default)] pub compact: bool, + #[serde(default)] + pub message_id: Option, pub pending: Vec, } @@ -183,6 +185,7 @@ mod tests { min_context: 8192, ping_enabled: false, compact: true, + message_id: None, pending: vec![Event { model, kind: "new".into(), @@ -199,6 +202,7 @@ mod tests { min_context: 0, ping_enabled: true, compact: false, + message_id: None, pending: Vec::new(), }, ),