mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-17 03:34:45 +08:00
feat: add citation/reference support to deep research reports (#1143)
* feat: add citation/reference support to deep research reports (#1141) - Enhance lead agent system prompt with mandatory citation requirements after web_search/web_fetch tool usage - Add citation examples and best practices to GitHub Deep Research skill - Add citation hints to report template (Executive Summary, Key Analysis) - Style regular markdown links in frontend for visual distinction (color, underline, hover effect) - Fix TitleMiddleware being registered when title generation is disabled * fix: address PR review comments - Revert TitleMiddleware conditional registration (agent.py) to avoid sync/async incompatibility with DeerFlowClient - Fix markdown link rendering: merge classNames instead of overwriting, only set target=_blank for external http(s) URLs - Remove unrelated package.json/pnpm-lock.yaml changes * fix: use plain markdown links in Sources section for cleaner rendering Inline citations in report body use [citation:Title](URL) for pill/badge style. Sources section uses plain [Title](URL) for simple underlined link style. * fix(frontend): render plain links as underlined text in artifact markdown Only links with citation: prefix render as Badge pills. Regular links in Sources section now render as underlined text links. --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
@@ -38,7 +38,7 @@ import { checkCodeFile, getFileName } from "@/core/utils/files";
|
||||
import { env } from "@/env";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { CitationLink } from "../citations/citation-link";
|
||||
import { ArtifactLink } from "../citations/artifact-link";
|
||||
import { useThread } from "../messages/context";
|
||||
import { Tooltip } from "../tooltip";
|
||||
|
||||
@@ -274,7 +274,7 @@ export function ArtifactFilePreview({
|
||||
<Streamdown
|
||||
className="size-full"
|
||||
{...streamdownPlugins}
|
||||
components={{ a: CitationLink }}
|
||||
components={{ a: ArtifactLink }}
|
||||
>
|
||||
{content ?? ""}
|
||||
</Streamdown>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { AnchorHTMLAttributes } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { CitationLink } from "./citation-link";
|
||||
|
||||
function isExternalUrl(href: string | undefined): boolean {
|
||||
return !!href && /^https?:\/\//.test(href);
|
||||
}
|
||||
|
||||
/** Link renderer for artifact markdown: citation: prefix → CitationLink, otherwise underlined text. */
|
||||
export function ArtifactLink(props: AnchorHTMLAttributes<HTMLAnchorElement>) {
|
||||
if (typeof props.children === "string") {
|
||||
const match = /^citation:(.+)$/.exec(props.children);
|
||||
if (match) {
|
||||
const [, text] = match;
|
||||
return <CitationLink {...props}>{text}</CitationLink>;
|
||||
}
|
||||
}
|
||||
const { className, target, rel, ...rest } = props;
|
||||
const external = isExternalUrl(props.href);
|
||||
return (
|
||||
<a
|
||||
{...rest}
|
||||
className={cn(
|
||||
"text-primary underline decoration-primary/30 underline-offset-2 hover:decoration-primary/60 transition-colors",
|
||||
className,
|
||||
)}
|
||||
target={target ?? (external ? "_blank" : undefined)}
|
||||
rel={rel ?? (external ? "noopener noreferrer" : undefined)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import type { HTMLAttributes } from "react";
|
||||
import type { AnchorHTMLAttributes } from "react";
|
||||
|
||||
import {
|
||||
MessageResponse,
|
||||
type MessageResponseProps,
|
||||
} from "@/components/ai-elements/message";
|
||||
import { streamdownPlugins } from "@/core/streamdown";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { CitationLink } from "../citations/citation-link";
|
||||
|
||||
function isExternalUrl(href: string | undefined): boolean {
|
||||
return !!href && /^https?:\/\//.test(href);
|
||||
}
|
||||
|
||||
export type MarkdownContentProps = {
|
||||
content: string;
|
||||
isLoading: boolean;
|
||||
@@ -30,7 +35,7 @@ export function MarkdownContent({
|
||||
}: MarkdownContentProps) {
|
||||
const components = useMemo(() => {
|
||||
return {
|
||||
a: (props: HTMLAttributes<HTMLAnchorElement>) => {
|
||||
a: (props: AnchorHTMLAttributes<HTMLAnchorElement>) => {
|
||||
if (typeof props.children === "string") {
|
||||
const match = /^citation:(.+)$/.exec(props.children);
|
||||
if (match) {
|
||||
@@ -38,7 +43,16 @@ export function MarkdownContent({
|
||||
return <CitationLink {...props}>{text}</CitationLink>;
|
||||
}
|
||||
}
|
||||
return <a {...props} />;
|
||||
const { className, target, rel, ...rest } = props;
|
||||
const external = isExternalUrl(props.href);
|
||||
return (
|
||||
<a
|
||||
{...rest}
|
||||
className={cn("text-primary underline decoration-primary/30 underline-offset-2 hover:decoration-primary/60 transition-colors", className)}
|
||||
target={target ?? (external ? "_blank" : undefined)}
|
||||
rel={rel ?? (external ? "noopener noreferrer" : undefined)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
...componentsFromProps,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user