Backup #1686

ID1686
Dateipfad/var/www/dev.campus.systemische-tools.de/src/View/content-pipeline/show.php
Version22
Typ modified
Größe16.8 KB
Hash63cc1e0f72eb695c14ed75fd8dc17f905d2e44de6966bed4c6199daea7476336
Datum2025-12-27 12:04:31
Geändert vonclaude-code-hook
GrundClaude Code Pre-Hook Backup vor Edit-Operation
Datei existiert Ja

Dateiinhalt

<?php ob_start(); ?>

<h1>Pipeline: <?= htmlspecialchars($pipeline['name']) ?></h1>

<?php if ($pipeline['is_default']): ?>
<span class="badge badge--success">Standard-Pipeline</span>
<?php endif; ?>

<div class="page-actions">
    <a href="/content-pipeline/<?= $pipeline['id'] ?>/edit" class="btn btn--secondary">Bearbeiten</a>
    <form action="/content-pipeline/<?= $pipeline['id'] ?>/run" method="POST" style="display:inline;">
        <input type="hidden" name="_csrf_token" value="<?= htmlspecialchars($_SESSION['_csrf_token'] ?? '') ?>">
        <button type="submit" class="btn btn--primary">Pipeline starten</button>
    </form>
</div>

<ul class="config-list">
    <li class="config-list__item">
        <span class="config-list__label">Quelle:</span>
        <span class="config-list__value editable" data-field="source_path" data-pipeline-id="<?= $pipeline['id'] ?>"><?= htmlspecialchars($pipeline['source_path']) ?></span>
    </li>
    <li class="config-list__item">
        <span class="config-list__label">Dateitypen:</span>
        <span class="config-list__value editable" data-field="extensions" data-pipeline-id="<?= $pipeline['id'] ?>"><?= implode(', ', $pipeline['extensions'] ?? []) ?></span>
    </li>
</ul>

<h2>Pipeline-Schritte</h2>

<table>
    <thead>
        <tr>
            <th>#</th>
            <th>Schritt</th>
            <th>Phase</th>
            <th>Modell</th>
            <th>Zielspeicher</th>
            <th>Konfiguration</th>
        </tr>
    </thead>
    <tbody>
        <?php foreach ($pipeline['steps'] as $step): ?>
        <?php
            $meta = $stepTypes[$step['step_type']] ?? ['label' => $step['step_type'], 'description' => '', 'phase' => '-', 'storage' => null];
            $config = $step['config'] ?? [];

            // Aktuelles Modell ermitteln (mit ollama: Prefix für Dropdown)
            $currentModel = null;
            $usesVision = $meta['uses_vision'] ?? false;
            $usesLlm = $meta['uses_llm'] ?? false;

            if (!empty($config['model'])) {
                $isAnthropic = str_contains($config['model'], 'claude') || ($config['provider'] ?? '') === 'anthropic';
                $currentModel = $isAnthropic ? $config['model'] : 'ollama:' . $config['model'];
            }

            // Config ohne model/provider für kompaktere Anzeige
            $displayConfig = array_filter($config, fn ($k) => !in_array($k, ['provider', 'model']), ARRAY_FILTER_USE_KEY);

            // Zielspeicher mit dynamischen Werten ersetzen
            $storage = $meta['storage'] ?? null;
            if ($storage !== null && isset($config['collection'])) {
                $storage = str_replace('{collection}', $config['collection'], $storage);
            }
            ?>
        <tr data-step-id="<?= $step['id'] ?>">
            <td><?= $step['sort_order'] ?></td>
            <td>
                <strong><?= $meta['label'] ?></strong>
                <br>
                <small><?= $meta['description'] ?></small>
            </td>
            <td><?= $meta['phase'] ?></td>
            <td class="model-cell">
                <?php if (!empty($meta['fixed_model'])): ?>
                <span class="fixed-model"><?= htmlspecialchars($meta['fixed_model']) ?></span>
                <?php elseif ($currentModel !== null || $usesVision || $usesLlm): ?>
                <?php
                    $selected = $currentModel ?? ($usesVision ? \Infrastructure\AI\ModelConfig::getDefaultVisionModel() : \Infrastructure\AI\ModelConfig::getDefaultModel());
                    $availableModels = $usesVision ? \Infrastructure\AI\ModelConfig::getVisionModels() : \Infrastructure\AI\ModelConfig::getAll();
                    $ollamaModels = array_filter($availableModels, fn ($k) => str_starts_with($k, 'ollama:'), ARRAY_FILTER_USE_KEY);
                    $anthropicModels = array_filter($availableModels, fn ($k) => !str_starts_with($k, 'ollama:'), ARRAY_FILTER_USE_KEY);
                ?>
                <select name="model" class="form-select form-select--compact"
                        hx-post="/content-pipeline/<?= $pipeline['id'] ?>/steps/<?= $step['id'] ?>/model"
                        hx-headers='{"X-CSRF-TOKEN": "<?= $csrfToken ?>"}'
                        hx-swap="none"
                        hx-disabled-elt="this"
                        hx-on::after-request="this.classList.toggle('is-saved', event.detail.successful); setTimeout(() => this.classList.remove('is-saved'), 1000)">
                    <?php if (!empty($ollamaModels)): ?>
                    <optgroup label="Ollama (lokal)">
                        <?php foreach ($ollamaModels as $modelId => $label): ?>
                        <option value="<?= htmlspecialchars($modelId) ?>" <?= $selected === $modelId ? 'selected' : '' ?>><?= htmlspecialchars($label) ?></option>
                        <?php endforeach; ?>
                    </optgroup>
                    <?php endif; ?>
                    <?php if (!empty($anthropicModels)): ?>
                    <optgroup label="Anthropic">
                        <?php foreach ($anthropicModels as $modelId => $label): ?>
                        <option value="<?= htmlspecialchars($modelId) ?>" <?= $selected === $modelId ? 'selected' : '' ?>><?= htmlspecialchars($label) ?></option>
                        <?php endforeach; ?>
                    </optgroup>
                    <?php endif; ?>
                </select>
                <?php else: ?>
                <span class="text-muted">-</span>
                <?php endif; ?>
            </td>
            <td class="storage-cell">
                <?php if (!empty($meta['has_collection'])): ?>
                <span class="storage-prefix">Qdrant:</span>
                <select name="collection" class="collection-select"
                        hx-post="/content-pipeline/<?= $pipeline['id'] ?>/steps/<?= $step['id'] ?>/collection"
                        hx-headers='{"X-CSRF-TOKEN": "<?= $csrfToken ?>"}'
                        hx-swap="none"
                        hx-disabled-elt="this"
                        hx-on::after-request="this.classList.toggle('is-saved', event.detail.successful); setTimeout(() => this.classList.remove('is-saved'), 1000)">
                    <?php foreach ($collections as $collKey => $collLabel): ?>
                    <option value="<?= htmlspecialchars($collKey) ?>" <?= ($config['collection'] ?? 'documents') === $collKey ? 'selected' : '' ?>>
                        <?= htmlspecialchars($collKey) ?>
                    </option>
                    <?php endforeach; ?>
                </select>
                <?php elseif ($storage): ?>
                <code class="storage-code"><?= htmlspecialchars($storage) ?></code>
                <?php else: ?>
                <span class="text-muted">-</span>
                <?php endif; ?>
            </td>
            <td>
                <?php if ($displayConfig): ?>
                <code class="config-code"><?= htmlspecialchars(json_encode($displayConfig, JSON_UNESCAPED_UNICODE)) ?></code>
                <?php else: ?>
                <span class="text-muted">-</span>
                <?php endif; ?>
            </td>
        </tr>
        <?php endforeach; ?>
    </tbody>
</table>

<h2>Ausführungen</h2>

<?php if (!empty($runs)): ?>
<table>
    <thead>
        <tr>
            <th>ID</th>
            <th>Status</th>
            <th>Gestartet</th>
            <th>Beendet</th>
            <th>Dokumente</th>
            <th>Chunks</th>
        </tr>
    </thead>
    <tbody>
        <?php foreach ($runs as $run): ?>
        <tr>
            <td><a href="/content-pipeline/<?= $pipeline['id'] ?>/run/<?= $run['id'] ?>/status">#<?= $run['id'] ?></a></td>
            <td>
                <span class="badge badge--<?= $run['status'] === 'completed' ? 'success' : ($run['status'] === 'failed' ? 'danger' : ($run['status'] === 'running' ? 'warning' : 'muted')) ?>">
                    <?= $run['status'] ?>
                </span>
            </td>
            <td><?= $run['started_at'] ?? '-' ?></td>
            <td><?= $run['completed_at'] ?? '-' ?></td>
            <td>
                <?= $run['documents_processed'] ?? 0 ?>/<?= $run['documents_total'] ?? 0 ?>
                <?php if (($run['documents_failed'] ?? 0) > 0): ?>
                <span class="text-danger">(<?= $run['documents_failed'] ?> Fehler)</span>
                <?php endif; ?>
            </td>
            <td><?= $run['chunks_created'] ?? 0 ?></td>
        </tr>
        <?php endforeach; ?>
    </tbody>
</table>
<?php else: ?>
<p class="empty-state empty-state--small">Noch keine Ausführungen vorhanden.</p>
<?php endif; ?>

<p class="links-bar">
    <a href="/content-pipeline">Zurück zur Übersicht</a>
</p>

<style>
.config-list {
    list-style: none;
    padding: 0;
    margin: 1rem 0 2rem;
}
.config-list__item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0;
    border-bottom: 1px solid var(--border-color-light, #eee);
}
.config-list__label {
    font-weight: 500;
    min-width: 100px;
}
.config-list__value {
    flex: 1;
    font-family: monospace;
    font-size: 0.9rem;
}
.config-list__value.editable {
    cursor: pointer;
    padding: 0.25rem 0.5rem;
    border-radius: 4px;
    transition: background-color 0.15s;
}
.config-list__value.editable:hover {
    background: var(--bg-muted, #f5f5f5);
}
.config-list__value.editable::after {
    content: ' \270E';
    font-size: 0.8rem;
    color: var(--text-muted, #999);
    opacity: 0;
    transition: opacity 0.15s;
}
.config-list__value.editable:hover::after {
    opacity: 1;
}
.config-list__input {
    flex: 1;
    font-family: monospace;
    font-size: 0.9rem;
    padding: 0.25rem 0.5rem;
    border: 1px solid var(--border-color, #ccc);
    border-radius: 4px;
}
.config-list__input:focus {
    outline: none;
    border-color: var(--color-primary, #0066cc);
}
.config-list__value.is-saving {
    opacity: 0.6;
}
.config-code,
.storage-code {
    font-size: 0.75rem;
    max-width: 200px;
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.config-code:hover,
.storage-code:hover {
    white-space: normal;
    word-break: break-all;
}
.storage-cell {
    min-width: 150px;
}
.storage-code {
    color: var(--color-primary, #0066cc);
    background: var(--bg-muted, #f5f5f5);
    padding: 0.15rem 0.4rem;
    border-radius: 3px;
}
.fixed-model {
    font-size: 0.8rem;
    color: var(--text-muted, #666);
    font-family: monospace;
}
.storage-prefix {
    font-size: 0.8rem;
    color: var(--text-muted, #666);
    margin-right: 0.25rem;
}
.collection-select {
    font-size: 0.8rem;
    padding: 0.15rem 0.3rem;
    border: 1px solid var(--border-color, #ccc);
    border-radius: 3px;
    background: var(--bg-input, #fff);
}
.collection-select.is-saving {
    opacity: 0.6;
    pointer-events: none;
}
.collection-select.is-saved {
    border-color: var(--color-success, #28a745);
    animation: flash-success 0.5s;
}
.model-cell .form-select--compact {
    font-size: 0.8rem;
    padding: 0.25rem 0.5rem;
    min-width: 140px;
}
.model-cell .form-select--compact.is-saving {
    opacity: 0.6;
    pointer-events: none;
}
.model-cell .form-select--compact.is-saved {
    border-color: var(--color-success, #28a745);
    animation: flash-success 0.5s;
}
@keyframes flash-success {
    0% { background-color: var(--color-success-light, #d4edda); }
    100% { background-color: transparent; }
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
    const pipelineId = <?= (int) $pipeline['id'] ?>;

    // Model-Dropdowns
    document.querySelectorAll('.model-cell select').forEach(select => {
        select.addEventListener('change', async function() {
            const row = this.closest('tr');
            const stepId = row.dataset.stepId;
            const model = this.value;

            this.classList.add('is-saving');

            try {
                const response = await fetch(`/content-pipeline/${pipelineId}/steps/${stepId}/model`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                    body: 'model=' + encodeURIComponent(model)
                });

                const data = await response.json();

                if (data.success) {
                    this.classList.remove('is-saving');
                    this.classList.add('is-saved');
                    setTimeout(() => this.classList.remove('is-saved'), 500);

                    // Speichere in localStorage für User-Präferenz
                    localStorage.setItem('pipeline_default_model', model);
                } else {
                    alert('Fehler: ' + (data.error || 'Unbekannter Fehler'));
                    this.classList.remove('is-saving');
                }
            } catch (error) {
                alert('Speichern fehlgeschlagen: ' + error.message);
                this.classList.remove('is-saving');
            }
        });
    });

    // User-Präferenz für neues Standard-Modell laden
    const savedModel = localStorage.getItem('pipeline_default_model');
    if (savedModel) {
        console.log('Gespeichertes Standard-Modell:', savedModel);
    }

    // Collection-Dropdowns
    document.querySelectorAll('.collection-select').forEach(select => {
        select.addEventListener('change', async function() {
            const stepId = this.dataset.stepId;
            const collection = this.value;

            this.classList.add('is-saving');

            try {
                const response = await fetch(`/content-pipeline/${pipelineId}/steps/${stepId}/collection`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                    body: 'collection=' + encodeURIComponent(collection)
                });

                const data = await response.json();

                if (data.success) {
                    this.classList.remove('is-saving');
                    this.classList.add('is-saved');
                    setTimeout(() => this.classList.remove('is-saved'), 500);
                } else {
                    alert('Fehler: ' + (data.error || 'Unbekannter Fehler'));
                    this.classList.remove('is-saving');
                }
            } catch (error) {
                alert('Speichern fehlgeschlagen: ' + error.message);
                this.classList.remove('is-saving');
            }
        });
    });

    // Inline-Edit für Quelle und Dateitypen
    document.querySelectorAll('.config-list__value.editable').forEach(el => {
        el.addEventListener('click', function() {
            if (this.querySelector('input')) return;

            const field = this.dataset.field;
            const currentValue = this.textContent.trim();
            const originalEl = this;

            const input = document.createElement('input');
            input.type = 'text';
            input.className = 'config-list__input';
            input.value = currentValue;

            originalEl.textContent = '';
            originalEl.appendChild(input);
            input.focus();
            input.select();

            async function saveValue() {
                const newValue = input.value.trim();
                if (newValue === currentValue) {
                    originalEl.textContent = currentValue;
                    return;
                }

                originalEl.classList.add('is-saving');

                try {
                    const response = await fetch(`/content-pipeline/${pipelineId}/config`, {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                        body: field + '=' + encodeURIComponent(newValue)
                    });

                    const data = await response.json();

                    if (data.success) {
                        originalEl.textContent = newValue;
                        originalEl.classList.remove('is-saving');
                    } else {
                        alert('Fehler: ' + (data.error || 'Unbekannter Fehler'));
                        originalEl.textContent = currentValue;
                        originalEl.classList.remove('is-saving');
                    }
                } catch (error) {
                    alert('Speichern fehlgeschlagen: ' + error.message);
                    originalEl.textContent = currentValue;
                    originalEl.classList.remove('is-saving');
                }
            }

            input.addEventListener('blur', saveValue);
            input.addEventListener('keydown', function(e) {
                if (e.key === 'Enter') {
                    e.preventDefault();
                    input.blur();
                } else if (e.key === 'Escape') {
                    originalEl.textContent = currentValue;
                }
            });
        });
    });
});
</script>

<?php $content = ob_get_clean(); ?>
<?php require VIEW_PATH . '/layout.php'; ?>

Vollständig herunterladen

Aktionen

Herunterladen

Andere Versionen dieser Datei

ID Version Typ Größe Datum
1790 26 modified 12.0 KB 2025-12-27 14:08
1689 25 modified 12.0 KB 2025-12-27 12:06
1688 24 modified 17.4 KB 2025-12-27 12:05
1687 23 modified 17.6 KB 2025-12-27 12:05
1686 22 modified 16.8 KB 2025-12-27 12:04
1685 21 modified 16.4 KB 2025-12-27 12:03
1684 20 modified 14.9 KB 2025-12-27 12:03
907 19 modified 14.8 KB 2025-12-23 16:32
683 18 modified 14.8 KB 2025-12-23 07:44
576 17 modified 12.4 KB 2025-12-23 04:18
575 16 modified 11.6 KB 2025-12-23 04:18
574 15 modified 11.8 KB 2025-12-23 04:18
553 14 modified 11.8 KB 2025-12-23 03:32
480 13 modified 10.6 KB 2025-12-22 15:22
479 12 modified 10.1 KB 2025-12-22 15:22
478 11 modified 9.5 KB 2025-12-22 15:22
474 10 modified 9.4 KB 2025-12-22 15:19
473 9 modified 9.3 KB 2025-12-22 15:19
441 8 modified 9.0 KB 2025-12-22 10:14
440 7 modified 8.0 KB 2025-12-22 10:14
430 6 modified 5.8 KB 2025-12-22 10:04
429 5 modified 5.5 KB 2025-12-22 10:03
424 4 modified 5.3 KB 2025-12-22 09:58
423 3 modified 5.0 KB 2025-12-22 09:58
422 2 modified 4.3 KB 2025-12-22 09:57
420 1 modified 5.7 KB 2025-12-22 09:42

← Zurück zur Übersicht