ChatController.php
- Pfad:
src/Controller/ChatController.php
- Namespace: Controller
- Zeilen: 511 | Größe: 17,560 Bytes
- Geändert: 2025-12-29 15:04:13 | Gescannt: 2025-12-31 10:22:15
Code Hygiene Score: 52
- Dependencies: 30 (25%)
- LOC: 0 (20%)
- Methods: 50 (20%)
- Secrets: 100 (15%)
- Classes: 100 (10%)
- Magic Numbers: 90 (10%)
Issues 2
| Zeile |
Typ |
Beschreibung |
| 326 |
magic_number |
Magic Number gefunden: 100 |
| - |
complexity |
Datei hat 511 Zeilen (max: 500) |
Dependencies 23
- extends Framework\Controller
- constructor UseCases\Chat\CreateChatSessionUseCaseInterface
- constructor UseCases\Chat\GetChatSessionUseCaseInterface
- constructor UseCases\Chat\UpdateChatSessionUseCaseInterface
- constructor UseCases\Chat\DeleteChatSessionUseCaseInterface
- constructor UseCases\Chat\SendChatMessageUseCase
- constructor UseCases\Chat\StreamingChatMessageUseCase
- constructor Infrastructure\Formatting\ChatMessageFormatter
- constructor UseCases\Chat\ExportChatSessionUseCase
- constructor Domain\Service\ModelRegistryInterface
- constructor Domain\Repository\ContentRepositoryInterface
- use Domain\Factory\ChatSessionFactory
- use Domain\Repository\ContentRepositoryInterface
- use Domain\Service\ModelRegistryInterface
- use Framework\Controller
- use Infrastructure\Formatting\ChatMessageFormatter
- use UseCases\Chat\CreateChatSessionUseCaseInterface
- use UseCases\Chat\DeleteChatSessionUseCaseInterface
- use UseCases\Chat\ExportChatSessionUseCase
- use UseCases\Chat\GetChatSessionUseCaseInterface
- use UseCases\Chat\SendChatMessageUseCase
- use UseCases\Chat\StreamingChatMessageUseCase
- use UseCases\Chat\UpdateChatSessionUseCaseInterface
Klassen 1
-
ChatController
class
Zeile 22
Funktionen 20
-
__construct()
public
Zeile 24
-
index()
public
Zeile 38
-
show()
public
Zeile 45
-
sessionList()
public
Zeile 72
-
message()
public
Zeile 80
-
messageStream()
public
Zeile 152
-
sseError()
private
Zeile 213
-
updateTitle()
public
Zeile 229
-
systemPrompt()
public
Zeile 241
-
getSystemPrompt()
public
Zeile 253
-
delete()
public
Zeile 268
-
deleteAll()
public
Zeile 279
-
export()
public
Zeile 285
-
promoteToContent()
public
Zeile 313
-
extractChatParams()
private
Zeile 371
-
updateSessionIfChanged()
private
Zeile 397
-
validateCollections()
private
Zeile 417
-
setupSseStream()
private
Zeile 434
-
setupProgressCallback()
private
Zeile 463
-
sendStreamResult()
private
Zeile 483
Versionen 131
-
v131
2025-12-29 15:04 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v130
2025-12-29 15:00 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v129
2025-12-29 15:00 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v128
2025-12-29 15:00 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v127
2025-12-29 09:18 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v126
2025-12-29 09:17 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v125
2025-12-29 08:49 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v124
2025-12-29 00:15 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v123
2025-12-29 00:14 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v122
2025-12-29 00:14 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v121
2025-12-29 00:13 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v120
2025-12-29 00:13 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v119
2025-12-26 21:07 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v118
2025-12-26 20:56 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v117
2025-12-26 20:55 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v116
2025-12-26 20:40 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v115
2025-12-26 20:39 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v114
2025-12-26 20:39 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v113
2025-12-26 20:39 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v112
2025-12-26 20:28 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v111
2025-12-26 20:28 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v110
2025-12-26 20:05 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v109
2025-12-26 20:04 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v108
2025-12-26 20:04 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v107
2025-12-26 19:45 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v106
2025-12-26 19:45 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v105
2025-12-25 17:31 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v104
2025-12-25 17:04 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v103
2025-12-25 17:04 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v102
2025-12-25 17:04 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v101
2025-12-25 17:04 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v100
2025-12-25 17:04 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v99
2025-12-25 17:04 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v98
2025-12-25 17:03 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v97
2025-12-25 17:02 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v96
2025-12-25 17:01 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v95
2025-12-25 17:01 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v94
2025-12-25 17:00 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v93
2025-12-25 17:00 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v92
2025-12-25 17:00 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v91
2025-12-25 17:00 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v90
2025-12-25 13:27 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v89
2025-12-25 13:27 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v88
2025-12-25 13:27 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v87
2025-12-25 09:45 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v86
2025-12-25 09:45 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v85
2025-12-25 09:45 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v84
2025-12-25 09:42 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v83
2025-12-25 09:42 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v82
2025-12-25 09:42 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v81
2025-12-23 07:51 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v80
2025-12-23 07:44 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v79
2025-12-23 04:44 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v78
2025-12-23 04:44 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v77
2025-12-23 04:44 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v76
2025-12-23 04:44 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v75
2025-12-23 04:42 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v74
2025-12-23 03:35 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v73
2025-12-23 02:39 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v72
2025-12-23 02:39 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v71
2025-12-23 02:38 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v70
2025-12-22 22:30 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v69
2025-12-22 22:29 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v68
2025-12-22 10:36 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Write-Operation
-
v67
2025-12-22 08:06 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v66
2025-12-22 08:03 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v65
2025-12-22 02:19 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v64
2025-12-22 02:19 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v63
2025-12-22 02:19 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v62
2025-12-22 02:14 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v61
2025-12-22 02:14 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v60
2025-12-22 02:14 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v59
2025-12-22 02:13 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v58
2025-12-22 02:13 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v57
2025-12-22 02:13 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v56
2025-12-22 02:12 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v55
2025-12-22 02:00 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v54
2025-12-22 01:59 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v53
2025-12-22 01:59 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v52
2025-12-22 01:59 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v51
2025-12-22 01:59 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v50
2025-12-22 01:59 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v49
2025-12-22 01:59 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v48
2025-12-22 01:59 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v47
2025-12-22 01:43 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v46
2025-12-22 01:43 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v45
2025-12-22 01:43 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v44
2025-12-21 14:39 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v43
2025-12-21 14:39 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v42
2025-12-21 14:39 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v41
2025-12-21 14:15 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v40
2025-12-21 14:15 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v39
2025-12-21 14:15 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v38
2025-12-21 14:15 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v37
2025-12-21 04:12 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v36
2025-12-21 04:12 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v35
2025-12-21 04:12 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v34
2025-12-21 02:30 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v33
2025-12-21 02:30 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v32
2025-12-21 02:30 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v31
2025-12-21 02:29 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v30
2025-12-21 02:29 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v29
2025-12-21 02:29 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v28
2025-12-21 02:16 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v27
2025-12-20 19:46 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v26
2025-12-20 19:46 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v25
2025-12-20 19:42 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v24
2025-12-20 19:13 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v23
2025-12-20 19:13 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v22
2025-12-20 19:13 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v21
2025-12-20 19:12 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v20
2025-12-20 19:12 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v19
2025-12-20 19:12 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v18
2025-12-20 19:10 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v17
2025-12-20 19:10 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v16
2025-12-20 19:10 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v15
2025-12-20 18:49 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v14
2025-12-20 18:49 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v13
2025-12-20 18:49 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v12
2025-12-20 18:49 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v11
2025-12-20 18:32 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v10
2025-12-20 18:31 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v9
2025-12-20 18:31 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v8
2025-12-20 18:31 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v7
2025-12-20 18:31 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v6
2025-12-20 18:31 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v5
2025-12-20 17:24 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v4
2025-12-20 17:24 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v3
2025-12-20 16:32 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v2
2025-12-20 16:08 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
-
v1
2025-12-20 16:08 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
Code
<?php
declare(strict_types=1);
namespace Controller;
// @responsibility: HTTP-Endpunkte für KI-Chat (Sessions, Nachrichten, Export)
use Domain\Factory\ChatSessionFactory;
use Domain\Repository\ContentRepositoryInterface;
use Domain\Service\ModelRegistryInterface;
use Framework\Controller;
use Infrastructure\Formatting\ChatMessageFormatter;
use UseCases\Chat\CreateChatSessionUseCaseInterface;
use UseCases\Chat\DeleteChatSessionUseCaseInterface;
use UseCases\Chat\ExportChatSessionUseCase;
use UseCases\Chat\GetChatSessionUseCaseInterface;
use UseCases\Chat\SendChatMessageUseCase;
use UseCases\Chat\StreamingChatMessageUseCase;
use UseCases\Chat\UpdateChatSessionUseCaseInterface;
class ChatController extends Controller
{
public function __construct(
private CreateChatSessionUseCaseInterface $createSessionUseCase,
private GetChatSessionUseCaseInterface $getSessionUseCase,
private UpdateChatSessionUseCaseInterface $updateSessionUseCase,
private DeleteChatSessionUseCaseInterface $deleteSessionUseCase,
private SendChatMessageUseCase $messageUseCase,
private StreamingChatMessageUseCase $streamingUseCase,
private ChatMessageFormatter $formatter,
private ExportChatSessionUseCase $exportUseCase,
private ModelRegistryInterface $modelRegistry,
private ContentRepositoryInterface $contentRepository
) {
}
public function index(): void
{
$uuid = $this->createSessionUseCase->createSession();
header('Location: /chat/' . $uuid);
exit;
}
public function show(string $uuid): void
{
$session = $this->getSessionUseCase->getSession($uuid);
if ($session === null) {
header('Location: /chat');
exit;
}
// Convert entities to arrays for views
$messages = $this->getSessionUseCase->getMessages($session->getId() ?? 0);
$messagesArray = array_map(fn ($m) => $m->toArray(), $messages);
$this->view('chat.index', [
'title' => $session->getTitle() ?? 'KI-Chat',
'session' => ChatSessionFactory::toArray($session),
'messages' => $messagesArray,
'sessions' => $this->getSessionUseCase->getAllSessionsWithStats(),
'authorProfiles' => $this->getSessionUseCase->getAuthorProfiles(),
'systemPrompts' => $this->getSessionUseCase->getSystemPrompts(),
'outputStructures' => $this->getSessionUseCase->getOutputStructures(),
'collections' => $this->getSessionUseCase->getAvailableCollections(),
'models' => $this->modelRegistry->getChatModels(),
'defaultModel' => $this->modelRegistry->getDefaultChatModel(),
]);
}
public function sessionList(): void
{
$this->view('chat.partials.session-list', [
'sessions' => $this->getSessionUseCase->getAllSessionsWithStats(),
'currentUuid' => $this->getString('current') ?: null,
]);
}
public function message(string $uuid): void
{
$session = $this->getSessionUseCase->getSession($uuid);
if ($session === null) {
$this->view('chat.partials.error', ['error' => 'Session nicht gefunden.']);
return;
}
$params = $this->extractChatParams($session);
$this->updateSessionIfChanged(
$session,
$params['model'],
$params['collections'],
$params['contextLimit'],
$params['authorProfileId'],
$params['temperature'],
$params['maxTokens']
);
if ($params['question'] === '') {
$this->view('chat.partials.error', ['error' => 'Bitte gib eine Frage ein.']);
return;
}
$validation = $this->validateCollections($params['collections']);
if (!$validation['valid']) {
$this->view('chat.partials.error', [
'error' => 'Collection-Fehler: ' . $validation['error'],
'details' => 'Bitte wähle nur Collections mit gleichem Embedding-Modell.',
]);
return;
}
// Release session lock before long-running LLM call
session_write_close();
$response = $this->messageUseCase->execute(
sessionUuid: $uuid,
message: $params['question'],
model: $params['model'],
collections: $params['collections'],
contextLimit: $params['contextLimit'],
authorProfileId: $params['authorProfileId'],
systemPromptId: $params['systemPromptId'],
temperature: $params['temperature'],
maxTokens: $params['maxTokens'],
structureId: $params['structureId'],
qualityCheck: $params['qualityCheck']
);
if ($response->hasError()) {
$this->view('chat.partials.error', ['error' => $response->getError()]);
return;
}
$result = $response->toArray();
$this->view('chat.partials.response', [
'question' => $params['question'],
'result' => $result,
'model' => $params['model'],
'formattedAnswer' => $this->formatter->formatAnswer($result['answer'] ?? ''),
]);
}
/**
* Streaming message endpoint with SSE progress events
*/
public function messageStream(string $uuid): void
{
$session = $this->getSessionUseCase->getSession($uuid);
if ($session === null) {
$this->sseError('Session nicht gefunden.');
return;
}
$params = $this->extractChatParams($session);
$this->updateSessionIfChanged(
$session,
$params['model'],
$params['collections'],
$params['contextLimit'],
$params['authorProfileId'],
$params['temperature'],
$params['maxTokens']
);
if ($params['question'] === '') {
$this->sseError('Bitte gib eine Frage ein.');
return;
}
$validation = $this->validateCollections($params['collections']);
if (!$validation['valid']) {
$this->sseError('Collection-Fehler: ' . $validation['error']);
return;
}
$this->setupSseStream();
$this->setupProgressCallback();
// Release session lock before long-running LLM call
// This allows other requests from same user to proceed
session_write_close();
// Execute with streaming
$response = $this->streamingUseCase->execute(
sessionUuid: $uuid,
message: $params['question'],
model: $params['model'],
collections: $params['collections'],
contextLimit: $params['contextLimit'],
authorProfileId: $params['authorProfileId'],
systemPromptId: $params['systemPromptId'],
temperature: $params['temperature'],
maxTokens: $params['maxTokens'],
structureId: $params['structureId'],
qualityCheck: $params['qualityCheck'],
requestIp: $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'
);
// Send final result
$this->sendStreamResult($response, $params['question'], $params['model']);
}
private function sseError(string $message): void
{
header('Content-Type: text/event-stream; charset=utf-8');
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Content-Encoding: none');
while (ob_get_level()) {
ob_end_clean();
}
// Padding for browser buffering
echo ':' . str_repeat(' ', 4096) . "\n\n";
$errorData = ['error' => $message];
echo "event: error\n";
echo 'data: ' . json_encode($errorData, JSON_UNESCAPED_UNICODE) . "\n\n";
flush();
}
public function updateTitle(string $uuid): void
{
$session = $this->getSessionUseCase->getSession($uuid);
if ($session === null) {
$this->notFound('Session nicht gefunden');
}
$title = $this->updateSessionUseCase->updateTitle($session->getId() ?? 0, $_POST['title'] ?? '');
$this->html(htmlspecialchars($title));
}
public function systemPrompt(string $uuid): void
{
$session = $this->getSessionUseCase->getSession($uuid);
if ($session === null) {
$this->notFound('Session nicht gefunden');
}
$result = $this->updateSessionUseCase->updateSystemPrompt($session->getId() ?? 0, $_POST['system_prompt'] ?? '');
$this->view('chat.partials.success', ['message' => $result->message]);
}
public function getSystemPrompt(string $uuid): void
{
$session = $this->getSessionUseCase->getSession($uuid);
if ($session === null) {
$this->notFound('Session nicht gefunden');
}
$this->view('chat.partials.system-prompt-modal', [
'session' => ChatSessionFactory::toArray($session),
'currentPrompt' => $this->getSessionUseCase->getSystemPromptById($session->getSystemPromptId()),
'defaultPrompt' => $this->getSessionUseCase->getDefaultSystemPrompt(),
]);
}
public function delete(string $uuid): void
{
$session = $this->getSessionUseCase->getSession($uuid);
if ($session !== null) {
$this->deleteSessionUseCase->deleteSession($session->getId() ?? 0);
}
$this->htmxRedirect('/chat');
}
public function deleteAll(): void
{
$this->deleteSessionUseCase->deleteAllSessions();
$this->htmxRedirect('/chat');
}
public function export(string $uuid): void
{
$format = $this->getString('format') ?: 'markdown';
$filename = $this->exportUseCase->generateFilename($uuid, $format);
if ($format === 'json') {
$data = $this->exportUseCase->exportAsJson($uuid);
if ($data === null) {
$this->notFound('Session nicht gefunden');
}
$content = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
$this->download($content, $filename, 'application/json');
} else {
$content = $this->exportUseCase->exportAsMarkdown($uuid);
if ($content === null) {
$this->notFound('Session nicht gefunden');
}
$this->download($content, $filename, 'text/markdown');
}
}
/**
* POST /chat/promote - Create Content-Studio order from chat response
*/
public function promoteToContent(): void
{
$this->requireCsrf();
$question = trim($_POST['question'] ?? '');
$answer = trim($_POST['answer'] ?? '');
$model = $_POST['model'] ?? 'claude-sonnet-4-20250514';
if ($question === '') {
$this->redirect('/chat');
}
// Create title from question (max 100 chars)
$title = mb_strlen($question) > 100
? mb_substr($question, 0, 97) . '...'
: $question;
// Create briefing with context from chat
$briefing = $question;
if ($answer !== '') {
$briefing .= "\n\n---\n\n**Kontext aus Chat:**\n\n" . $answer;
}
// Get defaults from last order
$defaults = $this->contentRepository->getLastOrderSettings();
// Create the content order
$orderId = $this->contentRepository->createOrder([
'title' => $title,
'briefing' => $briefing,
'author_profile_id' => $defaults['author_profile_id'],
'contract_id' => $defaults['contract_id'],
'structure_id' => $defaults['structure_id'],
'model' => $model,
'collections' => json_encode($defaults['collections']),
'context_limit' => $defaults['context_limit'],
]);
// Redirect to Content-Studio
$this->redirect('/content/' . $orderId);
}
/**
* Extract chat message parameters from POST and session.
*
* @return array{
* question: string,
* model: string,
* collections: array<string>,
* contextLimit: int,
* authorProfileId: int,
* systemPromptId: int,
* structureId: int,
* temperature: float,
* maxTokens: int,
* qualityCheck: bool
* }
*/
private function extractChatParams(\Domain\Entity\ChatSession $session): array
{
$requestedModel = $_POST['model'] ?? $session->getModel();
$model = $this->modelRegistry->isValid($requestedModel)
? $requestedModel
: $this->modelRegistry->getDefaultChatModel();
return [
'question' => trim($_POST['message'] ?? ''),
'model' => $model,
'collections' => $_POST['collections'] ?? $session->getCollections(),
'contextLimit' => (int) ($_POST['context_limit'] ?? $session->getContextLimit()),
'authorProfileId' => (int) ($_POST['author_profile_id'] ?? $session->getAuthorProfileId() ?? 0),
'systemPromptId' => (int) ($_POST['system_prompt_id'] ?? $session->getSystemPromptId() ?? 1),
'structureId' => (int) ($_POST['structure_id'] ?? 0),
'temperature' => (float) ($_POST['temperature'] ?? $session->getTemperature()),
'maxTokens' => (int) ($_POST['max_tokens'] ?? $session->getMaxTokens()),
'qualityCheck' => isset($_POST['quality_check']) && $_POST['quality_check'] === '1',
];
}
/**
* Update session settings if changed.
*
* @param array<string> $collections
*/
private function updateSessionIfChanged(
\Domain\Entity\ChatSession $session,
string $model,
array $collections,
int $contextLimit,
int $authorProfileId,
float $temperature,
int $maxTokens
): void {
if ($this->updateSessionUseCase->settingsHaveChanged($session, $model, $collections, $contextLimit, $authorProfileId, $temperature, $maxTokens)) {
$this->updateSessionUseCase->updateSettings($session->getId() ?? 0, $model, $collections, $contextLimit, $authorProfileId, $temperature, $maxTokens);
}
}
/**
* Validate collections compatibility.
*
* @param array<string> $collections
* @return array{valid: bool, error: string|null}
*/
private function validateCollections(array $collections): array
{
if (empty($collections)) {
return ['valid' => true, 'error' => null];
}
$compatibility = $this->updateSessionUseCase->validateCollectionCompatibility($collections);
return [
'valid' => $compatibility['valid'],
'error' => $compatibility['error'] ?? null,
];
}
/**
* Setup SSE stream headers and disable buffering.
*/
private function setupSseStream(): void
{
header('Content-Type: text/event-stream; charset=utf-8');
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
header('Connection: keep-alive');
header('X-Accel-Buffering: no');
header('Content-Encoding: none');
if (function_exists('apache_setenv')) {
apache_setenv('no-gzip', '1');
}
@ini_set('zlib.output_compression', '0');
@ini_set('implicit_flush', '1');
@ini_set('output_buffering', '0');
while (ob_get_level()) {
ob_end_clean();
}
ob_implicit_flush(true);
// Send padding to force buffer flush
echo ':' . str_repeat(' ', 4096) . "\n\n";
flush();
}
/**
* Setup progress callback for SSE events.
*/
private function setupProgressCallback(): void
{
$this->streamingUseCase->setProgressCallback(function (string $step, string $message, ?int $durationMs) {
$timestamp = (new \DateTime())->format('H:i:s.v');
$data = [
'ts' => $timestamp,
'step' => $step,
'msg' => $message,
'ms' => $durationMs,
];
echo "event: progress\n";
echo 'data: ' . json_encode($data, JSON_UNESCAPED_UNICODE) . "\n\n";
echo ':' . str_repeat(' ', 4096) . "\n";
flush();
});
}
/**
* Send streaming result as SSE event.
*/
private function sendStreamResult(\UseCases\Chat\ChatResponse $response, string $question, string $model): void
{
if ($response->hasError()) {
$errorData = ['error' => $response->getError()];
echo "event: error\n";
echo 'data: ' . json_encode($errorData, JSON_UNESCAPED_UNICODE) . "\n\n";
} else {
$result = $response->toArray();
$formattedAnswer = $this->formatter->formatAnswer($result['answer'] ?? '');
ob_start();
$this->view('chat.partials.response', [
'question' => $question,
'result' => $result,
'model' => $model,
'formattedAnswer' => $formattedAnswer,
]);
$html = ob_get_clean();
$doneData = ['html' => $html];
echo "event: done\n";
echo 'data: ' . json_encode($doneData, JSON_UNESCAPED_UNICODE) . "\n\n";
}
@ob_flush();
flush();
}
}