HEX
Server: LiteSpeed
System: Linux houston.panomity.com 6.8.0-100-generic #100-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 13 16:40:06 UTC 2026 x86_64
User: nudepix (1011)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: /home/panomity.de/public_html/unhinged-ai/chat-backend.php
<?php
declare(strict_types=1);

function load_system_prompt(): array
{
    $path = __DIR__ . DIRECTORY_SEPARATOR . 'system.txt';
    $default = "You are a precise, concise assistant. Answer directly, avoid fluff, and show steps only when asked.";

    if (is_file($path) && is_readable($path)) {
        $raw = file_get_contents($path);
        if ($raw !== false) {
            if (substr($raw, 0, 3) === "\xEF\xBB\xBF") {
                $raw = substr($raw, 3);
            }
            $content = trim($raw);
            return [$content === '' ? $default : $content, true];
        }
    }

    return [$default, false];
}

function current_history(): array
{
    if (!isset($_SESSION['history']) || !is_array($_SESSION['history'])) {
        $_SESSION['history'] = [];
    }
    return $_SESSION['history'];
}

function save_history(array $history): void
{
    $_SESSION['history'] = $history;
}

function reset_session_chat(): void
{
    $_SESSION['initialized'] = false;
    $_SESSION['system_content'] = null;
    $_SESSION['history'] = [];
}

function unhinged_read_json_body(): array
{
    $raw = file_get_contents('php://input');
    if ($raw === false || $raw === '') {
        return [];
    }
    $decoded = json_decode($raw, true);
    return is_array($decoded) ? $decoded : [];
}

function unhinged_json_response(array $payload, int $status = 200): void
{
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

function unhinged_http_json(string $method, string $url, ?array $payload, int $timeoutSeconds): array
{
    $ch = curl_init($url);
    if ($ch === false) {
        return [
            'ok' => false,
            'status' => 0,
            'error' => 'Failed to initialize cURL',
            'json' => null,
            'raw' => '',
        ];
    }

    $headers = ['Accept: application/json', 'Expect:'];
    $body = null;
    if ($payload !== null) {
        $body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if ($body === false) {
            curl_close($ch);
            return [
                'ok' => false,
                'status' => 0,
                'error' => 'JSON encoding failed',
                'json' => null,
                'raw' => '',
            ];
        }
        $headers[] = 'Content-Type: application/json';
    }

    $upperMethod = strtoupper($method);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST => $upperMethod,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_TIMEOUT => max(5, $timeoutSeconds),
    ]);

    if ($body !== null) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
    }

    $raw = curl_exec($ch);
    $status = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    if ($raw === false) {
        $err = curl_error($ch);
        curl_close($ch);
        return [
            'ok' => false,
            'status' => 0,
            'error' => $err !== '' ? $err : 'HTTP request failed',
            'json' => null,
            'raw' => '',
        ];
    }
    curl_close($ch);

    $decoded = json_decode((string)$raw, true);
    if (!is_array($decoded)) {
        $decoded = null;
    }

    return [
        'ok' => $status >= 200 && $status < 300,
        'status' => $status,
        'error' => '',
        'json' => $decoded,
        'raw' => (string)$raw,
    ];
}

function unhinged_is_ollama_reachable(string $ollamaBase, int $timeoutSeconds = 2): bool
{
    $res = unhinged_http_json('GET', rtrim($ollamaBase, '/') . '/api/tags', null, max(1, $timeoutSeconds));
    return $res['ok'] === true;
}

function unhinged_wait_for_ollama(string $ollamaBase, int $maxWaitSeconds = 30): bool
{
    $deadline = microtime(true) + max(2, $maxWaitSeconds);
    while (microtime(true) < $deadline) {
        if (unhinged_is_ollama_reachable($ollamaBase, 2)) {
            return true;
        }
        usleep(500000);
    }
    return false;
}

function unhinged_start_ollama_if_needed(string $ollamaBase): array
{
    if (unhinged_is_ollama_reachable($ollamaBase, 2)) {
        return ['ok' => true, 'started' => false];
    }

    @exec('sudo -n systemctl start ollama >/dev/null 2>&1');
    if (unhinged_wait_for_ollama($ollamaBase, 20)) {
        return ['ok' => true, 'started' => true];
    }

    @exec('nohup /usr/local/bin/ollama serve >/tmp/ollama-unhinged.log 2>&1 &');
    if (unhinged_wait_for_ollama($ollamaBase, 25)) {
        return ['ok' => true, 'started' => true];
    }

    return ['ok' => false, 'started' => false];
}

