mirror of
https://git.mirrors.martin98.com/https://github.com/bytedance/deer-flow
synced 2025-08-20 09:59:07 +08:00
feat: implement General tab
This commit is contained in:
parent
1e7036ed90
commit
17d53f98bd
@ -1,5 +1,15 @@
|
|||||||
import { BadgeInfo, Blocks, Settings } from "lucide-react";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useState } from "react";
|
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 { Button } from "~/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@ -11,7 +21,23 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "~/components/ui/dialog";
|
} 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 { Tabs, TabsContent } from "~/components/ui/tabs";
|
||||||
|
import {
|
||||||
|
type SettingsState,
|
||||||
|
changeSettings,
|
||||||
|
saveSettings,
|
||||||
|
useSettingsStore,
|
||||||
|
} from "~/core/store";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
import { Markdown } from "../_components/markdown";
|
import { Markdown } from "../_components/markdown";
|
||||||
@ -19,27 +45,35 @@ import { Tooltip } from "../_components/tooltip";
|
|||||||
|
|
||||||
import about from "./about.md";
|
import about from "./about.md";
|
||||||
|
|
||||||
const SETTINGS_TABS = [
|
|
||||||
{
|
|
||||||
id: "general",
|
|
||||||
label: "General",
|
|
||||||
icon: Settings,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "mcp",
|
|
||||||
label: "MCP",
|
|
||||||
icon: Blocks,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "about",
|
|
||||||
label: "About",
|
|
||||||
icon: BadgeInfo,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
export function SettingsDialog() {
|
export function SettingsDialog() {
|
||||||
const [activeTabId, setActiveTabId] = useState(SETTINGS_TABS[0]!.id);
|
const [activeTabId, setActiveTabId] = useState(SETTINGS_TABS[0]!.id);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [settings, setSettings] = useState(useSettingsStore.getState());
|
||||||
|
const changes = useRef<Partial<SettingsState>>({});
|
||||||
|
|
||||||
|
const handleTabChange = useCallback((newChanges: Partial<SettingsState>) => {
|
||||||
|
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 (
|
return (
|
||||||
<Dialog>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<Tooltip title="Settings">
|
<Tooltip title="Settings">
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="ghost" size="icon">
|
<Button variant="ghost" size="icon">
|
||||||
@ -76,22 +110,23 @@ export function SettingsDialog() {
|
|||||||
</ul>
|
</ul>
|
||||||
<div className="min-w-0 flex-grow">
|
<div className="min-w-0 flex-grow">
|
||||||
<div className="size-full overflow-auto p-4">
|
<div className="size-full overflow-auto p-4">
|
||||||
<TabsContent value="general">
|
{SETTINGS_TABS.map((tab) => (
|
||||||
<div>General</div>
|
<TabsContent key={tab.id} value={tab.id}>
|
||||||
</TabsContent>
|
<tab.component
|
||||||
<TabsContent value="mcp">
|
settings={settings}
|
||||||
<div>Coming soon...</div>
|
onChange={handleTabChange}
|
||||||
</TabsContent>
|
/>
|
||||||
<TabsContent value="about">
|
|
||||||
<Markdown>{about}</Markdown>
|
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline">Cancel</Button>
|
<Button variant="outline" onClick={() => setOpen(false)}>
|
||||||
<Button className="w-24" type="submit">
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button className="w-24" type="submit" onClick={handleSave}>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
@ -99,3 +134,134 @@ export function SettingsDialog() {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Tab = FunctionComponent<{
|
||||||
|
settings: SettingsState;
|
||||||
|
onChange: (changes: Partial<SettingsState>) => 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<SettingsState>) => void;
|
||||||
|
}) => {
|
||||||
|
const generalSettings = useMemo(() => settings.general, [settings]);
|
||||||
|
const form = useForm<z.infer<typeof generalFormSchema>>({
|
||||||
|
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 (
|
||||||
|
<Form {...form}>
|
||||||
|
<form className="space-y-8">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="maxPlanIterations"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Max plan iterations</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
className="w-60"
|
||||||
|
type="number"
|
||||||
|
{...field}
|
||||||
|
min={1}
|
||||||
|
onChange={(event) =>
|
||||||
|
field.onChange(parseInt(event.target.value))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
Set to 1 for single-step planning. Set to 2 to enable
|
||||||
|
re-planning.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="maxStepNum"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Max steps of a research plan</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
className="w-60"
|
||||||
|
type="number"
|
||||||
|
{...field}
|
||||||
|
min={1}
|
||||||
|
onChange={(event) =>
|
||||||
|
field.onChange(parseInt(event.target.value))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
By default, each research plan has 3 steps.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
GeneralTab.displayName = "GeneralTab";
|
||||||
|
GeneralTab.icon = Settings;
|
||||||
|
|
||||||
|
const MCPTab: Tab = () => {
|
||||||
|
return (
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
<p>Coming soon...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
MCPTab.icon = Blocks;
|
||||||
|
|
||||||
|
const AboutTab: Tab = () => {
|
||||||
|
return <Markdown>{about}</Markdown>;
|
||||||
|
};
|
||||||
|
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 ?? <Settings />) as LucideIcon,
|
||||||
|
component: tab,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user