🐘
api.php
Back
📝 Php ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
<?php declare(strict_types=1); session_start(); /* ---------- CORS ---------- */ header('Content-Type: application/json; charset=utf-8'); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: POST, GET, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, X-Session-Id, X-Username'); if (($_SERVER['REQUEST_METHOD'] ?? '') === 'OPTIONS') { http_response_code(204); exit; } /* ---------- Helpers ---------- */ function jerr(string $msg, array $debug = []): void { http_response_code(200); echo json_encode(['error' => $msg, 'debug' => $debug], JSON_UNESCAPED_SLASHES); exit; } function cut(string $s, int $n = 800): string { return function_exists('mb_substr') ? mb_substr($s, 0, $n) : substr($s, 0, $n); } function load_keys_from_file(?string $path): array { if (!$path || !is_file($path)) return []; $cfg = include $path; return is_array($cfg) ? $cfg : []; } function read_json_body(): array { $raw = file_get_contents('php://input'); if ($raw === false || $raw === '') return []; $data = json_decode($raw, true); if (json_last_error() !== JSON_ERROR_NONE) { jerr('Invalid JSON body: '.json_last_error_msg(), ['raw' => cut($raw, 300)]); } return is_array($data) ? $data : []; } /* ---------- Load API keys ---------- */ $cfg = []; $try1 = realpath(__DIR__ . '/../keys/keys.php'); $try2 = realpath(__DIR__ . '/keys/keys.php'); if (!$cfg) $cfg = load_keys_from_file($try1); if (!$cfg) $cfg = load_keys_from_file($try2); $DEEPSEEK_API_KEY = getenv('DEEPSEEK_API_KEY') ?: ($cfg['DEEPSEEK_API_KEY'] ?? ''); $OPENAI_API_KEY = getenv('OPENAI_API_KEY') ?: ($cfg['OPENAI_API_KEY'] ?? ''); $XAI_API_KEY = getenv('XAI_API_KEY') ?: ($cfg['XAI_API_KEY'] ?? ''); $ANTHROPIC_API_KEY= getenv('ANTHROPIC_API_KEY')?: ($cfg['ANTHROPIC_API_KEY']?? ''); /* ---------- DB Connection ---------- */ $cfgPath = realpath(__DIR__ . '/root/core/db_config.php'); if (!$cfgPath || !is_file($cfgPath)) { $alt = realpath(__DIR__ . '/../root/core/db_config.php'); if ($alt && is_file($alt)) { $cfgPath = $alt; } } if ($cfgPath && is_file($cfgPath)) { require_once $cfgPath; // defines getDB() } $pdo = function_exists('getDB') ? getDB() : null; /* ---------- Settings ---------- */ $ENABLE_HISTORY = true; $MAX_HISTORY_MESSAGES = 12; $TIMEOUT_SECONDS = 120; /* ---------- Pricing ---------- */ const MODEL_PRICING = [ 'deepseek-chat' => ['in'=>0.27, 'out'=>1.10], 'deepseek-reasoner' => ['in'=>0.55, 'out'=>2.19], 'gpt-4o' => ['in'=>2.50, 'out'=>10.00], 'gpt-4o-mini' => ['in'=>0.15, 'out'=>0.60], 'gpt-5' => ['in'=>1.25, 'out'=>10.00], 'gpt-5-mini' => ['in'=>0.25, 'out'=>2.00], 'gpt-5-nano' => ['in'=>0.05, 'out'=>0.40], 'gpt-5-pro' => ['in'=>2.50, 'out'=>15.00], 'grok-3' => ['in'=>3.00, 'out'=>15.00], 'grok-3-mini' => ['in'=>0.30, 'out'=>0.50], 'grok-code-fast-1' => ['in'=>0.20, 'out'=>1.50], /* Claude 4.5 — Sonnet, Haiku, Opus 4.1 */ 'claude-sonnet-4-5' => ['in'=>1.50, 'out'=>7.50], 'claude-sonnet-4-5-20250929' => ['in'=>1.50, 'out'=>7.50], 'claude-haiku-4-5' => ['in'=>0.50, 'out'=>2.50], 'claude-haiku-4-5-20251001' => ['in'=>0.50, 'out'=>2.50], 'claude-opus-4-1' => ['in'=>7.50, 'out'=>37.50], 'claude-opus-4-1-20250805' => ['in'=>7.50, 'out'=>37.50], ]; function estimate_cost(string $model, int $inTok, int $outTok): float { $p = MODEL_PRICING[$model] ?? null; if (!$p) return 0.0; return ($inTok / 1_000_000 * $p['in']) + ($outTok / 1_000_000 * $p['out']); } /* ---------- Optional: usage summary ---------- */ $action = $_GET['action'] ?? ''; if ($action === 'get_usage_summary') { if (!$pdo) jerr('DB not configured'); $username = $_SERVER['HTTP_X_USERNAME'] ?? ($_SESSION['username'] ?? null); if (!$username) jerr('Missing username header'); try { $sum = $pdo->prepare(" SELECT COALESCE(SUM(total_cost_est),0) AS total_cost, COALESCE(SUM(input_tokens),0) AS total_in, COALESCE(SUM(output_tokens),0) AS total_out, COUNT(*) AS calls FROM ai_usage WHERE username = :u "); $sum->execute([':u' => $username]); $summary = $sum->fetch() ?: []; $by = $pdo->prepare(" SELECT model, COUNT(*) AS calls, SUM(input_tokens) AS total_in, SUM(output_tokens) AS total_out, ROUND(SUM(total_cost_est),6) AS total_cost FROM ai_usage WHERE username = :u GROUP BY model ORDER BY total_cost DESC "); $by->execute([':u' => $username]); echo json_encode(['ok'=>true,'summary'=>$summary,'byModel'=>$by->fetchAll()], JSON_UNESCAPED_SLASHES); exit; } catch (Throwable $e) { jerr('DB query failed', ['err'=>$e->getMessage()]); } } /* ---------- Normal Chat Completion ---------- */ $req = read_json_body(); if (!$req && !empty($_POST)) $req = $_POST; $question = trim((string)($req['question'] ?? '')); $model = (string)($req['model'] ?? 'deepseek-chat'); $maxTokens = (int)($req['maxTokens'] ?? 800); $temperature = (float)($req['temperature'] ?? 0.7); $system = (string)($req['system'] ?? "You are a helpful assistant."); if ($question === '') jerr('Please enter a question.'); /* ---------- Provider Selection ---------- */ if (preg_match('/^claude/i', $model)) { $provider = 'anthropic'; $api_url = 'https://api.anthropic.com/v1/messages'; $api_key = $ANTHROPIC_API_KEY; if ($api_key === '') jerr('Missing Anthropic API key.'); } elseif (str_starts_with($model, 'deepseek')) { $provider = 'deepseek'; $api_url = 'https://api.deepseek.com/chat/completions'; $api_key = $DEEPSEEK_API_KEY; } elseif (preg_match('/^grok[-_]/i', $model)) { $provider = 'xai'; $api_url = 'https://api.x.ai/v1/chat/completions'; $api_key = $XAI_API_KEY; } else { $provider = 'openai'; $api_url = 'https://api.openai.com/v1/chat/completions'; $api_key = $OPENAI_API_KEY; } /* ---------- History ---------- */ $sessionId = (string)($req['sessionId'] ?? ($_SERVER['HTTP_X_SESSION_ID'] ?? 'default')); $_SESSION['chat_log_map'] ??= []; $history = $_SESSION['chat_log_map'][$sessionId] ?? []; $messages = [['role' => 'system', 'content' => $system]]; foreach (array_slice($history, -$MAX_HISTORY_MESSAGES) as $m) { $messages[] = ['role'=>$m['role'], 'content'=>$m['content']]; } $messages[] = ['role'=>'user','content'=>$question]; /* ---------- Payload ---------- */ if ($provider === 'anthropic') { $systemPrompt = ''; $filteredMessages = []; foreach ($messages as $m) { if ($m['role'] === 'system') { $systemPrompt .= $m['content'] . "\n"; } else { $filteredMessages[] = [ 'role' => $m['role'], 'content' => (string)$m['content'] ]; } } $payload = [ 'model' => $model, 'max_tokens' => $maxTokens, 'messages' => $filteredMessages, 'temperature' => $temperature ]; // Only add system if it exists and is non-empty if (trim($systemPrompt) !== '') { $payload['system'] = trim($systemPrompt); } } else { // OpenAI / Grok / DeepSeek compatible $payload = [ 'model' => $model, 'messages' => array_map(function($m) { return [ 'role' => $m['role'], 'content' => (string)$m['content'] ]; }, $messages), 'max_tokens' => $maxTokens, 'temperature' => $temperature, ]; } /* ---------- CURL Call ---------- */ if ($provider === 'anthropic') { $headers = [ 'Content-Type: application/json', 'x-api-key: ' . $api_key, 'Anthropic-Version: 2023-06-01', ]; } else { $headers = [ 'Content-Type: application/json', 'Authorization: Bearer ' . $api_key, ]; } $ch = curl_init($api_url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES), CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => $TIMEOUT_SECONDS, ]); $raw = curl_exec($ch); $http = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); $cerr = curl_error($ch); curl_close($ch); if ($cerr) jerr('cURL error: '.$cerr); if ($http < 200 || $http >= 300) jerr("Upstream HTTP $http", ['resp'=>cut((string)$raw,800)]); $json = json_decode((string)$raw, true); if (json_last_error() !== JSON_ERROR_NONE) jerr('Invalid JSON from upstream'); if ($provider === 'anthropic') { // Claude response $answer = ''; if (isset($json['content'][0]['text'])) { $answer = $json['content'][0]['text']; } elseif (is_array($json['content'])) { foreach ($json['content'] as $c) { if (isset($c['text'])) { $answer .= $c['text']; } } } } else { // OpenAI / DeepSeek / Grok $answer = $json['choices'][0]['message']['content'] ?? '(no content)'; } $rawUsage = $json['usage'] ?? []; if ($provider === 'anthropic') { $usage = [ 'prompt_tokens' => $rawUsage['input_tokens'] ?? 0, 'completion_tokens' => $rawUsage['output_tokens'] ?? 0, ]; } else { $usage = [ 'prompt_tokens' => $rawUsage['prompt_tokens'] ?? 0, 'completion_tokens' => $rawUsage['completion_tokens'] ?? 0, ]; } /* ---------- Store History ---------- */ $_SESSION['chat_log_map'][$sessionId][] = ['role'=>'user','content'=>$question]; $_SESSION['chat_log_map'][$sessionId][] = ['role'=>'assistant','content'=>$answer]; /* ---------- Store Usage ---------- */ if ($pdo && $usage) { $username = $_SERVER['HTTP_X_USERNAME'] ?? ($_SESSION['username'] ?? 'guest'); $inTok = (int)($usage['prompt_tokens'] ?? 0); $outTok = (int)($usage['completion_tokens'] ?? 0); $cost = estimate_cost($model, $inTok, $outTok); try { $stmt = $pdo->prepare(" INSERT INTO ai_usage (username, session_id, model, provider, input_tokens, output_tokens, total_cost_est) VALUES (:u,:sid,:m,:p,:inTok,:outTok,:cost) "); $stmt->execute([ ':u'=>$username, ':sid'=>$sessionId, ':m'=>$model, ':p'=>$provider, ':inTok'=>$inTok, ':outTok'=>$outTok, ':cost'=>$cost ]); } catch (Throwable $e) { error_log('Usage insert failed: '.$e->getMessage()); } } /* ---------- Output ---------- */ echo json_encode([ 'success'=>true, 'provider'=>$provider, 'model'=>$model, 'answer'=>$answer, 'usage'=>$usage, 'sessionId'=>$sessionId ], JSON_UNESCAPED_SLASHES); exit;