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
|
When using Tavily for `web_fetch`, extracted pages without a title use their URL
|
||||||
as the heading; their content remains available to the agent.
|
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
|
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
|
`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
|
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
|
## 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
|
Custom Agent `display_name` is an optional Unicode UI label, edited in
|
||||||
`AgentSettingsDialog`. Use it with a fallback to `name` for gallery/chat text;
|
`AgentSettingsDialog`. Use it with a fallback to `name` for gallery/chat text;
|
||||||
keep `name` for React identity, URLs, requests, and runtime `agent_name`.
|
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) {
|
export function extractTitleFromMarkdown(markdown: string) {
|
||||||
if (markdown.startsWith("# ")) {
|
const firstLine = markdown.split("\n").find((line) => line.trim() !== "");
|
||||||
let title = markdown.split("\n")[0]!.trim();
|
if (firstLine === undefined) {
|
||||||
if (title.startsWith("# ")) {
|
return undefined;
|
||||||
title = title.slice(2).trim();
|
|
||||||
}
|
|
||||||
return title;
|
|
||||||
}
|
}
|
||||||
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