From 444f1b66fd0d0fc0d19b03f3240cd3807c3541d7 Mon Sep 17 00:00:00 2001
From: victor-meng <56978073+victor-meng@users.noreply.github.com>
Date: Tue, 27 Jun 2023 13:43:10 -0700
Subject: [PATCH] Query copilot UI part 3 (#1490)
---
images/Hint.svg | 3 +
images/Recent.svg | 3 +
src/Explorer/QueryCopilot/QueryCopilotTab.tsx | 155 +++++++++++++++---
.../QueryCopilotTab.test.tsx.snap | 14 +-
4 files changed, 153 insertions(+), 22 deletions(-)
create mode 100644 images/Hint.svg
create mode 100644 images/Recent.svg
diff --git a/images/Hint.svg b/images/Hint.svg
new file mode 100644
index 000000000..9843bef36
--- /dev/null
+++ b/images/Hint.svg
@@ -0,0 +1,3 @@
+
diff --git a/images/Recent.svg b/images/Recent.svg
new file mode 100644
index 000000000..5295fb567
--- /dev/null
+++ b/images/Recent.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/Explorer/QueryCopilot/QueryCopilotTab.tsx b/src/Explorer/QueryCopilot/QueryCopilotTab.tsx
index 91d392f0d..1a20e57dc 100644
--- a/src/Explorer/QueryCopilot/QueryCopilotTab.tsx
+++ b/src/Explorer/QueryCopilot/QueryCopilotTab.tsx
@@ -3,7 +3,9 @@ import { FeedOptions } from "@azure/cosmos";
import {
Callout,
CommandBarButton,
+ DefaultButton,
DirectionalHint,
+ IButtonStyles,
IconButton,
Image,
Link,
@@ -31,6 +33,7 @@ import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdap
import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane";
import { submitFeedback } from "Explorer/QueryCopilot/QueryCopilotUtilities";
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";
@@ -38,6 +41,8 @@ import React, { useState } from "react";
import SplitterLayout from "react-splitter-layout";
import CopilotIcon from "../../../images/Copilot.svg";
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
+import HintIcon from "../../../images/Hint.svg";
+import RecentIcon from "../../../images/Recent.svg";
import SaveQueryIcon from "../../../images/save-cosmos.svg";
import { useTabs } from "../../hooks/useTabs";
@@ -54,12 +59,17 @@ interface GenerateSQLQueryResponse {
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 [userInput, setUserInput] = useState(initialInput || "");
+ const [userPrompt, setUserPrompt] = useState(initialInput || "");
const [generatedQuery, setGeneratedQuery] = useState("");
const [query, setQuery] = useState("");
const [selectedQuery, setSelectedQuery] = useState("");
@@ -67,17 +77,28 @@ export const QueryCopilotTab: React.FC = ({
const [isExecuting, setIsExecuting] = useState(false);
const [likeQuery, setLikeQuery] = useState();
const [showCallout, setShowCallout] = useState(false);
+ const [showSamplePrompts, setShowSamplePrompts] = useState(false);
const [queryIterator, setQueryIterator] = useState();
const [queryResults, setQueryResults] = useState();
const [errorMessage, setErrorMessage] = useState("");
+ const cachedHistoriesString = localStorage.getItem(`${userContext.databaseAccount?.id}-queryCopilotHistories`);
+ const cachedHistories = cachedHistoriesString?.split(",");
+ const [histories, setHistories] = useState(cachedHistories || []);
+
+ 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);
const payload = {
containerSchema: QueryCopilotSampleContainerSchema,
- userPrompt: userInput,
+ userPrompt: userPrompt,
};
const response = await fetch("https://copilotorchestrater.azurewebsites.net/generateSQLQuery", {
method: "POST",
@@ -89,10 +110,9 @@ export const QueryCopilotTab: React.FC = ({
const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json();
if (generateSQLQueryResponse?.sql) {
- let query = `-- ${userInput}\r\n`;
+ let query = `-- **Prompt:** ${userPrompt}\r\n`;
if (generateSQLQueryResponse.explanation) {
- query += "-- **Explanation of query**\r\n";
- query += `-- ${generateSQLQueryResponse.explanation}\r\n`;
+ query += `-- **Explanation of query:** ${generateSQLQueryResponse.explanation}\r\n`;
}
query += generateSQLQueryResponse.sql;
setQuery(query);
@@ -169,12 +189,6 @@ export const QueryCopilotTab: React.FC = ({
useCommandBar.getState().setContextButtons(getCommandbarButtons());
}, [query, selectedQuery]);
- React.useEffect(() => {
- if (initialInput) {
- generateSQLQuery();
- }
- }, []);
-
return (
@@ -183,19 +197,122 @@ export const QueryCopilotTab: React.FC = ({
setUserInput(newValue)}
+ id="naturalLanguageInput"
+ value={userPrompt}
+ onChange={(_, newValue) => setUserPrompt(newValue)}
style={{ lineHeight: 30 }}
- styles={{ root: { width: "90%" } }}
+ styles={{ root: { width: "95%" } }}
disabled={isGeneratingQuery}
+ onClick={() => setShowSamplePrompts(true)}
/>
generateSQLQuery()}
+ onClick={() => {
+ updateHistories();
+ generateSQLQuery();
+ }}
/>
{isGeneratingQuery && }
+ {showSamplePrompts && (
+ setShowSamplePrompts(false)}
+ directionalHint={DirectionalHint.bottomLeftEdge}
+ >
+
+ {histories?.length > 0 && (
+
+
+ Recent
+
+ {histories.map((history, i) => (
+ {
+ setUserPrompt(history);
+ setShowSamplePrompts(false);
+ }}
+ onRenderIcon={() => }
+ styles={promptStyles}
+ >
+ {history}
+
+ ))}
+
+ )}
+
+ Suggested Prompts
+
+ {
+ setUserPrompt("Give me all customers whose names start with C");
+ setShowSamplePrompts(false);
+ }}
+ onRenderIcon={() => }
+ styles={promptStyles}
+ >
+ Give me all customers whose names start with C
+
+ {
+ setUserPrompt("Show me all customers");
+ setShowSamplePrompts(false);
+ }}
+ onRenderIcon={() => }
+ styles={promptStyles}
+ >
+ Show me all customers
+
+ {
+ setUserPrompt("Show me all customers who bought a bike in 2019");
+ setShowSamplePrompts(false);
+ }}
+ onRenderIcon={() => }
+ styles={promptStyles}
+ >
+ Show me all customers who bought a bike in 2019
+
+
+
+ Learn about{" "}
+
+ writing effective prompts
+
+
+
+
+ )}
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.{" "}
@@ -212,7 +329,7 @@ export const QueryCopilotTab: React.FC = ({
target="#likeBtn"
onDismiss={() => {
setShowCallout(false);
- submitFeedback({ generatedQuery, likeQuery, description: "", userPrompt: userInput });
+ submitFeedback({ generatedQuery, likeQuery, description: "", userPrompt: userPrompt });
}}
directionalHint={DirectionalHint.topCenter}
>
@@ -221,7 +338,7 @@ export const QueryCopilotTab: React.FC = ({
{
setShowCallout(false);
- useQueryCopilot.getState().openFeedbackModal(generatedQuery, true, userInput);
+ useQueryCopilot.getState().openFeedbackModal(generatedQuery, true, userPrompt);
}}
>
more feedback?
@@ -244,10 +361,10 @@ export const QueryCopilotTab: React.FC = ({
onClick={() => {
setLikeQuery(false);
setShowCallout(false);
- useQueryCopilot.getState().openFeedbackModal(generatedQuery, false, userInput);
+ useQueryCopilot.getState().openFeedbackModal(generatedQuery, false, userPrompt);
}}
/>
-
+
Copy code
diff --git a/src/Explorer/QueryCopilot/__snapshots__/QueryCopilotTab.test.tsx.snap b/src/Explorer/QueryCopilot/__snapshots__/QueryCopilotTab.test.tsx.snap
index e13ee1254..e4087996b 100644
--- a/src/Explorer/QueryCopilot/__snapshots__/QueryCopilotTab.test.tsx.snap
+++ b/src/Explorer/QueryCopilot/__snapshots__/QueryCopilotTab.test.tsx.snap
@@ -42,7 +42,9 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
>