"use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { ResponsiveLayout } from "@/components/layout"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { fetchAIConfigs, createAIConfig, updateAIConfig, deleteAIConfig, type AIConfig, type CreateAIConfigRequest, type UpdateAIConfigRequest, } from "@/features/agent/services/aiConfigApi"; import { fetchEmbeddingConfig, updateEmbeddingConfig, type EmbeddingConfig, type UpdateEmbeddingConfigRequest, } from "@/features/agent/services/embeddingConfigApi"; import { deleteAutoCloseConversationDaysPolicy, fetchAutoCloseConversationDaysPolicy, putAutoCloseConversationDaysPolicy, type AutoCloseConversationDaysPolicy, } from "@/features/agent/services/conversationApi"; import { fetchEmailNotificationConfig, resetEmailNotificationConfig, sendEmailNotificationTest, updateEmailNotificationConfig, type EmailNotificationConfig, } from "@/features/agent/services/emailNotificationApi"; import { useProfile } from "@/features/agent/hooks/useProfile"; import { apiUrl } from "@/lib/config"; import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; import { toast } from "@/hooks/useToast"; import type { I18nKey } from "@/lib/i18n/dict"; import { useI18n } from "@/lib/i18n/provider"; export default function SettingsPage(props: any = {}) { const { embedded = false } = props; const router = useRouter(); const { t } = useI18n(); const modelTypeLabel = (mt: string) => { const map: Record = { text: "agent.settings.modelType.text", image: "agent.settings.modelType.image", audio: "agent.settings.modelType.audio", video: "agent.settings.modelType.video", }; const k = map[mt]; return k ? t(k) : mt; }; const [userId, setUserId] = useState(null); const [configs, setConfigs] = useState([]); const [loading, setLoading] = useState(true); const [editingId, setEditingId] = useState(null); const [formData, setFormData] = useState({ provider: "", api_url: "", api_key: "", model: "", model_type: "text", is_active: true, is_public: false, description: "", }); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(""); // 知识库向量配置(平台级,仅管理员可修改) const [embeddingConfig, setEmbeddingConfig] = useState(null); const [embeddingForm, setEmbeddingForm] = useState({ embedding_type: "openai", api_url: "", api_key: "", model: "text-embedding-3-small", customer_can_use_kb: true, visitor_web_search_enabled: false, web_search_source: "custom" as "vendor" | "custom", }); const [embeddingLoading, setEmbeddingLoading] = useState(false); const [embeddingSubmitting, setEmbeddingSubmitting] = useState(false); const [embeddingError, setEmbeddingError] = useState(""); // 会话维护:自动关闭长期未活跃 open 访客会话(平台级) const [autoClosePolicy, setAutoClosePolicy] = useState(null); const [autoCloseDaysDraft, setAutoCloseDaysDraft] = useState("7"); const [autoCloseLoading, setAutoCloseLoading] = useState(false); const [autoCloseSubmitting, setAutoCloseSubmitting] = useState(false); const [autoCloseError, setAutoCloseError] = useState(""); // 离线邮件通知(平台级,仅管理员可修改) const [isAdmin, setIsAdmin] = useState(false); const [emailConfig, setEmailConfig] = useState(null); const [emailForm, setEmailForm] = useState({ enabled: false, smtp_host: "", smtp_port: "465", smtp_user: "", smtp_password: "", from_email: "", from_name: "", offline_delay_seconds: "60", }); const [emailTestTo, setEmailTestTo] = useState(""); const [emailLoading, setEmailLoading] = useState(false); const [emailSubmitting, setEmailSubmitting] = useState(false); const [emailTesting, setEmailTesting] = useState(false); const [emailError, setEmailError] = useState(""); // 检查登录状态 useEffect(() => { const storedUserId = localStorage.getItem("agent_user_id"); if (!storedUserId) { router.push("/"); return; } setUserId(Number.parseInt(storedUserId, 10)); setIsAdmin(localStorage.getItem("agent_role") === "admin"); }, [router]); // 加载个人资料(用于获取和更新 AI 对话接收设置) const { profile, loading: profileLoading, update: updateProfile, } = useProfile({ userId: userId ?? null, enabled: Boolean(userId), }); // 加载配置列表 const loadConfigs = async () => { if (!userId) return; try { setLoading(true); const data = await fetchAIConfigs(userId); setConfigs(data); } catch (error) { console.error("加载配置失败:", error); setError(t("agent.settings.error.loadConfigs")); } finally { setLoading(false); } }; useEffect(() => { if (userId) { loadConfigs(); } }, [userId]); // 加载知识库向量配置 const loadEmbeddingConfig = async () => { if (!userId) return; try { setEmbeddingLoading(true); const data = await fetchEmbeddingConfig(userId); setEmbeddingConfig(data); setEmbeddingForm({ embedding_type: data.embedding_type || "openai", api_url: data.api_url || "", api_key: "", model: data.model || "text-embedding-3-small", customer_can_use_kb: data.customer_can_use_kb ?? true, visitor_web_search_enabled: data.visitor_web_search_enabled ?? false, web_search_source: data.web_search_source === "vendor" ? "vendor" : "custom", }); } catch (e) { console.error("加载知识库向量配置失败:", e); setEmbeddingError(t("agent.settings.error.loadEmbedding")); } finally { setEmbeddingLoading(false); } }; useEffect(() => { if (userId) { loadEmbeddingConfig(); } }, [userId]); const loadAutoClosePolicy = async () => { if (!userId) return; try { setAutoCloseLoading(true); setAutoCloseError(""); const policy = await fetchAutoCloseConversationDaysPolicy(); setAutoClosePolicy(policy); setAutoCloseDaysDraft(String(policy.effective_days)); } catch (e) { console.error("加载会话维护配置失败:", e); setAutoCloseError(t("agent.settings.autoClose.errorLoad")); } finally { setAutoCloseLoading(false); } }; useEffect(() => { if (userId) { void loadAutoClosePolicy(); } }, [userId]); const loadEmailConfig = async () => { if (!userId) return; try { setEmailLoading(true); setEmailError(""); const data = await fetchEmailNotificationConfig(userId); setEmailConfig(data); setEmailForm({ enabled: data.enabled, smtp_host: data.smtp_host || "", smtp_port: String(data.smtp_port || 465), smtp_user: data.smtp_user || "", smtp_password: "", from_email: data.from_email || "", from_name: data.from_name || "", offline_delay_seconds: String(data.offline_delay_seconds ?? 60), }); } catch (e) { console.error("加载离线邮件配置失败:", e); setEmailError(t("agent.settings.offlineEmail.errorLoad")); } finally { setEmailLoading(false); } }; useEffect(() => { if (userId) { void loadEmailConfig(); } }, [userId]); const handleSaveEmailConfig = async (e: React.FormEvent) => { e.preventDefault(); if (!userId || !isAdmin) return; const delay = Number.parseInt(emailForm.offline_delay_seconds, 10); const port = Number.parseInt(emailForm.smtp_port, 10); if (Number.isNaN(delay) || delay < 0) { setEmailError(t("agent.settings.offlineEmail.errorInvalidDelay")); return; } setEmailSubmitting(true); setEmailError(""); try { await updateEmailNotificationConfig(userId, { enabled: emailForm.enabled, smtp_host: emailForm.smtp_host || undefined, smtp_port: Number.isNaN(port) ? undefined : port, smtp_user: emailForm.smtp_user || undefined, from_email: emailForm.from_email || undefined, from_name: emailForm.from_name || undefined, offline_delay_seconds: delay, ...(emailForm.smtp_password ? { smtp_password: emailForm.smtp_password } : {}), }); await loadEmailConfig(); toast.success(t("agent.settings.offlineEmail.toastSaved")); } catch (err) { setEmailError((err as Error).message); } finally { setEmailSubmitting(false); } }; const handleResetEmailConfig = async () => { if (!userId || !isAdmin) return; setEmailSubmitting(true); setEmailError(""); try { await resetEmailNotificationConfig(userId); await loadEmailConfig(); toast.success(t("agent.settings.offlineEmail.toastReset")); } catch (err) { setEmailError((err as Error).message); } finally { setEmailSubmitting(false); } }; const handleSendEmailTest = async () => { if (!userId || !isAdmin) return; const to = emailTestTo.trim(); if (!to) return; setEmailTesting(true); setEmailError(""); try { await sendEmailNotificationTest(userId, to); toast.success(t("agent.settings.offlineEmail.toastTestSent")); } catch (err) { setEmailError((err as Error).message); } finally { setEmailTesting(false); } }; const handleSaveAutoClosePolicy = async (e: React.FormEvent) => { e.preventDefault(); if (!userId) return; const parsed = Number.parseInt(autoCloseDaysDraft, 10); if (Number.isNaN(parsed) || parsed < 0) { setAutoCloseError(t("agent.settings.autoClose.errorInvalid")); return; } setAutoCloseSubmitting(true); setAutoCloseError(""); try { await putAutoCloseConversationDaysPolicy(parsed); await loadAutoClosePolicy(); toast.success(t("agent.settings.autoClose.toastSaved")); } catch (err) { setAutoCloseError((err as Error).message); } finally { setAutoCloseSubmitting(false); } }; const handleResetAutoClosePolicy = async () => { if (!userId) return; setAutoCloseSubmitting(true); setAutoCloseError(""); try { await deleteAutoCloseConversationDaysPolicy(); await loadAutoClosePolicy(); toast.success(t("agent.settings.autoClose.toastReset")); } catch (err) { setAutoCloseError((err as Error).message); } finally { setAutoCloseSubmitting(false); } }; // 保存知识库向量配置(仅管理员;保存后立即生效,无需重启) const handleSaveEmbeddingConfig = async (e: React.FormEvent) => { e.preventDefault(); if (!userId) return; setEmbeddingSubmitting(true); setEmbeddingError(""); try { const data: UpdateEmbeddingConfigRequest = { embedding_type: embeddingForm.embedding_type, api_url: embeddingForm.api_url || undefined, model: embeddingForm.model || undefined, customer_can_use_kb: embeddingForm.customer_can_use_kb, visitor_web_search_enabled: embeddingForm.visitor_web_search_enabled, web_search_source: embeddingForm.web_search_source, }; if (embeddingForm.api_key) { data.api_key = embeddingForm.api_key; } await updateEmbeddingConfig(userId, data); await loadEmbeddingConfig(); toast.success(t("agent.settings.toast.embeddingSaved")); } catch (err) { setEmbeddingError((err as Error).message); } finally { setEmbeddingSubmitting(false); } }; // 重置表单 const resetForm = () => { setFormData({ provider: "", api_url: "", api_key: "", model: "", model_type: "text", is_active: true, is_public: false, description: "", }); setEditingId(null); setError(""); }; // 开始编辑 const handleEdit = (config: AIConfig) => { setFormData({ provider: config.provider, api_url: config.api_url, api_key: "", // 不显示 API Key(已加密) model: config.model, model_type: config.model_type, is_active: config.is_active, is_public: config.is_public, description: config.description, }); setEditingId(config.id); }; // 提交表单 const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!userId) return; setSubmitting(true); setError(""); try { if (editingId) { // 更新配置 const updateData: UpdateAIConfigRequest = { provider: formData.provider, api_url: formData.api_url, model: formData.model, model_type: formData.model_type, is_active: formData.is_active, is_public: formData.is_public, description: formData.description, }; // 如果提供了新的 API Key,才更新 if (formData.api_key) { updateData.api_key = formData.api_key; } await updateAIConfig(userId, editingId, updateData); } else { // 创建配置 await createAIConfig(userId, formData); } resetForm(); await loadConfigs(); } catch (error) { setError((error as Error).message || t("agent.settings.error.operation")); } finally { setSubmitting(false); } }; // 删除配置 const handleDelete = async (id: number) => { if (!userId) return; if (!confirm(t("agent.settings.confirmDeleteConfig"))) return; try { await deleteAIConfig(userId, id); await loadConfigs(); } catch (error) { setError((error as Error).message || t("agent.settings.error.delete")); } }; // 退出登录 const handleLogout = async () => { try { await fetch(apiUrl("/logout"), { method: "POST" }); } catch (error) { console.error("退出登录失败:", error); } finally { localStorage.removeItem("agent_user_id"); localStorage.removeItem("agent_username"); localStorage.removeItem("agent_role"); router.push("/"); } }; if (!userId) { return null; } // 构建头部内容 const headerContent = (

