@firestitch/report 18.0.10 → 18.0.12
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/app/reports/data/report.data.d.ts +4 -0
- package/app/reports/dialogs/entity-functions/entity-functions.component.d.ts +22 -0
- package/app/reports/dialogs/global-settings/components/general/general.component.d.ts +8 -0
- package/app/reports/dialogs/global-settings/components/semantic-database/semantic-database.component.d.ts +21 -21
- package/app/reports/dialogs/global-settings/global-settings.component.d.ts +8 -4
- package/app/reports/dialogs/instruction-history/instruction-history.component.d.ts +16 -0
- package/app/reports/interfaces/report.interface.d.ts +11 -1
- package/esm2022/app/reports/data/report.data.mjs +15 -1
- package/esm2022/app/reports/dialogs/entity-functions/entity-functions.component.mjs +81 -0
- package/esm2022/app/reports/dialogs/global-settings/components/general/general.component.mjs +34 -0
- package/esm2022/app/reports/dialogs/global-settings/components/semantic-database/semantic-database.component.mjs +70 -75
- package/esm2022/app/reports/dialogs/global-settings/global-settings.component.mjs +58 -24
- package/esm2022/app/reports/dialogs/instruction-history/instruction-history.component.mjs +60 -0
- package/esm2022/app/reports/interfaces/report.interface.mjs +1 -1
- package/fesm2022/{firestitch-report-echarts-Cel1iHUg.mjs → firestitch-report-echarts-1VNsyco5.mjs} +2 -2
- package/fesm2022/{firestitch-report-echarts-Cel1iHUg.mjs.map → firestitch-report-echarts-1VNsyco5.mjs.map} +1 -1
- package/fesm2022/{firestitch-report-firestitch-report-Y1XG_Gms.mjs → firestitch-report-firestitch-report-BNHGwPj2.mjs} +274 -119
- package/fesm2022/firestitch-report-firestitch-report-BNHGwPj2.mjs.map +1 -0
- package/fesm2022/firestitch-report.mjs +1 -1
- package/package.json +1 -1
- package/app/reports/dialogs/global-settings/components/history/history.component.d.ts +0 -8
- package/esm2022/app/reports/dialogs/global-settings/components/history/history.component.mjs +0 -32
- package/fesm2022/firestitch-report-firestitch-report-Y1XG_Gms.mjs.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, Injectable, Component, ChangeDetectionStrategy, Input, DestroyRef, ViewChild, EventEmitter, ChangeDetectorRef, NgZone, ElementRef, Output, HostBinding, signal, CUSTOM_ELEMENTS_SCHEMA, model, input,
|
|
2
|
+
import { inject, Injectable, Component, ChangeDetectionStrategy, Input, DestroyRef, ViewChild, EventEmitter, ChangeDetectorRef, NgZone, ElementRef, Output, HostBinding, signal, CUSTOM_ELEMENTS_SCHEMA, model, input, computed, viewChild, effect, output } from '@angular/core';
|
|
3
3
|
import * as i1$3 from '@angular/forms';
|
|
4
4
|
import { FormsModule, ControlContainer, NgForm } from '@angular/forms';
|
|
5
5
|
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialog, MatDialogClose } from '@angular/material/dialog';
|
|
6
|
-
import * as i4$
|
|
6
|
+
import * as i4$2 from '@angular/material/icon';
|
|
7
7
|
import { MatIcon, MatIconModule } from '@angular/material/icon';
|
|
8
8
|
import * as i2$2 from '@firestitch/autocomplete-chips';
|
|
9
9
|
import { FsAutocompleteChipsModule } from '@firestitch/autocomplete-chips';
|
|
@@ -53,11 +53,11 @@ import { guid } from '@firestitch/common';
|
|
|
53
53
|
import { Router, ActivatedRoute, RouterLink } from '@angular/router';
|
|
54
54
|
import * as i2$4 from '@firestitch/date';
|
|
55
55
|
import { FsDateModule } from '@firestitch/date';
|
|
56
|
-
import * as
|
|
56
|
+
import * as i4$1 from '@firestitch/skeleton';
|
|
57
57
|
import { FsSkeletonModule } from '@firestitch/skeleton';
|
|
58
|
-
import { FsMarkdownEditorComponent, FsMarkdownRendererComponent } from '@firestitch/markdown-editor';
|
|
59
58
|
import * as i1$4 from '@firestitch/chip';
|
|
60
59
|
import { FsChipModule } from '@firestitch/chip';
|
|
60
|
+
import { FsMarkdownRendererComponent, FsMarkdownEditorComponent } from '@firestitch/markdown-editor';
|
|
61
61
|
import * as i1$5 from '@angular/material/expansion';
|
|
62
62
|
import { MatExpansionModule } from '@angular/material/expansion';
|
|
63
63
|
|
|
@@ -245,6 +245,20 @@ class ReportData {
|
|
|
245
245
|
...config,
|
|
246
246
|
});
|
|
247
247
|
}
|
|
248
|
+
// The report module's General settings (semantic-build concurrency + its bounds).
|
|
249
|
+
settings(config = {}) {
|
|
250
|
+
return this._api.get(this._path('settings'), {}, {
|
|
251
|
+
key: 'settings',
|
|
252
|
+
...config,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
// Save the General settings; resolves to the same { settings } shape.
|
|
256
|
+
saveSettings(settings, config = {}) {
|
|
257
|
+
return this._api.put(this._path('settings'), settings, {
|
|
258
|
+
key: 'settings',
|
|
259
|
+
...config,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
248
262
|
// Update a filter group — its exposure level ('component' | 'report' | 'both')
|
|
249
263
|
// and/or its default value (the value seeding the report bar on open). Send
|
|
250
264
|
// `default: null` (or an empty default) to clear it. Only the keys provided
|
|
@@ -2527,7 +2541,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2527
2541
|
// complete synchronously after setOption.
|
|
2528
2542
|
async function renderChartImage(component, data, width, height, pixelRatio = 3) {
|
|
2529
2543
|
// The same tree-shaken echarts build + 'report' theme the live canvas uses.
|
|
2530
|
-
const echarts = (await import('./firestitch-report-echarts-
|
|
2544
|
+
const echarts = (await import('./firestitch-report-echarts-1VNsyco5.mjs')).default;
|
|
2531
2545
|
const host = document.createElement('div');
|
|
2532
2546
|
host.style.width = `${width}px`;
|
|
2533
2547
|
host.style.height = `${height}px`;
|
|
@@ -3271,7 +3285,7 @@ class ReportComponent {
|
|
|
3271
3285
|
ReportPdfService,
|
|
3272
3286
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily with
|
|
3273
3287
|
// this route's chunk.
|
|
3274
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
3288
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-1VNsyco5.mjs').then((module) => module.default) }),
|
|
3275
3289
|
], viewQueries: [{ propertyName: "_split", first: true, predicate: ["split"], descendants: true, static: true }, { propertyName: "_chatPanel", first: true, predicate: ["chatPanel"], descendants: true, read: ElementRef, static: true }], ngImport: i0, template: "<div\n #split\n class=\"report fs-row.align-start\">\n <fs-ai-chat\n #chatPanel\n class=\"chat\"\n style=\"min-height: 500px;\"\n basePath=\"reports\"\n [requestData]=\"{ reportId: selected?.id ?? null }\"\n [introMessage]=\"introMessage\"\n (response)=\"onChatResponse($event)\">\n </fs-ai-chat>\n <div\n class=\"resizer\"\n (pointerdown)=\"onResizeStart($event)\">\n </div>\n <div class=\"viewer fs-flex fs-column\">\n <div class=\"fs-row.align-center.gap-sm\">\n <fs-autocomplete-chips\n class=\"fs-flex\"\n [fetch]=\"fetchReports\"\n [(ngModel)]=\"selected\"\n [disabled]=\"loadingReports\"\n [padless]=\"true\"\n [multiple]=\"false\"\n [fetchOnFocus]=\"true\"\n (ngModelChange)=\"reportChange($event)\"\n placeholder=\"Report\"\n name=\"report\">\n <ng-template\n fsAutocompleteChipsTemplate\n let-object=\"object\">\n {{ object.name }}\n </ng-template>\n <ng-template\n fsAutocompleteChipsStatic\n (click)=\"createReport()\">\n Create Report\n </ng-template>\n </fs-autocomplete-chips>\n @if (report) {\n <fs-menu>\n <ng-template\n fs-menu-item\n (click)=\"reportSettings()\">\n <mat-icon>\n tune\n </mat-icon>\n Report settings\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"toggleEditMode()\"\n [disabled]=\"editMode\">\n <mat-icon>\n dashboard_2_edit\n </mat-icon>\n {{ editMode ? 'Done editing layout' : 'Edit layout' }}\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportPowerpoint()\">\n <mat-icon>\n slideshow\n </mat-icon>\n Export PowerPoint\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportPdf()\">\n <mat-icon>\n picture_as_pdf\n </mat-icon>\n Export PDF\n </ng-template>\n </fs-menu>\n }\n </div>\n @if (report) {\n <!-- Report-level filters only (the report's actions live in the menu\n above). fs-filter reads its config once at init, so it's keyed on the\n filter signature: when the report-level filter set changes the block\n is recreated and re-reads the rebuilt config. -->\n @if (reportHasFilters) {\n @for (key of [reportFilterKey]; track key) {\n <fs-filter [config]=\"reportFilterConfig\"></fs-filter>\n }\n }\n <app-report-canvas\n [report]=\"report\"\n [editMode]=\"editMode\"\n (componentSettings)=\"componentSettings($event)\"\n (reportChanged)=\"onCanvasReportChanged()\"\n (editDone)=\"toggleEditMode()\">\n </app-report-canvas>\n }\n </div>\n</div>", styles: [".report{height:100%;min-height:500px}.report .chat{display:block;flex:0 0 25%;width:100%;height:100%;min-height:500px;min-width:0;border:none}.report .viewer{display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden}.report .viewer fs-filter{flex:0 0 auto;margin-top:10px;margin-bottom:0}.report .viewer ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.report .resizer{flex:0 0 11px;align-self:stretch;display:flex;justify-content:center;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none}.report .resizer:before{content:\"\";width:0px;background:#0000001f;transition:width .12s ease,background-color .12s ease}.report .resizer:hover:before{width:3px;background:var(--brand-primary-color)}.report.resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.report.resizing .resizer:before{width:3px;background:var(--brand-primary-color)}.report.resizing .chat,.report.resizing .viewer{pointer-events:none}::ng-deep body.body-report-reports .mat-mdc-card-content{display:flex;flex-direction:column;box-sizing:border-box}::ng-deep body.body-report-reports .mat-mdc-card-content mat-tab-nav-panel{flex:1;min-height:0}::ng-deep body.body-report-reports .mat-mdc-card-content mat-tab-nav-panel router-outlet+ng-component{height:100%}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: FsAutocompleteChipsModule }, { kind: "component", type: i2$2.FsAutocompleteChipsComponent, selector: "fs-autocomplete-chips", inputs: ["fetch", "appearance", "floatLabel", "readonly", "size", "label", "placeholder", "chipImage", "chipBackground", "chipColor", "chipIcon", "chipIconColor", "chipClass", "chipPadding", "shape", "hint", "allowText", "allowObject", "delay", "minPanelWidth", "maxPanelHeight", "validateText", "removable", "allowClear", "color", "background", "orderable", "padless", "initOnClick", "fetchOnFocus", "multiple", "multipleAdd", "confirm", "disabled", "groupBy", "panelWidth", "panelClass", "compareWith"], outputs: ["selected", "removed", "reordered", "clear", "panelOpened", "panelClosed"] }, { kind: "directive", type: i2$2.FsAutocompleteObjectDirective, selector: "[fsAutocompleteObject],[fsAutocompleteChipsTemplate]" }, { kind: "directive", type: i2$2.FsAutocompleteChipsStaticDirective, selector: "[fsAutocompleteChipsStatic]", inputs: ["show", "disable"], outputs: ["click", "selected"] }, { kind: "ngmodule", type: FsFilterModule }, { kind: "component", type: i2.FilterComponent, selector: "fs-filter", inputs: ["config"], outputs: ["closed", "opened", "ready"] }, { kind: "ngmodule", type: FsMenuModule }, { kind: "component", type: i2$3.FsMenuComponent, selector: "fs-menu", inputs: ["class", "buttonClass", "buttonType", "buttonColor"], outputs: ["opened", "closed"] }, { kind: "directive", type: i2$3.FsMenuItemDirective, selector: "fs-menu-group,[fs-menu-item]" }, { kind: "component", type: FsAiChatComponent, selector: "fs-ai-chat", inputs: ["basePath", "requestData", "introMessage"], outputs: ["response"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: ReportCanvasComponent, selector: "app-report-canvas", inputs: ["report", "editMode"], outputs: ["componentSettings", "reportChanged", "editDone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3276
3290
|
}
|
|
3277
3291
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportComponent, decorators: [{
|
|
@@ -3285,7 +3299,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3285
3299
|
ReportPdfService,
|
|
3286
3300
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily with
|
|
3287
3301
|
// this route's chunk.
|
|
3288
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
3302
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-1VNsyco5.mjs').then((module) => module.default) }),
|
|
3289
3303
|
], imports: [
|
|
3290
3304
|
FormsModule,
|
|
3291
3305
|
FsAutocompleteChipsModule,
|
|
@@ -3303,6 +3317,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3303
3317
|
args: ['chatPanel', { static: true, read: ElementRef }]
|
|
3304
3318
|
}] } });
|
|
3305
3319
|
|
|
3320
|
+
// The AI Knowledge document's version history (append-only), each version
|
|
3321
|
+
// previewed and restorable — opened from the Report Settings footer's View
|
|
3322
|
+
// History button. Restores happen here (confirm, then flip the current flag —
|
|
3323
|
+
// no new version is created); the opener refreshes its instructions after this
|
|
3324
|
+
// dialog closes, so a restore made here reseeds the editor there.
|
|
3325
|
+
class InstructionHistoryComponent {
|
|
3326
|
+
_data = inject(MAT_DIALOG_DATA);
|
|
3327
|
+
versions = signal(this._data.versions);
|
|
3328
|
+
_reportData = inject(ReportData);
|
|
3329
|
+
_prompt = inject(FsPrompt);
|
|
3330
|
+
_message = inject(FsMessage);
|
|
3331
|
+
_destroyRef = inject(DestroyRef);
|
|
3332
|
+
// Make an earlier version current — flips the flag, creates no new version.
|
|
3333
|
+
restore(version) {
|
|
3334
|
+
this._prompt.confirm({
|
|
3335
|
+
title: 'Restore version',
|
|
3336
|
+
template: 'Make this version the current AI knowledge? The newer versions stay in the history.',
|
|
3337
|
+
commitLabel: 'Restore',
|
|
3338
|
+
})
|
|
3339
|
+
.pipe(switchMap(() => this._reportData.restoreInstruction(version.id)), takeUntilDestroyed(this._destroyRef))
|
|
3340
|
+
.subscribe((instructions) => {
|
|
3341
|
+
this.versions.set(instructions.versions);
|
|
3342
|
+
this._message.success('Version restored');
|
|
3343
|
+
});
|
|
3344
|
+
}
|
|
3345
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: InstructionHistoryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3346
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: InstructionHistoryComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: "<fs-dialog>\n <fs-dialog-title>\n AI Knowledge History\n </fs-dialog-title>\n <mat-dialog-content>\n @if (versions().length) {\n <div class=\"versions\">\n @for (version of versions(); track version.id) {\n <div class=\"version\">\n <div class=\"version-header\">\n <fs-date\n [date]=\"version.createDate\"\n format=\"date-time\">\n </fs-date>\n @if (version.accountName) {\n <span class=\"author\">{{ version.accountName }}</span>\n }\n <span class=\"spacer\"></span>\n @if (version.current) {\n <fs-chip\n size=\"tiny\"\n [backgroundColor]=\"'#2e7d32'\"\n color=\"#ffffff\">\n Current\n </fs-chip>\n } @else {\n <button\n type=\"button\"\n mat-stroked-button\n (click)=\"restore(version)\">\n Restore\n </button>\n }\n </div>\n <div class=\"version-preview\">\n <fs-markdown-renderer [markdown]=\"version.content\"></fs-markdown-renderer>\n </div>\n </div>\n }\n </div>\n } @else {\n <fs-message-info>\n No versions yet \u2014 every save of the AI Knowledge document lands here, and\n any version can be restored.\n </fs-message-info>\n }\n </mat-dialog-content>\n <mat-dialog-actions>\n <button\n type=\"button\"\n mat-button\n color=\"primary\"\n [mat-dialog-close]=\"null\">\n Close\n </button>\n </mat-dialog-actions>\n</fs-dialog>\n", styles: [".version{border:1px solid rgba(0,0,0,.12);border-radius:6px;padding:12px}.version+.version{margin-top:12px}.version .version-header{display:flex;align-items:center;gap:8px}.version .version-header .author{color:#0009}.version .version-header .spacer{flex:1}.version .version-preview{margin-top:8px;max-height:200px;overflow-y:auto;border-top:1px solid rgba(0,0,0,.08);padding-top:8px}\n"], dependencies: [{ kind: "component", type: MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "directive", type: MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "ngmodule", type: FsChipModule }, { kind: "component", type: i1$4.FsChipComponent, selector: "fs-chip", inputs: ["selectable", "removable", "value", "maxWidth", "width", "backgroundColor", "borderColor", "color", "shape", "outlined", "outlineDash", "icon", "image", "selected", "disabled", "padding", "contrastColor", "size"], outputs: ["selectedToggled", "removed", "click"] }, { kind: "ngmodule", type: FsDateModule }, { kind: "component", type: i2$4.FsDateComponent, selector: "fs-date", inputs: ["date", "format", "timezone"] }, { kind: "ngmodule", type: FsDialogModule }, { kind: "component", type: i3.FsDialogComponent, selector: "fs-dialog", inputs: ["mobileMode", "mobileButtonPlacement", "mobileWidth", "mode", "buttonLayout"] }, { kind: "component", type: i3.FsDialogTitleComponent, selector: "fs-dialog-title", inputs: ["close", "dockToggle", "back"] }, { kind: "component", type: FsMarkdownRendererComponent, selector: "fs-markdown-renderer", inputs: ["config", "markdown"] }, { kind: "ngmodule", type: FsMessageModule }, { kind: "component", type: i4.FsMessageInfoComponent, selector: "fs-message-info" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3347
|
+
}
|
|
3348
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: InstructionHistoryComponent, decorators: [{
|
|
3349
|
+
type: Component,
|
|
3350
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
|
|
3351
|
+
MatButton,
|
|
3352
|
+
MatDialogContent,
|
|
3353
|
+
MatDialogActions,
|
|
3354
|
+
MatDialogClose,
|
|
3355
|
+
FsChipModule,
|
|
3356
|
+
FsDateModule,
|
|
3357
|
+
FsDialogModule,
|
|
3358
|
+
FsMarkdownRendererComponent,
|
|
3359
|
+
FsMessageModule,
|
|
3360
|
+
], template: "<fs-dialog>\n <fs-dialog-title>\n AI Knowledge History\n </fs-dialog-title>\n <mat-dialog-content>\n @if (versions().length) {\n <div class=\"versions\">\n @for (version of versions(); track version.id) {\n <div class=\"version\">\n <div class=\"version-header\">\n <fs-date\n [date]=\"version.createDate\"\n format=\"date-time\">\n </fs-date>\n @if (version.accountName) {\n <span class=\"author\">{{ version.accountName }}</span>\n }\n <span class=\"spacer\"></span>\n @if (version.current) {\n <fs-chip\n size=\"tiny\"\n [backgroundColor]=\"'#2e7d32'\"\n color=\"#ffffff\">\n Current\n </fs-chip>\n } @else {\n <button\n type=\"button\"\n mat-stroked-button\n (click)=\"restore(version)\">\n Restore\n </button>\n }\n </div>\n <div class=\"version-preview\">\n <fs-markdown-renderer [markdown]=\"version.content\"></fs-markdown-renderer>\n </div>\n </div>\n }\n </div>\n } @else {\n <fs-message-info>\n No versions yet \u2014 every save of the AI Knowledge document lands here, and\n any version can be restored.\n </fs-message-info>\n }\n </mat-dialog-content>\n <mat-dialog-actions>\n <button\n type=\"button\"\n mat-button\n color=\"primary\"\n [mat-dialog-close]=\"null\">\n Close\n </button>\n </mat-dialog-actions>\n</fs-dialog>\n", styles: [".version{border:1px solid rgba(0,0,0,.12);border-radius:6px;padding:12px}.version+.version{margin-top:12px}.version .version-header{display:flex;align-items:center;gap:8px}.version .version-header .author{color:#0009}.version .version-header .spacer{flex:1}.version .version-preview{margin-top:8px;max-height:200px;overflow-y:auto;border-top:1px solid rgba(0,0,0,.08);padding-top:8px}\n"] }]
|
|
3361
|
+
}] });
|
|
3362
|
+
|
|
3306
3363
|
// The AI Knowledge tab: the operator-authored markdown document appended to the
|
|
3307
3364
|
// report agent's system instructions on every chat turn — the lever for
|
|
3308
3365
|
// terminology, business rules and special cases no code deploy can cover.
|
|
@@ -3327,35 +3384,106 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3327
3384
|
], template: "<div class=\"fs-column tab-body\">\n <p class=\"hint\">\n This document is given to the report AI on every chat message. Use it for\n terminology, business rules and special cases the AI can't work out from the\n data on its own \u2014 it takes effect immediately, no deploy needed.\n </p>\n <fs-markdown-editor\n [ngModel]=\"content()\"\n (ngModelChange)=\"content.set($event)\"\n [config]=\"config()\"\n name=\"content\">\n </fs-markdown-editor>\n</div>\n", styles: [".tab-body{padding-top:16px}.hint{margin:0 0 12px;color:#0009;font-size:13px;line-height:1.5}fs-markdown-editor{display:block;min-height:320px}\n"] }]
|
|
3328
3385
|
}] });
|
|
3329
3386
|
|
|
3330
|
-
// The
|
|
3331
|
-
//
|
|
3332
|
-
//
|
|
3333
|
-
//
|
|
3334
|
-
//
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3387
|
+
// The General tab: report-module settings that aren't the AI knowledge document.
|
|
3388
|
+
// Currently just the semantic-build concurrency — how many summary calls the
|
|
3389
|
+
// build runs at once. A field-level child of the settings dialog form (its input
|
|
3390
|
+
// registers with the parent <form fsForm> via the ControlContainer view
|
|
3391
|
+
// provider), so the dialog's Save button submits and validates it. The value
|
|
3392
|
+
// itself is held on the shell (bound through `concurrency`), and the safe band
|
|
3393
|
+
// (`min`/`max`) comes from the backend so the field can't be set out of range.
|
|
3394
|
+
class GeneralComponent {
|
|
3395
|
+
concurrency = model(0);
|
|
3396
|
+
min = input.required();
|
|
3397
|
+
max = input.required();
|
|
3398
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GeneralComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3399
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: GeneralComponent, isStandalone: true, selector: "fs-ai-report-global-settings-general", inputs: { concurrency: { classPropertyName: "concurrency", publicName: "concurrency", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: true, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { concurrency: "concurrencyChange" }, ngImport: i0, template: "<div class=\"fs-column tab-body\">\n <mat-form-field>\n <mat-label>Semantic build concurrency</mat-label>\n <input\n matInput\n type=\"number\"\n [ngModel]=\"concurrency()\"\n (ngModelChange)=\"concurrency.set($event)\"\n name=\"semanticBuildConcurrency\"\n [fsFormRequired]=\"true\"\n [fsFormInteger]=\"true\"\n [fsFormMin]=\"min()\"\n [fsFormMax]=\"max()\">\n <mat-hint>\n How many AI calls the semantic-database build runs at once ({{ min() }}\u2013{{ max() }}).\n Higher is faster but more likely to hit the AI provider's rate limit. Default is 8.\n </mat-hint>\n </mat-form-field>\n</div>\n", styles: [".tab-body{padding-top:16px}mat-form-field{display:block;max-width:340px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.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$3.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: FsFormModule }, { kind: "directive", type: i2$1.FsFormRequiredDirective, selector: "[fsFormRequired],[ngModel][required]", inputs: ["fsFormRequired", "required", "fsFormRequiredMessage"] }, { kind: "directive", type: i2$1.FsFormMinDirective, selector: "[fsFormMin]", inputs: ["fsFormMin", "fsFormMinMessage"] }, { kind: "directive", type: i2$1.FsFormMaxDirective, selector: "[fsFormMax]", inputs: ["fsFormMax", "fsFormMaxMessage"] }, { kind: "directive", type: i2$1.FsFormIntegerDirective, selector: "[fsFormInteger]", inputs: ["fsFormInteger", "fsFormIntegerMessage"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "directive", type: MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }], viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3340
3400
|
}
|
|
3341
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type:
|
|
3401
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GeneralComponent, decorators: [{
|
|
3342
3402
|
type: Component,
|
|
3343
|
-
args: [{ selector: 'fs-ai-report-global-settings-
|
|
3403
|
+
args: [{ selector: 'fs-ai-report-global-settings-general', viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
|
|
3404
|
+
FormsModule,
|
|
3405
|
+
FsFormModule,
|
|
3406
|
+
MatFormField,
|
|
3407
|
+
MatLabel,
|
|
3408
|
+
MatHint,
|
|
3409
|
+
MatInput,
|
|
3410
|
+
], template: "<div class=\"fs-column tab-body\">\n <mat-form-field>\n <mat-label>Semantic build concurrency</mat-label>\n <input\n matInput\n type=\"number\"\n [ngModel]=\"concurrency()\"\n (ngModelChange)=\"concurrency.set($event)\"\n name=\"semanticBuildConcurrency\"\n [fsFormRequired]=\"true\"\n [fsFormInteger]=\"true\"\n [fsFormMin]=\"min()\"\n [fsFormMax]=\"max()\">\n <mat-hint>\n How many AI calls the semantic-database build runs at once ({{ min() }}\u2013{{ max() }}).\n Higher is faster but more likely to hit the AI provider's rate limit. Default is 8.\n </mat-hint>\n </mat-form-field>\n</div>\n", styles: [".tab-body{padding-top:16px}mat-form-field{display:block;max-width:340px}\n"] }]
|
|
3411
|
+
}] });
|
|
3412
|
+
|
|
3413
|
+
// One group of an entity's AI-classified cached code functions — the model
|
|
3414
|
+
// class's or the handler class's, per the source it was opened for. Fetched on
|
|
3415
|
+
// open from the shared per-entity endpoint (one payload carries both groups,
|
|
3416
|
+
// each row tagged with its source) and filtered client-side.
|
|
3417
|
+
class EntityFunctionsComponent {
|
|
3418
|
+
// One color per AI classification kind, so a function's nature reads at a glance.
|
|
3419
|
+
static KIND_COLORS = {
|
|
3420
|
+
'calculation': '#1565c0',
|
|
3421
|
+
'business-rule': '#6a1b9a',
|
|
3422
|
+
'query': '#00695c',
|
|
3423
|
+
'mutation': '#e65100',
|
|
3424
|
+
'accessor': '#546e7a',
|
|
3425
|
+
'other': '#757575',
|
|
3426
|
+
};
|
|
3427
|
+
// Interesting logic first: derived values and rules, then data access, then the rest.
|
|
3428
|
+
static KIND_ORDER = ['calculation', 'business-rule', 'query', 'mutation', 'accessor', 'other'];
|
|
3429
|
+
_data = inject(MAT_DIALOG_DATA);
|
|
3430
|
+
entityKey = this._data.entityKey;
|
|
3431
|
+
sourceLabel = this._data.source === 'model' ? 'Model functions' : 'Handler functions';
|
|
3432
|
+
// null until loaded — gates the list behind the skeleton.
|
|
3433
|
+
entityFunctions = signal(null);
|
|
3434
|
+
// The class this group's functions were discovered from.
|
|
3435
|
+
discoveredClass = computed(() => {
|
|
3436
|
+
const loaded = this.entityFunctions();
|
|
3437
|
+
return this._data.source === 'model' ? loaded?.modelClass : loaded?.handlerClass;
|
|
3438
|
+
});
|
|
3439
|
+
// This group's functions, ordered by kind priority then method name.
|
|
3440
|
+
functions = computed(() => this._sortFunctions((this.entityFunctions()?.functions ?? [])
|
|
3441
|
+
.filter((fn) => fn.source === this._data.source)));
|
|
3442
|
+
constructor() {
|
|
3443
|
+
inject(ReportData).semanticEntityFunctions(this._data.entityKey)
|
|
3444
|
+
.pipe(takeUntilDestroyed())
|
|
3445
|
+
.subscribe((entityFunctions) => {
|
|
3446
|
+
this.entityFunctions.set(entityFunctions);
|
|
3447
|
+
});
|
|
3448
|
+
}
|
|
3449
|
+
kindColor(kind) {
|
|
3450
|
+
return EntityFunctionsComponent.KIND_COLORS[kind] ?? EntityFunctionsComponent.KIND_COLORS.other;
|
|
3451
|
+
}
|
|
3452
|
+
_sortFunctions(functions) {
|
|
3453
|
+
return [...functions]
|
|
3454
|
+
.sort((a, b) => {
|
|
3455
|
+
const kindDiff = this._kindRank(a.kind) - this._kindRank(b.kind);
|
|
3456
|
+
return kindDiff !== 0 ? kindDiff : a.method.localeCompare(b.method);
|
|
3457
|
+
});
|
|
3458
|
+
}
|
|
3459
|
+
_kindRank(kind) {
|
|
3460
|
+
const rank = EntityFunctionsComponent.KIND_ORDER.indexOf(kind);
|
|
3461
|
+
return rank === -1 ? EntityFunctionsComponent.KIND_ORDER.length : rank;
|
|
3462
|
+
}
|
|
3463
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: EntityFunctionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3464
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: EntityFunctionsComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: "<fs-dialog>\n <fs-dialog-title>\n {{ entityKey }} \u2014 {{ sourceLabel }}\n </fs-dialog-title>\n <mat-dialog-content>\n <div *fsSkeleton=\"entityFunctions()\">\n @if (entityFunctions(); as fns) {\n @if (functions().length) {\n @if (fns.generatedAt) {\n <div class=\"hint functions-generated\">\n Discovered from {{ discoveredClass() }} ·\n <fs-date\n [date]=\"fns.generatedAt\"\n format=\"date-time\">\n </fs-date>\n </div>\n }\n <div class=\"functions-list\">\n @for (fn of functions(); track fn.class + '::' + fn.method) {\n <div class=\"function-row\">\n <div class=\"function-head\">\n <fs-chip\n size=\"tiny\"\n [backgroundColor]=\"kindColor(fn.kind)\"\n color=\"#ffffff\">\n {{ fn.kind }}\n </fs-chip>\n <code class=\"function-method\">{{ fn.method }}</code>\n </div>\n <div class=\"function-summary\">{{ fn.summary }}</div>\n @if (fn.produces) {\n <div class=\"function-produces hint\">Produces: {{ fn.produces }}</div>\n }\n </div>\n }\n </div>\n } @else {\n <div class=\"hint\">\n No functions cached for this entity yet \u2014 run a build to discover its code.\n </div>\n }\n }\n </div>\n </mat-dialog-content>\n <mat-dialog-actions>\n <button\n type=\"button\"\n mat-button\n color=\"primary\"\n [mat-dialog-close]=\"undefined\">\n Close\n </button>\n </mat-dialog-actions>\n</fs-dialog>\n", styles: [".hint{margin:0;color:#0009;font-size:13px;line-height:1.5}.functions-generated{margin-bottom:10px}.functions-list{display:flex;flex-direction:column}.functions-list .function-row{display:flex;flex-direction:column;gap:3px;padding:10px 0}.functions-list .function-row+.function-row{border-top:1px solid rgba(0,0,0,.08)}.functions-list .function-row .function-head{display:flex;align-items:center;gap:8px}.functions-list .function-row .function-method{font-family:monospace;font-size:13px;font-weight:600}.functions-list .function-row .function-summary{color:#000000bf}.functions-list .function-row .function-produces{padding-left:10px}\n"], dependencies: [{ kind: "component", type: MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "directive", type: MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "ngmodule", type: FsChipModule }, { kind: "component", type: i1$4.FsChipComponent, selector: "fs-chip", inputs: ["selectable", "removable", "value", "maxWidth", "width", "backgroundColor", "borderColor", "color", "shape", "outlined", "outlineDash", "icon", "image", "selected", "disabled", "padding", "contrastColor", "size"], outputs: ["selectedToggled", "removed", "click"] }, { kind: "ngmodule", type: FsDateModule }, { kind: "component", type: i2$4.FsDateComponent, selector: "fs-date", inputs: ["date", "format", "timezone"] }, { kind: "ngmodule", type: FsDialogModule }, { kind: "component", type: i3.FsDialogComponent, selector: "fs-dialog", inputs: ["mobileMode", "mobileButtonPlacement", "mobileWidth", "mode", "buttonLayout"] }, { kind: "component", type: i3.FsDialogTitleComponent, selector: "fs-dialog-title", inputs: ["close", "dockToggle", "back"] }, { kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i4$1.FsSkeletonContentDirective, selector: "[fsSkeleton]", inputs: ["fsSkeleton", "fsSkeletonPattern"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3465
|
+
}
|
|
3466
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: EntityFunctionsComponent, decorators: [{
|
|
3467
|
+
type: Component,
|
|
3468
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
|
|
3344
3469
|
MatButton,
|
|
3470
|
+
MatDialogContent,
|
|
3471
|
+
MatDialogActions,
|
|
3472
|
+
MatDialogClose,
|
|
3345
3473
|
FsChipModule,
|
|
3346
3474
|
FsDateModule,
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
], template: "<div
|
|
3350
|
-
}] });
|
|
3475
|
+
FsDialogModule,
|
|
3476
|
+
FsSkeletonModule,
|
|
3477
|
+
], template: "<fs-dialog>\n <fs-dialog-title>\n {{ entityKey }} \u2014 {{ sourceLabel }}\n </fs-dialog-title>\n <mat-dialog-content>\n <div *fsSkeleton=\"entityFunctions()\">\n @if (entityFunctions(); as fns) {\n @if (functions().length) {\n @if (fns.generatedAt) {\n <div class=\"hint functions-generated\">\n Discovered from {{ discoveredClass() }} ·\n <fs-date\n [date]=\"fns.generatedAt\"\n format=\"date-time\">\n </fs-date>\n </div>\n }\n <div class=\"functions-list\">\n @for (fn of functions(); track fn.class + '::' + fn.method) {\n <div class=\"function-row\">\n <div class=\"function-head\">\n <fs-chip\n size=\"tiny\"\n [backgroundColor]=\"kindColor(fn.kind)\"\n color=\"#ffffff\">\n {{ fn.kind }}\n </fs-chip>\n <code class=\"function-method\">{{ fn.method }}</code>\n </div>\n <div class=\"function-summary\">{{ fn.summary }}</div>\n @if (fn.produces) {\n <div class=\"function-produces hint\">Produces: {{ fn.produces }}</div>\n }\n </div>\n }\n </div>\n } @else {\n <div class=\"hint\">\n No functions cached for this entity yet \u2014 run a build to discover its code.\n </div>\n }\n }\n </div>\n </mat-dialog-content>\n <mat-dialog-actions>\n <button\n type=\"button\"\n mat-button\n color=\"primary\"\n [mat-dialog-close]=\"undefined\">\n Close\n </button>\n </mat-dialog-actions>\n</fs-dialog>\n", styles: [".hint{margin:0;color:#0009;font-size:13px;line-height:1.5}.functions-generated{margin-bottom:10px}.functions-list{display:flex;flex-direction:column}.functions-list .function-row{display:flex;flex-direction:column;gap:3px;padding:10px 0}.functions-list .function-row+.function-row{border-top:1px solid rgba(0,0,0,.08)}.functions-list .function-row .function-head{display:flex;align-items:center;gap:8px}.functions-list .function-row .function-method{font-family:monospace;font-size:13px;font-weight:600}.functions-list .function-row .function-summary{color:#000000bf}.functions-list .function-row .function-produces{padding-left:10px}\n"] }]
|
|
3478
|
+
}], ctorParameters: () => [] });
|
|
3351
3479
|
|
|
3352
|
-
// The Semantic Database tab: the
|
|
3353
|
-
//
|
|
3354
|
-
//
|
|
3355
|
-
// entity to reveal its shape, inlined
|
|
3356
|
-
//
|
|
3357
|
-
// functions
|
|
3358
|
-
//
|
|
3480
|
+
// The Semantic Database tab: the entity scoreboard the report AI is built from
|
|
3481
|
+
// (an entity = a table + a model + a handler). Shows the last build's status,
|
|
3482
|
+
// runs (or stops) a build — streamed into the FsProcess dock so it survives the
|
|
3483
|
+
// dialog closing — and drills into every entity to reveal its shape, inlined
|
|
3484
|
+
// enum values, computed fields and its cached function counts, whose links open
|
|
3485
|
+
// the functions dialog (the full function set across all entities is far too
|
|
3486
|
+
// big to ship with the scoreboard).
|
|
3359
3487
|
class SemanticDatabaseComponent {
|
|
3360
3488
|
// The build-in-flight states of the ff_processes row (queued/running/stopping).
|
|
3361
3489
|
static ACTIVE_STATES = ['Q', 'R', 'X'];
|
|
@@ -3372,17 +3500,6 @@ class SemanticDatabaseComponent {
|
|
|
3372
3500
|
// failure.
|
|
3373
3501
|
static DONE_COLOR = '#2e7d32';
|
|
3374
3502
|
static PENDING_COLOR = '#9e9e9e';
|
|
3375
|
-
// One color per AI classification kind, so a function's nature reads at a glance.
|
|
3376
|
-
static KIND_COLORS = {
|
|
3377
|
-
'calculation': '#1565c0',
|
|
3378
|
-
'business-rule': '#6a1b9a',
|
|
3379
|
-
'query': '#00695c',
|
|
3380
|
-
'mutation': '#e65100',
|
|
3381
|
-
'accessor': '#546e7a',
|
|
3382
|
-
'other': '#757575',
|
|
3383
|
-
};
|
|
3384
|
-
// Interesting logic first: derived values and rules, then data access, then the rest.
|
|
3385
|
-
static KIND_ORDER = ['calculation', 'business-rule', 'query', 'mutation', 'accessor', 'other'];
|
|
3386
3503
|
// The scoreboard's per-entity stage chips, left to right.
|
|
3387
3504
|
stageKeys = ['introspected', 'enumsInlined', 'computed', 'functions'];
|
|
3388
3505
|
stageLabels = {
|
|
@@ -3398,9 +3515,10 @@ class SemanticDatabaseComponent {
|
|
|
3398
3515
|
buildActive = computed(() => this.building()
|
|
3399
3516
|
|| SemanticDatabaseComponent.ACTIVE_STATES.includes(this.status()?.build?.state));
|
|
3400
3517
|
buildStateLabel = computed(() => SemanticDatabaseComponent.STATE_LABELS[this.status()?.build?.state] ?? '');
|
|
3401
|
-
//
|
|
3402
|
-
//
|
|
3403
|
-
|
|
3518
|
+
// Guards the outcome message to fire exactly once per run — whether the stream
|
|
3519
|
+
// completes (build finished, possibly failed) or errors outright.
|
|
3520
|
+
_buildFinalized = false;
|
|
3521
|
+
_dialog = inject(MatDialog);
|
|
3404
3522
|
_reportData = inject(ReportData);
|
|
3405
3523
|
_process = inject(FsProcess);
|
|
3406
3524
|
_prompt = inject(FsPrompt);
|
|
@@ -3441,22 +3559,59 @@ class SemanticDatabaseComponent {
|
|
|
3441
3559
|
// dismissing the dialog never interrupts a running build.
|
|
3442
3560
|
_runBuild() {
|
|
3443
3561
|
this.building.set(true);
|
|
3562
|
+
this._buildFinalized = false;
|
|
3444
3563
|
const target$ = this._reportData.semanticBuild()
|
|
3445
|
-
.pipe(
|
|
3564
|
+
.pipe(
|
|
3565
|
+
// A hard stream/transport failure (the build couldn't even start)
|
|
3566
|
+
// finalizes here — completed$ won't fire in that case.
|
|
3567
|
+
tap({ error: (error) => this._finalizeBuild(error) }), filter((event) => event?.type === StreamEventType.Data
|
|
3446
3568
|
&& event.data?.kind === 'log'
|
|
3447
3569
|
&& typeof event.data.line === 'string'), map((event) => event.data.line));
|
|
3448
3570
|
this._process
|
|
3449
3571
|
.run('Semantic database build', target$)
|
|
3450
3572
|
.completed$
|
|
3451
3573
|
.pipe(take(1), takeUntilDestroyed(this._destroyRef))
|
|
3452
|
-
.subscribe(() =>
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3574
|
+
.subscribe(() => this._finalizeBuild(null));
|
|
3575
|
+
}
|
|
3576
|
+
/**
|
|
3577
|
+
* Wrap up one build run exactly once. On a hard stream error there is no
|
|
3578
|
+
* status to read, so say so directly; otherwise refresh the status and
|
|
3579
|
+
* announce how the build actually ended — a build that failed (e.g. the AI
|
|
3580
|
+
* couldn't be reached) reports 'Failed' with a reason, and that must surface
|
|
3581
|
+
* instead of the run silently doing nothing.
|
|
3582
|
+
*/
|
|
3583
|
+
_finalizeBuild(streamError) {
|
|
3584
|
+
if (this._buildFinalized) {
|
|
3585
|
+
return;
|
|
3586
|
+
}
|
|
3587
|
+
this._buildFinalized = true;
|
|
3588
|
+
this.building.set(false);
|
|
3589
|
+
if (streamError) {
|
|
3590
|
+
this._message.error('The semantic database build could not run — check the AI connection and try again.');
|
|
3457
3591
|
this._refreshStatus();
|
|
3592
|
+
return;
|
|
3593
|
+
}
|
|
3594
|
+
this._reportData.semanticBuildStatus()
|
|
3595
|
+
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
3596
|
+
.subscribe((status) => {
|
|
3597
|
+
this.status.set(status);
|
|
3598
|
+
this._announceOutcome(status);
|
|
3458
3599
|
});
|
|
3459
3600
|
}
|
|
3601
|
+
/**
|
|
3602
|
+
* Tell the operator how the build ended: a failed/stopped build surfaces its
|
|
3603
|
+
* reason (so an AI connection problem is never silent), a completed one
|
|
3604
|
+
* confirms success.
|
|
3605
|
+
*/
|
|
3606
|
+
_announceOutcome(status) {
|
|
3607
|
+
const build = status?.build;
|
|
3608
|
+
if (build?.state === 'F' || build?.state === 'K') {
|
|
3609
|
+
this._message.error(build.message || 'The semantic database build did not finish.');
|
|
3610
|
+
}
|
|
3611
|
+
else if (build?.state === 'C') {
|
|
3612
|
+
this._message.success('Semantic database build complete.');
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3460
3615
|
stageColor(stage) {
|
|
3461
3616
|
return stage === 'done'
|
|
3462
3617
|
? SemanticDatabaseComponent.DONE_COLOR
|
|
@@ -3465,37 +3620,18 @@ class SemanticDatabaseComponent {
|
|
|
3465
3620
|
stageTooltip(stage) {
|
|
3466
3621
|
return stage === 'done' ? 'Built' : 'Needs building';
|
|
3467
3622
|
}
|
|
3468
|
-
kindColor(kind) {
|
|
3469
|
-
return SemanticDatabaseComponent.KIND_COLORS[kind] ?? SemanticDatabaseComponent.KIND_COLORS.other;
|
|
3470
|
-
}
|
|
3471
|
-
/**
|
|
3472
|
-
* The loaded function cache for an entity: undefined until the panel is first
|
|
3473
|
-
* expanded, null while the request is in flight (the skeleton window), then
|
|
3474
|
-
* the loaded cache.
|
|
3475
|
-
*/
|
|
3476
|
-
entityFunctions(key) {
|
|
3477
|
-
return this._entityFunctions()[key];
|
|
3478
|
-
}
|
|
3479
3623
|
/**
|
|
3480
|
-
*
|
|
3481
|
-
*
|
|
3482
|
-
* the
|
|
3624
|
+
* Open the child dialog listing one group of an entity's cached functions —
|
|
3625
|
+
* the model class's or the handler class's. The dialog fetches fresh on open,
|
|
3626
|
+
* so the tab never holds function payloads.
|
|
3483
3627
|
*/
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
.
|
|
3491
|
-
.subscribe((entityFunctions) => {
|
|
3492
|
-
this._entityFunctions.update((loaded) => ({
|
|
3493
|
-
...loaded,
|
|
3494
|
-
[entity.key]: {
|
|
3495
|
-
...entityFunctions,
|
|
3496
|
-
functions: this._sortFunctions(entityFunctions),
|
|
3497
|
-
},
|
|
3498
|
-
}));
|
|
3628
|
+
openFunctions(entity, source) {
|
|
3629
|
+
this._dialog.open(EntityFunctionsComponent, {
|
|
3630
|
+
width: '700px',
|
|
3631
|
+
maxWidth: '95vw',
|
|
3632
|
+
maxHeight: '85vh',
|
|
3633
|
+
autoFocus: false,
|
|
3634
|
+
data: { entityKey: entity.key, source },
|
|
3499
3635
|
});
|
|
3500
3636
|
}
|
|
3501
3637
|
_init() {
|
|
@@ -3506,23 +3642,8 @@ class SemanticDatabaseComponent {
|
|
|
3506
3642
|
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
3507
3643
|
.subscribe((status) => this.status.set(status));
|
|
3508
3644
|
}
|
|
3509
|
-
/**
|
|
3510
|
-
* Order once at load time (kind priority, then method name) so the template
|
|
3511
|
-
* renders the list as-is.
|
|
3512
|
-
*/
|
|
3513
|
-
_sortFunctions(entityFunctions) {
|
|
3514
|
-
return [...entityFunctions.functions]
|
|
3515
|
-
.sort((a, b) => {
|
|
3516
|
-
const kindDiff = this._kindRank(a.kind) - this._kindRank(b.kind);
|
|
3517
|
-
return kindDiff !== 0 ? kindDiff : a.method.localeCompare(b.method);
|
|
3518
|
-
});
|
|
3519
|
-
}
|
|
3520
|
-
_kindRank(kind) {
|
|
3521
|
-
const rank = SemanticDatabaseComponent.KIND_ORDER.indexOf(kind);
|
|
3522
|
-
return rank === -1 ? SemanticDatabaseComponent.KIND_ORDER.length : rank;
|
|
3523
|
-
}
|
|
3524
3645
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SemanticDatabaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3525
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SemanticDatabaseComponent, isStandalone: true, selector: "fs-ai-report-global-settings-semantic-database", ngImport: i0, template: "<div class=\"fs-column tab-body\">\n <p class=\"hint\">\n The semantic database is what the report AI uses to understand your data \u2014\n tables, relationships and the calculations in code. Rebuild it after\n significant application updates. The build runs in the background; progress\n appears in the dock at the bottom right and this dialog can be closed while\n it runs.\n </p>\n\n <div *fsSkeleton=\"status()\">\n @if (status(); as buildStatus) {\n <div class=\"build-status\">\n @if (buildStatus.detail.generatedAt) {\n <div class=\"stat\">\n <span class=\"label\">Last built</span>\n <fs-date\n [date]=\"buildStatus.detail.generatedAt\"\n format=\"date-time\">\n </fs-date>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Coverage</span>\n <span>\n {{ buildStatus.detail.entityCount }} entities \u00B7\n {{ buildStatus.detail.functionCount }} code functions\n </span>\n </div>\n } @else {\n <fs-message-warning>\n The semantic database has not been built yet \u2014 the report AI can't\n answer data questions until it is. Run the first build.\n </fs-message-warning>\n }\n @if (buildStatus.build) {\n <div class=\"stat\">\n <span class=\"label\">Last build run</span>\n <span>\n {{ buildStateLabel() }}\n @if (buildStatus.build.message) {\n \u2014 {{ buildStatus.build.message }}\n }\n </span>\n </div>\n }\n </div>\n\n <!-- The model scoreboard: every entity the AI knows about, its\n built-vs-needs-building stages, and a drill-down into its shape, enum\n values, computed fields and AI-classified code functions. -->\n @if (buildStatus.detail.entities?.length) {\n <mat-accordion class=\"entities\">\n @for (entity of buildStatus.detail.entities; track entity.key) {\n <mat-expansion-panel (opened)=\"entityOpened(entity)\">\n <mat-expansion-panel-header>\n <mat-panel-title>{{ entity.key }}</mat-panel-title>\n <mat-panel-description>\n <div class=\"stage-chips\">\n @for (stage of stageKeys; track stage) {\n <fs-chip\n size=\"tiny\"\n [backgroundColor]=\"stageColor(entity.stages[stage])\"\n color=\"#ffffff\"\n [matTooltip]=\"stageTooltip(entity.stages[stage])\">\n {{ stageLabels[stage] }}\n </fs-chip>\n }\n </div>\n </mat-panel-description>\n </mat-expansion-panel-header>\n\n <!-- Deferred: the body (and its functions request) only exists\n once expanded. -->\n <ng-template matExpansionPanelContent>\n <div class=\"entity-detail\">\n <div><strong>Table:</strong> {{ entity.table }}</div>\n <div><strong>Columns:</strong> {{ entity.columns }}</div>\n <div><strong>Relationships:</strong> {{ entity.relationships }}</div>\n @if (entity.enums.length) {\n <div class=\"enum-list\">\n <strong>Enum values:</strong>\n @for (enumColumn of entity.enums; track enumColumn.column) {\n <div class=\"enum-row\">\n <span class=\"enum-column\">{{ enumColumn.column }}</span>\n <span class=\"enum-values\">{{ enumColumn.values.join(', ') }}</span>\n </div>\n }\n </div>\n }\n @if (entity.computedFields.length) {\n <div><strong>Computed fields:</strong> {{ entity.computedFields.join(', ') }}</div>\n }\n\n <div class=\"functions-section\">\n <h3 class=\"functions-heading\">\n Code functions\n @if (entity.functionCount) {\n <span class=\"functions-count\">({{ entity.functionCount }})</span>\n }\n </h3>\n\n <div class=\"functions-body\" *fsSkeleton=\"entityFunctions(entity.key)\">\n @if (entityFunctions(entity.key); as fns) {\n @if (fns.functions.length) {\n @if (fns.generatedAt) {\n <div class=\"hint functions-generated\">\n Discovered from {{ fns.class }} ·\n <fs-date\n [date]=\"fns.generatedAt\"\n format=\"date-time\">\n </fs-date>\n </div>\n }\n <div class=\"functions-list\">\n @for (fn of fns.functions; track fn.method) {\n <div class=\"function-row\">\n <div class=\"function-head\">\n <fs-chip\n size=\"tiny\"\n [backgroundColor]=\"kindColor(fn.kind)\"\n color=\"#ffffff\">\n {{ fn.kind }}\n </fs-chip>\n <code class=\"function-method\">{{ fn.method }}</code>\n </div>\n <div class=\"function-summary\">{{ fn.summary }}</div>\n @if (fn.produces) {\n <div class=\"function-produces hint\">Produces: {{ fn.produces }}</div>\n }\n </div>\n }\n </div>\n } @else {\n <div class=\"hint\">\n No functions cached for this entity yet \u2014 run a build to discover its code.\n </div>\n }\n }\n </div>\n </div>\n </div>\n </ng-template>\n </mat-expansion-panel>\n }\n </mat-accordion>\n }\n }\n </div>\n</div>\n", styles: [".tab-body{padding-top:16px}.hint{margin:0 0 12px;color:#0009;font-size:13px;line-height:1.5}.build-status{display:flex;flex-direction:column;gap:8px}.build-status .stat{display:flex;gap:8px}.build-status .stat .label{min-width:110px;color:#0009}.entities{display:block;padding:3px}.entities .stage-chips{display:flex;flex-wrap:wrap;gap:4px}.entities .entity-detail{display:flex;flex-direction:column;gap:5px}.entities .entity-detail .enum-list{display:flex;flex-direction:column;gap:3px}.entities .entity-detail .enum-list .enum-row{display:flex;gap:10px;padding-left:10px}.entities .entity-detail .enum-list .enum-row .enum-column{min-width:140px;font-weight:600}.entities .entity-detail .enum-list .enum-row .enum-values{color:#0009}.entities .entity-detail .functions-section{margin-top:10px}.entities .entity-detail .functions-section .functions-heading{margin:0 0 5px;font-size:15px}.entities .entity-detail .functions-section .functions-heading .functions-count{font-weight:400;color:#0009}.entities .entity-detail .functions-section .functions-generated{margin-bottom:10px}.entities .entity-detail .functions-section .functions-list{display:flex;flex-direction:column}.entities .entity-detail .functions-section .functions-list .function-row{display:flex;flex-direction:column;gap:3px;padding:10px 0}.entities .entity-detail .functions-section .functions-list .function-row+.function-row{border-top:1px solid rgba(0,0,0,.08)}.entities .entity-detail .functions-section .functions-list .function-row .function-head{display:flex;align-items:center;gap:8px}.entities .entity-detail .functions-section .functions-list .function-row .function-method{font-family:monospace;font-size:13px;font-weight:600}.entities .entity-detail .functions-section .functions-list .function-row .function-summary{color:#000000bf}.entities .entity-detail .functions-section .functions-list .function-row .function-produces{padding-left:10px}\n"], dependencies: [{ kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i1$5.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i1$5.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i1$5.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i1$5.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i1$5.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "directive", type: i1$5.MatExpansionPanelContent, selector: "ng-template[matExpansionPanelContent]" }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: FsChipModule }, { kind: "component", type: i1$4.FsChipComponent, selector: "fs-chip", inputs: ["selectable", "removable", "value", "maxWidth", "width", "backgroundColor", "borderColor", "color", "shape", "outlined", "outlineDash", "icon", "image", "selected", "disabled", "padding", "contrastColor", "size"], outputs: ["selectedToggled", "removed", "click"] }, { kind: "ngmodule", type: FsDateModule }, { kind: "component", type: i2$4.FsDateComponent, selector: "fs-date", inputs: ["date", "format", "timezone"] }, { kind: "ngmodule", type: FsMessageModule }, { kind: "component", type: i4.FsMessageWarningComponent, selector: "fs-message-warning" }, { kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i6$1.FsSkeletonContentDirective, selector: "[fsSkeleton]", inputs: ["fsSkeleton", "fsSkeletonPattern"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3646
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SemanticDatabaseComponent, isStandalone: true, selector: "fs-ai-report-global-settings-semantic-database", ngImport: i0, template: "<div class=\"fs-column tab-body\">\n <p class=\"hint\">\n The semantic database is what the report AI uses to understand your data \u2014\n tables, relationships and the calculations in code. Rebuild it after\n significant application updates. The build runs in the background; progress\n appears in the dock at the bottom right and this dialog can be closed while\n it runs.\n </p>\n\n <div *fsSkeleton=\"status()\">\n @if (status(); as buildStatus) {\n <div class=\"build-status\">\n @if (buildStatus.detail.generatedAt) {\n <div class=\"stat\">\n <span class=\"label\">Last built</span>\n <fs-date\n [date]=\"buildStatus.detail.generatedAt\"\n format=\"date-time\">\n </fs-date>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Coverage</span>\n <span>\n {{ buildStatus.detail.entityCount }} entities \u00B7\n {{ buildStatus.detail.functionCount }} code functions\n </span>\n </div>\n } @else {\n <fs-message-warning>\n The semantic database has not been built yet \u2014 the report AI can't\n answer data questions until it is. Run the first build.\n </fs-message-warning>\n }\n @if (buildStatus.build) {\n <div class=\"stat\">\n <span class=\"label\">Last build run</span>\n <span>\n {{ buildStateLabel() }}\n @if (buildStatus.build.message) {\n \u2014 {{ buildStatus.build.message }}\n }\n </span>\n </div>\n }\n </div>\n\n <!-- The entity scoreboard: every entity the AI knows about (an entity =\n a table + a model + a handler), its built-vs-needs-building stages,\n and a drill-down into its shape, enum values, computed fields and\n cached function counts \u2014 the counts link into the functions dialog. -->\n @if (buildStatus.detail.entities?.length) {\n <h2>Entities</h2>\n <mat-accordion class=\"entities\">\n @for (entity of buildStatus.detail.entities; track entity.key) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>{{ entity.key }}</mat-panel-title>\n <mat-panel-description>\n <div class=\"stage-chips\">\n @for (stage of stageKeys; track stage) {\n <fs-chip\n size=\"tiny\"\n [backgroundColor]=\"stageColor(entity.stages[stage])\"\n color=\"#ffffff\"\n [matTooltip]=\"stageTooltip(entity.stages[stage])\">\n {{ stageLabels[stage] }}\n </fs-chip>\n }\n </div>\n </mat-panel-description>\n </mat-expansion-panel-header>\n\n <!-- Deferred: the body only exists once expanded. -->\n <ng-template matExpansionPanelContent>\n <div class=\"entity-detail\">\n <div class=\"stat\">\n <span class=\"label\">Table</span>\n <span>{{ entity.table }}</span>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Columns</span>\n <span>{{ entity.columns }}</span>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Relationships</span>\n <span>{{ entity.relationships }}</span>\n </div>\n @if (entity.enums.length) {\n <div class=\"stat\">\n <span class=\"label\">Enumeration values</span>\n <div class=\"enum-list\">\n @for (enumColumn of entity.enums; track enumColumn.column) {\n <div class=\"enum-row\">\n <span class=\"enum-column\">{{ enumColumn.column }}</span>\n <span>{{ enumColumn.values.join(', ') }}</span>\n </div>\n }\n </div>\n </div>\n }\n @if (entity.computedFields.length) {\n <div class=\"stat\">\n <span class=\"label\">Computed fields</span>\n <span>{{ entity.computedFields.join(', ') }}</span>\n </div>\n }\n <div class=\"stat\">\n <span class=\"label\">Model functions</span>\n @if (entity.modelFunctionCount) {\n <a\n class=\"count-link\"\n (click)=\"openFunctions(entity, 'model')\">\n {{ entity.modelFunctionCount }}\n </a>\n } @else {\n <span>0</span>\n }\n </div>\n <div class=\"stat\">\n <span class=\"label\">Handler functions</span>\n @if (entity.handlerFunctionCount) {\n <a\n class=\"count-link\"\n (click)=\"openFunctions(entity, 'handler')\">\n {{ entity.handlerFunctionCount }}\n </a>\n } @else {\n <span>0</span>\n }\n </div>\n </div>\n </ng-template>\n </mat-expansion-panel>\n }\n </mat-accordion>\n }\n }\n </div>\n</div>\n", styles: [".hint{margin:0 0 12px;color:#0009;font-size:13px;line-height:1.5}.stat{display:flex;align-items:baseline;gap:12px}.stat>.label{flex:0 0 150px;white-space:nowrap;color:#0009}.build-status{display:flex;flex-direction:column;gap:8px}.entities{display:block;padding:3px}.entities mat-panel-description{justify-content:flex-end;margin-right:8px}.entities .stage-chips{display:flex;gap:4px}.entities .entity-detail{display:flex;flex-direction:column;gap:8px}.entities .entity-detail .enum-list{display:flex;flex-direction:column;gap:3px}.entities .entity-detail .enum-list .enum-row{display:flex;gap:12px}.entities .entity-detail .enum-list .enum-row .enum-column{flex:0 0 140px;white-space:nowrap;color:#0009}\n"], dependencies: [{ kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i1$5.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i1$5.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i1$5.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i1$5.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i1$5.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "directive", type: i1$5.MatExpansionPanelContent, selector: "ng-template[matExpansionPanelContent]" }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: FsChipModule }, { kind: "component", type: i1$4.FsChipComponent, selector: "fs-chip", inputs: ["selectable", "removable", "value", "maxWidth", "width", "backgroundColor", "borderColor", "color", "shape", "outlined", "outlineDash", "icon", "image", "selected", "disabled", "padding", "contrastColor", "size"], outputs: ["selectedToggled", "removed", "click"] }, { kind: "ngmodule", type: FsDateModule }, { kind: "component", type: i2$4.FsDateComponent, selector: "fs-date", inputs: ["date", "format", "timezone"] }, { kind: "ngmodule", type: FsMessageModule }, { kind: "component", type: i4.FsMessageWarningComponent, selector: "fs-message-warning" }, { kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i4$1.FsSkeletonContentDirective, selector: "[fsSkeleton]", inputs: ["fsSkeleton", "fsSkeletonPattern"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3526
3647
|
}
|
|
3527
3648
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SemanticDatabaseComponent, decorators: [{
|
|
3528
3649
|
type: Component,
|
|
@@ -3533,7 +3654,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3533
3654
|
FsDateModule,
|
|
3534
3655
|
FsMessageModule,
|
|
3535
3656
|
FsSkeletonModule,
|
|
3536
|
-
], template: "<div class=\"fs-column tab-body\">\n <p class=\"hint\">\n The semantic database is what the report AI uses to understand your data \u2014\n tables, relationships and the calculations in code. Rebuild it after\n significant application updates. The build runs in the background; progress\n appears in the dock at the bottom right and this dialog can be closed while\n it runs.\n </p>\n\n <div *fsSkeleton=\"status()\">\n @if (status(); as buildStatus) {\n <div class=\"build-status\">\n @if (buildStatus.detail.generatedAt) {\n <div class=\"stat\">\n <span class=\"label\">Last built</span>\n <fs-date\n [date]=\"buildStatus.detail.generatedAt\"\n format=\"date-time\">\n </fs-date>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Coverage</span>\n <span>\n {{ buildStatus.detail.entityCount }} entities \u00B7\n {{ buildStatus.detail.functionCount }} code functions\n </span>\n </div>\n } @else {\n <fs-message-warning>\n The semantic database has not been built yet \u2014 the report AI can't\n answer data questions until it is. Run the first build.\n </fs-message-warning>\n }\n @if (buildStatus.build) {\n <div class=\"stat\">\n <span class=\"label\">Last build run</span>\n <span>\n {{ buildStateLabel() }}\n @if (buildStatus.build.message) {\n \u2014 {{ buildStatus.build.message }}\n }\n </span>\n </div>\n }\n </div>\n\n <!-- The
|
|
3657
|
+
], template: "<div class=\"fs-column tab-body\">\n <p class=\"hint\">\n The semantic database is what the report AI uses to understand your data \u2014\n tables, relationships and the calculations in code. Rebuild it after\n significant application updates. The build runs in the background; progress\n appears in the dock at the bottom right and this dialog can be closed while\n it runs.\n </p>\n\n <div *fsSkeleton=\"status()\">\n @if (status(); as buildStatus) {\n <div class=\"build-status\">\n @if (buildStatus.detail.generatedAt) {\n <div class=\"stat\">\n <span class=\"label\">Last built</span>\n <fs-date\n [date]=\"buildStatus.detail.generatedAt\"\n format=\"date-time\">\n </fs-date>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Coverage</span>\n <span>\n {{ buildStatus.detail.entityCount }} entities \u00B7\n {{ buildStatus.detail.functionCount }} code functions\n </span>\n </div>\n } @else {\n <fs-message-warning>\n The semantic database has not been built yet \u2014 the report AI can't\n answer data questions until it is. Run the first build.\n </fs-message-warning>\n }\n @if (buildStatus.build) {\n <div class=\"stat\">\n <span class=\"label\">Last build run</span>\n <span>\n {{ buildStateLabel() }}\n @if (buildStatus.build.message) {\n \u2014 {{ buildStatus.build.message }}\n }\n </span>\n </div>\n }\n </div>\n\n <!-- The entity scoreboard: every entity the AI knows about (an entity =\n a table + a model + a handler), its built-vs-needs-building stages,\n and a drill-down into its shape, enum values, computed fields and\n cached function counts \u2014 the counts link into the functions dialog. -->\n @if (buildStatus.detail.entities?.length) {\n <h2>Entities</h2>\n <mat-accordion class=\"entities\">\n @for (entity of buildStatus.detail.entities; track entity.key) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>{{ entity.key }}</mat-panel-title>\n <mat-panel-description>\n <div class=\"stage-chips\">\n @for (stage of stageKeys; track stage) {\n <fs-chip\n size=\"tiny\"\n [backgroundColor]=\"stageColor(entity.stages[stage])\"\n color=\"#ffffff\"\n [matTooltip]=\"stageTooltip(entity.stages[stage])\">\n {{ stageLabels[stage] }}\n </fs-chip>\n }\n </div>\n </mat-panel-description>\n </mat-expansion-panel-header>\n\n <!-- Deferred: the body only exists once expanded. -->\n <ng-template matExpansionPanelContent>\n <div class=\"entity-detail\">\n <div class=\"stat\">\n <span class=\"label\">Table</span>\n <span>{{ entity.table }}</span>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Columns</span>\n <span>{{ entity.columns }}</span>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Relationships</span>\n <span>{{ entity.relationships }}</span>\n </div>\n @if (entity.enums.length) {\n <div class=\"stat\">\n <span class=\"label\">Enumeration values</span>\n <div class=\"enum-list\">\n @for (enumColumn of entity.enums; track enumColumn.column) {\n <div class=\"enum-row\">\n <span class=\"enum-column\">{{ enumColumn.column }}</span>\n <span>{{ enumColumn.values.join(', ') }}</span>\n </div>\n }\n </div>\n </div>\n }\n @if (entity.computedFields.length) {\n <div class=\"stat\">\n <span class=\"label\">Computed fields</span>\n <span>{{ entity.computedFields.join(', ') }}</span>\n </div>\n }\n <div class=\"stat\">\n <span class=\"label\">Model functions</span>\n @if (entity.modelFunctionCount) {\n <a\n class=\"count-link\"\n (click)=\"openFunctions(entity, 'model')\">\n {{ entity.modelFunctionCount }}\n </a>\n } @else {\n <span>0</span>\n }\n </div>\n <div class=\"stat\">\n <span class=\"label\">Handler functions</span>\n @if (entity.handlerFunctionCount) {\n <a\n class=\"count-link\"\n (click)=\"openFunctions(entity, 'handler')\">\n {{ entity.handlerFunctionCount }}\n </a>\n } @else {\n <span>0</span>\n }\n </div>\n </div>\n </ng-template>\n </mat-expansion-panel>\n }\n </mat-accordion>\n }\n }\n </div>\n</div>\n", styles: [".hint{margin:0 0 12px;color:#0009;font-size:13px;line-height:1.5}.stat{display:flex;align-items:baseline;gap:12px}.stat>.label{flex:0 0 150px;white-space:nowrap;color:#0009}.build-status{display:flex;flex-direction:column;gap:8px}.entities{display:block;padding:3px}.entities mat-panel-description{justify-content:flex-end;margin-right:8px}.entities .stage-chips{display:flex;gap:4px}.entities .entity-detail{display:flex;flex-direction:column;gap:8px}.entities .entity-detail .enum-list{display:flex;flex-direction:column;gap:3px}.entities .entity-detail .enum-list .enum-row{display:flex;gap:12px}.entities .entity-detail .enum-list .enum-row .enum-column{flex:0 0 140px;white-space:nowrap;color:#0009}\n"] }]
|
|
3537
3658
|
}], ctorParameters: () => [] });
|
|
3538
3659
|
|
|
3539
3660
|
// Matches the backend cap (ReportsView::INSTRUCTIONS_MAX_LENGTH) — the document
|
|
@@ -3559,12 +3680,14 @@ const SCAFFOLD = [
|
|
|
3559
3680
|
// the dialog frame, the tab group, and a per-tab footer. Each tab is its own
|
|
3560
3681
|
// component —
|
|
3561
3682
|
// AI Knowledge — the operator-authored markdown document appended to the
|
|
3562
|
-
// report agent's system instructions on every chat turn.
|
|
3563
|
-
// History
|
|
3683
|
+
// report agent's system instructions on every chat turn. Its footer carries a
|
|
3684
|
+
// View History button opening the version-history dialog (restores happen
|
|
3685
|
+
// there; the shell refreshes on close).
|
|
3564
3686
|
// Semantic Database — the build scoreboard the AI is built from, with a
|
|
3565
3687
|
// self-contained Build action.
|
|
3566
|
-
//
|
|
3567
|
-
//
|
|
3688
|
+
// General — report-module settings (the semantic-build concurrency).
|
|
3689
|
+
// The shell owns the shared data (the instructions document, the settings for
|
|
3690
|
+
// General) plus the tab-aware save; everything else lives in the children.
|
|
3568
3691
|
class GlobalSettingsComponent {
|
|
3569
3692
|
selectedTab = 'knowledge';
|
|
3570
3693
|
// The Semantic Database tab component, so the shared footer can drive its
|
|
@@ -3577,48 +3700,80 @@ class GlobalSettingsComponent {
|
|
|
3577
3700
|
maxLength: MAX_LENGTH,
|
|
3578
3701
|
placeholder: 'Terminology, business rules and special cases the report AI should know…',
|
|
3579
3702
|
};
|
|
3580
|
-
// null until loaded —
|
|
3703
|
+
// Both null until loaded — the form waits behind the skeleton until each is in.
|
|
3581
3704
|
instructions = signal(null);
|
|
3705
|
+
settings = signal(null);
|
|
3706
|
+
// The General tab's concurrency draft (bound to its field), seeded from settings.
|
|
3707
|
+
concurrency = signal(0);
|
|
3708
|
+
// Gate the whole form on both loads, so every tab's fields are ready to render.
|
|
3709
|
+
loaded = computed(() => !!this.instructions() && !!this.settings());
|
|
3582
3710
|
_dialogRef = inject(MatDialogRef);
|
|
3711
|
+
_dialog = inject(MatDialog);
|
|
3583
3712
|
_reportData = inject(ReportData);
|
|
3584
3713
|
_message = inject(FsMessage);
|
|
3585
|
-
_prompt = inject(FsPrompt);
|
|
3586
3714
|
_destroyRef = inject(DestroyRef);
|
|
3587
3715
|
constructor() {
|
|
3588
3716
|
this._init();
|
|
3589
3717
|
}
|
|
3590
|
-
// Save
|
|
3718
|
+
// The footer Save is shared, so it saves whatever the current tab owns: the
|
|
3719
|
+
// General tab persists its settings (and stays open); AI Knowledge saves a new
|
|
3720
|
+
// document version and closes.
|
|
3591
3721
|
save = () => {
|
|
3722
|
+
if (this.selectedTab === 'general') {
|
|
3723
|
+
return this._reportData.saveSettings({ semanticBuildConcurrency: this.concurrency() })
|
|
3724
|
+
.pipe(tap((settings) => {
|
|
3725
|
+
this._applySettings(settings);
|
|
3726
|
+
this._message.success('Settings saved');
|
|
3727
|
+
}));
|
|
3728
|
+
}
|
|
3592
3729
|
return this._reportData.saveInstructions(this.content())
|
|
3593
3730
|
.pipe(tap((instructions) => {
|
|
3594
3731
|
this._message.success('AI knowledge saved');
|
|
3595
3732
|
this._dialogRef.close(instructions);
|
|
3596
3733
|
}));
|
|
3597
3734
|
};
|
|
3598
|
-
//
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3735
|
+
// Open the version-history dialog. Restores happen inside it, so when it
|
|
3736
|
+
// closes (however it closes) re-fetch the document: a restore changed the
|
|
3737
|
+
// current version and must reseed the editor; otherwise just refresh the
|
|
3738
|
+
// version list without touching an unsaved draft.
|
|
3739
|
+
openHistory() {
|
|
3740
|
+
const currentId = this.instructions()?.instructions?.id;
|
|
3741
|
+
this._dialog.open(InstructionHistoryComponent, {
|
|
3742
|
+
width: '800px',
|
|
3743
|
+
maxWidth: '95vw',
|
|
3744
|
+
maxHeight: '85vh',
|
|
3745
|
+
autoFocus: false,
|
|
3746
|
+
data: { versions: this.instructions()?.versions ?? [] },
|
|
3604
3747
|
})
|
|
3605
|
-
.
|
|
3748
|
+
.afterClosed()
|
|
3749
|
+
.pipe(switchMap(() => this._reportData.instructions()), takeUntilDestroyed(this._destroyRef))
|
|
3606
3750
|
.subscribe((instructions) => {
|
|
3607
|
-
|
|
3608
|
-
|
|
3751
|
+
if (instructions.instructions?.id !== currentId) {
|
|
3752
|
+
this._applyInstructions(instructions);
|
|
3753
|
+
}
|
|
3754
|
+
else {
|
|
3755
|
+
this.instructions.set(instructions);
|
|
3756
|
+
}
|
|
3609
3757
|
});
|
|
3610
3758
|
}
|
|
3611
3759
|
_init() {
|
|
3612
3760
|
this._reportData.instructions()
|
|
3613
3761
|
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
3614
3762
|
.subscribe((instructions) => this._applyInstructions(instructions));
|
|
3763
|
+
this._reportData.settings()
|
|
3764
|
+
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
3765
|
+
.subscribe((settings) => this._applySettings(settings));
|
|
3615
3766
|
}
|
|
3616
3767
|
_applyInstructions(instructions) {
|
|
3617
3768
|
this.instructions.set(instructions);
|
|
3618
3769
|
this.content.set(instructions.instructions?.content ?? SCAFFOLD);
|
|
3619
3770
|
}
|
|
3771
|
+
_applySettings(settings) {
|
|
3772
|
+
this.settings.set(settings);
|
|
3773
|
+
this.concurrency.set(settings.semanticBuildConcurrency);
|
|
3774
|
+
}
|
|
3620
3775
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GlobalSettingsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3621
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: GlobalSettingsComponent, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "semanticDatabase", first: true, predicate: SemanticDatabaseComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<form\n *fsSkeletonForm=\"
|
|
3776
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: GlobalSettingsComponent, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "semanticDatabase", first: true, predicate: SemanticDatabaseComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<form\n *fsSkeletonForm=\"loaded()\"\n fsForm\n [submit]=\"save\">\n <fs-dialog>\n <fs-dialog-title>\n Report Settings\n </fs-dialog-title>\n <mat-dialog-content>\n <mat-tab-group [(selected)]=\"selectedTab\">\n <mat-tab\n label=\"AI Knowledge\"\n name=\"knowledge\">\n <fs-ai-report-global-settings-ai-knowledge\n [content]=\"content()\"\n (contentChange)=\"content.set($event)\"\n [config]=\"editorConfig\">\n </fs-ai-report-global-settings-ai-knowledge>\n </mat-tab>\n <mat-tab\n label=\"Semantic Database\"\n name=\"build\">\n <fs-ai-report-global-settings-semantic-database>\n </fs-ai-report-global-settings-semantic-database>\n </mat-tab>\n <mat-tab\n label=\"General\"\n name=\"general\">\n <fs-ai-report-global-settings-general\n [concurrency]=\"concurrency()\"\n (concurrencyChange)=\"concurrency.set($event)\"\n [min]=\"settings()!.semanticBuildConcurrencyMin\"\n [max]=\"settings()!.semanticBuildConcurrencyMax\">\n </fs-ai-report-global-settings-general>\n </mat-tab>\n </mat-tab-group>\n </mat-dialog-content>\n <mat-dialog-actions>\n <!-- The footer follows the selected tab. AI Knowledge projects its own\n Save | View History | Cancel row (fs-form-dialog-actions renders\n projected content after Save/Cancel, so the default pair can't keep\n View History beside Save); General uses the default Save/Cancel; the\n Semantic Database tab is a read/run surface, so it gets a primary\n Close plus its Build/Rebuild/Stop action. -->\n <fs-form-dialog-actions [save]=\"selectedTab === 'general'\">\n @if (selectedTab === 'knowledge') {\n <button\n type=\"submit\"\n mat-button\n color=\"primary\">\n Save\n </button>\n <button\n type=\"button\"\n mat-button\n (click)=\"openHistory()\">\n View History\n </button>\n <button\n type=\"button\"\n mat-button\n [mat-dialog-close]=\"null\">\n Cancel\n </button>\n }\n @if (selectedTab === 'build') {\n <button\n type=\"button\"\n mat-button\n color=\"primary\"\n [mat-dialog-close]=\"undefined\">\n Close\n </button>\n }\n @if (selectedTab === 'build' && semanticDatabase(); as db) {\n @if (db.buildActive()) {\n <button\n type=\"button\"\n mat-button\n color=\"warn\"\n (click)=\"db.stopBuild()\">\n Stop Build\n </button>\n } @else {\n @if (db.status(); as buildStatus) {\n <button\n type=\"button\"\n mat-button\n (click)=\"db.build()\">\n {{ buildStatus.detail.generatedAt ? 'Rebuild' : 'Build' }}\n </button>\n }\n }\n }\n </fs-form-dialog-actions>\n </mat-dialog-actions>\n </fs-dialog>\n</form>\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FsDialogModule }, { kind: "component", type: i3.FsDialogComponent, selector: "fs-dialog", inputs: ["mobileMode", "mobileButtonPlacement", "mobileWidth", "mode", "buttonLayout"] }, { kind: "component", type: i3.FsDialogTitleComponent, selector: "fs-dialog-title", inputs: ["close", "dockToggle", "back"] }, { kind: "ngmodule", type: FsFormModule }, { kind: "directive", type: i2$1.FsFormDirective, selector: "[fsForm]", inputs: ["wrapperSelector", "messageSelector", "hintSelector", "labelSelector", "autocomplete", "shortcuts", "confirm", "confirmDialog", "confirmDrawer", "confirmBrowser", "dirtySubmitButton", "submit", "successDelay", "errorDelay", "deactivationGuard"], outputs: ["fsForm", "invalid", "valid", "submitted", "reseted", "cleared"], exportAs: ["fsForm"] }, { kind: "component", type: i2$1.FsFormDialogActionsComponent, selector: "fs-form-dialog-actions", inputs: ["save", "create", "close", "done", "closeData", "name"] }, { kind: "directive", type: i2$1.FsButtonDirective, selector: "[mat-raised-button],[mat-button],[mat-flat-button],[mat-stroked-button]", inputs: ["name", "dirtySubmit"] }, { kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i4$1.FsSkeletonFormDirective, selector: "[fsSkeletonForm]", inputs: ["fsSkeletonForm", "fsSkeletonFormLines"] }, { kind: "ngmodule", type: FsTabsModule }, { kind: "directive", type: i5.FsTabsHeaderTabGroupDirective, selector: "mat-tab-group, matTabGroup, [matTabGroup]", inputs: ["orientation", "selected", "selectedData"], outputs: ["selectedChange", "selectedDataChange"], exportAs: ["fsTabsHeaderTabGroup"] }, { kind: "directive", type: i5.FsTabsTabDirective, selector: "mat-tab,matTab", inputs: ["name", "data"], exportAs: ["fsTabsTab"] }, { kind: "component", type: MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "directive", type: MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i5$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass"], exportAs: ["matTab"] }, { kind: "component", type: i5$1.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: AiKnowledgeComponent, selector: "fs-ai-report-global-settings-ai-knowledge", inputs: ["content", "config"], outputs: ["contentChange"] }, { kind: "component", type: GeneralComponent, selector: "fs-ai-report-global-settings-general", inputs: ["concurrency", "min", "max"], outputs: ["concurrencyChange"] }, { kind: "component", type: SemanticDatabaseComponent, selector: "fs-ai-report-global-settings-semantic-database" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3622
3777
|
}
|
|
3623
3778
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GlobalSettingsComponent, decorators: [{
|
|
3624
3779
|
type: Component,
|
|
@@ -3634,9 +3789,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3634
3789
|
MatDialogContent,
|
|
3635
3790
|
MatTabsModule,
|
|
3636
3791
|
AiKnowledgeComponent,
|
|
3637
|
-
|
|
3792
|
+
GeneralComponent,
|
|
3638
3793
|
SemanticDatabaseComponent,
|
|
3639
|
-
], template: "<form\n *fsSkeletonForm=\"
|
|
3794
|
+
], template: "<form\n *fsSkeletonForm=\"loaded()\"\n fsForm\n [submit]=\"save\">\n <fs-dialog>\n <fs-dialog-title>\n Report Settings\n </fs-dialog-title>\n <mat-dialog-content>\n <mat-tab-group [(selected)]=\"selectedTab\">\n <mat-tab\n label=\"AI Knowledge\"\n name=\"knowledge\">\n <fs-ai-report-global-settings-ai-knowledge\n [content]=\"content()\"\n (contentChange)=\"content.set($event)\"\n [config]=\"editorConfig\">\n </fs-ai-report-global-settings-ai-knowledge>\n </mat-tab>\n <mat-tab\n label=\"Semantic Database\"\n name=\"build\">\n <fs-ai-report-global-settings-semantic-database>\n </fs-ai-report-global-settings-semantic-database>\n </mat-tab>\n <mat-tab\n label=\"General\"\n name=\"general\">\n <fs-ai-report-global-settings-general\n [concurrency]=\"concurrency()\"\n (concurrencyChange)=\"concurrency.set($event)\"\n [min]=\"settings()!.semanticBuildConcurrencyMin\"\n [max]=\"settings()!.semanticBuildConcurrencyMax\">\n </fs-ai-report-global-settings-general>\n </mat-tab>\n </mat-tab-group>\n </mat-dialog-content>\n <mat-dialog-actions>\n <!-- The footer follows the selected tab. AI Knowledge projects its own\n Save | View History | Cancel row (fs-form-dialog-actions renders\n projected content after Save/Cancel, so the default pair can't keep\n View History beside Save); General uses the default Save/Cancel; the\n Semantic Database tab is a read/run surface, so it gets a primary\n Close plus its Build/Rebuild/Stop action. -->\n <fs-form-dialog-actions [save]=\"selectedTab === 'general'\">\n @if (selectedTab === 'knowledge') {\n <button\n type=\"submit\"\n mat-button\n color=\"primary\">\n Save\n </button>\n <button\n type=\"button\"\n mat-button\n (click)=\"openHistory()\">\n View History\n </button>\n <button\n type=\"button\"\n mat-button\n [mat-dialog-close]=\"null\">\n Cancel\n </button>\n }\n @if (selectedTab === 'build') {\n <button\n type=\"button\"\n mat-button\n color=\"primary\"\n [mat-dialog-close]=\"undefined\">\n Close\n </button>\n }\n @if (selectedTab === 'build' && semanticDatabase(); as db) {\n @if (db.buildActive()) {\n <button\n type=\"button\"\n mat-button\n color=\"warn\"\n (click)=\"db.stopBuild()\">\n Stop Build\n </button>\n } @else {\n @if (db.status(); as buildStatus) {\n <button\n type=\"button\"\n mat-button\n (click)=\"db.build()\">\n {{ buildStatus.detail.generatedAt ? 'Rebuild' : 'Build' }}\n </button>\n }\n }\n }\n </fs-form-dialog-actions>\n </mat-dialog-actions>\n </fs-dialog>\n</form>\n", styles: [":host{display:block}\n"] }]
|
|
3640
3795
|
}], ctorParameters: () => [] });
|
|
3641
3796
|
|
|
3642
3797
|
// The reports listing — an fs-list of reports. Rows are links built from a URL
|
|
@@ -4002,8 +4157,8 @@ class FsAiReportComponent {
|
|
|
4002
4157
|
ReportPptxService,
|
|
4003
4158
|
ReportPdfService,
|
|
4004
4159
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
|
|
4005
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
4006
|
-
], viewQueries: [{ propertyName: "_split", first: true, predicate: ["split"], descendants: true, static: true }, { propertyName: "_chatPanel", first: true, predicate: ["chatPanel"], descendants: true, read: ElementRef, static: true }], ngImport: i0, template: "<div class=\"fs-ai-report fs-column\">\n\n <div class=\"fs-ai-report__header fs-row.align-center\">\n <h2 class=\"fs-ai-report__title fs-flex\">{{ report?.name }}</h2>\n\n @if (report) {\n <!-- Report-level filters, inline with the toolbar actions. fs-filter\n reads its config once at init, so it's keyed on the filter\n signature: when the report-level filter set changes the block is\n recreated and re-reads the rebuilt config. -->\n @if (reportHasFilters) {\n @for (key of [reportFilterKey]; track key) {\n <fs-filter\n class=\"fs-ai-report__filters\"\n [config]=\"reportFilterConfig\">\n </fs-filter>\n }\n }\n\n <button\n mat-icon-button\n type=\"button\"\n [matTooltip]=\"editMode ? 'Done' : 'Edit layout'\"\n (click)=\"toggleEditMode()\">\n <mat-icon>{{ editMode ? 'lock' : 'open_with' }}</mat-icon>\n </button>\n\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Export\"\n [fsMenuTriggerFor]=\"exportMenu\">\n <mat-icon>download</mat-icon>\n </button>\n <fs-menu #exportMenu>\n <ng-template\n fs-menu-item\n (click)=\"exportPowerpoint()\">\n Export PowerPoint\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportPdf()\">\n Export PDF\n </ng-template>\n </fs-menu>\n\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Report settings\"\n (click)=\"reportSettings()\">\n <mat-icon>settings</mat-icon>\n </button>\n }\n </div>\n\n <div\n #split\n class=\"fs-ai-report__split fs-row.align-start\">\n <fs-ai-chat\n #chatPanel\n class=\"chat\"\n [basePath]=\"basePath()\"\n [requestData]=\"{ reportId: reportId() }\"\n (response)=\"onChatResponse($event)\">\n </fs-ai-chat>\n\n <div\n class=\"resizer\"\n (pointerdown)=\"onResizeStart($event)\">\n </div>\n\n <div class=\"viewer fs-flex fs-column\">\n @if (report) {\n <app-report-canvas\n [report]=\"report\"\n [editMode]=\"editMode\"\n (componentSettings)=\"componentSettings($event)\"\n (reportChanged)=\"onCanvasReportChanged()\"\n (editDone)=\"toggleEditMode()\">\n </app-report-canvas>\n }\n </div>\n </div>\n</div>\n", styles: [".fs-ai-report{height:100%;min-height:500px;display:flex;flex-direction:column}.fs-ai-report__header{flex:0 0 auto;margin-bottom:10px}.fs-ai-report__title{margin:0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fs-ai-report__filters{flex:0 0 auto;display:block;margin:0 5px 0 0}.fs-ai-report__filters ::ng-deep .filter-bar-container{align-items:center!important}.fs-ai-report__filters ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.fs-ai-report__split{flex:1 1 auto;min-height:0}.fs-ai-report .chat{display:block;flex:0 0 25%;width:100%;height:100%;min-height:400px;min-width:0;border:none}.fs-ai-report .viewer{display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden}.fs-ai-report .resizer{flex:0 0 11px;align-self:stretch;display:flex;justify-content:center;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none}.fs-ai-report .resizer:before{content:\"\";width:3px;border-radius:3px;background:transparent;transition:background-color .12s ease}.fs-ai-report .resizer:hover:before{background:var(--brand-primary-color)}.fs-ai-report__split.resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.fs-ai-report__split.resizing .resizer:before{background:var(--brand-primary-color)}.fs-ai-report__split.resizing .chat,.fs-ai-report__split.resizing .viewer{pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: FsFilterModule }, { kind: "component", type: i2.FilterComponent, selector: "fs-filter", inputs: ["config"], outputs: ["closed", "opened", "ready"] }, { kind: "ngmodule", type: FsMenuModule }, { kind: "component", type: i2$3.FsMenuComponent, selector: "fs-menu", inputs: ["class", "buttonClass", "buttonType", "buttonColor"], outputs: ["opened", "closed"] }, { kind: "directive", type: i2$3.FsMenuItemDirective, selector: "fs-menu-group,[fs-menu-item]" }, { kind: "directive", type: i2$3.FsMenuTriggerDirective, selector: "[fsMenuTriggerFor]", inputs: ["fsMenuTriggerFor"] }, { kind: "component", type: FsAiChatComponent, selector: "fs-ai-chat", inputs: ["basePath", "requestData", "introMessage"], outputs: ["response"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$
|
|
4160
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-1VNsyco5.mjs').then((module) => module.default) }),
|
|
4161
|
+
], viewQueries: [{ propertyName: "_split", first: true, predicate: ["split"], descendants: true, static: true }, { propertyName: "_chatPanel", first: true, predicate: ["chatPanel"], descendants: true, read: ElementRef, static: true }], ngImport: i0, template: "<div class=\"fs-ai-report fs-column\">\n\n <div class=\"fs-ai-report__header fs-row.align-center\">\n <h2 class=\"fs-ai-report__title fs-flex\">{{ report?.name }}</h2>\n\n @if (report) {\n <!-- Report-level filters, inline with the toolbar actions. fs-filter\n reads its config once at init, so it's keyed on the filter\n signature: when the report-level filter set changes the block is\n recreated and re-reads the rebuilt config. -->\n @if (reportHasFilters) {\n @for (key of [reportFilterKey]; track key) {\n <fs-filter\n class=\"fs-ai-report__filters\"\n [config]=\"reportFilterConfig\">\n </fs-filter>\n }\n }\n\n <button\n mat-icon-button\n type=\"button\"\n [matTooltip]=\"editMode ? 'Done' : 'Edit layout'\"\n (click)=\"toggleEditMode()\">\n <mat-icon>{{ editMode ? 'lock' : 'open_with' }}</mat-icon>\n </button>\n\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Export\"\n [fsMenuTriggerFor]=\"exportMenu\">\n <mat-icon>download</mat-icon>\n </button>\n <fs-menu #exportMenu>\n <ng-template\n fs-menu-item\n (click)=\"exportPowerpoint()\">\n Export PowerPoint\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportPdf()\">\n Export PDF\n </ng-template>\n </fs-menu>\n\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Report settings\"\n (click)=\"reportSettings()\">\n <mat-icon>settings</mat-icon>\n </button>\n }\n </div>\n\n <div\n #split\n class=\"fs-ai-report__split fs-row.align-start\">\n <fs-ai-chat\n #chatPanel\n class=\"chat\"\n [basePath]=\"basePath()\"\n [requestData]=\"{ reportId: reportId() }\"\n (response)=\"onChatResponse($event)\">\n </fs-ai-chat>\n\n <div\n class=\"resizer\"\n (pointerdown)=\"onResizeStart($event)\">\n </div>\n\n <div class=\"viewer fs-flex fs-column\">\n @if (report) {\n <app-report-canvas\n [report]=\"report\"\n [editMode]=\"editMode\"\n (componentSettings)=\"componentSettings($event)\"\n (reportChanged)=\"onCanvasReportChanged()\"\n (editDone)=\"toggleEditMode()\">\n </app-report-canvas>\n }\n </div>\n </div>\n</div>\n", styles: [".fs-ai-report{height:100%;min-height:500px;display:flex;flex-direction:column}.fs-ai-report__header{flex:0 0 auto;margin-bottom:10px}.fs-ai-report__title{margin:0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fs-ai-report__filters{flex:0 0 auto;display:block;margin:0 5px 0 0}.fs-ai-report__filters ::ng-deep .filter-bar-container{align-items:center!important}.fs-ai-report__filters ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.fs-ai-report__split{flex:1 1 auto;min-height:0}.fs-ai-report .chat{display:block;flex:0 0 25%;width:100%;height:100%;min-height:400px;min-width:0;border:none}.fs-ai-report .viewer{display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden}.fs-ai-report .resizer{flex:0 0 11px;align-self:stretch;display:flex;justify-content:center;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none}.fs-ai-report .resizer:before{content:\"\";width:3px;border-radius:3px;background:transparent;transition:background-color .12s ease}.fs-ai-report .resizer:hover:before{background:var(--brand-primary-color)}.fs-ai-report__split.resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.fs-ai-report__split.resizing .resizer:before{background:var(--brand-primary-color)}.fs-ai-report__split.resizing .chat,.fs-ai-report__split.resizing .viewer{pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: FsFilterModule }, { kind: "component", type: i2.FilterComponent, selector: "fs-filter", inputs: ["config"], outputs: ["closed", "opened", "ready"] }, { kind: "ngmodule", type: FsMenuModule }, { kind: "component", type: i2$3.FsMenuComponent, selector: "fs-menu", inputs: ["class", "buttonClass", "buttonType", "buttonColor"], outputs: ["opened", "closed"] }, { kind: "directive", type: i2$3.FsMenuItemDirective, selector: "fs-menu-group,[fs-menu-item]" }, { kind: "directive", type: i2$3.FsMenuTriggerDirective, selector: "[fsMenuTriggerFor]", inputs: ["fsMenuTriggerFor"] }, { kind: "component", type: FsAiChatComponent, selector: "fs-ai-chat", inputs: ["basePath", "requestData", "introMessage"], outputs: ["response"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: ReportCanvasComponent, selector: "app-report-canvas", inputs: ["report", "editMode"], outputs: ["componentSettings", "reportChanged", "editDone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4007
4162
|
}
|
|
4008
4163
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportComponent, decorators: [{
|
|
4009
4164
|
type: Component,
|
|
@@ -4015,7 +4170,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4015
4170
|
ReportPptxService,
|
|
4016
4171
|
ReportPdfService,
|
|
4017
4172
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
|
|
4018
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
4173
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-1VNsyco5.mjs').then((module) => module.default) }),
|
|
4019
4174
|
], imports: [
|
|
4020
4175
|
FsFilterModule,
|
|
4021
4176
|
FsMenuModule,
|
|
@@ -4046,4 +4201,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4046
4201
|
*/
|
|
4047
4202
|
|
|
4048
4203
|
export { FsAiReportsComponent as F, REPORT_CHART_COLORS_CSS as R, ReportComponent as a, FsAiReportComponent as b, ReportData as c, ReportService as d, ReportFilterStateService as e, FREQUENCY_OPTIONS as f };
|
|
4049
|
-
//# sourceMappingURL=firestitch-report-firestitch-report-
|
|
4204
|
+
//# sourceMappingURL=firestitch-report-firestitch-report-BNHGwPj2.mjs.map
|