/* 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 } from "Common/Constants"; import { getErrorMessage, handleError } from "Common/ErrorHandlingUtils"; import { shouldEnableCrossPartitionKey } from "Common/HeadersUtility"; import { MinimalQueryIterator } from "Common/IteratorUtilities"; 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 { 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/SamplePrompts/SamplePrompts"; import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection"; 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 SamplePromptsIcon from "../../../images/SamplePromptsIcon.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 { initialInput: string; 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 = ({ initialInput, explorer, }: QueryCopilotTabProps): JSX.Element => { const hideFeedbackModalForLikedQueries = useQueryCopilot((state) => state.hideFeedbackModalForLikedQueries); const [userPrompt, setUserPrompt] = useState(initialInput || ""); const [generatedQuery, setGeneratedQuery] = useState(""); const [query, setQuery] = useState(""); const [selectedQuery, setSelectedQuery] = useState(""); const [isGeneratingQuery, setIsGeneratingQuery] = useState(false); const [isExecuting, setIsExecuting] = useState(false); const [likeQuery, setLikeQuery] = useState(); const [dislikeQuery, setDislikeQuery] = useState(); const [showCallout, setShowCallout] = useState(false); const [showSamplePrompts, setShowSamplePrompts] = useState(false); const [queryIterator, setQueryIterator] = useState(); const [queryResults, setQueryResults] = useState(); const [errorMessage, setErrorMessage] = useState(""); const [copilotTeachingBubbleVisible, { toggle: toggleCopilotTeachingBubbleVisible }] = useBoolean(false); const inputEdited = useRef(false); const [isSamplePromptsOpen, setIsSamplePromptsOpen] = useState(false); const [showDeletePopup, setShowDeletePopup] = useState(false); const [showFeedbackBar, setShowFeedbackBar] = useState(false); const [showCopyPopup, setshowCopyPopup] = useState(false); const [showErrorMessageBar, setShowErrorMessageBar] = useState(false); 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: "Give me all customers whose names start with C" }, { id: 2, text: "Show me all customers" }, { id: 3, text: "Show me all customers who bought a bike in 2019" }, ]; 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 newHistories = histories.length < 3 ? [userPrompt, ...histories] : [userPrompt, histories[1], histories[2]]; setHistories(newHistories); localStorage.setItem(`${userContext.databaseAccount.id}-queryCopilotHistories`, newHistories.join(",")); }; const generateSQLQuery = async (): Promise => { try { setIsGeneratingQuery(true); useTabs.getState().setIsTabExecuting(true); useTabs.getState().setIsQueryErrorThrown(false); const payload = { containerSchema: QueryCopilotSampleContainerSchema, userPrompt: userPrompt, }; setShowDeletePopup(false); const response = await fetch("https://copilotorchestrater.azurewebsites.net/generateSQLQuery", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify(payload), }); const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json(); 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); } } 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 => { 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(""); } catch (error) { const errorMessage = getErrorMessage(error); 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, }; const saveQueryBtn = { iconSrc: SaveQueryIcon, iconAlt: "Save Query", onCommandClick: () => useSidePanel.getState().openSidePanel("Save Query", ), commandButtonLabel: "Save Query", ariaLabel: "Save Query", hasPopup: false, }; const samplePromptsBtn = { iconSrc: SamplePromptsIcon, iconAlt: "Sample Prompts", onCommandClick: () => setIsSamplePromptsOpen(true), commandButtonLabel: "Sample Prompts", ariaLabel: "Sample Prompts", hasPopup: false, }; return [executeQueryBtn, saveQueryBtn, samplePromptsBtn]; }; const showTeachingBubble = (): void => { if (!inputEdited.current) { setTimeout(() => { if (!inputEdited.current) { toggleCopilotTeachingBubbleVisible(); inputEdited.current = true; } }, 30000); } }; const resetButtonState = () => { setDislikeQuery(false); setLikeQuery(false); setShowCallout(false); }; React.useEffect(() => { useCommandBar.getState().setContextButtons(getCommandbarButtons()); }, [query, selectedQuery]); React.useEffect(() => { if (initialInput) { generateSQLQuery(); } showTeachingBubble(); useTabs.getState().setIsQueryErrorThrown(false); }, []); return ( Copilot { inputEdited.current = true; setShowSamplePrompts(true); }} 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 )} { updateHistories(); generateSQLQuery(); resetButtonState(); }} /> {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} ))} )} Suggested Prompts {filteredSuggestedPrompts.map((prompt) => ( { setUserPrompt(prompt.text); setShowSamplePrompts(false); }} onRenderIcon={() => } styles={promptStyles} > {prompt.text} ))} 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({ generatedQuery, likeQuery, description: "", userPrompt: userPrompt }); }} 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 && ( )} ); };