mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-15 03:04:44 +08:00
159 lines
4.8 KiB
TypeScript
159 lines
4.8 KiB
TypeScript
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
import { Check, Copy, Headphones, X } from "lucide-react";
|
|
import { useCallback, useEffect, useState } from "react";
|
|
|
|
import { Button } from "~/components/ui/button";
|
|
import { Card } from "~/components/ui/card";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
|
|
import { useReplay } from "~/core/replay";
|
|
import { listenToPodcast, openResearch, useStore } from "~/core/store";
|
|
import { cn } from "~/lib/utils";
|
|
|
|
import { ResearchActivitiesBlock } from "./research-activities-block";
|
|
import { ResearchReportBlock } from "./research-report-block";
|
|
import { ScrollContainer } from "./scroll-container";
|
|
import { Tooltip } from "./tooltip";
|
|
|
|
export function ResearchBlock({
|
|
className,
|
|
researchId = null,
|
|
}: {
|
|
className?: string;
|
|
researchId: string | null;
|
|
}) {
|
|
const reportId = useStore((state) =>
|
|
researchId ? state.researchReportIds.get(researchId) : undefined,
|
|
);
|
|
const [activeTab, setActiveTab] = useState("activities");
|
|
const hasReport = useStore((state) =>
|
|
researchId ? state.researchReportIds.has(researchId) : false,
|
|
);
|
|
const reportStreaming = useStore((state) =>
|
|
reportId ? (state.messages.get(reportId)?.isStreaming ?? false) : false,
|
|
);
|
|
const { isReplay } = useReplay();
|
|
useEffect(() => {
|
|
if (hasReport) {
|
|
setActiveTab("report");
|
|
}
|
|
}, [hasReport]);
|
|
|
|
const handleGeneratePodcast = useCallback(async () => {
|
|
if (!researchId) {
|
|
return;
|
|
}
|
|
await listenToPodcast(researchId);
|
|
}, [researchId]);
|
|
|
|
const [copied, setCopied] = useState(false);
|
|
const handleCopy = useCallback(() => {
|
|
if (!reportId) {
|
|
return;
|
|
}
|
|
const report = useStore.getState().messages.get(reportId);
|
|
if (!report) {
|
|
return;
|
|
}
|
|
void navigator.clipboard.writeText(report.content);
|
|
setCopied(true);
|
|
setTimeout(() => {
|
|
setCopied(false);
|
|
}, 1000);
|
|
}, [reportId]);
|
|
|
|
return (
|
|
<div className={cn("h-full w-full", className)}>
|
|
<Card className={cn("relative h-full w-full pt-4", className)}>
|
|
<div className="absolute right-4 flex h-9 items-center justify-center">
|
|
{hasReport && !reportStreaming && (
|
|
<>
|
|
<Tooltip title="Generate podcast">
|
|
<Button
|
|
className="text-gray-400"
|
|
size="icon"
|
|
variant="ghost"
|
|
disabled={isReplay}
|
|
onClick={handleGeneratePodcast}
|
|
>
|
|
<Headphones />
|
|
</Button>
|
|
</Tooltip>
|
|
<Tooltip title="Copy">
|
|
<Button
|
|
className="text-gray-400"
|
|
size="icon"
|
|
variant="ghost"
|
|
onClick={handleCopy}
|
|
>
|
|
{copied ? <Check /> : <Copy />}
|
|
</Button>
|
|
</Tooltip>
|
|
</>
|
|
)}
|
|
<Tooltip title="Close">
|
|
<Button
|
|
className="text-gray-400"
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={() => {
|
|
openResearch(null);
|
|
}}
|
|
>
|
|
<X />
|
|
</Button>
|
|
</Tooltip>
|
|
</div>
|
|
<Tabs
|
|
className="flex h-full w-full flex-col"
|
|
value={activeTab}
|
|
onValueChange={(value) => setActiveTab(value)}
|
|
>
|
|
<div className="flex w-full justify-center">
|
|
<TabsList className="">
|
|
<TabsTrigger
|
|
className="px-8"
|
|
value="report"
|
|
disabled={!hasReport}
|
|
>
|
|
Report
|
|
</TabsTrigger>
|
|
<TabsTrigger className="px-8" value="activities">
|
|
Activities
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
</div>
|
|
<TabsContent className="h-full min-h-0 flex-grow px-8" value="report">
|
|
<ScrollContainer
|
|
className="px-5pb-20 h-full"
|
|
scrollShadowColor="var(--card)"
|
|
>
|
|
{reportId && researchId && (
|
|
<ResearchReportBlock
|
|
className="mt-4"
|
|
researchId={researchId}
|
|
messageId={reportId}
|
|
/>
|
|
)}
|
|
</ScrollContainer>
|
|
</TabsContent>
|
|
<TabsContent
|
|
className="h-full min-h-0 flex-grow px-8"
|
|
value="activities"
|
|
>
|
|
<ScrollContainer className="h-full" scrollShadowColor="var(--card)">
|
|
{researchId && (
|
|
<ResearchActivitiesBlock
|
|
className="mt-4"
|
|
researchId={researchId}
|
|
/>
|
|
)}
|
|
</ScrollContainer>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|