Skip to content

Commit

Permalink
Merge branch 'main' into feature/cookie-based-auth
Browse files Browse the repository at this point in the history
  • Loading branch information
rbren authored Oct 31, 2024
2 parents 0cc70e9 + e17f7b2 commit 304689a
Show file tree
Hide file tree
Showing 22 changed files with 288 additions and 289 deletions.
46 changes: 46 additions & 0 deletions .github/workflows/ghcr-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,49 @@ jobs:
run: |
echo "Some runtime tests failed or were cancelled"
exit 1
update_pr_description:
name: Update PR Description
if: github.event_name == 'pull_request'
needs: [ghcr_build_runtime]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Get short SHA
id: short_sha
run: echo "SHORT_SHA=$(echo ${{ github.event.pull_request.head.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT

- name: Update PR Description
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
SHORT_SHA: ${{ steps.short_sha.outputs.SHORT_SHA }}
run: |
echo "updating PR description"
DOCKER_RUN_COMMAND="docker run -it --rm \
-p 3000:3000 \
-v /var/run/docker.sock:/var/run/docker.sock \
--add-host host.docker.internal:host-gateway \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:$SHORT_SHA-nikolaik \
--name openhands-app-$SHORT_SHA \
ghcr.io/all-hands-ai/runtime:$SHORT_SHA"
PR_BODY=$(gh pr view $PR_NUMBER --json body --jq .body)
if echo "$PR_BODY" | grep -q "To run this PR locally, use the following command:"; then
UPDATED_PR_BODY=$(echo "${PR_BODY}" | sed -E "s|docker run -it --rm.*|$DOCKER_RUN_COMMAND|")
else
UPDATED_PR_BODY="${PR_BODY}
---
To run this PR locally, use the following command:
\`\`\`
$DOCKER_RUN_COMMAND
\`\`\`"
fi
echo "updated body: $UPDATED_PR_BODY"
gh pr edit $PR_NUMBER --body "$UPDATED_PR_BODY"
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ See the [Installation](https://docs.all-hands.dev/modules/usage/installation) gu
system requirements and more information.

```bash
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.12-nikolaik

docker run -it --rm --pull=always \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.12-nikolaik \
-v /var/run/docker.sock:/var/run/docker.sock \
-p 3000:3000 \
--add-host host.docker.internal:host-gateway \
--name openhands-app \
docker.all-hands.dev/all-hands-ai/openhands:0.11
docker.all-hands.dev/all-hands-ai/openhands:0.12
```

You'll find OpenHands running at [http://localhost:3000](http://localhost:3000)!
Expand Down
4 changes: 2 additions & 2 deletions docs/modules/usage/how-to/cli-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ LLM_API_KEY="sk_test_12345"
```bash
docker run -it \
--pull=always \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.12-nikolaik \
-e SANDBOX_USER_ID=$(id -u) \
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
-e LLM_API_KEY=$LLM_API_KEY \
Expand All @@ -58,7 +59,7 @@ docker run -it \
-v /var/run/docker.sock:/var/run/docker.sock \
--add-host host.docker.internal:host-gateway \
--name openhands-app-$(date +%Y%m%d%H%M%S) \
docker.all-hands.dev/all-hands-ai/openhands:0.11 \
docker.all-hands.dev/all-hands-ai/openhands:0.12 \
python -m openhands.core.cli
```

Expand Down Expand Up @@ -107,4 +108,3 @@ Expected Output:
```bash
🤖 An error occurred. Please try again.
```

4 changes: 2 additions & 2 deletions docs/modules/usage/how-to/headless-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ LLM_API_KEY="sk_test_12345"
```bash
docker run -it \
--pull=always \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.12-nikolaik \
-e SANDBOX_USER_ID=$(id -u) \
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
-e LLM_API_KEY=$LLM_API_KEY \
Expand All @@ -52,7 +53,6 @@ docker run -it \
-v /var/run/docker.sock:/var/run/docker.sock \
--add-host host.docker.internal:host-gateway \
--name openhands-app-$(date +%Y%m%d%H%M%S) \
docker.all-hands.dev/all-hands-ai/openhands:0.11 \
docker.all-hands.dev/all-hands-ai/openhands:0.12 \
python -m openhands.core.main -t "write a bash script that prints hi"
```

6 changes: 3 additions & 3 deletions docs/modules/usage/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
The easiest way to run OpenHands is in Docker.

```bash
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.12-nikolaik

