Backup #498

ID498
Dateipfad/var/www/dev.campus.systemische-tools.de/src/Controller/ContentController.php
Version46
Typ modified
Größe12.0 KB
Hashef5daee68fae0027d6d249bbb53f86ab7d28bdeebde29c8da38df5d081db77a4
Datum2025-12-22 15:40:15
Geändert vonclaude-code-hook
GrundClaude Code Pre-Hook Backup vor Edit-Operation
Datei existiert Ja

Dateiinhalt

<?php

namespace Controller;

use Framework\Controller;
use Infrastructure\AI\ModelConfig;
use Infrastructure\Persistence\CollectionRepository;
use Infrastructure\Persistence\ContentRepository;
use Infrastructure\Validation\CollectionValidator;
use UseCases\Command\CreateContentOrderCommand;
use UseCases\Command\GenerateContentCommand;
use UseCases\Content\GenerateContentUseCase;

class ContentController extends Controller
{
    private ContentRepository $repository;
    private CollectionRepository $collectionRepository;
    private CollectionValidator $collectionValidator;
    private GenerateContentUseCase $generateUseCase;

    public function __construct()
    {
        $this->repository = new ContentRepository();
        $this->collectionRepository = new CollectionRepository();
        $this->collectionValidator = new CollectionValidator($this->collectionRepository);
        $this->generateUseCase = new GenerateContentUseCase();
    }

    /**
     * GET /content
     * List all content orders
     */
    public function index(): void
    {
        $filters = [];
        $status = $this->getString('status');
        if ($status !== '') {
            $filters['status'] = $status;
        }

        $orders = $this->repository->findAllOrders($filters);
        $stats = $this->repository->getStatistics();

        $this->view('content.index', [
            'title' => 'Content Studio',
            'orders' => $orders,
            'stats' => $stats,
            'currentStatus' => $status,
        ]);
    }

    /**
     * GET /content/new
     * Show create form
     */
    public function contentNew(): void
    {
        $collections = $this->getAvailableCollections();
        $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' => $collections,
            // Defaults from last order
            '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);
            header('Location: /content/new');
            exit;
        }

        // Auto-apply first active contract if none selected
        $contractId = $command->contractId;
        if ($contractId === null) {
            $contracts = $this->repository->findAllContracts();
            if ($contracts !== []) {
                $contractId = (int) $contracts[0]['id'];
            }
        }

        // Validate collection compatibility
        $collections = $this->validateCollections($command->collections);
        $compatibility = $this->validateCollectionCompatibility($collections);
        if (!$compatibility['valid']) {
            $_SESSION['error'] = 'Collection-Fehler: ' . $compatibility['error'];
            header('Location: /content/new');
            exit;
        }

        $model = ModelConfig::validate($command->model);

        $orderId = $this->repository->createOrder([
            'title' => $command->title,
            'briefing' => $command->briefing,
            'author_profile_id' => $command->authorProfileId,
            'contract_id' => $contractId,
            'structure_id' => $command->structureId,
            'model' => $model,
            'collections' => json_encode($collections),
            'context_limit' => $command->contextLimit,
        ]);

        // If "generate" action: generate content immediately
        if ($command->shouldGenerate()) {
            $collection = $collections[0] ?? 'documents';
            $result = $this->generateUseCase->generate($orderId, $model, $collection, $command->contextLimit);

            if ($result->hasError()) {
                $_SESSION['error'] = 'Generierung fehlgeschlagen: ' . $result->getError();
            } else {
                $_SESSION['success'] = 'Content wurde generiert.';
            }
        }

        header('Location: /content/' . $orderId);
        exit;
    }

    /**
     * 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;
        $critiques = $latestVersion ? $this->repository->findCritiquesByVersion($latestVersion['id']) : [];
        $sources = $this->repository->findSourcesByOrder($id);

        // Get available collections for the dropdown
        $availableCollections = $this->getAvailableCollections();

        $this->view('content.show', [
            'title' => $order['title'],
            'order' => $order,
            'versions' => $versions,
            'latestVersion' => $latestVersion,
            'critiques' => $critiques,
            'sources' => $sources,
            'models' => ModelConfig::getAll(),
            'availableCollections' => $availableCollections,
        ]);
    }

    /**
     * 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
     * Generate content (HTMX)
     */
    public function generate(int $id): void
    {
        $this->requireCsrf();

        $command = GenerateContentCommand::fromRequest($id, $_POST);
        $errors = $command->validate();

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

            return;
        }

        // Validate collection
        $collections = $this->validateCollections([$command->collection]);
        if (empty($collections)) {
            $this->htmxError('Ungültige Collection: ' . $command->collection);

            return;
        }
        $collection = $collections[0];

        // Validate compatibility (single collection always valid, but check exists)
        $compatibility = $this->validateCollectionCompatibility($collections);
        if (!$compatibility['valid']) {
            $this->htmxError($compatibility['error'] ?? 'Collection-Fehler');

            return;
        }

        $result = $this->generateUseCase->generate($id, $command->model, $collection, $command->contextLimit);

        if ($result->hasError()) {
            $this->htmxError('Fehler: ' . $result->getError());

            return;
        }

        // Return updated content section
        $this->renderVersionPartial($result->toArray());
    }

    /**
     * POST /content/{id}/critique
     * Run critique round (HTMX)
     */
    public function critique(int $id): void
    {
        $this->requireCsrf();

        // Get latest version
        $version = $this->repository->findLatestVersion($id);

        if ($version === null) {
            echo '<div class="alert error">Keine Version vorhanden.</div>';

            return;
        }

        $model = $_POST['model'] ?? 'claude-opus-4-5-20251101';
        $result = $this->generateUseCase->critique($version['id'], $model);

        if ($result->hasError()) {
            echo '<div class="alert error">Fehler: ' . htmlspecialchars($result->getError()) . '</div>';

            return;
        }

        // Return critique results
        $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) {
            echo '<div class="alert error">Keine Version vorhanden.</div>';

            return;
        }

        $model = $_POST['model'] ?? 'claude-opus-4-5-20251101';
        $result = $this->generateUseCase->revise($version['id'], $model);

        if ($result->hasError()) {
            echo '<div class="alert error">Fehler: ' . htmlspecialchars($result->getError()) . '</div>';

            return;
        }

        $this->renderVersionPartial($result->toArray());
    }

    /**
     * POST /content/{id}/approve
     * Approve content
     */
    public function approve(int $id): void
    {
        $this->requireCsrf();

        $this->repository->updateOrderStatus($id, 'approve');
        echo '<div class="alert success">Content genehmigt!</div>';
        echo '<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');
        echo '<div class="alert warning">Content abgelehnt. Zurück zu Entwurf.</div>';
        echo '<script>setTimeout(() => window.location.reload(), 1000);</script>';
    }

    /**
     * Render version partial
     */
    private function renderVersionPartial(array $result): void
    {
        $this->view('content.partials.version', [
            'content' => $result['content'] ?? '',
            'sources' => $result['sources'] ?? [],
            'versionNumber' => $result['version_number'] ?? '?',
        ]);
    }

    /**
     * Render critique partial
     */
    private function renderCritiquePartial(array $result): void
    {
        $this->view('content.partials.critique', [
            'critiques' => $result['critiques'] ?? [],
            'allPassed' => $result['all_passed'] ?? false,
            'round' => $result['round'] ?? '?',
        ]);
    }

    /**
     * Get available collections from database
     *
     * @return array<int, array<string,mixed>>
     */
    private function getAvailableCollections(): array
    {
        $collections = $this->collectionRepository->getSearchable();

        if ($collections === []) {
            return [
                ['collection_id' => 'documents', 'display_name' => 'Dokumente', 'points_count' => 0, 'vector_size' => 1024],
            ];
        }

        return $collections;
    }

    /**
     * Validate collections against available ones
     *
     * @param array<string> $collections
     * @return array<string>
     */
    private function validateCollections(array $collections): array
    {
        $availableIds = array_column($this->getAvailableCollections(), 'collection_id');
        $valid = array_filter($collections, fn ($c) => in_array($c, $availableIds, true));

        return array_values($valid);
    }

    /**
     * Validate collection compatibility (vector dimensions)
     *
     * @param array<string> $collectionIds
     * @return array{valid: bool, error: string|null}
     */
    private 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(),
        ];
    }
}

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