feat: Voxlen API proxy + token-based desktop auth #4760
1 changed file+43−4
Modifiedsrc-tauri/src/stt/streaming.rs+43−4View fileUnifiedSplit
@@ -48,20 +48,42 @@ enum SessionOutcome {
4848 Disconnected(Duration),
4949}
5050
51/// Fetch a short-lived Deepgram key from the Voxlen proxy.
52async fn fetch_proxy_deepgram_key(voxlen_key: &str) -> anyhow::Result<String> {
53 let client = reqwest::Client::new();
54 let res = client
55 .post("https://voxlen.ai/api/deepgram-token")
56 .header("Authorization", format!("Bearer {}", voxlen_key))
57 .send()
58 .await?;
59 if !res.status().is_success() {
60 anyhow::bail!("deepgram-token endpoint error: {}", res.status());
61 }
62 let json: serde_json::Value = res.json().await?;
63 json["key"]
64 .as_str()
65 .map(|s| s.to_string())
66 .ok_or_else(|| anyhow::anyhow!("No key in deepgram-token response"))
67}
68
5169/// Start a real-time streaming session with Deepgram using full SttConfig.
5270pub fn start_streaming(
5371 config: SttConfig,
5472 audio_receiver: Receiver<AudioChunk>,
5573 app_handle: AppHandle,
5674) -> anyhow::Result<StreamingSession> {
57 let api_key = config.api_key
58 .filter(|k| !k.is_empty())
59 .or_else(|| config.voxlen_api_key.filter(|k| !k.is_empty()))
60 .ok_or_else(|| anyhow::anyhow!("No API key configured for Deepgram streaming"))?;
75 // Validate that we have some form of auth before spawning the task
76 let has_direct_key = config.api_key.as_deref().filter(|k| !k.is_empty()).is_some();
77 let has_voxlen_key = config.voxlen_api_key.as_deref().filter(|k| !k.is_empty()).is_some();
78 if !has_direct_key && !has_voxlen_key {
79 anyhow::bail!("No API key configured for Deepgram streaming");
80 }
6181
6282 let stop_flag = Arc::new(AtomicBool::new(false));
6383 let stop_clone = stop_flag.clone();
6484
85 let direct_api_key = config.api_key.filter(|k| !k.is_empty());
86 let voxlen_api_key = config.voxlen_api_key.filter(|k| !k.is_empty());
6587 let language = config.language.clone();
6688 let auto_detect = config.auto_detect_language;
6789 let custom_vocabulary = config.custom_vocabulary.clone();
@@ -69,6 +91,23 @@ pub fn start_streaming(
6991 let tenant_id = config.voxlen_tenant_id.clone();
7092
7193 let handle = tokio::spawn(async move {
94 // Resolve the actual Deepgram API key to use for this session.
95 // If the user has a direct Deepgram key, use it. Otherwise fetch a
96 // short-lived proxy key from the Voxlen backend.
97 let api_key = if let Some(k) = direct_api_key {
98 k
99 } else if let Some(vk) = voxlen_api_key {
100 match fetch_proxy_deepgram_key(&vk).await {
101 Ok(k) => k,
102 Err(e) => {
103 let _ = app_handle.emit("transcription-error", format!("Failed to get streaming key from Voxlen proxy: {}", e));
104 return;
105 }
106 }
107 } else {
108 return;
109 };
110
72111 let max_retries = 3;
73112 let mut attempt = 0;
74113
75114
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts