Backup #577
| ID | 577 |
| Dateipfad | /var/www/dev.campus.systemische-tools.de/src/Controller/ContentPipelineController.php |
| Version | 15 |
| Typ |
modified |
| Größe | 12.1 KB |
| Hash | 59267be65d5b745a26e19cea6738bf617a9893a1b4d45e716cddc9a2a3605347 |
| Datum | 2025-12-23 04:19:18 |
| Geändert von | claude-code-hook |
| Grund | Claude Code Pre-Hook Backup vor Edit-Operation |
| Datei existiert |
Ja
|
Dateiinhalt
<?php
namespace Controller;
use Application\PipelineStepService;
use Framework\Controller;
use Infrastructure\AI\ModelConfig;
use Infrastructure\Config\PipelineStepConfig;
use Infrastructure\Persistence\PipelineRepository;
class ContentPipelineController extends Controller
{
private PipelineRepository $repository;
private PipelineStepService $stepService;
public function __construct()
{
$this->repository = new PipelineRepository();
$this->stepService = new PipelineStepService($this->repository);
}
/**
* GET /content-pipeline
*/
public function index(): void
{
$this->view('content-pipeline.index', [
'title' => 'Content Pipeline',
'pipelines' => $this->repository->findAll(),
'stats' => $this->repository->getStatistics(),
]);
}
/**
* GET /content-pipeline/import
*/
public function import(): void
{
$pipeline = $this->repository->findDefault();
if ($pipeline === null) {
$pipelines = $this->repository->findAll(1);
$pipeline = $pipelines[0] ?? null;
}
$this->view('content-pipeline.import', [
'title' => 'Import Pipeline',
'pipeline' => $pipeline,
'latestRun' => $pipeline !== null
? $this->repository->findLatestRun((int) $pipeline['id'])
: null,
]);
}
/**
* GET /content-pipeline/new
*/
public function pipelineNew(): void
{
$this->view('content-pipeline.form', [
'title' => 'Neue Pipeline',
'pipeline' => null,
'stepTypes' => PipelineStepConfig::getStepTypes(),
]);
}
/**
* GET /content-pipeline/{id}
*/
public function show(string $id): void
{
$pipeline = $this->repository->findById((int) $id);
if ($pipeline === null) {
$this->notFound('Pipeline nicht gefunden');
}
$this->view('content-pipeline.show', [
'title' => 'Pipeline: ' . $pipeline['name'],
'pipeline' => $pipeline,
'runs' => $this->repository->findRuns((int) $id, 10),
'stepTypes' => PipelineStepConfig::getStepTypes(),
'models' => ModelConfig::getAll(),
'defaultModel' => ModelConfig::DEFAULT_MODEL,
'collections' => PipelineStepConfig::getCollections(),
]);
}
/**
* GET /content-pipeline/{id}/edit
*/
public function edit(string $id): void
{
$pipeline = $this->repository->findById((int) $id);
if ($pipeline === null) {
$this->notFound('Pipeline nicht gefunden');
}
$this->view('content-pipeline.form', [
'title' => 'Pipeline bearbeiten: ' . $pipeline['name'],
'pipeline' => $pipeline,
'stepTypes' => PipelineStepConfig::getStepTypes(),
]);
}
/**
* POST /content-pipeline
*/
public function store(): void
{
$this->requireCsrf();
$name = trim($_POST['name'] ?? '');
if ($name === '') {
$_SESSION['error'] = 'Name ist erforderlich.';
$this->redirect('/content-pipeline/new');
}
$pipelineId = $this->repository->create([
'name' => $name,
'description' => trim($_POST['description'] ?? ''),
'source_path' => trim($_POST['source_path'] ?? '/var/www/nextcloud/data/root/files/Documents'),
'extensions' => PipelineStepConfig::parseExtensions($_POST['extensions'] ?? ''),
'is_default' => isset($_POST['is_default']) ? 1 : 0,
]);
$this->stepService->createDefaultSteps($pipelineId);
$_SESSION['success'] = 'Pipeline erfolgreich erstellt.';
$this->redirect('/content-pipeline/' . $pipelineId);
}
/**
* POST /content-pipeline/{id}
*/
public function update(string $id): void
{
$this->requireCsrf();
$pipeline = $this->repository->findById((int) $id);
if ($pipeline === null) {
$this->notFound('Pipeline nicht gefunden');
}
$name = trim($_POST['name'] ?? '');
if ($name === '') {
$_SESSION['error'] = 'Name ist erforderlich.';
$this->redirect('/content-pipeline/' . $id . '/edit');
}
$this->repository->update((int) $id, [
'name' => $name,
'description' => trim($_POST['description'] ?? ''),
'source_path' => trim($_POST['source_path'] ?? ''),
'extensions' => PipelineStepConfig::parseExtensions($_POST['extensions'] ?? ''),
'is_default' => isset($_POST['is_default']) ? 1 : 0,
]);
$_SESSION['success'] = 'Pipeline aktualisiert.';
$this->redirect('/content-pipeline/' . $id);
}
/**
* POST /content-pipeline/{id}/run
*/
public function run(string $id): void
{
$this->requireCsrf();
$pipeline = $this->repository->findById((int) $id);
if ($pipeline === null) {
$this->notFound('Pipeline nicht gefunden');
}
$runId = $this->repository->createRun((int) $id);
// Pipeline im Hintergrund starten
$cmd = sprintf(
'nohup %s %s all --pipeline-id=%d --run-id=%d > %s 2>&1 &',
escapeshellarg('/opt/scripts/pipeline/venv/bin/python'),
escapeshellarg('/opt/scripts/pipeline/pipeline.py'),
(int) $id,
$runId,
escapeshellarg('/tmp/pipeline_run_' . $runId . '.log')
);
exec($cmd);
// Redirect zur Live-Status-Seite
$this->redirect('/content-pipeline/' . $id . '/run/' . $runId . '/status');
}
/**
* GET /content-pipeline/{id}/status (AJAX)
*/
public function status(string $id): void
{
$pipeline = $this->repository->findById((int) $id);
if ($pipeline === null) {
$this->json(['error' => 'Pipeline nicht gefunden'], 404);
return;
}
$this->json([
'pipeline_id' => (int) $id,
'run' => $this->repository->findLatestRun((int) $id),
]);
}
/**
* GET /content-pipeline/{id}/run/{runId}/status
*/
public function runStatus(string $id, string $runId): void
{
$pipeline = $this->repository->findById((int) $id);
if ($pipeline === null) {
$this->notFound('Pipeline nicht gefunden');
}
$run = $this->repository->findRunById((int) $runId);
if ($run === null || (int) $run['pipeline_id'] !== (int) $id) {
$this->notFound('Run nicht gefunden');
}
$this->view('content-pipeline.run-status', [
'title' => 'Pipeline Status: ' . $pipeline['name'],
'pipeline' => $pipeline,
'run' => $run,
]);
}
/**
* GET /content-pipeline/{id}/run/{runId}/poll (AJAX/HTMX)
*/
public function runStatusPoll(string $id, string $runId): void
{
$run = $this->repository->findRunById((int) $runId);
if ($run === null || (int) $run['pipeline_id'] !== (int) $id) {
$this->json(['error' => 'Run nicht gefunden'], 404);
return;
}
// Berechne verstrichene Zeit
$startedAt = strtotime($run['started_at'] ?? 'now');
$elapsed = time() - $startedAt;
// Fortschrittsberechnung
$total = (int) ($run['documents_total'] ?? 0);
$processed = (int) ($run['documents_processed'] ?? 0);
$progress = $total > 0 ? round(($processed / $total) * 100) : 0;
// Geschätzte Restzeit
$estimatedRemaining = null;
if ($processed > 0 && $total > $processed) {
$avgTimePerDoc = $elapsed / $processed;
$remaining = $total - $processed;
$estimatedRemaining = (int) ($avgTimePerDoc * $remaining);
}
// Stall-Erkennung (keine Aktivität seit >60s)
$lastUpdate = strtotime($run['last_update_at'] ?? $run['started_at'] ?? 'now');
$isStalled = (time() - $lastUpdate) > 60 && $run['status'] === 'running';
// Terminal-Status (Polling stoppen wenn fertig)
$isTerminal = in_array($run['status'], ['completed', 'failed', 'cancelled'], true);
$this->json([
'status' => $run['status'],
'current_step' => $run['current_step'],
'current_document' => $run['current_document'],
'documents_total' => $total,
'documents_processed' => $processed,
'documents_failed' => (int) ($run['documents_failed'] ?? 0),
'chunks_created' => (int) ($run['chunks_created'] ?? 0),
'embeddings_created' => (int) ($run['embeddings_created'] ?? 0),
'progress' => $progress,
'elapsed' => $elapsed,
'elapsed_formatted' => gmdate('i:s', $elapsed),
'estimated_remaining' => $estimatedRemaining,
'estimated_formatted' => $estimatedRemaining !== null ? gmdate('i:s', $estimatedRemaining) : null,
'log_tail' => $run['log_tail'] ?? '',
'is_stalled' => $isStalled,
'is_terminal' => $isTerminal,
'completed_at' => $run['completed_at'],
'error_log' => $run['error_log'],
]);
}
/**
* POST /content-pipeline/{id}/run/{runId}/cancel
*/
public function runCancel(string $id, string $runId): void
{
$this->requireCsrf();
$run = $this->repository->findRunById((int) $runId);
if ($run === null || (int) $run['pipeline_id'] !== (int) $id) {
$this->notFound('Run nicht gefunden');
}
// Nur laufende Runs können abgebrochen werden
if ($run['status'] !== 'running') {
$_SESSION['error'] = 'Run kann nicht abgebrochen werden (Status: ' . $run['status'] . ')';
$this->redirect('/content-pipeline/' . $id . '/run/' . $runId . '/status');
}
// Status auf cancelled setzen (Python-Script prüft das)
$this->repository->updateRun((int) $runId, [
'status' => 'cancelled',
'completed_at' => date('Y-m-d H:i:s'),
]);
$_SESSION['success'] = 'Pipeline-Run wurde abgebrochen.';
$this->redirect('/content-pipeline/' . $id . '/run/' . $runId . '/status');
}
/**
* POST /content-pipeline/{id}/steps/{stepId}/toggle
*/
public function toggleStep(string $id, string $stepId): void
{
$this->requireCsrf();
if (!$this->stepService->toggleStep((int) $id, (int) $stepId)) {
$this->notFound('Pipeline oder Schritt nicht gefunden');
}
$this->redirect('/content-pipeline/' . $id);
}
/**
* POST /content-pipeline/{id}/steps/{stepId}/model (AJAX)
*/
public function updateStepModel(string $id, string $stepId): void
{
$result = $this->stepService->updateModel(
(int) $id,
(int) $stepId,
$_POST['model'] ?? ''
);
if (!$result['success']) {
$this->json(['error' => $result['error']], $result['error'] === 'Schritt nicht gefunden' ? 404 : 400);
return;
}
$this->json($result);
}
/**
* POST /content-pipeline/{id}/steps/{stepId}/collection (AJAX)
*/
public function updateStepCollection(string $id, string $stepId): void
{
$result = $this->stepService->updateCollection(
(int) $id,
(int) $stepId,
$_POST['collection'] ?? ''
);
if (!$result['success']) {
$this->json(['error' => $result['error']], $result['error'] === 'Schritt nicht gefunden' ? 404 : 400);
return;
}
$this->json($result);
}
/**
* POST /content-pipeline/{id}/delete
*/
public function delete(string $id): void
{
$this->requireCsrf();
$pipeline = $this->repository->findById((int) $id);
if ($pipeline === null) {
$this->notFound('Pipeline nicht gefunden');
}
$this->repository->delete((int) $id);
$_SESSION['success'] = 'Pipeline geloescht.';
$this->redirect('/content-pipeline');
}
}
Vollständig herunterladen
Aktionen
Andere Versionen dieser Datei
| ID |
Version |
Typ |
Größe |
Datum |
| 1701 |
33 |
modified |
9.9 KB |
2025-12-27 12:20 |
| 1683 |
32 |
modified |
9.9 KB |
2025-12-27 12:02 |
| 1682 |
31 |
modified |
9.9 KB |
2025-12-27 12:02 |
| 1681 |
30 |
modified |
9.9 KB |
2025-12-27 12:02 |
| 1299 |
29 |
modified |
9.8 KB |
2025-12-25 13:28 |
| 1298 |
28 |
modified |
10.2 KB |
2025-12-25 13:28 |
| 1275 |
27 |
modified |
10.2 KB |
2025-12-25 12:52 |
| 680 |
26 |
modified |
10.2 KB |
2025-12-23 07:44 |
| 678 |
25 |
modified |
10.1 KB |
2025-12-23 07:39 |
| 605 |
24 |
modified |
10.4 KB |
2025-12-23 04:39 |
| 603 |
23 |
modified |
11.2 KB |
2025-12-23 04:31 |
| 602 |
22 |
modified |
10.9 KB |
2025-12-23 04:31 |
| 601 |
21 |
modified |
11.3 KB |
2025-12-23 04:30 |
| 600 |
20 |
modified |
13.3 KB |
2025-12-23 04:30 |
| 599 |
19 |
modified |
13.4 KB |
2025-12-23 04:30 |
| 598 |
18 |
modified |
13.9 KB |
2025-12-23 04:30 |
| 597 |
17 |
modified |
13.4 KB |
2025-12-23 04:29 |
| 585 |
16 |
modified |
13.3 KB |
2025-12-23 04:24 |
| 577 |
15 |
modified |
12.1 KB |
2025-12-23 04:19 |
| 573 |
14 |
modified |
8.0 KB |
2025-12-23 04:11 |
| 572 |
13 |
modified |
8.0 KB |
2025-12-23 04:10 |
| 528 |
12 |
modified |
20.4 KB |
2025-12-22 19:03 |
| 477 |
11 |
modified |
18.6 KB |
2025-12-22 15:21 |
| 476 |
10 |
modified |
18.5 KB |
2025-12-22 15:21 |
| 475 |
9 |
modified |
18.4 KB |
2025-12-22 15:20 |
| 472 |
8 |
modified |
18.4 KB |
2025-12-22 15:19 |
| 439 |
7 |
modified |
16.5 KB |
2025-12-22 10:14 |
| 428 |
6 |
modified |
14.9 KB |
2025-12-22 10:02 |
| 427 |
5 |
modified |
14.8 KB |
2025-12-22 10:01 |
| 426 |
4 |
modified |
14.8 KB |
2025-12-22 10:01 |
| 421 |
3 |
modified |
13.8 KB |
2025-12-22 09:55 |
| 419 |
2 |
modified |
13.8 KB |
2025-12-22 09:42 |
| 418 |
1 |
modified |
10.6 KB |
2025-12-22 09:35 |
← Zurück zur Übersicht