From f53768612b1e2268697b5444e502032ef9f3fb3c Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Tue, 23 May 2023 23:45:12 -0400
Subject: copying from old repo

---
 extension/react-app/src/tabs/chat/index.tsx | 267 ++++++++++++++++++++++++++++
 1 file changed, 267 insertions(+)
 create mode 100644 extension/react-app/src/tabs/chat/index.tsx

(limited to 'extension/react-app/src/tabs/chat/index.tsx')

diff --git a/extension/react-app/src/tabs/chat/index.tsx b/extension/react-app/src/tabs/chat/index.tsx
new file mode 100644
index 00000000..a93ad4f9
--- /dev/null
+++ b/extension/react-app/src/tabs/chat/index.tsx
@@ -0,0 +1,267 @@
+import React, { useCallback, useEffect, useRef, useState } from "react";
+import { useDispatch, useSelector } from "react-redux";
+import { selectChatMessages } from "../../redux/selectors/chatSelectors";
+import MessageDiv from "./MessageDiv";
+import styled from "styled-components";
+import { addMessage, setIsStreaming } from "../../redux/slices/chatSlice";
+import { AnyAction, Dispatch } from "@reduxjs/toolkit";
+import { closeStream, streamUpdate } from "../../redux/slices/chatSlice";
+import { ChatMessage, RootStore } from "../../redux/store";
+import { postVscMessage, vscRequest } from "../../vscode";
+import { defaultBorderRadius, Loader } from "../../components";
+import { selectHighlightedCode } from "../../redux/selectors/miscSelectors";
+import { readRangeInVirtualFileSystem } from "../../util";
+import { selectDebugContext } from "../../redux/selectors/debugContextSelectors";
+
+let textEntryBarHeight = "30px";
+
+const ChatContainer = styled.div`
+  display: grid;
+  grid-template-rows: 1fr auto;
+  height: 100%;
+`;
+
+const BottomDiv = styled.div`
+  display: grid;
+  grid-template-rows: auto auto;
+`;
+
+const BottomButton = styled.button(
+  (props: { active: boolean }) => `
+  font-size: 10px;
+  border: none;
+  color: white;
+  margin-right: 4px;
+  cursor: pointer;
+  background-color: ${props.active ? "black" : "gray"};
+  border-radius: ${defaultBorderRadius};
+  padding: 8px;
+`
+);
+
+const TextEntryBar = styled.input`
+  height: ${textEntryBarHeight};
+  border-bottom-left-radius: ${defaultBorderRadius};
+  border-bottom-right-radius: ${defaultBorderRadius};
+  padding: 8px;
+  border: 1px solid white;
+  background-color: black;
+  color: white;
+  outline: none;
+`;
+
+function ChatTab() {
+  const dispatch = useDispatch();
+  const chatMessages = useSelector(selectChatMessages);
+  const isStreaming = useSelector((state: RootStore) => state.chat.isStreaming);
+  const baseUrl = useSelector((state: RootStore) => state.config.apiUrl);
+  const debugContext = useSelector(selectDebugContext);
+
+  const [includeHighlightedCode, setIncludeHighlightedCode] = useState(true);
+  const [writeToEditor, setWriteToEditor] = useState(false);
+  const [waitingForResponse, setWaitingForResponse] = useState(false);
+
+  const highlightedCode = useSelector(selectHighlightedCode);
+
+  const streamToStateThunk = useCallback(
+    (dispatch: Dispatch<AnyAction>, getResponse: () => Promise<Response>) => {
+      let streamToCursor = writeToEditor;
+      getResponse().then((resp) => {
+        setWaitingForResponse(false);
+        if (resp.body) {
+          resp.body.pipeTo(
+            new WritableStream({
+              write(chunk) {
+                let update = new TextDecoder("utf-8").decode(chunk);
+                dispatch(streamUpdate(update));
+                if (streamToCursor) {
+                  postVscMessage("streamUpdate", { update });
+                }
+              },
+              close() {
+                dispatch(closeStream());
+                if (streamToCursor) {
+                  postVscMessage("closeStream", null);
+                }
+              },
+            })
+          );
+        }
+      });
+    },
+    [writeToEditor]
+  );
+
+  const compileHiddenChatMessages = useCallback(async () => {
+    let messages: ChatMessage[] = [];
+    if (
+      includeHighlightedCode &&
+      highlightedCode?.filepath !== undefined &&
+      highlightedCode?.range !== undefined &&
+      debugContext.filesystem[highlightedCode.filepath] !== undefined
+    ) {
+      let fileContents = readRangeInVirtualFileSystem(
+        highlightedCode,
+        debugContext.filesystem
+      );
+      if (fileContents) {
+        messages.push({
+          role: "user",
+          content: fileContents,
+        });
+      }
+    } else {
+      // Similarity search over workspace
+      let data = await vscRequest("queryEmbeddings", {
+        query: chatMessages[chatMessages.length - 1].content,
+      });
+      let codeContextMessages = data.results.map(
+        (result: { id: string; document: string }) => {
+          let msg: ChatMessage = {
+            role: "user",
+            content: `File: ${result.id} \n ${result.document}`,
+          };
+          return msg;
+        }
+      );
+      codeContextMessages.push({
+        role: "user",
+        content:
+          "Use the above code to help you answer the question below. Answer in asterisk bullet points, and give the full path whenever you reference files.",
+      });
+      messages.push(...codeContextMessages);
+    }
+
+    let systemMsgContent = writeToEditor
+      ? "Respond only with the exact code requested, no additional text."
+      : "Use the above code to help you answer the question below. Respond in markdown if using bullets or other special formatting, being sure to specify language for code blocks.";
+
+    messages.push({
+      role: "system",
+      content: systemMsgContent,
+    });
+    return messages;
+  }, [highlightedCode, chatMessages, includeHighlightedCode, writeToEditor]);
+
+  useEffect(() => {
+    if (
+      chatMessages.length > 0 &&
+      chatMessages[chatMessages.length - 1].role === "user" &&
+      !isStreaming
+    ) {
+      dispatch(setIsStreaming(true));
+      streamToStateThunk(dispatch, async () => {
+        if (chatMessages.length === 0) {
+          return new Promise((resolve, _) => resolve(new Response()));
+        }
+        let hiddenChatMessages = await compileHiddenChatMessages();
+        let augmentedMessages = [
+          ...chatMessages.slice(0, -1),
+          ...hiddenChatMessages,
+          chatMessages[chatMessages.length - 1],
+        ];
+        console.log(augmentedMessages);
+        // The autogenerated client can't handle streams, so have to go raw
+        return fetch(`${baseUrl}/chat/complete`, {
+          method: "POST",
+          headers: {
+            "Content-Type": "application/json",
+          },
+          body: JSON.stringify({
+            messages: augmentedMessages,
+          }),
+        });
+      });
+    }
+  }, [chatMessages, dispatch, isStreaming, highlightedCode]);
+
+  const chatMessagesDiv = useRef<HTMLDivElement>(null);
+  useEffect(() => {
+    // Scroll to bottom
+    let interval = setInterval(() => {
+      if (chatMessagesDiv.current && !waitingForResponse) {
+        chatMessagesDiv.current.scrollTop += Math.max(
+          4,
+          0.05 * chatMessagesDiv.current.scrollHeight -
+            chatMessagesDiv.current.clientHeight -
+            chatMessagesDiv.current.scrollTop
+        );
+        if (
+          chatMessagesDiv.current.scrollTop >=
+          chatMessagesDiv.current.scrollHeight -
+            chatMessagesDiv.current.clientHeight
+        ) {
+          clearInterval(interval);
+        }
+      }
+    }, 10);
+  }, [chatMessages, chatMessagesDiv, waitingForResponse]);
+
+  return (
+    <ChatContainer>
+      <div className="mx-5 overflow-y-scroll" ref={chatMessagesDiv}>
+        <h1>Chat</h1>
+        <hr></hr>
+        <div>
+          {chatMessages.length > 0 ? (
+            chatMessages.map((message, idx) => {
+              return <MessageDiv key={idx} {...message}></MessageDiv>;
+            })
+          ) : (
+            <p className="text-gray-400 m-auto text-center">
+              You can ask questions about your codebase or ask for code written
+              directly in the editor.
+            </p>
+          )}
+          {waitingForResponse && <Loader></Loader>}
+        </div>
+      </div>
+
+      <BottomDiv>
+        <div className="h-12 bg-secondary-">
+          <div className="flex items-center p-2">
+            {/* <p className="mr-auto text-xs">
+              Highlighted code is automatically included in your chat message.
+            </p> */}
+            <BottomButton
+              className="ml-auto"
+              active={writeToEditor}
+              onClick={() => {
+                setWriteToEditor(!writeToEditor);
+              }}
+            >
+              {writeToEditor ? "Writing to editor" : "Write to editor"}
+            </BottomButton>
+
+            <BottomButton
+              active={includeHighlightedCode}
+              onClick={() => {
+                setIncludeHighlightedCode(!includeHighlightedCode);
+              }}
+            >
+              {includeHighlightedCode
+                ? "Including highlighted code"
+                : "Automatically finding relevant code"}
+            </BottomButton>
+          </div>
+        </div>
+        <TextEntryBar
+          type="text"
+          placeholder="Enter your message here"
+          onKeyDown={(e) => {
+            if (e.key === "Enter" && e.currentTarget.value !== "") {
+              console.log("Sending message", e.currentTarget.value);
+              dispatch(
+                addMessage({ content: e.currentTarget.value, role: "user" })
+              );
+              (e.target as any).value = "";
+              setWaitingForResponse(true);
+            }
+          }}
+        ></TextEntryBar>
+      </BottomDiv>
+    </ChatContainer>
+  );
+}
+
+export default ChatTab;
-- 
cgit v1.2.3-70-g09d2

