sessionRepo = $sessionRepo ?? new ChatSessionRepository(); $this->messageRepo = $messageRepo ?? new ChatMessageRepository(); $this->configRepo = $configRepo ?? new ContentConfigRepository(); $this->collectionRepo = $collectionRepo ?? new CollectionRepository(); $this->collectionValidator = $collectionValidator ?? new CollectionValidator($this->collectionRepo); } public function createSession(): string { $uuid = $this->sessionRepo->generateUuid(); $this->sessionRepo->create($uuid, 'claude-opus-4-5-20251101', '["documents"]', 5); return $uuid; } public function getSession(string $uuid): ?array { return $this->sessionRepo->findByUuid($uuid); } public function getAllSessions(int $limit = 50): array { return $this->sessionRepo->findAll($limit); } public function getMessages(int $sessionId): array { return $this->messageRepo->findBySessionId($sessionId); } public function updateTitle(int $sessionId, string $title): string { $title = trim($title); if ($title === '') { $title = 'Neuer Chat'; } $title = mb_substr($title, 0, 100); $this->sessionRepo->updateTitle($sessionId, $title); return $title; } public function updateSettings( int $sessionId, string $model, array $collections, int $contextLimit, int $authorProfileId, float $temperature, int $maxTokens ): void { $model = ModelConfig::validate($model); $collections = $this->validateCollections($collections); $contextLimit = $this->validateContextLimit($contextLimit); $authorProfileId = $this->validateAuthorProfileId($authorProfileId); $temperature = $this->validateTemperature($temperature); $maxTokens = $this->validateMaxTokens($maxTokens); $this->sessionRepo->updateSettings( $sessionId, $model, $collections, $contextLimit, $authorProfileId > 0 ? $authorProfileId : null, $temperature, $maxTokens ); } public function updateSystemPrompt(int $sessionId, string $systemPrompt): ChatSessionResult { $maxLength = 2000; $wasTruncated = mb_strlen($systemPrompt) > $maxLength; $systemPrompt = trim(mb_substr($systemPrompt, 0, $maxLength)); $this->logSystemPromptChange($systemPrompt, $wasTruncated); $this->sessionRepo->updateSystemPrompt($sessionId, $systemPrompt); return ChatSessionResult::success('System-Prompt gespeichert.'); } public function deleteSession(int $sessionId): void { $this->sessionRepo->delete($sessionId); } public function getAuthorProfiles(): array { return $this->configRepo->getAuthorProfiles(); } public function getSystemPrompts(): array { return $this->configRepo->getSystemPrompts(); } public function getOutputStructures(): array { return $this->configRepo->getStructures(); } public function getStructure(int $id): ?array { return $this->configRepo->getStructure($id); } public function getAvailableCollections(): array { if ($this->collectionsCache === null) { $this->collectionsCache = $this->collectionRepo->getSearchable(); if ($this->collectionsCache === []) { $this->collectionsCache = [ ['collection_id' => 'documents', 'display_name' => 'Dokumente', 'points_count' => 0, 'vector_size' => 1024], ]; } } return $this->collectionsCache; } public function validateCollectionCompatibility(array $collectionIds): array { if (empty($collectionIds)) { return ['valid' => true, 'error' => null]; } $result = $this->collectionValidator->validateSelection($collectionIds); return [ 'valid' => $result->isValid(), 'error' => $result->getError(), ]; } public function getDefaultSystemPrompt(): string { return <<<'PROMPT' Du bist ein hilfreicher Assistent für Fragen zu systemischem Teamcoaching und Teamentwicklung. Beantworte die Frage des Nutzers basierend auf dem bereitgestellten Kontext. - Antworte auf Deutsch - Sei präzise und hilfreich - Wenn der Kontext die Frage nicht beantwortet, sage das ehrlich - Verweise auf die Quellen wenn passend PROMPT; } public function settingsHaveChanged(array $session, string $model, array $collections, int $contextLimit, int $authorProfileId, float $temperature, int $maxTokens): bool { $currentModel = $session['model'] ?? ''; $currentCollections = $session['collections'] ?? '["documents"]'; $currentLimit = (int) ($session['context_limit'] ?? 5); $currentProfileId = (int) ($session['author_profile_id'] ?? 0); $currentTemperature = (float) ($session['temperature'] ?? 0.7); $currentMaxTokens = (int) ($session['max_tokens'] ?? 4096); $collectionsJson = json_encode($collections); return $model !== $currentModel || $collectionsJson !== $currentCollections || $contextLimit !== $currentLimit || $authorProfileId !== $currentProfileId || $temperature !== $currentTemperature || $maxTokens !== $currentMaxTokens; } private function validateCollections(array|string $collections): array { $availableIds = array_column($this->getAvailableCollections(), 'collection_id'); if (is_string($collections)) { $collections = [$collections]; } $valid = array_filter($collections, fn ($c) => in_array($c, $availableIds, true)); return array_values($valid); } private function validateContextLimit(int $limit): int { $allowedLimits = [3, 5, 10, 15]; return in_array($limit, $allowedLimits, true) ? $limit : 5; } private function validateAuthorProfileId(int $profileId): int { if ($profileId === 0) { return 0; } $profile = $this->configRepo->getAuthorProfile($profileId); return $profile !== null ? $profileId : 0; } private function validateTemperature(float $temperature): float { return max(0.0, min(1.0, $temperature)); } private function validateMaxTokens(int $maxTokens): int { $allowedValues = [1024, 2048, 4096, 8192]; return in_array($maxTokens, $allowedValues, true) ? $maxTokens : 4096; } private function logSystemPromptChange(string $prompt, bool $wasTruncated): void { $logData = [ 'prompt_length' => mb_strlen($prompt), 'truncated' => $wasTruncated, 'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', 'timestamp' => date('c'), ]; try { $devDb = \Infrastructure\Config\DatabaseFactory::dev(); $stmt = $devDb->prepare( "INSERT INTO mcp_log (tool, operation, parameters, result, logged_at) VALUES ('chat', 'system_prompt_change', :params, 'logged', NOW())" ); $stmt->execute(['params' => json_encode($logData)]); } catch (\Exception $e) { // Silently fail } } }