From 345386ae500273d0c6a3281db4491deecd13f56b Mon Sep 17 00:00:00 2001 From: Bolor Date: Fri, 13 Mar 2026 08:03:05 -0700 Subject: [PATCH 01/13] Add converter catalog panel scaffolding Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../components/Chat/ChatInputArea.styles.ts | 47 +++++++ .../components/Chat/ChatInputArea.test.tsx | 21 ++- .../src/components/Chat/ChatInputArea.tsx | 15 ++- .../src/components/Chat/ChatWindow.test.tsx | 107 ++++++++++++++- frontend/src/components/Chat/ChatWindow.tsx | 7 + .../components/Chat/ConverterPanel.styles.ts | 85 ++++++++++++ .../src/components/Chat/ConverterPanel.tsx | 122 ++++++++++++++++++ frontend/src/services/api.ts | 21 +++ frontend/src/types/index.ts | 26 ++++ pyrit/backend/models/converters.py | 25 ++++ pyrit/backend/routes/converters.py | 16 +++ pyrit/backend/services/converter_service.py | 27 ++++ tests/unit/backend/test_api_routes.py | 24 ++++ tests/unit/backend/test_converter_service.py | 26 ++++ 14 files changed, 565 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components/Chat/ConverterPanel.styles.ts create mode 100644 frontend/src/components/Chat/ConverterPanel.tsx diff --git a/frontend/src/components/Chat/ChatInputArea.styles.ts b/frontend/src/components/Chat/ChatInputArea.styles.ts index 75b67ff532..73a42efb9f 100644 --- a/frontend/src/components/Chat/ChatInputArea.styles.ts +++ b/frontend/src/components/Chat/ChatInputArea.styles.ts @@ -79,6 +79,7 @@ export const useChatInputAreaStyles = makeStyles({ display: 'flex', gap: tokens.spacingHorizontalXS, marginRight: tokens.spacingHorizontalS, + alignItems: 'center', }, iconButtonsRight: { display: 'flex', @@ -131,4 +132,50 @@ export const useChatInputAreaStyles = makeStyles({ color: tokens.colorPaletteRedForeground1, fontWeight: tokens.fontWeightSemibold as unknown as string, }, + conversionBarBottom: { + display: 'flex', + alignItems: 'center', + gap: tokens.spacingHorizontalXS, + padding: `${tokens.spacingVerticalXS} ${tokens.spacingHorizontalL}`, + paddingLeft: `calc(${tokens.spacingHorizontalL} + 32px + ${tokens.spacingHorizontalXS} + 32px + ${tokens.spacingHorizontalS})`, + borderTop: `1px solid ${tokens.colorNeutralStroke1}`, + backgroundColor: tokens.colorNeutralBackground4, + overflow: 'hidden', + }, + conversionLabel: { + display: 'flex', + alignItems: 'center', + gap: tokens.spacingHorizontalXXS, + flex: 1, + minWidth: 0, + overflow: 'hidden', + }, + conversionText: { + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + minWidth: 0, + flex: 1, + }, + originalBadge: { + display: 'inline-block', + padding: `0 ${tokens.spacingHorizontalXS}`, + marginRight: tokens.spacingHorizontalXS, + borderRadius: tokens.borderRadiusSmall, + backgroundColor: tokens.colorPaletteBlueBackground2, + color: tokens.colorPaletteBlueForeground2, + fontSize: tokens.fontSizeBase100, + fontWeight: tokens.fontWeightSemibold as unknown as string, + flexShrink: 0, + }, + convertedBadge: { + display: 'inline-block', + padding: `0 ${tokens.spacingHorizontalXS}`, + borderRadius: tokens.borderRadiusSmall, + backgroundColor: tokens.colorPaletteGreenBackground2, + color: tokens.colorPaletteGreenForeground2, + fontSize: tokens.fontSizeBase100, + fontWeight: tokens.fontWeightSemibold as unknown as string, + flexShrink: 0, + }, }) diff --git a/frontend/src/components/Chat/ChatInputArea.test.tsx b/frontend/src/components/Chat/ChatInputArea.test.tsx index c2712acddf..777be2dd08 100644 --- a/frontend/src/components/Chat/ChatInputArea.test.tsx +++ b/frontend/src/components/Chat/ChatInputArea.test.tsx @@ -26,12 +26,31 @@ describe("ChatInputArea", () => { it("should render input area and send button", () => { render( - + ); expect(screen.getByRole("textbox")).toBeInTheDocument(); expect(getSendButton()).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /convert/i })).toBeInTheDocument(); + }); + + it("should call converter panel toggle handler when convert button is clicked", async () => { + const user = userEvent.setup(); + const onToggleConverterPanel = jest.fn(); + + render( + + + + ); + + await user.click(screen.getByRole("button", { name: /convert/i })); + + expect(onToggleConverterPanel).toHaveBeenCalledTimes(1); }); it("should call onSend with input value when send button clicked", async () => { diff --git a/frontend/src/components/Chat/ChatInputArea.tsx b/frontend/src/components/Chat/ChatInputArea.tsx index 12ef7b3d23..3b9322d197 100644 --- a/frontend/src/components/Chat/ChatInputArea.tsx +++ b/frontend/src/components/Chat/ChatInputArea.tsx @@ -1,10 +1,10 @@ import { useState, useEffect, useLayoutEffect, useRef, forwardRef, useImperativeHandle, KeyboardEvent } from 'react' import { Button, - tokens, Caption1, Tooltip, Text, + tokens, } from '@fluentui/react-components' import { SendRegular, AttachRegular, DismissRegular, InfoRegular, AddRegular, CopyRegular, WarningRegular, SettingsRegular } from '@fluentui/react-icons' import { MessageAttachment, TargetInstance } from '../../types' @@ -68,9 +68,11 @@ interface ChatInputAreaProps { attackOperator?: string noTargetSelected?: boolean onConfigureTarget?: () => void + onToggleConverterPanel?: () => void + isConverterPanelOpen?: boolean } -const ChatInputArea = forwardRef(function ChatInputArea({ onSend, disabled = false, activeTarget, singleTurnLimitReached = false, onNewConversation, operatorLocked = false, crossTargetLocked = false, onUseAsTemplate, attackOperator, noTargetSelected = false, onConfigureTarget }, ref) { +const ChatInputArea = forwardRef(function ChatInputArea({ onSend, disabled = false, activeTarget, singleTurnLimitReached = false, onNewConversation, operatorLocked = false, crossTargetLocked = false, onUseAsTemplate, attackOperator, noTargetSelected = false, onConfigureTarget, onToggleConverterPanel, isConverterPanelOpen = false }, ref) { const styles = useChatInputAreaStyles() const [input, setInput] = useState('') const [attachments, setAttachments] = useState([]) @@ -261,6 +263,15 @@ const ChatInputArea = forwardRef(functi disabled={disabled} title="Attach files" /> +