From 361f31c0e4ecc16430a0e5513cfe90d4b3822a8d Mon Sep 17 00:00:00 2001
From: Xubin Ren <52506698+Re-bin@users.noreply.github.com>
Date: Sun, 17 May 2026 23:52:29 +0800
Subject: [PATCH] fix(webui): use portal file reference tooltips
---
webui/src/components/MarkdownTextRenderer.tsx | 4 ++
webui/src/components/ui/tooltip.tsx | 20 +++++----
webui/src/tests/message-bubble.test.tsx | 43 ++++++++++++++++++-
3 files changed, 57 insertions(+), 10 deletions(-)
diff --git a/webui/src/components/MarkdownTextRenderer.tsx b/webui/src/components/MarkdownTextRenderer.tsx
index ff75004a7..aa757ff00 100644
--- a/webui/src/components/MarkdownTextRenderer.tsx
+++ b/webui/src/components/MarkdownTextRenderer.tsx
@@ -6,6 +6,7 @@ import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import { CodeBlock } from "@/components/CodeBlock";
+import { FileReferenceChip, isLikelyFilePath } from "@/components/FileReferenceChip";
import { cn } from "@/lib/utils";
import "katex/dist/katex.min.css";
@@ -44,6 +45,9 @@ export default function MarkdownTextRenderer({
);
}
const raw = String(kids).replace(/\n$/, "");
+ if (isLikelyFilePath(raw)) {
+ return ;
+ }
/** Plain fenced ``` blocks (no language) & wide one-liners: block monospace, not inline pill. */
const widePlainBlock = raw.includes("\n") || raw.length > 120;
if (widePlainBlock) {
diff --git a/webui/src/components/ui/tooltip.tsx b/webui/src/components/ui/tooltip.tsx
index 95f7960c8..d69f12275 100644
--- a/webui/src/components/ui/tooltip.tsx
+++ b/webui/src/components/ui/tooltip.tsx
@@ -11,15 +11,17 @@ const TooltipContent = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, sideOffset = 4, ...props }, ref) => (
-
+
+
+
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
diff --git a/webui/src/tests/message-bubble.test.tsx b/webui/src/tests/message-bubble.test.tsx
index 410fbabaf..baae344dc 100644
--- a/webui/src/tests/message-bubble.test.tsx
+++ b/webui/src/tests/message-bubble.test.tsx
@@ -1,4 +1,4 @@
-import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { MessageBubble } from "@/components/MessageBubble";
@@ -179,6 +179,47 @@ describe("MessageBubble", () => {
expect(screen.getByText("Body line.")).toBeInTheDocument();
});
+ it("renders inline file paths as compact file references", async () => {
+ await import("@/components/MarkdownTextRenderer");
+ const message: UIMessage = {
+ id: "a-file-path",
+ role: "assistant",
+ content:
+ "改动在 `webui/src/components/MarkdownTextRenderer.tsx` 和 `/Users/renxubin/.nanobot/workspace/minecraft-fps/index.html`。",
+ createdAt: Date.now(),
+ };
+
+ try {
+ render();
+
+ const references = await screen.findAllByTestId("inline-file-path");
+ expect(references).toHaveLength(2);
+ expect(references[0].parentElement).not.toHaveClass("translate-y-[0.08em]");
+ expect(references[0].parentElement).toHaveClass("align-[0.14em]");
+ expect(references[0]).toHaveTextContent("MarkdownTextRenderer.tsx");
+ expect(references[0]).not.toHaveTextContent("webui/src/components");
+ expect(screen.getByText("index.html")).toBeInTheDocument();
+ expect(references[1]).not.toHaveTextContent("/Users/renxubin");
+ expect(references[1]).not.toHaveAttribute("title");
+ expect(references[1]).toHaveAttribute(
+ "aria-label",
+ "/Users/renxubin/.nanobot/workspace/minecraft-fps/index.html",
+ );
+
+ vi.useFakeTimers();
+ fireEvent.pointerMove(references[1].parentElement!);
+ await act(async () => {
+ vi.advanceTimersByTime(500);
+ });
+ const tooltip = screen.getByRole("tooltip");
+ expect(tooltip).toHaveTextContent(
+ "/Users/renxubin/.nanobot/workspace/minecraft-fps/index.html",
+ );
+ } finally {
+ vi.useRealTimers();
+ }
+ });
+
it("renders assistant image media as a larger generated result", () => {
const message: UIMessage = {
id: "a-image",