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
@@ -9,7 +9,7 @@ import * as i1$6 from 'primeng/dynamicdialog';
9
9
  import { DialogService, DynamicDialogRef, DynamicDialogConfig } from 'primeng/dynamicdialog';
10
10
  import * as i1 from 'ngx-cookie-service';
11
11
  import * as i1$1 from '@angular/common/http';
12
- import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
12
+ import { HttpClient, HttpHeaders, HttpContextToken, HttpErrorResponse } from '@angular/common/http';
13
13
  import '@angular/compiler';
14
14
  import { JitCompilerFactory } from '@angular/platform-browser-dynamic';
15
15
  import * as i8 from '@ngx-translate/core';
@@ -2707,7 +2707,7 @@ class WtoolboxService {
2707
2707
  const appRef = resolvedInjector.get(ApplicationRef);
2708
2708
  const environmentInjector = resolvedInjector.get(EnvironmentInjector);
2709
2709
  const close$ = new Subject();
2710
- const { PromptDialogFallbackHostComponent } = await import('./wuic-framework-lib-prompt-dialog-fallback-host.component-C9LvmFsL.mjs');
2710
+ const { PromptDialogFallbackHostComponent } = await import('./wuic-framework-lib-prompt-dialog-fallback-host.component-DZrhwMH5.mjs');
2711
2711
  const hostRef = createComponent(PromptDialogFallbackHostComponent, { environmentInjector });
2712
2712
  hostRef.instance.datasource = dialogConfig?.data?.datasource;
2713
2713
  hostRef.instance.header = String(dialogConfig?.header || '');
@@ -21663,7 +21663,7 @@ class LazySchedulerListComponent {
21663
21663
  * Inizializza il componente preparando stato, sottoscrizioni e primi caricamenti richiesti.
21664
21664
  */
21665
21665
  async ngOnInit() {
21666
- schedulerListComponentPromise ??= import('./wuic-framework-lib-scheduler-list.component-DHoukjf-.mjs').then((m) => m.SchedulerListComponent);
21666
+ schedulerListComponentPromise ??= import('./wuic-framework-lib-scheduler-list.component-CdcI1Uor.mjs').then((m) => m.SchedulerListComponent);
21667
21667
  this.loadedComponent = await schedulerListComponentPromise;
21668
21668
  this.cdr.markForCheck();
21669
21669
  }
@@ -21871,7 +21871,7 @@ class LazySpreadsheetListSfComponent {
21871
21871
  this.activatedRoute = activatedRoute;
21872
21872
  }
21873
21873
  async ngOnInit() {
21874
- spreadsheetListSfComponentPromise ??= import('./wuic-framework-lib-spreadsheet-list-sf.component-VsaXepUC.mjs').then((m) => m.SpreadsheetListSfComponent);
21874
+ spreadsheetListSfComponentPromise ??= import('./wuic-framework-lib-spreadsheet-list-sf.component-j1HTRI_P.mjs').then((m) => m.SpreadsheetListSfComponent);
21875
21875
  this.loadedComponent = await spreadsheetListSfComponentPromise;
21876
21876
  // Quando la promise e' gia' risolta dal cache module-scope (secondo mount
21877
21877
  // post-refresh), il microtask che risolve `await` non scatena sempre CD
@@ -22149,7 +22149,7 @@ class LazyChartListComponent {
22149
22149
  * Inizializza il componente preparando stato, sottoscrizioni e primi caricamenti richiesti.
22150
22150
  */
22151
22151
  async ngOnInit() {
22152
- chartListComponentPromise ??= import('./wuic-framework-lib-chart-list.component-C02jeFGy.mjs').then((m) => m.ChartListComponent);
22152
+ chartListComponentPromise ??= import('./wuic-framework-lib-chart-list.component-DAP-TPJR.mjs').then((m) => m.ChartListComponent);
22153
22153
  this.loadedComponent = await chartListComponentPromise;
22154
22154
  this.cdr.markForCheck();
22155
22155
  }
@@ -30741,8 +30741,24 @@ class NotificationRealtimeService {
30741
30741
  if (this.reconnectTimer || this.manuallyClosed || !this.currentUserId) {
30742
30742
  return;
30743
30743
  }
30744
- this.reconnectTimer = setTimeout(() => {
30744
+ this.reconnectTimer = setTimeout(async () => {
30745
30745
  this.reconnectTimer = null;
30746
+ if (this.manuallyClosed || !this.currentUserId) {
30747
+ return;
30748
+ }
30749
+ // Fallback a polling HTTP: finche' il WS non si riconnette, ad ogni tick
30750
+ // rifacciamo lo snapshot via HTTP (GET Notifications/unread) cosi' le
30751
+ // notifiche NUOVE compaiono comunque (ritardo ~5s) anche dove il WS non e'
30752
+ // disponibile -> durante il first-run (endpoint /ws non ancora registrato
30753
+ // perche' i servizi notifiche sono gated su firstRun al boot), dietro
30754
+ // proxy/firewall che bloccano l'upgrade, o su `dotnet run` senza restart.
30755
+ // Quando openSocket riesce, `ws.onopen` rifa lo snapshot e il push live
30756
+ // riprende; il dedup di loadSnapshot (<1s) evita doppi fetch ravvicinati.
30757
+ try {
30758
+ await this.loadSnapshot(this.currentUserId);
30759
+ }
30760
+ catch {
30761
+ }
30746
30762
  if (!this.manuallyClosed && this.currentUserId) {
30747
30763
  this.openSocket(this.currentUserId);
30748
30764
  }
@@ -45922,7 +45938,7 @@ class LazyFirstRunWizardComponent {
45922
45938
  this.cdr = cdr;
45923
45939
  }
45924
45940
  async ngOnInit() {
45925
- firstRunWizardComponentPromise ??= import('./wuic-framework-lib-first-run-wizard.component-lW7-xMm3.mjs').then((m) => m.WuicFirstRunWizardComponent);
45941
+ firstRunWizardComponentPromise ??= import('./wuic-framework-lib-first-run-wizard.component-BLrUrfZ9.mjs').then((m) => m.WuicFirstRunWizardComponent);
45926
45942
  const componentClass = await firstRunWizardComponentPromise;
45927
45943
  this.componentRef = this.vcr.createComponent(componentClass);
45928
45944
  // Forward output del componente reale al wrapper. Type assertion necessaria
@@ -57566,7 +57582,7 @@ class LazyCodeEditorComponent {
57566
57582
  this.cdr = cdr;
57567
57583
  }
57568
57584
  async ngOnInit() {
57569
- codeEditorComponentPromise ??= import('./wuic-framework-lib-code-editor.component-BZNLr0Jj.mjs').then((m) => m.CodeEditorComponent);
57585
+ codeEditorComponentPromise ??= import('./wuic-framework-lib-code-editor.component-CLMDvOWs.mjs').then((m) => m.CodeEditorComponent);
57570
57586
  this.loadedComponent = await codeEditorComponentPromise;
57571
57587
  this.cdr.markForCheck();
57572
57588
  }
@@ -59073,11 +59089,17 @@ class NotificationBellComponent {
59073
59089
  this.sub.add(this.realtime.enabled$.subscribe((enabled) => {
59074
59090
  this.isVisible = enabled !== false;
59075
59091
  }));
59076
- this.sub.add(this.realtime.unreadCount$.subscribe((count) => {
59077
- this.unreadCount = Number(count || 0);
59078
- }));
59079
59092
  this.sub.add(this.realtime.notifications$.subscribe((items) => {
59080
- this.notifications = Array.isArray(items) ? items : [];
59093
+ const arr = Array.isArray(items) ? items : [];
59094
+ // Escludi dal CAMPANELLO le notifiche "interne" del chatbot (rag.message.*):
59095
+ // servono SOLO al componente chatbot per refreshare il messaggio in chat, NON
59096
+ // sono voci utente. Senza questo filtro il bell si riempiva di "Errore RAG" /
59097
+ // "Risposta pronta" duplicate a ogni retry del prompt mentre l'engine non era
59098
+ // pronto. Il chatbot le riceve comunque (sottoscrizione separata a notifications$).
59099
+ this.notifications = arr.filter(n => !this.isChatbotInternalNotification(n));
59100
+ // Badge derivato dalla lista FILTRATA (lo snapshot include lette+non lette), cosi'
59101
+ // le rag.message.* non gonfiano il contatore.
59102
+ this.unreadCount = this.notifications.filter(n => !n.isRead).length;
59081
59103
  }));
59082
59104
  this.sub.add(this.realtime.progressEvents$.subscribe((evt) => {
59083
59105
  const guid = String(evt?.guid || '').trim();
@@ -59095,6 +59117,16 @@ class NotificationBellComponent {
59095
59117
  ngOnDestroy() {
59096
59118
  this.sub.unsubscribe();
59097
59119
  }
59120
+ /**
59121
+ * True per le notifiche "interne" del chatbot RAG (`rag.message.ready` /
59122
+ * `rag.message.error`): NON sono voci utente del campanello, ma segnali che il
59123
+ * componente chatbot usa per refreshare il messaggio in chat. Il bell le esclude
59124
+ * da lista e badge; le `rag.engine.bootstrap.*` (started/ready/error) restano
59125
+ * perche' sono feedback utente reale (download modelli in corso/pronto).
59126
+ */
59127
+ isChatbotInternalNotification(n) {
59128
+ return String(n?.type || '').startsWith('rag.message.');
59129
+ }
59098
59130
  async clearRead() {
59099
59131
  await this.realtime.clearRead(this.userId);
59100
59132
  }
@@ -59353,7 +59385,7 @@ class DesignerRouteComponent {
59353
59385
  * Inizializza il componente preparando stato, sottoscrizioni e primi caricamenti richiesti.
59354
59386
  */
59355
59387
  async ngOnInit() {
59356
- designerComponentPromise ??= import('./wuic-framework-lib-designer.component-N_CfbGnr.mjs').then((m) => m.DesignerComponent);
59388
+ designerComponentPromise ??= import('./wuic-framework-lib-designer.component-DA7abiLW.mjs').then((m) => m.DesignerComponent);
59357
59389
  this.loadedComponent = await designerComponentPromise;
59358
59390
  }
59359
59391
  /**
@@ -59430,7 +59462,7 @@ class LazyPivotBuilderComponent {
59430
59462
  this.cdr = cdr;
59431
59463
  }
59432
59464
  async ngOnInit() {
59433
- pivotBuilderComponentPromise ??= import('./wuic-framework-lib-pivot-builder.component-0gJz9RWr.mjs').then((m) => m.PivotBuilderComponent);
59465
+ pivotBuilderComponentPromise ??= import('./wuic-framework-lib-pivot-builder.component-BqUUwrJM.mjs').then((m) => m.PivotBuilderComponent);
59434
59466
  this.loadedComponent = await pivotBuilderComponentPromise;
59435
59467
  this.cdr.markForCheck();
59436
59468
  }
@@ -59469,7 +59501,7 @@ class WorkflowDesignerRouteComponent {
59469
59501
  * Inizializza il componente preparando stato, sottoscrizioni e primi caricamenti richiesti.
59470
59502
  */
59471
59503
  async ngOnInit() {
59472
- workflowDesignerComponentPromise ??= import('./wuic-framework-lib-workflow-designer.component-epj3oKq3.mjs').then((m) => m.WorkflowDesignerComponent);
59504
+ workflowDesignerComponentPromise ??= import('./wuic-framework-lib-workflow-designer.component-SE_7ZogW.mjs').then((m) => m.WorkflowDesignerComponent);
59473
59505
  this.loadedComponent = await workflowDesignerComponentPromise;
59474
59506
  }
59475
59507
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WorkflowDesignerRouteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -59526,7 +59558,7 @@ class LazyReportDesignerComponent {
59526
59558
  this.cdr = cdr;
59527
59559
  }
59528
59560
  async ngOnInit() {
59529
- reportDesignerComponentPromise ??= import('./wuic-framework-lib-report-designer.component-DcpdzFjh.mjs').then((m) => m.ReportDesignerComponent);
59561
+ reportDesignerComponentPromise ??= import('./wuic-framework-lib-report-designer.component-D_qbefud.mjs').then((m) => m.ReportDesignerComponent);
59530
59562
  this.loadedComponent = await reportDesignerComponentPromise;
59531
59563
  this.cdr.markForCheck();
59532
59564
  }
@@ -59581,7 +59613,7 @@ class LazyReportViewerComponent {
59581
59613
  this.cdr = cdr;
59582
59614
  }
59583
59615
  async ngOnInit() {
59584
- reportViewerComponentPromise ??= import('./wuic-framework-lib-report-viewer.component-BHpbGpp9.mjs').then((m) => m.ReportViewerComponent);
59616
+ reportViewerComponentPromise ??= import('./wuic-framework-lib-report-viewer.component-CIN2fY0P.mjs').then((m) => m.ReportViewerComponent);
59585
59617
  this.loadedComponent = await reportViewerComponentPromise;
59586
59618
  this.cdr.markForCheck();
59587
59619
  }
@@ -63504,7 +63536,7 @@ class WuicRagChatbotComponent {
63504
63536
  return JSON.stringify(err);
63505
63537
  }
63506
63538
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WuicRagChatbotComponent, deps: [{ token: WuicRagService }, { token: i0.ChangeDetectorRef }, { token: ChatbotHostRegistryService }, { token: i1$2.ActivatedRoute, optional: true }, { token: i1$2.Router, optional: true }, { token: NotificationRealtimeService, optional: true }, { token: MetadataProviderService, optional: true }], target: i0.ɵɵFactoryTarget.Component });
63507
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: WuicRagChatbotComponent, isStandalone: true, selector: "wuic-rag-chatbot", inputs: { title: "title", mode: "mode", topK: "topK", showSources: "showSources", maxHistory: "maxHistory", placeholder: "placeholder", model: "model", chatHeight: "chatHeight", showClearButton: "showClearButton", persistSession: "persistSession", sendRouteContext: "sendRouteContext", allowProfileToggle: "allowProfileToggle" }, outputs: { resultSelected: "resultSelected", errorOccurred: "errorOccurred", turnAdded: "turnAdded" }, viewQueries: [{ propertyName: "inputTextarea", first: true, predicate: ["inputTextarea"], descendants: true }, { propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true }], ngImport: i0, template: "<div class=\"wuic-rag-chatbot\" style=\"height: 100%;\">\n\n <!-- Header -->\n <div class=\"wuic-rag-chatbot__header\">\n <div class=\"wuic-rag-chatbot__title\">\n <i class=\"pi pi-comments wuic-rag-chatbot__title-icon\" aria-hidden=\"true\"></i>\n <span>{{ title }}</span>\n </div>\n <div class=\"wuic-rag-chatbot__header-actions\">\n @if (allowProfileToggle) {\n <p-button [label]=\"profileToggleLabel\" [icon]=\"profileOverride === 'release' ? 'pi pi-eye' : 'pi pi-code'\"\n [severity]=\"profileOverride === 'release' ? 'warn' : 'secondary'\" [outlined]=\"true\" [rounded]=\"true\"\n size=\"small\" (onClick)=\"toggleProfile()\" [pTooltip]=\"profileOverride === 'release'\n ? ('rag_chatbot.profile_toggle.tooltip_release' | translate)\n : ('rag_chatbot.profile_toggle.tooltip_dev' | translate)\" tooltipPosition=\"bottom\"\n [ariaLabel]=\"'rag_chatbot.profile_toggle.aria_label' | translate\">\n </p-button>\n }\n <p-badge [value]=\"modeBadgeLabel\" [severity]=\"modeBadgeSeverity\" [pTooltip]=\"healthInfo\n ? ('chunks indicizzati: ' + healthInfo.docs_loaded + ' | modello: ' + healthInfo.default_model)\n : (healthError || ('rag_chatbot.health_indicator.connecting' | translate))\" tooltipPosition=\"bottom\">\n </p-badge>\n <!-- Context cue: cerchio colorato che riflette % context window usato.\n Click + conferma -> /compact. Tooltip mostra k/k token + %. -->\n @if (contextWindowMax) {\n <button type=\"button\"\n class=\"wuic-rag-chatbot__context-cue\"\n [style.background]=\"contextCueColor\"\n [pTooltip]=\"contextCueTooltip\"\n tooltipPosition=\"bottom\"\n [disabled]=\"compactingSession || !currentSessionId\"\n (click)=\"onContextCueClick()\"\n attr.aria-label=\"Compatta sessione (context {{ contextUsagePercent }}%)\">\n @if (compactingSession) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n } @else {\n <span>{{ contextUsagePercent }}%</span>\n }\n </button>\n }\n <!-- Bottone Sessioni: apre popup con lista sessioni ordinate desc per updatedAt. -->\n <p-button icon=\"pi pi-list\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"openSessionsDialog()\" [pTooltip]=\"'rag_chatbot.sessions_button.tooltip' | translate\"\n tooltipPosition=\"bottom\" [ariaLabel]=\"'rag_chatbot.sessions_button.aria_label' | translate\">\n </p-button>\n @if (showClearButton) {\n <p-button icon=\"pi pi-trash\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n [disabled]=\"history.length === 0 || loading || clearingHistory\" [loading]=\"clearingHistory\"\n (onClick)=\"onClearHistory()\" [pTooltip]=\"'rag_chatbot.clear_button.tooltip' | translate\"\n tooltipPosition=\"bottom\" [ariaLabel]=\"'rag_chatbot.clear_button.aria_label' | translate\">\n </p-button>\n }\n </div>\n </div>\n\n <!-- Health error banner -->\n @if (healthError) {\n <p-message severity=\"error\" [text]=\"('rag_chatbot.health_error.banner' | translate) + ': ' + healthError\"\n styleClass=\"wuic-rag-chatbot__error-banner\"></p-message>\n }\n\n <!-- Chat history -->\n <div #scrollContainer class=\"wuic-rag-chatbot__history\" role=\"log\" aria-live=\"polite\">\n @if (history.length === 0) {\n <div class=\"wuic-rag-chatbot__empty\">\n <i class=\"pi pi-info-circle wuic-rag-chatbot__empty-icon\" aria-hidden=\"true\"></i>\n <p>{{ 'rag_chatbot.empty.prompt' | translate }}</p>\n <ul class=\"wuic-rag-chatbot__suggestions\">\n <li>{{ 'rag_chatbot.suggestion.invalidate_cache' | translate }}</li>\n <li>{{ 'rag_chatbot.suggestion.spreadsheet_massive' | translate }}</li>\n <li>{{ 'rag_chatbot.suggestion.save_report' | translate }}</li>\n </ul>\n </div>\n }\n\n @for (turn of history; track turn.messageId || $index) {\n <div class=\"wuic-rag-chatbot__turn\" [attr.data-message-id]=\"turn.messageId || null\"\n [class.wuic-rag-chatbot__turn--user]=\"turn.role === 'user'\"\n [class.wuic-rag-chatbot__turn--assistant]=\"turn.role === 'assistant'\">\n\n <div class=\"wuic-rag-chatbot__turn-header\">\n <i class=\"pi\" [class.pi-user]=\"turn.role === 'user'\" [class.pi-android]=\"turn.role === 'assistant'\"\n aria-hidden=\"true\"></i>\n <span>{{ (turn.role === 'user' ? 'rag_chatbot.turn.role_user' : 'rag_chatbot.turn.role_assistant') | translate }}</span>\n @if (turn.mode) {\n <span class=\"wuic-rag-chatbot__turn-mode\">[{{ turn.mode === 'rag-llm' ? 'RAG+LLM' : 'retrieval' }}]</span>\n }\n </div>\n\n @if (turn.loading) {\n <div class=\"wuic-rag-chatbot__turn-loading\">\n <p-progressSpinner styleClass=\"wuic-rag-chatbot__spinner\" strokeWidth=\"3\"\n animationDuration=\".9s\"></p-progressSpinner>\n <span>{{ 'rag_chatbot.turn.loading' | translate }}</span>\n </div>\n } @else if (turn.errorMessage) {\n <p-message severity=\"error\" [text]=\"(turn.errorMessage | translate)\" styleClass=\"wuic-rag-chatbot__turn-error\"></p-message>\n } @else {\n <div class=\"wuic-rag-chatbot__turn-content\">{{ turn.content }}</div>\n @if (turn.warning) {\n <p-message severity=\"warn\" [text]=\"turn.warning\" styleClass=\"wuic-rag-chatbot__turn-warning\"></p-message>\n }\n <!-- Box \"Proposta azione\" emessa dall'LLM via tool-use. Renderizzato solo\n per i turn assistant che hanno proposedAction valorizzata. Il monaco\n editor riusa la stessa codeContext del metadata-editor sulla colonna\n equivalente (autocomplete identico, type-check identico). -->\n @if (turn.role === 'assistant' && turn.proposedAction) {\n <div class=\"wuic-rag-chatbot__action-box\"\n [class.wuic-rag-chatbot__action-box--applied]=\"turn.proposedActionAppliedAt\"\n [class.wuic-rag-chatbot__action-box--error]=\"turn.applyError\">\n <div class=\"wuic-rag-chatbot__action-box-header\">\n <i class=\"pi pi-bolt\" aria-hidden=\"true\"></i>\n <span class=\"wuic-rag-chatbot__action-box-title\">\n {{ proposedActionTitle(turn.proposedAction) }}\n @if (turn.proposedAction.label) {\n <span class=\"wuic-rag-chatbot__action-box-label\">\"{{ turn.proposedAction.label }}\"</span>\n }\n @if (turn.proposedAction.route) {\n <span class=\"wuic-rag-chatbot__action-box-route\">{{ 'rag_chatbot.action_box.label_on_route_prefix' | translate }} <code>{{ turn.proposedAction.route }}</code></span>\n }\n </span>\n </div>\n @if (turn.proposedAction.rationale) {\n <div class=\"wuic-rag-chatbot__action-box-rationale\">{{ turn.proposedAction.rationale }}</div>\n }\n <!-- Hint trasparenza UX: l'utente sa in anticipo che, oltre a creare\n la toolbar action, abiliteremo md_multiple_selection sulla route\n (necessario per le bulk operations che leggono getSelectedRows). -->\n @if (turn.proposedAction.requires_multi_selection) {\n <div class=\"wuic-rag-chatbot__action-box-side-effect\">\n <i class=\"pi pi-info-circle\"></i>\n {{ 'rag_chatbot.action_box.side_effect_multi_selection' | translate }}\n </div>\n }\n <!-- Campi NON-body della proposta (es. per table_style: classe/stile;\n per toolbar/row_action: label+icon; per sql_metadata_field: field\n target). Visibili sopra la textarea del body cosi' l'utente capisce\n a colpo d'occhio COSA viene applicato oltre al CODICE del callback.\n Sorgente: proposedActionExtraFields(kind) nel .ts. -->\n @if (proposedActionExtraFields(turn.proposedAction).length > 0) {\n <div class=\"wuic-rag-chatbot__action-box-extra-fields\">\n @for (extra of proposedActionExtraFields(turn.proposedAction); track extra.fieldName) {\n <div class=\"wuic-rag-chatbot__action-box-extra-field\">\n <label class=\"wuic-rag-chatbot__action-box-extra-field-label\">{{ extra.label }}</label>\n <input\n class=\"wuic-rag-chatbot__action-box-extra-field-value\"\n [disabled]=\"!!turn.proposedActionAppliedAt\"\n [ngModel]=\"extra.value\"\n (ngModelChange)=\"onProposedExtraFieldEdit(turn, extra, $event)\"\n [name]=\"'extra-' + turn.messageId + '-' + extra.fieldName\">\n </div>\n }\n </div>\n }\n @if (proposedActionJsBody(turn.proposedAction)) {\n <!-- Pattern <lib-code-area-editor>: textarea readonly (zero overhead,\n niente flash nero, contenuto immediatamente visibile) di default.\n Toggle \"Apri editor avanzato\" attiva `editorMode` -> @defer carica\n Monaco con codeContext+autocomplete. Doppio livello di lazy:\n @defer in HTML + LazyCodeEditorComponent fa import() dinamico.\n Risultato: Monaco scaricato SOLO se l'utente lo richiede.\n proposedActionJsBody() unifica callback_js / condition_js / formula_js\n in base al kind: stesso UX per tutti i kind che hanno una body JS. -->\n <div class=\"wuic-rag-chatbot__action-box-editor-toolbar\">\n <p-button\n [label]=\"(turn.editorMode ? 'rag_chatbot.action_box.editor_close' : 'rag_chatbot.action_box.editor_open') | translate\"\n [icon]=\"turn.editorMode ? 'pi pi-times' : 'pi pi-code'\"\n severity=\"secondary\"\n [text]=\"true\"\n size=\"small\"\n (onClick)=\"toggleEditorMode(turn)\">\n </p-button>\n </div>\n @if (!turn.editorMode) {\n <textarea\n class=\"wuic-rag-chatbot__action-box-textarea\"\n rows=\"10\"\n [disabled]=\"!!turn.proposedActionAppliedAt\"\n [ngModel]=\"proposedActionJsBody(turn.proposedAction)\"\n (ngModelChange)=\"onProposedBodyEdit(turn, $event)\"\n [name]=\"'body-' + turn.messageId\">\n </textarea>\n } @else {\n @defer (when turn.editorMode) {\n <div class=\"wuic-rag-chatbot__action-box-editor\">\n <wuic-code-editor-lazy\n [field]=\"getProposedActionField(turn)\"\n [record]=\"getProposedActionRecord(turn)\"\n style=\"display: block; width: 100%; height: 100%;\">\n </wuic-code-editor-lazy>\n </div>\n } @loading (minimum 200ms) {\n <div class=\"wuic-rag-chatbot__action-box-editor-loading\">\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n {{ 'rag_chatbot.action_box.editor_loading' | translate }}\n </div>\n }\n }\n }\n <div class=\"wuic-rag-chatbot__action-box-actions\">\n @if (turn.proposedActionAppliedAt) {\n <span class=\"wuic-rag-chatbot__action-box-applied\">\n <i class=\"pi pi-check-circle\" aria-hidden=\"true\"></i>\n {{ 'rag_chatbot.action_box.applied' | translate }}\n </span>\n } @else {\n <p-button\n [label]=\"'rag_chatbot.action_box.apply' | translate\"\n icon=\"pi pi-check\"\n severity=\"primary\"\n size=\"small\"\n [loading]=\"!!turn.applyingAction\"\n (onClick)=\"onApplyAction(turn)\">\n </p-button>\n }\n @if (turn.applyError) {\n <span class=\"wuic-rag-chatbot__action-box-error\">\n <i class=\"pi pi-times-circle\" aria-hidden=\"true\"></i>\n {{ turn.applyError }}\n </span>\n }\n </div>\n </div>\n }\n @if (showSources && turn.role === 'assistant' && turn.sources && turn.sources.length > 0) {\n @let dedupedSources = dedupSources(turn.sources);\n <div class=\"wuic-rag-chatbot__sources\">\n <div class=\"wuic-rag-chatbot__sources-label\">{{ 'rag_chatbot.sources.label' | translate }}</div>\n <div class=\"wuic-rag-chatbot__sources-list\">\n @for (source of dedupedSources; track source.chunk_id || source.rank) {\n <p-chip [label]=\"sourceLabel(source, dedupedSources)\"\n [icon]=\"isDocsSource(source) ? 'pi pi-book' : (isSkillSource(source) ? 'pi pi-wrench' : 'pi pi-file-edit')\"\n [removable]=\"false\" [pTooltip]=\"sourceTooltip(source)\" tooltipPosition=\"top\" [styleClass]=\"isDocsSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--docs'\n : (isSkillSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--skill'\n : 'wuic-rag-chatbot__source-chip')\" (click)=\"onSourceClick(source)\">\n </p-chip>\n }\n </div>\n </div>\n }\n <!-- Follow-up questions emesse dall'LLM via tool `suggest_followups`.\n Renderizzate come chip cliccabili sotto la risposta: click popola\n l'input box (NON invia auto, l'utente puo' editare prima). -->\n @if (turn.role === 'assistant' && turn.followupQuestions && turn.followupQuestions.length > 0) {\n <div class=\"wuic-rag-chatbot__followups\">\n <div class=\"wuic-rag-chatbot__followups-label\">{{ 'rag_chatbot.followups.label' | translate }}</div>\n <div class=\"wuic-rag-chatbot__followups-list\">\n @for (q of turn.followupQuestions; track q) {\n <p-chip\n [label]=\"q\"\n icon=\"pi pi-arrow-right\"\n styleClass=\"wuic-rag-chatbot__followup-chip\"\n (click)=\"applyFollowup(q)\">\n </p-chip>\n }\n </div>\n </div>\n }\n @if (turn.tokensIn || turn.tokensOut) {\n <div class=\"wuic-rag-chatbot__tokens\">\n tokens in/out: {{ turn.tokensIn ?? '?' }}/{{ turn.tokensOut ?? '?' }}\n </div>\n }\n }\n </div>\n }\n </div>\n\n <!-- Input area -->\n <div class=\"wuic-rag-chatbot__input-area\">\n <textarea #inputTextarea pTextarea class=\"wuic-rag-chatbot__input\" [(ngModel)]=\"inputValue\"\n (keydown)=\"onKeyDown($event)\" [placeholder]=\"placeholder\" [disabled]=\"loading || !!healthError\"\n [autoResize]=\"true\" rows=\"1\" [attr.aria-label]=\"'rag_chatbot.input.aria_label' | translate\">\n </textarea>\n <p-button icon=\"pi pi-send\" [disabled]=\"sendDisabled\" [loading]=\"loading\" (onClick)=\"onSend()\" severity=\"primary\"\n [ariaLabel]=\"'rag_chatbot.input.send_aria_label' | translate\">\n </p-button>\n </div>\n\n <!-- Dialog preview snippet sorgente.\n Mostra SOLO il contenuto del chunk (gia' embedded nella response RAG).\n NON espone il path del file ne' apre editor esterni: il codebase del\n framework vive sul repo dev e non deve essere linkato all'utente finale. -->\n @if (selectedSource) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw' }\" [header]=\"sourceDialogTitle(selectedSource)\"\n styleClass=\"wuic-rag-chatbot__source-dialog\" (onHide)=\"closeSourcePreview()\">\n <div class=\"wuic-rag-chatbot__source-meta\">\n @if (sourceFilePath(selectedSource); as fpath) {\n <span class=\"wuic-rag-chatbot__source-tag wuic-rag-chatbot__source-tag--file\">\n <i class=\"pi pi-file\" aria-hidden=\"true\"></i>\n {{ fpath }}\n </span>\n }\n @if (selectedSource.symbol_type) {\n <span class=\"wuic-rag-chatbot__source-tag\">{{ selectedSource.symbol_type }}</span>\n }\n @if (selectedSource.start_line && selectedSource.end_line) {\n <span class=\"wuic-rag-chatbot__source-tag\">linee {{ selectedSource.start_line }}-{{ selectedSource.end_line\n }}</span>\n }\n @if (selectedSource.score_vector !== null && selectedSource.score_vector !== undefined) {\n <span class=\"wuic-rag-chatbot__source-tag\">vec {{ selectedSource.score_vector | number:'1.2-2' }}</span>\n }\n </div>\n <pre\n class=\"wuic-rag-chatbot__source-snippet\"><code>{{ selectedSource.snippet || ('rag_chatbot.source.snippet_unavailable' | translate) }}</code></pre>\n </p-dialog>\n }\n\n <!-- Dialog \"Sessioni\": elenco ordinato desc per updatedAt. Per ogni riga:\n data + titolo (truncated 100 char, tooltip full) + bottone rinomina\n inline + click row = switch session. -->\n @if (sessionsDialogOpen) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw', maxHeight: '85vh' }\"\n [header]=\"'rag_chatbot.sessions_dialog.header' | translate\"\n styleClass=\"wuic-rag-chatbot__sessions-dialog\"\n (onHide)=\"closeSessionsDialog()\">\n @if (sessionsLoading) {\n <div class=\"wuic-rag-chatbot__sessions-loading\">\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i> {{ 'rag_chatbot.sessions_dialog.loading' | translate }}\n </div>\n } @else if (sessionsList.length === 0) {\n <div class=\"wuic-rag-chatbot__sessions-empty\">{{ 'rag_chatbot.sessions_dialog.empty' | translate }}</div>\n } @else {\n <ul class=\"wuic-rag-chatbot__sessions-list\">\n @for (s of sessionsList; track s.id) {\n <li class=\"wuic-rag-chatbot__session-item\"\n [class.wuic-rag-chatbot__session-item--current]=\"s.id === currentSessionId\"\n (click)=\"selectSession(s)\">\n <div class=\"wuic-rag-chatbot__session-date\">{{ sessionDate(s) }}</div>\n @if (sessionRenameEditing[s.id]) {\n <div class=\"wuic-rag-chatbot__session-edit\" (click)=\"$event.stopPropagation()\">\n <input type=\"text\" pInputText\n [(ngModel)]=\"sessionRenameDraft[s.id]\"\n [autofocus]=\"true\"\n maxlength=\"200\"\n class=\"wuic-rag-chatbot__session-edit-input\"\n (keydown.enter)=\"confirmRenameSession(s, $event)\"\n (keydown.escape)=\"cancelRenameSession(s)\">\n <p-button icon=\"pi pi-check\" severity=\"success\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"confirmRenameSession(s, $event)\" [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_confirm_aria' | translate\"></p-button>\n <p-button icon=\"pi pi-times\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"cancelRenameSession(s)\" [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_cancel_aria' | translate\"></p-button>\n </div>\n } @else {\n <div class=\"wuic-rag-chatbot__session-row\">\n <span class=\"wuic-rag-chatbot__session-title\" [pTooltip]=\"s.title || ('rag_chatbot.sessions_dialog.untitled' | translate)\" tooltipPosition=\"top\">\n {{ sessionPreview(s) }}\n </span>\n <p-button icon=\"pi pi-pencil\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"startRenameSession(s, $event)\" [pTooltip]=\"'rag_chatbot.sessions_dialog.rename' | translate\" tooltipPosition=\"left\"\n [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_aria' | translate\"></p-button>\n <p-button icon=\"pi pi-trash\" severity=\"danger\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"onDeleteSessionFromList(s, $event)\" [pTooltip]=\"'rag_chatbot.sessions_dialog.delete' | translate\" tooltipPosition=\"left\"\n [ariaLabel]=\"'rag_chatbot.sessions_dialog.delete_aria' | translate\"></p-button>\n </div>\n }\n </li>\n }\n </ul>\n }\n </p-dialog>\n }\n\n <!-- Dialog context-overflow: l'ultima call ha fallito perche' la history\n sfora il context window. 2 azioni: Compatta ora (riassume e prosegue\n stessa session) / Nuova sessione (clear + ricomincia). -->\n @if (contextOverflowDialogOpen) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"false\"\n [style]=\"{ width: '480px', maxWidth: '92vw' }\"\n [header]=\"'rag_chatbot.overflow_dialog.header' | translate\"\n styleClass=\"wuic-rag-chatbot__overflow-dialog\"\n (onHide)=\"contextOverflowDialogOpen = false\">\n <p>{{ 'rag_chatbot.overflow_dialog.intro' | translate }}</p>\n <ul>\n <li><strong>{{ 'rag_chatbot.overflow_dialog.option_compact_title' | translate }}</strong> \u2014 {{ 'rag_chatbot.overflow_dialog.option_compact_desc' | translate }}</li>\n <li><strong>{{ 'rag_chatbot.overflow_dialog.option_new_title' | translate }}</strong> \u2014 {{ 'rag_chatbot.overflow_dialog.option_new_desc' | translate }}</li>\n </ul>\n <div class=\"wuic-rag-chatbot__overflow-actions\">\n <p-button [label]=\"'rag_chatbot.overflow_dialog.option_compact_title' | translate\" icon=\"pi pi-compress\" severity=\"primary\"\n [loading]=\"compactingSession\"\n (onClick)=\"doCompactSession()\"></p-button>\n <p-button [label]=\"'rag_chatbot.overflow_dialog.option_new_title' | translate\" icon=\"pi pi-refresh\" severity=\"secondary\"\n (onClick)=\"startNewSession()\"></p-button>\n </div>\n </p-dialog>\n }\n\n</div>", styles: [".wuic-rag-chatbot{--wuic-rag-chatbot-radius: .5rem;--wuic-rag-chatbot-padding: .75rem;--wuic-rag-chatbot-gap: .5rem;display:flex;flex-direction:column;width:100%;height:100%;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:var(--wuic-rag-chatbot-radius);background-color:var(--p-content-background, transparent);color:inherit;overflow:hidden;font-family:inherit}.wuic-rag-chatbot__header{display:flex;align-items:center;justify-content:space-between;padding:var(--wuic-rag-chatbot-padding);border-bottom:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__title{display:flex;align-items:center;gap:.5rem;font-weight:600;color:inherit}.wuic-rag-chatbot__title-icon{color:var(--p-primary-color, #3b82f6)}.wuic-rag-chatbot__header-actions{display:flex;align-items:center;gap:.5rem}.wuic-rag-chatbot__context-cue{width:28px;height:28px;border-radius:50%;border:2px solid rgba(255,255,255,.85);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:.62rem;font-weight:700;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.35);box-shadow:0 0 0 1px #0000000f,0 1px 3px #0000001f;transition:transform .12s ease,box-shadow .12s ease,opacity .12s ease;padding:0}.wuic-rag-chatbot__context-cue:hover:not(:disabled){transform:scale(1.08);box-shadow:0 0 0 1px #0000001a,0 2px 6px #0003}.wuic-rag-chatbot__context-cue:disabled{cursor:default;opacity:.65}.wuic-rag-chatbot__context-cue>i{font-size:.85rem}.wuic-rag-chatbot__sessions-loading,.wuic-rag-chatbot__sessions-empty{padding:1.5rem;text-align:center;color:var(--p-text-muted-color, #6b7280);font-style:italic}.wuic-rag-chatbot__sessions-list{list-style:none;margin:0;padding:0;max-height:60vh;overflow-y:auto}.wuic-rag-chatbot__session-item{padding:.6rem .85rem;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;margin-bottom:.4rem;cursor:pointer;transition:background .12s ease,border-color .12s ease}.wuic-rag-chatbot__session-item:hover{background:var(--p-surface-50, #f3f4f6);border-color:var(--p-primary-300, rgba(59, 130, 246, .4))}.wuic-rag-chatbot__session-item--current{background:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-400, rgba(59, 130, 246, .55))}.wuic-rag-chatbot__session-date{font-size:.72rem;color:var(--p-text-muted-color, #6b7280);font-family:ui-monospace,monospace;margin-bottom:.2rem}.wuic-rag-chatbot__session-row{display:flex;align-items:center;gap:.4rem}.wuic-rag-chatbot__session-title{flex:1 1 auto;font-size:.9rem;color:var(--p-text-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wuic-rag-chatbot__session-edit{display:flex;align-items:center;gap:.3rem}.wuic-rag-chatbot__session-edit-input{flex:1 1 auto;font-size:.85rem}.wuic-rag-chatbot__overflow-actions{display:flex;justify-content:flex-end;gap:.5rem;margin-top:1rem}.wuic-rag-chatbot__error-banner{margin:var(--wuic-rag-chatbot-padding);margin-bottom:0}.wuic-rag-chatbot__history{flex:1 1 auto;min-height:0;overflow-y:auto;padding:var(--wuic-rag-chatbot-padding);display:flex;flex-direction:column;gap:var(--wuic-rag-chatbot-gap);background-color:transparent}.wuic-rag-chatbot__empty{margin:auto;text-align:center;color:var(--p-text-muted-color, #6b7280);max-width:80%;padding:1.5rem 0}.wuic-rag-chatbot__empty-icon{font-size:1.5rem;margin-bottom:.5rem;display:block}.wuic-rag-chatbot__suggestions{list-style:none;padding:0;margin:.75rem 0 0;display:flex;flex-direction:column;gap:.25rem;text-align:left}.wuic-rag-chatbot__suggestions li{padding:.35rem .75rem;border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:.375rem;font-size:.875rem}.wuic-rag-chatbot__turn{display:flex;flex-direction:column;gap:.35rem;padding:.65rem .85rem;border-radius:var(--wuic-rag-chatbot-radius);max-width:92%;color:inherit;background-color:transparent;border:1px solid var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__turn--user{align-self:flex-end;background-color:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-200, rgba(59, 130, 246, .25))}.wuic-rag-chatbot__turn--assistant{align-self:flex-start;background-color:var(--p-surface-50, rgba(0, 0, 0, .025))}.wuic-rag-chatbot__turn-header{display:flex;align-items:center;gap:.4rem;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__turn-mode{margin-left:auto;font-family:ui-monospace,monospace;font-size:.7rem;opacity:.7}.wuic-rag-chatbot__turn-content{white-space:pre-wrap;word-break:break-word;line-height:1.45;font-size:.93rem;color:inherit}.wuic-rag-chatbot__turn-loading{display:flex;align-items:center;gap:.5rem;color:var(--p-text-muted-color, #6b7280);font-style:italic;font-size:.875rem}.wuic-rag-chatbot__spinner{width:1.25rem!important;height:1.25rem!important}.wuic-rag-chatbot__turn-error,.wuic-rag-chatbot__turn-warning{margin-top:.35rem}.wuic-rag-chatbot__sources{display:flex;flex-direction:column;gap:.35rem;margin-top:.5rem;padding-top:.5rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__sources-label{font-size:.72rem;text-transform:uppercase;letter-spacing:.05em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__sources-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__source-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease}.wuic-rag-chatbot__source-chip:hover{transform:translateY(-1px);box-shadow:0 1px 4px #0000001f}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:var(--p-blue-50, #eff6ff);color:var(--p-blue-700, #1d4ed8);border:1px solid var(--p-blue-200, #bfdbfe)}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:var(--p-blue-600, #2563eb)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:var(--p-green-50, #f0fdf4);color:var(--p-green-700, #15803d);border:1px solid var(--p-green-200, #bbf7d0)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:var(--p-green-600, #16a34a)}.wuic-rag-chatbot__tokens{margin-top:.4rem;font-size:.7rem;color:var(--p-text-muted-color, #6b7280);text-align:right;font-family:ui-monospace,monospace}.wuic-rag-chatbot__followups{display:flex;flex-direction:column;gap:.3rem;margin-top:.55rem;padding-top:.55rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__followups-label{font-size:.7rem;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__followups-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__followup-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease,background .12s ease}.wuic-rag-chatbot__followup-chip :host ::ng-deep .p-chip{background:var(--p-primary-50, rgba(59, 130, 246, .08));color:var(--p-primary-700, #1d4ed8);border:1px solid var(--p-primary-200, rgba(59, 130, 246, .25));font-size:.82rem;line-height:1.3;padding:.2rem .5rem}.wuic-rag-chatbot__followup-chip :host ::ng-deep .p-chip-icon{color:var(--p-primary-600, #2563eb);font-size:.75rem}.wuic-rag-chatbot__followup-chip:hover{transform:translateY(-1px);box-shadow:0 2px 6px #3b82f62e}.wuic-rag-chatbot__followup-chip:hover :host ::ng-deep .p-chip{background:var(--p-primary-100, rgba(59, 130, 246, .15))}.wuic-rag-chatbot__action-box{margin-top:.65rem;border:1px solid var(--p-primary-300, rgba(59, 130, 246, .45));border-radius:8px;background:var(--p-primary-50, rgba(59, 130, 246, .05));padding:.6rem .75rem;display:flex;flex-direction:column;gap:.45rem}.wuic-rag-chatbot__action-box--applied{border-color:var(--p-green-300, #86efac);background:var(--p-green-50, #f0fdf4)}.wuic-rag-chatbot__action-box--error{border-color:var(--p-red-300, #fca5a5);background:var(--p-red-50, #fef2f2)}.wuic-rag-chatbot__action-box-header{display:flex;align-items:center;gap:.45rem;font-size:.88rem;color:var(--p-primary-700, #1d4ed8);font-weight:600}.wuic-rag-chatbot__action-box-header>i{font-size:.95rem}.wuic-rag-chatbot__action-box-title{display:inline-flex;align-items:center;gap:.4rem;flex-wrap:wrap}.wuic-rag-chatbot__action-box-label{color:var(--p-text-color, #111827);font-weight:700}.wuic-rag-chatbot__action-box-route{color:var(--p-text-muted-color, #6b7280);font-weight:400;font-size:.8rem}.wuic-rag-chatbot__action-box-route>code{background:var(--p-surface-100, #f3f4f6);padding:1px 6px;border-radius:4px;font-family:ui-monospace,monospace;color:var(--p-text-color, #111827)}.wuic-rag-chatbot__action-box-rationale{font-size:.85rem;color:var(--p-text-color, #1f2937);line-height:1.4;font-style:italic}.wuic-rag-chatbot__action-box-side-effect{font-size:.8rem;color:var(--p-blue-700, #1d4ed8);background:var(--p-blue-50, #eff6ff);border-left:3px solid var(--p-blue-400, #60a5fa);padding:.4rem .6rem;border-radius:4px;display:flex;align-items:flex-start;gap:.4rem;line-height:1.35}.wuic-rag-chatbot__action-box-side-effect i{font-size:.9rem;margin-top:.1rem;flex-shrink:0}.wuic-rag-chatbot__action-box-editor-toolbar{display:flex;justify-content:flex-end;align-items:center}.wuic-rag-chatbot__action-box-extra-fields{display:flex;flex-direction:column;gap:6px;margin-bottom:8px}.wuic-rag-chatbot__action-box-extra-field{display:flex;align-items:center;gap:8px}.wuic-rag-chatbot__action-box-extra-field-label{flex:0 0 auto;min-width:100px;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__action-box-extra-field-value{flex:1 1 auto;min-width:0;font-family:ui-monospace,SFMono-Regular,Consolas,Cascadia Code,monospace;font-size:.78rem;padding:4px 8px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:4px;background:var(--p-surface-50, #fafafa);color:var(--p-text-color, #111827)}.wuic-rag-chatbot__action-box-extra-field-value:focus{outline:2px solid var(--p-primary-300, rgba(59, 130, 246, .4));outline-offset:-1px}.wuic-rag-chatbot__action-box-textarea{width:100%;min-height:200px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;padding:8px 10px;background:var(--p-surface-50, #fafafa);color:var(--p-text-color, #111827);font-family:ui-monospace,SFMono-Regular,Consolas,Cascadia Code,monospace;font-size:.78rem;line-height:1.45;resize:vertical;white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:hidden;overflow-y:auto;tab-size:2}.wuic-rag-chatbot__action-box-textarea:focus{outline:2px solid var(--p-primary-300, rgba(59, 130, 246, .4));outline-offset:-1px}.wuic-rag-chatbot__action-box-editor{height:240px;min-height:240px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;overflow:hidden;background:#1e1e1e}.wuic-rag-chatbot__action-box-editor-loading{display:flex;align-items:center;justify-content:center;gap:.5rem;min-height:100px;color:var(--p-text-muted-color, #6b7280);font-size:.85rem;font-style:italic;background:var(--p-surface-50, #fafafa);border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:6px}.wuic-rag-chatbot__action-box-actions{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap}.wuic-rag-chatbot__action-box-applied{display:inline-flex;align-items:center;gap:.35rem;color:var(--p-green-700, #15803d);font-weight:600;font-size:.85rem}.wuic-rag-chatbot__action-box-error{display:inline-flex;align-items:center;gap:.35rem;color:var(--p-red-700, #b91c1c);font-size:.8rem;font-family:ui-monospace,monospace}.wuic-rag-chatbot__input-area{display:flex;align-items:flex-end;gap:.5rem;padding:var(--wuic-rag-chatbot-padding);border-top:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__input{flex:1 1 auto;min-height:2.25rem;max-height:8rem;resize:none;color:inherit;background-color:transparent}.wuic-rag-chatbot__source-meta{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.75rem}.wuic-rag-chatbot__source-tag{display:inline-flex;align-items:center;gap:.25rem;padding:2px 8px;background:var(--p-surface-100, #f3f4f6);color:var(--p-text-color-secondary, #6b7280);border-radius:10px;font-size:.72rem;font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.wuic-rag-chatbot__source-tag--file{background:var(--p-surface-0, #fff);color:var(--p-primary-color, #2563eb);border:1px solid var(--p-primary-200, #bfdbfe);font-weight:500;max-width:100%;word-break:break-all;white-space:normal;line-height:1.3}.wuic-rag-chatbot__source-tag--file i{font-size:.7rem;flex-shrink:0}.wuic-rag-chatbot__source-snippet{margin:0;padding:12px 14px;max-height:420px;overflow:auto;background:var(--p-surface-50, #fafafa);border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.78rem;line-height:1.4;white-space:pre-wrap;word-break:break-word;color:var(--p-text-color, #111827)}:host-context(.theme-dark) .wuic-rag-chatbot__turn--user{background-color:#60a5fa2e;border-color:#60a5fa73}:host-context(.theme-dark) .wuic-rag-chatbot__turn--assistant{background-color:#ffffff0f;border-color:#ffffff1f}:host-context(.theme-dark) .wuic-rag-chatbot__turn-header,:host-context(.theme-dark) .wuic-rag-chatbot__turn-loading,:host-context(.theme-dark) .wuic-rag-chatbot__sources-label,:host-context(.theme-dark) .wuic-rag-chatbot__tokens{color:#ffffffa6}:host-context(.theme-dark) .wuic-rag-chatbot__sources{border-top-color:#ffffff2e}:host-context(.theme-dark) .wuic-rag-chatbot__input-area{border-top-color:#ffffff1f}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:#3b82f62e;color:#93c5fd;border:1px solid rgba(59,130,246,.4)}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:#bfdbfe}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:#22c55e2e;color:#86efac;border:1px solid rgba(34,197,94,.4)}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:#bbf7d0}:host-context(.theme-dark) .wuic-rag-chatbot__source-tag{background:#ffffff14;color:#ffffffc7}:host-context(.theme-dark) .wuic-rag-chatbot__source-tag--file{background:#3b82f624;color:#93c5fd;border-color:#3b82f673}:host-context(.theme-dark) .wuic-rag-chatbot__source-snippet{background:#00000052;border-color:#ffffff1f;color:#ffffffeb}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1$4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i7$1.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i13.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: BadgeModule }, { kind: "component", type: i10$1.Badge, selector: "p-badge", inputs: ["styleClass", "badgeSize", "size", "severity", "value", "badgeDisabled"] }, { kind: "ngmodule", type: ChipModule }, { kind: "component", type: i11$1.Chip, selector: "p-chip", inputs: ["label", "icon", "image", "alt", "styleClass", "disabled", "removable", "removeIcon", "chipProps"], outputs: ["onRemove", "onImageError"] }, { kind: "ngmodule", type: ScrollPanelModule }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i9$2.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: i7.Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant", "motionOptions"], outputs: ["onClose"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$3.DecimalPipe, name: "number" }, { kind: "pipe", type: i8.TranslatePipe, name: "translate" }], deferBlockDependencies: [() => [Promise.resolve().then(function () { return codeEditor_lazy_component; }).then(m => m.LazyCodeEditorComponent)]] });
63539
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: WuicRagChatbotComponent, isStandalone: true, selector: "wuic-rag-chatbot", inputs: { title: "title", mode: "mode", topK: "topK", showSources: "showSources", maxHistory: "maxHistory", placeholder: "placeholder", model: "model", chatHeight: "chatHeight", showClearButton: "showClearButton", persistSession: "persistSession", sendRouteContext: "sendRouteContext", allowProfileToggle: "allowProfileToggle" }, outputs: { resultSelected: "resultSelected", errorOccurred: "errorOccurred", turnAdded: "turnAdded" }, viewQueries: [{ propertyName: "inputTextarea", first: true, predicate: ["inputTextarea"], descendants: true }, { propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true }], ngImport: i0, template: "<div class=\"wuic-rag-chatbot\" style=\"height: 100%;\">\n\n <!-- Header -->\n <div class=\"wuic-rag-chatbot__header\">\n <div class=\"wuic-rag-chatbot__title\">\n <i class=\"pi pi-comments wuic-rag-chatbot__title-icon\" aria-hidden=\"true\"></i>\n <span>{{ title }}</span>\n </div>\n <div class=\"wuic-rag-chatbot__header-actions\">\n @if (allowProfileToggle) {\n <p-button [label]=\"profileToggleLabel\" [icon]=\"profileOverride === 'release' ? 'pi pi-eye' : 'pi pi-code'\"\n [severity]=\"profileOverride === 'release' ? 'warn' : 'secondary'\" [outlined]=\"true\" [rounded]=\"true\"\n size=\"small\" (onClick)=\"toggleProfile()\" [pTooltip]=\"profileOverride === 'release'\n ? ('rag_chatbot.profile_toggle.tooltip_release' | translate)\n : ('rag_chatbot.profile_toggle.tooltip_dev' | translate)\" tooltipPosition=\"bottom\"\n [ariaLabel]=\"'rag_chatbot.profile_toggle.aria_label' | translate\">\n </p-button>\n }\n <p-badge [value]=\"modeBadgeLabel\" [severity]=\"modeBadgeSeverity\" [pTooltip]=\"healthInfo\n ? ('chunks indicizzati: ' + healthInfo.docs_loaded + ' | modello: ' + healthInfo.default_model)\n : (healthError || ('rag_chatbot.health_indicator.connecting' | translate))\" tooltipPosition=\"bottom\">\n </p-badge>\n <!-- Context cue: cerchio colorato che riflette % context window usato.\n Click + conferma -> /compact. Tooltip mostra k/k token + %. -->\n @if (contextWindowMax) {\n <button type=\"button\"\n class=\"wuic-rag-chatbot__context-cue\"\n [style.background]=\"contextCueColor\"\n [pTooltip]=\"contextCueTooltip\"\n tooltipPosition=\"bottom\"\n [disabled]=\"compactingSession || !currentSessionId\"\n (click)=\"onContextCueClick()\"\n attr.aria-label=\"Compatta sessione (context {{ contextUsagePercent }}%)\">\n @if (compactingSession) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n } @else {\n <span>{{ contextUsagePercent }}%</span>\n }\n </button>\n }\n <!-- Bottone Sessioni: apre popup con lista sessioni ordinate desc per updatedAt. -->\n <p-button icon=\"pi pi-list\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"openSessionsDialog()\" [pTooltip]=\"'rag_chatbot.sessions_button.tooltip' | translate\"\n tooltipPosition=\"bottom\" [ariaLabel]=\"'rag_chatbot.sessions_button.aria_label' | translate\">\n </p-button>\n @if (showClearButton) {\n <p-button icon=\"pi pi-trash\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n [disabled]=\"history.length === 0 || loading || clearingHistory\" [loading]=\"clearingHistory\"\n (onClick)=\"onClearHistory()\" [pTooltip]=\"'rag_chatbot.clear_button.tooltip' | translate\"\n tooltipPosition=\"bottom\" [ariaLabel]=\"'rag_chatbot.clear_button.aria_label' | translate\">\n </p-button>\n }\n </div>\n </div>\n\n <!-- Health error banner -->\n @if (healthError) {\n <p-message severity=\"error\" [text]=\"('rag_chatbot.health_error.banner' | translate) + ': ' + healthError\"\n styleClass=\"wuic-rag-chatbot__error-banner\"></p-message>\n }\n\n <!-- Chat history -->\n <div #scrollContainer class=\"wuic-rag-chatbot__history\" role=\"log\" aria-live=\"polite\">\n @if (history.length === 0) {\n <div class=\"wuic-rag-chatbot__empty\">\n <i class=\"pi pi-info-circle wuic-rag-chatbot__empty-icon\" aria-hidden=\"true\"></i>\n <p>{{ 'rag_chatbot.empty.prompt' | translate }}</p>\n <ul class=\"wuic-rag-chatbot__suggestions\">\n <li>{{ 'rag_chatbot.suggestion.invalidate_cache' | translate }}</li>\n <li>{{ 'rag_chatbot.suggestion.spreadsheet_massive' | translate }}</li>\n <li>{{ 'rag_chatbot.suggestion.save_report' | translate }}</li>\n </ul>\n </div>\n }\n\n @for (turn of history; track turn.messageId || $index) {\n <div class=\"wuic-rag-chatbot__turn\" [attr.data-message-id]=\"turn.messageId || null\"\n [class.wuic-rag-chatbot__turn--user]=\"turn.role === 'user'\"\n [class.wuic-rag-chatbot__turn--assistant]=\"turn.role === 'assistant'\">\n\n <div class=\"wuic-rag-chatbot__turn-header\">\n <i class=\"pi\" [class.pi-user]=\"turn.role === 'user'\" [class.pi-android]=\"turn.role === 'assistant'\"\n aria-hidden=\"true\"></i>\n <span>{{ (turn.role === 'user' ? 'rag_chatbot.turn.role_user' : 'rag_chatbot.turn.role_assistant') | translate }}</span>\n @if (turn.mode) {\n <span class=\"wuic-rag-chatbot__turn-mode\">[{{ turn.mode === 'rag-llm' ? 'RAG+LLM' : 'retrieval' }}]</span>\n }\n </div>\n\n @if (turn.loading) {\n <div class=\"wuic-rag-chatbot__turn-loading\">\n <p-progressSpinner styleClass=\"wuic-rag-chatbot__spinner\" strokeWidth=\"3\"\n animationDuration=\".9s\"></p-progressSpinner>\n <span>{{ 'rag_chatbot.turn.loading' | translate }}</span>\n </div>\n } @else if (turn.errorMessage) {\n <p-message severity=\"error\" [text]=\"(turn.errorMessage | translate)\" styleClass=\"wuic-rag-chatbot__turn-error\"></p-message>\n } @else {\n <div class=\"wuic-rag-chatbot__turn-content\">{{ turn.content }}</div>\n @if (turn.warning) {\n <p-message severity=\"warn\" [text]=\"turn.warning\" styleClass=\"wuic-rag-chatbot__turn-warning\"></p-message>\n }\n <!-- Box \"Proposta azione\" emessa dall'LLM via tool-use. Renderizzato solo\n per i turn assistant che hanno proposedAction valorizzata. Il monaco\n editor riusa la stessa codeContext del metadata-editor sulla colonna\n equivalente (autocomplete identico, type-check identico). -->\n @if (turn.role === 'assistant' && turn.proposedAction) {\n <div class=\"wuic-rag-chatbot__action-box\"\n [class.wuic-rag-chatbot__action-box--applied]=\"turn.proposedActionAppliedAt\"\n [class.wuic-rag-chatbot__action-box--error]=\"turn.applyError\">\n <div class=\"wuic-rag-chatbot__action-box-header\">\n <i class=\"pi pi-bolt\" aria-hidden=\"true\"></i>\n <span class=\"wuic-rag-chatbot__action-box-title\">\n {{ proposedActionTitle(turn.proposedAction) }}\n @if (turn.proposedAction.label) {\n <span class=\"wuic-rag-chatbot__action-box-label\">\"{{ turn.proposedAction.label }}\"</span>\n }\n @if (turn.proposedAction.route) {\n <span class=\"wuic-rag-chatbot__action-box-route\">{{ 'rag_chatbot.action_box.label_on_route_prefix' | translate }} <code>{{ turn.proposedAction.route }}</code></span>\n }\n </span>\n </div>\n @if (turn.proposedAction.rationale) {\n <div class=\"wuic-rag-chatbot__action-box-rationale\">{{ turn.proposedAction.rationale }}</div>\n }\n <!-- Hint trasparenza UX: l'utente sa in anticipo che, oltre a creare\n la toolbar action, abiliteremo md_multiple_selection sulla route\n (necessario per le bulk operations che leggono getSelectedRows). -->\n @if (turn.proposedAction.requires_multi_selection) {\n <div class=\"wuic-rag-chatbot__action-box-side-effect\">\n <i class=\"pi pi-info-circle\"></i>\n {{ 'rag_chatbot.action_box.side_effect_multi_selection' | translate }}\n </div>\n }\n <!-- Campi NON-body della proposta (es. per table_style: classe/stile;\n per toolbar/row_action: label+icon; per sql_metadata_field: field\n target). Visibili sopra la textarea del body cosi' l'utente capisce\n a colpo d'occhio COSA viene applicato oltre al CODICE del callback.\n Sorgente: proposedActionExtraFields(kind) nel .ts. -->\n @if (proposedActionExtraFields(turn.proposedAction).length > 0) {\n <div class=\"wuic-rag-chatbot__action-box-extra-fields\">\n @for (extra of proposedActionExtraFields(turn.proposedAction); track extra.fieldName) {\n <div class=\"wuic-rag-chatbot__action-box-extra-field\">\n <label class=\"wuic-rag-chatbot__action-box-extra-field-label\">{{ extra.label }}</label>\n <input\n class=\"wuic-rag-chatbot__action-box-extra-field-value\"\n [disabled]=\"!!turn.proposedActionAppliedAt\"\n [ngModel]=\"extra.value\"\n (ngModelChange)=\"onProposedExtraFieldEdit(turn, extra, $event)\"\n [name]=\"'extra-' + turn.messageId + '-' + extra.fieldName\">\n </div>\n }\n </div>\n }\n @if (proposedActionJsBody(turn.proposedAction)) {\n <!-- Pattern <lib-code-area-editor>: textarea readonly (zero overhead,\n niente flash nero, contenuto immediatamente visibile) di default.\n Toggle \"Apri editor avanzato\" attiva `editorMode` -> @defer carica\n Monaco con codeContext+autocomplete. Doppio livello di lazy:\n @defer in HTML + LazyCodeEditorComponent fa import() dinamico.\n Risultato: Monaco scaricato SOLO se l'utente lo richiede.\n proposedActionJsBody() unifica callback_js / condition_js / formula_js\n in base al kind: stesso UX per tutti i kind che hanno una body JS. -->\n <div class=\"wuic-rag-chatbot__action-box-editor-toolbar\">\n <p-button\n [label]=\"(turn.editorMode ? 'rag_chatbot.action_box.editor_close' : 'rag_chatbot.action_box.editor_open') | translate\"\n [icon]=\"turn.editorMode ? 'pi pi-times' : 'pi pi-code'\"\n severity=\"secondary\"\n [text]=\"true\"\n size=\"small\"\n (onClick)=\"toggleEditorMode(turn)\">\n </p-button>\n </div>\n @if (!turn.editorMode) {\n <textarea\n class=\"wuic-rag-chatbot__action-box-textarea\"\n rows=\"10\"\n [disabled]=\"!!turn.proposedActionAppliedAt\"\n [ngModel]=\"proposedActionJsBody(turn.proposedAction)\"\n (ngModelChange)=\"onProposedBodyEdit(turn, $event)\"\n [name]=\"'body-' + turn.messageId\">\n </textarea>\n } @else {\n @defer (when turn.editorMode) {\n <div class=\"wuic-rag-chatbot__action-box-editor\">\n <wuic-code-editor-lazy\n [field]=\"getProposedActionField(turn)\"\n [record]=\"getProposedActionRecord(turn)\"\n style=\"display: block; width: 100%; height: 100%;\">\n </wuic-code-editor-lazy>\n </div>\n } @loading (minimum 200ms) {\n <div class=\"wuic-rag-chatbot__action-box-editor-loading\">\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n {{ 'rag_chatbot.action_box.editor_loading' | translate }}\n </div>\n }\n }\n }\n <div class=\"wuic-rag-chatbot__action-box-actions\">\n @if (turn.proposedActionAppliedAt) {\n <span class=\"wuic-rag-chatbot__action-box-applied\">\n <i class=\"pi pi-check-circle\" aria-hidden=\"true\"></i>\n {{ 'rag_chatbot.action_box.applied' | translate }}\n </span>\n } @else {\n <p-button\n [label]=\"'rag_chatbot.action_box.apply' | translate\"\n icon=\"pi pi-check\"\n severity=\"primary\"\n size=\"small\"\n [loading]=\"!!turn.applyingAction\"\n (onClick)=\"onApplyAction(turn)\">\n </p-button>\n }\n @if (turn.applyError) {\n <span class=\"wuic-rag-chatbot__action-box-error\">\n <i class=\"pi pi-times-circle\" aria-hidden=\"true\"></i>\n {{ turn.applyError }}\n </span>\n }\n </div>\n </div>\n }\n @if (showSources && turn.role === 'assistant' && turn.sources && turn.sources.length > 0) {\n @let dedupedSources = dedupSources(turn.sources);\n <div class=\"wuic-rag-chatbot__sources\">\n <div class=\"wuic-rag-chatbot__sources-label\">{{ 'rag_chatbot.sources.label' | translate }}</div>\n <div class=\"wuic-rag-chatbot__sources-list\">\n @for (source of dedupedSources; track source.chunk_id || (source.rel_path + ':' + source.start_line + ':' + source.end_line + ':' + source.symbol_name)) {\n <p-chip [label]=\"sourceLabel(source, dedupedSources)\"\n [icon]=\"isDocsSource(source) ? 'pi pi-book' : (isSkillSource(source) ? 'pi pi-wrench' : 'pi pi-file-edit')\"\n [removable]=\"false\" [pTooltip]=\"sourceTooltip(source)\" tooltipPosition=\"top\" [styleClass]=\"isDocsSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--docs'\n : (isSkillSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--skill'\n : 'wuic-rag-chatbot__source-chip')\" (click)=\"onSourceClick(source)\">\n </p-chip>\n }\n </div>\n </div>\n }\n <!-- Follow-up questions emesse dall'LLM via tool `suggest_followups`.\n Renderizzate come chip cliccabili sotto la risposta: click popola\n l'input box (NON invia auto, l'utente puo' editare prima). -->\n @if (turn.role === 'assistant' && turn.followupQuestions && turn.followupQuestions.length > 0) {\n <div class=\"wuic-rag-chatbot__followups\">\n <div class=\"wuic-rag-chatbot__followups-label\">{{ 'rag_chatbot.followups.label' | translate }}</div>\n <div class=\"wuic-rag-chatbot__followups-list\">\n @for (q of turn.followupQuestions; track q) {\n <p-chip\n [label]=\"q\"\n icon=\"pi pi-arrow-right\"\n styleClass=\"wuic-rag-chatbot__followup-chip\"\n (click)=\"applyFollowup(q)\">\n </p-chip>\n }\n </div>\n </div>\n }\n @if (turn.tokensIn || turn.tokensOut) {\n <div class=\"wuic-rag-chatbot__tokens\">\n tokens in/out: {{ turn.tokensIn ?? '?' }}/{{ turn.tokensOut ?? '?' }}\n </div>\n }\n }\n </div>\n }\n </div>\n\n <!-- Input area -->\n <div class=\"wuic-rag-chatbot__input-area\">\n <textarea #inputTextarea pTextarea class=\"wuic-rag-chatbot__input\" [(ngModel)]=\"inputValue\"\n (keydown)=\"onKeyDown($event)\" [placeholder]=\"placeholder\" [disabled]=\"loading || !!healthError\"\n [autoResize]=\"true\" rows=\"1\" [attr.aria-label]=\"'rag_chatbot.input.aria_label' | translate\">\n </textarea>\n <p-button icon=\"pi pi-send\" [disabled]=\"sendDisabled\" [loading]=\"loading\" (onClick)=\"onSend()\" severity=\"primary\"\n [ariaLabel]=\"'rag_chatbot.input.send_aria_label' | translate\">\n </p-button>\n </div>\n\n <!-- Dialog preview snippet sorgente.\n Mostra SOLO il contenuto del chunk (gia' embedded nella response RAG).\n NON espone il path del file ne' apre editor esterni: il codebase del\n framework vive sul repo dev e non deve essere linkato all'utente finale. -->\n @if (selectedSource) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw' }\" [header]=\"sourceDialogTitle(selectedSource)\"\n styleClass=\"wuic-rag-chatbot__source-dialog\" (onHide)=\"closeSourcePreview()\">\n <div class=\"wuic-rag-chatbot__source-meta\">\n @if (sourceFilePath(selectedSource); as fpath) {\n <span class=\"wuic-rag-chatbot__source-tag wuic-rag-chatbot__source-tag--file\">\n <i class=\"pi pi-file\" aria-hidden=\"true\"></i>\n {{ fpath }}\n </span>\n }\n @if (selectedSource.symbol_type) {\n <span class=\"wuic-rag-chatbot__source-tag\">{{ selectedSource.symbol_type }}</span>\n }\n @if (selectedSource.start_line && selectedSource.end_line) {\n <span class=\"wuic-rag-chatbot__source-tag\">linee {{ selectedSource.start_line }}-{{ selectedSource.end_line\n }}</span>\n }\n @if (selectedSource.score_vector !== null && selectedSource.score_vector !== undefined) {\n <span class=\"wuic-rag-chatbot__source-tag\">vec {{ selectedSource.score_vector | number:'1.2-2' }}</span>\n }\n </div>\n <pre\n class=\"wuic-rag-chatbot__source-snippet\"><code>{{ selectedSource.snippet || ('rag_chatbot.source.snippet_unavailable' | translate) }}</code></pre>\n </p-dialog>\n }\n\n <!-- Dialog \"Sessioni\": elenco ordinato desc per updatedAt. Per ogni riga:\n data + titolo (truncated 100 char, tooltip full) + bottone rinomina\n inline + click row = switch session. -->\n @if (sessionsDialogOpen) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw', maxHeight: '85vh' }\"\n [header]=\"'rag_chatbot.sessions_dialog.header' | translate\"\n styleClass=\"wuic-rag-chatbot__sessions-dialog\"\n (onHide)=\"closeSessionsDialog()\">\n @if (sessionsLoading) {\n <div class=\"wuic-rag-chatbot__sessions-loading\">\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i> {{ 'rag_chatbot.sessions_dialog.loading' | translate }}\n </div>\n } @else if (sessionsList.length === 0) {\n <div class=\"wuic-rag-chatbot__sessions-empty\">{{ 'rag_chatbot.sessions_dialog.empty' | translate }}</div>\n } @else {\n <ul class=\"wuic-rag-chatbot__sessions-list\">\n @for (s of sessionsList; track s.id) {\n <li class=\"wuic-rag-chatbot__session-item\"\n [class.wuic-rag-chatbot__session-item--current]=\"s.id === currentSessionId\"\n (click)=\"selectSession(s)\">\n <div class=\"wuic-rag-chatbot__session-date\">{{ sessionDate(s) }}</div>\n @if (sessionRenameEditing[s.id]) {\n <div class=\"wuic-rag-chatbot__session-edit\" (click)=\"$event.stopPropagation()\">\n <input type=\"text\" pInputText\n [(ngModel)]=\"sessionRenameDraft[s.id]\"\n [autofocus]=\"true\"\n maxlength=\"200\"\n class=\"wuic-rag-chatbot__session-edit-input\"\n (keydown.enter)=\"confirmRenameSession(s, $event)\"\n (keydown.escape)=\"cancelRenameSession(s)\">\n <p-button icon=\"pi pi-check\" severity=\"success\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"confirmRenameSession(s, $event)\" [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_confirm_aria' | translate\"></p-button>\n <p-button icon=\"pi pi-times\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"cancelRenameSession(s)\" [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_cancel_aria' | translate\"></p-button>\n </div>\n } @else {\n <div class=\"wuic-rag-chatbot__session-row\">\n <span class=\"wuic-rag-chatbot__session-title\" [pTooltip]=\"s.title || ('rag_chatbot.sessions_dialog.untitled' | translate)\" tooltipPosition=\"top\">\n {{ sessionPreview(s) }}\n </span>\n <p-button icon=\"pi pi-pencil\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"startRenameSession(s, $event)\" [pTooltip]=\"'rag_chatbot.sessions_dialog.rename' | translate\" tooltipPosition=\"left\"\n [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_aria' | translate\"></p-button>\n <p-button icon=\"pi pi-trash\" severity=\"danger\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"onDeleteSessionFromList(s, $event)\" [pTooltip]=\"'rag_chatbot.sessions_dialog.delete' | translate\" tooltipPosition=\"left\"\n [ariaLabel]=\"'rag_chatbot.sessions_dialog.delete_aria' | translate\"></p-button>\n </div>\n }\n </li>\n }\n </ul>\n }\n </p-dialog>\n }\n\n <!-- Dialog context-overflow: l'ultima call ha fallito perche' la history\n sfora il context window. 2 azioni: Compatta ora (riassume e prosegue\n stessa session) / Nuova sessione (clear + ricomincia). -->\n @if (contextOverflowDialogOpen) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"false\"\n [style]=\"{ width: '480px', maxWidth: '92vw' }\"\n [header]=\"'rag_chatbot.overflow_dialog.header' | translate\"\n styleClass=\"wuic-rag-chatbot__overflow-dialog\"\n (onHide)=\"contextOverflowDialogOpen = false\">\n <p>{{ 'rag_chatbot.overflow_dialog.intro' | translate }}</p>\n <ul>\n <li><strong>{{ 'rag_chatbot.overflow_dialog.option_compact_title' | translate }}</strong> \u2014 {{ 'rag_chatbot.overflow_dialog.option_compact_desc' | translate }}</li>\n <li><strong>{{ 'rag_chatbot.overflow_dialog.option_new_title' | translate }}</strong> \u2014 {{ 'rag_chatbot.overflow_dialog.option_new_desc' | translate }}</li>\n </ul>\n <div class=\"wuic-rag-chatbot__overflow-actions\">\n <p-button [label]=\"'rag_chatbot.overflow_dialog.option_compact_title' | translate\" icon=\"pi pi-compress\" severity=\"primary\"\n [loading]=\"compactingSession\"\n (onClick)=\"doCompactSession()\"></p-button>\n <p-button [label]=\"'rag_chatbot.overflow_dialog.option_new_title' | translate\" icon=\"pi pi-refresh\" severity=\"secondary\"\n (onClick)=\"startNewSession()\"></p-button>\n </div>\n </p-dialog>\n }\n\n</div>", styles: [".wuic-rag-chatbot{--wuic-rag-chatbot-radius: .5rem;--wuic-rag-chatbot-padding: .75rem;--wuic-rag-chatbot-gap: .5rem;display:flex;flex-direction:column;width:100%;height:100%;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:var(--wuic-rag-chatbot-radius);background-color:var(--p-content-background, transparent);color:inherit;overflow:hidden;font-family:inherit}.wuic-rag-chatbot__header{display:flex;align-items:center;justify-content:space-between;padding:var(--wuic-rag-chatbot-padding);border-bottom:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__title{display:flex;align-items:center;gap:.5rem;font-weight:600;color:inherit}.wuic-rag-chatbot__title-icon{color:var(--p-primary-color, #3b82f6)}.wuic-rag-chatbot__header-actions{display:flex;align-items:center;gap:.5rem}.wuic-rag-chatbot__context-cue{width:28px;height:28px;border-radius:50%;border:2px solid rgba(255,255,255,.85);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:.62rem;font-weight:700;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.35);box-shadow:0 0 0 1px #0000000f,0 1px 3px #0000001f;transition:transform .12s ease,box-shadow .12s ease,opacity .12s ease;padding:0}.wuic-rag-chatbot__context-cue:hover:not(:disabled){transform:scale(1.08);box-shadow:0 0 0 1px #0000001a,0 2px 6px #0003}.wuic-rag-chatbot__context-cue:disabled{cursor:default;opacity:.65}.wuic-rag-chatbot__context-cue>i{font-size:.85rem}.wuic-rag-chatbot__sessions-loading,.wuic-rag-chatbot__sessions-empty{padding:1.5rem;text-align:center;color:var(--p-text-muted-color, #6b7280);font-style:italic}.wuic-rag-chatbot__sessions-list{list-style:none;margin:0;padding:0;max-height:60vh;overflow-y:auto}.wuic-rag-chatbot__session-item{padding:.6rem .85rem;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;margin-bottom:.4rem;cursor:pointer;transition:background .12s ease,border-color .12s ease}.wuic-rag-chatbot__session-item:hover{background:var(--p-surface-50, #f3f4f6);border-color:var(--p-primary-300, rgba(59, 130, 246, .4))}.wuic-rag-chatbot__session-item--current{background:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-400, rgba(59, 130, 246, .55))}.wuic-rag-chatbot__session-date{font-size:.72rem;color:var(--p-text-muted-color, #6b7280);font-family:ui-monospace,monospace;margin-bottom:.2rem}.wuic-rag-chatbot__session-row{display:flex;align-items:center;gap:.4rem}.wuic-rag-chatbot__session-title{flex:1 1 auto;font-size:.9rem;color:var(--p-text-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wuic-rag-chatbot__session-edit{display:flex;align-items:center;gap:.3rem}.wuic-rag-chatbot__session-edit-input{flex:1 1 auto;font-size:.85rem}.wuic-rag-chatbot__overflow-actions{display:flex;justify-content:flex-end;gap:.5rem;margin-top:1rem}.wuic-rag-chatbot__error-banner{margin:var(--wuic-rag-chatbot-padding);margin-bottom:0}.wuic-rag-chatbot__history{flex:1 1 auto;min-height:0;overflow-y:auto;padding:var(--wuic-rag-chatbot-padding);display:flex;flex-direction:column;gap:var(--wuic-rag-chatbot-gap);background-color:transparent}.wuic-rag-chatbot__empty{margin:auto;text-align:center;color:var(--p-text-muted-color, #6b7280);max-width:80%;padding:1.5rem 0}.wuic-rag-chatbot__empty-icon{font-size:1.5rem;margin-bottom:.5rem;display:block}.wuic-rag-chatbot__suggestions{list-style:none;padding:0;margin:.75rem 0 0;display:flex;flex-direction:column;gap:.25rem;text-align:left}.wuic-rag-chatbot__suggestions li{padding:.35rem .75rem;border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:.375rem;font-size:.875rem}.wuic-rag-chatbot__turn{display:flex;flex-direction:column;gap:.35rem;padding:.65rem .85rem;border-radius:var(--wuic-rag-chatbot-radius);max-width:92%;color:inherit;background-color:transparent;border:1px solid var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__turn--user{align-self:flex-end;background-color:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-200, rgba(59, 130, 246, .25))}.wuic-rag-chatbot__turn--assistant{align-self:flex-start;background-color:var(--p-surface-50, rgba(0, 0, 0, .025))}.wuic-rag-chatbot__turn-header{display:flex;align-items:center;gap:.4rem;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__turn-mode{margin-left:auto;font-family:ui-monospace,monospace;font-size:.7rem;opacity:.7}.wuic-rag-chatbot__turn-content{white-space:pre-wrap;word-break:break-word;line-height:1.45;font-size:.93rem;color:inherit}.wuic-rag-chatbot__turn-loading{display:flex;align-items:center;gap:.5rem;color:var(--p-text-muted-color, #6b7280);font-style:italic;font-size:.875rem}.wuic-rag-chatbot__spinner{width:1.25rem!important;height:1.25rem!important}.wuic-rag-chatbot__turn-error,.wuic-rag-chatbot__turn-warning{margin-top:.35rem}.wuic-rag-chatbot__sources{display:flex;flex-direction:column;gap:.35rem;margin-top:.5rem;padding-top:.5rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__sources-label{font-size:.72rem;text-transform:uppercase;letter-spacing:.05em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__sources-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__source-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease}.wuic-rag-chatbot__source-chip:hover{transform:translateY(-1px);box-shadow:0 1px 4px #0000001f}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:var(--p-blue-50, #eff6ff);color:var(--p-blue-700, #1d4ed8);border:1px solid var(--p-blue-200, #bfdbfe)}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:var(--p-blue-600, #2563eb)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:var(--p-green-50, #f0fdf4);color:var(--p-green-700, #15803d);border:1px solid var(--p-green-200, #bbf7d0)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:var(--p-green-600, #16a34a)}.wuic-rag-chatbot__tokens{margin-top:.4rem;font-size:.7rem;color:var(--p-text-muted-color, #6b7280);text-align:right;font-family:ui-monospace,monospace}.wuic-rag-chatbot__followups{display:flex;flex-direction:column;gap:.3rem;margin-top:.55rem;padding-top:.55rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__followups-label{font-size:.7rem;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__followups-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__followup-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease,background .12s ease}.wuic-rag-chatbot__followup-chip :host ::ng-deep .p-chip{background:var(--p-primary-50, rgba(59, 130, 246, .08));color:var(--p-primary-700, #1d4ed8);border:1px solid var(--p-primary-200, rgba(59, 130, 246, .25));font-size:.82rem;line-height:1.3;padding:.2rem .5rem}.wuic-rag-chatbot__followup-chip :host ::ng-deep .p-chip-icon{color:var(--p-primary-600, #2563eb);font-size:.75rem}.wuic-rag-chatbot__followup-chip:hover{transform:translateY(-1px);box-shadow:0 2px 6px #3b82f62e}.wuic-rag-chatbot__followup-chip:hover :host ::ng-deep .p-chip{background:var(--p-primary-100, rgba(59, 130, 246, .15))}.wuic-rag-chatbot__action-box{margin-top:.65rem;border:1px solid var(--p-primary-300, rgba(59, 130, 246, .45));border-radius:8px;background:var(--p-primary-50, rgba(59, 130, 246, .05));padding:.6rem .75rem;display:flex;flex-direction:column;gap:.45rem}.wuic-rag-chatbot__action-box--applied{border-color:var(--p-green-300, #86efac);background:var(--p-green-50, #f0fdf4)}.wuic-rag-chatbot__action-box--error{border-color:var(--p-red-300, #fca5a5);background:var(--p-red-50, #fef2f2)}.wuic-rag-chatbot__action-box-header{display:flex;align-items:center;gap:.45rem;font-size:.88rem;color:var(--p-primary-700, #1d4ed8);font-weight:600}.wuic-rag-chatbot__action-box-header>i{font-size:.95rem}.wuic-rag-chatbot__action-box-title{display:inline-flex;align-items:center;gap:.4rem;flex-wrap:wrap}.wuic-rag-chatbot__action-box-label{color:var(--p-text-color, #111827);font-weight:700}.wuic-rag-chatbot__action-box-route{color:var(--p-text-muted-color, #6b7280);font-weight:400;font-size:.8rem}.wuic-rag-chatbot__action-box-route>code{background:var(--p-surface-100, #f3f4f6);padding:1px 6px;border-radius:4px;font-family:ui-monospace,monospace;color:var(--p-text-color, #111827)}.wuic-rag-chatbot__action-box-rationale{font-size:.85rem;color:var(--p-text-color, #1f2937);line-height:1.4;font-style:italic}.wuic-rag-chatbot__action-box-side-effect{font-size:.8rem;color:var(--p-blue-700, #1d4ed8);background:var(--p-blue-50, #eff6ff);border-left:3px solid var(--p-blue-400, #60a5fa);padding:.4rem .6rem;border-radius:4px;display:flex;align-items:flex-start;gap:.4rem;line-height:1.35}.wuic-rag-chatbot__action-box-side-effect i{font-size:.9rem;margin-top:.1rem;flex-shrink:0}.wuic-rag-chatbot__action-box-editor-toolbar{display:flex;justify-content:flex-end;align-items:center}.wuic-rag-chatbot__action-box-extra-fields{display:flex;flex-direction:column;gap:6px;margin-bottom:8px}.wuic-rag-chatbot__action-box-extra-field{display:flex;align-items:center;gap:8px}.wuic-rag-chatbot__action-box-extra-field-label{flex:0 0 auto;min-width:100px;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__action-box-extra-field-value{flex:1 1 auto;min-width:0;font-family:ui-monospace,SFMono-Regular,Consolas,Cascadia Code,monospace;font-size:.78rem;padding:4px 8px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:4px;background:var(--p-surface-50, #fafafa);color:var(--p-text-color, #111827)}.wuic-rag-chatbot__action-box-extra-field-value:focus{outline:2px solid var(--p-primary-300, rgba(59, 130, 246, .4));outline-offset:-1px}.wuic-rag-chatbot__action-box-textarea{width:100%;min-height:200px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;padding:8px 10px;background:var(--p-surface-50, #fafafa);color:var(--p-text-color, #111827);font-family:ui-monospace,SFMono-Regular,Consolas,Cascadia Code,monospace;font-size:.78rem;line-height:1.45;resize:vertical;white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:hidden;overflow-y:auto;tab-size:2}.wuic-rag-chatbot__action-box-textarea:focus{outline:2px solid var(--p-primary-300, rgba(59, 130, 246, .4));outline-offset:-1px}.wuic-rag-chatbot__action-box-editor{height:240px;min-height:240px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;overflow:hidden;background:#1e1e1e}.wuic-rag-chatbot__action-box-editor-loading{display:flex;align-items:center;justify-content:center;gap:.5rem;min-height:100px;color:var(--p-text-muted-color, #6b7280);font-size:.85rem;font-style:italic;background:var(--p-surface-50, #fafafa);border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:6px}.wuic-rag-chatbot__action-box-actions{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap}.wuic-rag-chatbot__action-box-applied{display:inline-flex;align-items:center;gap:.35rem;color:var(--p-green-700, #15803d);font-weight:600;font-size:.85rem}.wuic-rag-chatbot__action-box-error{display:inline-flex;align-items:center;gap:.35rem;color:var(--p-red-700, #b91c1c);font-size:.8rem;font-family:ui-monospace,monospace}.wuic-rag-chatbot__input-area{display:flex;align-items:flex-end;gap:.5rem;padding:var(--wuic-rag-chatbot-padding);border-top:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__input{flex:1 1 auto;min-height:2.25rem;max-height:8rem;resize:none;color:inherit;background-color:transparent}.wuic-rag-chatbot__source-meta{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.75rem}.wuic-rag-chatbot__source-tag{display:inline-flex;align-items:center;gap:.25rem;padding:2px 8px;background:var(--p-surface-100, #f3f4f6);color:var(--p-text-color-secondary, #6b7280);border-radius:10px;font-size:.72rem;font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.wuic-rag-chatbot__source-tag--file{background:var(--p-surface-0, #fff);color:var(--p-primary-color, #2563eb);border:1px solid var(--p-primary-200, #bfdbfe);font-weight:500;max-width:100%;word-break:break-all;white-space:normal;line-height:1.3}.wuic-rag-chatbot__source-tag--file i{font-size:.7rem;flex-shrink:0}.wuic-rag-chatbot__source-snippet{margin:0;padding:12px 14px;max-height:420px;overflow:auto;background:var(--p-surface-50, #fafafa);border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.78rem;line-height:1.4;white-space:pre-wrap;word-break:break-word;color:var(--p-text-color, #111827)}:host-context(.theme-dark) .wuic-rag-chatbot__turn--user{background-color:#60a5fa2e;border-color:#60a5fa73}:host-context(.theme-dark) .wuic-rag-chatbot__turn--assistant{background-color:#ffffff0f;border-color:#ffffff1f}:host-context(.theme-dark) .wuic-rag-chatbot__turn-header,:host-context(.theme-dark) .wuic-rag-chatbot__turn-loading,:host-context(.theme-dark) .wuic-rag-chatbot__sources-label,:host-context(.theme-dark) .wuic-rag-chatbot__tokens{color:#ffffffa6}:host-context(.theme-dark) .wuic-rag-chatbot__sources{border-top-color:#ffffff2e}:host-context(.theme-dark) .wuic-rag-chatbot__input-area{border-top-color:#ffffff1f}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:#3b82f62e;color:#93c5fd;border:1px solid rgba(59,130,246,.4)}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:#bfdbfe}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:#22c55e2e;color:#86efac;border:1px solid rgba(34,197,94,.4)}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:#bbf7d0}:host-context(.theme-dark) .wuic-rag-chatbot__source-tag{background:#ffffff14;color:#ffffffc7}:host-context(.theme-dark) .wuic-rag-chatbot__source-tag--file{background:#3b82f624;color:#93c5fd;border-color:#3b82f673}:host-context(.theme-dark) .wuic-rag-chatbot__source-snippet{background:#00000052;border-color:#ffffff1f;color:#ffffffeb}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1$4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i7$1.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i13.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: BadgeModule }, { kind: "component", type: i10$1.Badge, selector: "p-badge", inputs: ["styleClass", "badgeSize", "size", "severity", "value", "badgeDisabled"] }, { kind: "ngmodule", type: ChipModule }, { kind: "component", type: i11$1.Chip, selector: "p-chip", inputs: ["label", "icon", "image", "alt", "styleClass", "disabled", "removable", "removeIcon", "chipProps"], outputs: ["onRemove", "onImageError"] }, { kind: "ngmodule", type: ScrollPanelModule }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i9$2.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: i7.Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant", "motionOptions"], outputs: ["onClose"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$3.DecimalPipe, name: "number" }, { kind: "pipe", type: i8.TranslatePipe, name: "translate" }], deferBlockDependencies: [() => [Promise.resolve().then(function () { return codeEditor_lazy_component; }).then(m => m.LazyCodeEditorComponent)]] });
63508
63540
  }
63509
63541
  i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.2.9", ngImport: i0, type: WuicRagChatbotComponent, resolveDeferredDeps: () => [Promise.resolve().then(function () { return codeEditor_lazy_component; }).then(m => m.LazyCodeEditorComponent)], resolveMetadata: LazyCodeEditorComponent => ({ decorators: [{
63510
63542
  type: Component,
@@ -63523,7 +63555,7 @@ i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.2.9", ng
63523
63555
  DialogModule,
63524
63556
  TranslateModule,
63525
63557
  LazyCodeEditorComponent,
63526
- ], template: "<div class=\"wuic-rag-chatbot\" style=\"height: 100%;\">\n\n <!-- Header -->\n <div class=\"wuic-rag-chatbot__header\">\n <div class=\"wuic-rag-chatbot__title\">\n <i class=\"pi pi-comments wuic-rag-chatbot__title-icon\" aria-hidden=\"true\"></i>\n <span>{{ title }}</span>\n </div>\n <div class=\"wuic-rag-chatbot__header-actions\">\n @if (allowProfileToggle) {\n <p-button [label]=\"profileToggleLabel\" [icon]=\"profileOverride === 'release' ? 'pi pi-eye' : 'pi pi-code'\"\n [severity]=\"profileOverride === 'release' ? 'warn' : 'secondary'\" [outlined]=\"true\" [rounded]=\"true\"\n size=\"small\" (onClick)=\"toggleProfile()\" [pTooltip]=\"profileOverride === 'release'\n ? ('rag_chatbot.profile_toggle.tooltip_release' | translate)\n : ('rag_chatbot.profile_toggle.tooltip_dev' | translate)\" tooltipPosition=\"bottom\"\n [ariaLabel]=\"'rag_chatbot.profile_toggle.aria_label' | translate\">\n </p-button>\n }\n <p-badge [value]=\"modeBadgeLabel\" [severity]=\"modeBadgeSeverity\" [pTooltip]=\"healthInfo\n ? ('chunks indicizzati: ' + healthInfo.docs_loaded + ' | modello: ' + healthInfo.default_model)\n : (healthError || ('rag_chatbot.health_indicator.connecting' | translate))\" tooltipPosition=\"bottom\">\n </p-badge>\n <!-- Context cue: cerchio colorato che riflette % context window usato.\n Click + conferma -> /compact. Tooltip mostra k/k token + %. -->\n @if (contextWindowMax) {\n <button type=\"button\"\n class=\"wuic-rag-chatbot__context-cue\"\n [style.background]=\"contextCueColor\"\n [pTooltip]=\"contextCueTooltip\"\n tooltipPosition=\"bottom\"\n [disabled]=\"compactingSession || !currentSessionId\"\n (click)=\"onContextCueClick()\"\n attr.aria-label=\"Compatta sessione (context {{ contextUsagePercent }}%)\">\n @if (compactingSession) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n } @else {\n <span>{{ contextUsagePercent }}%</span>\n }\n </button>\n }\n <!-- Bottone Sessioni: apre popup con lista sessioni ordinate desc per updatedAt. -->\n <p-button icon=\"pi pi-list\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"openSessionsDialog()\" [pTooltip]=\"'rag_chatbot.sessions_button.tooltip' | translate\"\n tooltipPosition=\"bottom\" [ariaLabel]=\"'rag_chatbot.sessions_button.aria_label' | translate\">\n </p-button>\n @if (showClearButton) {\n <p-button icon=\"pi pi-trash\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n [disabled]=\"history.length === 0 || loading || clearingHistory\" [loading]=\"clearingHistory\"\n (onClick)=\"onClearHistory()\" [pTooltip]=\"'rag_chatbot.clear_button.tooltip' | translate\"\n tooltipPosition=\"bottom\" [ariaLabel]=\"'rag_chatbot.clear_button.aria_label' | translate\">\n </p-button>\n }\n </div>\n </div>\n\n <!-- Health error banner -->\n @if (healthError) {\n <p-message severity=\"error\" [text]=\"('rag_chatbot.health_error.banner' | translate) + ': ' + healthError\"\n styleClass=\"wuic-rag-chatbot__error-banner\"></p-message>\n }\n\n <!-- Chat history -->\n <div #scrollContainer class=\"wuic-rag-chatbot__history\" role=\"log\" aria-live=\"polite\">\n @if (history.length === 0) {\n <div class=\"wuic-rag-chatbot__empty\">\n <i class=\"pi pi-info-circle wuic-rag-chatbot__empty-icon\" aria-hidden=\"true\"></i>\n <p>{{ 'rag_chatbot.empty.prompt' | translate }}</p>\n <ul class=\"wuic-rag-chatbot__suggestions\">\n <li>{{ 'rag_chatbot.suggestion.invalidate_cache' | translate }}</li>\n <li>{{ 'rag_chatbot.suggestion.spreadsheet_massive' | translate }}</li>\n <li>{{ 'rag_chatbot.suggestion.save_report' | translate }}</li>\n </ul>\n </div>\n }\n\n @for (turn of history; track turn.messageId || $index) {\n <div class=\"wuic-rag-chatbot__turn\" [attr.data-message-id]=\"turn.messageId || null\"\n [class.wuic-rag-chatbot__turn--user]=\"turn.role === 'user'\"\n [class.wuic-rag-chatbot__turn--assistant]=\"turn.role === 'assistant'\">\n\n <div class=\"wuic-rag-chatbot__turn-header\">\n <i class=\"pi\" [class.pi-user]=\"turn.role === 'user'\" [class.pi-android]=\"turn.role === 'assistant'\"\n aria-hidden=\"true\"></i>\n <span>{{ (turn.role === 'user' ? 'rag_chatbot.turn.role_user' : 'rag_chatbot.turn.role_assistant') | translate }}</span>\n @if (turn.mode) {\n <span class=\"wuic-rag-chatbot__turn-mode\">[{{ turn.mode === 'rag-llm' ? 'RAG+LLM' : 'retrieval' }}]</span>\n }\n </div>\n\n @if (turn.loading) {\n <div class=\"wuic-rag-chatbot__turn-loading\">\n <p-progressSpinner styleClass=\"wuic-rag-chatbot__spinner\" strokeWidth=\"3\"\n animationDuration=\".9s\"></p-progressSpinner>\n <span>{{ 'rag_chatbot.turn.loading' | translate }}</span>\n </div>\n } @else if (turn.errorMessage) {\n <p-message severity=\"error\" [text]=\"(turn.errorMessage | translate)\" styleClass=\"wuic-rag-chatbot__turn-error\"></p-message>\n } @else {\n <div class=\"wuic-rag-chatbot__turn-content\">{{ turn.content }}</div>\n @if (turn.warning) {\n <p-message severity=\"warn\" [text]=\"turn.warning\" styleClass=\"wuic-rag-chatbot__turn-warning\"></p-message>\n }\n <!-- Box \"Proposta azione\" emessa dall'LLM via tool-use. Renderizzato solo\n per i turn assistant che hanno proposedAction valorizzata. Il monaco\n editor riusa la stessa codeContext del metadata-editor sulla colonna\n equivalente (autocomplete identico, type-check identico). -->\n @if (turn.role === 'assistant' && turn.proposedAction) {\n <div class=\"wuic-rag-chatbot__action-box\"\n [class.wuic-rag-chatbot__action-box--applied]=\"turn.proposedActionAppliedAt\"\n [class.wuic-rag-chatbot__action-box--error]=\"turn.applyError\">\n <div class=\"wuic-rag-chatbot__action-box-header\">\n <i class=\"pi pi-bolt\" aria-hidden=\"true\"></i>\n <span class=\"wuic-rag-chatbot__action-box-title\">\n {{ proposedActionTitle(turn.proposedAction) }}\n @if (turn.proposedAction.label) {\n <span class=\"wuic-rag-chatbot__action-box-label\">\"{{ turn.proposedAction.label }}\"</span>\n }\n @if (turn.proposedAction.route) {\n <span class=\"wuic-rag-chatbot__action-box-route\">{{ 'rag_chatbot.action_box.label_on_route_prefix' | translate }} <code>{{ turn.proposedAction.route }}</code></span>\n }\n </span>\n </div>\n @if (turn.proposedAction.rationale) {\n <div class=\"wuic-rag-chatbot__action-box-rationale\">{{ turn.proposedAction.rationale }}</div>\n }\n <!-- Hint trasparenza UX: l'utente sa in anticipo che, oltre a creare\n la toolbar action, abiliteremo md_multiple_selection sulla route\n (necessario per le bulk operations che leggono getSelectedRows). -->\n @if (turn.proposedAction.requires_multi_selection) {\n <div class=\"wuic-rag-chatbot__action-box-side-effect\">\n <i class=\"pi pi-info-circle\"></i>\n {{ 'rag_chatbot.action_box.side_effect_multi_selection' | translate }}\n </div>\n }\n <!-- Campi NON-body della proposta (es. per table_style: classe/stile;\n per toolbar/row_action: label+icon; per sql_metadata_field: field\n target). Visibili sopra la textarea del body cosi' l'utente capisce\n a colpo d'occhio COSA viene applicato oltre al CODICE del callback.\n Sorgente: proposedActionExtraFields(kind) nel .ts. -->\n @if (proposedActionExtraFields(turn.proposedAction).length > 0) {\n <div class=\"wuic-rag-chatbot__action-box-extra-fields\">\n @for (extra of proposedActionExtraFields(turn.proposedAction); track extra.fieldName) {\n <div class=\"wuic-rag-chatbot__action-box-extra-field\">\n <label class=\"wuic-rag-chatbot__action-box-extra-field-label\">{{ extra.label }}</label>\n <input\n class=\"wuic-rag-chatbot__action-box-extra-field-value\"\n [disabled]=\"!!turn.proposedActionAppliedAt\"\n [ngModel]=\"extra.value\"\n (ngModelChange)=\"onProposedExtraFieldEdit(turn, extra, $event)\"\n [name]=\"'extra-' + turn.messageId + '-' + extra.fieldName\">\n </div>\n }\n </div>\n }\n @if (proposedActionJsBody(turn.proposedAction)) {\n <!-- Pattern <lib-code-area-editor>: textarea readonly (zero overhead,\n niente flash nero, contenuto immediatamente visibile) di default.\n Toggle \"Apri editor avanzato\" attiva `editorMode` -> @defer carica\n Monaco con codeContext+autocomplete. Doppio livello di lazy:\n @defer in HTML + LazyCodeEditorComponent fa import() dinamico.\n Risultato: Monaco scaricato SOLO se l'utente lo richiede.\n proposedActionJsBody() unifica callback_js / condition_js / formula_js\n in base al kind: stesso UX per tutti i kind che hanno una body JS. -->\n <div class=\"wuic-rag-chatbot__action-box-editor-toolbar\">\n <p-button\n [label]=\"(turn.editorMode ? 'rag_chatbot.action_box.editor_close' : 'rag_chatbot.action_box.editor_open') | translate\"\n [icon]=\"turn.editorMode ? 'pi pi-times' : 'pi pi-code'\"\n severity=\"secondary\"\n [text]=\"true\"\n size=\"small\"\n (onClick)=\"toggleEditorMode(turn)\">\n </p-button>\n </div>\n @if (!turn.editorMode) {\n <textarea\n class=\"wuic-rag-chatbot__action-box-textarea\"\n rows=\"10\"\n [disabled]=\"!!turn.proposedActionAppliedAt\"\n [ngModel]=\"proposedActionJsBody(turn.proposedAction)\"\n (ngModelChange)=\"onProposedBodyEdit(turn, $event)\"\n [name]=\"'body-' + turn.messageId\">\n </textarea>\n } @else {\n @defer (when turn.editorMode) {\n <div class=\"wuic-rag-chatbot__action-box-editor\">\n <wuic-code-editor-lazy\n [field]=\"getProposedActionField(turn)\"\n [record]=\"getProposedActionRecord(turn)\"\n style=\"display: block; width: 100%; height: 100%;\">\n </wuic-code-editor-lazy>\n </div>\n } @loading (minimum 200ms) {\n <div class=\"wuic-rag-chatbot__action-box-editor-loading\">\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n {{ 'rag_chatbot.action_box.editor_loading' | translate }}\n </div>\n }\n }\n }\n <div class=\"wuic-rag-chatbot__action-box-actions\">\n @if (turn.proposedActionAppliedAt) {\n <span class=\"wuic-rag-chatbot__action-box-applied\">\n <i class=\"pi pi-check-circle\" aria-hidden=\"true\"></i>\n {{ 'rag_chatbot.action_box.applied' | translate }}\n </span>\n } @else {\n <p-button\n [label]=\"'rag_chatbot.action_box.apply' | translate\"\n icon=\"pi pi-check\"\n severity=\"primary\"\n size=\"small\"\n [loading]=\"!!turn.applyingAction\"\n (onClick)=\"onApplyAction(turn)\">\n </p-button>\n }\n @if (turn.applyError) {\n <span class=\"wuic-rag-chatbot__action-box-error\">\n <i class=\"pi pi-times-circle\" aria-hidden=\"true\"></i>\n {{ turn.applyError }}\n </span>\n }\n </div>\n </div>\n }\n @if (showSources && turn.role === 'assistant' && turn.sources && turn.sources.length > 0) {\n @let dedupedSources = dedupSources(turn.sources);\n <div class=\"wuic-rag-chatbot__sources\">\n <div class=\"wuic-rag-chatbot__sources-label\">{{ 'rag_chatbot.sources.label' | translate }}</div>\n <div class=\"wuic-rag-chatbot__sources-list\">\n @for (source of dedupedSources; track source.chunk_id || source.rank) {\n <p-chip [label]=\"sourceLabel(source, dedupedSources)\"\n [icon]=\"isDocsSource(source) ? 'pi pi-book' : (isSkillSource(source) ? 'pi pi-wrench' : 'pi pi-file-edit')\"\n [removable]=\"false\" [pTooltip]=\"sourceTooltip(source)\" tooltipPosition=\"top\" [styleClass]=\"isDocsSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--docs'\n : (isSkillSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--skill'\n : 'wuic-rag-chatbot__source-chip')\" (click)=\"onSourceClick(source)\">\n </p-chip>\n }\n </div>\n </div>\n }\n <!-- Follow-up questions emesse dall'LLM via tool `suggest_followups`.\n Renderizzate come chip cliccabili sotto la risposta: click popola\n l'input box (NON invia auto, l'utente puo' editare prima). -->\n @if (turn.role === 'assistant' && turn.followupQuestions && turn.followupQuestions.length > 0) {\n <div class=\"wuic-rag-chatbot__followups\">\n <div class=\"wuic-rag-chatbot__followups-label\">{{ 'rag_chatbot.followups.label' | translate }}</div>\n <div class=\"wuic-rag-chatbot__followups-list\">\n @for (q of turn.followupQuestions; track q) {\n <p-chip\n [label]=\"q\"\n icon=\"pi pi-arrow-right\"\n styleClass=\"wuic-rag-chatbot__followup-chip\"\n (click)=\"applyFollowup(q)\">\n </p-chip>\n }\n </div>\n </div>\n }\n @if (turn.tokensIn || turn.tokensOut) {\n <div class=\"wuic-rag-chatbot__tokens\">\n tokens in/out: {{ turn.tokensIn ?? '?' }}/{{ turn.tokensOut ?? '?' }}\n </div>\n }\n }\n </div>\n }\n </div>\n\n <!-- Input area -->\n <div class=\"wuic-rag-chatbot__input-area\">\n <textarea #inputTextarea pTextarea class=\"wuic-rag-chatbot__input\" [(ngModel)]=\"inputValue\"\n (keydown)=\"onKeyDown($event)\" [placeholder]=\"placeholder\" [disabled]=\"loading || !!healthError\"\n [autoResize]=\"true\" rows=\"1\" [attr.aria-label]=\"'rag_chatbot.input.aria_label' | translate\">\n </textarea>\n <p-button icon=\"pi pi-send\" [disabled]=\"sendDisabled\" [loading]=\"loading\" (onClick)=\"onSend()\" severity=\"primary\"\n [ariaLabel]=\"'rag_chatbot.input.send_aria_label' | translate\">\n </p-button>\n </div>\n\n <!-- Dialog preview snippet sorgente.\n Mostra SOLO il contenuto del chunk (gia' embedded nella response RAG).\n NON espone il path del file ne' apre editor esterni: il codebase del\n framework vive sul repo dev e non deve essere linkato all'utente finale. -->\n @if (selectedSource) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw' }\" [header]=\"sourceDialogTitle(selectedSource)\"\n styleClass=\"wuic-rag-chatbot__source-dialog\" (onHide)=\"closeSourcePreview()\">\n <div class=\"wuic-rag-chatbot__source-meta\">\n @if (sourceFilePath(selectedSource); as fpath) {\n <span class=\"wuic-rag-chatbot__source-tag wuic-rag-chatbot__source-tag--file\">\n <i class=\"pi pi-file\" aria-hidden=\"true\"></i>\n {{ fpath }}\n </span>\n }\n @if (selectedSource.symbol_type) {\n <span class=\"wuic-rag-chatbot__source-tag\">{{ selectedSource.symbol_type }}</span>\n }\n @if (selectedSource.start_line && selectedSource.end_line) {\n <span class=\"wuic-rag-chatbot__source-tag\">linee {{ selectedSource.start_line }}-{{ selectedSource.end_line\n }}</span>\n }\n @if (selectedSource.score_vector !== null && selectedSource.score_vector !== undefined) {\n <span class=\"wuic-rag-chatbot__source-tag\">vec {{ selectedSource.score_vector | number:'1.2-2' }}</span>\n }\n </div>\n <pre\n class=\"wuic-rag-chatbot__source-snippet\"><code>{{ selectedSource.snippet || ('rag_chatbot.source.snippet_unavailable' | translate) }}</code></pre>\n </p-dialog>\n }\n\n <!-- Dialog \"Sessioni\": elenco ordinato desc per updatedAt. Per ogni riga:\n data + titolo (truncated 100 char, tooltip full) + bottone rinomina\n inline + click row = switch session. -->\n @if (sessionsDialogOpen) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw', maxHeight: '85vh' }\"\n [header]=\"'rag_chatbot.sessions_dialog.header' | translate\"\n styleClass=\"wuic-rag-chatbot__sessions-dialog\"\n (onHide)=\"closeSessionsDialog()\">\n @if (sessionsLoading) {\n <div class=\"wuic-rag-chatbot__sessions-loading\">\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i> {{ 'rag_chatbot.sessions_dialog.loading' | translate }}\n </div>\n } @else if (sessionsList.length === 0) {\n <div class=\"wuic-rag-chatbot__sessions-empty\">{{ 'rag_chatbot.sessions_dialog.empty' | translate }}</div>\n } @else {\n <ul class=\"wuic-rag-chatbot__sessions-list\">\n @for (s of sessionsList; track s.id) {\n <li class=\"wuic-rag-chatbot__session-item\"\n [class.wuic-rag-chatbot__session-item--current]=\"s.id === currentSessionId\"\n (click)=\"selectSession(s)\">\n <div class=\"wuic-rag-chatbot__session-date\">{{ sessionDate(s) }}</div>\n @if (sessionRenameEditing[s.id]) {\n <div class=\"wuic-rag-chatbot__session-edit\" (click)=\"$event.stopPropagation()\">\n <input type=\"text\" pInputText\n [(ngModel)]=\"sessionRenameDraft[s.id]\"\n [autofocus]=\"true\"\n maxlength=\"200\"\n class=\"wuic-rag-chatbot__session-edit-input\"\n (keydown.enter)=\"confirmRenameSession(s, $event)\"\n (keydown.escape)=\"cancelRenameSession(s)\">\n <p-button icon=\"pi pi-check\" severity=\"success\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"confirmRenameSession(s, $event)\" [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_confirm_aria' | translate\"></p-button>\n <p-button icon=\"pi pi-times\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"cancelRenameSession(s)\" [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_cancel_aria' | translate\"></p-button>\n </div>\n } @else {\n <div class=\"wuic-rag-chatbot__session-row\">\n <span class=\"wuic-rag-chatbot__session-title\" [pTooltip]=\"s.title || ('rag_chatbot.sessions_dialog.untitled' | translate)\" tooltipPosition=\"top\">\n {{ sessionPreview(s) }}\n </span>\n <p-button icon=\"pi pi-pencil\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"startRenameSession(s, $event)\" [pTooltip]=\"'rag_chatbot.sessions_dialog.rename' | translate\" tooltipPosition=\"left\"\n [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_aria' | translate\"></p-button>\n <p-button icon=\"pi pi-trash\" severity=\"danger\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"onDeleteSessionFromList(s, $event)\" [pTooltip]=\"'rag_chatbot.sessions_dialog.delete' | translate\" tooltipPosition=\"left\"\n [ariaLabel]=\"'rag_chatbot.sessions_dialog.delete_aria' | translate\"></p-button>\n </div>\n }\n </li>\n }\n </ul>\n }\n </p-dialog>\n }\n\n <!-- Dialog context-overflow: l'ultima call ha fallito perche' la history\n sfora il context window. 2 azioni: Compatta ora (riassume e prosegue\n stessa session) / Nuova sessione (clear + ricomincia). -->\n @if (contextOverflowDialogOpen) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"false\"\n [style]=\"{ width: '480px', maxWidth: '92vw' }\"\n [header]=\"'rag_chatbot.overflow_dialog.header' | translate\"\n styleClass=\"wuic-rag-chatbot__overflow-dialog\"\n (onHide)=\"contextOverflowDialogOpen = false\">\n <p>{{ 'rag_chatbot.overflow_dialog.intro' | translate }}</p>\n <ul>\n <li><strong>{{ 'rag_chatbot.overflow_dialog.option_compact_title' | translate }}</strong> \u2014 {{ 'rag_chatbot.overflow_dialog.option_compact_desc' | translate }}</li>\n <li><strong>{{ 'rag_chatbot.overflow_dialog.option_new_title' | translate }}</strong> \u2014 {{ 'rag_chatbot.overflow_dialog.option_new_desc' | translate }}</li>\n </ul>\n <div class=\"wuic-rag-chatbot__overflow-actions\">\n <p-button [label]=\"'rag_chatbot.overflow_dialog.option_compact_title' | translate\" icon=\"pi pi-compress\" severity=\"primary\"\n [loading]=\"compactingSession\"\n (onClick)=\"doCompactSession()\"></p-button>\n <p-button [label]=\"'rag_chatbot.overflow_dialog.option_new_title' | translate\" icon=\"pi pi-refresh\" severity=\"secondary\"\n (onClick)=\"startNewSession()\"></p-button>\n </div>\n </p-dialog>\n }\n\n</div>", styles: [".wuic-rag-chatbot{--wuic-rag-chatbot-radius: .5rem;--wuic-rag-chatbot-padding: .75rem;--wuic-rag-chatbot-gap: .5rem;display:flex;flex-direction:column;width:100%;height:100%;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:var(--wuic-rag-chatbot-radius);background-color:var(--p-content-background, transparent);color:inherit;overflow:hidden;font-family:inherit}.wuic-rag-chatbot__header{display:flex;align-items:center;justify-content:space-between;padding:var(--wuic-rag-chatbot-padding);border-bottom:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__title{display:flex;align-items:center;gap:.5rem;font-weight:600;color:inherit}.wuic-rag-chatbot__title-icon{color:var(--p-primary-color, #3b82f6)}.wuic-rag-chatbot__header-actions{display:flex;align-items:center;gap:.5rem}.wuic-rag-chatbot__context-cue{width:28px;height:28px;border-radius:50%;border:2px solid rgba(255,255,255,.85);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:.62rem;font-weight:700;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.35);box-shadow:0 0 0 1px #0000000f,0 1px 3px #0000001f;transition:transform .12s ease,box-shadow .12s ease,opacity .12s ease;padding:0}.wuic-rag-chatbot__context-cue:hover:not(:disabled){transform:scale(1.08);box-shadow:0 0 0 1px #0000001a,0 2px 6px #0003}.wuic-rag-chatbot__context-cue:disabled{cursor:default;opacity:.65}.wuic-rag-chatbot__context-cue>i{font-size:.85rem}.wuic-rag-chatbot__sessions-loading,.wuic-rag-chatbot__sessions-empty{padding:1.5rem;text-align:center;color:var(--p-text-muted-color, #6b7280);font-style:italic}.wuic-rag-chatbot__sessions-list{list-style:none;margin:0;padding:0;max-height:60vh;overflow-y:auto}.wuic-rag-chatbot__session-item{padding:.6rem .85rem;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;margin-bottom:.4rem;cursor:pointer;transition:background .12s ease,border-color .12s ease}.wuic-rag-chatbot__session-item:hover{background:var(--p-surface-50, #f3f4f6);border-color:var(--p-primary-300, rgba(59, 130, 246, .4))}.wuic-rag-chatbot__session-item--current{background:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-400, rgba(59, 130, 246, .55))}.wuic-rag-chatbot__session-date{font-size:.72rem;color:var(--p-text-muted-color, #6b7280);font-family:ui-monospace,monospace;margin-bottom:.2rem}.wuic-rag-chatbot__session-row{display:flex;align-items:center;gap:.4rem}.wuic-rag-chatbot__session-title{flex:1 1 auto;font-size:.9rem;color:var(--p-text-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wuic-rag-chatbot__session-edit{display:flex;align-items:center;gap:.3rem}.wuic-rag-chatbot__session-edit-input{flex:1 1 auto;font-size:.85rem}.wuic-rag-chatbot__overflow-actions{display:flex;justify-content:flex-end;gap:.5rem;margin-top:1rem}.wuic-rag-chatbot__error-banner{margin:var(--wuic-rag-chatbot-padding);margin-bottom:0}.wuic-rag-chatbot__history{flex:1 1 auto;min-height:0;overflow-y:auto;padding:var(--wuic-rag-chatbot-padding);display:flex;flex-direction:column;gap:var(--wuic-rag-chatbot-gap);background-color:transparent}.wuic-rag-chatbot__empty{margin:auto;text-align:center;color:var(--p-text-muted-color, #6b7280);max-width:80%;padding:1.5rem 0}.wuic-rag-chatbot__empty-icon{font-size:1.5rem;margin-bottom:.5rem;display:block}.wuic-rag-chatbot__suggestions{list-style:none;padding:0;margin:.75rem 0 0;display:flex;flex-direction:column;gap:.25rem;text-align:left}.wuic-rag-chatbot__suggestions li{padding:.35rem .75rem;border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:.375rem;font-size:.875rem}.wuic-rag-chatbot__turn{display:flex;flex-direction:column;gap:.35rem;padding:.65rem .85rem;border-radius:var(--wuic-rag-chatbot-radius);max-width:92%;color:inherit;background-color:transparent;border:1px solid var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__turn--user{align-self:flex-end;background-color:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-200, rgba(59, 130, 246, .25))}.wuic-rag-chatbot__turn--assistant{align-self:flex-start;background-color:var(--p-surface-50, rgba(0, 0, 0, .025))}.wuic-rag-chatbot__turn-header{display:flex;align-items:center;gap:.4rem;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__turn-mode{margin-left:auto;font-family:ui-monospace,monospace;font-size:.7rem;opacity:.7}.wuic-rag-chatbot__turn-content{white-space:pre-wrap;word-break:break-word;line-height:1.45;font-size:.93rem;color:inherit}.wuic-rag-chatbot__turn-loading{display:flex;align-items:center;gap:.5rem;color:var(--p-text-muted-color, #6b7280);font-style:italic;font-size:.875rem}.wuic-rag-chatbot__spinner{width:1.25rem!important;height:1.25rem!important}.wuic-rag-chatbot__turn-error,.wuic-rag-chatbot__turn-warning{margin-top:.35rem}.wuic-rag-chatbot__sources{display:flex;flex-direction:column;gap:.35rem;margin-top:.5rem;padding-top:.5rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__sources-label{font-size:.72rem;text-transform:uppercase;letter-spacing:.05em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__sources-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__source-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease}.wuic-rag-chatbot__source-chip:hover{transform:translateY(-1px);box-shadow:0 1px 4px #0000001f}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:var(--p-blue-50, #eff6ff);color:var(--p-blue-700, #1d4ed8);border:1px solid var(--p-blue-200, #bfdbfe)}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:var(--p-blue-600, #2563eb)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:var(--p-green-50, #f0fdf4);color:var(--p-green-700, #15803d);border:1px solid var(--p-green-200, #bbf7d0)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:var(--p-green-600, #16a34a)}.wuic-rag-chatbot__tokens{margin-top:.4rem;font-size:.7rem;color:var(--p-text-muted-color, #6b7280);text-align:right;font-family:ui-monospace,monospace}.wuic-rag-chatbot__followups{display:flex;flex-direction:column;gap:.3rem;margin-top:.55rem;padding-top:.55rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__followups-label{font-size:.7rem;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__followups-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__followup-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease,background .12s ease}.wuic-rag-chatbot__followup-chip :host ::ng-deep .p-chip{background:var(--p-primary-50, rgba(59, 130, 246, .08));color:var(--p-primary-700, #1d4ed8);border:1px solid var(--p-primary-200, rgba(59, 130, 246, .25));font-size:.82rem;line-height:1.3;padding:.2rem .5rem}.wuic-rag-chatbot__followup-chip :host ::ng-deep .p-chip-icon{color:var(--p-primary-600, #2563eb);font-size:.75rem}.wuic-rag-chatbot__followup-chip:hover{transform:translateY(-1px);box-shadow:0 2px 6px #3b82f62e}.wuic-rag-chatbot__followup-chip:hover :host ::ng-deep .p-chip{background:var(--p-primary-100, rgba(59, 130, 246, .15))}.wuic-rag-chatbot__action-box{margin-top:.65rem;border:1px solid var(--p-primary-300, rgba(59, 130, 246, .45));border-radius:8px;background:var(--p-primary-50, rgba(59, 130, 246, .05));padding:.6rem .75rem;display:flex;flex-direction:column;gap:.45rem}.wuic-rag-chatbot__action-box--applied{border-color:var(--p-green-300, #86efac);background:var(--p-green-50, #f0fdf4)}.wuic-rag-chatbot__action-box--error{border-color:var(--p-red-300, #fca5a5);background:var(--p-red-50, #fef2f2)}.wuic-rag-chatbot__action-box-header{display:flex;align-items:center;gap:.45rem;font-size:.88rem;color:var(--p-primary-700, #1d4ed8);font-weight:600}.wuic-rag-chatbot__action-box-header>i{font-size:.95rem}.wuic-rag-chatbot__action-box-title{display:inline-flex;align-items:center;gap:.4rem;flex-wrap:wrap}.wuic-rag-chatbot__action-box-label{color:var(--p-text-color, #111827);font-weight:700}.wuic-rag-chatbot__action-box-route{color:var(--p-text-muted-color, #6b7280);font-weight:400;font-size:.8rem}.wuic-rag-chatbot__action-box-route>code{background:var(--p-surface-100, #f3f4f6);padding:1px 6px;border-radius:4px;font-family:ui-monospace,monospace;color:var(--p-text-color, #111827)}.wuic-rag-chatbot__action-box-rationale{font-size:.85rem;color:var(--p-text-color, #1f2937);line-height:1.4;font-style:italic}.wuic-rag-chatbot__action-box-side-effect{font-size:.8rem;color:var(--p-blue-700, #1d4ed8);background:var(--p-blue-50, #eff6ff);border-left:3px solid var(--p-blue-400, #60a5fa);padding:.4rem .6rem;border-radius:4px;display:flex;align-items:flex-start;gap:.4rem;line-height:1.35}.wuic-rag-chatbot__action-box-side-effect i{font-size:.9rem;margin-top:.1rem;flex-shrink:0}.wuic-rag-chatbot__action-box-editor-toolbar{display:flex;justify-content:flex-end;align-items:center}.wuic-rag-chatbot__action-box-extra-fields{display:flex;flex-direction:column;gap:6px;margin-bottom:8px}.wuic-rag-chatbot__action-box-extra-field{display:flex;align-items:center;gap:8px}.wuic-rag-chatbot__action-box-extra-field-label{flex:0 0 auto;min-width:100px;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__action-box-extra-field-value{flex:1 1 auto;min-width:0;font-family:ui-monospace,SFMono-Regular,Consolas,Cascadia Code,monospace;font-size:.78rem;padding:4px 8px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:4px;background:var(--p-surface-50, #fafafa);color:var(--p-text-color, #111827)}.wuic-rag-chatbot__action-box-extra-field-value:focus{outline:2px solid var(--p-primary-300, rgba(59, 130, 246, .4));outline-offset:-1px}.wuic-rag-chatbot__action-box-textarea{width:100%;min-height:200px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;padding:8px 10px;background:var(--p-surface-50, #fafafa);color:var(--p-text-color, #111827);font-family:ui-monospace,SFMono-Regular,Consolas,Cascadia Code,monospace;font-size:.78rem;line-height:1.45;resize:vertical;white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:hidden;overflow-y:auto;tab-size:2}.wuic-rag-chatbot__action-box-textarea:focus{outline:2px solid var(--p-primary-300, rgba(59, 130, 246, .4));outline-offset:-1px}.wuic-rag-chatbot__action-box-editor{height:240px;min-height:240px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;overflow:hidden;background:#1e1e1e}.wuic-rag-chatbot__action-box-editor-loading{display:flex;align-items:center;justify-content:center;gap:.5rem;min-height:100px;color:var(--p-text-muted-color, #6b7280);font-size:.85rem;font-style:italic;background:var(--p-surface-50, #fafafa);border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:6px}.wuic-rag-chatbot__action-box-actions{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap}.wuic-rag-chatbot__action-box-applied{display:inline-flex;align-items:center;gap:.35rem;color:var(--p-green-700, #15803d);font-weight:600;font-size:.85rem}.wuic-rag-chatbot__action-box-error{display:inline-flex;align-items:center;gap:.35rem;color:var(--p-red-700, #b91c1c);font-size:.8rem;font-family:ui-monospace,monospace}.wuic-rag-chatbot__input-area{display:flex;align-items:flex-end;gap:.5rem;padding:var(--wuic-rag-chatbot-padding);border-top:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__input{flex:1 1 auto;min-height:2.25rem;max-height:8rem;resize:none;color:inherit;background-color:transparent}.wuic-rag-chatbot__source-meta{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.75rem}.wuic-rag-chatbot__source-tag{display:inline-flex;align-items:center;gap:.25rem;padding:2px 8px;background:var(--p-surface-100, #f3f4f6);color:var(--p-text-color-secondary, #6b7280);border-radius:10px;font-size:.72rem;font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.wuic-rag-chatbot__source-tag--file{background:var(--p-surface-0, #fff);color:var(--p-primary-color, #2563eb);border:1px solid var(--p-primary-200, #bfdbfe);font-weight:500;max-width:100%;word-break:break-all;white-space:normal;line-height:1.3}.wuic-rag-chatbot__source-tag--file i{font-size:.7rem;flex-shrink:0}.wuic-rag-chatbot__source-snippet{margin:0;padding:12px 14px;max-height:420px;overflow:auto;background:var(--p-surface-50, #fafafa);border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.78rem;line-height:1.4;white-space:pre-wrap;word-break:break-word;color:var(--p-text-color, #111827)}:host-context(.theme-dark) .wuic-rag-chatbot__turn--user{background-color:#60a5fa2e;border-color:#60a5fa73}:host-context(.theme-dark) .wuic-rag-chatbot__turn--assistant{background-color:#ffffff0f;border-color:#ffffff1f}:host-context(.theme-dark) .wuic-rag-chatbot__turn-header,:host-context(.theme-dark) .wuic-rag-chatbot__turn-loading,:host-context(.theme-dark) .wuic-rag-chatbot__sources-label,:host-context(.theme-dark) .wuic-rag-chatbot__tokens{color:#ffffffa6}:host-context(.theme-dark) .wuic-rag-chatbot__sources{border-top-color:#ffffff2e}:host-context(.theme-dark) .wuic-rag-chatbot__input-area{border-top-color:#ffffff1f}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:#3b82f62e;color:#93c5fd;border:1px solid rgba(59,130,246,.4)}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:#bfdbfe}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:#22c55e2e;color:#86efac;border:1px solid rgba(34,197,94,.4)}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:#bbf7d0}:host-context(.theme-dark) .wuic-rag-chatbot__source-tag{background:#ffffff14;color:#ffffffc7}:host-context(.theme-dark) .wuic-rag-chatbot__source-tag--file{background:#3b82f624;color:#93c5fd;border-color:#3b82f673}:host-context(.theme-dark) .wuic-rag-chatbot__source-snippet{background:#00000052;border-color:#ffffff1f;color:#ffffffeb}\n"] }]
63558
+ ], template: "<div class=\"wuic-rag-chatbot\" style=\"height: 100%;\">\n\n <!-- Header -->\n <div class=\"wuic-rag-chatbot__header\">\n <div class=\"wuic-rag-chatbot__title\">\n <i class=\"pi pi-comments wuic-rag-chatbot__title-icon\" aria-hidden=\"true\"></i>\n <span>{{ title }}</span>\n </div>\n <div class=\"wuic-rag-chatbot__header-actions\">\n @if (allowProfileToggle) {\n <p-button [label]=\"profileToggleLabel\" [icon]=\"profileOverride === 'release' ? 'pi pi-eye' : 'pi pi-code'\"\n [severity]=\"profileOverride === 'release' ? 'warn' : 'secondary'\" [outlined]=\"true\" [rounded]=\"true\"\n size=\"small\" (onClick)=\"toggleProfile()\" [pTooltip]=\"profileOverride === 'release'\n ? ('rag_chatbot.profile_toggle.tooltip_release' | translate)\n : ('rag_chatbot.profile_toggle.tooltip_dev' | translate)\" tooltipPosition=\"bottom\"\n [ariaLabel]=\"'rag_chatbot.profile_toggle.aria_label' | translate\">\n </p-button>\n }\n <p-badge [value]=\"modeBadgeLabel\" [severity]=\"modeBadgeSeverity\" [pTooltip]=\"healthInfo\n ? ('chunks indicizzati: ' + healthInfo.docs_loaded + ' | modello: ' + healthInfo.default_model)\n : (healthError || ('rag_chatbot.health_indicator.connecting' | translate))\" tooltipPosition=\"bottom\">\n </p-badge>\n <!-- Context cue: cerchio colorato che riflette % context window usato.\n Click + conferma -> /compact. Tooltip mostra k/k token + %. -->\n @if (contextWindowMax) {\n <button type=\"button\"\n class=\"wuic-rag-chatbot__context-cue\"\n [style.background]=\"contextCueColor\"\n [pTooltip]=\"contextCueTooltip\"\n tooltipPosition=\"bottom\"\n [disabled]=\"compactingSession || !currentSessionId\"\n (click)=\"onContextCueClick()\"\n attr.aria-label=\"Compatta sessione (context {{ contextUsagePercent }}%)\">\n @if (compactingSession) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n } @else {\n <span>{{ contextUsagePercent }}%</span>\n }\n </button>\n }\n <!-- Bottone Sessioni: apre popup con lista sessioni ordinate desc per updatedAt. -->\n <p-button icon=\"pi pi-list\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"openSessionsDialog()\" [pTooltip]=\"'rag_chatbot.sessions_button.tooltip' | translate\"\n tooltipPosition=\"bottom\" [ariaLabel]=\"'rag_chatbot.sessions_button.aria_label' | translate\">\n </p-button>\n @if (showClearButton) {\n <p-button icon=\"pi pi-trash\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n [disabled]=\"history.length === 0 || loading || clearingHistory\" [loading]=\"clearingHistory\"\n (onClick)=\"onClearHistory()\" [pTooltip]=\"'rag_chatbot.clear_button.tooltip' | translate\"\n tooltipPosition=\"bottom\" [ariaLabel]=\"'rag_chatbot.clear_button.aria_label' | translate\">\n </p-button>\n }\n </div>\n </div>\n\n <!-- Health error banner -->\n @if (healthError) {\n <p-message severity=\"error\" [text]=\"('rag_chatbot.health_error.banner' | translate) + ': ' + healthError\"\n styleClass=\"wuic-rag-chatbot__error-banner\"></p-message>\n }\n\n <!-- Chat history -->\n <div #scrollContainer class=\"wuic-rag-chatbot__history\" role=\"log\" aria-live=\"polite\">\n @if (history.length === 0) {\n <div class=\"wuic-rag-chatbot__empty\">\n <i class=\"pi pi-info-circle wuic-rag-chatbot__empty-icon\" aria-hidden=\"true\"></i>\n <p>{{ 'rag_chatbot.empty.prompt' | translate }}</p>\n <ul class=\"wuic-rag-chatbot__suggestions\">\n <li>{{ 'rag_chatbot.suggestion.invalidate_cache' | translate }}</li>\n <li>{{ 'rag_chatbot.suggestion.spreadsheet_massive' | translate }}</li>\n <li>{{ 'rag_chatbot.suggestion.save_report' | translate }}</li>\n </ul>\n </div>\n }\n\n @for (turn of history; track turn.messageId || $index) {\n <div class=\"wuic-rag-chatbot__turn\" [attr.data-message-id]=\"turn.messageId || null\"\n [class.wuic-rag-chatbot__turn--user]=\"turn.role === 'user'\"\n [class.wuic-rag-chatbot__turn--assistant]=\"turn.role === 'assistant'\">\n\n <div class=\"wuic-rag-chatbot__turn-header\">\n <i class=\"pi\" [class.pi-user]=\"turn.role === 'user'\" [class.pi-android]=\"turn.role === 'assistant'\"\n aria-hidden=\"true\"></i>\n <span>{{ (turn.role === 'user' ? 'rag_chatbot.turn.role_user' : 'rag_chatbot.turn.role_assistant') | translate }}</span>\n @if (turn.mode) {\n <span class=\"wuic-rag-chatbot__turn-mode\">[{{ turn.mode === 'rag-llm' ? 'RAG+LLM' : 'retrieval' }}]</span>\n }\n </div>\n\n @if (turn.loading) {\n <div class=\"wuic-rag-chatbot__turn-loading\">\n <p-progressSpinner styleClass=\"wuic-rag-chatbot__spinner\" strokeWidth=\"3\"\n animationDuration=\".9s\"></p-progressSpinner>\n <span>{{ 'rag_chatbot.turn.loading' | translate }}</span>\n </div>\n } @else if (turn.errorMessage) {\n <p-message severity=\"error\" [text]=\"(turn.errorMessage | translate)\" styleClass=\"wuic-rag-chatbot__turn-error\"></p-message>\n } @else {\n <div class=\"wuic-rag-chatbot__turn-content\">{{ turn.content }}</div>\n @if (turn.warning) {\n <p-message severity=\"warn\" [text]=\"turn.warning\" styleClass=\"wuic-rag-chatbot__turn-warning\"></p-message>\n }\n <!-- Box \"Proposta azione\" emessa dall'LLM via tool-use. Renderizzato solo\n per i turn assistant che hanno proposedAction valorizzata. Il monaco\n editor riusa la stessa codeContext del metadata-editor sulla colonna\n equivalente (autocomplete identico, type-check identico). -->\n @if (turn.role === 'assistant' && turn.proposedAction) {\n <div class=\"wuic-rag-chatbot__action-box\"\n [class.wuic-rag-chatbot__action-box--applied]=\"turn.proposedActionAppliedAt\"\n [class.wuic-rag-chatbot__action-box--error]=\"turn.applyError\">\n <div class=\"wuic-rag-chatbot__action-box-header\">\n <i class=\"pi pi-bolt\" aria-hidden=\"true\"></i>\n <span class=\"wuic-rag-chatbot__action-box-title\">\n {{ proposedActionTitle(turn.proposedAction) }}\n @if (turn.proposedAction.label) {\n <span class=\"wuic-rag-chatbot__action-box-label\">\"{{ turn.proposedAction.label }}\"</span>\n }\n @if (turn.proposedAction.route) {\n <span class=\"wuic-rag-chatbot__action-box-route\">{{ 'rag_chatbot.action_box.label_on_route_prefix' | translate }} <code>{{ turn.proposedAction.route }}</code></span>\n }\n </span>\n </div>\n @if (turn.proposedAction.rationale) {\n <div class=\"wuic-rag-chatbot__action-box-rationale\">{{ turn.proposedAction.rationale }}</div>\n }\n <!-- Hint trasparenza UX: l'utente sa in anticipo che, oltre a creare\n la toolbar action, abiliteremo md_multiple_selection sulla route\n (necessario per le bulk operations che leggono getSelectedRows). -->\n @if (turn.proposedAction.requires_multi_selection) {\n <div class=\"wuic-rag-chatbot__action-box-side-effect\">\n <i class=\"pi pi-info-circle\"></i>\n {{ 'rag_chatbot.action_box.side_effect_multi_selection' | translate }}\n </div>\n }\n <!-- Campi NON-body della proposta (es. per table_style: classe/stile;\n per toolbar/row_action: label+icon; per sql_metadata_field: field\n target). Visibili sopra la textarea del body cosi' l'utente capisce\n a colpo d'occhio COSA viene applicato oltre al CODICE del callback.\n Sorgente: proposedActionExtraFields(kind) nel .ts. -->\n @if (proposedActionExtraFields(turn.proposedAction).length > 0) {\n <div class=\"wuic-rag-chatbot__action-box-extra-fields\">\n @for (extra of proposedActionExtraFields(turn.proposedAction); track extra.fieldName) {\n <div class=\"wuic-rag-chatbot__action-box-extra-field\">\n <label class=\"wuic-rag-chatbot__action-box-extra-field-label\">{{ extra.label }}</label>\n <input\n class=\"wuic-rag-chatbot__action-box-extra-field-value\"\n [disabled]=\"!!turn.proposedActionAppliedAt\"\n [ngModel]=\"extra.value\"\n (ngModelChange)=\"onProposedExtraFieldEdit(turn, extra, $event)\"\n [name]=\"'extra-' + turn.messageId + '-' + extra.fieldName\">\n </div>\n }\n </div>\n }\n @if (proposedActionJsBody(turn.proposedAction)) {\n <!-- Pattern <lib-code-area-editor>: textarea readonly (zero overhead,\n niente flash nero, contenuto immediatamente visibile) di default.\n Toggle \"Apri editor avanzato\" attiva `editorMode` -> @defer carica\n Monaco con codeContext+autocomplete. Doppio livello di lazy:\n @defer in HTML + LazyCodeEditorComponent fa import() dinamico.\n Risultato: Monaco scaricato SOLO se l'utente lo richiede.\n proposedActionJsBody() unifica callback_js / condition_js / formula_js\n in base al kind: stesso UX per tutti i kind che hanno una body JS. -->\n <div class=\"wuic-rag-chatbot__action-box-editor-toolbar\">\n <p-button\n [label]=\"(turn.editorMode ? 'rag_chatbot.action_box.editor_close' : 'rag_chatbot.action_box.editor_open') | translate\"\n [icon]=\"turn.editorMode ? 'pi pi-times' : 'pi pi-code'\"\n severity=\"secondary\"\n [text]=\"true\"\n size=\"small\"\n (onClick)=\"toggleEditorMode(turn)\">\n </p-button>\n </div>\n @if (!turn.editorMode) {\n <textarea\n class=\"wuic-rag-chatbot__action-box-textarea\"\n rows=\"10\"\n [disabled]=\"!!turn.proposedActionAppliedAt\"\n [ngModel]=\"proposedActionJsBody(turn.proposedAction)\"\n (ngModelChange)=\"onProposedBodyEdit(turn, $event)\"\n [name]=\"'body-' + turn.messageId\">\n </textarea>\n } @else {\n @defer (when turn.editorMode) {\n <div class=\"wuic-rag-chatbot__action-box-editor\">\n <wuic-code-editor-lazy\n [field]=\"getProposedActionField(turn)\"\n [record]=\"getProposedActionRecord(turn)\"\n style=\"display: block; width: 100%; height: 100%;\">\n </wuic-code-editor-lazy>\n </div>\n } @loading (minimum 200ms) {\n <div class=\"wuic-rag-chatbot__action-box-editor-loading\">\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n {{ 'rag_chatbot.action_box.editor_loading' | translate }}\n </div>\n }\n }\n }\n <div class=\"wuic-rag-chatbot__action-box-actions\">\n @if (turn.proposedActionAppliedAt) {\n <span class=\"wuic-rag-chatbot__action-box-applied\">\n <i class=\"pi pi-check-circle\" aria-hidden=\"true\"></i>\n {{ 'rag_chatbot.action_box.applied' | translate }}\n </span>\n } @else {\n <p-button\n [label]=\"'rag_chatbot.action_box.apply' | translate\"\n icon=\"pi pi-check\"\n severity=\"primary\"\n size=\"small\"\n [loading]=\"!!turn.applyingAction\"\n (onClick)=\"onApplyAction(turn)\">\n </p-button>\n }\n @if (turn.applyError) {\n <span class=\"wuic-rag-chatbot__action-box-error\">\n <i class=\"pi pi-times-circle\" aria-hidden=\"true\"></i>\n {{ turn.applyError }}\n </span>\n }\n </div>\n </div>\n }\n @if (showSources && turn.role === 'assistant' && turn.sources && turn.sources.length > 0) {\n @let dedupedSources = dedupSources(turn.sources);\n <div class=\"wuic-rag-chatbot__sources\">\n <div class=\"wuic-rag-chatbot__sources-label\">{{ 'rag_chatbot.sources.label' | translate }}</div>\n <div class=\"wuic-rag-chatbot__sources-list\">\n @for (source of dedupedSources; track source.chunk_id || (source.rel_path + ':' + source.start_line + ':' + source.end_line + ':' + source.symbol_name)) {\n <p-chip [label]=\"sourceLabel(source, dedupedSources)\"\n [icon]=\"isDocsSource(source) ? 'pi pi-book' : (isSkillSource(source) ? 'pi pi-wrench' : 'pi pi-file-edit')\"\n [removable]=\"false\" [pTooltip]=\"sourceTooltip(source)\" tooltipPosition=\"top\" [styleClass]=\"isDocsSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--docs'\n : (isSkillSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--skill'\n : 'wuic-rag-chatbot__source-chip')\" (click)=\"onSourceClick(source)\">\n </p-chip>\n }\n </div>\n </div>\n }\n <!-- Follow-up questions emesse dall'LLM via tool `suggest_followups`.\n Renderizzate come chip cliccabili sotto la risposta: click popola\n l'input box (NON invia auto, l'utente puo' editare prima). -->\n @if (turn.role === 'assistant' && turn.followupQuestions && turn.followupQuestions.length > 0) {\n <div class=\"wuic-rag-chatbot__followups\">\n <div class=\"wuic-rag-chatbot__followups-label\">{{ 'rag_chatbot.followups.label' | translate }}</div>\n <div class=\"wuic-rag-chatbot__followups-list\">\n @for (q of turn.followupQuestions; track q) {\n <p-chip\n [label]=\"q\"\n icon=\"pi pi-arrow-right\"\n styleClass=\"wuic-rag-chatbot__followup-chip\"\n (click)=\"applyFollowup(q)\">\n </p-chip>\n }\n </div>\n </div>\n }\n @if (turn.tokensIn || turn.tokensOut) {\n <div class=\"wuic-rag-chatbot__tokens\">\n tokens in/out: {{ turn.tokensIn ?? '?' }}/{{ turn.tokensOut ?? '?' }}\n </div>\n }\n }\n </div>\n }\n </div>\n\n <!-- Input area -->\n <div class=\"wuic-rag-chatbot__input-area\">\n <textarea #inputTextarea pTextarea class=\"wuic-rag-chatbot__input\" [(ngModel)]=\"inputValue\"\n (keydown)=\"onKeyDown($event)\" [placeholder]=\"placeholder\" [disabled]=\"loading || !!healthError\"\n [autoResize]=\"true\" rows=\"1\" [attr.aria-label]=\"'rag_chatbot.input.aria_label' | translate\">\n </textarea>\n <p-button icon=\"pi pi-send\" [disabled]=\"sendDisabled\" [loading]=\"loading\" (onClick)=\"onSend()\" severity=\"primary\"\n [ariaLabel]=\"'rag_chatbot.input.send_aria_label' | translate\">\n </p-button>\n </div>\n\n <!-- Dialog preview snippet sorgente.\n Mostra SOLO il contenuto del chunk (gia' embedded nella response RAG).\n NON espone il path del file ne' apre editor esterni: il codebase del\n framework vive sul repo dev e non deve essere linkato all'utente finale. -->\n @if (selectedSource) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw' }\" [header]=\"sourceDialogTitle(selectedSource)\"\n styleClass=\"wuic-rag-chatbot__source-dialog\" (onHide)=\"closeSourcePreview()\">\n <div class=\"wuic-rag-chatbot__source-meta\">\n @if (sourceFilePath(selectedSource); as fpath) {\n <span class=\"wuic-rag-chatbot__source-tag wuic-rag-chatbot__source-tag--file\">\n <i class=\"pi pi-file\" aria-hidden=\"true\"></i>\n {{ fpath }}\n </span>\n }\n @if (selectedSource.symbol_type) {\n <span class=\"wuic-rag-chatbot__source-tag\">{{ selectedSource.symbol_type }}</span>\n }\n @if (selectedSource.start_line && selectedSource.end_line) {\n <span class=\"wuic-rag-chatbot__source-tag\">linee {{ selectedSource.start_line }}-{{ selectedSource.end_line\n }}</span>\n }\n @if (selectedSource.score_vector !== null && selectedSource.score_vector !== undefined) {\n <span class=\"wuic-rag-chatbot__source-tag\">vec {{ selectedSource.score_vector | number:'1.2-2' }}</span>\n }\n </div>\n <pre\n class=\"wuic-rag-chatbot__source-snippet\"><code>{{ selectedSource.snippet || ('rag_chatbot.source.snippet_unavailable' | translate) }}</code></pre>\n </p-dialog>\n }\n\n <!-- Dialog \"Sessioni\": elenco ordinato desc per updatedAt. Per ogni riga:\n data + titolo (truncated 100 char, tooltip full) + bottone rinomina\n inline + click row = switch session. -->\n @if (sessionsDialogOpen) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw', maxHeight: '85vh' }\"\n [header]=\"'rag_chatbot.sessions_dialog.header' | translate\"\n styleClass=\"wuic-rag-chatbot__sessions-dialog\"\n (onHide)=\"closeSessionsDialog()\">\n @if (sessionsLoading) {\n <div class=\"wuic-rag-chatbot__sessions-loading\">\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i> {{ 'rag_chatbot.sessions_dialog.loading' | translate }}\n </div>\n } @else if (sessionsList.length === 0) {\n <div class=\"wuic-rag-chatbot__sessions-empty\">{{ 'rag_chatbot.sessions_dialog.empty' | translate }}</div>\n } @else {\n <ul class=\"wuic-rag-chatbot__sessions-list\">\n @for (s of sessionsList; track s.id) {\n <li class=\"wuic-rag-chatbot__session-item\"\n [class.wuic-rag-chatbot__session-item--current]=\"s.id === currentSessionId\"\n (click)=\"selectSession(s)\">\n <div class=\"wuic-rag-chatbot__session-date\">{{ sessionDate(s) }}</div>\n @if (sessionRenameEditing[s.id]) {\n <div class=\"wuic-rag-chatbot__session-edit\" (click)=\"$event.stopPropagation()\">\n <input type=\"text\" pInputText\n [(ngModel)]=\"sessionRenameDraft[s.id]\"\n [autofocus]=\"true\"\n maxlength=\"200\"\n class=\"wuic-rag-chatbot__session-edit-input\"\n (keydown.enter)=\"confirmRenameSession(s, $event)\"\n (keydown.escape)=\"cancelRenameSession(s)\">\n <p-button icon=\"pi pi-check\" severity=\"success\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"confirmRenameSession(s, $event)\" [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_confirm_aria' | translate\"></p-button>\n <p-button icon=\"pi pi-times\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"cancelRenameSession(s)\" [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_cancel_aria' | translate\"></p-button>\n </div>\n } @else {\n <div class=\"wuic-rag-chatbot__session-row\">\n <span class=\"wuic-rag-chatbot__session-title\" [pTooltip]=\"s.title || ('rag_chatbot.sessions_dialog.untitled' | translate)\" tooltipPosition=\"top\">\n {{ sessionPreview(s) }}\n </span>\n <p-button icon=\"pi pi-pencil\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"startRenameSession(s, $event)\" [pTooltip]=\"'rag_chatbot.sessions_dialog.rename' | translate\" tooltipPosition=\"left\"\n [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_aria' | translate\"></p-button>\n <p-button icon=\"pi pi-trash\" severity=\"danger\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"onDeleteSessionFromList(s, $event)\" [pTooltip]=\"'rag_chatbot.sessions_dialog.delete' | translate\" tooltipPosition=\"left\"\n [ariaLabel]=\"'rag_chatbot.sessions_dialog.delete_aria' | translate\"></p-button>\n </div>\n }\n </li>\n }\n </ul>\n }\n </p-dialog>\n }\n\n <!-- Dialog context-overflow: l'ultima call ha fallito perche' la history\n sfora il context window. 2 azioni: Compatta ora (riassume e prosegue\n stessa session) / Nuova sessione (clear + ricomincia). -->\n @if (contextOverflowDialogOpen) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"false\"\n [style]=\"{ width: '480px', maxWidth: '92vw' }\"\n [header]=\"'rag_chatbot.overflow_dialog.header' | translate\"\n styleClass=\"wuic-rag-chatbot__overflow-dialog\"\n (onHide)=\"contextOverflowDialogOpen = false\">\n <p>{{ 'rag_chatbot.overflow_dialog.intro' | translate }}</p>\n <ul>\n <li><strong>{{ 'rag_chatbot.overflow_dialog.option_compact_title' | translate }}</strong> \u2014 {{ 'rag_chatbot.overflow_dialog.option_compact_desc' | translate }}</li>\n <li><strong>{{ 'rag_chatbot.overflow_dialog.option_new_title' | translate }}</strong> \u2014 {{ 'rag_chatbot.overflow_dialog.option_new_desc' | translate }}</li>\n </ul>\n <div class=\"wuic-rag-chatbot__overflow-actions\">\n <p-button [label]=\"'rag_chatbot.overflow_dialog.option_compact_title' | translate\" icon=\"pi pi-compress\" severity=\"primary\"\n [loading]=\"compactingSession\"\n (onClick)=\"doCompactSession()\"></p-button>\n <p-button [label]=\"'rag_chatbot.overflow_dialog.option_new_title' | translate\" icon=\"pi pi-refresh\" severity=\"secondary\"\n (onClick)=\"startNewSession()\"></p-button>\n </div>\n </p-dialog>\n }\n\n</div>", styles: [".wuic-rag-chatbot{--wuic-rag-chatbot-radius: .5rem;--wuic-rag-chatbot-padding: .75rem;--wuic-rag-chatbot-gap: .5rem;display:flex;flex-direction:column;width:100%;height:100%;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:var(--wuic-rag-chatbot-radius);background-color:var(--p-content-background, transparent);color:inherit;overflow:hidden;font-family:inherit}.wuic-rag-chatbot__header{display:flex;align-items:center;justify-content:space-between;padding:var(--wuic-rag-chatbot-padding);border-bottom:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__title{display:flex;align-items:center;gap:.5rem;font-weight:600;color:inherit}.wuic-rag-chatbot__title-icon{color:var(--p-primary-color, #3b82f6)}.wuic-rag-chatbot__header-actions{display:flex;align-items:center;gap:.5rem}.wuic-rag-chatbot__context-cue{width:28px;height:28px;border-radius:50%;border:2px solid rgba(255,255,255,.85);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:.62rem;font-weight:700;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.35);box-shadow:0 0 0 1px #0000000f,0 1px 3px #0000001f;transition:transform .12s ease,box-shadow .12s ease,opacity .12s ease;padding:0}.wuic-rag-chatbot__context-cue:hover:not(:disabled){transform:scale(1.08);box-shadow:0 0 0 1px #0000001a,0 2px 6px #0003}.wuic-rag-chatbot__context-cue:disabled{cursor:default;opacity:.65}.wuic-rag-chatbot__context-cue>i{font-size:.85rem}.wuic-rag-chatbot__sessions-loading,.wuic-rag-chatbot__sessions-empty{padding:1.5rem;text-align:center;color:var(--p-text-muted-color, #6b7280);font-style:italic}.wuic-rag-chatbot__sessions-list{list-style:none;margin:0;padding:0;max-height:60vh;overflow-y:auto}.wuic-rag-chatbot__session-item{padding:.6rem .85rem;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;margin-bottom:.4rem;cursor:pointer;transition:background .12s ease,border-color .12s ease}.wuic-rag-chatbot__session-item:hover{background:var(--p-surface-50, #f3f4f6);border-color:var(--p-primary-300, rgba(59, 130, 246, .4))}.wuic-rag-chatbot__session-item--current{background:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-400, rgba(59, 130, 246, .55))}.wuic-rag-chatbot__session-date{font-size:.72rem;color:var(--p-text-muted-color, #6b7280);font-family:ui-monospace,monospace;margin-bottom:.2rem}.wuic-rag-chatbot__session-row{display:flex;align-items:center;gap:.4rem}.wuic-rag-chatbot__session-title{flex:1 1 auto;font-size:.9rem;color:var(--p-text-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wuic-rag-chatbot__session-edit{display:flex;align-items:center;gap:.3rem}.wuic-rag-chatbot__session-edit-input{flex:1 1 auto;font-size:.85rem}.wuic-rag-chatbot__overflow-actions{display:flex;justify-content:flex-end;gap:.5rem;margin-top:1rem}.wuic-rag-chatbot__error-banner{margin:var(--wuic-rag-chatbot-padding);margin-bottom:0}.wuic-rag-chatbot__history{flex:1 1 auto;min-height:0;overflow-y:auto;padding:var(--wuic-rag-chatbot-padding);display:flex;flex-direction:column;gap:var(--wuic-rag-chatbot-gap);background-color:transparent}.wuic-rag-chatbot__empty{margin:auto;text-align:center;color:var(--p-text-muted-color, #6b7280);max-width:80%;padding:1.5rem 0}.wuic-rag-chatbot__empty-icon{font-size:1.5rem;margin-bottom:.5rem;display:block}.wuic-rag-chatbot__suggestions{list-style:none;padding:0;margin:.75rem 0 0;display:flex;flex-direction:column;gap:.25rem;text-align:left}.wuic-rag-chatbot__suggestions li{padding:.35rem .75rem;border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:.375rem;font-size:.875rem}.wuic-rag-chatbot__turn{display:flex;flex-direction:column;gap:.35rem;padding:.65rem .85rem;border-radius:var(--wuic-rag-chatbot-radius);max-width:92%;color:inherit;background-color:transparent;border:1px solid var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__turn--user{align-self:flex-end;background-color:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-200, rgba(59, 130, 246, .25))}.wuic-rag-chatbot__turn--assistant{align-self:flex-start;background-color:var(--p-surface-50, rgba(0, 0, 0, .025))}.wuic-rag-chatbot__turn-header{display:flex;align-items:center;gap:.4rem;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__turn-mode{margin-left:auto;font-family:ui-monospace,monospace;font-size:.7rem;opacity:.7}.wuic-rag-chatbot__turn-content{white-space:pre-wrap;word-break:break-word;line-height:1.45;font-size:.93rem;color:inherit}.wuic-rag-chatbot__turn-loading{display:flex;align-items:center;gap:.5rem;color:var(--p-text-muted-color, #6b7280);font-style:italic;font-size:.875rem}.wuic-rag-chatbot__spinner{width:1.25rem!important;height:1.25rem!important}.wuic-rag-chatbot__turn-error,.wuic-rag-chatbot__turn-warning{margin-top:.35rem}.wuic-rag-chatbot__sources{display:flex;flex-direction:column;gap:.35rem;margin-top:.5rem;padding-top:.5rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__sources-label{font-size:.72rem;text-transform:uppercase;letter-spacing:.05em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__sources-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__source-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease}.wuic-rag-chatbot__source-chip:hover{transform:translateY(-1px);box-shadow:0 1px 4px #0000001f}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:var(--p-blue-50, #eff6ff);color:var(--p-blue-700, #1d4ed8);border:1px solid var(--p-blue-200, #bfdbfe)}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:var(--p-blue-600, #2563eb)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:var(--p-green-50, #f0fdf4);color:var(--p-green-700, #15803d);border:1px solid var(--p-green-200, #bbf7d0)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:var(--p-green-600, #16a34a)}.wuic-rag-chatbot__tokens{margin-top:.4rem;font-size:.7rem;color:var(--p-text-muted-color, #6b7280);text-align:right;font-family:ui-monospace,monospace}.wuic-rag-chatbot__followups{display:flex;flex-direction:column;gap:.3rem;margin-top:.55rem;padding-top:.55rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__followups-label{font-size:.7rem;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__followups-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__followup-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease,background .12s ease}.wuic-rag-chatbot__followup-chip :host ::ng-deep .p-chip{background:var(--p-primary-50, rgba(59, 130, 246, .08));color:var(--p-primary-700, #1d4ed8);border:1px solid var(--p-primary-200, rgba(59, 130, 246, .25));font-size:.82rem;line-height:1.3;padding:.2rem .5rem}.wuic-rag-chatbot__followup-chip :host ::ng-deep .p-chip-icon{color:var(--p-primary-600, #2563eb);font-size:.75rem}.wuic-rag-chatbot__followup-chip:hover{transform:translateY(-1px);box-shadow:0 2px 6px #3b82f62e}.wuic-rag-chatbot__followup-chip:hover :host ::ng-deep .p-chip{background:var(--p-primary-100, rgba(59, 130, 246, .15))}.wuic-rag-chatbot__action-box{margin-top:.65rem;border:1px solid var(--p-primary-300, rgba(59, 130, 246, .45));border-radius:8px;background:var(--p-primary-50, rgba(59, 130, 246, .05));padding:.6rem .75rem;display:flex;flex-direction:column;gap:.45rem}.wuic-rag-chatbot__action-box--applied{border-color:var(--p-green-300, #86efac);background:var(--p-green-50, #f0fdf4)}.wuic-rag-chatbot__action-box--error{border-color:var(--p-red-300, #fca5a5);background:var(--p-red-50, #fef2f2)}.wuic-rag-chatbot__action-box-header{display:flex;align-items:center;gap:.45rem;font-size:.88rem;color:var(--p-primary-700, #1d4ed8);font-weight:600}.wuic-rag-chatbot__action-box-header>i{font-size:.95rem}.wuic-rag-chatbot__action-box-title{display:inline-flex;align-items:center;gap:.4rem;flex-wrap:wrap}.wuic-rag-chatbot__action-box-label{color:var(--p-text-color, #111827);font-weight:700}.wuic-rag-chatbot__action-box-route{color:var(--p-text-muted-color, #6b7280);font-weight:400;font-size:.8rem}.wuic-rag-chatbot__action-box-route>code{background:var(--p-surface-100, #f3f4f6);padding:1px 6px;border-radius:4px;font-family:ui-monospace,monospace;color:var(--p-text-color, #111827)}.wuic-rag-chatbot__action-box-rationale{font-size:.85rem;color:var(--p-text-color, #1f2937);line-height:1.4;font-style:italic}.wuic-rag-chatbot__action-box-side-effect{font-size:.8rem;color:var(--p-blue-700, #1d4ed8);background:var(--p-blue-50, #eff6ff);border-left:3px solid var(--p-blue-400, #60a5fa);padding:.4rem .6rem;border-radius:4px;display:flex;align-items:flex-start;gap:.4rem;line-height:1.35}.wuic-rag-chatbot__action-box-side-effect i{font-size:.9rem;margin-top:.1rem;flex-shrink:0}.wuic-rag-chatbot__action-box-editor-toolbar{display:flex;justify-content:flex-end;align-items:center}.wuic-rag-chatbot__action-box-extra-fields{display:flex;flex-direction:column;gap:6px;margin-bottom:8px}.wuic-rag-chatbot__action-box-extra-field{display:flex;align-items:center;gap:8px}.wuic-rag-chatbot__action-box-extra-field-label{flex:0 0 auto;min-width:100px;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__action-box-extra-field-value{flex:1 1 auto;min-width:0;font-family:ui-monospace,SFMono-Regular,Consolas,Cascadia Code,monospace;font-size:.78rem;padding:4px 8px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:4px;background:var(--p-surface-50, #fafafa);color:var(--p-text-color, #111827)}.wuic-rag-chatbot__action-box-extra-field-value:focus{outline:2px solid var(--p-primary-300, rgba(59, 130, 246, .4));outline-offset:-1px}.wuic-rag-chatbot__action-box-textarea{width:100%;min-height:200px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;padding:8px 10px;background:var(--p-surface-50, #fafafa);color:var(--p-text-color, #111827);font-family:ui-monospace,SFMono-Regular,Consolas,Cascadia Code,monospace;font-size:.78rem;line-height:1.45;resize:vertical;white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:hidden;overflow-y:auto;tab-size:2}.wuic-rag-chatbot__action-box-textarea:focus{outline:2px solid var(--p-primary-300, rgba(59, 130, 246, .4));outline-offset:-1px}.wuic-rag-chatbot__action-box-editor{height:240px;min-height:240px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;overflow:hidden;background:#1e1e1e}.wuic-rag-chatbot__action-box-editor-loading{display:flex;align-items:center;justify-content:center;gap:.5rem;min-height:100px;color:var(--p-text-muted-color, #6b7280);font-size:.85rem;font-style:italic;background:var(--p-surface-50, #fafafa);border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:6px}.wuic-rag-chatbot__action-box-actions{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap}.wuic-rag-chatbot__action-box-applied{display:inline-flex;align-items:center;gap:.35rem;color:var(--p-green-700, #15803d);font-weight:600;font-size:.85rem}.wuic-rag-chatbot__action-box-error{display:inline-flex;align-items:center;gap:.35rem;color:var(--p-red-700, #b91c1c);font-size:.8rem;font-family:ui-monospace,monospace}.wuic-rag-chatbot__input-area{display:flex;align-items:flex-end;gap:.5rem;padding:var(--wuic-rag-chatbot-padding);border-top:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__input{flex:1 1 auto;min-height:2.25rem;max-height:8rem;resize:none;color:inherit;background-color:transparent}.wuic-rag-chatbot__source-meta{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.75rem}.wuic-rag-chatbot__source-tag{display:inline-flex;align-items:center;gap:.25rem;padding:2px 8px;background:var(--p-surface-100, #f3f4f6);color:var(--p-text-color-secondary, #6b7280);border-radius:10px;font-size:.72rem;font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.wuic-rag-chatbot__source-tag--file{background:var(--p-surface-0, #fff);color:var(--p-primary-color, #2563eb);border:1px solid var(--p-primary-200, #bfdbfe);font-weight:500;max-width:100%;word-break:break-all;white-space:normal;line-height:1.3}.wuic-rag-chatbot__source-tag--file i{font-size:.7rem;flex-shrink:0}.wuic-rag-chatbot__source-snippet{margin:0;padding:12px 14px;max-height:420px;overflow:auto;background:var(--p-surface-50, #fafafa);border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.78rem;line-height:1.4;white-space:pre-wrap;word-break:break-word;color:var(--p-text-color, #111827)}:host-context(.theme-dark) .wuic-rag-chatbot__turn--user{background-color:#60a5fa2e;border-color:#60a5fa73}:host-context(.theme-dark) .wuic-rag-chatbot__turn--assistant{background-color:#ffffff0f;border-color:#ffffff1f}:host-context(.theme-dark) .wuic-rag-chatbot__turn-header,:host-context(.theme-dark) .wuic-rag-chatbot__turn-loading,:host-context(.theme-dark) .wuic-rag-chatbot__sources-label,:host-context(.theme-dark) .wuic-rag-chatbot__tokens{color:#ffffffa6}:host-context(.theme-dark) .wuic-rag-chatbot__sources{border-top-color:#ffffff2e}:host-context(.theme-dark) .wuic-rag-chatbot__input-area{border-top-color:#ffffff1f}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:#3b82f62e;color:#93c5fd;border:1px solid rgba(59,130,246,.4)}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:#bfdbfe}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:#22c55e2e;color:#86efac;border:1px solid rgba(34,197,94,.4)}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:#bbf7d0}:host-context(.theme-dark) .wuic-rag-chatbot__source-tag{background:#ffffff14;color:#ffffffc7}:host-context(.theme-dark) .wuic-rag-chatbot__source-tag--file{background:#3b82f624;color:#93c5fd;border-color:#3b82f673}:host-context(.theme-dark) .wuic-rag-chatbot__source-snippet{background:#00000052;border-color:#ffffff1f;color:#ffffffeb}\n"] }]
63527
63559
  }], ctorParameters: () => [{ type: WuicRagService }, { type: i0.ChangeDetectorRef }, { type: ChatbotHostRegistryService }, { type: i1$2.ActivatedRoute, decorators: [{
63528
63560
  type: Optional
63529
63561
  }] }, { type: i1$2.Router, decorators: [{
@@ -63971,6 +64003,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
63971
64003
  ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<p-dialog [header]=\"currentException?.title || ('error' | translate)\"\n [attr.title]=\"currentException?.title || ('error' | translate)\"\n [modal]=\"true\" [(visible)]=\"visible\" [style]=\"{ width: '75%' }\"\n [contentStyle]=\"{ 'max-height': '70vh', 'overflow': 'auto' }\"\n (onHide)=\"onClose()\">\n\n <!-- Body principale.\n For SQL passthrough: raw engine text (NOT translated) + meta line.\n Per envelope tipizzato: title + parserMessage/line/route da args. -->\n <div class=\"wuic-err-main\" data-testid=\"wuic-error-dialog-body\">\n <ng-container *ngIf=\"currentException?.kind === 'sql-passthrough'; else legacyBody\">\n <p class=\"wuic-err-msg\" style=\"font-size: 1rem; margin: 0 0 0.5rem 0; white-space: pre-wrap;\">\n {{ currentException?.body }}\n </p>\n <p class=\"wuic-err-meta\" *ngIf=\"currentException?.sqlDetails\"\n style=\"color: var(--text-color-secondary); font-size: 0.85rem; margin: 0 0 1rem 0;\">\n {{ formatSqlDetails(currentException?.sqlDetails) }}\n </p>\n </ng-container>\n <ng-template #legacyBody>\n <p style=\"margin: 0 0 0.5rem 0; white-space: pre-wrap;\">\n {{ currentException?.title }}\n </p>\n <p *ngIf=\"currentException?.args?.['parserMessage']\"\n style=\"color: var(--text-color-secondary); font-size: 0.85rem; margin: 0 0 0.5rem 0; white-space: pre-wrap;\">\n {{ currentException?.args?.['parserMessage'] }}\n </p>\n <p *ngIf=\"currentException?.args?.['lineNumber'] !== undefined && currentException?.args?.['lineNumber'] !== null\"\n style=\"color: var(--text-color-secondary); font-size: 0.85rem; margin: 0 0 1rem 0;\">\n line {{ currentException?.args?.['lineNumber'] }} \u00B7 pos {{ currentException?.args?.['linePosition'] }}\n <ng-container *ngIf=\"currentException?.args?.['route']\"> \u00B7 route <code>{{ currentException?.args?.['route'] }}</code></ng-container>\n </p>\n </ng-template>\n </div>\n\n <!-- Executed query (SQL passthrough only) -->\n <p-fieldset *ngIf=\"currentException?.query\"\n [legend]=\"currentException?.labels?.query || 'Executed query'\"\n [toggleable]=\"true\" [collapsed]=\"true\"\n styleClass=\"wuic-err-section wuic-err-section-spacer\">\n <pre style=\"margin: 0; white-space: pre-wrap; word-break: break-word; font-size: 0.78rem; max-height: 28vh; overflow: auto;\">{{ currentException?.query }}</pre>\n </p-fieldset>\n\n <!-- Parameters (SQL passthrough only) -->\n <p-fieldset *ngIf=\"currentException?.parameters && (currentException?.parameters | json) !== '{}'\"\n [legend]=\"currentException?.labels?.params || 'Parameters'\"\n [toggleable]=\"true\" [collapsed]=\"true\"\n styleClass=\"wuic-err-section wuic-err-section-spacer\">\n <pre style=\"margin: 0; white-space: pre-wrap; font-size: 0.78rem; max-height: 22vh; overflow: auto;\">{{ formatParameters(currentException?.parameters) }}</pre>\n </p-fieldset>\n\n <!-- Stack trace (collapsed by default) -->\n <p-fieldset *ngIf=\"currentException?.stackTrace\"\n [legend]=\"currentException?.labels?.stack || 'Stack trace'\"\n [toggleable]=\"true\" [collapsed]=\"true\"\n styleClass=\"wuic-err-section wuic-err-section-spacer\">\n <pre style=\"margin: 0; white-space: pre-wrap; font-size: 0.78rem; max-height: 28vh; overflow: auto;\">{{ currentException?.stackTrace }}</pre>\n </p-fieldset>\n\n <!-- traceId footer line -->\n <p *ngIf=\"currentException?.traceId\"\n style=\"color: var(--text-color-secondary); font-size: 0.78rem; margin: 0 0 0.5rem 0;\">\n traceId: <code>{{ currentException?.traceId }}</code>\n </p>\n\n <ng-template pTemplate=\"footer\">\n <div class=\"flex justify-content-end gap-2\">\n <p-button [label]=\"currentException?.labels?.copy || 'Copia'\"\n severity=\"secondary\" icon=\"pi pi-copy\"\n (onClick)=\"copyErrorDetails()\" />\n <p-button [label]=\"'ok' | translate\" (onClick)=\"onClose()\" />\n </div>\n </ng-template>\n</p-dialog>\n", styles: [":host ::ng-deep .wuic-err-section+.wuic-err-section,:host ::ng-deep .wuic-err-section-spacer{margin-top:.5rem}\n"] }]
63972
64004
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }] });
63973
64005
 
