{"repo": "19125119617/ai-customer-service", "files": {"src/app/api/ai/chat/route.ts": {"db4291e5eaaede2c19cffd573e01d953bafb54e5": {"t": 1785746034.3805614, "n": 3100, "h": "5ebe05ef50393fcf", "c": "// AI 聊天 API\nimport { NextRequest, NextResponse } from 'next/server';\nimport { ragQuery } from '@/lib/rag';\nimport prisma from '@/lib/prisma';\n\n// AI 提供商配置\nconst AI_PROVIDERS = {\n deepseek: {\n baseURL: 'https://api.deepseek.com/v1',\n model: 'deepseek-chat',\n },\n qwen: {\n baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',\n model: 'qwen-turbo',\n },\n moonshot: {\n baseURL: 'https://api.moonshot.cn/v1',\n model: 'moonshot-v1-8k',\n },\n zhipu: {\n baseURL: 'https://open.bigmodel.cn/api/paas/v4',\n model: 'glm-4-flash',\n },\n};\n\n// 调用 AI 接口\nasync function callAI(prompt: string, provider: keyof typeof AI_PROVIDERS = 'deepseek', apiKey?: string) {\n const config = AI_PROVIDERS[provider];\n const key = apiKey || process.env.AI_API_KEY;\n\n if (!key) {\n throw new Error('未配置 API Key');\n }\n\n const response = await fetch(`${config.baseURL}/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${key}`,\n },\n body: JSON.stringify({\n model: config.model,\n messages: [\n { role: 'system', content: '你是一个专业的客服助手,请用友好的语气回答用户的问题。' },\n { role: 'user', content: prompt },\n ],\n temperature: 0.7,\n max_tokens: 1000,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`AI 调用失败: ${error}`);\n }\n\n const data = await response.json();\n return data.choices[0]?.message?.content || '抱歉,我暂时无法回答这个问题。';\n}\n\n// POST /api/ai/chat - AI 聊天\nexport async function POST(request: NextRequest) {\n try {\n const body = await request.json();\n const { conversationId, message, provider = 'deepseek' } = body;\n\n if (!message) {\n return NextResponse.json({ error: '缺少消息内容' }, { status: 400 });\n }\n\n // 1. 从知识库检索相关内容\n const teamId = 'default-team';\n const ragResult = await ragQuery(message, teamId);\n\n // 2. 构建 prompt\n let prompt = message;\n if (ragResult.sources.length > 0) {\n const context = ragResult.sources\n .map((s, i) => `[参考${i + 1}]: ${s.content}`)\n .join('\\n\\n');\n prompt = `参考以下知识库内容回答用户问题:\\n\\n${context}\\n\\n用户问题:${message}\\n\\n请根据知识库内容给出回答,如果没有相关信息,请如实说明。`;\n }\n\n // 3. 调用 AI\n const aiResponse = await callAI(prompt, provider as any);\n\n // 4. 如果有会话ID,保存 AI 回复到数据库\n if (conversationId) {\n // 获取默认用户或创建系统消息\n const aiMessage = await prisma.message.create({\n data: {\n conversationId,\n senderId: 'ai-assistant',\n senderType: 'AI',\n content: aiResponse,\n isAiGenerated: true,\n },\n });\n\n return NextResponse.json({\n message: aiMessage,\n sources: ragResult.sources,\n elapsed: ragResult.elapsed,\n });\n }\n\n return NextResponse.json({\n response: aiResponse,\n sources: ragResult.sources,\n elapsed: ragResult.elapsed,\n });\n } catch (error) {\n console.error('Error in AI chat:', error);\n return NextResponse.json(\n { error: error instanceof Error ? error.message : 'AI 聊天失败' },\n { status: 500 }\n );\n }\n}\n"}}, "src/app/settings/page.tsx": {"db4291e5eaaede2c19cffd573e01d953bafb54e5": {"t": 1785747362.861727, "n": 78799, "h": "3bfce95adb7c851c", "c": "\"use client\";\nimport { useState } from \"react\";\nimport PageLayout from \"@/components/PageLayout\";\nimport {\n Settings, Users, Bell, Shield, Globe, Palette, Database, Key,\n Check, X, Plus, Trash2, Copy, Eye, EyeOff, RefreshCw, Download,\n Mail, Smartphone, Webhook, Trash, AlertTriangle,\n CheckCircle2, Image as ImageIcon, Moon, Sun, Monitor,\n Store, Link, TestTube, Edit2, ToggleLeft, ToggleRight,\n Book, ExternalLink,\n} from \"lucide-react\";\n\nconst menuItems = [\n { icon: Book, label: \"使用教程\", desc: \"各模块使用指南、API配置教程\" },\n { icon: Settings, label: \"项目设置\", desc: \"基本信息、时区、语言\" },\n { icon: Users, label: \"成员管理\", desc: \"添加成员、设置角色\" },\n { icon: Shield, label: \"权限配置\", desc: \"角色权限、访问控制\" },\n { icon: Bell, label: \"通知设置\", desc: \"邮件、短信、推送通知\" },\n { icon: Globe, label: \"渠道配置\", desc: \"渠道接入、Webhook\" },\n { icon: Palette, label: \"界面定制\", desc: \"主题、Logo、颜色\" },\n { icon: Database, label: \"数据管理\", desc: \"数据导出、清理\" },\n { icon: Key, label: \"API密钥\", desc: \"管理API访问密钥\" },\n];\n\nconst permissionModules = [\n { id: \"dashboard\", name: \"首页仪表盘\", permissions: [\"查看\", \"编辑\"] },\n { id: \"conversations\", name: \"聊天会话\", permissions: [\"查看\", \"回复\", \"转接\", \"关闭\"] },\n { id: \"customers\", name: \"客户管理\", permissions: [\"查看\", \"编辑\", \"删除\", \"导入导出\"] },\n { id: \"marketing\", name: \"营销模块\", permissions: [\"查看\", \"创建\", \"编辑\", \"删除\", \"发送\"] },\n { id: \"ai\", name: \"AI机器人\", permissions: [\"查看\", \"配置\", \"训练\", \"启停\"] },\n { id: \"knowledge\", name: \"知识库\", permissions: [\"查看\", \"上传\", \"编辑\", \"删除\"] },\n { id: \"channels\", name: \"渠道管理\", permissions: [\"查看\", \"添加\", \"编辑\", \"删除\"] },\n { id: \"reports\", name: \"报告分析\", permissions: [\"查看\", \"导出\"] },\n { id: \"settings\", name: \"系统设置\", permissions: [\"查看\", \"编辑\"] },\n];\n\nconst roles = [\n { id: \"admin\", name: \"管理员\", description: \"拥有所有权限\", color: \"bg-red-100 text-red-700\" },\n { id: \"supervisor\", name: \"主管\", description: \"管理团队和查看报告\", color: \"bg-purple-100 text-purple-700\" },\n { id: \"agent\", name: \"客服\", description: \"处理会话和客户\", color: \"bg-blue-100 text-blue-700\" },\n { id: \"viewer\", name: \"访客\", description: \"仅查看权限\", color: \"bg-gray-100 text-gray-700\" },\n];\n\nconst notificationTypes = [\n { id: \"new_message\", name: \"新消息提醒\", description: \"当有新客户消息时\", email: true, push: true, sms: false },\n { id: \"assigned\", name: \"会话分配\", description: \"当有新会话分配给你时\", email: true, push: true, sms: false },\n { id: \"ai_handover\", name: \"AI转人工\", description: \"当AI无法处理转人工时\", email: true, push: true, sms: true },\n { id: \"customer_join\", name: \"客户接入\", description: \"当高价值客户接入时\", email: false, push: true, sms: false },\n { id: \"daily_report\", name: \"日报\", description: \"每日工作汇总\", email: true, push: false, sms: false },\n { id: \"weekly_report\", name: \"周报\", description: \"每周工作汇总\", email: true, push: false, sms: false },\n { id: \"system_alert\", name: \"系统告警\", description: \"系统异常或维护通知\", email: true, push: true, sms: true },\n];\n\ntype ChannelConfig = {\n id: string; name: string; type: string; status: string; icon: string;\n webhook: string; appId: string; appSecret: string; bindStore: string; enabled: boolean;\n};\n\nconst channelConfigs: ChannelConfig[] = [\n { id: \"whatsapp\", name: \"WhatsApp\", type: \"即时通讯\", status: \"已连接\", icon: \"💬\", webhook: \"https://api.example.com/webhook/whatsapp\", appId: \"WA_APP_12345\", appSecret: \"whasec_xxxx\", bindStore: \"主店铺\", enabled: true },\n { id: \"messenger\", name: \"Messenger\", type: \"即时通讯\", status: \"已连接\", icon: \"📘\", webhook: \"https://api.example.com/webhook/messenger\", appId: \"MSG_APP_67890\", appSecret: \"msgsec_xxxx\", bindStore: \"主店铺\", enabled: true },\n { id: \"telegram\", name: \"Telegram\", type: \"即时通讯\", status: \"已连接\", icon: \"✈️\", webhook: \"https://api.example.com/webhook/telegram\", appId: \"TG_BOT_11111\", appSecret: \"tgsec_xxxx\", bindStore: \"\", enabled: true },\n { id: \"email\", name: \"Email\", type: \"邮件\", status: \"已连接\", icon: \"📧\", webhook: \"https://api.example.com/webhook/email\", appId: \"support@company.com\", appSecret: \"\", bindStore: \"主店铺\", enabled: true },\n { id: \"line\", name: \"LINE\", type: \"即时通讯\", status: \"未连接\", icon: \"🟢\", webhook: \"\", appId: \"\", appSecret: \"\", bindStore: \"\", enabled: false },\n { id: \"wechat\", name: \"微信公众号\", type: \"即时通讯\", status: \"未连接\", icon: \"💚\", webhook: \"\", appId: \"\", appSecret: \"\", bindStore: \"\", enabled: false },\n];\n\nconst apiKeys = [\n { id: \"1\", name: \"生产环境密钥\", key: \"ak_live_xxxxxxxxxxxx\", createdAt: \"2026-01-15\", lastUsed: \"2小时前\", status: \"active\" },\n { id: \"2\", name: \"测试环境密钥\", key: \"ak_test_xxxxxxxxxxxx\", createdAt: \"2026-02-01\", lastUsed: \"1天前\", status: \"active\" },\n { id: \"3\", name: \"开发环境密钥\", key: \"ak_dev_xxxxxxxxxxxx\", createdAt: \"2026-01-20\", lastUsed: \"3天前\", status: \"inactive\" },\n];\n\n// ---- Toast 组件 ----\nfunction Toast({ msg, type = \"success\", onClose }: { msg: string; type?: \"success\" | \"error\" | \"info\"; onClose: () => void }) {\n const colorMap = { success: \"bg-gray-900 text-white\", error: \"bg-red-600 text-white\", info: \"bg-blue-600 text-white\" };\n return (\n
\n {type === \"success\" && }\n {type === \"error\" && }\n {msg}\n \n
\n );\n}\n\nexport default function SettingsPage() {\n const [activeMenu, setActiveMenu] = useState(\"使用教程\");\n\n // ---- 全局 Toast ----\n const [toastMsg, setToastMsg] = useState(\"\");\n const [toastType, setToastType] = useState<\"success\" | \"error\" | \"info\">(\"success\");\n const [showToast, setShowToast] = useState(false);\n const toast = (msg: string, type: \"success\" | \"error\" | \"info\" = \"success\") => {\n setToastMsg(msg); setToastType(type); setShowToast(true);\n setTimeout(() => setShowToast(false), 2500);\n };\n\n // ---- 项目设置 ----\n const [projectName, setProjectName] = useState(\"我的AI客服系统\");\n const [timezone, setTimezone] = useState(\"Asia/Shanghai\");\n const [language, setLanguage] = useState(\"zh-CN\");\n\n // ---- AI 模型配置 ----\n type AIModel = { name: string; provider: string; type: string; status: string; badge: string; apiKey: string; configured: boolean };\n const [aiModels, setAIModels] = useState([\n { name: \"DeepSeek R1\", provider: \"DeepSeek\", type: \"推理模型\", status: \"已配置\", badge: \"主用\", apiKey: \"sk-••••••••••••••••\", configured: true },\n { name: \"通义千问 Max\", provider: \"阿里云\", type: \"对话模型\", status: \"已配置\", badge: \"\", apiKey: \"sk-••••••••••••••••\", configured: true },\n { name: \"GLM-4 Flash\", provider: \"智谱AI\", type: \"快速模型\", status: \"已配置\", badge: \"免费\", apiKey: \"sk-••••••••••••••••\", configured: true },\n ]);\n const [showAIConfigModal, setShowAIConfigModal] = useState(false);\n const [editingAIModel, setEditingAIModel] = useState(null);\n const [showAddModelModal, setShowAddModelModal] = useState(false);\n const [newModel, setNewModel] = useState({ name: \"\", provider: \"DeepSeek\", type: \"对话模型\", apiKey: \"\" });\n const [showAIKeyValue, setShowAIKeyValue] = useState>({});\n\n const handleEditAIModel = (m: AIModel) => { setEditingAIModel({ ...m, apiKey: \"\" }); setShowAIConfigModal(true); };\n const handleSaveAIModel = () => {\n if (!editingAIModel) return;\n setAIModels(prev => prev.map(m => m.name === editingAIModel.name ? { ...m, apiKey: editingAIModel.apiKey || m.apiKey, configured: true, status: \"已配置\" } : m));\n setShowAIConfigModal(false); setEditingAIModel(null); toast(\"模型配置已保存\");\n };\n const handleSaveNewModel = () => {\n if (!newModel.name.trim() || !newModel.apiKey.trim()) return;\n setAIModels(prev => [...prev, { name: newModel.name, provider: newModel.provider, type: newModel.type, status: \"已配置\", badge: \"\", apiKey: \"sk-••••••••••••••••\", configured: true }]);\n setNewModel({ name: \"\", provider: \"DeepSeek\", type: \"对话模型\", apiKey: \"\" });\n setShowAddModelModal(false); toast(\"模型添加成功\");\n };\n\n // ---- 成员管理 ----\n const members = [\n { name: \"张三\", email: \"zhang@company.com\", role: \"管理员\", status: \"在线\", joined: \"2026-01-01\" },\n { name: \"李四\", email: \"li@company.com\", role: \"客服\", status: \"在线\", joined: \"2026-01-15\" },\n { name: \"王五\", email: \"wang@company.com\", role: \"客服\", status: \"离线\", joined: \"2026-02-01\" },\n { name: \"赵六\", email: \"zhao@company.com\", role: \"主管\", status: \"忙碌\", joined: \"2026-01-20\" },\n ];\n type Member = typeof members[0];\n const [memberList, setMemberList] = useState(members);\n const [showEditMemberModal, setShowEditMemberModal] = useState(false);\n const [editingMember, setEditingMember] = useState(null);\n const [showDeleteMemberConfirm, setShowDeleteMemberConfirm] = useState(false);\n const [deletingMember, setDeletingMember] = useState(null);\n const [showInviteModal, setShowInviteModal] = useState(false);\n const [inviteEmail, setInviteEmail] = useState(\"\");\n const [inviteRole, setInviteRole] = useState(\"客服\");\n\n // ---- 权限配置 ----\n const [selectedRole, setSelectedRole] = useState(\"admin\");\n const [rolePermissions, setRolePermissions] = useState>({\n admin: permissionModules.flatMap(m => m.permissions.map(p => `${m.id}:${p}`)),\n supervisor: [\"dashboard:查看\", \"conversations:查看\", \"conversations:回复\", \"conversations:转接\", \"customers:查看\", \"customers:编辑\", \"marketing:查看\", \"ai:查看\", \"knowledge:查看\", \"channels:查看\", \"reports:查看\", \"reports:导出\"],\n agent: [\"dashboard:查看\", \"conversations:查看\", \"conversations:回复\", \"conversations:转接\", \"customers:查看\", \"customers:编辑\", \"knowledge:查看\"],\n viewer: [\"dashboard:查看\", \"conversations:查看\", \"customers:查看\", \"reports:查看\"],\n });\n const togglePermission = (moduleId: string, permission: string) => {\n const key = `${moduleId}:${permission}`;\n setRolePermissions(prev => ({ ...prev, [selectedRole]: prev[selectedRole].includes(key) ? prev[selectedRole].filter(p => p !== key) : [...prev[selectedRole], key] }));\n };\n\n // ---- 通知设置 ----\n const [notifications, setNotifications] = useState(notificationTypes);\n const [notifyEmail, setNotifyEmail] = useState(\"admin@company.com\");\n const [notifyPhone, setNotifyPhone] = useState(\"+86 138 0000 0000\");\n const [notifyEmailEnabled, setNotifyEmailEnabled] = useState(true);\n const [notifySmsEnabled, setNotifySmsEnabled] = useState(true);\n const [notifyPushEnabled, setNotifyPushEnabled] = useState(true);\n const toggleNotification = (id: string, type: \"email\" | \"push\" | \"sms\") => {\n setNotifications(prev => prev.map(n => n.id === id ? { ...n, [type]: !n[type] } : n));\n };\n\n // ---- 渠道配置 ----\n const [channels, setChannels] = useState(channelConfigs);\n const [showChannelModal, setShowChannelModal] = useState(false);\n const [editingChannel, setEditingChannel] = useState(null);\n const [webhookTestResult, setWebhookTestResult] = useState>({});\n const [channelSecretVisible, setChannelSecretVisible] = useState>({});\n const openChannelEdit = (c: ChannelConfig) => { setEditingChannel({ ...c }); setShowChannelModal(true); };\n const saveChannel = () => {\n if (!editingChannel) return;\n setChannels(prev => prev.map(c => c.id === editingChannel.id ? { ...editingChannel, status: editingChannel.appId ? \"已连接\" : \"未连接\" } : c));\n setShowChannelModal(false); setEditingChannel(null); toast(\"渠道配置已保存\");\n };\n const testWebhook = (id: string) => {\n setWebhookTestResult(prev => ({ ...prev, [id]: \"testing\" }));\n setTimeout(() => setWebhookTestResult(prev => ({ ...prev, [id]: Math.random() > 0.2 ? \"ok\" : \"fail\" })), 1500);\n };\n const toggleChannel = (id: string) => {\n setChannels(prev => prev.map(c => c.id === id ? { ...c, enabled: !c.enabled } : c));\n };\n\n // ---- 界面定制 ----\n const [theme, setTheme] = useState(\"light\");\n const [primaryColor, setPrimaryColor] = useState(\"#3B82F6\");\n\n // ---- 数据管理 ----\n const [showExportConfirm, setShowExportConfirm] = useState<{ name: string; format: string } | null>(null);\n const [showCleanConfirm, setShowCleanConfirm] = useState<{ name: string; desc: string } | null>(null);\n const [exportRetentionDays, setExportRetentionDays] = useState(\"30 天\");\n const handleExport = (name: string, format: string) => { setShowExportConfirm({ name, format }); };\n const confirmExport = () => { toast(`${showExportConfirm?.name} 导出任务已提交,稍后将发送到您的邮箱`); setShowExportConfirm(null); };\n const handleClean = (name: string, desc: string) => { setShowCleanConfirm({ name, desc }); };\n const confirmClean = () => { toast(`${showCleanConfirm?.name} 已清理完成`); setShowCleanConfirm(null); };\n\n // ---- API密钥 ----\n const [keys, setKeys] = useState(apiKeys);\n const [showKeyId, setShowKeyId] = useState(null);\n const [newKeyName, setNewKeyName] = useState(\"\");\n const [showNewKeyModal, setShowNewKeyModal] = useState(false);\n const [generatedKey, setGeneratedKey] = useState(\"\");\n const generateNewKey = () => {\n const k = `ak_live_${Math.random().toString(36).substring(2, 15)}${Math.random().toString(36).substring(2, 15)}`;\n setGeneratedKey(k); setShowNewKeyModal(true);\n };\n const saveNewKey = () => {\n if (newKeyName && generatedKey) {\n setKeys(prev => [...prev, { id: Date.now().toString(), name: newKeyName, key: generatedKey, createdAt: new Date().toISOString().split(\"T\")[0], lastUsed: \"从未\", status: \"active\" }]);\n setNewKeyName(\"\"); setGeneratedKey(\"\"); setShowNewKeyModal(false); toast(\"密钥已保存\");\n }\n };\n const deleteKey = (id: string) => { setKeys(prev => prev.filter(k => k.id !== id)); toast(\"密钥已删除\"); };\n const copyKey = (key: string) => { navigator.clipboard.writeText(key); toast(\"已复制到剪贴板\"); };\n\n return (\n \n
\n {/* Settings Nav */}\n
\n
设置
\n \n
\n\n {/* Settings Content */}\n
\n\n {/* ───── 使用教程 ───── */}\n {activeMenu === \"使用教程\" && (\n
\n
\n

使用教程

\n

学习系统各功能的使用方法和API配置指南

\n
\n\n {/* 教程分类卡片 */}\n
\n {[\n {\n icon: \"🎯\",\n title: \"快速入门\",\n desc: \"5分钟快速上手系统核心功能\",\n color: \"bg-green-50 border-green-200\",\n href: \"/tutorials\",\n chapters: [\"创建第一个AI机器人\", \"连接第一个渠道\", \"配置知识库\", \"测试对话\"],\n },\n {\n icon: \"📚\",\n title: \"模块教程\",\n desc: \"各功能模块详细使用指南\",\n color: \"bg-blue-50 border-blue-200\",\n href: \"/tutorials\",\n chapters: [\"首页仪表盘\", \"聊天会话管理\", \"客户管理\", \"AI机器人配置\"],\n },\n {\n icon: \"🔌\",\n title: \"API配置教程\",\n desc: \"各平台API接入详细步骤\",\n color: \"bg-purple-50 border-purple-200\",\n href: \"/tutorials\",\n chapters: [\"WhatsApp Business\", \"LINE Messaging API\", \"企业邮箱配置\", \"Telegram Bot\"],\n },\n {\n icon: \"⚡\",\n title: \"高级配置\",\n desc: \"自动化工作流和高级功能\",\n color: \"bg-orange-50 border-orange-200\",\n href: \"/tutorials\",\n chapters: [\"自动化工作流\", \"AI员工配置\", \"代理IP配置\", \"指纹浏览器\"],\n },\n ].map((item, idx) => (\n \n
\n {item.icon}\n
\n

{item.title}

\n

{item.desc}

\n
\n
\n
    \n {item.chapters.map((ch, i) => (\n
  • \n \n {ch}\n
  • \n ))}\n
\n
\n 查看完整教程\n \n
\n
\n ))}\n
\n\n {/* 快速链接 */}\n
\n

\n \n 热门教程\n

\n
\n {[\n { title: \"WhatsApp Business API 配置指南\", icon: \"💬\", page: \"/binding\" },\n { title: \"LINE 官方账号接入教程\", icon: \"🟢\", page: \"/binding\" },\n { title: \"企业邮箱 SMTP/IMAP 配置\", icon: \"📧\", page: \"/binding\" },\n { title: \"代理IP配置与轮换策略\", icon: \"🌐\", page: \"/proxies\" },\n { title: \"AI模型选择与配置\", icon: \"🤖\", page: \"/ai\" },\n { title: \"指纹浏览器集成指南\", icon: \"🔐\", page: \"/fingerprint\" },\n { title: \"知识库构建与训练\", icon: \"📖\", page: \"/knowledge\" },\n { title: \"健康监控与告警配置\", icon: \"📊\", page: \"/monitoring\" },\n ].map((item, idx) => (\n \n {item.icon}\n {item.title}\n \n ))}\n
\n
\n\n {/* 视频教程入口 */}\n
\n
\n
\n

视频教程中心

\n

观看完整操作视频,手把手教学

\n
\n \n
\n
\n
\n )}\n\n {/* ───── 项目设置 ───── */}\n {activeMenu === \"项目设置\" && (\n
\n
\n

项目设置

\n

管理你的客服系统基本配置

\n
\n
\n
\n \n setProjectName(e.target.value)} className=\"w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:border-blue-400\" />\n
\n
\n \n \n
\n
\n \n \n
\n
\n \n
\n
\n\n {/* AI 模型配置 */}\n
\n

AI 模型配置

\n
\n {aiModels.map(m => (\n
\n
\n
\n {m.name}\n {m.badge && {m.badge}}\n
\n
{m.provider} · {m.type}
\n
\n
\n {m.status}\n \n
\n
\n ))}\n
\n \n
\n
\n
\n
\n )}\n\n {/* ───── 成员管理 ───── */}\n {activeMenu === \"成员管理\" && (\n
\n
\n
\n

成员管理

\n

管理团队成员和权限

\n
\n \n
\n
\n \n \n {[\"成员\", \"角色\", \"状态\", \"加入时间\", \"操作\"].map(h => )}\n \n \n {memberList.map((m, i) => (\n \n \n \n \n \n \n \n ))}\n \n
{h}
\n
\n
{m.name[0]}
\n
\n
{m.name}
\n
{m.email}
\n
\n
\n
\n {m.role}\n \n
\n \n {m.status}\n
\n
{m.joined}\n \n \n
\n
\n
\n )}\n\n {/* ───── 权限配置 ───── */}\n {activeMenu === \"权限配置\" && (\n
\n
\n

权限配置

\n

管理角色权限和访问控制

\n
\n
\n
\n {roles.map(role => (\n \n ))}\n
\n
\n
\n
\n

权限矩阵

\n 已勾选 {rolePermissions[selectedRole]?.length ?? 0} 项权限\n
\n \n \n \n \n {permissionModules[0]?.permissions.map(p => )}\n \n \n \n {permissionModules.map(module => (\n \n \n {module.permissions.map(permission => {\n const key = `${module.id}:${permission}`;\n const has = rolePermissions[selectedRole]?.includes(key);\n return (\n \n );\n })}\n \n ))}\n \n
功能模块{p}
{module.name}\n \n
\n
\n
\n \n \n
\n
\n )}\n\n {/* ───── 通知设置 ───── */}\n {activeMenu === \"通知设置\" && (\n
\n
\n

通知设置

\n

配置邮件、短信和推送通知

\n
\n\n {/* 渠道开关 + 绑定地址 */}\n
\n {/* 邮件 */}\n
\n
\n
\n \n 邮件通知\n
\n \n
\n
\n \n setNotifyEmail(e.target.value)} disabled={!notifyEmailEnabled}\n className=\"w-full px-2 py-1.5 border border-gray-200 rounded-lg text-xs focus:outline-none focus:border-blue-400 disabled:bg-gray-50 disabled:text-gray-400\" />\n
\n
\n {/* 短信 */}\n
\n
\n
\n \n 短信通知\n
\n \n
\n
\n \n setNotifyPhone(e.target.value)} disabled={!notifySmsEnabled}\n className=\"w-full px-2 py-1.5 border border-gray-200 rounded-lg text-xs focus:outline-none focus:border-blue-400 disabled:bg-gray-50 disabled:text-gray-400\" />\n
\n
\n {/* 推送 */}\n
\n
\n
\n \n 推送通知\n
\n \n
\n

浏览器 / APP 推送通知,无需额外配置

\n
\n
\n\n {/* 通知类型 */}\n
\n
\n

通知类型配置

\n
\n \n \n \n \n \n \n \n \n \n \n {notifications.map(n => (\n \n \n {([\"email\", \"push\", \"sms\"] as const).map(type => (\n \n ))}\n \n ))}\n \n
通知类型邮件推送短信
\n
{n.name}
\n
{n.description}
\n
\n toggleNotification(n.id, type)} className=\"rounded text-blue-600\" />\n
\n
\n
\n \n
\n
\n )}\n\n {/* ───── 渠道配置 ───── */}\n {activeMenu === \"渠道配置\" && (\n
\n
\n

渠道配置

\n

管理各渠道接入、绑定店铺和 Webhook 设置

\n
\n\n {/* Webhook 基础配置 */}\n
\n

\n Webhook 基础配置\n

\n
\n
\n \n
\n \n \n
\n
\n
\n \n
\n \n \n
\n
\n
\n
\n\n {/* 渠道列表 */}\n
\n
\n

渠道列表

\n {channels.filter(c => c.status === \"已连接\").length}/{channels.length} 已连接\n
\n
\n {channels.map(channel => (\n
\n
\n
\n {channel.icon}\n
\n
\n {channel.name}\n {channel.status}\n
\n
{channel.type}{channel.bindStore ? ` · 绑定店铺: ${channel.bindStore}` : \"\"}
\n
\n
\n
\n {/* 启用/禁用开关 */}\n \n {/* Webhook 测试 */}\n {channel.status === \"已连接\" && (\n testWebhook(channel.id)}\n className={`text-xs px-2 py-1 rounded border flex items-center gap-1 transition-colors ${\n webhookTestResult[channel.id] === \"testing\" ? \"border-yellow-300 text-yellow-600 bg-yellow-50\" :\n webhookTestResult[channel.id] === \"ok\" ? \"border-green-300 text-green-600 bg-green-50\" :\n webhookTestResult[channel.id] === \"fail\" ? \"border-red-300 text-red-600 bg-red-50\" :\n \"border-gray-200 text-gray-600 hover:bg-gray-50\"\n }`}\n >\n \n {webhookTestResult[channel.id] === \"testing\" ? \"测试中...\" : webhookTestResult[channel.id] === \"ok\" ? \"正常\" : webhookTestResult[channel.id] === \"fail\" ? \"失败\" : \"测试\"}\n \n )}\n {/* 配置按钮 */}\n \n
\n
\n {/* 绑定店铺快捷信息 */}\n {channel.appId && (\n
\n App ID: {channel.appId.substring(0, 12)}{channel.appId.length > 12 ? \"...\" : \"\"}\n {channel.bindStore && {channel.bindStore}}\n
\n )}\n
\n ))}\n
\n
\n
\n )}\n\n {/* ───── 界面定制 ───── */}\n {activeMenu === \"界面定制\" && (\n
\n
\n

界面定制

\n

自定义主题、Logo和品牌颜色

\n
\n
\n

主题模式

\n
\n {[{ key: \"light\", icon: , label: \"浅色模式\", desc: \"默认明亮主题\" },\n { key: \"dark\", icon: , label: \"深色模式\", desc: \"护眼暗色主题\" },\n { key: \"auto\", icon: , label: \"跟随系统\", desc: \"自动切换\" }].map(t => (\n \n ))}\n
\n
\n
\n

品牌颜色

\n
\n \n
\n {[\"#3B82F6\", \"#10B981\", \"#8B5CF6\", \"#F59E0B\", \"#EF4444\", \"#EC4899\"].map(color => (\n
\n
\n
\n
\n

品牌标识

\n
\n {[{ label: \"Logo\", hint: \"建议尺寸 200x50px\" }, { label: \"Favicon\", hint: \"建议尺寸 32x32px\" }].map(item => (\n
\n \n
toast(\"文件选择功能开发中\", \"info\")}>\n \n
点击上传 {item.label}
\n
{item.hint}
\n
\n
\n ))}\n
\n
\n
\n \n
\n
\n )}\n\n {/* ───── 数据管理 ───── */}\n {activeMenu === \"数据管理\" && (\n
\n
\n

数据管理

\n

数据导出、备份和清理

\n
\n\n {/* 数据导出 */}\n
\n

数据导出

\n
\n {[\n { name: \"客户数据\", desc: \"导出所有客户信息(姓名、联系方式、标签)\", format: \"CSV, Excel\" },\n { name: \"会话记录\", desc: \"导出历史会话数据(含会话摘要)\", format: \"JSON, CSV\" },\n { name: \"消息记录\", desc: \"导出所有消息内容(含附件链接)\", format: \"JSON\" },\n { name: \"报告数据\", desc: \"导出统计报告(含图表数据)\", format: \"Excel, PDF\" },\n ].map(item => (\n
\n
\n
{item.name}
\n
{item.desc}
\n
\n
\n {item.format}\n \n
\n
\n ))}\n
\n
\n\n {/* 数据备份 */}\n
\n

数据备份

\n
\n
\n
\n
自动备份
\n
每天凌晨 2:00 自动备份
\n
\n \n
\n
\n
\n
备份保留期
\n
超过保留期的备份将自动删除
\n
\n \n
\n
\n
\n
上次备份时间
\n
2026-04-01 02:00:00 · 共 128 MB
\n
\n \n
\n
\n
\n\n {/* 数据清理 */}\n
\n

数据清理

\n
\n {[\n { name: \"清理已删除客户\", desc: \"永久删除已标记删除的客户数据,不可恢复\", danger: true },\n { name: \"清理过期会话\", desc: \"删除超过 1 年的已结束会话记录\", danger: true },\n { name: \"清理系统日志\", desc: \"删除 90 天前的系统运行日志\", danger: false },\n ].map(item => (\n
\n
\n
{item.name}
\n
{item.desc}
\n
\n \n
\n ))}\n
\n
\n
\n )}\n\n {/* ───── API 密钥 ───── */}\n {activeMenu === \"API密钥\" && (\n
\n
\n
\n

API密钥

\n

管理API访问密钥和权限

\n
\n \n
\n
\n \n \n {[\"名称\", \"密钥\", \"创建时间\", \"最后使用\", \"状态\", \"操作\"].map(h => )}\n \n \n {keys.map(k => (\n \n \n \n \n \n \n \n \n ))}\n \n
{h}
{k.name}\n
\n {showKeyId === k.id ? k.key : `${k.key.substring(0, 8)}...`}\n \n
\n
{k.createdAt}{k.lastUsed}\n \n {k.status === \"active\" ? \"活跃\" : \"已禁用\"}\n \n \n
\n \n \n
\n
\n
\n
\n

API 文档

\n

查看完整的 API 文档和示例代码

\n \n
\n
\n )}\n\n
{/* end Settings Content */}\n
{/* end flex h-screen */}\n\n {/* ═══════════════ 全局 Toast ═══════════════ */}\n {showToast && setShowToast(false)} />}\n\n {/* ═══════════════ 邀请成员弹窗 ═══════════════ */}\n {showInviteModal && (\n
\n
\n
\n

邀请成员

\n \n
\n
\n
\n \n setInviteEmail(e.target.value)} placeholder=\"colleague@company.com\"\n className=\"w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500\" />\n
\n
\n \n \n
\n

邀请邮件将发送至上述地址,对方接受后成为团队成员。

\n
\n
\n \n \n
\n
\n
\n )}\n\n {/* ═══════════════ 编辑成员弹窗 ═══════════════ */}\n {showEditMemberModal && editingMember && (\n
\n
\n
\n

编辑成员

\n \n
\n
\n
\n \n setEditingMember({ ...editingMember, name: e.target.value })} className=\"w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500\" />\n
\n
\n \n setEditingMember({ ...editingMember, email: e.target.value })} className=\"w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500\" />\n
\n
\n
\n \n \n
\n
\n \n \n
\n
\n
\n
\n \n \n
\n
\n
\n )}\n\n {/* ═══════════════ 删除成员确认 ═══════════════ */}\n {showDeleteMemberConfirm && deletingMember && (\n
\n
\n

移除成员

\n

确定要移除成员 \"{deletingMember.name}\" 吗?移除后该成员将无法访问系统。

\n
\n \n \n
\n
\n
\n )}\n\n {/* ═══════════════ 渠道配置弹窗 ═══════════════ */}\n {showChannelModal && editingChannel && (\n
\n
\n
\n
\n {editingChannel.icon}\n
\n
{editingChannel.name} 配置
\n
{editingChannel.type}
\n
\n
\n \n
\n
\n {/* App ID */}\n
\n \n setEditingChannel({ ...editingChannel, appId: e.target.value })}\n placeholder={editingChannel.type === \"邮件\" ? \"support@yourcompany.com\" : \"输入 App ID 或 Bot Token\"}\n className=\"w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500\" />\n
\n {/* App Secret */}\n {editingChannel.type !== \"邮件\" && (\n
\n \n
\n setEditingChannel({ ...editingChannel, appSecret: e.target.value })}\n placeholder=\"输入 App Secret\"\n className=\"w-full px-3 py-2 pr-10 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500\" />\n \n
\n
\n )}\n {/* 绑定店铺 */}\n
\n \n \n

绑定后,该渠道的会话将关联到对应店铺的客户数据

\n
\n {/* Webhook URL */}\n
\n \n
\n setEditingChannel({ ...editingChannel, webhook: e.target.value })}\n placeholder=\"https://api.example.com/webhook/...\"\n className=\"flex-1 px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500\" />\n \n
\n {webhookTestResult[editingChannel.id] === \"ok\" &&

✓ Webhook 连通性正常

}\n {webhookTestResult[editingChannel.id] === \"fail\" &&

✗ Webhook 测试失败,请检查地址

}\n
\n
\n
\n 配置保存后自动更新连接状态\n
\n \n \n
\n
\n
\n
\n )}\n\n {/* ═══════════════ AI 模型配置弹窗 ═══════════════ */}\n {showAIConfigModal && editingAIModel && (\n
\n
\n
\n
\n
配置 {editingAIModel.name}
\n
{editingAIModel.provider} · {editingAIModel.type}
\n
\n \n
\n
\n
\n \n
\n setEditingAIModel({ ...editingAIModel, apiKey: e.target.value })}\n placeholder=\"请输入 API Key(如 sk-xxxxxxxxxxxxxxxx)\"\n className=\"w-full px-3 py-2 pr-10 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500\" />\n \n
\n

API Key 将加密保存,不会在页面中明文展示

\n
\n
\n 💡 获取 API Key:前往 {editingAIModel.provider} 官网 → 控制台 → API 密钥管理\n
\n
\n
\n \n \n
\n
\n
\n )}\n\n {/* ═══════════════ 添加模型弹窗 ═══════════════ */}\n {showAddModelModal && (\n
\n
\n
\n 添加 AI 模型\n \n
\n
\n
\n \n setNewModel({ ...newModel, name: e.target.value })} placeholder=\"例如:GPT-4o、Kimi-k2...\"\n className=\"w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500\" />\n
\n
\n
\n \n \n
\n
\n \n \n
\n
\n
\n \n setNewModel({ ...newModel, apiKey: e.target.value })} placeholder=\"请输入 API Key\"\n className=\"w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500\" />\n
\n
\n
\n \n \n
\n
\n
\n )}\n\n {/* ═══════════════ 新建 API 密钥弹窗 ═══════════════ */}\n {showNewKeyModal && (\n
\n
\n
\n
\n \n
\n
\n

密钥已生成

\n

请立即复制保存,此密钥仅显示一次

\n
\n
\n
\n {generatedKey}\n \n
\n
\n \n setNewKeyName(e.target.value)} placeholder=\"例如:生产环境\"\n className=\"w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:border-blue-400\" />\n
\n
\n \n \n
\n
\n
\n )}\n\n {/* ═══════════════ 导出确认弹窗 ═══════════════ */}\n {showExportConfirm && (\n
\n
\n
\n
\n \n
\n
\n

确认导出

\n

即将导出 {showExportConfirm.name}

\n
\n
\n
\n
格式:{showExportConfirm.format}
\n
导出结果将通过邮件发送,请注意查收
\n
\n
\n \n \n
\n
\n
\n )}\n\n {/* ═══════════════ 清理确认弹窗 ═══════════════ */}\n {showCleanConfirm && (\n
\n
\n
\n
\n \n
\n
\n

{showCleanConfirm.name}

\n

此操作不可恢复,请谨慎操作

\n
\n
\n

{showCleanConfirm.desc}

\n
\n \n \n
\n
\n
\n )}\n\n
\n );\n}\n"}}}}