mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-26 23:46: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: {
|
||||
global: {
|
||||
branches: 25,
|
||||
functions: 25,
|
||||
functions: 24,
|
||||
lines: 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 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
|
||||
*/
|
||||
public static async createSampleGeneratorAsync(container: Explorer): Promise<ContainerSampleGenerator> {
|
||||
public static async createSampleGeneratorAsync(
|
||||
container: Explorer,
|
||||
isCopilot?: boolean
|
||||
): Promise<ContainerSampleGenerator> {
|
||||
const generator = new ContainerSampleGenerator(container);
|
||||
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(
|
||||
/* webpackChunkName: "gremlinSampleJsonData" */ "../../../sampleData/gremlinSampleData.json"
|
||||
);
|
||||
|
@ -48,6 +48,7 @@ export const QueryCopilotCarousel: React.FC<QueryCopilotCarouselProps> = ({
|
||||
}: QueryCopilotCarouselProps): JSX.Element => {
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const [isCreatingDatabase, setIsCreatingDatabase] = useState<boolean>(false);
|
||||
const [spinnerText, setSpinnerText] = useState<string>("");
|
||||
const [selectedPrompt, setSelectedPrompt] = useState<number>(1);
|
||||
|
||||
const getHeaderText = (): string => {
|
||||
@ -84,6 +85,7 @@ export const QueryCopilotCarousel: React.FC<QueryCopilotCarouselProps> = ({
|
||||
|
||||
try {
|
||||
setIsCreatingDatabase(true);
|
||||
setSpinnerText("Setting up your database...");
|
||||
const params: DataModels.CreateCollectionParams = {
|
||||
createNewDatabase: true,
|
||||
collectionId: "SampleContainer",
|
||||
@ -93,7 +95,7 @@ export const QueryCopilotCarousel: React.FC<QueryCopilotCarouselProps> = ({
|
||||
offerThroughput: undefined,
|
||||
indexingPolicy: AllPropertiesIndexed,
|
||||
partitionKey: {
|
||||
paths: ["/CategoryId"],
|
||||
paths: ["/categoryId"],
|
||||
kind: "Hash",
|
||||
version: 2,
|
||||
},
|
||||
@ -101,22 +103,22 @@ export const QueryCopilotCarousel: React.FC<QueryCopilotCarouselProps> = ({
|
||||
await createCollection(params);
|
||||
await explorer.refreshAllDatabases();
|
||||
const database = useDatabases.getState().findDatabaseWithId(QueryCopilotSampleDatabaseId);
|
||||
// populate sample container with sample data
|
||||
await database.loadCollections();
|
||||
const collection = database.findCollectionWithId("SampleContainer");
|
||||
collection.isSampleCollection = true;
|
||||
const sampleGenerator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorer);
|
||||
await sampleGenerator.populateContainerAsync(collection, "/CategoryId");
|
||||
// auto-expand sample database + container and show teaching bubble
|
||||
|
||||
setSpinnerText("Adding sample data set...");
|
||||
const sampleGenerator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorer, true);
|
||||
await sampleGenerator.populateContainerAsync(collection);
|
||||
await database.expandDatabase();
|
||||
collection.expandCollection();
|
||||
useDatabases.getState().updateDatabase(database);
|
||||
} catch (error) {
|
||||
//TODO: show error in UI
|
||||
handleError(error, "Query copilot quickstart");
|
||||
handleError(error, "QueryCopilotCreateSampleDB");
|
||||
throw error;
|
||||
} finally {
|
||||
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 }}>SampleContainer</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>
|
||||
);
|
||||
case 3:
|
||||
@ -267,7 +269,7 @@ export const QueryCopilotCarousel: React.FC<QueryCopilotCarouselProps> = ({
|
||||
disabled={isCreatingDatabase}
|
||||
/>
|
||||
{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>
|
||||
</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 */
|
||||
import { FeedOptions } from "@azure/cosmos";
|
||||
import { IconButton, Image, Link, Stack, Text, TextField } from "@fluentui/react";
|
||||
import { QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
|
||||
import {
|
||||
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 { shouldEnableCrossPartitionKey } from "Common/HeadersUtility";
|
||||
import { MinimalQueryIterator } from "Common/IteratorUtilities";
|
||||
@ -13,8 +29,10 @@ 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 { submitFeedback } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
|
||||
import { queryPagesUntilContentPresent } from "Utils/QueryUtils";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import { useSidePanel } from "hooks/useSidePanel";
|
||||
import React, { useState } from "react";
|
||||
import SplitterLayout from "react-splitter-layout";
|
||||
@ -27,28 +45,62 @@ interface QueryCopilotTabProps {
|
||||
explorer: Explorer;
|
||||
}
|
||||
|
||||
interface GenerateSQLQueryResponse {
|
||||
apiVersion: string;
|
||||
sql: string;
|
||||
explanation: string;
|
||||
generateStart: string;
|
||||
generateEnd: string;
|
||||
}
|
||||
|
||||
export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
|
||||
initialInput,
|
||||
explorer,
|
||||
}: QueryCopilotTabProps): JSX.Element => {
|
||||
const hideFeedbackModalForLikedQueries = useQueryCopilot((state) => state.hideFeedbackModalForLikedQueries);
|
||||
const [userInput, setUserInput] = useState<string>(initialInput || "");
|
||||
const [generatedQuery, setGeneratedQuery] = useState<string>("");
|
||||
const [query, setQuery] = useState<string>("");
|
||||
const [selectedQuery, setSelectedQuery] = useState<string>("");
|
||||
const [isGeneratingQuery, setIsGeneratingQuery] = 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 [queryResults, setQueryResults] = useState<QueryResults>();
|
||||
const [errorMessage, setErrorMessage] = useState<string>("");
|
||||
|
||||
const generateQuery = (): string => {
|
||||
switch (userInput) {
|
||||
case "Write a query to return all records in this table":
|
||||
return "SELECT * FROM c";
|
||||
case "Write a query to return all records in this table created in the last thirty days":
|
||||
return "SELECT * FROM c WHERE c._ts > (DATEDIFF(s, '1970-01-01T00:00:00Z', GETUTCDATE()) - 2592000) * 1000";
|
||||
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`;
|
||||
default:
|
||||
return "";
|
||||
const generateSQLQuery = async (): Promise<void> => {
|
||||
try {
|
||||
setIsGeneratingQuery(true);
|
||||
const payload = {
|
||||
containerSchema: QueryCopilotSampleContainerSchema,
|
||||
userPrompt: userInput,
|
||||
};
|
||||
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 = `-- ${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(() => {
|
||||
useCommandBar.getState().setContextButtons(getCommandbarButtons());
|
||||
}, [query]);
|
||||
}, [query, selectedQuery]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (initialInput) {
|
||||
generateSQLQuery();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<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)}
|
||||
style={{ lineHeight: 30 }}
|
||||
styles={{ root: { width: "90%" } }}
|
||||
disabled={isGeneratingQuery}
|
||||
/>
|
||||
<IconButton
|
||||
iconProps={{ iconName: "Send" }}
|
||||
disabled={isGeneratingQuery}
|
||||
style={{ marginLeft: 8 }}
|
||||
onClick={() => setQuery(generateQuery())}
|
||||
onClick={() => generateSQLQuery()}
|
||||
/>
|
||||
{isGeneratingQuery && <Spinner style={{ marginLeft: 8 }} />}
|
||||
</Stack>
|
||||
<Text style={{ marginTop: 8, marginBottom: 24, fontSize: 12 }}>
|
||||
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>
|
||||
</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">
|
||||
<SplitterLayout vertical={true} primaryIndex={0} primaryMinSize={100} secondaryMinSize={200}>
|
||||
<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"
|
||||
>
|
||||
<StyledTextFieldBase
|
||||
disabled={false}
|
||||
onChange={[Function]}
|
||||
style={
|
||||
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"
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
disabled={false}
|
||||
iconProps={
|
||||
Object {
|
||||
"iconName": "Send",
|
||||
@ -88,6 +90,91 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
|
||||
Read preview terms
|
||||
</StyledLinkBase>
|
||||
</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
|
||||
className="tabPaneContentContainer"
|
||||
>
|
||||
|
@ -137,7 +137,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
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!"
|
||||
}
|
||||
onClick={() => useCarousel.getState().setShowCopilotCarousel(true)}
|
||||
onClick={() => useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot)}
|
||||
/>
|
||||
<SplashScreenButton
|
||||
imgSrc={ConnectIcon}
|
||||
|
@ -17,6 +17,7 @@ import "../externals/jquery.typeahead.min.css";
|
||||
import "../externals/jquery.typeahead.min.js";
|
||||
// Image Dependencies
|
||||
import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel";
|
||||
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/QueryCopilotFeedbackModal";
|
||||
import "../images/CosmosDB_rgb_ui_lighttheme.ico";
|
||||
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
|
||||
import "../images/favicon.ico";
|
||||
@ -125,6 +126,7 @@ const App: React.FunctionComponent = () => {
|
||||
{<SQLQuickstartTutorial />}
|
||||
{<MongoQuickstartTutorial />}
|
||||
{<QueryCopilotCarousel isOpen={isCopilotCarouselOpen} explorer={explorer} />}
|
||||
{<QueryCopilotFeedbackModal />}
|
||||
</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