Skip to content

Commit

Permalink
upload file to workspace with button (#1394)
Browse files Browse the repository at this point in the history
* upload file to workspace with button

* resolve filepath

* regenerate lock file

* fix lock file

* fix torch version

* fix lock

* fix poerty.lock

* fix lint

---------

Co-authored-by: Xingyao Wang <xingyao6@illinois.edu>
  • Loading branch information
TemryL and xingyaoww committed May 1, 2024
1 parent c7dd443 commit 545327c
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 16 deletions.
72 changes: 58 additions & 14 deletions frontend/src/components/file-explorer/FileExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,25 @@ import {
IoIosArrowBack,
IoIosArrowForward,
IoIosRefresh,
IoIosCloudUpload,
} from "react-icons/io";
import { twMerge } from "tailwind-merge";
import { WorkspaceFile, getWorkspace } from "#/services/fileService";
import { WorkspaceFile, getWorkspace, uploadFile } from "#/services/fileService";
import IconButton from "../IconButton";
import ExplorerTree from "./ExplorerTree";
import { removeEmptyNodes } from "./utils";

interface ExplorerActionsProps {
onRefresh: () => void;
onUpload: () => void;
toggleHidden: () => void;
isHidden: boolean;
}

function ExplorerActions({
toggleHidden,
onRefresh,
onUpload,
isHidden,
}: ExplorerActionsProps) {
return (
Expand All @@ -29,17 +32,30 @@ function ExplorerActions({
)}
>
{!isHidden && (
<IconButton
icon={
<IoIosRefresh
size={16}
className="text-neutral-400 hover:text-neutral-100 transition"
/>
}
testId="refresh"
ariaLabel="Refresh workspace"
onClick={onRefresh}
/>
<>
<IconButton
icon={
<IoIosRefresh
size={16}
className="text-neutral-400 hover:text-neutral-100 transition"
/>
}
testId="refresh"
ariaLabel="Refresh workspace"
onClick={onRefresh}
/>
<IconButton
icon={
<IoIosCloudUpload
size={16}
className="text-neutral-400 hover:text-neutral-100 transition"
/>
}
testId="upload"
ariaLabel="Upload File"
onClick={onUpload}
/>
</>
)}

<IconButton
Expand All @@ -56,8 +72,8 @@ function ExplorerActions({
/>
)
}
testId="close"
ariaLabel="Close workspace"
testId="toggle"
ariaLabel={isHidden ? "Open workspace" : "Close workspace"}
onClick={toggleHidden}
/>
</div>
Expand All @@ -71,12 +87,33 @@ interface FileExplorerProps {
function FileExplorer({ onFileClick }: FileExplorerProps) {
const [workspace, setWorkspace] = React.useState<WorkspaceFile>();
const [isHidden, setIsHidden] = React.useState(false);
const fileInputRef = React.useRef<HTMLInputElement | null>(null);

const getWorkspaceData = async () => {
const wsFile = await getWorkspace();
setWorkspace(removeEmptyNodes(wsFile));
};

const selectFileInput = () => {
fileInputRef.current?.click(); // Trigger the file browser
};

const uploadFileData = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files ? event.target.files[0] : null;
if (!file) {
console.log("No file selected.");
return;
}
console.log("File selected:", file);
try {
const response = await uploadFile(file);
console.log(response);
await getWorkspaceData(); // Refresh the workspace to show the new file
} catch (error) {
console.error("Error uploading file:", error);
}
};

React.useEffect(() => {
(async () => {
await getWorkspaceData();
Expand Down Expand Up @@ -105,8 +142,15 @@ function FileExplorer({ onFileClick }: FileExplorerProps) {
isHidden={isHidden}
toggleHidden={() => setIsHidden((prev) => !prev)}
onRefresh={getWorkspaceData}
onUpload={selectFileInput}
/>
</div>
<input
type="file"
ref={fileInputRef}
style={{ display: "none" }}
onChange={uploadFileData}
/>
</div>
);
}
Expand Down
18 changes: 18 additions & 0 deletions frontend/src/services/fileService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ export async function selectFile(file: string): Promise<string> {
return data.code as string;
}

export async function uploadFile(file: File): Promise<string> {
const formData = new FormData();
formData.append("file", file);

const res = await fetch("/api/upload-file", {
method: "POST",
body: formData,
});

const data = await res.json();

if (res.status !== 200) {
throw new Error(data.error || "Failed to upload file.");
}

return `File uploaded: ${data.filename}, Location: ${data.location}`;
}

export async function getWorkspace(): Promise<WorkspaceFile> {
const res = await fetch("/api/refresh-files");
const data = await res.json();
Expand Down
21 changes: 20 additions & 1 deletion opendevin/server/listen.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import json
import shutil
import uuid
from pathlib import Path

import litellm
from fastapi import Depends, FastAPI, Response, WebSocket, status
from fastapi import Depends, FastAPI, Response, UploadFile, WebSocket, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, RedirectResponse
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
Expand Down Expand Up @@ -137,6 +138,24 @@ def select_file(file: str):
return {'code': content}


@app.post('/api/upload-file')
async def upload_file(file: UploadFile):
try:
workspace_base = config.get(ConfigType.WORKSPACE_BASE)
file_path = Path(workspace_base, file.filename)
# The following will check if the file is within the workspace base and throw an exception if not
file_path.resolve().relative_to(Path(workspace_base).resolve())
with open(file_path, 'wb') as buffer:
shutil.copyfileobj(file.file, buffer)
except Exception as e:
logger.error(f'Error saving file {file.filename}: {e}', exc_info=True)
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={'error': f'Error saving file: {e}'}
)
return {'filename': file.filename, 'location': str(file_path)}


@app.get('/api/plan')
def get_plan(
credentials: HTTPAuthorizationCredentials = Depends(security_scheme),
Expand Down
16 changes: 15 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ playwright = "*"
e2b = "^0.14.13"
pexpect = "*"
jinja2 = "^3.1.3"
python-multipart = "*"

[tool.poetry.group.llama-index.dependencies]
llama-index = "*"
Expand Down

0 comments on commit 545327c

Please sign in to comment.