refactor: 清理未使用代码并添加未来功能标记
Some checks failed
CI / Rust Check (push) Has been cancelled
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Rust Check (push) Has been cancelled
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
style: 统一代码格式和注释风格 docs: 更新多个功能文档的完整度和状态 feat(runtime): 添加路径验证工具支持 fix(pipeline): 改进条件判断和变量解析逻辑 test(types): 为ID类型添加全面测试用例 chore: 更新依赖项和Cargo.lock文件 perf(mcp): 优化MCP协议传输和错误处理
This commit is contained in:
288
desktop/src/components/VikingPanel.tsx
Normal file
288
desktop/src/components/VikingPanel.tsx
Normal file
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* VikingPanel - OpenViking Semantic Memory UI
|
||||
*
|
||||
* Provides interface for semantic search and knowledge base management.
|
||||
* OpenViking is an optional sidecar for semantic memory operations.
|
||||
*/
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
Search,
|
||||
RefreshCw,
|
||||
AlertCircle,
|
||||
CheckCircle,
|
||||
FileText,
|
||||
Server,
|
||||
Play,
|
||||
Square,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
getVikingStatus,
|
||||
findVikingResources,
|
||||
getVikingServerStatus,
|
||||
startVikingServer,
|
||||
stopVikingServer,
|
||||
} from '../lib/viking-client';
|
||||
import type { VikingStatus, VikingFindResult } from '../lib/viking-client';
|
||||
|
||||
export function VikingPanel() {
|
||||
const [status, setStatus] = useState<VikingStatus | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [searchResults, setSearchResults] = useState<VikingFindResult[]>([]);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [serverRunning, setServerRunning] = useState(false);
|
||||
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
||||
|
||||
const loadStatus = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const vikingStatus = await getVikingStatus();
|
||||
setStatus(vikingStatus);
|
||||
|
||||
const serverStatus = await getVikingServerStatus();
|
||||
setServerRunning(serverStatus.running);
|
||||
} 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 handleServerToggle = async () => {
|
||||
try {
|
||||
if (serverRunning) {
|
||||
await stopVikingServer();
|
||||
setServerRunning(false);
|
||||
setMessage({ type: 'success', text: '服务器已停止' });
|
||||
} else {
|
||||
await startVikingServer();
|
||||
setServerRunning(true);
|
||||
setMessage({ type: 'success', text: '服务器已启动' });
|
||||
}
|
||||
} catch (error) {
|
||||
setMessage({
|
||||
type: 'error',
|
||||
text: `操作失败: ${error instanceof Error ? error.message : '未知错误'}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl">
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-gray-900 dark:text-white">语义记忆</h1>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
OpenViking 语义搜索引擎
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
{status?.available && (
|
||||
<span className="text-xs flex items-center gap-1 text-green-600">
|
||||
<CheckCircle className="w-3 h-3" /> 可用
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
onClick={loadStatus}
|
||||
disabled={isLoading}
|
||||
className="text-xs text-white bg-orange-500 hover:bg-orange-600 px-3 py-1.5 rounded-lg flex items-center gap-1 transition-colors disabled:opacity-50"
|
||||
>
|
||||
<RefreshCw className={`w-3 h-3 ${isLoading ? 'animate-spin' : ''}`} /> 刷新
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Status Banner */}
|
||||
{!status?.available && (
|
||||
<div className="mb-6 p-4 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg">
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertCircle className="w-4 h-4 text-amber-500 mt-0.5" />
|
||||
<div className="text-xs text-amber-700 dark:text-amber-300">
|
||||
<p className="font-medium">OpenViking CLI 不可用</p>
|
||||
<p className="mt-1">
|
||||
请安装 OpenViking CLI 或设置{' '}
|
||||
<code className="bg-amber-100 dark:bg-amber-800 px-1 rounded">ZCLAW_VIKING_BIN</code> 环境变量。
|
||||
</p>
|
||||
{status?.error && (
|
||||
<p className="mt-1 text-amber-600 dark:text-amber-400 font-mono text-xs">
|
||||
{status.error}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Message */}
|
||||
{message && (
|
||||
<div
|
||||
className={`mb-4 p-3 rounded-lg flex items-center gap-2 ${
|
||||
message.type === 'success'
|
||||
? 'bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300'
|
||||
: 'bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300'
|
||||
}`}
|
||||
>
|
||||
{message.type === 'success' ? (
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
) : (
|
||||
<AlertCircle className="w-4 h-4" />
|
||||
)}
|
||||
{message.text}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Server Control */}
|
||||
{status?.available && (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-4 mb-6 shadow-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className={`w-10 h-10 rounded-xl flex items-center justify-center ${
|
||||
serverRunning
|
||||
? 'bg-gradient-to-br from-green-500 to-emerald-500 text-white'
|
||||
: 'bg-gray-200 dark:bg-gray-700 text-gray-400'
|
||||
}`}
|
||||
>
|
||||
<Server className="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
Viking Server
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{serverRunning ? '运行中' : '已停止'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleServerToggle}
|
||||
className={`px-4 py-2 rounded-lg flex items-center gap-2 text-sm transition-colors ${
|
||||
serverRunning
|
||||
? 'bg-red-100 text-red-600 hover:bg-red-200 dark:bg-red-900/30 dark:text-red-400'
|
||||
: 'bg-green-100 text-green-600 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400'
|
||||
}`}
|
||||
>
|
||||
{serverRunning ? (
|
||||
<>
|
||||
<Square className="w-4 h-4" /> 停止
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Play className="w-4 h-4" /> 启动
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Search Box */}
|
||||
{status?.available && (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-4 mb-6 shadow-sm">
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-white mb-3">语义搜索</h3>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
<button
|
||||
onClick={handleSearch}
|
||||
disabled={isSearching || !searchQuery.trim()}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 flex items-center gap-2 text-sm"
|
||||
>
|
||||
{isSearching ? (
|
||||
<RefreshCw className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
<Search className="w-4 h-4" />
|
||||
)}
|
||||
搜索
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Search Results */}
|
||||
{searchResults.length > 0 && (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-sm divide-y divide-gray-100 dark:divide-gray-700">
|
||||
<div className="p-3 border-b border-gray-200 dark:border-gray-700">
|
||||
<span className="text-xs text-gray-500">
|
||||
找到 {searchResults.length} 个结果
|
||||
</span>
|
||||
</div>
|
||||
{searchResults.map((result, index) => (
|
||||
<div key={`${result.uri}-${index}`} className="p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center flex-shrink-0">
|
||||
<FileText className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-white truncate">
|
||||
{result.uri}
|
||||
</span>
|
||||
<span className="text-xs text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-0.5 rounded">
|
||||
{result.level}
|
||||
</span>
|
||||
<span className="text-xs text-blue-600 dark:text-blue-400">
|
||||
{Math.round(result.score * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
{result.overview && (
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1 line-clamp-2">
|
||||
{result.overview}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-xs text-gray-600 dark:text-gray-300 mt-2 line-clamp-3 font-mono">
|
||||
{result.content}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Info Section */}
|
||||
<div className="mt-6 p-4 bg-gray-50 dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-white mb-2">关于 OpenViking</h3>
|
||||
<ul className="text-xs text-gray-500 dark:text-gray-400 space-y-1">
|
||||
<li>• 语义搜索引擎,支持自然语言查询</li>
|
||||
<li>• 自动提取和索引知识资源</li>
|
||||
<li>• 支持多种文档格式和代码文件</li>
|
||||
<li>• 可作为本地知识库增强 AI 对话</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user