@finos/legend-application-studio 28.21.12 → 28.21.14

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 (49) hide show
  1. package/lib/components/editor/ActivityBar.d.ts +13 -0
  2. package/lib/components/editor/ActivityBar.d.ts.map +1 -1
  3. package/lib/components/editor/ActivityBar.js +36 -15
  4. package/lib/components/editor/ActivityBar.js.map +1 -1
  5. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts +2 -0
  6. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts.map +1 -1
  7. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +81 -19
  8. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
  9. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.d.ts.map +1 -1
  10. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js +109 -15
  11. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js.map +1 -1
  12. package/lib/components/editor/editor-group/service-editor/ServiceEditor.d.ts.map +1 -1
  13. package/lib/components/editor/editor-group/service-editor/ServiceEditor.js +4 -1
  14. package/lib/components/editor/editor-group/service-editor/ServiceEditor.js.map +1 -1
  15. package/lib/components/workspace-setup/WorkspaceSetup.d.ts.map +1 -1
  16. package/lib/components/workspace-setup/WorkspaceSetup.js +20 -3
  17. package/lib/components/workspace-setup/WorkspaceSetup.js.map +1 -1
  18. package/lib/index.css +2 -2
  19. package/lib/index.css.map +1 -1
  20. package/lib/index.d.ts +1 -0
  21. package/lib/index.d.ts.map +1 -1
  22. package/lib/index.js +1 -0
  23. package/lib/index.js.map +1 -1
  24. package/lib/package.json +1 -1
  25. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts +34 -1
  26. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts.map +1 -1
  27. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js +203 -2
  28. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js.map +1 -1
  29. package/lib/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.d.ts +61 -0
  30. package/lib/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.d.ts.map +1 -0
  31. package/lib/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.js +17 -0
  32. package/lib/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.js.map +1 -0
  33. package/lib/stores/extensions/DSL_Service_LegendStudioApplicationPlugin_Extension.d.ts +2 -2
  34. package/lib/stores/extensions/DSL_Service_LegendStudioApplicationPlugin_Extension.d.ts.map +1 -1
  35. package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts.map +1 -1
  36. package/lib/stores/workspace-setup/WorkspaceSetupStore.js +6 -2
  37. package/lib/stores/workspace-setup/WorkspaceSetupStore.js.map +1 -1
  38. package/package.json +13 -13
  39. package/src/components/editor/ActivityBar.tsx +61 -38
  40. package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +207 -29
  41. package/src/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.tsx +370 -22
  42. package/src/components/editor/editor-group/service-editor/ServiceEditor.tsx +5 -1
  43. package/src/components/workspace-setup/WorkspaceSetup.tsx +27 -2
  44. package/src/index.ts +1 -0
  45. package/src/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.ts +317 -0
  46. package/src/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.ts +77 -0
  47. package/src/stores/extensions/DSL_Service_LegendStudioApplicationPlugin_Extension.ts +1 -2
  48. package/src/stores/workspace-setup/WorkspaceSetupStore.ts +5 -0
  49. package/tsconfig.json +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finos/legend-application-studio",
3
- "version": "28.21.12",
3
+ "version": "28.21.14",
4
4
  "description": "Legend Studio application core",
