mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-21 21:24:46 +08:00
feat: add i18n support and add Chinese (#372)
* feat: add i18n support and add Chinese * fix: resolve conflicts * Update en.json with cancle settings * Update zh.json with settngs cancle --------- Co-authored-by: johnny0120 <15564476+johnny0120@users.noreply.github.com> Co-authored-by: Willem Jiang <willem.jiang@gmail.com> Co-authored-by: Willem Jiang <143703838+willem-bd@users.noreply.github.com>
This commit is contained in:
@@ -2,17 +2,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
import { Welcome } from "./welcome";
|
||||
|
||||
const questions = [
|
||||
"How many times taller is the Eiffel Tower than the tallest building in the world?",
|
||||
"How many years does an average Tesla battery last compared to a gasoline engine?",
|
||||
"How many liters of water are required to produce 1 kg of beef?",
|
||||
"How many times faster is the speed of light compared to the speed of sound?",
|
||||
];
|
||||
export function ConversationStarter({
|
||||
className,
|
||||
onSend,
|
||||
@@ -20,6 +15,9 @@ export function ConversationStarter({
|
||||
className?: string;
|
||||
onSend?: (message: string) => void;
|
||||
}) {
|
||||
const t = useTranslations("chat");
|
||||
const questions = t.raw("conversationStarters") as string[];
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col items-center", className)}>
|
||||
<div className="pointer-events-none fixed inset-0 flex items-center justify-center">
|
||||
@@ -41,7 +39,7 @@ export function ConversationStarter({
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="bg-card text-muted-foreground cursor-pointer rounded-2xl border px-4 py-4 opacity-75 transition-all duration-300 hover:opacity-100 hover:shadow-md"
|
||||
className="bg-card text-muted-foreground h-full w-full cursor-pointer rounded-2xl border px-4 py-4 opacity-75 transition-all duration-300 hover:opacity-100 hover:shadow-md"
|
||||
onClick={() => {
|
||||
onSend?.(question);
|
||||
}}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { MagicWandIcon } from "@radix-ui/react-icons";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { ArrowUp, Lightbulb, X } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { Detective } from "~/components/deer-flow/icons/detective";
|
||||
@@ -46,6 +47,8 @@ export function InputBox({
|
||||
onCancel?: () => void;
|
||||
onRemoveFeedback?: () => void;
|
||||
}) {
|
||||
const t = useTranslations("chat.inputBox");
|
||||
const tCommon = useTranslations("common");
|
||||
const enableDeepThinking = useSettingsStore(
|
||||
(state) => state.general.enableDeepThinking,
|
||||
);
|
||||
@@ -217,12 +220,14 @@ export function InputBox({
|
||||
title={
|
||||
<div>
|
||||
<h3 className="mb-2 font-bold">
|
||||
Deep Thinking Mode: {enableDeepThinking ? "On" : "Off"}
|
||||
{t("deepThinkingTooltip.title", {
|
||||
status: enableDeepThinking ? t("on") : t("off"),
|
||||
})}
|
||||
</h3>
|
||||
<p>
|
||||
When enabled, DeerFlow will use reasoning model (
|
||||
{config.models.reasoning?.[0]}) to generate more thoughtful
|
||||
plans.
|
||||
{t("deepThinkingTooltip.description", {
|
||||
model: config.models.reasoning?.[0] ?? "",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
@@ -237,7 +242,7 @@ export function InputBox({
|
||||
setEnableDeepThinking(!enableDeepThinking);
|
||||
}}
|
||||
>
|
||||
<Lightbulb /> Deep Thinking
|
||||
<Lightbulb /> {t("deepThinking")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
@@ -247,13 +252,11 @@ export function InputBox({
|
||||
title={
|
||||
<div>
|
||||
<h3 className="mb-2 font-bold">
|
||||
Investigation Mode: {backgroundInvestigation ? "On" : "Off"}
|
||||
{t("investigationTooltip.title", {
|
||||
status: backgroundInvestigation ? t("on") : t("off"),
|
||||
})}
|
||||
</h3>
|
||||
<p>
|
||||
When enabled, DeerFlow will perform a quick search before
|
||||
planning. This is useful for researches related to ongoing
|
||||
events and news.
|
||||
</p>
|
||||
<p>{t("investigationTooltip.description")}</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
@@ -267,13 +270,13 @@ export function InputBox({
|
||||
setEnableBackgroundInvestigation(!backgroundInvestigation)
|
||||
}
|
||||
>
|
||||
<Detective /> Investigation
|
||||
<Detective /> {t("investigation")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<ReportStyleDialog />
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<Tooltip title="Enhance prompt with AI">
|
||||
<Tooltip title={t("enhancePrompt")}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
@@ -293,7 +296,7 @@ export function InputBox({
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title={responding ? "Stop" : "Send"}>
|
||||
<Tooltip title={responding ? tCommon("stop") : tCommon("send")}>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
ChevronRight,
|
||||
Lightbulb,
|
||||
} from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import React, { useCallback, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { LoadingAnimation } from "~/components/deer-flow/loading-animation";
|
||||
@@ -252,6 +253,7 @@ function ResearchCard({
|
||||
researchId: string;
|
||||
onToggleResearch?: () => void;
|
||||
}) {
|
||||
const t = useTranslations("chat.research");
|
||||
const reportId = useStore((state) => state.researchReportIds.get(researchId));
|
||||
const hasReport = reportId !== undefined;
|
||||
const reportGenerating = useStore(
|
||||
@@ -260,10 +262,10 @@ function ResearchCard({
|
||||
const openResearchId = useStore((state) => state.openResearchId);
|
||||
const state = useMemo(() => {
|
||||
if (hasReport) {
|
||||
return reportGenerating ? "Generating report..." : "Report generated";
|
||||
return reportGenerating ? t("generatingReport") : t("reportGenerated");
|
||||
}
|
||||
return "Researching...";
|
||||
}, [hasReport, reportGenerating]);
|
||||
return t("researching");
|
||||
}, [hasReport, reportGenerating, t]);
|
||||
const msg = useResearchMessage(researchId);
|
||||
const title = useMemo(() => {
|
||||
if (msg) {
|
||||
@@ -283,8 +285,8 @@ function ResearchCard({
|
||||
<Card className={cn("w-full", className)}>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<RainbowText animated={state !== "Report generated"}>
|
||||
{title !== undefined && title !== "" ? title : "Deep Research"}
|
||||
<RainbowText animated={state !== t("reportGenerated")}>
|
||||
{title !== undefined && title !== "" ? title : t("deepResearch")}
|
||||
</RainbowText>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -297,7 +299,7 @@ function ResearchCard({
|
||||
variant={!openResearchId ? "default" : "outline"}
|
||||
onClick={handleOpen}
|
||||
>
|
||||
{researchId !== openResearchId ? "Open" : "Close"}
|
||||
{researchId !== openResearchId ? t("open") : t("close")}
|
||||
</Button>
|
||||
</div>
|
||||
</CardFooter>
|
||||
@@ -316,6 +318,7 @@ function ThoughtBlock({
|
||||
isStreaming?: boolean;
|
||||
hasMainContent?: boolean;
|
||||
}) {
|
||||
const t = useTranslations("chat.research");
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
const [hasAutoCollapsed, setHasAutoCollapsed] = useState(false);
|
||||
@@ -359,7 +362,7 @@ function ThoughtBlock({
|
||||
isStreaming ? "text-primary" : "text-foreground",
|
||||
)}
|
||||
>
|
||||
Deep Thinking
|
||||
{t("deepThinking")}
|
||||
</span>
|
||||
{isStreaming && <LoadingAnimation className="ml-2 scale-75" />}
|
||||
<div className="flex-grow" />
|
||||
@@ -432,6 +435,7 @@ function PlanCard({
|
||||
) => void;
|
||||
waitForFeedback?: boolean;
|
||||
}) {
|
||||
const t = useTranslations("chat.research");
|
||||
const plan = useMemo<{
|
||||
title?: string;
|
||||
thought?: string;
|
||||
@@ -482,7 +486,7 @@ function PlanCard({
|
||||
{`### ${
|
||||
plan.title !== undefined && plan.title !== ""
|
||||
? plan.title
|
||||
: "Deep Research"
|
||||
: t("deepResearch")
|
||||
}`}
|
||||
</Markdown>
|
||||
</CardTitle>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { FastForward, Play } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
|
||||
import { RainbowText } from "~/components/deer-flow/rainbow-text";
|
||||
@@ -27,6 +28,7 @@ import { MessageListView } from "./message-list-view";
|
||||
import { Welcome } from "./welcome";
|
||||
|
||||
export function MessagesBlock({ className }: { className?: string }) {
|
||||
const t = useTranslations("chat.messages");
|
||||
const messageIds = useMessageIds();
|
||||
const messageCount = messageIds.length;
|
||||
const responding = useStore((state) => state.responding);
|
||||
@@ -152,16 +154,16 @@ export function MessagesBlock({ className }: { className?: string }) {
|
||||
<CardHeader className={cn("flex-grow", responding && "pl-3")}>
|
||||
<CardTitle>
|
||||
<RainbowText animated={responding}>
|
||||
{responding ? "Replaying" : `${replayTitle}`}
|
||||
{responding ? t("replaying") : `${replayTitle}`}
|
||||
</RainbowText>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
<RainbowText animated={responding}>
|
||||
{responding
|
||||
? "DeerFlow is now replaying the conversation..."
|
||||
? t("replayDescription")
|
||||
: replayStarted
|
||||
? "The replay has been stopped."
|
||||
: `You're now in DeerFlow's replay mode. Click the "Play" button on the right to start.`}
|
||||
? t("replayHasStopped")
|
||||
: t("replayModeDescription")}
|
||||
</RainbowText>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
@@ -175,13 +177,13 @@ export function MessagesBlock({ className }: { className?: string }) {
|
||||
onClick={handleFastForwardReplay}
|
||||
>
|
||||
<FastForward size={16} />
|
||||
Fast Forward
|
||||
{t("fastForward")}
|
||||
</Button>
|
||||
)}
|
||||
{!replayStarted && (
|
||||
<Button className="w-24" onClick={handleStartReplay}>
|
||||
<Play size={16} />
|
||||
Play
|
||||
{t("play")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -190,17 +192,16 @@ export function MessagesBlock({ className }: { className?: string }) {
|
||||
</Card>
|
||||
{!replayStarted && env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY && (
|
||||
<div className="text-muted-foreground w-full text-center text-xs">
|
||||
* This site is for demo purposes only. If you want to try your
|
||||
own question, please{" "}
|
||||
{t("demoNotice")}{" "}
|
||||
<a
|
||||
className="underline"
|
||||
href="https://github.com/bytedance/deer-flow"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
click here
|
||||
{t("clickHere")}
|
||||
</a>{" "}
|
||||
to clone it locally and run it.
|
||||
{t("cloneLocally")}
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { PythonOutlined } from "@ant-design/icons";
|
||||
import { motion } from "framer-motion";
|
||||
import { LRUCache } from "lru-cache";
|
||||
import { BookOpenText, FileText, PencilRuler, Search } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useMemo } from "react";
|
||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||
@@ -122,6 +123,7 @@ type SearchResult =
|
||||
};
|
||||
|
||||
function WebSearchToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
||||
const t = useTranslations("chat.research");
|
||||
const searching = useMemo(() => {
|
||||
return toolCall.result === undefined;
|
||||
}, [toolCall.result]);
|
||||
@@ -159,7 +161,7 @@ function WebSearchToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
||||
animated={searchResults === undefined}
|
||||
>
|
||||
<Search size={16} className={"mr-2"} />
|
||||
<span>Searching for </span>
|
||||
<span>{t("searchingFor")} </span>
|
||||
<span className="max-w-[500px] overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{(toolCall.args as { query: string }).query}
|
||||
</span>
|
||||
@@ -238,6 +240,7 @@ function WebSearchToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
||||
}
|
||||
|
||||
function CrawlToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
||||
const t = useTranslations("chat.research");
|
||||
const url = useMemo(
|
||||
() => (toolCall.args as { url: string }).url,
|
||||
[toolCall.args],
|
||||
@@ -251,7 +254,7 @@ function CrawlToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
||||
animated={toolCall.result === undefined}
|
||||
>
|
||||
<BookOpenText size={16} className={"mr-2"} />
|
||||
<span>Reading</span>
|
||||
<span>{t("reading")}</span>
|
||||
</RainbowText>
|
||||
</div>
|
||||
<ul className="mt-2 flex flex-wrap gap-4">
|
||||
@@ -279,6 +282,7 @@ function CrawlToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
||||
}
|
||||
|
||||
function RetrieverToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
||||
const t = useTranslations("chat.research");
|
||||
const searching = useMemo(() => {
|
||||
return toolCall.result === undefined;
|
||||
}, [toolCall.result]);
|
||||
@@ -292,7 +296,7 @@ function RetrieverToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
||||
<div className="font-medium italic">
|
||||
<RainbowText className="flex items-center" animated={searching}>
|
||||
<Search size={16} className={"mr-2"} />
|
||||
<span>Retrieving documents from RAG </span>
|
||||
<span>{t("retrievingDocuments")} </span>
|
||||
<span className="max-w-[500px] overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{(toolCall.args as { keywords: string }).keywords}
|
||||
</span>
|
||||
@@ -337,6 +341,7 @@ function RetrieverToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
||||
}
|
||||
|
||||
function PythonToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
||||
const t = useTranslations("chat.research");
|
||||
const code = useMemo<string | undefined>(() => {
|
||||
return (toolCall.args as { code?: string }).code;
|
||||
}, [toolCall.args]);
|
||||
@@ -349,7 +354,7 @@ function PythonToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
||||
className="text-base font-medium italic"
|
||||
animated={toolCall.result === undefined}
|
||||
>
|
||||
Running Python code
|
||||
{t("runningPythonCode")}
|
||||
</RainbowText>
|
||||
</div>
|
||||
<div>
|
||||
@@ -373,6 +378,7 @@ function PythonToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
||||
}
|
||||
|
||||
function PythonToolCallResult({ result }: { result: string }) {
|
||||
const t = useTranslations("chat.research");
|
||||
const { resolvedTheme } = useTheme();
|
||||
const hasError = useMemo(
|
||||
() => result.includes("Error executing code:\n"),
|
||||
@@ -399,7 +405,7 @@ function PythonToolCallResult({ result }: { result: string }) {
|
||||
return (
|
||||
<>
|
||||
<div className="mt-4 font-medium italic">
|
||||
{hasError ? "Error when executing the above code" : "Execution output"}
|
||||
{hasError ? t("errorExecutingCode") : t("executionOutput")}
|
||||
</div>
|
||||
<div className="bg-accent mt-2 max-h-[400px] max-w-[calc(100%-120px)] overflow-y-auto rounded-md p-2 text-sm">
|
||||
<SyntaxHighlighter
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Check, Copy, Headphones, Pencil, Undo2, X, Download } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { ScrollContainer } from "~/components/deer-flow/scroll-container";
|
||||
@@ -23,6 +24,7 @@ export function ResearchBlock({
|
||||
className?: string;
|
||||
researchId: string | null;
|
||||
}) {
|
||||
const t = useTranslations("chat.research");
|
||||
const reportId = useStore((state) =>
|
||||
researchId ? state.researchReportIds.get(researchId) : undefined,
|
||||
);
|
||||
@@ -108,7 +110,7 @@ export function ResearchBlock({
|
||||
<div className="absolute right-4 flex h-9 items-center justify-center">
|
||||
{hasReport && !reportStreaming && (
|
||||
<>
|
||||
<Tooltip title="Generate podcast">
|
||||
<Tooltip title={t("generatePodcast")}>
|
||||
<Button
|
||||
className="text-gray-400"
|
||||
size="icon"
|
||||
@@ -119,7 +121,7 @@ export function ResearchBlock({
|
||||
<Headphones />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Edit">
|
||||
<Tooltip title={t("edit")}>
|
||||
<Button
|
||||
className="text-gray-400"
|
||||
size="icon"
|
||||
@@ -130,7 +132,7 @@ export function ResearchBlock({
|
||||
{editing ? <Undo2 /> : <Pencil />}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Copy">
|
||||
<Tooltip title={t("copy")}>
|
||||
<Button
|
||||
className="text-gray-400"
|
||||
size="icon"
|
||||
@@ -140,7 +142,7 @@ export function ResearchBlock({
|
||||
{copied ? <Check /> : <Copy />}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Download report as markdown">
|
||||
<Tooltip title={t("downloadReport")}>
|
||||
<Button
|
||||
className="text-gray-400"
|
||||
size="icon"
|
||||
@@ -152,7 +154,7 @@ export function ResearchBlock({
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
<Tooltip title="Close">
|
||||
<Tooltip title={t("close")}>
|
||||
<Button
|
||||
className="text-gray-400"
|
||||
size="sm"
|
||||
@@ -177,10 +179,10 @@ export function ResearchBlock({
|
||||
value="report"
|
||||
disabled={!hasReport}
|
||||
>
|
||||
Report
|
||||
{t("report")}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger className="px-8" value="activities">
|
||||
Activities
|
||||
{t("activities")}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
@@ -3,12 +3,16 @@
|
||||
|
||||
import { StarFilledIcon, GitHubLogoIcon } from "@radix-ui/react-icons";
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
import { LanguageSwitcher } from "~/components/deer-flow/language-switcher";
|
||||
import { NumberTicker } from "~/components/magicui/number-ticker";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { env } from "~/env";
|
||||
|
||||
export async function SiteHeader() {
|
||||
export function SiteHeader() {
|
||||
const t = useTranslations('common');
|
||||
|
||||
return (
|
||||
<header className="supports-backdrop-blur:bg-background/80 bg-background/40 sticky top-0 left-0 z-40 flex h-15 w-full flex-col items-center backdrop-blur-lg">
|
||||
<div className="container flex h-15 items-center justify-between px-3">
|
||||
@@ -16,7 +20,8 @@ export async function SiteHeader() {
|
||||
<span className="mr-1 text-2xl">🦌</span>
|
||||
<span>DeerFlow</span>
|
||||
</div>
|
||||
<div className="relative flex items-center">
|
||||
<div className="relative flex items-center gap-2">
|
||||
<LanguageSwitcher />
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 z-0 h-full w-full rounded-full opacity-60 blur-2xl"
|
||||
style={{
|
||||
@@ -32,7 +37,7 @@ export async function SiteHeader() {
|
||||
>
|
||||
<Link href="https://github.com/bytedance/deer-flow" target="_blank">
|
||||
<GitHubLogoIcon className="size-4" />
|
||||
Star on GitHub
|
||||
{t('starOnGitHub')}
|
||||
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY &&
|
||||
env.GITHUB_OAUTH_TOKEN && <StarCounter />}
|
||||
</Link>
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
export function Welcome({ className }: { className?: string }) {
|
||||
const t = useTranslations("chat.welcome");
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className={cn("flex flex-col", className)}
|
||||
@@ -13,21 +16,9 @@ export function Welcome({ className }: { className?: string }) {
|
||||
initial={{ opacity: 0, scale: 0.85 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
>
|
||||
<h3 className="mb-2 text-center text-3xl font-medium">
|
||||
👋 Hello, there!
|
||||
</h3>
|
||||
<h3 className="mb-2 text-center text-3xl font-medium">{t("greeting")}</h3>
|
||||
<div className="text-muted-foreground px-4 text-center text-lg">
|
||||
Welcome to{" "}
|
||||
<a
|
||||
href="https://github.com/bytedance/deer-flow"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hover:underline"
|
||||
>
|
||||
🦌 DeerFlow
|
||||
</a>
|
||||
, a deep research assistant built on cutting-edge language models, helps
|
||||
you search on web, browse information, and handle complex tasks.
|
||||
{t("description")}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { GithubOutlined } from "@ant-design/icons";
|
||||
import dynamic from "next/dynamic";
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { Button } from "~/components/ui/button";
|
||||
@@ -25,12 +26,14 @@ const Main = dynamic(() => import("./main"), {
|
||||
});
|
||||
|
||||
export default function HomePage() {
|
||||
const t = useTranslations("chat.page");
|
||||
|
||||
return (
|
||||
<div className="flex h-screen w-screen justify-center overscroll-none">
|
||||
<header className="fixed top-0 left-0 flex h-12 w-full items-center justify-between px-4">
|
||||
<Logo />
|
||||
<div className="flex items-center">
|
||||
<Tooltip title="Star DeerFlow on GitHub">
|
||||
<Tooltip title={t("starOnGitHub")}>
|
||||
<Button variant="ghost" size="icon" asChild>
|
||||
<Link
|
||||
href="https://github.com/bytedance/deer-flow"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { GithubFilled } from "@ant-design/icons";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
import { AuroraText } from "~/components/magicui/aurora-text";
|
||||
import { FlickeringGrid } from "~/components/magicui/flickering-grid";
|
||||
@@ -11,6 +12,9 @@ import { Button } from "~/components/ui/button";
|
||||
import { env } from "~/env";
|
||||
|
||||
export function Jumbotron() {
|
||||
const t = useTranslations('hero');
|
||||
const tCommon = useTranslations('common');
|
||||
|
||||
return (
|
||||
<section className="flex h-[95vh] w-full flex-col items-center justify-center pb-15">
|
||||
<FlickeringGrid
|
||||
@@ -34,15 +38,12 @@ export function Jumbotron() {
|
||||
<div className="relative z-10 flex flex-col items-center justify-center gap-12">
|
||||
<h1 className="text-center text-4xl font-bold md:text-6xl">
|
||||
<span className="bg-gradient-to-r from-white via-gray-200 to-gray-400 bg-clip-text text-transparent">
|
||||
Deep Research{" "}
|
||||
{t('title')}{" "}
|
||||
</span>
|
||||
<AuroraText>at Your Fingertips</AuroraText>
|
||||
<AuroraText>{t('subtitle')}</AuroraText>
|
||||
</h1>
|
||||
<p className="max-w-4xl p-2 text-center text-sm opacity-85 md:text-2xl">
|
||||
Meet DeerFlow, your personal Deep Research assistant. With powerful
|
||||
tools like search engines, web crawlers, Python and MCP services, it
|
||||
delivers instant insights, comprehensive reports, and even captivating
|
||||
podcasts.
|
||||
{t('description')}
|
||||
</p>
|
||||
<div className="flex gap-6">
|
||||
<Button className="hidden text-lg md:flex md:w-42" size="lg" asChild>
|
||||
@@ -56,7 +57,7 @@ export function Jumbotron() {
|
||||
: "/chat"
|
||||
}
|
||||
>
|
||||
Get Started <ChevronRight />
|
||||
{tCommon('getStarted')} <ChevronRight />
|
||||
</Link>
|
||||
</Button>
|
||||
{!env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY && (
|
||||
@@ -71,14 +72,14 @@ export function Jumbotron() {
|
||||
target="_blank"
|
||||
>
|
||||
<GithubFilled />
|
||||
Learn More
|
||||
{tCommon('learnMore')}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute bottom-8 flex text-xs opacity-50">
|
||||
<p>* DEER stands for Deep Exploration and Efficient Research.</p>
|
||||
<p>{t('footnote')}</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
Minimize,
|
||||
} from "lucide-react";
|
||||
import "@xyflow/react/dist/style.css";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
|
||||
import { Tooltip } from "~/components/deer-flow/tooltip";
|
||||
@@ -47,6 +48,7 @@ const nodeTypes = {
|
||||
};
|
||||
|
||||
export function MultiAgentVisualization({ className }: { className?: string }) {
|
||||
const t = useTranslations("chat.multiAgent");
|
||||
const {
|
||||
graph: { nodes, edges },
|
||||
activeStepIndex,
|
||||
@@ -120,12 +122,12 @@ export function MultiAgentVisualization({ className }: { className?: string }) {
|
||||
<div className="h-4 shrink-0"></div>
|
||||
<div className="flex h-6 w-full shrink-0 items-center justify-center">
|
||||
<div className="bg-muted/50 z-[200] flex rounded-3xl px-4 py-2">
|
||||
<Tooltip title="Move to the previous step">
|
||||
<Tooltip title={t("moveToPrevious")}>
|
||||
<Button variant="ghost" onClick={prevStep}>
|
||||
<ChevronLeft className="size-5" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Play / Pause">
|
||||
<Tooltip title={t("playPause")}>
|
||||
<Button variant="ghost" onClick={togglePlay}>
|
||||
{playing ? (
|
||||
<Pause className="size-5" />
|
||||
@@ -134,7 +136,7 @@ export function MultiAgentVisualization({ className }: { className?: string }) {
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Move to the next step">
|
||||
<Tooltip title={t("moveToNext")}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
@@ -158,7 +160,7 @@ export function MultiAgentVisualization({ className }: { className?: string }) {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Tooltip title="Toggle fullscreen">
|
||||
<Tooltip title={t("toggleFullscreen")}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
|
||||
@@ -3,93 +3,52 @@
|
||||
|
||||
import { Bike, Building, Film, Github, Ham, Home, Pizza } from "lucide-react";
|
||||
import { Bot } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { BentoCard } from "~/components/magicui/bento-grid";
|
||||
|
||||
import { SectionHeader } from "../components/section-header";
|
||||
|
||||
const caseStudies = [
|
||||
{
|
||||
id: "eiffel-tower-vs-tallest-building",
|
||||
icon: Building,
|
||||
title: "How tall is Eiffel Tower compared to tallest building?",
|
||||
description:
|
||||
"The research compares the heights and global significance of the Eiffel Tower and Burj Khalifa, and uses Python code to calculate the multiples.",
|
||||
},
|
||||
{
|
||||
id: "github-top-trending-repo",
|
||||
icon: Github,
|
||||
title: "What are the top trending repositories on GitHub?",
|
||||
description:
|
||||
"The research utilized MCP services to identify the most popular GitHub repositories and documented them in detail using search engines.",
|
||||
},
|
||||
{
|
||||
id: "nanjing-traditional-dishes",
|
||||
icon: Ham,
|
||||
title: "Write an article about Nanjing's traditional dishes",
|
||||
description:
|
||||
"The study vividly showcases Nanjing's famous dishes through rich content and imagery, uncovering their hidden histories and cultural significance.",
|
||||
},
|
||||
{
|
||||
id: "rental-apartment-decoration",
|
||||
icon: Home,
|
||||
title: "How to decorate a small rental apartment?",
|
||||
description:
|
||||
"The study provides readers with practical and straightforward methods for decorating apartments, accompanied by inspiring images.",
|
||||
},
|
||||
{
|
||||
id: "review-of-the-professional",
|
||||
icon: Film,
|
||||
title: "Introduce the movie 'Léon: The Professional'",
|
||||
description:
|
||||
"The research provides a comprehensive introduction to the movie 'Léon: The Professional', including its plot, characters, and themes.",
|
||||
},
|
||||
{
|
||||
id: "china-food-delivery",
|
||||
icon: Bike,
|
||||
title: "How do you view the takeaway war in China? (in Chinese)",
|
||||
description:
|
||||
"The research analyzes the intensifying competition between JD and Meituan, highlighting their strategies, technological innovations, and challenges.",
|
||||
},
|
||||
{
|
||||
id: "ultra-processed-foods",
|
||||
icon: Pizza,
|
||||
title: "Are ultra-processed foods linked to health?",
|
||||
description:
|
||||
"The research examines the health risks of rising ultra-processed food consumption, urging more research on long-term effects and individual differences.",
|
||||
},
|
||||
{
|
||||
id: "ai-twin-insurance",
|
||||
icon: Bot,
|
||||
title: 'Write an article on "Would you insure your AI twin?"',
|
||||
description:
|
||||
"The research explores the concept of insuring AI twins, highlighting their benefits, risks, ethical considerations, and the evolving regulatory.",
|
||||
},
|
||||
const caseStudyIcons = [
|
||||
{ id: "eiffel-tower-vs-tallest-building", icon: Building },
|
||||
{ id: "github-top-trending-repo", icon: Github },
|
||||
{ id: "nanjing-traditional-dishes", icon: Ham },
|
||||
{ id: "rental-apartment-decoration", icon: Home },
|
||||
{ id: "review-of-the-professional", icon: Film },
|
||||
{ id: "china-food-delivery", icon: Bike },
|
||||
{ id: "ultra-processed-foods", icon: Pizza },
|
||||
{ id: "ai-twin-insurance", icon: Bot },
|
||||
];
|
||||
|
||||
export function CaseStudySection() {
|
||||
const t = useTranslations("landing.caseStudies");
|
||||
const cases = t.raw("cases") as Array<{ title: string; description: string }>;
|
||||
|
||||
return (
|
||||
<section className="relative container hidden flex-col items-center justify-center md:flex">
|
||||
<SectionHeader
|
||||
anchor="case-studies"
|
||||
title="Case Studies"
|
||||
description="See DeerFlow in action through replays."
|
||||
title={t("title")}
|
||||
description={t("description")}
|
||||
/>
|
||||
<div className="grid w-3/4 grid-cols-1 gap-2 sm:w-full sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{caseStudies.map((caseStudy) => (
|
||||
<div key={caseStudy.title} className="w-full p-2">
|
||||
<BentoCard
|
||||
{...{
|
||||
Icon: caseStudy.icon,
|
||||
name: caseStudy.title,
|
||||
description: caseStudy.description,
|
||||
href: `/chat?replay=${caseStudy.id}`,
|
||||
cta: "Click to watch replay",
|
||||
className: "w-full h-full",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{cases.map((caseStudy, index) => {
|
||||
const iconData = caseStudyIcons[index];
|
||||
return (
|
||||
<div key={caseStudy.title} className="w-full p-2">
|
||||
<BentoCard
|
||||
{...{
|
||||
Icon: iconData?.icon ?? Building,
|
||||
name: caseStudy.title,
|
||||
description: caseStudy.description,
|
||||
href: `/chat?replay=${iconData?.id}`,
|
||||
cta: t("clickToWatch"),
|
||||
className: "w-full h-full",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -1,87 +1,90 @@
|
||||
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Bird, Microscope, Podcast, Usb, User } from "lucide-react";
|
||||
import {
|
||||
Bird,
|
||||
Microscope,
|
||||
Podcast,
|
||||
Usb,
|
||||
User,
|
||||
type LucideProps,
|
||||
} from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { ForwardRefExoticComponent, RefAttributes } from "react";
|
||||
|
||||
import { BentoCard, BentoGrid } from "~/components/magicui/bento-grid";
|
||||
|
||||
import { SectionHeader } from "../components/section-header";
|
||||
|
||||
const features = [
|
||||
type FeatureIcon = {
|
||||
Icon: ForwardRefExoticComponent<
|
||||
Omit<LucideProps, "ref"> & RefAttributes<SVGSVGElement>
|
||||
>;
|
||||
href: string;
|
||||
className: string;
|
||||
};
|
||||
|
||||
const featureIcons: Array<FeatureIcon> = [
|
||||
{
|
||||
Icon: Microscope,
|
||||
name: "Dive Deeper and Reach Wider",
|
||||
description:
|
||||
"Unlock deeper insights with advanced tools. Our powerful search + crawling and Python tools gathers comprehensive data, delivering in-depth reports to enhance your study.",
|
||||
href: "https://github.com/bytedance/deer-flow/blob/main/src/tools",
|
||||
cta: "Learn more",
|
||||
background: (
|
||||
<img alt="background" className="absolute -top-20 -right-20 opacity-60" />
|
||||
),
|
||||
className: "lg:col-start-1 lg:col-end-2 lg:row-start-1 lg:row-end-3",
|
||||
},
|
||||
{
|
||||
Icon: User,
|
||||
name: "Human-in-the-loop",
|
||||
description:
|
||||
"Refine your research plan, or adjust focus areas all through simple natural language.",
|
||||
href: "https://github.com/bytedance/deer-flow/blob/main/src/graph/nodes.py",
|
||||
cta: "Learn more",
|
||||
background: (
|
||||
<img alt="background" className="absolute -top-20 -right-20 opacity-60" />
|
||||
),
|
||||
className: "lg:col-start-1 lg:col-end-2 lg:row-start-3 lg:row-end-4",
|
||||
},
|
||||
{
|
||||
Icon: Bird,
|
||||
name: "Lang Stack",
|
||||
description:
|
||||
"Build with confidence using the LangChain and LangGraph frameworks.",
|
||||
href: "https://www.langchain.com/",
|
||||
cta: "Learn more",
|
||||
background: (
|
||||
<img alt="background" className="absolute -top-20 -right-20 opacity-60" />
|
||||
),
|
||||
className: "lg:col-start-2 lg:col-end-3 lg:row-start-1 lg:row-end-2",
|
||||
},
|
||||
{
|
||||
Icon: Usb,
|
||||
name: "MCP Integrations",
|
||||
description:
|
||||
"Supercharge your research workflow and expand your toolkit with seamless MCP integrations.",
|
||||
href: "https://github.com/bytedance/deer-flow/blob/main/src/graph/nodes.py",
|
||||
cta: "Learn more",
|
||||
background: (
|
||||
<img alt="background" className="absolute -top-20 -right-20 opacity-60" />
|
||||
),
|
||||
className: "lg:col-start-2 lg:col-end-3 lg:row-start-2 lg:row-end-3",
|
||||
},
|
||||
{
|
||||
Icon: Podcast,
|
||||
name: "Podcast Generation",
|
||||
description:
|
||||
"Instantly generate podcasts from reports. Perfect for on-the-go learning or sharing findings effortlessly. ",
|
||||
href: "https://github.com/bytedance/deer-flow/blob/main/src/podcast",
|
||||
cta: "Learn more",
|
||||
background: (
|
||||
<img alt="background" className="absolute -top-20 -right-20 opacity-60" />
|
||||
),
|
||||
className: "lg:col-start-2 lg:col-end-3 lg:row-start-3 lg:row-end-4",
|
||||
},
|
||||
];
|
||||
|
||||
export function CoreFeatureSection() {
|
||||
const t = useTranslations("landing.coreFeatures");
|
||||
const tCommon = useTranslations("common");
|
||||
const features = t.raw("features") as Array<{
|
||||
name: string;
|
||||
description: string;
|
||||
}>;
|
||||
|
||||
return (
|
||||
<section className="relative flex w-full flex-col content-around items-center justify-center">
|
||||
<SectionHeader
|
||||
anchor="core-features"
|
||||
title="Core Features"
|
||||
description="Find out what makes DeerFlow effective."
|
||||
title={t("title")}
|
||||
description={t("description")}
|
||||
/>
|
||||
<BentoGrid className="w-3/4 lg:grid-cols-2 lg:grid-rows-3">
|
||||
{features.map((feature) => (
|
||||
<BentoCard key={feature.name} {...feature} />
|
||||
))}
|
||||
{features.map((feature, index) => {
|
||||
const iconData = featureIcons[index];
|
||||
return iconData ? (
|
||||
<BentoCard
|
||||
key={feature.name}
|
||||
{...iconData}
|
||||
{...feature}
|
||||
background={
|
||||
<img
|
||||
alt="background"
|
||||
className="absolute -top-20 -right-20 opacity-60"
|
||||
/>
|
||||
}
|
||||
cta={tCommon("learnMore")}
|
||||
/>
|
||||
) : null;
|
||||
})}
|
||||
</BentoGrid>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import { GithubFilled } from "@ant-design/icons";
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { AuroraText } from "~/components/magicui/aurora-text";
|
||||
import { Button } from "~/components/ui/button";
|
||||
@@ -10,21 +11,22 @@ import { Button } from "~/components/ui/button";
|
||||
import { SectionHeader } from "../components/section-header";
|
||||
|
||||
export function JoinCommunitySection() {
|
||||
const t = useTranslations("landing.joinCommunity");
|
||||
return (
|
||||
<section className="flex w-full flex-col items-center justify-center pb-12">
|
||||
<SectionHeader
|
||||
anchor="join-community"
|
||||
title={
|
||||
<AuroraText colors={["#60A5FA", "#A5FA60", "#A560FA"]}>
|
||||
Join the DeerFlow Community
|
||||
{t("title")}
|
||||
</AuroraText>
|
||||
}
|
||||
description="Contribute brilliant ideas to shape the future of DeerFlow. Collaborate, innovate, and make impacts."
|
||||
description={t("description")}
|
||||
/>
|
||||
<Button className="text-xl" size="lg" asChild>
|
||||
<Link href="https://github.com/bytedance/deer-flow" target="_blank">
|
||||
<GithubFilled />
|
||||
Contribute Now
|
||||
{t("contributeNow")}
|
||||
</Link>
|
||||
</Button>
|
||||
</section>
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
// SPDX-License-Identifier: MIT
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { MultiAgentVisualization } from "../components/multi-agent-visualization";
|
||||
import { SectionHeader } from "../components/section-header";
|
||||
|
||||
export function MultiAgentSection() {
|
||||
const t = useTranslations("landing.multiAgent");
|
||||
return (
|
||||
<section className="relative flex w-full flex-col items-center justify-center">
|
||||
<SectionHeader
|
||||
anchor="multi-agent-architecture"
|
||||
title="Multi-Agent Architecture"
|
||||
description="Experience the agent teamwork with our Supervisor + Handoffs design pattern."
|
||||
title={t("title")}
|
||||
description={t("description")}
|
||||
/>
|
||||
<div className="flex h-[70vh] w-full flex-col items-center justify-center">
|
||||
<div className="h-full w-full">
|
||||
|
||||
@@ -6,6 +6,8 @@ import "~/styles/globals.css";
|
||||
import { type Metadata } from "next";
|
||||
import { Geist } from "next/font/google";
|
||||
import Script from "next/script";
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
import { getLocale, getMessages } from 'next-intl/server';
|
||||
|
||||
import { ThemeProviderWrapper } from "~/components/deer-flow/theme-provider-wrapper";
|
||||
import { env } from "~/env";
|
||||
@@ -27,8 +29,11 @@ const geist = Geist({
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{ children: React.ReactNode }>) {
|
||||
const locale = await getLocale();
|
||||
const messages = await getMessages();
|
||||
|
||||
return (
|
||||
<html lang="en" className={`${geist.variable}`} suppressHydrationWarning>
|
||||
<html lang={locale} className={`${geist.variable}`} suppressHydrationWarning>
|
||||
<head>
|
||||
{/* Define isSpace function globally to fix markdown-it issues with Next.js + Turbopack
|
||||
https://github.com/markdown-it/markdown-it/issues/1082#issuecomment-2749656365 */}
|
||||
@@ -43,8 +48,10 @@ export default async function RootLayout({
|
||||
</Script>
|
||||
</head>
|
||||
<body className="bg-app">
|
||||
<ThemeProviderWrapper>{children}</ThemeProviderWrapper>
|
||||
<Toaster />
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
<ThemeProviderWrapper>{children}</ThemeProviderWrapper>
|
||||
<Toaster />
|
||||
</NextIntlClientProvider>
|
||||
{
|
||||
// NO USER BEHAVIOR TRACKING OR PRIVATE DATA COLLECTION BY DEFAULT
|
||||
//
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { SiteHeader } from "./chat/components/site-header";
|
||||
@@ -27,20 +28,20 @@ export default function HomePage() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Footer() {
|
||||
const t = useTranslations('footer');
|
||||
const year = useMemo(() => new Date().getFullYear(), []);
|
||||
return (
|
||||
<footer className="container mt-32 flex flex-col items-center justify-center">
|
||||
<hr className="from-border/0 via-border/70 to-border/0 m-0 h-px w-full border-none bg-gradient-to-r" />
|
||||
<div className="text-muted-foreground container flex h-20 flex-col items-center justify-center text-sm">
|
||||
<p className="text-center font-serif text-lg md:text-xl">
|
||||
"Originated from Open Source, give back to Open Source."
|
||||
"{t('quote')}"
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-muted-foreground container mb-8 flex flex-col items-center justify-center text-xs">
|
||||
<p>Licensed under MIT License</p>
|
||||
<p>© {year} DeerFlow</p>
|
||||
<p>{t('license')}</p>
|
||||
<p>© {year} {t('copyright')}</p>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
|
||||
import { Button } from "~/components/ui/button";
|
||||
@@ -29,6 +30,7 @@ export function AddMCPServerDialog({
|
||||
}: {
|
||||
onAdd?: (servers: MCPServerMetadata[]) => void;
|
||||
}) {
|
||||
const t = useTranslations("settings");
|
||||
const [open, setOpen] = useState(false);
|
||||
const [input, setInput] = useState("");
|
||||
const [validationError, setValidationError] = useState<string | null>("");
|
||||
@@ -50,7 +52,7 @@ export function AddMCPServerDialog({
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
setValidationError("Invalid JSON");
|
||||
setValidationError(t("invalidJson"));
|
||||
return;
|
||||
}
|
||||
const result = MCPConfigSchema.safeParse(JSON.parse(value));
|
||||
@@ -65,17 +67,17 @@ export function AddMCPServerDialog({
|
||||
}
|
||||
}
|
||||
const errorMessage =
|
||||
result.error.errors[0]?.message ?? "Validation failed";
|
||||
result.error.errors[0]?.message ?? t("validationFailed");
|
||||
setValidationError(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
const keys = Object.keys(result.data.mcpServers);
|
||||
if (keys.length === 0) {
|
||||
setValidationError("Missing server name in `mcpServers`");
|
||||
setValidationError(t("missingServerName"));
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
}, [t]);
|
||||
|
||||
const handleAdd = useCallback(async () => {
|
||||
abortControllerRef.current = new AbortController();
|
||||
@@ -139,21 +141,21 @@ export function AddMCPServerDialog({
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button size="sm">Add Servers</Button>
|
||||
<Button size="sm">{t("addServers")}</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[560px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add New MCP Servers</DialogTitle>
|
||||
<DialogTitle>{t("addNewMCPServers")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogDescription>
|
||||
DeerFlow uses the standard JSON MCP config to create a new server.
|
||||
{t("mcpConfigDescription")}
|
||||
<br />
|
||||
Paste your config below and click "Add" to add new servers.
|
||||
{t("pasteConfigBelow")}
|
||||
</DialogDescription>
|
||||
|
||||
<main>
|
||||
<Textarea
|
||||
className="h-[360px] sm:max-w-[510px] break-all"
|
||||
className="h-[360px] break-all sm:max-w-[510px]"
|
||||
placeholder={
|
||||
'Example:\n\n{\n "mcpServers": {\n "My Server": {\n "command": "python",\n "args": [\n "-m", "mcp_server"\n ],\n "env": {\n "API_KEY": "YOUR_API_KEY"\n }\n }\n }\n}'
|
||||
}
|
||||
@@ -169,7 +171,7 @@ export function AddMCPServerDialog({
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
{t("cancel", { defaultValue: "Cancel" })}
|
||||
</Button>
|
||||
<Button
|
||||
className="w-24"
|
||||
@@ -178,7 +180,7 @@ export function AddMCPServerDialog({
|
||||
onClick={handleAdd}
|
||||
>
|
||||
{processing && <Loader2 className="animate-spin" />}
|
||||
Add
|
||||
{t("add")}
|
||||
</Button>
|
||||
{
|
||||
processing && (
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Settings } from "lucide-react";
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { Tooltip } from "~/components/deer-flow/tooltip";
|
||||
@@ -29,6 +30,8 @@ import { cn } from "~/lib/utils";
|
||||
import { SETTINGS_TABS } from "../tabs";
|
||||
|
||||
export function SettingsDialog() {
|
||||
const t = useTranslations('settings');
|
||||
const tCommon = useTranslations('common');
|
||||
const { isReplay } = useReplay();
|
||||
const [activeTabId, setActiveTabId] = useState(SETTINGS_TABS[0]!.id);
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -92,7 +95,7 @@ export function SettingsDialog() {
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<Tooltip title="Settings">
|
||||
<Tooltip title={tCommon('settings')}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Settings />
|
||||
@@ -101,9 +104,9 @@ export function SettingsDialog() {
|
||||
</Tooltip>
|
||||
<DialogContent className="sm:max-w-[850px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>DeerFlow Settings</DialogTitle>
|
||||
<DialogTitle>{t('title')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Manage your DeerFlow settings here.
|
||||
{t('description')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Tabs value={activeTabId}>
|
||||
@@ -157,10 +160,10 @@ export function SettingsDialog() {
|
||||
</Tabs>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
{tCommon('cancel')}
|
||||
</Button>
|
||||
<Button className="w-24" type="submit" onClick={handleSave}>
|
||||
Save
|
||||
{tCommon('save')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Settings } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
@@ -47,6 +48,7 @@ export const GeneralTab: Tab = ({
|
||||
settings: SettingsState;
|
||||
onChange: (changes: Partial<SettingsState>) => void;
|
||||
}) => {
|
||||
const t = useTranslations("settings.general");
|
||||
const generalSettings = useMemo(() => settings.general, [settings]);
|
||||
const form = useForm<z.infer<typeof generalFormSchema>>({
|
||||
resolver: zodResolver(generalFormSchema, undefined, undefined),
|
||||
@@ -75,7 +77,7 @@ export const GeneralTab: Tab = ({
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<header>
|
||||
<h1 className="text-lg font-medium">General</h1>
|
||||
<h1 className="text-lg font-medium">{t("title")}</h1>
|
||||
</header>
|
||||
<main>
|
||||
<Form {...form}>
|
||||
@@ -93,7 +95,7 @@ export const GeneralTab: Tab = ({
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm" htmlFor="autoAcceptedPlan">
|
||||
Allow automatic acceptance of plans
|
||||
{t("autoAcceptPlan")}
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
@@ -105,7 +107,7 @@ export const GeneralTab: Tab = ({
|
||||
name="maxPlanIterations"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Max plan iterations</FormLabel>
|
||||
<FormLabel>{t("maxPlanIterations")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="w-60"
|
||||
@@ -118,8 +120,7 @@ export const GeneralTab: Tab = ({
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Set to 1 for single-step planning. Set to 2 or more to
|
||||
enable re-planning.
|
||||
{t("maxPlanIterationsDescription")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -130,7 +131,7 @@ export const GeneralTab: Tab = ({
|
||||
name="maxStepNum"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Max steps of a research plan</FormLabel>
|
||||
<FormLabel>{t("maxStepsOfPlan")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="w-60"
|
||||
@@ -142,9 +143,7 @@ export const GeneralTab: Tab = ({
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
By default, each research plan has 3 steps.
|
||||
</FormDescription>
|
||||
<FormDescription>{t("maxStepsDescription")}</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -154,7 +153,7 @@ export const GeneralTab: Tab = ({
|
||||
name="maxSearchResults"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Max search results</FormLabel>
|
||||
<FormLabel>{t("maxSearchResults")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="w-60"
|
||||
@@ -167,7 +166,7 @@ export const GeneralTab: Tab = ({
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
By default, each search step has 3 results.
|
||||
{t("maxSearchResultsDescription")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
Reference in New Issue
Block a user