"use client"; import { useState, useEffect } from "react"; import useSWR from "swr"; import { toast } from "sonner"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Separator } from "@/components/ui/separator"; import { Trash2, Save, User, Settings, Wrench, Bot, HardDrive, Plus, Edit, Eye, EyeOff, Menu, Key, MessageSquare, Bell, Image as ImageIcon, BarChart3, Search, Calendar, TrendingUp, Users, Clock, } from "lucide-react"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Textarea } from "@/components/ui/textarea"; import { Switch } from "@/components/ui/switch"; const fetcher = (url: string) => fetch(url).then((res) => res.json()); // 类型定义 type User = { id: string; email: string; name: string | null; role: "guest" | "regular" | "admin"; isActive: boolean; createdAt: string; }; const ROLE_LABELS: Record = { guest: "访客用户", regular: "普通用户", admin: "管理员", }; type ToolConfig = { toolName: string; isEnabled: boolean; description?: string; config?: any; }; type AiProvider = { id: string; name: string; type: "alibaba" | "openai" | "custom" | "local" | "gemini" | "qianwen-video" | "volcano-vision" | "volcano-video"; baseUrl: string; apiKey: string; isEnabled: boolean; createdAt: string; }; type AiModel = { id: string; providerId: string; modelId: string; displayName: string; description?: string; supportsVision: boolean; supportsImageGen: boolean; isEnabled: boolean; isDefault: boolean; sortOrder: number; }; type StorageConfig = { id: string; name: string; type: "s3" | "local"; endpoint?: string; region?: string; bucket?: string; accessKeyId?: string; secretAccessKey?: string; publicUrl?: string; isDefault: boolean; isEnabled: boolean; }; type SiteSettings = { siteName: string; siteLogo: string | null; siteLogoDark: string | null; favicon: string | null; showAutoModel: boolean; usageTimeEnabled: boolean; usageTimeStart: string; usageTimeEnd: string; usageTimeMessage: string; }; type NavLink = { id: string; name: string; url: string; order: number; isEnabled: boolean; }; type AuthSettings = { dingTalkAppKey: string | null; dingTalkAppSecret: string | null; isDingTalkEnabled: boolean; isRegistrationEnabled?: boolean; }; type GeneratedImageRecord = { id: string; userId: string; userName: string | null; userEmail: string; thumbnailUrl: string; originalUrl: string; imageSize: string | null; prompt: string | null; modelId: string | null; createdAt: string; chatId: string | null; }; type ImageAnalytics = { overview: { totalImages: number; totalUsers: number; todayImages: number; todayUsers: number; weekImages: number; weekUsers: number; monthImages: number; monthUsers: number; }; userStats: Array<{ userId: string; userName: string | null; userEmail: string; imageCount: number; }>; dailyStats: Array<{ date: string; count: number }>; sizeStats: Array<{ imageSize: string | null; count: number }>; modelStats: Array<{ modelId: string | null; count: number }>; monthlyStats: Array<{ month: string; count: number }>; hourlyStats: Array<{ hour: number; count: number }>; }; const AVAILABLE_TOOLS = [ { name: "getWeather", description: "获取天气信息" }, { name: "createDocument", description: "创建文档" }, { name: "updateDocument", description: "更新文档" }, { name: "requestSuggestions", description: "请求文档建议" }, ]; const PROVIDER_TYPES = [ { value: "alibaba", label: "阿里云通义千问" }, { value: "openai", label: "OpenAI 兼容" }, { value: "custom", label: "自定义 OpenAI 兼容" }, { value: "local", label: "本地模型" }, { value: "gemini", label: "Gemini中转" }, { value: "qianwen-video", label: "千问视频生成" }, { value: "volcano-vision", label: "火山引擎视觉" }, { value: "volcano-video", label: "火山引擎视频" }, ]; export function AdminSettingsClient() { const [activeTab, setActiveTab] = useState("users"); // 用户管理状态 const [userSearch, setUserSearch] = useState(""); const [userPage, setUserPage] = useState(1); const [userLimit] = useState(10); // 🆕 生图记录状态 const [imageSearch, setImageSearch] = useState(""); const [imagePage, setImagePage] = useState(1); const [imageStartDate, setImageStartDate] = useState(""); const [imageEndDate, setImageEndDate] = useState(""); const [selectedImage, setSelectedImage] = useState(null); // 🆕 数据分析状态 const [selectedMonth, setSelectedMonth] = useState(() => { const now = new Date(); return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`; }); const [selectedDate, setSelectedDate] = useState(null); // 选中的日期,用于联动高峰时段 const [hoveredBar, setHoveredBar] = useState<{ type: string; index: number; value: number; label: string } | null>(null); // API 数据获取 const { data: usersData, mutate: mutateUsers } = useSWR<{ users: User[]; total: number; page: number; totalPages: number; }>( `/api/admin/users?page=${userPage}&limit=${userLimit}&search=${encodeURIComponent(userSearch)}`, fetcher ); const { data: providersData, mutate: mutateProviders } = useSWR<{ providers: AiProvider[] }>("/api/admin/ai-providers", fetcher); const { data: modelsData, mutate: mutateModels } = useSWR<{ models: AiModel[] }>("/api/admin/ai-models", fetcher); const { data: storageData, mutate: mutateStorage } = useSWR<{ configs: StorageConfig[] }>("/api/admin/storage", fetcher); const { data: siteSettingsData, mutate: mutateSiteSettings } = useSWR("/api/site-settings", fetcher); const { data: navLinksData, mutate: mutateNavLinks } = useSWR<{ links: NavLink[] }>("/api/nav-links", fetcher); const { data: authSettingsData, mutate: mutateAuthSettings } = useSWR("/api/auth-settings", fetcher); // 🆕 生图记录数据 const imageQueryParams = new URLSearchParams({ page: imagePage.toString(), pageSize: "200", ...(imageSearch && { search: imageSearch }), ...(imageStartDate && { startDate: imageStartDate }), ...(imageEndDate && { endDate: imageEndDate }), }); const { data: imagesData } = useSWR<{ images: GeneratedImageRecord[]; pagination: { page: number; pageSize: number; total: number; totalPages: number }; }>(`/api/admin/images?${imageQueryParams}`, fetcher); // 🆕 生图数据分析 const analyticsUrl = activeTab === "analytics" ? `/api/admin/images/analytics?month=${selectedMonth}${selectedDate ? `&date=${selectedDate}` : ""}` : null; const { data: analyticsData } = useSWR(analyticsUrl, fetcher); // 网站设置状态 const [siteSettings, setSiteSettings] = useState({ siteName: "AI Chatbot", siteLogo: "", siteLogoDark: "", favicon: "", showAutoModel: true, usageTimeEnabled: false, usageTimeStart: "08:00", usageTimeEnd: "20:00", usageTimeMessage: "由壹点累了也是需要休息的", }); useEffect(() => { if (siteSettingsData) { setSiteSettings({ siteName: siteSettingsData.siteName || "AI Chatbot", siteLogo: siteSettingsData.siteLogo || "", siteLogoDark: siteSettingsData.siteLogoDark || "", favicon: siteSettingsData.favicon || "", showAutoModel: siteSettingsData.showAutoModel ?? true, usageTimeEnabled: siteSettingsData.usageTimeEnabled ?? false, usageTimeStart: siteSettingsData.usageTimeStart || "08:00", usageTimeEnd: siteSettingsData.usageTimeEnd || "20:00", usageTimeMessage: siteSettingsData.usageTimeMessage || "由壹点累了也是需要休息的", }); } }, [siteSettingsData]); // 导航链接状态 const [navLinkForm, setNavLinkForm] = useState({ id: "", name: "", url: "", order: 0, isEnabled: true, }); const [showNavLinkDialog, setShowNavLinkDialog] = useState(false); const [editingNavLink, setEditingNavLink] = useState(null); // 快捷短语状态 const [questions, setQuestions] = useState([]); const { data: questionsData, mutate: mutateQuestions } = useSWR("/api/starter-questions", fetcher); // 系统通知状态 const [notification, setNotification] = useState<{ enabled: boolean; title: string; content: string }>({ enabled: false, title: "", content: "", }); useEffect(() => { fetch("/api/admin/settings?key=login_notification") .then((res) => res.json()) .then((data) => { if (data.setting?.value) { setNotification(data.setting.value); } }); }, []); const handleSaveNotification = async () => { try { await fetch("/api/admin/settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ key: "login_notification", value: notification, }), }); toast.success("系统通知已保存"); } catch (error) { toast.error("保存失败"); } }; useEffect(() => { if (questionsData) { setQuestions(questionsData); } }, [questionsData]); const handleQuestionChange = (index: number, value: string) => { const newQuestions = [...questions]; newQuestions[index] = value; setQuestions(newQuestions); }; const handleAddQuestion = () => { setQuestions([...questions, ""]); }; const handleRemoveQuestion = (index: number) => { const newQuestions = questions.filter((_, i) => i !== index); setQuestions(newQuestions); }; const handleSaveQuestions = async () => { try { const res = await fetch("/api/starter-questions", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(questions.filter(q => q.trim() !== "")), }); if (!res.ok) throw new Error("Failed"); toast.success("快捷短语已更新"); mutateQuestions(); } catch (error) { toast.error("更新失败"); } }; // 认证设置状态 const [authSettings, setAuthSettings] = useState({ dingTalkAppKey: "", dingTalkAppSecret: "", isDingTalkEnabled: false, isRegistrationEnabled: true, }); useEffect(() => { if (authSettingsData) { setAuthSettings({ dingTalkAppKey: authSettingsData.dingTalkAppKey || "", dingTalkAppSecret: authSettingsData.dingTalkAppSecret || "", isDingTalkEnabled: authSettingsData.isDingTalkEnabled || false, isRegistrationEnabled: authSettingsData.isRegistrationEnabled ?? true, }); } }, [authSettingsData]); // AI 提供商 & 模型状态 const [providerForm, setProviderForm] = useState({ id: "", name: "", type: "openai" as "alibaba" | "openai" | "custom" | "local" | "gemini" | "qianwen-video" | "volcano-vision" | "volcano-video", baseUrl: "", apiKey: "", isEnabled: true, }); const [showProviderDialog, setShowProviderDialog] = useState(false); const [editingProvider, setEditingProvider] = useState(null); const [showApiKey, setShowApiKey] = useState(false); const [modelForm, setModelForm] = useState({ id: "", providerId: "", modelId: "", displayName: "", description: "", supportsVision: false, supportsImageGen: false, isEnabled: true, isDefault: false, sortOrder: 0, }); const [showModelDialog, setShowModelDialog] = useState(false); const [editingModel, setEditingModel] = useState(null); // 🆕 火山引擎视频多模型表单(动态从API加载) const [volcanoVideoModels, setVolcanoVideoModels] = useState>([]); // 🆕 当选择火山引擎视频Provider时,从API加载预设模型配置 useEffect(() => { const selectedProvider = providersData?.providers?.find(p => p.id === modelForm.providerId); if (selectedProvider?.type === 'volcano-video' && volcanoVideoModels.length === 0) { // 从API获取预设模型配置(✅ 显示所有模型供管理员自定义) fetch('/api/admin/volcano-video-models') .then(res => res.json()) .then(data => { if (data.presets) { // ✅ 显示主模型和所有子模型,允许管理员自定义显示名称和描述 const allModels = data.presets.map((preset: any) => ({ modelId: preset.modelId, displayName: preset.displayName, description: preset.description, isSubModel: preset.isSubModel || false, // 标记是否为子模型 })); setVolcanoVideoModels(allModels); } }) .catch(err => { console.error('[Load Volcano Models] Error:', err); toast.error('加载预设模型配置失败'); }); } }, [modelForm.providerId, providersData?.providers, volcanoVideoModels.length]); // 存储配置状态 const [storageForm, setStorageForm] = useState({ id: "", name: "", type: "s3" as "s3" | "local", endpoint: "", region: "", bucket: "", accessKeyId: "", secretAccessKey: "", publicUrl: "", isDefault: false, isEnabled: true, }); const [showStorageDialog, setShowStorageDialog] = useState(false); const [editingStorage, setEditingStorage] = useState(null); // --- 用户管理逻辑 --- const handleUserSearch = (search: string) => { setUserSearch(search); setUserPage(1); // 搜索时重置到第一页 }; const handleUpdateUser = async (userId: string, updates: any) => { try { const response = await fetch("/api/admin/users", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: userId, ...updates }), }); if (!response.ok) throw new Error("更新失败"); toast.success("用户更新成功"); mutateUsers(); } catch (error) { toast.error("更新用户失败"); } }; const handleDeleteUser = async (userId: string) => { if (!confirm("确定要删除此用户吗?")) return; try { const response = await fetch(`/api/admin/users?id=${userId}`, { method: "DELETE" }); if (!response.ok) throw new Error("删除失败"); toast.success("用户删除成功"); mutateUsers(); } catch (error) { toast.error("删除用户失败"); } }; // --- 网站设置逻辑 --- const handleSaveSiteSettings = async () => { try { const response = await fetch("/api/site-settings", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(siteSettings), }); if (!response.ok) throw new Error("保存失败"); toast.success("网站设置保存成功"); mutateSiteSettings(); } catch (error) { toast.error("保存网站设置失败"); } }; // --- 导航链接逻辑 --- const resetNavLinkForm = () => { setNavLinkForm({ id: "", name: "", url: "", order: 0, isEnabled: true }); setEditingNavLink(null); }; const handleSaveNavLink = async () => { try { const method = editingNavLink ? "PUT" : "POST"; const body = editingNavLink ? { ...navLinkForm, id: editingNavLink } : navLinkForm; const response = await fetch("/api/nav-links", { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!response.ok) throw new Error("保存失败"); toast.success(editingNavLink ? "链接更新成功" : "链接创建成功"); mutateNavLinks(); setShowNavLinkDialog(false); resetNavLinkForm(); } catch (error) { toast.error("保存导航链接失败"); } }; const handleDeleteNavLink = async (id: string) => { if (!confirm("确定要删除此链接吗?")) return; try { const response = await fetch(`/api/nav-links?id=${id}`, { method: "DELETE" }); if (!response.ok) throw new Error("删除失败"); toast.success("链接删除成功"); mutateNavLinks(); } catch (error) { toast.error("删除链接失败"); } }; // --- 认证设置逻辑 --- const handleSaveAuthSettings = async () => { try { const response = await fetch("/api/auth-settings", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(authSettings), }); if (!response.ok) throw new Error("保存失败"); toast.success("认证设置保存成功"); mutateAuthSettings(); } catch (error) { toast.error("保存认证设置失败"); } }; // --- AI 提供商逻辑 --- const resetProviderForm = () => { setProviderForm({ id: "", name: "", type: "openai", baseUrl: "", apiKey: "", isEnabled: true, }); setEditingProvider(null); }; const handleSaveProvider = async () => { try { const method = editingProvider ? "PUT" : "POST"; const isNewProvider = !editingProvider; const isVolcanoVideo = providerForm.type === 'volcano-video'; let requestData: any = editingProvider ? { ...providerForm, id: editingProvider } : providerForm; if (editingProvider && !providerForm.apiKey) { const { apiKey, ...restData } = requestData; requestData = restData; } const response = await fetch("/api/admin/ai-providers", { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(requestData), }); if (!response.ok) throw new Error("保存失败"); const data = await response.json(); const providerId = data.provider?.id || editingProvider; toast.success(editingProvider ? "提供商更新成功" : "提供商创建成功"); mutateProviders(); setShowProviderDialog(false); resetProviderForm(); // 🆕 如果是新创建的火山引擎视频Provider,自动添加预设模型 if (isNewProvider && isVolcanoVideo && providerId) { toast.info("正在自动添加预设模型..."); try { const modelsResponse = await fetch("/api/admin/volcano-video-models", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ providerId }), }); const modelsData = await modelsResponse.json(); if (modelsResponse.ok && modelsData.addedCount > 0) { toast.success(`✅ 已自动添加 ${modelsData.addedCount} 个预设模型!`); mutateModels(); // 刷新模型列表 } } catch (modelError) { console.error('[Auto Add Models] Error:', modelError); toast.warning('预设模型添加失败,请手动点击"快速添加预设模型"按钮'); } } } catch (error) { toast.error("保存提供商失败"); } }; const handleDeleteProvider = async (id: string) => { if (!confirm("确定删除?关联模型也会被删除。")) return; try { const response = await fetch(`/api/admin/ai-providers?id=${id}`, { method: "DELETE" }); if (!response.ok) throw new Error("删除失败"); toast.success("提供商删除成功"); mutateProviders(); mutateModels(); } catch (error) { toast.error("删除提供商失败"); } }; // 快速添加火山引擎视频预设模型 const handleQuickAddVolcanoModels = async (providerId: string) => { if (!confirm("将为此服务商添加4个预设模型:\n- Seedance Pro (全功能)\n- Seedance Pro Fast (快速)\n- Seedance Lite I2V (图生视频)\n- Seedance Lite T2V (文生视频)\n\n继续吗?")) { return; } try { toast.info("正在添加预设模型..."); const response = await fetch("/api/admin/volcano-video-models", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ providerId }), }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || "添加失败"); } const { addedCount, errors } = data; if (addedCount > 0) { toast.success(`成功添加 ${addedCount} 个预设模型!`); mutateModels(); // 刷新模型列表 } if (errors && errors.length > 0) { toast.warning(`${errors.length} 个模型添加失败(可能已存在)`); } } catch (error: any) { console.error('[Quick Add Models] Error:', error); toast.error(error.message || "添加预设模型失败"); } }; // --- AI 模型逻辑 --- const resetModelForm = () => { setModelForm({ id: "", providerId: "", modelId: "", displayName: "", description: "", supportsVision: false, supportsImageGen: false, isEnabled: true, isDefault: false, sortOrder: 0, }); setEditingModel(null); }; const handleSaveModel = async () => { try { // 🆕 检查是否是火山引擎视频Provider const selectedProvider = providersData?.providers?.find(p => p.id === modelForm.providerId); const isVolcanoVideo = selectedProvider?.type === 'volcano-video'; // 如果是火山引擎视频且不是编辑模式,批量创建模型 if (isVolcanoVideo && !editingModel) { toast.info("正在添加火山引擎视频模型(1个主模型 + 4个子模型)..."); const response = await fetch("/api/admin/volcano-video-models", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ providerId: modelForm.providerId, // ✅ 传递所有模型配置(管理员可以自定义显示名称和描述) models: volcanoVideoModels, }), }); const data = await response.json(); if (!response.ok) throw new Error(data.error || "批量添加失败"); toast.success(`✅ 成功添加 ${data.addedCount} 个模型(1个主模型 + ${data.addedCount - 1} 个子模型)!`); mutateModels(); setShowModelDialog(false); resetModelForm(); return; } // 普通模型的单个创建/编辑逻辑 const method = editingModel ? "PUT" : "POST"; const body = editingModel ? { ...modelForm, id: editingModel } : modelForm; const response = await fetch("/api/admin/ai-models", { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!response.ok) throw new Error("保存失败"); toast.success(editingModel ? "模型更新成功" : "模型创建成功"); mutateModels(); setShowModelDialog(false); resetModelForm(); } catch (error: any) { console.error('[Save Model] Error:', error); toast.error(error.message || "保存模型失败"); } }; const handleDeleteModel = async (id: string) => { if (!confirm("确定删除此模型?")) return; try { const response = await fetch(`/api/admin/ai-models?id=${id}`, { method: "DELETE" }); if (!response.ok) throw new Error("删除失败"); toast.success("模型删除成功"); mutateModels(); } catch (error) { toast.error("删除模型失败"); } }; // --- 存储配置逻辑 --- const resetStorageForm = () => { setStorageForm({ id: "", name: "", type: "s3", endpoint: "", region: "", bucket: "", accessKeyId: "", secretAccessKey: "", publicUrl: "", isDefault: false, isEnabled: true, }); setEditingStorage(null); }; const handleSaveStorage = async () => { try { const method = editingStorage ? "PUT" : "POST"; const body = editingStorage ? { ...storageForm, id: editingStorage } : storageForm; const response = await fetch("/api/admin/storage", { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!response.ok) throw new Error("保存失败"); toast.success(editingStorage ? "存储配置更新成功" : "存储配置创建成功"); mutateStorage(); setShowStorageDialog(false); resetStorageForm(); } catch (error) { toast.error("保存存储配置失败"); } }; const handleDeleteStorage = async (id: string) => { if (!confirm("确定删除此存储配置?")) return; try { const response = await fetch(`/api/admin/storage?id=${id}`, { method: "DELETE" }); if (!response.ok) throw new Error("删除失败"); toast.success("存储配置删除成功"); mutateStorage(); } catch (error) { toast.error("删除存储配置失败"); } }; return (
用户管理 生图记录 数据分析 网站设置 导航设置 快捷短语 认证设置 系统通知 AI 模型 存储配置 {/* 用户管理 */} {/* 搜索框 */}
handleUserSearch(e.target.value)} className="max-w-md" />
{/* 用户列表 */}
{usersData?.users?.map((user) => (
{user.name || user.email}
邮箱: {user.email}
角色: {ROLE_LABELS[user.role] ?? user.role} | 状态: {user.isActive ? "已激活" : "未激活"}
))}
{/* 分页控件 */} {usersData && usersData.totalPages > 1 && (
显示 {((userPage - 1) * userLimit) + 1} - {Math.min(userPage * userLimit, usersData.total)} 条,共 {usersData.total} 条
{/* 首页按钮 */} {/* 上一页按钮 */} {/* 页码显示和跳转 */}
第 { const page = Number.parseInt(e.target.value, 10); if (page >= 1 && page <= usersData.totalPages) { setUserPage(page); } }} onKeyDown={(e) => { if (e.key === 'Enter') { const page = Number.parseInt(e.currentTarget.value, 10); if (page >= 1 && page <= usersData.totalPages) { setUserPage(page); } else { e.currentTarget.value = userPage.toString(); } } }} className="w-16 h-8 text-center" /> / {usersData.totalPages} 页
{/* 下一页按钮 */} {/* 末页按钮 */}
)}
{/* 🆕 生图记录 */} 所有生图记录 查看所有用户的 AI 生图历史记录 {/* 筛选条件 */}
{ setImageSearch(e.target.value); setImagePage(1); }} className="pl-9" />
{ setImageStartDate(e.target.value); setImagePage(1); }} />
{ setImageEndDate(e.target.value); setImagePage(1); }} />
{/* 统计信息 */}
共 {imagesData?.pagination.total || 0} 条记录
{/* 图片网格 */}
{imagesData?.images?.map((img) => (
setSelectedImage(img)} >
{img.prompt
{img.userName || img.userEmail?.split("@")[0]}
{new Date(img.createdAt).toLocaleDateString("zh-CN", { month: "numeric", day: "numeric" })}
))}
{/* 分页 */} {imagesData && imagesData.pagination.totalPages > 1 && (
第 {imagesData.pagination.page} / {imagesData.pagination.totalPages} 页
)}
{/* 图片详情弹窗 */} !open && setSelectedImage(null)}> 图片详情 {selectedImage && (
{selectedImage.prompt

{selectedImage.userName || selectedImage.userEmail}

{new Date(selectedImage.createdAt).toLocaleString("zh-CN")}

{selectedImage.imageSize || "未知"}

{selectedImage.modelId || "未知"}

{selectedImage.prompt || "无"}

)}
{/* 🆕 数据分析 */} {/* 概览卡片 */}

{analyticsData?.overview.totalImages || 0}

总生图数

{analyticsData?.overview.totalUsers || 0}

总用户数

{analyticsData?.overview.todayImages || 0}

今日生图

{analyticsData?.overview.monthImages || 0}

{selectedMonth === `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, "0")}` ? "本月生图" : `${selectedMonth} 生图`}

setSelectedMonth(e.target.value)} className="w-[140px] h-8 text-xs" />
{/* 用户排行 */} 用户生图排行 (Top 20)
{analyticsData?.userStats?.map((user, index) => (
{index + 1} {user.userName || user.userEmail?.split("@")[0]}
{user.imageCount}
))}
{/* 图片尺寸分布 */} 图片尺寸分布
{analyticsData?.sizeStats?.map((stat) => { const total = analyticsData.overview.totalImages || 1; const percentage = ((stat.count / total) * 100).toFixed(1); return (
{stat.imageSize || "未知"} {stat.count} ({percentage}%)
); })}
{/* 每日趋势 */} 最近30天趋势 {selectedDate && ( )}
{analyticsData?.dailyStats?.map((stat, index) => { const maxCount = Math.max(...(analyticsData.dailyStats?.map(s => s.count) || [1])); const height = (stat.count / maxCount) * 100; const isHovered = hoveredBar?.type === "daily" && hoveredBar?.index === index; const isSelected = selectedDate === stat.date; return (
setHoveredBar({ type: "daily", index, value: stat.count, label: stat.date })} onMouseLeave={() => setHoveredBar(null)} onClick={() => setSelectedDate(selectedDate === stat.date ? null : stat.date)} > {isHovered && (
{stat.date}: {stat.count} 张
点击查看当天高峰时段
)}
); })}
{analyticsData?.dailyStats?.[0]?.date} {analyticsData?.dailyStats?.at(-1)?.date}
{/* 高峰时段 */} 生图高峰时段 {selectedDate && ( {selectedDate} )} {!selectedDate && ( 点击左侧日期柱形查看当天数据 )}
{Array.from({ length: 24 }, (_, hour) => { const stat = analyticsData?.hourlyStats?.find(s => s.hour === hour); const count = stat?.count || 0; const maxCount = Math.max(...(analyticsData?.hourlyStats?.map(s => s.count) || [1]), 1); const height = (count / maxCount) * 100; const isHovered = hoveredBar?.type === "hourly" && hoveredBar?.index === hour; return (
setHoveredBar({ type: "hourly", index: hour, value: count, label: `${hour}:00` })} onMouseLeave={() => setHoveredBar(null)} > {isHovered && (
{hour}:00-{hour + 1}:00: {count} 张
)}
); })}
0:00 12:00 23:00
{/* 月度统计 */} 月度统计
{analyticsData?.monthlyStats?.slice(-6).map((stat) => (

{stat.count}

{stat.month}

))}
{/* 网站设置 */}
setSiteSettings({ ...siteSettings, siteName: e.target.value })} placeholder="输入网站名称" />
setSiteSettings({ ...siteSettings, siteLogo: e.target.value })} placeholder="https://example.com/logo.png" />
setSiteSettings({ ...siteSettings, siteLogoDark: e.target.value })} placeholder="https://example.com/logo-dark.png" />
setSiteSettings({ ...siteSettings, favicon: e.target.value })} placeholder="https://example.com/favicon.ico" />
开启后,用户可以选择"智能自动"模型,系统将根据场景智能选择最佳模型
setSiteSettings({ ...siteSettings, showAutoModel: checked })} />
开启后,仅在指定时间段内允许使用系统,超出时间段输入框将被禁用
setSiteSettings({ ...siteSettings, usageTimeEnabled: checked })} />
{siteSettings.usageTimeEnabled && ( <>