ngx-t-forms 2.0.29 → 2.0.31
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.
- package/fesm2022/ngx-t-forms-auto-complete-input-element.component-DCKuXHAW.mjs +104 -0
- package/fesm2022/ngx-t-forms-auto-complete-input-element.component-DCKuXHAW.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-basic-input-input-element.component-Ce4ipSUc.mjs +85 -0
- package/fesm2022/ngx-t-forms-basic-input-input-element.component-Ce4ipSUc.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-calculated-field-rules.component-C5TPddVe.mjs +643 -0
- package/fesm2022/ngx-t-forms-calculated-field-rules.component-C5TPddVe.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-chip-options-creator-editor.component-CICQaqz6.mjs +97 -0
- package/fesm2022/ngx-t-forms-chip-options-creator-editor.component-CICQaqz6.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-config-mscoa-additional-inputs.component-CzisLSIP.mjs +195 -0
- package/fesm2022/ngx-t-forms-config-mscoa-additional-inputs.component-CzisLSIP.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-data-source-picker.component-Dzz_o6fJ.mjs +261 -0
- package/fesm2022/ngx-t-forms-data-source-picker.component-Dzz_o6fJ.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-date-picker-input-element.component-CYUbVyzP.mjs +85 -0
- package/fesm2022/ngx-t-forms-date-picker-input-element.component-CYUbVyzP.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-date-range-picker-input-element.component-CmoquQGV.mjs +156 -0
- package/fesm2022/ngx-t-forms-date-range-picker-input-element.component-CmoquQGV.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-document-list-label-config-editor.component-CLUOXreG.mjs +368 -0
- package/fesm2022/ngx-t-forms-document-list-label-config-editor.component-CLUOXreG.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-document-picker.component-qObjcqhE.mjs +704 -0
- package/fesm2022/ngx-t-forms-document-picker.component-qObjcqhE.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-editor-input-element.component-BLXlfb6F.mjs +294 -0
- package/fesm2022/ngx-t-forms-editor-input-element.component-BLXlfb6F.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-editor-js-input.component-BQL0AH7H.mjs +240 -0
- package/fesm2022/ngx-t-forms-editor-js-input.component-BQL0AH7H.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-file-upload-input-element.component-C7mMeEjF.mjs +205 -0
- package/fesm2022/ngx-t-forms-file-upload-input-element.component-C7mMeEjF.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-form-input-selector.component-C9u8zq9B.mjs +86 -0
- package/fesm2022/ngx-t-forms-form-input-selector.component-C9u8zq9B.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-form-json-view.component-856Hx1Bg.mjs +22 -0
- package/fesm2022/ngx-t-forms-form-json-view.component-856Hx1Bg.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-form-payload-projection.component-CDkTuX9S.mjs +179 -0
- package/fesm2022/ngx-t-forms-form-payload-projection.component-CDkTuX9S.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-form-section-stepper.component-Bs50-nEB.mjs +319 -0
- package/fesm2022/ngx-t-forms-form-section-stepper.component-Bs50-nEB.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-forms-builder-menu.component-qrhM0jGL.mjs +379 -0
- package/fesm2022/ngx-t-forms-forms-builder-menu.component-qrhM0jGL.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-geo-location.component-Bosp1UzR.mjs +124 -0
- package/fesm2022/ngx-t-forms-geo-location.component-Bosp1UzR.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-getInputIcon-B4ADgevZ.mjs +31 -0
- package/fesm2022/ngx-t-forms-getInputIcon-B4ADgevZ.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-image-capture-input-element.component-C1g7Z0cK.mjs +180 -0
- package/fesm2022/ngx-t-forms-image-capture-input-element.component-C1g7Z0cK.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-index-dDSobs6A.mjs +2 -0
- package/fesm2022/ngx-t-forms-index-dDSobs6A.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-input-custom.component-BkbHFAyR.mjs +105 -0
- package/fesm2022/ngx-t-forms-input-custom.component-BkbHFAyR.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-input-editor.component-BPUOM9kQ.mjs +181 -0
- package/fesm2022/ngx-t-forms-input-editor.component-BPUOM9kQ.mjs.map +1 -0
- package/fesm2022/{ngx-t-forms-map-mat-options-keys-CbdW82su.mjs → ngx-t-forms-map-mat-options-keys-B6hJ7Io5.mjs} +12 -14
- package/fesm2022/ngx-t-forms-map-mat-options-keys-B6hJ7Io5.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-mat-chip-list-editor.component-c7uZT1sr.mjs +66 -0
- package/fesm2022/ngx-t-forms-mat-chip-list-editor.component-c7uZT1sr.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-mat-slider-editor.component-CTSBrM-j.mjs +211 -0
- package/fesm2022/ngx-t-forms-mat-slider-editor.component-CTSBrM-j.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-mat-slider-toggle-editor.component-CcYiwx-8.mjs +165 -0
- package/fesm2022/ngx-t-forms-mat-slider-toggle-editor.component-CcYiwx-8.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-missing-form-configs.component-DrnH8qdG.mjs +38 -0
- package/fesm2022/ngx-t-forms-missing-form-configs.component-DrnH8qdG.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-mscoa-chart-toolbar.component-C_abEBQ5.mjs +38 -0
- package/fesm2022/ngx-t-forms-mscoa-chart-toolbar.component-C_abEBQ5.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-mscoa-error-display.component-99DpVSy7.mjs +126 -0
- package/fesm2022/ngx-t-forms-mscoa-error-display.component-99DpVSy7.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-mscoa-segment-config.component-C0qsMfsq.mjs +336 -0
- package/fesm2022/ngx-t-forms-mscoa-segment-config.component-C0qsMfsq.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-mscoa-temporary-hint.component-B1Z-IXSL.mjs +74 -0
- package/fesm2022/ngx-t-forms-mscoa-temporary-hint.component-B1Z-IXSL.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-multiple-input-input-element.component-C7y1OGPx.mjs +905 -0
- package/fesm2022/ngx-t-forms-multiple-input-input-element.component-C7y1OGPx.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-ngx-t-forms-u_kigDid.mjs +19461 -0
- package/fesm2022/ngx-t-forms-ngx-t-forms-u_kigDid.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-paginated-selection-table-AQZSMmhr.mjs +555 -0
- package/fesm2022/ngx-t-forms-paginated-selection-table-AQZSMmhr.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-pipeline-generator.component-DmNSc5aw.mjs +748 -0
- package/fesm2022/ngx-t-forms-pipeline-generator.component-DmNSc5aw.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-record-list-manager.component-CUMMvMch.mjs +358 -0
- package/fesm2022/ngx-t-forms-record-list-manager.component-CUMMvMch.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-required-inputs.component-Ch2yNcIS.mjs +272 -0
- package/fesm2022/ngx-t-forms-required-inputs.component-Ch2yNcIS.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-rest-api-call-setup.component-C_aFtdvW.mjs +398 -0
- package/fesm2022/ngx-t-forms-rest-api-call-setup.component-C_aFtdvW.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-search-field.component-B2ZO7lqO.mjs +38 -0
- package/fesm2022/ngx-t-forms-search-field.component-B2ZO7lqO.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-section-report.component-BxOhR6C0.mjs +98 -0
- package/fesm2022/ngx-t-forms-section-report.component-BxOhR6C0.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-select-input-element.component-DbgZdNoe.mjs +150 -0
- package/fesm2022/ngx-t-forms-select-input-element.component-DbgZdNoe.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-selection-options-editor.component-Dhln81DL.mjs +169 -0
- package/fesm2022/ngx-t-forms-selection-options-editor.component-Dhln81DL.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-t-workflow-picker.component-leBokXvM.mjs +204 -0
- package/fesm2022/ngx-t-forms-t-workflow-picker.component-leBokXvM.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-textarea-input-element.component-BEbXJjFA.mjs +95 -0
- package/fesm2022/ngx-t-forms-textarea-input-element.component-BEbXJjFA.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-toggle-input-element.component-DDErRUJd.mjs +82 -0
- package/fesm2022/ngx-t-forms-toggle-input-element.component-DDErRUJd.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-validators-config.component-oGjQVGE2.mjs +733 -0
- package/fesm2022/ngx-t-forms-validators-config.component-oGjQVGE2.mjs.map +1 -0
- package/fesm2022/ngx-t-forms-workflow-adjudication.component-CtU8dECN.mjs +1303 -0
- package/fesm2022/ngx-t-forms-workflow-adjudication.component-CtU8dECN.mjs.map +1 -0
- package/fesm2022/ngx-t-forms.mjs +2 -1
- package/fesm2022/ngx-t-forms.mjs.map +1 -1
- package/package.json +20 -18
- package/styles/_editor-mixins.scss +62 -0
- package/styles/_json-editor-syntax.scss +26 -0
- package/styles/_signature-pad.scss +26 -0
- package/styles/_tokens.scss +148 -0
- package/types/ngx-t-forms.d.ts +1767 -621
- package/fesm2022/ngx-t-forms-calculated-field-rules.component-D-SBMdYg.mjs +0 -313
- package/fesm2022/ngx-t-forms-calculated-field-rules.component-D-SBMdYg.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-chip-options-creator-editor.component-1cpszpPN.mjs +0 -191
- package/fesm2022/ngx-t-forms-chip-options-creator-editor.component-1cpszpPN.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-config-mscoa-additional-inputs.component-DFdAVWTg.mjs +0 -207
- package/fesm2022/ngx-t-forms-config-mscoa-additional-inputs.component-DFdAVWTg.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-data-source-picker.component-DxORinAD.mjs +0 -204
- package/fesm2022/ngx-t-forms-data-source-picker.component-DxORinAD.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-document-list-label-config-editor.component-DcWS1txl.mjs +0 -289
- package/fesm2022/ngx-t-forms-document-list-label-config-editor.component-DcWS1txl.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-form-input-selector.component-B2QEnvkq.mjs +0 -134
- package/fesm2022/ngx-t-forms-form-input-selector.component-B2QEnvkq.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-form-json-view.component-DePf44w6.mjs +0 -22
- package/fesm2022/ngx-t-forms-form-json-view.component-DePf44w6.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-form-section-stepper.component-BTkcSjg7.mjs +0 -270
- package/fesm2022/ngx-t-forms-form-section-stepper.component-BTkcSjg7.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-forms-builder-menu.component-Wamzf_sq.mjs +0 -345
- package/fesm2022/ngx-t-forms-forms-builder-menu.component-Wamzf_sq.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-input-editor.component-D4xHO76K.mjs +0 -147
- package/fesm2022/ngx-t-forms-input-editor.component-D4xHO76K.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-map-mat-options-keys-CbdW82su.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-mat-chip-list-editor.component-DmTyO9Wi.mjs +0 -105
- package/fesm2022/ngx-t-forms-mat-chip-list-editor.component-DmTyO9Wi.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-mat-slider-editor.component-DZ4TenrI.mjs +0 -109
- package/fesm2022/ngx-t-forms-mat-slider-editor.component-DZ4TenrI.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-mat-slider-toggle-editor.component-DPyBYE4p.mjs +0 -155
- package/fesm2022/ngx-t-forms-mat-slider-toggle-editor.component-DPyBYE4p.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-missing-form-configs.component-BRmnwAK6.mjs +0 -28
- package/fesm2022/ngx-t-forms-missing-form-configs.component-BRmnwAK6.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-mscoa-chart-toolbar.component-D_umeAPL.mjs +0 -43
- package/fesm2022/ngx-t-forms-mscoa-chart-toolbar.component-D_umeAPL.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-mscoa-error-display.component-CSX2NCNU.mjs +0 -116
- package/fesm2022/ngx-t-forms-mscoa-error-display.component-CSX2NCNU.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-mscoa-segment-config.component-B6IF8kGg.mjs +0 -296
- package/fesm2022/ngx-t-forms-mscoa-segment-config.component-B6IF8kGg.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-mscoa-temporary-hint.component-BPkjsRmH.mjs +0 -83
- package/fesm2022/ngx-t-forms-mscoa-temporary-hint.component-BPkjsRmH.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-ngx-t-forms-D9qmig6g.mjs +0 -16844
- package/fesm2022/ngx-t-forms-ngx-t-forms-D9qmig6g.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-pipeline-generator.component-DBJEyCbd.mjs +0 -613
- package/fesm2022/ngx-t-forms-pipeline-generator.component-DBJEyCbd.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-record-list-manager.component-Dgs9lNSr.mjs +0 -269
- package/fesm2022/ngx-t-forms-record-list-manager.component-Dgs9lNSr.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-required-inputs.component-CSIJvSHq.mjs +0 -190
- package/fesm2022/ngx-t-forms-required-inputs.component-CSIJvSHq.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-rest-api-call-setup.component-CY-JSkGs.mjs +0 -291
- package/fesm2022/ngx-t-forms-rest-api-call-setup.component-CY-JSkGs.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-section-report.component-12-KdKT6.mjs +0 -156
- package/fesm2022/ngx-t-forms-section-report.component-12-KdKT6.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-selection-options-editor.component-Be3QAG_L.mjs +0 -186
- package/fesm2022/ngx-t-forms-selection-options-editor.component-Be3QAG_L.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-t-workflow-picker.component-a4f1r8gH.mjs +0 -187
- package/fesm2022/ngx-t-forms-t-workflow-picker.component-a4f1r8gH.mjs.map +0 -1
- package/fesm2022/ngx-t-forms-validators-config.component-B3j9Dmgu.mjs +0 -215
- package/fesm2022/ngx-t-forms-validators-config.component-B3j9Dmgu.mjs.map +0 -1
|
@@ -0,0 +1,748 @@
|
|
|
1
|
+
import { isPlatformBrowser, JsonPipe } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { inject, PLATFORM_ID, Injectable, input, output, signal, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, DestroyRef, viewChild, Input, Optional, Self } from '@angular/core';
|
|
4
|
+
import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
5
|
+
import * as i1$1 from '@angular/forms';
|
|
6
|
+
import { FormsModule } from '@angular/forms';
|
|
7
|
+
import * as i2 from '@angular/material/form-field';
|
|
8
|
+
import { MatFormFieldModule, MatFormFieldControl } from '@angular/material/form-field';
|
|
9
|
+
import { BehaviorSubject, switchMap, map, take, catchError, finalize, throwError, tap, combineLatest, filter, Subject, distinctUntilChanged } from 'rxjs';
|
|
10
|
+
import * as i2$1 from '@angular/cdk/overlay';
|
|
11
|
+
import { OverlayModule } from '@angular/cdk/overlay';
|
|
12
|
+
import * as i1 from '@angular/material/button';
|
|
13
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
14
|
+
import { MatDividerModule } from '@angular/material/divider';
|
|
15
|
+
import * as i5$1 from '@angular/material/expansion';
|
|
16
|
+
import { MatExpansionModule } from '@angular/material/expansion';
|
|
17
|
+
import * as i3$1 from '@angular/material/icon';
|
|
18
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
19
|
+
import * as i4 from '@angular/material/list';
|
|
20
|
+
import { MatListModule } from '@angular/material/list';
|
|
21
|
+
import * as i3$2 from '@angular/material/progress-spinner';
|
|
22
|
+
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
|
23
|
+
import * as i5$2 from '@angular/material/toolbar';
|
|
24
|
+
import { MatToolbarModule } from '@angular/material/toolbar';
|
|
25
|
+
import { J as JsonEditorComponent, _ as _isEqual, b as TDynamicDataViewComponent, E as EmptyStateComponent, D as DaysAgoPipe } from './ngx-t-forms-ngx-t-forms-u_kigDid.mjs';
|
|
26
|
+
import { TWorkflowPickerComponent } from './ngx-t-forms-t-workflow-picker.component-leBokXvM.mjs';
|
|
27
|
+
import { HttpClient } from '@angular/common/http';
|
|
28
|
+
import PipelineBuilder from 'db-aggregation-pipeline-builder';
|
|
29
|
+
import * as i3 from '@angular/material/card';
|
|
30
|
+
import { MatCardModule } from '@angular/material/card';
|
|
31
|
+
import * as i8 from '@angular/material/select';
|
|
32
|
+
import { MatSelectModule } from '@angular/material/select';
|
|
33
|
+
import * as i5 from '@angular/material/tooltip';
|
|
34
|
+
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @deprecated
|
|
38
|
+
*
|
|
39
|
+
* Phase 2 layering: prefer `inject(PIPELINE_REPOSITORY)` and wire the
|
|
40
|
+
* implementation via `withHttpPipeline()` (or your own `useClass` binding
|
|
41
|
+
* against `PIPELINE_REPOSITORY`). This service is preserved as-is for I-1
|
|
42
|
+
* parity — its self-contained `inject(HttpClient)` + UI-state surface
|
|
43
|
+
* continues to work for current callers and does NOT require the consumer to
|
|
44
|
+
* call `withHttpPipeline()`. Target removal: v3.0.0.
|
|
45
|
+
*
|
|
46
|
+
* Note for new code: the HTTP boundary lives on {@link PipelineRepository}
|
|
47
|
+
* (`runPipeline` / `generatePipeline` / `getSchema`). The state-management
|
|
48
|
+
* surface here (`pipeline$`, `chatHistory$`, etc.) is internal to the legacy
|
|
49
|
+
* pipeline-generator UI and is not part of the repository contract.
|
|
50
|
+
*/
|
|
51
|
+
class PipelineService {
|
|
52
|
+
#platformId;
|
|
53
|
+
constructor() {
|
|
54
|
+
//LOADING INDICATORS
|
|
55
|
+
this.isRunningPipeline = false;
|
|
56
|
+
this.aiIsGeneratingPipeline = false;
|
|
57
|
+
this.isLoadingAllowedStages = false;
|
|
58
|
+
this.isLoadingWorkflowSchema = false;
|
|
59
|
+
//States
|
|
60
|
+
this.workflowId$ = new BehaviorSubject(undefined);
|
|
61
|
+
this.activeStage$ = new BehaviorSubject(undefined);
|
|
62
|
+
this.prompt$ = new BehaviorSubject(undefined);
|
|
63
|
+
this.schemaLoadError$ = new BehaviorSubject(undefined);
|
|
64
|
+
this.pipeline$ = new BehaviorSubject([]);
|
|
65
|
+
this.threadId$ = new BehaviorSubject(undefined);
|
|
66
|
+
this.schema$ = new BehaviorSubject(undefined);
|
|
67
|
+
this.allowedStages$ = new BehaviorSubject([]);
|
|
68
|
+
this.pipeLineResult$ = new BehaviorSubject([]);
|
|
69
|
+
this.chatHistory$ = new BehaviorSubject([]);
|
|
70
|
+
this.http = inject(HttpClient);
|
|
71
|
+
this.#platformId = inject(PLATFORM_ID);
|
|
72
|
+
//Set local storage threadId
|
|
73
|
+
if (!isPlatformBrowser(this.#platformId))
|
|
74
|
+
return;
|
|
75
|
+
const threadId = localStorage.getItem('threadId');
|
|
76
|
+
if (threadId) {
|
|
77
|
+
this.threadId$.next(threadId);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
get config$() {
|
|
81
|
+
return this.workflowId$.pipe(switchMap(workflowId => this.pipeline$.pipe(switchMap(pipeline => this.pipeLineResult$.pipe(map(responseBody => ({ workflowId, pipeline, responseBody })))))));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Fetch available pipeline stages for the user to select and generate pipelines.
|
|
85
|
+
* Ideally, this could be static or fetched from a backend.
|
|
86
|
+
*/
|
|
87
|
+
runPipeLine(pipeline, workflowId) {
|
|
88
|
+
const url = "http://localhost:5000/api/v1/pipelineBuilder/runPipeline";
|
|
89
|
+
// For now, returning a static list of pipeline stages. In a real application, you can fetch from an API.
|
|
90
|
+
this.isRunningPipeline = true;
|
|
91
|
+
return this.http.post(url, { pipeline, workflowId }).pipe(take(1), catchError(this.handleError), // Error handling for the HTTP request,
|
|
92
|
+
finalize(() => this.isRunningPipeline = false));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Generate a MongoDB pipeline based on the given user prompt.
|
|
96
|
+
*
|
|
97
|
+
* @param prompt The user's input or description of the desired pipeline.
|
|
98
|
+
*/
|
|
99
|
+
generatePipeline(query, schema, threadId, existingPipeline, previousError) {
|
|
100
|
+
if (!schema) {
|
|
101
|
+
throw new Error("Schema is required");
|
|
102
|
+
}
|
|
103
|
+
const payload = { query, schema, threadId, existingPipeline, previousError };
|
|
104
|
+
const url = "http://localhost:5000/api/v1/pipelineBuilder/generatePipeline";
|
|
105
|
+
this.aiIsGeneratingPipeline = true;
|
|
106
|
+
return this.http.post(`${url}`, payload)
|
|
107
|
+
.pipe(take(1), catchError(this.handleError), // Error handling for the HTTP request,
|
|
108
|
+
finalize(() => this.aiIsGeneratingPipeline = false));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Handle any errors from the HTTP request.
|
|
112
|
+
*
|
|
113
|
+
* @param error The error response from the HTTP request
|
|
114
|
+
*/
|
|
115
|
+
handleError(error) {
|
|
116
|
+
// Return an observable with a user-facing error message
|
|
117
|
+
void error;
|
|
118
|
+
return throwError(() => new Error('Something went wrong with the pipeline generation; please try again later.'));
|
|
119
|
+
}
|
|
120
|
+
getSchema(workflowId) {
|
|
121
|
+
if (!workflowId) {
|
|
122
|
+
throw new Error("WorkflowId is required");
|
|
123
|
+
}
|
|
124
|
+
const url = "http://localhost:5000/api/v1/pipelineBuilder/getSchema";
|
|
125
|
+
return this.http.get(`${url}`, { params: { workflowId: workflowId } }).pipe(take(1), catchError(this.handleError) // Error handling for the HTTP request
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
/** OPERATIONS */
|
|
129
|
+
isSchemaEmpty(object) {
|
|
130
|
+
}
|
|
131
|
+
workflowChanged(event) {
|
|
132
|
+
this.workflowId$.next(event ?? undefined);
|
|
133
|
+
if (!event) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
this.getSchema(event).pipe(take(1), tap(({ schema }) => {
|
|
137
|
+
this.schema$.next(schema);
|
|
138
|
+
const isEmptySchema = Object.keys(schema)[0] === "" && Object.values(schema)[0] === "Array<>";
|
|
139
|
+
const error = isEmptySchema
|
|
140
|
+
? "Document schema is missing. Please ensure at least one document exists for the selected workflow."
|
|
141
|
+
: undefined;
|
|
142
|
+
this.schemaLoadError$.next(error);
|
|
143
|
+
this.runActivePipeline();
|
|
144
|
+
}), catchError((error) => {
|
|
145
|
+
this.schemaLoadError$.next("Error loading schema");
|
|
146
|
+
return this.handleError(error);
|
|
147
|
+
})).subscribe();
|
|
148
|
+
}
|
|
149
|
+
setActiveStage(i) {
|
|
150
|
+
const index = this.activeStage$.getValue();
|
|
151
|
+
if (index === i) {
|
|
152
|
+
this.activeStage$.next(undefined);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
this.activeStage$.next(i);
|
|
156
|
+
}
|
|
157
|
+
addStage() {
|
|
158
|
+
// NOTE(phase-4): an empty object `{}` is not a valid `PipelineStage`; it acts as a sentinel
|
|
159
|
+
// "blank slot" until the user picks a stage type. Tracked — proper fix is a discriminated
|
|
160
|
+
// union `PipelineStage | { kind: 'blank' }` for the in-progress pipeline.
|
|
161
|
+
const blankSlot = {};
|
|
162
|
+
const newPipeline = this.pipeline$.getValue().concat(blankSlot);
|
|
163
|
+
this.pipeline$.next(newPipeline);
|
|
164
|
+
}
|
|
165
|
+
onTextChange(event) {
|
|
166
|
+
this.prompt$.next(event);
|
|
167
|
+
}
|
|
168
|
+
promptSubmit() {
|
|
169
|
+
// Use combineLatest to fetch the latest values from multiple sources
|
|
170
|
+
combineLatest([this.schema$, this.threadId$, this.pipeline$, this.prompt$, this.chatHistory$])
|
|
171
|
+
.pipe(take(1), tap(() => { }), filter(([schema, , , prompt]) => !!schema && !!prompt), switchMap(([schema, threadId, pipeline, prompt, history]) => {
|
|
172
|
+
// Use a shallow copy of the pipeline
|
|
173
|
+
const previousError = history[history.length - 1]?.assistance?.error ?? '';
|
|
174
|
+
const pipelineCopy = pipeline.map(stage => JSON.parse(JSON.stringify(stage))) || [];
|
|
175
|
+
// `prompt` is filtered to truthy above; assert via narrowing rather than `!`.
|
|
176
|
+
const safePrompt = prompt ?? '';
|
|
177
|
+
return this.generatePipeline(safePrompt, schema, threadId, pipelineCopy, previousError).pipe(map((response) => ({ ...response, history, prompt: safePrompt })));
|
|
178
|
+
}), tap((response) => {
|
|
179
|
+
const { pipeline, threadId, history, prompt } = response;
|
|
180
|
+
const historyRecord = {
|
|
181
|
+
user: prompt,
|
|
182
|
+
assistance: response,
|
|
183
|
+
date: new Date()
|
|
184
|
+
};
|
|
185
|
+
// Use next with a concatenated value for chat history
|
|
186
|
+
this.chatHistory$.next([...(history || []), historyRecord]);
|
|
187
|
+
this.pipeline$.next(pipeline);
|
|
188
|
+
this.threadId$.next(threadId);
|
|
189
|
+
// Store threadID locally
|
|
190
|
+
if (isPlatformBrowser(this.#platformId)) {
|
|
191
|
+
localStorage.setItem('threadId', threadId);
|
|
192
|
+
}
|
|
193
|
+
// Run the active pipeline after all state updates are complete
|
|
194
|
+
this.runActivePipeline();
|
|
195
|
+
}), catchError((error) => {
|
|
196
|
+
return this.handleError(error);
|
|
197
|
+
})).subscribe();
|
|
198
|
+
}
|
|
199
|
+
runActivePipeline() {
|
|
200
|
+
const pipeline = [...(this.pipeline$.getValue().map(stage => (JSON.parse(JSON.stringify(stage)))) || [])];
|
|
201
|
+
const someAreNotValid = pipeline.some(stage => Object.keys(stage).length === 0);
|
|
202
|
+
if (pipeline.length === 0 || someAreNotValid) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
PipelineBuilder.validatePipeline(pipeline);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const workflowId = this.workflowId$.getValue();
|
|
212
|
+
if (!workflowId) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
this.runPipeLine(pipeline, workflowId).pipe(take(1), tap(({ results }) => {
|
|
216
|
+
this.pipeLineResult$.next(results);
|
|
217
|
+
}), catchError((error) => {
|
|
218
|
+
const err = JSON.parse(JSON.stringify(error));
|
|
219
|
+
const errorMessage = err.error;
|
|
220
|
+
if (errorMessage) {
|
|
221
|
+
this.pipelineErrorLogAndRetry(errorMessage);
|
|
222
|
+
}
|
|
223
|
+
return this.handleError(error);
|
|
224
|
+
})).subscribe();
|
|
225
|
+
}
|
|
226
|
+
pipelineErrorLogAndRetry(message) {
|
|
227
|
+
const history = this.chatHistory$.getValue();
|
|
228
|
+
const prompt = this.prompt$.getValue();
|
|
229
|
+
// NOTE(phase-4): the chatHistory `assistance` field is typed as a full
|
|
230
|
+
// `IPipelineGenerationResponse`, but the error path only carries `message`.
|
|
231
|
+
// Treat the partial record as a structural fill until the type is narrowed via
|
|
232
|
+
// a discriminated union `{ kind: 'ok', response } | { kind: 'error', message }`.
|
|
233
|
+
const partialAssistance = { message };
|
|
234
|
+
const historyRecord = {
|
|
235
|
+
user: prompt ?? '',
|
|
236
|
+
assistance: partialAssistance,
|
|
237
|
+
date: new Date()
|
|
238
|
+
};
|
|
239
|
+
this.chatHistory$.next([...(history || []), historyRecord]);
|
|
240
|
+
}
|
|
241
|
+
get activeStage() {
|
|
242
|
+
return this.pipeline$.asObservable().pipe(switchMap(pipeline => this.activeStage$.pipe(map(activeIndex => {
|
|
243
|
+
if (activeIndex === undefined)
|
|
244
|
+
return undefined;
|
|
245
|
+
// Each `PipelineStage` in the union has exactly one operator key (e.g. `$match`).
|
|
246
|
+
// Read it through a typed `Record` lookup rather than casting the whole row.
|
|
247
|
+
const stage = pipeline[activeIndex];
|
|
248
|
+
const stageRecord = (stage ?? {});
|
|
249
|
+
const name = Object.keys(stageRecord)[0] ?? '';
|
|
250
|
+
return {
|
|
251
|
+
name,
|
|
252
|
+
config: stageRecord[name],
|
|
253
|
+
};
|
|
254
|
+
}))));
|
|
255
|
+
}
|
|
256
|
+
saveStage(index, stage) {
|
|
257
|
+
const pipeline = this.pipeline$.getValue();
|
|
258
|
+
if (index === undefined) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const newPipeline = pipeline.map((s, i) => {
|
|
262
|
+
if (i === index && stage) {
|
|
263
|
+
// NOTE(phase-4): building a `PipelineStage` from `{ [stage.stage]: stage.config }` is
|
|
264
|
+
// correct at runtime — the union member is keyed by the operator — but TS cannot prove
|
|
265
|
+
// the dynamic key matches a specific variant. Tracked for a per-stage builder fn.
|
|
266
|
+
const built = { [stage.stage]: stage.config };
|
|
267
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO(phase-4): per-stage builder fn would let TS narrow; internal-only service.
|
|
268
|
+
return built;
|
|
269
|
+
}
|
|
270
|
+
return s;
|
|
271
|
+
});
|
|
272
|
+
this.pipeline$.next(newPipeline);
|
|
273
|
+
this.activeStage$.next(undefined);
|
|
274
|
+
this.runActivePipeline();
|
|
275
|
+
}
|
|
276
|
+
deleteStage(index) {
|
|
277
|
+
const pipeline = this.pipeline$.getValue();
|
|
278
|
+
if (index === undefined) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const newPipeline = pipeline.filter((_s, i) => i !== index);
|
|
282
|
+
this.pipeline$.next(newPipeline);
|
|
283
|
+
this.activeStage$.next(undefined);
|
|
284
|
+
this.runActivePipeline();
|
|
285
|
+
}
|
|
286
|
+
get pipelineGeneralValid() {
|
|
287
|
+
return this.pipeline$.asObservable().pipe(map(pipeline => {
|
|
288
|
+
const allHaveTypes = pipeline.every(stage => Object.keys(stage).length === 1);
|
|
289
|
+
if (!allHaveTypes) {
|
|
290
|
+
return {
|
|
291
|
+
valid: false,
|
|
292
|
+
message: "All stages must be a valid pipeline stage"
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
const AllStagesMustHaveConfig = pipeline.every((stage) => {
|
|
296
|
+
// Each PipelineStage union member has one operator key + value; reading via
|
|
297
|
+
// `Object.entries` keeps us off an `as unknown as Record<…>` cast.
|
|
298
|
+
const [firstEntry] = Object.entries(stage);
|
|
299
|
+
return firstEntry !== undefined && firstEntry[1] !== undefined;
|
|
300
|
+
});
|
|
301
|
+
if (!AllStagesMustHaveConfig) {
|
|
302
|
+
return {
|
|
303
|
+
valid: false,
|
|
304
|
+
message: "All stages must have a configuration"
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
valid: true
|
|
309
|
+
};
|
|
310
|
+
}));
|
|
311
|
+
}
|
|
312
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: PipelineService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
313
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: PipelineService, providedIn: 'root' }); }
|
|
314
|
+
}
|
|
315
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: PipelineService, decorators: [{
|
|
316
|
+
type: Injectable,
|
|
317
|
+
args: [{
|
|
318
|
+
providedIn: 'root'
|
|
319
|
+
}]
|
|
320
|
+
}], ctorParameters: () => [] });
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Embedded aggregate-pipeline stage editor used inside `lib-pipeline-generator`.
|
|
324
|
+
* Lets the user pick a `$stage` operator and edit its JSON configuration (via
|
|
325
|
+
* `lib-json-editor`), validating the config against the pipeline builder.
|
|
326
|
+
*
|
|
327
|
+
* Not part of the public API.
|
|
328
|
+
*
|
|
329
|
+
* @example
|
|
330
|
+
* <lib-aggregate-stage-editor
|
|
331
|
+
* [selectedStage]="stage"
|
|
332
|
+
* [isRunningPipeline]="running"
|
|
333
|
+
* (saveStage)="onSave($event)"
|
|
334
|
+
* (deleteStage)="onDelete()" />
|
|
335
|
+
*/
|
|
336
|
+
class AggregateStageEditorComponent {
|
|
337
|
+
constructor() {
|
|
338
|
+
/** Stage currently being edited (one row of the pipeline generator state). */
|
|
339
|
+
this.selectedStage = input(undefined, ...(ngDevMode ? [{ debugName: "selectedStage" }] : /* istanbul ignore next */ []));
|
|
340
|
+
/** When the parent pipeline is executing, destructive/save actions are disabled. */
|
|
341
|
+
this.isRunningPipeline = input(false, ...(ngDevMode ? [{ debugName: "isRunningPipeline" }] : /* istanbul ignore next */ []));
|
|
342
|
+
/** Emits the prepared stage when the user saves a valid configuration. */
|
|
343
|
+
this.saveStage = output();
|
|
344
|
+
/** Emits when the user requests the active stage be removed. */
|
|
345
|
+
this.deleteStage = output();
|
|
346
|
+
/** Stages the user can choose from (loaded once from the pipeline builder). */
|
|
347
|
+
this.allowedStages = PipelineBuilder.getViewOnlyStages();
|
|
348
|
+
/** Working draft of the stage being edited; `undefined` until the user edits. */
|
|
349
|
+
this.#draft = signal(undefined, ...(ngDevMode ? [{ debugName: "#draft" }] : /* istanbul ignore next */ []));
|
|
350
|
+
/** Validation message from the last config check, or `undefined` when valid. */
|
|
351
|
+
this.configValidationError = signal(undefined, ...(ngDevMode ? [{ debugName: "configValidationError" }] : /* istanbul ignore next */ []));
|
|
352
|
+
/** Resolved operator key for the select control (draft first, else the input). */
|
|
353
|
+
this.currentStage = computed(() => this.#draft()?.stage ?? this.selectedStage()?.name, ...(ngDevMode ? [{ debugName: "currentStage" }] : /* istanbul ignore next */ []));
|
|
354
|
+
/** Config value passed to the JSON editor (draft first, else the input). */
|
|
355
|
+
this.currentConfig = computed(() => {
|
|
356
|
+
const config = this.#draft()?.config ?? this.selectedStage()?.config;
|
|
357
|
+
return this.#asObject(config);
|
|
358
|
+
}, ...(ngDevMode ? [{ debugName: "currentConfig" }] : /* istanbul ignore next */ []));
|
|
359
|
+
/** Whether the editing surface (operator picker is set or config present) shows. */
|
|
360
|
+
this.showConfig = computed(() => this.#draft() !== undefined || this.selectedStage()?.config !== undefined, ...(ngDevMode ? [{ debugName: "showConfig" }] : /* istanbul ignore next */ []));
|
|
361
|
+
/** Human description for the currently selected operator, if any. */
|
|
362
|
+
this.activeDescription = computed(() => this.allowedStages.find((s) => s.stage === this.currentStage())?.description, ...(ngDevMode ? [{ debugName: "activeDescription" }] : /* istanbul ignore next */ []));
|
|
363
|
+
/** Whether the Save action is enabled: a draft exists, is valid and error-free. */
|
|
364
|
+
this.canSave = computed(() => {
|
|
365
|
+
const draft = this.#draft();
|
|
366
|
+
return draft !== undefined && draft.valid === true && this.configValidationError() === undefined;
|
|
367
|
+
}, ...(ngDevMode ? [{ debugName: "canSave" }] : /* istanbul ignore next */ []));
|
|
368
|
+
}
|
|
369
|
+
/** Working draft of the stage being edited; `undefined` until the user edits. */
|
|
370
|
+
#draft;
|
|
371
|
+
handleStageChange(event) {
|
|
372
|
+
const stage = event.value;
|
|
373
|
+
// BEHAVIOUR FIX: the previous implementation spread the source row AFTER
|
|
374
|
+
// setting `stage`, which silently reverted the operator to its prior value.
|
|
375
|
+
// Here the newly picked operator always wins; the existing config carries over.
|
|
376
|
+
const config = this.#draft()?.config ?? this.selectedStage()?.config;
|
|
377
|
+
this.#draft.set({ stage, config, valid: false });
|
|
378
|
+
this.#validate();
|
|
379
|
+
}
|
|
380
|
+
handleConfigChange(change) {
|
|
381
|
+
const stage = this.#draft()?.stage ?? this.#stageFromRow();
|
|
382
|
+
this.#draft.set({ stage, config: change.value, valid: false });
|
|
383
|
+
this.#validate();
|
|
384
|
+
}
|
|
385
|
+
saveConfiguration() {
|
|
386
|
+
const draft = this.#draft();
|
|
387
|
+
if (this.canSave() && draft?.stage) {
|
|
388
|
+
this.saveStage.emit({
|
|
389
|
+
stage: draft.stage,
|
|
390
|
+
description: this.activeDescription() ?? '',
|
|
391
|
+
config: draft.config,
|
|
392
|
+
valid: draft.valid,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
deleteActive() {
|
|
397
|
+
this.deleteStage.emit();
|
|
398
|
+
}
|
|
399
|
+
trackByStage(stage) {
|
|
400
|
+
return stage.stage;
|
|
401
|
+
}
|
|
402
|
+
/** Validates the working draft against the pipeline builder, updating state. */
|
|
403
|
+
#validate() {
|
|
404
|
+
this.configValidationError.set(undefined);
|
|
405
|
+
const draft = this.#draft();
|
|
406
|
+
if (!draft)
|
|
407
|
+
return;
|
|
408
|
+
const { config, stage } = draft;
|
|
409
|
+
if ((config === undefined || config === null) || !stage) {
|
|
410
|
+
this.#draft.set({ ...draft, valid: false });
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
const valid = PipelineBuilder.validateStageConfig(stage, config);
|
|
415
|
+
this.#draft.set({ ...draft, valid });
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
const message = error instanceof Error ? error.message : 'An unknown error occurred';
|
|
419
|
+
this.configValidationError.set(message);
|
|
420
|
+
this.#draft.set({ ...draft, valid: false });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/** Reads the operator key from the input row as a typed enum, if recognised. */
|
|
424
|
+
#stageFromRow() {
|
|
425
|
+
const name = this.selectedStage()?.name;
|
|
426
|
+
return this.allowedStages.find((s) => s.stage === name)?.stage;
|
|
427
|
+
}
|
|
428
|
+
/** Narrows an opaque config value to the `object | undefined` the JSON editor accepts. */
|
|
429
|
+
#asObject(value) {
|
|
430
|
+
return value !== null && typeof value === 'object' ? value : undefined;
|
|
431
|
+
}
|
|
432
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AggregateStageEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
433
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: AggregateStageEditorComponent, isStandalone: true, selector: "lib-aggregate-stage-editor", inputs: { selectedStage: { classPropertyName: "selectedStage", publicName: "selectedStage", isSignal: true, isRequired: false, transformFunction: null }, isRunningPipeline: { classPropertyName: "isRunningPipeline", publicName: "isRunningPipeline", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saveStage: "saveStage", deleteStage: "deleteStage" }, host: { classAttribute: "lib-aggregate-stage-editor" }, ngImport: i0, template: "<mat-card class=\"lib-aggregate-stage-editor__card\" appearance=\"outlined\">\n <mat-card-content>\n <p class=\"lib-aggregate-stage-editor__title\">Aggregate pipeline stage configuration</p>\n\n <mat-form-field\n class=\"lib-aggregate-stage-editor__field\"\n appearance=\"outline\"\n subscriptSizing=\"dynamic\">\n <mat-label>Select aggregate stage</mat-label>\n <mat-select\n [value]=\"currentStage()\"\n (selectionChange)=\"handleStageChange($event)\"\n placeholder=\"Choose a stage operator\">\n @for (stage of allowedStages; track trackByStage(stage)) {\n <mat-option [value]=\"stage.stage\">{{ stage.stage }}</mat-option>\n }\n </mat-select>\n <mat-hint>\n {{ activeDescription()\n ? 'Hover the info icon to view stage details.'\n : 'Select an aggregation operator from the list.' }}\n </mat-hint>\n @if (activeDescription(); as description) {\n <mat-icon\n class=\"lib-aggregate-stage-editor__hint-icon\"\n matSuffix\n matTooltipHideDelay=\"1000\"\n [matTooltip]=\"description\">info</mat-icon>\n }\n </mat-form-field>\n\n @if (configValidationError(); as error) {\n <div class=\"lib-aggregate-stage-editor__warning\" role=\"alert\">\n <mat-icon aria-hidden=\"true\">warning</mat-icon>\n <span>{{ error }}</span>\n </div>\n }\n\n @if (showConfig()) {\n <div class=\"lib-aggregate-stage-editor__config\">\n <lib-json-editor [value]=\"currentConfig()\" (valueChange)=\"handleConfigChange($event)\" />\n </div>\n }\n </mat-card-content>\n\n <mat-divider />\n\n <mat-card-actions class=\"lib-aggregate-stage-editor__actions\" align=\"end\">\n <button\n mat-button\n type=\"button\"\n color=\"warn\"\n [disabled]=\"isRunningPipeline()\"\n (click)=\"deleteActive()\">\n Delete\n <mat-icon>delete</mat-icon>\n </button>\n <button\n mat-flat-button\n type=\"button\"\n color=\"primary\"\n [disabled]=\"isRunningPipeline() || !canSave()\"\n (click)=\"saveConfiguration()\">\n Save\n <mat-icon>save</mat-icon>\n </button>\n </mat-card-actions>\n</mat-card>\n", styles: [":host{display:block}.lib-aggregate-stage-editor__card{margin-bottom:1rem}.lib-aggregate-stage-editor__title{margin:0 0 1.5rem;font-size:.625rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--lib-forms-on-surface-variant)}.lib-aggregate-stage-editor__field{width:100%}.lib-aggregate-stage-editor__hint-icon{cursor:help;color:var(--lib-forms-on-surface-variant)}.lib-aggregate-stage-editor__warning{display:flex;align-items:flex-start;gap:.5rem;padding:.75rem;margin:1rem 0;border-radius:var(--lib-forms-radius-sm, 8px);background:var(--sem-warning-surface);color:var(--lib-forms-on-surface)}.lib-aggregate-stage-editor__warning mat-icon{flex:0 0 auto;color:var(--sem-warning, var(--lib-forms-on-surface-variant))}.lib-aggregate-stage-editor__config{margin-top:1.5rem}.lib-aggregate-stage-editor__actions{display:flex;gap:.5rem}\n"], dependencies: [{ kind: "component", type: JsonEditorComponent, selector: "lib-json-editor", inputs: ["value", "height", "disabled"], outputs: ["valueChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i3.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i3.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i3.MatCardContent, selector: "mat-card-content" }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i4.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
434
|
+
}
|
|
435
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AggregateStageEditorComponent, decorators: [{
|
|
436
|
+
type: Component,
|
|
437
|
+
args: [{ selector: 'lib-aggregate-stage-editor', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, host: { 'class': 'lib-aggregate-stage-editor' }, imports: [
|
|
438
|
+
JsonEditorComponent,
|
|
439
|
+
MatButtonModule,
|
|
440
|
+
MatCardModule,
|
|
441
|
+
MatDividerModule,
|
|
442
|
+
MatIconModule,
|
|
443
|
+
MatFormFieldModule,
|
|
444
|
+
MatSelectModule,
|
|
445
|
+
MatTooltipModule,
|
|
446
|
+
], template: "<mat-card class=\"lib-aggregate-stage-editor__card\" appearance=\"outlined\">\n <mat-card-content>\n <p class=\"lib-aggregate-stage-editor__title\">Aggregate pipeline stage configuration</p>\n\n <mat-form-field\n class=\"lib-aggregate-stage-editor__field\"\n appearance=\"outline\"\n subscriptSizing=\"dynamic\">\n <mat-label>Select aggregate stage</mat-label>\n <mat-select\n [value]=\"currentStage()\"\n (selectionChange)=\"handleStageChange($event)\"\n placeholder=\"Choose a stage operator\">\n @for (stage of allowedStages; track trackByStage(stage)) {\n <mat-option [value]=\"stage.stage\">{{ stage.stage }}</mat-option>\n }\n </mat-select>\n <mat-hint>\n {{ activeDescription()\n ? 'Hover the info icon to view stage details.'\n : 'Select an aggregation operator from the list.' }}\n </mat-hint>\n @if (activeDescription(); as description) {\n <mat-icon\n class=\"lib-aggregate-stage-editor__hint-icon\"\n matSuffix\n matTooltipHideDelay=\"1000\"\n [matTooltip]=\"description\">info</mat-icon>\n }\n </mat-form-field>\n\n @if (configValidationError(); as error) {\n <div class=\"lib-aggregate-stage-editor__warning\" role=\"alert\">\n <mat-icon aria-hidden=\"true\">warning</mat-icon>\n <span>{{ error }}</span>\n </div>\n }\n\n @if (showConfig()) {\n <div class=\"lib-aggregate-stage-editor__config\">\n <lib-json-editor [value]=\"currentConfig()\" (valueChange)=\"handleConfigChange($event)\" />\n </div>\n }\n </mat-card-content>\n\n <mat-divider />\n\n <mat-card-actions class=\"lib-aggregate-stage-editor__actions\" align=\"end\">\n <button\n mat-button\n type=\"button\"\n color=\"warn\"\n [disabled]=\"isRunningPipeline()\"\n (click)=\"deleteActive()\">\n Delete\n <mat-icon>delete</mat-icon>\n </button>\n <button\n mat-flat-button\n type=\"button\"\n color=\"primary\"\n [disabled]=\"isRunningPipeline() || !canSave()\"\n (click)=\"saveConfiguration()\">\n Save\n <mat-icon>save</mat-icon>\n </button>\n </mat-card-actions>\n</mat-card>\n", styles: [":host{display:block}.lib-aggregate-stage-editor__card{margin-bottom:1rem}.lib-aggregate-stage-editor__title{margin:0 0 1.5rem;font-size:.625rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--lib-forms-on-surface-variant)}.lib-aggregate-stage-editor__field{width:100%}.lib-aggregate-stage-editor__hint-icon{cursor:help;color:var(--lib-forms-on-surface-variant)}.lib-aggregate-stage-editor__warning{display:flex;align-items:flex-start;gap:.5rem;padding:.75rem;margin:1rem 0;border-radius:var(--lib-forms-radius-sm, 8px);background:var(--sem-warning-surface);color:var(--lib-forms-on-surface)}.lib-aggregate-stage-editor__warning mat-icon{flex:0 0 auto;color:var(--sem-warning, var(--lib-forms-on-surface-variant))}.lib-aggregate-stage-editor__config{margin-top:1.5rem}.lib-aggregate-stage-editor__actions{display:flex;gap:.5rem}\n"] }]
|
|
447
|
+
}], propDecorators: { selectedStage: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedStage", required: false }] }], isRunningPipeline: [{ type: i0.Input, args: [{ isSignal: true, alias: "isRunningPipeline", required: false }] }], saveStage: [{ type: i0.Output, args: ["saveStage"] }], deleteStage: [{ type: i0.Output, args: ["deleteStage"] }] } });
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Internal editor for `editType = MongoPipelineBuilder`. Implements
|
|
451
|
+
* `MatFormFieldControl` so it can be embedded inside a `mat-form-field`, and
|
|
452
|
+
* drives a MongoDB aggregation-pipeline builder (stage list, results preview,
|
|
453
|
+
* AI prompt) via a component-scoped {@link PipelineService}.
|
|
454
|
+
*
|
|
455
|
+
* Not part of the public API.
|
|
456
|
+
*
|
|
457
|
+
* @deprecated The MongoDB pipeline builder (`editType = MongoPipelineBuilder`)
|
|
458
|
+
* is deprecated and no longer offered as a selectable editor option (filtered
|
|
459
|
+
* out in `getSectionElements`). It is retained only to render already-configured
|
|
460
|
+
* (legacy) inputs and the `data-source-picker` MongoDB-pipeline data source.
|
|
461
|
+
* Do not wire it to any new editor option.
|
|
462
|
+
*/
|
|
463
|
+
class PipelineGeneratorComponent {
|
|
464
|
+
static { this.nextId = 0; }
|
|
465
|
+
#platformId;
|
|
466
|
+
#destroyRef;
|
|
467
|
+
#pipeline;
|
|
468
|
+
constructor(ngControl, _elementRef) {
|
|
469
|
+
this.ngControl = ngControl;
|
|
470
|
+
this._elementRef = _elementRef;
|
|
471
|
+
this.stateChanges = new Subject();
|
|
472
|
+
this.#platformId = inject(PLATFORM_ID);
|
|
473
|
+
this.#destroyRef = inject(DestroyRef);
|
|
474
|
+
this.#pipeline = inject(PipelineService);
|
|
475
|
+
// MFC requires a plain mutable `id` field (string) — cannot be a signal.
|
|
476
|
+
this.id = `app-pipeline-generator-${PipelineGeneratorComponent.nextId++}`;
|
|
477
|
+
this.placeholder = '';
|
|
478
|
+
this.focused = false;
|
|
479
|
+
this.required = false;
|
|
480
|
+
this.touched = false;
|
|
481
|
+
this.disabled = false;
|
|
482
|
+
this.controlType = 'lib-pipeline-generator';
|
|
483
|
+
this.#errors = [];
|
|
484
|
+
/** Emits the updated pipeline configuration whenever the builder state changes. */
|
|
485
|
+
this.valueChanged = output();
|
|
486
|
+
this.onTouched = () => { };
|
|
487
|
+
// ---- Reactive view state (signals over | async) -------------------------
|
|
488
|
+
this.workflowId = toSignal(this.#pipeline.workflowId$, { initialValue: undefined });
|
|
489
|
+
this.activeStageIndex = toSignal(this.#pipeline.activeStage$, { initialValue: undefined });
|
|
490
|
+
this.pipeLineResults = toSignal(this.#pipeline.pipeLineResult$, { initialValue: undefined });
|
|
491
|
+
this.prompt = toSignal(this.#pipeline.prompt$, { initialValue: undefined });
|
|
492
|
+
this.schemaLoadError = toSignal(this.#pipeline.schemaLoadError$, { initialValue: undefined });
|
|
493
|
+
this.chatHistory = toSignal(this.#pipeline.chatHistory$, { initialValue: [] });
|
|
494
|
+
/** Pipeline stages projected into the row shape the template + stage editor consume. */
|
|
495
|
+
this.stages = toSignal(this.#pipeline.pipeline$.pipe(map((stages) => stages.map((stage, index) => {
|
|
496
|
+
const [name, config] = Object.entries(stage ?? {}).at(0) ?? [undefined, undefined];
|
|
497
|
+
return {
|
|
498
|
+
id: index,
|
|
499
|
+
name,
|
|
500
|
+
stage: name,
|
|
501
|
+
config,
|
|
502
|
+
valid: name !== undefined && config !== undefined,
|
|
503
|
+
};
|
|
504
|
+
}))), { initialValue: [] });
|
|
505
|
+
/** Schema document properties filtered by the live suggestion search. */
|
|
506
|
+
this.schema = toSignal(this.#pipeline.schema$, { initialValue: undefined });
|
|
507
|
+
this.optionsSearch = signal('', ...(ngDevMode ? [{ debugName: "optionsSearch" }] : /* istanbul ignore next */ []));
|
|
508
|
+
this.schemaProperties = computed(() => {
|
|
509
|
+
const search = this.optionsSearch().toLowerCase();
|
|
510
|
+
return Object.keys(this.schema() ?? {})
|
|
511
|
+
.map((key) => ({ label: key, value: key }))
|
|
512
|
+
.filter((entry) => (search ? entry.label.toLowerCase().includes(search) : true));
|
|
513
|
+
}, ...(ngDevMode ? [{ debugName: "schemaProperties" }] : /* istanbul ignore next */ []));
|
|
514
|
+
this.showSuggestions = signal(false, ...(ngDevMode ? [{ debugName: "showSuggestions" }] : /* istanbul ignore next */ []));
|
|
515
|
+
this.textareaRef = viewChild('textarea', ...(ngDevMode ? [{ debugName: "textareaRef" }] : /* istanbul ignore next */ []));
|
|
516
|
+
// ---- Lifecycle: emit config changes -------------------------------------
|
|
517
|
+
this.#initiated = false;
|
|
518
|
+
// ---- AI prompt -----------------------------------------------------------
|
|
519
|
+
this.#cursorPosition = 0;
|
|
520
|
+
}
|
|
521
|
+
// ---- MatFormFieldControl value ------------------------------------------
|
|
522
|
+
#value;
|
|
523
|
+
// MFC value setter — kept as @Input() (framework wiring).
|
|
524
|
+
set value(value) {
|
|
525
|
+
const workflowIdChanged = this.#value?.workflowId !== value?.workflowId;
|
|
526
|
+
const pipelineChanged = !_isEqual(this.#value?.pipeline, value?.pipeline);
|
|
527
|
+
if (workflowIdChanged || pipelineChanged) {
|
|
528
|
+
this.#value = value;
|
|
529
|
+
if (value?.workflowId) {
|
|
530
|
+
this.workflowChanged(value.workflowId);
|
|
531
|
+
}
|
|
532
|
+
if (value?.pipeline) {
|
|
533
|
+
this.#pipeline.pipeline$.next(value.pipeline);
|
|
534
|
+
}
|
|
535
|
+
this.stateChanges.next();
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
get value() {
|
|
539
|
+
return this.#value;
|
|
540
|
+
}
|
|
541
|
+
// ---- Other public inputs / outputs --------------------------------------
|
|
542
|
+
#workflows$;
|
|
543
|
+
/** Source of workflow options forwarded to the embedded workflow picker. */
|
|
544
|
+
set getWorkflowOptions(options$) {
|
|
545
|
+
this.#workflows$ = options$;
|
|
546
|
+
}
|
|
547
|
+
get getWorkflowOptions() {
|
|
548
|
+
return this.#workflows$;
|
|
549
|
+
}
|
|
550
|
+
/** Validation errors surfaced by the parent for error-state derivation. */
|
|
551
|
+
set errors(value) {
|
|
552
|
+
this.#errors = value;
|
|
553
|
+
}
|
|
554
|
+
get errors() {
|
|
555
|
+
return this.#errors;
|
|
556
|
+
}
|
|
557
|
+
#errors;
|
|
558
|
+
// ---- MatFormFieldControl derived state ----------------------------------
|
|
559
|
+
get empty() {
|
|
560
|
+
return !this.#value;
|
|
561
|
+
}
|
|
562
|
+
get shouldLabelFloat() {
|
|
563
|
+
return this.focused || !this.empty;
|
|
564
|
+
}
|
|
565
|
+
get errorState() {
|
|
566
|
+
const externalError = (this.#errors?.length ?? 0) > 0 && this.touched;
|
|
567
|
+
const ngControlError = this.ngControl?.control?.errors != null;
|
|
568
|
+
return (!this.#value && this.required) || externalError || ngControlError;
|
|
569
|
+
}
|
|
570
|
+
setDescribedByIds(ids) {
|
|
571
|
+
if (!isPlatformBrowser(this.#platformId))
|
|
572
|
+
return;
|
|
573
|
+
// DEFECT FIX: the original queried `.app-pipeline-generator`, a class that
|
|
574
|
+
// never existed in the template, so describedBy was silently dropped. Apply
|
|
575
|
+
// it to the host element instead.
|
|
576
|
+
this._elementRef.nativeElement.setAttribute('aria-describedby', ids.join(' '));
|
|
577
|
+
}
|
|
578
|
+
onContainerClick() {
|
|
579
|
+
this.markAsTouched();
|
|
580
|
+
this.stateChanges.next();
|
|
581
|
+
}
|
|
582
|
+
markAsTouched() {
|
|
583
|
+
if (!this.touched) {
|
|
584
|
+
this.onTouched();
|
|
585
|
+
this.touched = true;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
get isRunningPipeline() {
|
|
589
|
+
return this.#pipeline.isRunningPipeline;
|
|
590
|
+
}
|
|
591
|
+
get aiIsGeneratingPipeline() {
|
|
592
|
+
return this.#pipeline.aiIsGeneratingPipeline;
|
|
593
|
+
}
|
|
594
|
+
// ---- Lifecycle: emit config changes -------------------------------------
|
|
595
|
+
#initiated;
|
|
596
|
+
ngOnInit() {
|
|
597
|
+
// Skip the first (current) config emission so an inbound value does not
|
|
598
|
+
// immediately echo back out as a "change".
|
|
599
|
+
this.#pipeline.config$
|
|
600
|
+
.pipe(distinctUntilChanged((prev, curr) => _isEqual(prev, curr)), takeUntilDestroyed(this.#destroyRef))
|
|
601
|
+
.subscribe((config) => {
|
|
602
|
+
if (!this.#initiated) {
|
|
603
|
+
this.#initiated = true;
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const newConfig = {
|
|
607
|
+
...this.#value,
|
|
608
|
+
...config,
|
|
609
|
+
name: this.#workflowName ?? 'Unnamed Pipeline',
|
|
610
|
+
source: 'mongoPipeline',
|
|
611
|
+
};
|
|
612
|
+
this.valueChanged.emit(newConfig);
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
ngOnDestroy() {
|
|
616
|
+
this.stateChanges.complete();
|
|
617
|
+
}
|
|
618
|
+
// ---- Workflow picker -----------------------------------------------------
|
|
619
|
+
#workflowName;
|
|
620
|
+
workflowChanged(workflowId) {
|
|
621
|
+
// BEHAVIOUR FLAG: switching to a *different* workflow clears the AI chat
|
|
622
|
+
// history so prior suggestions for another collection do not leak into the
|
|
623
|
+
// new context. Re-setting the same workflow (e.g. an inbound value echo)
|
|
624
|
+
// leaves the chat intact.
|
|
625
|
+
if (this.#pipeline.workflowId$.getValue() !== workflowId) {
|
|
626
|
+
this.#pipeline.chatHistory$.next([]);
|
|
627
|
+
}
|
|
628
|
+
this.#pipeline.workflowChanged(workflowId);
|
|
629
|
+
}
|
|
630
|
+
workflowNameChanged(name) {
|
|
631
|
+
this.#workflowName = name;
|
|
632
|
+
}
|
|
633
|
+
// ---- Stage management ----------------------------------------------------
|
|
634
|
+
trackByStageId(_index, stage) {
|
|
635
|
+
return stage.id;
|
|
636
|
+
}
|
|
637
|
+
setActiveStage(index) {
|
|
638
|
+
this.#pipeline.setActiveStage(index);
|
|
639
|
+
}
|
|
640
|
+
addStage() {
|
|
641
|
+
this.#pipeline.addStage();
|
|
642
|
+
}
|
|
643
|
+
deleteStage(index) {
|
|
644
|
+
this.#pipeline.deleteStage(index);
|
|
645
|
+
}
|
|
646
|
+
saveStage(index, stage) {
|
|
647
|
+
this.#pipeline.saveStage(index, stage);
|
|
648
|
+
this.setActiveStage(undefined);
|
|
649
|
+
}
|
|
650
|
+
// ---- AI prompt -----------------------------------------------------------
|
|
651
|
+
#cursorPosition;
|
|
652
|
+
onTextChange(event) {
|
|
653
|
+
const textarea = this.textareaRef()?.nativeElement;
|
|
654
|
+
if (!textarea)
|
|
655
|
+
return;
|
|
656
|
+
const newValue = event.target.value;
|
|
657
|
+
this.#cursorPosition = textarea.selectionStart ?? 0;
|
|
658
|
+
const lastChar = newValue[this.#cursorPosition - 1];
|
|
659
|
+
const match = newValue.slice(0, this.#cursorPosition).match(/\$(\w*)$/);
|
|
660
|
+
if (lastChar === '$' || (match && match[1] !== undefined)) {
|
|
661
|
+
this.showSuggestions.set(true);
|
|
662
|
+
this.optionsSearch.set(match?.[1] ?? '');
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
this.showSuggestions.set(false);
|
|
666
|
+
this.optionsSearch.set('');
|
|
667
|
+
}
|
|
668
|
+
this.#pipeline.onTextChange(newValue);
|
|
669
|
+
}
|
|
670
|
+
selectSuggestion(suggestion) {
|
|
671
|
+
this.#pipeline.prompt$
|
|
672
|
+
.pipe(take(1), takeUntilDestroyed(this.#destroyRef))
|
|
673
|
+
.subscribe((prompt) => {
|
|
674
|
+
const match = prompt?.slice(0, this.#cursorPosition).match(/\$(\w*)$/);
|
|
675
|
+
const beforeCursor = prompt?.slice(0, match ? match.index : this.#cursorPosition - 1) ?? '';
|
|
676
|
+
const afterCursor = prompt?.slice(this.#cursorPosition) ?? '';
|
|
677
|
+
const updated = `${beforeCursor}${suggestion} ${afterCursor}`;
|
|
678
|
+
this.#pipeline.prompt$.next(updated);
|
|
679
|
+
this.#applySuggestionToTextarea(suggestion);
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
#applySuggestionToTextarea(suggestion) {
|
|
683
|
+
this.showSuggestions.set(false);
|
|
684
|
+
if (!isPlatformBrowser(this.#platformId))
|
|
685
|
+
return;
|
|
686
|
+
const textarea = this.textareaRef()?.nativeElement;
|
|
687
|
+
if (!textarea)
|
|
688
|
+
return;
|
|
689
|
+
textarea.focus();
|
|
690
|
+
const nextPosition = this.#cursorPosition + suggestion.length;
|
|
691
|
+
setTimeout(() => textarea.setSelectionRange(nextPosition, nextPosition), 0);
|
|
692
|
+
}
|
|
693
|
+
promptSubmit() {
|
|
694
|
+
this.#pipeline.promptSubmit();
|
|
695
|
+
}
|
|
696
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: PipelineGeneratorComponent, deps: [{ token: i1$1.NgControl, optional: true, self: true }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
697
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: PipelineGeneratorComponent, isStandalone: true, selector: "lib-pipeline-generator", inputs: { disabled: "disabled", value: "value", getWorkflowOptions: "getWorkflowOptions", userName: "userName", errors: "errors" }, outputs: { valueChanged: "valueChanged" }, host: { properties: { "id": "id" }, classAttribute: "lib-pipeline-generator" }, providers: [
|
|
698
|
+
PipelineService,
|
|
699
|
+
// MatFormFieldControl pattern: useExisting + the @Input() value/disabled/id
|
|
700
|
+
// members are framework wiring and stay together (CLAUDE.md MFC carve-out).
|
|
701
|
+
{ provide: MatFormFieldControl, useExisting: PipelineGeneratorComponent },
|
|
702
|
+
], viewQueries: [{ propertyName: "textareaRef", first: true, predicate: ["textarea"], descendants: true, isSignal: true }], ngImport: i0, template: "<p class=\"lib-pipeline-generator__intro\">\n MongoDB aggregation pipeline builder for the selected workflow.\n</p>\n\n<lib-t-workflow-picker\n [getWorkflowOptions]=\"getWorkflowOptions\"\n [value]=\"workflowId() || undefined\"\n (valueChanged)=\"workflowChanged($event)\"\n (workflowNameChanged)=\"workflowNameChanged($event)\" />\n\n@if (workflowId()) {\n <mat-accordion class=\"lib-pipeline-generator__accordion\" multi>\n\n <!-- Pipeline stages -->\n <mat-expansion-panel value=\"pipeline\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n @if (isRunningPipeline) {\n <mat-spinner class=\"lib-pipeline-generator__header-spinner\" diameter=\"20\" />\n }\n <mat-icon class=\"lib-pipeline-generator__title-icon\">dynamic_form</mat-icon>\n {{ isRunningPipeline ? 'Executing data pipeline\u2026' : 'Data retrieval pipeline' }}\n </mat-panel-title>\n </mat-expansion-panel-header>\n\n <div class=\"lib-pipeline-generator__content\">\n @if (stages().length === 0) {\n <lib-empty-state\n icon=\"account_tree\"\n message=\"Add stages to the pipeline below. Each stage filters, groups, or calculates values; its output documents flow into the next stage.\" />\n }\n\n <mat-nav-list class=\"lib-pipeline-generator__stages\">\n @for (stage of stages(); track trackByStageId($index, stage)) {\n <mat-list-item\n class=\"lib-pipeline-generator__stage\"\n [class.lib-pipeline-generator__stage--active]=\"activeStageIndex() === $index\"\n [activated]=\"activeStageIndex() === $index\"\n (click)=\"setActiveStage($index)\">\n <span class=\"lib-pipeline-generator__stage-index\" matListItemAvatar>{{ $index + 1 }}</span>\n <div matListItemTitle>\n <div class=\"lib-pipeline-generator__stage-name\">{{ stage.name || 'Stage' }}</div>\n @if (!stage.name && activeStageIndex() !== $index) {\n <div class=\"lib-pipeline-generator__stage-hint\">Incomplete \u2014 click to edit</div>\n }\n </div>\n\n @if (activeStageIndex() === $index) {\n <mat-icon matListItemMeta color=\"primary\">check_circle</mat-icon>\n } @else if (!stage.valid) {\n <mat-icon matListItemMeta color=\"warn\">warning</mat-icon>\n }\n </mat-list-item>\n <mat-divider />\n\n @if (activeStageIndex() === $index) {\n <lib-aggregate-stage-editor\n [selectedStage]=\"stage\"\n [isRunningPipeline]=\"isRunningPipeline\"\n (deleteStage)=\"deleteStage($index)\"\n (saveStage)=\"saveStage($index, $event)\" />\n }\n }\n\n <mat-toolbar class=\"lib-pipeline-generator__toolbar\">\n <span class=\"lib-pipeline-generator__spacer\"></span>\n <button\n mat-flat-button\n type=\"button\"\n color=\"primary\"\n class=\"lib-pipeline-generator__add\"\n [disabled]=\"isRunningPipeline\"\n (click)=\"addStage()\">\n Add\n <mat-icon>add</mat-icon>\n </button>\n </mat-toolbar>\n </mat-nav-list>\n </div>\n </mat-expansion-panel>\n\n <!-- Results -->\n <mat-expansion-panel value=\"results\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n @if (isRunningPipeline) {\n <mat-spinner class=\"lib-pipeline-generator__header-spinner\" diameter=\"20\" />\n }\n <mat-icon class=\"lib-pipeline-generator__title-icon\">dataset</mat-icon>\n {{ isRunningPipeline ? 'Running pipeline\u2026' : 'Data results' }}\n </mat-panel-title>\n </mat-expansion-panel-header>\n\n <div class=\"lib-pipeline-generator__content\">\n @if (pipeLineResults(); as results) {\n <lib-t-dynamic-data-view [data]=\"results\" />\n } @else {\n <lib-empty-state icon=\"dataset\" message=\"No results available\" />\n }\n </div>\n </mat-expansion-panel>\n\n <!-- AI agent -->\n <mat-expansion-panel value=\"ai\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n @if (aiIsGeneratingPipeline) {\n <mat-spinner class=\"lib-pipeline-generator__header-spinner\" diameter=\"20\" />\n }\n <mat-icon class=\"lib-pipeline-generator__title-icon\">tips_and_updates</mat-icon>\n {{ aiIsGeneratingPipeline ? 'Agent busy\u2026' : 'AI agent prompt' }}\n </mat-panel-title>\n </mat-expansion-panel-header>\n\n <div class=\"lib-pipeline-generator__content\">\n @if (schemaLoadError(); as schemaLoadError) {\n <div class=\"lib-pipeline-generator__warning\" role=\"alert\">\n <mat-icon aria-hidden=\"true\">error</mat-icon>\n <span>{{ schemaLoadError }}</span>\n </div>\n }\n\n @for (chat of chatHistory(); track $index) {\n <div class=\"lib-pipeline-generator__chat\">\n <div class=\"lib-pipeline-generator__bubble lib-pipeline-generator__bubble--user\">\n <div>{{ chat.user }}</div>\n <div class=\"lib-pipeline-generator__bubble-meta\">\n {{ chat.date | daysAgo }}\n <mat-icon class=\"lib-pipeline-generator__sent-icon\">done_all</mat-icon>\n </div>\n </div>\n\n <div class=\"lib-pipeline-generator__assistant\">\n <mat-icon class=\"lib-pipeline-generator__assistant-icon\">smart_toy</mat-icon>\n @if (chat.assistance.error) {\n <div class=\"lib-pipeline-generator__bubble lib-pipeline-generator__bubble--error\">\n {{ chat.assistance.error }}\n </div>\n } @else {\n <div class=\"lib-pipeline-generator__bubble lib-pipeline-generator__bubble--bot\">\n <pre>{{ chat.assistance.pipeline | json }}</pre>\n </div>\n }\n </div>\n </div>\n }\n\n <div class=\"lib-pipeline-generator__composer\">\n <textarea\n #textarea\n class=\"lib-pipeline-generator__textarea\"\n [value]=\"prompt() || ''\"\n (input)=\"onTextChange($event)\"\n placeholder=\"Provide a pipeline generation prompt\"></textarea>\n <button\n mat-mini-fab\n type=\"button\"\n color=\"primary\"\n class=\"lib-pipeline-generator__send\"\n [disabled]=\"aiIsGeneratingPipeline\"\n (click)=\"promptSubmit()\">\n @if (aiIsGeneratingPipeline) {\n <mat-spinner class=\"lib-pipeline-generator__send-spinner\" diameter=\"24\" />\n } @else {\n <mat-icon>send</mat-icon>\n }\n </button>\n </div>\n\n @if (showSuggestions()) {\n <section class=\"lib-pipeline-generator__suggestions\">\n <mat-divider />\n <p class=\"lib-pipeline-generator__suggestions-label\">Collection document properties</p>\n <div class=\"lib-pipeline-generator__suggestion-list\">\n @for (property of schemaProperties(); track property.value) {\n <button\n mat-stroked-button\n type=\"button\"\n class=\"lib-pipeline-generator__suggestion\"\n (click)=\"selectSuggestion(property.value)\">\n {{ property.label }}\n </button>\n }\n </div>\n </section>\n }\n </div>\n </mat-expansion-panel>\n </mat-accordion>\n}\n\n<ng-template cdkConnectedOverlay [cdkConnectedOverlayOpen]=\"disabled\">\n <div class=\"lib-pipeline-generator__scrim\"></div>\n</ng-template>\n", styles: [":host{display:block}.lib-pipeline-generator__intro{margin:0 0 1.5rem;font-size:.875rem;color:var(--lib-forms-on-surface-variant)}.lib-pipeline-generator__accordion{display:block;margin-top:1.5rem}.lib-pipeline-generator__title-icon{margin-right:.5rem;vertical-align:middle}.lib-pipeline-generator__header-spinner{margin-right:.5rem}.lib-pipeline-generator__content{padding-top:.5rem}.lib-pipeline-generator__stages{padding:0;border-radius:var(--lib-forms-radius-lg, 12px);background:var(--lib-forms-surface-container-low)}.lib-pipeline-generator__stage{cursor:pointer;transition:background var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing)}.lib-pipeline-generator__stage--active{background:color-mix(in srgb,var(--lib-forms-primary) 8%,transparent)}.lib-pipeline-generator__stage-index{display:flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;font-size:.8125rem;border-radius:var(--lib-forms-radius-pill, 999px);background:var(--sem-info-surface);color:var(--lib-forms-on-surface)}.lib-pipeline-generator__stage-name{line-height:normal}.lib-pipeline-generator__stage-hint{font-size:.8125rem;line-height:normal;color:var(--lib-forms-on-surface-variant)}.lib-pipeline-generator__toolbar{padding:.5rem;background:transparent}.lib-pipeline-generator__spacer{flex:1 1 auto}.lib-pipeline-generator__warning{display:flex;align-items:flex-start;gap:.5rem;padding:.75rem;margin-bottom:1.5rem;border-radius:var(--lib-forms-radius-sm, 8px);background:var(--sem-warning-surface);color:var(--lib-forms-on-surface)}.lib-pipeline-generator__warning mat-icon{flex:0 0 auto;color:var(--sem-warning, var(--lib-forms-on-surface-variant))}.lib-pipeline-generator__chat{display:flex;flex-direction:column;gap:1rem;margin-bottom:1.5rem}.lib-pipeline-generator__bubble{padding:.75rem 1rem;font-size:.8125rem;white-space:normal}.lib-pipeline-generator__bubble pre{margin:0;white-space:pre-wrap;word-break:break-word}.lib-pipeline-generator__bubble--user{align-self:flex-end;max-width:80%;text-align:end;border-radius:18px 18px 0;background:color-mix(in srgb,var(--lib-forms-primary) 12%,transparent)}.lib-pipeline-generator__bubble--bot{border-radius:0 18px 18px;background:var(--sem-info-surface)}.lib-pipeline-generator__bubble--error{border-radius:0 18px 18px;background:var(--sem-error-surface);color:var(--lib-forms-on-surface)}.lib-pipeline-generator__bubble-meta{display:flex;align-items:center;justify-content:flex-end;gap:.25rem;line-height:normal;opacity:.6}.lib-pipeline-generator__sent-icon{width:.875rem;height:.875rem;font-size:.875rem}.lib-pipeline-generator__assistant{display:flex;align-items:flex-start;gap:.75rem}.lib-pipeline-generator__assistant-icon{flex:0 0 auto;color:var(--lib-forms-on-surface-variant)}.lib-pipeline-generator__composer{display:flex;align-items:flex-end;gap:.5rem;margin-top:1.5rem}.lib-pipeline-generator__textarea{min-height:2.25rem;padding:.5rem .75rem;border:1px solid var(--lib-forms-outline);border-radius:var(--lib-forms-radius-sm, 8px);background:var(--lib-forms-surface);font-size:.875rem;color:var(--lib-forms-on-surface);transition:border-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing),box-shadow var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing)}.lib-pipeline-generator__textarea:focus{outline:none;border-color:var(--lib-forms-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--lib-forms-primary) 25%,transparent)}.lib-pipeline-generator__textarea{flex:1 1 auto;min-height:2.75rem;resize:vertical;border-radius:14px;font-family:inherit}.lib-pipeline-generator__send{flex:0 0 auto}.lib-pipeline-generator__send-spinner{--mdc-circular-progress-active-indicator-color: var(--lib-forms-on-primary, #fff)}.lib-pipeline-generator__suggestions{margin-top:1rem}.lib-pipeline-generator__suggestions-label{margin:.75rem 0;font-size:.625rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--lib-forms-on-surface-variant)}.lib-pipeline-generator__suggestion-list{display:flex;flex-wrap:wrap;gap:.5rem}.lib-pipeline-generator__suggestion{font-size:.8125rem}.lib-pipeline-generator__scrim{position:absolute;inset:0;background:color-mix(in srgb,var(--lib-forms-on-surface) 50%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i2$1.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i4.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i5$1.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i5$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i5$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i5$1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i4.MatNavList, selector: "mat-nav-list", exportAs: ["matNavList"] }, { kind: "component", type: i4.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i4.MatListItemAvatar, selector: "[matListItemAvatar]" }, { kind: "directive", type: i4.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "directive", type: i4.MatListItemMeta, selector: "[matListItemMeta]" }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i3$2.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "ngmodule", type: MatToolbarModule }, { kind: "component", type: i5$2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: TWorkflowPickerComponent, selector: "lib-t-workflow-picker", inputs: ["disabled", "errors", "required", "value", "placeholder", "getWorkflowOptions"], outputs: ["valueChange", "valueChanged", "workflowNameChanged"] }, { kind: "component", type: TDynamicDataViewComponent, selector: "lib-t-dynamic-data-view", inputs: ["data"] }, { kind: "component", type: AggregateStageEditorComponent, selector: "lib-aggregate-stage-editor", inputs: ["selectedStage", "isRunningPipeline"], outputs: ["saveStage", "deleteStage"] }, { kind: "component", type: EmptyStateComponent, selector: "lib-empty-state", inputs: ["icon", "message"] }, { kind: "pipe", type: JsonPipe, name: "json" }, { kind: "pipe", type: DaysAgoPipe, name: "daysAgo" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
703
|
+
}
|
|
704
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: PipelineGeneratorComponent, decorators: [{
|
|
705
|
+
type: Component,
|
|
706
|
+
args: [{ selector: 'lib-pipeline-generator', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, imports: [
|
|
707
|
+
JsonPipe,
|
|
708
|
+
FormsModule,
|
|
709
|
+
OverlayModule,
|
|
710
|
+
MatButtonModule,
|
|
711
|
+
MatDividerModule,
|
|
712
|
+
MatExpansionModule,
|
|
713
|
+
MatIconModule,
|
|
714
|
+
MatListModule,
|
|
715
|
+
MatProgressSpinnerModule,
|
|
716
|
+
MatToolbarModule,
|
|
717
|
+
TWorkflowPickerComponent,
|
|
718
|
+
TDynamicDataViewComponent,
|
|
719
|
+
AggregateStageEditorComponent,
|
|
720
|
+
EmptyStateComponent,
|
|
721
|
+
DaysAgoPipe,
|
|
722
|
+
], providers: [
|
|
723
|
+
PipelineService,
|
|
724
|
+
// MatFormFieldControl pattern: useExisting + the @Input() value/disabled/id
|
|
725
|
+
// members are framework wiring and stay together (CLAUDE.md MFC carve-out).
|
|
726
|
+
{ provide: MatFormFieldControl, useExisting: PipelineGeneratorComponent },
|
|
727
|
+
], host: {
|
|
728
|
+
'class': 'lib-pipeline-generator',
|
|
729
|
+
'[id]': 'id',
|
|
730
|
+
}, template: "<p class=\"lib-pipeline-generator__intro\">\n MongoDB aggregation pipeline builder for the selected workflow.\n</p>\n\n<lib-t-workflow-picker\n [getWorkflowOptions]=\"getWorkflowOptions\"\n [value]=\"workflowId() || undefined\"\n (valueChanged)=\"workflowChanged($event)\"\n (workflowNameChanged)=\"workflowNameChanged($event)\" />\n\n@if (workflowId()) {\n <mat-accordion class=\"lib-pipeline-generator__accordion\" multi>\n\n <!-- Pipeline stages -->\n <mat-expansion-panel value=\"pipeline\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n @if (isRunningPipeline) {\n <mat-spinner class=\"lib-pipeline-generator__header-spinner\" diameter=\"20\" />\n }\n <mat-icon class=\"lib-pipeline-generator__title-icon\">dynamic_form</mat-icon>\n {{ isRunningPipeline ? 'Executing data pipeline\u2026' : 'Data retrieval pipeline' }}\n </mat-panel-title>\n </mat-expansion-panel-header>\n\n <div class=\"lib-pipeline-generator__content\">\n @if (stages().length === 0) {\n <lib-empty-state\n icon=\"account_tree\"\n message=\"Add stages to the pipeline below. Each stage filters, groups, or calculates values; its output documents flow into the next stage.\" />\n }\n\n <mat-nav-list class=\"lib-pipeline-generator__stages\">\n @for (stage of stages(); track trackByStageId($index, stage)) {\n <mat-list-item\n class=\"lib-pipeline-generator__stage\"\n [class.lib-pipeline-generator__stage--active]=\"activeStageIndex() === $index\"\n [activated]=\"activeStageIndex() === $index\"\n (click)=\"setActiveStage($index)\">\n <span class=\"lib-pipeline-generator__stage-index\" matListItemAvatar>{{ $index + 1 }}</span>\n <div matListItemTitle>\n <div class=\"lib-pipeline-generator__stage-name\">{{ stage.name || 'Stage' }}</div>\n @if (!stage.name && activeStageIndex() !== $index) {\n <div class=\"lib-pipeline-generator__stage-hint\">Incomplete \u2014 click to edit</div>\n }\n </div>\n\n @if (activeStageIndex() === $index) {\n <mat-icon matListItemMeta color=\"primary\">check_circle</mat-icon>\n } @else if (!stage.valid) {\n <mat-icon matListItemMeta color=\"warn\">warning</mat-icon>\n }\n </mat-list-item>\n <mat-divider />\n\n @if (activeStageIndex() === $index) {\n <lib-aggregate-stage-editor\n [selectedStage]=\"stage\"\n [isRunningPipeline]=\"isRunningPipeline\"\n (deleteStage)=\"deleteStage($index)\"\n (saveStage)=\"saveStage($index, $event)\" />\n }\n }\n\n <mat-toolbar class=\"lib-pipeline-generator__toolbar\">\n <span class=\"lib-pipeline-generator__spacer\"></span>\n <button\n mat-flat-button\n type=\"button\"\n color=\"primary\"\n class=\"lib-pipeline-generator__add\"\n [disabled]=\"isRunningPipeline\"\n (click)=\"addStage()\">\n Add\n <mat-icon>add</mat-icon>\n </button>\n </mat-toolbar>\n </mat-nav-list>\n </div>\n </mat-expansion-panel>\n\n <!-- Results -->\n <mat-expansion-panel value=\"results\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n @if (isRunningPipeline) {\n <mat-spinner class=\"lib-pipeline-generator__header-spinner\" diameter=\"20\" />\n }\n <mat-icon class=\"lib-pipeline-generator__title-icon\">dataset</mat-icon>\n {{ isRunningPipeline ? 'Running pipeline\u2026' : 'Data results' }}\n </mat-panel-title>\n </mat-expansion-panel-header>\n\n <div class=\"lib-pipeline-generator__content\">\n @if (pipeLineResults(); as results) {\n <lib-t-dynamic-data-view [data]=\"results\" />\n } @else {\n <lib-empty-state icon=\"dataset\" message=\"No results available\" />\n }\n </div>\n </mat-expansion-panel>\n\n <!-- AI agent -->\n <mat-expansion-panel value=\"ai\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n @if (aiIsGeneratingPipeline) {\n <mat-spinner class=\"lib-pipeline-generator__header-spinner\" diameter=\"20\" />\n }\n <mat-icon class=\"lib-pipeline-generator__title-icon\">tips_and_updates</mat-icon>\n {{ aiIsGeneratingPipeline ? 'Agent busy\u2026' : 'AI agent prompt' }}\n </mat-panel-title>\n </mat-expansion-panel-header>\n\n <div class=\"lib-pipeline-generator__content\">\n @if (schemaLoadError(); as schemaLoadError) {\n <div class=\"lib-pipeline-generator__warning\" role=\"alert\">\n <mat-icon aria-hidden=\"true\">error</mat-icon>\n <span>{{ schemaLoadError }}</span>\n </div>\n }\n\n @for (chat of chatHistory(); track $index) {\n <div class=\"lib-pipeline-generator__chat\">\n <div class=\"lib-pipeline-generator__bubble lib-pipeline-generator__bubble--user\">\n <div>{{ chat.user }}</div>\n <div class=\"lib-pipeline-generator__bubble-meta\">\n {{ chat.date | daysAgo }}\n <mat-icon class=\"lib-pipeline-generator__sent-icon\">done_all</mat-icon>\n </div>\n </div>\n\n <div class=\"lib-pipeline-generator__assistant\">\n <mat-icon class=\"lib-pipeline-generator__assistant-icon\">smart_toy</mat-icon>\n @if (chat.assistance.error) {\n <div class=\"lib-pipeline-generator__bubble lib-pipeline-generator__bubble--error\">\n {{ chat.assistance.error }}\n </div>\n } @else {\n <div class=\"lib-pipeline-generator__bubble lib-pipeline-generator__bubble--bot\">\n <pre>{{ chat.assistance.pipeline | json }}</pre>\n </div>\n }\n </div>\n </div>\n }\n\n <div class=\"lib-pipeline-generator__composer\">\n <textarea\n #textarea\n class=\"lib-pipeline-generator__textarea\"\n [value]=\"prompt() || ''\"\n (input)=\"onTextChange($event)\"\n placeholder=\"Provide a pipeline generation prompt\"></textarea>\n <button\n mat-mini-fab\n type=\"button\"\n color=\"primary\"\n class=\"lib-pipeline-generator__send\"\n [disabled]=\"aiIsGeneratingPipeline\"\n (click)=\"promptSubmit()\">\n @if (aiIsGeneratingPipeline) {\n <mat-spinner class=\"lib-pipeline-generator__send-spinner\" diameter=\"24\" />\n } @else {\n <mat-icon>send</mat-icon>\n }\n </button>\n </div>\n\n @if (showSuggestions()) {\n <section class=\"lib-pipeline-generator__suggestions\">\n <mat-divider />\n <p class=\"lib-pipeline-generator__suggestions-label\">Collection document properties</p>\n <div class=\"lib-pipeline-generator__suggestion-list\">\n @for (property of schemaProperties(); track property.value) {\n <button\n mat-stroked-button\n type=\"button\"\n class=\"lib-pipeline-generator__suggestion\"\n (click)=\"selectSuggestion(property.value)\">\n {{ property.label }}\n </button>\n }\n </div>\n </section>\n }\n </div>\n </mat-expansion-panel>\n </mat-accordion>\n}\n\n<ng-template cdkConnectedOverlay [cdkConnectedOverlayOpen]=\"disabled\">\n <div class=\"lib-pipeline-generator__scrim\"></div>\n</ng-template>\n", styles: [":host{display:block}.lib-pipeline-generator__intro{margin:0 0 1.5rem;font-size:.875rem;color:var(--lib-forms-on-surface-variant)}.lib-pipeline-generator__accordion{display:block;margin-top:1.5rem}.lib-pipeline-generator__title-icon{margin-right:.5rem;vertical-align:middle}.lib-pipeline-generator__header-spinner{margin-right:.5rem}.lib-pipeline-generator__content{padding-top:.5rem}.lib-pipeline-generator__stages{padding:0;border-radius:var(--lib-forms-radius-lg, 12px);background:var(--lib-forms-surface-container-low)}.lib-pipeline-generator__stage{cursor:pointer;transition:background var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing)}.lib-pipeline-generator__stage--active{background:color-mix(in srgb,var(--lib-forms-primary) 8%,transparent)}.lib-pipeline-generator__stage-index{display:flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;font-size:.8125rem;border-radius:var(--lib-forms-radius-pill, 999px);background:var(--sem-info-surface);color:var(--lib-forms-on-surface)}.lib-pipeline-generator__stage-name{line-height:normal}.lib-pipeline-generator__stage-hint{font-size:.8125rem;line-height:normal;color:var(--lib-forms-on-surface-variant)}.lib-pipeline-generator__toolbar{padding:.5rem;background:transparent}.lib-pipeline-generator__spacer{flex:1 1 auto}.lib-pipeline-generator__warning{display:flex;align-items:flex-start;gap:.5rem;padding:.75rem;margin-bottom:1.5rem;border-radius:var(--lib-forms-radius-sm, 8px);background:var(--sem-warning-surface);color:var(--lib-forms-on-surface)}.lib-pipeline-generator__warning mat-icon{flex:0 0 auto;color:var(--sem-warning, var(--lib-forms-on-surface-variant))}.lib-pipeline-generator__chat{display:flex;flex-direction:column;gap:1rem;margin-bottom:1.5rem}.lib-pipeline-generator__bubble{padding:.75rem 1rem;font-size:.8125rem;white-space:normal}.lib-pipeline-generator__bubble pre{margin:0;white-space:pre-wrap;word-break:break-word}.lib-pipeline-generator__bubble--user{align-self:flex-end;max-width:80%;text-align:end;border-radius:18px 18px 0;background:color-mix(in srgb,var(--lib-forms-primary) 12%,transparent)}.lib-pipeline-generator__bubble--bot{border-radius:0 18px 18px;background:var(--sem-info-surface)}.lib-pipeline-generator__bubble--error{border-radius:0 18px 18px;background:var(--sem-error-surface);color:var(--lib-forms-on-surface)}.lib-pipeline-generator__bubble-meta{display:flex;align-items:center;justify-content:flex-end;gap:.25rem;line-height:normal;opacity:.6}.lib-pipeline-generator__sent-icon{width:.875rem;height:.875rem;font-size:.875rem}.lib-pipeline-generator__assistant{display:flex;align-items:flex-start;gap:.75rem}.lib-pipeline-generator__assistant-icon{flex:0 0 auto;color:var(--lib-forms-on-surface-variant)}.lib-pipeline-generator__composer{display:flex;align-items:flex-end;gap:.5rem;margin-top:1.5rem}.lib-pipeline-generator__textarea{min-height:2.25rem;padding:.5rem .75rem;border:1px solid var(--lib-forms-outline);border-radius:var(--lib-forms-radius-sm, 8px);background:var(--lib-forms-surface);font-size:.875rem;color:var(--lib-forms-on-surface);transition:border-color var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing),box-shadow var(--lib-forms-duration-hover, .3s) var(--lib-forms-easing)}.lib-pipeline-generator__textarea:focus{outline:none;border-color:var(--lib-forms-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--lib-forms-primary) 25%,transparent)}.lib-pipeline-generator__textarea{flex:1 1 auto;min-height:2.75rem;resize:vertical;border-radius:14px;font-family:inherit}.lib-pipeline-generator__send{flex:0 0 auto}.lib-pipeline-generator__send-spinner{--mdc-circular-progress-active-indicator-color: var(--lib-forms-on-primary, #fff)}.lib-pipeline-generator__suggestions{margin-top:1rem}.lib-pipeline-generator__suggestions-label{margin:.75rem 0;font-size:.625rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--lib-forms-on-surface-variant)}.lib-pipeline-generator__suggestion-list{display:flex;flex-wrap:wrap;gap:.5rem}.lib-pipeline-generator__suggestion{font-size:.8125rem}.lib-pipeline-generator__scrim{position:absolute;inset:0;background:color-mix(in srgb,var(--lib-forms-on-surface) 50%,transparent)}\n"] }]
|
|
731
|
+
}], ctorParameters: () => [{ type: i1$1.NgControl, decorators: [{
|
|
732
|
+
type: Optional
|
|
733
|
+
}, {
|
|
734
|
+
type: Self
|
|
735
|
+
}] }, { type: i0.ElementRef }], propDecorators: { disabled: [{
|
|
736
|
+
type: Input
|
|
737
|
+
}], value: [{
|
|
738
|
+
type: Input
|
|
739
|
+
}], getWorkflowOptions: [{
|
|
740
|
+
type: Input
|
|
741
|
+
}], userName: [{
|
|
742
|
+
type: Input
|
|
743
|
+
}], errors: [{
|
|
744
|
+
type: Input
|
|
745
|
+
}], valueChanged: [{ type: i0.Output, args: ["valueChanged"] }], textareaRef: [{ type: i0.ViewChild, args: ['textarea', { isSignal: true }] }] } });
|
|
746
|
+
|
|
747
|
+
export { PipelineGeneratorComponent };
|
|
748
|
+
//# sourceMappingURL=ngx-t-forms-pipeline-generator.component-DmNSc5aw.mjs.map
|