wuic-framework-lib 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (16) hide show
  1. package/fesm2022/{wuic-framework-lib-chart-list.component-C02jeFGy.mjs → wuic-framework-lib-chart-list.component-DAP-TPJR.mjs} +2 -2
  2. package/fesm2022/{wuic-framework-lib-code-editor.component-BZNLr0Jj.mjs → wuic-framework-lib-code-editor.component-CLMDvOWs.mjs} +2 -2
  3. package/fesm2022/{wuic-framework-lib-designer.component-N_CfbGnr.mjs → wuic-framework-lib-designer.component-DA7abiLW.mjs} +79 -3
  4. package/fesm2022/{wuic-framework-lib-first-run-wizard.component-lW7-xMm3.mjs → wuic-framework-lib-first-run-wizard.component-BLrUrfZ9.mjs} +78 -38
  5. package/fesm2022/{wuic-framework-lib-framework-docs.component-CGQbmSkv.mjs → wuic-framework-lib-framework-docs.component-DphKhTef.mjs} +38 -18
  6. package/fesm2022/{wuic-framework-lib-pivot-builder.component-0gJz9RWr.mjs → wuic-framework-lib-pivot-builder.component-BqUUwrJM.mjs} +2 -2
  7. package/fesm2022/{wuic-framework-lib-prompt-dialog-fallback-host.component-C9LvmFsL.mjs → wuic-framework-lib-prompt-dialog-fallback-host.component-DZrhwMH5.mjs} +2 -2
  8. package/fesm2022/{wuic-framework-lib-report-designer.component-DcpdzFjh.mjs → wuic-framework-lib-report-designer.component-D_qbefud.mjs} +2 -2
  9. package/fesm2022/{wuic-framework-lib-report-viewer.component-BHpbGpp9.mjs → wuic-framework-lib-report-viewer.component-CIN2fY0P.mjs} +2 -2
  10. package/fesm2022/{wuic-framework-lib-scheduler-list.component-DHoukjf-.mjs → wuic-framework-lib-scheduler-list.component-CdcI1Uor.mjs} +2 -2
  11. package/fesm2022/{wuic-framework-lib-spreadsheet-list-sf.component-VsaXepUC.mjs → wuic-framework-lib-spreadsheet-list-sf.component-j1HTRI_P.mjs} +2 -2
  12. package/fesm2022/{wuic-framework-lib-workflow-designer.component-epj3oKq3.mjs → wuic-framework-lib-workflow-designer.component-SE_7ZogW.mjs} +2 -2
  13. package/fesm2022/{wuic-framework-lib-wuic-framework-lib-Deudd4MY.mjs → wuic-framework-lib-wuic-framework-lib-BRujEwpR.mjs} +87 -33
  14. package/fesm2022/wuic-framework-lib.mjs +1 -1
  15. package/package.json +1 -1
  16. package/types/wuic-framework-lib.d.ts +23 -2
@@ -16,12 +16,12 @@ import * as i1$3 from 'primeng/image';
16
16
  import { ImageModule } from 'primeng/image';
17
17
  import * as i8 from '@ngx-translate/core';
18
18
  import { TranslateService, TranslateModule } from '@ngx-translate/core';
19
- import { q as getThemeOptions, T as TranslationManagerService, U as UserInfoService } from './wuic-framework-lib-wuic-framework-lib-Deudd4MY.mjs';
19
+ import { r as getThemeOptions, T as TranslationManagerService, U as UserInfoService } from './wuic-framework-lib-wuic-framework-lib-BRujEwpR.mjs';
20
20
  import * as i1 from '@angular/router';
21
21
 