{t("agent.settings.title")}

{t("agent.settings.subtitle")}
{!embedded && (
)}
); // 构建主内容区 const mainContent = (
{/* 全局设置 */} {t("agent.settings.section.global")}
{ if (userId) { try { await updateProfile({ receive_ai_conversations: !checked, }); } catch (error) { console.error("更新设置失败:", error); toast.error(t("agent.settings.toast.profileUpdateFailed")); } } }} disabled={profileLoading} />

{t("agent.settings.global.noReceiveAiHint")}

{/* 会话维护:自动关闭长期未活跃 open 访客会话 */} {t("agent.settings.autoClose.title")}

{t("agent.settings.autoClose.lead")}

{autoCloseLoading ? (
{t("common.loading")}
) : (
{autoCloseError && (
{autoCloseError}
)}
setAutoCloseDaysDraft(e.target.value)} className="max-w-xs" />

{t("agent.settings.autoClose.daysHint")}

{autoClosePolicy ? (

{t("agent.settings.autoClose.statusEffective")}: {autoClosePolicy.effective_days} {" · "} {t("agent.settings.autoClose.statusEnv")}: {autoClosePolicy.env_days} {" · "} {autoClosePolicy.persisted_in_database ? t("agent.settings.autoClose.statusDb") : t("agent.settings.autoClose.statusEnvOnly")}

) : null}
{autoClosePolicy?.persisted_in_database ? ( ) : null}
)}
{/* 离线邮件通知 */} {t("agent.settings.offlineEmail.title")}

