forked from xiaohei/taiji-pda-v0
3004 lines
135 KiB
TypeScript
3004 lines
135 KiB
TypeScript
"use client"
|
||
|
||
import { useEffect, useState } from "react"
|
||
import { useRouter } from "next/navigation"
|
||
import { Card } from "@/components/ui/card"
|
||
import { Button } from "@/components/ui/button"
|
||
import { Input } from "@/components/ui/input"
|
||
import {
|
||
Shield,
|
||
Users,
|
||
DollarSign,
|
||
Activity,
|
||
Database,
|
||
TrendingUp,
|
||
AlertTriangle,
|
||
Search,
|
||
MoreVertical,
|
||
Globe,
|
||
LogOut,
|
||
Settings,
|
||
BarChart3,
|
||
Clock,
|
||
Building2,
|
||
Plus,
|
||
Eye,
|
||
Edit,
|
||
Trash2,
|
||
Zap,
|
||
Cpu,
|
||
Server,
|
||
Bot,
|
||
Check,
|
||
Calendar,
|
||
Filter,
|
||
Download,
|
||
} from "lucide-react"
|
||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||
import {
|
||
DropdownMenu,
|
||
DropdownMenuContent,
|
||
DropdownMenuItem,
|
||
DropdownMenuSeparator,
|
||
DropdownMenuTrigger,
|
||
} from "@/components/ui/dropdown-menu"
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogDescription,
|
||
DialogFooter,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
DialogTrigger,
|
||
} from "@/components/ui/dialog"
|
||
import { Label } from "@/components/ui/label"
|
||
import { useLanguage } from "@/hooks/useLanguage" // Declare useLanguage import
|
||
import { Badge } from "@/components/ui/badge"
|
||
|
||
export default function AdminDashboard() {
|
||
const router = useRouter()
|
||
const { language, setLanguage } = useLanguage()
|
||
const [activeTab, setActiveTab] = useState("overview")
|
||
const [isAddChannelOpen, setIsAddChannelOpen] = useState(false)
|
||
// const [isConfigGoodsOpen, setIsConfigGoodsOpen] = useState(false) // REMOVED
|
||
// const [isAgentAllocationOpen, setIsAgentAllocationOpen] = useState(false) // REMOVED
|
||
const [isResourceManagementOpen, setIsResourceManagementOpen] = useState(false) // ADDED
|
||
|
||
const [isCommissionDialogOpen, setIsCommissionDialogOpen] = useState(false)
|
||
const [selectedChannel, setSelectedChannel] = useState<(typeof channels)[0] | null>(null)
|
||
const [selectedModels, setSelectedModels] = useState<string[]>([])
|
||
// const [monthlyQuota, setMonthlyQuota] = useState<string>(""); // REMOVED
|
||
const [creditLimit, setCreditLimit] = useState<string>("")
|
||
const [isSubscriptionDialogOpen, setIsSubscriptionDialogOpen] = useState(false)
|
||
const [selectedSubscriptionLevel, setSelectedSubscriptionLevel] = useState<string | null>(null)
|
||
const [commissionRate, setCommissionRate] = useState<string>("")
|
||
const [selectedTenant, setSelectedTenant] = useState<string | null>(null) // Declare selectedTenant variable
|
||
const [computeConfigOpen, setComputeConfigOpen] = useState(false) // State for compute configuration dialog
|
||
|
||
const [isAddProviderOpen, setIsAddProviderOpen] = useState(false)
|
||
const [isConfigProviderOpen, setIsConfigProviderOpen] = useState(false)
|
||
const [providerType, setProviderType] = useState<"model" | "data">("model")
|
||
const [selectedProvider, setSelectedProvider] = useState<any>(null)
|
||
const [providerForm, setProviderForm] = useState({
|
||
name: "",
|
||
url: "",
|
||
apiKey: "",
|
||
models: "",
|
||
rpm: "",
|
||
tpm: "",
|
||
})
|
||
|
||
const [selectedAgent, setSelectedAgent] = useState<any>(null) // State for selected agent in resources tab
|
||
const [customAgentCpu, setCustomAgentCpu] = useState("2")
|
||
const [customAgentMemory, setCustomAgentMemory] = useState("4")
|
||
|
||
// const [isAgentAllocationOpen, setIsAgentAllocationOpen] = useState(false) // REMOVED
|
||
const [selectedAgents, setSelectedAgents] = useState<string[]>([])
|
||
const [agentQuantities, setAgentQuantities] = useState<Record<string, number>>({})
|
||
|
||
// Billing state
|
||
const [billingView, setBillingView] = useState<"channel" | "tenant" | "calls">("channel")
|
||
const [showFilterDialog, setShowFilterDialog] = useState(false)
|
||
const [showDateDialog, setShowDateDialog] = useState(false)
|
||
const [showExportDialog, setShowExportDialog] = useState(false)
|
||
|
||
const [selectedRole, setSelectedRole] = useState<string | null>(null)
|
||
const [rolePermissions, setRolePermissions] = useState<{ [key: string]: string[] }>({
|
||
"billing-admin": ["overview", "channels", "billing"],
|
||
"operations-admin": ["overview", "channels", "resources", "monitoring"],
|
||
"super-admin": [
|
||
"overview",
|
||
"channels",
|
||
"resources",
|
||
"monitoring",
|
||
"billing",
|
||
"provider-backend",
|
||
"channel-backend",
|
||
"settings",
|
||
],
|
||
})
|
||
|
||
const [showAddAdminDialog, setShowAddAdminDialog] = useState(false)
|
||
const [newAdminForm, setNewAdminForm] = useState({
|
||
name: "",
|
||
email: "",
|
||
password: "",
|
||
role: "billing-admin",
|
||
})
|
||
|
||
const [providerApprovals, setProviderApprovals] = useState<any[]>([
|
||
{
|
||
id: 1,
|
||
channelName: "Enterprise Solutions Inc",
|
||
providerName: "Google AI",
|
||
expectedModels: "gemini-pro, palm-2",
|
||
reason: "Need Gemini Pro for advanced natural language processing tasks",
|
||
status: "pending",
|
||
submittedAt: "2024-01-15 14:30:22",
|
||
},
|
||
{
|
||
id: 2,
|
||
channelName: "Cloud Partners Asia",
|
||
providerName: "Azure OpenAI",
|
||
expectedModels: "gpt-4, gpt-35-turbo",
|
||
reason: "Regional compliance requirements for Azure infrastructure",
|
||
status: "pending",
|
||
submittedAt: "2024-01-14 09:15:45",
|
||
},
|
||
])
|
||
const [selectedApproval, setSelectedApproval] = useState<any>(null)
|
||
const [isApprovalDialogOpen, setIsApprovalDialogOpen] = useState(false)
|
||
|
||
// Agent申请审批相关状态
|
||
const [agentApprovals, setAgentApprovals] = useState<any[]>([
|
||
{
|
||
id: 1,
|
||
channelName: "Enterprise Solutions Inc",
|
||
agentType: "Weather Query Agent",
|
||
requestedQuantity: 50,
|
||
reason: "Need weather agents for IoT monitoring platform",
|
||
status: "pending",
|
||
submittedAt: "2024-01-15 16:20:10",
|
||
},
|
||
{
|
||
id: 2,
|
||
channelName: "Cloud Partners Asia",
|
||
agentType: "Data Analysis Agent",
|
||
requestedQuantity: 30,
|
||
reason: "Enterprise data analytics expansion",
|
||
status: "pending",
|
||
submittedAt: "2024-01-14 11:45:33",
|
||
},
|
||
])
|
||
const [selectedAgentApproval, setSelectedAgentApproval] = useState<any>(null)
|
||
const [isAgentApprovalDialogOpen, setIsAgentApprovalDialogOpen] = useState(false)
|
||
|
||
// Simplified t function for demonstration
|
||
const t = (zh: string, en: string) => (language === "zh" ? zh : en)
|
||
|
||
const translations = {
|
||
en: {
|
||
title: "Super Admin Console",
|
||
subtitle: "Platform Control Center",
|
||
overview: "Overview",
|
||
channels: "Channels",
|
||
resources: "Resources",
|
||
monitoring: "Monitoring",
|
||
billing: "Billing",
|
||
settings: "Settings",
|
||
goodsProviders: "Goods Providers", // renamed from separate model/data providers
|
||
logout: "Logout",
|
||
totalTenants: "Total Tenants",
|
||
totalChannels: "Total Channels",
|
||
activeTenants: "Active Tenants",
|
||
totalRevenue: "Total Revenue",
|
||
systemHealth: "System Health",
|
||
cpuUsage: "CPU Usage",
|
||
memoryUsage: "Memory Usage",
|
||
storageUsage: "Storage Usage",
|
||
apiRequests: "API Requests/min",
|
||
recentTenants: "Recent Tenants",
|
||
tenantName: "Tenant Name",
|
||
status: "Status",
|
||
plan: "Plan",
|
||
usage: "Usage",
|
||
actions: "Actions",
|
||
search: "Search tenants...",
|
||
searchChannels: "Search channels...",
|
||
active: "Active",
|
||
enterprise: "Enterprise",
|
||
professional: "Professional",
|
||
starter: "Starter",
|
||
viewDetails: "View Details",
|
||
suspend: "Suspend",
|
||
systemMetrics: "System Metrics",
|
||
healthy: "Healthy",
|
||
addChannel: "Add Channel",
|
||
channelName: "Channel Name",
|
||
channelManagement: "Channel Management",
|
||
channelDescription: "Manage distribution channels and their tenant portfolios",
|
||
tenantCount: "Tenants",
|
||
commission: "Commission Rate",
|
||
contactPerson: "Contact Person",
|
||
edit: "Edit",
|
||
delete: "Delete",
|
||
createChannel: "Create New Channel",
|
||
cancel: "Cancel",
|
||
create: "Create",
|
||
channelNamePlaceholder: "Enter channel name",
|
||
contactEmail: "Contact Email",
|
||
contactPhone: "Contact Phone",
|
||
commissionRate: "Commission Rate (%)",
|
||
assignSubscriptionLevel: "Assign Subscription Level",
|
||
selectedTenantPlaceholder: "Select a tenant",
|
||
agentHealthMonitoring: "Agent Health Monitoring",
|
||
agentHealthDesc: "Monitor performance and health status of all platform agents",
|
||
agentStatus: "Status",
|
||
cpuUsageLabel: "CPU Usage",
|
||
memoryUsageLabel: "Memory Usage",
|
||
responseTime: "Response Time",
|
||
requestCount: "Request Count",
|
||
errorRate: "Error Rate",
|
||
uptime: "Uptime",
|
||
lastActive: "Last Active",
|
||
ms: "ms",
|
||
requests: "requests",
|
||
warning: "Warning",
|
||
critical: "Critical",
|
||
// Added translations for role management
|
||
userRoleSettings: "User Role Settings",
|
||
billingAdministrator: "Billing Administrator",
|
||
responsibleForBilling: "Responsible for billing, invoices, and financial management",
|
||
operationsAdministrator: "Operations Administrator",
|
||
responsibleForChannels: "Responsible for channels, resources, and monitoring",
|
||
superAdministrator: "Super Administrator",
|
||
fullAccess: "Full access to all platform features",
|
||
permissions: "permissions",
|
||
tabAccessPermissions: "Tab Access Permissions",
|
||
selectTabs: "Select which tabs this role can access",
|
||
providerBackend: "Provider Backend",
|
||
channelBackend: "Channel Backend",
|
||
savePermissions: "Save Permissions",
|
||
// Added translations for add admin dialog
|
||
addAdministrator: "Add Administrator",
|
||
administratorName: "Administrator Name",
|
||
enterAdministratorName: "Enter administrator name",
|
||
email: "Email",
|
||
enterEmailAddress: "Enter email address",
|
||
password: "Password",
|
||
setLoginPassword: "Set login password",
|
||
role: "Role",
|
||
billingAdmin: "Billing Administrator",
|
||
operationsAdmin: "Operations Administrator",
|
||
superAdmin: "Super Administrator",
|
||
createAdministrator: "Create Administrator",
|
||
// ADDED translations for billing
|
||
billingManagement: "Billing Management",
|
||
dateQuery: "Date Query",
|
||
filter: "Filter",
|
||
export: "Export",
|
||
channelBillingDetails: "Channel Billing Details",
|
||
channelName: "Channel Name",
|
||
callCount: "Call Count",
|
||
totalEU: "Total EU",
|
||
channelTotal: "Channel Total",
|
||
billingPeriod: "Billing Period",
|
||
tenantBillingDetails: "Tenant Billing Details",
|
||
tenantName: "Tenant Name",
|
||
channel: "Channel",
|
||
userTotal: "User Total",
|
||
averageSpending: "Average Spending",
|
||
callRecordsDetail: "Call Records Detail",
|
||
callID: "Call ID",
|
||
tenant: "Tenant",
|
||
callTime: "Call Time",
|
||
durationSeconds: "Duration(s)",
|
||
singleCallCost: "Single Call Cost",
|
||
timestamp: "Timestamp",
|
||
EUCalculationRule: "EU Calculation: 1 EU = 10 seconds call time",
|
||
// ADDED translations for filter dialog
|
||
filterOptions: "Filter Options",
|
||
customerName: "Customer Name",
|
||
minCalls: "Min Calls",
|
||
maxCalls: "Max Calls",
|
||
applyFilter: "Apply Filter",
|
||
// ADDED translations for date dialog
|
||
dateTimeRange: "Date & Time Range",
|
||
startDate: "Start Date & Time (YYYY-MM-DD HH:MM)",
|
||
endDate: "End Date & Time (YYYY-MM-DD HH:MM)",
|
||
query: "Query",
|
||
// ADDED translations for export dialog
|
||
exportFormat: "Export Format",
|
||
selectExportFormat: "Select export file format",
|
||
},
|
||
zh: {
|
||
title: "超级管理员控制台",
|
||
subtitle: "平台控制中心",
|
||
overview: "概览",
|
||
channels: "渠道管理",
|
||
resources: "资源管理",
|
||
monitoring: "监控",
|
||
billing: "计费",
|
||
settings: "设置",
|
||
goodsProviders: "货源供应商", // renamed from separate model/data providers
|
||
logout: "退出",
|
||
totalTenants: "总租户数",
|
||
totalChannels: "总渠道数",
|
||
activeTenants: "活跃租户",
|
||
totalRevenue: "总收入",
|
||
systemHealth: "系统健康",
|
||
cpuUsage: "CPU 使用率",
|
||
memoryUsage: "内存使用率",
|
||
storageUsage: "存储使用率",
|
||
apiRequests: "API 请求/分钟",
|
||
recentTenants: "最近租户",
|
||
tenantName: "租户名称",
|
||
status: "状态",
|
||
plan: "方案",
|
||
usage: "使用量",
|
||
actions: "操作",
|
||
search: "搜索租户...",
|
||
searchChannels: "搜索渠道...",
|
||
active: "活跃",
|
||
enterprise: "企业版",
|
||
professional: "专业版",
|
||
starter: "入门版",
|
||
viewDetails: "查看详情",
|
||
suspend: "暂停",
|
||
systemMetrics: "系统指标",
|
||
healthy: "健康",
|
||
addChannel: "添加渠道",
|
||
channelName: "渠道名称",
|
||
channelManagement: "渠道管理",
|
||
channelDescription: "管理分销渠道及其租户组合",
|
||
tenantCount: "租户数",
|
||
commission: "佣金比例",
|
||
contactPerson: "联系人",
|
||
edit: "编辑",
|
||
delete: "删除",
|
||
createChannel: "创建新渠道",
|
||
cancel: "取消",
|
||
create: "创建",
|
||
channelNamePlaceholder: "输入渠道名称",
|
||
contactEmail: "联系邮箱",
|
||
contactPhone: "联系电话",
|
||
commissionRate: "佣金比例 (%)",
|
||
assignSubscriptionLevel: "分配订阅级别",
|
||
selectedTenantPlaceholder: "选择一个租户",
|
||
agentHealthMonitoring: "Agent健康监控",
|
||
agentHealthDesc: "监控所有平台Agent的性能和健康状态",
|
||
agentStatus: "状态",
|
||
cpuUsageLabel: "CPU使用率",
|
||
memoryUsageLabel: "内存使用率",
|
||
responseTime: "响应时间",
|
||
requestCount: "请求数量",
|
||
errorRate: "错误率",
|
||
uptime: "运行时间",
|
||
lastActive: "最后活跃",
|
||
warning: "警告",
|
||
critical: "严重",
|
||
// Added translations for role management
|
||
userRoleSettings: "用户身份设置",
|
||
billingAdministrator: "计费管理员",
|
||
responsibleForBilling: "负责平台计费、账单和财务管理",
|
||
operationsAdministrator: "运营管理员",
|
||
responsibleForChannels: "负责渠道、资源和系统监控管理",
|
||
superAdministrator: "超级管理员",
|
||
fullAccess: "拥有所有权限,管理整个平台",
|
||
permissions: "个权限",
|
||
tabAccessPermissions: "标签页访问权限",
|
||
selectTabs: "选择该角色可以访问的标签页",
|
||
providerBackend: "供应商管理中心后台",
|
||
channelBackend: "渠道管理中心后台",
|
||
savePermissions: "保存权限配置",
|
||
// Added translations for add admin dialog
|
||
addAdministrator: "添加管理员",
|
||
administratorName: "管理员名称",
|
||
enterAdministratorName: "输入管理员名称",
|
||
email: "邮箱",
|
||
enterEmailAddress: "输入邮箱地址",
|
||
password: "密码",
|
||
setLoginPassword: "设置登录密码",
|
||
role: "角色",
|
||
billingAdmin: "计费管理员",
|
||
operationsAdmin: "运营管理员",
|
||
superAdmin: "超级管理员",
|
||
createAdministrator: "创建管理员",
|
||
// ADDED translations for billing
|
||
billingManagement: "计费管理",
|
||
dateQuery: "时间查询",
|
||
filter: "筛选",
|
||
export: "导出",
|
||
channelBillingDetails: "渠道计费详情",
|
||
channelName: "渠道名称",
|
||
callCount: "调用次数",
|
||
totalEU: "总EU",
|
||
channelTotal: "渠道总价",
|
||
billingPeriod: "计费周期",
|
||
tenantBillingDetails: "租户计费详情",
|
||
tenantName: "租户名称",
|
||
channel: "渠道",
|
||
userTotal: "用户总价",
|
||
averageSpending: "平均消费",
|
||
callRecordsDetail: "调用记录明细",
|
||
callID: "调用ID",
|
||
tenant: "租户",
|
||
callTime: "调用时间",
|
||
durationSeconds: "时长(秒)",
|
||
singleCallCost: "单次调用总价",
|
||
timestamp: "时间戳",
|
||
EUCalculationRule: "EU计算:1 EU = 10秒调用时间",
|
||
// ADDED translations for filter dialog
|
||
filterOptions: "筛选选项",
|
||
customerName: "客户名称",
|
||
minCalls: "最小调用次数",
|
||
maxCalls: "最大调用次数",
|
||
applyFilter: "应用筛选",
|
||
// ADDED translations for date dialog
|
||
dateTimeRange: "时间范围",
|
||
startDate: "开始时间 (年-月-日 时:分)",
|
||
endDate: "结束时间 (年-月-日 时:分)",
|
||
query: "查询",
|
||
// ADDED translations for export dialog
|
||
exportFormat: "导出格式",
|
||
selectExportFormat: "选择导出文件格式",
|
||
},
|
||
}
|
||
|
||
const text = translations[language]
|
||
|
||
const agents = [
|
||
{
|
||
id: "weather-agent",
|
||
name: "Weather Query Agent",
|
||
nameCn: "天气查询代理",
|
||
status: "healthy",
|
||
cpuUsage: 45,
|
||
memoryUsage: 62,
|
||
responseTime: 120,
|
||
requestCount: 1520,
|
||
errorRate: 0.2,
|
||
uptime: "99.8%",
|
||
lastActive: "2 mins ago",
|
||
},
|
||
{
|
||
id: "data-agent",
|
||
name: "Data Analysis Agent",
|
||
nameCn: "数据分析代理",
|
||
status: "healthy",
|
||
cpuUsage: 68,
|
||
memoryUsage: 75,
|
||
responseTime: 250,
|
||
requestCount: 890,
|
||
errorRate: 0.5,
|
||
uptime: "99.5%",
|
||
lastActive: "1 min ago",
|
||
},
|
||
{
|
||
id: "doc-agent",
|
||
name: "Document Processing Agent",
|
||
nameCn: "文档处理代理",
|
||
status: "warning",
|
||
cpuUsage: 82,
|
||
memoryUsage: 88,
|
||
responseTime: 450,
|
||
requestCount: 650,
|
||
errorRate: 1.2,
|
||
uptime: "98.9%",
|
||
lastActive: "30 secs ago",
|
||
},
|
||
{
|
||
id: "email-agent",
|
||
name: "Email Management Agent",
|
||
nameCn: "邮件管理代理",
|
||
status: "healthy",
|
||
cpuUsage: 35,
|
||
memoryUsage: 48,
|
||
responseTime: 95,
|
||
requestCount: 2100,
|
||
errorRate: 0.1,
|
||
uptime: "99.9%",
|
||
lastActive: "5 mins ago",
|
||
},
|
||
{
|
||
id: "api-agent",
|
||
name: "API Integration Agent",
|
||
nameCn: "API集成代理",
|
||
status: "critical",
|
||
cpuUsage: 95,
|
||
memoryUsage: 92,
|
||
responseTime: 850,
|
||
requestCount: 320,
|
||
errorRate: 3.5,
|
||
uptime: "96.2%",
|
||
lastActive: "10 mins ago",
|
||
},
|
||
{
|
||
id: "db-agent",
|
||
name: "Database Operations Agent",
|
||
nameCn: "数据库操作代理",
|
||
status: "healthy",
|
||
cpuUsage: 52,
|
||
memoryUsage: 65,
|
||
responseTime: 180,
|
||
requestCount: 1100,
|
||
errorRate: 0.3,
|
||
uptime: "99.7%",
|
||
lastActive: "3 mins ago",
|
||
},
|
||
]
|
||
|
||
useEffect(() => {
|
||
const token = localStorage.getItem("admin_token")
|
||
if (!token) {
|
||
router.push("/admin/login")
|
||
}
|
||
}, [router])
|
||
|
||
const handleLogout = () => {
|
||
localStorage.removeItem("admin_token")
|
||
router.push("/admin/login")
|
||
}
|
||
|
||
const stats = [
|
||
{
|
||
title: text.totalChannels,
|
||
value: "32",
|
||
change: "+4 this month",
|
||
icon: Building2,
|
||
trend: "up",
|
||
},
|
||
{
|
||
title: text.totalTenants,
|
||
value: "247",
|
||
change: "+12.5%",
|
||
icon: Users,
|
||
trend: "up",
|
||
},
|
||
{
|
||
title: text.activeTenants,
|
||
value: "189",
|
||
change: "+8.2%",
|
||
icon: Activity,
|
||
trend: "up",
|
||
},
|
||
{
|
||
title: text.totalRevenue,
|
||
value: "$124.5K",
|
||
change: "+23.1%",
|
||
icon: DollarSign,
|
||
trend: "up",
|
||
},
|
||
]
|
||
|
||
const systemMetrics = [
|
||
{ label: text.cpuUsage, value: 45, max: 100, color: "bg-blue-500" },
|
||
{ label: text.memoryUsage, value: 62, max: 100, color: "bg-green-500" },
|
||
{ label: text.storageUsage, value: 78, max: 100, color: "bg-yellow-500" },
|
||
{ label: text.apiRequests, value: 2847, max: 5000, color: "bg-purple-500" },
|
||
]
|
||
|
||
const channels = [
|
||
{
|
||
id: 1,
|
||
name: "Enterprise Solutions Inc",
|
||
tenantCount: 45,
|
||
revenue: "$45.2K",
|
||
commission: 15,
|
||
status: "active",
|
||
contact: "John Smith",
|
||
email: "john@enterprise-solutions.com",
|
||
},
|
||
{
|
||
id: 2,
|
||
name: "Cloud Partners Asia",
|
||
tenantCount: 38,
|
||
revenue: "$38.9K",
|
||
commission: 12,
|
||
status: "active",
|
||
contact: "Li Wei",
|
||
email: "liwei@cloudpartners.cn",
|
||
},
|
||
{
|
||
id: 3,
|
||
name: "Tech Distribution EU",
|
||
tenantCount: 52,
|
||
revenue: "$52.4K",
|
||
commission: 18,
|
||
status: "active",
|
||
contact: "Maria Garcia",
|
||
email: "maria@techdist.eu",
|
||
},
|
||
{
|
||
id: 4,
|
||
name: "Digital Resellers Network",
|
||
tenantCount: 28,
|
||
revenue: "$28.1K",
|
||
commission: 10,
|
||
status: "active",
|
||
contact: "David Johnson",
|
||
email: "david@digitalresellers.com",
|
||
},
|
||
{
|
||
id: 5,
|
||
name: "Global Systems Partners",
|
||
tenantCount: 34,
|
||
revenue: "$34.7K",
|
||
commission: 14,
|
||
status: "active",
|
||
contact: "Sarah Chen",
|
||
email: "sarah@globalsystems.com",
|
||
},
|
||
{
|
||
id: 6,
|
||
name: "Innovation Hub Americas",
|
||
tenantCount: 22,
|
||
revenue: "$22.3K",
|
||
commission: 11,
|
||
status: "inactive",
|
||
contact: "Michael Brown",
|
||
email: "michael@innovationhub.us",
|
||
},
|
||
]
|
||
|
||
const tenants = [
|
||
{ id: 1, name: "Acme Corp", status: "active", plan: "enterprise", usage: 87, revenue: "$2,450" },
|
||
{ id: 2, name: "TechStart Inc", status: "active", plan: "professional", usage: 64, revenue: "$890" },
|
||
{ id: 3, name: "Global Systems", status: "active", plan: "enterprise", usage: 92, revenue: "$3,200" },
|
||
{ id: 4, name: "Innovation Labs", status: "active", plan: "starter", usage: 34, revenue: "$290" },
|
||
{ id: 5, name: "DataFlow Co", status: "active", plan: "professional", usage: 71, revenue: "$1,100" },
|
||
]
|
||
|
||
// available models and data sources for configuration
|
||
const availableModels = [
|
||
{ id: "gpt-4", name: "GPT-4", provider: "OpenAI" },
|
||
{ id: "gpt-3.5", name: "GPT-3.5 Turbo", provider: "OpenAI" },
|
||
{ id: "claude-3", name: "Claude 3", provider: "Anthropic" },
|
||
{ id: "claude-2", name: "Claude 2", provider: "Anthropic" },
|
||
{ id: "gemini-pro", name: "Gemini Pro", provider: "Google" },
|
||
{ id: "llama-2", name: "Llama 2", provider: "Meta" },
|
||
]
|
||
|
||
const availableDataSources = [
|
||
{ id: "rapidapi", name: "RapidAPI", type: "API Hub" },
|
||
{ id: "apihub", name: "API Hub", type: "Data Platform" },
|
||
{ id: "kaggle", name: "Kaggle Datasets", type: "Data Portal" },
|
||
{ id: "huggingface", name: "Hugging Face", type: "ML Data" },
|
||
]
|
||
|
||
const availableAgents = [
|
||
{ id: "weather-agent", name: "Weather Query Agent", type: "Weather" },
|
||
{ id: "data-analysis-agent", name: "Data Analysis Agent", type: "Analytics" },
|
||
{ id: "doc-processing-agent", name: "Document Processing Agent", type: "Document" },
|
||
{ id: "email-mgmt-agent", name: "Email Management Agent", type: "Email" },
|
||
{ id: "api-integration-agent", name: "API Integration Agent", type: "Integration" },
|
||
{ id: "db-operations-agent", name: "Database Operations Agent", type: "Database" },
|
||
]
|
||
|
||
// handlers for goods configuration
|
||
const handleConfigGoods = (channel: (typeof channels)[0]) => {
|
||
setSelectedChannel(channel)
|
||
// setIsConfigGoodsOpen(true) // REMOVED
|
||
setIsResourceManagementOpen(true) // ADDED
|
||
}
|
||
|
||
// const handleSaveGoodsConfig = () => { // REMOVED
|
||
// // Handle save logic
|
||
// console.log("Saving config for:", selectedChannel?.name)
|
||
// console.log("Selected models:", selectedModels)
|
||
// console.log("Selected data sources:", selectedDataSources)
|
||
// console.log("Monthly quota:", monthlyQuota)
|
||
// console.log("Monthly budget:", monthlyBudget)
|
||
|
||
// setIsConfigGoodsOpen(false)
|
||
// setSelectedModels([])
|
||
// setSelectedDataSources([])
|
||
// setMonthlyQuota("")
|
||
// setMonthlyBudget("")
|
||
// }
|
||
|
||
const handleEditCommission = (channel: (typeof channels)[0]) => {
|
||
setSelectedChannel(channel)
|
||
setCommissionRate(channel.commission.toString())
|
||
setIsCommissionDialogOpen(true)
|
||
}
|
||
|
||
const handleSaveCommission = () => {
|
||
console.log("Saving commission for:", selectedChannel?.name)
|
||
console.log("New commission rate:", commissionRate)
|
||
setIsCommissionDialogOpen(false)
|
||
setCommissionRate("")
|
||
}
|
||
|
||
const handleAddProvider = (type: "model" | "data") => {
|
||
setProviderType(type)
|
||
setProviderForm({
|
||
name: "",
|
||
url: "",
|
||
apiKey: "",
|
||
models: "",
|
||
rpm: "",
|
||
tpm: "",
|
||
})
|
||
setIsAddProviderOpen(true)
|
||
}
|
||
|
||
const handleConfigProvider = (provider: any, type: "model" | "data") => {
|
||
setProviderType(type)
|
||
setSelectedProvider(provider)
|
||
setProviderForm({
|
||
name: provider.name,
|
||
url: provider.url || "",
|
||
apiKey: "••••••••",
|
||
models: provider.models?.join(", ") || "",
|
||
rpm: provider.rpm?.toString() || "",
|
||
tpm: provider.tpm?.toString() || "",
|
||
})
|
||
setIsConfigProviderOpen(true)
|
||
}
|
||
|
||
const handleSaveProvider = () => {
|
||
console.log("Saving provider:", providerForm)
|
||
setIsAddProviderOpen(false)
|
||
setIsConfigProviderOpen(false)
|
||
setProviderForm({
|
||
name: "",
|
||
url: "",
|
||
apiKey: "",
|
||
models: "",
|
||
rpm: "",
|
||
tpm: "",
|
||
})
|
||
}
|
||
|
||
// const handleAgentAllocation = (channel: (typeof channels)[0]) => { // REMOVED
|
||
// setSelectedChannel(channel)
|
||
// setSelectedAgents([])
|
||
// setAgentQuantities({})
|
||
// setIsAgentAllocationOpen(true)
|
||
// }
|
||
|
||
// const handleSaveAgentAllocation = () => { // REMOVED
|
||
// console.log("Saving agent allocation for:", selectedChannel?.name)
|
||
// console.log("Selected agents:", selectedAgents)
|
||
// console.log("Agent quantities:", agentQuantities)
|
||
// setIsAgentAllocationOpen(false)
|
||
// setSelectedAgents([])
|
||
// setAgentQuantities({})
|
||
// }
|
||
|
||
// ADDED: Unified resource management handler
|
||
const handleResourceManagement = (channel: (typeof channels)[0]) => {
|
||
setSelectedChannel(channel)
|
||
setSelectedModels([])
|
||
setSelectedAgents([])
|
||
setAgentQuantities({})
|
||
// setMonthlyQuota("") // REMOVED
|
||
setCreditLimit("")
|
||
setCustomAgentCpu("2")
|
||
setCustomAgentMemory("4")
|
||
setIsResourceManagementOpen(true)
|
||
}
|
||
|
||
// ADDED: Unified resource management save handler
|
||
const handleSaveResourceManagement = () => {
|
||
console.log("Saving resource management for:", selectedChannel?.name)
|
||
console.log("Selected models:", selectedModels)
|
||
console.log("Selected agents:", selectedAgents)
|
||
console.log("Agent quantities:", agentQuantities)
|
||
// console.log("Monthly quota:", monthlyQuota) // REMOVED
|
||
console.log("Credit limit:", creditLimit)
|
||
console.log("Custom Agent CPU:", customAgentCpu)
|
||
console.log("Custom Agent Memory:", customAgentMemory)
|
||
|
||
setIsResourceManagementOpen(false)
|
||
setSelectedModels([])
|
||
setSelectedAgents([])
|
||
setAgentQuantities({})
|
||
// setMonthlyQuota("") // REMOVED
|
||
setCreditLimit("")
|
||
}
|
||
|
||
const subscriptionLevels = [
|
||
{
|
||
id: "free",
|
||
name: language === "zh" ? "免费版" : "Free",
|
||
description: language === "zh" ? "基础功能,有限额度" : "Basic features, limited quota",
|
||
},
|
||
{
|
||
id: "pro",
|
||
name: language === "zh" ? "专业版" : "Pro",
|
||
description: language === "zh" ? "高级功能,标准额度" : "Advanced features, standard quota",
|
||
},
|
||
{
|
||
id: "enterprise",
|
||
name: language === "zh" ? "企业版" : "Enterprise",
|
||
description: language === "zh" ? "完整功能,无限额度" : "Full features, unlimited quota",
|
||
},
|
||
]
|
||
|
||
return (
|
||
<div className="min-h-screen bg-background">
|
||
{/* Header */}
|
||
<header className="sticky top-0 z-50 border-b border-border bg-card/95 backdrop-blur-sm">
|
||
<div className="flex items-center justify-between px-6 py-4">
|
||
<div className="flex items-center gap-4">
|
||
<div className="flex items-center gap-2">
|
||
<Shield className="h-6 w-6 text-primary" />
|
||
<div>
|
||
<h1 className="text-lg font-bold text-foreground">{text.title}</h1>
|
||
<p className="text-xs text-muted-foreground">{text.subtitle}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-3">
|
||
<Select value={language} onValueChange={(value: "en" | "zh") => setLanguage(value)}>
|
||
<SelectTrigger className="w-[120px] bg-background border-border">
|
||
<Globe className="mr-2 h-4 w-4" />
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="en">English</SelectItem>
|
||
<SelectItem value="zh">中文</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
onClick={handleLogout}
|
||
className="text-muted-foreground hover:text-foreground"
|
||
>
|
||
<LogOut className="h-4 w-4 mr-2" />
|
||
{text.logout}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Navigation tabs */}
|
||
<div className="px-6 flex gap-6 border-t border-border overflow-x-auto">
|
||
{[
|
||
{ id: "overview", label: text.overview, icon: BarChart3 },
|
||
{ id: "channels", label: text.channels, icon: Building2 },
|
||
{ id: "resources", label: text.resources, icon: Database },
|
||
{ id: "monitoring", label: text.monitoring, icon: Activity },
|
||
{ id: "billing", label: text.billing, icon: DollarSign },
|
||
{ id: "settings", label: text.settings, icon: Settings },
|
||
].map((tab) => (
|
||
<button
|
||
key={tab.id}
|
||
onClick={() => setActiveTab(tab.id)}
|
||
className={`flex items-center gap-2 px-3 py-3 text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${
|
||
activeTab === tab.id
|
||
? "border-primary text-foreground"
|
||
: "border-transparent text-muted-foreground hover:text-foreground"
|
||
}`}
|
||
>
|
||
<tab.icon className="h-4 w-4" />
|
||
{tab.label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</header>
|
||
|
||
{/* Main content */}
|
||
<main className="p-6 space-y-6">
|
||
{activeTab === "overview" && (
|
||
<>
|
||
{/* Stats grid */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||
{stats.map((stat, index) => (
|
||
<Card key={index} className="p-6 bg-card border-border">
|
||
<div className="flex items-start justify-between">
|
||
<div>
|
||
<p className="text-sm text-muted-foreground mb-1">{stat.title}</p>
|
||
<p className="text-3xl font-bold text-foreground mb-2">{stat.value}</p>
|
||
<div className="flex items-center gap-1">
|
||
<TrendingUp className="h-3 w-3 text-green-500" />
|
||
<span className="text-xs text-green-500">{stat.change}</span>
|
||
</div>
|
||
</div>
|
||
<div className="p-3 rounded-lg bg-primary/10">
|
||
<stat.icon className="h-5 w-5 text-primary" />
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
))}
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||
{/* System Metrics */}
|
||
<Card className="lg:col-span-1 p-6 bg-card border-border">
|
||
<h3 className="text-lg font-semibold text-foreground mb-4">{text.systemMetrics}</h3>
|
||
<div className="space-y-4">
|
||
{systemMetrics.map((metric, index) => (
|
||
<div key={index}>
|
||
<div className="flex justify-between text-sm mb-2">
|
||
<span className="text-muted-foreground">{metric.label}</span>
|
||
<span className="text-foreground font-medium">
|
||
{typeof metric.value === "number" && metric.value < 200 ? `${metric.value}%` : metric.value}
|
||
</span>
|
||
</div>
|
||
<div className="h-2 bg-muted rounded-full overflow-hidden">
|
||
<div
|
||
className={`h-full ${metric.color} transition-all duration-300`}
|
||
style={{ width: `${(metric.value / metric.max) * 100}%` }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
|
||
{/* Recent Tenants */}
|
||
<Card className="lg:col-span-2 p-6 bg-card border-border">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h3 className="text-lg font-semibold text-foreground">{text.recentTenants}</h3>
|
||
<div className="relative">
|
||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||
<Input placeholder={text.search} className="pl-9 w-[240px] bg-background border-border" />
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-3">
|
||
{tenants.map((tenant) => (
|
||
<div
|
||
key={tenant.id}
|
||
className="flex items-center justify-between p-4 rounded-lg bg-background border border-border hover:border-primary/50 transition-colors"
|
||
>
|
||
<div className="flex items-center gap-4 flex-1">
|
||
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
|
||
<Users className="h-5 w-5 text-primary" />
|
||
</div>
|
||
<div className="flex-1">
|
||
<p className="font-medium text-foreground">{tenant.name}</p>
|
||
<p className="text-xs text-muted-foreground">
|
||
{text[tenant.plan as keyof typeof text] || tenant.plan} · {tenant.revenue}/mo
|
||
</p>
|
||
</div>
|
||
<div className="text-right">
|
||
<div className="flex items-center gap-2 mb-1">
|
||
<div className="h-2 w-2 rounded-full bg-green-500" />
|
||
<span className="text-sm text-muted-foreground">{text.active}</span>
|
||
</div>
|
||
<p className="text-xs text-muted-foreground">
|
||
{text.usage}: {tenant.usage}%
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<DropdownMenu>
|
||
<DropdownMenuTrigger asChild>
|
||
<Button variant="ghost" size="sm">
|
||
<MoreVertical className="h-4 w-4" />
|
||
</Button>
|
||
</DropdownMenuTrigger>
|
||
<DropdownMenuContent align="end">
|
||
<DropdownMenuItem>{text.viewDetails}</DropdownMenuItem>
|
||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||
<DropdownMenuSeparator />
|
||
<DropdownMenuItem className="text-destructive">{text.suspend}</DropdownMenuItem>
|
||
</DropdownMenuContent>
|
||
</DropdownMenu>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* System alerts */}
|
||
<Card className="p-6 bg-card border-border border-l-4 border-l-yellow-500">
|
||
<div className="flex items-start gap-4">
|
||
<div className="p-2 rounded-lg bg-yellow-500/10">
|
||
<AlertTriangle className="h-5 w-5 text-yellow-500" />
|
||
</div>
|
||
<div className="flex-1">
|
||
<h4 className="font-semibold text-foreground mb-1">
|
||
{language === "zh" ? "系统警告" : "System Alert"}
|
||
</h4>
|
||
<p className="text-sm text-muted-foreground">
|
||
{language === "zh"
|
||
? "数据库存储使用率已达 78%。建议尽快扩容以避免性能下降。"
|
||
: "Database storage usage has reached 78%. Consider scaling up to avoid performance degradation."}
|
||
</p>
|
||
<div className="flex items-center gap-2 mt-3">
|
||
<Clock className="h-4 w-4 text-muted-foreground" />
|
||
<span className="text-xs text-muted-foreground">
|
||
2 {language === "zh" ? "小时前" : "hours ago"}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<Button variant="outline" size="sm">
|
||
{language === "zh" ? "查看详情" : "View Details"}
|
||
</Button>
|
||
</div>
|
||
</Card>
|
||
</>
|
||
)}
|
||
|
||
{activeTab === "channels" && (
|
||
<div className="space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h2 className="text-2xl font-bold text-foreground">{text.channelManagement}</h2>
|
||
<p className="text-sm text-muted-foreground mt-1">{text.channelDescription}</p>
|
||
</div>
|
||
<Dialog open={isAddChannelOpen} onOpenChange={setIsAddChannelOpen}>
|
||
<DialogTrigger asChild>
|
||
<Button className="bg-primary text-primary-foreground">
|
||
<Plus className="h-4 w-4 mr-2" />
|
||
{text.addChannel}
|
||
</Button>
|
||
</DialogTrigger>
|
||
<DialogContent className="bg-card border-border">
|
||
<DialogHeader>
|
||
<DialogTitle>{text.createChannel}</DialogTitle>
|
||
<DialogDescription>
|
||
{language === "zh"
|
||
? "创建新的分销渠道账户,渠道可以管理自己的租户组合"
|
||
: "Create a new distribution channel account that can manage their own tenant portfolio"}
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
<div className="space-y-4 py-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="channelName">{text.channelName}</Label>
|
||
<Input id="channelName" placeholder={text.channelNamePlaceholder} className="bg-background" />
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="contactPerson">{text.contactPerson}</Label>
|
||
<Input id="contactPerson" placeholder="John Doe" className="bg-background" />
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="contactEmail">{text.contactEmail}</Label>
|
||
<Input
|
||
id="contactEmail"
|
||
type="email"
|
||
placeholder="contact@example.com"
|
||
className="bg-background"
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="contactPhone">{text.contactPhone}</Label>
|
||
<Input id="contactPhone" placeholder="+1 234 567 8900" className="bg-background" />
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="commission">{text.commissionRate}</Label>
|
||
<Input id="commission" type="number" placeholder="15" className="bg-background" />
|
||
</div>
|
||
</div>
|
||
<DialogFooter>
|
||
<Button variant="outline" onClick={() => setIsAddChannelOpen(false)}>
|
||
{text.cancel}
|
||
</Button>
|
||
<Button className="bg-primary text-primary-foreground" onClick={() => setIsAddChannelOpen(false)}>
|
||
{text.create}
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</div>
|
||
|
||
<div className="relative mb-6">
|
||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||
<Input placeholder={text.searchChannels} className="pl-9 bg-card border-border" />
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 gap-4">
|
||
{channels.map((channel) => (
|
||
<Card key={channel.id} className="p-6 bg-card border-border hover:border-primary/50 transition-colors">
|
||
<div className="flex items-start justify-between">
|
||
<div className="flex items-start gap-4 flex-1">
|
||
<div className="w-14 h-14 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0">
|
||
<Building2 className="h-7 w-7 text-primary" />
|
||
</div>
|
||
<div className="flex-1 min-w-0">
|
||
<div className="flex items-center gap-3 mb-2">
|
||
<h3 className="text-lg font-semibold text-foreground">{channel.name}</h3>
|
||
<div
|
||
className={`px-2 py-1 rounded text-xs font-medium ${
|
||
channel.status === "active"
|
||
? "bg-green-500/10 text-green-500"
|
||
: "bg-gray-500/10 text-gray-500"
|
||
}`}
|
||
>
|
||
{language === "zh" ? (channel.status === "active" ? "活跃" : "停用") : channel.status}
|
||
</div>
|
||
</div>
|
||
<div className="space-y-1 text-sm text-muted-foreground">
|
||
<p>
|
||
{text.contactPerson}: {channel.contact}
|
||
</p>
|
||
<p>{channel.email}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-start gap-8 ml-6">
|
||
<div className="text-center">
|
||
<p className="text-2xl font-bold text-foreground">{channel.tenantCount}</p>
|
||
<p className="text-xs text-muted-foreground mt-1">{text.tenantCount}</p>
|
||
</div>
|
||
<div className="text-center">
|
||
<p className="text-2xl font-bold text-foreground">{channel.revenue}</p>
|
||
<p className="text-xs text-muted-foreground mt-1">{language === "zh" ? "月收入" : "Monthly"}</p>
|
||
</div>
|
||
<div className="text-center">
|
||
<p className="text-2xl font-bold text-foreground">{channel.commission}%</p>
|
||
<p className="text-xs text-muted-foreground mt-1">{text.commission}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<DropdownMenu>
|
||
<DropdownMenuTrigger asChild>
|
||
<Button variant="ghost" size="sm" className="ml-4">
|
||
<MoreVertical className="h-4 w-4" />
|
||
</Button>
|
||
</DropdownMenuTrigger>
|
||
<DropdownMenuContent align="end" className="bg-card border-border">
|
||
<DropdownMenuItem>
|
||
<Eye className="h-4 w-4 mr-2" />
|
||
{text.viewDetails}
|
||
</DropdownMenuItem>
|
||
<DropdownMenuItem>
|
||
<Edit className="h-4 w-4 mr-2" />
|
||
{text.edit}
|
||
</DropdownMenuItem>
|
||
<DropdownMenuItem>
|
||
<Users className="h-4 w-4 mr-2" />
|
||
{language === "zh" ? "查看租户" : "View Tenants"}
|
||
</DropdownMenuItem>
|
||
<DropdownMenuItem onClick={() => handleEditCommission(channel)}>
|
||
<DollarSign className="h-4 w-4 mr-2" />
|
||
{language === "zh" ? "修改佣金" : "Edit Commission"}
|
||
</DropdownMenuItem>
|
||
<DropdownMenuSeparator />
|
||
<DropdownMenuItem className="text-destructive">
|
||
<Trash2 className="h-4 w-4 mr-2" />
|
||
{language === "zh" ? "删除渠道" : "Delete Channel"}
|
||
</DropdownMenuItem>
|
||
{/* <DropdownMenuItem onClick={() => handleConfigGoods(channel)}> // REMOVED */}
|
||
{/* <Package className="h-4 w-4 mr-2" /> */}
|
||
{/* {language === "zh" ? "货源配置" : "Goods Configuration"} */}
|
||
{/* </DropdownMenuItem> */}
|
||
{/* <DropdownMenuItem onClick={() => handleAgentAllocation(channel)}> // REMOVED */}
|
||
{/* <Bot className="h-4 w-4 mr-2" /> */}
|
||
{/* {language === "zh" ? "Agent分配" : "Agent Allocation"} */}
|
||
{/* </DropdownMenuItem> */}
|
||
{/* CHANGED: Combine goods configuration and agent allocation into resource management */}
|
||
<DropdownMenuItem onClick={() => handleResourceManagement(channel)}>
|
||
<Settings className="h-4 w-4 mr-2" />
|
||
{language === "zh" ? "资源管理" : "Resource Management"}
|
||
</DropdownMenuItem>
|
||
</DropdownMenuContent>
|
||
</DropdownMenu>
|
||
</div>
|
||
</Card>
|
||
))}
|
||
</div>
|
||
|
||
{/* Commission Edit Dialog */}
|
||
<Dialog open={isCommissionDialogOpen} onOpenChange={setIsCommissionDialogOpen}>
|
||
<DialogContent className="bg-card border-border">
|
||
<DialogHeader>
|
||
<DialogTitle>{language === "zh" ? "修改佣金比例" : "Edit Commission Rate"}</DialogTitle>
|
||
<DialogDescription>
|
||
{language === "zh"
|
||
? `为渠道 "${selectedChannel?.name}" 设置新的佣金比例`
|
||
: `Set a new commission rate for channel "${selectedChannel?.name}"`}
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
<div className="space-y-4 py-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="commission-rate">
|
||
{language === "zh" ? "佣金比例 (%)" : "Commission Rate (%)"}
|
||
</Label>
|
||
<Input
|
||
id="commission-rate"
|
||
type="number"
|
||
min="0"
|
||
max="100"
|
||
step="0.1"
|
||
placeholder={language === "zh" ? "例: 15" : "e.g., 15"}
|
||
value={commissionRate}
|
||
onChange={(e) => setCommissionRate(e.target.value)}
|
||
className="bg-background border-border"
|
||
/>
|
||
<p className="text-xs text-muted-foreground">
|
||
{language === "zh"
|
||
? "当前佣金比例: " + selectedChannel?.commission + "%"
|
||
: "Current rate: " + selectedChannel?.commission + "%"}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<DialogFooter>
|
||
<Button variant="outline" onClick={() => setIsCommissionDialogOpen(false)}>
|
||
{text.cancel}
|
||
</Button>
|
||
<Button className="bg-primary text-primary-foreground" onClick={handleSaveCommission}>
|
||
{language === "zh" ? "保存" : "Save"}
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
{/* CHANGED: Combined Goods Configuration and Agent Allocation into a single Resource Management Dialog */}
|
||
{/* <Dialog open={isConfigGoodsOpen} onOpenChange={setIsConfigGoodsOpen}> // REMOVED */}
|
||
<Dialog open={isResourceManagementOpen} onOpenChange={setIsResourceManagementOpen}>
|
||
<DialogContent className="bg-card border-border max-w-3xl max-h-[90vh] overflow-y-auto">
|
||
<DialogHeader>
|
||
<DialogTitle>{language === "zh" ? "资源管理" : "Resource Management"}</DialogTitle>
|
||
<DialogDescription>
|
||
{language === "zh"
|
||
? `为渠道 "${selectedChannel?.name}" 配置可用的模型、Agent和配额`
|
||
: `Configure available models, agents and quota for channel "${selectedChannel?.name}"`}
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
|
||
<div className="space-y-6 py-4">
|
||
{/* Models Selection */}
|
||
<div className="space-y-3">
|
||
<Label className="text-base font-semibold">
|
||
{language === "zh" ? "模型供应商" : "Model Providers"}
|
||
</Label>
|
||
<div className="grid grid-cols-1 gap-2">
|
||
{availableModels.map((model) => (
|
||
<div key={model.id} className="flex items-center space-x-2">
|
||
<input
|
||
type="checkbox"
|
||
id={`model-${model.id}`}
|
||
checked={selectedModels.includes(model.id)}
|
||
onChange={(e) => {
|
||
if (e.target.checked) {
|
||
setSelectedModels([...selectedModels, model.id])
|
||
} else {
|
||
setSelectedModels(selectedModels.filter((m) => m !== model.id))
|
||
}
|
||
}}
|
||
className="w-4 h-4 rounded border-border"
|
||
/>
|
||
<label htmlFor={`model-${model.id}`} className="text-sm font-medium cursor-pointer flex-1">
|
||
{model.name}
|
||
<span className="text-xs text-muted-foreground ml-2">({model.provider})</span>
|
||
</label>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Agent Selection with Quantities */}
|
||
<div className="space-y-3">
|
||
<Label className="text-base font-semibold">
|
||
{language === "zh" ? "Agent分配" : "Agent Allocation"}
|
||
</Label>
|
||
<p className="text-sm text-muted-foreground">
|
||
{language === "zh"
|
||
? "选择该渠道可以分配给租户的Agent并设置可用数量"
|
||
: "Select agents that this channel can assign to tenants and set available quantities"}
|
||
</p>
|
||
<div className="grid grid-cols-1 gap-3 mt-4">
|
||
{availableAgents.map((agent) => (
|
||
<div
|
||
key={agent.id}
|
||
className={`border rounded-lg p-4 transition-all ${
|
||
selectedAgents.includes(agent.id) ? "border-primary bg-primary/5" : "border-border"
|
||
}`}
|
||
>
|
||
<div className="flex items-center justify-between gap-4">
|
||
<div
|
||
className="flex items-center gap-3 flex-1 cursor-pointer"
|
||
onClick={() => {
|
||
if (selectedAgents.includes(agent.id)) {
|
||
setSelectedAgents(selectedAgents.filter((a) => a !== agent.id))
|
||
const newQuantities = { ...agentQuantities }
|
||
delete newQuantities[agent.id]
|
||
setAgentQuantities(newQuantities)
|
||
} else {
|
||
setSelectedAgents([...selectedAgents, agent.id])
|
||
setAgentQuantities({ ...agentQuantities, [agent.id]: 1 })
|
||
}
|
||
}}
|
||
>
|
||
<div
|
||
className={`w-10 h-10 rounded-lg flex items-center justify-center ${
|
||
selectedAgents.includes(agent.id) ? "bg-primary/20" : "bg-muted"
|
||
}`}
|
||
>
|
||
<Bot
|
||
className={`h-5 w-5 ${selectedAgents.includes(agent.id) ? "text-primary" : "text-muted-foreground"}`}
|
||
/>
|
||
</div>
|
||
<div>
|
||
<p className="font-medium text-foreground">{agent.name}</p>
|
||
<p className="text-xs text-muted-foreground">{agent.type}</p>
|
||
</div>
|
||
</div>
|
||
|
||
{selectedAgents.includes(agent.id) && (
|
||
<div className="flex items-center gap-2">
|
||
<Label className="text-sm text-muted-foreground whitespace-nowrap">
|
||
{language === "zh" ? "数量:" : "Quantity:"}
|
||
</Label>
|
||
<Input
|
||
type="number"
|
||
min="1"
|
||
max="100"
|
||
value={agentQuantities[agent.id] || 1}
|
||
onChange={(e) => {
|
||
const value = Math.max(1, Math.min(100, Number.parseInt(e.target.value) || 1))
|
||
setAgentQuantities({ ...agentQuantities, [agent.id]: value })
|
||
}}
|
||
className="w-20 h-9"
|
||
onClick={(e) => e.stopPropagation()}
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
<input
|
||
type="checkbox"
|
||
checked={selectedAgents.includes(agent.id)}
|
||
onChange={() => {}}
|
||
className="w-5 h-5 rounded border-border"
|
||
/>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-3 border-t border-border pt-4">
|
||
<Label className="text-base font-semibold">
|
||
{language === "zh" ? "自定义Agent资源配置" : "Custom Agent Resource Configuration"}
|
||
</Label>
|
||
<p className="text-sm text-muted-foreground">
|
||
{language === "zh"
|
||
? "为该渠道的租户创建的自定义Agent配置默认CPU和内存资源"
|
||
: "Configure default CPU and memory resources for custom agents created by this channel's tenants"}
|
||
</p>
|
||
<div className="grid grid-cols-2 gap-4 mt-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="custom-cpu">{language === "zh" ? "CPU核数" : "CPU Cores"}</Label>
|
||
<Input
|
||
id="custom-cpu"
|
||
type="number"
|
||
min="0.5"
|
||
max="16"
|
||
step="0.5"
|
||
value={customAgentCpu}
|
||
onChange={(e) => setCustomAgentCpu(e.target.value)}
|
||
className="bg-background border-border"
|
||
/>
|
||
<p className="text-xs text-muted-foreground">
|
||
{language === "zh" ? "推荐范围: 0.5-16核" : "Recommended: 0.5-16 cores"}
|
||
</p>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="custom-memory">
|
||
{language === "zh" ? "内存大小(GB)" : "Memory Size (GB)"}
|
||
</Label>
|
||
<Input
|
||
id="custom-memory"
|
||
type="number"
|
||
min="0.5"
|
||
max="64"
|
||
step="0.5"
|
||
value={customAgentMemory}
|
||
onChange={(e) => setCustomAgentMemory(e.target.value)}
|
||
className="bg-background border-border"
|
||
/>
|
||
<p className="text-xs text-muted-foreground">
|
||
{language === "zh" ? "推荐范围: 0.5-64GB" : "Recommended: 0.5-64GB"}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div className="bg-blue-500/10 border border-blue-500/20 rounded-lg p-3 mt-2">
|
||
<p className="text-xs text-muted-foreground">
|
||
{language === "zh"
|
||
? "此配置将应用于该渠道的租户创建的所有自定义Agent,不影响平台原生Agent。"
|
||
: "This configuration will apply to all custom agents created by this channel's tenants, not affecting platform native agents."}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* REMOVED monthly quota field */}
|
||
<div className="space-y-2">
|
||
<Label htmlFor="credit-limit">{language === "zh" ? "渠道授信额度" : "Channel Credit Limit"}</Label>
|
||
<Input
|
||
id="credit-limit"
|
||
type="number"
|
||
placeholder={language === "zh" ? "例: 10000" : "e.g., 10000"}
|
||
value={creditLimit}
|
||
onChange={(e) => setCreditLimit(e.target.value)}
|
||
className="bg-background border-border"
|
||
/>
|
||
<p className="text-xs text-muted-foreground">
|
||
{language === "zh" ? "渠道可用授信上限 (USD)" : "Channel credit limit (USD)"}
|
||
</p>
|
||
</div>
|
||
|
||
<div className="bg-primary/5 border border-primary/20 rounded-lg p-4">
|
||
<p className="text-sm font-semibold text-foreground mb-2">
|
||
{language === "zh" ? "资源配置摘要" : "Resource Configuration Summary"}
|
||
</p>
|
||
<div className="space-y-1 text-xs text-muted-foreground">
|
||
<p>
|
||
{language === "zh" ? "已选择模型" : "Models selected"}: {selectedModels.length}
|
||
</p>
|
||
<p>
|
||
{language === "zh" ? "已选择Agent" : "Agents selected"}: {selectedAgents.length}
|
||
</p>
|
||
{selectedAgents.length > 0 && (
|
||
<p>
|
||
{language === "zh" ? "总Agent数量: " : "Total agents: "}
|
||
{Object.values(agentQuantities).reduce((sum, qty) => sum + qty, 0)}
|
||
</p>
|
||
)}
|
||
<p className="pt-2 border-t border-border/50">
|
||
{language === "zh" ? "自定义Agent CPU: " : "Custom Agent CPU: "}
|
||
{customAgentCpu} {language === "zh" ? "核" : "cores"}
|
||
</p>
|
||
<p>
|
||
{language === "zh" ? "自定义Agent 内存: " : "Custom Agent Memory: "}
|
||
{customAgentMemory} GB
|
||
</p>
|
||
{/* REMOVED monthly quota from summary */}
|
||
<p>
|
||
{language === "zh" ? "渠道授信额度" : "Channel Credit Limit"}: ${creditLimit || "0"}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<DialogFooter>
|
||
<Button variant="outline" onClick={() => setIsResourceManagementOpen(false)}>
|
||
{text.cancel}
|
||
</Button>
|
||
<Button className="bg-primary text-primary-foreground" onClick={handleSaveResourceManagement}>
|
||
{language === "zh" ? "保存配置" : "Save Configuration"}
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<div className="mt-8">
|
||
<h3 className="text-lg font-semibold mb-4">
|
||
{language === "zh" ? "渠道申请审批" : "Channel Application Approvals"}
|
||
</h3>
|
||
<Card className="bg-card border-border">
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full">
|
||
<thead className="border-b border-border">
|
||
<tr>
|
||
<th className="text-left p-4 text-sm font-medium text-muted-foreground">
|
||
{language === "zh" ? "渠道" : "Channel"}
|
||
</th>
|
||
<th className="text-left p-4 text-sm font-medium text-muted-foreground">
|
||
{language === "zh" ? "供应商" : "Provider"}
|
||
</th>
|
||
<th className="text-left p-4 text-sm font-medium text-muted-foreground">
|
||
{language === "zh" ? "期望模型" : "Models"}
|
||
</th>
|
||
<th className="text-left p-4 text-sm font-medium text-muted-foreground">
|
||
{language === "zh" ? "提交时间" : "Submitted"}
|
||
</th>
|
||
<th className="text-left p-4 text-sm font-medium text-muted-foreground">
|
||
{language === "zh" ? "状态" : "Status"}
|
||
</th>
|
||
<th className="text-right p-4 text-sm font-medium text-muted-foreground">
|
||
{language === "zh" ? "操作" : "Actions"}
|
||
</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{providerApprovals.map((approval) => (
|
||
<tr key={approval.id} className="border-b border-border last:border-0 hover:bg-muted/50">
|
||
<td className="p-4 text-sm text-foreground font-medium">{approval.channelName}</td>
|
||
<td className="p-4 text-sm text-foreground">{approval.providerName}</td>
|
||
<td className="p-4 text-sm text-muted-foreground">{approval.expectedModels}</td>
|
||
<td className="p-4 text-sm text-muted-foreground">{approval.submittedAt}</td>
|
||
<td className="p-4">
|
||
<Badge
|
||
variant="secondary"
|
||
className="bg-yellow-500/10 text-yellow-500 border-yellow-500/20"
|
||
>
|
||
{language === "zh" ? "待审批" : "Pending"}
|
||
</Badge>
|
||
</td>
|
||
<td className="p-4 text-right">
|
||
<Button
|
||
size="sm"
|
||
variant="outline"
|
||
onClick={() => {
|
||
setSelectedApproval(approval)
|
||
setIsApprovalDialogOpen(true)
|
||
}}
|
||
>
|
||
{language === "zh" ? "审批" : "Review"}
|
||
</Button>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* Agent申请审批 */}
|
||
<div className="space-y-4">
|
||
<h3 className="text-lg font-semibold text-foreground">
|
||
{language === "zh" ? "Agent申请审批" : "Agent Application Approvals"}
|
||
</h3>
|
||
<Card className="bg-card border-border">
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full">
|
||
<thead className="border-b border-border">
|
||
<tr>
|
||
<th className="text-left p-4 text-sm font-medium text-muted-foreground">
|
||
{language === "zh" ? "渠道" : "Channel"}
|
||
</th>
|
||
<th className="text-left p-4 text-sm font-medium text-muted-foreground">
|
||
{language === "zh" ? "Agent类型" : "Agent Type"}
|
||
</th>
|
||
<th className="text-left p-4 text-sm font-medium text-muted-foreground">
|
||
{language === "zh" ? "申请数量" : "Quantity"}
|
||
</th>
|
||
<th className="text-left p-4 text-sm font-medium text-muted-foreground">
|
||
{language === "zh" ? "提交时间" : "Submitted"}
|
||
</th>
|
||
<th className="text-left p-4 text-sm font-medium text-muted-foreground">
|
||
{language === "zh" ? "状态" : "Status"}
|
||
</th>
|
||
<th className="text-right p-4 text-sm font-medium text-muted-foreground">
|
||
{language === "zh" ? "操作" : "Actions"}
|
||
</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{agentApprovals.map((approval) => (
|
||
<tr key={approval.id} className="border-b border-border last:border-0 hover:bg-muted/50">
|
||
<td className="p-4 text-sm text-foreground font-medium">{approval.channelName}</td>
|
||
<td className="p-4 text-sm text-foreground">{approval.agentType}</td>
|
||
<td className="p-4 text-sm text-muted-foreground">{approval.requestedQuantity}</td>
|
||
<td className="p-4 text-sm text-muted-foreground">{approval.submittedAt}</td>
|
||
<td className="p-4">
|
||
<Badge
|
||
variant="secondary"
|
||
className="bg-yellow-500/10 text-yellow-500 border-yellow-500/20"
|
||
>
|
||
{language === "zh" ? "待审批" : "Pending"}
|
||
</Badge>
|
||
</td>
|
||
<td className="p-4 text-right">
|
||
<Button
|
||
size="sm"
|
||
variant="outline"
|
||
onClick={() => {
|
||
setSelectedAgentApproval(approval)
|
||
setIsAgentApprovalDialogOpen(true)
|
||
}}
|
||
>
|
||
{language === "zh" ? "审批" : "Review"}
|
||
</Button>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
|
||
<Dialog open={isAgentApprovalDialogOpen} onOpenChange={setIsAgentApprovalDialogOpen}>
|
||
<DialogContent className="bg-card border-border max-w-2xl">
|
||
<DialogHeader>
|
||
<DialogTitle>{language === "zh" ? "审批Agent申请" : "Review Agent Application"}</DialogTitle>
|
||
<DialogDescription>
|
||
{language === "zh"
|
||
? `审批渠道 "${selectedAgentApproval?.channelName}" 的Agent申请`
|
||
: `Review agent application from channel "${selectedAgentApproval?.channelName}"`}
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
<div className="space-y-4 py-4">
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<label className="text-sm font-medium text-muted-foreground">
|
||
{language === "zh" ? "申请渠道" : "Channel"}
|
||
</label>
|
||
<p className="text-foreground mt-1">{selectedAgentApproval?.channelName}</p>
|
||
</div>
|
||
<div>
|
||
<label className="text-sm font-medium text-muted-foreground">
|
||
{language === "zh" ? "提交时间" : "Submitted At"}
|
||
</label>
|
||
<p className="text-foreground mt-1">{selectedAgentApproval?.submittedAt}</p>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label className="text-sm font-medium text-muted-foreground">
|
||
{language === "zh" ? "Agent类型" : "Agent Type"}
|
||
</label>
|
||
<p className="text-foreground mt-1">{selectedAgentApproval?.agentType}</p>
|
||
</div>
|
||
<div>
|
||
<label className="text-sm font-medium text-muted-foreground">
|
||
{language === "zh" ? "申请数量" : "Requested Quantity"}
|
||
</label>
|
||
<p className="text-foreground mt-1">{selectedAgentApproval?.requestedQuantity}</p>
|
||
</div>
|
||
<div>
|
||
<label className="text-sm font-medium text-muted-foreground">
|
||
{language === "zh" ? "申请理由" : "Reason"}
|
||
</label>
|
||
<p className="text-foreground mt-1">{selectedAgentApproval?.reason}</p>
|
||
</div>
|
||
</div>
|
||
<DialogFooter>
|
||
<Button
|
||
variant="outline"
|
||
className="border-destructive text-destructive hover:bg-destructive/10 bg-transparent"
|
||
onClick={() => {
|
||
setAgentApprovals(agentApprovals.filter((a) => a.id !== selectedAgentApproval?.id))
|
||
setIsAgentApprovalDialogOpen(false)
|
||
}}
|
||
>
|
||
{language === "zh" ? "拒绝" : "Reject"}
|
||
</Button>
|
||
<Button
|
||
className="bg-primary text-primary-foreground"
|
||
onClick={() => {
|
||
setAgentApprovals(agentApprovals.filter((a) => a.id !== selectedAgentApproval?.id))
|
||
setIsAgentApprovalDialogOpen(false)
|
||
}}
|
||
>
|
||
{language === "zh" ? "批准" : "Approve"}
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</div>
|
||
)}
|
||
|
||
{activeTab === "resources" && (
|
||
<div className="space-y-6">
|
||
<div>
|
||
<div className="flex items-center justify-between mb-4">
|
||
<div>
|
||
<h2 className="text-2xl font-bold text-foreground">
|
||
{language === "zh" ? "Agent计算资源分配" : "Agent Compute Resource Allocation"}
|
||
</h2>
|
||
<p className="text-sm text-muted-foreground mt-1">
|
||
{language === "zh"
|
||
? "为每个Agent单独配置CPU和内存资源"
|
||
: "Configure CPU and memory resources for each agent individually"}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Agent Resource Cards */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-8">
|
||
{[
|
||
{
|
||
id: "agent-001",
|
||
name: "Weather Query Agent",
|
||
status: "active",
|
||
cpu: 2,
|
||
memory: 4,
|
||
usage: { cpu: 45, memory: 60 },
|
||
},
|
||
{
|
||
id: "agent-002",
|
||
name: "Data Analysis Agent",
|
||
status: "active",
|
||
cpu: 4,
|
||
memory: 8,
|
||
usage: { cpu: 78, memory: 85 },
|
||
},
|
||
{
|
||
id: "agent-003",
|
||
name: "Document Processing Agent",
|
||
status: "active",
|
||
cpu: 2,
|
||
memory: 4,
|
||
usage: { cpu: 32, memory: 48 },
|
||
},
|
||
{
|
||
id: "agent-004",
|
||
name: "Email Management Agent",
|
||
status: "idle",
|
||
cpu: 1,
|
||
memory: 2,
|
||
usage: { cpu: 5, memory: 15 },
|
||
},
|
||
{
|
||
id: "agent-005",
|
||
name: "API Integration Agent",
|
||
status: "active",
|
||
cpu: 3,
|
||
memory: 6,
|
||
usage: { cpu: 65, memory: 72 },
|
||
},
|
||
{
|
||
id: "agent-006",
|
||
name: "Database Operations Agent",
|
||
status: "active",
|
||
cpu: 4,
|
||
memory: 8,
|
||
usage: { cpu: 82, memory: 90 },
|
||
},
|
||
].map((agent) => (
|
||
<Card
|
||
key={agent.id}
|
||
className="p-4 bg-card border-border cursor-pointer hover:border-primary transition-colors"
|
||
onClick={() => {
|
||
setSelectedAgent(agent)
|
||
setComputeConfigOpen(true)
|
||
}}
|
||
>
|
||
<div className="flex items-start justify-between mb-3">
|
||
<div className="flex-1">
|
||
<h3 className="font-semibold text-foreground">{agent.name}</h3>
|
||
<p className="text-xs text-muted-foreground">{agent.id}</p>
|
||
</div>
|
||
<div
|
||
className={`px-2 py-1 rounded text-xs ${
|
||
agent.status === "active" ? "bg-green-500/20 text-green-500" : "bg-gray-500/20 text-gray-500"
|
||
}`}
|
||
>
|
||
{agent.status === "active"
|
||
? language === "zh"
|
||
? "活跃"
|
||
: "Active"
|
||
: language === "zh"
|
||
? "空闲"
|
||
: "Idle"}
|
||
</div>
|
||
</div>
|
||
<div className="space-y-3">
|
||
<div className="flex items-center justify-between text-sm">
|
||
<div className="flex items-center gap-2">
|
||
<Cpu className="h-4 w-4 text-blue-500" />
|
||
<span className="text-muted-foreground">CPU:</span>
|
||
</div>
|
||
<span className="font-medium text-foreground">
|
||
{agent.cpu} {language === "zh" ? "核" : "Cores"}
|
||
</span>
|
||
</div>
|
||
<div className="flex items-center justify-between text-sm">
|
||
<div className="flex items-center gap-2">
|
||
<Server className="h-4 w-4 text-green-500" />
|
||
<span className="text-muted-foreground">{language === "zh" ? "内存" : "Memory"}:</span>
|
||
</div>
|
||
<span className="font-medium text-foreground">{agent.memory} GB</span>
|
||
</div>
|
||
<div className="pt-2 border-t border-border">
|
||
<div className="space-y-2">
|
||
<div className="flex items-center justify-between text-xs">
|
||
<span className="text-muted-foreground">CPU {language === "zh" ? "使用率" : "Usage"}</span>
|
||
<span className="text-foreground">{agent.usage.cpu}%</span>
|
||
</div>
|
||
<div className="w-full bg-muted rounded-full h-1.5">
|
||
<div className="bg-blue-500 h-1.5 rounded-full" style={{ width: `${agent.usage.cpu}%` }} />
|
||
</div>
|
||
<div className="flex items-center justify-between text-xs">
|
||
<span className="text-muted-foreground">
|
||
{language === "zh" ? "内存使用率" : "Memory Usage"}
|
||
</span>
|
||
<span className="text-foreground">{agent.usage.memory}%</span>
|
||
</div>
|
||
<div className="w-full bg-muted rounded-full h-1.5">
|
||
<div
|
||
className="bg-green-500 h-1.5 rounded-full"
|
||
style={{ width: `${agent.usage.memory}%` }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<Button variant="ghost" size="sm" className="w-full mt-3">
|
||
<Settings className="h-4 w-4 mr-2" />
|
||
{language === "zh" ? "配置资源" : "Configure Resources"}
|
||
</Button>
|
||
</Card>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Agent Resource Configuration Dialog */}
|
||
<Dialog open={computeConfigOpen} onOpenChange={setComputeConfigOpen}>
|
||
<DialogContent className="bg-background border-border max-w-2xl">
|
||
<DialogHeader>
|
||
<DialogTitle className="text-foreground">
|
||
{language === "zh" ? "Agent资源配置" : "Agent Resource Configuration"}
|
||
</DialogTitle>
|
||
</DialogHeader>
|
||
<div className="space-y-6">
|
||
{selectedAgent && (
|
||
<>
|
||
<div className="p-4 bg-muted/50 rounded-lg">
|
||
<h4 className="font-semibold text-foreground mb-1">{selectedAgent.name}</h4>
|
||
<p className="text-sm text-muted-foreground">{selectedAgent.id}</p>
|
||
</div>
|
||
<div className="space-y-4">
|
||
<div className="space-y-2">
|
||
<label className="text-sm font-medium text-foreground mb-1 block">
|
||
{language === "zh" ? "CPU核数" : "CPU Cores"}
|
||
</label>
|
||
<Input
|
||
type="number"
|
||
placeholder={language === "zh" ? "输入CPU核数" : "Enter CPU cores"}
|
||
defaultValue={selectedAgent.cpu}
|
||
min={1}
|
||
max={16}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
<p className="text-xs text-muted-foreground">
|
||
{language === "zh" ? "推荐范围: 1-16核" : "Recommended range: 1-16 cores"}
|
||
</p>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<label className="text-sm font-medium text-foreground mb-1 block">
|
||
{language === "zh" ? "内存大小(GB)" : "Memory Size (GB)"}
|
||
</label>
|
||
<Input
|
||
type="number"
|
||
placeholder={language === "zh" ? "输入内存大小" : "Enter memory size"}
|
||
defaultValue={selectedAgent.memory}
|
||
min={1}
|
||
max={32}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
<p className="text-xs text-muted-foreground">
|
||
{language === "zh" ? "推荐范围: 1-32GB" : "Recommended range: 1-32GB"}
|
||
</p>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<label className="text-sm font-medium text-foreground mb-1 block">
|
||
{language === "zh" ? "平台可用Agent个数" : "Available Agent Instances"}
|
||
</label>
|
||
<Input
|
||
type="number"
|
||
placeholder={language === "zh" ? "输入可用Agent个数" : "Enter available agents"}
|
||
defaultValue={10}
|
||
min={1}
|
||
max={100}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
<p className="text-xs text-muted-foreground">
|
||
{language === "zh" ? "推荐范围: 1-100个" : "Recommended range: 1-100 instances"}
|
||
</p>
|
||
</div>
|
||
<div className="p-3 bg-blue-500/10 border border-blue-500/20 rounded-lg">
|
||
<p className="text-sm text-foreground">
|
||
{language === "zh" ? "当前使用率: " : "Current Usage: "}
|
||
CPU {selectedAgent.usage.cpu}% · {language === "zh" ? "内存" : "Memory"}{" "}
|
||
{selectedAgent.usage.memory}%
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
<div className="flex justify-end gap-3">
|
||
<Button variant="outline" onClick={() => setComputeConfigOpen(false)}>
|
||
{language === "zh" ? "取消" : "Cancel"}
|
||
</Button>
|
||
<Button onClick={() => setComputeConfigOpen(false)}>
|
||
{language === "zh" ? "保存配置" : "Save Configuration"}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<div className="flex items-center justify-between mb-4">
|
||
<div>
|
||
<h2 className="text-2xl font-bold text-foreground">
|
||
{language === "zh" ? "货源供应商管理" : "Goods Source Management"}
|
||
</h2>
|
||
<p className="text-sm text-muted-foreground mt-1">
|
||
{language === "zh" ? "管理平台的模型供应商配置" : "Manage platform model provider configurations"}
|
||
</p>
|
||
</div>
|
||
<Button className="bg-primary text-primary-foreground" onClick={() => handleAddProvider("model")}>
|
||
<Plus className="h-4 w-4 mr-2" />
|
||
{language === "zh" ? "添加模型供应商" : "Add Model Provider"}
|
||
</Button>
|
||
</div>
|
||
|
||
{/* Model Providers Grid */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
{[
|
||
{
|
||
name: "OpenAI",
|
||
type: "Official",
|
||
models: ["gpt-4", "gpt-3.5-turbo"],
|
||
rpm: 10000,
|
||
tpm: 500000,
|
||
status: "active",
|
||
},
|
||
{
|
||
name: "Anthropic",
|
||
type: "Official",
|
||
models: ["claude-3-opus", "claude-2"],
|
||
rpm: 5000,
|
||
tpm: 300000,
|
||
status: "active",
|
||
},
|
||
{
|
||
name: "Google AI",
|
||
type: "GCP",
|
||
models: ["gemini-pro", "palm-2"],
|
||
rpm: 8000,
|
||
tpm: 400000,
|
||
status: "active",
|
||
},
|
||
{
|
||
name: "Azure OpenAI",
|
||
type: "Azure",
|
||
models: ["gpt-4", "gpt-35-turbo"],
|
||
rpm: 12000,
|
||
tpm: 600000,
|
||
status: "active",
|
||
},
|
||
].map((provider) => (
|
||
<Card
|
||
key={provider.name}
|
||
className="p-4 bg-card border-border hover:border-primary/50 transition-colors"
|
||
>
|
||
<div className="flex items-start justify-between mb-3">
|
||
<div>
|
||
<h4 className="font-semibold text-foreground">{provider.name}</h4>
|
||
<p className="text-xs text-muted-foreground">{provider.type}</p>
|
||
</div>
|
||
<Badge
|
||
variant={provider.status === "active" ? "default" : "secondary"}
|
||
className="bg-green-500/10 text-green-500 border-green-500/20"
|
||
>
|
||
{provider.status === "active"
|
||
? language === "zh"
|
||
? "活跃"
|
||
: "Active"
|
||
: language === "zh"
|
||
? "离线"
|
||
: "Offline"}
|
||
</Badge>
|
||
</div>
|
||
<div className="space-y-2 text-sm">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-muted-foreground">{language === "zh" ? "支持模型" : "Models"}</span>
|
||
<span className="text-foreground font-medium">{provider.models.length}</span>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-muted-foreground">RPM:</span>
|
||
<span className="text-foreground font-medium">{provider.rpm.toLocaleString()}</span>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-muted-foreground">TPM:</span>
|
||
<span className="text-foreground font-medium">{provider.tpm.toLocaleString()}</span>
|
||
</div>
|
||
</div>
|
||
<div className="flex gap-2 mt-4">
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className="flex-1 bg-transparent"
|
||
onClick={() => handleConfigProvider(provider, "model")}
|
||
>
|
||
<Settings className="h-3 w-3 mr-1" />
|
||
{language === "zh" ? "配置" : "Config"}
|
||
</Button>
|
||
<Button variant="outline" size="sm" className="flex-1 bg-transparent">
|
||
<Zap className="h-3 w-3 mr-1" />
|
||
{language === "zh" ? "测试延迟" : "Test Latency"}
|
||
</Button>
|
||
</div>
|
||
</Card>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{activeTab === "monitoring" && (
|
||
<div className="space-y-6">
|
||
{/* Agent health monitoring */}
|
||
<div className="mb-6">
|
||
<h2 className="text-2xl font-bold text-foreground">{text.agentHealthMonitoring}</h2>
|
||
<p className="text-sm text-muted-foreground mt-1">{text.agentHealthDesc}</p>
|
||
</div>
|
||
|
||
{/* Agent health cards grid */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
{agents.map((agent) => {
|
||
const statusColor =
|
||
agent.status === "healthy"
|
||
? "text-green-500 bg-green-500/10"
|
||
: agent.status === "warning"
|
||
? "text-yellow-500 bg-yellow-500/10"
|
||
: "text-red-500 bg-red-500/10"
|
||
|
||
const statusText =
|
||
agent.status === "healthy" ? text.healthy : agent.status === "warning" ? text.warning : text.critical
|
||
|
||
return (
|
||
<Card key={agent.id} className="p-6 bg-card border-border hover:border-primary/50 transition-colors">
|
||
{/* Agent header */}
|
||
<div className="flex items-start justify-between mb-4">
|
||
<div className="flex items-center gap-3">
|
||
<div className="p-2 rounded-lg bg-primary/10">
|
||
<Bot className="h-5 w-5 text-primary" />
|
||
</div>
|
||
<div>
|
||
<h3 className="text-base font-semibold text-foreground">
|
||
{language === "zh" ? agent.nameCn : agent.name}
|
||
</h3>
|
||
<p className="text-xs text-muted-foreground">{agent.lastActive}</p>
|
||
</div>
|
||
</div>
|
||
<Badge className={statusColor}>{statusText}</Badge>
|
||
</div>
|
||
|
||
{/* Agent metrics */}
|
||
<div className="space-y-3">
|
||
{/* CPU Usage */}
|
||
<div>
|
||
<div className="flex justify-between text-xs mb-1">
|
||
<span className="text-muted-foreground">{text.cpuUsageLabel}</span>
|
||
<span className="text-foreground font-medium">{agent.cpuUsage}%</span>
|
||
</div>
|
||
<div className="w-full bg-muted rounded-full h-2">
|
||
<div
|
||
className={`h-2 rounded-full ${
|
||
agent.cpuUsage > 80
|
||
? "bg-red-500"
|
||
: agent.cpuUsage > 60
|
||
? "bg-yellow-500"
|
||
: "bg-green-500"
|
||
}`}
|
||
style={{ width: `${agent.cpuUsage}%` }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Memory Usage */}
|
||
<div>
|
||
<div className="flex justify-between text-xs mb-1">
|
||
<span className="text-muted-foreground">{text.memoryUsageLabel}</span>
|
||
<span className="text-foreground font-medium">{agent.memoryUsage}%</span>
|
||
</div>
|
||
<div className="w-full bg-muted rounded-full h-2">
|
||
<div
|
||
className={`h-2 rounded-full ${
|
||
agent.memoryUsage > 80
|
||
? "bg-red-500"
|
||
: agent.memoryUsage > 60
|
||
? "bg-yellow-500"
|
||
: "bg-green-500"
|
||
}`}
|
||
style={{ width: `${agent.memoryUsage}%` }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Stats grid */}
|
||
<div className="grid grid-cols-2 gap-3 pt-3 border-t border-border">
|
||
<div>
|
||
<p className="text-xs text-muted-foreground">{text.responseTime}</p>
|
||
<p className="text-sm font-semibold text-foreground">
|
||
{agent.responseTime}
|
||
{text.ms}
|
||
</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-xs text-muted-foreground">{text.requestCount}</p>
|
||
<p className="text-sm font-semibold text-foreground">
|
||
{agent.requestCount.toLocaleString()} {text.requests}
|
||
</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-xs text-muted-foreground">{text.errorRate}</p>
|
||
<p className="text-sm font-semibold text-foreground">{agent.errorRate}%</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-xs text-muted-foreground">{text.uptime}</p>
|
||
<p className="text-sm font-semibold text-foreground">{agent.uptime}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
)
|
||
})}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{activeTab === "billing" && (
|
||
<div className="space-y-6">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h2 className="text-2xl font-bold text-foreground">{t("计费管理", "Billing Management")}</h2>
|
||
<div className="flex gap-2">
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className="gap-2 bg-transparent"
|
||
onClick={() => setShowDateDialog(true)}
|
||
>
|
||
<Calendar className="h-4 w-4" />
|
||
{t("时间查询", "Date Query")}
|
||
</Button>
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className="gap-2 bg-transparent"
|
||
onClick={() => setShowFilterDialog(true)}
|
||
>
|
||
<Filter className="h-4 w-4" />
|
||
{t("筛选", "Filter")}
|
||
</Button>
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className="gap-2 bg-transparent"
|
||
onClick={() => setShowExportDialog(true)}
|
||
>
|
||
<Download className="h-4 w-4" />
|
||
{t("导出", "Export")}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 计费维度切换 */}
|
||
<div className="flex gap-2 border-b border-border">
|
||
<button
|
||
onClick={() => setBillingView("channel")}
|
||
className={`px-4 py-2 font-medium transition-colors ${
|
||
billingView === "channel"
|
||
? "border-b-2 border-primary text-primary"
|
||
: "text-muted-foreground hover:text-foreground"
|
||
}`}
|
||
>
|
||
{t("渠道维度", "Channel Dimension")}
|
||
</button>
|
||
<button
|
||
onClick={() => setBillingView("tenant")}
|
||
className={`px-4 py-2 font-medium transition-colors ${
|
||
billingView === "tenant"
|
||
? "border-b-2 border-primary text-primary"
|
||
: "text-muted-foreground hover:text-foreground"
|
||
}`}
|
||
>
|
||
{t("租户维度", "Tenant Dimension")}
|
||
</button>
|
||
<button
|
||
onClick={() => setBillingView("calls")}
|
||
className={`px-4 py-2 font-medium transition-colors ${
|
||
billingView === "calls"
|
||
? "border-b-2 border-primary text-primary"
|
||
: "text-muted-foreground hover:text-foreground"
|
||
}`}
|
||
>
|
||
{t("调用记录", "Call Records")}
|
||
</button>
|
||
</div>
|
||
|
||
{/* 渠道维度计费 */}
|
||
{billingView === "channel" && (
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
<Card className="p-4 bg-card border-border">
|
||
<div className="text-sm text-muted-foreground">{t("渠道总数", "Total Channels")}</div>
|
||
<div className="text-2xl font-bold text-foreground mt-1">12</div>
|
||
</Card>
|
||
<Card className="p-4 bg-card border-border">
|
||
<div className="text-sm text-muted-foreground">{t("总计费额", "Total Billing")}</div>
|
||
<div className="text-2xl font-bold text-foreground mt-1">$52,840</div>
|
||
</Card>
|
||
<Card className="p-4 bg-card border-border">
|
||
<div className="text-sm text-muted-foreground">{t("总EU消耗", "Total EU Consumption")}</div>
|
||
<div className="text-2xl font-bold text-foreground mt-1">528,400 EU</div>
|
||
</Card>
|
||
</div>
|
||
|
||
<Card className="p-6 bg-card border-border">
|
||
<h3 className="text-lg font-semibold text-foreground mb-4">
|
||
{t("渠道计费详情", "Channel Billing Details")}
|
||
</h3>
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full">
|
||
<thead className="border-b border-border">
|
||
<tr className="text-left text-sm text-muted-foreground">
|
||
<th className="pb-3 font-medium">{t("渠道名称", "Channel Name")}</th>
|
||
<th className="pb-3 font-medium">{t("调用次数", "Call Count")}</th>
|
||
<th className="pb-3 font-medium">{t("总EU", "Total EU")}</th>
|
||
<th className="pb-3 font-medium">{t("渠道总价", "Channel Total")}</th>
|
||
<th className="pb-3 font-medium">{t("计费周期", "Billing Period")}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="text-sm">
|
||
{[
|
||
{ name: "Partner A", calls: 12500, eu: 125000, total: "$12,500", period: "2024-01" },
|
||
{ name: "Partner B", calls: 8300, eu: 83000, total: "$8,300", period: "2024-01" },
|
||
{ name: "Partner C", calls: 15200, eu: 152000, total: "$15,200", period: "2024-01" },
|
||
].map((channel, idx) => (
|
||
<tr key={idx} className="border-b border-border/50">
|
||
<td className="py-3 text-foreground font-medium">{channel.name}</td>
|
||
<td className="py-3 text-foreground">{channel.calls.toLocaleString()}</td>
|
||
<td className="py-3 text-foreground">{channel.eu.toLocaleString()} EU</td>
|
||
<td className="py-3 text-foreground font-semibold">{channel.total}</td>
|
||
<td className="py-3 text-muted-foreground">{channel.period}</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
)}
|
||
|
||
{/* 租户维度计费 */}
|
||
{billingView === "tenant" && (
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
<Card className="p-4 bg-card border-border">
|
||
<div className="text-sm text-muted-foreground">{t("租户总数", "Total Tenants")}</div>
|
||
<div className="text-2xl font-bold text-foreground mt-1">156</div>
|
||
</Card>
|
||
<Card className="p-4 bg-card border-border">
|
||
<div className="text-sm text-muted-foreground">{t("用户总价", "User Total")}</div>
|
||
<div className="text-2xl font-bold text-foreground mt-1">$52,840</div>
|
||
</Card>
|
||
<Card className="p-4 bg-card border-border">
|
||
<div className="text-sm text-muted-foreground">{t("平均消费", "Average Spending")}</div>
|
||
<div className="text-2xl font-bold text-foreground mt-1">$339</div>
|
||
</Card>
|
||
</div>
|
||
|
||
<Card className="p-6 bg-card border-border">
|
||
<h3 className="text-lg font-semibold text-foreground mb-4">
|
||
{t("租户计费详情", "Tenant Billing Details")}
|
||
</h3>
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full">
|
||
<thead className="border-b border-border">
|
||
<tr className="text-left text-sm text-muted-foreground">
|
||
<th className="pb-3 font-medium">{t("租户名称", "Tenant Name")}</th>
|
||
<th className="pb-3 font-medium">{t("所属渠道", "Channel")}</th>
|
||
<th className="pb-3 font-medium">{t("调用次数", "Call Count")}</th>
|
||
<th className="pb-3 font-medium">{t("总EU", "Total EU")}</th>
|
||
<th className="pb-3 font-medium">{t("用户总价", "User Total")}</th>
|
||
<th className="pb-3 font-medium">{t("计费周期", "Billing Period")}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="text-sm">
|
||
{[
|
||
{
|
||
name: "Tech Corp",
|
||
channel: "Partner A",
|
||
calls: 3200,
|
||
eu: 32000,
|
||
total: "$3,200",
|
||
period: "2024-01",
|
||
},
|
||
{
|
||
name: "AI Solutions",
|
||
channel: "Partner A",
|
||
calls: 2800,
|
||
eu: 28000,
|
||
total: "$2,800",
|
||
period: "2024-01",
|
||
},
|
||
{
|
||
name: "Data Inc",
|
||
channel: "Partner B",
|
||
calls: 4100,
|
||
eu: 41000,
|
||
total: "$4,100",
|
||
period: "2024-01",
|
||
},
|
||
{
|
||
name: "Cloud Systems",
|
||
channel: "Partner C",
|
||
calls: 5500,
|
||
eu: 55000,
|
||
total: "$5,500",
|
||
period: "2024-01",
|
||
},
|
||
].map((tenant, idx) => (
|
||
<tr key={idx} className="border-b border-border/50">
|
||
<td className="py-3 text-foreground font-medium">{tenant.name}</td>
|
||
<td className="py-3 text-muted-foreground">{tenant.channel}</td>
|
||
<td className="py-3 text-foreground">{tenant.calls.toLocaleString()}</td>
|
||
<td className="py-3 text-foreground">{tenant.eu.toLocaleString()} EU</td>
|
||
<td className="py-3 text-foreground font-semibold">{tenant.total}</td>
|
||
<td className="py-3 text-muted-foreground">{tenant.period}</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
)}
|
||
|
||
{/* 调用记录 */}
|
||
{billingView === "calls" && (
|
||
<div className="space-y-4">
|
||
<Card className="p-6 bg-card border-border">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h3 className="text-lg font-semibold text-foreground">
|
||
{t("调用记录明细", "Call Records Detail")}
|
||
</h3>
|
||
<div className="text-sm text-muted-foreground">
|
||
{t("EU计算规则:1 EU = 10秒调用时间", "EU Calculation: 1 EU = 10 seconds call time")}
|
||
</div>
|
||
</div>
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full">
|
||
<thead className="border-b border-border">
|
||
<tr className="text-left text-sm text-muted-foreground">
|
||
<th className="pb-3 font-medium">{t("调用ID", "Call ID")}</th>
|
||
<th className="pb-3 font-medium">{t("租户", "Tenant")}</th>
|
||
<th className="pb-3 font-medium">{t("渠道", "Channel")}</th>
|
||
<th className="pb-3 font-medium">{t("调用时间", "Call Time")}</th>
|
||
<th className="pb-3 font-medium">{t("时长(秒)", "Duration(s)")}</th>
|
||
<th className="pb-3 font-medium">{t("EU", "EU")}</th>
|
||
<th className="pb-3 font-medium">{t("单次调用总价", "Single Call Cost")}</th>
|
||
<th className="pb-3 font-medium">{t("时间戳", "Timestamp")}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="text-sm">
|
||
{[
|
||
{
|
||
id: "CALL-2024-001",
|
||
tenant: "Tech Corp",
|
||
channel: "Partner A",
|
||
time: "2024-01-15 14:23:45",
|
||
duration: 45,
|
||
eu: 4.5,
|
||
cost: "$0.45",
|
||
},
|
||
{
|
||
id: "CALL-2024-002",
|
||
tenant: "AI Solutions",
|
||
channel: "Partner A",
|
||
time: "2024-01-15 14:24:12",
|
||
duration: 30,
|
||
eu: 3.0,
|
||
cost: "$0.30",
|
||
},
|
||
{
|
||
id: "CALL-2024-003",
|
||
tenant: "Data Inc",
|
||
channel: "Partner B",
|
||
time: "2024-01-15 14:25:03",
|
||
duration: 120,
|
||
eu: 12.0,
|
||
cost: "$1.20",
|
||
},
|
||
{
|
||
id: "CALL-2024-004",
|
||
tenant: "Cloud Systems",
|
||
channel: "Partner C",
|
||
time: "2024-01-15 14:26:31",
|
||
duration: 65,
|
||
eu: 6.5,
|
||
cost: "$0.65",
|
||
},
|
||
{
|
||
id: "CALL-2024-005",
|
||
tenant: "Tech Corp",
|
||
channel: "Partner A",
|
||
time: "2024-01-15 14:27:15",
|
||
duration: 90,
|
||
eu: 9.0,
|
||
cost: "$0.90",
|
||
},
|
||
].map((call, idx) => (
|
||
<tr key={idx} className="border-b border-border/50">
|
||
<td className="py-3 text-foreground font-mono text-xs">{call.id}</td>
|
||
<td className="py-3 text-foreground">{call.tenant}</td>
|
||
<td className="py-3 text-muted-foreground">{call.channel}</td>
|
||
<td className="py-3 text-foreground">{call.time}</td>
|
||
<td className="py-3 text-foreground">{call.duration}s</td>
|
||
<td className="py-3 text-foreground">{call.eu} EU</td>
|
||
<td className="py-3 text-foreground font-semibold">{call.cost}</td>
|
||
<td className="py-3 text-muted-foreground text-xs">{call.time}</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
)}
|
||
<Dialog open={showFilterDialog} onOpenChange={setShowFilterDialog}>
|
||
<DialogContent className="bg-background border-border">
|
||
<DialogHeader>
|
||
<DialogTitle>{t("筛选选项", "Filter Options")}</DialogTitle>
|
||
</DialogHeader>
|
||
<div className="space-y-4 py-4">
|
||
<div>
|
||
<Label>{t("客户名称", "Customer Name")}</Label>
|
||
<Input
|
||
placeholder={t("输入客户名称", "Enter customer name")}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<Label>{t("最小调用次数", "Min Calls")}</Label>
|
||
<Input type="number" placeholder="0" className="bg-background border-border text-foreground" />
|
||
</div>
|
||
<div>
|
||
<Label>{t("最大调用次数", "Max Calls")}</Label>
|
||
<Input type="number" placeholder="10000" className="bg-background border-border text-foreground" />
|
||
</div>
|
||
<Button className="w-full">{t("应用筛选", "Apply Filter")}</Button>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Dialog open={showDateDialog} onOpenChange={setShowDateDialog}>
|
||
<DialogContent className="bg-background border-border">
|
||
<DialogHeader>
|
||
<DialogTitle>{t("时间范围", "Date & Time Range")}</DialogTitle>
|
||
</DialogHeader>
|
||
<div className="space-y-4 py-4">
|
||
<div>
|
||
<Label>{t("开始时间 (年-月-日 时:分)", "Start Date & Time (YYYY-MM-DD HH:MM)")}</Label>
|
||
<Input type="datetime-local" className="bg-background border-border text-foreground" />
|
||
</div>
|
||
<div>
|
||
<Label>{t("结束时间 (年-月-日 时:分)", "End Date & Time (YYYY-MM-DD HH:MM)")}</Label>
|
||
<Input type="datetime-local" className="bg-background border-border text-foreground" />
|
||
</div>
|
||
<Button className="w-full">{t("查询", "Query")}</Button>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Dialog open={showExportDialog} onOpenChange={setShowExportDialog}>
|
||
<DialogContent className="bg-background border-border">
|
||
<DialogHeader>
|
||
<DialogTitle>{t("导出格式", "Export Format")}</DialogTitle>
|
||
</DialogHeader>
|
||
<div className="space-y-4 py-4">
|
||
<p className="text-sm text-muted-foreground">{t("选择导出文件格式", "Select export file format")}</p>
|
||
<div className="grid grid-cols-2 gap-3">
|
||
<Button variant="outline" className="h-20 flex-col gap-2 bg-transparent">
|
||
<Download className="h-5 w-5" />
|
||
<span>Excel (.xlsx)</span>
|
||
</Button>
|
||
<Button variant="outline" className="h-20 flex-col gap-2 bg-transparent">
|
||
<Download className="h-5 w-5" />
|
||
<span>CSV (.csv)</span>
|
||
</Button>
|
||
<Button variant="outline" className="h-20 flex-col gap-2 col-span-2 bg-transparent">
|
||
<Download className="h-5 w-5" />
|
||
<span>PDF (.pdf)</span>
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</div>
|
||
)}
|
||
|
||
{activeTab === "settings" && (
|
||
<div className="space-y-6">
|
||
<div>
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h2 className="text-2xl font-bold text-foreground">{t("用户身份设置", "User Role Settings")}</h2>
|
||
<Button onClick={() => setShowAddAdminDialog(true)}>
|
||
<Plus className="w-4 h-4 mr-2" />
|
||
{t("添加管理员", "Add Administrator")}
|
||
</Button>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||
{/* Billing Admin Role Card */}
|
||
<Card
|
||
className={`p-6 cursor-pointer transition-all ${selectedRole === "billing-admin" ? "border-primary bg-primary/5" : "bg-card border-border hover:border-primary/50"}`}
|
||
onClick={() => setSelectedRole("billing-admin")}
|
||
>
|
||
<div className="flex items-start gap-3">
|
||
<DollarSign className="w-8 h-8 text-green-500" />
|
||
<div className="flex-1">
|
||
<h3 className="text-lg font-semibold text-foreground">
|
||
{t("计费管理员", "Billing Administrator")}
|
||
</h3>
|
||
<p className="text-sm text-muted-foreground mt-1">
|
||
{t(
|
||
"负责平台计费、账单和财务管理",
|
||
"Responsible for billing, invoices, and financial management",
|
||
)}
|
||
</p>
|
||
<div className="mt-3 flex items-center gap-2">
|
||
<Badge variant="secondary" className="text-xs">
|
||
{rolePermissions["billing-admin"].length} {t("个权限", "permissions")}
|
||
</Badge>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* Operations Admin Role Card */}
|
||
<Card
|
||
className={`p-6 cursor-pointer transition-all ${selectedRole === "operations-admin" ? "border-primary bg-primary/5" : "bg-card border-border hover:border-primary/50"}`}
|
||
onClick={() => setSelectedRole("operations-admin")}
|
||
>
|
||
<div className="flex items-start gap-3">
|
||
<Settings className="w-8 h-8 text-blue-500" />
|
||
<div className="flex-1">
|
||
<h3 className="text-lg font-semibold text-foreground">
|
||
{t("运营管理员", "Operations Administrator")}
|
||
</h3>
|
||
<p className="text-sm text-muted-foreground mt-1">
|
||
{t("负责渠道、资源和系统监控管理", "Responsible for channels, resources, and monitoring")}
|
||
</p>
|
||
<div className="mt-3 flex items-center gap-2">
|
||
<Badge variant="secondary" className="text-xs">
|
||
{rolePermissions["operations-admin"].length} {t("个权限", "permissions")}
|
||
</Badge>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* Super Admin Role Card */}
|
||
<Card
|
||
className={`p-6 cursor-pointer transition-all ${selectedRole === "super-admin" ? "border-primary bg-primary/5" : "bg-card border-border hover:border-primary/50"}`}
|
||
onClick={() => setSelectedRole("super-admin")}
|
||
>
|
||
<div className="flex items-start gap-3">
|
||
<Shield className="w-8 h-8 text-purple-500" />
|
||
<div className="flex-1">
|
||
<h3 className="text-lg font-semibold text-foreground">
|
||
{t("超级管理员", "Super Administrator")}
|
||
</h3>
|
||
<p className="text-sm text-muted-foreground mt-1">
|
||
{t("拥有所有权限,管理整个平台", "Full access to all platform features")}
|
||
</p>
|
||
<div className="mt-3 flex items-center gap-2">
|
||
<Badge variant="secondary" className="text-xs">
|
||
{rolePermissions["super-admin"].length} {t("个权限", "permissions")}
|
||
</Badge>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
|
||
{selectedRole && (
|
||
<Card className="p-6 bg-card border-border">
|
||
<h3 className="text-lg font-semibold text-foreground mb-4">
|
||
{t("标签页访问权限", "Tab Access Permissions")}
|
||
</h3>
|
||
<p className="text-sm text-muted-foreground mb-6">
|
||
{t("选择该角色可以访问的标签页", "Select which tabs this role can access")}
|
||
</p>
|
||
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||
{[
|
||
{ id: "overview", label: t("概览", "Overview"), icon: "📊" },
|
||
{ id: "channels", label: t("渠道管理", "Channel Management"), icon: "🏢" },
|
||
{ id: "resources", label: t("资源管理", "Resource Management"), icon: "⚙️" },
|
||
{ id: "monitoring", label: t("监控", "Monitoring"), icon: "📈" },
|
||
{ id: "billing", label: t("计费", "Billing"), icon: "💰" },
|
||
{ id: "provider-backend", label: t("供应商管理中心后台", "Provider Backend"), icon: "🔧" },
|
||
{ id: "channel-backend", label: t("渠道管理中心后台", "Channel Backend"), icon: "🏪" },
|
||
{ id: "settings", label: t("设置", "Settings"), icon: "⚙️" },
|
||
].map((tab) => {
|
||
const hasPermission = rolePermissions[selectedRole]?.includes(tab.id)
|
||
return (
|
||
<div
|
||
key={tab.id}
|
||
className={`p-4 rounded-lg border-2 cursor-pointer transition-all ${
|
||
hasPermission
|
||
? "border-primary bg-primary/10"
|
||
: "border-border bg-background hover:border-primary/30"
|
||
}`}
|
||
onClick={() => {
|
||
setRolePermissions((prev) => ({
|
||
...prev,
|
||
[selectedRole]: hasPermission
|
||
? prev[selectedRole].filter((p) => p !== tab.id)
|
||
: [...prev[selectedRole], tab.id],
|
||
}))
|
||
}}
|
||
>
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<span className="text-xl">{tab.icon}</span>
|
||
{hasPermission && <Check className="w-4 h-4 text-primary ml-auto" />}
|
||
</div>
|
||
<p className="text-sm font-medium text-foreground">{tab.label}</p>
|
||
</div>
|
||
)
|
||
})}
|
||
</div>
|
||
|
||
<Button
|
||
className="mt-6"
|
||
onClick={() => {
|
||
console.log("[v0] Saving permissions for", selectedRole, rolePermissions[selectedRole])
|
||
}}
|
||
>
|
||
{t("保存权限配置", "Save Permissions")}
|
||
</Button>
|
||
</Card>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</main>
|
||
|
||
{/* Subscription level assignment dialog */}
|
||
<Dialog open={isSubscriptionDialogOpen} onOpenChange={setIsSubscriptionDialogOpen}>
|
||
<DialogContent className="max-w-md">
|
||
<DialogHeader>
|
||
<DialogTitle>{text.assignSubscriptionLevel}</DialogTitle>
|
||
</DialogHeader>
|
||
<div className="space-y-4">
|
||
<p className="text-sm text-muted-foreground">
|
||
{language === "zh" ? "为租户选择订阅级别:" : "Select a subscription level for the tenant:"}
|
||
</p>
|
||
<div className="space-y-3">
|
||
{subscriptionLevels.map((level) => (
|
||
<div
|
||
key={level.id}
|
||
onClick={() => setSelectedSubscriptionLevel(level.id)}
|
||
className={`p-4 rounded-lg border-2 cursor-pointer transition-colors ${
|
||
selectedSubscriptionLevel === level.id
|
||
? "border-primary bg-primary/5"
|
||
: "border-border hover:border-primary/50"
|
||
}`}
|
||
>
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h4 className="font-semibold text-foreground">{level.name}</h4>
|
||
<p className="text-sm text-muted-foreground">{level.description}</p>
|
||
</div>
|
||
<div
|
||
className={`w-5 h-5 rounded-full border-2 flex items-center justify-center ${
|
||
selectedSubscriptionLevel === level.id ? "border-primary bg-primary" : "border-border"
|
||
}`}
|
||
>
|
||
{selectedSubscriptionLevel === level.id && <div className="w-2 h-2 rounded-full bg-white" />}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<div className="flex gap-2 justify-end pt-4">
|
||
<Button variant="outline" onClick={() => setIsSubscriptionDialogOpen(false)}>
|
||
{language === "zh" ? "取消" : "Cancel"}
|
||
</Button>
|
||
<Button
|
||
onClick={() => {
|
||
// Handle subscription level assignment
|
||
console.log(`Assigning subscription level ${selectedSubscriptionLevel} to tenant ${selectedTenant}`)
|
||
setIsSubscriptionDialogOpen(false)
|
||
}}
|
||
>
|
||
{language === "zh" ? "确认分配" : "Confirm Assignment"}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
{/* Dialogs for Add/Config Provider */}
|
||
<Dialog open={isAddProviderOpen} onOpenChange={setIsAddProviderOpen}>
|
||
<DialogContent className="bg-card border-border max-w-2xl">
|
||
<DialogHeader>
|
||
<DialogTitle>
|
||
{language === "zh"
|
||
? providerType === "model"
|
||
? "添加模型供应商"
|
||
: "添加数据供应商"
|
||
: providerType === "model"
|
||
? "Add Model Provider"
|
||
: "Add Data Provider"}
|
||
</DialogTitle>
|
||
<DialogDescription>
|
||
{language === "zh" ? "填写供应商信息以添加新的货源" : "Fill in provider information to add a new source"}
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
|
||
<div className="space-y-4 py-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="provider-name">{language === "zh" ? "供应商名称" : "Provider Name"}</Label>
|
||
<Input
|
||
id="provider-name"
|
||
placeholder={language === "zh" ? "例: OpenAI" : "e.g., OpenAI"}
|
||
value={providerForm.name}
|
||
onChange={(e) => setProviderForm({ ...providerForm, name: e.target.value })}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="provider-url">{language === "zh" ? "API URL / 服务端点" : "API URL / Endpoint"}</Label>
|
||
<Input
|
||
id="provider-url"
|
||
placeholder={language === "zh" ? "例: https://api.openai.com/v1" : "e.g., https://api.openai.com/v1"}
|
||
value={providerForm.url}
|
||
onChange={(e) => setProviderForm({ ...providerForm, url: e.target.value })}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="provider-key">{language === "zh" ? "API 密钥" : "API Key"}</Label>
|
||
<Input
|
||
id="provider-key"
|
||
type="password"
|
||
placeholder={language === "zh" ? "输入 API 密钥" : "Enter API key"}
|
||
value={providerForm.apiKey}
|
||
onChange={(e) => setProviderForm({ ...providerForm, apiKey: e.target.value })}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
</div>
|
||
|
||
{providerType === "model" && (
|
||
<>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="provider-models">{language === "zh" ? "可调用模型" : "Available Models"}</Label>
|
||
<Input
|
||
id="provider-models"
|
||
placeholder={
|
||
language === "zh"
|
||
? "例: gpt-4, gpt-3.5-turbo (逗号分隔)"
|
||
: "e.g., gpt-4, gpt-3.5-turbo (comma separated)"
|
||
}
|
||
value={providerForm.models}
|
||
onChange={(e) => setProviderForm({ ...providerForm, models: e.target.value })}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
<p className="text-xs text-muted-foreground">
|
||
{language === "zh" ? "多个模型请用逗号分隔" : "Separate multiple models with commas"}
|
||
</p>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="provider-rpm">
|
||
{language === "zh" ? "RPM (每分钟请求数)" : "RPM (Requests Per Minute)"}
|
||
</Label>
|
||
<Input
|
||
id="provider-rpm"
|
||
type="number"
|
||
placeholder={language === "zh" ? "例: 10000" : "e.g., 10000"}
|
||
value={providerForm.rpm}
|
||
onChange={(e) => setProviderForm({ ...providerForm, rpm: e.target.value })}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="provider-tpm">
|
||
{language === "zh" ? "TPM (每分钟令牌数)" : "TPM (Tokens Per Minute)"}
|
||
</Label>
|
||
<Input
|
||
id="provider-tpm"
|
||
type="number"
|
||
placeholder={language === "zh" ? "例: 500000" : "e.g., 500000"}
|
||
value={providerForm.tpm}
|
||
onChange={(e) => setProviderForm({ ...providerForm, tpm: e.target.value })}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
|
||
<DialogFooter>
|
||
<Button variant="outline" onClick={() => setIsAddProviderOpen(false)}>
|
||
{text.cancel}
|
||
</Button>
|
||
<Button className="bg-primary text-primary-foreground" onClick={handleSaveProvider}>
|
||
{language === "zh" ? "添加供应商" : "Add Provider"}
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Dialog open={isConfigProviderOpen} onOpenChange={setIsConfigProviderOpen}>
|
||
<DialogContent className="bg-card border-border max-w-2xl">
|
||
<DialogHeader>
|
||
<DialogTitle>{language === "zh" ? "配置供应商" : "Configure Provider"}</DialogTitle>
|
||
<DialogDescription>
|
||
{language === "zh"
|
||
? `修改 "${selectedProvider?.name}" 的配置信息`
|
||
: `Modify configuration for "${selectedProvider?.name}"`}
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
|
||
<div className="space-y-4 py-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="config-provider-name">{language === "zh" ? "供应商名称" : "Provider Name"}</Label>
|
||
<Input
|
||
id="config-provider-name"
|
||
value={providerForm.name}
|
||
onChange={(e) => setProviderForm({ ...providerForm, name: e.target.value })}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="config-provider-url">
|
||
{language === "zh" ? "API URL / 服务端点" : "API URL / Endpoint"}
|
||
</Label>
|
||
<Input
|
||
id="config-provider-url"
|
||
value={providerForm.url}
|
||
onChange={(e) => setProviderForm({ ...providerForm, url: e.target.value })}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="config-provider-key">{language === "zh" ? "API 密钥" : "API Key"}</Label>
|
||
<Input
|
||
id="config-provider-key"
|
||
type="password"
|
||
value={providerForm.apiKey}
|
||
onChange={(e) => setProviderForm({ ...providerForm, apiKey: e.target.value })}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
<p className="text-xs text-muted-foreground">
|
||
{language === "zh" ? "留空则不修改密钥" : "Leave blank to keep existing key"}
|
||
</p>
|
||
</div>
|
||
|
||
{providerType === "model" && (
|
||
<>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="config-provider-models">
|
||
{language === "zh" ? "可调用模型" : "Available Models"}
|
||
</Label>
|
||
<Input
|
||
id="config-provider-models"
|
||
value={providerForm.models}
|
||
onChange={(e) => setProviderForm({ ...providerForm, models: e.target.value })}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="config-provider-rpm">
|
||
{language === "zh" ? "RPM (每分钟请求数)" : "RPM (Requests Per Minute)"}
|
||
</Label>
|
||
<Input
|
||
id="config-provider-rpm"
|
||
type="number"
|
||
value={providerForm.rpm}
|
||
onChange={(e) => setProviderForm({ ...providerForm, rpm: e.target.value })}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="config-provider-tpm">
|
||
{language === "zh" ? "TPM (每分钟令牌数)" : "TPM (Tokens Per Minute)"}
|
||
</Label>
|
||
<Input
|
||
id="config-provider-tpm"
|
||
type="number"
|
||
value={providerForm.tpm}
|
||
onChange={(e) => setProviderForm({ ...providerForm, tpm: e.target.value })}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
|
||
<DialogFooter>
|
||
<Button variant="outline" onClick={() => setIsConfigProviderOpen(false)}>
|
||
{text.cancel}
|
||
</Button>
|
||
<Button className="bg-primary text-primary-foreground" onClick={handleSaveProvider}>
|
||
{language === "zh" ? "保存修改" : "Save Changes"}
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
{/* Dialog for Add New Admin */}
|
||
<Dialog open={showAddAdminDialog} onOpenChange={setShowAddAdminDialog}>
|
||
<DialogContent className="max-w-md bg-background border-border">
|
||
<DialogHeader>
|
||
<DialogTitle className="text-foreground">{t("添加新管理员", "Add New Administrator")}</DialogTitle>
|
||
<DialogDescription className="text-muted-foreground">
|
||
{t("创建新的管理员账户并分配角色", "Create a new administrator account and assign a role")}
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
|
||
<div className="space-y-4">
|
||
<div>
|
||
<label className="text-sm font-medium text-foreground mb-1 block">{t("姓名", "Name")}</label>
|
||
<Input
|
||
placeholder={t("输入管理员姓名", "Enter administrator name")}
|
||
value={newAdminForm.name}
|
||
onChange={(e) => setNewAdminForm({ ...newAdminForm, name: e.target.value })}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="text-sm font-medium text-foreground mb-1 block">{t("邮箱", "Email")}</label>
|
||
<Input
|
||
type="email"
|
||
placeholder={t("输入邮箱地址", "Enter email address")}
|
||
value={newAdminForm.email}
|
||
onChange={(e) => setNewAdminForm({ ...newAdminForm, email: e.target.value })}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="text-sm font-medium text-foreground mb-1 block">{t("密码", "Password")}</label>
|
||
<Input
|
||
type="password"
|
||
placeholder={t("设置登录密码", "Set login password")}
|
||
value={newAdminForm.password}
|
||
onChange={(e) => setNewAdminForm({ ...newAdminForm, password: e.target.value })}
|
||
className="bg-background border-border text-foreground"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="text-sm font-medium text-foreground mb-1 block">{t("角色", "Role")}</label>
|
||
<Select
|
||
value={newAdminForm.role}
|
||
onValueChange={(value) => setNewAdminForm({ ...newAdminForm, role: value })}
|
||
>
|
||
<SelectTrigger className="bg-background border-border text-foreground">
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent className="bg-background border-border">
|
||
<SelectItem value="billing-admin" className="text-foreground">
|
||
{t("计费管理员", "Billing Administrator")}
|
||
</SelectItem>
|
||
<SelectItem value="operations-admin" className="text-foreground">
|
||
{t("运营管理员", "Operations Administrator")}
|
||
</SelectItem>
|
||
<SelectItem value="super-admin" className="text-foreground">
|
||
{t("超级管理员", "Super Administrator")}
|
||
</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
<p className="text-xs text-muted-foreground mt-1">
|
||
{newAdminForm.role === "billing-admin" &&
|
||
t("负责平台计费、账单和财务管理", "Responsible for billing, invoices, and financial management")}
|
||
{newAdminForm.role === "operations-admin" &&
|
||
t("负责渠道、资源和系统监控管理", "Responsible for channels, resources, and monitoring")}
|
||
{newAdminForm.role === "super-admin" &&
|
||
t("拥有所有权限,管理整个平台", "Full access to all platform features")}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<DialogFooter>
|
||
<Button variant="outline" onClick={() => setShowAddAdminDialog(false)}>
|
||
{t("取消", "Cancel")}
|
||
</Button>
|
||
<Button
|
||
onClick={() => {
|
||
console.log("[v0] Creating admin:", newAdminForm)
|
||
setShowAddAdminDialog(false)
|
||
setNewAdminForm({ name: "", email: "", password: "", role: "billing-admin" })
|
||
}}
|
||
disabled={!newAdminForm.name || !newAdminForm.email || !newAdminForm.password}
|
||
>
|
||
{t("创建管理员", "Create Administrator")}
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</div>
|
||
)
|
||
}
|