22
22
  const frameworkDocsContent = {
23
23
  "version": "1.0.0",
24
- "generatedAt": "2026-06-07T22:48:39.198Z",
24
+ "generatedAt": "2026-06-13T18:44:57.721Z",
25
25
  "groups": [
26
26
  {
27
27
  "id": "primi-passi",
@@ -6774,14 +6774,18 @@ const frameworkDocsContent = {
6774
6774
  },
6775
6775
  {
6776
6776
  "id": "sec_3",
6777
- "title": "Pattern operativo"
6777
+ "title": "Robustezza del routing — varianti non-banali"
6778
6778
  },
6779
6779
  {
6780
6780
  "id": "sec_4",
6781
- "title": "Test docs-driven"
6781
+ "title": "Pattern operativo"
6782
6782
  },
6783
6783
  {
6784
6784
  "id": "sec_5",
6785
+ "title": "Test docs-driven"
6786
+ },
6787
+ {
6788
+ "id": "sec_6",
6785
6789
  "title": "Riferimenti"
6786
6790
  }
6787
6791
  ],
@@ -6789,7 +6793,7 @@ const frameworkDocsContent = {
6789
6793
  {
6790
6794
  "id": "overview",
6791
6795
  "title": "Overview",
6792
- "html": "<h1>RAG Chatbot — Tool catalog</h1><p>Catalogo dei &quot;tool&quot; che l&#39;LLM Anthropic puo&#39; invocare via <code>tool_use</code> quando l&#39;utente</p><p>fa una richiesta nel chatbot <code>&lt;wuic-rag-chatbot&gt;</code>. Per ogni tool: il <code>kind</code> che</p><p>viene emesso in <code>proposed_action_json</code>, un <strong>prompt utente canonico</strong> che lo triggera,</p><p>i campi minimi della proposta e i metadati WUIC modificati al click di &quot;Applica&quot;.</p><p>Ogni tool ha una corrispondente regola di test end-to-end in</p><p><code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> (single source of</p><p>truth: se la <code>prompt</code> cambia qui ma non li&#39;, il test fallisce). I prompt canonici</p><p>sotto sono ESATTAMENTE quelli del test.</p><h2>Indice</h2><table><thead><tr><th>Kind</th><th>Cosa fa</th><th>Metadata target</th></tr></thead><tbody><tr><td><code>toolbar_action</code></td><td>Aggiunge un button alla toolbar della list-grid</td><td><code>_mtdt__cstom__actions__tabelle</code></td></tr><tr><td><code>row_action</code></td><td>Aggiunge un button-action sulla singola riga</td><td><code>_metadati__colonne</code> (mc_voa_class=6)</td></tr><tr><td><code>table_style</code></td><td>Stile condizionale (classe CSS o inline) sull&#39;intera <code>&lt;tr&gt;</code></td><td><code>_metadati__u_i__stili__tabelle</code></td></tr><tr><td><code>column_style</code></td><td>Stile condizionale sulla singola cella</td><td><code>_metadati__u_i__stili__colonne</code></td></tr><tr><td><code>display_formula</code></td><td>Template Angular per la cella (formato compatto, badge, link)</td><td><code>_metadati__colonne.mc_ui_grid_column_data_template</code></td></tr><tr><td><code>form_title_formula</code></td><td>Titolo dinamico del form di edit</td><td><code>_metadati__tabelle.md_display_formula</code></td></tr><tr><td><code>default_value_callback</code></td><td>Default JS per un campo in INSERT</td><td><code>_metadati__colonne.mc_default_value_callback</code></td></tr><tr><td><code>custom_validation</code></td><td>Validazione blocking pre-save</td><td><code>_metadati__colonne.mc_validation_custom_callback</code></td></tr><tr><td><code>selection_changed</code></td><td>Trigger al change di un lookup</td><td><code>_metadati__colonne.mc_selection_changed_custom_function</code></td></tr><tr><td><code>lifecycle_callback</code></td><td>Hook <code>before_save</code> / <code>after_save</code> / <code>after_load</code></td><td><code>_metadati__tabelle.md_before_save</code> (et al)</td></tr><tr><td><code>simple_metadata_update</code></td><td>Update generic di un field semplice (label, pagesize, flag)</td><td><code>_metadati__tabelle</code> / <code>_metadati__colonne</code></td></tr><tr><td><code>metadata_column_create</code></td><td>Crea nuova colonna metadata (anche calcolata)</td><td><code>_metadati__colonne</code> (INSERT)</td></tr><tr><td><code>sql_metadata_field</code></td><td>Scrive uno snippet SQL su un field gated della tabella metadata (super-admin)</td><td><code>_metadati__tabelle.md_join_override</code> et al</td></tr><tr><td><code>designer_inject</code></td><td>Inietta tool/widget nel canvas del dashboard designer (client-only, no backend)</td><td>dashboard designer state</td></tr></tbody></table><h2>Esempi canonici di prompt</h2><h3><code>toolbar_action</code> — Button bulk sulla toolbar</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities aggiungi un&#39;azione di toolbar &#39;Archivia selezionate&#39; (icona pi pi-archive) che archivia in bulk i record selezionati via POST /api/cities/bulk-archive con i loro id. Richiedi conferma prima.</p><p>Campi richiesti nella proposta: <code>route</code>, <code>label</code>, <code>callback_js</code>. Opzionale</p><p><code>requires_multi_selection:true</code> se il callback opera su <code>datasource.getSelectedRows()</code></p><p>(il backend abilita <code>md_multiple_selection</code> sulla route in tal caso).</p><h3><code>row_action</code> — Button sulla singola riga</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities aggiungi un button di riga &#39;Genera PDF&#39; (icona pi pi-file-pdf) che apre /api/cities/{id}/pdf in nuova tab con l&#39;id della riga.</p><p>Campi richiesti: <code>route</code>, <code>label</code>, <code>callback_js</code>. Lo scope del callback ha <code>record</code></p><p>(BehaviorSubject map: <code>record.id?.value</code>).</p><h3><code>table_style</code> — Stile condizionale riga</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities colora in rosso (classe row-danger) le righe con LatestRecordedPopulation inferiore a 1000.</p><p>Campi richiesti: <code>route</code>, <code>css_class</code>, <code>condition_js</code>. Per i colori della palette</p><p>predefinita usa una classe (<code>row-danger</code> / <code>row-warning</code> / <code>row-info</code> / <code>row-success</code>).</p><p>Per colori arbitrari emetti <code>css_class</code> con prefisso <code>style:</code> (es.</p><p><code>style:background-color:#9b59b6;color:white</code>) — il framework iniettera&#39; <code>!important</code></p><p>automaticamente e neutralizzera&#39; il bg dei <code>&lt;td&gt;</code> cosi&#39; il colore arriva all&#39;occhio.</p><h3><code>column_style</code> — Stile condizionale cella</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities colora di rosso SOLO la cella LatestRecordedPopulation (classe cell-danger) quando il valore e&#39; inferiore a 1000, NON l&#39;intera riga.</p><p>Campi richiesti: <code>route</code>, <code>column_name</code>, <code>css_class</code>, <code>condition_js</code>.</p><h3><code>display_formula</code> — Template cella</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities, formatta la colonna LatestRecordedPopulation in formato compatto k/M (es. 12500 -&gt; 12.5k, 1500000 -&gt; 1.5M).</p><p>Campi richiesti: <code>route</code>, <code>column_name</code>, <code>template_html</code>. Scope: <code>rowData</code></p><p>(oggetto piatto, NO BehaviorSubject). Supporta Angular pipe standard</p><p>(<code>number:&#39;1.1-1&#39;</code>, <code>date:</code>, <code>currency:</code>, ecc.), interpolation <code>{{ rowData.x }}</code>,</p><p>ternario inline, <code>*ngIf</code> blocchi.</p><h3><code>form_title_formula</code> — Titolo dinamico edit form</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities imposta il titolo dinamico del form di edit a &#39;Modifica &lt;CityName&gt;&#39; dove &lt;CityName&gt; e&#39; il nome della citta&#39;, oppure &#39;Nuova citta&#39; su INSERT.</p><p>Campi richiesti: <code>route</code>, <code>formula_js</code>. Scope: <code>metaInfo</code>, <code>record</code>, <code>datasource</code>,</p><p><code>wtoolbox</code>. Deve ritornare <code>string</code>.</p><h3><code>default_value_callback</code> — Default in INSERT</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities, la colonna CityName deve avere come valore di default in inserimento la stringa &#39;Nuova citta&#39;.</p><p>Campi richiesti: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Lo scope del callback ha</p><p><code>record</code> (NUOVO record, plain object), <code>field</code> (<code>MetadatiColonna</code> target), <code>metaInfo</code>,</p><p><code>wtoolbox</code>. Il <code>return</code> e&#39; ignorato — il callback DEVE scrivere il valore in</p><p><code>record[field.mc_nome_colonna]</code>.</p><h3><code>custom_validation</code> — Validazione blocking</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities valida che LatestRecordedPopulation non sia negativo. Blocca il save con messaggio &#39;La popolazione non puo essere negativa&#39;.</p><p>Campi richiesti: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope: <code>record</code></p><p>(BehaviorSubject map), <code>field</code>, <code>vr</code> (ValidationRule: setta <code>vr.message=&#39;...&#39;</code>),</p><p><code>wtoolbox</code>. Deve ritornare <code>boolean</code>: <code>true</code> se valido, <code>false</code> per bloccare.</p><h3><code>selection_changed</code> — Trigger al change di un lookup</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities, al cambio del campo StateProvinceID (selection_changed), normalizza il valore tutto in maiuscolo via record.StateProvinceID.next(...).</p><p>Campi richiesti: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope: <code>record</code>, <code>value</code>,</p><p><code>datasource</code>, <code>wtoolbox</code>. L&#39;oggetto lookup risolto e&#39; in <code>record.&lt;col&gt;__lookup_obj.value</code>.</p><h3><code>lifecycle_callback</code> — Hook before/after save/load</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities, prima del save (event=before_save) metti il campo CityName in maiuscolo via record.CityName.next(record.CityName.value.toUpperCase()).</p><p>Campi richiesti: <code>route</code>, <code>event</code> (<code>before_save</code> / <code>after_save</code> / <code>after_load</code>),</p><p><code>callback_js</code>. Scope: <code>record</code> (BehaviorSubject map), <code>datasource</code>, <code>wtoolbox</code>.</p><h3><code>simple_metadata_update</code> — Update di field semplici</h3><p><strong>Prompt utente (esempio A — label colonna):</strong></p><p>&gt; Modifica il titolo della colonna LatestRecordedPopulation in &#39;Official Population&#39; su cities.</p><p><strong>Prompt utente (esempio B — pagesize tabella):</strong></p><p>&gt; Imposta il pagesize della tabella cities a 50.</p><p>Campi richiesti: <code>route</code>, <code>target</code> (<code>column</code> / <code>table</code>), <code>field_label</code> (label friendly),</p><p><code>value</code>. Il backend mappa il <code>field_label</code> al campo SQL fisico via mappa server-side</p><p>(circa 29 field coperti: header label, page size, hide-in-list, ecc.).</p><h3><code>metadata_column_create</code> — Crea colonna metadata</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities crea una colonna calcolata <code>e2e_total_chars</code> di tipo number che calcola LEN sul CityName. La formula a livello metadato deve essere LEN([Application].[Cities].[CityName]) e la colonna deve essere is_computed=true (non fisica nel DB).</p><p>Campi richiesti: <code>route</code>, <code>column_name</code>, <code>ui_column_type</code>, <code>is_computed</code>. Quando</p><p><code>is_computed=true</code> deve essere presente anche <code>computed_formula</code>.</p><h3><code>sql_metadata_field</code> — Snippet SQL su field gated (super-admin)</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities applica al campo md_join_override il join SQL: LEFT JOIN [Application].[People] AS [e2e_test_people] ON 1=0. E&#39; un test e2e no-op, scopo di verificare che il tool propose_sql_metadata_field scriva correttamente in _metadati__tabelle.mdjoinoverride. Dbms target: mssql.</p><p>Campi richiesti: <code>target_table</code>, <code>target_row_key</code>, <code>field_name</code>, <code>sql_snippet</code>,</p><p><code>dbms_target</code>. Auth: super-admin (D3 gate). Il prompt utente DEVE specificare il</p><p>dbms target — il backend non lo desume.</p><h3><code>designer_inject</code> — Iniezione tool nel dashboard designer</h3><p>Client-only kind: applicato non dal backend ma da un handler registrato dal</p><p><code>designer.component</code> via <code>ChatbotHostRegistryService</code>. Vedi</p><p><code>docs/pages/_internal/designer-tool-catalog.md</code> per il catalogo dei tool palette.</p><h2>Pattern operativo</h2><p>1. L&#39;utente apre il chatbot (FAB in basso a destra) e scrive il prompt.</p><p>2. Il LLM analizza la richiesta + il <code>routeContext</code> iniettato (route corrente, tabella,</p><p> colonne) e emette il <code>tool_use</code> con il <code>kind</code> appropriato.</p><p>3. La proposta viene mostrata in card con campi editabili:</p><p> - I campi NON-body (classe, label, target, ecc.) come <code>&lt;input&gt;</code> di testo.</p><p> - Il body del callback come <code>&lt;textarea&gt;</code> o Monaco editor avanzato.</p><p>4. L&#39;utente puo&#39; editare entrambi prima del click &quot;Applica&quot;.</p><p>5. Al click &quot;Applica&quot;:</p><p> - Se la proposta e&#39; stata editata: <code>POST /api/Rag/UpdateProposedAction</code> ri-scrive</p><p> il <code>proposed_action_json</code> nel DB del messaggio.</p><p> - <code>POST /api/Rag/ApplyAction</code> rilegge dal DB la proposta e applica i side-effect</p><p> sui metadati WUIC + chiama <code>InvalidateMetadataCachesAndSetVersion</code> cosi&#39; il</p><p> refresh successivo della route target vede la modifica.</p><h2>Test docs-driven</h2><p>Il file <code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> definisce</p><p>l&#39;array <code>TOOLS</code> con una entry per ogni <code>kind</code>. Ogni entry ha:</p><ul><li><code>kind</code> — valore atteso in <code>proposed_action_json</code>;</li><li><code>prompt</code> — testo esatto inviato al LLM;</li><li><code>assertFields</code> — campi minimi che la proposta deve contenere;</li><li><code>verify(api, mdId)</code> — controllo DB post-apply (la riga metadata esiste);</li><li><code>verifyDom(page)</code> — controllo runtime (effetto visibile in <code>/cities/list</code>);</li><li><code>cleanup(api, mdId)</code> — rollback chirurgico (DELETE / UPDATE NULL).</li></ul><p>Per rilanciare solo alcuni kind: <code>WUIC_RAG_TOOLS_ONLY=row_action,table_style npm run test:docs</code>.</p><h2>Riferimenti</h2><ul><li>Componente: <code>wuic-framework-lib/src/lib/component/rag-chatbot/rag-chatbot.component.ts</code></li><li>Backend dispatcher: <code>KonvergenceCore/Controllers/RagController.cs</code> (<code>ApplyAction</code>)</li><li>Repository: <code>KonvergenceCore/Services/RagChat/RagChatRepository.cs</code></li><li>Tool definition JSON: <code>codebase_embeddings/onnx_export/rag_tools.json</code> (passato al LLM)</li><li>Cookbook callback: <a href=\"callback-cookbook.md\">docs/pages/callback-cookbook.md</a></li><li>Pagina principale: <a href=\"rag-chatbot.md\">docs/pages/rag-chatbot.md</a></li></ul>",
6796
+ "html": "<h1>RAG Chatbot — Tool catalog</h1><p>Catalogo dei &quot;tool&quot; che l&#39;LLM Anthropic puo&#39; invocare via <code>tool_use</code> quando l&#39;utente</p><p>fa una richiesta nel chatbot <code>&lt;wuic-rag-chatbot&gt;</code>. Per ogni tool: il <code>kind</code> che</p><p>viene emesso in <code>proposed_action_json</code>, un <strong>prompt utente canonico</strong> che lo triggera,</p><p>i campi minimi della proposta e i metadati WUIC modificati al click di &quot;Applica&quot;.</p><p>Ogni tool ha una corrispondente regola di test end-to-end in</p><p><code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> (single source of</p><p>truth: se la <code>prompt</code> cambia qui ma non li&#39;, il test fallisce). I prompt canonici</p><p>sotto sono ESATTAMENTE quelli del test.</p><h2>Indice</h2><table><thead><tr><th>Kind</th><th>Cosa fa</th><th>Metadata target</th></tr></thead><tbody><tr><td><code>toolbar_action</code></td><td>Aggiunge un button alla toolbar della list-grid</td><td><code>_mtdt__cstom__actions__tabelle</code></td></tr><tr><td><code>row_action</code></td><td>Aggiunge un button-action sulla singola riga</td><td><code>_metadati__colonne</code> (mc_voa_class=6)</td></tr><tr><td><code>table_style</code></td><td>Stile condizionale (classe CSS o inline) sull&#39;intera <code>&lt;tr&gt;</code></td><td><code>_metadati__u_i__stili__tabelle</code></td></tr><tr><td><code>column_style</code></td><td>Stile condizionale sulla singola cella</td><td><code>_metadati__u_i__stili__colonne</code></td></tr><tr><td><code>display_formula</code></td><td>Template Angular per la cella (formato compatto, badge, link)</td><td><code>_metadati__colonne.mc_ui_grid_column_data_template</code></td></tr><tr><td><code>form_title_formula</code></td><td>Titolo dinamico del form di edit</td><td><code>_metadati__tabelle.md_display_formula</code></td></tr><tr><td><code>default_value_callback</code></td><td>Default JS per un campo in INSERT</td><td><code>_metadati__colonne.mc_default_value_callback</code></td></tr><tr><td><code>custom_validation</code></td><td>Validazione blocking pre-save</td><td><code>_metadati__colonne.mc_validation_custom_callback</code></td></tr><tr><td><code>selection_changed</code></td><td>Trigger al change di un lookup</td><td><code>_metadati__colonne.mc_selection_changed_custom_function</code></td></tr><tr><td><code>lifecycle_callback</code></td><td>Hook <code>before_save</code> / <code>after_save</code> / <code>after_load</code></td><td><code>_metadati__tabelle.md_before_save</code> (et al)</td></tr><tr><td><code>simple_metadata_update</code></td><td>Update generic di un field semplice (label, pagesize, flag)</td><td><code>_metadati__tabelle</code> / <code>_metadati__colonne</code></td></tr><tr><td><code>metadata_column_create</code></td><td>Crea nuova colonna metadata (anche calcolata)</td><td><code>_metadati__colonne</code> (INSERT)</td></tr><tr><td><code>sql_metadata_field</code></td><td>Scrive uno snippet SQL su un field gated della tabella metadata (super-admin)</td><td><code>_metadati__tabelle.md_join_override</code> et al</td></tr><tr><td><code>designer_inject</code></td><td>Inietta tool/widget nel canvas del dashboard designer (client-only, no backend)</td><td>dashboard designer state</td></tr></tbody></table><h2>Esempi canonici di prompt</h2><h3><code>toolbar_action</code> — Button bulk sulla toolbar</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities aggiungi un&#39;azione di toolbar &#39;Archivia selezionate&#39; (icona pi pi-archive) che archivia in bulk i record selezionati via POST /api/cities/bulk-archive con i loro id. Richiedi conferma prima.</p><p>Campi richiesti nella proposta: <code>route</code>, <code>label</code>, <code>callback_js</code>. Opzionale</p><p><code>requires_multi_selection:true</code> se il callback opera su <code>datasource.getSelectedRows()</code></p><p>(il backend abilita <code>md_multiple_selection</code> sulla route in tal caso).</p><h3><code>row_action</code> — Button sulla singola riga</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities aggiungi un button di riga &#39;Genera PDF&#39; (icona pi pi-file-pdf) che apre /api/cities/{id}/pdf in nuova tab con l&#39;id della riga.</p><p>Campi richiesti: <code>route</code>, <code>label</code>, <code>callback_js</code>. Lo scope del callback ha <code>record</code></p><p>(BehaviorSubject map: <code>record.id?.value</code>).</p><h3><code>table_style</code> — Stile condizionale riga</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities colora in rosso (classe row-danger) le righe con LatestRecordedPopulation inferiore a 1000.</p><p>Campi richiesti: <code>route</code>, <code>css_class</code>, <code>condition_js</code>. Per i colori della palette</p><p>predefinita usa una classe (<code>row-danger</code> / <code>row-warning</code> / <code>row-info</code> / <code>row-success</code>).</p><p>Per colori arbitrari emetti <code>css_class</code> con prefisso <code>style:</code> (es.</p><p><code>style:background-color:#9b59b6;color:white</code>) — il framework iniettera&#39; <code>!important</code></p><p>automaticamente e neutralizzera&#39; il bg dei <code>&lt;td&gt;</code> cosi&#39; il colore arriva all&#39;occhio.</p><h3><code>column_style</code> — Stile condizionale cella</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities colora di rosso SOLO la cella LatestRecordedPopulation (classe cell-danger) quando il valore e&#39; inferiore a 1000, NON l&#39;intera riga.</p><p>Campi richiesti: <code>route</code>, <code>column_name</code>, <code>css_class</code>, <code>condition_js</code>.</p><h3><code>display_formula</code> — Template cella</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities, formatta la colonna LatestRecordedPopulation in formato compatto k/M (es. 12500 -&gt; 12.5k, 1500000 -&gt; 1.5M).</p><p>Campi richiesti: <code>route</code>, <code>column_name</code>, <code>template_html</code>. Scope: <code>rowData</code></p><p>(oggetto piatto, NO BehaviorSubject). Supporta Angular pipe standard</p><p>(<code>number:&#39;1.1-1&#39;</code>, <code>date:</code>, <code>currency:</code>, ecc.), interpolation <code>{{ rowData.x }}</code>,</p><p>ternario inline, <code>*ngIf</code> blocchi.</p><h3><code>form_title_formula</code> — Titolo dinamico edit form</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities imposta il titolo dinamico del form di edit a &#39;Modifica &lt;CityName&gt;&#39; dove &lt;CityName&gt; e&#39; il nome della citta&#39;, oppure &#39;Nuova citta&#39; su INSERT.</p><p>Campi richiesti: <code>route</code>, <code>formula_js</code>. Scope: <code>metaInfo</code>, <code>record</code>, <code>datasource</code>,</p><p><code>wtoolbox</code>. Deve ritornare <code>string</code>.</p><h3><code>default_value_callback</code> — Default in INSERT</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities, la colonna CityName deve avere come valore di default in inserimento la stringa &#39;Nuova citta&#39;.</p><p>Campi richiesti: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Lo scope del callback ha</p><p><code>record</code> (NUOVO record, plain object), <code>field</code> (<code>MetadatiColonna</code> target), <code>metaInfo</code>,</p><p><code>wtoolbox</code>. Il <code>return</code> e&#39; ignorato — il callback DEVE scrivere il valore in</p><p><code>record[field.mc_nome_colonna]</code>.</p><h3><code>custom_validation</code> — Validazione blocking</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities valida che LatestRecordedPopulation non sia negativo. Blocca il save con messaggio &#39;La popolazione non puo essere negativa&#39;.</p><p>Campi richiesti: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope: <code>record</code></p><p>(BehaviorSubject map), <code>field</code>, <code>vr</code> (ValidationRule: setta <code>vr.message=&#39;...&#39;</code>),</p><p><code>wtoolbox</code>. Deve ritornare <code>boolean</code>: <code>true</code> se valido, <code>false</code> per bloccare.</p><h3><code>selection_changed</code> — Trigger al change di un lookup</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities, al cambio del campo StateProvinceID (selection_changed), normalizza il valore tutto in maiuscolo via record.StateProvinceID.next(...).</p><p>Campi richiesti: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope: <code>record</code>, <code>value</code>,</p><p><code>datasource</code>, <code>wtoolbox</code>. L&#39;oggetto lookup risolto e&#39; in <code>record.&lt;col&gt;__lookup_obj.value</code>.</p><h3><code>lifecycle_callback</code> — Hook before/after save/load</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities, prima del save (event=before_save) metti il campo CityName in maiuscolo via record.CityName.next(record.CityName.value.toUpperCase()).</p><p>Campi richiesti: <code>route</code>, <code>event</code> (<code>before_save</code> / <code>after_save</code> / <code>after_load</code>),</p><p><code>callback_js</code>. Scope: <code>record</code> (BehaviorSubject map), <code>datasource</code>, <code>wtoolbox</code>.</p><h3><code>simple_metadata_update</code> — Update di field semplici</h3><p><strong>Prompt utente (esempio A — label colonna):</strong></p><p>&gt; Modifica il titolo della colonna LatestRecordedPopulation in &#39;Official Population&#39; su cities.</p><p><strong>Prompt utente (esempio B — pagesize tabella):</strong></p><p>&gt; Imposta il pagesize della tabella cities a 50.</p><p>Campi richiesti: <code>route</code>, <code>target</code> (<code>column</code> / <code>table</code>), <code>field_label</code> (label friendly),</p><p><code>value</code>. Il backend mappa il <code>field_label</code> al campo SQL fisico via mappa server-side</p><p>(circa 29 field coperti: header label, page size, hide-in-list, ecc.).</p><h3><code>metadata_column_create</code> — Crea colonna metadata</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities crea una colonna calcolata <code>e2e_total_chars</code> di tipo number che calcola LEN sul CityName. La formula a livello metadato deve essere LEN([Application].[Cities].[CityName]) e la colonna deve essere is_computed=true (non fisica nel DB).</p><p>Campi richiesti: <code>route</code>, <code>column_name</code>, <code>ui_column_type</code>, <code>is_computed</code>. Quando</p><p><code>is_computed=true</code> deve essere presente anche <code>computed_formula</code>.</p><h3><code>sql_metadata_field</code> — Snippet SQL su field gated (super-admin)</h3><p><strong>Prompt utente:</strong></p><p>&gt; Su cities applica al campo md_join_override il join SQL: LEFT JOIN [Application].[People] AS [e2e_test_people] ON 1=0. E&#39; un test e2e no-op, scopo di verificare che il tool propose_sql_metadata_field scriva correttamente in _metadati__tabelle.mdjoinoverride. Dbms target: mssql.</p><p>Campi richiesti: <code>target_table</code>, <code>target_row_key</code>, <code>field_name</code>, <code>sql_snippet</code>,</p><p><code>dbms_target</code>. Auth: super-admin (D3 gate). Il prompt utente DEVE specificare il</p><p>dbms target — il backend non lo desume.</p><h3><code>designer_inject</code> — Iniezione tool nel dashboard designer</h3><p>Client-only kind: applicato non dal backend ma da un handler registrato dal</p><p><code>designer.component</code> via <code>ChatbotHostRegistryService</code>. Vedi</p><p><code>docs/pages/_internal/designer-tool-catalog.md</code> per il catalogo dei tool palette.</p><h2>Robustezza del routing — varianti non-banali</h2><p>I prompt canonici sopra sono <strong>un solo esempio per kind</strong>. Il routing del tool è</p><p>però robusto a formulazioni diverse: la suite</p><p><code>playwright/docs/rag-chatbot/rag-chatbot--tools-variations.mjs</code> prova **5+ varianti</p><p>per kind** (riformulazioni semantiche, vincoli realistici, coppie disambiguanti),</p><p>molte derivate da prompt reali, e misura il <em>routing-rate</em> su più ripetizioni.</p><p>Il routing è risultato <strong>language-agnostic</strong> (italiano ed inglese instradano allo</p><p>stesso modo — <code>WUIC_RAG_VAR_LANG=en</code>).</p><p>Esempi alternativi che instradano correttamente (oltre ai canonici):</p><ul><li><code>toolbar_action</code> → «metti in cima alla griglia un pulsante che esporta in CSV le righe spuntate»</li><li><code>row_action</code> → «su ogni riga un&#39;icona occhio che apre la scheda di quella riga in una nuova tab»</li><li><code>table_style</code> → «evidenzia di verde le righe modificate di recente»</li><li><code>column_style</code> → «metti uno sfondo giallo soltanto sulla cella CityName quando è vuota»</li><li><code>display_formula</code> → «visualizza la popolazione con il separatore delle migliaia»</li><li><code>custom_validation</code> → «impedisci di salvare se ValidTo è precedente a ValidFrom»</li><li><code>simple_metadata_update</code> → «nascondi la colonna LastEditedBy dalla lista» (nota: «filtro di default» è <code>simple_metadata_update</code> con <code>field_label=default_filter</code>, NON <code>sql_metadata_field</code>)</li><li><code>metadata_column_create</code> → «aggiungi una colonna calcolata che divide la popolazione per 1000»</li><li><code>designer_inject</code> → «crea un master-detail con 2 grid provincie e città», «componi un layout a 3 colonne con tre grid», «metti due grid affiancate»</li></ul><p><strong>Veri negativi (NON devono emettere un tool):</strong> richieste di <em>esempio di codice</em></p><p>(«mi dai un esempio di toolbar action senza applicarlo?») o domande concettuali</p><p>restano in risposta testuale senza proporre alcuna azione.</p><h3>Designer — verifica del canvas e limiti della palette</h3><p><code>rag-chatbot--designer-canvas-verify.mjs</code> verifica che ogni prompt designer produca</p><p><strong>davvero</strong> i componenti attesi nel canvas (istogramma per tipo, non solo presenza</p><p>nel DOM). I <strong>grafici</strong> si ottengono con un <code>DATAREPEATER</code> in modalità</p><p><code>action: &quot;chart&quot;</code> (non esiste un tool <code>CHART</code> separato — il chart è un datarepeater</p><p>in modalità chart, configurabile bar/line/pie via «Configura chart»). Altre <code>action</code></p><p>del datarepeater: <code>list</code> (grid, default), <code>edit</code>/<code>dialog</code>/<code>detail</code> (scheda/form),</p><p><code>map</code>, <code>scheduler</code>, <code>calendar</code>, <code>kanban</code>, <code>tree</code>, <code>carousel</code>, <code>pivot</code>, <code>spreadsheet</code>.</p><p>I titoli testuali sono emessi come <code>LABEL</code>/<code>H1</code>.</p><p>#### Config archetipo inline (<code>archetype_config</code>)</p><p>Un nodo <code>DATAREPEATER</code> del <code>designer_inject</code> può portare un campo <code>archetype_config</code></p><p>= il contenuto di <code>md_props_bag.archetypes.&lt;action&gt;</code>. Viene applicato <strong>in-memory</strong> al</p><p><code>metaInfo.tableMetadata</code> del datasource bindato (nessuna chiamata server) tramite lo</p><p><strong>stesso path di &quot;Configura chart&quot;</strong> (<code>customProps_&lt;action&gt;</code> + <code>ds.fetchData()</code> +</p><p><code>propertyTreeBuilder</code>), e persiste alla serializzazione del datasource al save della</p><p>dashboard. L&#39;applicazione è posticipata finché il datasource bindato è materializzato.</p><ul><li><strong>chart</strong>: <code>dataOptions</code> DEVE includere <code>dataProperty:&quot;dato&quot;</code> (chiave dell&#39;array dati</li></ul><p> che <code>parseData</code> legge — senza, il grafico resta vuoto) +</p><p> <code>datasets:[{label, labelField:&#39;&lt;col etichette&gt;&#39;, dataField:&#39;&lt;col valori&gt;&#39;}]</code>.</p><ul><li><strong>map</strong>: <code>markerColorField</code>, <code>titleField</code>, <code>zoom</code>, <code>center:{lat,lng}</code> (i marker</li></ul><p> prendono le coordinate dalla colonna <code>point</code> auto-rilevata della route).</p><ul><li>Altri archetipi (<code>scheduler</code>, <code>kanban</code>, <code>tree</code>, …) seguono lo stesso meccanismo con i</li></ul><p> rispettivi campi (vedi <code>mapOptions</code>/<code>kanbanOptions</code>/<code>schedulerOptions</code>).</p><p>Verificato: «grafico a torta della popolazione per provincia» → pie con dati; «città su</p><p>una mappa colorate per provincia» → Google Map con un marker per città.</p><p>&gt; ⚠️ Test full-UI del designer: ogni prompt va eseguito su <strong>conversazione pulita</strong></p><p>&gt; (il chatbot persiste l&#39;id sessione in <code>localStorage[&#39;wuic_rag_last_session_id&#39;]</code></p><p>&gt; e lo ripristina al boot — senza reset la history di un prompt contamina il</p><p>&gt; successivo). Inoltre richiede backend in <code>ASPNETCORE_ENVIRONMENT=Development</code></p><p>&gt; (CORS per il frontend dev) + frontend attivo.</p><h2>Pattern operativo</h2><p>1. L&#39;utente apre il chatbot (FAB in basso a destra) e scrive il prompt.</p><p>2. Il LLM analizza la richiesta + il <code>routeContext</code> iniettato (route corrente, tabella,</p><p> colonne) e emette il <code>tool_use</code> con il <code>kind</code> appropriato.</p><p>3. La proposta viene mostrata in card con campi editabili:</p><p> - I campi NON-body (classe, label, target, ecc.) come <code>&lt;input&gt;</code> di testo.</p><p> - Il body del callback come <code>&lt;textarea&gt;</code> o Monaco editor avanzato.</p><p>4. L&#39;utente puo&#39; editare entrambi prima del click &quot;Applica&quot;.</p><p>5. Al click &quot;Applica&quot;:</p><p> - Se la proposta e&#39; stata editata: <code>POST /api/Rag/UpdateProposedAction</code> ri-scrive</p><p> il <code>proposed_action_json</code> nel DB del messaggio.</p><p> - <code>POST /api/Rag/ApplyAction</code> rilegge dal DB la proposta e applica i side-effect</p><p> sui metadati WUIC + chiama <code>InvalidateMetadataCachesAndSetVersion</code> cosi&#39; il</p><p> refresh successivo della route target vede la modifica.</p><h2>Test docs-driven</h2><p>Il file <code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> definisce</p><p>l&#39;array <code>TOOLS</code> con una entry per ogni <code>kind</code>. Ogni entry ha:</p><ul><li><code>kind</code> — valore atteso in <code>proposed_action_json</code>;</li><li><code>prompt</code> — testo esatto inviato al LLM;</li><li><code>assertFields</code> — campi minimi che la proposta deve contenere;</li><li><code>verify(api, mdId)</code> — controllo DB post-apply (la riga metadata esiste);</li><li><code>verifyDom(page)</code> — controllo runtime (effetto visibile in <code>/cities/list</code>);</li><li><code>cleanup(api, mdId)</code> — rollback chirurgico (DELETE / UPDATE NULL).</li></ul><p>Per rilanciare solo alcuni kind: <code>WUIC_RAG_TOOLS_ONLY=row_action,table_style npm run test:docs</code>.</p><h2>Riferimenti</h2><ul><li>Componente: <code>wuic-framework-lib/src/lib/component/rag-chatbot/rag-chatbot.component.ts</code></li><li>Backend dispatcher: <code>KonvergenceCore/Controllers/RagController.cs</code> (<code>ApplyAction</code>)</li><li>Repository: <code>KonvergenceCore/Services/RagChat/RagChatRepository.cs</code></li><li>Tool definition JSON: <code>codebase_embeddings/onnx_export/rag_tools.json</code> (passato al LLM)</li><li>Cookbook callback: <a href=\"callback-cookbook.md\">docs/pages/callback-cookbook.md</a></li><li>Pagina principale: <a href=\"rag-chatbot.md\">docs/pages/rag-chatbot.md</a></li></ul>",
6793
6797
  "codeSamples": []
6794
6798
  }
6795
6799
  ],
@@ -12621,14 +12625,18 @@ const frameworkDocsContent = {
12621
12625
  },
12622
12626
  {
12623
12627
  "id": "sec_3",
12624
- "title": "Operational pattern"
12628
+ "title": "Routing robustness — non-trivial variants"
12625
12629
  },
12626
12630
  {
12627
12631
  "id": "sec_4",
12628
- "title": "Docs-driven tests"
12632
+ "title": "Operational pattern"
12629
12633
  },
12630
12634
  {
12631
12635
  "id": "sec_5",
12636
+ "title": "Docs-driven tests"
12637
+ },
12638
+ {
12639
+ "id": "sec_6",
12632
12640
  "title": "References"
12633
12641
  }
12634
12642
  ],
@@ -12636,7 +12644,7 @@ const frameworkDocsContent = {
12636
12644
  {
12637
12645
  "id": "overview",
12638
12646
  "title": "Overview",
12639
- "html": "<h1>RAG Chatbot — Tool catalog</h1><p>Catalog of the &quot;tools&quot; that the Anthropic LLM can invoke via <code>tool_use</code> when the</p><p>user makes a request in the <code>&lt;wuic-rag-chatbot&gt;</code> component. For each tool: the</p><p><code>kind</code> emitted in <code>proposed_action_json</code>, a <strong>canonical user prompt</strong> that</p><p>triggers it, the minimum required fields and the WUIC metadata modified on</p><p>click &quot;Apply&quot;.</p><p>Each tool has a corresponding end-to-end test rule in</p><p><code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> (single source of</p><p>truth: if the <code>prompt</code> here changes but not there, the test fails). The canonical</p><p>prompts below are EXACTLY those of the test — they are in Italian because the</p><p>test was authored that way; the LLM accepts the same questions in any language.</p><h2>Index</h2><table><thead><tr><th>Kind</th><th>What it does</th><th>Target metadata</th></tr></thead><tbody><tr><td><code>toolbar_action</code></td><td>Adds a button to the list-grid toolbar</td><td><code>_mtdt__cstom__actions__tabelle</code></td></tr><tr><td><code>row_action</code></td><td>Adds a button-action on the single row</td><td><code>_metadati__colonne</code> (mc_voa_class=6)</td></tr><tr><td><code>table_style</code></td><td>Conditional style (CSS class or inline) on the whole <code>&lt;tr&gt;</code></td><td><code>_metadati__u_i__stili__tabelle</code></td></tr><tr><td><code>column_style</code></td><td>Conditional style on the single cell</td><td><code>_metadati__u_i__stili__colonne</code></td></tr><tr><td><code>display_formula</code></td><td>Angular template for the cell (compact format, badge, link)</td><td><code>_metadati__colonne.mc_ui_grid_column_data_template</code></td></tr><tr><td><code>form_title_formula</code></td><td>Dynamic title for the edit form</td><td><code>_metadati__tabelle.md_display_formula</code></td></tr><tr><td><code>default_value_callback</code></td><td>JS default for a field on INSERT</td><td><code>_metadati__colonne.mc_default_value_callback</code></td></tr><tr><td><code>custom_validation</code></td><td>Blocking pre-save validation</td><td><code>_metadati__colonne.mc_validation_custom_callback</code></td></tr><tr><td><code>selection_changed</code></td><td>Trigger on lookup change</td><td><code>_metadati__colonne.mc_selection_changed_custom_function</code></td></tr><tr><td><code>lifecycle_callback</code></td><td><code>before_save</code> / <code>after_save</code> / <code>after_load</code> hooks</td><td><code>_metadati__tabelle.md_before_save</code> (et al)</td></tr><tr><td><code>simple_metadata_update</code></td><td>Generic update of a simple field (label, pagesize, flag)</td><td><code>_metadati__tabelle</code> / <code>_metadati__colonne</code></td></tr><tr><td><code>metadata_column_create</code></td><td>Creates a new metadata column (computed allowed)</td><td><code>_metadati__colonne</code> (INSERT)</td></tr><tr><td><code>sql_metadata_field</code></td><td>Writes a SQL snippet on a gated metadata table field (super-admin)</td><td><code>_metadati__tabelle.md_join_override</code> et al</td></tr><tr><td><code>designer_inject</code></td><td>Injects tool/widget on the dashboard designer canvas (client-only, no backend)</td><td>dashboard designer state</td></tr></tbody></table><h2>Canonical prompt examples</h2><h3><code>toolbar_action</code> — Bulk button on the toolbar</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities aggiungi un&#39;azione di toolbar &#39;Archivia selezionate&#39; (icona pi pi-archive) che archivia in bulk i record selezionati via POST /api/cities/bulk-archive con i loro id. Richiedi conferma prima.</p><p>Required fields in the proposal: <code>route</code>, <code>label</code>, <code>callback_js</code>. Optional</p><p><code>requires_multi_selection:true</code> if the callback operates on</p><p><code>datasource.getSelectedRows()</code> (the backend enables <code>md_multiple_selection</code> on the</p><p>route in that case).</p><h3><code>row_action</code> — Button on the single row</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities aggiungi un button di riga &#39;Genera PDF&#39; (icona pi pi-file-pdf) che apre /api/cities/{id}/pdf in nuova tab con l&#39;id della riga.</p><p>Required: <code>route</code>, <code>label</code>, <code>callback_js</code>. The callback scope has <code>record</code></p><p>(BehaviorSubject map: <code>record.id?.value</code>).</p><h3><code>table_style</code> — Conditional row style</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities colora in rosso (classe row-danger) le righe con LatestRecordedPopulation inferiore a 1000.</p><p>Required: <code>route</code>, <code>css_class</code>, <code>condition_js</code>. For predefined palette colors use</p><p>a class (<code>row-danger</code> / <code>row-warning</code> / <code>row-info</code> / <code>row-success</code>). For arbitrary</p><p>colors emit <code>css_class</code> with <code>style:</code> prefix (e.g.</p><p><code>style:background-color:#9b59b6;color:white</code>) — the framework automatically injects</p><p><code>!important</code> and neutralizes the <code>&lt;td&gt;</code> bg so the color reaches the eye.</p><h3><code>column_style</code> — Conditional cell style</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities colora di rosso SOLO la cella LatestRecordedPopulation (classe cell-danger) quando il valore e&#39; inferiore a 1000, NON l&#39;intera riga.</p><p>Required: <code>route</code>, <code>column_name</code>, <code>css_class</code>, <code>condition_js</code>.</p><h3><code>display_formula</code> — Cell template</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities, formatta la colonna LatestRecordedPopulation in formato compatto k/M (es. 12500 -&gt; 12.5k, 1500000 -&gt; 1.5M).</p><p>Required: <code>route</code>, <code>column_name</code>, <code>template_html</code>. Scope: <code>rowData</code> (flat object,</p><p>NO BehaviorSubject). Supports standard Angular pipes (<code>number:&#39;1.1-1&#39;</code>, <code>date:</code>,</p><p><code>currency:</code>, etc.), interpolation <code>{{ rowData.x }}</code>, inline ternary, <code>*ngIf</code>.</p><h3><code>form_title_formula</code> — Dynamic edit form title</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities imposta il titolo dinamico del form di edit a &#39;Modifica &lt;CityName&gt;&#39; dove &lt;CityName&gt; e&#39; il nome della citta&#39;, oppure &#39;Nuova citta&#39; su INSERT.</p><p>Required: <code>route</code>, <code>formula_js</code>. Scope: <code>metaInfo</code>, <code>record</code>, <code>datasource</code>,</p><p><code>wtoolbox</code>. Must return <code>string</code>.</p><h3><code>default_value_callback</code> — Default on INSERT</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities, la colonna CityName deve avere come valore di default in inserimento la stringa &#39;Nuova citta&#39;.</p><p>Required: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. The callback scope has <code>record</code></p><p>(NEW record, plain object), <code>field</code> (target <code>MetadatiColonna</code>), <code>metaInfo</code>,</p><p><code>wtoolbox</code>. The <code>return</code> is ignored — the callback MUST write the value into</p><p><code>record[field.mc_nome_colonna]</code>.</p><h3><code>custom_validation</code> — Blocking validation</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities valida che LatestRecordedPopulation non sia negativo. Blocca il save con messaggio &#39;La popolazione non puo essere negativa&#39;.</p><p>Required: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope: <code>record</code> (BehaviorSubject</p><p>map), <code>field</code>, <code>vr</code> (ValidationRule: set <code>vr.message=&#39;...&#39;</code>), <code>wtoolbox</code>. Must</p><p>return <code>boolean</code>: <code>true</code> if valid, <code>false</code> to block.</p><h3><code>selection_changed</code> — Trigger on lookup change</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities, al cambio del campo StateProvinceID (selection_changed), normalizza il valore tutto in maiuscolo via record.StateProvinceID.next(...).</p><p>Required: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope: <code>record</code>, <code>value</code>,</p><p><code>datasource</code>, <code>wtoolbox</code>. The resolved lookup object is in</p><p><code>record.&lt;col&gt;__lookup_obj.value</code>.</p><h3><code>lifecycle_callback</code> — Before/after save/load hooks</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities, prima del save (event=before_save) metti il campo CityName in maiuscolo via record.CityName.next(record.CityName.value.toUpperCase()).</p><p>Required: <code>route</code>, <code>event</code> (<code>before_save</code> / <code>after_save</code> / <code>after_load</code>),</p><p><code>callback_js</code>. Scope: <code>record</code> (BehaviorSubject map), <code>datasource</code>, <code>wtoolbox</code>.</p><h3><code>simple_metadata_update</code> — Update of simple fields</h3><p><strong>User prompt (example A — column label):</strong></p><p>&gt; Modifica il titolo della colonna LatestRecordedPopulation in &#39;Official Population&#39; su cities.</p><p><strong>User prompt (example B — table pagesize):</strong></p><p>&gt; Imposta il pagesize della tabella cities a 50.</p><p>Required: <code>route</code>, <code>target</code> (<code>column</code> / <code>table</code>), <code>field_label</code> (friendly label),</p><p><code>value</code>. The backend maps the <code>field_label</code> to the physical SQL field via a</p><p>server-side map (around 29 fields covered: header label, page size, hide-in-list,</p><p>etc.).</p><h3><code>metadata_column_create</code> — Create metadata column</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities crea una colonna calcolata <code>e2e_total_chars</code> di tipo number che calcola LEN sul CityName. La formula a livello metadato deve essere LEN([Application].[Cities].[CityName]) e la colonna deve essere is_computed=true (non fisica nel DB).</p><p>Required: <code>route</code>, <code>column_name</code>, <code>ui_column_type</code>, <code>is_computed</code>. When</p><p><code>is_computed=true</code> also <code>computed_formula</code> must be present.</p><h3><code>sql_metadata_field</code> — SQL snippet on gated field (super-admin)</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities applica al campo md_join_override il join SQL: LEFT JOIN [Application].[People] AS [e2e_test_people] ON 1=0. E&#39; un test e2e no-op, scopo di verificare che il tool propose_sql_metadata_field scriva correttamente in _metadati__tabelle.mdjoinoverride. Dbms target: mssql.</p><p>Required: <code>target_table</code>, <code>target_row_key</code>, <code>field_name</code>, <code>sql_snippet</code>,</p><p><code>dbms_target</code>. Auth: super-admin (D3 gate). The user prompt MUST specify the</p><p>target dbms — the backend doesn&#39;t infer it.</p><h3><code>designer_inject</code> — Tool injection in dashboard designer</h3><p>Client-only kind: applied not by the backend but by a handler registered by the</p><p><code>designer.component</code> via <code>ChatbotHostRegistryService</code>. See</p><p><code>docs/pages/_internal/designer-tool-catalog.md</code> for the tool palette catalog.</p><h2>Operational pattern</h2><p>1. The user opens the chatbot (FAB bottom right) and writes the prompt.</p><p>2. The LLM analyzes the request + the injected <code>routeContext</code> (current route,</p><p> table, columns) and emits the <code>tool_use</code> with the appropriate <code>kind</code>.</p><p>3. The proposal is shown in a card with editable fields:</p><p> - NON-body fields (class, label, target, etc.) as <code>&lt;input&gt;</code> text.</p><p> - The callback body as <code>&lt;textarea&gt;</code> or advanced Monaco editor.</p><p>4. The user can edit both before clicking &quot;Apply&quot;.</p><p>5. On click &quot;Apply&quot;:</p><p> - If the proposal has been edited: <code>POST /api/Rag/UpdateProposedAction</code> rewrites</p><p> the <code>proposed_action_json</code> in the message DB row.</p><p> - <code>POST /api/Rag/ApplyAction</code> reads the proposal back from DB and applies the</p><p> side-effects on WUIC metadata + calls</p><p> <code>InvalidateMetadataCachesAndSetVersion</code> so the next refresh of the target</p><p> route sees the change.</p><h2>Docs-driven tests</h2><p>The <code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> file defines</p><p>the <code>TOOLS</code> array with one entry per <code>kind</code>. Each entry has:</p><ul><li><code>kind</code> — value expected in <code>proposed_action_json</code>;</li><li><code>prompt</code> — exact text sent to the LLM;</li><li><code>assertFields</code> — minimum fields the proposal must contain;</li><li><code>verify(api, mdId)</code> — DB post-apply check (metadata row exists);</li><li><code>verifyDom(page)</code> — runtime check (visible effect in <code>/cities/list</code>);</li><li><code>cleanup(api, mdId)</code> — surgical rollback (DELETE / UPDATE NULL).</li></ul><p>To rerun only some kinds: <code>WUIC_RAG_TOOLS_ONLY=row_action,table_style npm run test:docs</code>.</p><h2>References</h2><ul><li>Component: <code>wuic-framework-lib/src/lib/component/rag-chatbot/rag-chatbot.component.ts</code></li><li>Backend dispatcher: <code>KonvergenceCore/Controllers/RagController.cs</code> (<code>ApplyAction</code>)</li><li>Repository: <code>KonvergenceCore/Services/RagChat/RagChatRepository.cs</code></li><li>Tool definition JSON: <code>codebase_embeddings/onnx_export/rag_tools.json</code> (passed to the LLM)</li><li>Callback cookbook: <a href=\"callback-cookbook.md\">docs/pages/callback-cookbook.md</a></li><li>Main page: <a href=\"rag-chatbot.md\">docs/pages/rag-chatbot.md</a></li></ul>",
12647
+ "html": "<h1>RAG Chatbot — Tool catalog</h1><p>Catalog of the &quot;tools&quot; that the Anthropic LLM can invoke via <code>tool_use</code> when the</p><p>user makes a request in the <code>&lt;wuic-rag-chatbot&gt;</code> component. For each tool: the</p><p><code>kind</code> emitted in <code>proposed_action_json</code>, a <strong>canonical user prompt</strong> that</p><p>triggers it, the minimum required fields and the WUIC metadata modified on</p><p>click &quot;Apply&quot;.</p><p>Each tool has a corresponding end-to-end test rule in</p><p><code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> (single source of</p><p>truth: if the <code>prompt</code> here changes but not there, the test fails). The canonical</p><p>prompts below are EXACTLY those of the test — they are in Italian because the</p><p>test was authored that way; the LLM accepts the same questions in any language.</p><h2>Index</h2><table><thead><tr><th>Kind</th><th>What it does</th><th>Target metadata</th></tr></thead><tbody><tr><td><code>toolbar_action</code></td><td>Adds a button to the list-grid toolbar</td><td><code>_mtdt__cstom__actions__tabelle</code></td></tr><tr><td><code>row_action</code></td><td>Adds a button-action on the single row</td><td><code>_metadati__colonne</code> (mc_voa_class=6)</td></tr><tr><td><code>table_style</code></td><td>Conditional style (CSS class or inline) on the whole <code>&lt;tr&gt;</code></td><td><code>_metadati__u_i__stili__tabelle</code></td></tr><tr><td><code>column_style</code></td><td>Conditional style on the single cell</td><td><code>_metadati__u_i__stili__colonne</code></td></tr><tr><td><code>display_formula</code></td><td>Angular template for the cell (compact format, badge, link)</td><td><code>_metadati__colonne.mc_ui_grid_column_data_template</code></td></tr><tr><td><code>form_title_formula</code></td><td>Dynamic title for the edit form</td><td><code>_metadati__tabelle.md_display_formula</code></td></tr><tr><td><code>default_value_callback</code></td><td>JS default for a field on INSERT</td><td><code>_metadati__colonne.mc_default_value_callback</code></td></tr><tr><td><code>custom_validation</code></td><td>Blocking pre-save validation</td><td><code>_metadati__colonne.mc_validation_custom_callback</code></td></tr><tr><td><code>selection_changed</code></td><td>Trigger on lookup change</td><td><code>_metadati__colonne.mc_selection_changed_custom_function</code></td></tr><tr><td><code>lifecycle_callback</code></td><td><code>before_save</code> / <code>after_save</code> / <code>after_load</code> hooks</td><td><code>_metadati__tabelle.md_before_save</code> (et al)</td></tr><tr><td><code>simple_metadata_update</code></td><td>Generic update of a simple field (label, pagesize, flag)</td><td><code>_metadati__tabelle</code> / <code>_metadati__colonne</code></td></tr><tr><td><code>metadata_column_create</code></td><td>Creates a new metadata column (computed allowed)</td><td><code>_metadati__colonne</code> (INSERT)</td></tr><tr><td><code>sql_metadata_field</code></td><td>Writes a SQL snippet on a gated metadata table field (super-admin)</td><td><code>_metadati__tabelle.md_join_override</code> et al</td></tr><tr><td><code>designer_inject</code></td><td>Injects tool/widget on the dashboard designer canvas (client-only, no backend)</td><td>dashboard designer state</td></tr></tbody></table><h2>Canonical prompt examples</h2><h3><code>toolbar_action</code> — Bulk button on the toolbar</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities aggiungi un&#39;azione di toolbar &#39;Archivia selezionate&#39; (icona pi pi-archive) che archivia in bulk i record selezionati via POST /api/cities/bulk-archive con i loro id. Richiedi conferma prima.</p><p>Required fields in the proposal: <code>route</code>, <code>label</code>, <code>callback_js</code>. Optional</p><p><code>requires_multi_selection:true</code> if the callback operates on</p><p><code>datasource.getSelectedRows()</code> (the backend enables <code>md_multiple_selection</code> on the</p><p>route in that case).</p><h3><code>row_action</code> — Button on the single row</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities aggiungi un button di riga &#39;Genera PDF&#39; (icona pi pi-file-pdf) che apre /api/cities/{id}/pdf in nuova tab con l&#39;id della riga.</p><p>Required: <code>route</code>, <code>label</code>, <code>callback_js</code>. The callback scope has <code>record</code></p><p>(BehaviorSubject map: <code>record.id?.value</code>).</p><h3><code>table_style</code> — Conditional row style</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities colora in rosso (classe row-danger) le righe con LatestRecordedPopulation inferiore a 1000.</p><p>Required: <code>route</code>, <code>css_class</code>, <code>condition_js</code>. For predefined palette colors use</p><p>a class (<code>row-danger</code> / <code>row-warning</code> / <code>row-info</code> / <code>row-success</code>). For arbitrary</p><p>colors emit <code>css_class</code> with <code>style:</code> prefix (e.g.</p><p><code>style:background-color:#9b59b6;color:white</code>) — the framework automatically injects</p><p><code>!important</code> and neutralizes the <code>&lt;td&gt;</code> bg so the color reaches the eye.</p><h3><code>column_style</code> — Conditional cell style</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities colora di rosso SOLO la cella LatestRecordedPopulation (classe cell-danger) quando il valore e&#39; inferiore a 1000, NON l&#39;intera riga.</p><p>Required: <code>route</code>, <code>column_name</code>, <code>css_class</code>, <code>condition_js</code>.</p><h3><code>display_formula</code> — Cell template</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities, formatta la colonna LatestRecordedPopulation in formato compatto k/M (es. 12500 -&gt; 12.5k, 1500000 -&gt; 1.5M).</p><p>Required: <code>route</code>, <code>column_name</code>, <code>template_html</code>. Scope: <code>rowData</code> (flat object,</p><p>NO BehaviorSubject). Supports standard Angular pipes (<code>number:&#39;1.1-1&#39;</code>, <code>date:</code>,</p><p><code>currency:</code>, etc.), interpolation <code>{{ rowData.x }}</code>, inline ternary, <code>*ngIf</code>.</p><h3><code>form_title_formula</code> — Dynamic edit form title</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities imposta il titolo dinamico del form di edit a &#39;Modifica &lt;CityName&gt;&#39; dove &lt;CityName&gt; e&#39; il nome della citta&#39;, oppure &#39;Nuova citta&#39; su INSERT.</p><p>Required: <code>route</code>, <code>formula_js</code>. Scope: <code>metaInfo</code>, <code>record</code>, <code>datasource</code>,</p><p><code>wtoolbox</code>. Must return <code>string</code>.</p><h3><code>default_value_callback</code> — Default on INSERT</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities, la colonna CityName deve avere come valore di default in inserimento la stringa &#39;Nuova citta&#39;.</p><p>Required: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. The callback scope has <code>record</code></p><p>(NEW record, plain object), <code>field</code> (target <code>MetadatiColonna</code>), <code>metaInfo</code>,</p><p><code>wtoolbox</code>. The <code>return</code> is ignored — the callback MUST write the value into</p><p><code>record[field.mc_nome_colonna]</code>.</p><h3><code>custom_validation</code> — Blocking validation</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities valida che LatestRecordedPopulation non sia negativo. Blocca il save con messaggio &#39;La popolazione non puo essere negativa&#39;.</p><p>Required: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope: <code>record</code> (BehaviorSubject</p><p>map), <code>field</code>, <code>vr</code> (ValidationRule: set <code>vr.message=&#39;...&#39;</code>), <code>wtoolbox</code>. Must</p><p>return <code>boolean</code>: <code>true</code> if valid, <code>false</code> to block.</p><h3><code>selection_changed</code> — Trigger on lookup change</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities, al cambio del campo StateProvinceID (selection_changed), normalizza il valore tutto in maiuscolo via record.StateProvinceID.next(...).</p><p>Required: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope: <code>record</code>, <code>value</code>,</p><p><code>datasource</code>, <code>wtoolbox</code>. The resolved lookup object is in</p><p><code>record.&lt;col&gt;__lookup_obj.value</code>.</p><h3><code>lifecycle_callback</code> — Before/after save/load hooks</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities, prima del save (event=before_save) metti il campo CityName in maiuscolo via record.CityName.next(record.CityName.value.toUpperCase()).</p><p>Required: <code>route</code>, <code>event</code> (<code>before_save</code> / <code>after_save</code> / <code>after_load</code>),</p><p><code>callback_js</code>. Scope: <code>record</code> (BehaviorSubject map), <code>datasource</code>, <code>wtoolbox</code>.</p><h3><code>simple_metadata_update</code> — Update of simple fields</h3><p><strong>User prompt (example A — column label):</strong></p><p>&gt; Modifica il titolo della colonna LatestRecordedPopulation in &#39;Official Population&#39; su cities.</p><p><strong>User prompt (example B — table pagesize):</strong></p><p>&gt; Imposta il pagesize della tabella cities a 50.</p><p>Required: <code>route</code>, <code>target</code> (<code>column</code> / <code>table</code>), <code>field_label</code> (friendly label),</p><p><code>value</code>. The backend maps the <code>field_label</code> to the physical SQL field via a</p><p>server-side map (around 29 fields covered: header label, page size, hide-in-list,</p><p>etc.).</p><h3><code>metadata_column_create</code> — Create metadata column</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities crea una colonna calcolata <code>e2e_total_chars</code> di tipo number che calcola LEN sul CityName. La formula a livello metadato deve essere LEN([Application].[Cities].[CityName]) e la colonna deve essere is_computed=true (non fisica nel DB).</p><p>Required: <code>route</code>, <code>column_name</code>, <code>ui_column_type</code>, <code>is_computed</code>. When</p><p><code>is_computed=true</code> also <code>computed_formula</code> must be present.</p><h3><code>sql_metadata_field</code> — SQL snippet on gated field (super-admin)</h3><p><strong>User prompt:</strong></p><p>&gt; Su cities applica al campo md_join_override il join SQL: LEFT JOIN [Application].[People] AS [e2e_test_people] ON 1=0. E&#39; un test e2e no-op, scopo di verificare che il tool propose_sql_metadata_field scriva correttamente in _metadati__tabelle.mdjoinoverride. Dbms target: mssql.</p><p>Required: <code>target_table</code>, <code>target_row_key</code>, <code>field_name</code>, <code>sql_snippet</code>,</p><p><code>dbms_target</code>. Auth: super-admin (D3 gate). The user prompt MUST specify the</p><p>target dbms — the backend doesn&#39;t infer it.</p><h3><code>designer_inject</code> — Tool injection in dashboard designer</h3><p>Client-only kind: applied not by the backend but by a handler registered by the</p><p><code>designer.component</code> via <code>ChatbotHostRegistryService</code>. See</p><p><code>docs/pages/_internal/designer-tool-catalog.md</code> for the tool palette catalog.</p><h2>Routing robustness — non-trivial variants</h2><p>The canonical prompts above are <strong>a single example per kind</strong>. Tool routing is</p><p>nonetheless robust to different phrasings: the suite</p><p><code>playwright/docs/rag-chatbot/rag-chatbot--tools-variations.mjs</code> tries **5+ variants</p><p>per kind** (semantic rewordings, realistic constraints, disambiguating pairs), many</p><p>derived from real prompts, and measures the <em>routing-rate</em> over multiple repetitions.</p><p>Routing turned out to be <strong>language-agnostic</strong> (Italian and English route the same</p><p>way — <code>WUIC_RAG_VAR_LANG=en</code>).</p><p>Alternative examples that route correctly (besides the canonical ones):</p><ul><li><code>toolbar_action</code> → «put a button at the top of the grid that exports the checked rows to CSV»</li><li><code>row_action</code> → «on each row an eye icon that opens the detail of that row in a new tab»</li><li><code>table_style</code> → «highlight in green the recently modified rows»</li><li><code>column_style</code> → «put a yellow background only on the CityName cell when it is empty»</li><li><code>display_formula</code> → «display the population with the thousands separator»</li><li><code>custom_validation</code> → «prevent saving if ValidTo is earlier than ValidFrom»</li><li><code>simple_metadata_update</code> → «hide the LastEditedBy column from the list» (note: a «default filter» is <code>simple_metadata_update</code> with <code>field_label=default_filter</code>, NOT <code>sql_metadata_field</code>)</li><li><code>metadata_column_create</code> → «add a computed column that divides the population by 1000»</li><li><code>designer_inject</code> → «create a master-detail with 2 grids (provinces and cities)», «compose a 3-column layout with three grids», «put two grids side by side»</li></ul><p><strong>True negatives (must NOT emit a tool):</strong> requests for a <em>code example</em></p><p>(«give me an example of a toolbar action without applying it?») or conceptual questions</p><p>stay as a text answer without proposing any action.</p><h3>Designer — canvas verification and palette limits</h3><p><code>rag-chatbot--designer-canvas-verify.mjs</code> checks that each designer prompt actually</p><p>produces the expected components in the canvas (histogram by type, not just DOM</p><p>presence). <strong>Charts</strong> are produced with a <code>DATAREPEATER</code> in <code>action: &quot;chart&quot;</code> mode</p><p>(there is no separate <code>CHART</code> tool — a chart is a datarepeater in chart mode,</p><p>configurable bar/line/pie via «Configura chart»). Other datarepeater <code>action</code>s:</p><p><code>list</code> (grid, default), <code>edit</code>/<code>dialog</code>/<code>detail</code> (record/form), <code>map</code>, <code>scheduler</code>,</p><p><code>calendar</code>, <code>kanban</code>, <code>tree</code>, <code>carousel</code>, <code>pivot</code>, <code>spreadsheet</code>. Text titles are</p><p>emitted as <code>LABEL</code>/<code>H1</code>.</p><p>#### Inline archetype config (<code>archetype_config</code>)</p><p>A <code>designer_inject</code> <code>DATAREPEATER</code> node can carry an <code>archetype_config</code> field = the</p><p>content of <code>md_props_bag.archetypes.&lt;action&gt;</code>. It is applied <strong>in-memory</strong> to the bound</p><p>datasource&#39;s <code>metaInfo.tableMetadata</code> (no server call) via the **same path as the chart</p><p>configurator** (<code>customProps_&lt;action&gt;</code> + <code>ds.fetchData()</code> + <code>propertyTreeBuilder</code>), and</p><p>persists when the datasource is serialized on dashboard save. The apply is deferred</p><p>until the bound datasource is materialized.</p><ul><li><strong>chart</strong>: <code>dataOptions</code> MUST include <code>dataProperty:&quot;dato&quot;</code> (the key of the data array</li></ul><p> that <code>parseData</code> reads — without it the chart stays empty) +</p><p> <code>datasets:[{label, labelField:&#39;&lt;labels col&gt;&#39;, dataField:&#39;&lt;values col&gt;&#39;}]</code>.</p><ul><li><strong>map</strong>: <code>markerColorField</code>, <code>titleField</code>, <code>zoom</code>, <code>center:{lat,lng}</code> (markers take</li></ul><p> their coordinates from the route&#39;s auto-detected <code>point</code> column).</p><ul><li>Other archetypes (<code>scheduler</code>, <code>kanban</code>, <code>tree</code>, …) follow the same mechanism with</li></ul><p> their respective fields (see <code>mapOptions</code>/<code>kanbanOptions</code>/<code>schedulerOptions</code>).</p><p>Verified: «pie chart of population by province» → pie with data; «cities on a map</p><p>colored by province» → Google Map with one marker per city.</p><p>&gt; ⚠️ Designer full-UI test: each prompt must run on a <strong>clean conversation</strong> (the</p><p>&gt; chatbot persists the session id in <code>localStorage[&#39;wuic_rag_last_session_id&#39;]</code> and</p><p>&gt; restores it on boot — without reset, one prompt&#39;s history contaminates the next).</p><p>&gt; It also requires the backend in <code>ASPNETCORE_ENVIRONMENT=Development</code> (CORS for the</p><p>&gt; dev frontend) + the frontend running.</p><h2>Operational pattern</h2><p>1. The user opens the chatbot (FAB bottom right) and writes the prompt.</p><p>2. The LLM analyzes the request + the injected <code>routeContext</code> (current route,</p><p> table, columns) and emits the <code>tool_use</code> with the appropriate <code>kind</code>.</p><p>3. The proposal is shown in a card with editable fields:</p><p> - NON-body fields (class, label, target, etc.) as <code>&lt;input&gt;</code> text.</p><p> - The callback body as <code>&lt;textarea&gt;</code> or advanced Monaco editor.</p><p>4. The user can edit both before clicking &quot;Apply&quot;.</p><p>5. On click &quot;Apply&quot;:</p><p> - If the proposal has been edited: <code>POST /api/Rag/UpdateProposedAction</code> rewrites</p><p> the <code>proposed_action_json</code> in the message DB row.</p><p> - <code>POST /api/Rag/ApplyAction</code> reads the proposal back from DB and applies the</p><p> side-effects on WUIC metadata + calls</p><p> <code>InvalidateMetadataCachesAndSetVersion</code> so the next refresh of the target</p><p> route sees the change.</p><h2>Docs-driven tests</h2><p>The <code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> file defines</p><p>the <code>TOOLS</code> array with one entry per <code>kind</code>. Each entry has:</p><ul><li><code>kind</code> — value expected in <code>proposed_action_json</code>;</li><li><code>prompt</code> — exact text sent to the LLM;</li><li><code>assertFields</code> — minimum fields the proposal must contain;</li><li><code>verify(api, mdId)</code> — DB post-apply check (metadata row exists);</li><li><code>verifyDom(page)</code> — runtime check (visible effect in <code>/cities/list</code>);</li><li><code>cleanup(api, mdId)</code> — surgical rollback (DELETE / UPDATE NULL).</li></ul><p>To rerun only some kinds: <code>WUIC_RAG_TOOLS_ONLY=row_action,table_style npm run test:docs</code>.</p><h2>References</h2><ul><li>Component: <code>wuic-framework-lib/src/lib/component/rag-chatbot/rag-chatbot.component.ts</code></li><li>Backend dispatcher: <code>KonvergenceCore/Controllers/RagController.cs</code> (<code>ApplyAction</code>)</li><li>Repository: <code>KonvergenceCore/Services/RagChat/RagChatRepository.cs</code></li><li>Tool definition JSON: <code>codebase_embeddings/onnx_export/rag_tools.json</code> (passed to the LLM)</li><li>Callback cookbook: <a href=\"callback-cookbook.md\">docs/pages/callback-cookbook.md</a></li><li>Main page: <a href=\"rag-chatbot.md\">docs/pages/rag-chatbot.md</a></li></ul>",
12640
12648
  "codeSamples": []
12641
12649
  }
12642
12650
  ],
@@ -18479,14 +18487,18 @@ const frameworkDocsContent = {
18479
18487
  },
18480
18488
  {
18481
18489
  "id": "sec_3",
18482
- "title": "Pattern opérationnel"
18490
+ "title": "Robustesse du routage — variantes non triviales"
18483
18491
  },
18484
18492
  {
18485
18493
  "id": "sec_4",
18486
- "title": "Tests docs-driven"
18494
+ "title": "Pattern opérationnel"
18487
18495
  },
18488
18496
  {
18489
18497
  "id": "sec_5",
18498
+ "title": "Tests docs-driven"
18499
+ },
18500
+ {
18501
+ "id": "sec_6",
18490
18502
  "title": "Références"
18491
18503
  }
18492
18504
  ],
