mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
1 Commits
openhands-
...
openhands-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9672f105e4 |
@@ -0,0 +1,90 @@
|
||||
import { test, expect } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { ExpandableMessage } from "#/components/features/chat/expandable-message";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
|
||||
// Initialize i18n for testing
|
||||
i18n.use(initReactI18next).init({
|
||||
lng: "en",
|
||||
resources: {
|
||||
en: {
|
||||
translation: {
|
||||
"ACTION_MESSAGE$RUN": "this action has not been executed",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
test("should show only the headline for unexecuted actions", () => {
|
||||
render(
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<ExpandableMessage
|
||||
type="action"
|
||||
id="ACTION_MESSAGE$RUN"
|
||||
message="Command:\n`ls -l`"
|
||||
success={undefined}
|
||||
/>
|
||||
</I18nextProvider>
|
||||
);
|
||||
|
||||
// The headline should be visible
|
||||
const headline = screen.getByText("this action has not been executed");
|
||||
expect(headline).toBeInTheDocument();
|
||||
expect(headline).toHaveClass("font-bold");
|
||||
|
||||
// The command details should not be visible
|
||||
const details = screen.queryByText(/Command:/, { exact: false });
|
||||
expect(details).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("should show only the details for completed successful actions", () => {
|
||||
render(
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<ExpandableMessage
|
||||
type="action"
|
||||
id="ACTION_MESSAGE$RUN"
|
||||
message="Command executed successfully"
|
||||
success={true}
|
||||
/>
|
||||
</I18nextProvider>
|
||||
);
|
||||
|
||||
// The command details should be visible
|
||||
const details = screen.getByText("Command executed successfully");
|
||||
expect(details).toBeInTheDocument();
|
||||
|
||||
// The success icon should be visible
|
||||
const statusIcon = screen.getByTestId("status-icon");
|
||||
expect(statusIcon).toHaveClass("fill-success");
|
||||
|
||||
// The headline should not be visible
|
||||
const headline = screen.queryByText("this action has not been executed");
|
||||
expect(headline).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("should show only the details for completed failed actions", () => {
|
||||
render(
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<ExpandableMessage
|
||||
type="action"
|
||||
id="ACTION_MESSAGE$RUN"
|
||||
message="Command failed"
|
||||
success={false}
|
||||
/>
|
||||
</I18nextProvider>
|
||||
);
|
||||
|
||||
// The command details should be visible
|
||||
const details = screen.getByText("Command failed");
|
||||
expect(details).toBeInTheDocument();
|
||||
|
||||
// The error icon should be visible
|
||||
const statusIcon = screen.getByTestId("status-icon");
|
||||
expect(statusIcon).toHaveClass("fill-danger");
|
||||
|
||||
// The headline should not be visible
|
||||
const headline = screen.queryByText("this action has not been executed");
|
||||
expect(headline).not.toBeInTheDocument();
|
||||
});
|
||||
@@ -115,10 +115,7 @@ describe("ModelSelector", () => {
|
||||
it("should have a default value if passed", async () => {
|
||||
render(<ModelSelector models={models} currentModel="azure/ada" />);
|
||||
|
||||
const providerInput = screen.getByLabelText("LLM Provider");
|
||||
const modelInput = screen.getByLabelText("LLM Model");
|
||||
|
||||
expect(providerInput).toHaveAttribute("value", "Azure");
|
||||
expect(modelInput).toHaveAttribute("value", "ada");
|
||||
expect(screen.getByLabelText("LLM Provider")).toHaveValue("Azure");
|
||||
expect(screen.getByLabelText("LLM Model")).toHaveValue("ada");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,11 +30,24 @@ export function ExpandableMessage({
|
||||
|
||||
useEffect(() => {
|
||||
if (id && i18n.exists(id)) {
|
||||
setHeadline(t(id));
|
||||
// Only show the headline if the action hasn't been executed yet (success is undefined)
|
||||
if (success === undefined) {
|
||||
setHeadline(t(id));
|
||||
setDetails("");
|
||||
setShowDetails(false);
|
||||
} else {
|
||||
// Only show the details if the action has been executed
|
||||
setHeadline("");
|
||||
setDetails(message);
|
||||
setShowDetails(true);
|
||||
}
|
||||
} else {
|
||||
// If no translation ID is provided, just show the message
|
||||
setHeadline("");
|
||||
setDetails(message);
|
||||
setShowDetails(false);
|
||||
setShowDetails(true);
|
||||
}
|
||||
}, [id, message, i18n.language]);
|
||||
}, [id, message, success, i18n.language, t]);
|
||||
|
||||
const statusIconClasses = "h-4 w-4 ml-2 inline";
|
||||
|
||||
@@ -46,7 +59,8 @@ export function ExpandableMessage({
|
||||
)}
|
||||
>
|
||||
<div className="text-sm w-full">
|
||||
{headline && (
|
||||
{headline ? (
|
||||
// Show headline for unexecuted actions
|
||||
<div className="flex flex-row justify-between items-center w-full">
|
||||
<span
|
||||
className={cn(
|
||||
@@ -55,30 +69,26 @@ export function ExpandableMessage({
|
||||
)}
|
||||
>
|
||||
{headline}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowDetails(!showDetails)}
|
||||
className="cursor-pointer text-left"
|
||||
>
|
||||
{showDetails ? (
|
||||
<ArrowUp
|
||||
className={cn(
|
||||
"h-4 w-4 ml-2 inline",
|
||||
type === "error" ? "fill-danger" : "fill-neutral-300",
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<ArrowDown
|
||||
className={cn(
|
||||
"h-4 w-4 ml-2 inline",
|
||||
type === "error" ? "fill-danger" : "fill-neutral-300",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
// Show details for executed actions
|
||||
<div className="flex flex-row justify-between items-center w-full">
|
||||
<div className="flex-grow">
|
||||
<Markdown
|
||||
className="text-sm overflow-auto"
|
||||
components={{
|
||||
code,
|
||||
ul,
|
||||
ol,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
>
|
||||
{details}
|
||||
</Markdown>
|
||||
</div>
|
||||
{type === "action" && success !== undefined && (
|
||||
<span className="flex-shrink-0">
|
||||
<span className="flex-shrink-0 ml-2">
|
||||
{success ? (
|
||||
<CheckCircle
|
||||
data-testid="status-icon"
|
||||
@@ -94,19 +104,6 @@ export function ExpandableMessage({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{showDetails && (
|
||||
<Markdown
|
||||
className="text-sm overflow-auto"
|
||||
components={{
|
||||
code,
|
||||
ul,
|
||||
ol,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
>
|
||||
{details}
|
||||
</Markdown>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
Autocomplete,
|
||||
AutocompleteItem,
|
||||
AutocompleteSection,
|
||||
Tooltip,
|
||||
} from "@nextui-org/react";
|
||||
import React from "react";
|
||||
import { mapProvider } from "#/utils/map-provider";
|
||||
@@ -64,7 +63,7 @@ export function ModelSelector({
|
||||
return (
|
||||
<div data-testid="model-selector" className="flex flex-col gap-2">
|
||||
<div className="flex flex-row gap-3">
|
||||
<fieldset className="flex flex-col gap-2 w-1/2">
|
||||
<fieldset className="flex flex-col gap-2">
|
||||
<label htmlFor="agent" className="font-[500] text-[#A3A3A3] text-xs">
|
||||
LLM Provider
|
||||
</label>
|
||||
@@ -114,7 +113,7 @@ export function ModelSelector({
|
||||
</Autocomplete>
|
||||
</fieldset>
|
||||
|
||||
<fieldset className="flex flex-col gap-2 w-1/2">
|
||||
<fieldset className="flex flex-col gap-2">
|
||||
<label htmlFor="agent" className="font-[500] text-[#A3A3A3] text-xs">
|
||||
LLM Model
|
||||
</label>
|
||||
@@ -143,9 +142,7 @@ export function ModelSelector({
|
||||
.filter((model) => VERIFIED_MODELS.includes(model))
|
||||
.map((model) => (
|
||||
<AutocompleteItem key={model} value={model}>
|
||||
<Tooltip content={model}>
|
||||
<span>{model}</span>
|
||||
</Tooltip>
|
||||
{model}
|
||||
</AutocompleteItem>
|
||||
))}
|
||||
</AutocompleteSection>
|
||||
@@ -158,9 +155,7 @@ export function ModelSelector({
|
||||
key={model}
|
||||
value={model}
|
||||
>
|
||||
<Tooltip content={model}>
|
||||
<span>{model}</span>
|
||||
</Tooltip>
|
||||
{model}
|
||||
</AutocompleteItem>
|
||||
))}
|
||||
</AutocompleteSection>
|
||||
|
||||
Reference in New Issue
Block a user