"use client" import { useEffect, useState } from "react" import { zodResolver } from "@hookform/resolvers/zod" import { Controller, Resolver, useForm, useWatch } from "react-hook-form" import { z } from "zod/v4" import { OptionCombobox } from "@/components/option-combobox" import { ProjectDialog } from "@/components/project-dialog" import { Button } from "@/components/ui/button" import { Field, FieldContent, FieldError, FieldLabel, } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { type AIAgent, type AdminChannel, type CreateAdminChannelPayload, fetchAIAgentsAll, fetchChannel, } from "@/lib/api/admin" type ChannelFormDialogProps = { open: boolean saving: boolean itemId: number | null onOpenChange: (open: boolean) => void onSubmit: (payload: CreateAdminChannelPayload) => Promise } const channelTypeOptions = [ { value: "web", label: "Web 站点" }, { value: "wxwork_kf", label: "企业微信客服" }, ] as const const widgetPositionOptions = [ { value: "right", label: "右下角" }, { value: "left", label: "左下角" }, ] as const type WebChannelConfig = { title?: string subtitle?: string themeColor?: string position?: "left" | "right" width?: string } const defaultWebChannelConfig: Required = { title: "在线客服", subtitle: "欢迎咨询", themeColor: "#2563eb", position: "right", width: "380px", } const schema = z.object({ channelType: z.enum(["web", "wxwork_kf"], "请选择渠道类型"), aiAgentId: z.string().trim().regex(/^\d+$/, "请选择 AI Agent"), name: z.string().trim().min(1, "渠道名称不能为空"), openKfId: z.string().trim(), widgetTitle: z.string().trim(), widgetSubtitle: z.string().trim(), widgetThemeColor: z.string().trim(), widgetPosition: z.enum(["left", "right"]), widgetWidth: z.string().trim(), remark: z.string().trim(), }) type EditForm = z.infer const resolver = zodResolver(schema as never) as Resolver< z.input, undefined, z.output > const emptyForm: EditForm = { channelType: "web", aiAgentId: "", name: "", openKfId: "", widgetTitle: defaultWebChannelConfig.title, widgetSubtitle: defaultWebChannelConfig.subtitle, widgetThemeColor: defaultWebChannelConfig.themeColor, widgetPosition: defaultWebChannelConfig.position, widgetWidth: defaultWebChannelConfig.width, remark: "", } function parseOpenKfId(configJson: string): string { if (!configJson.trim()) { return "" } try { const parsed = JSON.parse(configJson) as { openKfId?: string } return typeof parsed.openKfId === "string" ? parsed.openKfId.trim() : "" } catch { return "" } } function parseWebChannelConfig(configJson: string): Required { if (!configJson.trim()) { return defaultWebChannelConfig } try { const parsed = JSON.parse(configJson) as WebChannelConfig const position = parsed.position === "left" ? "left" : "right" return { title: parsed.title?.trim() || defaultWebChannelConfig.title, subtitle: parsed.subtitle?.trim() ?? defaultWebChannelConfig.subtitle, themeColor: parsed.themeColor?.trim() || defaultWebChannelConfig.themeColor, position, width: parsed.width?.trim() || defaultWebChannelConfig.width, } } catch { return defaultWebChannelConfig } } function buildForm(item: AdminChannel | null): EditForm { if (!item) { return emptyForm } const widgetConfig = parseWebChannelConfig(item.configJson) return { channelType: item.channelType === "wxwork_kf" ? "wxwork_kf" : "web", aiAgentId: item.aiAgentId > 0 ? String(item.aiAgentId) : "", name: item.name, openKfId: parseOpenKfId(item.configJson), widgetTitle: widgetConfig.title, widgetSubtitle: widgetConfig.subtitle, widgetThemeColor: widgetConfig.themeColor, widgetPosition: widgetConfig.position, widgetWidth: widgetConfig.width, remark: item.remark || "", } } function buildPayload(form: EditForm, status: number): CreateAdminChannelPayload { const channelType = form.channelType const configJson = channelType === "wxwork_kf" ? JSON.stringify({ openKfId: form.openKfId.trim() }) : JSON.stringify({ title: form.widgetTitle.trim() || defaultWebChannelConfig.title, subtitle: form.widgetSubtitle.trim(), themeColor: form.widgetThemeColor.trim() || defaultWebChannelConfig.themeColor, position: form.widgetPosition || defaultWebChannelConfig.position, width: form.widgetWidth.trim() || defaultWebChannelConfig.width, }) return { channelType, aiAgentId: Number(form.aiAgentId), name: form.name.trim(), configJson, status, remark: form.remark.trim(), } } type ChannelFormBodyProps = Omit export function EditDialog({ open, saving, itemId, onOpenChange, onSubmit, }: ChannelFormDialogProps) { if (!open) { return null } return ( ) } function ChannelFormBody({ saving, itemId, onOpenChange, onSubmit, }: ChannelFormBodyProps) { const formId = "channel-edit-form" const [loading, setLoading] = useState(false) const [aiAgents, setAIAgents] = useState([]) const [currentStatus, setCurrentStatus] = useState(0) const form = useForm< z.input, undefined, z.output >({ resolver, defaultValues: emptyForm, }) const { control, handleSubmit, register, reset, formState: { errors }, } = form const channelType = useWatch({ control, name: "channelType" }) useEffect(() => { async function loadAIAgents() { try { const data = await fetchAIAgentsAll({ status: 1 }) setAIAgents(data) } catch (error) { console.error("Failed to load AI agents:", error) } } void loadAIAgents() }, []) useEffect(() => { async function loadDetail() { if (!itemId) { setCurrentStatus(0) reset(emptyForm) return } setLoading(true) try { const data = await fetchChannel(itemId) setCurrentStatus(data.status) reset(buildForm(data)) } catch (error) { console.error("Failed to load channel:", error) } finally { setLoading(false) } } void loadDetail() }, [itemId, reset]) const aiAgentOptions = aiAgents.map((item) => ({ value: String(item.id), label: item.name, })) async function onFormSubmit(values: EditForm) { await onSubmit(buildPayload(values, currentStatus)) } return ( } > {loading ? (
加载中...
) : (
渠道名称 渠道类型 ( )} /> 接待 Agent ( )} /> {channelType === "wxwork_kf" ? ( OpenKfID ) : null}
{channelType === "web" ? (
窗口标题 窗口副标题 主题色 挂载位置 ( )} /> 窗口宽度
) : null} 备注