@@ -18494,7 +18506,7 @@ const frameworkDocsContent = {
18494
18506
  {
18495
18507
  "id": "overview",
18496
18508
  "title": "Overview",
18497
- "html": "<h1>RAG Chatbot — Catalogue des outils</h1><p>Catalogue des &quot;outils&quot; que le LLM Anthropic peut invoquer via <code>tool_use</code> lorsque</p><p>l&#39;utilisateur fait une requête dans le composant <code>&lt;wuic-rag-chatbot&gt;</code>. Pour chaque</p><p>outil : le <code>kind</code> émis dans <code>proposed_action_json</code>, un **prompt utilisateur</p><p>canonique** qui le déclenche, les champs minimaux requis et les métadonnées WUIC</p><p>modifiées au clic &quot;Appliquer&quot;.</p><p>Chaque outil a une règle de test end-to-end correspondante dans</p><p><code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> (source de vérité</p><p>unique : si le <code>prompt</code> change ici mais pas là, le test échoue). Les prompts</p><p>canoniques ci-dessous sont EXACTEMENT ceux du test — ils sont en italien parce</p><p>que le test a été écrit ainsi ; le LLM accepte les mêmes questions dans n&#39;importe</p><p>quelle langue.</p><h2>Index</h2><table><thead><tr><th>Kind</th><th>Description</th><th>Métadonnées cible</th></tr></thead><tbody><tr><td><code>toolbar_action</code></td><td>Ajoute un bouton à la barre d&#39;outils de la list-grid</td><td><code>_mtdt__cstom__actions__tabelle</code></td></tr><tr><td><code>row_action</code></td><td>Ajoute un bouton-action sur la ligne individuelle</td><td><code>_metadati__colonne</code> (mc_voa_class=6)</td></tr><tr><td><code>table_style</code></td><td>Style conditionnel (classe CSS ou inline) sur toute la <code>&lt;tr&gt;</code></td><td><code>_metadati__u_i__stili__tabelle</code></td></tr><tr><td><code>column_style</code></td><td>Style conditionnel sur la cellule individuelle</td><td><code>_metadati__u_i__stili__colonne</code></td></tr><tr><td><code>display_formula</code></td><td>Template Angular pour la cellule (format compact, badge, lien)</td><td><code>_metadati__colonne.mc_ui_grid_column_data_template</code></td></tr><tr><td><code>form_title_formula</code></td><td>Titre dynamique du formulaire d&#39;édition</td><td><code>_metadati__tabelle.md_display_formula</code></td></tr><tr><td><code>default_value_callback</code></td><td>Valeur par défaut JS pour un champ en INSERT</td><td><code>_metadati__colonne.mc_default_value_callback</code></td></tr><tr><td><code>custom_validation</code></td><td>Validation bloquante avant save</td><td><code>_metadati__colonne.mc_validation_custom_callback</code></td></tr><tr><td><code>selection_changed</code></td><td>Déclencheur au changement d&#39;un lookup</td><td><code>_metadati__colonne.mc_selection_changed_custom_function</code></td></tr><tr><td><code>lifecycle_callback</code></td><td>Hooks <code>before_save</code> / <code>after_save</code> / <code>after_load</code></td><td><code>_metadati__tabelle.md_before_save</code> (et al)</td></tr><tr><td><code>simple_metadata_update</code></td><td>Mise à jour générique d&#39;un champ simple (label, pagesize, flag)</td><td><code>_metadati__tabelle</code> / <code>_metadati__colonne</code></td></tr><tr><td><code>metadata_column_create</code></td><td>Crée une nouvelle colonne de métadonnées (calculée autorisée)</td><td><code>_metadati__colonne</code> (INSERT)</td></tr><tr><td><code>sql_metadata_field</code></td><td>Écrit un snippet SQL sur un champ gated de la table métadonnées (super-admin)</td><td><code>_metadati__tabelle.md_join_override</code> et al</td></tr><tr><td><code>designer_inject</code></td><td>Injecte outil/widget sur le canvas du dashboard designer (client-only, no backend)</td><td>dashboard designer state</td></tr></tbody></table><h2>Exemples de prompts canoniques</h2><h3><code>toolbar_action</code> — Bouton bulk sur la barre d&#39;outils</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities aggiungi un&#39;azione di toolbar &#39;Archivia selezionate&#39; (icona pi pi-archive) che archivia in bulk i record selezionati via POST /api/cities/bulk-archive con i loro id. Richiedi conferma prima.</p><p>Champs requis dans la proposition : <code>route</code>, <code>label</code>, <code>callback_js</code>. Optionnel</p><p><code>requires_multi_selection:true</code> si le callback opère sur</p><p><code>datasource.getSelectedRows()</code> (le backend active <code>md_multiple_selection</code> sur la</p><p>route dans ce cas).</p><h3><code>row_action</code> — Bouton sur la ligne individuelle</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities aggiungi un button di riga &#39;Genera PDF&#39; (icona pi pi-file-pdf) che apre /api/cities/{id}/pdf in nuova tab con l&#39;id della riga.</p><p>Requis : <code>route</code>, <code>label</code>, <code>callback_js</code>. Le scope du callback a <code>record</code></p><p>(BehaviorSubject map: <code>record.id?.value</code>).</p><h3><code>table_style</code> — Style de ligne conditionnel</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities colora in rosso (classe row-danger) le righe con LatestRecordedPopulation inferiore a 1000.</p><p>Requis : <code>route</code>, <code>css_class</code>, <code>condition_js</code>. Pour les couleurs de la palette</p><p>prédéfinie utilisez une classe (<code>row-danger</code> / <code>row-warning</code> / <code>row-info</code> /</p><p><code>row-success</code>). Pour les couleurs arbitraires émettez <code>css_class</code> avec préfixe</p><p><code>style:</code> (ex. <code>style:background-color:#9b59b6;color:white</code>) — le framework injecte</p><p>automatiquement <code>!important</code> et neutralise le bg des <code>&lt;td&gt;</code> pour que la couleur</p><p>parvienne à l&#39;œil.</p><h3><code>column_style</code> — Style de cellule conditionnel</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities colora di rosso SOLO la cella LatestRecordedPopulation (classe cell-danger) quando il valore e&#39; inferiore a 1000, NON l&#39;intera riga.</p><p>Requis : <code>route</code>, <code>column_name</code>, <code>css_class</code>, <code>condition_js</code>.</p><h3><code>display_formula</code> — Template de cellule</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities, formatta la colonna LatestRecordedPopulation in formato compatto k/M (es. 12500 -&gt; 12.5k, 1500000 -&gt; 1.5M).</p><p>Requis : <code>route</code>, <code>column_name</code>, <code>template_html</code>. Scope : <code>rowData</code> (objet plat,</p><p>PAS BehaviorSubject). Supporte les pipes Angular standard (<code>number:&#39;1.1-1&#39;</code>,</p><p><code>date:</code>, <code>currency:</code>, etc.), interpolation <code>{{ rowData.x }}</code>, ternaire inline,</p><p><code>*ngIf</code>.</p><h3><code>form_title_formula</code> — Titre dynamique du formulaire d&#39;édition</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities imposta il titolo dinamico del form di edit a &#39;Modifica &lt;CityName&gt;&#39; dove &lt;CityName&gt; e&#39; il nome della citta&#39;, oppure &#39;Nuova citta&#39; su INSERT.</p><p>Requis : <code>route</code>, <code>formula_js</code>. Scope : <code>metaInfo</code>, <code>record</code>, <code>datasource</code>,</p><p><code>wtoolbox</code>. Doit retourner <code>string</code>.</p><h3><code>default_value_callback</code> — Valeur par défaut en INSERT</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities, la colonna CityName deve avere come valore di default in inserimento la stringa &#39;Nuova citta&#39;.</p><p>Requis : <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Le scope du callback a <code>record</code></p><p>(NOUVEAU record, plain object), <code>field</code> (<code>MetadatiColonna</code> cible), <code>metaInfo</code>,</p><p><code>wtoolbox</code>. Le <code>return</code> est ignoré — le callback DOIT écrire la valeur dans</p><p><code>record[field.mc_nome_colonna]</code>.</p><h3><code>custom_validation</code> — Validation bloquante</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities valida che LatestRecordedPopulation non sia negativo. Blocca il save con messaggio &#39;La popolazione non puo essere negativa&#39;.</p><p>Requis : <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope : <code>record</code> (BehaviorSubject</p><p>map), <code>field</code>, <code>vr</code> (ValidationRule : définissez <code>vr.message=&#39;...&#39;</code>), <code>wtoolbox</code>.</p><p>Doit retourner <code>boolean</code> : <code>true</code> si valide, <code>false</code> pour bloquer.</p><h3><code>selection_changed</code> — Déclencheur au changement d&#39;un lookup</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities, al cambio del campo StateProvinceID (selection_changed), normalizza il valore tutto in maiuscolo via record.StateProvinceID.next(...).</p><p>Requis : <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope : <code>record</code>, <code>value</code>,</p><p><code>datasource</code>, <code>wtoolbox</code>. L&#39;objet lookup résolu est dans</p><p><code>record.&lt;col&gt;__lookup_obj.value</code>.</p><h3><code>lifecycle_callback</code> — Hooks before/after save/load</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities, prima del save (event=before_save) metti il campo CityName in maiuscolo via record.CityName.next(record.CityName.value.toUpperCase()).</p><p>Requis : <code>route</code>, <code>event</code> (<code>before_save</code> / <code>after_save</code> / <code>after_load</code>),</p><p><code>callback_js</code>. Scope : <code>record</code> (BehaviorSubject map), <code>datasource</code>, <code>wtoolbox</code>.</p><h3><code>simple_metadata_update</code> — Mise à jour de champs simples</h3><p><strong>Prompt utilisateur (exemple A — label colonne) :</strong></p><p>&gt; Modifica il titolo della colonna LatestRecordedPopulation in &#39;Official Population&#39; su cities.</p><p><strong>Prompt utilisateur (exemple B — pagesize tabella) :</strong></p><p>&gt; Imposta il pagesize della tabella cities a 50.</p><p>Requis : <code>route</code>, <code>target</code> (<code>column</code> / <code>table</code>), <code>field_label</code> (label friendly),</p><p><code>value</code>. Le backend mappe le <code>field_label</code> au champ SQL physique via une map</p><p>côté serveur (environ 29 champs couverts : header label, page size, hide-in-list,</p><p>etc.).</p><h3><code>metadata_column_create</code> — Créer une colonne de métadonnées</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities crea una colonna calcolata <code>e2e_total_chars</code> di tipo number che calcola LEN sul CityName. La formula a livello metadato deve essere LEN([Application].[Cities].[CityName]) e la colonna deve essere is_computed=true (non fisica nel DB).</p><p>Requis : <code>route</code>, <code>column_name</code>, <code>ui_column_type</code>, <code>is_computed</code>. Quand</p><p><code>is_computed=true</code> aussi <code>computed_formula</code> doit être présent.</p><h3><code>sql_metadata_field</code> — Snippet SQL sur un champ gated (super-admin)</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities applica al campo md_join_override il join SQL: LEFT JOIN [Application].[People] AS [e2e_test_people] ON 1=0. E&#39; un test e2e no-op, scopo di verificare che il tool propose_sql_metadata_field scriva correttamente in _metadati__tabelle.mdjoinoverride. Dbms target: mssql.</p><p>Requis : <code>target_table</code>, <code>target_row_key</code>, <code>field_name</code>, <code>sql_snippet</code>,</p><p><code>dbms_target</code>. Auth : super-admin (D3 gate). Le prompt utilisateur DOIT spécifier</p><p>le dbms cible — le backend ne le déduit pas.</p><h3><code>designer_inject</code> — Injection d&#39;outil dans le dashboard designer</h3><p>Kind client-only : appliqué non par le backend mais par un handler enregistré par</p><p>le <code>designer.component</code> via <code>ChatbotHostRegistryService</code>. Voir</p><p><code>docs/pages/_internal/designer-tool-catalog.md</code> pour le catalogue de palette</p><p>d&#39;outils.</p><h2>Pattern opérationnel</h2><p>1. L&#39;utilisateur ouvre le chatbot (FAB en bas à droite) et écrit le prompt.</p><p>2. Le LLM analyse la requête + le <code>routeContext</code> injecté (route courante, table,</p><p> colonnes) et émet le <code>tool_use</code> avec le <code>kind</code> approprié.</p><p>3. La proposition est affichée dans une carte avec champs éditables :</p><p> - Champs NON-body (classe, label, target, etc.) comme <code>&lt;input&gt;</code> texte.</p><p> - Le body du callback comme <code>&lt;textarea&gt;</code> ou éditeur Monaco avancé.</p><p>4. L&#39;utilisateur peut éditer les deux avant de cliquer &quot;Appliquer&quot;.</p><p>5. Au clic &quot;Appliquer&quot; :</p><p> - Si la proposition a été éditée : <code>POST /api/Rag/UpdateProposedAction</code> réécrit</p><p> le <code>proposed_action_json</code> dans la ligne DB du message.</p><p> - <code>POST /api/Rag/ApplyAction</code> relit la proposition depuis le DB et applique</p><p> les side-effects sur les métadonnées WUIC + appelle</p><p> <code>InvalidateMetadataCachesAndSetVersion</code> pour que le refresh suivant de la</p><p> route cible voie le changement.</p><h2>Tests docs-driven</h2><p>Le fichier <code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> définit</p><p>l&#39;array <code>TOOLS</code> avec une entrée par <code>kind</code>. Chaque entrée a :</p><ul><li><code>kind</code> — valeur attendue dans <code>proposed_action_json</code> ;</li><li><code>prompt</code> — texte exact envoyé au LLM ;</li><li><code>assertFields</code> — champs minimaux que la proposition doit contenir ;</li><li><code>verify(api, mdId)</code> — vérification DB post-apply (la ligne metadata existe) ;</li><li><code>verifyDom(page)</code> — vérification runtime (effet visible dans <code>/cities/list</code>) ;</li><li><code>cleanup(api, mdId)</code> — rollback chirurgical (DELETE / UPDATE NULL).</li></ul><p>Pour relancer seulement certains kinds : <code>WUIC_RAG_TOOLS_ONLY=row_action,table_style npm run test:docs</code>.</p><h2>Références</h2><ul><li>Composant : <code>wuic-framework-lib/src/lib/component/rag-chatbot/rag-chatbot.component.ts</code></li><li>Backend dispatcher : <code>KonvergenceCore/Controllers/RagController.cs</code> (<code>ApplyAction</code>)</li><li>Repository : <code>KonvergenceCore/Services/RagChat/RagChatRepository.cs</code></li><li>Tool definition JSON : <code>codebase_embeddings/onnx_export/rag_tools.json</code> (passé au LLM)</li><li>Cookbook callback : <a href=\"callback-cookbook.md\">docs/pages/callback-cookbook.md</a></li><li>Page principale : <a href=\"rag-chatbot.md\">docs/pages/rag-chatbot.md</a></li></ul>",
18509
+ "html": "<h1>RAG Chatbot — Catalogue des outils</h1><p>Catalogue des &quot;outils&quot; que le LLM Anthropic peut invoquer via <code>tool_use</code> lorsque</p><p>l&#39;utilisateur fait une requête dans le composant <code>&lt;wuic-rag-chatbot&gt;</code>. Pour chaque</p><p>outil : le <code>kind</code> émis dans <code>proposed_action_json</code>, un **prompt utilisateur</p><p>canonique** qui le déclenche, les champs minimaux requis et les métadonnées WUIC</p><p>modifiées au clic &quot;Appliquer&quot;.</p><p>Chaque outil a une règle de test end-to-end correspondante dans</p><p><code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> (source de vérité</p><p>unique : si le <code>prompt</code> change ici mais pas là, le test échoue). Les prompts</p><p>canoniques ci-dessous sont EXACTEMENT ceux du test — ils sont en italien parce</p><p>que le test a été écrit ainsi ; le LLM accepte les mêmes questions dans n&#39;importe</p><p>quelle langue.</p><h2>Index</h2><table><thead><tr><th>Kind</th><th>Description</th><th>Métadonnées cible</th></tr></thead><tbody><tr><td><code>toolbar_action</code></td><td>Ajoute un bouton à la barre d&#39;outils de la list-grid</td><td><code>_mtdt__cstom__actions__tabelle</code></td></tr><tr><td><code>row_action</code></td><td>Ajoute un bouton-action sur la ligne individuelle</td><td><code>_metadati__colonne</code> (mc_voa_class=6)</td></tr><tr><td><code>table_style</code></td><td>Style conditionnel (classe CSS ou inline) sur toute la <code>&lt;tr&gt;</code></td><td><code>_metadati__u_i__stili__tabelle</code></td></tr><tr><td><code>column_style</code></td><td>Style conditionnel sur la cellule individuelle</td><td><code>_metadati__u_i__stili__colonne</code></td></tr><tr><td><code>display_formula</code></td><td>Template Angular pour la cellule (format compact, badge, lien)</td><td><code>_metadati__colonne.mc_ui_grid_column_data_template</code></td></tr><tr><td><code>form_title_formula</code></td><td>Titre dynamique du formulaire d&#39;édition</td><td><code>_metadati__tabelle.md_display_formula</code></td></tr><tr><td><code>default_value_callback</code></td><td>Valeur par défaut JS pour un champ en INSERT</td><td><code>_metadati__colonne.mc_default_value_callback</code></td></tr><tr><td><code>custom_validation</code></td><td>Validation bloquante avant save</td><td><code>_metadati__colonne.mc_validation_custom_callback</code></td></tr><tr><td><code>selection_changed</code></td><td>Déclencheur au changement d&#39;un lookup</td><td><code>_metadati__colonne.mc_selection_changed_custom_function</code></td></tr><tr><td><code>lifecycle_callback</code></td><td>Hooks <code>before_save</code> / <code>after_save</code> / <code>after_load</code></td><td><code>_metadati__tabelle.md_before_save</code> (et al)</td></tr><tr><td><code>simple_metadata_update</code></td><td>Mise à jour générique d&#39;un champ simple (label, pagesize, flag)</td><td><code>_metadati__tabelle</code> / <code>_metadati__colonne</code></td></tr><tr><td><code>metadata_column_create</code></td><td>Crée une nouvelle colonne de métadonnées (calculée autorisée)</td><td><code>_metadati__colonne</code> (INSERT)</td></tr><tr><td><code>sql_metadata_field</code></td><td>Écrit un snippet SQL sur un champ gated de la table métadonnées (super-admin)</td><td><code>_metadati__tabelle.md_join_override</code> et al</td></tr><tr><td><code>designer_inject</code></td><td>Injecte outil/widget sur le canvas du dashboard designer (client-only, no backend)</td><td>dashboard designer state</td></tr></tbody></table><h2>Exemples de prompts canoniques</h2><h3><code>toolbar_action</code> — Bouton bulk sur la barre d&#39;outils</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities aggiungi un&#39;azione di toolbar &#39;Archivia selezionate&#39; (icona pi pi-archive) che archivia in bulk i record selezionati via POST /api/cities/bulk-archive con i loro id. Richiedi conferma prima.</p><p>Champs requis dans la proposition : <code>route</code>, <code>label</code>, <code>callback_js</code>. Optionnel</p><p><code>requires_multi_selection:true</code> si le callback opère sur</p><p><code>datasource.getSelectedRows()</code> (le backend active <code>md_multiple_selection</code> sur la</p><p>route dans ce cas).</p><h3><code>row_action</code> — Bouton sur la ligne individuelle</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities aggiungi un button di riga &#39;Genera PDF&#39; (icona pi pi-file-pdf) che apre /api/cities/{id}/pdf in nuova tab con l&#39;id della riga.</p><p>Requis : <code>route</code>, <code>label</code>, <code>callback_js</code>. Le scope du callback a <code>record</code></p><p>(BehaviorSubject map: <code>record.id?.value</code>).</p><h3><code>table_style</code> — Style de ligne conditionnel</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities colora in rosso (classe row-danger) le righe con LatestRecordedPopulation inferiore a 1000.</p><p>Requis : <code>route</code>, <code>css_class</code>, <code>condition_js</code>. Pour les couleurs de la palette</p><p>prédéfinie utilisez une classe (<code>row-danger</code> / <code>row-warning</code> / <code>row-info</code> /</p><p><code>row-success</code>). Pour les couleurs arbitraires émettez <code>css_class</code> avec préfixe</p><p><code>style:</code> (ex. <code>style:background-color:#9b59b6;color:white</code>) — le framework injecte</p><p>automatiquement <code>!important</code> et neutralise le bg des <code>&lt;td&gt;</code> pour que la couleur</p><p>parvienne à l&#39;œil.</p><h3><code>column_style</code> — Style de cellule conditionnel</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities colora di rosso SOLO la cella LatestRecordedPopulation (classe cell-danger) quando il valore e&#39; inferiore a 1000, NON l&#39;intera riga.</p><p>Requis : <code>route</code>, <code>column_name</code>, <code>css_class</code>, <code>condition_js</code>.</p><h3><code>display_formula</code> — Template de cellule</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities, formatta la colonna LatestRecordedPopulation in formato compatto k/M (es. 12500 -&gt; 12.5k, 1500000 -&gt; 1.5M).</p><p>Requis : <code>route</code>, <code>column_name</code>, <code>template_html</code>. Scope : <code>rowData</code> (objet plat,</p><p>PAS BehaviorSubject). Supporte les pipes Angular standard (<code>number:&#39;1.1-1&#39;</code>,</p><p><code>date:</code>, <code>currency:</code>, etc.), interpolation <code>{{ rowData.x }}</code>, ternaire inline,</p><p><code>*ngIf</code>.</p><h3><code>form_title_formula</code> — Titre dynamique du formulaire d&#39;édition</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities imposta il titolo dinamico del form di edit a &#39;Modifica &lt;CityName&gt;&#39; dove &lt;CityName&gt; e&#39; il nome della citta&#39;, oppure &#39;Nuova citta&#39; su INSERT.</p><p>Requis : <code>route</code>, <code>formula_js</code>. Scope : <code>metaInfo</code>, <code>record</code>, <code>datasource</code>,</p><p><code>wtoolbox</code>. Doit retourner <code>string</code>.</p><h3><code>default_value_callback</code> — Valeur par défaut en INSERT</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities, la colonna CityName deve avere come valore di default in inserimento la stringa &#39;Nuova citta&#39;.</p><p>Requis : <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Le scope du callback a <code>record</code></p><p>(NOUVEAU record, plain object), <code>field</code> (<code>MetadatiColonna</code> cible), <code>metaInfo</code>,</p><p><code>wtoolbox</code>. Le <code>return</code> est ignoré — le callback DOIT écrire la valeur dans</p><p><code>record[field.mc_nome_colonna]</code>.</p><h3><code>custom_validation</code> — Validation bloquante</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities valida che LatestRecordedPopulation non sia negativo. Blocca il save con messaggio &#39;La popolazione non puo essere negativa&#39;.</p><p>Requis : <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope : <code>record</code> (BehaviorSubject</p><p>map), <code>field</code>, <code>vr</code> (ValidationRule : définissez <code>vr.message=&#39;...&#39;</code>), <code>wtoolbox</code>.</p><p>Doit retourner <code>boolean</code> : <code>true</code> si valide, <code>false</code> pour bloquer.</p><h3><code>selection_changed</code> — Déclencheur au changement d&#39;un lookup</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities, al cambio del campo StateProvinceID (selection_changed), normalizza il valore tutto in maiuscolo via record.StateProvinceID.next(...).</p><p>Requis : <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope : <code>record</code>, <code>value</code>,</p><p><code>datasource</code>, <code>wtoolbox</code>. L&#39;objet lookup résolu est dans</p><p><code>record.&lt;col&gt;__lookup_obj.value</code>.</p><h3><code>lifecycle_callback</code> — Hooks before/after save/load</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities, prima del save (event=before_save) metti il campo CityName in maiuscolo via record.CityName.next(record.CityName.value.toUpperCase()).</p><p>Requis : <code>route</code>, <code>event</code> (<code>before_save</code> / <code>after_save</code> / <code>after_load</code>),</p><p><code>callback_js</code>. Scope : <code>record</code> (BehaviorSubject map), <code>datasource</code>, <code>wtoolbox</code>.</p><h3><code>simple_metadata_update</code> — Mise à jour de champs simples</h3><p><strong>Prompt utilisateur (exemple A — label colonne) :</strong></p><p>&gt; Modifica il titolo della colonna LatestRecordedPopulation in &#39;Official Population&#39; su cities.</p><p><strong>Prompt utilisateur (exemple B — pagesize tabella) :</strong></p><p>&gt; Imposta il pagesize della tabella cities a 50.</p><p>Requis : <code>route</code>, <code>target</code> (<code>column</code> / <code>table</code>), <code>field_label</code> (label friendly),</p><p><code>value</code>. Le backend mappe le <code>field_label</code> au champ SQL physique via une map</p><p>côté serveur (environ 29 champs couverts : header label, page size, hide-in-list,</p><p>etc.).</p><h3><code>metadata_column_create</code> — Créer une colonne de métadonnées</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities crea una colonna calcolata <code>e2e_total_chars</code> di tipo number che calcola LEN sul CityName. La formula a livello metadato deve essere LEN([Application].[Cities].[CityName]) e la colonna deve essere is_computed=true (non fisica nel DB).</p><p>Requis : <code>route</code>, <code>column_name</code>, <code>ui_column_type</code>, <code>is_computed</code>. Quand</p><p><code>is_computed=true</code> aussi <code>computed_formula</code> doit être présent.</p><h3><code>sql_metadata_field</code> — Snippet SQL sur un champ gated (super-admin)</h3><p><strong>Prompt utilisateur :</strong></p><p>&gt; Su cities applica al campo md_join_override il join SQL: LEFT JOIN [Application].[People] AS [e2e_test_people] ON 1=0. E&#39; un test e2e no-op, scopo di verificare che il tool propose_sql_metadata_field scriva correttamente in _metadati__tabelle.mdjoinoverride. Dbms target: mssql.</p><p>Requis : <code>target_table</code>, <code>target_row_key</code>, <code>field_name</code>, <code>sql_snippet</code>,</p><p><code>dbms_target</code>. Auth : super-admin (D3 gate). Le prompt utilisateur DOIT spécifier</p><p>le dbms cible — le backend ne le déduit pas.</p><h3><code>designer_inject</code> — Injection d&#39;outil dans le dashboard designer</h3><p>Kind client-only : appliqué non par le backend mais par un handler enregistré par</p><p>le <code>designer.component</code> via <code>ChatbotHostRegistryService</code>. Voir</p><p><code>docs/pages/_internal/designer-tool-catalog.md</code> pour le catalogue de palette</p><p>d&#39;outils.</p><h2>Robustesse du routage — variantes non triviales</h2><p>Les prompts canoniques ci-dessus sont <strong>un seul exemple par kind</strong>. Le routage de</p><p>l&#39;outil est néanmoins robuste face à des formulations différentes : la suite</p><p><code>playwright/docs/rag-chatbot/rag-chatbot--tools-variations.mjs</code> teste **5+ variantes</p><p>par kind** (reformulations sémantiques, contraintes réalistes, paires</p><p>désambiguïsantes), souvent issues de prompts réels, et mesure le <em>routing-rate</em> sur</p><p>plusieurs répétitions. Le routage s&#39;est révélé <strong>indépendant de la langue</strong></p><p>(l&#39;italien et l&#39;anglais routent de la même manière — <code>WUIC_RAG_VAR_LANG=en</code>).</p><p>Exemples alternatifs qui routent correctement (en plus des canoniques) :</p><ul><li><code>toolbar_action</code> → «put a button at the top of the grid that exports the checked rows to CSV»</li><li><code>row_action</code> → «on each row an eye icon that opens the detail of that row in a new tab»</li><li><code>table_style</code> → «highlight in green the recently modified rows»</li><li><code>column_style</code> → «put a yellow background only on the CityName cell when it is empty»</li><li><code>display_formula</code> → «display the population with the thousands separator»</li><li><code>custom_validation</code> → «prevent saving if ValidTo is earlier than ValidFrom»</li><li><code>simple_metadata_update</code> → «hide the LastEditedBy column from the list» (note : un «filtre par défaut» est un <code>simple_metadata_update</code> avec <code>field_label=default_filter</code>, PAS <code>sql_metadata_field</code>)</li><li><code>metadata_column_create</code> → «add a computed column that divides the population by 1000»</li><li><code>designer_inject</code> → «create a master-detail with 2 grids (provinces and cities)», «compose a 3-column layout with three grids», «put two grids side by side»</li></ul><p><strong>Vrais négatifs (ne DOIVENT PAS émettre d&#39;outil) :</strong> les demandes d&#39;*exemple de</p><p>code* («give me an example of a toolbar action without applying it?») ou les questions</p><p>conceptuelles restent une réponse textuelle sans proposer d&#39;action.</p><h3>Designer — vérification du canvas et limites de la palette</h3><p><code>rag-chatbot--designer-canvas-verify.mjs</code> vérifie que chaque prompt designer produit</p><p><strong>réellement</strong> les composants attendus dans le canvas (histogramme par type, pas</p><p>seulement la présence dans le DOM). La palette du designer expose : <code>DATASOURCE</code>,</p><p><code>SPLITTER</code>, <code>DATAREPEATER</code>, <code>TABLE</code>, <code>KPI</code>, <code>DIV</code>, <code>LABEL</code>, <code>SPAN</code>, <code>CONTAINER</code>,</p><p><code>BUTTON</code>. Les <strong>graphiques</strong> s&#39;obtiennent avec un <code>DATAREPEATER</code> en mode</p><p><code>action: &quot;chart&quot;</code> (il n&#39;existe pas d&#39;outil <code>CHART</code> séparé — le graphique est un</p><p>datarepeater en mode chart, configurable bar/line/pie via «Configura chart»). Autres</p><p><code>action</code> du datarepeater : <code>list</code> (grille, défaut), <code>edit</code>/<code>dialog</code>/<code>detail</code></p><p>(fiche/form), <code>map</code>, <code>scheduler</code>, <code>calendar</code>, <code>kanban</code>, <code>tree</code>, <code>carousel</code>, <code>pivot</code>,</p><p><code>spreadsheet</code>. Les titres textuels sont émis sous forme de <code>LABEL</code>/<code>H1</code>.</p><p>#### Config d&#39;archétype inline (<code>archetype_config</code>)</p><p>Un nœud <code>DATAREPEATER</code> du <code>designer_inject</code> peut porter un champ <code>archetype_config</code> =</p><p>le contenu de <code>md_props_bag.archetypes.&lt;action&gt;</code>. Il est appliqué <strong>en mémoire</strong> au</p><p><code>metaInfo.tableMetadata</code> du datasource lié (aucun appel serveur) via le **même chemin</p><p>que le configurateur de graphique** (<code>customProps_&lt;action&gt;</code> + <code>ds.fetchData()</code> +</p><p><code>propertyTreeBuilder</code>), et persiste à la sérialisation du datasource lors de</p><p>l&#39;enregistrement du dashboard. L&#39;application est différée jusqu&#39;à ce que le datasource</p><p>lié soit matérialisé.</p><ul><li><strong>chart</strong> : <code>dataOptions</code> DOIT inclure <code>dataProperty:&quot;dato&quot;</code> (la clé du tableau de</li></ul><p> données que <code>parseData</code> lit — sans elle, le graphique reste vide) +</p><p> <code>datasets:[{label, labelField:&#39;&lt;col libellés&gt;&#39;, dataField:&#39;&lt;col valeurs&gt;&#39;}]</code>.</p><ul><li><strong>map</strong> : <code>markerColorField</code>, <code>titleField</code>, <code>zoom</code>, <code>center:{lat,lng}</code> (les marqueurs</li></ul><p> prennent leurs coordonnées de la colonne <code>point</code> auto-détectée de la route).</p><ul><li>Les autres archétypes (<code>scheduler</code>, <code>kanban</code>, <code>tree</code>, …) suivent le même mécanisme</li></ul><p> avec leurs champs respectifs (voir <code>mapOptions</code>/<code>kanbanOptions</code>/<code>schedulerOptions</code>).</p><p>Vérifié : «pie chart of population by province» → camembert avec données ; «cities on a</p><p>map colored by province» → Google Map avec un marqueur par ville.</p><p>&gt; ⚠️ Test full-UI du designer : chaque prompt doit s&#39;exécuter sur une **conversation</p><p>&gt; propre** (le chatbot persiste l&#39;id de session dans</p><p>&gt; <code>localStorage[&#39;wuic_rag_last_session_id&#39;]</code> et le restaure au boot — sans reset,</p><p>&gt; l&#39;historique d&#39;un prompt contamine le suivant). Il requiert aussi le backend en</p><p>&gt; <code>ASPNETCORE_ENVIRONMENT=Development</code> (CORS pour le frontend dev) + le frontend actif.</p><h2>Pattern opérationnel</h2><p>1. L&#39;utilisateur ouvre le chatbot (FAB en bas à droite) et écrit le prompt.</p><p>2. Le LLM analyse la requête + le <code>routeContext</code> injecté (route courante, table,</p><p> colonnes) et émet le <code>tool_use</code> avec le <code>kind</code> approprié.</p><p>3. La proposition est affichée dans une carte avec champs éditables :</p><p> - Champs NON-body (classe, label, target, etc.) comme <code>&lt;input&gt;</code> texte.</p><p> - Le body du callback comme <code>&lt;textarea&gt;</code> ou éditeur Monaco avancé.</p><p>4. L&#39;utilisateur peut éditer les deux avant de cliquer &quot;Appliquer&quot;.</p><p>5. Au clic &quot;Appliquer&quot; :</p><p> - Si la proposition a été éditée : <code>POST /api/Rag/UpdateProposedAction</code> réécrit</p><p> le <code>proposed_action_json</code> dans la ligne DB du message.</p><p> - <code>POST /api/Rag/ApplyAction</code> relit la proposition depuis le DB et applique</p><p> les side-effects sur les métadonnées WUIC + appelle</p><p> <code>InvalidateMetadataCachesAndSetVersion</code> pour que le refresh suivant de la</p><p> route cible voie le changement.</p><h2>Tests docs-driven</h2><p>Le fichier <code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> définit</p><p>l&#39;array <code>TOOLS</code> avec une entrée par <code>kind</code>. Chaque entrée a :</p><ul><li><code>kind</code> — valeur attendue dans <code>proposed_action_json</code> ;</li><li><code>prompt</code> — texte exact envoyé au LLM ;</li><li><code>assertFields</code> — champs minimaux que la proposition doit contenir ;</li><li><code>verify(api, mdId)</code> — vérification DB post-apply (la ligne metadata existe) ;</li><li><code>verifyDom(page)</code> — vérification runtime (effet visible dans <code>/cities/list</code>) ;</li><li><code>cleanup(api, mdId)</code> — rollback chirurgical (DELETE / UPDATE NULL).</li></ul><p>Pour relancer seulement certains kinds : <code>WUIC_RAG_TOOLS_ONLY=row_action,table_style npm run test:docs</code>.</p><h2>Références</h2><ul><li>Composant : <code>wuic-framework-lib/src/lib/component/rag-chatbot/rag-chatbot.component.ts</code></li><li>Backend dispatcher : <code>KonvergenceCore/Controllers/RagController.cs</code> (<code>ApplyAction</code>)</li><li>Repository : <code>KonvergenceCore/Services/RagChat/RagChatRepository.cs</code></li><li>Tool definition JSON : <code>codebase_embeddings/onnx_export/rag_tools.json</code> (passé au LLM)</li><li>Cookbook callback : <a href=\"callback-cookbook.md\">docs/pages/callback-cookbook.md</a></li><li>Page principale : <a href=\"rag-chatbot.md\">docs/pages/rag-chatbot.md</a></li></ul>",
18498
18510
  "codeSamples": []
18499
18511
  }
18500
18512
  ],
