@genesislcap/foundation-ui 14.398.0 → 14.400.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/custom-elements.json +1937 -766
  2. package/dist/dts/ai-criteria-search/ai-criteria-search.d.ts +41 -0
  3. package/dist/dts/ai-criteria-search/ai-criteria-search.d.ts.map +1 -0
  4. package/dist/dts/ai-criteria-search/ai-criteria-search.styles.d.ts +2 -0
  5. package/dist/dts/ai-criteria-search/ai-criteria-search.styles.d.ts.map +1 -0
  6. package/dist/dts/ai-criteria-search/ai-criteria-search.template.d.ts +4 -0
  7. package/dist/dts/ai-criteria-search/ai-criteria-search.template.d.ts.map +1 -0
  8. package/dist/dts/ai-criteria-search/index.d.ts +7 -0
  9. package/dist/dts/ai-criteria-search/index.d.ts.map +1 -0
  10. package/dist/dts/ai-criteria-search/validation/criteria-ir.d.ts +36 -0
  11. package/dist/dts/ai-criteria-search/validation/criteria-ir.d.ts.map +1 -0
  12. package/dist/dts/ai-criteria-search/validation/operator-map.d.ts +17 -0
  13. package/dist/dts/ai-criteria-search/validation/operator-map.d.ts.map +1 -0
  14. package/dist/dts/ai-criteria-search/validation/schema-validator.d.ts +15 -0
  15. package/dist/dts/ai-criteria-search/validation/schema-validator.d.ts.map +1 -0
  16. package/dist/dts/ai-criteria-search/validation-error-notification.d.ts +14 -0
  17. package/dist/dts/ai-criteria-search/validation-error-notification.d.ts.map +1 -0
  18. package/dist/dts/ai-indicator/ai-indicator.d.ts +31 -0
  19. package/dist/dts/ai-indicator/ai-indicator.d.ts.map +1 -0
  20. package/dist/dts/ai-indicator/ai-indicator.styles.d.ts +4 -0
  21. package/dist/dts/ai-indicator/ai-indicator.styles.d.ts.map +1 -0
  22. package/dist/dts/ai-indicator/ai-indicator.template.d.ts +4 -0
  23. package/dist/dts/ai-indicator/ai-indicator.template.d.ts.map +1 -0
  24. package/dist/dts/ai-indicator/index.d.ts +4 -0
  25. package/dist/dts/ai-indicator/index.d.ts.map +1 -0
  26. package/dist/dts/base-components.d.ts +12 -0
  27. package/dist/dts/base-components.d.ts.map +1 -1
  28. package/dist/dts/index.d.ts +2 -0
  29. package/dist/dts/index.d.ts.map +1 -1
  30. package/dist/esm/ai-criteria-search/ai-criteria-search.js +172 -0
  31. package/dist/esm/ai-criteria-search/ai-criteria-search.styles.js +36 -0
  32. package/dist/esm/ai-criteria-search/ai-criteria-search.template.js +44 -0
  33. package/dist/esm/ai-criteria-search/index.js +6 -0
  34. package/dist/esm/ai-criteria-search/validation/criteria-ir.js +7 -0
  35. package/dist/esm/ai-criteria-search/validation/operator-map.js +97 -0
  36. package/dist/esm/ai-criteria-search/validation/schema-validator.js +150 -0
  37. package/dist/esm/ai-criteria-search/validation-error-notification.js +34 -0
  38. package/dist/esm/ai-indicator/ai-indicator.js +190 -0
  39. package/dist/esm/ai-indicator/ai-indicator.styles.js +132 -0
  40. package/dist/esm/ai-indicator/ai-indicator.template.js +72 -0
  41. package/dist/esm/ai-indicator/index.js +3 -0
  42. package/dist/esm/base-components.js +4 -0
  43. package/dist/esm/index.js +2 -0
  44. package/package.json +19 -18
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Maps IR operators to Groovy criteria using foundation-criteria.
3
+ *
4
+ * @public
5
+ */
6
+ import { CriteriaBuilder, ExpressionBuilder, Join, Serialisers, } from '@genesislcap/foundation-criteria';
7
+ const OPERATOR_TO_SERIALISER = {
8
+ contains: Serialisers.containsIgnoreCase,
9
+ equals: Serialisers.EQ,
10
+ startsWith: Serialisers.startsWith,
11
+ endsWith: Serialisers.endsWith,
12
+ greaterThan: Serialisers.GT,
13
+ lessThan: Serialisers.LT,
14
+ greaterThanOrEqual: Serialisers.GE,
15
+ lessThanOrEqual: Serialisers.LE,
16
+ notEqual: Serialisers.NE,
17
+ dateIsToday: Serialisers.dateIsToday,
18
+ dateIsAfter: Serialisers.dateIsGreaterEqual,
19
+ dateIsGreaterEqual: Serialisers.dateIsGreaterEqual,
20
+ dateIsBefore: Serialisers.dateIsLessEqual,
21
+ dateIsLessEqual: Serialisers.dateIsLessEqual,
22
+ dateIsEqual: Serialisers.dateIsEqual,
23
+ dateTimeIsAfter: Serialisers.dateTimeIsGreaterEqual,
24
+ dateTimeIsGreaterEqual: Serialisers.dateTimeIsGreaterEqual,
25
+ dateTimeIsBefore: Serialisers.dateTimeIsLessEqual,
26
+ dateTimeIsLessEqual: Serialisers.dateTimeIsLessEqual,
27
+ };
28
+ function clauseToExpression(clause) {
29
+ var _a, _b;
30
+ const serialiser = (_a = OPERATOR_TO_SERIALISER[clause.operator]) !== null && _a !== void 0 ? _a : Serialisers.containsIgnoreCase;
31
+ return new ExpressionBuilder()
32
+ .withField(clause.field)
33
+ .withValue((_b = clause.value) !== null && _b !== void 0 ? _b : null)
34
+ .withSerialiser(serialiser)
35
+ .withGroup('default')
36
+ .build();
37
+ }
38
+ function buildGroupExpression(expressions, groupJoin) {
39
+ if (expressions.length === 1)
40
+ return expressions[0];
41
+ return expressions.reduce((acc, expr, i) => (i === 0 ? [expr] : [...acc, groupJoin, expr]), []);
42
+ }
43
+ /**
44
+ * Converts validated CriteriaGroup[] to a Groovy criteria string using CriteriaBuilder.
45
+ * Groups are AND-ed at top level. Within each group, clauses use group.logic (and/or).
46
+ * If group.negated is true, the group's combined expression is wrapped in NOT.
47
+ */
48
+ export function groupsToCriteria(groups) {
49
+ var _a;
50
+ const builder = new CriteriaBuilder();
51
+ let hasAny = false;
52
+ for (const group of groups) {
53
+ if (!((_a = group.clauses) === null || _a === void 0 ? void 0 : _a.length))
54
+ continue;
55
+ const expressions = group.clauses.map(clauseToExpression);
56
+ const groupJoin = group.logic === 'or' ? Join.Or() : Join.And();
57
+ const compound = buildGroupExpression(expressions, groupJoin);
58
+ const opts = expressions.length > 1 ? { join: groupJoin } : {};
59
+ if (group.negated) {
60
+ builder.And([Join.Not(), compound]);
61
+ }
62
+ else {
63
+ builder.And(expressions.length === 1 ? expressions[0] : expressions, opts);
64
+ }
65
+ hasAny = true;
66
+ }
67
+ return hasAny ? builder.build() : '';
68
+ }
69
+ export const STRING_OPERATORS = [
70
+ 'contains',
71
+ 'equals',
72
+ 'startsWith',
73
+ 'endsWith',
74
+ 'notEqual',
75
+ ];
76
+ export const NUMERIC_OPERATORS = [
77
+ 'equals',
78
+ 'greaterThan',
79
+ 'lessThan',
80
+ 'greaterThanOrEqual',
81
+ 'lessThanOrEqual',
82
+ 'notEqual',
83
+ ];
84
+ export const DATE_OPERATORS = [
85
+ 'dateIsToday',
86
+ 'dateIsAfter',
87
+ 'dateIsBefore',
88
+ 'dateIsEqual',
89
+ 'dateIsGreaterEqual',
90
+ 'dateIsLessEqual',
91
+ ];
92
+ export const DATETIME_OPERATORS = [
93
+ 'dateTimeIsAfter',
94
+ 'dateTimeIsBefore',
95
+ 'dateTimeIsGreaterEqual',
96
+ 'dateTimeIsLessEqual',
97
+ ];
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Schema-first validation for AI-generated criteria clauses.
3
+ * Never auto-corrects; surfaces structured errors.
4
+ *
5
+ * @public
6
+ */
7
+ import { DATE_OPERATORS, DATETIME_OPERATORS, NUMERIC_OPERATORS, STRING_OPERATORS, } from './operator-map';
8
+ /** Genesis server date types - exact match to avoid false positives (e.g. STATUS containing INT) */
9
+ const DATE_TYPE_SET = new Set(['DATE', 'LOCALDATE']);
10
+ /** Genesis server datetime types */
11
+ const DATETIME_TYPE_SET = new Set(['DATETIME', 'NANO_TIMESTAMP', 'LOCALDATETIME', 'TIMESTAMP']);
12
+ /** Genesis server numeric types */
13
+ const NUMERIC_TYPE_SET = new Set([
14
+ 'INT',
15
+ 'INTEGER',
16
+ 'LONG',
17
+ 'SHORT',
18
+ 'DOUBLE',
19
+ 'BIGDECIMAL',
20
+ 'DECIMAL',
21
+ ]);
22
+ /** Normalize type by stripping parameters e.g. STRING(256) -> STRING, BIGDECIMAL(10,3) -> BIGDECIMAL */
23
+ function normalizeFieldType(fieldType) {
24
+ return (fieldType || '').toUpperCase().trim().split('(')[0].trim();
25
+ }
26
+ function getOperatorsForFieldType(fieldType) {
27
+ const normalized = normalizeFieldType(fieldType);
28
+ if (DATE_TYPE_SET.has(normalized))
29
+ return DATE_OPERATORS;
30
+ if (DATETIME_TYPE_SET.has(normalized))
31
+ return DATETIME_OPERATORS;
32
+ if (NUMERIC_TYPE_SET.has(normalized))
33
+ return NUMERIC_OPERATORS;
34
+ return STRING_OPERATORS;
35
+ }
36
+ function isNumericFieldType(fieldType) {
37
+ return NUMERIC_TYPE_SET.has(normalizeFieldType(fieldType));
38
+ }
39
+ function isValidDateValue(value) {
40
+ if (value === null || value === undefined)
41
+ return false;
42
+ if (typeof value === 'number')
43
+ return !Number.isNaN(value) && value > 0;
44
+ if (typeof value === 'string') {
45
+ return (/^\d{8}$/.test(value) ||
46
+ /^\d{4}-\d{2}-\d{2}$/.test(value) ||
47
+ /^\d{8}-\d{2}:\d{2}(:\d{2})?$/.test(value));
48
+ }
49
+ return false;
50
+ }
51
+ function isValidNumericValue(value) {
52
+ if (value === null || value === undefined)
53
+ return false;
54
+ if (typeof value === 'number')
55
+ return !Number.isNaN(value);
56
+ if (typeof value === 'string')
57
+ return !Number.isNaN(Number(value));
58
+ return false;
59
+ }
60
+ export function validateClauses(clauses, fieldMetadata) {
61
+ const valid = [];
62
+ const invalid = [];
63
+ const fieldMap = new Map();
64
+ if (Array.isArray(fieldMetadata) && fieldMetadata.length > 0) {
65
+ const first = fieldMetadata[0];
66
+ if (typeof first === 'string') {
67
+ fieldMetadata.forEach((name) => fieldMap.set(name, undefined));
68
+ }
69
+ else {
70
+ fieldMetadata.forEach((m) => fieldMap.set(m.NAME, m));
71
+ }
72
+ }
73
+ for (const clause of clauses) {
74
+ const { field, operator, value } = clause;
75
+ if (!field || typeof field !== 'string') {
76
+ invalid.push({ field: String(field), message: 'Field name is required' });
77
+ continue;
78
+ }
79
+ const meta = fieldMap.get(field);
80
+ const fieldType = meta && 'TYPE' in meta ? meta.TYPE : 'STRING';
81
+ const allowedOperators = getOperatorsForFieldType(fieldType);
82
+ if (!allowedOperators.includes(operator)) {
83
+ invalid.push({
84
+ field,
85
+ operator,
86
+ value,
87
+ message: `Operator '${operator}' is not allowed for field type ${fieldType}`,
88
+ });
89
+ continue;
90
+ }
91
+ if (DATE_OPERATORS.includes(operator) || DATETIME_OPERATORS.includes(operator)) {
92
+ if (operator !== 'dateIsToday' && !isValidDateValue(value)) {
93
+ invalid.push({
94
+ field,
95
+ operator,
96
+ value,
97
+ message: 'Invalid date/datetime value; use yyyyMMdd, yyyy-MM-dd, or epoch',
98
+ });
99
+ continue;
100
+ }
101
+ }
102
+ if (isNumericFieldType(fieldType) &&
103
+ NUMERIC_OPERATORS.includes(operator) &&
104
+ !isValidNumericValue(value)) {
105
+ invalid.push({
106
+ field,
107
+ operator,
108
+ value,
109
+ message: 'Invalid numeric value',
110
+ });
111
+ continue;
112
+ }
113
+ if (fieldMap.size > 0 && !fieldMap.has(field)) {
114
+ invalid.push({
115
+ field,
116
+ operator,
117
+ value,
118
+ message: `Unknown field '${field}'`,
119
+ });
120
+ continue;
121
+ }
122
+ valid.push(clause);
123
+ }
124
+ return { valid, invalid };
125
+ }
126
+ /**
127
+ * Validates groups of criteria clauses. Each group's clauses are validated
128
+ * using the same per-clause logic as validateClauses.
129
+ */
130
+ export function validateGroups(groups, fieldMetadata) {
131
+ var _a;
132
+ const valid = [];
133
+ const invalid = [];
134
+ for (const group of groups) {
135
+ if (!(group === null || group === void 0 ? void 0 : group.clauses) || !Array.isArray(group.clauses)) {
136
+ invalid.push({ field: '', message: 'Group must have a clauses array' });
137
+ continue;
138
+ }
139
+ const { valid: validClauses, invalid: invalidClauses } = validateClauses(group.clauses, fieldMetadata);
140
+ invalid.push(...invalidClauses);
141
+ if (validClauses.length > 0) {
142
+ valid.push({
143
+ logic: (_a = group.logic) !== null && _a !== void 0 ? _a : 'and',
144
+ negated: group.negated,
145
+ clauses: validClauses,
146
+ });
147
+ }
148
+ }
149
+ return { valid, invalid };
150
+ }
@@ -0,0 +1,34 @@
1
+ import { showNotification } from '@genesislcap/foundation-notifications';
2
+ const TOAST_CLOSE_TIMEOUT_MS = 4000;
3
+ /**
4
+ * Formats validation errors into a user-friendly message.
5
+ */
6
+ export function formatValidationErrors(errors) {
7
+ if (errors.length === 0)
8
+ return '';
9
+ if (errors.length === 1)
10
+ return errors[0].message;
11
+ return errors.map((e) => e.message).join('; ');
12
+ }
13
+ /**
14
+ * Shows a criteria error as a toast notification.
15
+ *
16
+ * @param title - Notification title (e.g. "Criteria validation error")
17
+ * @param body - Error message to display
18
+ * @param tagName - Design system prefix (e.g. "rapid", "foundation")
19
+ */
20
+ export function showCriteriaError(title, body, tagName) {
21
+ if (!(body === null || body === void 0 ? void 0 : body.trim()))
22
+ return;
23
+ showNotification({
24
+ title,
25
+ body,
26
+ config: {
27
+ toast: {
28
+ autoClose: true,
29
+ closeTimeout: TOAST_CLOSE_TIMEOUT_MS,
30
+ type: 'error',
31
+ },
32
+ },
33
+ }, tagName);
34
+ }
@@ -0,0 +1,190 @@
1
+ import { __awaiter, __decorate } from "tslib";
2
+ import { AIProvider } from '@genesislcap/foundation-ai';
3
+ import { observable, volatile } from '@microsoft/fast-element';
4
+ import { FoundationElement } from '@microsoft/fast-foundation';
5
+ import { wasClickOutsideElement } from '../utils';
6
+ import { foundationAiIndicatorStyles as styles } from './ai-indicator.styles';
7
+ import { foundationAiIndicatorTemplate as template } from './ai-indicator.template';
8
+ const POLL_INTERVAL_MS = 2500;
9
+ /**
10
+ * @tagname %%prefix%%-ai-indicator
11
+ */
12
+ export class AiIndicator extends FoundationElement {
13
+ constructor() {
14
+ super(...arguments);
15
+ this.status = null;
16
+ this.open = false;
17
+ this.state = 'none';
18
+ this.isInstalling = false;
19
+ this.pollTimer = null;
20
+ this.clickOutside = (e) => this.handleClickOutside(e);
21
+ }
22
+ get chromeStatusLabel() {
23
+ var _a, _b;
24
+ if (((_a = this.status) === null || _a === void 0 ? void 0 : _a.provider) !== 'chrome')
25
+ return null;
26
+ const a = (_b = this.status) === null || _b === void 0 ? void 0 : _b.chromeAvailability;
27
+ if (a === 'available')
28
+ return 'Installed';
29
+ if (a === 'downloading' || this.isInstalling)
30
+ return 'Downloading…';
31
+ if (a === 'downloadable')
32
+ return 'Downloadable';
33
+ if (a === 'unavailable')
34
+ return 'Unavailable';
35
+ return null;
36
+ }
37
+ get canInstall() {
38
+ var _a, _b, _c;
39
+ return (!!((_a = this.aiProvider) === null || _a === void 0 ? void 0 : _a.triggerDownload) &&
40
+ ((_b = this.status) === null || _b === void 0 ? void 0 : _b.provider) === 'chrome' &&
41
+ ((_c = this.status) === null || _c === void 0 ? void 0 : _c.chromeAvailability) === 'downloadable' &&
42
+ !this.isInstalling);
43
+ }
44
+ get isDownloading() {
45
+ var _a;
46
+ return ((_a = this.status) === null || _a === void 0 ? void 0 : _a.chromeAvailability) === 'downloading' || this.isInstalling;
47
+ }
48
+ connectedCallback() {
49
+ const _super = Object.create(null, {
50
+ connectedCallback: { get: () => super.connectedCallback }
51
+ });
52
+ return __awaiter(this, void 0, void 0, function* () {
53
+ _super.connectedCallback.call(this);
54
+ yield this.refreshStatus();
55
+ this.maybeStartPolling();
56
+ });
57
+ }
58
+ disconnectedCallback() {
59
+ this.stopPolling();
60
+ document.removeEventListener('mousedown', this.clickOutside);
61
+ }
62
+ openChanged() {
63
+ if (this.open) {
64
+ document.addEventListener('mousedown', this.clickOutside);
65
+ this.refreshStatus();
66
+ }
67
+ else {
68
+ document.removeEventListener('mousedown', this.clickOutside);
69
+ }
70
+ }
71
+ handleClickOutside(event) {
72
+ if (wasClickOutsideElement(event, this)) {
73
+ this.open = false;
74
+ }
75
+ }
76
+ toggleDropdown() {
77
+ this.open = !this.open;
78
+ }
79
+ refreshStatus() {
80
+ return __awaiter(this, void 0, void 0, function* () {
81
+ var _a;
82
+ if (!((_a = this.aiProvider) === null || _a === void 0 ? void 0 : _a.getStatus)) {
83
+ this.status = null;
84
+ this.state = 'none';
85
+ return;
86
+ }
87
+ try {
88
+ const s = yield this.aiProvider.getStatus();
89
+ this.status = s;
90
+ this.state = this.deriveState(s);
91
+ if (this.state === 'available') {
92
+ this.isInstalling = false;
93
+ }
94
+ }
95
+ catch (_b) {
96
+ this.status = null;
97
+ this.state = 'none';
98
+ this.isInstalling = false;
99
+ }
100
+ });
101
+ }
102
+ deriveState(s) {
103
+ if (!s || s.provider === 'none')
104
+ return 'none';
105
+ if (s.provider === 'chrome') {
106
+ const a = s.chromeAvailability;
107
+ if (a === 'available')
108
+ return 'available';
109
+ if (a === 'downloading')
110
+ return 'downloading';
111
+ if (a === 'downloadable')
112
+ return 'downloadable';
113
+ if (a === 'unavailable')
114
+ return 'unavailable';
115
+ return 'unavailable';
116
+ }
117
+ return 'server';
118
+ }
119
+ maybeStartPolling() {
120
+ var _a;
121
+ if (((_a = this.status) === null || _a === void 0 ? void 0 : _a.chromeAvailability) === 'downloading' || this.isInstalling) {
122
+ this.startPolling();
123
+ }
124
+ else {
125
+ this.stopPolling();
126
+ }
127
+ }
128
+ startPolling() {
129
+ if (this.pollTimer)
130
+ return;
131
+ this.pollTimer = setInterval(() => {
132
+ this.refreshStatus().then(() => {
133
+ this.maybeStartPolling();
134
+ });
135
+ }, POLL_INTERVAL_MS);
136
+ }
137
+ stopPolling() {
138
+ if (this.pollTimer) {
139
+ clearInterval(this.pollTimer);
140
+ this.pollTimer = null;
141
+ }
142
+ }
143
+ onInstall() {
144
+ return __awaiter(this, void 0, void 0, function* () {
145
+ var _a;
146
+ if (!this.canInstall || !((_a = this.aiProvider) === null || _a === void 0 ? void 0 : _a.triggerDownload))
147
+ return;
148
+ this.isInstalling = true;
149
+ try {
150
+ yield this.aiProvider.triggerDownload();
151
+ this.startPolling();
152
+ }
153
+ catch (_b) {
154
+ this.isInstalling = false;
155
+ }
156
+ finally {
157
+ yield this.refreshStatus();
158
+ }
159
+ });
160
+ }
161
+ }
162
+ __decorate([
163
+ AIProvider
164
+ ], AiIndicator.prototype, "aiProvider", void 0);
165
+ __decorate([
166
+ observable
167
+ ], AiIndicator.prototype, "status", void 0);
168
+ __decorate([
169
+ observable
170
+ ], AiIndicator.prototype, "open", void 0);
171
+ __decorate([
172
+ observable
173
+ ], AiIndicator.prototype, "state", void 0);
174
+ __decorate([
175
+ observable
176
+ ], AiIndicator.prototype, "isInstalling", void 0);
177
+ __decorate([
178
+ volatile
179
+ ], AiIndicator.prototype, "chromeStatusLabel", null);
180
+ __decorate([
181
+ volatile
182
+ ], AiIndicator.prototype, "canInstall", null);
183
+ __decorate([
184
+ volatile
185
+ ], AiIndicator.prototype, "isDownloading", null);
186
+ export const foundationAiIndicator = AiIndicator.compose({
187
+ baseName: 'ai-indicator',
188
+ template,
189
+ styles,
190
+ });
@@ -0,0 +1,132 @@
1
+ import { css } from '@microsoft/fast-element';
2
+ export const foundationAiIndicatorStyles = (context, definition) => css `
3
+ :host {
4
+ display: flex;
5
+ justify-content: center;
6
+ align-items: center;
7
+ width: 100%;
8
+ margin: calc(var(--design-unit) * 2px) 0;
9
+ position: relative;
10
+
11
+ /* Same as connection indicator */
12
+ --green: 50 164 49;
13
+ --trafic-light-green: rgb(var(--green));
14
+ --red: 187 30 16;
15
+ --trafic-light-red: rgb(var(--red));
16
+ --amber: 230 160 20;
17
+ --trafic-light-amber: rgb(var(--amber));
18
+ }
19
+
20
+ .ai-indicator-container {
21
+ display: flex;
22
+ flex-direction: column;
23
+ align-items: center;
24
+ position: relative;
25
+ }
26
+
27
+ .ai-indicator-badge {
28
+ display: inline-flex;
29
+ align-items: center;
30
+ gap: calc(var(--design-unit) * 1.4px);
31
+ font-size: 12px;
32
+ line-height: 1;
33
+ font-weight: 600;
34
+ text-transform: uppercase;
35
+ height: calc(var(--design-unit) * 6px);
36
+ padding: calc(var(--design-unit) * 0.5px) calc(var(--design-unit) * 2px);
37
+ border-radius: calc(var(--design-unit) * 1px);
38
+ cursor: pointer;
39
+ color: #fff;
40
+ }
41
+
42
+ .ai-indicator-badge:hover {
43
+ opacity: 90%;
44
+ }
45
+
46
+ .ai-indicator-badge.green {
47
+ background: var(--trafic-light-green);
48
+ }
49
+
50
+ .ai-indicator-badge.amber {
51
+ background: var(--trafic-light-amber);
52
+ }
53
+
54
+ .ai-indicator-badge.red {
55
+ background: var(--trafic-light-red);
56
+ }
57
+
58
+ .ai-indicator-badge .ai-indicator-icon {
59
+ flex-shrink: 0;
60
+ color: inherit;
61
+ }
62
+
63
+ .ai-indicator-badge.pulsing {
64
+ animation: ai-indicator-pulse 1.5s ease-in-out infinite;
65
+ }
66
+
67
+ @keyframes ai-indicator-pulse {
68
+ 0%,
69
+ 100% {
70
+ opacity: 100%;
71
+ }
72
+
73
+ 50% {
74
+ opacity: 50%;
75
+ }
76
+ }
77
+
78
+ .ai-indicator-dropdown {
79
+ position: absolute;
80
+ top: 100%;
81
+ right: 0;
82
+ margin-top: calc(var(--design-unit) * 2px);
83
+ min-width: 200px;
84
+ padding: calc(var(--design-unit) * 4px);
85
+ background: var(--neutral-layer-floating, #fff);
86
+ border: 1px solid var(--neutral-stroke-rest, #e0e0e0);
87
+ border-radius: calc(var(--design-unit) * 2px);
88
+ box-shadow: 0 calc(var(--design-unit) * 1px) calc(var(--design-unit) * 4px) rgb(0 0 0 / 12%);
89
+ z-index: 1000;
90
+ }
91
+
92
+ .ai-indicator-info {
93
+ display: flex;
94
+ flex-direction: column;
95
+ gap: 0;
96
+ margin: 0;
97
+ padding: 0;
98
+ }
99
+
100
+ .ai-indicator-info .info-row {
101
+ display: grid;
102
+ grid-template-columns: 72px 1fr;
103
+ gap: calc(var(--design-unit) * 4px);
104
+ align-items: baseline;
105
+ padding: calc(var(--design-unit) * 1.5px) 0;
106
+ border-bottom: 1px solid var(--neutral-stroke-subtle, #eee);
107
+ }
108
+
109
+ .ai-indicator-info .info-row:last-child {
110
+ border-bottom: none;
111
+ }
112
+
113
+ .ai-indicator-info .info-label {
114
+ margin: 0;
115
+ font-size: var(--type-ramp-base-font-size);
116
+ font-weight: 500;
117
+ color: var(--neutral-foreground-hint, #666);
118
+ }
119
+
120
+ .ai-indicator-info .info-value {
121
+ margin: 0;
122
+ font-size: var(--type-ramp-base-font-size);
123
+ color: var(--neutral-foreground-rest, #333);
124
+ text-transform: capitalize;
125
+ word-break: break-word;
126
+ }
127
+
128
+ .install-button {
129
+ margin-top: calc(var(--design-unit) * 3px);
130
+ width: 100%;
131
+ }
132
+ `;
@@ -0,0 +1,72 @@
1
+ import { isAIFeatureEnabled } from '@genesislcap/foundation-ai';
2
+ import { html, when } from '@microsoft/fast-element';
3
+ import { classNames } from '@microsoft/fast-web-utilities';
4
+ import { getPrefix } from '../utils';
5
+ const aiIndicatorTemplate = (prefix) => html `
6
+ ${when(() => isAIFeatureEnabled(), html `
7
+ <div class="ai-indicator-container" part="ai-indicator-container">
8
+ <div
9
+ class=${(x) => classNames('ai-indicator-badge', ['green', x.state === 'available' || x.state === 'server'], ['amber', x.state === 'downloading' || x.state === 'downloadable'], ['red', x.state === 'unavailable' || x.state === 'none'], ['pulsing', x.state === 'downloading' || x.isInstalling])}
10
+ data-test-id="ai-indicator-badge"
11
+ part="ai-indicator-badge"
12
+ role="button"
13
+ tabindex="0"
14
+ aria-label="AI status"
15
+ aria-haspopup="true"
16
+ aria-expanded="${(x) => x.open}"
17
+ @click=${(x) => x.toggleDropdown()}
18
+ @keydown=${(x, c) => {
19
+ const e = c.event;
20
+ if (e.key === 'Enter' || e.key === ' ') {
21
+ e.preventDefault();
22
+ x.toggleDropdown();
23
+ }
24
+ }}
25
+ >
26
+ <${prefix}-icon
27
+ class="ai-indicator-icon"
28
+ name="wand-magic-sparkles"
29
+ size="xs"
30
+ variant="solid"
31
+ part="ai-indicator-icon"
32
+ ></${prefix}-icon>
33
+ <span class="ai-indicator-text" data-test-id="ai-indicator-text" part="ai-indicator-text">AI</span>
34
+ </div>
35
+ ${when((x) => x.open, html `
36
+ <div class="ai-indicator-dropdown" part="ai-indicator-dropdown">
37
+ <dl class="ai-indicator-info">
38
+ <div class="info-row">
39
+ <dt class="info-label">Provider</dt>
40
+ <dd class="info-value">${(x) => { var _a, _b; return (_b = (_a = x.status) === null || _a === void 0 ? void 0 : _a.provider) !== null && _b !== void 0 ? _b : 'none'; }}</dd>
41
+ </div>
42
+ <div class="info-row">
43
+ <dt class="info-label">Model</dt>
44
+ <dd class="info-value">${(x) => { var _a; return ((_a = x.status) === null || _a === void 0 ? void 0 : _a.model) || '—'; }}</dd>
45
+ </div>
46
+ ${when((x) => x.chromeStatusLabel, html `
47
+ <div class="info-row">
48
+ <dt class="info-label">Status</dt>
49
+ <dd class="info-value">${(x) => x.chromeStatusLabel}</dd>
50
+ </div>
51
+ `)}
52
+ </dl>
53
+ ${when((x) => x.canInstall, html `
54
+ <${prefix}-button
55
+ class="install-button"
56
+ appearance="accent"
57
+ @click=${(x, c) => {
58
+ c.event.preventDefault();
59
+ x.onInstall();
60
+ }}
61
+ >
62
+ Install model
63
+ </${prefix}-button>
64
+ `)}
65
+ </div>
66
+ `)}
67
+ </div>
68
+ `)}
69
+ `;
70
+ export const foundationAiIndicatorTemplate = html `
71
+ ${(x) => aiIndicatorTemplate(getPrefix(x))}
72
+ `;
@@ -0,0 +1,3 @@
1
+ export { AiIndicator, foundationAiIndicator } from './ai-indicator';
2
+ export { foundationAiIndicatorTemplate } from './ai-indicator.template';
3
+ export { foundationAiIndicatorStyles } from './ai-indicator.styles';
@@ -2,6 +2,8 @@
2
2
  import { foundationAccordion } from './accordion';
3
3
  import { foundationAccordionItem } from './accordion-item';
4
4
  import { foundationActionsMenu } from './actions-menu';
5
+ import { foundationAiCriteriaSearch } from './ai-criteria-search';
6
+ import { foundationAiIndicator } from './ai-indicator';
5
7
  import { foundationAnchor } from './anchor';
6
8
  import { foundationAnchoredRegion } from './anchored-region';
7
9
  import { foundationAvatar } from './avatar';
@@ -84,6 +86,8 @@ import { foundationTreeView } from './tree-view';
84
86
  import { foundationUrlInput } from './url-input';
85
87
  export const baseComponents = {
86
88
  foundationAccordion,
89
+ foundationAiCriteriaSearch,
90
+ foundationAiIndicator,
87
91
  foundationAccordionItem,
88
92
  foundationActionsMenu,
89
93
  foundationAnchor,