Compare commits

...

8 Commits

Author SHA1 Message Date
Flatlogic Bot
3e469e4cb2 Edit ai/LocalAIApi.php via Editor 2025-12-08 22:38:18 +00:00
Flatlogic Bot
81870873e6 Edit ai/config.php via Editor 2025-12-08 22:34:54 +00:00
Flatlogic Bot
f0662ef19f Edit .htaccess via Editor 2025-12-08 22:33:26 +00:00
Flatlogic Bot
bf3bd6faea Edit ai/LocalAIApi.php via Editor 2025-12-08 22:18:04 +00:00
Flatlogic Bot
3dfb3c6972 Edit ai/config.php via Editor 2025-12-08 22:11:21 +00:00
Flatlogic Bot
23b74863ee Edit .htaccess via Editor 2025-12-08 22:08:47 +00:00
Flatlogic Bot
3153fc5bc8 Edit ki-check-full.html via Editor 2025-12-08 21:23:59 +00:00
Flatlogic Bot
9929d1dd39 Edit ki-check-full.html via Editor 2025-12-08 20:56:12 +00:00
4 changed files with 4569 additions and 120 deletions

176
.htaccess
View File

@ -1,18 +1,182 @@
# KI-Fit Check Questionnaire - Server Configuration
# For Appwizzy platform compatibility
# Set default index files
DirectoryIndex index.php index.html DirectoryIndex index.php index.html
# Security & Performance
Options -Indexes Options -Indexes
Options -MultiViews Options -MultiViews
ServerSignature Off
# Enable Rewrite Engine
RewriteEngine On RewriteEngine On
# 0) Serve existing files/directories as-is # Force HTTPS (uncomment when SSL is installed)
# RewriteCond %{HTTPS} off
# RewriteCond %{HTTP_HOST} !^localhost [NC]
# RewriteCond %{HTTP_HOST} !^127\.0\.0\.1 [NC]
# RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
# ===== SECURITY HEADERS =====
<IfModule mod_headers.c>
# Prevent MIME type sniffing
Header set X-Content-Type-Options "nosniff"
# Enable XSS protection
Header set X-XSS-Protection "1; mode=block"
# Prevent clickjacking
Header set X-Frame-Options "SAMEORIGIN"
# Referrer Policy
Header set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>
# ===== URL REWRITING =====
# 1) Serve existing files/directories as-is
RewriteCond %{REQUEST_FILENAME} -f [OR] RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L] RewriteRule ^ - [L]
# 1) Internal map: /page or /page/ -> /page.php (if such PHP file exists) # 2) Handle clean URLs for questionnaire
RewriteCond %{REQUEST_FILENAME}.php -f # Rewrite /ki-fit-check to index.php (main questionnaire)
RewriteRule ^(.+?)/?$ $1.php [L] RewriteRule ^ki-fit-check/?$ index.php [L]
# 2) Optional: strip trailing slash for non-directories (keeps .php links working) # 3) Handle other pages
RewriteRule ^kontakt/?$ ki-check.php [L]
RewriteRule ^ergebnisse/?$ results.php [L]
RewriteRule ^erfolg/?$ success.php [L]
# 4) Handle API endpoints
RewriteRule ^api/submit/?$ api/submit.php [L]
RewriteRule ^api/analyze/?$ api/analyze.php [L]
RewriteRule ^api/generate-pdf/?$ api/generate-pdf.php [L]
RewriteRule ^api/send-email/?$ api/send-email.php [L]
# 5) Remove trailing slashes for non-directories
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)/$ $1 [R=301,L] RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [R=301,L]
# 6) Custom error pages
ErrorDocument 404 /404.html
ErrorDocument 500 /500.html
# ===== REDIRECTIONS =====
# Redirect old .php URLs to clean URLs
RewriteRule ^index\.php$ / [R=301,L]
RewriteRule ^ki-check\.php$ /ki-fit-check [R=301,L]
# ===== PERFORMANCE OPTIMIZATION =====
<IfModule mod_expires.c>
ExpiresActive On
# Images
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
# Fonts
ExpiresByType font/ttf "access plus 1 year"
ExpiresByType font/otf "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
# CSS & JavaScript
ExpiresByType text/css "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
# HTML
ExpiresByType text/html "access plus 1 hour"
</IfModule>
<IfModule mod_deflate.c>
# Compress HTML, CSS, JavaScript, Text, XML and fonts
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/atom+xml
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE font/ttf
AddOutputFilterByType DEFLATE font/otf
AddOutputFilterByType DEFLATE font/woff
AddOutputFilterByType DEFLATE font/woff2
</IfModule>
# ===== CORS SETTINGS =====
<IfModule mod_headers.c>
# Allow requests from any origin (adjust for production)
Header set Access-Control-Allow-Origin "*"
# Allow specific methods
Header set Access-Control-Allow-Methods "GET, POST, OPTIONS"
# Allow specific headers
Header set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With"
# Allow credentials
Header set Access-Control-Allow-Credentials "true"
</IfModule>
# ===== CACHE CONTROL =====
<IfModule mod_headers.c>
# Cache static assets
<FilesMatch "\.(css|js|jpg|jpeg|png|gif|svg|woff|woff2|ttf|eot|ico)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
# Don't cache HTML files (except static pages)
<FilesMatch "\.(html|php)$">
Header set Cache-Control "public, max-age=3600, must-revalidate"
</FilesMatch>
</IfModule>
# ===== BLOCK ACCESS TO SENSITIVE FILES =====
<FilesMatch "^\.">
Order allow,deny
Deny from all
</FilesMatch>
<FilesMatch "\.(log|sql|bak|inc|cfg|config|ini|env)$">
Order allow,deny
Deny from all
</FilesMatch>
# Block access to config directories
RedirectMatch 403 ^/ai/.*$
RedirectMatch 403 ^/db/.*$
RedirectMatch 403 ^/mail/.*$
RedirectMatch 403 ^/api/.*$
# ===== PHP SETTINGS =====
<IfModule mod_php.c>
php_value upload_max_filesize 10M
php_value post_max_size 10M
php_value max_execution_time 300
php_value max_input_time 300
php_value memory_limit 256M
</IfModule>
# ===== FOR APPWIZZY COMPATIBILITY =====
# Ensure PHP files are processed correctly
AddType application/x-httpd-php .php
AddHandler application/x-httpd-php .php
# Set default charset
AddDefaultCharset UTF-8
# Disable directory listing
IndexIgnore *

