diff --git a/desktop/src/components/chat/AssistantMessage.linkrouting.test.tsx b/desktop/src/components/chat/AssistantMessage.linkrouting.test.tsx index c0de7ea7..e5ec5927 100644 --- a/desktop/src/components/chat/AssistantMessage.linkrouting.test.tsx +++ b/desktop/src/components/chat/AssistantMessage.linkrouting.test.tsx @@ -130,3 +130,46 @@ describe('AssistantMessage open-with trigger injection', () => { expect(screen.getByText('openWith.systemBrowser')).toBeInTheDocument() }) }) + +describe('AssistantMessage open-with trigger injection — inline code URLs', () => { + it('injects a ▾ trigger next to an inline-code localhost URL', async () => { + render( + , + ) + // The trigger comes from the path (no in this content) + expect(screen.getByLabelText('打开方式')).toBeInTheDocument() + }) + + it('clicking the trigger next to inline-code URL opens menu with in-app-browser and system-browser items', async () => { + render( + , + ) + + const trigger = screen.getByLabelText('打开方式') + fireEvent.click(trigger) + + await waitFor(() => { + expect(screen.getByText('openWith.inAppBrowser')).toBeInTheDocument() + }) + expect(screen.getByText('openWith.systemBrowser')).toBeInTheDocument() + }) + + it('does NOT inject a trigger for non-URL inline code', () => { + render( + , + ) + expect(screen.queryByLabelText('打开方式')).toBeNull() + }) +}) diff --git a/desktop/src/components/chat/AssistantMessage.tsx b/desktop/src/components/chat/AssistantMessage.tsx index 4b72cd72..24b285f5 100644 --- a/desktop/src/components/chat/AssistantMessage.tsx +++ b/desktop/src/components/chat/AssistantMessage.tsx @@ -52,10 +52,8 @@ export const AssistantMessage = memo(function AssistantMessage({ content, isStre useEffect(() => { const root = contentRef.current if (!root || !sessionId || isStreaming) return - root.querySelectorAll('a[href]').forEach((link) => { - const href = link.getAttribute('href') ?? '' - if (classifyPreviewLink(href).kind === 'ignored') return - if (link.nextElementSibling instanceof HTMLElement && link.nextElementSibling.classList.contains('md-open-with')) return + + function makeTrigger(href: string): HTMLButtonElement { const trigger = document.createElement('button') trigger.type = 'button' trigger.className = 'md-open-with' @@ -64,7 +62,23 @@ export const AssistantMessage = memo(function AssistantMessage({ content, isStre trigger.tabIndex = -1 trigger.textContent = '▾' trigger.style.cssText = 'margin-left:3px;padding:0 3px;border:none;background:transparent;color:var(--color-text-tertiary);cursor:pointer;font-size:11px;line-height:1;vertical-align:middle' - link.after(trigger) + return trigger + } + + root.querySelectorAll('a[href]').forEach((link) => { + const href = link.getAttribute('href') ?? '' + if (classifyPreviewLink(href).kind === 'ignored') return + if (link.nextElementSibling instanceof HTMLElement && link.nextElementSibling.classList.contains('md-open-with')) return + link.after(makeTrigger(href)) + }) + + root.querySelectorAll('code').forEach((code) => { + if (code.closest('pre')) return + const text = (code.textContent ?? '').trim() + const kind = classifyPreviewLink(text).kind + if (kind !== 'browser-localhost' && kind !== 'remote') return + if (code.nextElementSibling instanceof HTMLElement && code.nextElementSibling.classList.contains('md-open-with')) return + code.after(makeTrigger(text)) }) }, [content, isStreaming, sessionId])