38751-vm/ai/LocalAIApi.php
2026-03-16 10:14:32 +00:00

157 lines
5.8 KiB
PHP

<?php
// LocalAIApi — proxy client, now with direct OpenAI support for Shared Hosting.
class LocalAIApi
{
private static ?array $configCache = null;
public static function createResponse(array $params, array $options = []): array
{
// Check if local key exists (from workspace .env)
$localKey = getenv('OPENAI_API_KEY');
if ($localKey && str_starts_with($localKey, 'sk-')) {
return self::directRequest($params, $localKey);
}
// Default to original proxy flow
$cfg = self::config();
$payload = $params;
if (empty($payload['input']) || !is_array($payload['input'])) {
return ['success' => false, 'error' => 'input_missing', 'message' => 'Parameter "input" is required.'];
}
if (!isset($payload['model']) || $payload['model'] === '') {
$payload['model'] = $cfg['default_model'];
}
$initial = self::request($options['path'] ?? null, $payload, $options);
if (empty($initial['success'])) return $initial;
$data = $initial['data'] ?? null;
if (is_array($data) && isset($data['ai_request_id'])) {
return self::awaitResponse($data['ai_request_id'], $options);
}
return $initial;
}
private static function directRequest(array $params, string $apiKey): array
{
$url = 'https://api.openai.com/v1/chat/completions';
// Map input to messages
$messages = [];
foreach ($params['input'] as $msg) {
$messages[] = ['role' => $msg['role'] ?? 'user', 'content' => $msg['content'] ?? ''];
}
$payload = [
'model' => $params['model'] ?? 'gpt-4o',
'messages' => $messages
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey
]);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$result = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
if ($err) return ['success' => false, 'error' => $err];
$data = json_decode($result, true);
// Wrap to match existing structure
return [
'success' => true,
'data' => [
'choices' => $data['choices'] ?? []
]
];
}
public static function create_response(array $params, array $options = []): array { return self::createResponse($params, $options); }
public static function request(?string $path = null, array $payload = [], array $options = []): array
{
$cfg = self::config();
$projectUuid = $cfg['project_uuid'];
$defaultPath = $cfg['responses_path'] ?? null;
$resolvedPath = $path ?? ($options['path'] ?? $defaultPath);
$url = self::buildUrl($resolvedPath, $cfg['base_url']);
$timeout = isset($options['timeout']) ? (int) $options['timeout'] : 30;
$headers = ['Content-Type: application/json', 'Accept: application/json', ($cfg['project_header'] ?? 'Project-UUID') . ': ' . $projectUuid];
$payload['project_uuid'] = $projectUuid;
return self::sendCurl($url, 'POST', json_encode($payload), $headers, $timeout, true);
}
public static function awaitResponse($aiRequestId, array $options = []): array
{
$deadline = time() + 300;
while (time() < $deadline) {
$statusResp = self::fetchStatus($aiRequestId);
if (!empty($statusResp['success'])) {
$data = $statusResp['data'];
if (($data['status'] ?? '') === 'success') return ['success' => true, 'data' => $data['response'] ?? $data];
if (($data['status'] ?? '') === 'failed') return ['success' => false, 'error' => 'AI request failed'];
}
sleep(5);
}
return ['success' => false, 'error' => 'timeout'];
}
public static function fetchStatus($aiRequestId): array
{
$cfg = self::config();
$url = self::buildUrl('/ai-request/' . rawurlencode((string)$aiRequestId) . '/status', $cfg['base_url']);
$headers = [($cfg['project_header'] ?? 'Project-UUID') . ': ' . $cfg['project_uuid']];
return self::sendCurl($url, 'GET', null, $headers, 30, true);
}
public static function extractText(array $response): string
{
$payload = $response['data'] ?? $response;
if (!empty($payload['choices'][0]['message']['content'])) return (string) $payload['choices'][0]['message']['content'];
return '';
}
private static function config(): array
{
if (self::$configCache === null) self::$configCache = require __DIR__ . '/config.php';
return self::$configCache;
}
private static function buildUrl(string $path, string $baseUrl): string
{
return str_starts_with($path, 'http') ? $path : $baseUrl . '/' . ltrim($path, '/');
}
private static function sendCurl(string $url, string $method, ?string $body, array $headers, int $timeout, bool $verifyTls): array
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_SSL_VERIFYPEER => $verifyTls,
]);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$resp = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return ['success' => ($status >= 200 && $status < 300), 'data' => json_decode($resp, true)];
}
}