Files
deer-flow/web/src/app/settings/tabs/general-tab.tsx
2025-06-11 19:48:46 +08:00

184 lines
5.7 KiB
TypeScript

// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
// SPDX-License-Identifier: MIT
import { zodResolver } from "@hookform/resolvers/zod";
import { Settings } from "lucide-react";
import { useEffect, useMemo } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "~/components/ui/form";
import { Input } from "~/components/ui/input";
import { Label } from "~/components/ui/label";
import { Switch } from "~/components/ui/switch";
import type { SettingsState } from "~/core/store";
import type { Tab } from "./types";
const generalFormSchema = z.object({
autoAcceptedPlan: z.boolean(),
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.",
}),
maxSearchResults: z.number().min(1, {
message: "Max search results must be at least 1.",
}),
// Others
enableBackgroundInvestigation: z.boolean(),
enableDeepThinking: z.boolean(),
reportStyle: z.enum(["academic", "popular_science", "news", "social_media"]),
});
export 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,
mode: "all",
reValidateMode: "onBlur",
});
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 (
<div className="flex flex-col gap-4">
<header>
<h1 className="text-lg font-medium">General</h1>
</header>
<main>
<Form {...form}>
<form className="space-y-8">
<FormField
control={form.control}
name="autoAcceptedPlan"
render={({ field }) => (
<FormItem>
<FormControl>
<div className="flex items-center gap-2">
<Switch
id="autoAcceptedPlan"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label className="text-sm" htmlFor="autoAcceptedPlan">
Allow automatic acceptance of plans
</Label>
</div>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="maxPlanIterations"
render={({ field }) => (
<FormItem>
<FormLabel>Max plan iterations</FormLabel>
<FormControl>
<Input
className="w-60"
type="number"
defaultValue={field.value}
min={1}
onChange={(event) =>
field.onChange(parseInt(event.target.value || "0"))
}
/>
</FormControl>
<FormDescription>
Set to 1 for single-step planning. Set to 2 or more 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"
defaultValue={field.value}
min={1}
onChange={(event) =>
field.onChange(parseInt(event.target.value || "0"))
}
/>
</FormControl>
<FormDescription>
By default, each research plan has 3 steps.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="maxSearchResults"
render={({ field }) => (
<FormItem>
<FormLabel>Max search results</FormLabel>
<FormControl>
<Input
className="w-60"
type="number"
defaultValue={field.value}
min={1}
onChange={(event) =>
field.onChange(parseInt(event.target.value || "0"))
}
/>
</FormControl>
<FormDescription>
By default, each search step has 3 results.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</main>
</div>
);
};
GeneralTab.displayName = "";
GeneralTab.icon = Settings;