Files
users/app/ai-insight/data-cleaning/page.tsx
v0 b17b488f8e refactor: restructure navigation and module layout
Reorganize navigation and module structure based on new requirements.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
2026-01-31 04:32:36 +00:00

882 lines
38 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Input } from "@/components/ui/input"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import { Slider } from "@/components/ui/slider"
import {
ArrowLeft,
Sparkles,
CheckCircle,
AlertTriangle,
Play,
Settings,
Eye,
RefreshCw,
Database,
Zap,
Plus,
Brain,
MessageSquare,
Shield,
History,
Check,
X,
Pause,
FileText,
} from "lucide-react"
import Link from "next/link"
const availableDataSources = [
{ id: "1", name: "存客宝-用户主库", type: "mysql", records: 128956342, tables: ["users", "orders", "profiles"] },
{ id: "2", name: "触客宝-行为数据", type: "kafka", records: 89234567, tables: ["events", "sessions"] },
{ id: "3", name: "数智员工-账号API", type: "restapi", records: 45678901, tables: ["accounts", "activities"] },
{ id: "7", name: "微信消息-Webhook", type: "webhook", records: 34567890, tables: ["messages", "contacts"] },
]
const aiCleaningRules = [
{
id: 1,
name: "用户数据智能清洗",
description: "自动识别并修正用户数据中的格式错误、缺失值和异常值",
dataSource: "存客宝-用户主库",
targetTable: "users",
prompt:
"分析用户数据表识别以下问题并提供修正建议1) 手机号格式不规范 2) 邮箱格式错误 3) 姓名包含特殊字符 4) 地址信息不完整 5) 日期格式不统一。对于每个问题,给出具体的修正规则。",
model: "GPT-4",
status: "running",
confidenceThreshold: 0.8,
autoApproveThreshold: 0.95,
processed: 125800,
pending: 3400,
approved: 120000,
rejected: 2400,
lastRun: "10分钟前",
accuracy: 96.2,
},
{
id: 2,
name: "交易流水异常检测",
description: "识别交易数据中的异常记录和潜在错误",
dataSource: "存客宝-用户主库",
targetTable: "orders",
prompt:
"检查交易流水数据识别以下异常1) 金额为负或超出合理范围 2) 时间戳异常 3) 重复交易记录 4) 用户ID与订单不匹配 5) 状态字段值异常。标记问题记录并建议处理方式。",
model: "GPT-4",
status: "running",
confidenceThreshold: 0.7,
autoApproveThreshold: 0.9,
processed: 89000,
pending: 1200,
approved: 85000,
rejected: 2800,
lastRun: "30分钟前",
accuracy: 94.5,
},
{
id: 3,
name: "聊天记录语义清洗",
description: "清洗聊天记录中的敏感信息和无效内容",
dataSource: "微信消息-Webhook",
targetTable: "messages",
prompt:
"处理聊天记录数据1) 识别并脱敏个人敏感信息身份证、银行卡号等2) 过滤垃圾信息和广告 3) 标准化时间格式 4) 提取并分类用户意图 5) 标记需要人工处理的复杂内容。",
model: "Claude-3",
status: "paused",
confidenceThreshold: 0.6,
autoApproveThreshold: 0.85,
processed: 456000,
pending: 15000,
approved: 420000,
rejected: 21000,
lastRun: "2小时前",
accuracy: 91.8,
},
]
const pendingCleaningSuggestions = [
{
id: 1,
ruleId: 1,
ruleName: "用户数据智能清洗",
field: "phone",
originalValue: "86-138-1234-5678",
suggestedValue: "13812345678",
confidence: 0.95,
reason: "去除国际区号前缀和分隔符,保留纯数字手机号",
affectedRows: 2350,
createdAt: "2025-12-12 14:30",
},
{
id: 2,
ruleId: 1,
ruleName: "用户数据智能清洗",
field: "email",
originalValue: "user@example,com",
suggestedValue: "user@example.com",
confidence: 0.88,
reason: "修正邮箱格式,将逗号替换为点号",
affectedRows: 156,
createdAt: "2025-12-12 14:28",
},
{
id: 3,
ruleId: 2,
ruleName: "交易流水异常检测",
field: "amount",
originalValue: "-500.00",
suggestedValue: "500.00",
confidence: 0.72,
reason: "检测到负数金额,建议修正为正数(可能是退款记录录入错误)",
affectedRows: 45,
createdAt: "2025-12-12 14:25",
},
{
id: 4,
ruleId: 3,
ruleName: "聊天记录语义清洗",
field: "content",
originalValue: "我的身份证号是310101199001011234",
suggestedValue: "我的身份证号是310***********1234",
confidence: 0.92,
reason: "检测到身份证号,进行脱敏处理保护用户隐私",
affectedRows: 890,
createdAt: "2025-12-12 14:22",
},
]
// 清洗历史
const cleaningHistory = [
{ id: 1, rule: "用户数据智能清洗", action: "approved", operator: "系统自动", count: 5000, time: "2025-12-12 14:00" },
{ id: 2, rule: "交易流水异常检测", action: "approved", operator: "张管理员", count: 120, time: "2025-12-12 13:45" },
{
id: 3,
rule: "聊天记录语义清洗",
action: "rejected",
operator: "李管理员",
count: 35,
time: "2025-12-12 13:30",
reason: "置信度过低",
},
]
export default function AIDataCleaningPage() {
const [activeTab, setActiveTab] = useState("rules")
const [createRuleDialogOpen, setCreateRuleDialogOpen] = useState(false)
const [settingsDialogOpen, setSettingsDialogOpen] = useState(false)
const [previewDialogOpen, setPreviewDialogOpen] = useState(false)
const [selectedRule, setSelectedRule] = useState<(typeof aiCleaningRules)[0] | null>(null)
const [selectedSuggestion, setSelectedSuggestion] = useState<(typeof pendingCleaningSuggestions)[0] | null>(null)
const [selectedSuggestions, setSelectedSuggestions] = useState<number[]>([])
const [confidenceFilter, setConfidenceFilter] = useState([0.5])
const [newRule, setNewRule] = useState({
name: "",
dataSource: "",
targetTable: "",
prompt: "",
model: "gpt-4",
confidenceThreshold: 0.7,
autoApproveThreshold: 0.9,
})
const totalPending = aiCleaningRules.reduce((sum, r) => sum + r.pending, 0)
const totalApproved = aiCleaningRules.reduce((sum, r) => sum + r.approved, 0)
const totalProcessed = aiCleaningRules.reduce((sum, r) => sum + r.processed, 0)
const filteredSuggestions = pendingCleaningSuggestions.filter((s) => s.confidence >= confidenceFilter[0])
// 获取选中数据源的表列表
const getTablesForDataSource = (dsName: string) => {
const ds = availableDataSources.find((d) => d.name === dsName)
return ds?.tables || []
}
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-violet-50/30 p-6">
<div className="max-w-7xl mx-auto space-y-6">
{/* 页面标题 */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Link href="/ai-insight">
<Button variant="ghost" size="icon" className="rounded-full">
<ArrowLeft className="w-5 h-5" />
</Button>
</Link>
<div>
<h1 className="text-2xl font-bold text-slate-800">AI数据清洗</h1>
<p className="text-slate-500 mt-1"></p>
</div>
</div>
<div className="flex gap-2">
<Button variant="outline">
<RefreshCw className="w-4 h-4 mr-2" />
</Button>
<Button
className="bg-gradient-to-r from-violet-500 to-purple-600 hover:from-violet-600 hover:to-purple-700"
onClick={() => setCreateRuleDialogOpen(true)}
>
<Plus className="w-4 h-4 mr-2" />
AI规则
</Button>
</div>
</div>
{/* 统计概览 */}
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-slate-500"></p>
<p className="text-2xl font-bold text-slate-800">{(totalProcessed / 1000).toFixed(0)}K</p>
</div>
<Database className="w-8 h-8 text-blue-500" />
</div>
</CardContent>
</Card>
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-slate-500"></p>
<p className="text-2xl font-bold text-emerald-600">
{((totalApproved / totalProcessed) * 100).toFixed(1)}%
</p>
</div>
<CheckCircle className="w-8 h-8 text-emerald-500" />
</div>
</CardContent>
</Card>
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-slate-500"></p>
<p className="text-2xl font-bold text-amber-600">{totalPending.toLocaleString()}</p>
</div>
<AlertTriangle className="w-8 h-8 text-amber-500" />
</div>
</CardContent>
</Card>
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-slate-500"></p>
<p className="text-2xl font-bold text-slate-800">
{aiCleaningRules.filter((r) => r.status === "running").length} / {aiCleaningRules.length}
</p>
</div>
<RefreshCw className="w-8 h-8 text-blue-500" />
</div>
</CardContent>
</Card>
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-slate-500"></p>
<p className="text-2xl font-bold text-violet-600">0.89</p>
</div>
<Brain className="w-8 h-8 text-violet-500" />
</div>
</CardContent>
</Card>
</div>
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="bg-white/60 backdrop-blur">
<TabsTrigger value="rules" className="gap-2">
<Sparkles className="w-4 h-4" />
AI清洗规则
</TabsTrigger>
<TabsTrigger value="review" className="gap-2">
<Shield className="w-4 h-4" />
{totalPending > 0 && <Badge className="ml-1 bg-amber-100 text-amber-700">{totalPending}</Badge>}
</TabsTrigger>
<TabsTrigger value="history" className="gap-2">
<History className="w-4 h-4" />
</TabsTrigger>
</TabsList>
{/* AI清洗规则列表 */}
<TabsContent value="rules" className="mt-4">
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-base">AI清洗规则配置</CardTitle>
<p className="text-sm text-slate-500"></p>
</div>
</CardHeader>
<CardContent>
<div className="space-y-4">
{aiCleaningRules.map((rule) => (
<div key={rule.id} className="p-4 bg-slate-50/80 rounded-lg border border-slate-200/60">
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-blue-500 to-cyan-600 flex items-center justify-center">
<Zap className="w-5 h-5 text-white" />
</div>
<div>
<div className="flex items-center gap-2">
<h4 className="font-medium text-slate-800">{rule.name}</h4>
<Badge variant="outline" className="text-xs">
{rule.model}
</Badge>
{rule.status === "running" ? (
<Badge className="bg-emerald-100 text-emerald-700"></Badge>
) : (
<Badge variant="secondary"></Badge>
)}
</div>
<p className="text-sm text-slate-500 mt-0.5">{rule.description}</p>
</div>
</div>
<div className="flex items-center gap-2">
{rule.status === "running" ? (
<Button variant="outline" size="sm">
<Pause className="w-4 h-4" />
</Button>
) : (
<Button variant="outline" size="sm">
<Play className="w-4 h-4" />
</Button>
)}
<Button
variant="ghost"
size="sm"
onClick={() => {
setSelectedRule(rule)
setSettingsDialogOpen(true)
}}
>
<Settings className="w-4 h-4" />
</Button>
<Button variant="ghost" size="sm">
<FileText className="w-4 h-4" />
</Button>
</div>
</div>
{/* 数据源和目标表 */}
<div className="grid grid-cols-2 gap-4 mb-3">
<div className="p-3 bg-white rounded-lg">
<div className="flex items-center gap-2 text-xs text-slate-500 mb-1">
<Database className="w-3 h-3" />
</div>
<p className="text-sm font-medium text-slate-700">{rule.dataSource}</p>
<p className="text-xs text-slate-500 mt-1">: {rule.targetTable}</p>
</div>
<div className="p-3 bg-white rounded-lg">
<div className="flex items-center gap-2 text-xs text-slate-500 mb-1">
<Zap className="w-3 h-3" />
</div>
<p className="text-sm font-medium text-slate-700">
: {">="}
{rule.confidenceThreshold} | : {">="}
{rule.autoApproveThreshold}
</p>
</div>
</div>
{/* 提示词 */}
<div className="p-3 bg-white rounded-lg mb-3">
<div className="flex items-center gap-2 text-xs text-slate-500 mb-1">
<MessageSquare className="w-3 h-3" />
AI提示词
</div>
<p className="text-sm text-slate-600 line-clamp-2">{rule.prompt}</p>
</div>
{/* 统计数据 */}
<div className="grid grid-cols-5 gap-4 text-sm">
<div className="text-center">
<p className="text-lg font-bold text-slate-800">{rule.accuracy}%</p>
<p className="text-xs text-slate-500"></p>
</div>
<div className="text-center">
<p className="text-lg font-bold text-slate-800">{(rule.processed / 1000).toFixed(0)}K</p>
<p className="text-xs text-slate-500"></p>
</div>
<div className="text-center">
<p className="text-lg font-bold text-amber-600">{rule.pending.toLocaleString()}</p>
<p className="text-xs text-slate-500"></p>
</div>
<div className="text-center">
<p className="text-lg font-bold text-emerald-600">{(rule.approved / 1000).toFixed(0)}K</p>
<p className="text-xs text-slate-500"></p>
</div>
<div className="text-center">
<p className="text-lg font-bold text-red-600">{rule.rejected.toLocaleString()}</p>
<p className="text-xs text-slate-500"></p>
</div>
</div>
<div className="mt-3 pt-3 border-t border-slate-200/60 flex items-center justify-between text-xs text-slate-500">
<span>: {rule.lastRun}</span>
<Button variant="link" size="sm" className="h-auto p-0 text-violet-600">
</Button>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</TabsContent>
{/* 审核中心 */}
<TabsContent value="review" className="mt-4">
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div>
<CardTitle className="text-base"></CardTitle>
<p className="text-sm text-slate-500 mt-1">AI生成的数据修正建议</p>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<span className="text-sm text-slate-500">:</span>
<div className="w-32">
<Slider
value={confidenceFilter}
onValueChange={setConfidenceFilter}
min={0}
max={1}
step={0.1}
/>
</div>
<span className="text-sm font-medium">
{">="}
{confidenceFilter[0]}
</span>
</div>
{selectedSuggestions.length > 0 && (
<div className="flex items-center gap-2">
<span className="text-sm text-slate-500"> {selectedSuggestions.length} </span>
<Button size="sm" variant="outline" className="text-emerald-600 bg-transparent">
<Check className="w-4 h-4 mr-1" />
</Button>
<Button size="sm" variant="outline" className="text-red-600 bg-transparent">
<X className="w-4 h-4 mr-1" />
</Button>
</div>
)}
</div>
</div>
</CardHeader>
<CardContent>
<div className="space-y-3">
{filteredSuggestions.map((suggestion) => (
<div key={suggestion.id} className="p-4 bg-slate-50/80 rounded-lg border border-slate-200/60">
<div className="flex items-start justify-between">
<div className="flex items-start gap-3">
<input
type="checkbox"
className="mt-1 rounded"
checked={selectedSuggestions.includes(suggestion.id)}
onChange={(e) => {
if (e.target.checked) {
setSelectedSuggestions([...selectedSuggestions, suggestion.id])
} else {
setSelectedSuggestions(selectedSuggestions.filter((id) => id !== suggestion.id))
}
}}
/>
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<Badge variant="outline">{suggestion.ruleName}</Badge>
<Badge className="bg-blue-100 text-blue-700">: {suggestion.field}</Badge>
<Badge
className={`${
suggestion.confidence >= 0.9
? "bg-emerald-100 text-emerald-700"
: suggestion.confidence >= 0.7
? "bg-amber-100 text-amber-700"
: "bg-red-100 text-red-700"
}`}
>
: {(suggestion.confidence * 100).toFixed(0)}%
</Badge>
<span className="text-xs text-slate-500"> {suggestion.affectedRows} </span>
</div>
<div className="grid grid-cols-2 gap-4 p-3 bg-white rounded-lg mb-2">
<div>
<p className="text-xs text-slate-500 mb-1"></p>
<code className="text-sm text-red-600 bg-red-50 px-2 py-1 rounded">
{suggestion.originalValue}
</code>
</div>
<div>
<p className="text-xs text-slate-500 mb-1"></p>
<code className="text-sm text-emerald-600 bg-emerald-50 px-2 py-1 rounded">
{suggestion.suggestedValue}
</code>
</div>
</div>
<div className="p-3 bg-white rounded-lg">
<div className="flex items-center gap-2 text-xs text-slate-500 mb-1">
<Brain className="w-3 h-3" />
AI分析理由
</div>
<p className="text-sm text-slate-600">{suggestion.reason}</p>
</div>
<p className="text-xs text-slate-500 mt-2">: {suggestion.createdAt}</p>
</div>
</div>
<div className="flex items-center gap-2">
<Button size="sm" variant="outline" className="text-emerald-600 bg-transparent">
<Check className="w-4 h-4" />
</Button>
<Button size="sm" variant="outline" className="text-red-600 bg-transparent">
<X className="w-4 h-4" />
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => {
setSelectedSuggestion(suggestion)
setPreviewDialogOpen(true)
}}
>
<Eye className="w-4 h-4" />
</Button>
</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</TabsContent>
{/* 操作历史 */}
<TabsContent value="history" className="mt-4">
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardHeader className="pb-3">
<CardTitle className="text-base"></CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
{cleaningHistory.map((item) => (
<div key={item.id} className="flex items-center justify-between p-3 bg-slate-50/80 rounded-lg">
<div className="flex items-center gap-3">
<Badge variant="outline">{item.rule}</Badge>
<Badge
className={`${
item.action === "approved" ? "bg-emerald-100 text-emerald-700" : "bg-red-100 text-red-700"
}`}
>
{item.action === "approved" ? "通过" : "拒绝"}
</Badge>
<span className="text-sm text-slate-600">{item.count} </span>
{item.reason && <span className="text-sm text-slate-500">: {item.reason}</span>}
</div>
<div className="flex items-center gap-4 text-sm text-slate-500">
<span>{item.operator}</span>
<span>{item.time}</span>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
{/* 创建AI规则弹窗 */}
<Dialog open={createRuleDialogOpen} onOpenChange={setCreateRuleDialogOpen}>
<DialogContent className="sm:max-w-2xl">
<DialogHeader>
<DialogTitle>AI清洗规则</DialogTitle>
<DialogDescription>AI模型</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label></Label>
<Input
placeholder="例如:用户数据智能清洗"
value={newRule.name}
onChange={(e) => setNewRule({ ...newRule, name: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label>AI模型</Label>
<Select value={newRule.model} onValueChange={(v) => setNewRule({ ...newRule, model: v })}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="gpt-4">GPT-4</SelectItem>
<SelectItem value="gpt-4-turbo">GPT-4 Turbo</SelectItem>
<SelectItem value="claude-3">Claude 3</SelectItem>
<SelectItem value="qwen-max"> Max</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label></Label>
<Select
value={newRule.dataSource}
onValueChange={(v) => setNewRule({ ...newRule, dataSource: v, targetTable: "" })}
>
<SelectTrigger>
<SelectValue placeholder="从数据治理模块选择" />
</SelectTrigger>
<SelectContent>
{availableDataSources.map((ds) => (
<SelectItem key={ds.id} value={ds.name}>
<div className="flex items-center gap-2">
<Database className="w-4 h-4" />
<span>{ds.name}</span>
<Badge variant="secondary" className="text-xs">
{ds.type}
</Badge>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label></Label>
<Select
value={newRule.targetTable}
onValueChange={(v) => setNewRule({ ...newRule, targetTable: v })}
disabled={!newRule.dataSource}
>
<SelectTrigger>
<SelectValue placeholder="选择要清洗的表" />
</SelectTrigger>
<SelectContent>
{getTablesForDataSource(newRule.dataSource).map((table) => (
<SelectItem key={table} value={table}>
{table}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-2">
<Label>AI提示词</Label>
<Textarea
placeholder="描述你希望AI如何清洗数据。例如分析用户数据表识别以下问题并提供修正建议..."
value={newRule.prompt}
onChange={(e) => setNewRule({ ...newRule, prompt: e.target.value })}
rows={4}
/>
<p className="text-xs text-slate-500"></p>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label> ()</Label>
<div className="flex items-center gap-4">
<Slider
value={[newRule.confidenceThreshold]}
onValueChange={(v) => setNewRule({ ...newRule, confidenceThreshold: v[0] })}
min={0.1}
max={1}
step={0.1}
className="flex-1"
/>
<span className="w-12 text-sm font-medium">{newRule.confidenceThreshold}</span>
</div>
</div>
<div className="space-y-2">
<Label></Label>
<div className="flex items-center gap-4">
<Slider
value={[newRule.autoApproveThreshold]}
onValueChange={(v) => setNewRule({ ...newRule, autoApproveThreshold: v[0] })}
min={0.1}
max={1}
step={0.1}
className="flex-1"
/>
<span className="w-12 text-sm font-medium">{newRule.autoApproveThreshold}</span>
</div>
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setCreateRuleDialogOpen(false)}>
</Button>
<Button onClick={() => setCreateRuleDialogOpen(false)}>
<Sparkles className="w-4 h-4 mr-2" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* 规则设置弹窗 */}
<Dialog open={settingsDialogOpen} onOpenChange={setSettingsDialogOpen}>
<DialogContent className="sm:max-w-2xl">
<DialogHeader>
<DialogTitle> - {selectedRule?.name}</DialogTitle>
</DialogHeader>
{selectedRule && (
<div className="space-y-4 py-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label></Label>
<Input defaultValue={selectedRule.name} />
</div>
<div className="space-y-2">
<Label>AI模型</Label>
<Select defaultValue={selectedRule.model.toLowerCase().replace(" ", "-")}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="gpt-4">GPT-4</SelectItem>
<SelectItem value="claude-3">Claude 3</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-2">
<Label></Label>
<Input defaultValue={selectedRule.dataSource} disabled />
</div>
<div className="space-y-2">
<Label>AI提示词</Label>
<Textarea defaultValue={selectedRule.prompt} rows={4} />
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label></Label>
<div className="flex items-center gap-4">
<Slider
defaultValue={[selectedRule.confidenceThreshold]}
min={0.1}
max={1}
step={0.1}
className="flex-1"
/>
<span className="w-12 text-sm">{selectedRule.confidenceThreshold}</span>
</div>
</div>
<div className="space-y-2">
<Label></Label>
<div className="flex items-center gap-4">
<Slider
defaultValue={[selectedRule.autoApproveThreshold]}
min={0.1}
max={1}
step={0.1}
className="flex-1"
/>
<span className="w-12 text-sm">{selectedRule.autoApproveThreshold}</span>
</div>
</div>
</div>
</div>
)}
<DialogFooter>
<Button variant="outline" onClick={() => setSettingsDialogOpen(false)}>
</Button>
<Button onClick={() => setSettingsDialogOpen(false)}></Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* 预览弹窗 */}
<Dialog open={previewDialogOpen} onOpenChange={setPreviewDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
{selectedSuggestion && (
<div className="space-y-4 py-4">
<div className="grid grid-cols-2 gap-4">
<div className="p-3 bg-slate-50 rounded-lg">
<p className="text-xs text-slate-500"></p>
<p className="font-medium">{selectedSuggestion.ruleName}</p>
</div>
<div className="p-3 bg-slate-50 rounded-lg">
<p className="text-xs text-slate-500"></p>
<p className="font-medium">{selectedSuggestion.field}</p>
</div>
</div>
<div className="p-4 bg-slate-50 rounded-lg">
<p className="text-xs text-slate-500 mb-2"></p>
<div className="flex items-center gap-4">
<code className="flex-1 p-2 bg-red-50 text-red-600 rounded text-sm">
{selectedSuggestion.originalValue}
</code>
<span className="text-slate-400"></span>
<code className="flex-1 p-2 bg-emerald-50 text-emerald-600 rounded text-sm">
{selectedSuggestion.suggestedValue}
</code>
</div>
</div>
<div className="p-4 bg-slate-50 rounded-lg">
<div className="flex items-center gap-2 text-xs text-slate-500 mb-1">
<Brain className="w-3 h-3" />
AI分析理由
</div>
<p className="text-sm">{selectedSuggestion.reason}</p>
</div>
<div className="flex items-center justify-between text-sm text-slate-500">
<span>: {selectedSuggestion.affectedRows}</span>
<span>: {(selectedSuggestion.confidence * 100).toFixed(0)}%</span>
</div>
</div>
)}
<DialogFooter>
<Button
variant="outline"
className="text-red-600 bg-transparent"
onClick={() => setPreviewDialogOpen(false)}
>
<X className="w-4 h-4 mr-2" />
</Button>
<Button className="bg-emerald-600 hover:bg-emerald-700" onClick={() => setPreviewDialogOpen(false)}>
<Check className="w-4 h-4 mr-2" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
)
}