/* eslint-disable no-console */ import { FeedOptions } from "@azure/cosmos"; import { Callout, CommandBarButton, DefaultButton, DirectionalHint, IButtonStyles, IconButton, Image, Link, Separator, Spinner, Stack, TeachingBubble, Text, TextField, } from "@fluentui/react"; import { useBoolean } from "@fluentui/react-hooks"; import { QueryCopilotSampleContainerId, QueryCopilotSampleContainerSchema, ShortenedQueryCopilotSampleContainerSchema, } from "Common/Constants"; import { getErrorMessage, handleError } from "Common/ErrorHandlingUtils"; import { shouldEnableCrossPartitionKey } from "Common/HeadersUtility"; import { MinimalQueryIterator } from "Common/IteratorUtilities"; import { createUri } from "Common/UrlUtility"; import { queryDocumentsPage } from "Common/dataAccess/queryDocumentsPage"; import { QueryResults } from "Contracts/ViewModels"; import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent"; import { EditorReact } from "Explorer/Controls/Editor/EditorReact"; import Explorer from "Explorer/Explorer"; import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter"; import { useNotebook } from "Explorer/Notebook/useNotebook"; import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane"; import { WelcomeModal } from "Explorer/QueryCopilot/Modal/WelcomeModal"; import { CopyPopup } from "Explorer/QueryCopilot/Popup/CopyPopup"; import { DeletePopup } from "Explorer/QueryCopilot/Popup/DeletePopup"; import { querySampleDocuments, submitFeedback } from "Explorer/QueryCopilot/QueryCopilotUtilities"; import { SamplePrompts, SamplePromptsProps } from "Explorer/QueryCopilot/Shared/SamplePrompts/SamplePrompts"; import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection"; import { Action } from "Shared/Telemetry/TelemetryConstants"; import { traceFailure, traceStart, traceSuccess } from "Shared/Telemetry/TelemetryProcessor"; import { userContext } from "UserContext"; import { queryPagesUntilContentPresent } from "Utils/QueryUtils"; import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useSidePanel } from "hooks/useSidePanel"; import React, { useRef, useState } from "react"; import SplitterLayout from "react-splitter-layout"; import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg"; import HintIcon from "../../../images/Hint.svg"; import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg"; import RecentIcon from "../../../images/Recent.svg"; import XErrorMessage from "../../../images/X-errorMessage.svg"; import SaveQueryIcon from "../../../images/save-cosmos.svg"; import { useTabs } from "../../hooks/useTabs"; interface SuggestedPrompt { id: number; text: string; } interface QueryCopilotTabProps { explorer: Explorer; } interface GenerateSQLQueryResponse { apiVersion: string; sql: string; explanation: string; generateStart: string; generateEnd: string; } const promptStyles: IButtonStyles = { root: { border: 0, selectors: { ":hover": { outline: "1px dashed #605e5c" } } }, label: { fontWeight: 400, textAlign: "left", paddingLeft: 8 }, }; export const QueryCopilotTab: React.FC = ({ explorer }: QueryCopilotTabProps): JSX.Element => { const [copilotTeachingBubbleVisible, { toggle: toggleCopilotTeachingBubbleVisible }] = useBoolean(false); const inputEdited = useRef(false); const { hideFeedbackModalForLikedQueries, userPrompt, setUserPrompt, generatedQuery, setGeneratedQuery, query, setQuery, selectedQuery, setSelectedQuery, isGeneratingQuery, setIsGeneratingQuery, isExecuting, setIsExecuting, likeQuery, setLikeQuery, dislikeQuery, setDislikeQuery, showCallout, setShowCallout, showSamplePrompts, setShowSamplePrompts, queryIterator, setQueryIterator, queryResults, setQueryResults, errorMessage, setErrorMessage, isSamplePromptsOpen, setIsSamplePromptsOpen, showDeletePopup, setShowDeletePopup, showFeedbackBar, setShowFeedbackBar, showCopyPopup, setshowCopyPopup, showErrorMessageBar, setShowErrorMessageBar, generatedQueryComments, setGeneratedQueryComments, shouldAllocateContainer, setShouldAllocateContainer, } = useQueryCopilot(); const sampleProps: SamplePromptsProps = { isSamplePromptsOpen: isSamplePromptsOpen, setIsSamplePromptsOpen: setIsSamplePromptsOpen, setTextBox: setUserPrompt, }; const copyGeneratedCode = () => { if (!query) { return; } const queryElement = document.createElement("textarea"); queryElement.value = query; document.body.appendChild(queryElement); queryElement.select(); document.execCommand("copy"); document.body.removeChild(queryElement); setshowCopyPopup(true); setTimeout(() => { setshowCopyPopup(false); }, 6000); }; const cachedHistoriesString = localStorage.getItem(`${userContext.databaseAccount?.id}-queryCopilotHistories`); const cachedHistories = cachedHistoriesString?.split("|"); const [histories, setHistories] = useState(cachedHistories || []); const suggestedPrompts: SuggestedPrompt[] = [ { id: 1, text: 'Show all products that have the word "ultra" in the name or description' }, { id: 2, text: "What are all of the possible categories for the products, and their counts?" }, { id: 3, text: 'Show me all products that have been reviewed by someone with a username that contains "bob"' }, ]; const [filteredHistories, setFilteredHistories] = useState(histories); const [filteredSuggestedPrompts, setFilteredSuggestedPrompts] = useState(suggestedPrompts); const handleUserPromptChange = (event: React.ChangeEvent) => { inputEdited.current = true; const { value } = event.target; setUserPrompt(value); // Filter history prompts const filteredHistory = histories.filter((history) => history.toLowerCase().includes(value.toLowerCase())); setFilteredHistories(filteredHistory); // Filter suggested prompts const filteredSuggested = suggestedPrompts.filter((prompt) => prompt.text.toLowerCase().includes(value.toLowerCase()) ); setFilteredSuggestedPrompts(filteredSuggested); }; const updateHistories = (): void => { const formattedUserPrompt = userPrompt.replace(/\s+/g, " ").trim(); const existingHistories = histories.map((history) => history.replace(/\s+/g, " ").trim()); const updatedHistories = existingHistories.filter( (history) => history.toLowerCase() !== formattedUserPrompt.toLowerCase() ); const newHistories = [formattedUserPrompt, ...updatedHistories.slice(0, 2)]; setHistories(newHistories); localStorage.setItem(`${userContext.databaseAccount.id}-queryCopilotHistories`, newHistories.join("|")); }; const generateSQLQuery = async (): Promise => { try { if (shouldAllocateContainer && userContext.features.enableCopilotPhoenixGateaway) { await explorer.allocateContainer(); setShouldAllocateContainer(false); } setIsGeneratingQuery(true); useTabs.getState().setIsTabExecuting(true); useTabs.getState().setIsQueryErrorThrown(false); const payload = { containerSchema: userContext.features.enableCopilotFullSchema ? QueryCopilotSampleContainerSchema : ShortenedQueryCopilotSampleContainerSchema, userPrompt: userPrompt, }; setShowDeletePopup(false); useQueryCopilot.getState().refreshCorrelationId(); const serverInfo = useNotebook.getState().notebookServerInfo; const queryUri = userContext.features.enableCopilotPhoenixGateaway ? createUri(serverInfo.notebookServerEndpoint, "generateSQLQuery") : createUri("https://copilotorchestrater.azurewebsites.net/", "generateSQLQuery"); const response = await fetch(queryUri, { method: "POST", headers: { "content-type": "application/json", "x-ms-correlationid": useQueryCopilot.getState().correlationId, }, body: JSON.stringify(payload), }); const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json(); if (response.status === 404) { setShouldAllocateContainer(true); } if (response.ok) { if (generateSQLQueryResponse?.sql) { let query = `-- **Prompt:** ${userPrompt}\r\n`; if (generateSQLQueryResponse.explanation) { query += `-- **Explanation of query:** ${generateSQLQueryResponse.explanation}\r\n`; } query += generateSQLQueryResponse.sql; setQuery(query); setGeneratedQuery(generateSQLQueryResponse.sql); setGeneratedQueryComments(generateSQLQueryResponse.explanation); setShowErrorMessageBar(false); } } else { handleError(JSON.stringify(generateSQLQueryResponse), "copilotInternalServerError"); useTabs.getState().setIsQueryErrorThrown(true); setShowErrorMessageBar(true); } } catch (error) { handleError(error, "executeNaturalLanguageQuery"); useTabs.getState().setIsQueryErrorThrown(true); setShowErrorMessageBar(true); throw error; } finally { setIsGeneratingQuery(false); useTabs.getState().setIsTabExecuting(false); setShowFeedbackBar(true); } }; const onExecuteQueryClick = async (): Promise => { traceStart(Action.ExecuteQueryGeneratedFromQueryCopilot, { correlationId: useQueryCopilot.getState().correlationId, userPrompt: userPrompt, generatedQuery: generatedQuery, generatedQueryComments: generatedQueryComments, executedQuery: selectedQuery || query, }); const queryToExecute = selectedQuery || query; const queryIterator = querySampleDocuments(queryToExecute, { enableCrossPartitionQuery: shouldEnableCrossPartitionKey(), } as FeedOptions); setQueryIterator(queryIterator); setTimeout(async () => { await queryDocumentsPerPage(0, queryIterator); }, 100); }; const queryDocumentsPerPage = async (firstItemIndex: number, queryIterator: MinimalQueryIterator): Promise => { try { setIsExecuting(true); useTabs.getState().setIsTabExecuting(true); useTabs.getState().setIsQueryErrorThrown(false); const queryResults: QueryResults = await queryPagesUntilContentPresent( firstItemIndex, async (firstItemIndex: number) => queryDocumentsPage(QueryCopilotSampleContainerId, queryIterator, firstItemIndex) ); setQueryResults(queryResults); setErrorMessage(""); setShowErrorMessageBar(false); traceSuccess(Action.ExecuteQueryGeneratedFromQueryCopilot, { correlationId: useQueryCopilot.getState().correlationId, }); } catch (error) { const errorMessage = getErrorMessage(error); traceFailure(Action.ExecuteQueryGeneratedFromQueryCopilot, { correlationId: useQueryCopilot.getState().correlationId, errorMessage: errorMessage, }); setErrorMessage(errorMessage); handleError(errorMessage, "executeQueryCopilotTab"); useTabs.getState().setIsQueryErrorThrown(true); setShowErrorMessageBar(true); } finally { setIsExecuting(false); useTabs.getState().setIsTabExecuting(false); } }; const getCommandbarButtons = (): CommandButtonComponentProps[] => { const executeQueryBtnLabel = selectedQuery ? "Execute Selection" : "Execute Query"; const executeQueryBtn = { iconSrc: ExecuteQueryIcon, iconAlt: executeQueryBtnLabel, onCommandClick: () => onExecuteQueryClick(), commandButtonLabel: executeQueryBtnLabel, ariaLabel: executeQueryBtnLabel, hasPopup: false, disabled: query?.trim() === "", }; const saveQueryBtn = { iconSrc: SaveQueryIcon, iconAlt: "Save Query", onCommandClick: () => useSidePanel.getState().openSidePanel("Save Query", ), commandButtonLabel: "Save Query", ariaLabel: "Save Query", hasPopup: false, disabled: query?.trim() === "", }; // Sample Prompts temporary disabled due current design // const samplePromptsBtn = { // iconSrc: SamplePromptsIcon, // iconAlt: "Sample Prompts", // onCommandClick: () => setIsSamplePromptsOpen(true), // commandButtonLabel: "Sample Prompts", // ariaLabel: "Sample Prompts", // hasPopup: false, // }; return [executeQueryBtn, saveQueryBtn]; }; const showTeachingBubble = (): void => { const shouldShowTeachingBubble = !inputEdited.current && userPrompt.trim() === ""; if (shouldShowTeachingBubble) { setTimeout(() => { if (shouldShowTeachingBubble) { toggleCopilotTeachingBubbleVisible(); inputEdited.current = true; } }, 30000); } }; const resetButtonState = () => { setDislikeQuery(false); setLikeQuery(false); setShowCallout(false); }; const startGenerateQueryProcess = () => { updateHistories(); generateSQLQuery(); resetButtonState(); }; React.useEffect(() => { useCommandBar.getState().setContextButtons(getCommandbarButtons()); }, [query, selectedQuery]); React.useEffect(() => { showTeachingBubble(); useTabs.getState().setIsQueryErrorThrown(false); }, []); return (
Copilot { inputEdited.current = true; setShowSamplePrompts(true); }} onKeyDown={(e) => { if (e.key === "Enter") { startGenerateQueryProcess(); } }} style={{ lineHeight: 30 }} styles={{ root: { width: "95%" } }} disabled={isGeneratingQuery} autoComplete="off" /> {copilotTeachingBubbleVisible && ( Write a prompt here and Copilot will generate the query for you. You can also choose from our{" "} { setShowSamplePrompts(true); toggleCopilotTeachingBubbleVisible(); }} style={{ color: "white", fontWeight: 600 }} > sample prompts {" "} or write your own query )} startGenerateQueryProcess()} /> {isGeneratingQuery && } {showSamplePrompts && ( setShowSamplePrompts(false)} directionalHintFixed={true} directionalHint={DirectionalHint.bottomLeftEdge} alignTargetEdge={true} gapSpace={4} > {filteredHistories?.length > 0 && ( Recent {filteredHistories.map((history, i) => ( { setUserPrompt(history); setShowSamplePrompts(false); }} onRenderIcon={() => } styles={promptStyles} > {history} ))} )} {filteredSuggestedPrompts?.length > 0 && ( Suggested Prompts {filteredSuggestedPrompts.map((prompt) => ( { setUserPrompt(prompt.text); setShowSamplePrompts(false); }} onRenderIcon={() => } styles={promptStyles} > {prompt.text} ))} )} {(filteredHistories?.length > 0 || filteredSuggestedPrompts?.length > 0) && ( Learn about{" "} writing effective prompts )} )} AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.{" "} Read preview terms {showErrorMessageBar && ( We ran into an error and were not able to execute query. Please try again after sometime )} {showFeedbackBar && ( Provide feedback on the query generated {showCallout && !hideFeedbackModalForLikedQueries && ( { setShowCallout(false); submitFeedback({ params: { generatedQuery: generatedQuery, likeQuery: likeQuery, description: "", userPrompt: userPrompt, }, explorer: explorer, }); }} directionalHint={DirectionalHint.topCenter} > Thank you. Need to give{" "} { setShowCallout(false); useQueryCopilot.getState().openFeedbackModal(generatedQuery, true, userPrompt); }} > more feedback? )} { setShowCallout(!likeQuery); setLikeQuery(!likeQuery); if (dislikeQuery) { setDislikeQuery(!dislikeQuery); } }} /> { if (!dislikeQuery) { useQueryCopilot.getState().openFeedbackModal(generatedQuery, false, userPrompt); setLikeQuery(false); } setDislikeQuery(!dislikeQuery); setShowCallout(false); }} /> Copy code { setShowDeletePopup(true); }} iconProps={{ iconName: "Delete" }} style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }} > Delete code )} setQuery(newQuery)} onContentSelected={(selectedQuery: string) => setSelectedQuery(selectedQuery)} /> queryDocumentsPerPage(firstItemIndex, queryIterator) } /> {isSamplePromptsOpen && } {query !== "" && query.trim().length !== 0 && ( )}
); };