Эх сурвалжийг харах

feat(frontend): Create push to Github action button in chat interface (#4993)

sp.wack 1 жил өмнө
parent
commit
1acb66c2b3

+ 5 - 0
frontend/__tests__/components/chat/chat-interface.test.tsx

@@ -21,6 +21,11 @@ describe("Empty state", () => {
   }));
 
   beforeAll(() => {
+    vi.mock("@remix-run/react", async (importActual) => ({
+      ...(await importActual<typeof import("@remix-run/react")>()),
+      useRouteLoaderData: vi.fn(() => ({})),
+    }));
+
     vi.mock("#/context/socket", async (importActual) => ({
       ...(await importActual<typeof import("#/context/ws-client-provider")>()),
       useWsClient: useWsClientMock,

+ 46 - 0
frontend/src/components/chat-interface.tsx

@@ -1,6 +1,7 @@
 import { useDispatch, useSelector } from "react-redux";
 import React from "react";
 import posthog from "posthog-js";
+import { useRouteLoaderData } from "@remix-run/react";
 import { convertImageToBase64 } from "#/utils/convert-image-to-base-64";
 import { ChatMessage } from "./chat-message";
 import { FeedbackActions } from "./feedback-actions";
@@ -26,6 +27,9 @@ import {
   WsClientProviderStatus,
 } from "#/context/ws-client-provider";
 import OpenHands from "#/api/open-hands";
+import { clientLoader } from "#/routes/_oh";
+import { downloadWorkspace } from "#/utils/download-workspace";
+import { SuggestionItem } from "./suggestion-item";
 
 const isErrorMessage = (
   message: Message | ErrorMessage,
@@ -38,6 +42,7 @@ export function ChatInterface() {
   const scrollRef = React.useRef<HTMLDivElement>(null);
   const { scrollDomToBottom, onChatBodyScroll, hitBottom } =
     useScrollToBottom(scrollRef);
+  const rootLoaderData = useRouteLoaderData<typeof clientLoader>("routes/_oh");
 
   const { messages } = useSelector((state: RootState) => state.chat);
   const { curAgentState } = useSelector((state: RootState) => state.agent);
@@ -47,6 +52,7 @@ export function ChatInterface() {
   >("positive");
   const [feedbackModalIsOpen, setFeedbackModalIsOpen] = React.useState(false);
   const [messageToSend, setMessageToSend] = React.useState<string | null>(null);
+  const [isDownloading, setIsDownloading] = React.useState(false);
 
   React.useEffect(() => {
     if (status === WsClientProviderStatus.ACTIVE) {
@@ -94,6 +100,17 @@ export function ChatInterface() {
     setFeedbackPolarity(polarity);
   };
 
+  const handleDownloadWorkspace = async () => {
+    setIsDownloading(true);
+    try {
+      await downloadWorkspace();
+    } catch (error) {
+      // TODO: Handle error
+    } finally {
+      setIsDownloading(false);
+    }
+  };
+
   return (
     <div className="h-full flex flex-col justify-between">
       {messages.length === 0 && (
@@ -128,6 +145,7 @@ export function ChatInterface() {
             <div className="w-6 h-6 border-2 border-t-[4px] border-primary-500 rounded-full animate-spin" />
           </div>
         )}
+
         {!isLoadingMessages &&
           messages.map((message, index) =>
             isErrorMessage(message) ? (
@@ -153,6 +171,34 @@ export function ChatInterface() {
               </ChatMessage>
             ),
           )}
+
+        {(curAgentState === AgentState.AWAITING_USER_INPUT ||
+          curAgentState === AgentState.FINISHED) && (
+          <div className="flex flex-col gap-2 mb-2">
+            {rootLoaderData?.ghToken ? (
+              <SuggestionItem
+                suggestion={{
+                  label: "Push to GitHub",
+                  value:
+                    "Please push the changes to GitHub and open a pull request.",
+                }}
+                onClick={(value) => {
+                  handleSendMessage(value, []);
+                }}
+              />
+            ) : (
+              <SuggestionItem
+                suggestion={{
+                  label: !isDownloading
+                    ? "Download .zip"
+                    : "Downloading, please wait...",
+                  value: "Download .zip",
+                }}
+                onClick={handleDownloadWorkspace}
+              />
+            )}
+          </div>
+        )}
       </div>
 
       <div className="flex flex-col gap-[6px] px-4 pb-4">

+ 2 - 2
frontend/src/components/suggestion-item.tsx

@@ -7,12 +7,12 @@ interface SuggestionItemProps {
 
 export function SuggestionItem({ suggestion, onClick }: SuggestionItemProps) {
   return (
-    <li className="border border-neutral-600 rounded-xl hover:bg-neutral-700">
+    <li className="list-none border border-neutral-600 rounded-xl hover:bg-neutral-700">
       <button
         type="button"
         data-testid="suggestion"
         onClick={() => onClick(suggestion.value)}
-        className="text-[16px] leading-6 -tracking-[0.01em] text-center w-full p-4 font-semibold"
+        className="text-[16px] leading-6 -tracking-[0.01em] text-center w-full p-3 font-semibold"
       >
         {suggestion.label}
       </button>