mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 21:26:39 +00:00
fix(frontend): strip closing hashes from web fetch titles (#6103)
* fix(frontend): strip closing hashes from web fetch titles Signed-off-by: 97three <2212371308@qq.com> * fix(frontend): scan web fetch heading suffixes in linear time Signed-off-by: 97three <2212371308@qq.com> --------- Signed-off-by: 97three <2212371308@qq.com>
This commit is contained in:
parent
52b21d13ac
commit
9c75236ea1
@ -87,8 +87,13 @@ More specific `AGENTS.md` files under `src/` contain the frontend sections split
|
||||
|
||||
`core/utils/markdown.ts` reads web-fetch titles from the first nonblank line.
|
||||
Match zero to three literal spaces before `# ` without trimming indentation;
|
||||
mixed space/tab code blocks must fall back to the URL. Keep this local to title
|
||||
extraction rather than changing the shared streamdown fence parser.
|
||||
mixed space/tab code blocks must fall back to the URL. Strip optional closing
|
||||
hashes with a backwards scan so long fetched titles cannot cause quadratic regex
|
||||
backtracking. Closing hashes require a preceding space/tab (or an empty title)
|
||||
and only ASCII spaces/tabs afterward, apart from the terminal CR in CRLF input.
|
||||
Non-ASCII trailing whitespace must not turn literal hashes into closing syntax.
|
||||
Keep this local to title extraction rather than changing the shared streamdown
|
||||
fence parser.
|
||||
|
||||
Custom Agent `display_name` is an optional Unicode UI label, edited in
|
||||
`AgentSettingsDialog`. Use it with a fallback to `name` for gallery/chat text;
|
||||
|
||||
@ -9,5 +9,24 @@ export function extractTitleFromMarkdown(markdown: string) {
|
||||
if (!headingPrefix) {
|
||||
return undefined;
|
||||
}
|
||||
return firstLine.slice(headingPrefix[0].length).trim() || undefined;
|
||||
const title = firstLine.slice(headingPrefix[0].length);
|
||||
// Scan backwards once: a whitespace-search regex can backtrack quadratically
|
||||
// on long fetched titles. Only ASCII spaces/tabs may follow closing hashes.
|
||||
let end = title.endsWith("\r") ? title.length - 1 : title.length;
|
||||
while (end > 0 && (title[end - 1] === " " || title[end - 1] === "\t")) {
|
||||
end--;
|
||||
}
|
||||
let hashStart = end;
|
||||
while (hashStart > 0 && title[hashStart - 1] === "#") {
|
||||
hashStart--;
|
||||
}
|
||||
if (
|
||||
hashStart < end &&
|
||||
(hashStart === 0 ||
|
||||
title[hashStart - 1] === " " ||
|
||||
title[hashStart - 1] === "\t")
|
||||
) {
|
||||
return title.slice(0, hashStart).trim() || undefined;
|
||||
}
|
||||
return title.trim() || undefined;
|
||||
}
|
||||
|
||||
@ -6,6 +6,48 @@ test("reads the title from a leading ATX heading", () => {
|
||||
expect(extractTitleFromMarkdown("# Real Title\n\nbody")).toBe("Real Title");
|
||||
});
|
||||
|
||||
test.each([
|
||||
["# Release Notes ###", "Release Notes"],
|
||||
["# Release Notes ### ", "Release Notes"],
|
||||
["# Release Notes\t###\t", "Release Notes"],
|
||||
["# Release Notes ###\r\nbody", "Release Notes"],
|
||||
["# ###", undefined],
|
||||
])("removes a closing ATX heading sequence from %j", (markdown, expected) => {
|
||||
expect(extractTitleFromMarkdown(markdown)).toBe(expected);
|
||||
});
|
||||
|
||||
test.each([
|
||||
["# C#", "C#"],
|
||||
["# Release ### notes", "Release ### notes"],
|
||||
["# Release ###x", "Release ###x"],
|
||||
["# Release \\###", "Release \\###"],
|
||||
])("preserves literal hashes in %j", (markdown, expected) => {
|
||||
expect(extractTitleFromMarkdown(markdown)).toBe(expected);
|
||||
});
|
||||
|
||||
test.each(["\u00a0", "\u2003"])(
|
||||
"preserves literal hashes followed by non-ASCII whitespace %j",
|
||||
(whitespace) => {
|
||||
expect(extractTitleFromMarkdown(`# Release ###${whitespace}`)).toBe(
|
||||
"Release ###",
|
||||
);
|
||||
expect(extractTitleFromMarkdown(`# #${whitespace}\r\nbody`)).toBe("#");
|
||||
},
|
||||
);
|
||||
|
||||
test("handles long whitespace and hash runs without changing literal content", () => {
|
||||
const spaces = " ".repeat(100_000);
|
||||
const hashes = "#".repeat(100_000);
|
||||
expect(extractTitleFromMarkdown(`# Title${spaces}#x`)).toBe(
|
||||
`Title${spaces}#x`,
|
||||
);
|
||||
expect(extractTitleFromMarkdown(`# Title${spaces}${hashes}\t\r\nbody`)).toBe(
|
||||
"Title",
|
||||
);
|
||||
expect(extractTitleFromMarkdown(`# ${hashes}x`)).toBe(`${hashes}x`);
|
||||
expect(extractTitleFromMarkdown(`# ${hashes}\t`)).toBeUndefined();
|
||||
});
|
||||
|
||||
test("skips blank lines before the first heading", () => {
|
||||
expect(extractTitleFromMarkdown("\n# Real Title\n\nbody")).toBe("Real Title");
|
||||
expect(extractTitleFromMarkdown(" \n\n# Real Title")).toBe("Real Title");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user