@@ -24337,14 +24349,18 @@ const frameworkDocsContent = {
24337
24349
  },
24338
24350
  {
24339
24351
  "id": "sec_3",
24340
- "title": "Patrón operativo"
24352
+ "title": "Robustez del enrutamiento — variantes no triviales"
24341
24353
  },
24342
24354
  {
24343
24355
  "id": "sec_4",
24344
- "title": "Tests docs-driven"
24356
+ "title": "Patrón operativo"
24345
24357
  },
24346
24358
  {
24347
24359
  "id": "sec_5",
24360
+ "title": "Tests docs-driven"
24361
+ },
24362
+ {
24363
+ "id": "sec_6",
24348
24364
  "title": "Referencias"
24349
24365
  }
24350
24366
  ],
@@ -24352,7 +24368,7 @@ const frameworkDocsContent = {
24352
24368
  {
24353
24369
  "id": "overview",
24354
24370
  "title": "Overview",
24355
- "html": "<h1>RAG Chatbot — Catálogo de herramientas</h1><p>Catálogo de las &quot;herramientas&quot; que el LLM Anthropic puede invocar vía <code>tool_use</code></p><p>cuando el usuario hace una solicitud en el componente <code>&lt;wuic-rag-chatbot&gt;</code>. Para</p><p>cada herramienta: el <code>kind</code> emitido en <code>proposed_action_json</code>, un **prompt de</p><p>usuario canónico** que lo dispara, los campos mínimos requeridos y los metadatos</p><p>WUIC modificados al hacer clic en &quot;Aplicar&quot;.</p><p>Cada herramienta tiene una regla de prueba end-to-end correspondiente en</p><p><code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> (fuente única de</p><p>verdad: si el <code>prompt</code> cambia aquí pero no allí, la prueba falla). Los prompts</p><p>canónicos a continuación son EXACTAMENTE los de la prueba — están en italiano</p><p>porque la prueba se escribió así; el LLM acepta las mismas preguntas en</p><p>cualquier idioma.</p><h2>Índice</h2><table><thead><tr><th>Kind</th><th>Qué hace</th><th>Metadata objetivo</th></tr></thead><tbody><tr><td><code>toolbar_action</code></td><td>Añade un botón a la barra de herramientas del list-grid</td><td><code>_mtdt__cstom__actions__tabelle</code></td></tr><tr><td><code>row_action</code></td><td>Añade un botón-acción en la fila individual</td><td><code>_metadati__colonne</code> (mc_voa_class=6)</td></tr><tr><td><code>table_style</code></td><td>Estilo condicional (clase CSS o inline) en toda la <code>&lt;tr&gt;</code></td><td><code>_metadati__u_i__stili__tabelle</code></td></tr><tr><td><code>column_style</code></td><td>Estilo condicional en la celda individual</td><td><code>_metadati__u_i__stili__colonne</code></td></tr><tr><td><code>display_formula</code></td><td>Template Angular para la celda (formato compacto, badge, link)</td><td><code>_metadati__colonne.mc_ui_grid_column_data_template</code></td></tr><tr><td><code>form_title_formula</code></td><td>Título dinámico del formulario de edición</td><td><code>_metadati__tabelle.md_display_formula</code></td></tr><tr><td><code>default_value_callback</code></td><td>Valor por defecto JS para un campo en INSERT</td><td><code>_metadati__colonne.mc_default_value_callback</code></td></tr><tr><td><code>custom_validation</code></td><td>Validación bloqueante pre-save</td><td><code>_metadati__colonne.mc_validation_custom_callback</code></td></tr><tr><td><code>selection_changed</code></td><td>Trigger al cambio de un lookup</td><td><code>_metadati__colonne.mc_selection_changed_custom_function</code></td></tr><tr><td><code>lifecycle_callback</code></td><td>Hooks <code>before_save</code> / <code>after_save</code> / <code>after_load</code></td><td><code>_metadati__tabelle.md_before_save</code> (et al)</td></tr><tr><td><code>simple_metadata_update</code></td><td>Update genérico de un campo simple (label, pagesize, flag)</td><td><code>_metadati__tabelle</code> / <code>_metadati__colonne</code></td></tr><tr><td><code>metadata_column_create</code></td><td>Crea una nueva columna de metadatos (calculada permitida)</td><td><code>_metadati__colonne</code> (INSERT)</td></tr><tr><td><code>sql_metadata_field</code></td><td>Escribe un snippet SQL en un campo gated de la tabla metadatos (super-admin)</td><td><code>_metadati__tabelle.md_join_override</code> et al</td></tr><tr><td><code>designer_inject</code></td><td>Inyecta herramienta/widget en el canvas del dashboard designer (client-only, sin backend)</td><td>dashboard designer state</td></tr></tbody></table><h2>Ejemplos de prompts canónicos</h2><h3><code>toolbar_action</code> — Botón bulk en la barra de herramientas</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities aggiungi un&#39;azione di toolbar &#39;Archivia selezionate&#39; (icona pi pi-archive) che archivia in bulk i record selezionati via POST /api/cities/bulk-archive con i loro id. Richiedi conferma prima.</p><p>Campos requeridos en la propuesta: <code>route</code>, <code>label</code>, <code>callback_js</code>. Opcional</p><p><code>requires_multi_selection:true</code> si el callback opera en</p><p><code>datasource.getSelectedRows()</code> (el backend habilita <code>md_multiple_selection</code> en la</p><p>ruta en ese caso).</p><h3><code>row_action</code> — Botón en la fila individual</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities aggiungi un button di riga &#39;Genera PDF&#39; (icona pi pi-file-pdf) che apre /api/cities/{id}/pdf in nuova tab con l&#39;id della riga.</p><p>Requerido: <code>route</code>, <code>label</code>, <code>callback_js</code>. El scope del callback tiene <code>record</code></p><p>(BehaviorSubject map: <code>record.id?.value</code>).</p><h3><code>table_style</code> — Estilo condicional de fila</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities colora in rosso (classe row-danger) le righe con LatestRecordedPopulation inferiore a 1000.</p><p>Requerido: <code>route</code>, <code>css_class</code>, <code>condition_js</code>. Para colores de la paleta</p><p>predefinida usa una clase (<code>row-danger</code> / <code>row-warning</code> / <code>row-info</code> /</p><p><code>row-success</code>). Para colores arbitrarios emite <code>css_class</code> con prefijo <code>style:</code></p><p>(ej. <code>style:background-color:#9b59b6;color:white</code>) — el framework inyecta</p><p>automáticamente <code>!important</code> y neutraliza el bg de los <code>&lt;td&gt;</code> para que el color</p><p>llegue al ojo.</p><h3><code>column_style</code> — Estilo condicional de celda</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities colora di rosso SOLO la cella LatestRecordedPopulation (classe cell-danger) quando il valore e&#39; inferiore a 1000, NON l&#39;intera riga.</p><p>Requerido: <code>route</code>, <code>column_name</code>, <code>css_class</code>, <code>condition_js</code>.</p><h3><code>display_formula</code> — Template de celda</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities, formatta la colonna LatestRecordedPopulation in formato compatto k/M (es. 12500 -&gt; 12.5k, 1500000 -&gt; 1.5M).</p><p>Requerido: <code>route</code>, <code>column_name</code>, <code>template_html</code>. Scope: <code>rowData</code> (objeto</p><p>plano, NO BehaviorSubject). Soporta pipes Angular estándar (<code>number:&#39;1.1-1&#39;</code>,</p><p><code>date:</code>, <code>currency:</code>, etc.), interpolación <code>{{ rowData.x }}</code>, ternario inline,</p><p><code>*ngIf</code>.</p><h3><code>form_title_formula</code> — Título dinámico del form de edición</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities imposta il titolo dinamico del form di edit a &#39;Modifica &lt;CityName&gt;&#39; dove &lt;CityName&gt; e&#39; il nome della citta&#39;, oppure &#39;Nuova citta&#39; su INSERT.</p><p>Requerido: <code>route</code>, <code>formula_js</code>. Scope: <code>metaInfo</code>, <code>record</code>, <code>datasource</code>,</p><p><code>wtoolbox</code>. Debe retornar <code>string</code>.</p><h3><code>default_value_callback</code> — Valor por defecto en INSERT</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities, la colonna CityName deve avere come valore di default in inserimento la stringa &#39;Nuova citta&#39;.</p><p>Requerido: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. El scope del callback tiene</p><p><code>record</code> (NUEVO record, plain object), <code>field</code> (<code>MetadatiColonna</code> objetivo),</p><p><code>metaInfo</code>, <code>wtoolbox</code>. El <code>return</code> se ignora — el callback DEBE escribir el</p><p>valor en <code>record[field.mc_nome_colonna]</code>.</p><h3><code>custom_validation</code> — Validación bloqueante</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities valida che LatestRecordedPopulation non sia negativo. Blocca il save con messaggio &#39;La popolazione non puo essere negativa&#39;.</p><p>Requerido: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope: <code>record</code> (BehaviorSubject</p><p>map), <code>field</code>, <code>vr</code> (ValidationRule: establece <code>vr.message=&#39;...&#39;</code>), <code>wtoolbox</code>.</p><p>Debe retornar <code>boolean</code>: <code>true</code> si válido, <code>false</code> para bloquear.</p><h3><code>selection_changed</code> — Trigger al cambio de un lookup</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities, al cambio del campo StateProvinceID (selection_changed), normalizza il valore tutto in maiuscolo via record.StateProvinceID.next(...).</p><p>Requerido: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope: <code>record</code>, <code>value</code>,</p><p><code>datasource</code>, <code>wtoolbox</code>. El objeto lookup resuelto está en</p><p><code>record.&lt;col&gt;__lookup_obj.value</code>.</p><h3><code>lifecycle_callback</code> — Hooks before/after save/load</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities, prima del save (event=before_save) metti il campo CityName in maiuscolo via record.CityName.next(record.CityName.value.toUpperCase()).</p><p>Requerido: <code>route</code>, <code>event</code> (<code>before_save</code> / <code>after_save</code> / <code>after_load</code>),</p><p><code>callback_js</code>. Scope: <code>record</code> (BehaviorSubject map), <code>datasource</code>, <code>wtoolbox</code>.</p><h3><code>simple_metadata_update</code> — Update de campos simples</h3><p><strong>Prompt del usuario (ejemplo A — label de columna):</strong></p><p>&gt; Modifica il titolo della colonna LatestRecordedPopulation in &#39;Official Population&#39; su cities.</p><p><strong>Prompt del usuario (ejemplo B — pagesize de tabla):</strong></p><p>&gt; Imposta il pagesize della tabella cities a 50.</p><p>Requerido: <code>route</code>, <code>target</code> (<code>column</code> / <code>table</code>), <code>field_label</code> (label</p><p>amigable), <code>value</code>. El backend mapea el <code>field_label</code> al campo SQL físico vía</p><p>un mapa server-side (alrededor de 29 campos cubiertos: header label, page size,</p><p>hide-in-list, etc.).</p><h3><code>metadata_column_create</code> — Crear columna de metadatos</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities crea una colonna calcolata <code>e2e_total_chars</code> di tipo number che calcola LEN sul CityName. La formula a livello metadato deve essere LEN([Application].[Cities].[CityName]) e la colonna deve essere is_computed=true (non fisica nel DB).</p><p>Requerido: <code>route</code>, <code>column_name</code>, <code>ui_column_type</code>, <code>is_computed</code>. Cuando</p><p><code>is_computed=true</code> también <code>computed_formula</code> debe estar presente.</p><h3><code>sql_metadata_field</code> — Snippet SQL en campo gated (super-admin)</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities applica al campo md_join_override il join SQL: LEFT JOIN [Application].[People] AS [e2e_test_people] ON 1=0. E&#39; un test e2e no-op, scopo di verificare che il tool propose_sql_metadata_field scriva correttamente in _metadati__tabelle.mdjoinoverride. Dbms target: mssql.</p><p>Requerido: <code>target_table</code>, <code>target_row_key</code>, <code>field_name</code>, <code>sql_snippet</code>,</p><p><code>dbms_target</code>. Auth: super-admin (D3 gate). El prompt del usuario DEBE</p><p>especificar el dbms objetivo — el backend no lo deduce.</p><h3><code>designer_inject</code> — Inyección de herramienta en el dashboard designer</h3><p>Kind client-only: aplicado no por el backend sino por un handler registrado por</p><p><code>designer.component</code> vía <code>ChatbotHostRegistryService</code>. Ver</p><p><code>docs/pages/_internal/designer-tool-catalog.md</code> para el catálogo de paleta de</p><p>herramientas.</p><h2>Patrón operativo</h2><p>1. El usuario abre el chatbot (FAB abajo a la derecha) y escribe el prompt.</p><p>2. El LLM analiza la solicitud + el <code>routeContext</code> inyectado (ruta actual, tabla,</p><p> columnas) y emite el <code>tool_use</code> con el <code>kind</code> apropiado.</p><p>3. La propuesta se muestra en una tarjeta con campos editables:</p><p> - Campos NON-body (clase, label, target, etc.) como <code>&lt;input&gt;</code> de texto.</p><p> - El body del callback como <code>&lt;textarea&gt;</code> o editor Monaco avanzado.</p><p>4. El usuario puede editar ambos antes de hacer clic en &quot;Aplicar&quot;.</p><p>5. Al hacer clic en &quot;Aplicar&quot;:</p><p> - Si la propuesta ha sido editada: <code>POST /api/Rag/UpdateProposedAction</code></p><p> reescribe el <code>proposed_action_json</code> en la fila DB del mensaje.</p><p> - <code>POST /api/Rag/ApplyAction</code> relee la propuesta desde el DB y aplica los</p><p> side-effects en los metadatos WUIC + llama a</p><p> <code>InvalidateMetadataCachesAndSetVersion</code> para que el siguiente refresh de la</p><p> ruta objetivo vea el cambio.</p><h2>Tests docs-driven</h2><p>El archivo <code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> define</p><p>el array <code>TOOLS</code> con una entrada por <code>kind</code>. Cada entrada tiene:</p><ul><li><code>kind</code> — valor esperado en <code>proposed_action_json</code>;</li><li><code>prompt</code> — texto exacto enviado al LLM;</li><li><code>assertFields</code> — campos mínimos que la propuesta debe contener;</li><li><code>verify(api, mdId)</code> — verificación DB post-apply (la fila metadata existe);</li><li><code>verifyDom(page)</code> — verificación runtime (efecto visible en <code>/cities/list</code>);</li><li><code>cleanup(api, mdId)</code> — rollback quirúrgico (DELETE / UPDATE NULL).</li></ul><p>Para relanzar solo algunos kinds: <code>WUIC_RAG_TOOLS_ONLY=row_action,table_style npm run test:docs</code>.</p><h2>Referencias</h2><ul><li>Componente: <code>wuic-framework-lib/src/lib/component/rag-chatbot/rag-chatbot.component.ts</code></li><li>Backend dispatcher: <code>KonvergenceCore/Controllers/RagController.cs</code> (<code>ApplyAction</code>)</li><li>Repository: <code>KonvergenceCore/Services/RagChat/RagChatRepository.cs</code></li><li>Tool definition JSON: <code>codebase_embeddings/onnx_export/rag_tools.json</code> (pasado al LLM)</li><li>Cookbook de callbacks: <a href=\"callback-cookbook.md\">docs/pages/callback-cookbook.md</a></li><li>Página principal: <a href=\"rag-chatbot.md\">docs/pages/rag-chatbot.md</a></li></ul>",
24371
+ "html": "<h1>RAG Chatbot — Catálogo de herramientas</h1><p>Catálogo de las &quot;herramientas&quot; que el LLM Anthropic puede invocar vía <code>tool_use</code></p><p>cuando el usuario hace una solicitud en el componente <code>&lt;wuic-rag-chatbot&gt;</code>. Para</p><p>cada herramienta: el <code>kind</code> emitido en <code>proposed_action_json</code>, un **prompt de</p><p>usuario canónico** que lo dispara, los campos mínimos requeridos y los metadatos</p><p>WUIC modificados al hacer clic en &quot;Aplicar&quot;.</p><p>Cada herramienta tiene una regla de prueba end-to-end correspondiente en</p><p><code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> (fuente única de</p><p>verdad: si el <code>prompt</code> cambia aquí pero no allí, la prueba falla). Los prompts</p><p>canónicos a continuación son EXACTAMENTE los de la prueba — están en italiano</p><p>porque la prueba se escribió así; el LLM acepta las mismas preguntas en</p><p>cualquier idioma.</p><h2>Índice</h2><table><thead><tr><th>Kind</th><th>Qué hace</th><th>Metadata objetivo</th></tr></thead><tbody><tr><td><code>toolbar_action</code></td><td>Añade un botón a la barra de herramientas del list-grid</td><td><code>_mtdt__cstom__actions__tabelle</code></td></tr><tr><td><code>row_action</code></td><td>Añade un botón-acción en la fila individual</td><td><code>_metadati__colonne</code> (mc_voa_class=6)</td></tr><tr><td><code>table_style</code></td><td>Estilo condicional (clase CSS o inline) en toda la <code>&lt;tr&gt;</code></td><td><code>_metadati__u_i__stili__tabelle</code></td></tr><tr><td><code>column_style</code></td><td>Estilo condicional en la celda individual</td><td><code>_metadati__u_i__stili__colonne</code></td></tr><tr><td><code>display_formula</code></td><td>Template Angular para la celda (formato compacto, badge, link)</td><td><code>_metadati__colonne.mc_ui_grid_column_data_template</code></td></tr><tr><td><code>form_title_formula</code></td><td>Título dinámico del formulario de edición</td><td><code>_metadati__tabelle.md_display_formula</code></td></tr><tr><td><code>default_value_callback</code></td><td>Valor por defecto JS para un campo en INSERT</td><td><code>_metadati__colonne.mc_default_value_callback</code></td></tr><tr><td><code>custom_validation</code></td><td>Validación bloqueante pre-save</td><td><code>_metadati__colonne.mc_validation_custom_callback</code></td></tr><tr><td><code>selection_changed</code></td><td>Trigger al cambio de un lookup</td><td><code>_metadati__colonne.mc_selection_changed_custom_function</code></td></tr><tr><td><code>lifecycle_callback</code></td><td>Hooks <code>before_save</code> / <code>after_save</code> / <code>after_load</code></td><td><code>_metadati__tabelle.md_before_save</code> (et al)</td></tr><tr><td><code>simple_metadata_update</code></td><td>Update genérico de un campo simple (label, pagesize, flag)</td><td><code>_metadati__tabelle</code> / <code>_metadati__colonne</code></td></tr><tr><td><code>metadata_column_create</code></td><td>Crea una nueva columna de metadatos (calculada permitida)</td><td><code>_metadati__colonne</code> (INSERT)</td></tr><tr><td><code>sql_metadata_field</code></td><td>Escribe un snippet SQL en un campo gated de la tabla metadatos (super-admin)</td><td><code>_metadati__tabelle.md_join_override</code> et al</td></tr><tr><td><code>designer_inject</code></td><td>Inyecta herramienta/widget en el canvas del dashboard designer (client-only, sin backend)</td><td>dashboard designer state</td></tr></tbody></table><h2>Ejemplos de prompts canónicos</h2><h3><code>toolbar_action</code> — Botón bulk en la barra de herramientas</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities aggiungi un&#39;azione di toolbar &#39;Archivia selezionate&#39; (icona pi pi-archive) che archivia in bulk i record selezionati via POST /api/cities/bulk-archive con i loro id. Richiedi conferma prima.</p><p>Campos requeridos en la propuesta: <code>route</code>, <code>label</code>, <code>callback_js</code>. Opcional</p><p><code>requires_multi_selection:true</code> si el callback opera en</p><p><code>datasource.getSelectedRows()</code> (el backend habilita <code>md_multiple_selection</code> en la</p><p>ruta en ese caso).</p><h3><code>row_action</code> — Botón en la fila individual</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities aggiungi un button di riga &#39;Genera PDF&#39; (icona pi pi-file-pdf) che apre /api/cities/{id}/pdf in nuova tab con l&#39;id della riga.</p><p>Requerido: <code>route</code>, <code>label</code>, <code>callback_js</code>. El scope del callback tiene <code>record</code></p><p>(BehaviorSubject map: <code>record.id?.value</code>).</p><h3><code>table_style</code> — Estilo condicional de fila</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities colora in rosso (classe row-danger) le righe con LatestRecordedPopulation inferiore a 1000.</p><p>Requerido: <code>route</code>, <code>css_class</code>, <code>condition_js</code>. Para colores de la paleta</p><p>predefinida usa una clase (<code>row-danger</code> / <code>row-warning</code> / <code>row-info</code> /</p><p><code>row-success</code>). Para colores arbitrarios emite <code>css_class</code> con prefijo <code>style:</code></p><p>(ej. <code>style:background-color:#9b59b6;color:white</code>) — el framework inyecta</p><p>automáticamente <code>!important</code> y neutraliza el bg de los <code>&lt;td&gt;</code> para que el color</p><p>llegue al ojo.</p><h3><code>column_style</code> — Estilo condicional de celda</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities colora di rosso SOLO la cella LatestRecordedPopulation (classe cell-danger) quando il valore e&#39; inferiore a 1000, NON l&#39;intera riga.</p><p>Requerido: <code>route</code>, <code>column_name</code>, <code>css_class</code>, <code>condition_js</code>.</p><h3><code>display_formula</code> — Template de celda</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities, formatta la colonna LatestRecordedPopulation in formato compatto k/M (es. 12500 -&gt; 12.5k, 1500000 -&gt; 1.5M).</p><p>Requerido: <code>route</code>, <code>column_name</code>, <code>template_html</code>. Scope: <code>rowData</code> (objeto</p><p>plano, NO BehaviorSubject). Soporta pipes Angular estándar (<code>number:&#39;1.1-1&#39;</code>,</p><p><code>date:</code>, <code>currency:</code>, etc.), interpolación <code>{{ rowData.x }}</code>, ternario inline,</p><p><code>*ngIf</code>.</p><h3><code>form_title_formula</code> — Título dinámico del form de edición</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities imposta il titolo dinamico del form di edit a &#39;Modifica &lt;CityName&gt;&#39; dove &lt;CityName&gt; e&#39; il nome della citta&#39;, oppure &#39;Nuova citta&#39; su INSERT.</p><p>Requerido: <code>route</code>, <code>formula_js</code>. Scope: <code>metaInfo</code>, <code>record</code>, <code>datasource</code>,</p><p><code>wtoolbox</code>. Debe retornar <code>string</code>.</p><h3><code>default_value_callback</code> — Valor por defecto en INSERT</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities, la colonna CityName deve avere come valore di default in inserimento la stringa &#39;Nuova citta&#39;.</p><p>Requerido: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. El scope del callback tiene</p><p><code>record</code> (NUEVO record, plain object), <code>field</code> (<code>MetadatiColonna</code> objetivo),</p><p><code>metaInfo</code>, <code>wtoolbox</code>. El <code>return</code> se ignora — el callback DEBE escribir el</p><p>valor en <code>record[field.mc_nome_colonna]</code>.</p><h3><code>custom_validation</code> — Validación bloqueante</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities valida che LatestRecordedPopulation non sia negativo. Blocca il save con messaggio &#39;La popolazione non puo essere negativa&#39;.</p><p>Requerido: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope: <code>record</code> (BehaviorSubject</p><p>map), <code>field</code>, <code>vr</code> (ValidationRule: establece <code>vr.message=&#39;...&#39;</code>), <code>wtoolbox</code>.</p><p>Debe retornar <code>boolean</code>: <code>true</code> si válido, <code>false</code> para bloquear.</p><h3><code>selection_changed</code> — Trigger al cambio de un lookup</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities, al cambio del campo StateProvinceID (selection_changed), normalizza il valore tutto in maiuscolo via record.StateProvinceID.next(...).</p><p>Requerido: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope: <code>record</code>, <code>value</code>,</p><p><code>datasource</code>, <code>wtoolbox</code>. El objeto lookup resuelto está en</p><p><code>record.&lt;col&gt;__lookup_obj.value</code>.</p><h3><code>lifecycle_callback</code> — Hooks before/after save/load</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities, prima del save (event=before_save) metti il campo CityName in maiuscolo via record.CityName.next(record.CityName.value.toUpperCase()).</p><p>Requerido: <code>route</code>, <code>event</code> (<code>before_save</code> / <code>after_save</code> / <code>after_load</code>),</p><p><code>callback_js</code>. Scope: <code>record</code> (BehaviorSubject map), <code>datasource</code>, <code>wtoolbox</code>.</p><h3><code>simple_metadata_update</code> — Update de campos simples</h3><p><strong>Prompt del usuario (ejemplo A — label de columna):</strong></p><p>&gt; Modifica il titolo della colonna LatestRecordedPopulation in &#39;Official Population&#39; su cities.</p><p><strong>Prompt del usuario (ejemplo B — pagesize de tabla):</strong></p><p>&gt; Imposta il pagesize della tabella cities a 50.</p><p>Requerido: <code>route</code>, <code>target</code> (<code>column</code> / <code>table</code>), <code>field_label</code> (label</p><p>amigable), <code>value</code>. El backend mapea el <code>field_label</code> al campo SQL físico vía</p><p>un mapa server-side (alrededor de 29 campos cubiertos: header label, page size,</p><p>hide-in-list, etc.).</p><h3><code>metadata_column_create</code> — Crear columna de metadatos</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities crea una colonna calcolata <code>e2e_total_chars</code> di tipo number che calcola LEN sul CityName. La formula a livello metadato deve essere LEN([Application].[Cities].[CityName]) e la colonna deve essere is_computed=true (non fisica nel DB).</p><p>Requerido: <code>route</code>, <code>column_name</code>, <code>ui_column_type</code>, <code>is_computed</code>. Cuando</p><p><code>is_computed=true</code> también <code>computed_formula</code> debe estar presente.</p><h3><code>sql_metadata_field</code> — Snippet SQL en campo gated (super-admin)</h3><p><strong>Prompt del usuario:</strong></p><p>&gt; Su cities applica al campo md_join_override il join SQL: LEFT JOIN [Application].[People] AS [e2e_test_people] ON 1=0. E&#39; un test e2e no-op, scopo di verificare che il tool propose_sql_metadata_field scriva correttamente in _metadati__tabelle.mdjoinoverride. Dbms target: mssql.</p><p>Requerido: <code>target_table</code>, <code>target_row_key</code>, <code>field_name</code>, <code>sql_snippet</code>,</p><p><code>dbms_target</code>. Auth: super-admin (D3 gate). El prompt del usuario DEBE</p><p>especificar el dbms objetivo — el backend no lo deduce.</p><h3><code>designer_inject</code> — Inyección de herramienta en el dashboard designer</h3><p>Kind client-only: aplicado no por el backend sino por un handler registrado por</p><p><code>designer.component</code> vía <code>ChatbotHostRegistryService</code>. Ver</p><p><code>docs/pages/_internal/designer-tool-catalog.md</code> para el catálogo de paleta de</p><p>herramientas.</p><h2>Robustez del enrutamiento — variantes no triviales</h2><p>Los prompts canónicos anteriores son <strong>un único ejemplo por kind</strong>. No obstante, el</p><p>enrutamiento de la herramienta es robusto frente a formulaciones distintas: la suite</p><p><code>playwright/docs/rag-chatbot/rag-chatbot--tools-variations.mjs</code> prueba **5+ variantes</p><p>por kind** (reformulaciones semánticas, restricciones realistas, pares</p><p>desambiguadores), muchas derivadas de prompts reales, y mide el <em>routing-rate</em> sobre</p><p>varias repeticiones. El enrutamiento resultó <strong>independiente del idioma</strong> (italiano e</p><p>inglés enrutan igual — <code>WUIC_RAG_VAR_LANG=en</code>).</p><p>Ejemplos alternativos que enrutan correctamente (además de los canónicos):</p><ul><li><code>toolbar_action</code> → «put a button at the top of the grid that exports the checked rows to CSV»</li><li><code>row_action</code> → «on each row an eye icon that opens the detail of that row in a new tab»</li><li><code>table_style</code> → «highlight in green the recently modified rows»</li><li><code>column_style</code> → «put a yellow background only on the CityName cell when it is empty»</li><li><code>display_formula</code> → «display the population with the thousands separator»</li><li><code>custom_validation</code> → «prevent saving if ValidTo is earlier than ValidFrom»</li><li><code>simple_metadata_update</code> → «hide the LastEditedBy column from the list» (nota: un «filtro por defecto» es <code>simple_metadata_update</code> con <code>field_label=default_filter</code>, NO <code>sql_metadata_field</code>)</li><li><code>metadata_column_create</code> → «add a computed column that divides the population by 1000»</li><li><code>designer_inject</code> → «create a master-detail with 2 grids (provinces and cities)», «compose a 3-column layout with three grids», «put two grids side by side»</li></ul><p><strong>Verdaderos negativos (NO deben emitir herramienta):</strong> las solicitudes de *ejemplo</p><p>de código* («give me an example of a toolbar action without applying it?») o las</p><p>preguntas conceptuales permanecen como respuesta textual sin proponer ninguna acción.</p><h3>Designer — verificación del canvas y límites de la paleta</h3><p><code>rag-chatbot--designer-canvas-verify.mjs</code> comprueba que cada prompt del designer</p><p>produzca <strong>realmente</strong> los componentes esperados en el canvas (histograma por tipo,</p><p>no solo presencia en el DOM). La paleta del designer expone: <code>DATASOURCE</code>,</p><p><code>SPLITTER</code>, <code>DATAREPEATER</code>, <code>TABLE</code>, <code>KPI</code>, <code>DIV</code>, <code>LABEL</code>, <code>SPAN</code>, <code>CONTAINER</code>,</p><p><code>BUTTON</code>. Los <strong>gráficos</strong> se obtienen con un <code>DATAREPEATER</code> en modo</p><p><code>action: &quot;chart&quot;</code> (no existe una herramienta <code>CHART</code> separada — el gráfico es un</p><p>datarepeater en modo chart, configurable bar/line/pie vía «Configura chart»). Otras</p><p><code>action</code> del datarepeater: <code>list</code> (grid, por defecto), <code>edit</code>/<code>dialog</code>/<code>detail</code></p><p>(ficha/form), <code>map</code>, <code>scheduler</code>, <code>calendar</code>, <code>kanban</code>, <code>tree</code>, <code>carousel</code>, <code>pivot</code>,</p><p><code>spreadsheet</code>. Los títulos de texto se emiten como <code>LABEL</code>/<code>H1</code>.</p><p>#### Config de arquetipo inline (<code>archetype_config</code>)</p><p>Un nodo <code>DATAREPEATER</code> del <code>designer_inject</code> puede llevar un campo <code>archetype_config</code> =</p><p>el contenido de <code>md_props_bag.archetypes.&lt;action&gt;</code>. Se aplica <strong>en memoria</strong> al</p><p><code>metaInfo.tableMetadata</code> del datasource vinculado (sin llamada al servidor) mediante el</p><p><strong>mismo camino que el configurador de gráfico</strong> (<code>customProps_&lt;action&gt;</code> +</p><p><code>ds.fetchData()</code> + <code>propertyTreeBuilder</code>), y persiste al serializar el datasource al</p><p>guardar el dashboard. La aplicación se difiere hasta que el datasource vinculado se</p><p>materializa.</p><ul><li><strong>chart</strong>: <code>dataOptions</code> DEBE incluir <code>dataProperty:&quot;dato&quot;</code> (la clave del array de</li></ul><p> datos que lee <code>parseData</code> — sin ella el gráfico queda vacío) +</p><p> <code>datasets:[{label, labelField:&#39;&lt;col etiquetas&gt;&#39;, dataField:&#39;&lt;col valores&gt;&#39;}]</code>.</p><ul><li><strong>map</strong>: <code>markerColorField</code>, <code>titleField</code>, <code>zoom</code>, <code>center:{lat,lng}</code> (los marcadores</li></ul><p> toman sus coordenadas de la columna <code>point</code> auto-detectada de la route).</p><ul><li>Otros arquetipos (<code>scheduler</code>, <code>kanban</code>, <code>tree</code>, …) siguen el mismo mecanismo con sus</li></ul><p> campos respectivos (ver <code>mapOptions</code>/<code>kanbanOptions</code>/<code>schedulerOptions</code>).</p><p>Verificado: «pie chart of population by province» → tarta con datos; «cities on a map</p><p>colored by province» → Google Map con un marcador por ciudad.</p><p>&gt; ⚠️ Test full-UI del designer: cada prompt debe ejecutarse sobre una **conversación</p><p>&gt; limpia** (el chatbot persiste el id de sesión en</p><p>&gt; <code>localStorage[&#39;wuic_rag_last_session_id&#39;]</code> y lo restaura al arrancar — sin reset, el</p><p>&gt; historial de un prompt contamina el siguiente). Además requiere el backend en</p><p>&gt; <code>ASPNETCORE_ENVIRONMENT=Development</code> (CORS para el frontend dev) + el frontend activo.</p><h2>Patrón operativo</h2><p>1. El usuario abre el chatbot (FAB abajo a la derecha) y escribe el prompt.</p><p>2. El LLM analiza la solicitud + el <code>routeContext</code> inyectado (ruta actual, tabla,</p><p> columnas) y emite el <code>tool_use</code> con el <code>kind</code> apropiado.</p><p>3. La propuesta se muestra en una tarjeta con campos editables:</p><p> - Campos NON-body (clase, label, target, etc.) como <code>&lt;input&gt;</code> de texto.</p><p> - El body del callback como <code>&lt;textarea&gt;</code> o editor Monaco avanzado.</p><p>4. El usuario puede editar ambos antes de hacer clic en &quot;Aplicar&quot;.</p><p>5. Al hacer clic en &quot;Aplicar&quot;:</p><p> - Si la propuesta ha sido editada: <code>POST /api/Rag/UpdateProposedAction</code></p><p> reescribe el <code>proposed_action_json</code> en la fila DB del mensaje.</p><p> - <code>POST /api/Rag/ApplyAction</code> relee la propuesta desde el DB y aplica los</p><p> side-effects en los metadatos WUIC + llama a</p><p> <code>InvalidateMetadataCachesAndSetVersion</code> para que el siguiente refresh de la</p><p> ruta objetivo vea el cambio.</p><h2>Tests docs-driven</h2><p>El archivo <code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> define</p><p>el array <code>TOOLS</code> con una entrada por <code>kind</code>. Cada entrada tiene:</p><ul><li><code>kind</code> — valor esperado en <code>proposed_action_json</code>;</li><li><code>prompt</code> — texto exacto enviado al LLM;</li><li><code>assertFields</code> — campos mínimos que la propuesta debe contener;</li><li><code>verify(api, mdId)</code> — verificación DB post-apply (la fila metadata existe);</li><li><code>verifyDom(page)</code> — verificación runtime (efecto visible en <code>/cities/list</code>);</li><li><code>cleanup(api, mdId)</code> — rollback quirúrgico (DELETE / UPDATE NULL).</li></ul><p>Para relanzar solo algunos kinds: <code>WUIC_RAG_TOOLS_ONLY=row_action,table_style npm run test:docs</code>.</p><h2>Referencias</h2><ul><li>Componente: <code>wuic-framework-lib/src/lib/component/rag-chatbot/rag-chatbot.component.ts</code></li><li>Backend dispatcher: <code>KonvergenceCore/Controllers/RagController.cs</code> (<code>ApplyAction</code>)</li><li>Repository: <code>KonvergenceCore/Services/RagChat/RagChatRepository.cs</code></li><li>Tool definition JSON: <code>codebase_embeddings/onnx_export/rag_tools.json</code> (pasado al LLM)</li><li>Cookbook de callbacks: <a href=\"callback-cookbook.md\">docs/pages/callback-cookbook.md</a></li><li>Página principal: <a href=\"rag-chatbot.md\">docs/pages/rag-chatbot.md</a></li></ul>",
24356
24372
  "codeSamples": []
24357
24373
  }
24358
24374
  ],
