mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Merge branch 'main' into rel-cli-1.0.2
This commit is contained in:
commit
fd8768642c
@ -105,10 +105,17 @@ describe("Content", () => {
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("Advanced form", () => {
|
||||
it("should conditionally show security analyzer based on confirmation mode", async () => {
|
||||
renderLlmSettingsScreen();
|
||||
await screen.findByTestId("llm-settings-screen");
|
||||
|
||||
// Enable advanced mode first
|
||||
const advancedSwitch = screen.getByTestId("advanced-settings-switch");
|
||||
await userEvent.click(advancedSwitch);
|
||||
|
||||
const confirmation = screen.getByTestId(
|
||||
"enable-confirmation-mode-switch",
|
||||
);
|
||||
@ -135,9 +142,7 @@ describe("Content", () => {
|
||||
screen.queryByTestId("security-analyzer-input"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Advanced form", () => {
|
||||
it("should render the advanced form if the switch is toggled", async () => {
|
||||
renderLlmSettingsScreen();
|
||||
await screen.findByTestId("llm-settings-screen");
|
||||
@ -615,7 +620,7 @@ describe("Form submission", () => {
|
||||
expect.objectContaining({
|
||||
llm_model: "openhands/claude-sonnet-4-20250514",
|
||||
llm_base_url: "",
|
||||
confirmation_mode: true, // Confirmation mode is now a basic setting, should be preserved
|
||||
confirmation_mode: false, // Confirmation mode is now an advanced setting, should be cleared when saving basic settings
|
||||
}),
|
||||
);
|
||||
});
|
||||
@ -776,9 +781,6 @@ describe("SaaS mode", () => {
|
||||
const modelInput = screen.getByTestId("llm-model-input");
|
||||
const apiKeyInput = screen.getByTestId("llm-api-key-input");
|
||||
const advancedSwitch = screen.getByTestId("advanced-settings-switch");
|
||||
const confirmationModeSwitch = screen.getByTestId(
|
||||
"enable-confirmation-mode-switch",
|
||||
);
|
||||
const submitButton = screen.getByTestId("submit-button");
|
||||
|
||||
// Inputs should be disabled
|
||||
@ -786,9 +788,13 @@ describe("SaaS mode", () => {
|
||||
expect(modelInput).toBeDisabled();
|
||||
expect(apiKeyInput).toBeDisabled();
|
||||
expect(advancedSwitch).toBeDisabled();
|
||||
expect(confirmationModeSwitch).toBeDisabled();
|
||||
expect(submitButton).toBeDisabled();
|
||||
|
||||
// Confirmation mode switch is in advanced view, so it's not visible in basic view
|
||||
expect(
|
||||
screen.queryByTestId("enable-confirmation-mode-switch"),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
// Try to interact with inputs - they should not respond
|
||||
await userEvent.click(providerInput);
|
||||
await userEvent.type(apiKeyInput, "test-key");
|
||||
@ -935,19 +941,17 @@ describe("SaaS mode", () => {
|
||||
renderLlmSettingsScreen();
|
||||
await screen.findByTestId("llm-settings-screen");
|
||||
|
||||
// Verify that form elements are disabled for unsubscribed users
|
||||
const confirmationModeSwitch = screen.getByTestId(
|
||||
"enable-confirmation-mode-switch",
|
||||
);
|
||||
// Verify that basic form elements are disabled for unsubscribed users
|
||||
const advancedSwitch = screen.getByTestId("advanced-settings-switch");
|
||||
const submitButton = screen.getByTestId("submit-button");
|
||||
|
||||
expect(confirmationModeSwitch).not.toBeChecked();
|
||||
expect(confirmationModeSwitch).toBeDisabled();
|
||||
expect(advancedSwitch).toBeDisabled();
|
||||
expect(submitButton).toBeDisabled();
|
||||
|
||||
// Try to click the disabled confirmation mode switch - it should not change state
|
||||
await userEvent.click(confirmationModeSwitch);
|
||||
expect(confirmationModeSwitch).not.toBeChecked(); // Should remain unchecked
|
||||
// Confirmation mode switch is in advanced view, which can't be accessed when form is disabled
|
||||
expect(
|
||||
screen.queryByTestId("enable-confirmation-mode-switch"),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
// Try to submit the form - button should remain disabled
|
||||
await userEvent.click(submitButton);
|
||||
@ -1107,14 +1111,17 @@ describe("SaaS mode", () => {
|
||||
const providerInput = screen.getByTestId("llm-provider-input");
|
||||
const modelInput = screen.getByTestId("llm-model-input");
|
||||
const apiKeyInput = screen.getByTestId("llm-api-key-input");
|
||||
const confirmationModeSwitch = screen.getByTestId(
|
||||
"enable-confirmation-mode-switch",
|
||||
);
|
||||
const advancedSwitch = screen.getByTestId("advanced-settings-switch");
|
||||
|
||||
expect(providerInput).toBeDisabled();
|
||||
expect(modelInput).toBeDisabled();
|
||||
expect(apiKeyInput).toBeDisabled();
|
||||
expect(confirmationModeSwitch).toBeDisabled();
|
||||
expect(advancedSwitch).toBeDisabled();
|
||||
|
||||
// Confirmation mode switch is in advanced view, which can't be accessed when form is disabled
|
||||
expect(
|
||||
screen.queryByTestId("enable-confirmation-mode-switch"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -30,12 +30,14 @@ import { WebSocketProviderWrapper } from "#/contexts/websocket-provider-wrapper"
|
||||
import { useErrorMessageStore } from "#/stores/error-message-store";
|
||||
import { useUnifiedResumeConversationSandbox } from "#/hooks/mutation/use-unified-start-conversation";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { useEventStore } from "#/stores/use-event-store";
|
||||
|
||||
function AppContent() {
|
||||
useConversationConfig();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { conversationId } = useConversationId();
|
||||
const clearEvents = useEventStore((state) => state.clearEvents);
|
||||
|
||||
// Handle both task IDs (task-{uuid}) and regular conversation IDs
|
||||
const { isTask, taskStatus, taskDetail } = useTaskPolling();
|
||||
@ -72,6 +74,7 @@ function AppContent() {
|
||||
resetConversationState();
|
||||
setCurrentAgentState(AgentState.LOADING);
|
||||
removeErrorMessage();
|
||||
clearEvents();
|
||||
|
||||
// Reset tracking ONLY if we're navigating to a DIFFERENT conversation
|
||||
// Don't reset on StrictMode remounts (conversationId is the same)
|
||||
@ -85,6 +88,7 @@ function AppContent() {
|
||||
resetConversationState,
|
||||
setCurrentAgentState,
|
||||
removeErrorMessage,
|
||||
clearEvents,
|
||||
]);
|
||||
|
||||
// 2. Task Error Display Effect
|
||||
|
||||
@ -531,34 +531,6 @@ function LlmSettingsScreen() {
|
||||
linkText={t(I18nKey.SETTINGS$CLICK_FOR_INSTRUCTIONS)}
|
||||
href="https://docs.all-hands.dev/usage/local-setup#getting-an-api-key"
|
||||
/>
|
||||
|
||||
{config?.APP_MODE !== "saas" && (
|
||||
<SettingsInput
|
||||
testId="search-api-key-input"
|
||||
name="search-api-key-input"
|
||||
label={t(I18nKey.SETTINGS$SEARCH_API_KEY)}
|
||||
type="password"
|
||||
className="w-full max-w-[680px]"
|
||||
defaultValue={settings.SEARCH_API_KEY || ""}
|
||||
onChange={handleSearchApiKeyIsDirty}
|
||||
placeholder={t(I18nKey.API$TAVILY_KEY_EXAMPLE)}
|
||||
isDisabled={shouldShowUpgradeBanner}
|
||||
startContent={
|
||||
settings.SEARCH_API_KEY_SET && (
|
||||
<KeyStatusIcon isSet={settings.SEARCH_API_KEY_SET} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{config?.APP_MODE !== "saas" && (
|
||||
<HelpLink
|
||||
testId="search-api-key-help-anchor"
|
||||
text={t(I18nKey.SETTINGS$SEARCH_API_KEY_OPTIONAL)}
|
||||
linkText={t(I18nKey.SETTINGS$SEARCH_API_KEY_INSTRUCTIONS)}
|
||||
href="https://tavily.com/"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -686,68 +658,68 @@ function LlmSettingsScreen() {
|
||||
>
|
||||
{t(I18nKey.SETTINGS$ENABLE_MEMORY_CONDENSATION)}
|
||||
</SettingsSwitch>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Confirmation mode and security analyzer - always visible */}
|
||||
<div className="flex items-center gap-2">
|
||||
<SettingsSwitch
|
||||
testId="enable-confirmation-mode-switch"
|
||||
name="enable-confirmation-mode-switch"
|
||||
onToggle={handleConfirmationModeIsDirty}
|
||||
defaultIsToggled={settings.CONFIRMATION_MODE}
|
||||
isBeta
|
||||
isDisabled={shouldShowUpgradeBanner}
|
||||
>
|
||||
{t(I18nKey.SETTINGS$CONFIRMATION_MODE)}
|
||||
</SettingsSwitch>
|
||||
<TooltipButton
|
||||
tooltip={t(I18nKey.SETTINGS$CONFIRMATION_MODE_TOOLTIP)}
|
||||
ariaLabel={t(I18nKey.SETTINGS$CONFIRMATION_MODE)}
|
||||
className="text-[#9099AC] hover:text-white cursor-help"
|
||||
>
|
||||
<QuestionCircleIcon width={16} height={16} />
|
||||
</TooltipButton>
|
||||
</div>
|
||||
|
||||
{confirmationModeEnabled && (
|
||||
<>
|
||||
<div className="w-full max-w-[680px]">
|
||||
<SettingsDropdownInput
|
||||
testId="security-analyzer-input"
|
||||
name="security-analyzer-display"
|
||||
label={t(I18nKey.SETTINGS$SECURITY_ANALYZER)}
|
||||
items={getSecurityAnalyzerOptions()}
|
||||
placeholder={t(
|
||||
I18nKey.SETTINGS$SECURITY_ANALYZER_PLACEHOLDER,
|
||||
)}
|
||||
selectedKey={selectedSecurityAnalyzer || "none"}
|
||||
isClearable={false}
|
||||
onSelectionChange={(key) => {
|
||||
const newValue = key?.toString() || "";
|
||||
setSelectedSecurityAnalyzer(newValue);
|
||||
handleSecurityAnalyzerIsDirty(newValue);
|
||||
}}
|
||||
onInputChange={(value) => {
|
||||
// Handle when input is cleared
|
||||
if (!value) {
|
||||
setSelectedSecurityAnalyzer("");
|
||||
handleSecurityAnalyzerIsDirty("");
|
||||
}
|
||||
}}
|
||||
wrapperClassName="w-full"
|
||||
/>
|
||||
{/* Hidden input to store the actual key value for form submission */}
|
||||
<input
|
||||
type="hidden"
|
||||
name="security-analyzer-input"
|
||||
value={selectedSecurityAnalyzer || ""}
|
||||
/>
|
||||
{/* Confirmation mode and security analyzer */}
|
||||
<div className="flex items-center gap-2">
|
||||
<SettingsSwitch
|
||||
testId="enable-confirmation-mode-switch"
|
||||
name="enable-confirmation-mode-switch"
|
||||
onToggle={handleConfirmationModeIsDirty}
|
||||
defaultIsToggled={settings.CONFIRMATION_MODE}
|
||||
isBeta
|
||||
isDisabled={shouldShowUpgradeBanner}
|
||||
>
|
||||
{t(I18nKey.SETTINGS$CONFIRMATION_MODE)}
|
||||
</SettingsSwitch>
|
||||
<TooltipButton
|
||||
tooltip={t(I18nKey.SETTINGS$CONFIRMATION_MODE_TOOLTIP)}
|
||||
ariaLabel={t(I18nKey.SETTINGS$CONFIRMATION_MODE)}
|
||||
className="text-[#9099AC] hover:text-white cursor-help"
|
||||
>
|
||||
<QuestionCircleIcon width={16} height={16} />
|
||||
</TooltipButton>
|
||||
</div>
|
||||
<p className="text-xs text-tertiary-alt max-w-[680px]">
|
||||
{t(I18nKey.SETTINGS$SECURITY_ANALYZER_DESCRIPTION)}
|
||||
</p>
|
||||
</>
|
||||
|
||||
{confirmationModeEnabled && (
|
||||
<>
|
||||
<div className="w-full max-w-[680px]">
|
||||
<SettingsDropdownInput
|
||||
testId="security-analyzer-input"
|
||||
name="security-analyzer-display"
|
||||
label={t(I18nKey.SETTINGS$SECURITY_ANALYZER)}
|
||||
items={getSecurityAnalyzerOptions()}
|
||||
placeholder={t(
|
||||
I18nKey.SETTINGS$SECURITY_ANALYZER_PLACEHOLDER,
|
||||
)}
|
||||
selectedKey={selectedSecurityAnalyzer || "none"}
|
||||
isClearable={false}
|
||||
onSelectionChange={(key) => {
|
||||
const newValue = key?.toString() || "";
|
||||
setSelectedSecurityAnalyzer(newValue);
|
||||
handleSecurityAnalyzerIsDirty(newValue);
|
||||
}}
|
||||
onInputChange={(value) => {
|
||||
// Handle when input is cleared
|
||||
if (!value) {
|
||||
setSelectedSecurityAnalyzer("");
|
||||
handleSecurityAnalyzerIsDirty("");
|
||||
}
|
||||
}}
|
||||
wrapperClassName="w-full"
|
||||
/>
|
||||
{/* Hidden input to store the actual key value for form submission */}
|
||||
<input
|
||||
type="hidden"
|
||||
name="security-analyzer-input"
|
||||
value={selectedSecurityAnalyzer || ""}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-tertiary-alt max-w-[680px]">
|
||||
{t(I18nKey.SETTINGS$SECURITY_ANALYZER_DESCRIPTION)}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user