From c0a2cfd443afa5b7199f5b3471c1a487a0b1a8c9 Mon Sep 17 00:00:00 2001 From: Debsmita Santra Date: Wed, 4 Mar 2026 16:27:30 +0530 Subject: [PATCH 1/2] fix(lightspeed): fixed newchat cta behavior --- .../.changeset/smart-turkeys-watch.md | 7 +++ .../src/components/LightSpeedChat.tsx | 5 +- .../components/LightspeedChatBoxHeader.tsx | 63 ++++++++----------- .../src/hooks/useConversationMessages.ts | 22 +++---- 4 files changed, 47 insertions(+), 50 deletions(-) create mode 100644 workspaces/lightspeed/.changeset/smart-turkeys-watch.md diff --git a/workspaces/lightspeed/.changeset/smart-turkeys-watch.md b/workspaces/lightspeed/.changeset/smart-turkeys-watch.md new file mode 100644 index 0000000000..1243ea57b4 --- /dev/null +++ b/workspaces/lightspeed/.changeset/smart-turkeys-watch.md @@ -0,0 +1,7 @@ +--- +'@red-hat-developer-hub/backstage-plugin-lightspeed': patch +--- + +Fixed "new chat" cta behavior +Added vertical scroll when too many models are available +Removed model grouping/categories in the model selector dropdown diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx index 9593620eba..31bc260e06 100644 --- a/workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx +++ b/workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx @@ -225,8 +225,11 @@ export const LightspeedChat = ({ const { allowed: hasUpdateAccess } = useLightspeedUpdatePermission(); const samplePrompts = useWelcomePrompts(); useEffect(() => { - if (user && lastOpenedId === null && isReady) { + if (!user || !isReady) return; + if (lastOpenedId === null) { setConversationId(TEMP_CONVERSATION_ID); + } + if (lastOpenedId === TEMP_CONVERSATION_ID || lastOpenedId === null) { setNewChatCreated(true); } }, [user, isReady, lastOpenedId, setConversationId]); diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatBoxHeader.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatBoxHeader.tsx index bbfe819630..0ce0edf6cc 100644 --- a/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatBoxHeader.tsx +++ b/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatBoxHeader.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Ref, useMemo, useState } from 'react'; +import { Ref, useState } from 'react'; import { createStyles, makeStyles } from '@material-ui/core'; import ToggleOffOutlinedIcon from '@mui/icons-material/ToggleOffOutlined'; @@ -89,22 +89,12 @@ export const LightspeedChatBoxHeader = ({ const styles = useStyles(); - // Group models by provider - const groupedModels = useMemo(() => { - const groups: { - [key: string]: { label: string; value: string; provider: string }[]; - } = {}; - - models.forEach(model => { - const provider = model.provider || t('chatbox.provider.other'); - if (!groups[provider]) { - groups[provider] = []; - } - groups[provider].push(model); - }); - - return groups; - }, [models, t]); + const maxLabelLength = Math.max( + ...models.map(m => m.label.length), + selectedModel.length, + 1, + ); + const toggleMinWidth = `${maxLabelLength + 4}ch`; const toggle = (toggleRef: Ref) => ( setIsOptionsMenuOpen(!isOptionsMenuOpen)} + style={{ minWidth: toggleMinWidth }} > {selectedModel} @@ -136,6 +127,10 @@ export const LightspeedChatBoxHeader = ({ setDisplayMode(ChatbotDisplayMode.default); }; + const isOverlayMode = displayMode === ChatbotDisplayMode.default; + const scrollThreshold = isOverlayMode ? 8 : 10; + const isModelDropdownScrollable = models.length > scrollThreshold; + return ( - {Object.entries(groupedModels).map( - ([provider, providerModels], index) => ( - <> - - {providerModels.map(model => ( - - {model.label} - - ))} - - {index < Object.entries(groupedModels).length - 1 && ( - - )} - - ), - )} + {models.map(model => ( + + + {model.label} + + + ))} Promise; conversations: Conversations; - scrollToBottomRef: React.RefObject; + scrollToBottomRef: RefObject; data?: BaseMessage[] | undefined; error: Error | null; isPending: boolean; @@ -105,25 +105,25 @@ export const useConversationMessages = ( onStart?: (conversation_id: string) => void, ): UseConversationMessagesReturn => { const { mutateAsync: createMessage } = useCreateConversationMessage(); - const scrollToBottomRef = React.useRef(null); + const scrollToBottomRef = useRef(null); const [currentConversation, setCurrentConversation] = - React.useState(conversationId); - const [conversations, setConversations] = React.useState({ + useState(conversationId); + const [conversations, setConversations] = useState({ [currentConversation]: [], }); - const streamingConversations = React.useRef({ + const streamingConversations = useRef({ [currentConversation]: [], }); // Track pending tool calls during streaming - const pendingToolCalls = React.useRef<{ [id: number]: ToolCall }>({}); + const pendingToolCalls = useRef<{ [id: number]: ToolCall }>({}); // Cache tool calls by conversation ID and message index to persist across refetches // Key format: `${conversationId}-${messageIndex}` - const toolCallsCache = React.useRef<{ [key: string]: ToolCall[] }>({}); + const toolCallsCache = useRef<{ [key: string]: ToolCall[] }>({}); - React.useEffect(() => { + useEffect(() => { if (currentConversation !== conversationId) { setCurrentConversation(conversationId); setConversations(prev => { @@ -140,7 +140,7 @@ export const useConversationMessages = ( const { data: conversationsData = [], ...queryProps } = useFetchConversationMessages(currentConversation); - React.useEffect(() => { + useEffect(() => { if ( !Array.isArray(conversationsData) || (conversationsData.length === 0 && @@ -212,7 +212,7 @@ export const useConversationMessages = ( streamingConversations, ]); - const handleInputPrompt = React.useCallback( + const handleInputPrompt = useCallback( async (prompt: string, attachments: Attachment[] = []) => { let newConversationId = ''; From ff6294046f97ef0a9e6b7dfc9b5398378f0cbe01 Mon Sep 17 00:00:00 2001 From: HusneShabbir Date: Thu, 5 Mar 2026 12:31:40 +0530 Subject: [PATCH 2/2] fix(lightspeed): verify no-results message by heading and text only, remove button dependency Made-with: Cursor --- .../packages/app/e2e-tests/utils/chatManagement.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/workspaces/lightspeed/packages/app/e2e-tests/utils/chatManagement.ts b/workspaces/lightspeed/packages/app/e2e-tests/utils/chatManagement.ts index 417490d86f..6975938bb6 100644 --- a/workspaces/lightspeed/packages/app/e2e-tests/utils/chatManagement.ts +++ b/workspaces/lightspeed/packages/app/e2e-tests/utils/chatManagement.ts @@ -345,11 +345,14 @@ export const verifyNoResultsFoundMessage = async ( page: Page, translations: LightspeedMessages, ) => { - await expect(page.getByLabel(translations['button.newChat'])) - .toMatchAriaSnapshot(` - - heading "${translations['chatbox.emptyState.noResults.title']}" - - text: ${translations['chatbox.emptyState.noResults.body']} - `); + await expect( + page.getByRole('heading', { + name: translations['chatbox.emptyState.noResults.title'], + }), + ).toBeVisible(); + await expect( + page.getByText(translations['chatbox.emptyState.noResults.body']), + ).toBeVisible(); }; export const verifyChatUnpinned = async (