import { useState, useEffect, useCallback } from 'react'; import { useButlerInsights } from '../../hooks/useButlerInsights'; import { useChatStore } from '../../store/chatStore'; import { useIndustryStore } from '../../store/industryStore'; import { extractAndStoreMemories } from '../../lib/viking-client'; import { resolveKernelAgentId } from '../../lib/kernel-agent'; import { InsightsSection } from './InsightsSection'; import { ProposalsSection } from './ProposalsSection'; import { MemorySection } from './MemorySection'; interface ButlerPanelProps { agentId: string | undefined; } export function ButlerPanel({ agentId }: ButlerPanelProps) { const [resolvedAgentId, setResolvedAgentId] = useState(null); // Use resolved kernel UUID for queries — raw agentId may be "1" from SaaS relay // while pain points/proposals are stored under kernel UUID const effectiveAgentId = resolvedAgentId ?? agentId; const { painPoints, proposals, loading, error, refresh } = useButlerInsights(effectiveAgentId); const messageCount = useChatStore((s) => s.messages.length); const { accountIndustries, configs, lastSynced, isLoading: industryLoading, fetchIndustries } = useIndustryStore(); const [analyzing, setAnalyzing] = useState(false); const [memoryRefreshKey, setMemoryRefreshKey] = useState(0); // Resolve SaaS relay agentId ("1") to kernel UUID for VikingStorage queries useEffect(() => { if (!agentId) { setResolvedAgentId(null); return; } resolveKernelAgentId(agentId) .then(setResolvedAgentId) .catch(() => setResolvedAgentId(agentId)); }, [agentId]); // Auto-fetch industry configs once per session useEffect(() => { if (accountIndustries.length === 0 && !industryLoading) { fetchIndustries().catch(() => {/* SaaS unavailable — ignore */}); } }, []); const hasData = (painPoints?.length ?? 0) > 0 || (proposals?.length ?? 0) > 0; const canAnalyze = messageCount >= 2; const handleAnalyze = useCallback(async () => { if (!canAnalyze || analyzing || !resolvedAgentId) return; setAnalyzing(true); try { // 1. Refresh pain points & proposals await refresh(); // 2. Extract and store memories from current conversation const messages = useChatStore.getState().messages; if (messages.length >= 2) { const extractionMessages = messages.map((m) => ({ role: m.role as 'user' | 'assistant', content: typeof m.content === 'string' ? m.content : '', })); await extractAndStoreMemories(extractionMessages, resolvedAgentId); // Trigger MemorySection to reload setMemoryRefreshKey((k) => k + 1); } } catch { // Extraction failure should not block UI — insights still refreshed } finally { setAnalyzing(false); } }, [canAnalyze, analyzing, resolvedAgentId, refresh]); if (!agentId) { return (

请先选择一个 Agent

); } return (
{error && (
{error}
)} {loading && (
)} {!loading && !hasData && (

管家正在了解您,多轮对话后会自动发现您的需求

)} {(hasData || loading) && ( <> {/* Insights section */}

我最近在关注

{/* Proposals section */}

我提出的方案

)} {/* Memory section (always show) */}

我记得关于您

{/* Industry section */} {accountIndustries.length > 0 && (

行业专长

{accountIndustries.map((item) => { const config = configs[item.industry_id]; const keywords = config?.keywords ?? []; return (
{item.industry_name || item.industry_id}
{keywords.length > 0 && (
{keywords.slice(0, 8).map((kw) => ( {kw} ))} {keywords.length > 8 && ( +{keywords.length - 8} )}
)}
); })} {lastSynced && (
同步于 {new Date(lastSynced).toLocaleString('zh-CN')}
)}
)}
); }