'use client'; import { useMemo, useState } from 'react'; import Link from 'next/link'; import { AlertTriangle, ArrowRight, Check, Clipboard, ExternalLink, FileText, Link2, Radar, Sparkles, Wrench, } from 'lucide-react'; import type { ReactNode } from 'react'; export interface DashboardSignalItem { chatroom_id: string; chat_name: string; local_id: number; sender: string; time: string; title: string; snippet: string; score: number; reasons: string[]; } export interface DashboardOpportunityItem extends DashboardSignalItem { action: string; } export interface DashboardActionItem extends DashboardOpportunityItem { why: string; urgency: 'high' | 'medium' | 'low'; } export interface DashboardSignalSource { sender: string; signal_count: number; group_count: number; top_group: string; last_seen: string; strengths: string[]; } export interface DashboardTopicLifecycle { title: string; status: 'rising' | 'spreading' | 'hot' | 'cooling'; today_count: number; previous_avg: number; group_count: number; reason: string; keywords: string[]; } export interface DashboardLinkHighlight { kind: 'article' | 'tool'; title: string; url: string; domain: string; source: string; score: number; verdict: string; count: number; group_count: number; last_seen: string; } export interface DashboardPeopleRadar { sender: string; role: '分享者' | '需求提出者' | '连接者' | '观点源'; score: number; group_count: number; signal_count: number; top_group: string; reason: string; } export interface DashboardContentIdea { title: string; angle: string; suggested_channel: '公众号' | 'X' | '小红书' | '博客'; evidence: string; source_count: number; } export interface DashboardAnomalySignal { kind: 'spike' | 'cross_group' | 'dense_links' | 'quiet_day'; title: string; description: string; severity: 'high' | 'medium' | 'low'; href?: string; } export interface DashboardIntelligence { date: string; must_read: DashboardSignalItem[]; opportunities: DashboardOpportunityItem[]; signal_sources: DashboardSignalSource[]; action_items: DashboardActionItem[]; topic_lifecycle: DashboardTopicLifecycle[]; link_highlights: DashboardLinkHighlight[]; people_radar: DashboardPeopleRadar[]; content_ideas: DashboardContentIdea[]; anomalies: DashboardAnomalySignal[]; } const EMPTY: DashboardIntelligence = { date: '', must_read: [], opportunities: [], signal_sources: [], action_items: [], topic_lifecycle: [], link_highlights: [], people_radar: [], content_ideas: [], anomalies: [], }; type QueueItem = { title: string; href?: string; external?: boolean; }; export default function IntelligenceBrief({ intelligence }: { intelligence?: DashboardIntelligence }) { const data = intelligence ?? EMPTY; const articles = data.link_highlights.filter((item) => item.kind === 'article'); const tools = data.link_highlights.filter((item) => item.kind === 'tool'); const summary = useMemo(() => buildSummary(data, articles, tools), [data, articles, tools]); const queue = useMemo( () => ({ messages: data.must_read.slice(0, 2).map((item) => ({ title: item.title, href: `/groups/${encodeURIComponent(item.chatroom_id)}?date=${data.date}`, })), articles: articles.slice(0, 2).map((item) => ({ title: item.title, href: item.url, external: true, })), tools: tools.slice(0, 2).map((item) => ({ title: item.title, href: item.url, external: true, })), anomalies: data.anomalies.slice(0, 2).map((item) => ({ title: item.title, href: item.href, external: item.href?.startsWith('http') ?? false, })), }), [articles, data.anomalies, data.date, data.must_read, tools], ); const [copied, setCopied] = useState(false); async function copySummary() { await navigator.clipboard.writeText(summary); setCopied(true); window.setTimeout(() => setCopied(false), 1200); } return (
Today Queue
今日先看这些
); } function QueueBlock({ label, items, empty, }: { label: string; items: QueueItem[]; empty: string; }) { return (
{label}
{items.length === 0 ? (
{empty}
) : ( items.map((item, i) => ( )) )}
); } function QueueLink({ item }: { item: QueueItem }) { const content = ( <> {item.title} {item.href ? ( item.external ? ( ) : ( ) ) : null} ); const className = 'flex items-start gap-2 rounded px-1.5 py-1 text-[12px] leading-5 text-[var(--text-2)] transition-colors hover:bg-[var(--surface)] hover:text-[var(--text)]'; if (!item.href) return
{content}
; if (item.external) { return ( {content} ); } return ( {content} ); } function MustReadPanel({ date, items, actions, }: { date: string; items: DashboardSignalItem[]; actions: DashboardActionItem[]; }) { const promoted = mergeSignals(actions, items).slice(0, 7); return (
} title="关键话题与消息" meta={`${promoted.length} 条`} /> {promoted.length === 0 ? ( ) : (
{promoted.map((item, index) => ( {index + 1} {item.title} {'action' in item && ( {(item as DashboardActionItem).action} )} {item.chat_name} · {item.sender} {item.time.slice(11)} {'why' in item ? (item as DashboardActionItem).why : item.reasons.join(' / ')} ))}
)}
); } function ResourcePanel({ articles, tools, }: { articles: DashboardLinkHighlight[]; tools: DashboardLinkHighlight[]; }) { return (
} title="链接情报" meta={`${articles.length + tools.length} 条`} />
} title="文章 / 内容" items={articles.slice(0, 5)} /> } title="工具 / 资源" items={tools.slice(0, 5)} />
); } function ResourceColumn({ icon, title, items, }: { icon: ReactNode; title: string; items: DashboardLinkHighlight[]; }) { return (
{icon} {title}
{items.length === 0 ? (
暂无
) : (
{items.map((item) => (
{item.title}
{sourceLabel(item.source)} {item.domain} {item.group_count} 群 {item.count} 次
))}
)}
); } function sourceLabel(source: string): string { if (source === 'wechat_raw') return '微信原文'; if (source === 'public_search') return '公开补链'; if (source === 'manual') return '手动补链'; return '网页链接'; } function sourceClass(source: string): string { const base = 'shrink-0 rounded px-1.5 py-0.5'; if (source === 'wechat_raw') return `${base} bg-[var(--accent-soft)] text-[var(--accent)]`; if (source === 'public_search' || source === 'manual') return `${base} bg-[var(--warn-soft)] text-[var(--warn)]`; return `${base} bg-[var(--surface-2)] text-[var(--text-3)]`; } function WatchPanel({ date, anomalies, people, }: { date: string; anomalies: DashboardAnomalySignal[]; people: DashboardPeopleRadar[]; }) { return (
} title="需要盯一下" meta={`${anomalies.length} 个异动`} />
{anomalies.length === 0 ? ( ) : ( anomalies.slice(0, 4).map((item) => { const body = ( <>
{item.title}
{severityText(item.severity)}
{item.description}
); if (!item.href) { return (
{body}
); } if (item.href.startsWith('http')) { return ( {body} ); } return ( {body} ); }) )}
高信号人物
{people.slice(0, 4).map((person) => (
{person.sender}
{person.score}
{person.role} · {person.reason} · {date}
))}
); } function mergeSignals(actions: DashboardActionItem[], mustRead: DashboardSignalItem[]) { const seen = new Set(); const out: Array = []; for (const item of [...actions.slice(0, 4), ...mustRead]) { const key = `${item.chatroom_id}:${item.local_id}`; if (seen.has(key)) continue; seen.add(key); out.push(item); } return out; } function buildSummary( data: DashboardIntelligence, articles: DashboardLinkHighlight[], tools: DashboardLinkHighlight[], ): string { const lines = [`${data.date || '今日'} 情报队列`]; lines.push(`消息:${data.must_read.slice(0, 2).map((item) => item.title).join(';') || '暂无'}`); lines.push(`文章:${articles.slice(0, 2).map((item) => item.title).join(';') || '暂无'}`); lines.push(`工具:${tools.slice(0, 2).map((item) => item.title).join(';') || '暂无'}`); lines.push(`异动:${data.anomalies.slice(0, 2).map((item) => item.title).join(';') || '暂无'}`); return lines.join('\n'); } function PanelTitle({ icon, title, meta }: { icon: ReactNode; title: string; meta: string }) { return (
{icon} {title}
{meta}
); } function EmptyState({ text, compact }: { text: string; compact?: boolean }) { return (
{text}
); } function urgencyClass(urgency: DashboardActionItem['urgency']): string { const base = 'shrink-0 rounded px-1.5 py-0.5 text-[10px]'; if (urgency === 'high') return `${base} bg-[var(--warn-soft)] text-[var(--warn)]`; if (urgency === 'medium') return `${base} bg-[var(--accent-soft)] text-[var(--accent)]`; return `${base} bg-[var(--surface-2)] text-[var(--text-3)]`; } function severityText(severity: DashboardAnomalySignal['severity']): string { if (severity === 'high') return '高'; if (severity === 'medium') return '中'; return '低'; } function severityClass(severity: DashboardAnomalySignal['severity']): string { const base = 'shrink-0 rounded px-1.5 py-0.5 text-[10px]'; if (severity === 'high') return `${base} bg-[var(--warn-soft)] text-[var(--warn)]`; if (severity === 'medium') return `${base} bg-[var(--accent-soft)] text-[var(--accent)]`; return `${base} bg-[var(--surface-2)] text-[var(--text-3)]`; }