use axum::{ extract::State, http::StatusCode, response::Html, routing::{get, post}, Json, Router, }; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::env; use tower_http::services::ServeDir; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; #[derive(Debug, Deserialize)] struct ChatRequest { model: String, messages: Vec, } #[derive(Debug, Serialize, Deserialize, Clone)] struct ChatMessage { role: String, content: String, } #[derive(Debug, Serialize)] struct ChatResponse { reply: String, } #[derive(Clone)] struct AppState { client: Client, api_key: String, base_url: String, } #[tokio::main] async fn main() { tracing_subscriber::registry() .with(tracing_subscriber::EnvFilter::new("info")) .with(tracing_subscriber::fmt::layer()) .init(); let api_key = env::var("PUTER_API_KEY").expect("❌ PUTER_API_KEY not set"); let base_url = env::var("PUTER_BASE_URL") .unwrap_or_else(|_| "https://api.puter.com/puterai/openai/v1/".to_string()); let port = env::var("PORT").unwrap_or_else(|_| "7860".to_string()); let state = AppState { client: Client::new(), api_key, base_url, }; // Раздаём статические файлы из папки static/ let app = Router::new() .nest_service("/", ServeDir::new("static")) .route("/api/chat", post(handle_chat)) .with_state(state); let addr = format!("0.0.0.0:{}", port); tracing::info!("🚀 Server running on http://{}", addr); let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); axum::serve(listener, app).await.unwrap(); } async fn handle_chat( State(state): State, Json(req): Json, ) -> Result, (StatusCode, String)> { let mut messages = vec![ChatMessage { role: "system".into(), content: "Ты полезный ассистент. Отвечай кратко и по делу.".into(), }]; messages.extend(req.messages); let payload = serde_json::json!({ "model": req.model, "messages": messages, "temperature": 0.7, "max_tokens": 2048 }); let res = state .client .post(&format!("{}chat/completions", state.base_url)) .header("Authorization", format!("Bearer {}", state.api_key)) .header("Content-Type", "application/json") .json(&payload) .send() .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Network error: {}", e)))?; if !res.status().is_success() { let err_text = res.text().await.unwrap_or_default(); tracing::error!("Puter API error: {}", err_text); return Err((StatusCode::BAD_GATEWAY, format!("API error: {}", err_text))); } let json: serde_json::Value = res.json().await.map_err(|e| { (StatusCode::INTERNAL_SERVER_ERROR, format!("JSON parse error: {}", e)) })?; let reply = json["choices"][0]["message"]["content"] .as_str() .unwrap_or("❌ Пустой ответ от API") .to_string(); Ok(Json(ChatResponse { reply })) }