mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-26 23:34:47 +08:00
feat: refactor crawler trust link style (#166)
* feat: refactor crawler trust link style * feat: enhance link credibility checks in Markdown and related components
This commit is contained in:
@@ -75,7 +75,9 @@ function ActivityMessage({ messageId }: { messageId: string }) {
|
|||||||
if (message.agent !== "reporter" && message.agent !== "planner") {
|
if (message.agent !== "reporter" && message.agent !== "planner") {
|
||||||
return (
|
return (
|
||||||
<div className="px-4 py-2">
|
<div className="px-4 py-2">
|
||||||
<Markdown animated>{message.content}</Markdown>
|
<Markdown animated checkLinkCredibility>
|
||||||
|
{message.content}
|
||||||
|
</Markdown>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,9 @@ export function ResearchReportBlock({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Markdown animated>{message?.content}</Markdown>
|
<Markdown animated checkLinkCredibility>
|
||||||
|
{message?.content}
|
||||||
|
</Markdown>
|
||||||
{message?.isStreaming && <LoadingAnimation className="my-12" />}
|
{message?.isStreaming && <LoadingAnimation className="my-12" />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useToolCalls } from "~/core/store";
|
import { useStore, useToolCalls } from "~/core/store";
|
||||||
import { cn } from "~/lib/utils";
|
|
||||||
import { Tooltip } from "./tooltip";
|
import { Tooltip } from "./tooltip";
|
||||||
|
import { WarningFilled } from "@ant-design/icons";
|
||||||
|
|
||||||
export const Link = ({
|
export const Link = ({
|
||||||
href,
|
href,
|
||||||
children,
|
children,
|
||||||
|
checkLinkCredibility = false,
|
||||||
}: {
|
}: {
|
||||||
href: string | undefined;
|
href: string | undefined;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
checkLinkCredibility: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const toolCalls = useToolCalls();
|
const toolCalls = useToolCalls();
|
||||||
|
const responding = useStore((state) => state.responding);
|
||||||
|
|
||||||
const credibleLinks = useMemo(() => {
|
const credibleLinks = useMemo(() => {
|
||||||
const links = new Set<string>();
|
const links = new Set<string>();
|
||||||
|
if (!checkLinkCredibility) return links;
|
||||||
|
|
||||||
(toolCalls || []).forEach((call) => {
|
(toolCalls || []).forEach((call) => {
|
||||||
if (call && call.name === "web_search" && call.result) {
|
if (call && call.name === "web_search" && call.result) {
|
||||||
const result = JSON.parse(call.result) as Array<{ url: string }>;
|
const result = JSON.parse(call.result) as Array<{ url: string }>;
|
||||||
@@ -23,28 +29,26 @@ export const Link = ({
|
|||||||
});
|
});
|
||||||
return links;
|
return links;
|
||||||
}, [toolCalls]);
|
}, [toolCalls]);
|
||||||
const isCredible = useMemo(() => {
|
|
||||||
return href ? credibleLinks.has(href) : true;
|
|
||||||
}, [credibleLinks, href]);
|
|
||||||
|
|
||||||
if (!isCredible) {
|
const isCredible = useMemo(() => {
|
||||||
return (
|
return checkLinkCredibility && href && !responding
|
||||||
<Tooltip title="This link might be a hallucination from AI model and may not be reliable.">
|
? credibleLinks.has(href)
|
||||||
<a
|
: true;
|
||||||
href={href}
|
}, [credibleLinks, href, responding, checkLinkCredibility]);
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className={cn(isCredible && "after:ml-0.5 after:content-['⚠️']")}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</a>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a href={href} target="_blank" rel="noopener noreferrer">
|
<span className="flex items-center gap-1.5">
|
||||||
{children}
|
<a href={href} target="_blank" rel="noopener noreferrer">
|
||||||
</a>
|
{children}
|
||||||
|
</a>
|
||||||
|
{!isCredible && (
|
||||||
|
<Tooltip
|
||||||
|
title="This link might be a hallucination from AI model and may not be reliable."
|
||||||
|
delayDuration={300}
|
||||||
|
>
|
||||||
|
<WarningFilled className="text-sx transition-colors hover:!text-yellow-500" />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,28 +20,36 @@ import Image from "./image";
|
|||||||
import { Tooltip } from "./tooltip";
|
import { Tooltip } from "./tooltip";
|
||||||
import { Link } from "./link";
|
import { Link } from "./link";
|
||||||
|
|
||||||
const components: ReactMarkdownOptions["components"] = {
|
|
||||||
a: ({ href, children }) => <Link href={href}>{children}</Link>,
|
|
||||||
img: ({ src, alt }) => (
|
|
||||||
<a href={src as string} target="_blank" rel="noopener noreferrer">
|
|
||||||
<Image className="rounded" src={src as string} alt={alt ?? ""} />
|
|
||||||
</a>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Markdown({
|
export function Markdown({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
style,
|
style,
|
||||||
enableCopy,
|
enableCopy,
|
||||||
animated = false,
|
animated = false,
|
||||||
|
checkLinkCredibility = false,
|
||||||
...props
|
...props
|
||||||
}: ReactMarkdownOptions & {
|
}: ReactMarkdownOptions & {
|
||||||
className?: string;
|
className?: string;
|
||||||
enableCopy?: boolean;
|
enableCopy?: boolean;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
animated?: boolean;
|
animated?: boolean;
|
||||||
|
checkLinkCredibility?: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
const components: ReactMarkdownOptions["components"] = useMemo(() => {
|
||||||
|
return {
|
||||||
|
a: ({ href, children }) => (
|
||||||
|
<Link href={href} checkLinkCredibility={checkLinkCredibility}>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
img: ({ src, alt }) => (
|
||||||
|
<a href={src as string} target="_blank" rel="noopener noreferrer">
|
||||||
|
<Image className="rounded" src={src as string} alt={alt ?? ""} />
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}, [checkLinkCredibility]);
|
||||||
|
|
||||||
const rehypePlugins = useMemo(() => {
|
const rehypePlugins = useMemo(() => {
|
||||||
if (animated) {
|
if (animated) {
|
||||||
return [rehypeKatex, rehypeSplitWordsIntoSpans];
|
return [rehypeKatex, rehypeSplitWordsIntoSpans];
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export function Tooltip({
|
|||||||
open,
|
open,
|
||||||
side,
|
side,
|
||||||
sideOffset,
|
sideOffset,
|
||||||
|
delayDuration = 750,
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
@@ -27,10 +28,11 @@ export function Tooltip({
|
|||||||
open?: boolean;
|
open?: boolean;
|
||||||
side?: "left" | "right" | "top" | "bottom";
|
side?: "left" | "right" | "top" | "bottom";
|
||||||
sideOffset?: number;
|
sideOffset?: number;
|
||||||
|
delayDuration?: number;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<ShadcnTooltip delayDuration={750} open={open}>
|
<ShadcnTooltip delayDuration={delayDuration} open={open}>
|
||||||
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
||||||
<TooltipContent
|
<TooltipContent
|
||||||
className={cn(className)}
|
className={cn(className)}
|
||||||
|
|||||||
Reference in New Issue
Block a user