Query copilot UI part 3 (#1490)

This commit is contained in:
victor-meng 2023-06-27 13:43:10 -07:00 committed by GitHub
parent 42e24d0e99
commit 444f1b66fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 153 additions and 22 deletions

3
images/Hint.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.39804 9.80882C4.57428 9.93313 4.78476 9.99968 5.00043 9.9993C5.21633 9.99946 5.42686 9.93197 5.60243 9.8063C5.77993 9.67582 5.91464 9.49552 5.98943 9.2883L6.43643 7.9153C6.55086 7.57101 6.74391 7.25811 7.00028 7.00139C7.25665 6.74467 7.56929 6.5512 7.91343 6.4363L9.30443 5.9853C9.45636 5.93095 9.59364 5.84214 9.70551 5.72586C9.81738 5.60957 9.9008 5.46896 9.94924 5.31503C9.99767 5.16111 10.0098 4.99806 9.98468 4.83867C9.95955 4.67927 9.89786 4.52786 9.80443 4.3963C9.67034 4.21038 9.47939 4.07314 9.26043 4.0053L7.88543 3.5583C7.54091 3.44407 7.22777 3.25111 6.97087 2.99472C6.71396 2.73833 6.52035 2.42558 6.40543 2.0813L5.95343 0.693301C5.88113 0.490997 5.74761 0.316236 5.57143 0.193301C5.43877 0.0995741 5.28607 0.0380931 5.12548 0.0137472C4.96489 -0.0105984 4.80083 0.00286224 4.64636 0.0530596C4.49188 0.103257 4.35125 0.188806 4.23564 0.302903C4.12004 0.417 4.03265 0.556497 3.98043 0.710301L3.52343 2.1103C3.40884 2.44513 3.21967 2.74954 2.97022 3.00055C2.72076 3.25157 2.41753 3.44263 2.08343 3.5593L0.692428 4.0073C0.540653 4.0617 0.403522 4.15048 0.291767 4.26669C0.180011 4.3829 0.0966621 4.5234 0.0482407 4.67719C-0.000180673 4.83097 -0.0123605 4.99388 0.0126534 5.15315C0.0376676 5.31243 0.0991972 5.46376 0.192428 5.5953C0.320272 5.77475 0.501046 5.90972 0.709428 5.9813L2.08343 6.4263C2.52354 6.57278 2.90999 6.84713 3.19343 7.2143C3.35585 7.42494 3.4813 7.66164 3.56443 7.9143L4.01643 9.3053C4.08846 9.50859 4.22179 9.68452 4.39804 9.80882ZM4.48343 2.3943L5.01043 1.0173L5.44943 2.3943C5.61312 2.88745 5.88991 3.33546 6.25767 3.70253C6.62544 4.0696 7.07397 4.34554 7.56743 4.5083L8.97343 5.0373L7.59143 5.4853C7.09866 5.6496 6.65095 5.92646 6.28382 6.29393C5.9167 6.66141 5.64026 7.10938 5.47643 7.6023L4.95343 8.9803L4.50443 7.6013C4.34335 7.10803 4.06943 6.65913 3.70443 6.2903C3.3356 5.92226 2.88653 5.64467 2.39243 5.4793L1.01443 4.9573L2.40043 4.5073C2.88672 4.33867 3.32775 4.06051 3.68943 3.6943C4.04901 3.3266 4.32049 2.88211 4.48343 2.3943ZM10.5353 13.8513C10.6713 13.9475 10.8337 13.9992 11.0003 13.9993C11.1654 13.9994 11.3264 13.9484 11.4613 13.8533C11.6008 13.7548 11.7058 13.6149 11.7613 13.4533L12.0093 12.6913C12.0625 12.5329 12.1515 12.3888 12.2693 12.2703C12.3867 12.1518 12.5307 12.063 12.6893 12.0113L13.4613 11.7593C13.619 11.7048 13.7557 11.6024 13.8523 11.4663C13.9257 11.3633 13.9736 11.2444 13.9921 11.1193C14.0106 10.9942 13.9992 10.8665 13.9588 10.7467C13.9184 10.6268 13.8501 10.5183 13.7597 10.4299C13.6692 10.3415 13.5591 10.2759 13.4383 10.2383L12.6743 9.98933C12.5162 9.93676 12.3724 9.84814 12.2544 9.73048C12.1364 9.61281 12.0473 9.46932 11.9943 9.31133L11.7423 8.53833C11.6886 8.38048 11.586 8.24387 11.4493 8.14833C11.3473 8.07538 11.2295 8.02744 11.1056 8.00838C10.9816 7.98932 10.8549 7.99967 10.7357 8.0386C10.6164 8.07753 10.508 8.14395 10.4192 8.23249C10.3304 8.32104 10.2636 8.42923 10.2243 8.54833L9.97731 9.31033C9.92502 9.46798 9.83747 9.61162 9.72131 9.73033C9.60657 9.8468 9.46665 9.93541 9.31231 9.98933L8.53931 10.2413C8.38025 10.2952 8.2422 10.3978 8.1447 10.5346C8.04721 10.6713 7.99522 10.8353 7.99611 11.0032C7.99699 11.1712 8.0507 11.3346 8.14963 11.4703C8.24856 11.606 8.38769 11.7071 8.54731 11.7593L9.31031 12.0063C9.46917 12.0597 9.61358 12.149 9.73231 12.2673C9.85053 12.3856 9.93896 12.5302 9.99031 12.6893L10.2433 13.4633C10.2981 13.6198 10.4001 13.7554 10.5353 13.8513ZM9.62231 11.0583L9.44331 10.9993L9.62731 10.9353C9.92907 10.8304 10.2027 10.6576 10.4273 10.4303C10.6537 10.2013 10.8248 9.92359 10.9273 9.61833L10.9853 9.44033L11.0443 9.62133C11.1463 9.92803 11.3185 10.2067 11.5471 10.4352C11.7757 10.6636 12.0545 10.8356 12.3613 10.9373L12.5563 11.0003L12.3763 11.0593C12.0689 11.1615 11.7898 11.3342 11.5611 11.5636C11.3324 11.793 11.1606 12.0727 11.0593 12.3803L11.0003 12.5613L10.9423 12.3803C10.8409 12.0722 10.6687 11.792 10.4394 11.5625C10.2102 11.3329 9.93033 11.1602 9.62231 11.0583Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

3
images/Recent.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.9342 7.04998C15.4095 3.21975 11.8792 0.540074 8.04894 1.06475C6.10618 1.33087 4.46008 2.36951 3.37682 3.82964L3.25046 3.99997H6.49985C6.77599 3.99997 6.99985 4.22383 6.99985 4.49997C6.99985 4.77611 6.77599 4.99997 6.49985 4.99997H2.49985C2.22371 4.99997 1.99985 4.77611 1.99985 4.49997V0.499969C1.99985 0.223826 2.22371 -3.14998e-05 2.49985 -3.14998e-05C2.77599 -3.14998e-05 2.99985 0.223826 2.99985 0.499968V2.70729C4.22416 1.31847 5.93463 0.345036 7.91322 0.0740017C12.2906 -0.525627 16.3253 2.53686 16.9249 6.91426C17.5246 11.2917 14.4621 15.3263 10.0847 15.926C5.70727 16.5256 1.67259 13.4631 1.07296 9.08571C0.998819 8.54443 0.980672 8.00788 1.01426 7.48208C1.03186 7.2065 1.26953 6.99737 1.54511 7.01497C1.82069 7.03258 2.02982 7.27025 2.01222 7.54583C1.98287 8.00544 1.99867 8.47516 2.06371 8.94999C2.58839 12.7802 6.11873 15.4599 9.94896 14.9352C13.7792 14.4105 16.4589 10.8802 15.9342 7.04998Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -3,7 +3,9 @@ import { FeedOptions } from "@azure/cosmos";
import { import {
Callout, Callout,
CommandBarButton, CommandBarButton,
DefaultButton,
DirectionalHint, DirectionalHint,
IButtonStyles,
IconButton, IconButton,
Image, Image,
Link, Link,
@ -31,6 +33,7 @@ import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdap
import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane"; import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane";
import { submitFeedback } from "Explorer/QueryCopilot/QueryCopilotUtilities"; import { submitFeedback } from "Explorer/QueryCopilot/QueryCopilotUtilities";
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection"; import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
import { userContext } from "UserContext";
import { queryPagesUntilContentPresent } from "Utils/QueryUtils"; import { queryPagesUntilContentPresent } from "Utils/QueryUtils";
import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useQueryCopilot } from "hooks/useQueryCopilot";
import { useSidePanel } from "hooks/useSidePanel"; import { useSidePanel } from "hooks/useSidePanel";
@ -38,6 +41,8 @@ import React, { useState } from "react";
import SplitterLayout from "react-splitter-layout"; import SplitterLayout from "react-splitter-layout";
import CopilotIcon from "../../../images/Copilot.svg"; import CopilotIcon from "../../../images/Copilot.svg";
import ExecuteQueryIcon from "../../../images/ExecuteQuery.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 SaveQueryIcon from "../../../images/save-cosmos.svg";
import { useTabs } from "../../hooks/useTabs"; import { useTabs } from "../../hooks/useTabs";
@ -54,12 +59,17 @@ interface GenerateSQLQueryResponse {
generateEnd: 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<QueryCopilotTabProps> = ({ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
initialInput, initialInput,
explorer, explorer,
}: QueryCopilotTabProps): JSX.Element => { }: QueryCopilotTabProps): JSX.Element => {
const hideFeedbackModalForLikedQueries = useQueryCopilot((state) => state.hideFeedbackModalForLikedQueries); const hideFeedbackModalForLikedQueries = useQueryCopilot((state) => state.hideFeedbackModalForLikedQueries);
const [userInput, setUserInput] = useState<string>(initialInput || ""); const [userPrompt, setUserPrompt] = useState<string>(initialInput || "");
const [generatedQuery, setGeneratedQuery] = useState<string>(""); const [generatedQuery, setGeneratedQuery] = useState<string>("");
const [query, setQuery] = useState<string>(""); const [query, setQuery] = useState<string>("");
const [selectedQuery, setSelectedQuery] = useState<string>(""); const [selectedQuery, setSelectedQuery] = useState<string>("");
@ -67,17 +77,28 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
const [isExecuting, setIsExecuting] = useState<boolean>(false); const [isExecuting, setIsExecuting] = useState<boolean>(false);
const [likeQuery, setLikeQuery] = useState<boolean>(); const [likeQuery, setLikeQuery] = useState<boolean>();
const [showCallout, setShowCallout] = useState<boolean>(false); const [showCallout, setShowCallout] = useState<boolean>(false);
const [showSamplePrompts, setShowSamplePrompts] = useState<boolean>(false);
const [queryIterator, setQueryIterator] = useState<MinimalQueryIterator>(); const [queryIterator, setQueryIterator] = useState<MinimalQueryIterator>();
const [queryResults, setQueryResults] = useState<QueryResults>(); const [queryResults, setQueryResults] = useState<QueryResults>();
const [errorMessage, setErrorMessage] = useState<string>(""); const [errorMessage, setErrorMessage] = useState<string>("");
const cachedHistoriesString = localStorage.getItem(`${userContext.databaseAccount?.id}-queryCopilotHistories`);
const cachedHistories = cachedHistoriesString?.split(",");
const [histories, setHistories] = useState<string[]>(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<void> => { const generateSQLQuery = async (): Promise<void> => {
try { try {
setIsGeneratingQuery(true); setIsGeneratingQuery(true);
useTabs.getState().setIsTabExecuting(true); useTabs.getState().setIsTabExecuting(true);
const payload = { const payload = {
containerSchema: QueryCopilotSampleContainerSchema, containerSchema: QueryCopilotSampleContainerSchema,
userPrompt: userInput, userPrompt: userPrompt,
}; };
const response = await fetch("https://copilotorchestrater.azurewebsites.net/generateSQLQuery", { const response = await fetch("https://copilotorchestrater.azurewebsites.net/generateSQLQuery", {
method: "POST", method: "POST",
@ -89,10 +110,9 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json(); const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json();
if (generateSQLQueryResponse?.sql) { if (generateSQLQueryResponse?.sql) {
let query = `-- ${userInput}\r\n`; let query = `-- **Prompt:** ${userPrompt}\r\n`;
if (generateSQLQueryResponse.explanation) { if (generateSQLQueryResponse.explanation) {
query += "-- **Explanation of query**\r\n"; query += `-- **Explanation of query:** ${generateSQLQueryResponse.explanation}\r\n`;
query += `-- ${generateSQLQueryResponse.explanation}\r\n`;
} }
query += generateSQLQueryResponse.sql; query += generateSQLQueryResponse.sql;
setQuery(query); setQuery(query);
@ -169,12 +189,6 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
useCommandBar.getState().setContextButtons(getCommandbarButtons()); useCommandBar.getState().setContextButtons(getCommandbarButtons());
}, [query, selectedQuery]); }, [query, selectedQuery]);
React.useEffect(() => {
if (initialInput) {
generateSQLQuery();
}
}, []);
return ( return (
<Stack className="tab-pane" style={{ padding: 24, width: "100%", height: "100%" }}> <Stack className="tab-pane" style={{ padding: 24, width: "100%", height: "100%" }}>
<Stack horizontal verticalAlign="center"> <Stack horizontal verticalAlign="center">
@ -183,19 +197,122 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
</Stack> </Stack>
<Stack horizontal verticalAlign="center" style={{ marginTop: 16, width: "100%" }}> <Stack horizontal verticalAlign="center" style={{ marginTop: 16, width: "100%" }}>
<TextField <TextField
value={userInput} id="naturalLanguageInput"
onChange={(_, newValue) => setUserInput(newValue)} value={userPrompt}
onChange={(_, newValue) => setUserPrompt(newValue)}
style={{ lineHeight: 30 }} style={{ lineHeight: 30 }}
styles={{ root: { width: "90%" } }} styles={{ root: { width: "95%" } }}
disabled={isGeneratingQuery} disabled={isGeneratingQuery}
onClick={() => setShowSamplePrompts(true)}
/> />
<IconButton <IconButton
iconProps={{ iconName: "Send" }} iconProps={{ iconName: "Send" }}
disabled={isGeneratingQuery} disabled={isGeneratingQuery}
style={{ marginLeft: 8 }} style={{ marginLeft: 8 }}
onClick={() => generateSQLQuery()} onClick={() => {
updateHistories();
generateSQLQuery();
}}
/> />
{isGeneratingQuery && <Spinner style={{ marginLeft: 8 }} />} {isGeneratingQuery && <Spinner style={{ marginLeft: 8 }} />}
{showSamplePrompts && (
<Callout
styles={{ root: { minWidth: 400 } }}
style={{ padding: "8px 0" }}
target="#naturalLanguageInput"
isBeakVisible={false}
onDismiss={() => setShowSamplePrompts(false)}
directionalHint={DirectionalHint.bottomLeftEdge}
>
<Stack>
{histories?.length > 0 && (
<Stack>
<Text
style={{
width: "100%",
fontSize: 14,
fontWeight: 600,
color: "#0078D4",
marginLeft: 16,
padding: "4px 0",
}}
>
Recent
</Text>
{histories.map((history, i) => (
<DefaultButton
key={i}
onClick={() => {
setUserPrompt(history);
setShowSamplePrompts(false);
}}
onRenderIcon={() => <Image src={RecentIcon} />}
styles={promptStyles}
>
{history}
</DefaultButton>
))}
</Stack>
)}
<Text
style={{
width: "100%",
fontSize: 14,
fontWeight: 600,
color: "#0078D4",
marginLeft: 16,
padding: "4px 0",
}}
>
Suggested Prompts
</Text>
<DefaultButton
onClick={() => {
setUserPrompt("Give me all customers whose names start with C");
setShowSamplePrompts(false);
}}
onRenderIcon={() => <Image src={HintIcon} />}
styles={promptStyles}
>
Give me all customers whose names start with C
</DefaultButton>
<DefaultButton
onClick={() => {
setUserPrompt("Show me all customers");
setShowSamplePrompts(false);
}}
onRenderIcon={() => <Image src={HintIcon} />}
styles={promptStyles}
>
Show me all customers
</DefaultButton>
<DefaultButton
onClick={() => {
setUserPrompt("Show me all customers who bought a bike in 2019");
setShowSamplePrompts(false);
}}
onRenderIcon={() => <Image src={HintIcon} />}
styles={promptStyles}
>
Show me all customers who bought a bike in 2019
</DefaultButton>
<Separator styles={{ root: { selectors: { "::before": { background: "#E1DFDD" } }, padding: 0 } }} />
<Text
style={{
width: "100%",
fontSize: 14,
marginLeft: 16,
padding: "4px 0",
}}
>
Learn about{" "}
<Link target="_blank" href="">
writing effective prompts
</Link>
</Text>
</Stack>
</Callout>
)}
</Stack> </Stack>
<Text style={{ marginTop: 8, marginBottom: 24, fontSize: 12 }}> <Text style={{ marginTop: 8, marginBottom: 24, fontSize: 12 }}>
AI-generated content can have mistakes. Make sure it&apos;s accurate and appropriate before using it.{" "} AI-generated content can have mistakes. Make sure it&apos;s accurate and appropriate before using it.{" "}
@ -212,7 +329,7 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
target="#likeBtn" target="#likeBtn"
onDismiss={() => { onDismiss={() => {
setShowCallout(false); setShowCallout(false);
submitFeedback({ generatedQuery, likeQuery, description: "", userPrompt: userInput }); submitFeedback({ generatedQuery, likeQuery, description: "", userPrompt: userPrompt });
}} }}
directionalHint={DirectionalHint.topCenter} directionalHint={DirectionalHint.topCenter}
> >
@ -221,7 +338,7 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
<Link <Link
onClick={() => { onClick={() => {
setShowCallout(false); setShowCallout(false);
useQueryCopilot.getState().openFeedbackModal(generatedQuery, true, userInput); useQueryCopilot.getState().openFeedbackModal(generatedQuery, true, userPrompt);
}} }}
> >
more feedback? more feedback?
@ -244,10 +361,10 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
onClick={() => { onClick={() => {
setLikeQuery(false); setLikeQuery(false);
setShowCallout(false); setShowCallout(false);
useQueryCopilot.getState().openFeedbackModal(generatedQuery, false, userInput); useQueryCopilot.getState().openFeedbackModal(generatedQuery, false, userPrompt);
}} }}
/> />
<Separator vertical style={{ color: "#EDEBE9" }} /> <Separator vertical styles={{ root: { selectors: { "::before": { background: "#E1DFDD" } } } }} />
<CommandBarButton iconProps={{ iconName: "Copy" }} style={{ margin: "0 10px", backgroundColor: "#FFF8F0" }}> <CommandBarButton iconProps={{ iconName: "Copy" }} style={{ margin: "0 10px", backgroundColor: "#FFF8F0" }}>
Copy code Copy code
</CommandBarButton> </CommandBarButton>

View File

@ -42,7 +42,9 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
> >
<StyledTextFieldBase <StyledTextFieldBase
disabled={false} disabled={false}
id="naturalLanguageInput"
onChange={[Function]} onChange={[Function]}
onClick={[Function]}
style={ style={
Object { Object {
"lineHeight": 30, "lineHeight": 30,
@ -51,7 +53,7 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"width": "90%", "width": "95%",
}, },
} }
} }
@ -138,9 +140,15 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
} }
/> />
<Separator <Separator
style={ styles={
Object { Object {
"color": "#EDEBE9", "root": Object {
"selectors": Object {
"::before": Object {
"background": "#E1DFDD",
},
},
},
} }
} }
vertical={true} vertical={true}