offline_intelligence/api/
online_api.rs1use axum::{
6 extract::State,
7 response::{
8 sse::{Event, Sse},
9 IntoResponse, Response,
10 },
11 http::StatusCode,
12 Json,
13};
14use futures_util::StreamExt;
15use serde::Deserialize;
16use std::convert::Infallible;
17use tracing::{info, error, debug};
18use reqwest;
19use serde_json::Value;
20
21use crate::memory::Message;
22use crate::shared_state::UnifiedAppState;
23
24#[derive(Debug, Deserialize)]
26pub struct OnlineStreamRequest {
27 pub model: String,
28 pub messages: Vec<Message>,
29 pub session_id: String,
30 #[serde(default = "default_max_tokens")]
31 pub max_tokens: u32,
32 #[serde(default = "default_temperature")]
33 pub temperature: f32,
34 #[serde(default = "default_stream")]
35 pub stream: bool,
36 pub api_key: Option<String>, }
38
39fn default_max_tokens() -> u32 { 2000 }
40fn default_temperature() -> f32 { 0.7 }
41fn default_stream() -> bool { true }
42
43pub async fn online_stream(
46 State(state): State<UnifiedAppState>,
47 Json(req): Json<OnlineStreamRequest>,
48) -> Response {
49 info!("Online stream request for session: {}", req.session_id);
50
51 debug!("Request api_key present: {}",
53 req.api_key.is_some()
54 );
55
56 let api_key = if let Some(key) = &req.api_key {
62 if !key.is_empty() {
63 key.clone()
64 } else {
65 state.get_openrouter_api_key().await.unwrap_or_default()
66 }
67 } else {
68 state.get_openrouter_api_key().await.unwrap_or_default()
69 };
70
71 debug!("Final API key length: {}", api_key.len());
72
73 if api_key.is_empty() {
74 error!("OpenRouter API key is empty");
75 return (StatusCode::UNAUTHORIZED, "OpenRouter API key not configured. Please add your API key in Settings.").into_response();
76 }
77
78 let openrouter_messages = req.messages.iter().map(|m| {
80 serde_json::json!({
81 "role": m.role,
82 "content": m.content
83 })
84 }).collect::<Vec<_>>();
85
86 let openrouter_request = serde_json::json!({
88 "model": req.model,
89 "messages": openrouter_messages,
90 "max_tokens": req.max_tokens,
91 "temperature": req.temperature,
92 "stream": req.stream,
93 });
94
95 let client = reqwest::Client::new();
97 let response = client
98 .post("https://openrouter.ai/api/v1/chat/completions")
99 .header("Authorization", format!("Bearer {}", api_key))
100 .header("Content-Type", "application/json")
101 .header("HTTP-Referer", "https://aud.io")
102 .header("X-Title", "Aud.io")
103 .json(&openrouter_request)
104 .send()
105 .await;
106
107 match response {
108 Ok(resp) => {
109 if !resp.status().is_success() {
110 let status = resp.status();
111 let body = resp.text().await.unwrap_or_default();
112 error!("OpenRouter API error ({}): {}", status, body);
113 return (StatusCode::BAD_GATEWAY, format!("OpenRouter API error: {}", body)).into_response();
114 }
115
116 let byte_stream = resp.bytes_stream();
117
118 let sse_stream = async_stream::stream! {
119 let mut buffer = String::new();
120
121 futures_util::pin_mut!(byte_stream);
122
123 while let Some(chunk_result) = byte_stream.next().await {
124 match chunk_result {
125 Ok(chunk) => {
126 buffer.push_str(&String::from_utf8_lossy(&chunk));
127
128 while let Some(newline_pos) = buffer.find('\n') {
129 let line = buffer[..newline_pos].trim().to_string();
130 buffer = buffer[newline_pos + 1..].to_string();
131
132 if line.is_empty() {
133 continue;
134 }
135
136 if line.starts_with("data: ") {
137 let data = &line[6..];
138
139 if data == "[DONE]" {
140 yield Ok::<_, Infallible>(Event::default().data("[DONE]"));
141 return;
142 }
143
144 yield Ok(Event::default().data(data));
146 }
147 }
148 }
149 Err(e) => {
150 error!("Stream error: {}", e);
151 yield Ok(Event::default().data(
152 format!("{{\"error\": \"{}\"}}", e)
153 ));
154 break;
155 }
156 }
157 }
158 };
159
160 Sse::new(sse_stream)
161 .keep_alive(
162 axum::response::sse::KeepAlive::new()
163 .interval(std::time::Duration::from_secs(15))
164 )
165 .into_response()
166 }
167 Err(e) => {
168 error!("Failed to connect to OpenRouter: {}", e);
169 (StatusCode::BAD_GATEWAY, format!("Failed to connect to OpenRouter: {}", e)).into_response()
170 }
171 }
172}