{t("agent.settings.offlineEmail.lead")}

{!isAdmin ? (

{t("agent.settings.offlineEmail.adminOnly")}

) : null}
{emailLoading ? (
{t("common.loading")}
) : (
{emailError && (
{emailError}
)}
setEmailForm({ ...emailForm, enabled: checked === true }) } />
setEmailForm({ ...emailForm, offline_delay_seconds: e.target.value }) } className="max-w-xs" />

{t("agent.settings.offlineEmail.delayHint")}

setEmailForm({ ...emailForm, smtp_host: e.target.value }) } placeholder="smtp.example.com" />
setEmailForm({ ...emailForm, smtp_port: e.target.value }) } placeholder="465" />
setEmailForm({ ...emailForm, smtp_user: e.target.value }) } />
setEmailForm({ ...emailForm, smtp_password: e.target.value }) } placeholder={ emailConfig?.smtp_password_masked ? t("agent.settings.offlineEmail.smtpPasswordKeepEmpty") : undefined } />
setEmailForm({ ...emailForm, from_email: e.target.value }) } />
setEmailForm({ ...emailForm, from_name: e.target.value }) } />
{emailConfig ? (

{t("agent.settings.offlineEmail.statusEffective")}:{" "} {emailConfig.effective_enabled ? t("agent.settings.offlineEmail.statusOn") : t("agent.settings.offlineEmail.statusOff")} {" · "} {t("agent.settings.offlineEmail.delayLabel")}:{" "} {emailConfig.effective_delay_seconds}s {" · "} {t("agent.settings.offlineEmail.statusEnv")}:{" "} {emailConfig.env_enabled ? "on" : "off"},{" "} {emailConfig.env_delay_seconds}s {" · "} {emailConfig.persisted_in_database ? t("agent.settings.offlineEmail.statusDb") : t("agent.settings.offlineEmail.statusEnvOnly")}

) : null}
{isAdmin && emailConfig?.persisted_in_database ? ( ) : null}
{isAdmin ? (
setEmailTestTo(e.target.value)} placeholder={t("agent.settings.offlineEmail.testTo")} className="sm:max-w-xs" />
) : null}
)}
{/* 知识库向量模型(平台级,仅管理员可修改;保存后立即生效) */} {t("agent.settings.embedding.title")}

