Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding and removing snippets #4041

Merged
merged 15 commits into from
Jun 18, 2024
7 changes: 7 additions & 0 deletions sweep_chat/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,10 @@
color: #ff3333;
}

.vertical-text {
display: inline-block;
transform: rotate(90deg);
transform-origin: left top 0;
white-space: nowrap;
}

208 changes: 84 additions & 124 deletions sweep_chat/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { Session } from "next-auth";
import { PostHogProvider, usePostHog } from "posthog-js/react";
import Survey from "./Survey";
import * as jsonpatch from 'fast-json-patch';
import { ReadableStreamDefaultReadResult } from "stream/web";
import { Textarea } from "./ui/textarea";
import { Slider } from "./ui/slider";
import { Dialog, DialogContent, DialogTrigger } from "./ui/dialog";
Expand All @@ -36,6 +35,7 @@ import { Octokit } from "octokit";
import { renderPRDiffs, getJSONPrefix, getFunctionCallHeaderString, getDiff } from "@/lib/str_utils";
import { CODE_CHANGE_PATTERN, MarkdownRenderer } from "./shared/MarkdownRenderer";
import { SnippetBadge } from "./shared/SnippetBadge";
import { ContextSideBar } from "./shared/ContextSideBar";
import { posthog } from "@/lib/posthog";

import CodeMirrorMerge from 'react-codemirror-merge';
Expand All @@ -44,6 +44,7 @@ import { dracula } from '@uiw/codemirror-theme-dracula';
import { EditorView } from 'codemirror';
import { EditorState } from '@codemirror/state';
import { debounce } from "lodash"
import { streamMessages } from "@/lib/streamingUtils";