@@ -30195,14 +30211,18 @@ const frameworkDocsContent = {
30195
30211
  },
30196
30212
  {
30197
30213
  "id": "sec_3",
30198
- "title": "Operatives Muster"
30214
+ "title": "Routing-Robustheit — nicht triviale Varianten"
30199
30215
  },
30200
30216
  {
30201
30217
  "id": "sec_4",
30202
- "title": "Docs-Driven Tests"
30218
+ "title": "Operatives Muster"
30203
30219
  },
30204
30220
  {
30205
30221
  "id": "sec_5",
30222
+ "title": "Docs-Driven Tests"
30223
+ },
30224
+ {
30225
+ "id": "sec_6",
30206
30226
  "title": "Referenzen"
30207
30227
  }
30208
30228
  ],
@@ -30210,7 +30230,7 @@ const frameworkDocsContent = {
30210
30230
  {
30211
30231
  "id": "overview",
30212
30232
  "title": "Overview",
30213
- "html": "<h1>RAG Chatbot — Tool-Katalog</h1><p>Katalog der &quot;Tools&quot;, die das Anthropic LLM via <code>tool_use</code> aufrufen kann, wenn der</p><p>Benutzer eine Anfrage in der Komponente <code>&lt;wuic-rag-chatbot&gt;</code> stellt. Für jedes</p><p>Tool: der <code>kind</code> der in <code>proposed_action_json</code> emittiert wird, ein **kanonischer</p><p>Benutzer-Prompt** der es auslöst, die minimal erforderlichen Felder und die WUIC-</p><p>Metadaten die beim Klick auf &quot;Anwenden&quot; geändert werden.</p><p>Jedes Tool hat eine entsprechende End-to-End-Testregel in</p><p><code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> (Single Source</p><p>of Truth: wenn der <code>prompt</code> hier geändert wird aber nicht dort, schlägt der Test</p><p>fehl). Die kanonischen Prompts unten sind GENAU jene des Tests — sie sind auf</p><p>Italienisch weil der Test so geschrieben wurde; das LLM akzeptiert dieselben</p><p>Fragen in jeder Sprache.</p><h2>Index</h2><table><thead><tr><th>Kind</th><th>Was es tut</th><th>Ziel-Metadaten</th></tr></thead><tbody><tr><td><code>toolbar_action</code></td><td>Fügt einen Button zur list-grid Toolbar hinzu</td><td><code>_mtdt__cstom__actions__tabelle</code></td></tr><tr><td><code>row_action</code></td><td>Fügt einen Button-Action auf der einzelnen Zeile hinzu</td><td><code>_metadati__colonne</code> (mc_voa_class=6)</td></tr><tr><td><code>table_style</code></td><td>Bedingter Stil (CSS-Klasse oder inline) auf der gesamten <code>&lt;tr&gt;</code></td><td><code>_metadati__u_i__stili__tabelle</code></td></tr><tr><td><code>column_style</code></td><td>Bedingter Stil auf der einzelnen Zelle</td><td><code>_metadati__u_i__stili__colonne</code></td></tr><tr><td><code>display_formula</code></td><td>Angular Template für die Zelle (Kompaktformat, Badge, Link)</td><td><code>_metadati__colonne.mc_ui_grid_column_data_template</code></td></tr><tr><td><code>form_title_formula</code></td><td>Dynamischer Titel des Bearbeitungsformulars</td><td><code>_metadati__tabelle.md_display_formula</code></td></tr><tr><td><code>default_value_callback</code></td><td>JS-Standardwert für ein Feld bei INSERT</td><td><code>_metadati__colonne.mc_default_value_callback</code></td></tr><tr><td><code>custom_validation</code></td><td>Blockierende Pre-Save Validierung</td><td><code>_metadati__colonne.mc_validation_custom_callback</code></td></tr><tr><td><code>selection_changed</code></td><td>Trigger bei Lookup-Änderung</td><td><code>_metadati__colonne.mc_selection_changed_custom_function</code></td></tr><tr><td><code>lifecycle_callback</code></td><td><code>before_save</code> / <code>after_save</code> / <code>after_load</code> Hooks</td><td><code>_metadati__tabelle.md_before_save</code> (et al)</td></tr><tr><td><code>simple_metadata_update</code></td><td>Generisches Update eines einfachen Feldes (Label, Pagesize, Flag)</td><td><code>_metadati__tabelle</code> / <code>_metadati__colonne</code></td></tr><tr><td><code>metadata_column_create</code></td><td>Erstellt eine neue Metadaten-Spalte (berechnet erlaubt)</td><td><code>_metadati__colonne</code> (INSERT)</td></tr><tr><td><code>sql_metadata_field</code></td><td>Schreibt einen SQL-Snippet auf ein gated Feld der Metadatentabelle (Super-Admin)</td><td><code>_metadati__tabelle.md_join_override</code> et al</td></tr><tr><td><code>designer_inject</code></td><td>Injiziert Tool/Widget auf den Dashboard-Designer Canvas (client-only, kein Backend)</td><td>dashboard designer state</td></tr></tbody></table><h2>Beispiele kanonischer Prompts</h2><h3><code>toolbar_action</code> — Bulk-Button auf der Toolbar</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities aggiungi un&#39;azione di toolbar &#39;Archivia selezionate&#39; (icona pi pi-archive) che archivia in bulk i record selezionati via POST /api/cities/bulk-archive con i loro id. Richiedi conferma prima.</p><p>Erforderliche Felder im Vorschlag: <code>route</code>, <code>label</code>, <code>callback_js</code>. Optional</p><p><code>requires_multi_selection:true</code> wenn der Callback auf</p><p><code>datasource.getSelectedRows()</code> operiert (das Backend aktiviert</p><p><code>md_multiple_selection</code> auf der Route in diesem Fall).</p><h3><code>row_action</code> — Button auf der einzelnen Zeile</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities aggiungi un button di riga &#39;Genera PDF&#39; (icona pi pi-file-pdf) che apre /api/cities/{id}/pdf in nuova tab con l&#39;id della riga.</p><p>Erforderlich: <code>route</code>, <code>label</code>, <code>callback_js</code>. Der Callback-Scope hat <code>record</code></p><p>(BehaviorSubject Map: <code>record.id?.value</code>).</p><h3><code>table_style</code> — Bedingter Zeilenstil</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities colora in rosso (classe row-danger) le righe con LatestRecordedPopulation inferiore a 1000.</p><p>Erforderlich: <code>route</code>, <code>css_class</code>, <code>condition_js</code>. Für vordefinierte</p><p>Palettenfarben verwende eine Klasse (<code>row-danger</code> / <code>row-warning</code> / <code>row-info</code> /</p><p><code>row-success</code>). Für beliebige Farben emittiere <code>css_class</code> mit <code>style:</code> Präfix</p><p>(z.B. <code>style:background-color:#9b59b6;color:white</code>) — das Framework injiziert</p><p>automatisch <code>!important</code> und neutralisiert den <code>&lt;td&gt;</code> bg damit die Farbe das</p><p>Auge erreicht.</p><h3><code>column_style</code> — Bedingter Zellenstil</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities colora di rosso SOLO la cella LatestRecordedPopulation (classe cell-danger) quando il valore e&#39; inferiore a 1000, NON l&#39;intera riga.</p><p>Erforderlich: <code>route</code>, <code>column_name</code>, <code>css_class</code>, <code>condition_js</code>.</p><h3><code>display_formula</code> — Zellen-Template</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities, formatta la colonna LatestRecordedPopulation in formato compatto k/M (es. 12500 -&gt; 12.5k, 1500000 -&gt; 1.5M).</p><p>Erforderlich: <code>route</code>, <code>column_name</code>, <code>template_html</code>. Scope: <code>rowData</code> (flaches</p><p>Objekt, KEIN BehaviorSubject). Unterstützt Standard Angular Pipes</p><p>(<code>number:&#39;1.1-1&#39;</code>, <code>date:</code>, <code>currency:</code>, etc.), Interpolation <code>{{ rowData.x }}</code>,</p><p>inline Ternär, <code>*ngIf</code>.</p><h3><code>form_title_formula</code> — Dynamischer Edit-Form Titel</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities imposta il titolo dinamico del form di edit a &#39;Modifica &lt;CityName&gt;&#39; dove &lt;CityName&gt; e&#39; il nome della citta&#39;, oppure &#39;Nuova citta&#39; su INSERT.</p><p>Erforderlich: <code>route</code>, <code>formula_js</code>. Scope: <code>metaInfo</code>, <code>record</code>, <code>datasource</code>,</p><p><code>wtoolbox</code>. Muss <code>string</code> zurückgeben.</p><h3><code>default_value_callback</code> — Standardwert bei INSERT</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities, la colonna CityName deve avere come valore di default in inserimento la stringa &#39;Nuova citta&#39;.</p><p>Erforderlich: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Der Callback-Scope hat</p><p><code>record</code> (NEUER Record, plain object), <code>field</code> (<code>MetadatiColonna</code> Ziel),</p><p><code>metaInfo</code>, <code>wtoolbox</code>. Das <code>return</code> wird ignoriert — der Callback MUSS den</p><p>Wert in <code>record[field.mc_nome_colonna]</code> schreiben.</p><h3><code>custom_validation</code> — Blockierende Validierung</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities valida che LatestRecordedPopulation non sia negativo. Blocca il save con messaggio &#39;La popolazione non puo essere negativa&#39;.</p><p>Erforderlich: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope: <code>record</code></p><p>(BehaviorSubject Map), <code>field</code>, <code>vr</code> (ValidationRule: setze <code>vr.message=&#39;...&#39;</code>),</p><p><code>wtoolbox</code>. Muss <code>boolean</code> zurückgeben: <code>true</code> wenn gültig, <code>false</code> zum Blockieren.</p><h3><code>selection_changed</code> — Trigger bei Lookup-Änderung</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities, al cambio del campo StateProvinceID (selection_changed), normalizza il valore tutto in maiuscolo via record.StateProvinceID.next(...).</p><p>Erforderlich: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope: <code>record</code>, <code>value</code>,</p><p><code>datasource</code>, <code>wtoolbox</code>. Das aufgelöste Lookup-Objekt ist in</p><p><code>record.&lt;col&gt;__lookup_obj.value</code>.</p><h3><code>lifecycle_callback</code> — Before/After Save/Load Hooks</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities, prima del save (event=before_save) metti il campo CityName in maiuscolo via record.CityName.next(record.CityName.value.toUpperCase()).</p><p>Erforderlich: <code>route</code>, <code>event</code> (<code>before_save</code> / <code>after_save</code> / <code>after_load</code>),</p><p><code>callback_js</code>. Scope: <code>record</code> (BehaviorSubject Map), <code>datasource</code>, <code>wtoolbox</code>.</p><h3><code>simple_metadata_update</code> — Update einfacher Felder</h3><p><strong>Benutzer-Prompt (Beispiel A — Spalten-Label):</strong></p><p>&gt; Modifica il titolo della colonna LatestRecordedPopulation in &#39;Official Population&#39; su cities.</p><p><strong>Benutzer-Prompt (Beispiel B — Tabellen-Pagesize):</strong></p><p>&gt; Imposta il pagesize della tabella cities a 50.</p><p>Erforderlich: <code>route</code>, <code>target</code> (<code>column</code> / <code>table</code>), <code>field_label</code> (freundliches</p><p>Label), <code>value</code>. Das Backend mappt das <code>field_label</code> auf das physische SQL-Feld</p><p>über eine server-side Map (ca. 29 Felder abgedeckt: Header-Label, Page Size,</p><p>Hide-in-List, etc.).</p><h3><code>metadata_column_create</code> — Metadaten-Spalte erstellen</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities crea una colonna calcolata <code>e2e_total_chars</code> di tipo number che calcola LEN sul CityName. La formula a livello metadato deve essere LEN([Application].[Cities].[CityName]) e la colonna deve essere is_computed=true (non fisica nel DB).</p><p>Erforderlich: <code>route</code>, <code>column_name</code>, <code>ui_column_type</code>, <code>is_computed</code>. Wenn</p><p><code>is_computed=true</code> muss auch <code>computed_formula</code> vorhanden sein.</p><h3><code>sql_metadata_field</code> — SQL-Snippet auf gated Feld (Super-Admin)</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities applica al campo md_join_override il join SQL: LEFT JOIN [Application].[People] AS [e2e_test_people] ON 1=0. E&#39; un test e2e no-op, scopo di verificare che il tool propose_sql_metadata_field scriva correttamente in _metadati__tabelle.mdjoinoverride. Dbms target: mssql.</p><p>Erforderlich: <code>target_table</code>, <code>target_row_key</code>, <code>field_name</code>, <code>sql_snippet</code>,</p><p><code>dbms_target</code>. Auth: Super-Admin (D3 Gate). Der Benutzer-Prompt MUSS das Ziel-</p><p>DBMS angeben — das Backend leitet es nicht ab.</p><h3><code>designer_inject</code> — Tool-Injektion im Dashboard-Designer</h3><p>Client-only Kind: angewendet nicht vom Backend sondern von einem Handler der vom</p><p><code>designer.component</code> via <code>ChatbotHostRegistryService</code> registriert wird. Siehe</p><p><code>docs/pages/_internal/designer-tool-catalog.md</code> für den Tool-Paletten-Katalog.</p><h2>Operatives Muster</h2><p>1. Der Benutzer öffnet den Chatbot (FAB unten rechts) und schreibt den Prompt.</p><p>2. Das LLM analysiert die Anfrage + den injizierten <code>routeContext</code> (aktuelle</p><p> Route, Tabelle, Spalten) und emittiert den <code>tool_use</code> mit dem passenden <code>kind</code>.</p><p>3. Der Vorschlag wird in einer Karte mit editierbaren Feldern angezeigt:</p><p> - NICHT-Body Felder (Klasse, Label, Target, etc.) als <code>&lt;input&gt;</code> Text.</p><p> - Der Callback-Body als <code>&lt;textarea&gt;</code> oder erweiterter Monaco Editor.</p><p>4. Der Benutzer kann beide editieren bevor er auf &quot;Anwenden&quot; klickt.</p><p>5. Beim Klick auf &quot;Anwenden&quot;:</p><p> - Wenn der Vorschlag editiert wurde: <code>POST /api/Rag/UpdateProposedAction</code></p><p> überschreibt das <code>proposed_action_json</code> in der Message-DB-Zeile.</p><p> - <code>POST /api/Rag/ApplyAction</code> liest den Vorschlag aus dem DB zurück und</p><p> wendet die Side-Effects auf die WUIC-Metadaten an + ruft</p><p> <code>InvalidateMetadataCachesAndSetVersion</code> auf damit der nächste Refresh der</p><p> Ziel-Route die Änderung sieht.</p><h2>Docs-Driven Tests</h2><p>Die Datei <code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code></p><p>definiert das <code>TOOLS</code> Array mit einem Eintrag pro <code>kind</code>. Jeder Eintrag hat:</p><ul><li><code>kind</code> — erwarteter Wert in <code>proposed_action_json</code>;</li><li><code>prompt</code> — exakter Text der an das LLM gesendet wird;</li><li><code>assertFields</code> — minimale Felder die der Vorschlag enthalten muss;</li><li><code>verify(api, mdId)</code> — DB Post-Apply Check (die Metadaten-Zeile existiert);</li><li><code>verifyDom(page)</code> — Runtime Check (sichtbare Wirkung in <code>/cities/list</code>);</li><li><code>cleanup(api, mdId)</code> — chirurgischer Rollback (DELETE / UPDATE NULL).</li></ul><p>Um nur einige Kinds erneut auszuführen: <code>WUIC_RAG_TOOLS_ONLY=row_action,table_style npm run test:docs</code>.</p><h2>Referenzen</h2><ul><li>Komponente: <code>wuic-framework-lib/src/lib/component/rag-chatbot/rag-chatbot.component.ts</code></li><li>Backend Dispatcher: <code>KonvergenceCore/Controllers/RagController.cs</code> (<code>ApplyAction</code>)</li><li>Repository: <code>KonvergenceCore/Services/RagChat/RagChatRepository.cs</code></li><li>Tool-Definition JSON: <code>codebase_embeddings/onnx_export/rag_tools.json</code> (an das LLM übergeben)</li><li>Callback Cookbook: <a href=\"callback-cookbook.md\">docs/pages/callback-cookbook.md</a></li><li>Hauptseite: <a href=\"rag-chatbot.md\">docs/pages/rag-chatbot.md</a></li></ul>",
30233
+ "html": "<h1>RAG Chatbot — Tool-Katalog</h1><p>Katalog der &quot;Tools&quot;, die das Anthropic LLM via <code>tool_use</code> aufrufen kann, wenn der</p><p>Benutzer eine Anfrage in der Komponente <code>&lt;wuic-rag-chatbot&gt;</code> stellt. Für jedes</p><p>Tool: der <code>kind</code> der in <code>proposed_action_json</code> emittiert wird, ein **kanonischer</p><p>Benutzer-Prompt** der es auslöst, die minimal erforderlichen Felder und die WUIC-</p><p>Metadaten die beim Klick auf &quot;Anwenden&quot; geändert werden.</p><p>Jedes Tool hat eine entsprechende End-to-End-Testregel in</p><p><code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code> (Single Source</p><p>of Truth: wenn der <code>prompt</code> hier geändert wird aber nicht dort, schlägt der Test</p><p>fehl). Die kanonischen Prompts unten sind GENAU jene des Tests — sie sind auf</p><p>Italienisch weil der Test so geschrieben wurde; das LLM akzeptiert dieselben</p><p>Fragen in jeder Sprache.</p><h2>Index</h2><table><thead><tr><th>Kind</th><th>Was es tut</th><th>Ziel-Metadaten</th></tr></thead><tbody><tr><td><code>toolbar_action</code></td><td>Fügt einen Button zur list-grid Toolbar hinzu</td><td><code>_mtdt__cstom__actions__tabelle</code></td></tr><tr><td><code>row_action</code></td><td>Fügt einen Button-Action auf der einzelnen Zeile hinzu</td><td><code>_metadati__colonne</code> (mc_voa_class=6)</td></tr><tr><td><code>table_style</code></td><td>Bedingter Stil (CSS-Klasse oder inline) auf der gesamten <code>&lt;tr&gt;</code></td><td><code>_metadati__u_i__stili__tabelle</code></td></tr><tr><td><code>column_style</code></td><td>Bedingter Stil auf der einzelnen Zelle</td><td><code>_metadati__u_i__stili__colonne</code></td></tr><tr><td><code>display_formula</code></td><td>Angular Template für die Zelle (Kompaktformat, Badge, Link)</td><td><code>_metadati__colonne.mc_ui_grid_column_data_template</code></td></tr><tr><td><code>form_title_formula</code></td><td>Dynamischer Titel des Bearbeitungsformulars</td><td><code>_metadati__tabelle.md_display_formula</code></td></tr><tr><td><code>default_value_callback</code></td><td>JS-Standardwert für ein Feld bei INSERT</td><td><code>_metadati__colonne.mc_default_value_callback</code></td></tr><tr><td><code>custom_validation</code></td><td>Blockierende Pre-Save Validierung</td><td><code>_metadati__colonne.mc_validation_custom_callback</code></td></tr><tr><td><code>selection_changed</code></td><td>Trigger bei Lookup-Änderung</td><td><code>_metadati__colonne.mc_selection_changed_custom_function</code></td></tr><tr><td><code>lifecycle_callback</code></td><td><code>before_save</code> / <code>after_save</code> / <code>after_load</code> Hooks</td><td><code>_metadati__tabelle.md_before_save</code> (et al)</td></tr><tr><td><code>simple_metadata_update</code></td><td>Generisches Update eines einfachen Feldes (Label, Pagesize, Flag)</td><td><code>_metadati__tabelle</code> / <code>_metadati__colonne</code></td></tr><tr><td><code>metadata_column_create</code></td><td>Erstellt eine neue Metadaten-Spalte (berechnet erlaubt)</td><td><code>_metadati__colonne</code> (INSERT)</td></tr><tr><td><code>sql_metadata_field</code></td><td>Schreibt einen SQL-Snippet auf ein gated Feld der Metadatentabelle (Super-Admin)</td><td><code>_metadati__tabelle.md_join_override</code> et al</td></tr><tr><td><code>designer_inject</code></td><td>Injiziert Tool/Widget auf den Dashboard-Designer Canvas (client-only, kein Backend)</td><td>dashboard designer state</td></tr></tbody></table><h2>Beispiele kanonischer Prompts</h2><h3><code>toolbar_action</code> — Bulk-Button auf der Toolbar</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities aggiungi un&#39;azione di toolbar &#39;Archivia selezionate&#39; (icona pi pi-archive) che archivia in bulk i record selezionati via POST /api/cities/bulk-archive con i loro id. Richiedi conferma prima.</p><p>Erforderliche Felder im Vorschlag: <code>route</code>, <code>label</code>, <code>callback_js</code>. Optional</p><p><code>requires_multi_selection:true</code> wenn der Callback auf</p><p><code>datasource.getSelectedRows()</code> operiert (das Backend aktiviert</p><p><code>md_multiple_selection</code> auf der Route in diesem Fall).</p><h3><code>row_action</code> — Button auf der einzelnen Zeile</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities aggiungi un button di riga &#39;Genera PDF&#39; (icona pi pi-file-pdf) che apre /api/cities/{id}/pdf in nuova tab con l&#39;id della riga.</p><p>Erforderlich: <code>route</code>, <code>label</code>, <code>callback_js</code>. Der Callback-Scope hat <code>record</code></p><p>(BehaviorSubject Map: <code>record.id?.value</code>).</p><h3><code>table_style</code> — Bedingter Zeilenstil</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities colora in rosso (classe row-danger) le righe con LatestRecordedPopulation inferiore a 1000.</p><p>Erforderlich: <code>route</code>, <code>css_class</code>, <code>condition_js</code>. Für vordefinierte</p><p>Palettenfarben verwende eine Klasse (<code>row-danger</code> / <code>row-warning</code> / <code>row-info</code> /</p><p><code>row-success</code>). Für beliebige Farben emittiere <code>css_class</code> mit <code>style:</code> Präfix</p><p>(z.B. <code>style:background-color:#9b59b6;color:white</code>) — das Framework injiziert</p><p>automatisch <code>!important</code> und neutralisiert den <code>&lt;td&gt;</code> bg damit die Farbe das</p><p>Auge erreicht.</p><h3><code>column_style</code> — Bedingter Zellenstil</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities colora di rosso SOLO la cella LatestRecordedPopulation (classe cell-danger) quando il valore e&#39; inferiore a 1000, NON l&#39;intera riga.</p><p>Erforderlich: <code>route</code>, <code>column_name</code>, <code>css_class</code>, <code>condition_js</code>.</p><h3><code>display_formula</code> — Zellen-Template</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities, formatta la colonna LatestRecordedPopulation in formato compatto k/M (es. 12500 -&gt; 12.5k, 1500000 -&gt; 1.5M).</p><p>Erforderlich: <code>route</code>, <code>column_name</code>, <code>template_html</code>. Scope: <code>rowData</code> (flaches</p><p>Objekt, KEIN BehaviorSubject). Unterstützt Standard Angular Pipes</p><p>(<code>number:&#39;1.1-1&#39;</code>, <code>date:</code>, <code>currency:</code>, etc.), Interpolation <code>{{ rowData.x }}</code>,</p><p>inline Ternär, <code>*ngIf</code>.</p><h3><code>form_title_formula</code> — Dynamischer Edit-Form Titel</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities imposta il titolo dinamico del form di edit a &#39;Modifica &lt;CityName&gt;&#39; dove &lt;CityName&gt; e&#39; il nome della citta&#39;, oppure &#39;Nuova citta&#39; su INSERT.</p><p>Erforderlich: <code>route</code>, <code>formula_js</code>. Scope: <code>metaInfo</code>, <code>record</code>, <code>datasource</code>,</p><p><code>wtoolbox</code>. Muss <code>string</code> zurückgeben.</p><h3><code>default_value_callback</code> — Standardwert bei INSERT</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities, la colonna CityName deve avere come valore di default in inserimento la stringa &#39;Nuova citta&#39;.</p><p>Erforderlich: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Der Callback-Scope hat</p><p><code>record</code> (NEUER Record, plain object), <code>field</code> (<code>MetadatiColonna</code> Ziel),</p><p><code>metaInfo</code>, <code>wtoolbox</code>. Das <code>return</code> wird ignoriert — der Callback MUSS den</p><p>Wert in <code>record[field.mc_nome_colonna]</code> schreiben.</p><h3><code>custom_validation</code> — Blockierende Validierung</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities valida che LatestRecordedPopulation non sia negativo. Blocca il save con messaggio &#39;La popolazione non puo essere negativa&#39;.</p><p>Erforderlich: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope: <code>record</code></p><p>(BehaviorSubject Map), <code>field</code>, <code>vr</code> (ValidationRule: setze <code>vr.message=&#39;...&#39;</code>),</p><p><code>wtoolbox</code>. Muss <code>boolean</code> zurückgeben: <code>true</code> wenn gültig, <code>false</code> zum Blockieren.</p><h3><code>selection_changed</code> — Trigger bei Lookup-Änderung</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities, al cambio del campo StateProvinceID (selection_changed), normalizza il valore tutto in maiuscolo via record.StateProvinceID.next(...).</p><p>Erforderlich: <code>route</code>, <code>column_name</code>, <code>callback_js</code>. Scope: <code>record</code>, <code>value</code>,</p><p><code>datasource</code>, <code>wtoolbox</code>. Das aufgelöste Lookup-Objekt ist in</p><p><code>record.&lt;col&gt;__lookup_obj.value</code>.</p><h3><code>lifecycle_callback</code> — Before/After Save/Load Hooks</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities, prima del save (event=before_save) metti il campo CityName in maiuscolo via record.CityName.next(record.CityName.value.toUpperCase()).</p><p>Erforderlich: <code>route</code>, <code>event</code> (<code>before_save</code> / <code>after_save</code> / <code>after_load</code>),</p><p><code>callback_js</code>. Scope: <code>record</code> (BehaviorSubject Map), <code>datasource</code>, <code>wtoolbox</code>.</p><h3><code>simple_metadata_update</code> — Update einfacher Felder</h3><p><strong>Benutzer-Prompt (Beispiel A — Spalten-Label):</strong></p><p>&gt; Modifica il titolo della colonna LatestRecordedPopulation in &#39;Official Population&#39; su cities.</p><p><strong>Benutzer-Prompt (Beispiel B — Tabellen-Pagesize):</strong></p><p>&gt; Imposta il pagesize della tabella cities a 50.</p><p>Erforderlich: <code>route</code>, <code>target</code> (<code>column</code> / <code>table</code>), <code>field_label</code> (freundliches</p><p>Label), <code>value</code>. Das Backend mappt das <code>field_label</code> auf das physische SQL-Feld</p><p>über eine server-side Map (ca. 29 Felder abgedeckt: Header-Label, Page Size,</p><p>Hide-in-List, etc.).</p><h3><code>metadata_column_create</code> — Metadaten-Spalte erstellen</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities crea una colonna calcolata <code>e2e_total_chars</code> di tipo number che calcola LEN sul CityName. La formula a livello metadato deve essere LEN([Application].[Cities].[CityName]) e la colonna deve essere is_computed=true (non fisica nel DB).</p><p>Erforderlich: <code>route</code>, <code>column_name</code>, <code>ui_column_type</code>, <code>is_computed</code>. Wenn</p><p><code>is_computed=true</code> muss auch <code>computed_formula</code> vorhanden sein.</p><h3><code>sql_metadata_field</code> — SQL-Snippet auf gated Feld (Super-Admin)</h3><p><strong>Benutzer-Prompt:</strong></p><p>&gt; Su cities applica al campo md_join_override il join SQL: LEFT JOIN [Application].[People] AS [e2e_test_people] ON 1=0. E&#39; un test e2e no-op, scopo di verificare che il tool propose_sql_metadata_field scriva correttamente in _metadati__tabelle.mdjoinoverride. Dbms target: mssql.</p><p>Erforderlich: <code>target_table</code>, <code>target_row_key</code>, <code>field_name</code>, <code>sql_snippet</code>,</p><p><code>dbms_target</code>. Auth: Super-Admin (D3 Gate). Der Benutzer-Prompt MUSS das Ziel-</p><p>DBMS angeben — das Backend leitet es nicht ab.</p><h3><code>designer_inject</code> — Tool-Injektion im Dashboard-Designer</h3><p>Client-only Kind: angewendet nicht vom Backend sondern von einem Handler der vom</p><p><code>designer.component</code> via <code>ChatbotHostRegistryService</code> registriert wird. Siehe</p><p><code>docs/pages/_internal/designer-tool-catalog.md</code> für den Tool-Paletten-Katalog.</p><h2>Routing-Robustheit — nicht triviale Varianten</h2><p>Die kanonischen Prompts oben sind <strong>nur ein Beispiel pro Kind</strong>. Das Tool-Routing ist</p><p>dennoch robust gegenüber unterschiedlichen Formulierungen: die Suite</p><p><code>playwright/docs/rag-chatbot/rag-chatbot--tools-variations.mjs</code> testet **5+ Varianten</p><p>pro Kind** (semantische Umformulierungen, realistische Einschränkungen,</p><p>disambiguierende Paare), viele aus realen Prompts abgeleitet, und misst die</p><p><em>Routing-Rate</em> über mehrere Wiederholungen. Das Routing erwies sich als</p><p><strong>sprachunabhängig</strong> (Italienisch und Englisch routen gleich — <code>WUIC_RAG_VAR_LANG=en</code>).</p><p>Alternative Beispiele, die korrekt routen (zusätzlich zu den kanonischen):</p><ul><li><code>toolbar_action</code> → «put a button at the top of the grid that exports the checked rows to CSV»</li><li><code>row_action</code> → «on each row an eye icon that opens the detail of that row in a new tab»</li><li><code>table_style</code> → «highlight in green the recently modified rows»</li><li><code>column_style</code> → «put a yellow background only on the CityName cell when it is empty»</li><li><code>display_formula</code> → «display the population with the thousands separator»</li><li><code>custom_validation</code> → «prevent saving if ValidTo is earlier than ValidFrom»</li><li><code>simple_metadata_update</code> → «hide the LastEditedBy column from the list» (Hinweis: ein «Standardfilter» ist <code>simple_metadata_update</code> mit <code>field_label=default_filter</code>, NICHT <code>sql_metadata_field</code>)</li><li><code>metadata_column_create</code> → «add a computed column that divides the population by 1000»</li><li><code>designer_inject</code> → «create a master-detail with 2 grids (provinces and cities)», «compose a 3-column layout with three grids», «put two grids side by side»</li></ul><p><strong>Echte Negative (dürfen KEIN Tool emittieren):</strong> Anfragen nach einem <em>Codebeispiel</em></p><p>(«give me an example of a toolbar action without applying it?») oder konzeptionelle</p><p>Fragen bleiben eine Textantwort, ohne eine Aktion vorzuschlagen.</p><h3>Designer — Canvas-Verifikation und Paletten-Grenzen</h3><p><code>rag-chatbot--designer-canvas-verify.mjs</code> prüft, dass jeder Designer-Prompt die</p><p>erwarteten Komponenten <strong>tatsächlich</strong> im Canvas erzeugt (Histogramm nach Typ, nicht</p><p>nur DOM-Präsenz). Die Designer-Palette bietet: <code>DATASOURCE</code>, <code>SPLITTER</code>,</p><p><code>DATAREPEATER</code>, <code>TABLE</code>, <code>KPI</code>, <code>DIV</code>, <code>LABEL</code>, <code>SPAN</code>, <code>CONTAINER</code>, <code>BUTTON</code>.</p><p><strong>Diagramme</strong> entstehen mit einem <code>DATAREPEATER</code> im Modus <code>action: &quot;chart&quot;</code> (es gibt</p><p>kein separates <code>CHART</code>-Tool — das Diagramm ist ein Datarepeater im Chart-Modus,</p><p>konfigurierbar bar/line/pie über «Configura chart»). Weitere <code>action</code>s des</p><p>Datarepeaters: <code>list</code> (Grid, Standard), <code>edit</code>/<code>dialog</code>/<code>detail</code> (Datensatz/Form),</p><p><code>map</code>, <code>scheduler</code>, <code>calendar</code>, <code>kanban</code>, <code>tree</code>, <code>carousel</code>, <code>pivot</code>, <code>spreadsheet</code>.</p><p>Texttitel werden als <code>LABEL</code>/<code>H1</code> emittiert.</p><p>#### Inline-Archetyp-Konfiguration (<code>archetype_config</code>)</p><p>Ein <code>DATAREPEATER</code>-Knoten des <code>designer_inject</code> kann ein Feld <code>archetype_config</code> tragen</p><p>= der Inhalt von <code>md_props_bag.archetypes.&lt;action&gt;</code>. Es wird <strong>im Speicher</strong> auf das</p><p><code>metaInfo.tableMetadata</code> der gebundenen Datenquelle angewandt (kein Serveraufruf) über</p><p>den <strong>gleichen Pfad wie der Chart-Konfigurator</strong> (<code>customProps_&lt;action&gt;</code> +</p><p><code>ds.fetchData()</code> + <code>propertyTreeBuilder</code>) und bleibt beim Serialisieren der Datenquelle</p><p>beim Speichern des Dashboards erhalten. Die Anwendung wird aufgeschoben, bis die</p><p>gebundene Datenquelle materialisiert ist.</p><ul><li><strong>chart</strong>: <code>dataOptions</code> MUSS <code>dataProperty:&quot;dato&quot;</code> enthalten (der Schlüssel des</li></ul><p> Daten-Arrays, das <code>parseData</code> liest — ohne ihn bleibt das Diagramm leer) +</p><p> <code>datasets:[{label, labelField:&#39;&lt;Labels-Spalte&gt;&#39;, dataField:&#39;&lt;Werte-Spalte&gt;&#39;}]</code>.</p><ul><li><strong>map</strong>: <code>markerColorField</code>, <code>titleField</code>, <code>zoom</code>, <code>center:{lat,lng}</code> (die Marker</li></ul><p> beziehen ihre Koordinaten aus der automatisch erkannten <code>point</code>-Spalte der Route).</p><ul><li>Andere Archetypen (<code>scheduler</code>, <code>kanban</code>, <code>tree</code>, …) folgen dem gleichen Mechanismus</li></ul><p> mit ihren jeweiligen Feldern (siehe <code>mapOptions</code>/<code>kanbanOptions</code>/<code>schedulerOptions</code>).</p><p>Verifiziert: «pie chart of population by province» → Tortendiagramm mit Daten; «cities</p><p>on a map colored by province» → Google Map mit einem Marker pro Stadt.</p><p>&gt; ⚠️ Designer-Full-UI-Test: jeder Prompt muss in einer <strong>sauberen Konversation</strong></p><p>&gt; laufen (der Chatbot persistiert die Session-ID in</p><p>&gt; <code>localStorage[&#39;wuic_rag_last_session_id&#39;]</code> und stellt sie beim Boot wieder her — ohne</p><p>&gt; Reset kontaminiert die Historie eines Prompts den nächsten). Außerdem ist das Backend</p><p>&gt; in <code>ASPNETCORE_ENVIRONMENT=Development</code> (CORS für das Dev-Frontend) + ein aktives</p><p>&gt; Frontend erforderlich.</p><h2>Operatives Muster</h2><p>1. Der Benutzer öffnet den Chatbot (FAB unten rechts) und schreibt den Prompt.</p><p>2. Das LLM analysiert die Anfrage + den injizierten <code>routeContext</code> (aktuelle</p><p> Route, Tabelle, Spalten) und emittiert den <code>tool_use</code> mit dem passenden <code>kind</code>.</p><p>3. Der Vorschlag wird in einer Karte mit editierbaren Feldern angezeigt:</p><p> - NICHT-Body Felder (Klasse, Label, Target, etc.) als <code>&lt;input&gt;</code> Text.</p><p> - Der Callback-Body als <code>&lt;textarea&gt;</code> oder erweiterter Monaco Editor.</p><p>4. Der Benutzer kann beide editieren bevor er auf &quot;Anwenden&quot; klickt.</p><p>5. Beim Klick auf &quot;Anwenden&quot;:</p><p> - Wenn der Vorschlag editiert wurde: <code>POST /api/Rag/UpdateProposedAction</code></p><p> überschreibt das <code>proposed_action_json</code> in der Message-DB-Zeile.</p><p> - <code>POST /api/Rag/ApplyAction</code> liest den Vorschlag aus dem DB zurück und</p><p> wendet die Side-Effects auf die WUIC-Metadaten an + ruft</p><p> <code>InvalidateMetadataCachesAndSetVersion</code> auf damit der nächste Refresh der</p><p> Ziel-Route die Änderung sieht.</p><h2>Docs-Driven Tests</h2><p>Die Datei <code>playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs</code></p><p>definiert das <code>TOOLS</code> Array mit einem Eintrag pro <code>kind</code>. Jeder Eintrag hat:</p><ul><li><code>kind</code> — erwarteter Wert in <code>proposed_action_json</code>;</li><li><code>prompt</code> — exakter Text der an das LLM gesendet wird;</li><li><code>assertFields</code> — minimale Felder die der Vorschlag enthalten muss;</li><li><code>verify(api, mdId)</code> — DB Post-Apply Check (die Metadaten-Zeile existiert);</li><li><code>verifyDom(page)</code> — Runtime Check (sichtbare Wirkung in <code>/cities/list</code>);</li><li><code>cleanup(api, mdId)</code> — chirurgischer Rollback (DELETE / UPDATE NULL).</li></ul><p>Um nur einige Kinds erneut auszuführen: <code>WUIC_RAG_TOOLS_ONLY=row_action,table_style npm run test:docs</code>.</p><h2>Referenzen</h2><ul><li>Komponente: <code>wuic-framework-lib/src/lib/component/rag-chatbot/rag-chatbot.component.ts</code></li><li>Backend Dispatcher: <code>KonvergenceCore/Controllers/RagController.cs</code> (<code>ApplyAction</code>)</li><li>Repository: <code>KonvergenceCore/Services/RagChat/RagChatRepository.cs</code></li><li>Tool-Definition JSON: <code>codebase_embeddings/onnx_export/rag_tools.json</code> (an das LLM übergeben)</li><li>Callback Cookbook: <a href=\"callback-cookbook.md\">docs/pages/callback-cookbook.md</a></li><li>Hauptseite: <a href=\"rag-chatbot.md\">docs/pages/rag-chatbot.md</a></li></ul>",
30214
30234
  "codeSamples": []
30215
30235
  }
30216
30236
  ],
@@ -30876,4 +30896,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
30876
30896
  }] } });
30877
30897
 
30878
30898
  export { FrameworkDocsComponent };
30879
- //# sourceMappingURL=wuic-framework-lib-framework-docs.component-CGQbmSkv.mjs.map
30899
+ //# sourceMappingURL=wuic-framework-lib-framework-docs.component-DphKhTef.mjs.map