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/index1.php
<?php
/**
 * ollama_chat_stream_dark.php
 *
 * Single-file PHP app for:
 *  - Dark-mode chat UI (Verdana)
 *  - Streaming multi-turn *chat* responses from Ollama (/api/chat)
 *  - System prompt loaded from ./system.txt
 *  - Mode switch in UI:
 *      - "Schnell"     → gpt-oss:20b
 *      - "Durchdacht"  → gpt-oss:120b
 *
 * Special requirement:
 *  - On the very first user message of a session, prepend that message to the
 *    system prompt (as a prefix), and ALSO send it as the normal user message.
 *
 * UI specifics:
 *  - No model/server/system badges
 *  - No admin/system prompt editor
 */

declare(strict_types=1);
session_start();

// ---------------- Configuration ----------------
$OLLAMA_BASE        = getenv('UNHINGED_OLLAMA_BASE') ?: 'http://127.0.0.1:11434';
$FAST_MODEL         = 'gpt-oss:20b';
$THOUGHTFUL_MODEL   = 'gpt-oss:120b';

// Optional generation settings for Ollama
$OPTIONS = [
  'temperature' => 0.7,
  // 'num_ctx' => 4096,
  // 'top_p'   => 0.9,
  // 'repeat_penalty' => 1.05,
];

$UNHINGED_CONFIG = [
  'ollama_base' => $OLLAMA_BASE,
  'gpuq_api_base' => 'http://127.0.0.1:8020',
  'execute_url' => 'https://panomity.de/unhinged-ai/' . basename(__FILE__) . '?execute=1',
  'fast_model' => $FAST_MODEL,
  'thoughtful_model' => $THOUGHTFUL_MODEL,
  'options' => $OPTIONS,
];

require_once __DIR__ . '/chat-backend.php';

// ---------------- Routing ----------------
if (isset($_GET['execute'])) {
  unhinged_handle_execute_request($UNHINGED_CONFIG);
}

if (PHP_SAPI === 'cli') {
  // CLI usage: php ollama_chat_stream_dark.php "your prompt" [fast|deep]
  $prompt = $argv[1] ?? '';
  $mode   = $argv[2] ?? 'fast';
  if ($prompt === '') {
    fwrite(STDERR, "Usage: php " . basename(__FILE__) . " \"your prompt\" [fast|deep]\n");
    exit(1);
  }
  unhinged_stream_chat($prompt, $mode === 'deep' ? 'deep' : 'fast', $UNHINGED_CONFIG);
  exit;
}

if (isset($_GET['stream'])) {
  $raw    = file_get_contents('php://input');
  $data   = json_decode($raw, true);
  $prompt = '';
  $mode   = 'fast';

  if (is_array($data)) {
    if (isset($data['prompt'])) $prompt = (string)$data['prompt'];
    if (isset($data['mode']))   $mode   = (string)$data['mode'];
  } else {
    $prompt = $_POST['q'] ?? $_GET['q'] ?? '';
    $mode   = $_POST['mode'] ?? $_GET['mode'] ?? 'fast';
  }

  $mode = ($mode === 'deep') ? 'deep' : 'fast';

  if (trim($prompt) === '') {
    http_response_code(400);
    header('Content-Type: text/plain; charset=utf-8');
    echo "Missing prompt.";
    exit;
  }

  unhinged_stream_chat($prompt, $mode, $UNHINGED_CONFIG);
  exit;
}

if (isset($_GET['reset'])) {
  reset_session_chat();
  // Respond with 204 No Content for fetch-based reset
  http_response_code(204);
  exit;
}

// ---------------- HTML (Dark Chat UI) ----------------
$history = current_history();
?>
<!doctype html>
<html lang="de" class="dark">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="color-scheme" content="dark" />
  <meta name="theme-color" content="#0b1020" />
  <title>Chat</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <style>
    body { font-family: Verdana, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif; }
    #chatScroll::-webkit-scrollbar { width: 8px; }
    #chatScroll::-webkit-scrollbar-thumb { background: #4f46e5; border-radius: 6px; }
    #chatScroll::-webkit-scrollbar-track { background: #111827; }
    code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; }
    .msg { white-space: pre-wrap; }
  </style>
