Files
users/app/data-market/api/page.tsx

453 lines
15 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, useEffect } 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 {
Server,
Copy,
CheckCircle2,
Key,
RefreshCw,
ExternalLink,
Code,
Zap,
Users,
Search,
Tags,
Brain,
Database,
FileText,
Package,
Activity,
} from "lucide-react"
// API端点接口
interface APIEndpoint {
id: string
method: 'GET' | 'POST' | 'PUT' | 'DELETE'
path: string
name: string
description: string
category: string
auth: boolean
params?: { name: string; type: string; required: boolean; desc: string }[]
response?: string
}
// API分类
const API_CATEGORIES = [
{ id: 'query', name: '用户查询', icon: Search },
{ id: 'tag', name: '标签服务', icon: Tags },
{ id: 'ai', name: 'AI服务', icon: Brain },
{ id: 'data', name: '数据服务', icon: Database },
{ id: 'report', name: '报告服务', icon: FileText },
{ id: 'package', name: '流量包', icon: Package },
]
// 预定义API端点
const API_ENDPOINTS: APIEndpoint[] = [
// 用户查询
{
id: 'api_1',
method: 'GET',
path: '/api/shensheshou/user',
name: '用户画像查询',
description: '根据手机号或QQ查询完整用户画像',
category: 'query',
auth: true,
params: [
{ name: 'phone', type: 'string', required: false, desc: '11位手机号' },
{ name: 'qq', type: 'string', required: false, desc: 'QQ号码' },
],
response: '{ "user": { "phone": "138xxx", "rfm": 85, "level": "A", "tags": [...] } }',
},
{
id: 'api_2',
method: 'POST',
path: '/api/shensheshou/users/batch',
name: '批量用户查询',
description: '批量查询多个用户的画像信息',
category: 'query',
auth: true,
params: [
{ name: 'phones', type: 'array', required: false, desc: '手机号数组' },
{ name: 'qqs', type: 'array', required: false, desc: 'QQ号数组' },
],
},
// 标签服务
{
id: 'api_3',
method: 'GET',
path: '/api/shensheshou/tags',
name: '标签列表',
description: '获取所有可用标签',
category: 'tag',
auth: true,
params: [
{ name: 'category', type: 'string', required: false, desc: '标签分类' },
],
},
{
id: 'api_4',
method: 'POST',
path: '/api/shensheshou/tags/apply',
name: '应用标签',
description: '为用户应用指定标签',
category: 'tag',
auth: true,
params: [
{ name: 'userId', type: 'string', required: true, desc: '用户ID' },
{ name: 'tags', type: 'array', required: true, desc: '标签ID数组' },
],
},
// AI服务
{
id: 'api_5',
method: 'POST',
path: '/api/shensheshou/chat',
name: 'AI对话',
description: '与神射手AI进行对话支持自然语言查询',
category: 'ai',
auth: true,
params: [
{ name: 'message', type: 'string', required: true, desc: '对话内容' },
],
response: '{ "success": true, "response": { "content": "查询结果...", "data": {...} } }',
},
{
id: 'api_6',
method: 'POST',
path: '/api/shensheshou/ai/analyze',
name: 'AI数据分析',
description: 'AI自动分析数据并生成洞察',
category: 'ai',
auth: true,
params: [
{ name: 'type', type: 'string', required: true, desc: '分析类型: rfm/behavior/preference' },
{ name: 'filters', type: 'object', required: false, desc: '筛选条件' },
],
},
{
id: 'api_7',
method: 'POST',
path: '/api/shensheshou/ai/tag',
name: 'AI智能打标',
description: 'AI自动为用户打标签',
category: 'ai',
auth: true,
params: [
{ name: 'source', type: 'string', required: true, desc: '数据源' },
{ name: 'model', type: 'string', required: false, desc: 'AI模型: qwen/deepseek' },
],
},
// 数据服务
{
id: 'api_8',
method: 'GET',
path: '/api/shensheshou/sources',
name: '数据源列表',
description: '获取所有数据源连接状态',
category: 'data',
auth: true,
},
{
id: 'api_9',
method: 'POST',
path: '/api/shensheshou/ingest',
name: '数据导入',
description: '导入外部数据并通过AI标签引擎处理',
category: 'data',
auth: true,
params: [
{ name: 'source', type: 'string', required: true, desc: '数据源标识' },
{ name: 'target', type: 'string', required: true, desc: '目标表' },
],
},
// 报告服务
{
id: 'api_10',
method: 'POST',
path: '/api/shensheshou/report/generate',
name: '生成报告',
description: 'AI自动生成数据分析报告',
category: 'report',
auth: true,
params: [
{ name: 'template', type: 'string', required: true, desc: '报告模板ID' },
{ name: 'dateRange', type: 'string', required: false, desc: '日期范围' },
],
},
// 流量包
{
id: 'api_11',
method: 'GET',
path: '/api/shensheshou/packages',
name: '流量包列表',
description: '获取所有流量包',
category: 'package',
auth: true,
},
{
id: 'api_12',
method: 'POST',
path: '/api/shensheshou/packages/create',
name: '创建流量包',
description: '根据筛选条件创建流量包',
category: 'package',
auth: true,
params: [
{ name: 'name', type: 'string', required: true, desc: '流量包名称' },
{ name: 'criteria', type: 'object', required: true, desc: '筛选条件' },
],
},
{
id: 'api_13',
method: 'POST',
path: '/api/shensheshou/packages/send',
name: '发送流量包',
description: '发送流量包到邮箱/飞书/微信',
category: 'package',
auth: true,
params: [
{ name: 'packageId', type: 'string', required: true, desc: '流量包ID' },
{ name: 'targets', type: 'array', required: true, desc: '发送目标' },
],
},
]
export default function APIServicePage() {
const [activeCategory, setActiveCategory] = useState('all')
const [apiBaseUrl, setApiBaseUrl] = useState('')
const [showKeyDialog, setShowKeyDialog] = useState(false)
const [copiedId, setCopiedId] = useState<string | null>(null)
useEffect(() => {
const host = typeof window !== 'undefined' ? window.location.origin : ''
setApiBaseUrl(host)
}, [])
const copyToClipboard = (text: string, id: string) => {
navigator.clipboard.writeText(text)
setCopiedId(id)
setTimeout(() => setCopiedId(null), 2000)
}
const getMethodColor = (method: string) => {
switch (method) {
case 'GET': return 'bg-green-100 text-green-700'
case 'POST': return 'bg-blue-100 text-blue-700'
case 'PUT': return 'bg-yellow-100 text-yellow-700'
case 'DELETE': return 'bg-red-100 text-red-700'
default: return 'bg-gray-100 text-gray-700'
}
}
const filteredEndpoints = activeCategory === 'all'
? API_ENDPOINTS
: API_ENDPOINTS.filter(e => e.category === activeCategory)
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50/30 to-purple-50/20">
<div className="p-6 space-y-6">
{/* 顶部标题 */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900">API服务</h1>
<p className="text-sm text-gray-500 mt-1">API</p>
</div>
<div className="flex items-center gap-3">
<Button variant="outline" onClick={() => setShowKeyDialog(true)}>
<Key className="h-4 w-4 mr-2" />
API密钥
</Button>
<Button variant="outline">
<ExternalLink className="h-4 w-4 mr-2" />
API文档
</Button>
</div>
</div>
{/* API基础信息 */}
<Card className="border-0 shadow-sm bg-gradient-to-r from-purple-50 to-blue-50">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<h3 className="font-semibold text-gray-900 mb-1">API基础地址</h3>
<div className="flex items-center gap-2">
<code className="bg-white px-4 py-2 rounded-lg text-purple-600 font-mono">
{apiBaseUrl || 'https://your-domain.com'}
</code>
<Button
variant="ghost"
size="sm"
onClick={() => copyToClipboard(apiBaseUrl, 'base')}
>
{copiedId === 'base' ? <CheckCircle2 className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
</Button>
</div>
</div>
<div className="text-right">
<div className="text-sm text-gray-500">API版本</div>
<Badge>v1.0</Badge>
</div>
</div>
</CardContent>
</Card>
{/* 认证说明 */}
<Card className="border-0 shadow-sm bg-white/80">
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium"></CardTitle>
</CardHeader>
<CardContent>
<div className="p-4 rounded-lg bg-gray-50">
<p className="text-sm text-gray-600 mb-3">API请求需要在Header中携带API密钥</p>
<pre className="bg-gray-900 text-green-400 p-4 rounded-lg text-sm overflow-x-auto">
{`curl -X GET "${apiBaseUrl}/api/shensheshou/user?phone=13800138000" \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-H "Content-Type: application/json"`}
</pre>
</div>
</CardContent>
</Card>
{/* API分类筛选 */}
<div className="flex items-center gap-2 overflow-x-auto pb-2">
<Button
variant={activeCategory === 'all' ? 'default' : 'outline'}
size="sm"
onClick={() => setActiveCategory('all')}
>
({API_ENDPOINTS.length})
</Button>
{API_CATEGORIES.map(cat => {
const count = API_ENDPOINTS.filter(e => e.category === cat.id).length
const Icon = cat.icon
return (
<Button
key={cat.id}
variant={activeCategory === cat.id ? 'default' : 'outline'}
size="sm"
onClick={() => setActiveCategory(cat.id)}
>
<Icon className="h-4 w-4 mr-1" />
{cat.name} ({count})
</Button>
)
})}
</div>
{/* API端点列表 */}
<div className="space-y-3">
{filteredEndpoints.map(endpoint => (
<Card key={endpoint.id} className="border-0 shadow-sm bg-white/80">
<CardContent className="p-4">
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-3">
<Badge className={`${getMethodColor(endpoint.method)} font-mono`}>
{endpoint.method}
</Badge>
<code className="text-sm font-mono text-gray-700">{endpoint.path}</code>
<Button
variant="ghost"
size="sm"
onClick={() => copyToClipboard(`${apiBaseUrl}${endpoint.path}`, endpoint.id)}
>
{copiedId === endpoint.id ? (
<CheckCircle2 className="h-3 w-3 text-green-500" />
) : (
<Copy className="h-3 w-3" />
)}
</Button>
</div>
{endpoint.auth && (
<Badge variant="outline" className="text-xs">
<Key className="h-3 w-3 mr-1" />
</Badge>
)}
</div>
<h3 className="font-semibold text-gray-900 mb-1">{endpoint.name}</h3>
<p className="text-sm text-gray-500 mb-3">{endpoint.description}</p>
{/* 参数 */}
{endpoint.params && endpoint.params.length > 0 && (
<div className="mb-3">
<h4 className="text-xs font-medium text-gray-500 mb-2"></h4>
<div className="space-y-1">
{endpoint.params.map((param, i) => (
<div key={i} className="flex items-center gap-2 text-sm">
<code className="bg-gray-100 px-2 py-0.5 rounded text-purple-600">{param.name}</code>
<Badge variant="outline" className="text-xs">{param.type}</Badge>
{param.required && <Badge className="bg-red-100 text-red-700 text-xs"></Badge>}
<span className="text-gray-500">{param.desc}</span>
</div>
))}
</div>
</div>
)}
{/* 响应示例 */}
{endpoint.response && (
<div>
<h4 className="text-xs font-medium text-gray-500 mb-2"></h4>
<pre className="bg-gray-100 p-2 rounded text-xs font-mono text-gray-700 overflow-x-auto">
{endpoint.response}
</pre>
</div>
)}
</CardContent>
</Card>
))}
</div>
{/* API密钥弹窗 */}
<Dialog open={showKeyDialog} onOpenChange={setShowKeyDialog}>
<DialogContent>
<DialogHeader>
<DialogTitle>API密钥管理</DialogTitle>
<DialogDescription>API</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label>API密钥</Label>
<div className="flex items-center gap-2">
<Input value="sk-archer-xxxxxxxxxxxxx" readOnly className="font-mono" />
<Button variant="outline" size="icon">
<Copy className="h-4 w-4" />
</Button>
</div>
<p className="text-xs text-gray-500"> 2024-01-15</p>
</div>
<div className="p-3 rounded-lg bg-yellow-50 text-sm text-yellow-700">
API密钥
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setShowKeyDialog(false)}></Button>
<Button variant="destructive">
<RefreshCw className="h-4 w-4 mr-2" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</div>
)
}