chore: 以本地为准,上传全部并替换 GitHub
This commit is contained in:
@@ -1,23 +1,55 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { useState } from "react"
|
||||
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 } from "lucide-react"
|
||||
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 [chatHistory, setChatHistory] = 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助手,可以帮助你分析用户数据、生成报告、预测趋势。有什么我可以帮助你的吗?",
|
||||
timestamp: "10:30",
|
||||
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" />,
|
||||
@@ -53,24 +85,52 @@ export default function AIAssistant() {
|
||||
},
|
||||
]
|
||||
|
||||
const quickQuestions = ["分析今日新增用户特征", "预测本月用户增长趋势", "高价值用户流失预警", "生成本周运营报告"]
|
||||
const quickQuestions = ["查 13407000001", "系统状态", "RFM分析", "高价值用户 TOP10", "帮助"]
|
||||
|
||||
const handleSend = () => {
|
||||
if (!message.trim()) return
|
||||
setChatHistory([
|
||||
...chatHistory,
|
||||
{
|
||||
role: "user",
|
||||
content: message,
|
||||
timestamp: new Date().toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }),
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: "正在分析您的问题,请稍候...",
|
||||
timestamp: new Date().toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }),
|
||||
},
|
||||
])
|
||||
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 (
|
||||
@@ -97,22 +157,24 @@ export default function AIAssistant() {
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-600">系统状态</span>
|
||||
<Badge variant="outline" className="border-green-200 text-green-600 bg-green-50">
|
||||
<div className="w-2 h-2 rounded-full bg-green-500 animate-pulse mr-2"></div>
|
||||
运行中
|
||||
<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">v3.5</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">0.8s</span>
|
||||
<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">94.2%</span>
|
||||
<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>
|
||||
@@ -184,13 +246,24 @@ export default function AIAssistant() {
|
||||
<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">{chat.content}</div>
|
||||
<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">
|
||||
@@ -224,9 +297,10 @@ export default function AIAssistant() {
|
||||
/>
|
||||
<Button
|
||||
onClick={handleSend}
|
||||
className="bg-gradient-to-r from-blue-500 to-purple-500 h-[88px] w-20 shadow-md hover:shadow-lg transition-shadow"
|
||||
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"
|
||||
>
|
||||
<Send className="w-6 h-6" />
|
||||
{isLoading ? <Loader2 className="w-6 h-6 animate-spin" /> : <Send className="w-6 h-6" />}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
Reference in New Issue
Block a user