157 lines
5.8 KiB
PHP
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)];
|
|
}
|
|
} |