5
5
  "keywords": [
6
6
  "legend",
@@ -47,18 +47,18 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@dagrejs/dagre": "1.1.4",
50
- "@finos/legend-application": "16.0.111",
51
- "@finos/legend-art": "7.1.152",
52
- "@finos/legend-code-editor": "2.0.177",
53
- "@finos/legend-data-cube": "0.3.94",
54
- "@finos/legend-extension-dsl-data-product": "0.0.87",
55
- "@finos/legend-extension-dsl-diagram": "8.1.241",
56
- "@finos/legend-graph": "32.6.8",
57
- "@finos/legend-lego": "2.0.197",
58
- "@finos/legend-query-builder": "4.18.16",
59
- "@finos/legend-server-depot": "6.1.12",
60
- "@finos/legend-server-lakehouse": "0.3.57",
61
- "@finos/legend-server-sdlc": "5.4.3",
50
+ "@finos/legend-application": "16.0.112",
51
+ "@finos/legend-art": "7.1.153",
52
+ "@finos/legend-code-editor": "2.0.179",
53
+ "@finos/legend-data-cube": "0.3.96",
54
+ "@finos/legend-extension-dsl-data-product": "0.0.89",
55
+ "@finos/legend-extension-dsl-diagram": "8.1.243",
56
+ "@finos/legend-graph": "32.6.10",
57
+ "@finos/legend-lego": "2.0.199",
58
+ "@finos/legend-query-builder": "4.18.18",
59
+ "@finos/legend-server-depot": "6.1.13",
60
+ "@finos/legend-server-lakehouse": "0.3.59",
61
+ "@finos/legend-server-sdlc": "5.4.4",
62
62
  "@finos/legend-server-showcase": "0.2.66",
63
63
  "@finos/legend-shared": "11.0.25",
64
64
  "@finos/legend-storage": "3.0.146",
@@ -24,13 +24,11 @@ import {
24
24
  import { LEGEND_STUDIO_TEST_ID } from '../../__lib__/LegendStudioTesting.js';
25
25
  import {
26
26
  clsx,
27
- CheckIcon,
28
27
  ControlledDropdownMenu,
29
28
  RepoIcon,
30
29
  MenuContent,
31
30
  MenuContentItem,
32
31
  MenuContentItemBlankIcon,
33
- MenuContentItemIcon,
34
32
  MenuContentItemLabel,
35
33
  GitPullRequestIcon,
36
34
  GitMergeIcon,
@@ -47,6 +45,8 @@ import {
47
45
  WorkflowIcon,
48
46
  ReadMeIcon,
49
47
  DevIcon,
48
+ MoonIcon,
49
+ SunIcon,
50
50
  } from '@finos/legend-art';
51
51
  import { useEditorStore } from './EditorStoreProvider.js';
52
52
  import { forwardRef, useEffect, useState } from 'react';
@@ -97,19 +97,10 @@ import { useAuth, type AuthContextProps } from 'react-oidc-context';
97
97
  const SettingsMenu = observer(
98
98
  forwardRef<HTMLDivElement, unknown>(function SettingsMenu(props, ref) {
99
99
  const editorStore = useEditorStore();
100
- const applicationStore = useLegendStudioApplicationStore();
101
- const { layoutService } = applicationStore;
102
100
  const showDeveloperTool = (): void => {
103
101
  editorStore.panelGroupDisplayState.open();
104
102
  editorStore.setActivePanelMode(PANEL_MODE.DEV_TOOL);
105
103
  };
106
- const selectColorTheme = (key: string): void => {
107
- layoutService.setColorTheme(key, { persist: true });
108
- };
109
- // Non-production themes (e.g. the in-progress light theme) are only exposed
110
- // when the `NonProductionFeatureFlag` config option is on.
111
- const showNonProductionThemes =
112
- applicationStore.config.options.NonProductionFeatureFlag;
113
104
 
114
105
  return (
115
106
  <MenuContent ref={ref} className="activity-bar__setting__menu">
@@ -117,33 +108,6 @@ const SettingsMenu = observer(
117
108
  <MenuContentItemBlankIcon />
118
109
  <MenuContentItemLabel>Show Developer Tool</MenuContentItemLabel>
119
110
  </MenuContentItem>
120
- <MenuContentDivider />
121
- <MenuContentItem disabled={true}>
122
- <MenuContentItemBlankIcon />
123
- <MenuContentItemLabel>Color Theme</MenuContentItemLabel>
124
- </MenuContentItem>
125
- {layoutService.availableColorThemes
126
- .filter(
127
- (theme) =>
128
- STUDIO_SUPPORTED_COLOR_THEMES.has(theme.key) &&
129
- (showNonProductionThemes ||
130
- !STUDIO_NON_PRODUCTION_COLOR_THEMES.has(theme.key)),
131
- )
132
- .map((theme) => (
133
- <MenuContentItem
134
- key={theme.key}
135
- onClick={() => selectColorTheme(theme.key)}
136
- >
137
- {theme.key === layoutService.currentColorTheme.key ? (
138
- <MenuContentItemIcon>
139
- <CheckIcon />
140
- </MenuContentItemIcon>
141
- ) : (
142
- <MenuContentItemBlankIcon />
143
- )}
144
- <MenuContentItemLabel>{theme.name}</MenuContentItemLabel>
145
- </MenuContentItem>
146
- ))}
147
111
  </MenuContent>
148
112
  );
149
113
  }),
@@ -157,6 +121,64 @@ const useOptionalAuth = (): AuthContextProps | undefined => {
157
121
  }
158
122
  };
159
123
 
124
+ /**
125
+ * One-click toggle between the default dark and default light color themes,
126
+ * surfaced directly in the activity bar so users don't have to dig through the
127
+ * Settings menu to flip themes.
128
+ *
129
+ * The toggle only renders when both themes are actually exposed in the current
130
+ * environment (i.e. it respects the same `STUDIO_SUPPORTED_COLOR_THEMES` /
131
+ * `NonProductionFeatureFlag` gating as the Settings menu picker). This keeps
132
+ * the in-progress light theme hidden in production until it's stabilized.
133
+ */
134
+ export const ColorThemeToggle = observer(() => {
135
+ const applicationStore = useLegendStudioApplicationStore();
136
+ const { layoutService } = applicationStore;
137
+ const showNonProductionThemes =
138
+ applicationStore.config.options.NonProductionFeatureFlag;
139
+
140
+ const exposedThemeKeys = new Set(
141
+ layoutService.availableColorThemes
142
+ .filter(
143
+ (theme) =>
144
+ STUDIO_SUPPORTED_COLOR_THEMES.has(theme.key) &&
145
+ (showNonProductionThemes ||
146
+ !STUDIO_NON_PRODUCTION_COLOR_THEMES.has(theme.key)),
147
+ )
148
+ .map((theme) => theme.key),
149
+ );
150
+
151
+ // Only show the toggle when both endpoints of the dark<->light flip are
152
+ // actually available; otherwise it would be a no-op control.
153
+ if (
154
+ !exposedThemeKeys.has(LEGEND_APPLICATION_COLOR_THEME.DEFAULT_DARK) ||
155
+ !exposedThemeKeys.has(LEGEND_APPLICATION_COLOR_THEME.DEFAULT_LIGHT)
156
+ ) {
157
+ return null;
158
+ }
159
+
160
+ const isLight = layoutService.TEMPORARY__isLightColorThemeEnabled;
161
+ const toggleTheme = (): void => {
162
+ layoutService.setColorTheme(
163
+ isLight
164
+ ? LEGEND_APPLICATION_COLOR_THEME.DEFAULT_DARK
165
+ : LEGEND_APPLICATION_COLOR_THEME.DEFAULT_LIGHT,
166
+ { persist: true },
167
+ );
168
+ };
169
+
170
+ return (
171
+ <button
172
+ className="activity-bar__item"
173
+ onClick={toggleTheme}
174
+ tabIndex={-1}
175
+ title={isLight ? 'Switch to dark theme' : 'Switch to light theme'}
176
+ >
177
+ {isLight ? <MoonIcon /> : <SunIcon />}
178
+ </button>
179
+ );
180
+ });
181
+
160
182
  export const ActivityBarMenu: React.FC<{
161
183
  openShowcasePanel?: () => void;
162
184
  }> = ({ openShowcasePanel }) => {
@@ -570,6 +592,7 @@ export const ActivityBar = observer(() => {
570
592
  >
571
593
  <ReadMeIcon />
572
594
  </button>
595
+ <ColorThemeToggle />
573
596
  <ControlledDropdownMenu
574
597
  className="activity-bar__item"
575
598
  title="Settings"
@@ -80,6 +80,7 @@ import {
80
80
  PanelLoadingIndicator,
81
81
  GearSuggestIcon,
82
82
  FlaskIcon,
83
+ SparkleIcon,
83
84
  } from '@finos/legend-art';
84
85
  import {
85
86
  type ChangeEventHandler,
@@ -106,7 +107,7 @@ import {
106
107
  QueryBuilderAdvancedWorkflowState,
107
108
  type QueryBuilderState,
108
109
  } from '@finos/legend-query-builder';
109
- import { action, autorun, flowResult } from 'mobx';
110
+ import { action, autorun, flowResult, runInAction } from 'mobx';
110
111
  import { DataProductTestableEditor } from './testable/DataProductTestableEditor.js';
111
112
  import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor';
112
113
  import { CodeEditor } from '@finos/legend-lego/code-editor';
@@ -202,6 +203,11 @@ import {
202
203
  import type { LegendStudioApplicationStore } from '../../../../stores/LegendStudioBaseStore.js';
203
204
  import type { DepotServerClient } from '@finos/legend-server-depot';
204
205
  import { RelationElementEditor } from '../data-editor/RelationElementsDataEditor.js';
206
+ import type {
207
+ AccessPointMeta,
208
+ DataProductDocResponse,
209
+ DSL_DataProduct_LegendStudioApplicationPlugin_Extension,
210
+ } from '../../../../stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.js';
205
211
 
206
212
  export enum AP_GROUP_MODAL_ERRORS {
207
213
  GROUP_NAME_EMPTY = 'Group Name is empty',
@@ -262,8 +268,11 @@ const hoverIcon = () => {
262
268
  };
263
269
 
264
270
  const AccessPointTitle = observer(
265
- (props: { accessPointState: LakehouseAccessPointState }) => {
266
- const { accessPointState } = props;
271
+ (props: {
272
+ accessPointState: LakehouseAccessPointState;
273
+ aiSuggestionMeta?: AccessPointMeta | undefined;
274
+ }) => {
275
+ const { accessPointState, aiSuggestionMeta } = props;
267
276
  const accessPoint = accessPointState.accessPoint;
268
277
  const [editingName, setEditingName] = useState(
269
278
  accessPoint.id === newNamePlaceholder,
@@ -286,29 +295,37 @@ const AccessPointTitle = observer(
286
295
  }
287
296
  });
288
297
 
289
- return editingName ? (
290
- <textarea
291
- className="access-point-editor__name"
292
- spellCheck={false}
293
- value={accessPoint.id}
294
- onChange={updateAccessPointName}
295
- placeholder={'Access Point Name'}
296
- onBlur={handleNameBlur}
297
- style={{
298
- borderColor:
299
- accessPoint.id === newNamePlaceholder
300
- ? 'var(--color-red-300)'
301
- : 'transparent',
302
- }}
303
- />
304
- ) : (
305
- <div
306
- className="access-point-editor__name__label"
307
- onClick={handleNameEdit}
308
- title="Click to edit access point name"
309
- style={{ flex: '1 1 auto' }}
310
- >
311
- {accessPoint.id}
298
+ return (
299
+ <div style={{ flex: '1 1 auto', minWidth: 0 }}>
300
+ {editingName ? (
301
+ <textarea
302
+ className="access-point-editor__name"
303
+ spellCheck={false}
304
+ value={accessPoint.id}
305
+ onChange={updateAccessPointName}
306
+ placeholder={'Access Point Name'}
307
+ onBlur={handleNameBlur}
308
+ style={{
309
+ borderColor:
310
+ accessPoint.id === newNamePlaceholder
311
+ ? 'var(--color-red-300)'
312
+ : 'transparent',
313
+ }}
314
+ />
315
+ ) : (
316
+ <div
317
+ className="access-point-editor__name__label"
318
+ onClick={handleNameEdit}
319
+ title="Click to edit access point name"
320
+ >
321
+ {accessPoint.id}
322
+ </div>
323
+ )}
324
+ {aiSuggestionMeta && (
325
+ <div className="data-product-editor__ai-suggestion-inline">
326
+ {aiSuggestionMeta.name}
327
+ </div>
328
+ )}
312
329
  </div>
313
330
  );
314
331
  },
@@ -697,8 +714,9 @@ export const LakehouseDataProductAccessPointEditor = observer(
697
714
  (props: {
698
715
  accessPointState: LakehouseAccessPointState;
699
716
  isReadOnly: boolean;
717
+ aiSuggestionMeta?: AccessPointMeta | undefined;
700
718
  }) => {
701
- const { accessPointState } = props;
719
+ const { accessPointState, aiSuggestionMeta } = props;
702
720
  const editorStore = useEditorStore();
703
721
  const accessPoint = accessPointState.accessPoint;
704
722
  const groupState = accessPointState.state;
@@ -925,7 +943,10 @@ export const LakehouseDataProductAccessPointEditor = observer(
925
943
  />
926
944
  <div style={{ flex: 1 }}>
927
945
  <div className="access-point-editor__metadata">
928
- <AccessPointTitle accessPointState={accessPointState} />
946
+ <AccessPointTitle
947
+ accessPointState={accessPointState}
948
+ aiSuggestionMeta={aiSuggestionMeta}
949
+ />
929
950
  <div className="access-point-editor__info">
930
951
  <div className="access-point-editor__reproducible">
931
952
  <Checkbox
@@ -1166,6 +1187,11 @@ export const LakehouseDataProductAccessPointEditor = observer(
1166
1187
  {isHoveringTitle && hoverIcon()}
1167
1188
  </div>
1168
1189
  )}
1190
+ {aiSuggestionMeta && (
1191
+ <div className="data-product-editor__ai-suggestion-inline">
1192
+ {aiSuggestionMeta.title}
1193
+ </div>
1194
+ )}
1169
1195
  {editingDescription ? (
1170
1196
  <textarea
1171
1197
  className="panel__content__form__section__input"
@@ -1209,6 +1235,11 @@ export const LakehouseDataProductAccessPointEditor = observer(
1209
1235
  {isHoveringDesc && hoverIcon()}
1210
1236
  </div>
1211
1237
  )}
1238
+ {aiSuggestionMeta && (
1239
+ <div className="data-product-editor__ai-suggestion-inline">
1240
+ {aiSuggestionMeta.description}
1241
+ </div>
1242
+ )}
1212
1243
  <div className="access-point-editor__content">
1213
1244
  <div className="access-point-editor__generic-entry">
1214
1245
  <div className="access-point-editor__entry__container">
@@ -1712,6 +1743,10 @@ const AccessPointGroupEditor = observer(
1712
1743
  const [isHoveringName, setIsHoveringName] = useState(false);
1713
1744
  const [editingTitle, setEditingTitle] = useState(false);
1714
1745
  const [isHoveringTitle, setIsHoveringTitle] = useState(false);
1746
+ const [isSuggestingWithAI, setIsSuggestingWithAI] = useState(false);
1747
+ const [aiSuggestion, setAISuggestion] = useState<
1748
+ DataProductDocResponse | undefined
1749
+ >(undefined);
1715
1750
  const handleDescriptionEdit = () => setEditingDescription(true);
1716
1751
  const handleDescriptionBlur = () => {
1717
1752
  setEditingDescription(false);
@@ -1767,6 +1802,96 @@ const AccessPointGroupEditor = observer(
1767
1802
  accessPointGroup_setTitle(groupState.value, val);
1768
1803
  };
1769
1804
 
1805
+ // AI suggestion for APG
1806
+ const legendAIUrl = editorStore.applicationStore.config.legendAIUrl;
1807
+ const aiDocSuggester = legendAIUrl
1808
+ ? editorStore.pluginManager
1809
+ .getApplicationPlugins()
1810
+ .map((p) =>
1811
+ (
1812
+ p as DSL_DataProduct_LegendStudioApplicationPlugin_Extension
1813
+ ).getExtraDataProductDocumentationAISuggester?.bind(p),
1814
+ )
1815
+ .find(Boolean)
1816
+ : undefined;
1817
+ const suggestWithAI = async (): Promise<void> => {
1818
+ if (!aiDocSuggester || !legendAIUrl) {
1819
+ return;
1820
+ }
1821
+ setIsSuggestingWithAI(true);
1822
+ setAISuggestion(undefined);
1823
+ try {
1824
+ const definitions =
1825
+ await editorStore.graphManagerState.graphManager.graphToPureCode(
1826
+ editorStore.graphManagerState.graph,
1827
+ );
1828
+ const product = productEditorState.product;
1829
+ const suggestion = await aiDocSuggester(
1830
+ { definitions, data_product_name: product.path },
1831
+ legendAIUrl,
1832
+ );
1833
+ runInAction(() => {
1834
+ setAISuggestion(suggestion);
1835
+ });
1836
+ } finally {
1837
+ runInAction(() => {
1838
+ setIsSuggestingWithAI(false);
1839
+ });
1840
+ }
1841
+ };
1842
+ const applyAISuggestion = (): void => {
1843
+ if (!aiSuggestion) {
1844
+ return;
1845
+ }
1846
+ // Find matching group in the response
1847
+ const gIdx =
1848
+ productEditorState.accessPointGroupStates.indexOf(groupState);
1849
+ const groupMeta =
1850
+ aiSuggestion.access_point_groups.find(
1851
+ (g) => g.name === groupState.value.id,
1852
+ ) ?? aiSuggestion.access_point_groups[gIdx];
1853
+ if (groupMeta) {
1854
+ accessPointGroup_setTitle(groupState.value, groupMeta.title);
1855
+ accessPointGroup_setDescription(
1856
+ groupState.value,
1857
+ groupMeta.description,
1858
+ );
1859
+ }
1860
+ // Apply access point metadata for this group
1861
+ const apMetas = aiSuggestion.access_points.filter(
1862
+ (ap) => ap.group === (groupMeta?.name ?? groupState.value.id),
1863
+ );
1864
+ for (
1865
+ let j = 0;
1866
+ j < apMetas.length && j < groupState.value.accessPoints.length;
1867
+ j++
1868
+ ) {
1869
+ const apMeta = apMetas[j] as (typeof apMetas)[number];
1870
+ const ap = groupState.value.accessPoints[
1871
+ j
1872
+ ] as (typeof groupState.value.accessPoints)[number];
1873
+ // Convert suggested name to valid identifier (alphanumeric + underscore)
1874
+ const sanitizedName = apMeta.name.replace(/[^0-9a-zA-Z_]/g, '_');
1875
+ if (sanitizedName) {
1876
+ ap.id = sanitizedName;
1877
+ }
1878
+ accessPoint_setTitle(ap, apMeta.title);
1879
+ accessPoint_setDescription(ap, apMeta.description);
1880
+ }
1881
+ setAISuggestion(undefined);
1882
+ };
1883
+
1884
+ // Computed AI suggestion for current group
1885
+ const groupIndex =
1886
+ productEditorState.accessPointGroupStates.indexOf(groupState);
1887
+ const aiGroupMeta =
1888
+ aiSuggestion?.access_point_groups.find(
1889
+ (g) => g.name === groupState.value.id,
1890
+ ) ?? aiSuggestion?.access_point_groups[groupIndex];
1891
+ const aiAccessPointMetas = aiSuggestion?.access_points.filter(
1892
+ (ap) => ap.group === (aiGroupMeta?.name ?? groupState.value.id),
1893
+ );
1894
+
1770
1895
  const handleRemoveAccessPointGroup = (): void => {
1771
1896
  editorStore.applicationStore.alertService.setActionAlertInfo({
1772
1897
  message:
@@ -1852,6 +1977,48 @@ const AccessPointGroupEditor = observer(
1852
1977
  <TimesIcon />
1853
1978
  </button>
1854
1979
  </div>
1980
+ {aiDocSuggester && (
1981
+ <div style={{ padding: '0.25rem 0.5rem' }}>
1982
+ <PanelLoadingIndicator isLoading={isSuggestingWithAI} />
1983
+ {aiSuggestion ? (
1984
+ <div className="data-product-editor__ai-suggestion__actions">
1985
+ <button
1986
+ className="btn btn--dark data-product-editor__ai-suggestion__apply-btn"
1987
+ onClick={applyAISuggestion}
1988
+ title="Apply AI-generated title, description, and access point metadata for this group"
1989
+ >
1990
+ Apply
1991
+ </button>
1992
+ <button
1993
+ className="btn data-product-editor__ai-suggestion__dismiss-btn"
1994
+ onClick={(): void => setAISuggestion(undefined)}
1995
+ >
1996
+ Dismiss
1997
+ </button>
1998
+ <span className="data-product-editor__ai-suggestion-badge">
1999
+ <SparkleIcon />
2000
+ AI Suggestion
2001
+ </span>
2002
+ </div>
2003
+ ) : (
2004
+ <button
2005
+ className="data-product-editor__ai-suggest-btn"
2006
+ onClick={(): void => {
2007
+ suggestWithAI().catch(
2008
+ editorStore.applicationStore.alertUnhandledError,
2009
+ );
2010
+ }}
2011
+ disabled={isSuggestingWithAI || isReadOnly}
2012
+ title="Use AI to suggest title and descriptions for this access point group"
2013
+ >
2014
+ <SparkleIcon />
2015
+ <span>
2016
+ {isSuggestingWithAI ? 'Suggesting...' : 'Suggest with AI'}
2017
+ </span>
2018
+ </button>
2019
+ )}
2020
+ </div>
2021
+ )}
1855
2022
  <div className="access-point-editor__group-container__name-editor">
1856
2023
  {editingTitle ? (
1857
2024
  <textarea
@@ -1896,6 +2063,11 @@ const AccessPointGroupEditor = observer(
1896
2063
  </div>
1897
2064
  )}
1898
2065
  </div>
2066
+ {aiGroupMeta && (
2067
+ <div className="data-product-editor__ai-suggestion-inline">
2068
+ {aiGroupMeta.title}
2069
+ </div>
2070
+ )}
1899
2071
  <div className="access-point-editor__group-container__description-editor">
1900
2072
  {editingDescription ? (
1901
2073
  <textarea
@@ -1938,6 +2110,11 @@ const AccessPointGroupEditor = observer(
1938
2110
  {isHoveringDescription && hoverIcon()}
1939
2111
  </div>
1940
2112
  )}
2113
+ {aiGroupMeta && (
2114
+ <div className="data-product-editor__ai-suggestion-inline">
2115
+ {aiGroupMeta.description}
2116
+ </div>
2117
+ )}
1941
2118
  </div>
1942
2119
  {editorStore.applicationStore.config.options.dataProductConfig && (
1943
2120
  <AccessPointGroupPublicToggle groupState={groupState} />
@@ -1975,11 +2152,12 @@ const AccessPointGroupEditor = observer(
1975
2152
  >
1976
2153
  {groupState.accessPointStates
1977
2154
  .filter(filterByType(LakehouseAccessPointState))
1978
- .map((apState) => (
2155
+ .map((apState, idx) => (
1979
2156
  <LakehouseDataProductAccessPointEditor
1980
2157
  key={apState.uuid}
1981
2158
  isReadOnly={isReadOnly}
1982
2159
  accessPointState={apState}
2160
+ aiSuggestionMeta={aiAccessPointMetas?.[idx]}
1983
2161
  />
1984
2162
  ))}
1985
2163
  </div>