mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-03 06:12:14 +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") {
|
||||
return (
|
||||
<div className="px-4 py-2">
|
||||
<Markdown animated>{message.content}</Markdown>
|
||||
<Markdown animated checkLinkCredibility>
|
||||
{message.content}
|
||||
</Markdown>
|
||||
</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" />}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useToolCalls } from "~/core/store";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { useMemo } from "react";
|
||||
import { useStore, useToolCalls } from "~/core/store";
|
||||
import { Tooltip } from "./tooltip";
|
||||
import { WarningFilled } from "@ant-design/icons";
|
||||
|
||||
export const Link = ({
|
||||
href,
|
||||
children,
|
||||
checkLinkCredibility = false,
|
||||
}: {
|
||||
href: string | undefined;
|
||||
children: React.ReactNode;
|
||||
checkLinkCredibility: boolean;
|
||||
}) => {
|
||||
const toolCalls = useToolCalls();
|
||||
const responding = useStore((state) => state.responding);
|
||||
|
||||
const credibleLinks = useMemo(() => {
|
||||
const links = new Set<string>();
|
||||
if (!checkLinkCredibility) return links;
|
||||
|
||||
(toolCalls || []).forEach((call) => {
|
||||
if (call && call.name === "web_search" && call.result) {
|
||||
const result = JSON.parse(call.result) as Array<{ url: string }>;
|
||||
@@ -23,28 +29,26 @@ export const Link = ({
|
||||
});
|
||||
return links;
|
||||
}, [toolCalls]);
|
||||
const isCredible = useMemo(() => {
|
||||
return href ? credibleLinks.has(href) : true;
|
||||
}, [credibleLinks, href]);
|
||||
|
||||
if (!isCredible) {
|
||||
return (
|
||||
<Tooltip title="This link might be a hallucination from AI model and may not be reliable.">
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={cn(isCredible && "after:ml-0.5 after:content-['⚠️']")}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
const isCredible = useMemo(() => {
|
||||
return checkLinkCredibility && href && !responding
|
||||
? credibleLinks.has(href)
|
||||
: true;
|
||||
}, [credibleLinks, href, responding, checkLinkCredibility]);
|
||||
|
||||
return (
|
||||
<a href={href} target="_blank" rel="noopener noreferrer">
|
||||
{children}
|
||||
</a>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<a href={href} target="_blank" rel="noopener noreferrer">
|
||||
{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 { 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({
|
||||
className,
|
||||
children,
|
||||
style,
|
||||
enableCopy,
|
||||
animated = false,
|
||||
checkLinkCredibility = false,
|
||||
...props
|
||||
}: ReactMarkdownOptions & {
|
||||
className?: string;
|
||||
enableCopy?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
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(() => {
|
||||
if (animated) {
|
||||
return [rehypeKatex, rehypeSplitWordsIntoSpans];
|
||||
|
||||
@@ -19,6 +19,7 @@ export function Tooltip({
|
||||
open,
|
||||
side,
|
||||
sideOffset,
|
||||
delayDuration = 750,
|
||||
}: {
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
@@ -27,10 +28,11 @@ export function Tooltip({
|
||||
open?: boolean;
|
||||
side?: "left" | "right" | "top" | "bottom";
|
||||
sideOffset?: number;
|
||||
delayDuration?: number;
|
||||
}) {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<ShadcnTooltip delayDuration={750} open={open}>
|
||||
<ShadcnTooltip delayDuration={delayDuration} open={open}>
|
||||
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
||||
<TooltipContent
|
||||
className={cn(className)}
|
||||
|
||||
Reference in New Issue
Block a user