64006
+ /**
64007
+ * Quando una richiesta HTTP imposta questo token nel suo `HttpContext`,
64008
+ * l'interceptor NON inoltra l'errore al `GlobalHandler` (niente dialog/toast
64009
+ * globale): l'errore viene comunque normalizzato e rilanciato, ma la sua
64010
+ * presentazione e' lasciata interamente al chiamante (es. il first-run wizard
64011
+ * che mostra l'errore SOLO inline nel form). Default: false (comportamento
64012
+ * standard col dialog globale).
64013
+ *
64014
+ * Uso:
64015
+ * import { HttpContext } from '@angular/common/http';
64016
+ * this.http.post(url, body, { context: new HttpContext().set(WUIC_SUPPRESS_GLOBAL_ERROR, true) });
64017
+ */
64018
+ const WUIC_SUPPRESS_GLOBAL_ERROR = new HttpContextToken(() => false);
63974
64019
  /**
63975
64020
  * Normalizes HTTP errors so the GlobalHandler always sees an
63976
64021
  * `errorCode + args + fallbackMessage` envelope.
@@ -63990,14 +64035,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
63990
64035
  * still fire (they may close busy spinners, etc).
63991
64036
  */
63992
64037
  function processHttpError(req, err) {
64038
+ // Se la richiesta ha richiesto la soppressione del dialog/toast globale
64039
+ // (es. first-run wizard, che mostra gli errori SOLO inline nel form), NON
64040
+ // inoltriamo al GlobalHandler. L'errore viene comunque normalizzato e
64041
+ // rilanciato: il catch del chiamante lo presenta come preferisce.
64042
+ const suppressGlobal = req.context?.get(WUIC_SUPPRESS_GLOBAL_ERROR) === true;
63993
64043
  // Already typed by the server → notify GlobalHandler directly so the
63994
64044
  // dialog renders even if the subscribing component handles the error
63995
64045
  // in its own catchError.
63996
64046
  if (err.error && typeof err.error === 'object' && 'errorCode' in err.error) {
63997
- try {
63998
- GlobalHandler.handleErrorStatic({ error: err.error });
64047
+ if (!suppressGlobal) {
64048
+ try {
64049
+ GlobalHandler.handleErrorStatic({ error: err.error });
64050
+ }
64051
+ catch { /* noop */ }
63999
64052
  }
64000
- catch { /* noop */ }
64001
64053
  return err;
64002
64054
  }
64003
64055
  // Generic HTTP failure — wrap with a stable code so the
@@ -64014,10 +64066,12 @@ function processHttpError(req, err) {
64014
64066
  fallbackMessage: typeof err.error === 'string' ? err.error : err.message,
64015
64067
  traceId: err.headers?.get('traceparent') || undefined,
64016
64068
  };
64017
- try {
64018
- GlobalHandler.handleErrorStatic({ error: wrappedError });
64069
+ if (!suppressGlobal) {
64070
+ try {
64071
+ GlobalHandler.handleErrorStatic({ error: wrappedError });
64072
+ }
64073
+ catch { /* noop */ }
64019
64074
  }
64020
- catch { /* noop */ }
64021
64075
  return new HttpErrorResponse({
64022
64076
  error: wrappedError,
64023
64077
  headers: err.headers,
@@ -64062,7 +64116,7 @@ const designerPendingChangesGuard = async (component) => {
64062
64116
  const routes = [
64063
64117
  {
64064
64118
  path: 'framework-docs',
64065
- loadComponent: () => import('./wuic-framework-lib-framework-docs.component-CGQbmSkv.mjs').then((m) => m.FrameworkDocsComponent),
64119
+ loadComponent: () => import('./wuic-framework-lib-framework-docs.component-DphKhTef.mjs').then((m) => m.FrameworkDocsComponent),
64066
64120
  canMatch: [menuRouteAccessCanMatchGuard],
64067
64121
  canActivate: [menuRouteAccessCanActivateGuard],
64068
64122
  data: {
@@ -64071,7 +64125,7 @@ const routes = [
64071
64125
  },
64072
64126
  {
64073
64127
  path: 'framework-docs/:slug',
64074
- loadComponent: () => import('./wuic-framework-lib-framework-docs.component-CGQbmSkv.mjs').then((m) => m.FrameworkDocsComponent),
64128
+ loadComponent: () => import('./wuic-framework-lib-framework-docs.component-DphKhTef.mjs').then((m) => m.FrameworkDocsComponent),
64075
64129
  canMatch: [menuRouteAccessCanMatchGuard],
64076
64130
  canActivate: [menuRouteAccessCanActivateGuard],
64077
64131
  data: {
@@ -64089,7 +64143,7 @@ const routes = [
64089
64143
  },
64090
64144
  {
64091
64145
  path: ':route/report-designer',
64092
- loadComponent: () => import('./wuic-framework-lib-report-designer.component-DcpdzFjh.mjs').then((m) => m.ReportDesignerComponent),
64146
+ loadComponent: () => import('./wuic-framework-lib-report-designer.component-D_qbefud.mjs').then((m) => m.ReportDesignerComponent),
64093
64147
  canMatch: [menuRouteAccessCanMatchGuard, featureRouteCanMatchGuard],
64094
64148
  canActivate: [menuRouteAccessCanActivateGuard, featureRouteCanActivateGuard],
64095
64149
  data: {
@@ -64099,7 +64153,7 @@ const routes = [
64099
64153
  },
64100
64154
  {
64101
64155
  path: ':route/report-viewer',
64102
- loadComponent: () => import('./wuic-framework-lib-report-viewer.component-BHpbGpp9.mjs').then((m) => m.ReportViewerComponent),
64156
+ loadComponent: () => import('./wuic-framework-lib-report-viewer.component-CIN2fY0P.mjs').then((m) => m.ReportViewerComponent),
64103
64157
  canMatch: [menuRouteAccessCanMatchGuard],
64104
64158
  canActivate: [menuRouteAccessCanActivateGuard],
64105
64159
  data: {
@@ -64119,7 +64173,7 @@ const routes = [
64119
64173
  },
64120
64174
  {
64121
64175
  path: ':route/pivot-builder',
64122
- loadComponent: () => import('./wuic-framework-lib-pivot-builder.component-0gJz9RWr.mjs').then((m) => m.PivotBuilderComponent),
64176
+ loadComponent: () => import('./wuic-framework-lib-pivot-builder.component-BqUUwrJM.mjs').then((m) => m.PivotBuilderComponent),
64123
64177
  canMatch: [menuRouteAccessCanMatchGuard, featureRouteCanMatchGuard],
64124
64178
  canActivate: [menuRouteAccessCanActivateGuard, featureRouteCanActivateGuard],
64125
64179
  data: {
@@ -64129,7 +64183,7 @@ const routes = [
64129
64183
  },
64130
64184
  {
64131
64185
  path: 'pivot-builder',
64132
- loadComponent: () => import('./wuic-framework-lib-pivot-builder.component-0gJz9RWr.mjs').then((m) => m.PivotBuilderComponent),
64186
+ loadComponent: () => import('./wuic-framework-lib-pivot-builder.component-BqUUwrJM.mjs').then((m) => m.PivotBuilderComponent),
64133
64187
  canMatch: [menuRouteAccessCanMatchGuard, featureRouteCanMatchGuard],
64134
64188
  canActivate: [menuRouteAccessCanActivateGuard, featureRouteCanActivateGuard],
64135
64189
  data: {
@@ -64387,5 +64441,5 @@ function getThemePrimaryHex(value) {
64387
64441
  * Generated bundle index. Do not edit.
64388
64442
  */
64389
64443
 
64390
- export { IsSelectedRowPipe as $, ArchetypeConfiguratorComponent as A, BooleanEditorComponent as B, ChatbotHostRegistryService as C, DynamicGenericTemplateComponent as D, ColorEditorComponent as E, FilterBarComponent as F, GlobalHandler as G, CrashReporterService as H, ImportExportButtonComponent as I, CustomAttributesService as J, CustomException as K, LazyParametricDialogComponent as L, MetadataProviderService as M, DataActionButtonComponent as N, DataProviderOdataService as O, PagerComponent as P, DateEditorComponent as Q, DesignerRouteComponent as R, DictionaryEditorComponent as S, TranslationManagerService as T, UserInfoService as U, DynamicRowTemplateComponent as V, WtoolboxService as W, FormatGridViewValuePipe as X, GetSrcUploadPreviewPipe as Y, HtmlEditorComponent as Z, ImageWrapperComponent as _, DynamicCompilerService as a, loadHtmlEditorComponent as a$, KanbanListComponent as a0, KanbanOptions as a1, KanbanStatusColumnItem as a2, LazyChartListComponent as a3, LazyCodeEditorComponent as a4, LazyDataActionButtonComponent as a5, LazyDataSourceComponent as a6, LazyFieldEditorComponent as a7, LazyFirstRunWizardComponent as a8, LazyImageWrapperComponent as a9, ValueChangedPayload as aA, VisibleFieldListPipe as aB, WidgetDefinition as aC, WorkflowDesignerRouteComponent as aD, WorkflowRunnerComponent as aE, WuicAziendaSwitcherComponent as aF, WuicErrorDialogComponent as aG, WuicErrorInterceptor as aH, WuicFeatureDirective as aI, WuicFrozenColumnDirective as aJ, WuicRagChatbotComponent as aK, WuicRagChatbotFabComponent as aL, WuicRagService as aM, WuicRowTogglerDirective as aN, _PRIMENG_TABLE_DIRECTIVES_NAMES as aO, authExpiredInterceptor as aP, boundedRepeaterPendingChangesGuard as aQ, featureRouteCanActivateGuard as aR, featureRouteCanMatchGuard as aS, getThemePrimaryHex as aT, inferTemplateFieldFromBaseClass as aU, loadBooleanEditorComponent as aV, loadButtonEditorComponent as aW, loadCodeAreaEditorComponent as aX, loadColorEditorComponent as aY, loadDateEditorComponent as aZ, loadDictionaryEditorComponent as a_, LazyKanbanListComponent as aa, LazyMetaMenuComponent as ab, LazyPivotBuilderComponent as ac, LazyReportDesignerComponent as ad, LazyReportViewerComponent as ae, LazySchedulerListComponent as af, LazySpreadsheetListSfComponent as ag, ListGridComponent as ah, LookupEditorComponent as ai, MapListComponent as aj, MetaMenuComponent as ak, MetadatiTabella as al, NotificationBellComponent as am, NotificationRealtimeService as an, NumberEditorComponent as ao, PRIMARY_PALETTES as ap, PropertyArrayEditorComponent as aq, PropertyObjectEditorComponent as ar, THEME_OPTIONS as as, TextAreaEditorComponent as at, TextEditorComponent as au, TreeListComponent as av, TreeViewSelectorComponent as aw, UploadDialogComponent as ax, UploadEditorComponent as ay, ValidationRule as az, DataSourceComponent as b, loadLookupEditorComponent as b0, loadNumberEditorComponent as b1, loadPropertyArrayEditorComponent as b2, loadPropertyObjectEditorComponent as b3, loadTextAreaEditorComponent as b4, loadTextEditorComponent as b5, loadTreeViewSelectorComponent as b6, loadUploadEditorComponent as b7, menuRouteAccessCanActivateGuard as b8, menuRouteAccessCanMatchGuard as b9, routes as ba, wuicErrorInterceptor as bb, DataRepeaterComponent as c, ParametricDialogComponent as d, MetadatiColonna as e, DataProviderService as f, MetadataEditorService as g, WorkflowRuntimeMetadataService as h, FieldEditorComponent as i, MetadataEditorComponent as j, WuicClientException as k, WuicErrorCodes as l, MetaInfo as m, FilterInfo as n, LicenseFeatureService as o, FilterItem as p, getThemeOptions as q, AUTH_REDIRECT_REASON_KEY as r, AppSettingsEditorComponent as s, AuthSessionService as t, BoundedRepeaterComponent as u, ButtonEditorComponent as v, CallbackPipe as w, CallbackPipe2 as x, CarouselListComponent as y, CodeAreaEditorComponent as z };
64391
- //# sourceMappingURL=wuic-framework-lib-wuic-framework-lib-Deudd4MY.mjs.map
64444
+ export { ImageWrapperComponent as $, ArchetypeConfiguratorComponent as A, BooleanEditorComponent as B, ChatbotHostRegistryService as C, DynamicGenericTemplateComponent as D, CodeAreaEditorComponent as E, FilterBarComponent as F, GlobalHandler as G, ColorEditorComponent as H, ImportExportButtonComponent as I, CrashReporterService as J, CustomAttributesService as K, LazyParametricDialogComponent as L, MetadataProviderService as M, CustomException as N, DataActionButtonComponent as O, PagerComponent as P, DataProviderOdataService as Q, DateEditorComponent as R, DesignerRouteComponent as S, TranslationManagerService as T, UserInfoService as U, DictionaryEditorComponent as V, WtoolboxService as W, DynamicRowTemplateComponent as X, FormatGridViewValuePipe as Y, GetSrcUploadPreviewPipe as Z, HtmlEditorComponent as _, DynamicCompilerService as a, loadDictionaryEditorComponent as a$, IsSelectedRowPipe as a0, KanbanListComponent as a1, KanbanOptions as a2, KanbanStatusColumnItem as a3, LazyChartListComponent as a4, LazyCodeEditorComponent as a5, LazyDataActionButtonComponent as a6, LazyDataSourceComponent as a7, LazyFieldEditorComponent as a8, LazyFirstRunWizardComponent as a9, ValidationRule as aA, ValueChangedPayload as aB, VisibleFieldListPipe as aC, WidgetDefinition as aD, WorkflowDesignerRouteComponent as aE, WorkflowRunnerComponent as aF, WuicAziendaSwitcherComponent as aG, WuicErrorDialogComponent as aH, WuicErrorInterceptor as aI, WuicFeatureDirective as aJ, WuicFrozenColumnDirective as aK, WuicRagChatbotComponent as aL, WuicRagChatbotFabComponent as aM, WuicRagService as aN, WuicRowTogglerDirective as aO, _PRIMENG_TABLE_DIRECTIVES_NAMES as aP, authExpiredInterceptor as aQ, boundedRepeaterPendingChangesGuard as aR, featureRouteCanActivateGuard as aS, featureRouteCanMatchGuard as aT, getThemePrimaryHex as aU, inferTemplateFieldFromBaseClass as aV, loadBooleanEditorComponent as aW, loadButtonEditorComponent as aX, loadCodeAreaEditorComponent as aY, loadColorEditorComponent as aZ, loadDateEditorComponent as a_, LazyImageWrapperComponent as aa, LazyKanbanListComponent as ab, LazyMetaMenuComponent as ac, LazyPivotBuilderComponent as ad, LazyReportDesignerComponent as ae, LazyReportViewerComponent as af, LazySchedulerListComponent as ag, LazySpreadsheetListSfComponent as ah, ListGridComponent as ai, LookupEditorComponent as aj, MapListComponent as ak, MetaMenuComponent as al, MetadatiTabella as am, NotificationBellComponent as an, NotificationRealtimeService as ao, NumberEditorComponent as ap, PRIMARY_PALETTES as aq, PropertyArrayEditorComponent as ar, PropertyObjectEditorComponent as as, THEME_OPTIONS as at, TextAreaEditorComponent as au, TextEditorComponent as av, TreeListComponent as aw, TreeViewSelectorComponent as ax, UploadDialogComponent as ay, UploadEditorComponent as az, DataSourceComponent as b, loadHtmlEditorComponent as b0, loadLookupEditorComponent as b1, loadNumberEditorComponent as b2, loadPropertyArrayEditorComponent as b3, loadPropertyObjectEditorComponent as b4, loadTextAreaEditorComponent as b5, loadTextEditorComponent as b6, loadTreeViewSelectorComponent as b7, loadUploadEditorComponent as b8, menuRouteAccessCanActivateGuard as b9, menuRouteAccessCanMatchGuard as ba, routes as bb, wuicErrorInterceptor as bc, DataRepeaterComponent as c, ParametricDialogComponent as d, MetadatiColonna as e, DataProviderService as f, MetadataEditorService as g, WorkflowRuntimeMetadataService as h, FieldEditorComponent as i, MetadataEditorComponent as j, WuicClientException as k, WuicErrorCodes as l, MetaInfo as m, WUIC_SUPPRESS_GLOBAL_ERROR as n, FilterInfo as o, LicenseFeatureService as p, FilterItem as q, getThemeOptions as r, AUTH_REDIRECT_REASON_KEY as s, AppSettingsEditorComponent as t, AuthSessionService as u, BoundedRepeaterComponent as v, ButtonEditorComponent as w, CallbackPipe as x, CallbackPipe2 as y, CarouselListComponent as z };
64445
+ //# sourceMappingURL=wuic-framework-lib-wuic-framework-lib-BRujEwpR.mjs.map