UNPKG

@finos/legend-application-marketplace

Version:
141 lines 15.6 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; /** * Copyright (c) 2026-present, Goldman Sachs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { useRef, useEffect, useCallback, useState } from 'react'; import { observer } from 'mobx-react-lite'; import { flowResult } from 'mobx'; import { LoadingIcon, SparkleStarsIcon, CodeIcon, TableIcon, CopyIcon, RefreshIcon, TimesIcon, CheckIcon, CaretDownIcon, CaretRightIcon, DotIcon, MarkdownTextViewer, ExternalLinkIcon, } from '@finos/legend-art'; import { noop } from '@finos/legend-shared'; import { LegendAIMessageRole, LegendAIErrorType, LegendAIResultGrid, LegendAIAnalysisPanel, renderStepStatusIcon, LAKEHOUSE_ENV_PROD, COVERAGE_NAME_PROD, COVERAGE_NAME_SANDBOX, } from '@finos/legend-lego/legend-ai'; import { useLegendMarketplaceAIChatStore } from '../../application/providers/LegendMarketplaceAIChatStoreProvider.js'; import { MarketplaceAIChatStage } from '../../stores/ai/LegendMarketplaceAIChatStore.js'; import { MarketplaceAIProductCards } from './MarketplaceAIProductCards.js'; import { MarketplaceAIProductAutosuggest } from './MarketplaceAIProductAutosuggest.js'; import { MarketplaceAIInputBar } from './MarketplaceAIInputBar.js'; const COPY_FEEDBACK_DURATION_MS = 2000; const AISummaryRenderer = ({ value }) => (_jsx(MarkdownTextViewer, { value: { value }, className: "legend-ai__text-answer-md" })); const AssistantMessageView = observer((props) => { const { msg, onSuggestedQueryClick, onFallbackAction } = props; const store = useLegendMarketplaceAIChatStore(); const { enghubDocUrl, enthubRequestAccessUrl, lakehouseEnvironment } = store.config; const isProd = lakehouseEnvironment === LAKEHOUSE_ENV_PROD; const hasAccessLinks = enghubDocUrl !== undefined || enthubRequestAccessUrl !== undefined; const [isThinkingVisible, setIsThinkingVisible] = useState(msg.isProcessing); const [sqlCopied, setSqlCopied] = useState(false); const copyTimerRef = useRef(undefined); useEffect(() => () => { if (copyTimerRef.current !== undefined) { clearTimeout(copyTimerRef.current); } }, []); useEffect(() => { setIsThinkingVisible(msg.isProcessing); }, [msg.isProcessing]); const handleCopySql = useCallback(() => { if (msg.sql) { navigator.clipboard.writeText(msg.sql).catch(noop()); setSqlCopied(true); if (copyTimerRef.current !== undefined) { clearTimeout(copyTimerRef.current); } copyTimerRef.current = setTimeout(() => { setSqlCopied(false); copyTimerRef.current = undefined; }, COPY_FEEDBACK_DURATION_MS); } }, [msg.sql]); return (_jsxs("div", { className: "legend-ai__msg legend-ai__msg--assistant", children: [_jsx("div", { className: "legend-ai__msg-avatar", children: _jsx(SparkleStarsIcon, {}) }), _jsxs("div", { className: "legend-ai__msg-content", children: [msg.thinkingSteps.length > 0 && (_jsxs("div", { className: "legend-ai__thinking", children: [!msg.isProcessing && (_jsxs("button", { type: "button", className: "legend-ai__thinking-toggle", onClick: () => setIsThinkingVisible(!isThinkingVisible), children: [_jsx("span", { className: "legend-ai__thinking-toggle-icon", children: isThinkingVisible ? _jsx(CaretDownIcon, {}) : _jsx(CaretRightIcon, {}) }), "Thought for ", msg.thinkingDuration ?? '...', "s"] })), isThinkingVisible && (_jsx("div", { className: "legend-ai__thinking-steps", children: msg.thinkingSteps.map((step) => (_jsxs("div", { className: `legend-ai__thinking-step legend-ai__thinking-step--${step.status}`, children: [_jsx("span", { className: "legend-ai__thinking-step-icon", children: renderStepStatusIcon(step.status) }), _jsx("span", { children: step.label })] }, step.id))) }))] })), msg.dataContext && (_jsx("div", { className: "legend-ai__data-context", children: _jsx(MarkdownTextViewer, { value: { value: msg.dataContext }, className: "legend-ai__text-answer-md" }) })), msg.sql && (_jsxs("div", { className: "legend-ai__sql-block", children: [_jsxs("div", { className: "legend-ai__sql-block-header", children: [_jsx("span", { className: "legend-ai__sql-block-header-icon", children: _jsx(CodeIcon, {}) }), _jsx("span", { children: "Generated Query" }), msg.sqlGenTime && (_jsxs("span", { className: "legend-ai__sql-block-time", children: [msg.sqlGenTime, "s"] })), _jsx("button", { type: "button", className: "legend-ai__sql-copy-btn", title: "Copy query", "aria-label": "Copy query", onClick: handleCopySql, children: sqlCopied ? (_jsx("span", { className: "legend-ai__sql-copy-btn--copied", children: _jsx(CheckIcon, {}) })) : (_jsx(CopyIcon, {})) })] }), _jsx("div", { className: "legend-ai__sql-scroll", children: _jsx("pre", { className: "legend-ai__sql-display", children: msg.sql }) })] })), msg.isExecuting && (_jsxs("div", { className: "legend-ai__executing", children: [_jsx(LoadingIcon, { isLoading: true }), _jsx("span", { children: "Executing query..." })] })), msg.textAnswer && !msg.gridData && (_jsx("div", { className: "legend-ai__text-answer", children: _jsx(MarkdownTextViewer, { value: { value: msg.textAnswer }, className: "legend-ai__text-answer-md" }) })), msg.error && (_jsxs("div", { className: "legend-ai__exec-error", children: [msg.error, msg.errorType === LegendAIErrorType.PERMISSION && hasAccessLinks && (_jsxs("div", { className: "legend-ai__permission-error-action", children: [_jsxs("span", { className: "legend-ai__permission-error-note", children: ["Select coverage:", ' ', _jsx("strong", { children: isProd ? COVERAGE_NAME_PROD : COVERAGE_NAME_SANDBOX })] }), _jsxs("div", { className: "legend-ai__permission-error-btns", children: [enghubDocUrl && (_jsxs("a", { className: "legend-ai__permission-error-btn", href: enghubDocUrl, target: "_blank", rel: "noopener noreferrer", children: [_jsx(ExternalLinkIcon, {}), _jsx("span", { children: "View Documentation" })] })), enthubRequestAccessUrl && (_jsxs("a", { className: "legend-ai__permission-error-btn legend-ai__permission-error-btn--primary", href: enthubRequestAccessUrl, target: "_blank", rel: "noopener noreferrer", children: [_jsx(ExternalLinkIcon, {}), _jsx("span", { children: "Request Access" })] }))] })] })), msg.errorType === LegendAIErrorType.NETWORK && (_jsx("div", { className: "legend-ai__permission-error-action", children: _jsx("span", { className: "legend-ai__permission-error-note", children: "Please check your network connection and try again." }) }))] })), msg.fallbackAction && !msg.isProcessing && onFallbackAction && (_jsxs("button", { type: "button", className: "legend-ai__fallback-action-btn", onClick: () => { const actionId = msg.fallbackAction?.actionId; if (actionId) { onFallbackAction(msg.id, actionId); } }, children: [_jsx(SparkleStarsIcon, {}), _jsx("span", { children: msg.fallbackAction.label })] })), msg.gridData && (_jsxs("div", { className: "legend-ai__results-block", children: [_jsxs("div", { className: "legend-ai__results-header", children: [_jsx("span", { className: "legend-ai__results-header-icon", children: _jsx(TableIcon, {}) }), _jsx("span", { children: "Results" }), _jsxs("span", { className: "legend-ai__results-meta", children: [msg.gridData.rowData.length, " row", msg.gridData.rowData.length === 1 ? '' : 's', msg.execTime ? (_jsxs(_Fragment, { children: [' ', _jsx(DotIcon, { className: "legend-ai__results-meta-dot" }), ' ', msg.execTime, "s"] })) : ('')] })] }), _jsx(LegendAIResultGrid, { data: msg.gridData })] })), msg.textAnswer && msg.gridData && (_jsx(LegendAIAnalysisPanel, { gridData: msg.gridData, summary: msg.textAnswer, SummaryRenderer: AISummaryRenderer })), msg.isProcessing && !msg.isExecuting && msg.gridData && (_jsxs("div", { className: "legend-ai__analyzing", children: [_jsx(LoadingIcon, { isLoading: true }), _jsx("span", { children: "Analyzing results..." })] })), !msg.isProcessing && msg.suggestedQueries.length > 0 && onSuggestedQueryClick && (_jsxs("div", { className: "legend-ai__follow-up-suggestions", children: [_jsx("span", { className: "legend-ai__follow-up-label", children: "Follow-up questions:" }), msg.suggestedQueries.map((q) => (_jsx("button", { type: "button", className: "legend-ai__follow-up-btn", onClick: () => onSuggestedQueryClick(q), children: q }, q)))] }))] })] })); }); export const MarketplaceAIChatView = observer((props) => { const { initialQuery } = props; const store = useLegendMarketplaceAIChatStore(); const conversationRef = useRef(null); const hasMessages = store.messages.length > 0; const initialQuerySubmitted = useRef(false); useEffect(() => { const el = conversationRef.current; if (el) { el.scrollTop = el.scrollHeight; } }, [store.messages.length]); const dispatchQuery = useCallback((text) => { if (store.selectedProduct) { flowResult(store.askFollowUp(text)).catch(noop()); } else { flowResult(store.submitQuery(text)).catch(noop()); } }, [store]); useEffect(() => { if (initialQuery && initialQuery.trim().length > 0 && !initialQuerySubmitted.current && store.isEnabled) { initialQuerySubmitted.current = true; store.setQuestionText(initialQuery); flowResult(store.submitQuery(initialQuery)).catch(noop()); } }, [initialQuery, store]); const handleSubmit = useCallback(() => { if (!store.questionText.trim() || store.isSending) { return; } dispatchQuery(store.questionText); }, [store, dispatchQuery]); const handleFallbackAction = useCallback((messageId, _actionId) => { flowResult(store.runOrchestratorFallback(messageId)).catch(noop()); }, [store]); const handleSuggestedQueryClick = useCallback((query) => { store.setQuestionText(query); dispatchQuery(query); }, [store, dispatchQuery]); if (!store.isEnabled) { return (_jsx("div", { className: "marketplace-ai-chat marketplace-ai-chat--disabled", children: _jsx("div", { className: "marketplace-ai-chat__empty", children: _jsx("div", { className: "marketplace-ai-chat__empty-text", children: "Legend AI is not configured. Please contact your administrator." }) }) })); } return (_jsx("div", { className: "marketplace-ai-chat", children: hasMessages ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "marketplace-ai-chat__header", children: [store.selectedProduct && (_jsxs("div", { className: "marketplace-ai-chat__product-pill", children: [_jsx(SparkleStarsIcon, {}), _jsxs("span", { className: "marketplace-ai-chat__product-pill-text", children: ["Scoped to:", ' ', _jsx("strong", { children: store.selectedProduct.dataProductTitle ?? 'Data Product' })] }), _jsx("button", { type: "button", className: "marketplace-ai-chat__product-pill-dismiss", title: "Remove product scope", "aria-label": "Remove product scope", onClick: () => { store.deselectProduct(); }, children: _jsx(TimesIcon, {}) })] })), _jsxs("button", { type: "button", className: "marketplace-ai-chat__clear-btn", title: "Clear chat", "aria-label": "Clear chat", onClick: () => store.clearChat(), children: [_jsx(RefreshIcon, {}), _jsx("span", { children: "Clear chat" })] })] }), _jsxs("div", { className: "marketplace-ai-chat__messages", ref: conversationRef, children: [store.messages.map((msg) => { if (msg.role === LegendAIMessageRole.USER) { return (_jsx("div", { className: "marketplace-ai-chat__msg marketplace-ai-chat__msg--user", children: _jsx("div", { className: "marketplace-ai-chat__msg-bubble", children: msg.text }) }, msg.id)); } return (_jsx(AssistantMessageView, { msg: msg, onSuggestedQueryClick: handleSuggestedQueryClick, onFallbackAction: handleFallbackAction }, msg.id)); }), store.stage === MarketplaceAIChatStage.PRODUCT_SELECTION && store.suggestedProducts.length > 0 && (_jsx(MarketplaceAIProductCards, { products: store.suggestedProducts, ...(store.scoredCandidates.length > 0 ? { scoredCandidates: store.scoredCandidates, } : {}), onSelect: (product) => { store.selectDataProduct(product); dispatchQuery(store.lastUserMessageText); } })), store.stage === MarketplaceAIChatStage.PRODUCT_SELECTION && (_jsxs("div", { className: "marketplace-ai-chat__product-search", children: [_jsx("div", { className: "marketplace-ai-chat__product-search-label", children: "Don't see the right product? Search for it:" }), _jsx(MarketplaceAIProductAutosuggest, { onSelect: (result) => { store.selectAutosuggestProduct(result); dispatchQuery(store.lastUserMessageText); }, className: "marketplace-ai-chat__product-search-autosuggest" })] }))] }), _jsx("div", { className: "marketplace-ai-chat__input-bar", children: _jsx(MarketplaceAIInputBar, { placeholder: store.selectedProduct || store.scopeProducts.length > 0 ? `Ask about ${store.selectedProduct?.dataProductTitle ?? store.scopeProducts[0]?.name ?? 'this data product'}...` : 'Ask a follow-up...', onSubmit: handleSubmit }) })] })) : (_jsxs("div", { className: "marketplace-ai-chat__welcome", children: [_jsx("div", { className: "marketplace-ai-chat__welcome-spacer" }), _jsx("div", { className: "marketplace-ai-chat__welcome-icon", children: _jsx(SparkleStarsIcon, {}) }), _jsx("h1", { className: "marketplace-ai-chat__welcome-title", children: "Legend Marketplace AI" }), _jsx("p", { className: "marketplace-ai-chat__welcome-subtitle", children: "Ask anything about your data. I'll find the right data product and query it for you." }), _jsx("div", { className: "marketplace-ai-chat__welcome-input", children: _jsx(MarketplaceAIInputBar, { placeholder: "Ask anything about your data...", onSubmit: handleSubmit }) }), _jsx("div", { className: "marketplace-ai-chat__suggestions", children: store.welcomeSuggestedQueries.map((q) => (_jsx("button", { type: "button", className: "marketplace-ai-chat__suggestion", onClick: () => { store.setQuestionText(q); dispatchQuery(q); }, children: q }, q))) }), _jsx("div", { className: "marketplace-ai-chat__welcome-spacer-bottom" })] })) })); }); //# sourceMappingURL=MarketplaceAIChatView.js.map