| 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<ChatMessage>, |
| } |
|
|
| #[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, |
| }; |
|
|
| |
| 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<AppState>, |
| Json(req): Json<ChatRequest>, |
| ) -> Result<Json<ChatResponse>, (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 })) |
| } |