[Query Copilot V2] Wire and adjust Output bubble with backend communication (#1599)

* Initial wiring of copilot backend and bubble

* Additional changes in explanation bubbles

* Changes based on checks

* test snapshots updated

---------

Co-authored-by: Predrag Klepic <v-prklepic@microsoft.com>
This commit is contained in:
Predrag Klepic 2023-09-06 19:49:27 +02:00 committed by GitHub
parent 8c2ca4ab8e
commit 76408e2f98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 107 additions and 132 deletions

View File

@ -20,15 +20,13 @@ export const SendQueryRequest = async ({
explorer: Explorer; explorer: Explorer;
}): Promise<void> => { }): Promise<void> => {
if (userPrompt.trim() !== "") { if (userPrompt.trim() !== "") {
useQueryCopilot.getState().setIsGeneratingQuery(true);
useQueryCopilot.getState().setShouldIncludeInMessages(true);
useQueryCopilot.getState().setShowQueryExplanation(false);
useQueryCopilot.getState().setShowExplanationBubble(false);
useTabs.getState().setIsTabExecuting(true);
useTabs.getState().setIsQueryErrorThrown(false);
useQueryCopilot useQueryCopilot
.getState() .getState()
.setChatMessages([...useQueryCopilot.getState().chatMessages, { source: 0, message: userPrompt }]); .setChatMessages([...useQueryCopilot.getState().chatMessages, { source: 0, message: userPrompt }]);
useQueryCopilot.getState().setIsGeneratingQuery(true);
useQueryCopilot.getState().setShouldIncludeInMessages(true);
useTabs.getState().setIsTabExecuting(true);
useTabs.getState().setIsQueryErrorThrown(false);
try { try {
if ( if (
useQueryCopilot.getState().containerStatus.status !== ContainerStatusType.Active && useQueryCopilot.getState().containerStatus.status !== ContainerStatusType.Active &&
@ -62,14 +60,16 @@ export const SendQueryRequest = async ({
const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json(); const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json();
if (response.ok) { if (response.ok) {
if (generateSQLQueryResponse?.sql) { if (generateSQLQueryResponse?.sql) {
let query = `Here is a query which will help you with provided prompt.\r\n **Prompt:** ${userPrompt}`; const bubbleMessage = `Here is a query which will help you with provided prompt.\r\n **Prompt:** "${userPrompt}"`;
query += `\r\n${generateSQLQueryResponse.sql}`;
if (useQueryCopilot.getState().shouldIncludeInMessages) { if (useQueryCopilot.getState().shouldIncludeInMessages) {
useQueryCopilot useQueryCopilot.getState().setChatMessages([
.getState()
.setChatMessages([
...useQueryCopilot.getState().chatMessages, ...useQueryCopilot.getState().chatMessages,
{ source: 1, message: query, explanation: generateSQLQueryResponse.explanation }, {
source: 1,
message: bubbleMessage,
sqlQuery: generateSQLQueryResponse.sql,
explanation: generateSQLQueryResponse.explanation,
},
]); ]);
useQueryCopilot.getState().setShowExplanationBubble(true); useQueryCopilot.getState().setShowExplanationBubble(true);
useQueryCopilot.getState().setGeneratedQuery(generateSQLQueryResponse.sql); useQueryCopilot.getState().setGeneratedQuery(generateSQLQueryResponse.sql);

View File

@ -11,11 +11,13 @@ export interface GenerateSQLQueryResponse {
enum MessageSource { enum MessageSource {
User, User,
AI, AI,
AIExplanation,
} }
export interface CopilotMessage { export interface CopilotMessage {
source: MessageSource; source: MessageSource;
message: string; message: string;
sqlQuery?: string;
explanation?: string; explanation?: string;
} }

View File

@ -19,25 +19,25 @@ describe("Explanation Bubble", () => {
}); });
it("should render explanation bubble with generated comments", () => { it("should render explanation bubble with generated comments", () => {
useQueryCopilot.getState().showQueryExplanation = true;
useQueryCopilot.getState().shouldIncludeInMessages = true; useQueryCopilot.getState().shouldIncludeInMessages = true;
const wrapper = shallow(<ExplanationBubble />); const wrapper = shallow(<ExplanationBubble />);
expect(wrapper.find("Stack")).toHaveLength(1); expect(wrapper.find("Stack")).toHaveLength(1);
expect(wrapper.find("Text")).toHaveLength(0); expect(wrapper.find("Text")).toHaveLength(1);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
it("should render 'Explain this query' link", () => { it("should render 'Explain this query' link", () => {
useQueryCopilot.getState().shouldIncludeInMessages = true;
const mockSetChatMessages = jest.fn(); const mockSetChatMessages = jest.fn();
const mockSetIsGeneratingExplanation = jest.fn(); const mockSetIsGeneratingExplanation = jest.fn();
const mockSetShouldIncludeInMessages = jest.fn(); const mockSetShouldIncludeInMessages = jest.fn();
const mockSetShowQueryExplanation = jest.fn(); const mockSetShowExplanationBubble = jest.fn();
useQueryCopilot.getState().setChatMessages = mockSetChatMessages; useQueryCopilot.getState().setChatMessages = mockSetChatMessages;
useQueryCopilot.getState().setIsGeneratingExplanation = mockSetIsGeneratingExplanation; useQueryCopilot.getState().setIsGeneratingExplanation = mockSetIsGeneratingExplanation;
useQueryCopilot.getState().setShouldIncludeInMessages = mockSetShouldIncludeInMessages; useQueryCopilot.getState().setShouldIncludeInMessages = mockSetShouldIncludeInMessages;
useQueryCopilot.getState().setShowQueryExplanation = mockSetShowQueryExplanation; useQueryCopilot.getState().setShowExplanationBubble = mockSetShowExplanationBubble;
const wrapper = shallow(<ExplanationBubble />); const wrapper = shallow(<ExplanationBubble />);
@ -50,12 +50,12 @@ describe("Explanation Bubble", () => {
]); ]);
expect(mockSetIsGeneratingExplanation).toHaveBeenCalledWith(true); expect(mockSetIsGeneratingExplanation).toHaveBeenCalledWith(true);
expect(mockSetShouldIncludeInMessages).toHaveBeenCalledWith(true); expect(mockSetShouldIncludeInMessages).toHaveBeenCalledWith(true);
expect(mockSetShowQueryExplanation).not.toHaveBeenCalled(); expect(mockSetShowExplanationBubble).toHaveBeenCalledWith(false);
jest.advanceTimersByTime(3000); jest.advanceTimersByTime(3000);
expect(mockSetIsGeneratingExplanation).toHaveBeenCalledWith(false); expect(mockSetIsGeneratingExplanation).toHaveBeenCalledWith(false);
expect(mockSetShowQueryExplanation).toHaveBeenCalledWith(true); expect(mockSetChatMessages).toHaveBeenCalled();
}); });
it("should render nothing when conditions are not met", () => { it("should render nothing when conditions are not met", () => {

View File

@ -6,53 +6,39 @@ export const ExplanationBubble: React.FC = (): JSX.Element => {
const { const {
showExplanationBubble, showExplanationBubble,
isGeneratingQuery, isGeneratingQuery,
showQueryExplanation,
setShowQueryExplanation,
chatMessages, chatMessages,
setChatMessages, setChatMessages,
generatedQueryComments, generatedQueryComments,
isGeneratingExplanation, isGeneratingExplanation,
setIsGeneratingExplanation, setIsGeneratingExplanation,
shouldIncludeInMessages,
setShouldIncludeInMessages, setShouldIncludeInMessages,
setShowExplanationBubble,
} = useQueryCopilot(); } = useQueryCopilot();
const showExplanation = () => { const showExplanation = () => {
setChatMessages([...chatMessages, { source: 0, message: "Explain this query to me" }]); setChatMessages([...chatMessages, { source: 0, message: "Explain this query to me" }]);
setIsGeneratingExplanation(true); setIsGeneratingExplanation(true);
setShouldIncludeInMessages(true); setShouldIncludeInMessages(true);
setShowExplanationBubble(false);
setTimeout(() => { setTimeout(() => {
if (useQueryCopilot.getState().shouldIncludeInMessages) {
setIsGeneratingExplanation(false); setIsGeneratingExplanation(false);
setShowQueryExplanation(true); setChatMessages([...chatMessages, { source: 2, message: generatedQueryComments }]);
}
}, 3000); }, 3000);
}; };
return ( return (
showExplanationBubble && showExplanationBubble &&
!isGeneratingQuery && !isGeneratingQuery &&
!isGeneratingExplanation && !isGeneratingExplanation && (
(showQueryExplanation && shouldIncludeInMessages ? (
<Stack
horizontalAlign="center"
tokens={{ padding: 8, childrenGap: 8 }}
style={{
backgroundColor: "white",
borderRadius: "8px",
margin: "5px 10px",
textAlign: "start",
}}
>
{generatedQueryComments}
</Stack>
) : (
<Stack <Stack
style={{ style={{
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
padding: "5px 5px 5px 40px", padding: "5px 5px 5px 50px",
margin: "5px", margin: "5px",
width: "100%",
}} }}
> >
<Text <Text
@ -69,6 +55,6 @@ export const ExplanationBubble: React.FC = (): JSX.Element => {
Explain this query to me Explain this query to me
</Text> </Text>
</Stack> </Stack>
)) )
); );
}; };

View File

@ -2,22 +2,31 @@
exports[`Explanation Bubble should render explanation bubble with generated comments 1`] = ` exports[`Explanation Bubble should render explanation bubble with generated comments 1`] = `
<Stack <Stack
horizontalAlign="center"
style={ style={
Object { Object {
"backgroundColor": "white", "alignItems": "center",
"borderRadius": "8px", "display": "flex",
"margin": "5px 10px", "margin": "5px",
"textAlign": "start", "padding": "5px 5px 5px 50px",
} }
} }
tokens={ >
<Text
onClick={[Function]}
style={
Object { Object {
"childrenGap": 8, "border": "1.5px solid #B0BEFF",
"padding": 8, "borderRadius": "4px",
"cursor": "pointer",
"marginBottom": "5px",
"padding": "2px",
"width": "100%",
} }
} }
/> >
Explain this query to me
</Text>
</Stack>
`; `;
exports[`Explanation Bubble should render nothing when conditions are not met 1`] = `""`; exports[`Explanation Bubble should render nothing when conditions are not met 1`] = `""`;

View File

@ -10,7 +10,7 @@ describe("Copy button snapshot tests", () => {
it("should render and click copy", async () => { it("should render and click copy", async () => {
const testInput = "test input query"; const testInput = "test input query";
useQueryCopilot.getState().setGeneratedQuery(testInput); useQueryCopilot.getState().setGeneratedQuery(testInput);
const wrapper = shallow(<CopyButton />); const wrapper = shallow(<CopyButton sqlQuery={""} />);
const button = wrapper.find(IconButton).first(); const button = wrapper.find(IconButton).first();
button.simulate("click", {}); button.simulate("click", {});

View File

@ -1,12 +1,11 @@
import { IconButton } from "@fluentui/react"; import { IconButton } from "@fluentui/react";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import React from "react"; import React from "react";
import CopilotCopy from "../../../../../../../../images/CopilotCopy.svg"; import CopilotCopy from "../../../../../../../../images/CopilotCopy.svg";
export const CopyButton: React.FC = (): JSX.Element => { export const CopyButton = ({ sqlQuery }: { sqlQuery: string }): JSX.Element => {
const copyGeneratedCode = (): void => { const copyGeneratedCode = (): void => {
const queryElement = document.createElement("textarea"); const queryElement = document.createElement("textarea");
queryElement.value = useQueryCopilot.getState().generatedQuery; queryElement.value = sqlQuery;
document.body.appendChild(queryElement); document.body.appendChild(queryElement);
queryElement.select(); queryElement.select();
document.execCommand("copy"); document.execCommand("copy");

View File

@ -20,7 +20,7 @@ beforeEach(() => {
describe("Feedback buttons snapshot tests", () => { describe("Feedback buttons snapshot tests", () => {
it("should click like and show callout", () => { it("should click like and show callout", () => {
const wrapper = shallow(<FeedbackButtons />); const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
let likeButton = wrapper.find(IconButton).first(); let likeButton = wrapper.find(IconButton).first();
const dislikeButton = wrapper.find(IconButton).last(); const dislikeButton = wrapper.find(IconButton).last();
@ -35,7 +35,7 @@ describe("Feedback buttons snapshot tests", () => {
}); });
it("should click like and dismiss callout", () => { it("should click like and dismiss callout", () => {
const wrapper = shallow(<FeedbackButtons />); const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
const likeButton = wrapper.find(IconButton).first(); const likeButton = wrapper.find(IconButton).first();
likeButton.simulate("click"); likeButton.simulate("click");
@ -49,7 +49,7 @@ describe("Feedback buttons snapshot tests", () => {
it("should click like and submit feedback", () => { it("should click like and submit feedback", () => {
const spy = jest.spyOn(useQueryCopilot.getState(), "openFeedbackModal"); const spy = jest.spyOn(useQueryCopilot.getState(), "openFeedbackModal");
const wrapper = shallow(<FeedbackButtons />); const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
const likeButton = wrapper.find(IconButton).first(); const likeButton = wrapper.find(IconButton).first();
likeButton.simulate("click"); likeButton.simulate("click");
@ -61,7 +61,7 @@ describe("Feedback buttons snapshot tests", () => {
}); });
it("should hover over like", () => { it("should hover over like", () => {
const wrapper = shallow(<FeedbackButtons />); const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
let likeButton = wrapper.find(IconButton).first(); let likeButton = wrapper.find(IconButton).first();
likeButton.simulate("mouseover"); likeButton.simulate("mouseover");
@ -72,7 +72,7 @@ describe("Feedback buttons snapshot tests", () => {
}); });
it("should hover over rest like and leave", () => { it("should hover over rest like and leave", () => {
const wrapper = shallow(<FeedbackButtons />); const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
let likeButton = wrapper.find(IconButton).first(); let likeButton = wrapper.find(IconButton).first();
likeButton.simulate("mouseover"); likeButton.simulate("mouseover");
@ -84,7 +84,7 @@ describe("Feedback buttons snapshot tests", () => {
}); });
it("should hover over pressed like and leave", () => { it("should hover over pressed like and leave", () => {
const wrapper = shallow(<FeedbackButtons />); const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
let likeButton = wrapper.find(IconButton).first(); let likeButton = wrapper.find(IconButton).first();
likeButton.simulate("click"); likeButton.simulate("click");
@ -97,7 +97,7 @@ describe("Feedback buttons snapshot tests", () => {
}); });
it("should hover over like and click", () => { it("should hover over like and click", () => {
const wrapper = shallow(<FeedbackButtons />); const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
let likeButton = wrapper.find(IconButton).first(); let likeButton = wrapper.find(IconButton).first();
likeButton.simulate("mouseover"); likeButton.simulate("mouseover");
@ -109,7 +109,7 @@ describe("Feedback buttons snapshot tests", () => {
}); });
it("should dobule click on like", () => { it("should dobule click on like", () => {
const wrapper = shallow(<FeedbackButtons />); const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
let likeButton = wrapper.find(IconButton).first(); let likeButton = wrapper.find(IconButton).first();
likeButton.simulate("click"); likeButton.simulate("click");
@ -124,7 +124,7 @@ describe("Feedback buttons snapshot tests", () => {
it("should click dislike and show popup", () => { it("should click dislike and show popup", () => {
const spy = jest.spyOn(useQueryCopilot.getState(), "openFeedbackModal"); const spy = jest.spyOn(useQueryCopilot.getState(), "openFeedbackModal");
const wrapper = shallow(<FeedbackButtons />); const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
const likeButton = wrapper.find(IconButton).first(); const likeButton = wrapper.find(IconButton).first();
let dislikeButton = wrapper.find(IconButton).last(); let dislikeButton = wrapper.find(IconButton).last();
@ -140,7 +140,7 @@ describe("Feedback buttons snapshot tests", () => {
}); });
it("should hover over dislike", () => { it("should hover over dislike", () => {
const wrapper = shallow(<FeedbackButtons />); const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
let dislikeButton = wrapper.find(IconButton).last(); let dislikeButton = wrapper.find(IconButton).last();
dislikeButton.simulate("mouseover"); dislikeButton.simulate("mouseover");
@ -151,7 +151,7 @@ describe("Feedback buttons snapshot tests", () => {
}); });
it("should hover over rest dislike and leave", () => { it("should hover over rest dislike and leave", () => {
const wrapper = shallow(<FeedbackButtons />); const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
let dislikeButton = wrapper.find(IconButton).last(); let dislikeButton = wrapper.find(IconButton).last();
dislikeButton.simulate("mouseover"); dislikeButton.simulate("mouseover");
@ -163,7 +163,7 @@ describe("Feedback buttons snapshot tests", () => {
}); });
it("should hover over pressed dislike and leave", () => { it("should hover over pressed dislike and leave", () => {
const wrapper = shallow(<FeedbackButtons />); const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
let dislikeButton = wrapper.find(IconButton).last(); let dislikeButton = wrapper.find(IconButton).last();
dislikeButton.simulate("click"); dislikeButton.simulate("click");
@ -178,7 +178,7 @@ describe("Feedback buttons snapshot tests", () => {
}); });
it("should hover over dislike and click", () => { it("should hover over dislike and click", () => {
const wrapper = shallow(<FeedbackButtons />); const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
let dislikeButton = wrapper.find(IconButton).last(); let dislikeButton = wrapper.find(IconButton).last();
dislikeButton.simulate("mouseover"); dislikeButton.simulate("mouseover");
@ -190,7 +190,7 @@ describe("Feedback buttons snapshot tests", () => {
}); });
it("should dobule click on dislike", () => { it("should dobule click on dislike", () => {
const wrapper = shallow(<FeedbackButtons />); const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
let dislikeButton = wrapper.find(IconButton).last(); let dislikeButton = wrapper.find(IconButton).last();
dislikeButton.simulate("click"); dislikeButton.simulate("click");

View File

@ -6,8 +6,8 @@ import LikeHover from "../../../../../../../../images/CopilotLikeHover.svg";
import LikePressed from "../../../../../../../../images/CopilotLikePressed.svg"; import LikePressed from "../../../../../../../../images/CopilotLikePressed.svg";
import LikeRest from "../../../../../../../../images/CopilotLikeRest.svg"; import LikeRest from "../../../../../../../../images/CopilotLikeRest.svg";
export const FeedbackButtons: React.FC = (): JSX.Element => { export const FeedbackButtons = ({ sqlQuery }: { sqlQuery: string }): JSX.Element => {
const { generatedQuery, userPrompt } = useQueryCopilot(); const { userPrompt } = useQueryCopilot();
const [likeQuery, setLikeQuery] = useState<boolean>(false); const [likeQuery, setLikeQuery] = useState<boolean>(false);
const [dislikeQuery, setDislikeQuery] = useState<boolean>(false); const [dislikeQuery, setDislikeQuery] = useState<boolean>(false);
@ -35,7 +35,7 @@ export const FeedbackButtons: React.FC = (): JSX.Element => {
<Link <Link
onClick={() => { onClick={() => {
setCalloutVisible(false); setCalloutVisible(false);
useQueryCopilot.getState().openFeedbackModal(generatedQuery, true, userPrompt); useQueryCopilot.getState().openFeedbackModal(sqlQuery, true, userPrompt);
}} }}
> >
more feedback? more feedback?
@ -82,7 +82,7 @@ export const FeedbackButtons: React.FC = (): JSX.Element => {
setLikeQuery(false); setLikeQuery(false);
setDislikeImageLink(LikePressed); setDislikeImageLink(LikePressed);
setLikeImageLink(LikeRest); setLikeImageLink(LikeRest);
useQueryCopilot.getState().openFeedbackModal(generatedQuery, false, userPrompt); useQueryCopilot.getState().openFeedbackModal(sqlQuery, false, userPrompt);
} }
}} }}
onMouseOver={() => setDislikeImageLink(LikeHover)} onMouseOver={() => setDislikeImageLink(LikeHover)}

View File

@ -1,19 +1,10 @@
import { ActionButton } from "@fluentui/react";
import { InsertButton } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/Insert/InsertButton"; import { InsertButton } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/Insert/InsertButton";
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import React from "react"; import React from "react";
describe("Insert button snapshot tests", () => { describe("Insert button snapshot tests", () => {
it("should click and update state", () => { it("should click and update state", () => {
const testQuery = "test query"; const wrapper = shallow(<InsertButton sqlQuery={""} />);
useQueryCopilot.getState().setGeneratedQuery(testQuery);
const wrapper = shallow(<InsertButton />);
const button = wrapper.find(ActionButton).first();
button.simulate("click");
expect(useQueryCopilot.getState().query).toEqual(testQuery);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
}); });

View File

@ -3,12 +3,12 @@ import { useQueryCopilot } from "hooks/useQueryCopilot";
import React from "react"; import React from "react";
import CopilotInsert from "../../../../../../../../images/CopilotInsert.svg"; import CopilotInsert from "../../../../../../../../images/CopilotInsert.svg";
export const InsertButton: React.FC = (): JSX.Element => { export const InsertButton = ({ sqlQuery }: { sqlQuery: string }): JSX.Element => {
return ( return (
<ActionButton <ActionButton
iconProps={{ imageProps: { src: CopilotInsert } }} iconProps={{ imageProps: { src: CopilotInsert } }}
style={{ borderRadius: "4px", borderWidth: "1px", borderColor: "#D1D1D1", height: "24px", paddingBottom: "2px" }} style={{ borderRadius: "4px", borderWidth: "1px", borderColor: "#D1D1D1", height: "24px", paddingBottom: "2px" }}
onClick={() => useQueryCopilot.getState().setQuery(useQueryCopilot.getState().generatedQuery)} onClick={() => useQueryCopilot.getState().setQuery(sqlQuery)}
> >
Insert Insert
</ActionButton> </ActionButton>

View File

@ -4,7 +4,7 @@ import React from "react";
describe("Output Bubble Buttons snapshot tests", () => { describe("Output Bubble Buttons snapshot tests", () => {
it("should render", () => { it("should render", () => {
const wrapper = shallow(<OutputBubbleButtons />); const wrapper = shallow(<OutputBubbleButtons sqlQuery={""} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });

View File

@ -5,17 +5,17 @@ import { InsertButton } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/In
import { MoreButton } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/More/MoreButton"; import { MoreButton } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/More/MoreButton";
import React from "react"; import React from "react";
export const OutputBubbleButtons: React.FC = (): JSX.Element => { export const OutputBubbleButtons = ({ sqlQuery }: { sqlQuery: string }): JSX.Element => {
return ( return (
<Stack horizontal> <Stack horizontal>
<Stack.Item style={{ paddingTop: "5px" }}> <Stack.Item style={{ paddingTop: "5px" }}>
<InsertButton /> <InsertButton sqlQuery={sqlQuery} />
</Stack.Item> </Stack.Item>
<Stack.Item> <Stack.Item>
<CopyButton /> <CopyButton sqlQuery={sqlQuery} />
</Stack.Item> </Stack.Item>
<Stack.Item> <Stack.Item>
<FeedbackButtons /> <FeedbackButtons sqlQuery={sqlQuery} />
</Stack.Item> </Stack.Item>
<Stack.Item> <Stack.Item>
<MoreButton /> <MoreButton />

View File

@ -11,13 +11,19 @@ exports[`Output Bubble Buttons snapshot tests should render 1`] = `
} }
} }
> >
<InsertButton /> <InsertButton
sqlQuery=""
/>
</StackItem> </StackItem>
<StackItem> <StackItem>
<CopyButton /> <CopyButton
sqlQuery=""
/>
</StackItem> </StackItem>
<StackItem> <StackItem>
<FeedbackButtons /> <FeedbackButtons
sqlQuery=""
/>
</StackItem> </StackItem>
<StackItem> <StackItem>
<MoreButton /> <MoreButton />

View File

@ -1,16 +1,17 @@
import { EditorReact } from "Explorer/Controls/Editor/EditorReact"; import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
import { OutputBubble } from "Explorer/QueryCopilot/V2/Bubbles/Output/OutputBubble"; import { OutputBubble } from "Explorer/QueryCopilot/V2/Bubbles/Output/OutputBubble";
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import { withHooks } from "jest-react-hooks-shallow"; import { withHooks } from "jest-react-hooks-shallow";
import React from "react"; import React from "react";
describe("Output Bubble snapshot tests", () => { describe("Output Bubble snapshot tests", () => {
it("should render and update height", () => { it("should render and update height", () => {
withHooks(() => { withHooks(() => {
useQueryCopilot.getState().setGeneratedQuery("test query"); const wrapper = shallow(
useQueryCopilot.getState().setGeneratedQueryComments("test comments"); <OutputBubble
const wrapper = shallow(<OutputBubble />); copilotMessage={{ message: "testMessage", source: 1, explanation: "testExplanation", sqlQuery: "testSQL" }}
/>
);
const editor = wrapper.find(EditorReact).first(); const editor = wrapper.find(EditorReact).first();

View File

@ -1,10 +1,10 @@
import { Stack, Text } from "@fluentui/react"; import { Stack, Text } from "@fluentui/react";
import { EditorReact } from "Explorer/Controls/Editor/EditorReact"; import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
import { CopilotMessage } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
import { OutputBubbleButtons } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/OutputBubbleButtons"; import { OutputBubbleButtons } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/OutputBubbleButtons";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import React, { useState } from "react"; import React, { useState } from "react";
export const OutputBubble: React.FC = (): JSX.Element => { export const OutputBubble = ({ copilotMessage }: { copilotMessage: CopilotMessage }): JSX.Element => {
const [windowHeight, setWindowHeight] = useState<string>(); const [windowHeight, setWindowHeight] = useState<string>();
const calculateQueryWindowHeight = (): string => { const calculateQueryWindowHeight = (): string => {
@ -29,13 +29,11 @@ export const OutputBubble: React.FC = (): JSX.Element => {
}} }}
tokens={{ padding: 8, childrenGap: 8 }} tokens={{ padding: 8, childrenGap: 8 }}
> >
<Stack.Item style={{ alignSelf: "flex-start", paddingLeft: "2px" }}> <Stack.Item style={{ alignSelf: "flex-start", paddingLeft: "2px" }}>{copilotMessage.message}</Stack.Item>
{useQueryCopilot.getState()?.generatedQueryComments}
</Stack.Item>
<Stack.Item style={{ alignSelf: "stretch", flexGrow: 4 }}> <Stack.Item style={{ alignSelf: "stretch", flexGrow: 4 }}>
<EditorReact <EditorReact
language={"sql"} language={"sql"}
content={useQueryCopilot.getState()?.generatedQuery} content={copilotMessage.sqlQuery}
isReadOnly={true} isReadOnly={true}
ariaLabel={"AI Response"} ariaLabel={"AI Response"}
wordWrap="on" wordWrap="on"
@ -48,7 +46,7 @@ export const OutputBubble: React.FC = (): JSX.Element => {
/> />
</Stack.Item> </Stack.Item>
<Stack.Item style={{ alignSelf: "flex-start" }}> <Stack.Item style={{ alignSelf: "flex-start" }}>
<OutputBubbleButtons /> <OutputBubbleButtons sqlQuery={copilotMessage.sqlQuery} />
</Stack.Item> </Stack.Item>
<Stack.Item> <Stack.Item>
<Text style={{ fontWeight: 400, fontSize: "10px", lineHeight: "14px" }}> <Text style={{ fontWeight: 400, fontSize: "10px", lineHeight: "14px" }}>

View File

@ -28,7 +28,7 @@ exports[`Output Bubble snapshot tests should render and update height 1`] = `
} }
} }
> >
test comments testMessage
</StackItem> </StackItem>
<StackItem <StackItem
style={ style={
@ -40,7 +40,7 @@ exports[`Output Bubble snapshot tests should render and update height 1`] = `
> >
<EditorReact <EditorReact
ariaLabel="AI Response" ariaLabel="AI Response"
content="test query" content="testSQL"
isReadOnly={true} isReadOnly={true}
language="sql" language="sql"
lineDecorationsWidth={0} lineDecorationsWidth={0}
@ -68,7 +68,9 @@ exports[`Output Bubble snapshot tests should render and update height 1`] = `
} }
} }
> >
<OutputBubbleButtons /> <OutputBubbleButtons
sqlQuery="testSQL"
/>
</StackItem> </StackItem>
<StackItem> <StackItem>
<Text <Text

View File

@ -43,13 +43,13 @@ export const QueryCopilotSidebar: React.FC<QueryCopilotProps> = ({ explorer }: Q
> >
<WelcomeBubble /> <WelcomeBubble />
{chatMessages.map((message, index) => {chatMessages.map((message, index) =>
message.source === 0 ? ( message.source === 0 || message.source === 2 ? (
<Stack <Stack
key={index} key={index}
horizontalAlign="center" horizontalAlign="center"
tokens={{ padding: 8, childrenGap: 8 }} tokens={{ padding: 8, childrenGap: 8 }}
style={{ style={{
backgroundColor: "#E0E7FF", backgroundColor: message.source === 0 ? "#E0E7FF" : "white",
borderRadius: "8px", borderRadius: "8px",
margin: "5px 10px", margin: "5px 10px",
textAlign: "start", textAlign: "start",
@ -58,23 +58,9 @@ export const QueryCopilotSidebar: React.FC<QueryCopilotProps> = ({ explorer }: Q
{message.message} {message.message}
</Stack> </Stack>
) : ( ) : (
// This part should be wired with OutputBubble <OutputBubble key={index} copilotMessage={message} />
<Stack
key={index}
horizontalAlign="center"
tokens={{ padding: 8, childrenGap: 8 }}
style={{
backgroundColor: "white",
borderRadius: "8px",
margin: "5px 10px",
textAlign: "start",
}}
>
{message.message}
</Stack>
) )
)} )}
<OutputBubble />
<RetrievingBubble /> <RetrievingBubble />
<ExplanationBubble /> <ExplanationBubble />

View File

@ -37,7 +37,6 @@ export interface QueryCopilotState {
chatMessages: CopilotMessage[]; chatMessages: CopilotMessage[];
shouldIncludeInMessages: boolean; shouldIncludeInMessages: boolean;
showExplanationBubble: boolean; showExplanationBubble: boolean;
showQueryExplanation: boolean;
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo; notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
containerStatus: ContainerInfo; containerStatus: ContainerInfo;
isAllocatingContainer: boolean; isAllocatingContainer: boolean;
@ -72,7 +71,6 @@ export interface QueryCopilotState {
setChatMessages: (chatMessages: CopilotMessage[]) => void; setChatMessages: (chatMessages: CopilotMessage[]) => void;
setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => void; setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => void;
setShowExplanationBubble: (showExplanationBubble: boolean) => void; setShowExplanationBubble: (showExplanationBubble: boolean) => void;
setShowQueryExplanation: (showQueryExplanation: boolean) => void;
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void; setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
setContainerStatus: (containerStatus: ContainerInfo) => void; setContainerStatus: (containerStatus: ContainerInfo) => void;
setIsAllocatingContainer: (isAllocatingContainer: boolean) => void; setIsAllocatingContainer: (isAllocatingContainer: boolean) => void;
@ -113,7 +111,6 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
chatMessages: [], chatMessages: [],
shouldIncludeInMessages: true, shouldIncludeInMessages: true,
showExplanationBubble: false, showExplanationBubble: false,
showQueryExplanation: false,
notebookServerInfo: { notebookServerInfo: {
notebookServerEndpoint: undefined, notebookServerEndpoint: undefined,
authToken: undefined, authToken: undefined,
@ -158,7 +155,6 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
setChatMessages: (chatMessages: CopilotMessage[]) => set({ chatMessages }), setChatMessages: (chatMessages: CopilotMessage[]) => set({ chatMessages }),
setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => set({ shouldIncludeInMessages }), setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => set({ shouldIncludeInMessages }),
setShowExplanationBubble: (showExplanationBubble: boolean) => set({ showExplanationBubble }), setShowExplanationBubble: (showExplanationBubble: boolean) => set({ showExplanationBubble }),
setShowQueryExplanation: (showQueryExplanation: boolean) => set({ showQueryExplanation }),
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
set({ notebookServerInfo }), set({ notebookServerInfo }),
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }), setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
@ -206,7 +202,6 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
chatMessages: [], chatMessages: [],
shouldIncludeInMessages: true, shouldIncludeInMessages: true,
showExplanationBubble: false, showExplanationBubble: false,
showQueryExplanation: false,
notebookServerInfo: { notebookServerInfo: {
notebookServerEndpoint: undefined, notebookServerEndpoint: undefined,
authToken: undefined, authToken: undefined,