/** * VikingPanel - ZCLAW Semantic Memory UI * * Provides interface for semantic search and knowledge base management. * Uses native Rust SqliteStorage with TF-IDF semantic search. */ import { useState, useEffect } from 'react'; import { Search, RefreshCw, AlertCircle, CheckCircle, FileText, Database, Sparkles, BookOpen, } from 'lucide-react'; import { getVikingStatus, findVikingResources, listVikingResources, readVikingResource, storeWithSummaries, } from '../lib/viking-client'; import type { VikingStatus, VikingFindResult } from '../lib/viking-client'; import { saasClient } from '../lib/saas-client'; import type { KnowledgeSearchResult } from '../lib/saas-client'; import { useSaaSStore } from '../store/saasStore'; export function VikingPanel() { const [status, setStatus] = useState(null); const [isLoading, setIsLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const [searchResults, setSearchResults] = useState([]); const [isSearching, setIsSearching] = useState(false); const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); const [memoryCount, setMemoryCount] = useState(null); const [expandedUri, setExpandedUri] = useState(null); const [expandedContent, setExpandedContent] = useState(null); const [isLoadingL2, setIsLoadingL2] = useState(false); const [isGeneratingSummary, setIsGeneratingSummary] = useState(false); const [summaryUri, setSummaryUri] = useState(''); const [summaryContent, setSummaryContent] = useState(''); // SaaS knowledge search state const [kbQuery, setKbQuery] = useState(''); const [kbResults, setKbResults] = useState([]); const [isKbSearching, setIsKbSearching] = useState(false); const saasReady = useSaaSStore((s) => s.isLoggedIn); const loadStatus = async () => { setIsLoading(true); setMessage(null); try { const vikingStatus = await getVikingStatus(); setStatus(vikingStatus); if (vikingStatus.available) { // Load memory count (use empty path — viking_ls('') returns all, viking_ls('/') returns 0) try { const resources = await listVikingResources(''); setMemoryCount(resources.length); } catch (e) { console.warn('[VikingPanel] Failed to list resources:', e); setMemoryCount(null); } } } catch (error) { console.error('Failed to load Viking status:', error); setStatus({ available: false, error: String(error) }); } finally { setIsLoading(false); } }; useEffect(() => { loadStatus(); }, []); const handleSearch = async () => { if (!searchQuery.trim()) return; setIsSearching(true); setMessage(null); try { const results = await findVikingResources(searchQuery, undefined, 10); setSearchResults(results); if (results.length === 0) { setMessage({ type: 'error', text: '未找到匹配的资源' }); } } catch (error) { setMessage({ type: 'error', text: `搜索失败: ${error instanceof Error ? error.message : '未知错误'}`, }); } finally { setIsSearching(false); } }; const handleKbSearch = async () => { if (!kbQuery.trim()) return; setIsKbSearching(true); try { const results = await saasClient.searchKnowledge(kbQuery, { limit: 10 }); setKbResults(results); } catch { setKbResults([]); } finally { setIsKbSearching(false); } }; const handleExpandL2 = async (uri: string) => { if (expandedUri === uri) { setExpandedUri(null); setExpandedContent(null); return; } setExpandedUri(uri); setIsLoadingL2(true); try { const fullContent = await readVikingResource(uri, 'L2'); setExpandedContent(fullContent); } catch (e) { console.warn('[VikingPanel] Failed to read resource:', e); setExpandedContent(null); } finally { setIsLoadingL2(false); } }; return (
{/* Header */}

语义记忆

ZCLAW 语义记忆搜索引擎

{status?.available && ( 可用 )}
{/* Status Banner */} {!status?.available && (

语义记忆存储不可用

本地 SQLite 存储初始化失败。请检查数据目录权限后重启应用。

{status?.error && (

{status.error}

)}
)} {/* Message */} {message && (
{message.type === 'success' ? ( ) : ( )} {message.text}
)} {/* Storage Info */}
本地存储
{status?.available ? `${status.version || 'Native'} · ${status.dataDir || '默认路径'}` : '存储未连接'}
{!status?.available && ( )}
{status?.available ? ( ) : ( )} SQLite + FTS5
{status?.available ? ( ) : ( )} TF-IDF 语义评分
{memoryCount !== null && (
{memoryCount} 条记忆
)}
{/* Search Box */}

语义搜索

{!status?.available && (

存储未连接,搜索功能不可用

)}
setSearchQuery(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSearch()} placeholder="输入自然语言查询..." disabled={!status?.available} className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed" />
{/* Search Results */} {searchResults.length > 0 && (
找到 {searchResults.length} 个结果
{searchResults.map((result, index) => (
{result.uri} {result.level} {Math.round(result.score * 100)}%

{result.content}

{result.level === 'L1' && ( )} {expandedUri === result.uri && (
{isLoadingL2 ? (
加载中...
) : expandedContent ? (

{expandedContent}

) : (

加载失败

)}
)}
))}
)} {/* SaaS Knowledge Base Search */} {saasReady && (

知识库搜索

搜索 SaaS 端共享知识库

setKbQuery(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleKbSearch()} placeholder="搜索知识库..." className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent" />
{kbResults.length > 0 && (
{kbResults.map((r) => (
{r.item_title} {r.category_name && ( {r.category_name} )} {Math.round(r.score * 100)}%

{r.content}

{r.keywords.length > 0 && (
{r.keywords.slice(0, 5).map((kw) => ( {kw} ))}
)}
))}
)}
)} {/* Summary Generation */}

智能摘要

存储资源并自动通过 LLM 生成 L0/L1 多级摘要(需配置摘要驱动)

{!status?.available && (

存储未连接,摘要功能不可用

)}
setSummaryUri(e.target.value)} placeholder="资源 URI (如: notes/project-plan)" disabled={!status?.available} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed" />