aboutsummaryrefslogtreecommitdiffstats
path: root/src/main.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-18 23:18:37 +0200
committersalmonik <salmonik@tuta.io>2026-08-18 23:18:37 +0200
commitb252c39517b9fce181d1a0fe4761404c7d981397 (patch)
tree564cf01aed601f1974e0de8801d4452215ec05f5 /src/main.rs
parentfir auto completion and send file in the response body upon grajcuj file (diff)
downloadsenator-sperma-b252c39517b9fce181d1a0fe4761404c7d981397.tar.gz
senator-sperma-b252c39517b9fce181d1a0fe4761404c7d981397.zip
feat: local TTS via piper subprocess with Google fallback
- New src/tts.rs: centralize TTS in synthesize(config, http_client, text) Piper subprocess primary, Google Translate fallback on error/missing - TTS_MODEL_PATH/TTS_VOICE env config (defaults to pl_PL-bass-high) - Fix Unknown interaction errors: spawn play_tts_in_channel in background so voice-state TTS no longer blocks the shard event loop (3s Discord limit) - Best-effort confirmation replies in /tts to prevent error cascade - Custom on_error handler: graceful interaction-expired error handling - Comprehensive tracing logging (engine, model, duration, stderr)
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs38
1 files changed, 31 insertions, 7 deletions
diff --git a/src/main.rs b/src/main.rs
index d06176d..31c9e94 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -15,6 +15,7 @@ mod events;
15mod lang; 15mod lang;
16mod openrouter; 16mod openrouter;
17mod tasks; 17mod tasks;
18mod tts;
18mod voice; 19mod voice;
19 20
20use database::DbPool; 21use database::DbPool;
@@ -26,9 +27,7 @@ const AC_SEARCH_CACHE_TTL: Duration = Duration::from_secs(10);
26type AcCache = tokio::sync::Mutex<HashMap<(serenity::UserId, String), copyparty::SearchHit>>; 27type AcCache = tokio::sync::Mutex<HashMap<(serenity::UserId, String), copyparty::SearchHit>>;
27 28
28/// Shared autocomplete search cache: query → (hits, cached_at). 29/// Shared autocomplete search cache: query → (hits, cached_at).
29type AcSearchCache = Arc< 30type AcSearchCache = Arc<tokio::sync::Mutex<HashMap<String, (Vec<copyparty::SearchHit>, Instant)>>>;
30 tokio::sync::Mutex<HashMap<String, (Vec<copyparty::SearchHit>, Instant)>>,
31>;
32 31
33pub struct Data { 32pub struct Data {
34 pub db_pool: DbPool, 33 pub db_pool: DbPool,
@@ -84,10 +83,7 @@ impl Data {
84 83
85 /// Look up search cache for an autocomplete query. Returns `None` if 84 /// Look up search cache for an autocomplete query. Returns `None` if
86 /// uncached or stale. 85 /// uncached or stale.
87 pub async fn ac_search_cache_lookup( 86 pub async fn ac_search_cache_lookup(&self, query: &str) -> Option<Vec<copyparty::SearchHit>> {
88 &self,
89 query: &str,
90 ) -> Option<Vec<copyparty::SearchHit>> {
91 let cache = self.ac_search_cache.lock().await; 87 let cache = self.ac_search_cache.lock().await;
92 let (hits, cached_at) = cache.get(query)?; 88 let (hits, cached_at) = cache.get(query)?;
93 if cached_at.elapsed() > AC_SEARCH_CACHE_TTL { 89 if cached_at.elapsed() > AC_SEARCH_CACHE_TTL {
@@ -150,6 +146,34 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
150 Ok(()) 146 Ok(())
151 }) 147 })
152 }, 148 },
149 on_error: |error| {
150 Box::pin(async move {
151 match error {
152 poise::FrameworkError::Command { ctx, error, .. } => {
153 tracing::error!(command = %ctx.command().name, "Command failed: {error}");
154 if let Err(e) = ctx
155 .send(
156 poise::CreateReply::default()
157 .content(format!("Błąd: {error}"))
158 .ephemeral(true),
159 )
160 .await
161 {
162 tracing::debug!(
163 "Could not send error reply (interaction likely expired): {e}"
164 );
165 }
166 }
167 other => {
168 if let Err(e) = poise::builtins::on_error(other).await {
169 tracing::warn!(
170 "Error while sending error message (interaction likely expired): {e}"
171 );
172 }
173 }
174 }
175 })
176 },
153 ..Default::default() 177 ..Default::default()
154 }) 178 })
155 .setup(move |ctx, _ready, framework| { 179 .setup(move |ctx, _ready, framework| {