const Original = CodeMirrorMerge.Original
const Modified = CodeMirrorMerge.Modified
Expand Down Expand Up @@ -279,79 +280,70 @@ const MessageDisplay = ({
return (
<>
<div className={`flex justify-start`}>
{(!message.annotations?.pulls || message.annotations!.pulls?.length == 0) && (
<div
className={`transition-color text-sm p-3 rounded-xl mb-4 inline-block max-w-[80%] text-left w-[80%]
${message.role === "assistant" ? "py-1" : ""} ${className || roleToColor[message.role]}`}
>
{message.role === "function" ? (
<Accordion
type="single"
collapsible className="w-full"
defaultValue={((message.content && message.function_call?.function_name === "search_codebase") || (message.function_call?.snippets?.length !== undefined && message.function_call?.snippets?.length > 0)) ? "function" : undefined}
>
<AccordionItem value="function" className="border-none">
<AccordionTrigger className="border-none py-0 text-left">
<div className="text-xs text-gray-400 flex align-center">
{!message.function_call!.is_complete ? (
<PulsingLoader size={0.5} />
) : (
<FaCheck
className="inline-block mr-2"
style={{ marginTop: 2 }}
/>
)}
<span>{getFunctionCallHeaderString(message.function_call)}</span>
</div>
</AccordionTrigger>
<AccordionContent className={`pb-0 ${message.content && message.function_call?.function_name === "search_codebase" && !message.function_call?.is_complete ? "pt-6" : "pt-0"}`}>
{message.function_call?.function_name === "search_codebase" && message.content && !message.function_call.is_complete && (
<span className="p-4 pl-2">
{message.content}
</span>
)}
{message.function_call!.snippets ? (
<div className="pb-0 pt-4">
{message.function_call!.snippets.map((snippet, index) => (
<SnippetBadge
key={index}
snippet={snippet}
repoName={repoName}
branch={branch}
/>
))}
</div>
) : (message.function_call!.function_name === "self_critique" || message.function_call!.function_name === "analysis" ? (
<MarkdownRenderer content={message.content} className="reactMarkdown mt-4 mb-0 py-2" />
{(!message.annotations?.pulls || message.annotations!.pulls?.length == 0) && (
<div
className={`transition-color text-sm p-3 rounded-xl mb-4 inline-block max-w-[80%] text-left w-[80%]
${message.role === "assistant" ? "py-1" : ""} ${className || roleToColor[message.role]}`}
>
{message.role === "function" ? (
<Accordion
type="single"
collapsible
className="w-full"
// include defaultValue if there we want a message to be default open
// defaultValue={((message.content && message.function_call?.function_name === "search_codebase") || (message.function_call?.snippets?.length !== undefined && message.function_call?.snippets?.length > 0)) ? "function" : undefined}
>
<AccordionItem value="function" className="border-none">
<AccordionTrigger className="border-none py-0 text-left">
<div className="text-xs text-gray-400 flex align-center">
{!message.function_call!.is_complete ? (
<PulsingLoader size={0.5} />
) : (
<SyntaxHighlighter
language="xml"
style={codeStyle}
customStyle={{
backgroundColor: 'transparent',
whiteSpace: 'pre-wrap',
maxHeight: '300px',
}}
className="rounded-xl p-4"
>
{message.content}
</SyntaxHighlighter>
)
<FaCheck
className="inline-block mr-2"
style={{ marginTop: 2 }}
/>
)}
<FeedbackBlock message={message} index={index} />
</AccordionContent>
</AccordionItem>
</Accordion>
) : message.role === "assistant" ? (
<>
<MarkdownRenderer content={message.content} className="reactMarkdown mb-0 py-2" />
<FeedbackBlock message={message} index={index} />
</>
) : (
<UserMessageDisplay message={message} onEdit={onEdit} />
)}
</div>
)}
<span>{getFunctionCallHeaderString(message.function_call)}</span>
</div>
</AccordionTrigger>
<AccordionContent className={`pb-0 ${message.content && message.function_call?.function_name === "search_codebase" && !message.function_call?.is_complete ? "pt-6" : "pt-0"}`}>
{message.function_call?.function_name === "search_codebase" && message.content && !message.function_call.is_complete && (
<span className="p-4 pl-2">
{message.content}
</span>
)}
{message.function_call!.function_name === "self_critique" || message.function_call!.function_name === "analysis" ? (
<MarkdownRenderer content={message.content} className="reactMarkdown mt-4 mb-0 py-2" />
) : (
<SyntaxHighlighter
language="xml"
style={codeStyle}
customStyle={{
backgroundColor: 'transparent',
whiteSpace: 'pre-wrap',
maxHeight: '300px',
}}
className="rounded-xl p-4"
>
{message.content}
</SyntaxHighlighter>
)
}
<FeedbackBlock message={message} index={index} />
</AccordionContent>
</AccordionItem>
</Accordion>
) : message.role === "assistant" ? (
<>
<MarkdownRenderer content={message.content} className="reactMarkdown mb-0 py-2" />
<FeedbackBlock message={message} index={index} />
</>
) : (
<UserMessageDisplay message={message} onEdit={onEdit} />
)}
</div>
)}
</div>
{showApplySuggestedChangeButton && matches.length > 0 && (
<div className="flex justify-start w-[80%]">
Expand All @@ -373,55 +365,6 @@ const MessageDisplay = ({
);
};

async function* streamMessages(
reader: ReadableStreamDefaultReader<Uint8Array>,
isStream?: React.MutableRefObject<boolean>,
timeout: number = 90000
): AsyncGenerator<any, void, unknown> {
let done = false;
let buffer = "";
let timeoutId: ReturnType<typeof setTimeout> | null = null;

while (!done && (isStream ? isStream.current : true)) {
try {
const { value, done: streamDone } = await Promise.race([
reader.read(),
new Promise<ReadableStreamDefaultReadResult<Uint8Array>>((_, reject) => {
if (timeoutId) {
clearTimeout(timeoutId)
}
timeoutId = setTimeout(() => reject(new Error("Stream timeout after " + timeout / 1000 + " seconds, this is likely caused by the LLM freezing. You can try again by editing your last message. Further, decreasing the number of snippets to retrieve in the settings will help mitigate this issue.")), timeout)
})
]);

if (streamDone) {
done = true;
continue;
}

if (value) {
const decodedValue = new TextDecoder().decode(value);
buffer += decodedValue;

const [parsedObjects, currentIndex] = getJSONPrefix(buffer)
for (let parsedObject of parsedObjects) {
yield parsedObject
}
buffer = buffer.slice(currentIndex)
}
} catch (error) {
console.error("Error during streaming:", error);
throw error;
} finally {
if (timeoutId) {
clearTimeout(timeoutId)
}
}
}
if (buffer) {
console.warn("Buffer:", buffer)
}
}

const parsePullRequests = async (repoName: string, message: string, octokit: Octokit): Promise<PullRequest[]> => {
const [orgName, repo] = repoName.split("/")
Expand Down Expand Up @@ -509,6 +452,7 @@ function App({
const [pullRequestBody, setPullRequestBody] = useState<string | null>(null)
const [isCreatingPullRequest, setIsCreatingPullRequest] = useState<boolean>(false)
const [pullRequest, setPullRequest] = useState<PullRequest | null>(null)
const [pullRequests, setPullRequests] = useState<PullRequest[]>([])
const [baseBranch, setBaseBranch] = useState<string>(branch)
const [featureBranch, setFeatureBranch] = useState<string | null>(null)

Expand Down Expand Up @@ -639,12 +583,17 @@ function App({
<Original
value={suggestion.originalCode}
readOnly={true}
extensions={[EditorView.editable.of(false), EditorState.readOnly.of(true), languageMapping[suggestion.filePath.split(".")[suggestion.filePath.split(".").length - 1]]]}
extensions={[
EditorView.editable.of(false),
languageMapping[suggestion.filePath.split(".")[suggestion.filePath.split(".").length - 1]]
]}
/>
<Modified
value={suggestion.newCode}
readOnly={suggestion.state != "done"}
extensions={[EditorState.readOnly.of(false), languageMapping[suggestion.filePath.split(".")[suggestion.filePath.split(".").length - 1]]]}
extensions={[
languageMapping[suggestion.filePath.split(".")[suggestion.filePath.split(".").length - 1]]
]}
onChange={debounce((value: string) => {
setSuggestedChanges((suggestedChanges) => suggestedChanges.map((suggestion, i) => i == index ? { ...suggestion, newCode: value } : suggestion))
save(repoName, messages, snippets, suggestedChanges, pullRequest)
Expand Down Expand Up @@ -725,7 +674,6 @@ function App({
) => {
setIsLoading(true);
isStream.current = true;

var currentSnippets = snippets;
if (currentSnippets.length == 0) {
try {
Expand Down Expand Up @@ -903,6 +851,7 @@ function App({
}

return (
<>
<main className="flex h-screen flex-col items-center justify-between p-12">
<Toaster />
{showSurvey && process.env.NEXT_PUBLIC_SURVEY_ID && (
Expand Down Expand Up @@ -1065,6 +1014,15 @@ function App({
</DropdownMenuContent>
</DropdownMenu>
</div>
{snippets.length && repoName ?
<ContextSideBar
snippets={snippets}
setSnippets={setSnippets}
repoName={repoName}
branch={branch}
pulls={pullRequests}
k={k}
/> : <></>}
<div
ref={messagesContainerRef}
className="w-full border flex-grow mb-4 p-4 max-h-[90%] overflow-y-auto rounded-xl"
Expand All @@ -1084,6 +1042,7 @@ function App({
setOpenSuggestionDialog(false);

const pulls = await parsePullRequests(repoName, content, octokit!)
setPullRequests(pulls)

const newMessages: Message[] = [
...messages.slice(0, index),
Expand Down Expand Up @@ -1421,6 +1380,7 @@ function App({
</div>
)}
</main>
</>
);
}

Expand Down
81 changes: 81 additions & 0 deletions sweep_chat/components/shared/ContextSideBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"
import { SnippetBadge } from "../shared/SnippetBadge";
import { PullRequest, Snippet } from "@/lib/types";
import { Dispatch, SetStateAction, useState } from "react";
import { ScrollArea } from "../ui/scroll-area";
import { SnippetSearch } from "./SnippetSearch";

const ContextSideBar = ({
snippets,
setSnippets,
repoName,
branch,
pulls,
k,
}: {
snippets: Snippet[];
setSnippets: Dispatch<SetStateAction<Snippet[]>>;
repoName: string;
branch: string;
pulls: PullRequest[];
k: number;
}) => {
const side = "left"
const [isOpen, setIsOpen] = useState<boolean>(false)
return (
<>
<div className="grid grid-cols-4 gap-2">
<Sheet key={side}>
<SheetTrigger asChild>
<Button variant="outline" className="fixed left-10 top-1/2 bg-gray-800 text-white vertical-text">Context</Button>
</SheetTrigger>
<SheetContent side={side}>
<SheetHeader className="mb-2">
<SheetTitle>Current Context</SheetTitle>
<span>
<SheetDescription className="w-3/4 inline-block align-middle">
List of current snippets in context. Run a custom search query to find new snippets.
</SheetDescription>
<SnippetSearch
snippets={snippets}
setSnippets={setSnippets}
repoName={repoName}
branch={branch}
pulls={pulls}
k={k}
/>
</span>
</SheetHeader>
<ScrollArea className="h-3/4 w-full rounded-md border">
{snippets.map((snippet, index) => (
<SnippetBadge
key={index}
snippet={snippet}
repoName={repoName}
branch={branch}
snippets={snippets}
setSnippets={setSnippets}
options={["remove"]}
/>
))}
</ScrollArea>
</SheetContent>
</Sheet>
</div>
</>
)
}

export { ContextSideBar }
Loading
Loading