@finos/legend-lego 2.0.211 → 2.0.213
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/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/legend-ai/LegendAIDocEnrichment.d.ts +12 -12
- package/lib/legend-ai/LegendAIDocEnrichment.d.ts.map +1 -1
- package/lib/legend-ai/LegendAIDocEnrichment.js +317 -86
- package/lib/legend-ai/LegendAIDocEnrichment.js.map +1 -1
- package/lib/legend-ai/LegendAIHostIntegration.d.ts +31 -0
- package/lib/legend-ai/LegendAIHostIntegration.d.ts.map +1 -0
- package/lib/legend-ai/LegendAIHostIntegration.js +50 -0
- package/lib/legend-ai/LegendAIHostIntegration.js.map +1 -0
- package/lib/legend-ai/LegendAIServiceRetrieval.d.ts.map +1 -1
- package/lib/legend-ai/LegendAIServiceRetrieval.js +34 -35
- package/lib/legend-ai/LegendAIServiceRetrieval.js.map +1 -1
- package/lib/legend-ai/LegendAITypes.d.ts +85 -7
- package/lib/legend-ai/LegendAITypes.d.ts.map +1 -1
- package/lib/legend-ai/LegendAITypes.js +99 -1
- package/lib/legend-ai/LegendAITypes.js.map +1 -1
- package/lib/legend-ai/LegendAI_LegendApplicationPlugin_Extension.d.ts +36 -3
- package/lib/legend-ai/LegendAI_LegendApplicationPlugin_Extension.d.ts.map +1 -1
- package/lib/legend-ai/LegendAI_LegendApplicationPlugin_Extension.js +29 -0
- package/lib/legend-ai/LegendAI_LegendApplicationPlugin_Extension.js.map +1 -1
- package/lib/legend-ai/__test-utils__/LegendAITestUtils.d.ts.map +1 -1
- package/lib/legend-ai/__test-utils__/LegendAITestUtils.js +7 -0
- package/lib/legend-ai/__test-utils__/LegendAITestUtils.js.map +1 -1
- package/lib/legend-ai/components/LegendAICharts.d.ts.map +1 -1
- package/lib/legend-ai/components/LegendAICharts.js +6 -10
- package/lib/legend-ai/components/LegendAICharts.js.map +1 -1
- package/lib/legend-ai/components/LegendAIChat.d.ts.map +1 -1
- package/lib/legend-ai/components/LegendAIChat.js +263 -41
- package/lib/legend-ai/components/LegendAIChat.js.map +1 -1
- package/lib/legend-ai/components/LegendAIChatInput.d.ts +1 -1
- package/lib/legend-ai/components/LegendAIChatInput.d.ts.map +1 -1
- package/lib/legend-ai/components/LegendAIChatInput.js +2 -1
- package/lib/legend-ai/components/LegendAIChatInput.js.map +1 -1
- package/lib/legend-ai/index.d.ts +5 -2
- package/lib/legend-ai/index.d.ts.map +1 -1
- package/lib/legend-ai/index.js +5 -2
- package/lib/legend-ai/index.js.map +1 -1
- package/lib/legend-ai/stores/LegendAIChatProcessors.d.ts +9 -3
- package/lib/legend-ai/stores/LegendAIChatProcessors.d.ts.map +1 -1
- package/lib/legend-ai/stores/LegendAIChatProcessors.js +794 -220
- package/lib/legend-ai/stores/LegendAIChatProcessors.js.map +1 -1
- package/lib/legend-ai/stores/LegendAIChatState.d.ts.map +1 -1
- package/lib/legend-ai/stores/LegendAIChatState.js +47 -21
- package/lib/legend-ai/stores/LegendAIChatState.js.map +1 -1
- package/lib/legend-ai/stores/LegendAIJoinAnalysis.d.ts +29 -0
- package/lib/legend-ai/stores/LegendAIJoinAnalysis.d.ts.map +1 -0
- package/lib/legend-ai/stores/LegendAIJoinAnalysis.js +124 -0
- package/lib/legend-ai/stores/LegendAIJoinAnalysis.js.map +1 -0
- package/lib/legend-ai/stores/LegendAISqlHelpers.d.ts +25 -0
- package/lib/legend-ai/stores/LegendAISqlHelpers.d.ts.map +1 -0
- package/lib/legend-ai/stores/LegendAISqlHelpers.js +64 -0
- package/lib/legend-ai/stores/LegendAISqlHelpers.js.map +1 -0
- package/lib/legend-ai/stores/LegendAISqlJoinSanitizers.d.ts +22 -0
- package/lib/legend-ai/stores/LegendAISqlJoinSanitizers.d.ts.map +1 -0
- package/lib/legend-ai/stores/LegendAISqlJoinSanitizers.js +411 -0
- package/lib/legend-ai/stores/LegendAISqlJoinSanitizers.js.map +1 -0
- package/package.json +6 -6
- package/src/legend-ai/LegendAIDocEnrichment.ts +435 -97
- package/src/legend-ai/LegendAIHostIntegration.tsx +80 -0
- package/src/legend-ai/LegendAIServiceRetrieval.ts +50 -35
- package/src/legend-ai/LegendAITypes.ts +169 -5
- package/src/legend-ai/LegendAI_LegendApplicationPlugin_Extension.ts +73 -1
- package/src/legend-ai/__test-utils__/LegendAITestUtils.ts +10 -0
- package/src/legend-ai/components/LegendAICharts.tsx +30 -33
- package/src/legend-ai/components/LegendAIChat.tsx +555 -91
- package/src/legend-ai/components/LegendAIChatInput.tsx +6 -2
- package/src/legend-ai/index.ts +22 -3
- package/src/legend-ai/stores/LegendAIChatProcessors.ts +1286 -322
- package/src/legend-ai/stores/LegendAIChatState.ts +73 -28
- package/src/legend-ai/stores/LegendAIJoinAnalysis.ts +165 -0
- package/src/legend-ai/stores/LegendAISqlHelpers.ts +82 -0
- package/src/legend-ai/stores/LegendAISqlJoinSanitizers.ts +515 -0
- package/tsconfig.json +4 -0
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { useMemo, useCallback, useState, useRef, useEffect } from 'react';
|
|
17
|
+
import { memo, useMemo, useCallback, useState, useRef, useEffect } from 'react';
|
|
18
18
|
import {
|
|
19
19
|
SparkleStarsIcon,
|
|
20
20
|
CodeIcon,
|
|
@@ -32,10 +32,18 @@ import {
|
|
|
32
32
|
DislikeIcon,
|
|
33
33
|
ExternalLinkIcon,
|
|
34
34
|
InfoCircleIcon,
|
|
35
|
+
PythonIcon,
|
|
36
|
+
JupyterIcon,
|
|
37
|
+
CubeIcon,
|
|
35
38
|
MarkdownTextViewer,
|
|
36
39
|
clsx,
|
|
37
40
|
} from '@finos/legend-art';
|
|
38
|
-
import { noop } from '@finos/legend-shared';
|
|
41
|
+
import { assertErrorThrown, noop } from '@finos/legend-shared';
|
|
42
|
+
import type {
|
|
43
|
+
LegendAIPythonQueryCode,
|
|
44
|
+
LegendAIPythonCodegenRequest,
|
|
45
|
+
LegendAIDataCubeQueryTranslationRequest,
|
|
46
|
+
} from '../LegendAI_LegendApplicationPlugin_Extension.js';
|
|
39
47
|
import {
|
|
40
48
|
type LegendAIChatProps,
|
|
41
49
|
type LegendAIAssistantMessage,
|
|
@@ -43,14 +51,20 @@ import {
|
|
|
43
51
|
type LegendAIThinkingStep,
|
|
44
52
|
type LegendAIScopeItem,
|
|
45
53
|
type LegendAIQuestionIntent,
|
|
54
|
+
type LegendAIChatTelemetryEvent,
|
|
46
55
|
LegendAIMessageFeedbackRating,
|
|
47
56
|
LegendAIThinkingStepStatus,
|
|
48
57
|
LegendAIMessageRole,
|
|
49
58
|
LegendAIErrorType,
|
|
50
59
|
TDSServiceSourceType,
|
|
60
|
+
LEGEND_AI_FEEDBACK_PROMPT,
|
|
51
61
|
classifyQuestionIntentFast,
|
|
62
|
+
LegendAIChatTelemetryEventType,
|
|
63
|
+
LegendAITelemetryArtifact,
|
|
64
|
+
LegendAISuggestedQuerySource,
|
|
52
65
|
} from '../LegendAITypes.js';
|
|
53
66
|
import { useLegendAIChatState } from '../stores/LegendAIChatState.js';
|
|
67
|
+
import { looksLikeAccessError } from '../stores/LegendAIChatProcessors.js';
|
|
54
68
|
import { LegendAIResultGrid } from './LegendAIResultGrid.js';
|
|
55
69
|
import { LegendAIAnalysisPanel } from './LegendAIAnalysisPanel.js';
|
|
56
70
|
import { LegendAIChatInput } from './LegendAIChatInput.js';
|
|
@@ -61,6 +75,11 @@ export const LEGEND_AI_ANCHOR_ID = 'legend-ai-anchor';
|
|
|
61
75
|
const COPY_FEEDBACK_DURATION_MS = 2000;
|
|
62
76
|
const CONTEXT_BANNER_AUTO_DISMISS_MS = 20000;
|
|
63
77
|
|
|
78
|
+
type LegendAIPythonCodeEntry =
|
|
79
|
+
| { status: 'loading' }
|
|
80
|
+
| { status: 'ready'; code: LegendAIPythonQueryCode | undefined }
|
|
81
|
+
| { status: 'error'; errorMessage: string };
|
|
82
|
+
|
|
64
83
|
const LegendAIContextBanner = (props: {
|
|
65
84
|
message: string;
|
|
66
85
|
onDismiss: () => void;
|
|
@@ -211,26 +230,40 @@ export function renderStepStatusIcon(
|
|
|
211
230
|
);
|
|
212
231
|
}
|
|
213
232
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
233
|
+
const SuggestionButton = (props: {
|
|
234
|
+
query: string;
|
|
235
|
+
position: number;
|
|
236
|
+
className: string;
|
|
237
|
+
source: LegendAISuggestedQuerySource;
|
|
238
|
+
onSelect: (query: string) => void;
|
|
239
|
+
onLogTelemetryEvent?: (event: LegendAIChatTelemetryEvent) => void;
|
|
240
|
+
}): React.ReactNode => {
|
|
241
|
+
const { query, position, className, source, onSelect, onLogTelemetryEvent } =
|
|
242
|
+
props;
|
|
243
|
+
return (
|
|
244
|
+
<button
|
|
245
|
+
type="button"
|
|
246
|
+
className={className}
|
|
247
|
+
onClick={(): void => {
|
|
248
|
+
onLogTelemetryEvent?.({
|
|
249
|
+
type: LegendAIChatTelemetryEventType.SUGGESTED_QUERY_CLICKED,
|
|
250
|
+
position,
|
|
251
|
+
source,
|
|
252
|
+
});
|
|
253
|
+
onSelect(query);
|
|
254
|
+
}}
|
|
255
|
+
>
|
|
256
|
+
{query}
|
|
257
|
+
</button>
|
|
258
|
+
);
|
|
259
|
+
};
|
|
228
260
|
|
|
229
|
-
const AssistantMessageView = (props: {
|
|
261
|
+
const AssistantMessageView = memo(function AssistantMessageView(props: {
|
|
230
262
|
msg: LegendAIAssistantMessage;
|
|
263
|
+
msgIndex: number;
|
|
231
264
|
questionText: string;
|
|
232
265
|
isThinkingVisible: boolean;
|
|
233
|
-
onToggleThinking: () => void;
|
|
266
|
+
onToggleThinking: (msgIndex: number) => void;
|
|
234
267
|
onMessageFeedback?: (
|
|
235
268
|
feedback: LegendAIMessageFeedback,
|
|
236
269
|
) => Promise<void> | void;
|
|
@@ -241,9 +274,15 @@ const AssistantMessageView = (props: {
|
|
|
241
274
|
enghubDocUrl?: string;
|
|
242
275
|
enthubRequestAccessUrl?: string;
|
|
243
276
|
onRequestAccess?: (accessPointGroupTitle: string) => void;
|
|
244
|
-
|
|
277
|
+
pythonEntry?: LegendAIPythonCodeEntry;
|
|
278
|
+
onRequestPython?: (msg: LegendAIAssistantMessage) => void;
|
|
279
|
+
onOpenInDataCube?: (msg: LegendAIAssistantMessage) => void;
|
|
280
|
+
isOpeningInDataCube?: boolean;
|
|
281
|
+
onLogTelemetryEvent?: (event: LegendAIChatTelemetryEvent) => void;
|
|
282
|
+
}): React.ReactNode {
|
|
245
283
|
const {
|
|
246
284
|
msg,
|
|
285
|
+
msgIndex,
|
|
247
286
|
questionText,
|
|
248
287
|
isThinkingVisible,
|
|
249
288
|
onToggleThinking,
|
|
@@ -255,6 +294,11 @@ const AssistantMessageView = (props: {
|
|
|
255
294
|
enghubDocUrl,
|
|
256
295
|
enthubRequestAccessUrl,
|
|
257
296
|
onRequestAccess,
|
|
297
|
+
pythonEntry,
|
|
298
|
+
onRequestPython,
|
|
299
|
+
onOpenInDataCube,
|
|
300
|
+
isOpeningInDataCube,
|
|
301
|
+
onLogTelemetryEvent,
|
|
258
302
|
} = props;
|
|
259
303
|
|
|
260
304
|
const hasPermissionAccessLinks =
|
|
@@ -264,12 +308,20 @@ const AssistantMessageView = (props: {
|
|
|
264
308
|
const copyTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(
|
|
265
309
|
undefined,
|
|
266
310
|
);
|
|
311
|
+
const [showPython, setShowPython] = useState(false);
|
|
312
|
+
const [pythonCopied, setPythonCopied] = useState(false);
|
|
313
|
+
const pythonCopyTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(
|
|
314
|
+
undefined,
|
|
315
|
+
);
|
|
267
316
|
|
|
268
317
|
useEffect(
|
|
269
318
|
() => () => {
|
|
270
319
|
if (copyTimerRef.current !== undefined) {
|
|
271
320
|
clearTimeout(copyTimerRef.current);
|
|
272
321
|
}
|
|
322
|
+
if (pythonCopyTimerRef.current !== undefined) {
|
|
323
|
+
clearTimeout(pythonCopyTimerRef.current);
|
|
324
|
+
}
|
|
273
325
|
},
|
|
274
326
|
[],
|
|
275
327
|
);
|
|
@@ -278,6 +330,10 @@ const AssistantMessageView = (props: {
|
|
|
278
330
|
if (msg.sql) {
|
|
279
331
|
navigator.clipboard.writeText(msg.sql).catch(noop);
|
|
280
332
|
setSqlCopied(true);
|
|
333
|
+
onLogTelemetryEvent?.({
|
|
334
|
+
type: LegendAIChatTelemetryEventType.ARTIFACT_COPIED,
|
|
335
|
+
artifact: LegendAITelemetryArtifact.SQL,
|
|
336
|
+
});
|
|
281
337
|
if (copyTimerRef.current !== undefined) {
|
|
282
338
|
clearTimeout(copyTimerRef.current);
|
|
283
339
|
}
|
|
@@ -286,23 +342,64 @@ const AssistantMessageView = (props: {
|
|
|
286
342
|
copyTimerRef.current = undefined;
|
|
287
343
|
}, COPY_FEEDBACK_DURATION_MS);
|
|
288
344
|
}
|
|
289
|
-
}, [msg.sql]);
|
|
345
|
+
}, [msg.sql, onLogTelemetryEvent]);
|
|
346
|
+
|
|
347
|
+
const handleCopyPython = useCallback(() => {
|
|
348
|
+
if (pythonEntry?.status === 'ready' && pythonEntry.code) {
|
|
349
|
+
navigator.clipboard.writeText(pythonEntry.code.code).catch(noop);
|
|
350
|
+
setPythonCopied(true);
|
|
351
|
+
onLogTelemetryEvent?.({
|
|
352
|
+
type: LegendAIChatTelemetryEventType.ARTIFACT_COPIED,
|
|
353
|
+
artifact: LegendAITelemetryArtifact.PYTHON,
|
|
354
|
+
});
|
|
355
|
+
if (pythonCopyTimerRef.current !== undefined) {
|
|
356
|
+
clearTimeout(pythonCopyTimerRef.current);
|
|
357
|
+
}
|
|
358
|
+
pythonCopyTimerRef.current = setTimeout(() => {
|
|
359
|
+
setPythonCopied(false);
|
|
360
|
+
pythonCopyTimerRef.current = undefined;
|
|
361
|
+
}, COPY_FEEDBACK_DURATION_MS);
|
|
362
|
+
}
|
|
363
|
+
}, [pythonEntry, onLogTelemetryEvent]);
|
|
364
|
+
|
|
365
|
+
const handleTogglePython = useCallback((): void => {
|
|
366
|
+
const opening = !showPython;
|
|
367
|
+
setShowPython(opening);
|
|
368
|
+
onLogTelemetryEvent?.({
|
|
369
|
+
type: LegendAIChatTelemetryEventType.PYTHON_CODE_TOGGLED,
|
|
370
|
+
shown: opening,
|
|
371
|
+
});
|
|
372
|
+
if (opening && !pythonEntry && onRequestPython) {
|
|
373
|
+
onLogTelemetryEvent?.({
|
|
374
|
+
type: LegendAIChatTelemetryEventType.PYTHON_CODE_REQUESTED,
|
|
375
|
+
});
|
|
376
|
+
onRequestPython(msg);
|
|
377
|
+
}
|
|
378
|
+
}, [showPython, pythonEntry, onRequestPython, msg, onLogTelemetryEvent]);
|
|
290
379
|
|
|
291
380
|
const canShowFeedback =
|
|
292
381
|
!msg.isProcessing &&
|
|
293
382
|
(msg.textAnswer !== null || msg.gridData !== null || msg.error !== null);
|
|
294
|
-
const visibleThinkingSteps =
|
|
295
|
-
|
|
296
|
-
msg.
|
|
383
|
+
const visibleThinkingSteps = useMemo(
|
|
384
|
+
() => formatThinkingSteps(msg.thinkingSteps),
|
|
385
|
+
[msg.thinkingSteps],
|
|
297
386
|
);
|
|
298
|
-
const analysisSummary = (() => {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
return
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
387
|
+
const { metadataContext, analysisSummary, plainAnswer } = useMemo(() => {
|
|
388
|
+
const split = splitCombinedAnswer(msg.textAnswer);
|
|
389
|
+
const gridAnalysisFallback =
|
|
390
|
+
split.metadataContext === null ? msg.textAnswer : null;
|
|
391
|
+
return {
|
|
392
|
+
metadataContext: split.metadataContext,
|
|
393
|
+
analysisSummary:
|
|
394
|
+
msg.gridData === null
|
|
395
|
+
? null
|
|
396
|
+
: (split.queryAnalysis ?? gridAnalysisFallback),
|
|
397
|
+
plainAnswer:
|
|
398
|
+
msg.gridData === null
|
|
399
|
+
? (split.metadataContext ?? msg.textAnswer)
|
|
400
|
+
: null,
|
|
401
|
+
};
|
|
402
|
+
}, [msg.textAnswer, msg.gridData]);
|
|
306
403
|
|
|
307
404
|
const submitFeedback = useCallback(
|
|
308
405
|
(rating: LegendAIMessageFeedbackRating): void => {
|
|
@@ -335,7 +432,7 @@ const AssistantMessageView = (props: {
|
|
|
335
432
|
<button
|
|
336
433
|
type="button"
|
|
337
434
|
className="legend-ai__thinking-toggle"
|
|
338
|
-
onClick={onToggleThinking}
|
|
435
|
+
onClick={(): void => onToggleThinking(msgIndex)}
|
|
339
436
|
>
|
|
340
437
|
<span className="legend-ai__thinking-toggle-icon">
|
|
341
438
|
{isThinkingVisible ? <CaretDownIcon /> : <CaretRightIcon />}
|
|
@@ -374,37 +471,51 @@ const AssistantMessageView = (props: {
|
|
|
374
471
|
)}
|
|
375
472
|
|
|
376
473
|
{msg.sql && (
|
|
377
|
-
<
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
474
|
+
<details
|
|
475
|
+
className="legend-ai__sql-details"
|
|
476
|
+
open={msg.gridData !== null}
|
|
477
|
+
onToggle={(event): void =>
|
|
478
|
+
onLogTelemetryEvent?.({
|
|
479
|
+
type: LegendAIChatTelemetryEventType.SQL_DETAILS_TOGGLED,
|
|
480
|
+
shown: event.currentTarget.open,
|
|
481
|
+
})
|
|
482
|
+
}
|
|
483
|
+
>
|
|
484
|
+
<summary className="legend-ai__sql-details-summary">
|
|
485
|
+
{msg.gridData === null ? 'Show the query I tried' : 'Query'}
|
|
486
|
+
</summary>
|
|
487
|
+
<div className="legend-ai__sql-block">
|
|
488
|
+
<div className="legend-ai__sql-block-header">
|
|
489
|
+
<span className="legend-ai__sql-block-header-icon">
|
|
490
|
+
<CodeIcon />
|
|
386
491
|
</span>
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
title="Copy SQL"
|
|
392
|
-
aria-label="Copy SQL"
|
|
393
|
-
onClick={handleCopySql}
|
|
394
|
-
>
|
|
395
|
-
{sqlCopied ? (
|
|
396
|
-
<span className="legend-ai__sql-copy-btn--copied">
|
|
397
|
-
<CheckIcon />
|
|
492
|
+
<span>Generated SQL</span>
|
|
493
|
+
{msg.sqlGenTime && (
|
|
494
|
+
<span className="legend-ai__sql-block-time">
|
|
495
|
+
{msg.sqlGenTime}s
|
|
398
496
|
</span>
|
|
399
|
-
) : (
|
|
400
|
-
<CopyIcon />
|
|
401
497
|
)}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
498
|
+
<button
|
|
499
|
+
type="button"
|
|
500
|
+
className="legend-ai__sql-copy-btn"
|
|
501
|
+
title="Copy SQL"
|
|
502
|
+
aria-label="Copy SQL"
|
|
503
|
+
onClick={handleCopySql}
|
|
504
|
+
>
|
|
505
|
+
{sqlCopied ? (
|
|
506
|
+
<span className="legend-ai__sql-copy-btn--copied">
|
|
507
|
+
<CheckIcon />
|
|
508
|
+
</span>
|
|
509
|
+
) : (
|
|
510
|
+
<CopyIcon />
|
|
511
|
+
)}
|
|
512
|
+
</button>
|
|
513
|
+
</div>
|
|
514
|
+
<div className="legend-ai__sql-scroll">
|
|
515
|
+
<pre className="legend-ai__sql-display">{msg.sql}</pre>
|
|
516
|
+
</div>
|
|
406
517
|
</div>
|
|
407
|
-
</
|
|
518
|
+
</details>
|
|
408
519
|
)}
|
|
409
520
|
|
|
410
521
|
{msg.isExecuting && (
|
|
@@ -533,15 +644,16 @@ const AssistantMessageView = (props: {
|
|
|
533
644
|
<span className="legend-ai__follow-up-label">
|
|
534
645
|
Try a data query:
|
|
535
646
|
</span>
|
|
536
|
-
{msg.suggestedQueries.map((q) => (
|
|
537
|
-
<
|
|
647
|
+
{msg.suggestedQueries.map((q, position) => (
|
|
648
|
+
<SuggestionButton
|
|
538
649
|
key={q}
|
|
539
|
-
|
|
650
|
+
query={q}
|
|
651
|
+
position={position}
|
|
540
652
|
className="legend-ai__follow-up-btn"
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
{
|
|
544
|
-
|
|
653
|
+
source={LegendAISuggestedQuerySource.FOLLOW_UP}
|
|
654
|
+
onSelect={onSuggestedQueryClick}
|
|
655
|
+
{...(onLogTelemetryEvent ? { onLogTelemetryEvent } : {})}
|
|
656
|
+
/>
|
|
545
657
|
))}
|
|
546
658
|
</div>
|
|
547
659
|
)}
|
|
@@ -561,10 +673,147 @@ const AssistantMessageView = (props: {
|
|
|
561
673
|
</button>
|
|
562
674
|
)}
|
|
563
675
|
|
|
676
|
+
{(onRequestPython || onOpenInDataCube) &&
|
|
677
|
+
!msg.isProcessing &&
|
|
678
|
+
(msg.sql !== null || msg.gridData !== null) && (
|
|
679
|
+
<div className="legend-ai__python-block">
|
|
680
|
+
<div className="legend-ai__cta-row">
|
|
681
|
+
{onRequestPython && (
|
|
682
|
+
<button
|
|
683
|
+
type="button"
|
|
684
|
+
className="legend-ai__python-cta"
|
|
685
|
+
onClick={handleTogglePython}
|
|
686
|
+
>
|
|
687
|
+
<PythonIcon />
|
|
688
|
+
<span>
|
|
689
|
+
{showPython
|
|
690
|
+
? 'Hide Python code'
|
|
691
|
+
: 'Want the Python code for this query?'}
|
|
692
|
+
</span>
|
|
693
|
+
<span className="legend-ai__python-cta-caret">
|
|
694
|
+
{showPython ? <CaretDownIcon /> : <CaretRightIcon />}
|
|
695
|
+
</span>
|
|
696
|
+
</button>
|
|
697
|
+
)}
|
|
698
|
+
{onOpenInDataCube && msg.sql !== null && (
|
|
699
|
+
<button
|
|
700
|
+
type="button"
|
|
701
|
+
className="legend-ai__datacube-cta"
|
|
702
|
+
onClick={(): void => {
|
|
703
|
+
onLogTelemetryEvent?.({
|
|
704
|
+
type: LegendAIChatTelemetryEventType.OPEN_IN_DATACUBE_CLICKED,
|
|
705
|
+
});
|
|
706
|
+
onOpenInDataCube(msg);
|
|
707
|
+
}}
|
|
708
|
+
disabled={isOpeningInDataCube}
|
|
709
|
+
title="Open this query in DataCube"
|
|
710
|
+
aria-label="Open this query in DataCube"
|
|
711
|
+
>
|
|
712
|
+
{isOpeningInDataCube ? (
|
|
713
|
+
<LoadingIcon isLoading={true} />
|
|
714
|
+
) : (
|
|
715
|
+
<CubeIcon />
|
|
716
|
+
)}
|
|
717
|
+
<span>
|
|
718
|
+
{isOpeningInDataCube
|
|
719
|
+
? 'Opening in DataCube...'
|
|
720
|
+
: 'Open in DataCube'}
|
|
721
|
+
</span>
|
|
722
|
+
<span className="legend-ai__datacube-cta-launch">
|
|
723
|
+
<ExternalLinkIcon />
|
|
724
|
+
</span>
|
|
725
|
+
</button>
|
|
726
|
+
)}
|
|
727
|
+
</div>
|
|
728
|
+
{showPython && onRequestPython && (
|
|
729
|
+
<div className="legend-ai__python-panel">
|
|
730
|
+
<div className="legend-ai__python-panel-header">
|
|
731
|
+
<span className="legend-ai__python-panel-header-icon">
|
|
732
|
+
<CodeIcon />
|
|
733
|
+
</span>
|
|
734
|
+
<span>Python</span>
|
|
735
|
+
{pythonEntry?.status === 'ready' && pythonEntry.code && (
|
|
736
|
+
<button
|
|
737
|
+
type="button"
|
|
738
|
+
className="legend-ai__sql-copy-btn"
|
|
739
|
+
title="Copy Python code"
|
|
740
|
+
aria-label="Copy Python code"
|
|
741
|
+
onClick={handleCopyPython}
|
|
742
|
+
>
|
|
743
|
+
{pythonCopied ? (
|
|
744
|
+
<span className="legend-ai__sql-copy-btn--copied">
|
|
745
|
+
<CheckIcon />
|
|
746
|
+
</span>
|
|
747
|
+
) : (
|
|
748
|
+
<CopyIcon />
|
|
749
|
+
)}
|
|
750
|
+
</button>
|
|
751
|
+
)}
|
|
752
|
+
</div>
|
|
753
|
+
{pythonEntry?.status === 'loading' && (
|
|
754
|
+
<div className="legend-ai__python-panel-loading">
|
|
755
|
+
<LoadingIcon isLoading={true} />
|
|
756
|
+
<span>Generating Python…</span>
|
|
757
|
+
</div>
|
|
758
|
+
)}
|
|
759
|
+
{pythonEntry?.status === 'error' && (
|
|
760
|
+
<div className="legend-ai__python-panel-error">
|
|
761
|
+
<span>
|
|
762
|
+
Could not generate Python code. Try again in a moment.
|
|
763
|
+
</span>
|
|
764
|
+
<button
|
|
765
|
+
type="button"
|
|
766
|
+
className="legend-ai__permission-error-btn"
|
|
767
|
+
onClick={(): void => {
|
|
768
|
+
onLogTelemetryEvent?.({
|
|
769
|
+
type: LegendAIChatTelemetryEventType.PYTHON_CODE_REQUESTED,
|
|
770
|
+
});
|
|
771
|
+
onRequestPython(msg);
|
|
772
|
+
}}
|
|
773
|
+
>
|
|
774
|
+
Retry
|
|
775
|
+
</button>
|
|
776
|
+
</div>
|
|
777
|
+
)}
|
|
778
|
+
{pythonEntry?.status === 'ready' &&
|
|
779
|
+
pythonEntry.code === undefined && (
|
|
780
|
+
<div className="legend-ai__python-panel-loading">
|
|
781
|
+
<span>
|
|
782
|
+
Python code is not available for this data source.
|
|
783
|
+
</span>
|
|
784
|
+
</div>
|
|
785
|
+
)}
|
|
786
|
+
{pythonEntry?.status === 'ready' && pythonEntry.code && (
|
|
787
|
+
<>
|
|
788
|
+
<div className="legend-ai__sql-scroll">
|
|
789
|
+
<pre className="legend-ai__sql-display">
|
|
790
|
+
{pythonEntry.code.code}
|
|
791
|
+
</pre>
|
|
792
|
+
</div>
|
|
793
|
+
{pythonEntry.code.notebookUrl && (
|
|
794
|
+
<div className="legend-ai__python-panel-actions">
|
|
795
|
+
<a
|
|
796
|
+
className="legend-ai__permission-error-btn"
|
|
797
|
+
href={pythonEntry.code.notebookUrl}
|
|
798
|
+
target="_blank"
|
|
799
|
+
rel="noopener noreferrer"
|
|
800
|
+
>
|
|
801
|
+
<JupyterIcon />
|
|
802
|
+
<span>Launch Notebook</span>
|
|
803
|
+
</a>
|
|
804
|
+
</div>
|
|
805
|
+
)}
|
|
806
|
+
</>
|
|
807
|
+
)}
|
|
808
|
+
</div>
|
|
809
|
+
)}
|
|
810
|
+
</div>
|
|
811
|
+
)}
|
|
812
|
+
|
|
564
813
|
{canShowFeedback && (
|
|
565
814
|
<div className="legend-ai__message-feedback">
|
|
566
815
|
<span className="legend-ai__message-feedback-label">
|
|
567
|
-
|
|
816
|
+
{LEGEND_AI_FEEDBACK_PROMPT}
|
|
568
817
|
</span>
|
|
569
818
|
<div className="legend-ai__message-feedback-actions">
|
|
570
819
|
<button
|
|
@@ -605,7 +854,7 @@ const AssistantMessageView = (props: {
|
|
|
605
854
|
</div>
|
|
606
855
|
</div>
|
|
607
856
|
);
|
|
608
|
-
};
|
|
857
|
+
});
|
|
609
858
|
|
|
610
859
|
export const LegendAIChat = (props: LegendAIChatProps): React.ReactNode => {
|
|
611
860
|
const {
|
|
@@ -623,6 +872,7 @@ export const LegendAIChat = (props: LegendAIChatProps): React.ReactNode => {
|
|
|
623
872
|
onClose,
|
|
624
873
|
onMinimize,
|
|
625
874
|
onRequestAccess,
|
|
875
|
+
onOpenInDataCube,
|
|
626
876
|
contextBannerMessage,
|
|
627
877
|
onLogTelemetryEvent,
|
|
628
878
|
} = props;
|
|
@@ -641,6 +891,26 @@ export const LegendAIChat = (props: LegendAIChatProps): React.ReactNode => {
|
|
|
641
891
|
() => buildSuggestedQueries(services, metadata),
|
|
642
892
|
[services, metadata],
|
|
643
893
|
);
|
|
894
|
+
const overview = useMemo(() => {
|
|
895
|
+
const raw =
|
|
896
|
+
modelContext?.dataspaceDescription ?? metadata.description ?? '';
|
|
897
|
+
const summary = raw
|
|
898
|
+
.split('\n')
|
|
899
|
+
.map((line) => line.trim())
|
|
900
|
+
.filter(
|
|
901
|
+
(line) =>
|
|
902
|
+
line.length > 0 && !line.startsWith('#') && !line.startsWith('|'),
|
|
903
|
+
)
|
|
904
|
+
.slice(0, 3)
|
|
905
|
+
.join(' ')
|
|
906
|
+
.slice(0, 300);
|
|
907
|
+
const entityCount = modelContext?.entities.length ?? 0;
|
|
908
|
+
const serviceCount =
|
|
909
|
+
metadata.serviceSummaries.length > 0
|
|
910
|
+
? metadata.serviceSummaries.length
|
|
911
|
+
: services.length;
|
|
912
|
+
return { summary, entityCount, serviceCount };
|
|
913
|
+
}, [modelContext, metadata, services]);
|
|
644
914
|
const hasServices = services.length > 0;
|
|
645
915
|
|
|
646
916
|
const inferSuggestedQueryIntent = useCallback(
|
|
@@ -648,13 +918,154 @@ export const LegendAIChat = (props: LegendAIChatProps): React.ReactNode => {
|
|
|
648
918
|
classifyQuestionIntentFast(query, hasServices).intent,
|
|
649
919
|
[hasServices],
|
|
650
920
|
);
|
|
651
|
-
const isDataProduct =
|
|
652
|
-
(
|
|
921
|
+
const { isDataProduct, supportsPython, supportsDataCube } = useMemo(
|
|
922
|
+
() => ({
|
|
923
|
+
isDataProduct: services.some(
|
|
924
|
+
(s) => s.sourceType === TDSServiceSourceType.ACCESS_POINT,
|
|
925
|
+
),
|
|
926
|
+
supportsPython: services.some((s) => plugin.supportsPythonCodegen(s)),
|
|
927
|
+
supportsDataCube: services.some((s) => plugin.supportsOpenInDataCube(s)),
|
|
928
|
+
}),
|
|
929
|
+
[services, plugin],
|
|
930
|
+
);
|
|
931
|
+
const [pythonCodeByMessageId, setPythonCodeByMessageId] = useState<
|
|
932
|
+
Map<string, LegendAIPythonCodeEntry>
|
|
933
|
+
>(new Map());
|
|
934
|
+
const pythonCodeByMessageIdRef = useRef(pythonCodeByMessageId);
|
|
935
|
+
pythonCodeByMessageIdRef.current = pythonCodeByMessageId;
|
|
936
|
+
const requestPythonCode = useCallback(
|
|
937
|
+
async (msg: LegendAIAssistantMessage): Promise<void> => {
|
|
938
|
+
const existing = pythonCodeByMessageIdRef.current.get(msg.id);
|
|
939
|
+
if (existing?.status === 'loading' || existing?.status === 'ready') {
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
setPythonCodeByMessageId((prev) => {
|
|
943
|
+
const next = new Map(prev);
|
|
944
|
+
next.set(msg.id, { status: 'loading' });
|
|
945
|
+
return next;
|
|
946
|
+
});
|
|
947
|
+
const resolvedSet = new Set(msg.queriedAccessPoints);
|
|
948
|
+
const service =
|
|
949
|
+
services.find((s) => resolvedSet.has(s.pattern.replace(/^\//u, ''))) ??
|
|
950
|
+
services[0];
|
|
951
|
+
if (!service) {
|
|
952
|
+
setPythonCodeByMessageId((prev) => {
|
|
953
|
+
const next = new Map(prev);
|
|
954
|
+
next.set(msg.id, { status: 'ready', code: undefined });
|
|
955
|
+
return next;
|
|
956
|
+
});
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
const msgIndex = state.messages.findIndex((m) => m.id === msg.id);
|
|
960
|
+
const previousUser =
|
|
961
|
+
msgIndex > 0 ? state.messages[msgIndex - 1] : undefined;
|
|
962
|
+
const question =
|
|
963
|
+
previousUser?.role === LegendAIMessageRole.USER
|
|
964
|
+
? previousUser.text
|
|
965
|
+
: undefined;
|
|
966
|
+
const request: LegendAIPythonCodegenRequest = {
|
|
967
|
+
service,
|
|
968
|
+
config,
|
|
969
|
+
...(dataProductCoordinates ? { dataProductCoordinates } : {}),
|
|
970
|
+
...(question === undefined ? {} : { question }),
|
|
971
|
+
...(msg.sql === null ? {} : { sql: msg.sql }),
|
|
972
|
+
};
|
|
973
|
+
try {
|
|
974
|
+
const code = await plugin.generatePythonQueryCodeAsync(request);
|
|
975
|
+
setPythonCodeByMessageId((prev) => {
|
|
976
|
+
const next = new Map(prev);
|
|
977
|
+
next.set(msg.id, { status: 'ready', code });
|
|
978
|
+
return next;
|
|
979
|
+
});
|
|
980
|
+
} catch (error) {
|
|
981
|
+
assertErrorThrown(error);
|
|
982
|
+
setPythonCodeByMessageId((prev) => {
|
|
983
|
+
const next = new Map(prev);
|
|
984
|
+
next.set(msg.id, { status: 'error', errorMessage: error.message });
|
|
985
|
+
return next;
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
},
|
|
989
|
+
[plugin, services, dataProductCoordinates, config, state.messages],
|
|
990
|
+
);
|
|
991
|
+
const [openingDataCubeMessageIds, setOpeningDataCubeMessageIds] = useState<
|
|
992
|
+
Set<string>
|
|
993
|
+
>(new Set());
|
|
994
|
+
|
|
995
|
+
// Resolves the AP, best-effort translates its SQL to a DataCube Pure query, and
|
|
996
|
+
// launches DataCube; on translation failure it opens on the bare access point.
|
|
997
|
+
const handleOpenInDataCubeMsg = useCallback(
|
|
998
|
+
async (msg: LegendAIAssistantMessage): Promise<void> => {
|
|
999
|
+
if (!onOpenInDataCube || msg.sql === null) {
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
const accessPointName = msg.queriedAccessPoints[0];
|
|
1003
|
+
if (accessPointName === undefined) {
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
const service =
|
|
1007
|
+
services.find(
|
|
1008
|
+
(s) => s.pattern.replace(/^\//u, '') === accessPointName,
|
|
1009
|
+
) ?? services[0];
|
|
1010
|
+
if (!service) {
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
const dataProductPath = dataProductCoordinates?.data_product ?? '';
|
|
1014
|
+
setOpeningDataCubeMessageIds((prev) => {
|
|
1015
|
+
const next = new Set(prev);
|
|
1016
|
+
next.add(msg.id);
|
|
1017
|
+
return next;
|
|
1018
|
+
});
|
|
1019
|
+
const msgIndex = state.messages.findIndex((m) => m.id === msg.id);
|
|
1020
|
+
const previousUser =
|
|
1021
|
+
msgIndex > 0 ? state.messages[msgIndex - 1] : undefined;
|
|
1022
|
+
const question =
|
|
1023
|
+
previousUser?.role === LegendAIMessageRole.USER
|
|
1024
|
+
? previousUser.text
|
|
1025
|
+
: undefined;
|
|
1026
|
+
const request: LegendAIDataCubeQueryTranslationRequest = {
|
|
1027
|
+
sql: msg.sql,
|
|
1028
|
+
service,
|
|
1029
|
+
dataProductPath,
|
|
1030
|
+
config,
|
|
1031
|
+
...(question === undefined ? {} : { question }),
|
|
1032
|
+
};
|
|
1033
|
+
let pureQuery: string | undefined;
|
|
1034
|
+
try {
|
|
1035
|
+
pureQuery =
|
|
1036
|
+
await plugin.translateAccessPointSqlToDataCubeQuery(request);
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
assertErrorThrown(error);
|
|
1039
|
+
}
|
|
1040
|
+
try {
|
|
1041
|
+
onOpenInDataCube(accessPointName, pureQuery);
|
|
1042
|
+
} finally {
|
|
1043
|
+
setOpeningDataCubeMessageIds((prev) => {
|
|
1044
|
+
if (!prev.has(msg.id)) {
|
|
1045
|
+
return prev;
|
|
1046
|
+
}
|
|
1047
|
+
const next = new Set(prev);
|
|
1048
|
+
next.delete(msg.id);
|
|
1049
|
+
return next;
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
},
|
|
1053
|
+
[
|
|
1054
|
+
onOpenInDataCube,
|
|
1055
|
+
plugin,
|
|
1056
|
+
services,
|
|
1057
|
+
dataProductCoordinates,
|
|
1058
|
+
config,
|
|
1059
|
+
state.messages,
|
|
1060
|
+
],
|
|
653
1061
|
);
|
|
654
1062
|
const [showContextBanner, setShowContextBanner] = useState(true);
|
|
655
1063
|
const dismissBanner = useCallback(() => setShowContextBanner(false), []);
|
|
656
1064
|
const hasMessages = state.messages.length > 0;
|
|
657
|
-
const scopes =
|
|
1065
|
+
const scopes = useMemo(
|
|
1066
|
+
() => (isDataProduct ? [] : (availableScopes ?? DEFAULT_SCOPES)),
|
|
1067
|
+
[isDataProduct, availableScopes],
|
|
1068
|
+
);
|
|
658
1069
|
const [feedbackByMessageId, setFeedbackByMessageId] = useState<
|
|
659
1070
|
Map<string, LegendAIMessageFeedbackRating>
|
|
660
1071
|
>(new Map());
|
|
@@ -682,7 +1093,8 @@ export const LegendAIChat = (props: LegendAIChatProps): React.ReactNode => {
|
|
|
682
1093
|
|
|
683
1094
|
try {
|
|
684
1095
|
await onMessageFeedback(feedback);
|
|
685
|
-
} catch {
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
assertErrorThrown(error);
|
|
686
1098
|
setFeedbackByMessageId((prev) => {
|
|
687
1099
|
const next = new Map(prev);
|
|
688
1100
|
next.delete(feedback.messageId);
|
|
@@ -699,6 +1111,25 @@ export const LegendAIChat = (props: LegendAIChatProps): React.ReactNode => {
|
|
|
699
1111
|
[onMessageFeedback],
|
|
700
1112
|
);
|
|
701
1113
|
|
|
1114
|
+
const { toggleThinking, runFallbackAction, askQuestionWithIntent } = state;
|
|
1115
|
+
const handleSuggestedQueryClick = useCallback(
|
|
1116
|
+
(query: string): void =>
|
|
1117
|
+
askQuestionWithIntent(query, inferSuggestedQueryIntent(query)),
|
|
1118
|
+
[askQuestionWithIntent, inferSuggestedQueryIntent],
|
|
1119
|
+
);
|
|
1120
|
+
const handleRequestPython = useCallback(
|
|
1121
|
+
(message: LegendAIAssistantMessage): void => {
|
|
1122
|
+
requestPythonCode(message).catch(noop);
|
|
1123
|
+
},
|
|
1124
|
+
[requestPythonCode],
|
|
1125
|
+
);
|
|
1126
|
+
const handleOpenInDataCube = useCallback(
|
|
1127
|
+
(message: LegendAIAssistantMessage): void => {
|
|
1128
|
+
handleOpenInDataCubeMsg(message).catch(noop);
|
|
1129
|
+
},
|
|
1130
|
+
[handleOpenInDataCubeMsg],
|
|
1131
|
+
);
|
|
1132
|
+
|
|
702
1133
|
return (
|
|
703
1134
|
<div className="legend-ai" id={LEGEND_AI_ANCHOR_ID}>
|
|
704
1135
|
<div className="legend-ai__header">
|
|
@@ -733,7 +1164,12 @@ export const LegendAIChat = (props: LegendAIChatProps): React.ReactNode => {
|
|
|
733
1164
|
className="legend-ai__header-action"
|
|
734
1165
|
title="Close"
|
|
735
1166
|
aria-label="Close"
|
|
736
|
-
onClick={
|
|
1167
|
+
onClick={(): void => {
|
|
1168
|
+
onLogTelemetryEvent?.({
|
|
1169
|
+
type: LegendAIChatTelemetryEventType.ASSISTANT_CLOSED,
|
|
1170
|
+
});
|
|
1171
|
+
onClose();
|
|
1172
|
+
}}
|
|
737
1173
|
>
|
|
738
1174
|
<TimesIcon />
|
|
739
1175
|
</button>
|
|
@@ -757,21 +1193,40 @@ export const LegendAIChat = (props: LegendAIChatProps): React.ReactNode => {
|
|
|
757
1193
|
<div className="legend-ai__empty-text">
|
|
758
1194
|
Ask a question about your data
|
|
759
1195
|
</div>
|
|
1196
|
+
{overview.summary.length > 0 && (
|
|
1197
|
+
<div className="legend-ai__empty-overview">
|
|
1198
|
+
{overview.summary}
|
|
1199
|
+
</div>
|
|
1200
|
+
)}
|
|
1201
|
+
{(overview.entityCount > 0 || overview.serviceCount > 0) && (
|
|
1202
|
+
<div className="legend-ai__empty-meta">
|
|
1203
|
+
{overview.entityCount > 0 &&
|
|
1204
|
+
`${overview.entityCount} entit${
|
|
1205
|
+
overview.entityCount === 1 ? 'y' : 'ies'
|
|
1206
|
+
}`}
|
|
1207
|
+
{overview.entityCount > 0 && overview.serviceCount > 0 && ' · '}
|
|
1208
|
+
{overview.serviceCount > 0 &&
|
|
1209
|
+
`${overview.serviceCount} service${
|
|
1210
|
+
overview.serviceCount === 1 ? '' : 's'
|
|
1211
|
+
}`}
|
|
1212
|
+
</div>
|
|
1213
|
+
)}
|
|
760
1214
|
<div className="legend-ai__suggestions">
|
|
761
|
-
{suggestedQueries.map((q) => (
|
|
762
|
-
<
|
|
1215
|
+
{suggestedQueries.map((q, position) => (
|
|
1216
|
+
<SuggestionButton
|
|
763
1217
|
key={q}
|
|
764
|
-
|
|
1218
|
+
query={q}
|
|
1219
|
+
position={position}
|
|
765
1220
|
className="legend-ai__suggestion-chip"
|
|
766
|
-
|
|
1221
|
+
source={LegendAISuggestedQuerySource.STARTER}
|
|
1222
|
+
onSelect={(query): void =>
|
|
767
1223
|
state.askQuestionWithIntent(
|
|
768
|
-
|
|
769
|
-
inferSuggestedQueryIntent(
|
|
770
|
-
)
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
</button>
|
|
1224
|
+
query,
|
|
1225
|
+
inferSuggestedQueryIntent(query),
|
|
1226
|
+
)
|
|
1227
|
+
}
|
|
1228
|
+
{...(onLogTelemetryEvent ? { onLogTelemetryEvent } : {})}
|
|
1229
|
+
/>
|
|
775
1230
|
))}
|
|
776
1231
|
</div>
|
|
777
1232
|
</div>
|
|
@@ -794,13 +1249,15 @@ export const LegendAIChat = (props: LegendAIChatProps): React.ReactNode => {
|
|
|
794
1249
|
previousMessage?.role === LegendAIMessageRole.USER
|
|
795
1250
|
? previousMessage.text
|
|
796
1251
|
: '';
|
|
1252
|
+
const messagePython = pythonCodeByMessageId.get(msg.id);
|
|
797
1253
|
return (
|
|
798
1254
|
<AssistantMessageView
|
|
799
1255
|
key={msg.id}
|
|
800
1256
|
msg={msg}
|
|
1257
|
+
msgIndex={msgIndex}
|
|
801
1258
|
questionText={questionText}
|
|
802
1259
|
isThinkingVisible={isThinkingVisible}
|
|
803
|
-
onToggleThinking={
|
|
1260
|
+
onToggleThinking={toggleThinking}
|
|
804
1261
|
onMessageFeedback={handleMessageFeedback}
|
|
805
1262
|
selectedFeedbackRating={feedbackByMessageId.get(msg.id)}
|
|
806
1263
|
feedbackSubmitting={pendingFeedbackByMessageId.has(msg.id)}
|
|
@@ -811,12 +1268,19 @@ export const LegendAIChat = (props: LegendAIChatProps): React.ReactNode => {
|
|
|
811
1268
|
? {}
|
|
812
1269
|
: { enthubRequestAccessUrl: config.enthubRequestAccessUrl })}
|
|
813
1270
|
{...(onRequestAccess ? { onRequestAccess } : {})}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1271
|
+
{...(messagePython ? { pythonEntry: messagePython } : {})}
|
|
1272
|
+
{...(supportsPython
|
|
1273
|
+
? { onRequestPython: handleRequestPython }
|
|
1274
|
+
: {})}
|
|
1275
|
+
{...(supportsDataCube && onOpenInDataCube
|
|
1276
|
+
? {
|
|
1277
|
+
onOpenInDataCube: handleOpenInDataCube,
|
|
1278
|
+
isOpeningInDataCube: openingDataCubeMessageIds.has(msg.id),
|
|
1279
|
+
}
|
|
1280
|
+
: {})}
|
|
1281
|
+
onFallbackAction={runFallbackAction}
|
|
1282
|
+
onSuggestedQueryClick={handleSuggestedQueryClick}
|
|
1283
|
+
{...(onLogTelemetryEvent ? { onLogTelemetryEvent } : {})}
|
|
820
1284
|
/>
|
|
821
1285
|
);
|
|
822
1286
|
})}
|