{t("agent.settings.embedding.lead")}

{embeddingLoading ? (
{t("common.loading")}
) : (
{embeddingError && (
{embeddingError}
)}
setEmbeddingForm({ ...embeddingForm, api_url: e.target.value }) } placeholder={t("agent.settings.embedding.apiUrlPh")} />
setEmbeddingForm({ ...embeddingForm, api_key: e.target.value }) } placeholder={ embeddingConfig?.api_key_masked ? t("agent.settings.embedding.apiKeyKeepEmpty") : t("agent.settings.embedding.apiKeyInput") } />
setEmbeddingForm({ ...embeddingForm, model: e.target.value }) } placeholder={t("agent.settings.embedding.modelPh")} />
setEmbeddingForm({ ...embeddingForm, customer_can_use_kb: checked === true, }) } />
)}
{/* 联网搜索设置(与知识库向量模型独立;实际仍写入同一配置,仅 UI 分离) */} {t("agent.settings.webSearch.title")}

{t("agent.settings.webSearch.lead")}

{embeddingLoading ? (
{t("common.loading")}
) : (
{embeddingError && (
{embeddingError}
)}

{t("agent.settings.webSearch.modeHint")}

setEmbeddingForm({ ...embeddingForm, visitor_web_search_enabled: checked === true, }) } />
)}
{/* 配置表单 */} {editingId ? t("agent.settings.aiCard.titleEdit") : t("agent.settings.aiCard.titleAdd")}
{error && (
{error}
)}
setFormData({ ...formData, provider: e.target.value }) } placeholder={t("agent.settings.aiForm.providerPh")} required />
setFormData({ ...formData, api_url: e.target.value }) } placeholder={t("agent.settings.aiForm.apiUrlPh")} required />
setFormData({ ...formData, api_key: e.target.value }) } placeholder={ editingId ? t("agent.settings.embedding.apiKeyKeepEmpty") : t("agent.settings.embedding.apiKeyInput") } required={!editingId} />
setFormData({ ...formData, model: e.target.value }) } placeholder={t("agent.settings.aiForm.modelPh")} required />
setFormData({ ...formData, description: e.target.value }) } placeholder={t("agent.settings.aiForm.descPh")} />
{editingId && ( )}
{/* 配置列表 */} {t("agent.settings.list.title")} {loading ? (
{t("common.loading")}
) : configs.length === 0 ? (
{t("agent.settings.list.empty")}
) : (
{configs.map((config) => (

{config.provider} - {config.model}

{config.is_active && ( {t("agent.settings.badge.active")} )} {config.is_public && ( {t("agent.settings.badge.public")} )}

{t("agent.settings.list.apiUrlLabel")} {config.api_url}

{t("agent.settings.list.modelTypeLabel")} {modelTypeLabel(config.model_type)}

{config.description && (

{t("agent.settings.list.descLabel")} {config.description}

)}
))}
)}
); // 如果是嵌入模式,只返回内容,不包含 ResponsiveLayout if (embedded) { return (
{headerContent} {mainContent}
); } return ( ); }