chore: 以本地为准,上传全部并替换 GitHub

This commit is contained in:
卡若
2026-02-03 11:36:53 +08:00
parent 1219166526
commit b404bf546e
131 changed files with 37618 additions and 3930 deletions

View File

@@ -1,396 +1,478 @@
"use client"
import { useState, useEffect } from "react"
import { useState, useEffect, useRef } from "react"
import {
Users,
Database,
RefreshCw,
Activity,
Globe,
Brain,
Tags,
LineChart,
Package,
ArrowUpRight,
Zap,
Send,
Loader2,
Search,
Phone,
User,
TrendingUp,
Settings,
ChevronRight,
Sparkles,
Target,
CheckCircle,
} from "lucide-react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { useRouter } from "next/navigation"
import { Input } from "@/components/ui/input"
import Link from "next/link"
interface SystemMetrics {
dataTaskSuccessRate: number
tagCoverageRate: number
valuePredictionAccuracy: number
apiDailyCallGrowth: number
interface ChatMessage {
role: "user" | "assistant" | "thinking"
content: string
timestamp: string
data?: any
thinking?: string // AI思考过程
portrait?: UserPortrait // 用户画像
}
interface DataFlowStats {
totalUsers: number
totalProjects: number
totalDataSources: number
totalTags: number
totalModels: number
totalApiCalls: number
// 标准用户画像模板
interface UserPortrait {
phone?: string
qq?: string
name?: string
gender?: string
province?: string
city?: string
level?: string
rfmScore?: number
tags?: string[]
behavior?: {
lastActive?: string
frequency?: string
}
dataCompleteness?: number
}
export default function OverviewPage() {
const router = useRouter()
const [searchQuery, setSearchQuery] = useState("")
const [isRefreshing, setIsRefreshing] = useState(false)
const [lastUpdate, setLastUpdate] = useState(new Date())
interface AIStatus {
status: string
database?: {
connected: boolean
totalUsers: number
latency: number
}
}
// 核心OKR指标 (按PRD定义)
const [metrics, setMetrics] = useState<SystemMetrics>({
dataTaskSuccessRate: 99.7,
tagCoverageRate: 82.3,
valuePredictionAccuracy: 87.5,
apiDailyCallGrowth: 32.8,
})
// 数据流统计
const [stats, setStats] = useState<DataFlowStats>({
totalUsers: 4028567890,
totalProjects: 1256,
totalDataSources: 28,
totalTags: 1892,
totalModels: 15,
totalApiCalls: 8956234,
})
// 实时数据流
const [dataFlows, setDataFlows] = useState([
{ source: "存客宝", type: "用户行为", count: 125680, status: "running" },
{ source: "触客宝", type: "互动数据", count: 89234, status: "running" },
{ source: "数智员工", type: "账号数据", count: 45678, status: "running" },
{ source: "外部API", type: "征信数据", count: 12890, status: "waiting" },
export default function HomePage() {
// AI对话状态
const [query, setQuery] = useState("")
const [loading, setLoading] = useState(false)
const [aiStatus, setAiStatus] = useState<AIStatus | null>(null)
const [messages, setMessages] = useState<ChatMessage[]>([
{
role: "assistant",
content: "你好我是神射手AI助手。\n\n你可以\n• 输入手机号查询用户画像\n• 输入QQ号查询关联信息\n• 问我任何关于用户数据的问题",
timestamp: new Date().toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }),
}
])
const messagesEndRef = useRef<HTMLDivElement>(null)
// 统计数据
const [stats, setStats] = useState({
totalUsers: 0,
totalDataSources: 0,
latency: 0,
})
// 获取AI状态
useEffect(() => {
const interval = setInterval(() => {
// 模拟实时数据更新
setStats((prev) => ({
...prev,
totalUsers: prev.totalUsers + Math.floor(Math.random() * 1000),
totalApiCalls: prev.totalApiCalls + Math.floor(Math.random() * 100),
}))
setDataFlows((prev) =>
prev.map((flow) => ({
...flow,
count: flow.count + Math.floor(Math.random() * 100),
})),
)
}, 3000)
return () => clearInterval(interval)
fetch("/api/ai-chat")
.then(res => res.json())
.then(data => {
setAiStatus(data)
if (data.database) {
setStats({
totalUsers: data.database.totalUsers || 0,
totalDataSources: 26,
latency: data.database.latency || 0,
})
}
})
.catch(console.error)
}, [])
const refreshData = async () => {
setIsRefreshing(true)
await new Promise((r) => setTimeout(r, 1000))
setLastUpdate(new Date())
setIsRefreshing(false)
// 自动滚动到底部
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
}, [messages])
// 解析用户画像数据
const parsePortrait = (data: any): UserPortrait | undefined => {
if (!data) return undefined
return {
phone: data.phone || data.,
qq: data.qq || data.QQ,
name: data.name || data.,
gender: data.gender || data.,
province: data.province || data.,
city: data.city || data.,
level: data.level || data.user_level || data.,
rfmScore: data.rfm_score || data.rfmScore || data.RFM评分,
tags: data.tags || [],
behavior: {
lastActive: data.last_active || data.,
frequency: data.frequency || data.,
},
dataCompleteness: data.data_completeness || data.,
}
}
// 生成思考过程
const generateThinking = (query: string): string => {
const isPhone = /^1[3-9]\d{9}$/.test(query.replace(/\s/g, ''))
const isQQ = /^\d{5,11}$/.test(query.replace(/\s|qq/gi, ''))
if (isPhone) {
return `🔍 识别到手机号查询\n→ 拆解意图: 查询用户画像\n→ 数据源: KR.用户估值, KR_腾讯\n→ 执行: 手机号精确匹配\n→ 输出: 标准用户画像模板`
} else if (isQQ) {
return `🔍 识别到QQ号查询\n→ 拆解意图: QQ关联查询\n→ 数据源: KR_腾讯.qq_phone\n→ 执行: QQ号精确匹配\n→ 输出: 关联手机号 + 用户画像`
} else {
return `🔍 自然语言理解中...\n→ 拆解意图: ${query.slice(0, 20)}...\n→ 思考策略: 语义分析\n→ 执行: Skill查询`
}
}
// 发送消息
const handleSend = async () => {
if (!query.trim() || loading) return
const userMessage: ChatMessage = {
role: "user",
content: query,
timestamp: new Date().toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }),
}
// 添加思考过程消息
const thinkingMessage: ChatMessage = {
role: "thinking",
content: generateThinking(query),
timestamp: new Date().toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }),
}
setMessages(prev => [...prev, userMessage, thinkingMessage])
setQuery("")
setLoading(true)
try {
const response = await fetch("/api/ai-chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: query })
})
const data = await response.json()
// 解析用户画像
const portrait = parsePortrait(data.response?.data)
setMessages(prev => {
// 移除思考消息,添加结果
const filtered = prev.filter(m => m.role !== "thinking")
return [...filtered, {
role: "assistant",
content: data.success ? data.response.content : `查询失败: ${data.error || "未知错误"}`,
timestamp: new Date().toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }),
data: data.response?.data,
portrait,
thinking: generateThinking(query),
}]
})
} catch (error: any) {
setMessages(prev => {
const filtered = prev.filter(m => m.role !== "thinking")
return [...filtered, {
role: "assistant",
content: `网络错误: ${error.message}`,
timestamp: new Date().toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" })
}]
})
} finally {
setLoading(false)
}
}
// 快捷查询
const quickQuery = (q: string) => {
setQuery(q)
setTimeout(() => handleSend(), 100)
}
const formatNumber = (num: number): string => {
if (num >= 1000000000) return `${(num / 1000000000).toFixed(2)}B`
if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`
if (num >= 1000) return `${(num / 1000).toFixed(1)}K`
return num.toString()
if (num >= 1000) return `${(num / 1000).toFixed(0)}K`
return num.toLocaleString()
}
// 5大功能模块快捷入口按HTML文档重构
// 核心功能模块
const modules = [
{
title: "数据接入",
icon: Database,
color: "text-blue-600",
bg: "bg-blue-100",
color: "from-blue-500 to-cyan-500",
href: "/data-ingestion/sources",
desc: "数据源管理、清洗规则、任务调度、数据血缘",
stats: `${stats.totalDataSources}个数据源`,
desc: "数据源 · 清洗 · 调度",
},
{
title: "标签画像",
icon: Tags,
color: "text-green-600",
bg: "bg-green-100",
href: "/tag-portrait/tags",
desc: "标签体系、用户画像、人群圈选",
stats: `${stats.totalTags}个标签`,
color: "from-green-500 to-emerald-500",
href: "/tag-portrait",
desc: "标签 · 画像 · 流量池",
},
{
title: "AI Agent",
icon: Brain,
color: "text-purple-600",
bg: "bg-purple-100",
href: "/ai-agent/chat",
desc: "智能对话、AI打标、AI清洗、自然语言查询",
stats: "5大AI能力",
},
{
title: "数据输出",
title: "数据市场",
icon: Package,
color: "text-orange-600",
bg: "bg-orange-100",
href: "/data-output/packages",
desc: "流量包、API市场、数据订阅",
stats: `${formatNumber(stats.totalApiCalls)}次调用`,
},
{
title: "系统监控",
icon: LineChart,
color: "text-cyan-600",
bg: "bg-cyan-100",
href: "/system/health",
desc: "系统健康、告警中心、操作日志",
stats: "实时监控",
color: "from-orange-500 to-red-500",
href: "/data-market",
desc: "API · 订阅 · 变现",
},
]
return (
<div className="space-y-6 p-6">
{/* Header */}
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50/30 to-purple-50/20 p-4 md:p-6">
{/* 顶部状态栏 */}
<div className="flex items-center justify-between mb-4">
<div>
<h1 className="text-3xl font-bold text-gray-900"></h1>
<p className="text-gray-500 mt-1"></p>
<h1 className="text-xl font-bold text-gray-900"></h1>
<p className="text-xs text-gray-500"></p>
</div>
<div className="flex items-center gap-3">
<Badge variant="outline" className="bg-green-50 text-green-600 border-green-200 px-3 py-1">
<Activity className="w-3 h-3 mr-1" />
<div className="flex items-center gap-2">
<Badge className={`${aiStatus?.status === 'online' ? 'bg-green-100 text-green-700' : 'bg-yellow-100 text-yellow-700'} text-xs`}>
<div className={`w-1.5 h-1.5 rounded-full ${aiStatus?.status === 'online' ? 'bg-green-500' : 'bg-yellow-500'} mr-1`}></div>
{stats.latency}ms
</Badge>
<span className="text-xs text-gray-400"> {lastUpdate.toLocaleTimeString()}</span>
<Button variant="outline" size="sm" onClick={refreshData} disabled={isRefreshing} className="bg-white/50">
<RefreshCw className={`w-4 h-4 mr-2 ${isRefreshing ? "animate-spin" : ""}`} />
</Button>
<Link href="/monitoring/health">
<Button variant="ghost" size="icon" className="h-8 w-8">
<Activity className="h-4 w-4" />
</Button>
</Link>
<Link href="/settings">
<Button variant="ghost" size="icon" className="h-8 w-8">
<Settings className="h-4 w-4" />
</Button>
</Link>
</div>
</div>
{/* 核心OKR指标 */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<Card className="border-none shadow-sm bg-white/60 backdrop-blur-sm">
<CardContent className="p-4">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-gray-500"></span>
<Badge className="bg-green-100 text-green-700 text-xs">99.5%</Badge>
{/* 统计卡片 */}
<div className="grid grid-cols-3 gap-3 mb-4">
<Card className="border-0 shadow-sm bg-white/70">
<CardContent className="p-3">
<div className="flex items-center gap-2">
<Users className="h-4 w-4 text-blue-500" />
<div>
<div className="text-lg font-bold">{formatNumber(stats.totalUsers)}</div>
<div className="text-xs text-gray-500"></div>
</div>
</div>
<div className="text-3xl font-bold text-gray-900">{metrics.dataTaskSuccessRate}%</div>
<Progress value={metrics.dataTaskSuccessRate} className="h-2 mt-2" />
</CardContent>
</Card>
<Card className="border-none shadow-sm bg-white/60 backdrop-blur-sm">
<CardContent className="p-4">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-gray-500"></span>
<Badge className="bg-blue-100 text-blue-700 text-xs">80%</Badge>
<Card className="border-0 shadow-sm bg-white/70">
<CardContent className="p-3">
<div className="flex items-center gap-2">
<Database className="h-4 w-4 text-green-500" />
<div>
<div className="text-lg font-bold">{stats.totalDataSources}</div>
<div className="text-xs text-gray-500"></div>
</div>
</div>
<div className="text-3xl font-bold text-gray-900">{metrics.tagCoverageRate}%</div>
<Progress value={metrics.tagCoverageRate} className="h-2 mt-2" />
</CardContent>
</Card>
<Card className="border-none shadow-sm bg-white/60 backdrop-blur-sm">
<CardContent className="p-4">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-gray-500"></span>
<Badge className="bg-purple-100 text-purple-700 text-xs">85%</Badge>
<Card className="border-0 shadow-sm bg-white/70">
<CardContent className="p-3">
<div className="flex items-center gap-2">
<Zap className="h-4 w-4 text-orange-500" />
<div>
<div className="text-lg font-bold">{stats.latency}ms</div>
<div className="text-xs text-gray-500"></div>
</div>
</div>
<div className="text-3xl font-bold text-gray-900">{metrics.valuePredictionAccuracy}%</div>
<Progress value={metrics.valuePredictionAccuracy} className="h-2 mt-2" />
</CardContent>
</Card>
<Card className="border-none shadow-sm bg-white/60 backdrop-blur-sm">
<CardContent className="p-4">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-gray-500">API调用增长</span>
<Badge className="bg-orange-100 text-orange-700 text-xs">30%QoQ</Badge>
</div>
<div className="text-3xl font-bold text-gray-900">+{metrics.apiDailyCallGrowth}%</div>
<Progress value={metrics.apiDailyCallGrowth} className="h-2 mt-2" />
</CardContent>
</Card>
</div>
{/* 用户资产总览 */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<Card className="lg:col-span-2 border-none shadow-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-white overflow-hidden relative">
<div className="absolute top-0 right-0 w-64 h-64 bg-white/10 rounded-full -mr-16 -mt-16 blur-3xl" />
<CardHeader className="pb-2 relative z-10">
<CardTitle className="flex items-center gap-2 text-blue-100">
<Users className="w-5 h-5" />
</CardTitle>
</CardHeader>
<CardContent className="relative z-10">
<div className="flex items-baseline gap-2 mb-4">
<span className="text-5xl font-bold">{formatNumber(stats.totalUsers)}</span>
<span className="text-blue-100"></span>
{/* AI对话区域 - 核心功能 */}
<Card className="border-0 shadow-lg bg-white/90 backdrop-blur mb-4 flex flex-col" style={{ height: 'calc(100vh - 340px)', minHeight: '300px' }}>
<CardHeader className="pb-2 border-b flex-shrink-0">
<CardTitle className="text-sm font-medium flex items-center justify-between">
<div className="flex items-center gap-2">
<Brain className="h-4 w-4 text-purple-500" />
AI
</div>
<div className="grid grid-cols-3 gap-4 pt-4 border-t border-white/20">
<div>
<div className="text-2xl font-bold text-green-300">+2.3%</div>
<div className="text-sm text-blue-100"></div>
</div>
<div>
<div className="text-2xl font-bold">{stats.totalProjects.toLocaleString()}</div>
<div className="text-sm text-blue-100"></div>
</div>
<div>
<div className="text-2xl font-bold">125.6B</div>
<div className="text-sm text-blue-100">()</div>
</div>
</div>
</CardContent>
</Card>
{/* 实时数据流 */}
<Card className="border-none shadow-md bg-white/60 backdrop-blur-md">
<CardHeader className="pb-2">
<CardTitle className="text-gray-700 text-sm font-medium flex items-center gap-2">
<Zap className="w-4 h-4 text-yellow-500" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
{dataFlows.map((flow, i) => (
<div key={i} className="flex items-center justify-between p-2 rounded-lg bg-gray-50">
<div className="flex items-center gap-2">
<div
className={`w-2 h-2 rounded-full ${flow.status === "running" ? "bg-green-500 animate-pulse" : "bg-yellow-500"}`}
/>
<div>
<div className="text-sm font-medium text-gray-900">{flow.source}</div>
<div className="text-xs text-gray-500">{flow.type}</div>
</div>
</div>
<div className="text-right">
<div className="text-sm font-semibold text-gray-900">{flow.count.toLocaleString()}</div>
<div className="text-xs text-gray-500">/</div>
</div>
</div>
))}
</CardContent>
</Card>
</div>
{/* 5大功能模块入口 */}
<div>
<h2 className="text-lg font-semibold text-gray-900 mb-4"></h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4">
{modules.map((module, index) => (
<Link key={index} href={module.href}>
<Card className="h-full cursor-pointer hover:shadow-lg transition-all duration-300 border-none shadow-sm bg-white/60 backdrop-blur-md group hover:-translate-y-1">
<CardContent className="p-5">
<div
className={`w-12 h-12 rounded-2xl ${module.bg} flex items-center justify-center mb-3 group-hover:scale-110 transition-transform`}
>
<module.icon className={`w-6 h-6 ${module.color}`} />
</div>
<h3 className="font-bold text-gray-900 mb-1">{module.title}</h3>
<p className="text-xs text-gray-500 mb-2 line-clamp-2">{module.desc}</p>
<div className="flex items-center justify-between">
<Badge variant="secondary" className="text-xs">
{module.stats}
</Badge>
<ArrowUpRight className="w-4 h-4 text-gray-400 group-hover:text-blue-500 transition-colors" />
</div>
</CardContent>
</Card>
</Link>
))}
</div>
</div>
{/* 系统关联图说明 */}
<Card className="border-none shadow-md bg-white/60 backdrop-blur-md">
<CardHeader>
<CardTitle className="text-gray-700 text-sm font-medium flex items-center gap-2">
<Globe className="w-4 h-4" />
<Badge variant="outline" className="text-xs">
{formatNumber(stats.totalUsers)}
</Badge>
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{/* 数据流入 */}
<div className="space-y-3">
<h4 className="font-semibold text-gray-900 flex items-center gap-2">
<ArrowUpRight className="w-4 h-4 text-blue-500" />
</h4>
<div className="space-y-2">
{[
{ name: "存客宝", desc: "用户基础信息、交易流水" },
{ name: "触客宝", desc: "互动行为、活动参与" },
{ name: "数智员工", desc: "账号信息、粉丝画像" },
{ name: "外部系统", desc: "征信数据、行业数据" },
].map((item, i) => (
<div key={i} className="flex items-start gap-2 p-2 rounded-lg bg-blue-50">
<CheckCircle className="w-4 h-4 text-blue-500 mt-0.5" />
<div>
<div className="text-sm font-medium text-gray-900">{item.name}</div>
<div className="text-xs text-gray-500">{item.desc}</div>
</div>
{/* 消息列表 */}
<CardContent className="flex-1 overflow-y-auto p-4 space-y-3">
{messages.map((msg, i) => (
<div key={i} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
{/* 思考过程 */}
{msg.role === 'thinking' && (
<div className="max-w-[85%] rounded-2xl px-4 py-2.5 bg-yellow-50 border border-yellow-200">
<div className="flex items-center gap-2 text-yellow-700 text-xs mb-1">
<Sparkles className="h-3 w-3 animate-pulse" />
AI思考中...
</div>
))}
<div className="text-xs text-yellow-800 whitespace-pre-wrap font-mono">{msg.content}</div>
</div>
)}
{/* 用户消息 */}
{msg.role === 'user' && (
<div className="max-w-[85%] rounded-2xl px-4 py-2.5 bg-gradient-to-r from-blue-500 to-purple-500 text-white">
<div className="text-sm whitespace-pre-wrap">{msg.content}</div>
<div className="text-xs mt-1 text-blue-100">{msg.timestamp}</div>
</div>
)}
{/* AI回复 */}
{msg.role === 'assistant' && (
<div className="max-w-[85%] space-y-2">
{/* 思考过程折叠显示 */}
{msg.thinking && (
<div className="rounded-xl px-3 py-2 bg-yellow-50 border border-yellow-100 text-xs">
<div className="text-yellow-600 font-medium mb-1">💭 </div>
<div className="text-yellow-800 whitespace-pre-wrap font-mono">{msg.thinking}</div>
</div>
)}
{/* 用户画像卡片 */}
{msg.portrait && (msg.portrait.phone || msg.portrait.qq) && (
<div className="rounded-xl p-3 bg-gradient-to-r from-purple-50 to-blue-50 border border-purple-100">
<div className="flex items-center gap-2 mb-2">
<div className="w-10 h-10 rounded-full bg-gradient-to-r from-purple-500 to-blue-500 flex items-center justify-center text-white font-bold">
{(msg.portrait.name || '?')[0]}
</div>
<div>
<div className="font-semibold text-gray-900">{msg.portrait.name || '未知用户'}</div>
<div className="text-xs text-gray-500">
{msg.portrait.phone && <span className="mr-2">📱 {msg.portrait.phone}</span>}
{msg.portrait.qq && <span>QQ: {msg.portrait.qq}</span>}
</div>
</div>
{msg.portrait.level && (
<Badge className="ml-auto bg-purple-100 text-purple-700">{msg.portrait.level}</Badge>
)}
</div>
<div className="grid grid-cols-3 gap-2 text-xs">
{msg.portrait.rfmScore !== undefined && (
<div className="p-2 rounded bg-white/80">
<div className="text-gray-500">RFM</div>
<div className="font-bold text-purple-600">{msg.portrait.rfmScore}</div>
</div>
)}
{msg.portrait.province && (
<div className="p-2 rounded bg-white/80">
<div className="text-gray-500"></div>
<div className="font-medium">{msg.portrait.province} {msg.portrait.city}</div>
</div>
)}
{msg.portrait.dataCompleteness !== undefined && (
<div className="p-2 rounded bg-white/80">
<div className="text-gray-500"></div>
<div className="font-medium text-green-600">{msg.portrait.dataCompleteness}%</div>
</div>
)}
</div>
{msg.portrait.tags && msg.portrait.tags.length > 0 && (
<div className="flex flex-wrap gap-1 mt-2">
{msg.portrait.tags.slice(0, 5).map((tag, ti) => (
<Badge key={ti} variant="secondary" className="text-xs">{tag}</Badge>
))}
</div>
)}
</div>
)}
{/* 文本回复 */}
<div className="rounded-2xl px-4 py-2.5 bg-gray-100 text-gray-800">
<div className="text-sm whitespace-pre-wrap">{msg.content}</div>
<div className="text-xs mt-1 text-gray-400">{msg.timestamp}</div>
</div>
</div>
)}
</div>
))}
{loading && (
<div className="flex justify-start">
<div className="bg-gray-100 rounded-2xl px-4 py-2.5 flex items-center gap-2 text-gray-500">
<Loader2 className="w-4 h-4 animate-spin" />
<span className="text-sm">...</span>
</div>
</div>
{/* 神射手处理 */}
<div className="space-y-3">
<h4 className="font-semibold text-gray-900 flex items-center gap-2">
<Target className="w-4 h-4 text-purple-500" />
</h4>
<div className="space-y-2">
{[
{ name: "数据治理", desc: "清洗、标准化、质量监控" },
{ name: "标签计算", desc: "规则/模型/AI标签生成" },
{ name: "价值评估", desc: "CLV、RFM、流失预测" },
{ name: "AI洞察", desc: "智能分析、报告生成" },
].map((item, i) => (
<div key={i} className="flex items-start gap-2 p-2 rounded-lg bg-purple-50">
<Zap className="w-4 h-4 text-purple-500 mt-0.5" />
<div>
<div className="text-sm font-medium text-gray-900">{item.name}</div>
<div className="text-xs text-gray-500">{item.desc}</div>
</div>
</div>
))}
</div>
</div>
{/* 服务输出 */}
<div className="space-y-3">
<h4 className="font-semibold text-gray-900 flex items-center gap-2">
<Package className="w-4 h-4 text-green-500" />
</h4>
<div className="space-y-2">
{[
{ name: "存客宝/触客宝", desc: "画像API、人群包推送" },
{ name: "数智员工", desc: "账号价值评估API" },
{ name: "聚宝盆", desc: "核心指标聚合接口" },
{ name: "外部客户", desc: "行业报告、趋势分析包" },
].map((item, i) => (
<div key={i} className="flex items-start gap-2 p-2 rounded-lg bg-green-50">
<ArrowUpRight className="w-4 h-4 text-green-500 mt-0.5" />
<div>
<div className="text-sm font-medium text-gray-900">{item.name}</div>
<div className="text-xs text-gray-500">{item.desc}</div>
</div>
</div>
))}
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</CardContent>
{/* 快捷查询 */}
<div className="px-4 py-2 border-t flex-shrink-0">
<div className="flex gap-2 overflow-x-auto pb-1">
<Button variant="outline" size="sm" className="text-xs shrink-0 h-7" onClick={() => quickQuery("13407000001")}>
<Phone className="w-3 h-3 mr-1" />
</Button>
<Button variant="outline" size="sm" className="text-xs shrink-0 h-7" onClick={() => quickQuery("28533368 qq")}>
<User className="w-3 h-3 mr-1" /> QQ
</Button>
<Button variant="outline" size="sm" className="text-xs shrink-0 h-7" onClick={() => quickQuery("系统状态")}>
<Activity className="w-3 h-3 mr-1" />
</Button>
<Button variant="outline" size="sm" className="text-xs shrink-0 h-7" onClick={() => quickQuery("高价值用户")}>
<TrendingUp className="w-3 h-3 mr-1" />
</Button>
</div>
</div>
{/* 输入框 */}
<div className="p-3 border-t flex-shrink-0">
<div className="flex gap-2">
<Input
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyPress={(e) => e.key === "Enter" && handleSend()}
placeholder="输入手机号、QQ号或问题..."
className="flex-1 h-10 bg-gray-50 border-0"
/>
<Button
onClick={handleSend}
disabled={loading || !query.trim()}
className="h-10 px-4 bg-gradient-to-r from-blue-500 to-purple-500"
>
{loading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Send className="w-4 h-4" />}
</Button>
</div>
</div>
</Card>
{/* 功能模块入口 */}
<div className="grid grid-cols-3 gap-3">
{modules.map((module, i) => (
<Link key={i} href={module.href}>
<Card className="border-0 shadow-sm bg-white/70 cursor-pointer hover:shadow-md transition-all group">
<CardContent className="p-3">
<div className={`w-10 h-10 rounded-xl bg-gradient-to-r ${module.color} flex items-center justify-center mb-2 group-hover:scale-110 transition-transform`}>
<module.icon className="w-5 h-5 text-white" />
</div>
<h3 className="font-medium text-sm text-gray-900">{module.title}</h3>
<p className="text-xs text-gray-500 mt-0.5">{module.desc}</p>
</CardContent>
</Card>
</Link>
))}
</div>
</div>
)
}