From 4f03b8f6e607a46db9df3d4917e2987972d79235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Fri, 29 May 2026 16:22:57 +0800 Subject: [PATCH] feat(desktop): show open-with affordance on URLs rendered as inline code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the injection effect in AssistantMessage to also append a ▾ trigger after inline elements whose text is a browser-localhost or remote URL, so AI-written run URLs like \`http://localhost:9527/\` get the same open-with menu that markdown links already receive. Co-Authored-By: Claude Sonnet 4.6 --- .../AssistantMessage.linkrouting.test.tsx | 43 +++++++++++++++++++ .../src/components/chat/AssistantMessage.tsx | 24 ++++++++--- 2 files changed, 62 insertions(+), 5 deletions(-) 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])