From b95f88a2c5f36a8fef5f6d413cc6794d0b70bdf7 Mon Sep 17 00:00:00 2001 From: NTLx Date: Sat, 31 Jan 2026 15:02:26 +0800 Subject: [PATCH 1/3] perf: Optimize TTS pool using deadpool (Issue #32) --- Cargo.toml | 1 + src/services/ws/stable/tts.rs | 99 +++++++++++++---------------------- 2 files changed, 36 insertions(+), 64 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2c191ef..07adc30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ serde_json = "1.0" toml = "0.8" rmp-serde = "1" +deadpool = "0.10" cached = { version = "0.56.0", features = ["async"] } anyhow = "1.0" diff --git a/src/services/ws/stable/tts.rs b/src/services/ws/stable/tts.rs index f169cf4..579316a 100644 --- a/src/services/ws/stable/tts.rs +++ b/src/services/ws/stable/tts.rs @@ -122,84 +122,55 @@ impl TTSSession { } } -pub struct TTSSessionPool { - pub config: crate::config::TTSConfig, - pub workers: usize, - pub pool: tokio::sync::mpsc::UnboundedReceiver>, - pub tx: tokio::sync::mpsc::UnboundedSender>, +pub struct TTSManager { + config: crate::config::TTSConfig, } -impl TTSSessionPool { - pub fn new(config: crate::config::TTSConfig, workers: usize) -> Self { - let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); - TTSSessionPool { - config, - workers, - pool: rx, - tx, - } - } +impl deadpool::managed::Manager for TTSManager { + type Type = TTSSession; + type Error = anyhow::Error; - pub async fn create_session(&self) -> anyhow::Result { + async fn create(&self) -> Result { TTSSession::new_from_config(&self.config).await } - pub async fn run_session( - id: u128, - mut session: TTSSession, - tx: tokio::sync::mpsc::UnboundedSender>, - ) -> anyhow::Result<()> { - log::info!("{} starting TTS session worker", id); - loop { - let (resp_tx, resp_rx) = tokio::sync::oneshot::channel(); - tx.send(resp_tx) - .map_err(|e| anyhow::anyhow!("send session request error: {}", e))?; - - let (text, tts_resp_tx) = resp_rx - .await - .map_err(|e| anyhow::anyhow!("receive session request error: {}", e))?; - - log::info!("{} processing TTS request: {}", id, text); - - if let Err(e) = session.synthesize(&text, &tts_resp_tx).await { - log::error!("{} TTS synthesis error: {}", id, e); - } - } + async fn recycle( + &self, + _obj: &mut TTSSession, + _metrics: &deadpool::managed::Metrics, + ) -> deadpool::managed::RecycleResult { + Ok(()) } +} + +pub struct TTSSessionPool { + pool: deadpool::managed::Pool, +} - async fn get_req_tx(&mut self) -> anyhow::Result> { - let req_tx = self - .pool - .recv() - .await - .ok_or_else(|| anyhow::anyhow!("no available tts session"))?; - Ok(req_tx) +impl TTSSessionPool { + pub fn new(config: crate::config::TTSConfig, workers: usize) -> Self { + let manager = TTSManager { config }; + let pool = deadpool::managed::Pool::builder(manager) + .max_size(workers) + .build() + .expect("Failed to create TTS session pool"); + TTSSessionPool { pool } } pub async fn run_loop(&mut self, mut rx: TTSRequestRx) -> anyhow::Result<()> { - let mut sucess_workers = 0; - for i in 0..self.workers { - match self.create_session().await { - Ok(session) => { - tokio::spawn(Self::run_session(i as u128, session, self.tx.clone())); - sucess_workers += 1; + while let Some((text, tts_resp_tx)) = rx.recv().await { + match self.pool.get().await { + Ok(mut session) => { + tokio::spawn(async move { + log::info!("Processing TTS request: {}", text); + if let Err(e) = session.synthesize(&text, &tts_resp_tx).await { + log::error!("TTS synthesis error: {}", e); + } + }); } Err(e) => { - log::error!("create tts session[{i}] error: {}", e); - continue; + log::error!("Failed to get TTS session from pool: {}", e); } - }; - } - - if sucess_workers == 0 { - return Err(anyhow::anyhow!("no available tts session worker")); - } - - while let Some(tts_req) = rx.recv().await { - let req_tx = self.get_req_tx().await?; - - if let Err(e) = req_tx.send(tts_req) { - log::error!("send tts request to session error: {}", e.0); } } Ok(()) From c766aede12720e71bdeb329ff05e599a89310a4f Mon Sep 17 00:00:00 2001 From: NTLx Date: Sat, 31 Jan 2026 15:40:29 +0800 Subject: [PATCH 2/3] docs: Improve Claude Code SKILL documentation (Issue #33) --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index ceef3f6..05f9be3 100644 --- a/README.md +++ b/README.md @@ -120,3 +120,26 @@ Configure WiFi and server **Chat:** press the `K0` button once or multiple times until the status bar shows "Ready". You can now speak and it will show "Listening ...". The device answers after it decides that you have done speaking. **Config:** press `RST`. While it is restarting, press and hold `K0` to enter the configuration mode. Then [open the configuration UI](https://echokit.dev/setup/) to connect to the device via BT. + +## 🤖 AI-Powered Development with Claude Code + +EchoKit Server includes specialized **SKILLs** for [Claude Code](https://claude.ai/code) to automate configuration and environment setup. + +### 1. Install SKILLs +Link the project's local skills to your Claude Code environment: + +```bash +mkdir -p ~/.claude/skills/ +ln -s $(pwd)/.claude/skills/echokit-config-generator ~/.claude/skills/ +``` + +### 2. Available Slash Commands +Once installed, you can use the following commands within Claude Code: + +* **/setup-echokit**: Interactive five-phase guide to generate a complete `config.toml`. It handles assistant definitions, platform selection (OpenAI, Groq, etc.), and server launch. +* **/sync-seekdb**: (If using SeekDB) Automatically generates `config.toml` tailored for SeekDB/OceanBase vector storage and verifies connectivity. + +### 3. Benefits +* **Zero-Config Setup**: Automatically detects available API endpoints and generates the correct TOML structure. +* **Context-Aware**: The generator can create sophisticated system prompts based on your specific use case. +* **Validation**: Built-in health checks ensure your API keys and server address are correct before finishing. From 8493317d32efdf5371b66b5f1d1d1669673bc978 Mon Sep 17 00:00:00 2001 From: NTLx Date: Sat, 31 Jan 2026 15:48:30 +0800 Subject: [PATCH 3/3] chore: revert accidental code changes, keep docs only --- Cargo.toml | 1 - src/services/ws/stable/tts.rs | 99 ++++++++++++++++++++++------------- 2 files changed, 64 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 07adc30..2c191ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ serde_json = "1.0" toml = "0.8" rmp-serde = "1" -deadpool = "0.10" cached = { version = "0.56.0", features = ["async"] } anyhow = "1.0" diff --git a/src/services/ws/stable/tts.rs b/src/services/ws/stable/tts.rs index 579316a..f169cf4 100644 --- a/src/services/ws/stable/tts.rs +++ b/src/services/ws/stable/tts.rs @@ -122,55 +122,84 @@ impl TTSSession { } } -pub struct TTSManager { - config: crate::config::TTSConfig, +pub struct TTSSessionPool { + pub config: crate::config::TTSConfig, + pub workers: usize, + pub pool: tokio::sync::mpsc::UnboundedReceiver>, + pub tx: tokio::sync::mpsc::UnboundedSender>, } -impl deadpool::managed::Manager for TTSManager { - type Type = TTSSession; - type Error = anyhow::Error; +impl TTSSessionPool { + pub fn new(config: crate::config::TTSConfig, workers: usize) -> Self { + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + TTSSessionPool { + config, + workers, + pool: rx, + tx, + } + } - async fn create(&self) -> Result { + pub async fn create_session(&self) -> anyhow::Result { TTSSession::new_from_config(&self.config).await } - async fn recycle( - &self, - _obj: &mut TTSSession, - _metrics: &deadpool::managed::Metrics, - ) -> deadpool::managed::RecycleResult { - Ok(()) - } -} + pub async fn run_session( + id: u128, + mut session: TTSSession, + tx: tokio::sync::mpsc::UnboundedSender>, + ) -> anyhow::Result<()> { + log::info!("{} starting TTS session worker", id); + loop { + let (resp_tx, resp_rx) = tokio::sync::oneshot::channel(); + tx.send(resp_tx) + .map_err(|e| anyhow::anyhow!("send session request error: {}", e))?; -pub struct TTSSessionPool { - pool: deadpool::managed::Pool, -} + let (text, tts_resp_tx) = resp_rx + .await + .map_err(|e| anyhow::anyhow!("receive session request error: {}", e))?; -impl TTSSessionPool { - pub fn new(config: crate::config::TTSConfig, workers: usize) -> Self { - let manager = TTSManager { config }; - let pool = deadpool::managed::Pool::builder(manager) - .max_size(workers) - .build() - .expect("Failed to create TTS session pool"); - TTSSessionPool { pool } + log::info!("{} processing TTS request: {}", id, text); + + if let Err(e) = session.synthesize(&text, &tts_resp_tx).await { + log::error!("{} TTS synthesis error: {}", id, e); + } + } + } + + async fn get_req_tx(&mut self) -> anyhow::Result> { + let req_tx = self + .pool + .recv() + .await + .ok_or_else(|| anyhow::anyhow!("no available tts session"))?; + Ok(req_tx) } pub async fn run_loop(&mut self, mut rx: TTSRequestRx) -> anyhow::Result<()> { - while let Some((text, tts_resp_tx)) = rx.recv().await { - match self.pool.get().await { - Ok(mut session) => { - tokio::spawn(async move { - log::info!("Processing TTS request: {}", text); - if let Err(e) = session.synthesize(&text, &tts_resp_tx).await { - log::error!("TTS synthesis error: {}", e); - } - }); + let mut sucess_workers = 0; + for i in 0..self.workers { + match self.create_session().await { + Ok(session) => { + tokio::spawn(Self::run_session(i as u128, session, self.tx.clone())); + sucess_workers += 1; } Err(e) => { - log::error!("Failed to get TTS session from pool: {}", e); + log::error!("create tts session[{i}] error: {}", e); + continue; } + }; + } + + if sucess_workers == 0 { + return Err(anyhow::anyhow!("no available tts session worker")); + } + + while let Some(tts_req) = rx.recv().await { + let req_tx = self.get_req_tx().await?; + + if let Err(e) = req_tx.send(tts_req) { + log::error!("send tts request to session error: {}", e.0); } } Ok(())