feat(session): link agent references

This commit is contained in:
Xubin Ren
2026-08-04 12:14:51 +08:00
parent cf01978e71
commit 4c07c40b34
4 changed files with 67 additions and 3 deletions
@@ -16,6 +16,10 @@ import { Streamdown, type Components, type StreamdownProps } from "streamdown";
import { AttachmentTile } from "@/components/AttachmentTile";
import { CodeBlock } from "@/components/CodeBlock";
import {
INLINE_TOKEN_HIGHLIGHT_COLOR,
InlineTokenHighlight,
} from "@/components/InlineTokenHighlight";
import {
useFilePreviewAvailabilityResolver,
type FilePreviewAvailabilityResolver,
@@ -348,6 +352,17 @@ function fileReferenceFromLink(href: string | undefined): string | null {
return isPreviewableFileTarget(target) ? target : null;
}
function sessionReferenceHref(href: string): string | null {
if (!href.startsWith("#/chat/")) return null;
try {
const sessionKey = decodeURIComponent(href.slice("#/chat/".length)).trim();
if (!sessionKey.startsWith("websocket:") || sessionKey === "websocket:") return null;
return `#/chat/${encodeURIComponent(sessionKey)}`;
} catch {
return null;
}
}
function linkPreviewParts(value: ReactNode): { text: string; href?: string } {
let text = "";
let href: string | undefined;
@@ -592,6 +607,20 @@ export default function MarkdownTextRenderer({
if (href === "streamdown:incomplete-link") {
return <>{markdownChildren}</>;
}
const sessionHref = sessionReferenceHref(href);
if (sessionHref) {
return (
<a
href={sessionHref}
className="rounded-sm underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/60"
>
<InlineTokenHighlight color={INLINE_TOKEN_HIGHLIGHT_COLOR}>
{markdownChildren}
</InlineTokenHighlight>
</a>
);
}
if (href.startsWith("#/chat/")) return <>{markdownChildren}</>;
const filePath = fileReferenceFromLink(href);
if (filePath) {
const label = nodeText(markdownChildren).trim();
@@ -13,6 +13,29 @@ describe("MarkdownTextRenderer", () => {
expect(link).toHaveClass("text-blue-500", "dark:text-blue-300");
});
it("renders canonical session references as same-tab links", () => {
render(
<MarkdownTextRenderer>
{"We discussed this in [收费设计](#/chat/websocket%3Apricing)."}
</MarkdownTextRenderer>,
);
const link = screen.getByRole("link", { name: "收费设计" });
expect(link).toHaveAttribute("href", "#/chat/websocket%3Apricing");
expect(link).not.toHaveAttribute("target");
});
it("does not link non-WebUI session references", () => {
const { container } = render(
<MarkdownTextRenderer>
{"[private channel](#/chat/telegram%3Aprivate)"}
</MarkdownTextRenderer>,
);
expect(container).toHaveTextContent("private channel");
expect(container.querySelector("a")).toBeNull();
});
it("does not render active URL protocols from untrusted markdown", () => {
const { container } = render(
<MarkdownTextRenderer>