function unhinged_call_ollama_chat(
    string $ollamaBase,
    string $model,
    array $messages,
    array $options,
    int $timeoutSeconds
): array {
    $start = unhinged_start_ollama_if_needed($ollamaBase);
    if (empty($start['ok'])) {
        return [
            'ok' => false,
            'message' => 'Ollama ist offline und konnte nicht gestartet werden.',
            'assistant_content' => '',
            'ollama_started' => false,
        ];
    }

    $payload = [
        'model' => $model,
        'stream' => false,
        'messages' => array_values($messages),
        'options' => (array)$options,
    ];

    $res = unhinged_http_json(
        'POST',
        rtrim($ollamaBase, '/') . '/api/chat',
        $payload,
        max(20, min(1800, $timeoutSeconds))
    );

    if ($res['ok'] !== true) {
        $message = $res['error'];
        if ($message === '') {
            $message = 'HTTP ' . (int)$res['status'];
        }
        if (is_array($res['json']) && isset($res['json']['error'])) {
            $message = (string)$res['json']['error'];
        }
        return [
            'ok' => false,
            'message' => $message,
            'assistant_content' => '',
            'ollama_started' => (bool)$start['started'],
        ];
    }

    $json = is_array($res['json']) ? $res['json'] : [];
    $assistant = trim((string)($json['message']['content'] ?? ''));
    if ($assistant === '') {
        return [
            'ok' => false,
            'message' => 'Leere Antwort vom Modell.',
            'assistant_content' => '',
            'ollama_started' => (bool)$start['started'],
        ];
    }

    return [
        'ok' => true,
        'message' => '',
        'assistant_content' => $assistant,
        'ollama_started' => (bool)$start['started'],
    ];
}

function unhinged_handle_execute_request(array $config): void
{
    $method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
    if (strtoupper($method) !== 'POST') {
        unhinged_json_response(['success' => false, 'message' => 'Nur POST wird unterstützt.'], 405);
    }

    $body = unhinged_read_json_body();
    $model = trim((string)($body['model'] ?? ''));
    $messagesIn = $body['messages'] ?? null;
    $options = (array)($body['options'] ?? []);
    $timeoutSeconds = (int)($body['timeout_seconds'] ?? 300);

    if ($model === '') {
        unhinged_json_response(['success' => false, 'message' => 'model fehlt'], 400);
    }
    if (!is_array($messagesIn) || $messagesIn === []) {
        unhinged_json_response(['success' => false, 'message' => 'messages fehlt'], 400);
    }

    $messages = [];
    foreach ($messagesIn as $item) {
        if (!is_array($item)) {
            continue;
        }
        $role = trim((string)($item['role'] ?? ''));
        $content = (string)($item['content'] ?? '');
        if ($role === '' || $content === '') {
            continue;
        }
        $messages[] = ['role' => $role, 'content' => $content];
    }
    if ($messages === []) {
        unhinged_json_response(['success' => false, 'message' => 'messages ist ungültig'], 400);
    }

    $ollamaBase = (string)($config['ollama_base'] ?? 'http://127.0.0.1:11434');
    $run = unhinged_call_ollama_chat($ollamaBase, $model, $messages, $options, $timeoutSeconds);
    if (empty($run['ok'])) {
        unhinged_json_response(
            [
                'success' => false,
                'message' => (string)($run['message'] ?? 'Ausführung fehlgeschlagen'),
                'ollama_started' => (bool)($run['ollama_started'] ?? false),
            ],
            502
        );
    }

    unhinged_json_response(
        [
            'success' => true,
            'assistant_content' => (string)$run['assistant_content'],
            'model' => $model,
            'ollama_started' => (bool)($run['ollama_started'] ?? false),
        ]
    );
}

function unhinged_submit_gpuq_job(array $config, string $mode, string $model, array $messages): array
{
    $gpuqBase = rtrim((string)($config['gpuq_api_base'] ?? 'http://127.0.0.1:8020'), '/');
    $executeUrl = (string)($config['execute_url'] ?? '');
    $options = (array)($config['options'] ?? []);
    $chatTimeout = $mode === 'deep' ? 1500 : 900;
    $queueTimeout = $mode === 'deep' ? 1800 : 1200;

    if ($executeUrl === '') {
        return ['ok' => false, 'message' => 'execute_url ist nicht gesetzt.'];
    }

    $submitPayload = [
        'app_name' => 'unhinged',
        'job_type' => 'chat_generate',
        'priority' => $mode === 'deep' ? 'normal' : 'high',
        'execute_url' => $executeUrl,
        'execute_method' => 'POST',
        'timeout_seconds' => $queueTimeout,
        'payload' => [
            'model' => $model,
            'messages' => $messages,
            'options' => $options,
            'timeout_seconds' => $chatTimeout,
        ],
    ];

    $res = unhinged_http_json('POST', $gpuqBase . '/api/v1/jobs', $submitPayload, 20);
    if ($res['ok'] !== true) {
        $message = $res['error'];
        if ($message === '' && is_array($res['json']) && isset($res['json']['detail'])) {
            $message = is_string($res['json']['detail']) ? $res['json']['detail'] : json_encode($res['json']['detail']);
        }
        if ($message === '') {
            $message = 'GPUQ HTTP ' . (int)$res['status'];
        }
        return ['ok' => false, 'message' => $message];
    }

    $json = is_array($res['json']) ? $res['json'] : [];
    $jobId = trim((string)($json['job_id'] ?? ''));
    if ($jobId === '') {
        return ['ok' => false, 'message' => 'GPUQ lieferte keine job_id zurück.'];
    }

    return ['ok' => true, 'job_id' => $jobId];
}

