Skip to content

Commit

Permalink
Introduce mutations
Browse files Browse the repository at this point in the history
  • Loading branch information
amanape committed Nov 20, 2024
1 parent 26e7a87 commit e5502d3
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 94 deletions.
33 changes: 17 additions & 16 deletions frontend/src/components/event-handler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
import store, { RootState } from "#/store";
import { createChatMessage } from "#/services/chatService";
import { isGitHubErrorReponse } from "#/api/github";
import OpenHands from "#/api/open-hands";
import { base64ToBlob } from "#/utils/base64-to-blob";
import { setCurrentAgentState } from "#/state/agentSlice";
import AgentState from "#/types/AgentState";
Expand All @@ -32,6 +31,7 @@ import { generateAgentStateChangeEvent } from "#/services/agentStateService";
import { useGitHubUser } from "#/hooks/query/use-github-user";
import { getGitHubToken } from "#/services/auth";
import { clearSession } from "#/utils/clear-session";
import { useUploadFiles } from "#/hooks/mutation/use-upload-files";

interface ServerError {
error: boolean | string;
Expand Down Expand Up @@ -59,6 +59,7 @@ export function EventHandler({ children }: React.PropsWithChildren) {
};

const { data: user } = useGitHubUser(ghToken);
const { mutate: uploadFiles } = useUploadFiles();

const sendInitialQuery = (query: string, base64Files: string[]) => {
const timestamp = new Date().toISOString();
Expand Down Expand Up @@ -166,21 +167,21 @@ export function EventHandler({ children }: React.PropsWithChildren) {
}, [userId, ghToken, runtimeActive]);

React.useEffect(() => {
(async () => {
if (runtimeActive && importedProjectZip) {
// upload files action
try {
const blob = base64ToBlob(importedProjectZip);
const file = new File([blob], "imported-project.zip", {
type: blob.type,
});
await OpenHands.uploadFiles([file]);
dispatch(setImportedProjectZip(null));
} catch (error) {
toast.error("Failed to upload project files.");
}
}
})();
if (runtimeActive && importedProjectZip) {
const blob = base64ToBlob(importedProjectZip);
const file = new File([blob], "imported-project.zip", {
type: blob.type,
});
uploadFiles(
{ files: [file] },
{
onError: () => {
toast.error("Failed to upload project files.");
},
},
);
dispatch(setImportedProjectZip(null));
}
}, [runtimeActive, importedProjectZip]);

React.useEffect(() => {
Expand Down
27 changes: 16 additions & 11 deletions frontend/src/components/feedback-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";
import hotToast from "react-hot-toast";
import ModalButton from "./buttons/ModalButton";
import { Feedback } from "#/api/open-hands.types";
import OpenHands from "#/api/open-hands";
import { useSubmitFeedback } from "#/hooks/mutation/use-submit-feedback";

const FEEDBACK_VERSION = "1.0";
const VIEWER_PAGE = "https://www.all-hands.dev/share";
Expand All @@ -13,8 +13,6 @@ interface FeedbackFormProps {
}

export function FeedbackForm({ onClose, polarity }: FeedbackFormProps) {
const [isSubmitting, setIsSubmitting] = React.useState(false);

const copiedToClipboardToast = () => {
hotToast("Password copied to clipboard", {
icon: "📋",
Expand Down Expand Up @@ -53,10 +51,11 @@ export function FeedbackForm({ onClose, polarity }: FeedbackFormProps) {
);
};

const { mutate: submitFeedback, isPending } = useSubmitFeedback();

const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event?.preventDefault();
const formData = new FormData(event.currentTarget);
setIsSubmitting(true);

const email = formData.get("email")?.toString() || "";
const permissions = (formData.get("permissions")?.toString() ||
Expand All @@ -71,11 +70,17 @@ export function FeedbackForm({ onClose, polarity }: FeedbackFormProps) {
token: "",
};

const response = await OpenHands.submitFeedback(feedback);
const { message, feedback_id, password } = response.body; // eslint-disable-line
const link = `${VIEWER_PAGE}?share_id=${feedback_id}`;
shareFeedbackToast(message, link, password);
setIsSubmitting(false);
submitFeedback(
{ feedback },
{
onSuccess: (data) => {
const { message, feedback_id, password } = data.body; // eslint-disable-line
const link = `${VIEWER_PAGE}?share_id=${feedback_id}`;
shareFeedbackToast(message, link, password);
onClose();
},
},
);
};

return (
Expand Down Expand Up @@ -109,13 +114,13 @@ export function FeedbackForm({ onClose, polarity }: FeedbackFormProps) {

<div className="flex gap-2">
<ModalButton
disabled={isSubmitting}
disabled={isPending}
type="submit"
text="Submit"
className="bg-[#4465DB] grow"
/>
<ModalButton
disabled={isSubmitting}
disabled={isPending}
text="Cancel"
onClick={onClose}
className="bg-[#737373] grow"
Expand Down
95 changes: 46 additions & 49 deletions frontend/src/components/file-explorer/FileExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import toast from "#/utils/toast";
import { RootState } from "#/store";
import { I18nKey } from "#/i18n/declaration";
import OpenHands from "#/api/open-hands";
import { isOpenHandsErrorResponse } from "#/api/open-hands.utils";
import VSCodeIcon from "#/assets/vscode-alt.svg?react";
import { useGetFiles } from "#/hooks/query/use-get-files";
import { getToken } from "#/services/auth";
import { FileUploadSuccessResponse } from "#/api/open-hands.types";
import { useUploadFiles } from "#/hooks/mutation/use-upload-files";

interface ExplorerActionsProps {
onRefresh: () => void;
Expand Down Expand Up @@ -108,65 +110,60 @@ function FileExplorer({ error, isOpen, onToggle }: FileExplorerProps) {
};

const { data: paths, refetch } = useGetFiles({
token: localStorage.getItem("token"),
token: getToken(),
});

const refreshWorkspace = () => {
if (
curAgentState === AgentState.LOADING ||
curAgentState === AgentState.STOPPED
) {
return;
}
refetch();
};

const uploadFileData = async (files: FileList) => {
try {
const result = await OpenHands.uploadFiles(Array.from(files));
const handleUploadSuccess = (data: FileUploadSuccessResponse) => {
const uploadedCount = data.uploaded_files.length;
const skippedCount = data.skipped_files.length;

if (isOpenHandsErrorResponse(result)) {
// Handle error response
toast.error(
`upload-error-${new Date().getTime()}`,
result.error || t(I18nKey.EXPLORER$UPLOAD_ERROR_MESSAGE),
);
return;
}
if (uploadedCount > 0) {
toast.success(
`upload-success-${new Date().getTime()}`,
t(I18nKey.EXPLORER$UPLOAD_SUCCESS_MESSAGE, {
count: uploadedCount,
}),
);
}

const uploadedCount = result.uploaded_files.length;
const skippedCount = result.skipped_files.length;
if (skippedCount > 0) {
const message = t(I18nKey.EXPLORER$UPLOAD_PARTIAL_SUCCESS_MESSAGE, {
count: skippedCount,
});
toast.info(message);
}

if (uploadedCount > 0) {
toast.success(
`upload-success-${new Date().getTime()}`,
t(I18nKey.EXPLORER$UPLOAD_SUCCESS_MESSAGE, {
count: uploadedCount,
}),
);
}
if (uploadedCount === 0 && skippedCount === 0) {
toast.info(t(I18nKey.EXPLORER$NO_FILES_UPLOADED_MESSAGE));
}
};

if (skippedCount > 0) {
const message = t(I18nKey.EXPLORER$UPLOAD_PARTIAL_SUCCESS_MESSAGE, {
count: skippedCount,
});
toast.info(message);
}
const handleUploadError = (e: Error) => {
toast.error(
`upload-error-${new Date().getTime()}`,
e.message || t(I18nKey.EXPLORER$UPLOAD_ERROR_MESSAGE),
);
};

if (uploadedCount === 0 && skippedCount === 0) {
toast.info(t(I18nKey.EXPLORER$NO_FILES_UPLOADED_MESSAGE));
}
const { mutate: uploadFiles } = useUploadFiles();

refreshWorkspace();
} catch (e) {
// Handle unexpected errors (network issues, etc.)
toast.error(
`upload-error-${new Date().getTime()}`,
t(I18nKey.EXPLORER$UPLOAD_ERROR_MESSAGE),
);
const refreshWorkspace = () => {
if (
curAgentState !== AgentState.LOADING &&
curAgentState !== AgentState.STOPPED
) {
refetch();
}
};

const uploadFileData = (files: FileList) => {
uploadFiles(
{ files: Array.from(files) },
{ onSuccess: handleUploadSuccess, onError: handleUploadError },
);
refreshWorkspace();
};

const handleVSCodeClick = async (e: React.MouseEvent) => {
e.preventDefault();
try {
Expand Down
42 changes: 42 additions & 0 deletions frontend/src/hooks/mutation/use-save-file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useMutation } from "@tanstack/react-query";
import toast from "react-hot-toast";
import { getToken } from "#/services/auth";
import { SaveFileSuccessResponse, ErrorResponse } from "#/api/open-hands.types";

const saveFileMutationFn = async (variables: {
path: string;
content: string;
}) => {
const { path, content } = variables;

const response = await fetch("/api/save-file", {
method: "POST",
body: JSON.stringify({ filePath: path, content }),
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getToken()}`,
},
});

if (!response.ok) {
throw new Error("Failed to save file");
}

const data = (await response.json()) as
| SaveFileSuccessResponse
| ErrorResponse;

if ("error" in data) {
throw new Error(data.error);
}

return data;
};

export const useSaveFile = () =>
useMutation({
mutationFn: saveFileMutationFn,
onError: (error) => {
toast.error(error.message);
},
});
27 changes: 27 additions & 0 deletions frontend/src/hooks/mutation/use-submit-feedback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useMutation } from "@tanstack/react-query";
import toast from "react-hot-toast";
import { Feedback, FeedbackResponse } from "#/api/open-hands.types";
import { getToken } from "#/services/auth";

export const useSubmitFeedback = () =>
useMutation({
mutationFn: async (variables: { feedback: Feedback }) => {
const response = await fetch("/api/submit-feedback", {
method: "POST",
body: JSON.stringify(variables.feedback),
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getToken()}`,
},
});

if (!response.ok) {
throw new Error("Failed to submit feedback");
}

return (await response.json()) as FeedbackResponse;
},
onError: (error) => {
toast.error(error.message);
},
});
38 changes: 38 additions & 0 deletions frontend/src/hooks/mutation/use-upload-files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useMutation } from "@tanstack/react-query";
import { getToken } from "#/services/auth";
import {
FileUploadSuccessResponse,
ErrorResponse,
} from "#/api/open-hands.types";

const uploadFilesMutationFn = async (variables: { files: File[] }) => {
const formData = new FormData();
variables.files.forEach((file) => formData.append("files", file));

const response = await fetch("/api/upload-files", {
method: "POST",
body: formData,
headers: {
Authorization: `Bearer ${getToken()}`,
},
});

if (!response.ok) {
throw new Error("Failed to upload files");
}

const data = (await response.json()) as
| FileUploadSuccessResponse
| ErrorResponse;

if ("error" in data) {
throw new Error(data.error);
}

return data;
};

export const useUploadFiles = () =>
useMutation({
mutationFn: uploadFilesMutationFn,
});
1 change: 1 addition & 0 deletions frontend/src/hooks/query/use-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const useConfig = () => {
queryFn: OpenHands.getConfig,
});

// Remove this. Instead, we should retrieve the data directly from the config query
React.useEffect(() => {
if (config.data) {
window.__APP_MODE__ = config.data.APP_MODE;
Expand Down
Loading

0 comments on commit e5502d3

Please sign in to comment.