import { zodResolver } from "@hookform/resolvers/zod"; import { BadgeInfo, Blocks, Settings, type LucideIcon } from "lucide-react"; import { type FunctionComponent, useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { Button } from "~/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "~/components/ui/dialog"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "~/components/ui/form"; import { Input } from "~/components/ui/input"; import { Tabs, TabsContent } from "~/components/ui/tabs"; import { type SettingsState, changeSettings, saveSettings, useSettingsStore, } from "~/core/store"; import { cn } from "~/lib/utils"; import { Markdown } from "../_components/markdown"; import { Tooltip } from "../_components/tooltip"; import about from "./about.md"; export function SettingsDialog() { const [activeTabId, setActiveTabId] = useState(SETTINGS_TABS[0]!.id); const [open, setOpen] = useState(false); const [settings, setSettings] = useState(useSettingsStore.getState()); const changes = useRef>({}); const handleTabChange = useCallback((newChanges: Partial) => { changes.current = { ...changes.current, ...newChanges, }; }, []); const handleSave = useCallback(() => { if (Object.keys(changes.current).length > 0) { const newSettings: SettingsState = { ...settings, ...changes.current, }; setSettings(newSettings); changes.current = {}; changeSettings(newSettings); saveSettings(); } setOpen(false); }, [settings, changes]); return ( DeerFlow Settings Manage your DeerFlow settings here.
    {SETTINGS_TABS.map((tab) => (
  • setActiveTabId(tab.id)} > {tab.label}
  • ))}
{SETTINGS_TABS.map((tab) => ( ))}
); } type Tab = FunctionComponent<{ settings: SettingsState; onChange: (changes: Partial) => void; }> & { displayName?: string; icon?: LucideIcon; }; const generalFormSchema = z.object({ maxPlanIterations: z.number().min(1, { message: "Max plan iterations must be at least 1.", }), maxStepNum: z.number().min(1, { message: "Max step number must be at least 1.", }), }); const GeneralTab: Tab = ({ settings, onChange, }: { settings: SettingsState; onChange: (changes: Partial) => void; }) => { const generalSettings = useMemo(() => settings.general, [settings]); const form = useForm>({ resolver: zodResolver(generalFormSchema, undefined, undefined), defaultValues: generalSettings, }); const currentSettings = form.watch(); useEffect(() => { let hasChanges = false; for (const key in currentSettings) { if ( currentSettings[key as keyof typeof currentSettings] !== settings.general[key as keyof SettingsState["general"]] ) { hasChanges = true; break; } } if (hasChanges) { onChange({ general: currentSettings }); } }, [currentSettings, onChange, settings]); return (
( Max plan iterations field.onChange(parseInt(event.target.value)) } /> Set to 1 for single-step planning. Set to 2 to enable re-planning. )} /> ( Max steps of a research plan field.onChange(parseInt(event.target.value)) } /> By default, each research plan has 3 steps. )} /> ); }; GeneralTab.displayName = "GeneralTab"; GeneralTab.icon = Settings; const MCPTab: Tab = () => { return (

Coming soon...

); }; MCPTab.icon = Blocks; const AboutTab: Tab = () => { return {about}; }; AboutTab.icon = BadgeInfo; const SETTINGS_TABS = [GeneralTab, MCPTab, AboutTab].map((tab) => { const name = tab.name ?? tab.displayName; return { ...tab, id: name.replace(/Tab$/, "").toLocaleLowerCase(), label: name.replace(/Tab$/, ""), icon: (tab.icon ?? ) as LucideIcon, component: tab, }; });