313 lines
13 KiB
TypeScript
313 lines
13 KiB
TypeScript
"use client"
|
||
|
||
import Link from "next/link"
|
||
import { useState, useEffect, useRef } from "react"
|
||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||
import { Badge } from "@/components/ui/badge"
|
||
import { Button } from "@/components/ui/button"
|
||
import { Textarea } from "@/components/ui/textarea"
|
||
import { Cpu, BarChart3, TrendingUp, Users, Target, Brain, ArrowRight, Send, Sparkles, Loader2 } from "lucide-react"
|
||
|
||
interface ChatMessage {
|
||
role: "user" | "assistant"
|
||
content: string
|
||
timestamp: string
|
||
}
|
||
|
||
interface AIStatus {
|
||
status: string
|
||
model: string
|
||
database?: {
|
||
connected: boolean
|
||
totalUsers: number
|
||
latency: number
|
||
}
|
||
}
|
||
|
||
export default function AIAssistant() {
|
||
const [message, setMessage] = useState("")
|
||
const [isLoading, setIsLoading] = useState(false)
|
||
const [aiStatus, setAIStatus] = useState<AIStatus | null>(null)
|
||
const chatEndRef = useRef<HTMLDivElement>(null)
|
||
const [chatHistory, setChatHistory] = useState<ChatMessage[]>([
|
||
{
|
||
role: "assistant",
|
||
content: "你好!我是神射手AI助手,可以帮助你查询用户数据、分析RFM估值。\n\n💡 试试:\n- 查 13407000001\n- 28533368 qq\n- 系统状态\n- 帮助",
|
||
timestamp: new Date().toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }),
|
||
},
|
||
])
|
||
|
||
// 获取 AI 状态
|
||
useEffect(() => {
|
||
fetch("/api/ai-chat")
|
||
.then(res => res.json())
|
||
.then(data => setAIStatus(data))
|
||
.catch(console.error)
|
||
}, [])
|
||
|
||
// 自动滚动到底部
|
||
useEffect(() => {
|
||
chatEndRef.current?.scrollIntoView({ behavior: "smooth" })
|
||
}, [chatHistory])
|
||
|
||
const aiFeatures = [
|
||
{
|
||
icon: <BarChart3 className="w-8 h-8" />,
|
||
title: "数据分析",
|
||
description: "自动分析用户行为,生成洞察报告",
|
||
color: "from-blue-500 to-cyan-500",
|
||
href: "/ai-assistant/data-analysis",
|
||
stats: { reports: 1258, insights: 856 },
|
||
},
|
||
{
|
||
icon: <TrendingUp className="w-8 h-8" />,
|
||
title: "趋势预测",
|
||
description: "预测用户行为和价值变化趋势",
|
||
color: "from-purple-500 to-pink-500",
|
||
href: "/ai-assistant/trend-prediction",
|
||
stats: { accuracy: 94.2, predictions: 2450 },
|
||
},
|
||
{
|
||
icon: <Users className="w-8 h-8" />,
|
||
title: "用户画像",
|
||
description: "智能生成用户群体画像",
|
||
color: "from-green-500 to-emerald-500",
|
||
href: "/ai-assistant/user-profiling",
|
||
stats: { profiles: 4285, segments: 128 },
|
||
},
|
||
{
|
||
icon: <Target className="w-8 h-8" />,
|
||
title: "精准推荐",
|
||
description: "推荐最佳营销策略和用户群",
|
||
color: "from-orange-500 to-red-500",
|
||
href: "/ai-assistant/recommendation",
|
||
stats: { campaigns: 586, conversion: 18.5 },
|
||
},
|
||
]
|
||
|
||
const quickQuestions = ["查 13407000001", "系统状态", "RFM分析", "高价值用户 TOP10", "帮助"]
|
||
|
||
const handleSend = async () => {
|
||
if (!message.trim() || isLoading) return
|
||
|
||
const userMessage: ChatMessage = {
|
||
role: "user",
|
||
content: message,
|
||
timestamp: new Date().toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }),
|
||
}
|
||
|
||
setChatHistory(prev => [...prev, userMessage])
|
||
setMessage("")
|
||
setIsLoading(true)
|
||
|
||
try {
|
||
const response = await fetch("/api/ai-chat", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ message: userMessage.content })
|
||
})
|
||
|
||
const data = await response.json()
|
||
|
||
if (data.success && data.response) {
|
||
setChatHistory(prev => [...prev, {
|
||
role: "assistant",
|
||
content: data.response.content,
|
||
timestamp: data.response.timestamp || new Date().toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" })
|
||
}])
|
||
} else {
|
||
setChatHistory(prev => [...prev, {
|
||
role: "assistant",
|
||
content: `⚠️ ${data.error || "请求失败,请稍后重试"}`,
|
||
timestamp: new Date().toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" })
|
||
}])
|
||
}
|
||
} catch (error: any) {
|
||
setChatHistory(prev => [...prev, {
|
||
role: "assistant",
|
||
content: `⚠️ 网络错误: ${error.message}`,
|
||
timestamp: new Date().toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" })
|
||
}])
|
||
} finally {
|
||
setIsLoading(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-8">
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-blue-500 to-purple-500 flex items-center justify-center shadow-lg">
|
||
<Brain className="w-6 h-6 text-white" />
|
||
</div>
|
||
<div>
|
||
<h1 className="text-3xl font-bold text-gray-900">AI智能助手</h1>
|
||
<p className="text-gray-500">数据分析、报告生成与智能预测</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||
<Card className="bg-white/60 backdrop-blur-md border-gray-200 shadow-sm">
|
||
<CardHeader>
|
||
<CardTitle className="text-gray-900 flex items-center gap-2">
|
||
<Cpu className="w-5 h-5 text-green-600" />
|
||
AI运行状态
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-3">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-gray-600">系统状态</span>
|
||
<Badge variant="outline" className={`${aiStatus?.status === 'online' ? 'border-green-200 text-green-600 bg-green-50' : 'border-yellow-200 text-yellow-600 bg-yellow-50'}`}>
|
||
<div className={`w-2 h-2 rounded-full ${aiStatus?.status === 'online' ? 'bg-green-500' : 'bg-yellow-500'} animate-pulse mr-2`}></div>
|
||
{aiStatus?.status === 'online' ? '运行中' : '加载中'}
|
||
</Badge>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-gray-600">模型版本</span>
|
||
<span className="text-gray-900 font-semibold">{aiStatus?.model || '神射手 AI'}</span>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-gray-600">响应延迟</span>
|
||
<span className="text-gray-900 font-semibold">{aiStatus?.database?.latency || '--'}ms</span>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-gray-600">数据量</span>
|
||
<span className="text-green-600 font-semibold">
|
||
{aiStatus?.database?.totalUsers ? `${(aiStatus.database.totalUsers / 1e8).toFixed(1)}亿` : '--'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<div className="lg:col-span-2 grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
{aiFeatures.map((feature, index) => (
|
||
<Link key={index} href={feature.href}>
|
||
<Card className="hover:shadow-lg transition-all cursor-pointer h-full bg-white/60 backdrop-blur-md border-gray-200 shadow-sm group">
|
||
<CardContent className="p-6">
|
||
<div className="flex items-start gap-4 mb-4">
|
||
<div
|
||
className={`w-16 h-16 rounded-xl bg-gradient-to-br ${feature.color} flex items-center justify-center text-white shadow-md group-hover:scale-105 transition-transform`}
|
||
>
|
||
{feature.icon}
|
||
</div>
|
||
<div className="flex-1">
|
||
<div className="flex items-center justify-between mb-2">
|
||
<h3 className="text-xl font-bold text-gray-900">{feature.title}</h3>
|
||
<ArrowRight className="w-5 h-5 text-gray-400 group-hover:text-blue-500 transition-colors" />
|
||
</div>
|
||
<p className="text-gray-500 text-sm">{feature.description}</p>
|
||
</div>
|
||
</div>
|
||
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-gray-100">
|
||
{Object.entries(feature.stats).map(([key, value], i) => (
|
||
<div key={i}>
|
||
<div className="text-xs text-gray-500 mb-1">
|
||
{key === "reports"
|
||
? "报告数"
|
||
: key === "insights"
|
||
? "洞察数"
|
||
: key === "accuracy"
|
||
? "准确率"
|
||
: key === "predictions"
|
||
? "预测数"
|
||
: key === "profiles"
|
||
? "画像数"
|
||
: key === "segments"
|
||
? "分组数"
|
||
: key === "campaigns"
|
||
? "活动数"
|
||
: "转化率"}
|
||
</div>
|
||
<div className="text-lg font-bold text-gray-900">
|
||
{typeof value === "number" && value % 1 !== 0 ? `${value}%` : value}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</Link>
|
||
))}
|
||
</div>
|
||
|
||
<div className="lg:col-span-3">
|
||
<Card className="bg-white/60 backdrop-blur-md border-gray-200 shadow-sm h-[600px] flex flex-col">
|
||
<CardHeader className="border-b border-gray-100">
|
||
<CardTitle className="text-gray-900 flex items-center gap-2">
|
||
<BarChart3 className="w-5 h-5 text-blue-600" />
|
||
AI对话
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="p-6 flex flex-col flex-1 overflow-hidden">
|
||
<div className="flex-1 overflow-y-auto space-y-4 mb-6 pr-2">
|
||
{chatHistory.map((chat, index) => (
|
||
<div key={index} className={`flex ${chat.role === "user" ? "justify-end" : "justify-start"}`}>
|
||
<div
|
||
className={`max-w-[80%] rounded-2xl p-4 shadow-sm ${chat.role === "user" ? "bg-gradient-to-br from-blue-500 to-purple-500 text-white" : "bg-white border border-gray-100 text-gray-800"}`}
|
||
>
|
||
<div className="mb-1 whitespace-pre-wrap">{chat.content}</div>
|
||
<div className={`text-xs ${chat.role === "user" ? "text-blue-100" : "text-gray-400"}`}>
|
||
{chat.timestamp}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
{isLoading && (
|
||
<div className="flex justify-start">
|
||
<div className="bg-white border border-gray-100 rounded-2xl p-4 shadow-sm">
|
||
<div className="flex items-center gap-2 text-gray-500">
|
||
<Loader2 className="w-4 h-4 animate-spin" />
|
||
<span>正在查询中...</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
<div ref={chatEndRef} />
|
||
</div>
|
||
|
||
<div className="mb-4 flex flex-wrap gap-2">
|
||
{quickQuestions.map((question, index) => (
|
||
<Button
|
||
key={index}
|
||
variant="outline"
|
||
size="sm"
|
||
onClick={() => setMessage(question)}
|
||
className="bg-white hover:bg-gray-50 text-xs border-gray-200 text-gray-600"
|
||
>
|
||
<Sparkles className="w-3 h-3 mr-1 text-yellow-500" />
|
||
{question}
|
||
</Button>
|
||
))}
|
||
</div>
|
||
|
||
<div className="flex items-end gap-3">
|
||
<Textarea
|
||
value={message}
|
||
onChange={(e) => setMessage(e.target.value)}
|
||
onKeyPress={(e) => {
|
||
if (e.key === "Enter" && !e.shiftKey) {
|
||
e.preventDefault()
|
||
handleSend()
|
||
}
|
||
}}
|
||
placeholder="输入您的问题,按Enter发送..."
|
||
className="bg-white border-gray-200 text-gray-900 resize-none focus:ring-blue-500"
|
||
rows={3}
|
||
/>
|
||
<Button
|
||
onClick={handleSend}
|
||
disabled={isLoading || !message.trim()}
|
||
className="bg-gradient-to-r from-blue-500 to-purple-500 h-[88px] w-20 shadow-md hover:shadow-lg transition-shadow disabled:opacity-50"
|
||
>
|
||
{isLoading ? <Loader2 className="w-6 h-6 animate-spin" /> : <Send className="w-6 h-6" />}
|
||
</Button>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|