mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-27 07:56:47 +00:00
integrate copilot UI with backend (#1478)
This commit is contained in:
parent
b954b14f56
commit
a282ad9242
@ -37,7 +37,7 @@ module.exports = {
|
|||||||
coverageThreshold: {
|
coverageThreshold: {
|
||||||
global: {
|
global: {
|
||||||
branches: 25,
|
branches: 25,
|
||||||
functions: 25,
|
functions: 24,
|
||||||
lines: 28,
|
lines: 28,
|
||||||
statements: 28,
|
statements: 28,
|
||||||
},
|
},
|
||||||
|
11484
sampleData/queryCopilotSampleData.json
Normal file
11484
sampleData/queryCopilotSampleData.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -431,3 +431,88 @@ export class JunoEndpoints {
|
|||||||
|
|
||||||
export const QueryCopilotSampleDatabaseId = "CopilotSampleDb";
|
export const QueryCopilotSampleDatabaseId = "CopilotSampleDb";
|
||||||
export const QueryCopilotSampleContainerId = "SampleContainer";
|
export const QueryCopilotSampleContainerId = "SampleContainer";
|
||||||
|
|
||||||
|
export const QueryCopilotSampleContainerSchema = {
|
||||||
|
product: {
|
||||||
|
sampleData: {
|
||||||
|
id: "de6fadec-0384-43c8-93ea-16c0170b5460",
|
||||||
|
name: "Premium Phone Mini (Red)",
|
||||||
|
price: 652.04,
|
||||||
|
category: "Electronics",
|
||||||
|
description:
|
||||||
|
"This Premium Phone Mini (Red) is designed by the company under agreement with the FCC, so we'd give it a pass in either direction, but no one should be using this handset without a compatible handset. All in all, this phone is one of the most affordable Android handsets out there at $100. Check them out.\n\n9. HTC One M9 4",
|
||||||
|
stock: 74,
|
||||||
|
countryOfOrigin: "Mexico",
|
||||||
|
firstAvailable: "2018-07-07 17:42:28",
|
||||||
|
priceHistory: [592.81],
|
||||||
|
customerRatings: [
|
||||||
|
{ username: "dannyhowell", stars: 1, date: "2022-03-12 17:01:23", verifiedUser: true },
|
||||||
|
{ username: "lindsay26", stars: 1, date: "2022-12-29 07:18:20", verifiedUser: false },
|
||||||
|
{ username: "smithmiguel", stars: 3, date: "2022-09-08 11:43:27", verifiedUser: false },
|
||||||
|
{ username: "julie07", stars: 3, date: "2021-03-14 23:54:10", verifiedUser: true },
|
||||||
|
{ username: "kelly93", stars: 3, date: "2022-11-05 12:45:43", verifiedUser: false },
|
||||||
|
{ username: "katherinereynolds", stars: 2, date: "2022-09-14 11:49:36", verifiedUser: false },
|
||||||
|
{ username: "chandlerkenneth", stars: 1, date: "2022-01-14 12:14:43", verifiedUser: true },
|
||||||
|
],
|
||||||
|
rareProperty: true,
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
price: {
|
||||||
|
type: "number",
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
stock: {
|
||||||
|
type: "number",
|
||||||
|
},
|
||||||
|
countryOfOrigin: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
firstAvailable: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
priceHistory: {
|
||||||
|
items: {
|
||||||
|
type: "number",
|
||||||
|
},
|
||||||
|
type: "array",
|
||||||
|
},
|
||||||
|
customerRatings: {
|
||||||
|
items: {
|
||||||
|
properties: {
|
||||||
|
username: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
stars: {
|
||||||
|
type: "number",
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
verifiedUser: {
|
||||||
|
type: "boolean",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: "object",
|
||||||
|
},
|
||||||
|
type: "array",
|
||||||
|
},
|
||||||
|
rareProperty: {
|
||||||
|
type: "boolean",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: "object",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
@ -22,10 +22,17 @@ export class ContainerSampleGenerator {
|
|||||||
/**
|
/**
|
||||||
* Factory function to load the json data file
|
* Factory function to load the json data file
|
||||||
*/
|
*/
|
||||||
public static async createSampleGeneratorAsync(container: Explorer): Promise<ContainerSampleGenerator> {
|
public static async createSampleGeneratorAsync(
|
||||||
|
container: Explorer,
|
||||||
|
isCopilot?: boolean
|
||||||
|
): Promise<ContainerSampleGenerator> {
|
||||||
const generator = new ContainerSampleGenerator(container);
|
const generator = new ContainerSampleGenerator(container);
|
||||||
let dataFileContent: any;
|
let dataFileContent: any;
|
||||||
if (userContext.apiType === "Gremlin") {
|
if (isCopilot) {
|
||||||
|
dataFileContent = await import(
|
||||||
|
/* webpackChunkName: "queryCopilotSampleData" */ "../../../sampleData/queryCopilotSampleData.json"
|
||||||
|
);
|
||||||
|
} else if (userContext.apiType === "Gremlin") {
|
||||||
dataFileContent = await import(
|
dataFileContent = await import(
|
||||||
/* webpackChunkName: "gremlinSampleJsonData" */ "../../../sampleData/gremlinSampleData.json"
|
/* webpackChunkName: "gremlinSampleJsonData" */ "../../../sampleData/gremlinSampleData.json"
|
||||||
);
|
);
|
||||||
|
@ -48,6 +48,7 @@ export const QueryCopilotCarousel: React.FC<QueryCopilotCarouselProps> = ({
|
|||||||
}: QueryCopilotCarouselProps): JSX.Element => {
|
}: QueryCopilotCarouselProps): JSX.Element => {
|
||||||
const [page, setPage] = useState<number>(1);
|
const [page, setPage] = useState<number>(1);
|
||||||
const [isCreatingDatabase, setIsCreatingDatabase] = useState<boolean>(false);
|
const [isCreatingDatabase, setIsCreatingDatabase] = useState<boolean>(false);
|
||||||
|
const [spinnerText, setSpinnerText] = useState<string>("");
|
||||||
const [selectedPrompt, setSelectedPrompt] = useState<number>(1);
|
const [selectedPrompt, setSelectedPrompt] = useState<number>(1);
|
||||||
|
|
||||||
const getHeaderText = (): string => {
|
const getHeaderText = (): string => {
|
||||||
@ -84,6 +85,7 @@ export const QueryCopilotCarousel: React.FC<QueryCopilotCarouselProps> = ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setIsCreatingDatabase(true);
|
setIsCreatingDatabase(true);
|
||||||
|
setSpinnerText("Setting up your database...");
|
||||||
const params: DataModels.CreateCollectionParams = {
|
const params: DataModels.CreateCollectionParams = {
|
||||||
createNewDatabase: true,
|
createNewDatabase: true,
|
||||||
collectionId: "SampleContainer",
|
collectionId: "SampleContainer",
|
||||||
@ -93,7 +95,7 @@ export const QueryCopilotCarousel: React.FC<QueryCopilotCarouselProps> = ({
|
|||||||
offerThroughput: undefined,
|
offerThroughput: undefined,
|
||||||
indexingPolicy: AllPropertiesIndexed,
|
indexingPolicy: AllPropertiesIndexed,
|
||||||
partitionKey: {
|
partitionKey: {
|
||||||
paths: ["/CategoryId"],
|
paths: ["/categoryId"],
|
||||||
kind: "Hash",
|
kind: "Hash",
|
||||||
version: 2,
|
version: 2,
|
||||||
},
|
},
|
||||||
@ -101,22 +103,22 @@ export const QueryCopilotCarousel: React.FC<QueryCopilotCarouselProps> = ({
|
|||||||
await createCollection(params);
|
await createCollection(params);
|
||||||
await explorer.refreshAllDatabases();
|
await explorer.refreshAllDatabases();
|
||||||
const database = useDatabases.getState().findDatabaseWithId(QueryCopilotSampleDatabaseId);
|
const database = useDatabases.getState().findDatabaseWithId(QueryCopilotSampleDatabaseId);
|
||||||
// populate sample container with sample data
|
|
||||||
await database.loadCollections();
|
await database.loadCollections();
|
||||||
const collection = database.findCollectionWithId("SampleContainer");
|
const collection = database.findCollectionWithId("SampleContainer");
|
||||||
collection.isSampleCollection = true;
|
|
||||||
const sampleGenerator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorer);
|
setSpinnerText("Adding sample data set...");
|
||||||
await sampleGenerator.populateContainerAsync(collection, "/CategoryId");
|
const sampleGenerator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorer, true);
|
||||||
// auto-expand sample database + container and show teaching bubble
|
await sampleGenerator.populateContainerAsync(collection);
|
||||||
await database.expandDatabase();
|
await database.expandDatabase();
|
||||||
collection.expandCollection();
|
collection.expandCollection();
|
||||||
useDatabases.getState().updateDatabase(database);
|
useDatabases.getState().updateDatabase(database);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//TODO: show error in UI
|
//TODO: show error in UI
|
||||||
handleError(error, "Query copilot quickstart");
|
handleError(error, "QueryCopilotCreateSampleDB");
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
setIsCreatingDatabase(false);
|
setIsCreatingDatabase(false);
|
||||||
|
setSpinnerText("");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -183,7 +185,7 @@ export const QueryCopilotCarousel: React.FC<QueryCopilotCarouselProps> = ({
|
|||||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Container Id</Text>
|
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Container Id</Text>
|
||||||
<Text style={{ fontSize: 13 }}>SampleContainer</Text>
|
<Text style={{ fontSize: 13 }}>SampleContainer</Text>
|
||||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Partition key</Text>
|
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Partition key</Text>
|
||||||
<Text style={{ fontSize: 13 }}>CategoryId</Text>
|
<Text style={{ fontSize: 13 }}>categoryId</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
case 3:
|
case 3:
|
||||||
@ -267,7 +269,7 @@ export const QueryCopilotCarousel: React.FC<QueryCopilotCarouselProps> = ({
|
|||||||
disabled={isCreatingDatabase}
|
disabled={isCreatingDatabase}
|
||||||
/>
|
/>
|
||||||
{isCreatingDatabase && <Spinner style={{ marginLeft: 8 }} />}
|
{isCreatingDatabase && <Spinner style={{ marginLeft: 8 }} />}
|
||||||
{isCreatingDatabase && <Text style={{ marginLeft: 8, color: "#0078D4" }}>Setting up your database...</Text>}
|
{isCreatingDatabase && <Text style={{ marginLeft: 8, color: "#0078D4" }}>{spinnerText}</Text>}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
105
src/Explorer/QueryCopilot/QueryCopilotFeedbackModal.tsx
Normal file
105
src/Explorer/QueryCopilot/QueryCopilotFeedbackModal.tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import {
|
||||||
|
Checkbox,
|
||||||
|
ChoiceGroup,
|
||||||
|
DefaultButton,
|
||||||
|
IconButton,
|
||||||
|
Link,
|
||||||
|
Modal,
|
||||||
|
PrimaryButton,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextField,
|
||||||
|
} from "@fluentui/react";
|
||||||
|
import { submitFeedback } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||||
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const QueryCopilotFeedbackModal: React.FC = (): JSX.Element => {
|
||||||
|
const {
|
||||||
|
generatedQuery,
|
||||||
|
userPrompt,
|
||||||
|
likeQuery,
|
||||||
|
showFeedbackModal,
|
||||||
|
closeFeedbackModal,
|
||||||
|
setHideFeedbackModalForLikedQueries,
|
||||||
|
} = useQueryCopilot();
|
||||||
|
const [isContactAllowed, setIsContactAllowed] = React.useState<boolean>(true);
|
||||||
|
const [description, setDescription] = React.useState<string>("");
|
||||||
|
const [doNotShowAgainChecked, setDoNotShowAgainChecked] = React.useState<boolean>(false);
|
||||||
|
return (
|
||||||
|
<Modal isOpen={showFeedbackModal}>
|
||||||
|
<Stack style={{ padding: 24 }}>
|
||||||
|
<Stack horizontal horizontalAlign="space-between">
|
||||||
|
<Text style={{ fontSize: 20, fontWeight: 600, marginBottom: 20 }}>Send feedback to Microsoft</Text>
|
||||||
|
<IconButton iconProps={{ iconName: "Cancel" }} onClick={() => closeFeedbackModal()} />
|
||||||
|
</Stack>
|
||||||
|
<Text style={{ fontSize: 14, marginBottom: 14 }}>Your feedback will help improve the experience.</Text>
|
||||||
|
<TextField
|
||||||
|
styles={{ root: { marginBottom: 14 } }}
|
||||||
|
label="Description"
|
||||||
|
required
|
||||||
|
placeholder="Provide more details"
|
||||||
|
value={description}
|
||||||
|
onChange={(_, newValue) => setDescription(newValue)}
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
styles={{ root: { marginBottom: 14 } }}
|
||||||
|
label="Query generated"
|
||||||
|
defaultValue={generatedQuery}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
<ChoiceGroup
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
marginBottom: 14,
|
||||||
|
},
|
||||||
|
flexContainer: {
|
||||||
|
selectors: {
|
||||||
|
".ms-ChoiceField-field::before": { marginTop: 4 },
|
||||||
|
".ms-ChoiceField-field::after": { marginTop: 4 },
|
||||||
|
".ms-ChoiceFieldLabel": { paddingLeft: 6 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
label="May we contact you about your feedback?"
|
||||||
|
options={[
|
||||||
|
{ key: "yes", text: "Yes, you may contact me." },
|
||||||
|
{ key: "no", text: "No, do not contact me." },
|
||||||
|
]}
|
||||||
|
selectedKey={isContactAllowed ? "yes" : "no"}
|
||||||
|
onChange={(_, option) => setIsContactAllowed(option.key === "yes")}
|
||||||
|
></ChoiceGroup>
|
||||||
|
<Text style={{ fontSize: 12, marginBottom: 14 }}>
|
||||||
|
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your
|
||||||
|
organization will be able to view and manage your feedback data.{" "}
|
||||||
|
<Link href="" target="_blank">
|
||||||
|
Privacy statement
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
{likeQuery && (
|
||||||
|
<Checkbox
|
||||||
|
styles={{ label: { paddingLeft: 0 }, root: { marginBottom: 14 } }}
|
||||||
|
label="Don't show me this next time"
|
||||||
|
checked={doNotShowAgainChecked}
|
||||||
|
onChange={(_, checked) => setDoNotShowAgainChecked(checked)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Stack horizontal horizontalAlign="end">
|
||||||
|
<PrimaryButton
|
||||||
|
styles={{ root: { marginRight: 8 } }}
|
||||||
|
onClick={() => {
|
||||||
|
closeFeedbackModal();
|
||||||
|
setHideFeedbackModalForLikedQueries(doNotShowAgainChecked);
|
||||||
|
submitFeedback({ generatedQuery, likeQuery, description, userPrompt });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</PrimaryButton>
|
||||||
|
<DefaultButton onClick={() => closeFeedbackModal()}>Cancel</DefaultButton>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
@ -1,7 +1,23 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import { FeedOptions } from "@azure/cosmos";
|
import { FeedOptions } from "@azure/cosmos";
|
||||||
import { IconButton, Image, Link, Stack, Text, TextField } from "@fluentui/react";
|
import {
|
||||||
import { QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
|
Callout,
|
||||||
|
CommandBarButton,
|
||||||
|
DirectionalHint,
|
||||||
|
IconButton,
|
||||||
|
Image,
|
||||||
|
Link,
|
||||||
|
Separator,
|
||||||
|
Spinner,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextField,
|
||||||
|
} from "@fluentui/react";
|
||||||
|
import {
|
||||||
|
QueryCopilotSampleContainerId,
|
||||||
|
QueryCopilotSampleContainerSchema,
|
||||||
|
QueryCopilotSampleDatabaseId,
|
||||||
|
} from "Common/Constants";
|
||||||
import { getErrorMessage, handleError } from "Common/ErrorHandlingUtils";
|
import { getErrorMessage, handleError } from "Common/ErrorHandlingUtils";
|
||||||
import { shouldEnableCrossPartitionKey } from "Common/HeadersUtility";
|
import { shouldEnableCrossPartitionKey } from "Common/HeadersUtility";
|
||||||
import { MinimalQueryIterator } from "Common/IteratorUtilities";
|
import { MinimalQueryIterator } from "Common/IteratorUtilities";
|
||||||
@ -13,8 +29,10 @@ import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
|||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane";
|
import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane";
|
||||||
|
import { submitFeedback } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||||
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
|
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
|
||||||
import { queryPagesUntilContentPresent } from "Utils/QueryUtils";
|
import { queryPagesUntilContentPresent } from "Utils/QueryUtils";
|
||||||
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import SplitterLayout from "react-splitter-layout";
|
import SplitterLayout from "react-splitter-layout";
|
||||||
@ -27,28 +45,62 @@ interface QueryCopilotTabProps {
|
|||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GenerateSQLQueryResponse {
|
||||||
|
apiVersion: string;
|
||||||
|
sql: string;
|
||||||
|
explanation: string;
|
||||||
|
generateStart: string;
|
||||||
|
generateEnd: string;
|
||||||
|
}
|
||||||
|
|
||||||
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 [userInput, setUserInput] = useState<string>(initialInput || "");
|
const [userInput, setUserInput] = useState<string>(initialInput || "");
|
||||||
|
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>("");
|
||||||
|
const [isGeneratingQuery, setIsGeneratingQuery] = useState<boolean>(false);
|
||||||
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||||
|
const [likeQuery, setLikeQuery] = useState<boolean>();
|
||||||
|
const [showCallout, setShowCallout] = 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 generateQuery = (): string => {
|
const generateSQLQuery = async (): Promise<void> => {
|
||||||
switch (userInput) {
|
try {
|
||||||
case "Write a query to return all records in this table":
|
setIsGeneratingQuery(true);
|
||||||
return "SELECT * FROM c";
|
const payload = {
|
||||||
case "Write a query to return all records in this table created in the last thirty days":
|
containerSchema: QueryCopilotSampleContainerSchema,
|
||||||
return "SELECT * FROM c WHERE c._ts > (DATEDIFF(s, '1970-01-01T00:00:00Z', GETUTCDATE()) - 2592000) * 1000";
|
userPrompt: userInput,
|
||||||
case `Write a query to return all records in this table created in the last thirty days which also have the record owner as "Contoso"`:
|
};
|
||||||
return `SELECT * FROM c WHERE c.owner = "Contoso" AND c._ts > (DATEDIFF(s, '1970-01-01T00:00:00Z', GETUTCDATE()) - 2592000) * 1000`;
|
const response = await fetch("https://copilotorchestrater.azurewebsites.net/generateSQLQuery", {
|
||||||
default:
|
method: "POST",
|
||||||
return "";
|
headers: {
|
||||||
|
"content-type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
|
||||||
|
const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json();
|
||||||
|
if (generateSQLQueryResponse?.sql) {
|
||||||
|
let query = `-- ${userInput}\r\n`;
|
||||||
|
if (generateSQLQueryResponse.explanation) {
|
||||||
|
query += "-- **Explanation of query**\r\n";
|
||||||
|
query += `-- ${generateSQLQueryResponse.explanation}\r\n`;
|
||||||
|
}
|
||||||
|
query += generateSQLQueryResponse.sql;
|
||||||
|
setQuery(query);
|
||||||
|
setGeneratedQuery(generateSQLQueryResponse.sql);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "executeNaturalLanguageQuery");
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
setIsGeneratingQuery(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -110,7 +162,13 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
useCommandBar.getState().setContextButtons(getCommandbarButtons());
|
useCommandBar.getState().setContextButtons(getCommandbarButtons());
|
||||||
}, [query]);
|
}, [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%" }}>
|
||||||
@ -124,12 +182,15 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
|
|||||||
onChange={(_, newValue) => setUserInput(newValue)}
|
onChange={(_, newValue) => setUserInput(newValue)}
|
||||||
style={{ lineHeight: 30 }}
|
style={{ lineHeight: 30 }}
|
||||||
styles={{ root: { width: "90%" } }}
|
styles={{ root: { width: "90%" } }}
|
||||||
|
disabled={isGeneratingQuery}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
iconProps={{ iconName: "Send" }}
|
iconProps={{ iconName: "Send" }}
|
||||||
|
disabled={isGeneratingQuery}
|
||||||
style={{ marginLeft: 8 }}
|
style={{ marginLeft: 8 }}
|
||||||
onClick={() => setQuery(generateQuery())}
|
onClick={() => generateSQLQuery()}
|
||||||
/>
|
/>
|
||||||
|
{isGeneratingQuery && <Spinner style={{ marginLeft: 8 }} />}
|
||||||
</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's accurate and appropriate before using it.{" "}
|
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.{" "}
|
||||||
@ -138,6 +199,57 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
|
|||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
<Stack style={{ backgroundColor: "#FFF8F0", padding: "2px 8px" }} horizontal verticalAlign="center">
|
||||||
|
<Text style={{ fontWeight: 600, fontSize: 12 }}>Provide feedback on the query generated</Text>
|
||||||
|
{showCallout && !hideFeedbackModalForLikedQueries && (
|
||||||
|
<Callout
|
||||||
|
style={{ padding: 8 }}
|
||||||
|
target="#likeBtn"
|
||||||
|
onDismiss={() => {
|
||||||
|
setShowCallout(false);
|
||||||
|
submitFeedback({ generatedQuery, likeQuery, description: "", userPrompt: userInput });
|
||||||
|
}}
|
||||||
|
directionalHint={DirectionalHint.topCenter}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
Thank you. Need to give{" "}
|
||||||
|
<Link
|
||||||
|
onClick={() => {
|
||||||
|
setShowCallout(false);
|
||||||
|
useQueryCopilot.getState().openFeedbackModal(generatedQuery, true, userInput);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
more feedback?
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
</Callout>
|
||||||
|
)}
|
||||||
|
<IconButton
|
||||||
|
id="likeBtn"
|
||||||
|
style={{ marginLeft: 20 }}
|
||||||
|
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }}
|
||||||
|
onClick={() => {
|
||||||
|
setLikeQuery(true);
|
||||||
|
setShowCallout(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
style={{ margin: "0 10px" }}
|
||||||
|
iconProps={{ iconName: likeQuery === false ? "DislikeSolid" : "Dislike" }}
|
||||||
|
onClick={() => {
|
||||||
|
setLikeQuery(false);
|
||||||
|
setShowCallout(false);
|
||||||
|
useQueryCopilot.getState().openFeedbackModal(generatedQuery, false, userInput);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Separator vertical style={{ color: "#EDEBE9" }} />
|
||||||
|
<CommandBarButton iconProps={{ iconName: "Copy" }} style={{ margin: "0 10px", backgroundColor: "#FFF8F0" }}>
|
||||||
|
Copy code
|
||||||
|
</CommandBarButton>
|
||||||
|
<CommandBarButton iconProps={{ iconName: "Delete" }} style={{ backgroundColor: "#FFF8F0" }}>
|
||||||
|
Delete code
|
||||||
|
</CommandBarButton>
|
||||||
|
</Stack>
|
||||||
<Stack className="tabPaneContentContainer">
|
<Stack className="tabPaneContentContainer">
|
||||||
<SplitterLayout vertical={true} primaryIndex={0} primaryMinSize={100} secondaryMinSize={200}>
|
<SplitterLayout vertical={true} primaryIndex={0} primaryMinSize={100} secondaryMinSize={200}>
|
||||||
<EditorReact
|
<EditorReact
|
||||||
|
36
src/Explorer/QueryCopilot/QueryCopilotUtilities.ts
Normal file
36
src/Explorer/QueryCopilot/QueryCopilotUtilities.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { QueryCopilotSampleContainerSchema } from "Common/Constants";
|
||||||
|
import { handleError } from "Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
|
interface FeedbackParams {
|
||||||
|
likeQuery: boolean;
|
||||||
|
generatedQuery: string;
|
||||||
|
userPrompt: string;
|
||||||
|
description?: string;
|
||||||
|
contact?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const submitFeedback = async (params: FeedbackParams): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const { likeQuery, generatedQuery, userPrompt, description, contact } = params;
|
||||||
|
const payload = {
|
||||||
|
containerSchema: QueryCopilotSampleContainerSchema,
|
||||||
|
like: likeQuery ? "like" : "dislike",
|
||||||
|
generatedSql: generatedQuery,
|
||||||
|
userPrompt,
|
||||||
|
description: description || "",
|
||||||
|
contact: contact || "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch("https://copilotorchestrater.azurewebsites.net/feedback", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(response);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "copilotSubmitFeedback");
|
||||||
|
}
|
||||||
|
};
|
@ -41,6 +41,7 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
|
|||||||
verticalAlign="center"
|
verticalAlign="center"
|
||||||
>
|
>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
|
disabled={false}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
@ -57,6 +58,7 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
|
|||||||
value="Write a query to return all records in this table"
|
value="Write a query to return all records in this table"
|
||||||
/>
|
/>
|
||||||
<CustomizedIconButton
|
<CustomizedIconButton
|
||||||
|
disabled={false}
|
||||||
iconProps={
|
iconProps={
|
||||||
Object {
|
Object {
|
||||||
"iconName": "Send",
|
"iconName": "Send",
|
||||||
@ -88,6 +90,91 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
|
|||||||
Read preview terms
|
Read preview terms
|
||||||
</StyledLinkBase>
|
</StyledLinkBase>
|
||||||
</Text>
|
</Text>
|
||||||
|
<Stack
|
||||||
|
horizontal={true}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"backgroundColor": "#FFF8F0",
|
||||||
|
"padding": "2px 8px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verticalAlign="center"
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"fontSize": 12,
|
||||||
|
"fontWeight": 600,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Provide feedback on the query generated
|
||||||
|
</Text>
|
||||||
|
<CustomizedIconButton
|
||||||
|
iconProps={
|
||||||
|
Object {
|
||||||
|
"iconName": "Like",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
id="likeBtn"
|
||||||
|
onClick={[Function]}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"marginLeft": 20,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<CustomizedIconButton
|
||||||
|
iconProps={
|
||||||
|
Object {
|
||||||
|
"iconName": "Dislike",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onClick={[Function]}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"margin": "0 10px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Separator
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"color": "#EDEBE9",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vertical={true}
|
||||||
|
/>
|
||||||
|
<CustomizedCommandBarButton
|
||||||
|
iconProps={
|
||||||
|
Object {
|
||||||
|
"iconName": "Copy",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"backgroundColor": "#FFF8F0",
|
||||||
|
"margin": "0 10px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Copy code
|
||||||
|
</CustomizedCommandBarButton>
|
||||||
|
<CustomizedCommandBarButton
|
||||||
|
iconProps={
|
||||||
|
Object {
|
||||||
|
"iconName": "Delete",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"backgroundColor": "#FFF8F0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Delete code
|
||||||
|
</CustomizedCommandBarButton>
|
||||||
|
</Stack>
|
||||||
<Stack
|
<Stack
|
||||||
className="tabPaneContentContainer"
|
className="tabPaneContentContainer"
|
||||||
>
|
>
|
||||||
|
@ -137,7 +137,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
description={
|
description={
|
||||||
"Copilot is your AI buddy that helps you write Azure Cosmos DB queries like a pro. Try it using our sample data set now!"
|
"Copilot is your AI buddy that helps you write Azure Cosmos DB queries like a pro. Try it using our sample data set now!"
|
||||||
}
|
}
|
||||||
onClick={() => useCarousel.getState().setShowCopilotCarousel(true)}
|
onClick={() => useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot)}
|
||||||
/>
|
/>
|
||||||
<SplashScreenButton
|
<SplashScreenButton
|
||||||
imgSrc={ConnectIcon}
|
imgSrc={ConnectIcon}
|
||||||
|
@ -17,6 +17,7 @@ import "../externals/jquery.typeahead.min.css";
|
|||||||
import "../externals/jquery.typeahead.min.js";
|
import "../externals/jquery.typeahead.min.js";
|
||||||
// Image Dependencies
|
// Image Dependencies
|
||||||
import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel";
|
import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel";
|
||||||
|
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/QueryCopilotFeedbackModal";
|
||||||
import "../images/CosmosDB_rgb_ui_lighttheme.ico";
|
import "../images/CosmosDB_rgb_ui_lighttheme.ico";
|
||||||
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
|
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
|
||||||
import "../images/favicon.ico";
|
import "../images/favicon.ico";
|
||||||
@ -125,6 +126,7 @@ const App: React.FunctionComponent = () => {
|
|||||||
{<SQLQuickstartTutorial />}
|
{<SQLQuickstartTutorial />}
|
||||||
{<MongoQuickstartTutorial />}
|
{<MongoQuickstartTutorial />}
|
||||||
{<QueryCopilotCarousel isOpen={isCopilotCarouselOpen} explorer={explorer} />}
|
{<QueryCopilotCarousel isOpen={isCopilotCarouselOpen} explorer={explorer} />}
|
||||||
|
{<QueryCopilotFeedbackModal />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
25
src/hooks/useQueryCopilot.ts
Normal file
25
src/hooks/useQueryCopilot.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
|
interface QueryCopilotState {
|
||||||
|
generatedQuery: string;
|
||||||
|
likeQuery: boolean;
|
||||||
|
userPrompt: string;
|
||||||
|
showFeedbackModal: boolean;
|
||||||
|
hideFeedbackModalForLikedQueries: boolean;
|
||||||
|
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) => void;
|
||||||
|
closeFeedbackModal: () => void;
|
||||||
|
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useQueryCopilot: UseStore<QueryCopilotState> = create((set) => ({
|
||||||
|
generatedQuery: "",
|
||||||
|
likeQuery: false,
|
||||||
|
userPrompt: "",
|
||||||
|
showFeedbackModal: false,
|
||||||
|
hideFeedbackModalForLikedQueries: false,
|
||||||
|
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) =>
|
||||||
|
set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }),
|
||||||
|
closeFeedbackModal: () => set({ generatedQuery: "", likeQuery: false, userPrompt: "", showFeedbackModal: false }),
|
||||||
|
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) =>
|
||||||
|
set({ hideFeedbackModalForLikedQueries }),
|
||||||
|
}));
|
Loading…
Reference in New Issue
Block a user