ContentController.php

Code Hygiene Score: 77

Issues 2

Zeile Typ Beschreibung
396 magic_number Magic Number gefunden: 1000
407 magic_number Magic Number gefunden: 1000

Dependencies 12

Klassen 1

Funktionen 16

Versionen 71

Code

<?php

declare(strict_types=1);

namespace Controller;

// @responsibility: HTTP-Endpunkte für Content Studio (Aufträge, Generierung, Kritik)

use Application\ContentCollectionService;
use Domain\Repository\ContentRepositoryInterface;
use Framework\Controller;
use Infrastructure\AI\ModelRegistry;
use UseCases\Command\CreateContentOrderCommand;
use UseCases\Command\GenerateContentCommand;
use UseCases\Content\GenerateContentUseCase;

class ContentController extends Controller
{
    public function __construct(
        private ContentRepositoryInterface $repository,
        private ContentCollectionService $collectionService,
        private GenerateContentUseCase $generateUseCase,
        private ModelRegistry $modelRegistry
    ) {
    }

    /**
     * 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(),
            'systemPrompts' => $this->repository->findAllSystemPrompts(),
            'critics' => $this->repository->findAllCritics(),
            'models' => $this->modelRegistry->getChatModels(),
            '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'],
            'defaultTemperature' => $lastSettings['temperature'],
            'defaultMaxTokens' => $lastSettings['max_tokens'],
            'defaultSystemPromptId' => $lastSettings['system_prompt_id'],
            'defaultSelectedCritics' => $lastSettings['selected_critics'],
            'defaultQualityCheck' => $lastSettings['quality_check'],
        ]);
    }

    /**
     * POST /content - Store new order
     */
    public function store(): void
    {
        $this->requireCsrf();

        $isHtmx = $this->isHtmxRequest();
        $command = CreateContentOrderCommand::fromRequest($_POST);
        $errors = $command->validate();

        if ($errors !== []) {
            if ($isHtmx) {
                $this->htmxError(implode(' ', $errors));

                return;
            }
            $_SESSION['error'] = implode(' ', $errors);
            $this->redirect('/content/new');
        }

        // Validate collections
        $result = $this->collectionService->validateWithCompatibility($command->collections);
        if (!$result['valid']) {
            if ($isHtmx) {
                $this->htmxError('Collection-Fehler: ' . $result['error']);

                return;
            }
            $_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' => $this->modelRegistry->isValid($command->model) ? $command->model : $this->modelRegistry->getDefaultChatModel(),
            'collections' => json_encode($result['collections']),
            'context_limit' => $command->contextLimit,
            'temperature' => $command->temperature,
            'max_tokens' => $command->maxTokens,
            'system_prompt_id' => $command->systemPromptId,
            'selected_critics' => $command->selectedCritics,
            'quality_check' => $command->qualityCheck,
        ]);

        if ($command->shouldGenerate()) {
            // Start async generation (non-blocking)
            $this->repository->updateGenerationStatus($orderId, 'generating');
            $this->generateUseCase->generateAsync(
                $orderId,
                $this->modelRegistry->isValid($command->model) ? $command->model : $this->modelRegistry->getDefaultChatModel(),
                $result['collections'][0] ?? 'documents',
                $command->contextLimit
            );
        }

        if ($isHtmx) {
            $this->htmxRedirect('/content/' . $orderId);

            return;
        }

        $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' => $this->modelRegistry->getChatModels(),
            'availableCollections' => $this->collectionService->getAvailable(),
            'systemPrompts' => $this->repository->findAllSystemPrompts(),
            'allCritics' => $this->repository->findAllCritics(),
        ]);
    }

    /**
     * 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(),
            'systemPrompts' => $this->repository->findAllSystemPrompts(),
            'critics' => $this->repository->findAllCritics(),
            'models' => $this->modelRegistry->getChatModels(),
            'collections' => $this->collectionService->getAvailable(),
        ]);
    }

    /**
     * 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 with log
            $this->view('content.partials.generating', [
                'orderId' => $id,
                'log' => $order['generation_log'] ?? '',
                'step' => $order['generation_step'] ?? '',
            ]);

            return;
        }

        // Status is idle - return empty (nothing happening)
        $this->html('');
    }

    /**
     * POST /content/{id}/critique - Start async 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;
        }

        // Set status to critiquing and start async
        $this->repository->updateCritiqueStatus($id, 'critiquing');
        $this->generateUseCase->critiqueAsync($id, $version['id'], $_POST['model'] ?? 'claude-opus-4-5-20251101');

        // Return polling partial
        $this->view('content.partials.critiquing-live', ['orderId' => $id]);
    }

    /**
     * GET /content/{id}/critique-status - Poll critique status (HTMX)
     */
    public function critiqueStatus(int $id): void
    {
        $order = $this->repository->findOrder($id);
        if ($order === null) {
            $this->htmxError('Auftrag nicht gefunden');

            return;
        }

        $status = $order['critique_status'] ?? 'idle';

        if ($status === 'completed') {
            // Get latest version and its critiques
            $version = $this->repository->findLatestVersion($id);
            if ($version !== null) {
                $critiques = $this->repository->findCritiquesByVersion($version['id']);
                // Get the latest round
                $latestRound = $order['current_critique_round'] ?? 1;
                // Filter critiques for latest round
                $roundCritiques = array_filter($critiques, fn ($c) => ($c['round'] ?? 0) === $latestRound);

                // Reset status to idle after showing result
                $this->repository->updateCritiqueStatus($id, 'idle');

                // Check if all passed
                $allPassed = true;
                foreach ($roundCritiques as $c) {
                    if (!($c['passed'] ?? false)) {
                        $allPassed = false;
                        break;
                    }
                }

                $this->renderCritiquePartial([
                    'critiques' => array_values($roundCritiques),
                    'all_passed' => $allPassed,
                    'round' => $latestRound,
                ]);

                return;
            }
        }

        if ($status === 'failed') {
            $error = $order['critique_error'] ?? 'Unbekannter Fehler';
            $this->repository->updateCritiqueStatus($id, 'idle');
            $this->htmxError('Kritik fehlgeschlagen: ' . $error);

            return;
        }

        if ($status === 'critiquing') {
            // Still critiquing - return polling partial with log
            $this->view('content.partials.critiquing-live', [
                'orderId' => $id,
                'log' => $order['critique_log'] ?? '',
                'step' => $order['critique_step'] ?? '',
            ]);

            return;
        }

        // Status is idle - return empty
        $this->html('');
    }

    /**
     * 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('<script>setTimeout(() => window.location.reload(), 1000);</script>');
    }

    /**
     * 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('<script>setTimeout(() => window.location.reload(), 1000);</script>');
    }

    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'] ?? '?',
        ]);
    }
}
← Übersicht Graph