docker run -it --rm --pull=always \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.12-nikolaik \
-v /var/run/docker.sock:/var/run/docker.sock \
-p 3000:3000 \
--add-host host.docker.internal:host-gateway \
--name openhands-app \
docker.all-hands.dev/all-hands-ai/openhands:0.11
docker.all-hands.dev/all-hands-ai/openhands:0.12
```

You can also run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/modules/usage/how-to/headless-mode), as an [interactive CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode), or using the [OpenHands GitHub Action](https://docs.all-hands.dev/modules/usage/how-to/github-action).
Expand Down
2 changes: 1 addition & 1 deletion evaluation/swe_bench/eval_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ def process_instance(
# Create a directory structure that matches the expected format
# NOTE: this is a hack to make the eval report format consistent
# with the original SWE-Bench eval script
log_dir = os.path.join(temp_dir, 'logs', instance_id)
log_dir = os.path.join(temp_dir, 'logs', instance_id.lower())
os.makedirs(log_dir, exist_ok=True)
test_output_path = os.path.join(log_dir, 'test_output.txt')
with open(test_output_path, 'w') as f:
Expand Down
2 changes: 1 addition & 1 deletion evaluation/swe_bench/run_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def get_instance_docker_image(instance_id: str) -> str:
image_name = image_name.replace(
'__', '_s_'
) # to comply with docker image naming convention
return DOCKER_IMAGE_PREFIX.rstrip('/') + '/' + image_name
return (DOCKER_IMAGE_PREFIX.rstrip('/') + '/' + image_name).lower()


def get_config(
Expand Down
65 changes: 3 additions & 62 deletions frontend/__tests__/components/feedback-form.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import { FeedbackForm } from "#/components/feedback-form";

describe("FeedbackForm", () => {
const user = userEvent.setup();
const onSubmitMock = vi.fn();
const onCloseMock = vi.fn();

afterEach(() => {
vi.clearAllMocks();
});

it("should render correctly", () => {
render(<FeedbackForm onSubmit={onSubmitMock} onClose={onCloseMock} />);
render(<FeedbackForm polarity="positive" onClose={onCloseMock} />);

screen.getByLabelText("Email");
screen.getByLabelText("Private");
Expand All @@ -24,7 +23,7 @@ describe("FeedbackForm", () => {
});

it("should switch between private and public permissions", async () => {
render(<FeedbackForm onSubmit={onSubmitMock} onClose={onCloseMock} />);
render(<FeedbackForm polarity="positive" onClose={onCloseMock} />);
const privateRadio = screen.getByLabelText("Private");
const publicRadio = screen.getByLabelText("Public");

Expand All @@ -40,69 +39,11 @@ describe("FeedbackForm", () => {
expect(publicRadio).not.toBeChecked();
});

it("should call onSubmit when the form is submitted", async () => {
render(<FeedbackForm onSubmit={onSubmitMock} onClose={onCloseMock} />);
const email = screen.getByLabelText("Email");

await user.type(email, "[email protected]");
await user.click(screen.getByRole("button", { name: "Submit" }));

expect(onSubmitMock).toHaveBeenCalledWith("private", "[email protected]"); // private is the default value
});

it("should not call onSubmit when the email is invalid", async () => {
render(<FeedbackForm onSubmit={onSubmitMock} onClose={onCloseMock} />);
const email = screen.getByLabelText("Email");
const submitButton = screen.getByRole("button", { name: "Submit" });

await user.click(submitButton);

expect(onSubmitMock).not.toHaveBeenCalled();

await user.type(email, "test");
await user.click(submitButton);

expect(onSubmitMock).not.toHaveBeenCalled();
});

it("should submit public permissions when the public radio is checked", async () => {
render(<FeedbackForm onSubmit={onSubmitMock} onClose={onCloseMock} />);
const email = screen.getByLabelText("Email");
const publicRadio = screen.getByLabelText("Public");

await user.type(email, "[email protected]");
await user.click(publicRadio);
await user.click(screen.getByRole("button", { name: "Submit" }));

expect(onSubmitMock).toHaveBeenCalledWith("public", "[email protected]");
});

it("should call onClose when the close button is clicked", async () => {
render(<FeedbackForm onSubmit={onSubmitMock} onClose={onCloseMock} />);
render(<FeedbackForm polarity="positive" onClose={onCloseMock} />);
await user.click(screen.getByRole("button", { name: "Cancel" }));

expect(onSubmitMock).not.toHaveBeenCalled();
expect(onCloseMock).toHaveBeenCalled();
});

it("should disable the buttons if isSubmitting is true", () => {
const { rerender } = render(
<FeedbackForm onSubmit={onSubmitMock} onClose={onCloseMock} />,
);
const submitButton = screen.getByRole("button", { name: "Submit" });
const cancelButton = screen.getByRole("button", { name: "Cancel" });

expect(submitButton).not.toBeDisabled();
expect(cancelButton).not.toBeDisabled();

rerender(
<FeedbackForm
onSubmit={onSubmitMock}
onClose={onCloseMock}
isSubmitting
/>,
);
expect(submitButton).toBeDisabled();
expect(cancelButton).toBeDisabled();
});
});
4 changes: 2 additions & 2 deletions frontend/package-lock.json

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

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "openhands-frontend",
"version": "0.11.0",
"version": "0.12.0",
"private": true,
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/open-hands.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export interface Feedback {
version: string;
email: string;
token: string;
feedback: "positive" | "negative";
polarity: "positive" | "negative";
permissions: "public" | "private";
trajectory: unknown[];
}
Expand Down
56 changes: 10 additions & 46 deletions frontend/src/components/chat-interface.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useDispatch, useSelector } from "react-redux";
import React from "react";
import { useFetcher } from "@remix-run/react";
import { useSocket } from "#/context/socket";
import { convertImageToBase64 } from "#/utils/convert-image-to-base-64";
import { ChatMessage } from "./chat-message";
Expand All @@ -13,27 +12,20 @@ import { RootState } from "#/store";
import AgentState from "#/types/AgentState";
import { generateAgentStateChangeEvent } from "#/services/agentStateService";
import { FeedbackModal } from "./feedback-modal";
import { Feedback } from "#/api/open-hands.types";
import { getToken } from "#/services/auth";
import { removeApiKey, removeUnwantedKeys } from "#/utils/utils";
import { clientAction } from "#/routes/submit-feedback";
import { useScrollToBottom } from "#/hooks/useScrollToBottom";
import TypingIndicator from "./chat/TypingIndicator";
import ConfirmationButtons from "./chat/ConfirmationButtons";
import { ErrorMessage } from "./error-message";
import { ContinueButton } from "./continue-button";
import { ScrollToBottomButton } from "./scroll-to-bottom-button";

const FEEDBACK_VERSION = "1.0";

const isErrorMessage = (
message: Message | ErrorMessage,
): message is ErrorMessage => "error" in message;

export function ChatInterface() {
const { send, events } = useSocket();
const { send } = useSocket();
const dispatch = useDispatch();
const fetcher = useFetcher<typeof clientAction>({ key: "feedback" });
const scrollRef = React.useRef<HTMLDivElement>(null);
const { scrollDomToBottom, onChatBodyScroll, hitBottom } =
useScrollToBottom(scrollRef);
Expand All @@ -44,7 +36,6 @@ export function ChatInterface() {
const [feedbackPolarity, setFeedbackPolarity] = React.useState<
"positive" | "negative"
>("positive");
const [feedbackShared, setFeedbackShared] = React.useState(0);
const [feedbackModalIsOpen, setFeedbackModalIsOpen] = React.useState(false);

const handleSendMessage = async (content: string, files: File[]) => {
Expand All @@ -71,30 +62,6 @@ export function ChatInterface() {
setFeedbackPolarity(polarity);
};

const handleSubmitFeedback = (
permissions: "private" | "public",
email: string,
) => {
const feedback: Feedback = {
version: FEEDBACK_VERSION,
feedback: feedbackPolarity,
email,
permissions,
token: getToken(),
trajectory: removeApiKey(removeUnwantedKeys(events)),
};

const formData = new FormData();
formData.append("feedback", JSON.stringify(feedback));

fetcher.submit(formData, {
action: "/submit-feedback",
method: "POST",
});

setFeedbackShared(messages.length);
};

return (
<div className="h-full flex flex-col justify-between">
<div
Expand Down Expand Up @@ -130,16 +97,14 @@ export function ChatInterface() {

<div className="flex flex-col gap-[6px] px-4 pb-4">
<div className="flex justify-between relative">
{feedbackShared !== messages.length && messages.length > 3 && (
<FeedbackActions
onPositiveFeedback={() =>
onClickShareFeedbackActionButton("positive")
}
onNegativeFeedback={() =>
onClickShareFeedbackActionButton("negative")
}
/>
)}
<FeedbackActions
onPositiveFeedback={() =>
onClickShareFeedbackActionButton("positive")
}
onNegativeFeedback={() =>
onClickShareFeedbackActionButton("negative")
}
/>
<div className="absolute left-1/2 transform -translate-x-1/2 bottom-0">
{messages.length > 2 &&
curAgentState === AgentState.AWAITING_USER_INPUT && (
Expand All @@ -163,9 +128,8 @@ export function ChatInterface() {

<FeedbackModal
isOpen={feedbackModalIsOpen}
isSubmitting={fetcher.state === "submitting"}
onClose={() => setFeedbackModalIsOpen(false)}
onSubmit={handleSubmitFeedback}
polarity={feedbackPolarity}
/>
</div>
);
Expand Down
Loading

0 comments on commit 304689a

Please sign in to comment.