@finos/legend-application-marketplace 0.2.19 → 0.2.21
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/lib/application/LegendMarketplaceApplicationConfig.d.ts +6 -3
- package/lib/application/LegendMarketplaceApplicationConfig.d.ts.map +1 -1
- package/lib/application/LegendMarketplaceApplicationConfig.js +13 -4
- package/lib/application/LegendMarketplaceApplicationConfig.js.map +1 -1
- package/lib/components/Pagination/PaginationControls.d.ts.map +1 -1
- package/lib/components/Pagination/PaginationControls.js +2 -2
- package/lib/components/Pagination/PaginationControls.js.map +1 -1
- package/lib/components/ProviderCard/LegendMarketplaceOrderProfileCard.d.ts +23 -0
- package/lib/components/ProviderCard/LegendMarketplaceOrderProfileCard.d.ts.map +1 -0
- package/lib/components/ProviderCard/LegendMarketplaceOrderProfileCard.js +93 -0
- package/lib/components/ProviderCard/LegendMarketplaceOrderProfileCard.js.map +1 -0
- package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.d.ts.map +1 -1
- package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.js +5 -11
- package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.js.map +1 -1
- package/lib/components/ProviderCard/OrderProfileDetailModal.d.ts +26 -0
- package/lib/components/ProviderCard/OrderProfileDetailModal.d.ts.map +1 -0
- package/lib/components/ProviderCard/OrderProfileDetailModal.js +36 -0
- package/lib/components/ProviderCard/OrderProfileDetailModal.js.map +1 -0
- package/lib/components/ProviderCard/OrderProfileMultiselectModal.d.ts +26 -0
- package/lib/components/ProviderCard/OrderProfileMultiselectModal.d.ts.map +1 -0
- package/lib/components/ProviderCard/OrderProfileMultiselectModal.js +31 -0
- package/lib/components/ProviderCard/OrderProfileMultiselectModal.js.map +1 -0
- package/lib/components/ProviderCard/orderProfileUtils.d.ts +71 -0
- package/lib/components/ProviderCard/orderProfileUtils.d.ts.map +1 -0
- package/lib/components/ProviderCard/orderProfileUtils.js +122 -0
- package/lib/components/ProviderCard/orderProfileUtils.js.map +1 -0
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/package.json +1 -1
- package/lib/pages/Lakehouse/entitlements/PermitDataAccessRequest.d.ts.map +1 -1
- package/lib/pages/Lakehouse/entitlements/PermitDataAccessRequest.js +3 -0
- package/lib/pages/Lakehouse/entitlements/PermitDataAccessRequest.js.map +1 -1
- package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.d.ts.map +1 -1
- package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.js +42 -9
- package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.js.map +1 -1
- package/lib/stores/LegendMarketPlaceVendorDataStore.d.ts +6 -2
- package/lib/stores/LegendMarketPlaceVendorDataStore.d.ts.map +1 -1
- package/lib/stores/LegendMarketPlaceVendorDataStore.js +25 -2
- package/lib/stores/LegendMarketPlaceVendorDataStore.js.map +1 -1
- package/lib/stores/LegendMarketplaceBaseStore.d.ts +1 -1
- package/lib/stores/LegendMarketplaceBaseStore.d.ts.map +1 -1
- package/lib/stores/LegendMarketplaceBaseStore.js +10 -5
- package/lib/stores/LegendMarketplaceBaseStore.js.map +1 -1
- package/lib/stores/ai/LegendMarketplaceAIChatStore.d.ts +10 -0
- package/lib/stores/ai/LegendMarketplaceAIChatStore.d.ts.map +1 -1
- package/lib/stores/ai/LegendMarketplaceAIChatStore.js +115 -50
- package/lib/stores/ai/LegendMarketplaceAIChatStore.js.map +1 -1
- package/lib/stores/cart/CartStore.d.ts +14 -2
- package/lib/stores/cart/CartStore.d.ts.map +1 -1
- package/lib/stores/cart/CartStore.js +68 -5
- package/lib/stores/cart/CartStore.js.map +1 -1
- package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.d.ts +2 -1
- package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.d.ts.map +1 -1
- package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.js +8 -3
- package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.js.map +1 -1
- package/package.json +10 -10
- package/src/application/LegendMarketplaceApplicationConfig.ts +19 -11
- package/src/components/Pagination/PaginationControls.tsx +19 -17
- package/src/components/ProviderCard/LegendMarketplaceOrderProfileCard.tsx +246 -0
- package/src/components/ProviderCard/LegendMarketplaceTerminalCard.tsx +9 -16
- package/src/components/ProviderCard/OrderProfileDetailModal.tsx +224 -0
- package/src/components/ProviderCard/OrderProfileMultiselectModal.tsx +142 -0
- package/src/components/ProviderCard/orderProfileUtils.ts +165 -0
- package/src/pages/Lakehouse/entitlements/PermitDataAccessRequest.tsx +3 -0
- package/src/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.tsx +170 -21
- package/src/stores/LegendMarketPlaceVendorDataStore.tsx +33 -1
- package/src/stores/LegendMarketplaceBaseStore.ts +13 -9
- package/src/stores/ai/LegendMarketplaceAIChatStore.ts +273 -69
- package/src/stores/cart/CartStore.ts +90 -4
- package/src/stores/lakehouse/entitlements/EntitlementsDashboardState.ts +10 -1
- package/tsconfig.json +4 -0
|
@@ -44,9 +44,12 @@ import {
|
|
|
44
44
|
LegendAIQuestionIntent,
|
|
45
45
|
LegendAIResolvedEntities,
|
|
46
46
|
TDSServiceSourceType,
|
|
47
|
+
classifyQuestionIntentFast,
|
|
47
48
|
findLegendAIPlugin,
|
|
48
49
|
processQuestionViaOrchestrator,
|
|
49
50
|
handleMetadataQuestion,
|
|
51
|
+
buildMetadataOverview,
|
|
52
|
+
attachMetadataOverview,
|
|
50
53
|
generateAndJudgeSql,
|
|
51
54
|
executeSqlAndReport,
|
|
52
55
|
analyzeOrchestratorResults,
|
|
@@ -59,8 +62,14 @@ import {
|
|
|
59
62
|
createMessagePair,
|
|
60
63
|
elapsedSeconds,
|
|
61
64
|
LEGEND_AI_ORCHESTRATOR_FALLBACK_ACTION_ID,
|
|
65
|
+
cleanLlmSqlResponse,
|
|
66
|
+
isValidSqlCorrection,
|
|
62
67
|
} from '@finos/legend-lego/legend-ai';
|
|
63
|
-
import {
|
|
68
|
+
import {
|
|
69
|
+
QueryExplicitExecutionContextInfo,
|
|
70
|
+
extractElementNameFromPath,
|
|
71
|
+
} from '@finos/legend-graph';
|
|
72
|
+
import { generateGAVCoordinates } from '@finos/legend-storage';
|
|
64
73
|
import {
|
|
65
74
|
type V1_DataSpace,
|
|
66
75
|
V1_deserializeDataSpace,
|
|
@@ -146,6 +155,16 @@ export function unwrapProductDetails(product: DataProductSearchResult): {
|
|
|
146
155
|
return { groupId: '', artifactId: '', versionId: '', path: '' };
|
|
147
156
|
}
|
|
148
157
|
|
|
158
|
+
function toCoordinatesString(
|
|
159
|
+
coords: LegendAIOrchestratorDataProductCoordinates,
|
|
160
|
+
): string {
|
|
161
|
+
return generateGAVCoordinates(
|
|
162
|
+
coords.group_id,
|
|
163
|
+
coords.artifact_id,
|
|
164
|
+
coords.version,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
149
168
|
export class LegendMarketplaceAIChatStore {
|
|
150
169
|
readonly baseStore: LegendMarketplaceBaseStore;
|
|
151
170
|
|
|
@@ -263,7 +282,7 @@ export class LegendMarketplaceAIChatStore {
|
|
|
263
282
|
this.selectedProductMetadata = firstScope
|
|
264
283
|
? {
|
|
265
284
|
name: firstScope.name,
|
|
266
|
-
coordinates:
|
|
285
|
+
coordinates: toCoordinatesString(firstScope.coordinates),
|
|
267
286
|
serviceSummaries: [],
|
|
268
287
|
}
|
|
269
288
|
: undefined;
|
|
@@ -322,7 +341,7 @@ export class LegendMarketplaceAIChatStore {
|
|
|
322
341
|
): LegendAIProductMetadata {
|
|
323
342
|
const metadata: LegendAIProductMetadata = {
|
|
324
343
|
name: result.dataProductTitle ?? 'Unknown',
|
|
325
|
-
coordinates:
|
|
344
|
+
coordinates: toCoordinatesString(coordinates),
|
|
326
345
|
serviceSummaries: [],
|
|
327
346
|
accessPointGroups: [],
|
|
328
347
|
};
|
|
@@ -880,13 +899,9 @@ export class LegendMarketplaceAIChatStore {
|
|
|
880
899
|
if (!groupId || !artifactId || !versionId || !path) {
|
|
881
900
|
return;
|
|
882
901
|
}
|
|
883
|
-
const key =
|
|
902
|
+
const key = generateGAVCoordinates(groupId, artifactId, versionId);
|
|
884
903
|
if (
|
|
885
|
-
this.scopeProducts.some(
|
|
886
|
-
(p) =>
|
|
887
|
-
`${p.coordinates.group_id}:${p.coordinates.artifact_id}:${p.coordinates.version}` ===
|
|
888
|
-
key,
|
|
889
|
-
)
|
|
904
|
+
this.scopeProducts.some((p) => toCoordinatesString(p.coordinates) === key)
|
|
890
905
|
) {
|
|
891
906
|
return;
|
|
892
907
|
}
|
|
@@ -923,7 +938,7 @@ export class LegendMarketplaceAIChatStore {
|
|
|
923
938
|
this.selectedProductMetadata = firstScope
|
|
924
939
|
? {
|
|
925
940
|
name: firstScope.name,
|
|
926
|
-
coordinates:
|
|
941
|
+
coordinates: toCoordinatesString(firstScope.coordinates),
|
|
927
942
|
serviceSummaries: [],
|
|
928
943
|
}
|
|
929
944
|
: undefined;
|
|
@@ -1091,7 +1106,7 @@ export class LegendMarketplaceAIChatStore {
|
|
|
1091
1106
|
this.baseStore.marketplaceServerClient
|
|
1092
1107
|
.entitySearch(
|
|
1093
1108
|
env,
|
|
1094
|
-
coordinates.data_product
|
|
1109
|
+
extractElementNameFromPath(coordinates.data_product),
|
|
1095
1110
|
entitySearchOptions,
|
|
1096
1111
|
)
|
|
1097
1112
|
.catch(() => undefined),
|
|
@@ -1343,6 +1358,73 @@ export class LegendMarketplaceAIChatStore {
|
|
|
1343
1358
|
return relevant;
|
|
1344
1359
|
}
|
|
1345
1360
|
|
|
1361
|
+
private async handleNoServices(
|
|
1362
|
+
question: string,
|
|
1363
|
+
setMessages: MessageSetter,
|
|
1364
|
+
startTime: number,
|
|
1365
|
+
contextPromise: Promise<void>,
|
|
1366
|
+
): Promise<void> {
|
|
1367
|
+
addThinkingStep(
|
|
1368
|
+
setMessages,
|
|
1369
|
+
'No dataset schemas available — entity search did not return results for this data product.',
|
|
1370
|
+
);
|
|
1371
|
+
completeThinkingSteps(setMessages);
|
|
1372
|
+
updateLastAssistant(setMessages, () => ({
|
|
1373
|
+
textAnswer:
|
|
1374
|
+
'Could not resolve dataset schemas for this data product. You can try the Legend AI Orchestrator to generate a Pure query instead.',
|
|
1375
|
+
isProcessing: false,
|
|
1376
|
+
}));
|
|
1377
|
+
this.offerOrchestratorFallback(question, setMessages, startTime);
|
|
1378
|
+
await contextPromise;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
private async handleZeroRows(
|
|
1382
|
+
judgedSql: string,
|
|
1383
|
+
question: string,
|
|
1384
|
+
services: TDSServiceSchema[],
|
|
1385
|
+
coordinates: LegendAIOrchestratorDataProductCoordinates,
|
|
1386
|
+
metadata: LegendAIProductMetadata,
|
|
1387
|
+
context: LegendAIOperationContext,
|
|
1388
|
+
timing: { startTime: number; contextPromise: Promise<void> },
|
|
1389
|
+
): Promise<void> {
|
|
1390
|
+
const { startTime, contextPromise } = timing;
|
|
1391
|
+
const { setMessages } = context;
|
|
1392
|
+
const coordinatesStr = toCoordinatesString(coordinates);
|
|
1393
|
+
const corrected = await this.attemptZeroRowCorrection(
|
|
1394
|
+
judgedSql,
|
|
1395
|
+
question,
|
|
1396
|
+
services,
|
|
1397
|
+
coordinatesStr,
|
|
1398
|
+
setMessages,
|
|
1399
|
+
coordinates,
|
|
1400
|
+
);
|
|
1401
|
+
if (corrected) {
|
|
1402
|
+
await contextPromise;
|
|
1403
|
+
await this.safeAnalyzeResults(
|
|
1404
|
+
question,
|
|
1405
|
+
corrected.sql,
|
|
1406
|
+
corrected.result,
|
|
1407
|
+
metadata,
|
|
1408
|
+
context,
|
|
1409
|
+
startTime,
|
|
1410
|
+
);
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
const datasetList = services
|
|
1414
|
+
.slice(0, MAX_RELEVANT_SERVICES)
|
|
1415
|
+
.map((s) => s.title)
|
|
1416
|
+
.join(', ');
|
|
1417
|
+
const datasetSuffix =
|
|
1418
|
+
services.length > MAX_RELEVANT_SERVICES
|
|
1419
|
+
? ` and ${services.length - MAX_RELEVANT_SERVICES} more`
|
|
1420
|
+
: '';
|
|
1421
|
+
updateLastAssistant(setMessages, () => ({
|
|
1422
|
+
textAnswer: `The SQL 2.0 query executed successfully but returned **0 rows**. The applied filters may not match any records in the available datasets, or the specific values may not exist.\n\n**Queried datasets:** ${datasetList}${datasetSuffix}`,
|
|
1423
|
+
}));
|
|
1424
|
+
this.offerOrchestratorFallback(question, setMessages, startTime);
|
|
1425
|
+
await contextPromise;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1346
1428
|
private async dispatchWithSql2(
|
|
1347
1429
|
question: string,
|
|
1348
1430
|
relevantDatasetNames: string[],
|
|
@@ -1358,47 +1440,202 @@ export class LegendMarketplaceAIChatStore {
|
|
|
1358
1440
|
|
|
1359
1441
|
const config = this.config;
|
|
1360
1442
|
const history = this.buildConversationHistory();
|
|
1361
|
-
const context = {
|
|
1443
|
+
const context = {
|
|
1444
|
+
config,
|
|
1445
|
+
plugin,
|
|
1446
|
+
history,
|
|
1447
|
+
setMessages,
|
|
1448
|
+
};
|
|
1362
1449
|
|
|
1363
|
-
const
|
|
1450
|
+
const services = this.getServicesForQuery(relevantDatasetNames);
|
|
1451
|
+
const contextPromise =
|
|
1452
|
+
services.length > 0
|
|
1453
|
+
? this.buildContextPromise(question, metadata, setMessages)
|
|
1454
|
+
: Promise.resolve();
|
|
1364
1455
|
|
|
1365
|
-
|
|
1456
|
+
const fastIntent = classifyQuestionIntentFast(question, true);
|
|
1457
|
+
|
|
1458
|
+
// ── Pure METADATA: fast classifier is confident, no data signals ──
|
|
1459
|
+
if (
|
|
1460
|
+
fastIntent.intent === LegendAIQuestionIntent.METADATA &&
|
|
1461
|
+
!fastIntent.ambiguous
|
|
1462
|
+
) {
|
|
1366
1463
|
await handleMetadataQuestion(
|
|
1367
1464
|
question,
|
|
1368
1465
|
metadata,
|
|
1369
1466
|
context,
|
|
1370
1467
|
Date.now(),
|
|
1371
|
-
|
|
1468
|
+
services.length > 0,
|
|
1372
1469
|
);
|
|
1373
1470
|
return;
|
|
1374
1471
|
}
|
|
1375
1472
|
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1473
|
+
// ── Ambiguous: show both metadata overview + SQL results ──
|
|
1474
|
+
if (fastIntent.ambiguous && services.length > 0) {
|
|
1475
|
+
await this.handleAmbiguousIntent(
|
|
1476
|
+
question,
|
|
1477
|
+
services,
|
|
1478
|
+
coordinates,
|
|
1479
|
+
metadata,
|
|
1480
|
+
context,
|
|
1481
|
+
contextPromise,
|
|
1482
|
+
setMessages,
|
|
1483
|
+
);
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1379
1486
|
|
|
1380
|
-
|
|
1381
|
-
question,
|
|
1487
|
+
await this.handleLlmJudgeFallback(
|
|
1488
|
+
{ question, ...fastIntent },
|
|
1489
|
+
services,
|
|
1490
|
+
coordinates,
|
|
1382
1491
|
metadata,
|
|
1492
|
+
context,
|
|
1493
|
+
contextPromise,
|
|
1383
1494
|
setMessages,
|
|
1384
1495
|
);
|
|
1496
|
+
}
|
|
1385
1497
|
|
|
1386
|
-
|
|
1498
|
+
private async handleLlmJudgeFallback(
|
|
1499
|
+
fastIntent: {
|
|
1500
|
+
question: string;
|
|
1501
|
+
intent: LegendAIQuestionIntent;
|
|
1502
|
+
ambiguous: boolean;
|
|
1503
|
+
},
|
|
1504
|
+
services: TDSServiceSchema[],
|
|
1505
|
+
coordinates: LegendAIOrchestratorDataProductCoordinates,
|
|
1506
|
+
metadata: LegendAIProductMetadata,
|
|
1507
|
+
context: LegendAIOperationContext,
|
|
1508
|
+
contextPromise: Promise<void>,
|
|
1509
|
+
setMessages: MessageSetter,
|
|
1510
|
+
): Promise<void> {
|
|
1511
|
+
if (
|
|
1512
|
+
fastIntent.intent === LegendAIQuestionIntent.METADATA ||
|
|
1513
|
+
fastIntent.ambiguous
|
|
1514
|
+
) {
|
|
1387
1515
|
addThinkingStep(
|
|
1388
1516
|
setMessages,
|
|
1389
|
-
|
|
1517
|
+
services.length > 0
|
|
1518
|
+
? 'Checking product capabilities first and trying a data query if the datasets support it...'
|
|
1519
|
+
: 'Checking product capabilities first...',
|
|
1520
|
+
);
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
const intent = await context.plugin.classifyQuestionIntent(
|
|
1524
|
+
fastIntent.question,
|
|
1525
|
+
services.length > 0,
|
|
1526
|
+
context.config,
|
|
1527
|
+
);
|
|
1528
|
+
|
|
1529
|
+
if (intent === LegendAIQuestionIntent.METADATA) {
|
|
1530
|
+
await handleMetadataQuestion(
|
|
1531
|
+
fastIntent.question,
|
|
1532
|
+
metadata,
|
|
1533
|
+
context,
|
|
1534
|
+
Date.now(),
|
|
1535
|
+
services.length > 0,
|
|
1536
|
+
);
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
const startTime = Date.now();
|
|
1541
|
+
|
|
1542
|
+
if (services.length === 0) {
|
|
1543
|
+
await this.handleNoServices(
|
|
1544
|
+
fastIntent.question,
|
|
1545
|
+
setMessages,
|
|
1546
|
+
startTime,
|
|
1547
|
+
contextPromise,
|
|
1390
1548
|
);
|
|
1391
|
-
completeThinkingSteps(setMessages);
|
|
1392
|
-
updateLastAssistant(setMessages, () => ({
|
|
1393
|
-
textAnswer:
|
|
1394
|
-
'Could not resolve dataset schemas for this data product. You can try the Legend AI Orchestrator to generate a Pure query instead.',
|
|
1395
|
-
isProcessing: false,
|
|
1396
|
-
}));
|
|
1397
|
-
this.offerOrchestratorFallback(question, setMessages, startTime);
|
|
1398
|
-
await contextPromise;
|
|
1399
1549
|
return;
|
|
1400
1550
|
}
|
|
1401
1551
|
|
|
1552
|
+
await this.runSqlPath(
|
|
1553
|
+
fastIntent.question,
|
|
1554
|
+
services,
|
|
1555
|
+
coordinates,
|
|
1556
|
+
metadata,
|
|
1557
|
+
context,
|
|
1558
|
+
contextPromise,
|
|
1559
|
+
setMessages,
|
|
1560
|
+
);
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
private async handleAmbiguousIntent(
|
|
1564
|
+
question: string,
|
|
1565
|
+
services: TDSServiceSchema[],
|
|
1566
|
+
coordinates: LegendAIOrchestratorDataProductCoordinates,
|
|
1567
|
+
metadata: LegendAIProductMetadata,
|
|
1568
|
+
context: LegendAIOperationContext,
|
|
1569
|
+
contextPromise: Promise<void>,
|
|
1570
|
+
setMessages: MessageSetter,
|
|
1571
|
+
): Promise<void> {
|
|
1572
|
+
addThinkingStep(
|
|
1573
|
+
setMessages,
|
|
1574
|
+
'Intent is ambiguous, providing metadata context and querying data...',
|
|
1575
|
+
);
|
|
1576
|
+
|
|
1577
|
+
let metadataOverview: string | undefined;
|
|
1578
|
+
try {
|
|
1579
|
+
addThinkingStep(setMessages, 'Building metadata context...');
|
|
1580
|
+
metadataOverview = await buildMetadataOverview(
|
|
1581
|
+
question,
|
|
1582
|
+
metadata,
|
|
1583
|
+
context,
|
|
1584
|
+
);
|
|
1585
|
+
} catch {
|
|
1586
|
+
addThinkingStep(
|
|
1587
|
+
setMessages,
|
|
1588
|
+
'Could not build metadata context — continuing with data query...',
|
|
1589
|
+
);
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
try {
|
|
1593
|
+
await this.runSqlPath(
|
|
1594
|
+
question,
|
|
1595
|
+
services,
|
|
1596
|
+
coordinates,
|
|
1597
|
+
metadata,
|
|
1598
|
+
context,
|
|
1599
|
+
contextPromise,
|
|
1600
|
+
setMessages,
|
|
1601
|
+
);
|
|
1602
|
+
if (metadataOverview) {
|
|
1603
|
+
attachMetadataOverview(setMessages, metadataOverview);
|
|
1604
|
+
}
|
|
1605
|
+
} catch (queryError) {
|
|
1606
|
+
assertErrorThrown(queryError);
|
|
1607
|
+
addThinkingStep(
|
|
1608
|
+
setMessages,
|
|
1609
|
+
'Query failed, answering from product metadata...',
|
|
1610
|
+
);
|
|
1611
|
+
await handleMetadataQuestion(
|
|
1612
|
+
question,
|
|
1613
|
+
metadata,
|
|
1614
|
+
context,
|
|
1615
|
+
Date.now(),
|
|
1616
|
+
true,
|
|
1617
|
+
);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
/**
|
|
1622
|
+
* Core SQL generation → execution → analysis pipeline.
|
|
1623
|
+
* Extracted so both the direct DATA_QUERY path and the ambiguous-intent
|
|
1624
|
+
* path can reuse it.
|
|
1625
|
+
*/
|
|
1626
|
+
private async runSqlPath(
|
|
1627
|
+
question: string,
|
|
1628
|
+
services: TDSServiceSchema[],
|
|
1629
|
+
coordinates: LegendAIOrchestratorDataProductCoordinates,
|
|
1630
|
+
metadata: LegendAIProductMetadata,
|
|
1631
|
+
context: LegendAIOperationContext,
|
|
1632
|
+
contextPromise: Promise<void>,
|
|
1633
|
+
setMessages: MessageSetter,
|
|
1634
|
+
): Promise<void> {
|
|
1635
|
+
const { config, plugin } = context;
|
|
1636
|
+
const coordinatesStr = toCoordinatesString(coordinates);
|
|
1637
|
+
const startTime = Date.now();
|
|
1638
|
+
|
|
1402
1639
|
const totalColumns = services.reduce((sum, s) => sum + s.columns.length, 0);
|
|
1403
1640
|
addThinkingStep(
|
|
1404
1641
|
setMessages,
|
|
@@ -1445,39 +1682,15 @@ export class LegendMarketplaceAIChatStore {
|
|
|
1445
1682
|
}
|
|
1446
1683
|
|
|
1447
1684
|
if (sqlResult.rows.length === 0) {
|
|
1448
|
-
|
|
1685
|
+
await this.handleZeroRows(
|
|
1449
1686
|
judgedSql,
|
|
1450
1687
|
question,
|
|
1451
1688
|
services,
|
|
1452
|
-
coordinatesStr,
|
|
1453
|
-
setMessages,
|
|
1454
1689
|
coordinates,
|
|
1690
|
+
metadata,
|
|
1691
|
+
context,
|
|
1692
|
+
{ startTime, contextPromise },
|
|
1455
1693
|
);
|
|
1456
|
-
if (corrected) {
|
|
1457
|
-
await contextPromise;
|
|
1458
|
-
await this.safeAnalyzeResults(
|
|
1459
|
-
question,
|
|
1460
|
-
corrected.sql,
|
|
1461
|
-
corrected.result,
|
|
1462
|
-
metadata,
|
|
1463
|
-
context,
|
|
1464
|
-
startTime,
|
|
1465
|
-
);
|
|
1466
|
-
return;
|
|
1467
|
-
}
|
|
1468
|
-
const datasetList = services
|
|
1469
|
-
.slice(0, MAX_RELEVANT_SERVICES)
|
|
1470
|
-
.map((s) => s.title)
|
|
1471
|
-
.join(', ');
|
|
1472
|
-
const datasetSuffix =
|
|
1473
|
-
services.length > MAX_RELEVANT_SERVICES
|
|
1474
|
-
? ` and ${services.length - MAX_RELEVANT_SERVICES} more`
|
|
1475
|
-
: '';
|
|
1476
|
-
updateLastAssistant(setMessages, () => ({
|
|
1477
|
-
textAnswer: `The SQL 2.0 query executed successfully but returned **0 rows**. The applied filters may not match any records in the available datasets, or the specific values may not exist.\n\n**Queried datasets:** ${datasetList}${datasetSuffix}`,
|
|
1478
|
-
}));
|
|
1479
|
-
this.offerOrchestratorFallback(question, setMessages, startTime);
|
|
1480
|
-
await contextPromise;
|
|
1481
1694
|
return;
|
|
1482
1695
|
}
|
|
1483
1696
|
|
|
@@ -1572,17 +1785,8 @@ export class LegendMarketplaceAIChatStore {
|
|
|
1572
1785
|
}
|
|
1573
1786
|
try {
|
|
1574
1787
|
const raw = await plugin.callLLM(prompt, config);
|
|
1575
|
-
const trimmed = raw
|
|
1576
|
-
|
|
1577
|
-
.replace(/^```\w*\n?/, '')
|
|
1578
|
-
.replace(/\n?```$/, '')
|
|
1579
|
-
.replace(/;\s*$/, '')
|
|
1580
|
-
.trim();
|
|
1581
|
-
if (
|
|
1582
|
-
trimmed.length === 0 ||
|
|
1583
|
-
!trimmed.toLowerCase().startsWith('select') ||
|
|
1584
|
-
trimmed === currentSql
|
|
1585
|
-
) {
|
|
1788
|
+
const trimmed = cleanLlmSqlResponse(raw);
|
|
1789
|
+
if (!isValidSqlCorrection(trimmed, currentSql)) {
|
|
1586
1790
|
return undefined;
|
|
1587
1791
|
}
|
|
1588
1792
|
addThinkingStep(setMessages, 'Retrying with corrected filters...');
|
|
@@ -36,11 +36,17 @@ import {
|
|
|
36
36
|
type CartSummary,
|
|
37
37
|
type OrderDetails,
|
|
38
38
|
type TerminalResult,
|
|
39
|
+
type TraderProfile,
|
|
40
|
+
type TraderProfileItem,
|
|
41
|
+
RecommendationSource,
|
|
39
42
|
} from '@finos/legend-server-marketplace';
|
|
40
43
|
import type { LegendMarketplaceBaseStore } from '../LegendMarketplaceBaseStore.js';
|
|
41
44
|
import { APPLICATION_EVENT } from '@finos/legend-application';
|
|
42
45
|
import { toastManager } from '../../components/Toast/CartToast.js';
|
|
43
46
|
|
|
47
|
+
const boolToString = (val: boolean | undefined): 'true' | 'false' =>
|
|
48
|
+
val ? 'true' : 'false';
|
|
49
|
+
|
|
44
50
|
enum BUSINESS_REASONS {
|
|
45
51
|
NEW_HIRE = 'New Hire',
|
|
46
52
|
NEW_ROLE = 'New Role',
|
|
@@ -83,6 +89,7 @@ export class CartStore {
|
|
|
83
89
|
clearCart: flow,
|
|
84
90
|
deleteCartItem: flow,
|
|
85
91
|
addToCartWithAPI: flow,
|
|
92
|
+
addOrderProfileItemsToCart: flow,
|
|
86
93
|
});
|
|
87
94
|
this.baseStore = baseStore;
|
|
88
95
|
}
|
|
@@ -168,7 +175,10 @@ export class CartStore {
|
|
|
168
175
|
return [];
|
|
169
176
|
}
|
|
170
177
|
|
|
171
|
-
*addToCartWithAPI(
|
|
178
|
+
*addToCartWithAPI(
|
|
179
|
+
cartItemData: CartItemRequest,
|
|
180
|
+
suppressSuccessToast = false,
|
|
181
|
+
): GeneratorFn<{
|
|
172
182
|
success: boolean;
|
|
173
183
|
recommendations?: TerminalResult[];
|
|
174
184
|
message: string;
|
|
@@ -194,7 +204,7 @@ export class CartStore {
|
|
|
194
204
|
const responseMessage: string = response.message;
|
|
195
205
|
if (!/^2\d\d$/.test(String(response.status_code))) {
|
|
196
206
|
toastManager.warning(responseMessage);
|
|
197
|
-
} else {
|
|
207
|
+
} else if (!suppressSuccessToast) {
|
|
198
208
|
toastManager.success(responseMessage);
|
|
199
209
|
}
|
|
200
210
|
|
|
@@ -229,20 +239,96 @@ export class CartStore {
|
|
|
229
239
|
}
|
|
230
240
|
}
|
|
231
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Adds a list of order-profile items to the cart, skipping already-owned ones.
|
|
244
|
+
* Each item is added sequentially so that vendor-profile items can be added
|
|
245
|
+
* before their associated add-ons.
|
|
246
|
+
*/
|
|
247
|
+
*addOrderProfileItemsToCart(
|
|
248
|
+
items: TraderProfileItem[],
|
|
249
|
+
suppressSuccessToast = false,
|
|
250
|
+
): GeneratorFn<void> {
|
|
251
|
+
for (const item of items) {
|
|
252
|
+
if (item.isOwned) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
yield flowResult(
|
|
256
|
+
this.addToCartWithAPI(
|
|
257
|
+
{
|
|
258
|
+
id: item.id,
|
|
259
|
+
productName: item.productName,
|
|
260
|
+
providerName: item.providerName,
|
|
261
|
+
category: item.category,
|
|
262
|
+
price: item.price,
|
|
263
|
+
description: item.description ?? '',
|
|
264
|
+
isOwned: boolToString(item.isOwned),
|
|
265
|
+
...(item.model === null || item.model === undefined
|
|
266
|
+
? {}
|
|
267
|
+
: { model: item.model }),
|
|
268
|
+
skipWorkflow: true,
|
|
269
|
+
...(item.isMandatory === undefined
|
|
270
|
+
? {}
|
|
271
|
+
: { isMandatory: item.isMandatory }),
|
|
272
|
+
...(item.vendorProfileId === undefined
|
|
273
|
+
? {}
|
|
274
|
+
: { vendorProfileId: item.vendorProfileId }),
|
|
275
|
+
...(item.permissionId === undefined
|
|
276
|
+
? {}
|
|
277
|
+
: { permissionId: item.permissionId }),
|
|
278
|
+
},
|
|
279
|
+
suppressSuccessToast,
|
|
280
|
+
),
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Returns true when all non-owned items of the profile are present in the
|
|
287
|
+
* cart. For multiselect profiles, at least one complete terminal bundle
|
|
288
|
+
* (terminal + its associated add-ons) must be fully in the cart.
|
|
289
|
+
*/
|
|
290
|
+
isOrderProfileInCart(profile: TraderProfile): boolean {
|
|
291
|
+
const nonOwnedItems = profile.items.filter((item) => !item.isOwned);
|
|
292
|
+
const nonOwnedTerminals = nonOwnedItems.filter((item) => item.isTerminal);
|
|
293
|
+
if (profile.multiselect) {
|
|
294
|
+
return nonOwnedTerminals.some((terminal) => {
|
|
295
|
+
const selectedModel = terminal.model ?? null;
|
|
296
|
+
const bundleItems = [
|
|
297
|
+
terminal,
|
|
298
|
+
...profile.items.filter(
|
|
299
|
+
(item) =>
|
|
300
|
+
!item.isTerminal &&
|
|
301
|
+
!item.isOwned &&
|
|
302
|
+
(selectedModel === null || item.model === selectedModel),
|
|
303
|
+
),
|
|
304
|
+
];
|
|
305
|
+
return bundleItems.every((item) => this.isItemInCart(item.id));
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return (
|
|
309
|
+
nonOwnedItems.length > 0 &&
|
|
310
|
+
nonOwnedItems.every((item) => this.isItemInCart(item.id))
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
232
314
|
providerToCartRequest(provider: TerminalResult): CartItemRequest {
|
|
315
|
+
const isInventory = provider.source === RecommendationSource.INVENTORY;
|
|
233
316
|
return {
|
|
234
|
-
id: provider.id,
|
|
317
|
+
id: isInventory ? (provider.permissionId ?? provider.id) : provider.id,
|
|
235
318
|
productName: provider.productName,
|
|
236
319
|
providerName: provider.providerName,
|
|
237
320
|
category: provider.category,
|
|
238
321
|
price: provider.price,
|
|
239
322
|
description: provider.description,
|
|
240
|
-
isOwned: provider.isOwned
|
|
323
|
+
isOwned: boolToString(provider.isOwned),
|
|
241
324
|
model: provider.model ?? provider.productName,
|
|
242
325
|
skipWorkflow: provider.skipWorkflow ?? false,
|
|
243
326
|
...(provider.vendorProfileId !== undefined && {
|
|
244
327
|
vendorProfileId: provider.vendorProfileId,
|
|
245
328
|
}),
|
|
329
|
+
...(provider.permissionId !== undefined && {
|
|
330
|
+
permissionId: provider.permissionId,
|
|
331
|
+
}),
|
|
246
332
|
...(provider.source !== undefined && {
|
|
247
333
|
source: provider.source,
|
|
248
334
|
}),
|
|
@@ -311,17 +311,19 @@ export class EntitlementsDashboardState {
|
|
|
311
311
|
filteredTasks,
|
|
312
312
|
filteredContractsForUser,
|
|
313
313
|
filteredCreatedByUserMap,
|
|
314
|
+
filteredDataRequests,
|
|
314
315
|
} = this.filterByUserEnvironment(
|
|
315
316
|
pendingTasksData,
|
|
316
317
|
contractsForUser,
|
|
317
318
|
contractsCreatedByUserMap,
|
|
319
|
+
dataRequestsCreatedByUser,
|
|
318
320
|
envMap,
|
|
319
321
|
);
|
|
320
322
|
this.pendingTaskContractMap = pendingTasksData.taskContractMap;
|
|
321
323
|
this.pendingTasks = filteredTasks;
|
|
322
324
|
this.allContractsForUser = filteredContractsForUser;
|
|
323
325
|
this.allContractsCreatedByUserMap = filteredCreatedByUserMap;
|
|
324
|
-
this.dataRequestsCreatedByUser =
|
|
326
|
+
this.dataRequestsCreatedByUser = filteredDataRequests;
|
|
325
327
|
|
|
326
328
|
this.fetchingPendingTasksState.complete();
|
|
327
329
|
this.fetchingContractsForUserState.complete();
|
|
@@ -766,11 +768,13 @@ export class EntitlementsDashboardState {
|
|
|
766
768
|
},
|
|
767
769
|
contractsForUser: V1_LiteDataContractWithUserStatus[],
|
|
768
770
|
contractsCreatedByUserMap: Map<string, ContractCreatedByUserDetails>,
|
|
771
|
+
dataRequests: V1_DataRequestWithWorkflow[],
|
|
769
772
|
envMap: Map<number, string>,
|
|
770
773
|
): {
|
|
771
774
|
filteredTasks: V1_ContractUserEventRecord[];
|
|
772
775
|
filteredContractsForUser: V1_LiteDataContractWithUserStatus[];
|
|
773
776
|
filteredCreatedByUserMap: Map<string, ContractCreatedByUserDetails>;
|
|
777
|
+
filteredDataRequests: V1_DataRequestWithWorkflow[];
|
|
774
778
|
} {
|
|
775
779
|
const userEnv =
|
|
776
780
|
this.lakehouseEntitlementsStore.marketplaceBaseStore.envState
|
|
@@ -796,10 +800,15 @@ export class EntitlementsDashboardState {
|
|
|
796
800
|
filteredCreatedByUserMap.set(guid, details);
|
|
797
801
|
}
|
|
798
802
|
}
|
|
803
|
+
const filteredDataRequests = dataRequests.filter((dr) => {
|
|
804
|
+
const envType = dr.dataRequest.resourceEnvType;
|
|
805
|
+
return !envType || envType === userEnv;
|
|
806
|
+
});
|
|
799
807
|
return {
|
|
800
808
|
filteredTasks,
|
|
801
809
|
filteredContractsForUser,
|
|
802
810
|
filteredCreatedByUserMap,
|
|
811
|
+
filteredDataRequests,
|
|
803
812
|
};
|
|
804
813
|
}
|
|
805
814
|
|
package/tsconfig.json
CHANGED
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"./src/application/LegendMarketplacePluginManager.ts",
|
|
63
63
|
"./src/application/__test-utils__/LegendMarketplaceApplicationTestUtils.ts",
|
|
64
64
|
"./src/application/extensions/Core_LegendMarketplace_LegendApplicationPlugin.ts",
|
|
65
|
+
"./src/components/ProviderCard/orderProfileUtils.ts",
|
|
65
66
|
"./src/components/__test-utils__/TEST_DATA__DataAPIs.ts",
|
|
66
67
|
"./src/components/__test-utils__/TEST_DATA__LakehouseData.ts",
|
|
67
68
|
"./src/components/__test-utils__/TEST_DATA__LakehouseSearchResultData.ts",
|
|
@@ -122,7 +123,10 @@
|
|
|
122
123
|
"./src/components/MarketplaceCard/LegendMarketplaceListItem.tsx",
|
|
123
124
|
"./src/components/MarketplaceSearchFiltersPanel/MarketplaceSearchFiltersPanel.tsx",
|
|
124
125
|
"./src/components/Pagination/PaginationControls.tsx",
|
|
126
|
+
"./src/components/ProviderCard/LegendMarketplaceOrderProfileCard.tsx",
|
|
125
127
|
"./src/components/ProviderCard/LegendMarketplaceTerminalCard.tsx",
|
|
128
|
+
"./src/components/ProviderCard/OrderProfileDetailModal.tsx",
|
|
129
|
+
"./src/components/ProviderCard/OrderProfileMultiselectModal.tsx",
|
|
126
130
|
"./src/components/SearchBar/LegendMarketplaceSearchBar.tsx",
|
|
127
131
|
"./src/components/Toast/CartToast.tsx",
|
|
128
132
|
"./src/components/__test-utils__/LegendMarketplaceStoreTestUtils.tsx",
|