Backup #220
| ID | 220 |
| Dateipfad | /var/www/dev.campus.systemische-tools.de/src/Controller/SemanticExplorerController.php |
| Version | 5 |
| Typ |
modified |
| Größe | 24.6 KB |
| Hash | 57964bbdf18f7a8d0a5b270e3f9916958057da64061faaa8f3236c47c613fd29 |
| Datum | 2025-12-22 01:44:30 |
| Geändert von | claude-code-hook |
| Grund | Claude Code Pre-Hook Backup vor Edit-Operation |
| Datei existiert |
Ja
|
Dateiinhalt
<?php
namespace Controller;
use Framework\Controller;
use Infrastructure\SemanticExplorerRepository;
/**
* SemanticExplorerController - Nutzdaten Explorer
*
* Zeigt Dokumente und Chunks aus Nextcloud (documents, chunks Tabellen).
* Für Endnutzer - Coaching-Materialien, PDFs, später Mails.
*/
class SemanticExplorerController extends Controller
{
private SemanticExplorerRepository $repository;
public function __construct()
{
$this->repository = new SemanticExplorerRepository();
}
/**
* GET /semantic-explorer
* Dashboard mit Statistiken
*/
public function index(): void
{
$docStats = $this->repository->getDocumentStats();
$chunkStats = $this->repository->getChunkStats();
$documents = $this->repository->getDocuments();
$recentChunks = $this->repository->getRecentChunks(5);
$this->view('semantic-explorer.index', [
'title' => 'Semantic Explorer',
'docStats' => $docStats,
'chunkStats' => $chunkStats,
'documents' => $documents,
'recentChunks' => $recentChunks,
]);
}
/**
* GET /semantic-explorer/dokumente
* Liste aller Dokumente
*/
public function dokumente(): void
{
$status = $_GET['status'] ?? '';
$search = $_GET['search'] ?? '';
$documents = $this->repository->getDocumentsFiltered($status, $search);
$this->view('semantic-explorer.dokumente.index', [
'title' => 'Dokumente',
'documents' => $documents,
'currentStatus' => $status,
'currentSearch' => $search,
]);
}
/**
* GET /semantic-explorer/dokumente/{id}
* Dokument-Details mit Chunks
*/
public function dokumentShow(int $id): void
{
$document = $this->repository->getDocument($id);
if ($document === null) {
$this->notFound('Dokument nicht gefunden');
}
$chunks = $this->repository->getChunksForDocument($id);
// Heading-Paths dekodieren
foreach ($chunks as &$chunk) {
$chunk['heading_path_decoded'] = json_decode($chunk['heading_path'] ?? '[]', true) ?: [];
$chunk['metadata_decoded'] = json_decode($chunk['metadata'] ?? '{}', true) ?: [];
}
$this->view('semantic-explorer.dokumente.show', [
'title' => $document['filename'],
'document' => $document,
'chunks' => $chunks,
]);
}
/**
* GET /semantic-explorer/chunks
* Liste aller Chunks
*/
public function chunks(): void
{
$search = $_GET['search'] ?? '';
$embedded = $_GET['embedded'] ?? '';
$page = max(1, (int) ($_GET['page'] ?? 1));
$limit = 50;
$offset = ($page - 1) * $limit;
$totalCount = $this->repository->getChunksCount($search, $embedded);
$chunks = $this->repository->getChunksFiltered($search, $embedded, $limit, $offset);
$this->view('semantic-explorer.chunks.index', [
'title' => 'Chunks',
'chunks' => $chunks,
'currentSearch' => $search,
'currentEmbedded' => $embedded,
'currentPage' => $page,
'totalCount' => $totalCount,
'totalPages' => ceil($totalCount / $limit),
]);
}
/**
* GET /semantic-explorer/chunks/{id}
* Chunk-Details
*/
public function chunkShow(int $id): void
{
$chunk = $this->repository->getChunk($id);
if ($chunk === null) {
$this->notFound('Chunk nicht gefunden');
}
// JSON-Felder dekodieren
$chunk['heading_path_decoded'] = json_decode($chunk['heading_path'] ?? '[]', true) ?: [];
$chunk['metadata_decoded'] = json_decode($chunk['metadata'] ?? '{}', true) ?: [];
// Nachbar-Chunks
$prevChunk = $this->repository->getChunkByDocumentAndIndex(
$chunk['document_id'],
$chunk['chunk_index'] - 1
);
$nextChunk = $this->repository->getChunkByDocumentAndIndex(
$chunk['document_id'],
$chunk['chunk_index'] + 1
);
$this->view('semantic-explorer.chunks.show', [
'title' => 'Chunk #' . $chunk['id'],
'chunk' => $chunk,
'prevChunk' => $prevChunk,
'nextChunk' => $nextChunk,
]);
}
/**
* GET /semantic-explorer/suche
* Semantische Suche in Nutzdaten
*/
public function suche(): void
{
$query = $_GET['q'] ?? '';
$limit = min(20, max(1, (int) ($_GET['limit'] ?? 10)));
$results = [];
if ($query !== '') {
// Vektor-Suche via Qdrant
$results = $this->vectorSearch($query, $limit);
}
$this->view('semantic-explorer.suche', [
'title' => 'Semantische Suche',
'query' => $query,
'results' => $results,
'limit' => $limit,
]);
}
/**
* Vektor-Suche in documents Collection
*/
private function vectorSearch(string $query, int $limit): array
{
// Embedding generieren
$embedding = $this->getEmbedding($query);
if (empty($embedding)) {
return [];
}
// Qdrant suchen
$response = $this->qdrantSearch($embedding, $limit);
if (empty($response)) {
return [];
}
// Chunk-Details aus DB laden
$results = [];
foreach ($response as $point) {
$chunkId = $point['payload']['chunk_id'] ?? null;
if ($chunkId === null) {
continue;
}
$chunk = $this->repository->getChunkById($chunkId);
if ($chunk !== null) {
$chunk['score'] = $point['score'];
$chunk['heading_path_decoded'] = json_decode($chunk['heading_path'] ?? '[]', true) ?: [];
$results[] = $chunk;
}
}
return $results;
}
/**
* Embedding via Ollama
*/
private function getEmbedding(string $text): array
{
$ch = curl_init('http://localhost:11434/api/embeddings');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'model' => 'mxbai-embed-large',
'prompt' => $text,
]),
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
return $data['embedding'] ?? [];
}
/**
* Qdrant-Suche
*/
private function qdrantSearch(array $embedding, int $limit): array
{
$ch = curl_init('http://localhost:6333/collections/documents/points/search');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'vector' => $embedding,
'limit' => $limit,
'with_payload' => true,
]),
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
return $data['result'] ?? [];
}
/**
* GET /semantic-explorer/entitaeten
* Liste aller Entitaeten
*/
public function entitaeten(): void
{
$type = $_GET['type'] ?? '';
$search = $_GET['search'] ?? '';
$entities = $this->repository->getEntitiesFiltered($type, $search);
$stats = $this->repository->getEntityStats();
$this->view('semantic-explorer.entitaeten.index', [
'title' => 'Entitaeten',
'entities' => $entities,
'stats' => $stats,
'currentType' => $type,
'currentSearch' => $search,
]);
}
/**
* GET /semantic-explorer/entitaeten/{id}
* Entitaet-Details
*/
public function entitaetShow(int $id): void
{
$entity = $this->repository->getEntity($id);
if ($entity === null) {
http_response_code(404);
echo '404 - Entitaet nicht gefunden';
return;
}
$synonyms = $this->repository->getEntitySynonyms($id);
$outgoingRelations = $this->repository->getOutgoingRelations($id);
$incomingRelations = $this->repository->getIncomingRelations($id);
$chunks = $this->repository->getChunksForEntity($id);
$classifications = $this->repository->getEntityClassifications($id);
$this->view('semantic-explorer.entitaeten.show', [
'title' => $entity['name'],
'entity' => $entity,
'synonyms' => $synonyms,
'outgoingRelations' => $outgoingRelations,
'incomingRelations' => $incomingRelations,
'chunks' => $chunks,
'classifications' => $classifications,
]);
}
/**
* GET /semantic-explorer/relationen
* Beziehungen zwischen Entitaeten
*/
public function relationen(): void
{
$type = $_GET['type'] ?? '';
$relations = $this->repository->getRelationsFiltered($type);
$relationTypes = $this->repository->getRelationTypes();
$stats = $this->repository->getRelationStats();
$this->view('semantic-explorer.relationen', [
'title' => 'Relationen',
'relations' => $relations,
'relationTypes' => $relationTypes,
'stats' => $stats,
'currentType' => $type,
]);
}
/**
* GET /semantic-explorer/taxonomie
* Hierarchische Kategorisierung
*/
public function taxonomie(): void
{
$terms = $this->repository->getTaxonomyTerms();
$hierarchy = $this->buildTaxonomyTree($terms);
$stats = $this->repository->getTaxonomyStats();
$this->view('semantic-explorer.taxonomie', [
'title' => 'Taxonomie',
'terms' => $terms,
'hierarchy' => $hierarchy,
'stats' => $stats,
]);
}
/**
* Baut Baum aus flacher Liste
*/
private function buildTaxonomyTree(array $items, ?int $parentId = null): array
{
$tree = [];
foreach ($items as $item) {
if ($item['parent_id'] == $parentId) {
$item['children'] = $this->buildTaxonomyTree($items, $item['id']);
$tree[] = $item;
}
}
return $tree;
}
/**
* GET /semantic-explorer/ontologie
* Konzept-Klassen
*/
public function ontologie(): void
{
$classes = $this->repository->getOntologyClasses();
// Properties dekodieren
foreach ($classes as &$class) {
$class['properties_decoded'] = json_decode($class['properties'] ?? '{}', true) ?: [];
}
$stats = $this->repository->getOntologyStats();
$this->view('semantic-explorer.ontologie', [
'title' => 'Ontologie',
'classes' => $classes,
'stats' => $stats,
]);
}
/**
* GET /semantic-explorer/semantik
* Semantische Analyse pro Chunk
*/
public function semantik(): void
{
$sentiment = $_GET['sentiment'] ?? '';
$page = max(1, (int) ($_GET['page'] ?? 1));
$limit = 50;
$offset = ($page - 1) * $limit;
$totalCount = $this->repository->getSemanticsCount($sentiment);
$semantics = $this->repository->getSemanticsFiltered($sentiment, $limit, $offset);
// JSON dekodieren
foreach ($semantics as &$s) {
$s['keywords_decoded'] = json_decode($s['keywords'] ?? '[]', true) ?: [];
$s['topics_decoded'] = json_decode($s['topics'] ?? '[]', true) ?: [];
}
$stats = $this->repository->getSemanticStats();
$this->view('semantic-explorer.semantik', [
'title' => 'Semantik',
'semantics' => $semantics,
'stats' => $stats,
'currentSentiment' => $sentiment,
'currentPage' => $page,
'totalCount' => $totalCount,
'totalPages' => ceil($totalCount / $limit),
]);
}
// =========================================================================
// ENTITIES - CRUD
// =========================================================================
/**
* GET /semantic-explorer/entitaeten/new
*/
public function entitaetNew(): void
{
$this->view('semantic-explorer.entitaeten.new', [
'title' => 'Neue Entitaet',
'types' => $this->repository->getEntityTypes(),
]);
}
/**
* POST /semantic-explorer/entitaeten
*/
public function entitaetStore(): void
{
$input = json_decode(file_get_contents('php://input'), true);
$name = trim($input['name'] ?? '');
$type = trim($input['type'] ?? '');
$description = trim($input['description'] ?? '') ?: null;
if ($name === '' || $type === '') {
$this->json(['success' => false, 'error' => 'Name und Typ sind erforderlich'], 400);
return;
}
try {
$id = $this->repository->createEntity($name, $type, $description);
$this->json(['success' => true, 'id' => $id]);
} catch (\Exception $e) {
$this->json(['success' => false, 'error' => $e->getMessage()], 500);
}
}
/**
* GET /semantic-explorer/entitaeten/{id}/edit
*/
public function entitaetEdit(int $id): void
{
$entity = $this->repository->getEntity($id);
if ($entity === null) {
http_response_code(404);
echo '404 - Entitaet nicht gefunden';
return;
}
$this->view('semantic-explorer.entitaeten.edit', [
'title' => 'Entitaet bearbeiten',
'entity' => $entity,
'types' => $this->repository->getEntityTypes(),
]);
}
/**
* POST /semantic-explorer/entitaeten/{id}
*/
public function entitaetUpdate(int $id): void
{
$input = json_decode(file_get_contents('php://input'), true);
$name = trim($input['name'] ?? '');
$type = trim($input['type'] ?? '');
$description = trim($input['description'] ?? '') ?: null;
if ($name === '' || $type === '') {
$this->json(['success' => false, 'error' => 'Name und Typ sind erforderlich'], 400);
return;
}
try {
... (331 weitere Zeilen)
Vollständig herunterladen
Aktionen
Andere Versionen dieser Datei
| ID |
Version |
Typ |
Größe |
Datum |
| 1991 |
56 |
modified |
9.3 KB |
2025-12-28 03:05 |
| 1982 |
55 |
modified |
9.1 KB |
2025-12-28 02:54 |
| 1699 |
54 |
modified |
8.8 KB |
2025-12-27 12:18 |
| 1107 |
53 |
modified |
8.8 KB |
2025-12-25 09:22 |
| 1106 |
52 |
modified |
8.8 KB |
2025-12-25 09:22 |
| 1105 |
51 |
modified |
8.8 KB |
2025-12-25 09:22 |
| 1104 |
50 |
modified |
8.8 KB |
2025-12-25 09:22 |
| 1099 |
49 |
modified |
8.8 KB |
2025-12-25 09:20 |
| 1098 |
48 |
modified |
8.8 KB |
2025-12-25 09:20 |
| 1097 |
47 |
modified |
8.6 KB |
2025-12-25 09:20 |
| 1082 |
46 |
modified |
8.6 KB |
2025-12-25 02:29 |
| 1081 |
45 |
modified |
8.4 KB |
2025-12-25 02:29 |
| 1071 |
44 |
modified |
8.3 KB |
2025-12-25 02:26 |
| 1070 |
43 |
modified |
8.3 KB |
2025-12-25 02:26 |
| 1069 |
42 |
modified |
8.3 KB |
2025-12-25 02:26 |
| 1068 |
41 |
modified |
8.3 KB |
2025-12-25 02:26 |
| 1067 |
40 |
modified |
8.3 KB |
2025-12-25 02:26 |
| 1066 |
39 |
modified |
8.3 KB |
2025-12-25 02:26 |
| 1065 |
38 |
modified |
8.3 KB |
2025-12-25 02:25 |
| 1064 |
37 |
modified |
8.3 KB |
2025-12-25 02:25 |
| 1063 |
36 |
modified |
7.8 KB |
2025-12-25 02:25 |
| 912 |
35 |
modified |
7.7 KB |
2025-12-23 16:41 |
| 910 |
34 |
modified |
7.1 KB |
2025-12-23 16:40 |
| 698 |
33 |
modified |
7.2 KB |
2025-12-23 07:53 |
| 676 |
32 |
modified |
7.2 KB |
2025-12-23 07:00 |
| 641 |
31 |
modified |
7.2 KB |
2025-12-23 04:47 |
| 612 |
30 |
modified |
7.3 KB |
2025-12-23 04:42 |
| 586 |
29 |
modified |
7.2 KB |
2025-12-23 04:24 |
| 339 |
28 |
modified |
7.1 KB |
2025-12-22 08:11 |
| 338 |
27 |
modified |
7.0 KB |
2025-12-22 08:11 |
| 337 |
26 |
modified |
7.0 KB |
2025-12-22 08:11 |
| 313 |
25 |
modified |
7.0 KB |
2025-12-22 08:05 |
| 312 |
24 |
modified |
7.0 KB |
2025-12-22 08:05 |
| 311 |
23 |
modified |
7.0 KB |
2025-12-22 08:05 |
| 310 |
22 |
modified |
7.0 KB |
2025-12-22 08:05 |
| 289 |
21 |
modified |
7.1 KB |
2025-12-22 08:00 |
| 288 |
20 |
modified |
7.1 KB |
2025-12-22 08:00 |
| 287 |
19 |
modified |
7.1 KB |
2025-12-22 08:00 |
| 286 |
18 |
modified |
7.1 KB |
2025-12-22 08:00 |
| 265 |
17 |
modified |
8.5 KB |
2025-12-22 02:07 |
| 264 |
16 |
modified |
8.4 KB |
2025-12-22 02:07 |
| 263 |
15 |
modified |
14.7 KB |
2025-12-22 02:06 |
| 262 |
14 |
modified |
16.2 KB |
2025-12-22 02:05 |
| 261 |
13 |
modified |
19.2 KB |
2025-12-22 02:04 |
| 260 |
12 |
modified |
19.8 KB |
2025-12-22 02:04 |
| 259 |
11 |
modified |
22.8 KB |
2025-12-22 02:02 |
| 258 |
10 |
modified |
24.4 KB |
2025-12-22 02:02 |
| 224 |
9 |
modified |
24.4 KB |
2025-12-22 01:44 |
| 223 |
8 |
modified |
24.5 KB |
2025-12-22 01:44 |
| 222 |
7 |
modified |
24.5 KB |
2025-12-22 01:44 |
| 221 |
6 |
modified |
24.6 KB |
2025-12-22 01:44 |
| 220 |
5 |
modified |
24.6 KB |
2025-12-22 01:44 |
| 219 |
4 |
modified |
24.7 KB |
2025-12-22 01:44 |
| 218 |
3 |
modified |
24.7 KB |
2025-12-22 01:44 |
| 46 |
2 |
modified |
12.2 KB |
2025-12-20 17:44 |
| 10 |
1 |
modified |
23.5 KB |
2025-12-20 16:35 |
← Zurück zur Übersicht