function unhinged_wait_for_gpuq_result(array $config, string $jobId, int $maxWaitSeconds): array
{
    $gpuqBase = rtrim((string)($config['gpuq_api_base'] ?? 'http://127.0.0.1:8020'), '/');
    $deadline = time() + max(15, $maxWaitSeconds);

    while (time() <= $deadline) {
        $res = unhinged_http_json(
            'GET',
            $gpuqBase . '/api/v1/jobs/' . rawurlencode($jobId),
            null,
            20
        );
        if ($res['ok'] !== true || !is_array($res['json'])) {
            $msg = $res['error'] !== '' ? $res['error'] : ('GPUQ Status HTTP ' . (int)$res['status']);
            return ['ok' => false, 'message' => $msg];
        }

        $statusPayload = $res['json'];
        $status = (string)($statusPayload['status'] ?? '');

        if ($status === 'completed') {
            $result = $statusPayload['result'] ?? [];
            if (!is_array($result)) {
                $result = ['raw' => (string)$result];
            }
            return ['ok' => true, 'result' => $result];
        }

        if ($status === 'failed' || $status === 'canceled' || $status === 'killed') {
            $message = (string)($statusPayload['message'] ?? '');
            $result = $statusPayload['result'] ?? null;
            if ($message === '' && is_array($result) && isset($result['message'])) {
                $message = (string)$result['message'];
            }
            if ($message === '') {
                $message = 'GPUQ-Job fehlgeschlagen (' . $status . ').';
            }
            return ['ok' => false, 'message' => $message];
        }

        usleep(900000);
    }

    return ['ok' => false, 'message' => 'GPUQ-Timeout beim Warten auf das Ergebnis.'];
}

function unhinged_stream_chat(string $userPrompt, string $mode, array $config): void
{
    $fastModel = (string)($config['fast_model'] ?? 'huihui_ai/gemma3-abliterated:12b');
    $deepModel = (string)($config['thoughtful_model'] ?? 'huihui_ai/qwen3.5-abliterated:35b');
    $model = $mode === 'deep' ? $deepModel : $fastModel;

    [$systemPrompt] = load_system_prompt();

    header('Content-Type: text/plain; charset=utf-8');
    header('Cache-Control: no-cache, no-transform');
    header('X-Accel-Buffering: no');
    @ini_set('output_buffering', 'off');
    @ini_set('zlib.output_compression', '0');
    while (ob_get_level() > 0) {
        @ob_end_flush();
    }
    ob_implicit_flush(true);

    if (empty($_SESSION['initialized'])) {
        $_SESSION['system_content'] = $userPrompt . "\n\n" . $systemPrompt;
        $_SESSION['initialized'] = true;
        if (!isset($_SESSION['history']) || !is_array($_SESSION['history'])) {
            $_SESSION['history'] = [];
        }
    }

    $systemContent = (string)($_SESSION['system_content'] ?? $systemPrompt);
    $history = current_history();

    $messages = [];
    $messages[] = ['role' => 'system', 'content' => $systemContent];
    foreach ($history as $msg) {
        if (!is_array($msg)) {
            continue;
        }
        if (!isset($msg['role'], $msg['content'])) {
            continue;
        }
        $messages[] = [
            'role' => (string)$msg['role'],
            'content' => (string)$msg['content'],
        ];
    }
    $messages[] = ['role' => 'user', 'content' => $userPrompt];

    $submit = unhinged_submit_gpuq_job($config, $mode, $model, $messages);
    if (empty($submit['ok'])) {
        http_response_code(502);
        echo '[GPUQ submit error] ' . (string)($submit['message'] ?? 'Unbekannter Fehler') . "\n";
        flush();
        return;
    }

    $maxWait = $mode === 'deep' ? 1800 : 1200;
    $resultPayload = unhinged_wait_for_gpuq_result($config, (string)$submit['job_id'], $maxWait);
    if (empty($resultPayload['ok'])) {
        http_response_code(502);
        echo '[GPUQ job error] ' . (string)($resultPayload['message'] ?? 'Unbekannter Fehler') . "\n";
        flush();
        return;
    }

    $result = (array)($resultPayload['result'] ?? []);
    $success = !empty($result['success']);
    if (!$success) {
        http_response_code(502);
        $message = (string)($result['message'] ?? 'Backend-Fehler');
        echo '[Backend error] ' . ($message !== '' ? $message : 'Unbekannter Fehler') . "\n";
        flush();
        return;
    }

    $assistantBuffer = (string)($result['assistant_content'] ?? '');
    if ($assistantBuffer === '') {
        http_response_code(502);
        echo "[Backend error] Leere Antwort vom Modell.\n";
        flush();
        return;
    }

    // Persist turn to session history.
    $history[] = ['role' => 'user', 'content' => $userPrompt];
    $history[] = ['role' => 'assistant', 'content' => $assistantBuffer];
    save_history($history);

    echo $assistantBuffer;
    echo "\n";
    flush();
}