diff --git a/frontend/src/components/common/PromptStudioModal.css b/frontend/src/components/common/PromptStudioModal.css
index f3f5916c55..4676b8affb 100644
--- a/frontend/src/components/common/PromptStudioModal.css
+++ b/frontend/src/components/common/PromptStudioModal.css
@@ -105,7 +105,6 @@
.prompt-studio-guide-btn {
transition: all 0.2s ease-in-out !important;
- margin-top: 8px !important;
height: 32px !important;
font-size: 13px !important;
padding: 4px 0 !important;
@@ -123,7 +122,6 @@
.prompt-studio-cancel-btn {
transition: all 0.2s ease-in-out !important;
- margin-top: 8px !important;
height: 32px !important;
font-size: 13px !important;
padding: 4px 0 !important;
diff --git a/frontend/src/components/custom-tools/prompt-card/PromptCard.css b/frontend/src/components/custom-tools/prompt-card/PromptCard.css
index 46f9c5d0e8..2ea89f3da8 100644
--- a/frontend/src/components/custom-tools/prompt-card/PromptCard.css
+++ b/frontend/src/components/custom-tools/prompt-card/PromptCard.css
@@ -77,7 +77,6 @@
width: 100%;
padding: 8px 12px;
background-color: #f5f7f9;
- row-gap: 2;
}
.prompt-card-actions-dropdowns {
@@ -94,7 +93,6 @@
background-color: #fff8e6;
max-height: 300px;
overflow-y: scroll;
- scrollbar-color: inherit #fff8e6 !important;
display: flex;
flex-grow: 1;
}
@@ -186,7 +184,6 @@
.prompt-card-llm-container {
border-right: 1px solid #0000000f;
- width: fill-available !important;
}
.prompt-profile-run-expanded {
diff --git a/frontend/src/components/custom-tools/tool-ide/ToolIde.css b/frontend/src/components/custom-tools/tool-ide/ToolIde.css
index 9a09ed3842..898e8637f6 100644
--- a/frontend/src/components/custom-tools/tool-ide/ToolIde.css
+++ b/frontend/src/components/custom-tools/tool-ide/ToolIde.css
@@ -73,7 +73,6 @@
position: fixed;
transform: translate(-50%, 100%);
z-index: 1;
- transition: "left 0.1s linear";
}
.tool-ide-collapse-panel {
diff --git a/frontend/src/components/navigations/side-nav-bar/SideNavBar.css b/frontend/src/components/navigations/side-nav-bar/SideNavBar.css
index 44d31b9d65..309b7928ca 100644
--- a/frontend/src/components/navigations/side-nav-bar/SideNavBar.css
+++ b/frontend/src/components/navigations/side-nav-bar/SideNavBar.css
@@ -103,7 +103,6 @@
}
.secondary-list-wrapper .sidebar-item-icons {
- margin-top: 1;
height: auto;
width: 25px;
}
diff --git a/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx b/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx
index 98fdbdd635..8dda1e13ad 100644
--- a/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx
+++ b/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx
@@ -30,7 +30,7 @@ import "./TopNavBar.css";
import config from "../../../config";
import { useExceptionHandler } from "../../../hooks/useExceptionHandler.jsx";
import { useAlertStore } from "../../../store/alert-store.js";
-import { ConfirmModal } from "../../widgets/confirm-modal/ConfirmModal.jsx";
+import { openConfirm } from "../../widgets/confirm-modal/confirmStore.js";
let TrialDaysInfo;
try {
@@ -250,28 +250,29 @@ function TopNavBar({ isSimpleLayout, topNavBarOptions }) {
// Prepare org list for switching
const cascadeOptions = useMemo(() => {
return allOrganization?.map((org) => {
+ const isCurrentOrg = org?.id === sessionDetails?.orgId;
return {
key: org?.id,
- label:
- org?.id === sessionDetails?.orgId ? (
-
- setAlertDetails({
- type: "error",
- content: `You are already in ${org?.display_name}`,
- })
- }
- >
- {org?.display_name}
-
- ) : (
- handleContinue(org?.id)}
- content={`Want to switch to ${org?.display_name}?`}
- >
- {org?.display_name}
-
- ),
+ /*
+ * Both actions sit on the menu item, not inside its label: the item is
+ * what the keyboard focuses, and Radix turns Enter/Space on it into a
+ * click on the item itself, which never reaches a descendant's handler.
+ */
+ onClick: isCurrentOrg
+ ? () =>
+ setAlertDetails({
+ type: "error",
+ content: `You are already in ${org?.display_name}`,
+ })
+ : () =>
+ openConfirm({
+ title: "Are you sure?",
+ content: `Want to switch to ${org?.display_name}?`,
+ okText: "Confirm",
+ cancelText: "Cancel",
+ onOk: () => handleContinue(org?.id),
+ }),
+ label: org?.display_name,
};
});
}, [allOrganization, handleContinue]);
diff --git a/frontend/src/components/navigations/top-nav-bar/TopNavBar.test.jsx b/frontend/src/components/navigations/top-nav-bar/TopNavBar.test.jsx
index e5ccd9d913..8b04b82a22 100644
--- a/frontend/src/components/navigations/top-nav-bar/TopNavBar.test.jsx
+++ b/frontend/src/components/navigations/top-nav-bar/TopNavBar.test.jsx
@@ -4,6 +4,7 @@ import axios from "axios";
import { MemoryRouter } from "react-router-dom";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { useAlertStore } from "../../../store/alert-store.js";
import { useSessionStore } from "../../../store/session-store.js";
import { ConfirmHost } from "../../widgets/confirm-modal/ConfirmHost.jsx";
import { resetConfirm } from "../../widgets/confirm-modal/confirmStore.js";
@@ -54,6 +55,7 @@ async function openSwitchOrg(user) {
}
beforeEach(() => {
+ useAlertStore.setState({ alertDetails: {} });
useSessionStore.setState({
sessionDetails: {
isLoggedIn: true,
@@ -103,6 +105,7 @@ describe("TopNavBar Switch Org", () => {
expect(
await screen.findByText("Want to switch to Org B?"),
).toBeInTheDocument();
+ expect(useAlertStore.getState().alertDetails).toEqual({});
await user.click(screen.getByRole("button", { name: "Confirm" }));
expect(axios).toHaveBeenCalledWith(
@@ -112,4 +115,64 @@ describe("TopNavBar Switch Org", () => {
}),
);
});
+
+ it("asks to switch when a keyboard user picks another org", async () => {
+ axios.mockResolvedValue({});
+ const user = userEvent.setup();
+ renderTopNavBar();
+
+ await openSwitchOrg(user);
+ const otherOrg = await screen.findByRole("menuitem", { name: "Org B" });
+ otherOrg.focus();
+ await user.keyboard("{Enter}");
+ expect(
+ await screen.findByText("Want to switch to Org B?"),
+ ).toBeInTheDocument();
+ await user.click(screen.getByRole("button", { name: "Confirm" }));
+
+ expect(axios).toHaveBeenCalledWith(
+ expect.objectContaining({
+ method: "POST",
+ url: "/api/v1/organization/org-b/set",
+ }),
+ );
+ });
+});
+
+describe("TopNavBar current org", () => {
+ it("tells a keyboard user they are already in the org", async () => {
+ const user = userEvent.setup();
+ renderTopNavBar();
+
+ await openSwitchOrg(user);
+ const currentOrg = await screen.findByRole("menuitem", { name: "Org A" });
+ currentOrg.focus();
+ await user.keyboard("{Enter}");
+
+ expect(useAlertStore.getState().alertDetails).toMatchObject({
+ type: "error",
+ content: "You are already in Org A",
+ });
+ expect(
+ screen.queryByText("Want to switch to Org A?"),
+ ).not.toBeInTheDocument();
+ expect(axios).not.toHaveBeenCalled();
+ });
+
+ it("tells a mouse user they are already in the org", async () => {
+ const user = userEvent.setup();
+ renderTopNavBar();
+
+ await openSwitchOrg(user);
+ await user.click(await screen.findByText("Org A"));
+
+ expect(useAlertStore.getState().alertDetails).toMatchObject({
+ type: "error",
+ content: "You are already in Org A",
+ });
+ expect(
+ screen.queryByText("Want to switch to Org A?"),
+ ).not.toBeInTheDocument();
+ expect(axios).not.toHaveBeenCalled();
+ });
});
diff --git a/x2text-service/app/controllers/controller.py b/x2text-service/app/controllers/controller.py
index 195cef9682..ed713c4533 100644
--- a/x2text-service/app/controllers/controller.py
+++ b/x2text-service/app/controllers/controller.py
@@ -89,7 +89,13 @@ def process() -> Any:
if uploaded_file.filename == "":
return {"message": "No selected file"}, 400
- file_size_in_kb = int(request.headers["Content-Length"]) / 1024
+ # Missing (chunked upload) or non-integer Content-Length returns 400, not a 500.
+ content_length = request.headers.get("Content-Length")
+ try:
+ file_size_in_kb = int(content_length) / 1024
+ except (TypeError, ValueError):
+ logging.warning("Rejecting /process: invalid Content-Length %r", content_length)
+ return {"message": "Missing or invalid Content-Length header"}, 400
bearer_token = AuthenticationMiddleware.get_token_from_auth_header(request)
_, org_id = AuthenticationMiddleware.get_organization_from_bearer_token(bearer_token)