repository = $repository; $this->collectionService = $collectionService; $this->generateUseCase = $generateUseCase; } /** * GET /content - List all content orders */ public function index(): void { $status = $this->getString('status'); $this->view('content.index', [ 'title' => 'Content Studio', 'orders' => $this->repository->findAllOrders($status !== '' ? ['status' => $status] : []), 'stats' => $this->repository->getStatistics(), 'currentStatus' => $status, ]); } /** * GET /content/new - Show create form */ public function contentNew(): void { $lastSettings = $this->repository->getLastOrderSettings(); $this->view('content.new', [ 'title' => 'Neuer Content-Auftrag', 'profiles' => $this->repository->findAllProfiles(), 'contracts' => $this->repository->findAllContracts(), 'structures' => $this->repository->findAllStructures(), 'models' => ModelConfig::getAll(), 'collections' => $this->collectionService->getAvailable(), 'defaultModel' => $lastSettings['model'], 'defaultCollections' => $lastSettings['collections'], 'defaultContextLimit' => $lastSettings['context_limit'], 'defaultProfileId' => $lastSettings['author_profile_id'], 'defaultContractId' => $lastSettings['contract_id'], 'defaultStructureId' => $lastSettings['structure_id'], ]); } /** * POST /content - Store new order */ public function store(): void { $this->requireCsrf(); $command = CreateContentOrderCommand::fromRequest($_POST); $errors = $command->validate(); if ($errors !== []) { $_SESSION['error'] = implode(' ', $errors); $this->redirect('/content/new'); } // Validate collections $result = $this->collectionService->validateWithCompatibility($command->collections); if (!$result['valid']) { $_SESSION['error'] = 'Collection-Fehler: ' . $result['error']; $this->redirect('/content/new'); } $orderId = $this->repository->createOrder([ 'title' => $command->title, 'briefing' => $command->briefing, 'author_profile_id' => $command->authorProfileId, 'contract_id' => $command->contractId ?? $this->getFirstContractId(), 'structure_id' => $command->structureId, 'model' => ModelConfig::validate($command->model), 'collections' => json_encode($result['collections']), 'context_limit' => $command->contextLimit, ]); if ($command->shouldGenerate()) { // Start async generation (non-blocking) $this->repository->updateGenerationStatus($orderId, 'generating'); $this->generateUseCase->generateAsync( $orderId, ModelConfig::validate($command->model), $result['collections'][0] ?? 'documents', $command->contextLimit ); } $this->redirect('/content/' . $orderId); } /** * GET /content/{id} - Show order details */ public function show(int $id): void { $order = $this->repository->findOrder($id); if ($order === null) { $this->notFound('Auftrag nicht gefunden'); } $versions = $this->repository->findVersionsByOrder($id); $latestVersion = $versions[0] ?? null; $this->view('content.show', [ 'title' => $order['title'], 'order' => $order, 'versions' => $versions, 'latestVersion' => $latestVersion, 'critiques' => $latestVersion ? $this->repository->findCritiquesByVersion($latestVersion['id']) : [], 'sources' => $this->repository->findSourcesByOrder($id), 'models' => ModelConfig::getAll(), 'availableCollections' => $this->collectionService->getAvailable(), ]); } /** * GET /content/{id}/edit - Show edit form */ public function edit(int $id): void { $order = $this->repository->findOrder($id); if ($order === null) { $this->notFound('Auftrag nicht gefunden'); } $this->view('content.edit', [ 'title' => 'Auftrag bearbeiten', 'order' => $order, 'profiles' => $this->repository->findAllProfiles(), 'contracts' => $this->repository->findAllContracts(), 'structures' => $this->repository->findAllStructures(), ]); } /** * POST /content/{id}/generate - Start async content generation (HTMX) */ public function generate(int $id): void { $this->requireCsrf(); $command = GenerateContentCommand::fromRequest($id, $_POST); if (($errors = $command->validate()) !== []) { $this->htmxError(implode(' ', $errors)); return; } $result = $this->collectionService->validateWithCompatibility([$command->collection]); if (!$result['valid']) { $this->htmxError($result['error'] ?? 'Collection-Fehler'); return; } // Set status to generating and start async $this->repository->updateGenerationStatus($id, 'generating'); $this->generateUseCase->generateAsync($id, $command->model, $result['collections'][0], $command->contextLimit); // Return polling partial $this->view('content.partials.generating', ['orderId' => $id]); } /** * GET /content/{id}/generation-status - Poll generation status (HTMX) */ public function generationStatus(int $id): void { $order = $this->repository->findOrder($id); if ($order === null) { $this->htmxError('Auftrag nicht gefunden'); return; } $status = $order['generation_status'] ?? 'idle'; if ($status === 'completed') { $version = $this->repository->findLatestVersion($id); if ($version !== null) { // Reset status to idle after showing result $this->repository->updateGenerationStatus($id, 'idle'); $this->renderVersionPartial([ 'content' => $version['content'], 'sources' => $this->repository->findSourcesByOrder($id), 'version_number' => $version['version_number'], ]); return; } } if ($status === 'failed') { // Reset status after showing error $error = $order['generation_error'] ?? 'Unbekannter Fehler'; $this->repository->updateGenerationStatus($id, 'idle'); $this->htmxError('Generierung fehlgeschlagen: ' . $error); return; } if ($status === 'generating' || $status === 'queued') { // Still generating - return polling partial $this->view('content.partials.generating', ['orderId' => $id]); return; } // Status is idle - return empty (nothing happening) $this->html(''); } /** * POST /content/{id}/critique - Run critique round (HTMX) */ public function critique(int $id): void { $this->requireCsrf(); $version = $this->repository->findLatestVersion($id); if ($version === null) { $this->htmxError('Keine Version vorhanden.'); return; } $result = $this->generateUseCase->critique($version['id'], $_POST['model'] ?? 'claude-opus-4-5-20251101'); if ($result->hasError()) { $this->htmxError('Fehler: ' . $result->getError()); return; } $this->renderCritiquePartial($result->toArray()); } /** * POST /content/{id}/revise - Create revision (HTMX) */ public function revise(int $id): void { $this->requireCsrf(); $version = $this->repository->findLatestVersion($id); if ($version === null) { $this->htmxError('Keine Version vorhanden.'); return; } $result = $this->generateUseCase->revise($version['id'], $_POST['model'] ?? 'claude-opus-4-5-20251101'); if ($result->hasError()) { $this->htmxError('Fehler: ' . $result->getError()); return; } $this->renderVersionPartial($result->toArray()); } /** * POST /content/{id}/approve - Approve content */ public function approve(int $id): void { $this->requireCsrf(); $this->repository->updateOrderStatus($id, 'approve'); $this->htmxSuccess('Content genehmigt!'); $this->html(''); } /** * POST /content/{id}/decline - Decline content */ public function decline(int $id): void { $this->requireCsrf(); $this->repository->updateOrderStatus($id, 'draft'); $this->htmxAlert('warning', 'Content abgelehnt. Zurück zu Entwurf.'); $this->html(''); } private function getFirstContractId(): ?int { $contracts = $this->repository->findAllContracts(); return $contracts !== [] ? (int) $contracts[0]['id'] : null; } private function renderVersionPartial(array $result): void { $this->view('content.partials.version', [ 'content' => $result['content'] ?? '', 'sources' => $result['sources'] ?? [], 'versionNumber' => $result['version_number'] ?? '?', ]); } private function renderCritiquePartial(array $result): void { $this->view('content.partials.critique', [ 'critiques' => $result['critiques'] ?? [], 'allPassed' => $result['all_passed'] ?? false, 'round' => $result['round'] ?? '?', ]); } }