From 3282d9489f3a14f0b9299b2c121bef30994c36cd Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Thu, 14 May 2026 18:56:54 +0200 Subject: [PATCH] [Toolkit] Enhance Markdown file generation for LLM support --- src/Command/GenerateLlmsFilesCommand.php | 8 +--- src/Service/Toolkit/ToolkitService.php | 33 +++++++++++++++++ src/Twig/Components/Toolkit/ComponentDoc.php | 30 +-------------- src/Twig/Extension/ToolkitExtension.php | 6 +-- src/Twig/Extension/ToolkitRuntime.php | 26 ++++++++++--- templates/llms/toolkit_component.md.twig | 37 ------------------- .../toolkit/docs/_base_component.md.twig | 13 ++++++- 7 files changed, 71 insertions(+), 82 deletions(-) delete mode 100644 templates/llms/toolkit_component.md.twig diff --git a/src/Command/GenerateLlmsFilesCommand.php b/src/Command/GenerateLlmsFilesCommand.php index 7a431cb1..adcc900d 100644 --- a/src/Command/GenerateLlmsFilesCommand.php +++ b/src/Command/GenerateLlmsFilesCommand.php @@ -216,13 +216,7 @@ private function generateToolkit(SymfonyStyle $io): array $toolkitKits[] = ['kitId' => $kitId, 'kit' => $kit]; foreach ($kit->getRecipes(RecipeType::Component) as $recipe) { - $apiRef = $this->toolkitService->extractRecipeApiReference($recipe); - $md = $this->twig->render('llms/toolkit_component.md.twig', [ - 'kitId' => $kitId, - 'kit' => $kit, - 'recipe' => $recipe, - 'apiRef' => $apiRef, - ]); + $md = $this->toolkitService->renderRecipeMarkdown($kitId, $recipe, isLlm: true); $path = $this->generateMdPath('app_toolkit_component', [ 'kitId' => $kitId->value, 'componentName' => $recipe->name, diff --git a/src/Service/Toolkit/ToolkitService.php b/src/Service/Toolkit/ToolkitService.php index d057e1bd..96f2775e 100644 --- a/src/Service/Toolkit/ToolkitService.php +++ b/src/Service/Toolkit/ToolkitService.php @@ -19,6 +19,7 @@ use Symfony\UX\Toolkit\Kit\Kit; use Symfony\UX\Toolkit\Recipe\Recipe; use Symfony\UX\Toolkit\Registry\RegistryFactory; +use Twig\Environment; use function Symfony\Component\String\s; @@ -37,6 +38,7 @@ class ToolkitService public function __construct( #[Autowire(service: 'ux_toolkit.registry.registry_factory')] private RegistryFactory $registryFactory, + private Environment $twig, ) { } @@ -67,6 +69,37 @@ public function resolveRecipePool(Kit $kit, Recipe $component): Pool return (new PoolResolver())->resolveForRecipe($kit, $component); } + public function renderRecipeMarkdown(ToolkitKitId $kitId, Recipe $recipe, bool $isLlm = false): string + { + $kit = $this->getKit($kitId); + $pool = $this->resolveRecipePool($kit, $recipe); + $apiReference = $this->extractRecipeApiReference($recipe); + + $files = []; + foreach ($pool->getFiles() as $recipeFullPath => $recipeFiles) { + foreach ($recipeFiles as $recipeFile) { + $recipeFileSourcePath = Path::join($recipeFullPath, $recipeFile->sourceRelativePathName); + $files[] = [ + 'path_name' => $recipeFile->sourceRelativePathName, + 'content' => file_get_contents($recipeFileSourcePath), + 'language' => pathinfo($recipeFileSourcePath, \PATHINFO_EXTENSION), + ]; + } + } + + return $this->twig->render(\sprintf('toolkit/docs/%s/%s.md.twig', $kitId->value, $recipe->name), [ + 'kit_id' => $kitId, + 'kit' => $kit, + 'component' => $recipe, + 'files' => $files, + 'php_package_dependencies' => $pool->getPhpPackageDependencies(), + 'npm_package_dependencies' => $pool->getNpmPackageDependencies(), + 'importmap_package_dependencies' => $pool->getImportmapPackageDependencies(), + 'api_reference' => $apiReference, + 'is_llm' => $isLlm, + ]); + } + /** * @return array, blocks: list}> */ diff --git a/src/Twig/Components/Toolkit/ComponentDoc.php b/src/Twig/Components/Toolkit/ComponentDoc.php index 73a20bc2..f0fc1b65 100644 --- a/src/Twig/Components/Toolkit/ComponentDoc.php +++ b/src/Twig/Components/Toolkit/ComponentDoc.php @@ -13,7 +13,6 @@ use App\Enum\ToolkitKitId; use App\Service\Toolkit\ToolkitService; -use Symfony\Component\Filesystem\Path; use Symfony\UX\Toolkit\Recipe\Recipe; use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; @@ -25,38 +24,11 @@ final class ComponentDoc public function __construct( private readonly ToolkitService $toolkitService, - private readonly \Twig\Environment $twig, ) { } public function getMarkdownContent(): string { - $kit = $this->toolkitService->getKit($this->kitId); - $pool = $this->toolkitService->resolveRecipePool($kit, $this->component); - $apiReference = $this->toolkitService->extractRecipeApiReference($this->component); - - $files = []; - foreach ($pool->getFiles() as $recipeFullPath => $recipeFiles) { - foreach ($recipeFiles as $recipeFile) { - $recipeFileSourcePath = Path::join($recipeFullPath, $recipeFile->sourceRelativePathName); - $files[] = [ - 'path_name' => $recipeFile->sourceRelativePathName, - 'content' => file_get_contents($recipeFileSourcePath), - 'language' => pathinfo($recipeFileSourcePath, \PATHINFO_EXTENSION), - ]; - } - } - - $templateName = \sprintf('toolkit/docs/%s/%s.md.twig', $this->kitId->value, $this->component->name); - - return $this->twig->render($templateName, [ - 'kit_id' => $this->kitId, - 'component' => $this->component, - 'files' => $files, - 'php_package_dependencies' => $pool->getPhpPackageDependencies(), - 'npm_package_dependencies' => $pool->getNpmPackageDependencies(), - 'importmap_package_dependencies' => $pool->getImportmapPackageDependencies(), - 'api_reference' => $apiReference, - ]); + return $this->toolkitService->renderRecipeMarkdown($this->kitId, $this->component); } } diff --git a/src/Twig/Extension/ToolkitExtension.php b/src/Twig/Extension/ToolkitExtension.php index 52984d04..a84d4776 100644 --- a/src/Twig/Extension/ToolkitExtension.php +++ b/src/Twig/Extension/ToolkitExtension.php @@ -18,9 +18,9 @@ final class ToolkitExtension extends AbstractExtension { public function getFunctions(): iterable { - yield new TwigFunction('toolkit_code_example', [ToolkitRuntime::class, 'codeExample'], ['is_safe' => ['html']]); - yield new TwigFunction('toolkit_code_demo', [ToolkitRuntime::class, 'codeDemo'], ['is_safe' => ['html']]); - yield new TwigFunction('toolkit_code_usage', [ToolkitRuntime::class, 'codeUsage'], ['is_safe' => ['html']]); + yield new TwigFunction('toolkit_code_example', [ToolkitRuntime::class, 'codeExample'], ['is_safe' => ['html'], 'needs_context' => true]); + yield new TwigFunction('toolkit_code_demo', [ToolkitRuntime::class, 'codeDemo'], ['is_safe' => ['html'], 'needs_context' => true]); + yield new TwigFunction('toolkit_code_usage', [ToolkitRuntime::class, 'codeUsage'], ['is_safe' => ['html'], 'needs_context' => true]); yield new TwigFunction('toolkit_color', [ToolkitRuntime::class, 'kitColor']); } } diff --git a/src/Twig/Extension/ToolkitRuntime.php b/src/Twig/Extension/ToolkitRuntime.php index 2af68a62..57198c0b 100644 --- a/src/Twig/Extension/ToolkitRuntime.php +++ b/src/Twig/Extension/ToolkitRuntime.php @@ -23,9 +23,10 @@ public function __construct( } /** + * @param array $context * @param array $options */ - public function codeExample(string $kitId, string $recipeName, string $exampleName, array $options = [], bool $preview = true): string + public function codeExample(array $context, string $kitId, string $recipeName, string $exampleName, array $options = [], bool $preview = true): string { $kitId = ToolkitKitId::from($kitId); $kit = $this->toolkitService->getKit($kitId); @@ -38,6 +39,19 @@ public function codeExample(string $kitId, string $recipeName, string $exampleNa $exampleCode = trim(file_get_contents($exampleFile)); $language = 'twig'; + + if ($context['is_llm'] ?? false) { + return \sprintf( + <<<'MARKDOWN' + ```%2$s + %1$s + ``` + MARKDOWN, + $exampleCode, + $language, + ); + } + $options = json_encode($options + ['kit' => $kitId->value]); if ($preview) { @@ -78,19 +92,21 @@ public function codeExample(string $kitId, string $recipeName, string $exampleNa } /** + * @param array $context * @param array $options */ - public function codeDemo(string $kitId, string $recipeName, array $options = []): string + public function codeDemo(array $context, string $kitId, string $recipeName, array $options = []): string { - return $this->codeExample($kitId, $recipeName, 'Demo', $options + ['height' => '450px'], preview: true); + return $this->codeExample($context, $kitId, $recipeName, 'Demo', $options + ['height' => '450px'], preview: true); } /** + * @param array $context * @param array $options */ - public function codeUsage(string $kitId, string $recipeName, array $options = []): string + public function codeUsage(array $context, string $kitId, string $recipeName, array $options = []): string { - return $this->codeExample($kitId, $recipeName, 'Usage', $options, preview: false); + return $this->codeExample($context, $kitId, $recipeName, 'Usage', $options, preview: false); } public function kitColor(string $kitId): string diff --git a/templates/llms/toolkit_component.md.twig b/templates/llms/toolkit_component.md.twig deleted file mode 100644 index e9479002..00000000 --- a/templates/llms/toolkit_component.md.twig +++ /dev/null @@ -1,37 +0,0 @@ -{% autoescape false %} -# {{ recipe.name }} ({{ kit.manifest.name }} kit) - -{% if recipe.manifest.description -%} -> {{ recipe.manifest.description }} - -{% endif -%} -{% for component_name, ref in apiRef %} -{% if ref.props is not empty %} -## Props for `{{ component_name }}` - -| Name | Type | Description | -|------|------|-------------| -{% for prop in ref.props %} -| `{{ prop.name }}` | `{{ prop.type }}` | {{ prop.description }} | -{% endfor %} - -{% endif %} -{% if ref.blocks is not empty %} -## Blocks for `{{ component_name }}` - -{% for block in ref.blocks %} -- `{{ block.name }}`: {{ block.description }} -{% endfor %} - -{% endif %} -{% endfor %} -{% if recipe.manifest.dependencies is not empty %} -## Dependencies - -{% for dep in recipe.manifest.dependencies %} -- {{ dep }} -{% endfor %} - -{% endif %} -- [View component](https://ux.symfony.com/toolkit/kits/{{ kitId.value }}/components/{{ recipe.name }}) -{% endautoescape %} diff --git a/templates/toolkit/docs/_base_component.md.twig b/templates/toolkit/docs/_base_component.md.twig index c18da445..0eaefea3 100644 --- a/templates/toolkit/docs/_base_component.md.twig +++ b/templates/toolkit/docs/_base_component.md.twig @@ -1,5 +1,5 @@ {# twig-cs-fixer-disable FileName.Error #} -# {{ component.manifest.name }} +# {{ component.manifest.name }}{% if is_llm ?? false %} ({{ kit.manifest.name }} kit){% endif %} {{ component.manifest.description }} @@ -7,6 +7,16 @@ ## Installation +{% if is_llm ?? false %} +{% if component.manifest.versionAdded is not null %} +> [!NOTE] +> Available since UX Toolkit {{ component.manifest.versionAdded }}. +{% endif %} + +```shell +bin/console ux:install {{ component.name }} --kit {{ kit_id.value }} +``` +{% else %} ::: tabs :: tab Automatic @@ -63,6 +73,7 @@ Copy the following file(s) into your Symfony app: Happy coding! ::: +{% endif %} {% if block('usage') is defined %} ## Usage