feat: use toolbar instead of single button

This commit is contained in:
Li Xin 2025-04-20 21:56:08 +08:00
parent a970ab2b32
commit 9c9afb383b
2 changed files with 56 additions and 38 deletions

View File

@ -1,13 +1,13 @@
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { X } from "lucide-react"; import { Check, Copy, Headphones, X } from "lucide-react";
import { useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Card } from "~/components/ui/card"; import { Card } from "~/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
import { openResearch, useStore } from "~/core/store"; import { listenToPodcast, openResearch, useStore } from "~/core/store";
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils";
import { ResearchActivitiesBlock } from "./research-activities-block"; import { ResearchActivitiesBlock } from "./research-activities-block";
@ -29,16 +29,66 @@ export function ResearchBlock({
const hasReport = useStore((state) => const hasReport = useStore((state) =>
researchId ? state.researchReportIds.has(researchId) : false, researchId ? state.researchReportIds.has(researchId) : false,
); );
const reportStreaming = useStore((state) =>
reportId ? (state.messages.get(reportId)?.isStreaming ?? false) : false,
);
useEffect(() => { useEffect(() => {
if (hasReport) { if (hasReport) {
setActiveTab("report"); setActiveTab("report");
} }
}, [hasReport]); }, [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 ( return (
<div className={cn("h-full w-full", className)}> <div className={cn("h-full w-full", className)}>
<Card className={cn("relative h-full w-full pt-4", className)}> <Card className={cn("relative h-full w-full pt-4", className)}>
<div className="absolute right-4 flex h-9 items-center"> <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"
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"> <Tooltip title="Close">
<Button <Button
className="text-gray-400" className="text-gray-400"

View File

@ -1,20 +1,16 @@
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { Podcast } from "lucide-react"; import { useRef } from "react";
import { useCallback, useRef, useState } from "react";
import { Button } from "~/components/ui/button"; import { useMessage } from "~/core/store";
import { listenToPodcast, useMessage } from "~/core/store";
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils";
import { LoadingAnimation } from "./loading-animation"; import { LoadingAnimation } from "./loading-animation";
import { Markdown } from "./markdown"; import { Markdown } from "./markdown";
import { Tooltip } from "./tooltip";
export function ResearchReportBlock({ export function ResearchReportBlock({
className, className,
researchId,
messageId, messageId,
}: { }: {
className?: string; className?: string;
@ -23,39 +19,11 @@ export function ResearchReportBlock({
}) { }) {
const message = useMessage(messageId); const message = useMessage(messageId);
const contentRef = useRef<HTMLDivElement>(null); const contentRef = useRef<HTMLDivElement>(null);
const [isGenerated, setGenerated] = useState(false);
const handleListenToReport = useCallback(async () => {
if (isGenerated) {
return;
}
setGenerated(true);
await listenToPodcast(researchId);
}, [isGenerated, researchId]);
return ( return (
<div <div
ref={contentRef} ref={contentRef}
className={cn("relative flex flex-col pb-8", className)} className={cn("relative flex flex-col pb-8", className)}
> >
<div className="absolute top-2 right-6">
{message?.content && !message.isStreaming && (
<Tooltip
title={
isGenerated ? "The podcast is generated" : "Generate podcast"
}
>
<Button
variant="outline"
size="icon"
disabled={isGenerated}
onClick={() => {
void handleListenToReport();
}}
>
<Podcast />
</Button>
</Tooltip>
)}
</div>
<Markdown animate>{message?.content}</Markdown> <Markdown animate>{message?.content}</Markdown>
{message?.isStreaming && <LoadingAnimation className="my-12" />} {message?.isStreaming && <LoadingAnimation className="my-12" />}
</div> </div>