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)