Backup #595
| ID | 595 |
| Dateipfad | /var/www/dev.campus.systemische-tools.de/src/Controller/Api/DocsController.php |
| Version | 3 |
| Typ |
modified |
| Größe | 11.7 KB |
| Hash | 4a4ba6adc4f362668b32963cc193d0bfe5afdf5602b0bd39834019122779606b |
| Datum | 2025-12-23 04:26:47 |
| Geändert von | claude-code-hook |
| Grund | Claude Code Pre-Hook Backup vor Edit-Operation |
| Datei existiert |
Ja
|
Dateiinhalt
<?php
declare(strict_types=1);
namespace Controller\Api;
use Framework\Controller;
use Infrastructure\Docs\ChunkSyncService;
use Infrastructure\Persistence\DokumentationRepository;
/**
* DocsController - REST API für Dokumentationsverwaltung
*
* Endpoints:
* - GET /api/v1/docs - Liste aller Dokumente
* - GET /api/v1/docs/{id} - Einzelnes Dokument
* - GET /api/v1/docs/path/{path} - Dokument nach Pfad
* - POST /api/v1/docs - Dokument erstellen
* - PUT /api/v1/docs/{id} - Dokument aktualisieren
* - DELETE /api/v1/docs/{id} - Dokument löschen
* - GET /api/v1/docs/search - Semantic Search
* - GET /api/v1/docs/hierarchy - Dokumentbaum
* - POST /api/v1/docs/chat - Chat mit Dokumentation
*/
class DocsController extends Controller
{
private DokumentationRepository $repository;
private ChunkSyncService $syncService;
public function __construct(
?DokumentationRepository $repository = null,
?ChunkSyncService $syncService = null
) {
$this->repository = $repository ?? new DokumentationRepository();
$this->syncService = $syncService ?? new ChunkSyncService();
}
/**
* GET /api/v1/docs
* Liste aller Dokumente mit optionalen Filtern.
*/
public function index(): void
{
try {
$status = $this->getString('status');
$parentId = $this->getInt('parent_id');
$search = $this->getString('search');
$limit = $this->getLimit(100, 50);
$offset = $this->getInt('offset');
$docs = $this->repository->findAll(
status: $status ?: null,
parentId: $parentId > 0 ? $parentId : null,
search: $search ?: null,
limit: $limit,
offset: $offset
);
$total = $this->repository->count(
status: $status ?: null,
parentId: $parentId > 0 ? $parentId : null,
search: $search ?: null
);
$this->json([
'success' => true,
'data' => $docs,
'meta' => [
'total' => $total,
'limit' => $limit,
'offset' => $offset,
],
]);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* GET /api/v1/docs/{id}
* Einzelnes Dokument mit optionalen Kindern und Breadcrumb.
*/
public function show(string $id): void
{
try {
$includeChildren = $this->getString('include_children') === '1';
$includeBreadcrumb = $this->getString('include_breadcrumb') === '1';
$doc = $this->repository->findById((int) $id);
if ($doc === null) {
$this->json(['success' => false, 'error' => 'Dokument nicht gefunden'], 404);
return;
}
$response = [
'success' => true,
'data' => $doc,
];
if ($includeChildren) {
$response['children'] = $this->repository->findChildren((int) $id);
}
if ($includeBreadcrumb) {
$response['breadcrumb'] = $this->repository->getBreadcrumb((int) $id);
}
$this->json($response);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* GET /api/v1/docs/path/{path}
* Dokument nach Pfad.
*/
public function showByPath(string $path): void
{
try {
$fullPath = '/' . ltrim($path, '/');
$doc = $this->repository->findByPath($fullPath);
if ($doc === null) {
$this->json(['success' => false, 'error' => 'Dokument nicht gefunden'], 404);
return;
}
$this->json([
'success' => true,
'data' => $doc,
]);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* POST /api/v1/docs
* Neues Dokument erstellen.
*/
public function store(): void
{
try {
$input = $this->getJsonInput();
$required = ['title', 'slug'];
foreach ($required as $field) {
if (empty($input[$field])) {
$this->json(['success' => false, 'error' => "Feld '$field' ist erforderlich"], 400);
return;
}
}
$docId = $this->repository->create([
'title' => trim($input['title']),
'slug' => trim($input['slug']),
'content' => $input['content'] ?? '',
'description' => $input['description'] ?? null,
'parent_id' => $input['parent_id'] ?? null,
'status' => $input['status'] ?? 'draft',
'sort_order' => $input['sort_order'] ?? 0,
]);
$doc = $this->repository->findById($docId);
$this->json([
'success' => true,
'data' => $doc,
'message' => 'Dokument erstellt',
], 201);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* PUT /api/v1/docs/{id}
* Dokument aktualisieren.
*/
public function update(string $id): void
{
try {
$doc = $this->repository->findById((int) $id);
if ($doc === null) {
$this->json(['success' => false, 'error' => 'Dokument nicht gefunden'], 404);
return;
}
$input = $this->getJsonInput();
$this->repository->update((int) $id, [
'title' => $input['title'] ?? $doc['title'],
'content' => $input['content'] ?? $doc['content'],
'description' => $input['description'] ?? $doc['description'],
'status' => $input['status'] ?? $doc['status'],
]);
$updated = $this->repository->findById((int) $id);
$this->json([
'success' => true,
'data' => $updated,
'message' => 'Dokument aktualisiert',
]);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* DELETE /api/v1/docs/{id}
* Dokument löschen.
*/
public function destroy(string $id): void
{
try {
$doc = $this->repository->findById((int) $id);
if ($doc === null) {
$this->json(['success' => false, 'error' => 'Dokument nicht gefunden'], 404);
return;
}
// Check for children
$children = $this->repository->findChildren((int) $id);
if (!empty($children)) {
$this->json([
'success' => false,
'error' => 'Dokument hat Unterdokumente. Lösche diese zuerst.',
], 400);
return;
}
$this->repository->delete((int) $id);
$this->json([
'success' => true,
'message' => 'Dokument gelöscht',
]);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* GET /api/v1/docs/search
* Semantic search über Dokumentation.
*/
public function search(): void
{
try {
$query = $this->getString('q');
$limit = $this->getInt('limit', 5);
$category = $this->getString('category');
if ($query === '') {
$this->json(['success' => false, 'error' => 'Keine Suchanfrage'], 400);
return;
}
if ($category !== '') {
$results = $this->syncService->searchByTaxonomy($query, $category, $limit);
} else {
$results = $this->syncService->search($query, $limit);
}
$this->json([
'success' => true,
'data' => $results,
'meta' => [
'query' => $query,
'limit' => $limit,
'count' => count($results),
],
]);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* GET /api/v1/docs/hierarchy
* Vollständiger Dokumentationsbaum.
*/
public function hierarchy(): void
{
try {
$tree = $this->repository->getTree();
$this->json([
'success' => true,
'data' => $tree,
]);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* POST /api/v1/docs/chat
* Chat mit Dokumentation (RAG).
*/
public function chat(): void
{
try {
$input = $this->getJsonInput();
$question = trim($input['question'] ?? '');
$model = $input['model'] ?? 'mistral';
$limit = (int) ($input['limit'] ?? 5);
if ($question === '') {
$this->json(['success' => false, 'error' => 'Keine Frage angegeben'], 400);
return;
}
// Get relevant chunks via semantic search
$chunks = $this->syncService->search($question, $limit);
if (empty($chunks)) {
$this->json([
'success' => true,
'data' => [
'answer' => 'Leider konnte ich keine relevanten Informationen in der Dokumentation finden.',
'sources' => [],
],
]);
return;
}
// Build context from chunks
$context = $this->buildContext($chunks);
// Generate answer using Ollama
$ollama = new \Infrastructure\AI\OllamaService();
$prompt = $this->buildChatPrompt($question, $context);
$answer = $ollama->generate($prompt, $model, 0.3);
$sources = array_map(static fn ($chunk) => [
'id' => $chunk['doc_id'],
'path' => $chunk['path'],
'title' => $chunk['title'],
'score' => round($chunk['score'], 3),
], $chunks);
$this->json([
'success' => true,
'data' => [
'answer' => $answer,
'sources' => $sources,
],
]);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* Build context from chunks.
*/
private function buildContext(array $chunks): string
{
$parts = [];
foreach ($chunks as $chunk) {
$part = "## {$chunk['title']}\n";
$part .= "Pfad: {$chunk['path']}\n";
$part .= $chunk['content'];
$parts[] = $part;
}
return implode("\n\n---\n\n", $parts);
}
/**
* Build chat prompt.
*/
private function buildChatPrompt(string $question, string $context): string
{
return <<<PROMPT
Du bist ein Dokumentations-Assistent. Beantworte die Frage basierend auf dem bereitgestellten Kontext.
KONTEXT:
{$context}
FRAGE:
{$question}
ANLEITUNG:
- Antworte auf Deutsch
- Sei präzise und hilfreich
- Wenn der Kontext die Frage nicht beantwortet, sage das ehrlich
- Verweise auf die relevanten Abschnitte der Dokumentation
PROMPT;
}
}
Vollständig herunterladen
Aktionen
Andere Versionen dieser Datei
| ID |
Version |
Typ |
Größe |
Datum |
| 2045 |
20 |
modified |
10.0 KB |
2025-12-28 23:26 |
| 2044 |
19 |
modified |
9.9 KB |
2025-12-28 23:26 |
| 2043 |
18 |
modified |
9.8 KB |
2025-12-28 23:25 |
| 2042 |
17 |
modified |
9.7 KB |
2025-12-28 23:25 |
| 2040 |
16 |
modified |
9.6 KB |
2025-12-28 23:25 |
| 2038 |
15 |
modified |
9.5 KB |
2025-12-28 23:25 |
| 2037 |
14 |
modified |
9.4 KB |
2025-12-28 23:25 |
| 2036 |
13 |
modified |
9.3 KB |
2025-12-28 23:25 |
| 2034 |
12 |
modified |
9.2 KB |
2025-12-28 23:25 |
| 1888 |
11 |
modified |
9.2 KB |
2025-12-28 01:01 |
| 1884 |
10 |
modified |
9.2 KB |
2025-12-28 01:00 |
| 1283 |
9 |
modified |
9.2 KB |
2025-12-25 13:01 |
| 1282 |
8 |
modified |
9.2 KB |
2025-12-25 13:01 |
| 1273 |
7 |
modified |
9.1 KB |
2025-12-25 12:52 |
| 700 |
6 |
modified |
9.7 KB |
2025-12-23 07:53 |
| 621 |
5 |
modified |
9.8 KB |
2025-12-23 04:43 |
| 596 |
4 |
modified |
11.9 KB |
2025-12-23 04:27 |
| 595 |
3 |
modified |
11.7 KB |
2025-12-23 04:26 |
| 594 |
2 |
modified |
11.5 KB |
2025-12-23 04:25 |
| 570 |
1 |
modified |
11.6 KB |
2025-12-23 03:41 |
← Zurück zur Übersicht