diff --git a/README.md b/README.md index ce39624736..1f097e2c36 100644 --- a/README.md +++ b/README.md @@ -1708,7 +1708,10 @@ provider, document title, and retrieved excerpt in chat, including after reload and when a subagent performed the retrieval. Missing page numbers and public URLs remain absent; sources without a URL are readable as local evidence. Inline citations remain readable after Markdown sanitization, and supplied page -numbers use the selected interface language. New providers use the shared +numbers use the selected interface language. +Copying a source reference produces a Markdown link that escapes brackets in the +visible title and wraps a URL containing parentheses in angle brackets, so the +copied reference stays a valid link. New providers use the shared envelope; existing RAGFlow citation histories remain supported. Expand a completed subtask card to inspect its report's forwarded knowledge sources, including when reopening a saved conversation. diff --git a/frontend/src/core/citations/sources.ts b/frontend/src/core/citations/sources.ts index 63813e6936..21d6840464 100644 --- a/frontend/src/core/citations/sources.ts +++ b/frontend/src/core/citations/sources.ts @@ -206,7 +206,13 @@ export function extractCitationSources( export function formatCitationMarkdownReference( source: CitationSource, ): string { - return `[${source.title}](${source.url})`; + // Link text may contain balanced brackets, but a stray "[" or "]" would end + // it early; escape both so a copied reference keeps the visible title. + const title = source.title.replace(/([\\[\]])/g, "\\$1"); + // A bare link destination stops at an unbalanced parenthesis. The + // angle-bracket form keeps URLs with parentheses valid. + const url = /[()]/.test(source.url) ? `<${source.url}>` : source.url; + return `[${title}](${url})`; } function normalizeTitle(title: string, domain: string): string { diff --git a/frontend/tests/unit/core/citations/sources.test.ts b/frontend/tests/unit/core/citations/sources.test.ts index bba339ed49..aaeba36c5c 100644 --- a/frontend/tests/unit/core/citations/sources.test.ts +++ b/frontend/tests/unit/core/citations/sources.test.ts @@ -1042,4 +1042,30 @@ describe("formatCitationMarkdownReference", () => { "[Paper A](https://example.com/a)", ); }); + + it("escapes brackets in the title so the copied link keeps its label", () => { + const [source] = extractCitationSources( + "Evidence [citation:Paper A](https://example.com/a).", + ); + + expect( + formatCitationMarkdownReference({ + ...source!, + title: "Why ] matters [draft", + }), + ).toBe("[Why \\] matters \\[draft](https://example.com/a)"); + }); + + it("wraps a URL with parentheses so the destination stays intact", () => { + const [source] = extractCitationSources( + "Evidence [citation:Paper A](https://example.com/a).", + ); + + expect( + formatCitationMarkdownReference({ + ...source!, + url: "https://example.com/a)b(c", + }), + ).toBe("[Paper A]()"); + }); });