/** * 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, } from 'lucide-react'; import { getVikingStatus, findVikingResources, listVikingResources, readVikingResource, storeWithSummaries, } from '../lib/viking-client'; import type { VikingStatus, VikingFindResult } from '../lib/viking-client'; 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(''); 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 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 || '默认路径'}
SQLite + FTS5
TF-IDF 语义评分
{memoryCount !== null && (
{memoryCount} 条记忆
)}
)} {/* Search Box */} {status?.available && (

语义搜索

setSearchQuery(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSearch()} 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-blue-500 focus:border-transparent" />
)} {/* 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}

) : (

加载失败

)}
)}
))}
)} {/* Summary Generation */} {status?.available && (

智能摘要

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

setSummaryUri(e.target.value)} placeholder="资源 URI (如: notes/project-plan)" 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" />