Skip to content

Commit fd87686

Browse files
authored
Merge branch 'main' into rel-cli-1.0.2
2 parents 0f19311 + a68576b commit fd87686

File tree

3 files changed

+91
-108
lines changed

3 files changed

+91
-108
lines changed

frontend/__tests__/routes/llm-settings.test.tsx

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,17 @@ describe("Content", () => {
105105
});
106106
});
107107

108+
});
109+
110+
describe("Advanced form", () => {
108111
it("should conditionally show security analyzer based on confirmation mode", async () => {
109112
renderLlmSettingsScreen();
110113
await screen.findByTestId("llm-settings-screen");
111114

115+
// Enable advanced mode first
116+
const advancedSwitch = screen.getByTestId("advanced-settings-switch");
117+
await userEvent.click(advancedSwitch);
118+
112119
const confirmation = screen.getByTestId(
113120
"enable-confirmation-mode-switch",
114121
);
@@ -135,9 +142,7 @@ describe("Content", () => {
135142
screen.queryByTestId("security-analyzer-input"),
136143
).not.toBeInTheDocument();
137144
});
138-
});
139145

140-
describe("Advanced form", () => {
141146
it("should render the advanced form if the switch is toggled", async () => {
142147
renderLlmSettingsScreen();
143148
await screen.findByTestId("llm-settings-screen");
@@ -615,7 +620,7 @@ describe("Form submission", () => {
615620
expect.objectContaining({
616621
llm_model: "openhands/claude-sonnet-4-20250514",
617622
llm_base_url: "",
618-
confirmation_mode: true, // Confirmation mode is now a basic setting, should be preserved
623+
confirmation_mode: false, // Confirmation mode is now an advanced setting, should be cleared when saving basic settings
619624
}),
620625
);
621626
});
@@ -776,19 +781,20 @@ describe("SaaS mode", () => {
776781
const modelInput = screen.getByTestId("llm-model-input");
777782
const apiKeyInput = screen.getByTestId("llm-api-key-input");
778783
const advancedSwitch = screen.getByTestId("advanced-settings-switch");
779-
const confirmationModeSwitch = screen.getByTestId(
780-
"enable-confirmation-mode-switch",
781-
);
782784
const submitButton = screen.getByTestId("submit-button");
783785

784786
// Inputs should be disabled
785787
expect(providerInput).toBeDisabled();
786788
expect(modelInput).toBeDisabled();
787789
expect(apiKeyInput).toBeDisabled();
788790
expect(advancedSwitch).toBeDisabled();
789-
expect(confirmationModeSwitch).toBeDisabled();
790791
expect(submitButton).toBeDisabled();
791792

793+
// Confirmation mode switch is in advanced view, so it's not visible in basic view
794+
expect(
795+
screen.queryByTestId("enable-confirmation-mode-switch"),
796+
).not.toBeInTheDocument();
797+
792798
// Try to interact with inputs - they should not respond
793799
await userEvent.click(providerInput);
794800
await userEvent.type(apiKeyInput, "test-key");
@@ -935,19 +941,17 @@ describe("SaaS mode", () => {
935941
renderLlmSettingsScreen();
936942
await screen.findByTestId("llm-settings-screen");
937943

938-
// Verify that form elements are disabled for unsubscribed users
939-
const confirmationModeSwitch = screen.getByTestId(
940-
"enable-confirmation-mode-switch",
941-
);
944+
// Verify that basic form elements are disabled for unsubscribed users
945+
const advancedSwitch = screen.getByTestId("advanced-settings-switch");
942946
const submitButton = screen.getByTestId("submit-button");
943947

944-
expect(confirmationModeSwitch).not.toBeChecked();
945-
expect(confirmationModeSwitch).toBeDisabled();
948+
expect(advancedSwitch).toBeDisabled();
946949
expect(submitButton).toBeDisabled();
947950

948-
// Try to click the disabled confirmation mode switch - it should not change state
949-
await userEvent.click(confirmationModeSwitch);
950-
expect(confirmationModeSwitch).not.toBeChecked(); // Should remain unchecked
951+
// Confirmation mode switch is in advanced view, which can't be accessed when form is disabled
952+
expect(
953+
screen.queryByTestId("enable-confirmation-mode-switch"),
954+
).not.toBeInTheDocument();
951955

952956
// Try to submit the form - button should remain disabled
953957
await userEvent.click(submitButton);
@@ -1107,14 +1111,17 @@ describe("SaaS mode", () => {
11071111
const providerInput = screen.getByTestId("llm-provider-input");
11081112
const modelInput = screen.getByTestId("llm-model-input");
11091113
const apiKeyInput = screen.getByTestId("llm-api-key-input");
1110-
const confirmationModeSwitch = screen.getByTestId(
1111-
"enable-confirmation-mode-switch",
1112-
);
1114+
const advancedSwitch = screen.getByTestId("advanced-settings-switch");
11131115

11141116
expect(providerInput).toBeDisabled();
11151117
expect(modelInput).toBeDisabled();
11161118
expect(apiKeyInput).toBeDisabled();
1117-
expect(confirmationModeSwitch).toBeDisabled();
1119+
expect(advancedSwitch).toBeDisabled();
1120+
1121+
// Confirmation mode switch is in advanced view, which can't be accessed when form is disabled
1122+
expect(
1123+
screen.queryByTestId("enable-confirmation-mode-switch"),
1124+
).not.toBeInTheDocument();
11181125
});
11191126
});
11201127
});

frontend/src/routes/conversation.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@ import { WebSocketProviderWrapper } from "#/contexts/websocket-provider-wrapper"
3030
import { useErrorMessageStore } from "#/stores/error-message-store";
3131
import { useUnifiedResumeConversationSandbox } from "#/hooks/mutation/use-unified-start-conversation";
3232
import { I18nKey } from "#/i18n/declaration";
33+
import { useEventStore } from "#/stores/use-event-store";
3334

3435
function AppContent() {
3536
useConversationConfig();
3637

3738
const { t } = useTranslation();
3839
const { conversationId } = useConversationId();
40+
const clearEvents = useEventStore((state) => state.clearEvents);
3941

4042
// Handle both task IDs (task-{uuid}) and regular conversation IDs
4143
const { isTask, taskStatus, taskDetail } = useTaskPolling();
@@ -72,6 +74,7 @@ function AppContent() {
7274
resetConversationState();
7375
setCurrentAgentState(AgentState.LOADING);
7476
removeErrorMessage();
77+
clearEvents();
7578

7679
// Reset tracking ONLY if we're navigating to a DIFFERENT conversation
7780
// Don't reset on StrictMode remounts (conversationId is the same)
@@ -85,6 +88,7 @@ function AppContent() {
8588
resetConversationState,
8689
setCurrentAgentState,
8790
removeErrorMessage,
91+
clearEvents,
8892
]);
8993

9094
// 2. Task Error Display Effect

frontend/src/routes/llm-settings.tsx

Lines changed: 60 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -531,34 +531,6 @@ function LlmSettingsScreen() {
531531
linkText={t(I18nKey.SETTINGS$CLICK_FOR_INSTRUCTIONS)}
532532
href="https://docs.all-hands.dev/usage/local-setup#getting-an-api-key"
533533
/>
534-
535-
{config?.APP_MODE !== "saas" && (
536-
<SettingsInput
537-
testId="search-api-key-input"
538-
name="search-api-key-input"
539-
label={t(I18nKey.SETTINGS$SEARCH_API_KEY)}
540-
type="password"
541-
className="w-full max-w-[680px]"
542-
defaultValue={settings.SEARCH_API_KEY || ""}
543-
onChange={handleSearchApiKeyIsDirty}
544-
placeholder={t(I18nKey.API$TAVILY_KEY_EXAMPLE)}
545-
isDisabled={shouldShowUpgradeBanner}
546-
startContent={
547-
settings.SEARCH_API_KEY_SET && (
548-
<KeyStatusIcon isSet={settings.SEARCH_API_KEY_SET} />
549-
)
550-
}
551-
/>
552-
)}
553-
554-
{config?.APP_MODE !== "saas" && (
555-
<HelpLink
556-
testId="search-api-key-help-anchor"
557-
text={t(I18nKey.SETTINGS$SEARCH_API_KEY_OPTIONAL)}
558-
linkText={t(I18nKey.SETTINGS$SEARCH_API_KEY_INSTRUCTIONS)}
559-
href="https://tavily.com/"
560-
/>
561-
)}
562534
</div>
563535
)}
564536

@@ -686,68 +658,68 @@ function LlmSettingsScreen() {
686658
>
687659
{t(I18nKey.SETTINGS$ENABLE_MEMORY_CONDENSATION)}
688660
</SettingsSwitch>
689-
</div>
690-
)}
691-
692-
{/* Confirmation mode and security analyzer - always visible */}
693-
<div className="flex items-center gap-2">
694-
<SettingsSwitch
695-
testId="enable-confirmation-mode-switch"
696-
name="enable-confirmation-mode-switch"
697-
onToggle={handleConfirmationModeIsDirty}
698-
defaultIsToggled={settings.CONFIRMATION_MODE}
699-
isBeta
700-
isDisabled={shouldShowUpgradeBanner}
701-
>
702-
{t(I18nKey.SETTINGS$CONFIRMATION_MODE)}
703-
</SettingsSwitch>
704-
<TooltipButton
705-
tooltip={t(I18nKey.SETTINGS$CONFIRMATION_MODE_TOOLTIP)}
706-
ariaLabel={t(I18nKey.SETTINGS$CONFIRMATION_MODE)}
707-
className="text-[#9099AC] hover:text-white cursor-help"
708-
>
709-
<QuestionCircleIcon width={16} height={16} />
710-
</TooltipButton>
711-
</div>
712661

713-
{confirmationModeEnabled && (
714-
<>
715-
<div className="w-full max-w-[680px]">
716-
<SettingsDropdownInput
717-
testId="security-analyzer-input"
718-
name="security-analyzer-display"
719-
label={t(I18nKey.SETTINGS$SECURITY_ANALYZER)}
720-
items={getSecurityAnalyzerOptions()}
721-
placeholder={t(
722-
I18nKey.SETTINGS$SECURITY_ANALYZER_PLACEHOLDER,
723-
)}
724-
selectedKey={selectedSecurityAnalyzer || "none"}
725-
isClearable={false}
726-
onSelectionChange={(key) => {
727-
const newValue = key?.toString() || "";
728-
setSelectedSecurityAnalyzer(newValue);
729-
handleSecurityAnalyzerIsDirty(newValue);
730-
}}
731-
onInputChange={(value) => {
732-
// Handle when input is cleared
733-
if (!value) {
734-
setSelectedSecurityAnalyzer("");
735-
handleSecurityAnalyzerIsDirty("");
736-
}
737-
}}
738-
wrapperClassName="w-full"
739-
/>
740-
{/* Hidden input to store the actual key value for form submission */}
741-
<input
742-
type="hidden"
743-
name="security-analyzer-input"
744-
value={selectedSecurityAnalyzer || ""}
745-
/>
662+
{/* Confirmation mode and security analyzer */}
663+
<div className="flex items-center gap-2">
664+
<SettingsSwitch
665+
testId="enable-confirmation-mode-switch"
666+
name="enable-confirmation-mode-switch"
667+
onToggle={handleConfirmationModeIsDirty}
668+
defaultIsToggled={settings.CONFIRMATION_MODE}
669+
isBeta
670+
isDisabled={shouldShowUpgradeBanner}
671+
>
672+
{t(I18nKey.SETTINGS$CONFIRMATION_MODE)}
673+
</SettingsSwitch>
674+
<TooltipButton
675+
tooltip={t(I18nKey.SETTINGS$CONFIRMATION_MODE_TOOLTIP)}
676+
ariaLabel={t(I18nKey.SETTINGS$CONFIRMATION_MODE)}
677+
className="text-[#9099AC] hover:text-white cursor-help"
678+
>
679+
<QuestionCircleIcon width={16} height={16} />
680+
</TooltipButton>
746681
</div>
747-
<p className="text-xs text-tertiary-alt max-w-[680px]">
748-
{t(I18nKey.SETTINGS$SECURITY_ANALYZER_DESCRIPTION)}
749-
</p>
750-
</>
682+
683+
{confirmationModeEnabled && (
684+
<>
685+
<div className="w-full max-w-[680px]">
686+
<SettingsDropdownInput
687+
testId="security-analyzer-input"
688+
name="security-analyzer-display"
689+
label={t(I18nKey.SETTINGS$SECURITY_ANALYZER)}
690+
items={getSecurityAnalyzerOptions()}
691+
placeholder={t(
692+
I18nKey.SETTINGS$SECURITY_ANALYZER_PLACEHOLDER,
693+
)}
694+
selectedKey={selectedSecurityAnalyzer || "none"}
695+
isClearable={false}
696+
onSelectionChange={(key) => {
697+
const newValue = key?.toString() || "";
698+
setSelectedSecurityAnalyzer(newValue);
699+
handleSecurityAnalyzerIsDirty(newValue);
700+
}}
701+
onInputChange={(value) => {
702+
// Handle when input is cleared
703+
if (!value) {
704+
setSelectedSecurityAnalyzer("");
705+
handleSecurityAnalyzerIsDirty("");
706+
}
707+
}}
708+
wrapperClassName="w-full"
709+
/>
710+
{/* Hidden input to store the actual key value for form submission */}
711+
<input
712+
type="hidden"
713+
name="security-analyzer-input"
714+
value={selectedSecurityAnalyzer || ""}
715+
/>
716+
</div>
717+
<p className="text-xs text-tertiary-alt max-w-[680px]">
718+
{t(I18nKey.SETTINGS$SECURITY_ANALYZER_DESCRIPTION)}
719+
</p>
720+
</>
721+
)}
722+
</div>
751723
)}
752724
</div>
753725

0 commit comments

Comments
 (0)