Backup #1297

ID1297
Dateipfad/var/www/dev.campus.systemische-tools.de/src/Controller/ContentController.php
Version66
Typ modified
Größe13.1 KB
Hasha04367b5a426fa54799f7171044b1cadcea7aa3d625e07ed9d498f65e449d008
Datum2025-12-25 13:27:56
Geändert vonclaude-code-hook
GrundClaude Code Pre-Hook Backup vor Edit-Operation
Datei existiert Ja

Dateiinhalt

<?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(),
            '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'],
        ]);
    }

    /**
     * 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' => $this->modelRegistry->isValid($command->model) ? $command->model : $this->modelRegistry->getDefaultChatModel(),
            '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' => $this->modelRegistry->getChatModels(),
            '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 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'] ?? '?',
        ]);
    }
}

Vollständig herunterladen

Aktionen

Herunterladen

Andere Versionen dieser Datei

ID Version Typ Größe Datum
2162 71 modified 14.5 KB 2025-12-30 22:24
2161 70 modified 14.3 KB 2025-12-30 22:24
2160 69 modified 14.1 KB 2025-12-30 22:24
2159 68 modified 13.6 KB 2025-12-30 22:23
1768 67 modified 13.2 KB 2025-12-27 12:55
1297 66 modified 13.1 KB 2025-12-25 13:27
1296 65 modified 13.0 KB 2025-12-25 13:27
1295 64 modified 13.0 KB 2025-12-25 13:27
1294 63 modified 13.2 KB 2025-12-25 13:27
1276 62 modified 13.2 KB 2025-12-25 12:52
1015 61 modified 10.9 KB 2025-12-24 01:29
986 60 modified 10.7 KB 2025-12-24 00:41
985 59 modified 10.8 KB 2025-12-24 00:31
984 58 modified 10.4 KB 2025-12-24 00:18
974 57 modified 9.2 KB 2025-12-24 00:12
686 56 modified 9.1 KB 2025-12-23 07:51
633 55 modified 9.2 KB 2025-12-23 04:45
618 54 modified 9.5 KB 2025-12-23 04:42
590 53 modified 9.2 KB 2025-12-23 04:24
530 52 modified 11.8 KB 2025-12-22 21:19
509 51 modified 11.9 KB 2025-12-22 15:42
508 50 modified 11.9 KB 2025-12-22 15:42
501 49 modified 11.9 KB 2025-12-22 15:40
500 48 modified 11.9 KB 2025-12-22 15:40
499 47 modified 12.0 KB 2025-12-22 15:40
498 46 modified 12.0 KB 2025-12-22 15:40
497 45 modified 12.2 KB 2025-12-22 15:38
489 44 modified 12.1 KB 2025-12-22 15:27
488 43 modified 12.4 KB 2025-12-22 15:27
487 42 modified 12.3 KB 2025-12-22 15:27
320 41 modified 12.3 KB 2025-12-22 08:06
280 40 modified 14.1 KB 2025-12-22 02:18
279 39 modified 14.1 KB 2025-12-22 02:17
278 38 modified 14.1 KB 2025-12-22 02:17
277 37 modified 14.1 KB 2025-12-22 02:17
276 36 modified 14.1 KB 2025-12-22 02:17
275 35 modified 14.1 KB 2025-12-22 02:17
267 34 modified 15.4 KB 2025-12-22 02:10
266 33 modified 16.0 KB 2025-12-22 02:10
234 32 modified 16.0 KB 2025-12-22 01:46
233 31 modified 16.1 KB 2025-12-22 01:46
207 30 modified 15.6 KB 2025-12-21 17:01
198 29 modified 15.9 KB 2025-12-21 14:39
190 28 modified 14.4 KB 2025-12-21 14:36
189 27 modified 13.8 KB 2025-12-21 14:35
188 26 modified 13.4 KB 2025-12-21 14:35
187 25 modified 13.5 KB 2025-12-21 14:35
186 24 modified 13.6 KB 2025-12-21 14:35
185 23 modified 13.2 KB 2025-12-21 14:35
178 22 modified 13.4 KB 2025-12-21 09:14
177 21 modified 13.0 KB 2025-12-21 09:14
173 20 modified 12.8 KB 2025-12-21 04:13
172 19 modified 12.6 KB 2025-12-21 04:13
166 18 modified 12.6 KB 2025-12-21 04:11
165 17 modified 12.6 KB 2025-12-21 04:11
164 16 modified 12.6 KB 2025-12-21 04:11
163 15 modified 12.8 KB 2025-12-21 04:11
160 14 modified 12.5 KB 2025-12-21 03:00
157 13 modified 12.1 KB 2025-12-21 02:59
156 12 modified 11.8 KB 2025-12-21 02:59
155 11 modified 11.4 KB 2025-12-21 02:59
139 10 modified 11.4 KB 2025-12-20 19:58
138 9 modified 11.3 KB 2025-12-20 19:57
137 8 modified 10.8 KB 2025-12-20 19:57
134 7 modified 10.4 KB 2025-12-20 19:55
113 6 modified 10.4 KB 2025-12-20 19:20
112 5 modified 10.3 KB 2025-12-20 19:20
111 4 modified 10.3 KB 2025-12-20 19:20
110 3 modified 10.3 KB 2025-12-20 19:20
109 2 modified 10.2 KB 2025-12-20 19:19
108 1 modified 10.2 KB 2025-12-20 19:19

← Zurück zur Übersicht