@firestitch/report 18.0.11 → 18.0.13
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/dialogs/entity-functions/entity-functions.component.d.ts +22 -0
- package/app/reports/dialogs/global-settings/components/semantic-database/semantic-database.component.d.ts +6 -21
- package/app/reports/dialogs/global-settings/global-settings.component.d.ts +3 -3
- package/app/reports/dialogs/instruction-history/instruction-history.component.d.ts +16 -0
- package/app/reports/interfaces/report.interface.d.ts +10 -1
- package/app/reports/report-filter-items.d.ts +7 -0
- package/esm2022/app/reports/components/component-list/component-list.component.mjs +4 -2
- package/esm2022/app/reports/dialogs/entity-functions/entity-functions.component.mjs +81 -0
- package/esm2022/app/reports/dialogs/global-settings/components/semantic-database/semantic-database.component.mjs +23 -71
- package/esm2022/app/reports/dialogs/global-settings/global-settings.component.mjs +31 -22
- package/esm2022/app/reports/dialogs/instruction-history/instruction-history.component.mjs +60 -0
- package/esm2022/app/reports/dialogs/report-settings/report-settings.component.mjs +28 -7
- package/esm2022/app/reports/interfaces/report.interface.mjs +1 -1
- package/esm2022/app/reports/report-filter-items.mjs +28 -10
- package/fesm2022/{firestitch-report-echarts-DaBOoR9Z.mjs → firestitch-report-echarts-D9IzyGer.mjs} +2 -2
- package/fesm2022/{firestitch-report-echarts-DaBOoR9Z.mjs.map → firestitch-report-echarts-D9IzyGer.mjs.map} +1 -1
- package/fesm2022/{firestitch-report-firestitch-report-Cwsww3Jz.mjs → firestitch-report-firestitch-report-BwC8lvzk.mjs} +219 -130
- package/fesm2022/firestitch-report-firestitch-report-BwC8lvzk.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-Cwsww3Jz.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
|
|
|
@@ -350,12 +350,15 @@ function dateBoundString(value) {
|
|
|
350
350
|
// the group-keyed path for charts/KPIs and the report bar.)
|
|
351
351
|
// The query key fs-filter uses for a group's item(s).
|
|
352
352
|
const itemName = (group) => `g${group.id}`;
|
|
353
|
-
// A select group's distinct options, as { name, value } pairs.
|
|
354
|
-
//
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
//
|
|
358
|
-
//
|
|
353
|
+
// A select group's distinct options, as { name, value } pairs. The server sends
|
|
354
|
+
// { value, label }: the value is what the filter matches on (an entity's stable
|
|
355
|
+
// id) and the label is what the viewer reads — so a renamed entity keeps its id
|
|
356
|
+
// and simply shows its new name here. Options come from the first member filter
|
|
357
|
+
// that declares them — members are the same data point, so any member's list
|
|
358
|
+
// serves the group. The list is fetched once (the server caps it) and replayed
|
|
359
|
+
// (shareReplay), so repeated reads never refetch. Shared by the report-bar
|
|
360
|
+
// select control and the report-settings default picker so the two never drift.
|
|
361
|
+
// Returns an empty list when no member provides options.
|
|
359
362
|
function loadGroupOptions(group, reportData, reportId) {
|
|
360
363
|
const optionFilter = (group.filters ?? []).find((member) => member.hasOptions);
|
|
361
364
|
if (!optionFilter) {
|
|
@@ -363,7 +366,19 @@ function loadGroupOptions(group, reportData, reportId) {
|
|
|
363
366
|
}
|
|
364
367
|
return reportData.filterOptions(reportId, optionFilter.id)
|
|
365
368
|
.pipe(map((options) => (options ?? [])
|
|
366
|
-
.map((option) => ({ name:
|
|
369
|
+
.map((option) => ({ name: option.label, value: option.value }))), shareReplay({ bufferSize: 1, refCount: false }));
|
|
370
|
+
}
|
|
371
|
+
// Stored filter values (ids) paired back with the labels to show for them.
|
|
372
|
+
// A saved default holds only values, so the names have to be read back off the
|
|
373
|
+
// option list — that indirection is exactly what survives a rename. A value with
|
|
374
|
+
// no matching option (its entity was deleted, so it is no longer listed) falls
|
|
375
|
+
// back to showing the raw value rather than dropping the chip, which would
|
|
376
|
+
// silently widen the filter.
|
|
377
|
+
function resolveOptionValues(options$, values) {
|
|
378
|
+
return options$.pipe(map((options) => values.map((value) => {
|
|
379
|
+
const option = options.find((candidate) => String(candidate.value) === String(value));
|
|
380
|
+
return { name: option?.name ?? String(value), value };
|
|
381
|
+
})));
|
|
367
382
|
}
|
|
368
383
|
// Case-insensitive name filter applied to a loaded option list — the typed
|
|
369
384
|
// keyword has to be matched here because fs-autocomplete renders what we return
|
|
@@ -392,14 +407,17 @@ function filterItemForGroup(group, reportData, reportId, initial) {
|
|
|
392
407
|
case 'select': {
|
|
393
408
|
// One shared, replayed option stream so typing never refetches.
|
|
394
409
|
const options$ = loadGroupOptions(group, reportData, reportId);
|
|
410
|
+
const values = initial?.values;
|
|
395
411
|
return {
|
|
396
412
|
name,
|
|
397
413
|
type: ItemType.AutoCompleteChips,
|
|
398
414
|
label,
|
|
399
415
|
fetchOnFocus: true,
|
|
400
416
|
values: (keyword) => matchOptions(options$, keyword),
|
|
401
|
-
|
|
402
|
-
|
|
417
|
+
// Stored values are ids, so the chips can only be named once the option
|
|
418
|
+
// list has loaded — hence the observable form of `default`.
|
|
419
|
+
default: values?.length
|
|
420
|
+
? () => resolveOptionValues(options$, values)
|
|
403
421
|
: undefined,
|
|
404
422
|
};
|
|
405
423
|
}
|
|
@@ -1104,9 +1122,11 @@ class ComponentListComponent {
|
|
|
1104
1122
|
name,
|
|
1105
1123
|
type: ItemType.AutoCompleteChips,
|
|
1106
1124
|
label,
|
|
1125
|
+
// The server sends { value, label }: the filter matches on the value
|
|
1126
|
+
// (an entity's stable id) while the viewer reads the label.
|
|
1107
1127
|
values: () => this._reportData.filterOptions(this.reportId, filter.id)
|
|
1108
1128
|
.pipe(map$1((options) => (options ?? [])
|
|
1109
|
-
.map((option) => ({ name:
|
|
1129
|
+
.map((option) => ({ name: option.label, value: option.value })))),
|
|
1110
1130
|
};
|
|
1111
1131
|
case 'keyword':
|
|
1112
1132
|
default:
|
|
@@ -2335,24 +2355,45 @@ class ReportSettingsComponent {
|
|
|
2335
2355
|
// in bar order, seeding each draft from the group's stored default.
|
|
2336
2356
|
_buildFilterRows(report) {
|
|
2337
2357
|
const reportId = report.id;
|
|
2338
|
-
const
|
|
2358
|
+
const built = (report.filterGroups ?? [])
|
|
2339
2359
|
.filter((group) => group.level === 'report' || group.level === 'both')
|
|
2340
2360
|
.sort((a, b) => a.order - b.order)
|
|
2341
2361
|
.map((group) => {
|
|
2342
2362
|
const groupDefault = group.config?.default;
|
|
2343
|
-
|
|
2363
|
+
// One shared, replayed option stream per group: the chips fetch and the
|
|
2364
|
+
// stored default's labels read the same list, so it is fetched once.
|
|
2365
|
+
const options$ = loadGroupOptions(group, this._reportData, reportId);
|
|
2366
|
+
const row = {
|
|
2344
2367
|
group,
|
|
2345
2368
|
label: group.label || group.filters?.[0]?.filterColumn || 'Filter',
|
|
2346
2369
|
start: dateFromBound(groupDefault?.start),
|
|
2347
2370
|
end: dateFromBound(groupDefault?.end),
|
|
2348
|
-
|
|
2371
|
+
// Named below, once the option list carrying their labels is in.
|
|
2372
|
+
values: [],
|
|
2349
2373
|
value: groupDefault?.value ?? '',
|
|
2350
2374
|
// fs-autocomplete-chips fetch: the group's distinct options, name-filtered.
|
|
2351
2375
|
// Same loader the report bar uses, so the two never drift.
|
|
2352
|
-
fetch: (keyword) => matchOptions(
|
|
2376
|
+
fetch: (keyword) => matchOptions(options$, keyword),
|
|
2353
2377
|
};
|
|
2378
|
+
return { row, options$, values: groupDefault?.values ?? [] };
|
|
2379
|
+
});
|
|
2380
|
+
const rows = built.map((entry) => entry.row);
|
|
2381
|
+
const seeded = built.filter((entry) => entry.values.length);
|
|
2382
|
+
// Nothing to name — publish immediately rather than waiting on a request
|
|
2383
|
+
// no row needs.
|
|
2384
|
+
if (!seeded.length) {
|
|
2385
|
+
this.filterRows.set(rows);
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
// A stored select default holds ids, so its chips can only be named once the
|
|
2389
|
+
// option list is in. Resolve every seeded row first and publish the rows in
|
|
2390
|
+
// one go, so the tab never renders a chip showing a raw id.
|
|
2391
|
+
forkJoin(seeded.map((entry) => resolveOptionValues(entry.options$, entry.values)))
|
|
2392
|
+
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
2393
|
+
.subscribe((resolved) => {
|
|
2394
|
+
resolved.forEach((values, index) => seeded[index].row.values = values);
|
|
2395
|
+
this.filterRows.set(rows);
|
|
2354
2396
|
});
|
|
2355
|
-
this.filterRows.set(rows);
|
|
2356
2397
|
}
|
|
2357
2398
|
// The draft default normalized to the wire shape for this row's type, or null
|
|
2358
2399
|
// when empty (which clears the stored default).
|
|
@@ -2541,7 +2582,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2541
2582
|
// complete synchronously after setOption.
|
|
2542
2583
|
async function renderChartImage(component, data, width, height, pixelRatio = 3) {
|
|
2543
2584
|
// The same tree-shaken echarts build + 'report' theme the live canvas uses.
|
|
2544
|
-
const echarts = (await import('./firestitch-report-echarts-
|
|
2585
|
+
const echarts = (await import('./firestitch-report-echarts-D9IzyGer.mjs')).default;
|
|
2545
2586
|
const host = document.createElement('div');
|
|
2546
2587
|
host.style.width = `${width}px`;
|
|
2547
2588
|
host.style.height = `${height}px`;
|
|
@@ -3285,7 +3326,7 @@ class ReportComponent {
|
|
|
3285
3326
|
ReportPdfService,
|
|
3286
3327
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily with
|
|
3287
3328
|
// this route's chunk.
|
|
3288
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
3329
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-D9IzyGer.mjs').then((module) => module.default) }),
|
|
3289
3330
|
], 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 });
|
|
3290
3331
|
}
|
|
3291
3332
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportComponent, decorators: [{
|
|
@@ -3299,7 +3340,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3299
3340
|
ReportPdfService,
|
|
3300
3341
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily with
|
|
3301
3342
|
// this route's chunk.
|
|
3302
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
3343
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-D9IzyGer.mjs').then((module) => module.default) }),
|
|
3303
3344
|
], imports: [
|
|
3304
3345
|
FormsModule,
|
|
3305
3346
|
FsAutocompleteChipsModule,
|
|
@@ -3317,6 +3358,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3317
3358
|
args: ['chatPanel', { static: true, read: ElementRef }]
|
|
3318
3359
|
}] } });
|
|
3319
3360
|
|
|
3361
|
+
// The AI Knowledge document's version history (append-only), each version
|
|
3362
|
+
// previewed and restorable — opened from the Report Settings footer's View
|
|
3363
|
+
// History button. Restores happen here (confirm, then flip the current flag —
|
|
3364
|
+
// no new version is created); the opener refreshes its instructions after this
|
|
3365
|
+
// dialog closes, so a restore made here reseeds the editor there.
|
|
3366
|
+
class InstructionHistoryComponent {
|
|
3367
|
+
_data = inject(MAT_DIALOG_DATA);
|
|
3368
|
+
versions = signal(this._data.versions);
|
|
3369
|
+
_reportData = inject(ReportData);
|
|
3370
|
+
_prompt = inject(FsPrompt);
|
|
3371
|
+
_message = inject(FsMessage);
|
|
3372
|
+
_destroyRef = inject(DestroyRef);
|
|
3373
|
+
// Make an earlier version current — flips the flag, creates no new version.
|
|
3374
|
+
restore(version) {
|
|
3375
|
+
this._prompt.confirm({
|
|
3376
|
+
title: 'Restore version',
|
|
3377
|
+
template: 'Make this version the current AI knowledge? The newer versions stay in the history.',
|
|
3378
|
+
commitLabel: 'Restore',
|
|
3379
|
+
})
|
|
3380
|
+
.pipe(switchMap(() => this._reportData.restoreInstruction(version.id)), takeUntilDestroyed(this._destroyRef))
|
|
3381
|
+
.subscribe((instructions) => {
|
|
3382
|
+
this.versions.set(instructions.versions);
|
|
3383
|
+
this._message.success('Version restored');
|
|
3384
|
+
});
|
|
3385
|
+
}
|
|
3386
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: InstructionHistoryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3387
|
+
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 });
|
|
3388
|
+
}
|
|
3389
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: InstructionHistoryComponent, decorators: [{
|
|
3390
|
+
type: Component,
|
|
3391
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
|
|
3392
|
+
MatButton,
|
|
3393
|
+
MatDialogContent,
|
|
3394
|
+
MatDialogActions,
|
|
3395
|
+
MatDialogClose,
|
|
3396
|
+
FsChipModule,
|
|
3397
|
+
FsDateModule,
|
|
3398
|
+
FsDialogModule,
|
|
3399
|
+
FsMarkdownRendererComponent,
|
|
3400
|
+
FsMessageModule,
|
|
3401
|
+
], 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"] }]
|
|
3402
|
+
}] });
|
|
3403
|
+
|
|
3320
3404
|
// The AI Knowledge tab: the operator-authored markdown document appended to the
|
|
3321
3405
|
// report agent's system instructions on every chat turn — the lever for
|
|
3322
3406
|
// terminology, business rules and special cases no code deploy can cover.
|
|
@@ -3367,35 +3451,80 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3367
3451
|
], 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"] }]
|
|
3368
3452
|
}] });
|
|
3369
3453
|
|
|
3370
|
-
//
|
|
3371
|
-
//
|
|
3372
|
-
//
|
|
3373
|
-
//
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3454
|
+
// One group of an entity's AI-classified cached code functions — the model
|
|
3455
|
+
// class's or the handler class's, per the source it was opened for. Fetched on
|
|
3456
|
+
// open from the shared per-entity endpoint (one payload carries both groups,
|
|
3457
|
+
// each row tagged with its source) and filtered client-side.
|
|
3458
|
+
class EntityFunctionsComponent {
|
|
3459
|
+
// One color per AI classification kind, so a function's nature reads at a glance.
|
|
3460
|
+
static KIND_COLORS = {
|
|
3461
|
+
'calculation': '#1565c0',
|
|
3462
|
+
'business-rule': '#6a1b9a',
|
|
3463
|
+
'query': '#00695c',
|
|
3464
|
+
'mutation': '#e65100',
|
|
3465
|
+
'accessor': '#546e7a',
|
|
3466
|
+
'other': '#757575',
|
|
3467
|
+
};
|
|
3468
|
+
// Interesting logic first: derived values and rules, then data access, then the rest.
|
|
3469
|
+
static KIND_ORDER = ['calculation', 'business-rule', 'query', 'mutation', 'accessor', 'other'];
|
|
3470
|
+
_data = inject(MAT_DIALOG_DATA);
|
|
3471
|
+
entityKey = this._data.entityKey;
|
|
3472
|
+
sourceLabel = this._data.source === 'model' ? 'Model functions' : 'Handler functions';
|
|
3473
|
+
// null until loaded — gates the list behind the skeleton.
|
|
3474
|
+
entityFunctions = signal(null);
|
|
3475
|
+
// The class this group's functions were discovered from.
|
|
3476
|
+
discoveredClass = computed(() => {
|
|
3477
|
+
const loaded = this.entityFunctions();
|
|
3478
|
+
return this._data.source === 'model' ? loaded?.modelClass : loaded?.handlerClass;
|
|
3479
|
+
});
|
|
3480
|
+
// This group's functions, ordered by kind priority then method name.
|
|
3481
|
+
functions = computed(() => this._sortFunctions((this.entityFunctions()?.functions ?? [])
|
|
3482
|
+
.filter((fn) => fn.source === this._data.source)));
|
|
3483
|
+
constructor() {
|
|
3484
|
+
inject(ReportData).semanticEntityFunctions(this._data.entityKey)
|
|
3485
|
+
.pipe(takeUntilDestroyed())
|
|
3486
|
+
.subscribe((entityFunctions) => {
|
|
3487
|
+
this.entityFunctions.set(entityFunctions);
|
|
3488
|
+
});
|
|
3489
|
+
}
|
|
3490
|
+
kindColor(kind) {
|
|
3491
|
+
return EntityFunctionsComponent.KIND_COLORS[kind] ?? EntityFunctionsComponent.KIND_COLORS.other;
|
|
3492
|
+
}
|
|
3493
|
+
_sortFunctions(functions) {
|
|
3494
|
+
return [...functions]
|
|
3495
|
+
.sort((a, b) => {
|
|
3496
|
+
const kindDiff = this._kindRank(a.kind) - this._kindRank(b.kind);
|
|
3497
|
+
return kindDiff !== 0 ? kindDiff : a.method.localeCompare(b.method);
|
|
3498
|
+
});
|
|
3499
|
+
}
|
|
3500
|
+
_kindRank(kind) {
|
|
3501
|
+
const rank = EntityFunctionsComponent.KIND_ORDER.indexOf(kind);
|
|
3502
|
+
return rank === -1 ? EntityFunctionsComponent.KIND_ORDER.length : rank;
|
|
3503
|
+
}
|
|
3504
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: EntityFunctionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3505
|
+
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 });
|
|
3380
3506
|
}
|
|
3381
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type:
|
|
3507
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: EntityFunctionsComponent, decorators: [{
|
|
3382
3508
|
type: Component,
|
|
3383
|
-
args: [{
|
|
3509
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
|
|
3384
3510
|
MatButton,
|
|
3511
|
+
MatDialogContent,
|
|
3512
|
+
MatDialogActions,
|
|
3513
|
+
MatDialogClose,
|
|
3385
3514
|
FsChipModule,
|
|
3386
3515
|
FsDateModule,
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
], template: "<div
|
|
3390
|
-
}] });
|
|
3516
|
+
FsDialogModule,
|
|
3517
|
+
FsSkeletonModule,
|
|
3518
|
+
], 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"] }]
|
|
3519
|
+
}], ctorParameters: () => [] });
|
|
3391
3520
|
|
|
3392
|
-
// The Semantic Database tab: the
|
|
3393
|
-
//
|
|
3394
|
-
//
|
|
3395
|
-
// entity to reveal its shape, inlined
|
|
3396
|
-
//
|
|
3397
|
-
// functions
|
|
3398
|
-
//
|
|
3521
|
+
// The Semantic Database tab: the entity scoreboard the report AI is built from
|
|
3522
|
+
// (an entity = a table + a model + a handler). Shows the last build's status,
|
|
3523
|
+
// runs (or stops) a build — streamed into the FsProcess dock so it survives the
|
|
3524
|
+
// dialog closing — and drills into every entity to reveal its shape, inlined
|
|
3525
|
+
// enum values, computed fields and its cached function counts, whose links open
|
|
3526
|
+
// the functions dialog (the full function set across all entities is far too
|
|
3527
|
+
// big to ship with the scoreboard).
|
|
3399
3528
|
class SemanticDatabaseComponent {
|
|
3400
3529
|
// The build-in-flight states of the ff_processes row (queued/running/stopping).
|
|
3401
3530
|
static ACTIVE_STATES = ['Q', 'R', 'X'];
|
|
@@ -3412,17 +3541,6 @@ class SemanticDatabaseComponent {
|
|
|
3412
3541
|
// failure.
|
|
3413
3542
|
static DONE_COLOR = '#2e7d32';
|
|
3414
3543
|
static PENDING_COLOR = '#9e9e9e';
|
|
3415
|
-
// One color per AI classification kind, so a function's nature reads at a glance.
|
|
3416
|
-
static KIND_COLORS = {
|
|
3417
|
-
'calculation': '#1565c0',
|
|
3418
|
-
'business-rule': '#6a1b9a',
|
|
3419
|
-
'query': '#00695c',
|
|
3420
|
-
'mutation': '#e65100',
|
|
3421
|
-
'accessor': '#546e7a',
|
|
3422
|
-
'other': '#757575',
|
|
3423
|
-
};
|
|
3424
|
-
// Interesting logic first: derived values and rules, then data access, then the rest.
|
|
3425
|
-
static KIND_ORDER = ['calculation', 'business-rule', 'query', 'mutation', 'accessor', 'other'];
|
|
3426
3544
|
// The scoreboard's per-entity stage chips, left to right.
|
|
3427
3545
|
stageKeys = ['introspected', 'enumsInlined', 'computed', 'functions'];
|
|
3428
3546
|
stageLabels = {
|
|
@@ -3438,12 +3556,10 @@ class SemanticDatabaseComponent {
|
|
|
3438
3556
|
buildActive = computed(() => this.building()
|
|
3439
3557
|
|| SemanticDatabaseComponent.ACTIVE_STATES.includes(this.status()?.build?.state));
|
|
3440
3558
|
buildStateLabel = computed(() => SemanticDatabaseComponent.STATE_LABELS[this.status()?.build?.state] ?? '');
|
|
3441
|
-
// Per-entity function caches, loaded on first expand. undefined = not
|
|
3442
|
-
// requested yet, null = request in flight (the skeleton window), object = loaded.
|
|
3443
|
-
_entityFunctions = signal({});
|
|
3444
3559
|
// Guards the outcome message to fire exactly once per run — whether the stream
|
|
3445
3560
|
// completes (build finished, possibly failed) or errors outright.
|
|
3446
3561
|
_buildFinalized = false;
|
|
3562
|
+
_dialog = inject(MatDialog);
|
|
3447
3563
|
_reportData = inject(ReportData);
|
|
3448
3564
|
_process = inject(FsProcess);
|
|
3449
3565
|
_prompt = inject(FsPrompt);
|
|
@@ -3511,9 +3627,6 @@ class SemanticDatabaseComponent {
|
|
|
3511
3627
|
}
|
|
3512
3628
|
this._buildFinalized = true;
|
|
3513
3629
|
this.building.set(false);
|
|
3514
|
-
// The build rewrote every entity's function cache — drop the loaded copies
|
|
3515
|
-
// so the next expand refetches fresh data.
|
|
3516
|
-
this._entityFunctions.set({});
|
|
3517
3630
|
if (streamError) {
|
|
3518
3631
|
this._message.error('The semantic database build could not run — check the AI connection and try again.');
|
|
3519
3632
|
this._refreshStatus();
|
|
@@ -3548,37 +3661,18 @@ class SemanticDatabaseComponent {
|
|
|
3548
3661
|
stageTooltip(stage) {
|
|
3549
3662
|
return stage === 'done' ? 'Built' : 'Needs building';
|
|
3550
3663
|
}
|
|
3551
|
-
kindColor(kind) {
|
|
3552
|
-
return SemanticDatabaseComponent.KIND_COLORS[kind] ?? SemanticDatabaseComponent.KIND_COLORS.other;
|
|
3553
|
-
}
|
|
3554
|
-
/**
|
|
3555
|
-
* The loaded function cache for an entity: undefined until the panel is first
|
|
3556
|
-
* expanded, null while the request is in flight (the skeleton window), then
|
|
3557
|
-
* the loaded cache.
|
|
3558
|
-
*/
|
|
3559
|
-
entityFunctions(key) {
|
|
3560
|
-
return this._entityFunctions()[key];
|
|
3561
|
-
}
|
|
3562
3664
|
/**
|
|
3563
|
-
*
|
|
3564
|
-
*
|
|
3565
|
-
* the
|
|
3665
|
+
* Open the child dialog listing one group of an entity's cached functions —
|
|
3666
|
+
* the model class's or the handler class's. The dialog fetches fresh on open,
|
|
3667
|
+
* so the tab never holds function payloads.
|
|
3566
3668
|
*/
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
.
|
|
3574
|
-
.subscribe((entityFunctions) => {
|
|
3575
|
-
this._entityFunctions.update((loaded) => ({
|
|
3576
|
-
...loaded,
|
|
3577
|
-
[entity.key]: {
|
|
3578
|
-
...entityFunctions,
|
|
3579
|
-
functions: this._sortFunctions(entityFunctions),
|
|
3580
|
-
},
|
|
3581
|
-
}));
|
|
3669
|
+
openFunctions(entity, source) {
|
|
3670
|
+
this._dialog.open(EntityFunctionsComponent, {
|
|
3671
|
+
width: '700px',
|
|
3672
|
+
maxWidth: '95vw',
|
|
3673
|
+
maxHeight: '85vh',
|
|
3674
|
+
autoFocus: false,
|
|
3675
|
+
data: { entityKey: entity.key, source },
|
|
3582
3676
|
});
|
|
3583
3677
|
}
|
|
3584
3678
|
_init() {
|
|
@@ -3589,23 +3683,8 @@ class SemanticDatabaseComponent {
|
|
|
3589
3683
|
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
3590
3684
|
.subscribe((status) => this.status.set(status));
|
|
3591
3685
|
}
|
|
3592
|
-
/**
|
|
3593
|
-
* Order once at load time (kind priority, then method name) so the template
|
|
3594
|
-
* renders the list as-is.
|
|
3595
|
-
*/
|
|
3596
|
-
_sortFunctions(entityFunctions) {
|
|
3597
|
-
return [...entityFunctions.functions]
|
|
3598
|
-
.sort((a, b) => {
|
|
3599
|
-
const kindDiff = this._kindRank(a.kind) - this._kindRank(b.kind);
|
|
3600
|
-
return kindDiff !== 0 ? kindDiff : a.method.localeCompare(b.method);
|
|
3601
|
-
});
|
|
3602
|
-
}
|
|
3603
|
-
_kindRank(kind) {
|
|
3604
|
-
const rank = SemanticDatabaseComponent.KIND_ORDER.indexOf(kind);
|
|
3605
|
-
return rank === -1 ? SemanticDatabaseComponent.KIND_ORDER.length : rank;
|
|
3606
|
-
}
|
|
3607
3686
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SemanticDatabaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3608
|
-
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 });
|
|
3687
|
+
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 });
|
|
3609
3688
|
}
|
|
3610
3689
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SemanticDatabaseComponent, decorators: [{
|
|
3611
3690
|
type: Component,
|
|
@@ -3616,7 +3695,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3616
3695
|
FsDateModule,
|
|
3617
3696
|
FsMessageModule,
|
|
3618
3697
|
FsSkeletonModule,
|
|
3619
|
-
], 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
|
|
3698
|
+
], 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"] }]
|
|
3620
3699
|
}], ctorParameters: () => [] });
|
|
3621
3700
|
|
|
3622
3701
|
// Matches the backend cap (ReportsView::INSTRUCTIONS_MAX_LENGTH) — the document
|
|
@@ -3642,14 +3721,14 @@ const SCAFFOLD = [
|
|
|
3642
3721
|
// the dialog frame, the tab group, and a per-tab footer. Each tab is its own
|
|
3643
3722
|
// component —
|
|
3644
3723
|
// AI Knowledge — the operator-authored markdown document appended to the
|
|
3645
|
-
// report agent's system instructions on every chat turn.
|
|
3646
|
-
// History
|
|
3724
|
+
// report agent's system instructions on every chat turn. Its footer carries a
|
|
3725
|
+
// View History button opening the version-history dialog (restores happen
|
|
3726
|
+
// there; the shell refreshes on close).
|
|
3647
3727
|
// Semantic Database — the build scoreboard the AI is built from, with a
|
|
3648
3728
|
// self-contained Build action.
|
|
3649
3729
|
// General — report-module settings (the semantic-build concurrency).
|
|
3650
|
-
// The shell owns the shared data (the instructions document
|
|
3651
|
-
//
|
|
3652
|
-
// in the children.
|
|
3730
|
+
// The shell owns the shared data (the instructions document, the settings for
|
|
3731
|
+
// General) plus the tab-aware save; everything else lives in the children.
|
|
3653
3732
|
class GlobalSettingsComponent {
|
|
3654
3733
|
selectedTab = 'knowledge';
|
|
3655
3734
|
// The Semantic Database tab component, so the shared footer can drive its
|
|
@@ -3670,9 +3749,9 @@ class GlobalSettingsComponent {
|
|
|
3670
3749
|
// Gate the whole form on both loads, so every tab's fields are ready to render.
|
|
3671
3750
|
loaded = computed(() => !!this.instructions() && !!this.settings());
|
|
3672
3751
|
_dialogRef = inject(MatDialogRef);
|
|
3752
|
+
_dialog = inject(MatDialog);
|
|
3673
3753
|
_reportData = inject(ReportData);
|
|
3674
3754
|
_message = inject(FsMessage);
|
|
3675
|
-
_prompt = inject(FsPrompt);
|
|
3676
3755
|
_destroyRef = inject(DestroyRef);
|
|
3677
3756
|
constructor() {
|
|
3678
3757
|
this._init();
|
|
@@ -3694,17 +3773,28 @@ class GlobalSettingsComponent {
|
|
|
3694
3773
|
this._dialogRef.close(instructions);
|
|
3695
3774
|
}));
|
|
3696
3775
|
};
|
|
3697
|
-
//
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3776
|
+
// Open the version-history dialog. Restores happen inside it, so when it
|
|
3777
|
+
// closes (however it closes) re-fetch the document: a restore changed the
|
|
3778
|
+
// current version and must reseed the editor; otherwise just refresh the
|
|
3779
|
+
// version list without touching an unsaved draft.
|
|
3780
|
+
openHistory() {
|
|
3781
|
+
const currentId = this.instructions()?.instructions?.id;
|
|
3782
|
+
this._dialog.open(InstructionHistoryComponent, {
|
|
3783
|
+
width: '800px',
|
|
3784
|
+
maxWidth: '95vw',
|
|
3785
|
+
maxHeight: '85vh',
|
|
3786
|
+
autoFocus: false,
|
|
3787
|
+
data: { versions: this.instructions()?.versions ?? [] },
|
|
3703
3788
|
})
|
|
3704
|
-
.
|
|
3789
|
+
.afterClosed()
|
|
3790
|
+
.pipe(switchMap(() => this._reportData.instructions()), takeUntilDestroyed(this._destroyRef))
|
|
3705
3791
|
.subscribe((instructions) => {
|
|
3706
|
-
|
|
3707
|
-
|
|
3792
|
+
if (instructions.instructions?.id !== currentId) {
|
|
3793
|
+
this._applyInstructions(instructions);
|
|
3794
|
+
}
|
|
3795
|
+
else {
|
|
3796
|
+
this.instructions.set(instructions);
|
|
3797
|
+
}
|
|
3708
3798
|
});
|
|
3709
3799
|
}
|
|
3710
3800
|
_init() {
|
|
@@ -3724,7 +3814,7 @@ class GlobalSettingsComponent {
|
|
|
3724
3814
|
this.concurrency.set(settings.semanticBuildConcurrency);
|
|
3725
3815
|
}
|
|
3726
3816
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GlobalSettingsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3727
|
-
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=\"
|
|
3817
|
+
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 });
|
|
3728
3818
|
}
|
|
3729
3819
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GlobalSettingsComponent, decorators: [{
|
|
3730
3820
|
type: Component,
|
|
@@ -3741,9 +3831,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3741
3831
|
MatTabsModule,
|
|
3742
3832
|
AiKnowledgeComponent,
|
|
3743
3833
|
GeneralComponent,
|
|
3744
|
-
HistoryComponent,
|
|
3745
3834
|
SemanticDatabaseComponent,
|
|
3746
|
-
], 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=\"
|
|
3835
|
+
], 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"] }]
|
|
3747
3836
|
}], ctorParameters: () => [] });
|
|
3748
3837
|
|
|
3749
3838
|
// The reports listing — an fs-list of reports. Rows are links built from a URL
|
|
@@ -4109,8 +4198,8 @@ class FsAiReportComponent {
|
|
|
4109
4198
|
ReportPptxService,
|
|
4110
4199
|
ReportPdfService,
|
|
4111
4200
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
|
|
4112
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
4113
|
-
], 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$
|
|
4201
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-D9IzyGer.mjs').then((module) => module.default) }),
|
|
4202
|
+
], 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 });
|
|
4114
4203
|
}
|
|
4115
4204
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportComponent, decorators: [{
|
|
4116
4205
|
type: Component,
|
|
@@ -4122,7 +4211,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4122
4211
|
ReportPptxService,
|
|
4123
4212
|
ReportPdfService,
|
|
4124
4213
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
|
|
4125
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
4214
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-D9IzyGer.mjs').then((module) => module.default) }),
|
|
4126
4215
|
], imports: [
|
|
4127
4216
|
FsFilterModule,
|
|
4128
4217
|
FsMenuModule,
|
|
@@ -4153,4 +4242,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4153
4242
|
*/
|
|
4154
4243
|
|
|
4155
4244
|
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 };
|
|
4156
|
-
//# sourceMappingURL=firestitch-report-firestitch-report-
|
|
4245
|
+
//# sourceMappingURL=firestitch-report-firestitch-report-BwC8lvzk.mjs.map
|