</head>
<body class="min-h-screen bg-gradient-to-br from-slate-950 via-indigo-950 to-slate-900 text-slate-200">
  <div class="max-w-4xl mx-auto p-4 sm:p-6">
    <div class="rounded-2xl shadow-xl bg-slate-900 ring-1 ring-slate-800 flex flex-col h-[80vh]">
      <!-- Header -->
      <div class="px-4 py-3 border-b border-slate-800">
        <div class="flex items-center justify-between gap-3">
          <h1 class="text-lg md:text-xl font-bold tracking-tight text-white">Chat</h1>

          <div class="flex items-center gap-3">
            <label for="modeSelect" class="text-xs text-slate-400">Modus</label>
            <select id="modeSelect" class="text-xs rounded-lg border border-slate-700 bg-slate-800 text-slate-200 px-3 py-1.5 hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-indigo-600">
              <option value="fast" selected>Schnell</option>
              <option value="deep">Durchdacht</option>
            </select>

            <button id="resetBtn"
              class="text-xs rounded-lg border border-slate-700 bg-slate-800 px-3 py-1.5 font-semibold text-slate-200 hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-indigo-600">
              Neue Unterhaltung
            </button>
          </div>
        </div>
      </div>

      <!-- Chat history -->
      <div id="chatScroll" class="flex-1 overflow-auto p-4 space-y-4">
        <?php if (empty($history)): ?>
          <div class="text-sm text-slate-400">
	    Starte eine Unterhaltung unten. Im durchdachten Modus benötigst du Geduld. 
          </div>
        <?php else: ?>
          <?php foreach ($history as $m): ?>
            <?php if (($m['role'] ?? '') === 'user'): ?>
              <div class="flex justify-end">
                <div class="max-w-[85%] rounded-2xl rounded-br-sm bg-indigo-600 text-white px-4 py-2 shadow msg"><?= htmlspecialchars((string)$m['content'], ENT_QUOTES, 'UTF-8') ?></div>
              </div>
            <?php elseif (($m['role'] ?? '') === 'assistant'): ?>
              <div class="flex justify-start">
                <div class="max-w-[85%] rounded-2xl rounded-bl-sm bg-slate-800 text-slate-100 px-4 py-2 shadow msg"><?= htmlspecialchars((string)$m['content'], ENT_QUOTES, 'UTF-8') ?></div>
              </div>
            <?php endif; ?>
          <?php endforeach; ?>
        <?php endif; ?>
      </div>

      <!-- Composer -->
      <form id="chatForm" class="border-t border-slate-800 p-3">
        <div class="flex items-end gap-2">
          <textarea id="prompt" name="q" rows="2"
            class="flex-1 resize-y min-h-[2.5rem] max-h-48 rounded-xl border border-slate-700 bg-slate-900 text-slate-100 placeholder-slate-500 p-3 shadow-sm outline-none focus:ring-2 focus:ring-indigo-600 focus:border-indigo-600"
            placeholder="Schreibe eine Nachricht…"></textarea>

          <button id="sendBtn" type="submit"
            class="inline-flex items-center gap-2 rounded-xl bg-indigo-600 text-white px-4 py-2 font-semibold shadow hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 disabled:opacity-60">
            <svg id="spinner" class="animate-spin h-4 w-4 hidden" viewBox="0 0 24 24" fill="none" aria-hidden="true">
              <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
              <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
            </svg>
            <span id="btnLabel">Senden</span>
          </button>

          <button id="stopBtn" type="button"
            class="inline-flex items-center gap-2 rounded-xl bg-slate-700 text-slate-200 px-4 py-2 font-semibold shadow hover:bg-slate-600 focus:ring-2 focus:ring-slate-500 disabled:opacity-60"
            disabled>Stopp</button>
        </div>
        <p class="mt-1 text-xs text-slate-500">
          Tipp: Mit <kbd class="px-1 py-0.5 bg-slate-800 border border-slate-700 rounded">Shift</kbd> + <kbd class="px-1 py-0.5 bg-slate-800 border border-slate-700 rounded">Enter</kbd> fügst du einen Zeilenumbruch ein.
        </p>
      </form>
    </div>
  </div>

  <script>
  (() => {
    const form      = document.getElementById('chatForm');
    const promptEl  = document.getElementById('prompt');
    const chat      = document.getElementById('chatScroll');
    const sendBtn   = document.getElementById('sendBtn');
    const stopBtn   = document.getElementById('stopBtn');
    const spinner   = document.getElementById('spinner');
    const btnLabel  = document.getElementById('btnLabel');
    const resetBtn  = document.getElementById('resetBtn');
    const modeSel   = document.getElementById('modeSelect');

    const MODE_KEY = 'ollama.chat.mode';
    // Restore saved mode
    const savedMode = localStorage.getItem(MODE_KEY);
    if (savedMode === 'fast' || savedMode === 'deep') {
      modeSel.value = savedMode;
    }
    modeSel.addEventListener('change', () => {
      localStorage.setItem(MODE_KEY, modeSel.value);
    });

    let controller = null;

    function setBusy(busy) {
      sendBtn.disabled = busy;
      stopBtn.disabled = !busy;
      spinner.classList.toggle('hidden', !busy);
      btnLabel.textContent = busy ? 'Streaming…' : 'Senden';
    }

    function appendUserBubble(text) {
      const wrap = document.createElement('div');
      wrap.className = 'flex justify-end';
      const bubble = document.createElement('div');
      bubble.className = 'max-w-[85%] rounded-2xl rounded-br-sm bg-indigo-600 text-white px-4 py-2 shadow msg';
      bubble.textContent = text;
      wrap.appendChild(bubble);
      chat.appendChild(wrap);
      chat.scrollTop = chat.scrollHeight;
    }

    function appendAssistantBubble() {
      const wrap = document.createElement('div');
      wrap.className = 'flex justify-start';
      const bubble = document.createElement('div');
      bubble.className = 'max-w-[85%] rounded-2xl rounded-bl-sm bg-slate-800 text-slate-100 px-4 py-2 shadow msg';
      bubble.textContent = '';
      wrap.appendChild(bubble);
      chat.appendChild(wrap);
      chat.scrollTop = chat.scrollHeight;
      return bubble;
    }

    stopBtn.addEventListener('click', () => {
      if (controller) controller.abort();
      setBusy(false);
    });

    resetBtn.addEventListener('click', async () => {
      try {
        await fetch(window.location.pathname + '?reset=1', { method: 'POST' });
      } catch (e) {}
      window.location.reload();
    });

    form.addEventListener('submit', async (e) => {
      e.preventDefault();
      const prompt = promptEl.value.trim();
      if (!prompt) {
        promptEl.focus();
        return;
      }

      appendUserBubble(prompt);
      promptEl.value = '';

      const assistantBubble = appendAssistantBubble();

      controller = new AbortController();
      setBusy(true);

      try {
        const resp = await fetch(window.location.pathname + '?stream=1', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ prompt, mode: modeSel.value }),
          signal: controller.signal
        });

        if (!resp.ok || !resp.body) {
          const text = await resp.text().catch(()=>'');
          assistantBubble.textContent = 'Fehler: ' + (text || (resp.status + ' ' + resp.statusText));
          setBusy(false);
          return;
        }

        const reader  = resp.body.getReader();
        const decoder = new TextDecoder();
        while (true) {
          const { value, done } = await reader.read();
          if (done) break;
          assistantBubble.textContent += decoder.decode(value, { stream: true });
          chat.scrollTop = chat.scrollHeight;
        }
      } catch (err) {
        if (err.name !== 'AbortError') {
          assistantBubble.textContent += '\n[Netzwerkfehler] ' + err;
        }
      } finally {
        setBusy(false);
        controller = null;
      }
    });

    // UX: Enter to send, Shift+Enter for newline
    promptEl.addEventListener('keydown', (e) => {
      if (e.key === 'Enter' && !e.shiftKey) {
        e.preventDefault();
        sendBtn.click();
      }
    });
  })();
  </script>
</body>
</html>