mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-25 14:06:18 +00:00
fix(frontend): read web_fetch titles that start with blank lines or indented headings (#5560)
* fix(frontend): read web_fetch titles that start with blank lines or indented headings * docs(frontend): describe the indented-code guard as it actually behaves * fix(frontend): reject mixed code indentation in web-fetch titles --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
parent
c52ad191f4
commit
1e3bfa09d4
@ -1128,6 +1128,9 @@ empty list is forwarded and imposes no restriction of that kind. See the
|
||||
|
||||
When using Tavily for `web_fetch`, extracted pages without a title use their URL
|
||||
as the heading; their content remains available to the agent.
|
||||
Chat tool-step titles accept leading blank lines and up to three spaces before
|
||||
a page's first H1 heading. Indented code, including mixed spaces and tabs, is
|
||||
not used as a title; the tool step falls back to the URL.
|
||||
Tavily search and fetch each read `api_key` from their own tool entry in
|
||||
`config.yaml`, falling back to `TAVILY_API_KEY` when omitted. Fetch does not
|
||||
reuse the search entry's key, so search can use a different provider. If you
|
||||
|
||||
@ -83,6 +83,11 @@ More specific `AGENTS.md` files under `src/` contain the frontend sections split
|
||||
|
||||
## Code Style
|
||||
|
||||
`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.
|
||||
|
||||
Custom Agent `display_name` is an optional Unicode UI label, edited in
|
||||
`AgentSettingsDialog`. Use it with a fallback to `name` for gallery/chat text;
|
||||
keep `name` for React identity, URLs, requests, and runtime `agent_name`.
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
// Converter output can start with blank lines, and CommonMark allows up to three
|
||||
// literal spaces before an ATX heading. Do not trim code indentation into a title.
|
||||
export function extractTitleFromMarkdown(markdown: string) {
|
||||
if (markdown.startsWith("# ")) {
|
||||
let title = markdown.split("\n")[0]!.trim();
|
||||
if (title.startsWith("# ")) {
|
||||
title = title.slice(2).trim();
|
||||
}
|
||||
return title;
|
||||
const firstLine = markdown.split("\n").find((line) => line.trim() !== "");
|
||||
if (firstLine === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
const headingPrefix = /^ {0,3}# /.exec(firstLine);
|
||||
if (!headingPrefix) {
|
||||
return undefined;
|
||||
}
|
||||
return firstLine.slice(headingPrefix[0].length).trim() || undefined;
|
||||
}
|
||||
|
||||
60
frontend/tests/unit/core/utils/markdown.test.ts
Normal file
60
frontend/tests/unit/core/utils/markdown.test.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { expect, test } from "@rstest/core";
|
||||
|
||||
import { extractTitleFromMarkdown } from "@/core/utils/markdown";
|
||||
|
||||
test("reads the title from a leading ATX heading", () => {
|
||||
expect(extractTitleFromMarkdown("# Real Title\n\nbody")).toBe("Real Title");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
test("accepts the up-to-three-space indentation CommonMark allows", () => {
|
||||
expect(extractTitleFromMarkdown(" # Real Title")).toBe("Real Title");
|
||||
});
|
||||
|
||||
test("ignores an indented code block that starts with a hash", () => {
|
||||
expect(extractTitleFromMarkdown(" # Not A Title")).toBeUndefined();
|
||||
expect(extractTitleFromMarkdown("\t# Not A Title")).toBeUndefined();
|
||||
});
|
||||
|
||||
test.each([" \t", " \t", " \t"])(
|
||||
"does not treat mixed indentation %j as a heading",
|
||||
(indent) => {
|
||||
expect(
|
||||
extractTitleFromMarkdown(`${indent}# Code comment\n# Later heading`),
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
extractTitleFromMarkdown(`\n \n${indent}# Code comment`),
|
||||
).toBeUndefined();
|
||||
},
|
||||
);
|
||||
|
||||
test.each(["", " ", " ", " "])(
|
||||
"accepts a heading with %j indentation and CRLF line endings",
|
||||
(indent) => {
|
||||
expect(extractTitleFromMarkdown(`\r\n${indent}# Real Title\r\nbody`)).toBe(
|
||||
"Real Title",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("ignores headings that are not level 1", () => {
|
||||
expect(extractTitleFromMarkdown("## Section")).toBeUndefined();
|
||||
expect(extractTitleFromMarkdown("#NoSpace")).toBeUndefined();
|
||||
});
|
||||
|
||||
test("does not report an empty heading as a title", () => {
|
||||
expect(extractTitleFromMarkdown("# \n\nbody")).toBeUndefined();
|
||||
expect(extractTitleFromMarkdown("#")).toBeUndefined();
|
||||
});
|
||||
|
||||
test("returns undefined when the document has no content", () => {
|
||||
expect(extractTitleFromMarkdown("")).toBeUndefined();
|
||||
expect(extractTitleFromMarkdown(" \n ")).toBeUndefined();
|
||||
expect(
|
||||
extractTitleFromMarkdown("Plain text with no heading"),
|
||||
).toBeUndefined();
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user