{
"event": "PreToolUse",
"tool_name": "Read",
"tool_input": {
"file_path": "\/var\/www\/dev.campus.systemische-tools.de\/src\/UseCases\/Chat\/ChatPromptLoader.php"
}
}
{
"tool_response": {
"type": "text",
"file": {
"filePath": "\/var\/www\/dev.campus.systemische-tools.de\/src\/UseCases\/Chat\/ChatPromptLoader.php",
"content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace UseCases\\Chat;\n\n\/\/ @responsibility: Lädt Chat-Prompts aus ContentConfig-Repository\n\nuse Domain\\Repository\\ContentConfigRepositoryInterface;\n\nfinal class ChatPromptLoader\n{\n public function __construct(\n private ContentConfigRepositoryInterface $configRepo\n ) {\n }\n\n \/**\n * Load style prompt from author profile.\n *\n * @throws \\RuntimeException When profile cannot be parsed\n *\/\n public function getStylePrompt(int $profileId): ?string\n {\n if ($profileId === 0) {\n return null;\n }\n\n $profile = $this->configRepo->getAuthorProfile($profileId);\n if ($profile === null) {\n return null;\n }\n\n $config = json_decode($profile['content'] ?? '{}', true);\n if ($config === null) {\n return null;\n }\n\n $parts = [];\n\n if (isset($config['stimme']['ton'])) {\n $parts[] = 'Ton: ' . $config['stimme']['ton'];\n }\n if (isset($config['stimme']['perspektive'])) {\n $parts[] = 'Perspektive: ' . $config['stimme']['perspektive'];\n }\n if (isset($config['stil']['fachsprache']) && $config['stil']['fachsprache']) {\n $parts[] = 'Verwende Fachsprache';\n }\n if (isset($config['stil']['beispiele']) && $config['stil']['beispiele'] === 'häufig') {\n $parts[] = 'Nutze häufig Beispiele';\n }\n if (isset($config['stil']['listen']) && $config['stil']['listen'] === 'bevorzugt') {\n $parts[] = 'Bevorzuge Listen und Bullet-Points';\n }\n if (isset($config['tabus']) && is_array($config['tabus'])) {\n $parts[] = 'Vermeide: ' . implode(', ', $config['tabus']);\n }\n\n if ($parts === []) {\n return null;\n }\n\n return 'Schreibstil (' . ($profile['name'] ?? 'Profil') . '): ' . implode('. ', $parts) . '.';\n }\n\n \/**\n * Load system prompt by ID.\n *\n * @throws \\RuntimeException When prompt cannot be parsed\n *\/\n public function getSystemPrompt(int $promptId): ?string\n {\n if ($promptId === 0) {\n return null;\n }\n\n $prompt = $this->configRepo->getSystemPrompt($promptId);\n if ($prompt === null) {\n return null;\n }\n\n $content = json_decode($prompt['content'] ?? '{}', true);\n\n return $content['prompt'] ?? null;\n }\n\n \/**\n * Load structure prompt.\n *\n * @throws \\RuntimeException When structure cannot be parsed\n *\/\n public function getStructurePrompt(int $structureId): ?string\n {\n if ($structureId === 0) {\n return null;\n }\n\n $structure = $this->configRepo->getStructure($structureId);\n if ($structure === null) {\n return null;\n }\n\n $name = $structure['name'] ?? 'Struktur';\n $config = json_decode($structure['content'] ?? '{}', true);\n if ($config === null) {\n return null;\n }\n\n \/\/ Complex structure (Blog-Beitrag, LinkedIn-Beitrag etc.)\n if (isset($config['ausgabe']) || isset($config['aufbau']) || isset($config['gesamtaufbau'])) {\n return $this->buildComplexStructurePrompt($name, $config);\n }\n\n \/\/ Simple structure (legacy format)\n $parts = [\"Formatiere deine Antwort als: {$name}\"];\n\n if (isset($config['sections']) && is_array($config['sections'])) {\n $parts[] = 'Struktur: ' . implode(' → ', $config['sections']);\n }\n if (isset($config['max_chars'])) {\n $parts[] = 'Maximale Länge: ' . $config['max_chars'] . ' Zeichen';\n }\n if (isset($config['min_words'])) {\n $parts[] = 'Mindestens ' . $config['min_words'] . ' Wörter';\n }\n if (isset($config['max_words'])) {\n $parts[] = 'Maximal ' . $config['max_words'] . ' Wörter';\n }\n if (isset($config['format']) && $config['format'] === 'qa') {\n $min = $config['min_questions'] ?? 3;\n $parts[] = \"Formatiere als FAQ mit mindestens {$min} Frage-Antwort-Paaren\";\n }\n if (isset($config['hashtags']) && $config['hashtags']) {\n $parts[] = 'Füge passende Hashtags hinzu';\n }\n if (isset($config['cta']) && $config['cta']) {\n $parts[] = 'Schließe mit einem Call-to-Action ab';\n }\n\n return implode('. ', $parts) . '.';\n }\n\n \/**\n * Build prompt from complex structure config.\n *\n * @param array<string, mixed> $config\n *\/\n private function buildComplexStructurePrompt(string $name, array $config): string\n {\n $prompt = \"## Ausgabeformat: {$name}\\n\\n\";\n $prompt .= \"Befolge diese Struktur-Vorgaben exakt:\\n\\n\";\n $prompt .= $this->configToYaml($config, 0);\n\n return $prompt;\n }\n\n \/**\n * Convert config array to readable YAML-like format.\n *\n * @param array<string, mixed> $config\n *\/\n private function configToYaml(array $config, int $indent): string\n {\n $result = '';\n $prefix = str_repeat(' ', $indent);\n\n foreach ($config as $key => $value) {\n if (is_array($value)) {\n if ($this->isIndexedArray($value)) {\n $result .= \"{$prefix}{$key}:\\n\";\n foreach ($value as $item) {\n if (is_array($item)) {\n $result .= \"{$prefix}- \" . trim($this->configToYaml($item, 0)) . \"\\n\";\n } else {\n $result .= \"{$prefix}- {$item}\\n\";\n }\n }\n } else {\n $result .= \"{$prefix}{$key}:\\n\";\n $result .= $this->configToYaml($value, $indent + 1);\n }\n } else {\n $result .= \"{$prefix}{$key}: {$value}\\n\";\n }\n }\n\n return $result;\n }\n\n \/**\n * Check if array is indexed (list) vs associative.\n *\n * @param array<mixed> $arr\n *\/\n private function isIndexedArray(array $arr): bool\n {\n if ($arr === []) {\n return true;\n }\n\n return array_keys($arr) === range(0, count($arr) - 1);\n }\n\n \/**\n * Get structure name by ID.\n *\/\n public function getStructureName(int $structureId): ?string\n {\n if ($structureId === 0) {\n return null;\n }\n\n $structure = $this->configRepo->getStructure($structureId);\n\n return $structure['name'] ?? null;\n }\n\n \/**\n * Compose effective system prompt from components.\n *\/\n public function composeSystemPrompt(\n int $systemPromptId,\n int $structureId,\n int $authorProfileId,\n bool $hasContext,\n string $defaultRagPrompt,\n string $defaultNoContextPrompt\n ): string {\n $systemPrompt = $this->getSystemPrompt($systemPromptId);\n $structurePrompt = $this->getStructurePrompt($structureId);\n $stylePrompt = $this->getStylePrompt($authorProfileId);\n\n \/\/ Add structure to system prompt\n if ($structurePrompt !== null) {\n $systemPrompt = ($systemPrompt ?? '') . \"\\n\\n\" . $structurePrompt;\n }\n\n \/\/ Use default if no system prompt\n $effectivePrompt = $systemPrompt ?? ($hasContext ? $defaultRagPrompt : $defaultNoContextPrompt);\n\n \/\/ Add style prompt\n if ($stylePrompt !== null && $stylePrompt !== '') {\n $effectivePrompt .= \"\\n\\n\" . $stylePrompt;\n }\n\n return $effectivePrompt;\n }\n}\n",
"numLines": 250,
"startLine": 1,
"totalLines": 250
}
}
}