Backup #39
| ID | 39 |
| Dateipfad | /var/www/dev.campus.systemische-tools.de/src/Controller/ChatController.php |
| Version | 4 |
| Typ |
modified |
| Größe | 24.9 KB |
| Hash | e36f9b3be75fba9a49d14a5a969a83b4cd72cbefd2641a82c2c835d6bb0e5518 |
| Datum | 2025-12-20 17:24:17 |
| Geändert von | claude-code-hook |
| Grund | Claude Code Pre-Hook Backup vor Edit-Operation |
| Datei existiert |
Ja
|
Dateiinhalt
<?php
namespace Controller;
use Framework\Controller;
use Infrastructure\AI\AIConfig;
use Infrastructure\AI\ChatService;
use Infrastructure\Persistence\SystemPromptRepository;
class ChatController extends Controller
{
private ChatService $chatService;
private \PDO $db;
private SystemPromptRepository $systemPromptRepo;
public function __construct()
{
$this->chatService = $this->initializeChatService();
$this->db = $this->initializeDatabase();
$this->systemPromptRepo = new SystemPromptRepository();
}
/**
* GET /chat
* Show chat interface with session list, create new session if none
*/
public function index(): void
{
$sessions = $this->getSessions();
// Create new session and redirect
$uuid = $this->createSession();
header('Location: /chat/' . $uuid);
exit;
}
/**
* GET /chat/{uuid}
* Show specific chat session
*/
public function show(string $uuid): void
{
$session = $this->getSession($uuid);
if ($session === null) {
header('Location: /chat');
exit;
}
$messages = $this->getMessages($session['id']);
$sessions = $this->getSessions();
$authorProfiles = $this->getAuthorProfiles();
$systemPrompts = $this->systemPromptRepo->findAllActive();
$this->view('chat.index', [
'title' => $session['title'] ?? 'KI-Chat',
'session' => $session,
'messages' => $messages,
'sessions' => $sessions,
'authorProfiles' => $authorProfiles,
'systemPrompts' => $systemPrompts,
]);
}
/**
* GET /chat/sessions (HTMX partial)
* Return session list HTML
*/
public function sessionList(): void
{
$sessions = $this->getSessions();
$this->view('chat.partials.session-list', [
'sessions' => $sessions,
'currentUuid' => $_GET['current'] ?? null,
]);
}
/**
* POST /chat/{uuid}/message (HTMX)
* Process message and return HTML response
*/
public function message(string $uuid): void
{
$session = $this->getSession($uuid);
if ($session === null) {
echo '<div class="chat-error">Session nicht gefunden.</div>';
return;
}
$question = trim($_POST['message'] ?? '');
$model = $this->validateModel($_POST['model'] ?? $session['model']);
$collection = $this->validateCollection($_POST['collection'] ?? $session['collection']);
$contextLimit = $this->validateContextLimit((int) ($_POST['context_limit'] ?? $session['context_limit'] ?? 5));
$authorProfileId = $this->validateAuthorProfileId((int) ($_POST['author_profile_id'] ?? $session['author_profile_id'] ?? 0));
$systemPromptId = (int) ($_POST['system_prompt_id'] ?? $session['system_prompt_id'] ?? 1);
// Update session if settings changed
$currentLimit = (int) ($session['context_limit'] ?? 5);
$currentProfileId = (int) ($session['author_profile_id'] ?? 0);
if ($model !== $session['model'] || $collection !== $session['collection'] || $contextLimit !== $currentLimit || $authorProfileId !== $currentProfileId) {
$this->updateSessionSettings($session['id'], $model, $collection, $contextLimit, $authorProfileId);
}
if ($question === '') {
echo '<div class="chat-error">Bitte gib eine Frage ein.</div>';
return;
}
try {
// Save user message
$this->saveMessage($session['id'], 'user', $question, $model);
// Auto-set title from first message
if ($session['title'] === null) {
$title = mb_substr($question, 0, 50) . (mb_strlen($question) > 50 ? '...' : '');
$this->updateSessionTitle($session['id'], $title);
}
// Get style prompt from author profile
$stylePrompt = $this->getStylePromptFromProfile($authorProfileId);
// Get system prompt from database
$systemPromptData = $this->systemPromptRepo->findById($systemPromptId);
$customSystemPrompt = $systemPromptData['content'] ?? null;
// Track timing
$startMicrotime = microtime(true);
// Get response from AI
$result = $this->askChat(
$question,
$model,
$collection,
$contextLimit,
$stylePrompt,
$customSystemPrompt
);
$endMicrotime = microtime(true);
if (isset($result['error'])) {
echo '<div class="chat-error">' . htmlspecialchars($result['error']) . '</div>';
return;
}
// Save assistant message with full tracking
$tokensInput = $result['usage']['input_tokens'] ?? null;
$tokensOutput = $result['usage']['output_tokens'] ?? null;
$sources = $result['sources'] ?? [];
$this->saveMessage(
$session['id'],
'assistant',
$result['answer'],
$model,
$tokensInput,
$tokensOutput,
$sources,
$startMicrotime,
$endMicrotime,
$authorProfileId > 0 ? $authorProfileId : null,
$systemPromptId > 0 ? $systemPromptId : null,
$collection,
$contextLimit
);
$this->renderResponse($question, $result, $model);
} catch (\Exception $e) {
echo '<div class="chat-error">Fehler: ' . htmlspecialchars($e->getMessage()) . '</div>';
}
}
/**
* POST /chat/{uuid}/title (HTMX)
* Update session title
*/
public function updateTitle(string $uuid): void
{
$session = $this->getSession($uuid);
if ($session === null) {
http_response_code(404);
echo '';
return;
}
$title = trim($_POST['title'] ?? '');
// Validate title
if ($title === '') {
$title = 'Neuer Chat';
}
// Max 100 characters
$title = mb_substr($title, 0, 100);
// Save to database
$this->updateSessionTitle($session['id'], $title);
// Return updated title HTML
echo htmlspecialchars($title);
}
/**
* POST /chat/{uuid}/system-prompt (HTMX)
* Update session system prompt
*/
public function systemPrompt(string $uuid): void
{
$session = $this->getSession($uuid);
if ($session === null) {
http_response_code(404);
echo '<div class="chat-error">Session nicht gefunden.</div>';
return;
}
$systemPrompt = trim($_POST['system_prompt'] ?? '');
// Validate and sanitize system prompt
$systemPrompt = $this->validateSystemPrompt($systemPrompt);
// Save to database
$this->updateSystemPrompt($session['id'], $systemPrompt);
// Return success message
echo '<div class="chat-success">System-Prompt gespeichert.</div>';
}
/**
* GET /chat/{uuid}/system-prompt (HTMX)
* Get system prompt form
*/
public function getSystemPrompt(string $uuid): void
{
$session = $this->getSession($uuid);
if ($session === null) {
http_response_code(404);
echo '<div class="chat-error">Session nicht gefunden.</div>';
return;
}
$defaultPrompt = $this->getDefaultSystemPrompt();
$currentPrompt = $session['system_prompt'] ?? '';
$this->view('chat.partials.system-prompt-modal', [
'session' => $session,
'currentPrompt' => $currentPrompt,
'defaultPrompt' => $defaultPrompt,
]);
}
/**
* DELETE /chat/{uuid}
* Delete a session
*/
public function delete(string $uuid): void
{
$session = $this->getSession($uuid);
if ($session !== null) {
$stmt = $this->db->prepare('DELETE FROM chat_sessions WHERE id = ?');
$stmt->execute([$session['id']]);
}
// Return success for HTMX
header('HX-Redirect: /chat');
echo 'OK';
}
// ========== Private Helper Methods ==========
/**
* Initialize database connection
*/
private function initializeDatabase(): \PDO
{
$credentials = $this->loadCredentials();
return new \PDO(
'mysql:host=localhost;dbname=ki_content;charset=utf8mb4',
'root',
$credentials['db_password'],
[\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]
);
}
/**
* Load credentials from file
*/
private function loadCredentials(): array
{
$file = '/var/www/docs/credentials/credentials.md';
$content = file_get_contents($file);
$password = '';
foreach (explode("\n", $content) as $line) {
if (str_contains($line, 'MariaDB') && str_contains($line, 'root')) {
$parts = explode('|', $line);
if (count($parts) >= 4) {
$password = trim($parts[3]);
break;
}
}
}
return ['db_password' => $password];
}
/**
* Create a new chat session
*/
private function createSession(): string
{
$uuid = $this->generateUuid();
$stmt = $this->db->prepare(
'INSERT INTO chat_sessions (uuid, model, collection, context_limit) VALUES (?, ?, ?, ?)'
);
$stmt->execute([$uuid, 'claude-opus-4-5-20251101', 'documents', 5]);
return $uuid;
}
/**
* Get session by UUID
*/
private function getSession(string $uuid): ?array
{
$stmt = $this->db->prepare('SELECT * FROM chat_sessions WHERE uuid = ?');
$stmt->execute([$uuid]);
$result = $stmt->fetch(\PDO::FETCH_ASSOC);
return $result !== false ? $result : null;
}
/**
* Get all sessions ordered by most recent
*/
private function getSessions(): array
{
$stmt = $this->db->query(
'SELECT s.*,
(SELECT COUNT(*) FROM chat_messages WHERE session_id = s.id) as message_count,
(SELECT COALESCE(SUM(tokens_input), 0) FROM chat_messages WHERE session_id = s.id) as total_input_tokens,
(SELECT COALESCE(SUM(tokens_output), 0) FROM chat_messages WHERE session_id = s.id) as total_output_tokens,
(SELECT COALESCE(SUM(end_microtime - start_microtime), 0) FROM chat_messages WHERE session_id = s.id AND start_microtime IS NOT NULL) as total_duration,
(SELECT model FROM chat_messages WHERE session_id = s.id AND role = "assistant" ORDER BY id DESC LIMIT 1) as last_model
FROM chat_sessions s
ORDER BY s.last_activity DESC
LIMIT 50'
);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Get author profiles from ki_content database
*/
private function getAuthorProfiles(): array
{
$credentials = $this->loadCredentials();
$dbSystem = new \PDO(
'mysql:host=localhost;dbname=ki_content;charset=utf8mb4',
'root',
$credentials['db_password'],
[\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]
);
$stmt = $dbSystem->query('SELECT id, name, slug, config FROM author_profiles WHERE is_active = 1 ORDER BY id');
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Get author profile by ID
*/
private function getAuthorProfile(int $profileId): ?array
{
$profiles = $this->getAuthorProfiles();
foreach ($profiles as $profile) {
if ((int) $profile['id'] === $profileId) {
return $profile;
}
}
return null;
}
/**
* Get messages for a session
*/
private function getMessages(int $sessionId): array
{
$stmt = $this->db->prepare(
'SELECT * FROM chat_messages WHERE session_id = ? ORDER BY created_at ASC'
);
$stmt->execute([$sessionId]);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Save a message to the database
*/
private function saveMessage(
int $sessionId,
string $role,
string $content,
string $model,
?int $tokensInput = null,
?int $tokensOutput = null,
array $sources = [],
?float $startMicrotime = null,
?float $endMicrotime = null,
?int $authorProfileId = null,
?int $systemPromptId = null,
?string $collection = null,
?int $contextLimit = null
): void {
$stmt = $this->db->prepare(
'INSERT INTO chat_messages
(session_id, role, content, model, tokens_input, tokens_output, sources,
start_microtime, end_microtime, author_profile_id, system_prompt_id, collection, context_limit)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
);
$stmt->execute([
$sessionId,
$role,
$content,
$model,
$tokensInput,
$tokensOutput,
$sources !== [] ? json_encode($sources) : null,
$startMicrotime,
$endMicrotime,
$authorProfileId,
$systemPromptId,
$collection,
$contextLimit,
]);
// Update session timestamp
$this->db->prepare('UPDATE chat_sessions SET updated_at = NOW() WHERE id = ?')
->execute([$sessionId]);
}
/**
* Update session title
*/
private function updateSessionTitle(int $sessionId, string $title): void
{
$stmt = $this->db->prepare('UPDATE chat_sessions SET title = ? WHERE id = ?');
$stmt->execute([$title, $sessionId]);
}
/**
* Generate UUID v4
*/
private function generateUuid(): string
{
$data = random_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
/**
* Initialize ChatService with all required AI services
*/
private function initializeChatService(): ChatService
{
$config = AIConfig::fromCredentialsFile();
return $config->createChatService();
}
/**
* Validate model parameter
* Format: claude-* or ollama:*
*/
private function validateModel(string $model): string
{
if (str_starts_with($model, 'claude-') || str_starts_with($model, 'ollama:')) {
return $model;
}
return 'claude-opus-4-5-20251101';
}
/**
... (280 weitere Zeilen)
Vollständig herunterladen
Aktionen
Andere Versionen dieser Datei
| ID |
Version |
Typ |
Größe |
Datum |
| 132 |
27 |
modified |
27.8 KB |
2025-12-20 19:46 |
| 131 |
26 |
modified |
27.7 KB |
2025-12-20 19:46 |
| 130 |
25 |
modified |
26.5 KB |
2025-12-20 19:42 |
| 82 |
24 |
modified |
26.4 KB |
2025-12-20 19:13 |
| 81 |
23 |
modified |
26.3 KB |
2025-12-20 19:13 |
| 80 |
22 |
modified |
26.3 KB |
2025-12-20 19:13 |
| 79 |
21 |
modified |
25.9 KB |
2025-12-20 19:12 |
| 78 |
20 |
modified |
25.9 KB |
2025-12-20 19:12 |
| 77 |
19 |
modified |
25.9 KB |
2025-12-20 19:12 |
| 76 |
18 |
modified |
25.9 KB |
2025-12-20 19:10 |
| 75 |
17 |
modified |
25.8 KB |
2025-12-20 19:10 |
| 74 |
16 |
modified |
25.7 KB |
2025-12-20 19:10 |
| 68 |
15 |
modified |
25.9 KB |
2025-12-20 18:49 |
| 67 |
14 |
modified |
25.4 KB |
2025-12-20 18:49 |
| 66 |
13 |
modified |
25.3 KB |
2025-12-20 18:49 |
| 65 |
12 |
modified |
25.0 KB |
2025-12-20 18:49 |
| 60 |
11 |
modified |
24.5 KB |
2025-12-20 18:32 |
| 59 |
10 |
modified |
24.4 KB |
2025-12-20 18:31 |
| 58 |
9 |
modified |
24.3 KB |
2025-12-20 18:31 |
| 57 |
8 |
modified |
24.3 KB |
2025-12-20 18:31 |
| 56 |
7 |
modified |
24.0 KB |
2025-12-20 18:31 |
| 55 |
6 |
modified |
23.8 KB |
2025-12-20 18:31 |
| 40 |
5 |
modified |
24.1 KB |
2025-12-20 17:24 |
| 39 |
4 |
modified |
24.9 KB |
2025-12-20 17:24 |
| 9 |
3 |
modified |
24.9 KB |
2025-12-20 16:32 |
| 5 |
2 |
modified |
24.8 KB |
2025-12-20 16:08 |
| 4 |
1 |
modified |
24.8 KB |
2025-12-20 16:08 |
← Zurück zur Übersicht