/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable no-console */ import { Callout, CommandBarButton, DefaultButton, DirectionalHint, IButtonStyles, IconButton, Image, Link, MessageBar, MessageBarType, Separator, Spinner, Stack, TeachingBubble, Text, TextField, } from "@fluentui/react"; import { useBoolean } from "@fluentui/react-hooks"; import { HttpStatusCodes } from "Common/Constants"; import { handleError } from "Common/ErrorHandlingUtils"; import { createUri } from "Common/UrlUtility"; import { WelcomeModal } from "Explorer/QueryCopilot/Modal/WelcomeModal"; import { CopyPopup } from "Explorer/QueryCopilot/Popup/CopyPopup"; import { DeletePopup } from "Explorer/QueryCopilot/Popup/DeletePopup"; import { SuggestedPrompt, getSampleDatabaseSuggestedPrompts, getSuggestedPrompts, } from "Explorer/QueryCopilot/QueryCopilotUtilities"; import { SubmitFeedback, allocatePhoenixContainer } from "Explorer/QueryCopilot/Shared/QueryCopilotClient"; import { GenerateSQLQueryResponse, QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces"; import { SamplePrompts, SamplePromptsProps } from "Explorer/QueryCopilot/Shared/SamplePrompts/SamplePrompts"; import { Action } from "Shared/Telemetry/TelemetryConstants"; import { userContext } from "UserContext"; import { useQueryCopilot } from "hooks/useQueryCopilot"; import React, { useCallback, useRef, useState } from "react"; import HintIcon from "../../../images/Hint.svg"; import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg"; import RecentIcon from "../../../images/Recent.svg"; import errorIcon from "../../../images/close-black.svg"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { useTabs } from "../../hooks/useTabs"; import "../Controls/ThroughputInput/ThroughputInput.less"; import { useCopilotStore } from "../QueryCopilot/QueryCopilotContext"; import { useSelectedNode } from "../useSelectedNode"; type QueryCopilotPromptProps = QueryCopilotProps & { databaseId: string; containerId: string; toggleCopilot: (toggle: boolean) => void; }; const promptStyles: IButtonStyles = { root: { border: 0, selectors: { ":hover": { outline: "1px dashed #605e5c" } } }, label: { fontWeight: 400, textAlign: "left", paddingLeft: 8, overflow: "hidden", whiteSpace: "nowrap", textOverflow: "ellipsis", }, textContainer: { overflow: "hidden" }, }; export const QueryCopilotPromptbar: React.FC = ({ explorer, toggleCopilot, databaseId, containerId, }: QueryCopilotPromptProps): JSX.Element => { const [copilotTeachingBubbleVisible, { toggle: toggleCopilotTeachingBubbleVisible }] = useBoolean(false); const inputEdited = useRef(false); const { openFeedbackModal, hideFeedbackModalForLikedQueries, userPrompt, setUserPrompt, generatedQuery, setGeneratedQuery, query, setQuery, isGeneratingQuery, setIsGeneratingQuery, likeQuery, setLikeQuery, dislikeQuery, setDislikeQuery, showCallout, setShowCallout, isSamplePromptsOpen, setIsSamplePromptsOpen, showSamplePrompts, setShowSamplePrompts, showDeletePopup, setShowDeletePopup, showFeedbackBar, setShowFeedbackBar, showCopyPopup, setshowCopyPopup, showErrorMessageBar, showInvalidQueryMessageBar, setShowInvalidQueryMessageBar, setShowErrorMessageBar, setGeneratedQueryComments, setQueryResults, setErrorMessage, errorMessage, } = useCopilotStore(); const [focusedindex, setFocusedindex] = useState(-1); 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 isSampleCopilotActive = useSelectedNode.getState().isQueryCopilotCollectionSelected(); const cachedHistoriesString = localStorage.getItem(`${userContext.databaseAccount?.id}-queryCopilotHistories`); const cachedHistories = cachedHistoriesString?.split("|"); const [histories, setHistories] = useState(cachedHistories || []); const suggestedPrompts: SuggestedPrompt[] = isSampleCopilotActive ? getSampleDatabaseSuggestedPrompts() : getSuggestedPrompts(); 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 resetMessageStates = (): void => { setShowErrorMessageBar(false); setShowInvalidQueryMessageBar(false); setShowFeedbackBar(false); }; const resetQueryResults = (): void => { setQueryResults(null); setErrorMessage(""); }; const generateSQLQuery = async (): Promise => { try { resetMessageStates(); setIsGeneratingQuery(true); setShowDeletePopup(false); useTabs.getState().setIsTabExecuting(true); useTabs.getState().setIsQueryErrorThrown(false); const mode: string = isSampleCopilotActive ? "Sample" : "User"; await allocatePhoenixContainer({ explorer, databaseId, containerId, mode }); const payload = { userPrompt: userPrompt, }; useQueryCopilot.getState().refreshCorrelationId(); const serverInfo = useQueryCopilot.getState().notebookServerInfo; const queryUri = userContext.features.disableCopilotPhoenixGateaway ? createUri("https://copilotorchestrater.azurewebsites.net/", "generateSQLQuery") : createUri(serverInfo.notebookServerEndpoint, "public/generateSQLQuery"); const response = await fetch(queryUri, { method: "POST", headers: { "content-type": "application/json", "x-ms-correlationid": useQueryCopilot.getState().correlationId, Authorization: `token ${useQueryCopilot.getState().notebookServerInfo.authToken}`, }, body: JSON.stringify(payload), }); const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json(); if (response.ok) { if (generateSQLQueryResponse?.sql !== "N/A") { 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); setShowFeedbackBar(true); resetQueryResults(); TelemetryProcessor.traceSuccess(Action.QueryGenerationFromCopilotPrompt, { databaseName: databaseId, collectionId: containerId, copilotLatency: Date.parse(generateSQLQueryResponse?.generateEnd) - Date.parse(generateSQLQueryResponse?.generateStart), responseCode: response.status, }); } else { setShowInvalidQueryMessageBar(true); TelemetryProcessor.traceFailure(Action.QueryGenerationFromCopilotPrompt, { databaseName: databaseId, collectionId: containerId, responseCode: response.status, }); } } else if (response?.status === HttpStatusCodes.TooManyRequests) { handleError(JSON.stringify(generateSQLQueryResponse), "copilotTooManyRequestError"); useTabs.getState().setIsQueryErrorThrown(true); setShowErrorMessageBar(true); setErrorMessage("Ratelimit exceeded 5 per 1 minute. Please try again after sometime"); TelemetryProcessor.traceFailure(Action.QueryGenerationFromCopilotPrompt, { databaseName: databaseId, collectionId: containerId, responseCode: response.status, }); } else { handleError(JSON.stringify(generateSQLQueryResponse), "copilotInternalServerError"); useTabs.getState().setIsQueryErrorThrown(true); setShowErrorMessageBar(true); TelemetryProcessor.traceFailure(Action.QueryGenerationFromCopilotPrompt, { databaseName: databaseId, collectionId: containerId, responseCode: response.status, }); } } catch (error) { handleError(error, "executeNaturalLanguageQuery"); useTabs.getState().setIsQueryErrorThrown(true); setShowErrorMessageBar(true); throw error; } finally { setIsGeneratingQuery(false); useTabs.getState().setIsTabExecuting(false); } }; const showTeachingBubble = (): void => { if (!inputEdited.current) { setTimeout(() => { if (!inputEdited.current && !isWelcomModalVisible()) { toggleCopilotTeachingBubbleVisible(); inputEdited.current = true; } }, 30000); } }; const isWelcomModalVisible = (): boolean => { return localStorage.getItem("hideWelcomeModal") !== "true"; }; const clearFeedback = () => { resetButtonState(); resetQueryResults(); }; const resetButtonState = () => { setDislikeQuery(false); setLikeQuery(false); setShowCallout(false); }; const startGenerateQueryProcess = () => { updateHistories(); generateSQLQuery(); resetButtonState(); }; React.useEffect(() => { showTeachingBubble(); useTabs.getState().setIsQueryErrorThrown(false); }, []); const suggestionsnhistory = filteredHistories.concat(filteredSuggestedPrompts.map((item) => item.text)); const handlekeydown: React.KeyboardEventHandler = (e) => { const { key } = e; let nexIndexCount = 0; if (key === "ArrowDown") { nexIndexCount = (focusedindex + 1) % suggestionsnhistory.length; console.log(nexIndexCount); } if (key === "ArrowUp") { nexIndexCount = (focusedindex + suggestionsnhistory.length - 1) % suggestionsnhistory.length; console.log(nexIndexCount); } if (key === "Enter") { e.preventDefault(); console.log(focusedindex); handleSelection(focusedindex); } setFocusedindex(nexIndexCount); }; const handleSelection = (selectedIndex: number) => { const selecteditem = suggestionsnhistory[selectedIndex]; if (!selecteditem) return resetSearchComplete(); else { handlepromptset(selecteditem); } }; const resetSearchComplete = useCallback(() => { setFocusedindex(-1); setShowSamplePrompts(false); }, []); const handlepromptset = (prompttext: string) => { inputEdited.current = true; setUserPrompt(prompttext); console.log("prompt", userPrompt); }; return ( Copilot { toggleCopilot(false); clearFeedback(); resetMessageStates(); }} styles={{ root: { marginLeft: "auto !important", }, }} ariaLabel="Close" /> { inputEdited.current = true; setShowSamplePrompts(true); }} onKeyDown={handlekeydown} onFocus={() => setShowSamplePrompts(true)} style={{ lineHeight: 30 }} styles={{ root: { width: "95%" }, fieldGroup: { borderRadius: 6 } }} disabled={isGeneratingQuery} autoComplete="off" placeholder="Ask a question in natural language and we’ll generate the query for you." tabIndex={0} /> {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(); setShowSamplePrompts(false); }} /> {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); inputEdited.current = true; }} onRenderIcon={() => } styles={promptStyles} className={focusedindex === i ? "highlighted-buttonstyles" : "buttonstyles"} > {history} ))} )} {filteredSuggestedPrompts?.length > 0 && ( Suggested Prompts {filteredSuggestedPrompts.map((prompt, index) => ( { setUserPrompt(prompt.text); setShowSamplePrompts(false); inputEdited.current = true; }} onRenderIcon={() => } styles={promptStyles} className={focusedindex === filteredHistories.length + index ? "highlighted-buttonstyles" : ""} > {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 && ( {errorMessage ? errorMessage : "We ran into an error and were not able to execute query."} )} {showInvalidQueryMessageBar && ( We were unable to generate a query based upon the prompt provided. Please modify the prompt and try again. For examples of how to write a good prompt, please read this article. {" "} Our content guidelines can be found here. )} {showFeedbackBar && ( Provide feedback on the query generated {showCallout && !hideFeedbackModalForLikedQueries && ( { setShowCallout(false); SubmitFeedback({ params: { generatedQuery: generatedQuery, likeQuery: likeQuery, description: "", userPrompt: userPrompt, }, explorer, databaseId, containerId, mode: isSampleCopilotActive ? "Sample" : "User", }); }} directionalHint={DirectionalHint.topCenter} > Thank you. Need to give{" "} { setShowCallout(false); openFeedbackModal(generatedQuery, true, userPrompt); }} > more feedback? )} { setShowCallout(!likeQuery); setLikeQuery(!likeQuery); if (dislikeQuery) { setDislikeQuery(!dislikeQuery); } }} /> { if (!dislikeQuery) { openFeedbackModal(generatedQuery, false, userPrompt); setLikeQuery(false); } setDislikeQuery(!dislikeQuery); setShowCallout(false); }} /> Copy query { setShowDeletePopup(true); }} iconProps={{ iconName: "Delete" }} style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }} > Delete query )} {isSamplePromptsOpen && } {query !== "" && query.trim().length !== 0 && ( )} ); };