From d4eeeb8f8055e1d5f90607f8cfcbf28b89618952 Mon Sep 17 00:00:00 2001 From: AidanShipperley <70974869+AidanShipperley@users.noreply.github.com> Date: Sat, 7 Sep 2024 06:33:08 -0400 Subject: [PATCH] Allow clicking links in chat profile description (#1276), resolves #1256 This pull request resolves #1256, where users are unable to interact with chat profile descriptions, such as scrolling through long descriptions or clicking links within them. The modifications ensure that the description popover remains open and interactable when the mouse hovers over it, and closes appropriately when the mouse leaves the popover area or a selection is made. --- cypress/e2e/chat_profiles/main.py | 2 +- cypress/e2e/chat_profiles/spec.cy.ts | 56 +++++++++++++++++++ .../src/components/molecules/chatProfiles.tsx | 25 +++++++-- 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/cypress/e2e/chat_profiles/main.py b/cypress/e2e/chat_profiles/main.py index 04446a42c0..bc38e29c86 100644 --- a/cypress/e2e/chat_profiles/main.py +++ b/cypress/e2e/chat_profiles/main.py @@ -30,7 +30,7 @@ async def chat_profile(current_user: cl.User): ), cl.ChatProfile( name="GPT-4", - markdown_description="The underlying LLM model is **GPT-4**, a *1.5T parameter model* trained on 3.5TB of text data.", + markdown_description="The underlying LLM model is **GPT-4**, a *1.5T parameter model* trained on 3.5TB of text data. [Learn more](https://example.com/gpt4)", icon="https://picsum.photos/250", starters=starters, ), diff --git a/cypress/e2e/chat_profiles/spec.cy.ts b/cypress/e2e/chat_profiles/spec.cy.ts index f80cb5f0da..0558691fe9 100644 --- a/cypress/e2e/chat_profiles/spec.cy.ts +++ b/cypress/e2e/chat_profiles/spec.cy.ts @@ -67,4 +67,60 @@ describe('Chat profiles', () => { cy.get('#starter-ask-for-help').should('exist'); }); + + it('should keep chat profile description visible when hovering over a link', () => { + cy.visit('/'); + cy.get("input[name='email']").type('admin'); + cy.get("input[name='password']").type('admin'); + cy.get("button[type='submit']").click(); + cy.get('#chat-input').should('exist'); + + cy.get('#chat-profile-selector').parent().click(); + + // Force hover over GPT-4 profile to show description + cy.get('[data-test="select-item:GPT-4"]').trigger('mouseover', { force: true }); + + // Wait for the popover to appear and check its content + cy.get('#chat-profile-description').within(() => { + cy.contains('Learn more').should('be.visible'); + }); + + // Check if the link is present in the description and has correct attributes + const linkSelector = '#chat-profile-description a:contains("Learn more")'; + cy.get(linkSelector) + .should('have.attr', 'href', 'https://example.com/gpt4') + .and('have.attr', 'target', '_blank'); + + // Move mouse to the link + cy.get(linkSelector).trigger('mouseover', { force: true }); + + // Verify that the description is still visible after + cy.get('#chat-profile-description').within(() => { + cy.contains('Learn more').should('be.visible'); + }); + + // Verify that the link is still present and clickable + cy.get(linkSelector) + .should('exist') + .and('be.visible') + .and('not.have.css', 'pointer-events', 'none') + .and('not.have.attr', 'disabled'); + + // Ensure the chat profile selector is still open + cy.get('[data-test="select-item:GPT-4"]').should('be.visible'); + + // Select GPT-4 profile + cy.get('[data-test="select-item:GPT-4"]').click(); + + // Verify the profile has been changed + submitMessage('hello'); + cy.get('.step') + .should('have.length', 2) + .last() + .should( + 'contain', + 'starting chat with admin using the GPT-4 chat profile' + ); + + }); }); diff --git a/frontend/src/components/molecules/chatProfiles.tsx b/frontend/src/components/molecules/chatProfiles.tsx index 06f4f30029..08f2df111c 100644 --- a/frontend/src/components/molecules/chatProfiles.tsx +++ b/frontend/src/components/molecules/chatProfiles.tsx @@ -27,6 +27,7 @@ export default function ChatProfiles() { const { clear } = useChatInteract(); const [newChatProfile, setNewChatProfile] = useState(null); const [openDialog, setOpenDialog] = useState(false); + const [popoverOpen, setPopoverOpen] = useState(false); const navigate = useNavigate(); const handleClose = () => { @@ -58,8 +59,6 @@ export default function ChatProfiles() { const allowHtml = config?.features?.unsafe_allow_html; const latex = config?.features?.latex; - const popoverOpen = Boolean(anchorEl); - const items = config.chatProfiles.map((item) => { const icon = item.icon?.includes('/public') ? apiClient.buildEndpoint(item.icon) @@ -94,7 +93,8 @@ export default function ChatProfiles() { theme.palette.mode === 'light' ? '0px 2px 4px 0px #0000000D' : '0px 10px 10px 0px #0000000D', - ml: 2 + ml: 2, + pointerEvents: 'auto' // Allow mouse interaction with the chat profile description } }} sx={{ @@ -110,6 +110,11 @@ export default function ChatProfiles() { horizontal: 'left' }} disableRestoreFocus + onMouseEnter={() => setPopoverOpen(true)} + onMouseLeave={() => { + setPopoverOpen(false); + setAnchorEl(null); + }} > setAnchorEl(null)} + onItemMouseLeave={() => setPopoverOpen(false)} onChange={(e) => { const newValue = e.target.value; + + // Close the chat profile description when any selection is made + setPopoverOpen(false); + setAnchorEl(null); + + // Handle user selection setNewChatProfile(newValue); if (firstInteraction) { setOpenDialog(true); @@ -145,7 +157,10 @@ export default function ChatProfiles() { handleConfirm(newValue); } }} - onClose={() => setAnchorEl(null)} + onClose={() => { + setPopoverOpen(false); + setAnchorEl(null); + }} />