|
"use client" |
|
|
|
import * as React from "react" |
|
import { Button } from "@/components/ui/button" |
|
import { |
|
Dialog, |
|
DialogContent, |
|
DialogDescription, |
|
DialogFooter, |
|
DialogHeader, |
|
DialogTitle, |
|
DialogTrigger, |
|
} from "@/components/ui/dialog" |
|
import { Input } from "@/components/ui/input" |
|
import { Label } from "@/components/ui/label" |
|
import { |
|
Form, |
|
FormControl, |
|
FormDescription, |
|
FormField, |
|
FormItem, |
|
FormLabel, |
|
FormMessage, |
|
} from "@/components/ui/form" |
|
import { Textarea } from "@/components/ui/textarea" |
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" |
|
import { |
|
Select, |
|
SelectContent, |
|
SelectGroup, |
|
SelectItem, |
|
SelectLabel, |
|
SelectTrigger, |
|
SelectValue, |
|
} from "@/components/ui/select" |
|
import { SettingsIcon, EraserIcon, ShieldCheckIcon } from "lucide-react" |
|
import { zodResolver } from "@hookform/resolvers/zod" |
|
import { useForm } from "react-hook-form" |
|
import { z } from "zod" |
|
import { toast } from "sonner" |
|
import { useAppDispatch, useAppSelector } from "@/common/hooks" |
|
import { ECozeBaseUrl } from "@/common/constant" |
|
import { |
|
setAgentSettings, |
|
setCozeSettings, |
|
resetCozeSettings, |
|
resetDifySettings, |
|
setGlobalSettingsDialog, |
|
setDifySettings, |
|
} from "@/store/reducers/global" |
|
|
|
const TABS_OPTIONS = [ |
|
{ |
|
label: "Agent", |
|
value: "agent", |
|
}, |
|
{ |
|
label: "Coze", |
|
value: "coze", |
|
}, |
|
{ |
|
label: "Dify", |
|
value: "dify", |
|
}, |
|
] |
|
|
|
export const useSettingsTabs = () => { |
|
const [tabs, setTabs] = React.useState(TABS_OPTIONS) |
|
|
|
const graphName = useAppSelector((state) => state.global.graphName) |
|
|
|
const enableCozeSettingsMemo = React.useMemo(() => { |
|
return isCozeGraph(graphName) |
|
}, [graphName]) |
|
|
|
const enableDifySettingsMemo = React.useMemo(() => { |
|
return isDifyGraph(graphName) |
|
}, [graphName]) |
|
|
|
const enableGreetingsOrPromptMemo: { greeting: boolean, prompt: boolean } = React.useMemo(() => { |
|
if (graphName === "va_gemini_v2v") { |
|
return { |
|
greeting: false, |
|
prompt: true, |
|
} |
|
} else if (graphName === "va_dify_azure") { |
|
return { |
|
greeting: true, |
|
prompt: false, |
|
} |
|
} else if (graphName === "story_teller_stt_integrated") { |
|
return { |
|
greeting: true, |
|
prompt: false, |
|
} |
|
} |
|
|
|
return { |
|
greeting: true, |
|
prompt: true, |
|
} |
|
}, [graphName]) |
|
|
|
|
|
|
|
React.useEffect(() => { |
|
if (enableCozeSettingsMemo) { |
|
setTabs((prev) => |
|
[ |
|
{ label: "Agent", value: "agent" }, |
|
{ label: "Coze", value: "coze" }, |
|
] |
|
) |
|
} else if (enableDifySettingsMemo) { |
|
setTabs((prev) => |
|
[ |
|
{ label: "Agent", value: "agent" }, |
|
{ label: "Dify", value: "dify" }, |
|
] |
|
) |
|
} else { |
|
setTabs((prev) => prev.filter((tab) => tab.value !== "coze" && tab.value !== "dify")) |
|
} |
|
}, [enableCozeSettingsMemo, enableDifySettingsMemo]) |
|
|
|
return { |
|
tabs, |
|
enableGreetingsOrPromptMemo, |
|
} |
|
} |
|
|
|
export default function SettingsDialog() { |
|
const dispatch = useAppDispatch() |
|
const globalSettingsDialog = useAppSelector( |
|
(state) => state.global.globalSettingsDialog, |
|
) |
|
|
|
const { tabs, enableGreetingsOrPromptMemo } = useSettingsTabs() |
|
|
|
const handleClose = () => { |
|
dispatch(setGlobalSettingsDialog({ open: false, tab: undefined })) |
|
} |
|
|
|
return ( |
|
<Dialog |
|
open={globalSettingsDialog.open} |
|
onOpenChange={(open) => |
|
dispatch(setGlobalSettingsDialog({ open, tab: undefined })) |
|
} |
|
> |
|
<DialogTrigger asChild> |
|
<Button variant="ghost" size="sm" className="w-9"> |
|
<SettingsIcon /> |
|
</Button> |
|
</DialogTrigger> |
|
<DialogContent className="w-fit"> |
|
<DialogHeader> |
|
<DialogTitle>Settings</DialogTitle> |
|
</DialogHeader> |
|
<Tabs |
|
defaultValue={globalSettingsDialog.tab || TABS_OPTIONS[0].value} |
|
className="w-full min-w-96 max-w-screen-sm" |
|
> |
|
{tabs.length > 1 && ( |
|
<TabsList className="w-full"> |
|
{tabs.map((tab) => ( |
|
<TabsTrigger |
|
key={tab.value} |
|
className="w-full" |
|
value={tab.value} |
|
> |
|
{tab.label} |
|
</TabsTrigger> |
|
))} |
|
</TabsList> |
|
)} |
|
<TabsContent value="agent"> |
|
<CommonAgentSettingsTab |
|
enableGreeting={enableGreetingsOrPromptMemo.greeting} |
|
enablePrompt={enableGreetingsOrPromptMemo.prompt} |
|
handleClose={handleClose} |
|
handleSubmit={handleClose} |
|
/> |
|
</TabsContent> |
|
<TabsContent value="coze"> |
|
<CozeSettingsTab |
|
handleClose={handleClose} |
|
handleSubmit={handleClose} |
|
/> |
|
</TabsContent> |
|
<TabsContent value="dify"> |
|
<DifySettingsTab |
|
handleClose={handleClose} |
|
handleSubmit={handleClose} |
|
/> |
|
</TabsContent> |
|
</Tabs> |
|
</DialogContent> |
|
</Dialog> |
|
) |
|
} |
|
|
|
const formSchema = z.object({ |
|
greeting: z.string().optional(), |
|
prompt: z.string().optional(), |
|
}) |
|
|
|
export function CommonAgentSettingsTab(props: { |
|
enableGreeting?: boolean |
|
enablePrompt?: boolean |
|
handleClose?: () => void |
|
handleSubmit?: (values: z.infer<typeof formSchema>) => void |
|
}) { |
|
const { handleSubmit, enableGreeting, enablePrompt } = props |
|
|
|
const dispatch = useAppDispatch() |
|
const agentSettings = useAppSelector((state) => state.global.agentSettings) |
|
|
|
const form = useForm<z.infer<typeof formSchema>>({ |
|
resolver: zodResolver(formSchema), |
|
defaultValues: { |
|
greeting: agentSettings.greeting, |
|
prompt: agentSettings.prompt, |
|
}, |
|
}) |
|
|
|
function onSubmit(values: z.infer<typeof formSchema>) { |
|
console.log("Form Values:", values) |
|
dispatch(setAgentSettings(values)) |
|
handleSubmit?.(values) |
|
} |
|
|
|
return ( |
|
<Form {...form}> |
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> |
|
{enableGreeting && <FormField |
|
control={form.control} |
|
name="greeting" |
|
render={({ field }) => ( |
|
<FormItem> |
|
<FormLabel>Greeting</FormLabel> |
|
<FormControl> |
|
<Textarea |
|
placeholder="Enter the greeting, leave it blank if you want to use default one." |
|
className="resize-none" |
|
{...field} |
|
/> |
|
</FormControl> |
|
<FormMessage /> |
|
</FormItem> |
|
)} |
|
/>} |
|
{enablePrompt && <FormField |
|
control={form.control} |
|
name="prompt" |
|
render={({ field }) => ( |
|
<FormItem> |
|
<FormLabel>Prompt</FormLabel> |
|
<FormControl> |
|
<Textarea |
|
placeholder="Enter the prompt, leave it blank if you want to use default one." |
|
className="resize-none" |
|
rows={4} |
|
{...field} |
|
/> |
|
</FormControl> |
|
<FormMessage /> |
|
</FormItem> |
|
)} |
|
/>} |
|
<DialogFooter> |
|
<Button type="submit" disabled={!form.formState.isValid}> |
|
Save Agent Settings |
|
</Button> |
|
</DialogFooter> |
|
</form> |
|
</Form> |
|
) |
|
} |
|
|
|
export const cozeSettingsFormSchema = z.object({ |
|
token: z |
|
.string({ |
|
message: "Token is required", |
|
}) |
|
.min(1), |
|
bot_id: z |
|
.string({ |
|
message: "Bot ID is required", |
|
}) |
|
.min(1), |
|
base_url: z.nativeEnum(ECozeBaseUrl).default(ECozeBaseUrl.GLOBAL), |
|
}) |
|
|
|
export const isCozeGraph = (graphName: string) => { |
|
return graphName.toLowerCase().includes("coze") |
|
} |
|
|
|
export function CozeSettingsTab(props: { |
|
handleClose?: () => void |
|
handleSubmit?: (values: z.infer<typeof cozeSettingsFormSchema>) => void |
|
}) { |
|
const { handleSubmit } = props |
|
|
|
const dispatch = useAppDispatch() |
|
const cozeSettings = useAppSelector((state) => state.global.cozeSettings) |
|
|
|
const form = useForm<z.infer<typeof cozeSettingsFormSchema>>({ |
|
resolver: zodResolver(cozeSettingsFormSchema), |
|
defaultValues: { |
|
token: cozeSettings.token, |
|
bot_id: cozeSettings.bot_id, |
|
base_url: cozeSettings.base_url as z.infer< |
|
typeof cozeSettingsFormSchema |
|
>["base_url"], |
|
}, |
|
}) |
|
|
|
function onSubmit(values: z.infer<typeof cozeSettingsFormSchema>) { |
|
console.log("Coze Form Values:", values) |
|
dispatch(setCozeSettings(values)) |
|
handleSubmit?.(values) |
|
} |
|
|
|
return ( |
|
<Form {...form}> |
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> |
|
<FormField |
|
control={form.control} |
|
name="bot_id" |
|
render={({ field }) => ( |
|
<FormItem> |
|
<FormLabel>Bot ID*</FormLabel> |
|
<FormControl> |
|
<Input placeholder="Enter your Coze bot ID" {...field} /> |
|
</FormControl> |
|
<FormMessage /> |
|
</FormItem> |
|
)} |
|
/> |
|
<FormField |
|
control={form.control} |
|
name="token" |
|
render={({ field }) => ( |
|
<FormItem> |
|
<FormLabel>Token*</FormLabel> |
|
<FormControl> |
|
<Input placeholder="Enter your Coze API token" {...field} /> |
|
</FormControl> |
|
<FormMessage /> |
|
</FormItem> |
|
)} |
|
/> |
|
<FormField |
|
control={form.control} |
|
name="base_url" |
|
render={({ field }) => ( |
|
<FormItem> |
|
<FormLabel>Base URL*</FormLabel> |
|
<FormControl> |
|
<Select {...field} onValueChange={field.onChange}> |
|
<SelectTrigger> |
|
<SelectValue placeholder="Select base URL" /> |
|
</SelectTrigger> |
|
<SelectContent> |
|
<SelectItem value={ECozeBaseUrl.GLOBAL}> |
|
{ECozeBaseUrl.GLOBAL} |
|
</SelectItem> |
|
<SelectItem value={ECozeBaseUrl.CN}> |
|
{ECozeBaseUrl.CN} |
|
</SelectItem> |
|
{/* <SelectItem value={ECozeBaseUrl.CN}> |
|
{ECozeBaseUrl.CN} |
|
</SelectItem> */} |
|
</SelectContent> |
|
</Select> |
|
</FormControl> |
|
<FormMessage /> |
|
</FormItem> |
|
)} |
|
/> |
|
<div className="flex flex-col items-end"> |
|
<div className="flex items-center gap-2"> |
|
<Button |
|
variant="destructive" |
|
size="icon" |
|
onClick={(e) => { |
|
e.preventDefault() |
|
e.stopPropagation() |
|
form.reset({ |
|
token: "", |
|
bot_id: "", |
|
base_url: ECozeBaseUrl.GLOBAL, |
|
}) |
|
dispatch(resetCozeSettings()) |
|
toast.success("Coze settings reset") |
|
}} |
|
> |
|
<EraserIcon /> |
|
</Button> |
|
<Button type="submit" disabled={!form.formState.isValid}> |
|
Save Coze Settings |
|
</Button> |
|
</div> |
|
<Label className="flex select-none items-center gap-1 pt-2 text-right text-xs text-muted-foreground"> |
|
<ShieldCheckIcon className="me-1 size-3" /> |
|
Settings are saved in your browser only |
|
</Label> |
|
</div> |
|
</form> |
|
</Form> |
|
) |
|
} |
|
|
|
export const difySettingsFormSchema = z.object({ |
|
api_key: z |
|
.string({ |
|
message: "API Key is required", |
|
}) |
|
.min(1), |
|
}) |
|
|
|
export const isDifyGraph = (graphName: string) => { |
|
return graphName.toLowerCase().includes("dify") |
|
} |
|
|
|
export function DifySettingsTab(props: { |
|
handleClose?: () => void |
|
handleSubmit?: (values: z.infer<typeof difySettingsFormSchema>) => void |
|
}) { |
|
const { handleSubmit } = props |
|
|
|
const dispatch = useAppDispatch() |
|
const difySettings = useAppSelector((state) => state.global.difySettings) |
|
|
|
const form = useForm<z.infer<typeof difySettingsFormSchema>>({ |
|
resolver: zodResolver(difySettingsFormSchema), |
|
defaultValues: { |
|
api_key: difySettings.api_key, |
|
}, |
|
}) |
|
|
|
function onSubmit(values: z.infer<typeof difySettingsFormSchema>) { |
|
console.log("Dify Form Values:", values) |
|
dispatch(setDifySettings(values)) |
|
handleSubmit?.(values) |
|
} |
|
|
|
return ( |
|
<Form {...form}> |
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> |
|
<FormField |
|
control={form.control} |
|
name="api_key" |
|
render={({ field }) => ( |
|
<FormItem> |
|
<FormLabel>API Key*</FormLabel> |
|
<FormControl> |
|
<Input placeholder="Enter your Dify API Key" {...field} /> |
|
</FormControl> |
|
<FormMessage /> |
|
</FormItem> |
|
)} |
|
/> |
|
<div className="flex flex-col items-end"> |
|
<div className="flex items-center gap-2"> |
|
<Button |
|
variant="destructive" |
|
size="icon" |
|
onClick={(e) => { |
|
e.preventDefault() |
|
e.stopPropagation() |
|
form.reset({ |
|
api_key: "", |
|
}) |
|
dispatch(resetDifySettings()) |
|
toast.success("Dify settings reset") |
|
}} |
|
> |
|
<EraserIcon /> |
|
</Button> |
|
<Button type="submit" disabled={!form.formState.isValid}> |
|
Save Dify Settings |
|
</Button> |
|
</div> |
|
<Label className="flex select-none items-center gap-1 pt-2 text-right text-xs text-muted-foreground"> |
|
<ShieldCheckIcon className="me-1 size-3" /> |
|
Settings are saved in your browser only |
|
</Label> |
|
</div> |
|
</form> |
|
</Form> |
|
) |
|
} |