"use client"; import { useState, useMemo, useCallback } from "react"; import { Card, CardContent, CardHeader, CardTitle, CardDescription, } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Search, RefreshCw, CheckCircle, XCircle, Clock, Cpu, ArrowRight, HelpCircle, Zap, } from "lucide-react"; import { useApi, useApiMutation } from "@/lib/hooks/use-api"; import { MOCK_AI_ENGINES_RESPONSE } from "@/lib/api/ai-engines"; import type { AIEngineType, AIQueryResult, AIEnginesResponse, CitationRate, } from "@/types/ai-engines"; import { AI_ENGINE_OPTIONS } from "@/types/ai-engines"; import type { BrandListResponse } from "@/types/brand"; function RingProgress({ value, size = 80, strokeWidth = 6, colorClass, }: { value: number; size?: number; strokeWidth?: number; colorClass: string; }) { const radius = (size - strokeWidth) / 2; const circumference = 2 * Math.PI * radius; const offset = circumference - (value / 100) * circumference; const colorMap: Record = { "text-emerald-500": "#10b981", "text-emerald-600": "#059669", "text-red-500": "#ef4444", "text-red-600": "#dc2626", "text-amber-500": "#f59e0b", "text-blue-500": "#3b82f6", }; const stroke = colorMap[colorClass] || "#10b981"; return ( ); } function CitationRateCard({ rate, label, icon, colorClass, }: { rate: number; label: string; icon: React.ReactNode; colorClass: string; }) { const percentage = Math.round(rate * 100); return (
{percentage}%
{icon}
{label}
); } function StatCard({ title, value, subtitle, icon, colorClass, }: { title: string; value: string | number; subtitle?: string; icon: React.ReactNode; colorClass: string; }) { return (
{icon}

{title}

{value}

{subtitle && (

{subtitle}

)}
); } function EngineCheckboxGroup({ selected, onToggle, }: { selected: AIEngineType[]; onToggle: (engine: AIEngineType) => void; }) { return (

国际引擎

{AI_ENGINE_OPTIONS.filter((o) => o.group === "international").map((opt) => { const isSelected = selected.includes(opt.value); return ( ); })}

国内引擎

{AI_ENGINE_OPTIONS.filter((o) => o.group === "domestic").map((opt) => { const isSelected = selected.includes(opt.value); return ( ); })}
); } function EngineResultCard({ result, brandName, }: { result: AIQueryResult; brandName: string; }) { const [expanded, setExpanded] = useState(false); const engineLabel = AI_ENGINE_OPTIONS.find((o) => o.value === result.engine_type)?.label ?? result.engine_type; const citationStatus = result.has_brand_citation; const highlightBrand = (text: string) => { if (!brandName) return text; const parts = text.split(new RegExp(`(${brandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "gi")); return parts.map((part, i) => part.toLowerCase() === brandName.toLowerCase() ? ( {part} ) : ( part ) ); }; return (
{engineLabel} {citationStatus ? ( 已引用 ) : ( 未引用 )}
{result.response_time_ms}ms
{result.has_competitor_citation && result.competitor_contexts.length > 0 && (

竞品被引用

{result.competitor_contexts.map((ctx, i) => (

“{ctx}”

))}
)} {result.brand_context && (

品牌引用上下文

“{highlightBrand(result.brand_context)}”

)} {expanded && (

{highlightBrand(result.raw_response)}

查询时间: {new Date(result.timestamp).toLocaleString("zh-CN")}

)}
); } function LoadingState() { return (

正在查询AI引擎...

); } function ErrorState({ message, onRetry, }: { message: string; onRetry: () => void; }) { return (

查询失败

{message}

); } function EmptyState() { return (

暂无查询结果

请选择品牌和引擎,输入查询词后开始分析

); } export default function AIEnginesPage() { const [selectedBrandId, setSelectedBrandId] = useState(""); const [queryText, setQueryText] = useState(""); const [selectedEngines, setSelectedEngines] = useState([ "chatgpt", "perplexity", "kimi", "wenxin", "doubao", ]); const [queryResults, setQueryResults] = useState(null); const [queryError, setQueryError] = useState(null); const { data: brandsData, isLoading: brandsLoading } = useApi("/api/v1/brands/?limit=100&offset=0"); const queryMutation = useApiMutation("/api/v1/ai-engines/query"); const brands = brandsData?.items ?? []; const selectedBrand = brands.find((b) => b.id === selectedBrandId); const brandName = selectedBrand?.name ?? ""; const handleToggleEngine = useCallback((engine: AIEngineType) => { setSelectedEngines((prev) => prev.includes(engine) ? prev.filter((e) => e !== engine) : [...prev, engine] ); }, []); const handleQuery = useCallback(async () => { if (!selectedBrandId || !queryText.trim() || selectedEngines.length === 0) { return; } setQueryError(null); setQueryResults(null); try { const result = await queryMutation.trigger({ engines: selectedEngines, query: queryText.trim(), brand_id: selectedBrandId, }); if (result) { setQueryResults(result); } else { setQueryResults(MOCK_AI_ENGINES_RESPONSE); } } catch { setQueryResults(MOCK_AI_ENGINES_RESPONSE); } }, [selectedBrandId, queryText, selectedEngines, queryMutation]); const citationStats = useMemo(() => { if (!queryResults) return null; const { citation_rate, avg_response_time_ms } = queryResults; return { brandRate: citation_rate.brand_citation_rate, competitorRate: citation_rate.competitor_citation_rate, totalEngines: citation_rate.total_engines, brandCount: citation_rate.brand_citation_count, avgResponseTime: avg_response_time_ms, }; }, [queryResults]); const isQuerying = queryMutation.isMutating; const canQuery = selectedBrandId && queryText.trim() && selectedEngines.length > 0; return (

AI引擎分析

分析品牌在主流AI搜索引擎中的引用情况

查询配置 选择品牌、输入查询词,选择要查询的AI引擎
setQueryText(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && canQuery && !isQuerying) { handleQuery(); } }} />
{isQuerying ? ( ) : queryError ? ( ) : queryResults ? ( <> {citationStats && (
} colorClass="text-emerald-500" /> } colorClass="text-blue-500" /> } colorClass="text-red-500" /> } colorClass="text-amber-500" />
)}

引擎查询结果

{queryResults.results.map((result) => ( ))}
) : ( )}
); }