View File

@ -1,38 +1,41 @@
<?php <?php
// LocalAIApi — proxy client for the Responses API. // ai/LocalAIApi.php — proxy client for the Responses API.
// Usage (async: auto-polls status until ready): // For Appwizzy platform compatibility
// require_once __DIR__ . '/ai/LocalAIApi.php';
// $response = LocalAIApi::createResponse([
// 'input' => [
// ['role' => 'system', 'content' => 'You are a helpful assistant.'],
// ['role' => 'user', 'content' => 'Tell me a bedtime story.'],
// ],
// ]);
// if (!empty($response['success'])) {
// // response['data'] contains full payload, e.g.:
// // {
// // "id": "resp_xxx",
// // "status": "completed",
// // "output": [
// // {"type": "reasoning", "summary": []},
// // {"type": "message", "content": [{"type": "output_text", "text": "Your final answer here."}]}
// // ]
// // }
// $decoded = LocalAIApi::decodeJsonFromResponse($response); // or inspect $response['data'] / extractText(...)
// }
// Poll settings override:
// LocalAIApi::createResponse($payload, ['poll_interval' => 5, 'poll_timeout' => 300]);
class LocalAIApi class LocalAIApi
{ {
/** @var array<string,mixed>|null */ /** @var array<string,mixed>|null */
private static ?array $configCache = null; private static ?array $configCache = null;
/** @var array<string,mixed> Default options */
private static array $defaultOptions = [
'poll_interval' => 5,
'poll_timeout' => 300,
'timeout' => 30,
'verify_tls' => true,
'max_retries' => 3,
'retry_delay' => 2,
];
/** /**
* Create an AI response (async: auto-polls status until ready).
* Signature compatible with the OpenAI Responses API. * Signature compatible with the OpenAI Responses API.
* *
* @param array<string,mixed> $params Request body (model, input, text, reasoning, metadata, etc.). * Usage:
* @param array<string,mixed> $options Extra options (timeout, verify_tls, headers, path, project_uuid). * require_once __DIR__ . '/ai/LocalAIApi.php';
* $response = LocalAIApi::createResponse([
* 'input' => [
* ['role' => 'system', 'content' => 'You are a helpful assistant.'],
* ['role' => 'user', 'content' => 'Tell me a bedtime story.'],
* ],
* ]);
* if (!empty($response['success'])) {
* // response['data'] contains full payload
* $decoded = LocalAIApi::decodeJsonFromResponse($response);
* }
*
* @param array<string,mixed> $params Request body (model, input, text, reasoning, metadata, etc.)
* @param array<string,mixed> $options Extra options (timeout, verify_tls, headers, path, project_uuid)
* @return array{ * @return array{
* success: bool, * success: bool,
* status?: int, * status?: int,
@ -47,6 +50,7 @@ class LocalAIApi
$cfg = self::config(); $cfg = self::config();
$payload = $params; $payload = $params;
// Validate input
if (empty($payload['input']) || !is_array($payload['input'])) { if (empty($payload['input']) || !is_array($payload['input'])) {
return [ return [
'success' => false, 'success' => false,
@ -55,10 +59,15 @@ class LocalAIApi
]; ];
} }
// Set default model if not provided
if (!isset($payload['model']) || $payload['model'] === '') { if (!isset($payload['model']) || $payload['model'] === '') {
$payload['model'] = $cfg['default_model']; $payload['model'] = $cfg['default_model'];
} }
// Merge with default options
$options = array_merge(self::$defaultOptions, $options);
// Make initial request
$initial = self::request($options['path'] ?? null, $payload, $options); $initial = self::request($options['path'] ?? null, $payload, $options);
if (empty($initial['success'])) { if (empty($initial['success'])) {
return $initial; return $initial;
@ -68,14 +77,7 @@ class LocalAIApi
$data = $initial['data'] ?? null; $data = $initial['data'] ?? null;
if (is_array($data) && isset($data['ai_request_id'])) { if (is_array($data) && isset($data['ai_request_id'])) {
$aiRequestId = $data['ai_request_id']; $aiRequestId = $data['ai_request_id'];
$pollTimeout = isset($options['poll_timeout']) ? (int) $options['poll_timeout'] : 300; // seconds return self::awaitResponse($aiRequestId, $options);
$pollInterval = isset($options['poll_interval']) ? (int) $options['poll_interval'] : 5; // seconds
return self::awaitResponse($aiRequestId, [
'timeout' => $pollTimeout,
'interval' => $pollInterval,
'headers' => $options['headers'] ?? [],
'timeout_per_call' => $options['timeout'] ?? null,
]);
} }
return $initial; return $initial;
@ -96,9 +98,9 @@ class LocalAIApi
/** /**
* Perform a raw request to the AI proxy. * Perform a raw request to the AI proxy.
* *
* @param string $path Endpoint (may be an absolute URL). * @param string|null $path Endpoint (may be an absolute URL)
* @param array<string,mixed> $payload JSON payload. * @param array<string,mixed> $payload JSON payload
* @param array<string,mixed> $options Additional request options. * @param array<string,mixed> $options Additional request options
* @return array<string,mixed> * @return array<string,mixed>
*/ */
public static function request(?string $path = null, array $payload = [], array $options = []): array public static function request(?string $path = null, array $payload = [], array $options = []): array
@ -125,16 +127,10 @@ class LocalAIApi
} }
$url = self::buildUrl($resolvedPath, $cfg['base_url']); $url = self::buildUrl($resolvedPath, $cfg['base_url']);
$baseTimeout = isset($cfg['timeout']) ? (int) $cfg['timeout'] : 30; $timeout = $options['timeout'] ?? $cfg['timeout'] ?? 30;
$timeout = isset($options['timeout']) ? (int) $options['timeout'] : $baseTimeout; $verifyTls = $options['verify_tls'] ?? $cfg['verify_tls'] ?? true;
if ($timeout <= 0) { $maxRetries = $options['max_retries'] ?? $cfg['max_retries'] ?? 3;
$timeout = 30; $retryDelay = $options['retry_delay'] ?? $cfg['retry_delay'] ?? 2;
}
$baseVerifyTls = array_key_exists('verify_tls', $cfg) ? (bool) $cfg['verify_tls'] : true;
$verifyTls = array_key_exists('verify_tls', $options)
? (bool) $options['verify_tls']
: $baseVerifyTls;
$projectHeader = $cfg['project_header']; $projectHeader = $cfg['project_header'];
@ -143,6 +139,7 @@ class LocalAIApi
'Accept: application/json', 'Accept: application/json',
]; ];
$headers[] = $projectHeader . ': ' . $projectUuid; $headers[] = $projectHeader . ': ' . $projectUuid;
if (!empty($options['headers']) && is_array($options['headers'])) { if (!empty($options['headers']) && is_array($options['headers'])) {
foreach ($options['headers'] as $header) { foreach ($options['headers'] as $header) {
if (is_string($header) && $header !== '') { if (is_string($header) && $header !== '') {
@ -151,11 +148,17 @@ class LocalAIApi
} }
} }
// Add project_uuid to payload if not present
if (!empty($projectUuid) && !array_key_exists('project_uuid', $payload)) { if (!empty($projectUuid) && !array_key_exists('project_uuid', $payload)) {
$payload['project_uuid'] = $projectUuid; $payload['project_uuid'] = $projectUuid;
} }
$body = json_encode($payload, JSON_UNESCAPED_UNICODE); // Add debug info if enabled
if ($cfg['debug'] ?? false) {
$payload['debug'] = true;
}
$body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($body === false) { if ($body === false) {
return [ return [
'success' => false, 'success' => false,
@ -164,7 +167,38 @@ class LocalAIApi
]; ];
} }
return self::sendCurl($url, 'POST', $body, $headers, $timeout, $verifyTls); // Retry logic
$attempt = 0;
$lastError = null;
while ($attempt < $maxRetries) {
$attempt++;
$result = self::sendCurl($url, 'POST', $body, $headers, $timeout, $verifyTls);
if ($result['success']) {
return $result;
}
$lastError = $result;
// Don't retry on client errors (4xx)
$status = $result['status'] ?? 0;
if ($status >= 400 && $status < 500) {
break;
}
// Wait before retrying
if ($attempt < $maxRetries) {
sleep($retryDelay);
}
}
return $lastError ?? [
'success' => false,
'error' => 'max_retries_exceeded',
'message' => 'Maximum retry attempts exceeded.',
];
} }
/** /**
@ -178,38 +212,44 @@ class LocalAIApi
{ {
$cfg = self::config(); $cfg = self::config();
$timeout = isset($options['timeout']) ? (int) $options['timeout'] : 300; // seconds $timeout = $options['poll_timeout'] ?? 300; // seconds
$interval = isset($options['interval']) ? (int) $options['interval'] : 5; // seconds $interval = $options['poll_interval'] ?? 5; // seconds
if ($interval <= 0) { if ($interval <= 0) {
$interval = 5; $interval = 5;
} }
$perCallTimeout = isset($options['timeout_per_call']) ? (int) $options['timeout_per_call'] : null; $perCallTimeout = $options['timeout'] ?? null;
$deadline = time() + max($timeout, $interval); $deadline = time() + max($timeout, $interval);
$headers = $options['headers'] ?? []; $headers = $options['headers'] ?? [];
$attempt = 0;
while (true) { while (true) {
$attempt++;
$statusResp = self::fetchStatus($aiRequestId, [ $statusResp = self::fetchStatus($aiRequestId, [
'headers' => $headers, 'headers' => $headers,
'timeout' => $perCallTimeout, 'timeout' => $perCallTimeout,
]); ]);
if (!empty($statusResp['success'])) { if (!empty($statusResp['success'])) {
$data = $statusResp['data'] ?? []; $data = $statusResp['data'] ?? [];
if (is_array($data)) { if (is_array($data)) {
$statusValue = $data['status'] ?? null; $statusValue = $data['status'] ?? null;
if ($statusValue === 'success') { if ($statusValue === 'success' || $statusValue === 'completed') {
return [ return [
'success' => true, 'success' => true,
'status' => 200, 'status' => 200,
'data' => $data['response'] ?? $data, 'data' => $data['response'] ?? $data,
'attempts' => $attempt,
]; ];
} }
if ($statusValue === 'failed') { if ($statusValue === 'failed' || $statusValue === 'error') {
return [ return [
'success' => false, 'success' => false,
'status' => 500, 'status' => 500,
'error' => isset($data['error']) ? (string)$data['error'] : 'AI request failed', 'error' => isset($data['error']) ? (string)$data['error'] : 'AI request failed',
'data' => $data, 'data' => $data,
'attempts' => $attempt,
]; ];
} }
} }
@ -221,10 +261,14 @@ class LocalAIApi
return [ return [
'success' => false, 'success' => false,
'error' => 'timeout', 'error' => 'timeout',
'message' => 'Timed out waiting for AI response.', 'message' => 'Timed out waiting for AI response after ' . $attempt . ' attempts.',
'attempts' => $attempt,
]; ];
} }
sleep($interval);
// Exponential backoff
$sleepTime = $interval * (1 + ($attempt * 0.1));
sleep(min($sleepTime, 30)); // Max 30 seconds between polls
} }
} }
@ -250,16 +294,8 @@ class LocalAIApi
$statusPath = self::resolveStatusPath($aiRequestId, $cfg); $statusPath = self::resolveStatusPath($aiRequestId, $cfg);
$url = self::buildUrl($statusPath, $cfg['base_url']); $url = self::buildUrl($statusPath, $cfg['base_url']);
$baseTimeout = isset($cfg['timeout']) ? (int) $cfg['timeout'] : 30; $timeout = $options['timeout'] ?? $cfg['timeout'] ?? 30;
$timeout = isset($options['timeout']) ? (int) $options['timeout'] : $baseTimeout; $verifyTls = $options['verify_tls'] ?? $cfg['verify_tls'] ?? true;
if ($timeout <= 0) {
$timeout = 30;
}
$baseVerifyTls = array_key_exists('verify_tls', $cfg) ? (bool) $cfg['verify_tls'] : true;
$verifyTls = array_key_exists('verify_tls', $options)
? (bool) $options['verify_tls']
: $baseVerifyTls;
$projectHeader = $cfg['project_header']; $projectHeader = $cfg['project_header'];
$headers = [ $headers = [
@ -280,7 +316,7 @@ class LocalAIApi
/** /**
* Extract plain text from a Responses API payload. * Extract plain text from a Responses API payload.
* *
* @param array<string,mixed> $response Result of LocalAIApi::createResponse|request. * @param array<string,mixed> $response Result of LocalAIApi::createResponse|request
* @return string * @return string
*/ */
public static function extractText(array $response): string public static function extractText(array $response): string
@ -290,6 +326,7 @@ class LocalAIApi
return ''; return '';
} }
// Try to extract from OpenAI Responses API format
if (!empty($payload['output']) && is_array($payload['output'])) { if (!empty($payload['output']) && is_array($payload['output'])) {
$combined = ''; $combined = '';
foreach ($payload['output'] as $item) { foreach ($payload['output'] as $item) {
@ -307,10 +344,16 @@ class LocalAIApi
} }
} }
// Try to extract from OpenAI Chat Completion format
if (!empty($payload['choices'][0]['message']['content'])) { if (!empty($payload['choices'][0]['message']['content'])) {
return (string) $payload['choices'][0]['message']['content']; return (string) $payload['choices'][0]['message']['content'];
} }
// Try to extract from generic response
if (!empty($payload['text'])) {
return (string) $payload['text'];
}
return ''; return '';
} }
@ -327,12 +370,14 @@ class LocalAIApi
return null; return null;
} }
// First try to decode directly
$decoded = json_decode($text, true); $decoded = json_decode($text, true);
if (is_array($decoded)) { if (is_array($decoded)) {
return $decoded; return $decoded;
} }
$stripped = preg_replace('/^```json|```$/m', '', trim($text)); // Try stripping markdown code fences
$stripped = preg_replace('/^```(?:json)?\s*\n|\n```$/m', '', trim($text));
if ($stripped !== null && $stripped !== $text) { if ($stripped !== null && $stripped !== $text) {
$decoded = json_decode($stripped, true); $decoded = json_decode($stripped, true);
if (is_array($decoded)) { if (is_array($decoded)) {
@ -340,6 +385,14 @@ class LocalAIApi
} }
} }
// Try to extract JSON from text
if (preg_match('/\{.*\}/s', $text, $matches)) {
$decoded = json_decode($matches[0], true);
if (is_array($decoded)) {
return $decoded;
}
}
return null; return null;
} }
@ -347,6 +400,7 @@ class LocalAIApi
* Load configuration from ai/config.php. * Load configuration from ai/config.php.
* *
* @return array<string,mixed> * @return array<string,mixed>
* @throws RuntimeException
*/ */
private static function config(): array private static function config(): array
{ {
@ -359,7 +413,20 @@ class LocalAIApi
if (!is_array($cfg)) { if (!is_array($cfg)) {
throw new RuntimeException('Invalid AI config format: expected array'); throw new RuntimeException('Invalid AI config format: expected array');
} }
self::$configCache = $cfg;
// Merge with defaults
$defaults = [
'debug' => false,
'max_retries' => 3,
'retry_delay' => 2,
'features' => [
'async_polling' => true,
'json_response' => true,
'streaming' => false,
],
];
self::$configCache = array_merge($defaults, $cfg);
} }
return self::$configCache; return self::$configCache;
@ -420,7 +487,7 @@ class LocalAIApi
return [ return [
'success' => false, 'success' => false,
'error' => 'curl_missing', 'error' => 'curl_missing',
'message' => 'PHP cURL extension is missing. Install or enable it on the VM.', 'message' => 'PHP cURL extension is missing. Install or enable it on the server.',
]; ];
} }
@ -433,6 +500,16 @@ class LocalAIApi
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verifyTls ? 2 : 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verifyTls ? 2 : 0);
curl_setopt($ch, CURLOPT_FAILONERROR, false); curl_setopt($ch, CURLOPT_FAILONERROR, false);
// Add debug info if enabled
$cfg = self::config();
if ($cfg['debug'] ?? false) {
curl_setopt($ch, CURLOPT_VERBOSE, true);
$debugFile = fopen(__DIR__ . '/../logs/curl_debug.log', 'a');
if ($debugFile) {
curl_setopt($ch, CURLOPT_STDERR, $debugFile);
}
}
$upper = strtoupper($method); $upper = strtoupper($method);
if ($upper === 'POST') { if ($upper === 'POST') {
curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POST, true);
@ -442,19 +519,24 @@ class LocalAIApi
} }
$responseBody = curl_exec($ch); $responseBody = curl_exec($ch);
if ($responseBody === false) { $error = curl_error($ch);
$error = curl_error($ch) ?: 'Unknown cURL error'; $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch); curl_close($ch);
// Close debug file if opened
if (isset($debugFile) && is_resource($debugFile)) {
fclose($debugFile);
}
if ($responseBody === false) {
return [ return [
'success' => false, 'success' => false,
'error' => 'curl_error', 'error' => 'curl_error',
'message' => $error, 'message' => $error ?: 'Unknown cURL error',
'status' => $status,
]; ];
} }
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$decoded = null; $decoded = null;
if ($responseBody !== '' && $responseBody !== null) { if ($responseBody !== '' && $responseBody !== null) {
$decoded = json_decode($responseBody, true); $decoded = json_decode($responseBody, true);
@ -491,3 +573,17 @@ class LocalAIApi
if (!class_exists('OpenAIService')) { if (!class_exists('OpenAIService')) {
class_alias(LocalAIApi::class, 'OpenAIService'); class_alias(LocalAIApi::class, 'OpenAIService');
} }
// Helper function for quick usage
if (!function_exists('ai_request')) {
/**
* Helper function for quick AI requests
*
* @param array<string,mixed> $params
* @param array<string,mixed> $options
* @return array<string,mixed>
*/
function ai_request(array $params, array $options = []): array {
return LocalAIApi::createResponse($params, $options);
}
}

View File

@ -1,7 +1,14 @@
<?php <?php
// OpenAI proxy configuration (workspace scope). // ai/config.php - OpenAI proxy configuration (workspace scope)
// Reads values from environment variables or executor/.env. // Reads values from environment variables or executor/.env
// For Appwizzy platform compatibility
/**
* Load environment variables from .env file if not already set
* This ensures compatibility with both CLI and web environments
*/
function loadEnvIfNeeded() {
// Check if required environment variables are missing
$projectUuid = getenv('PROJECT_UUID'); $projectUuid = getenv('PROJECT_UUID');
$projectId = getenv('PROJECT_ID'); $projectId = getenv('PROJECT_ID');
@ -29,17 +36,26 @@ if (
putenv("{$key}={$value}"); putenv("{$key}={$value}");
} }
} }
}
}
}
// Load environment variables if needed
loadEnvIfNeeded();
// Get configuration values
$projectUuid = getenv('PROJECT_UUID'); $projectUuid = getenv('PROJECT_UUID');
$projectId = getenv('PROJECT_ID'); $projectId = getenv('PROJECT_ID');
}
}
$projectUuid = ($projectUuid === false) ? null : $projectUuid; // Set defaults if not found
$projectId = ($projectId === false) ? null : $projectId; $projectUuid = ($projectUuid === false || $projectUuid === '') ? null : $projectUuid;
$projectId = ($projectId === false || $projectId === '') ? null : $projectId;
// Base configuration
$baseUrl = 'https://flatlogic.com'; $baseUrl = 'https://flatlogic.com';
$responsesPath = $projectId ? "/projects/{$projectId}/ai-request" : null; $responsesPath = $projectId ? "/projects/{$projectId}/ai-request" : null;
// Return configuration array
return [ return [
'base_url' => $baseUrl, 'base_url' => $baseUrl,
'responses_path' => $responsesPath, 'responses_path' => $responsesPath,
@ -49,4 +65,23 @@ return [
'default_model' => 'gpt-5-mini', 'default_model' => 'gpt-5-mini',
'timeout' => 30, 'timeout' => 30,
'verify_tls' => true, 'verify_tls' => true,
// Additional settings for Appwizzy compatibility
'debug' => getenv('APP_ENV') === 'development' || getenv('APP_DEBUG') === 'true',
'max_retries' => 3,
'retry_delay' => 2,
// Feature flags
'features' => [
'async_polling' => true,
'json_response' => true,
'streaming' => false,
],
// Cache settings
'cache' => [
'enabled' => getenv('AI_CACHE_ENABLED') === 'true',
'ttl' => 3600, // 1 hour
'path' => __DIR__ . '/../cache/ai-responses',
],
]; ];

4154
ki-check-full.html Normal file

File diff suppressed because it is too large Load Diff