252 lines
9.3 KiB
TypeScript
252 lines
9.3 KiB
TypeScript
/**
|
||
* RAG配置管理组件
|
||
* 提供可视化的RAG检索参数配置界面
|
||
*/
|
||
|
||
import React, { useState, useEffect } from 'react';
|
||
import { RagGroundingConfig } from '../types/ragGrounding';
|
||
import { RagConfigOptimizer, RagConfigPresets } from '../utils/ragConfigOptimizer';
|
||
|
||
interface RagConfigManagerProps {
|
||
config: RagGroundingConfig;
|
||
onConfigChange: (config: RagGroundingConfig) => void;
|
||
onClose?: () => void;
|
||
}
|
||
|
||
export const RagConfigManager: React.FC<RagConfigManagerProps> = ({
|
||
config,
|
||
onConfigChange,
|
||
onClose
|
||
}) => {
|
||
const [currentConfig, setCurrentConfig] = useState<RagGroundingConfig>(config);
|
||
const [selectedScenario, setSelectedScenario] = useState<string>('BALANCED');
|
||
const [customPresets, setCustomPresets] = useState<Record<string, RagGroundingConfig>>({});
|
||
|
||
useEffect(() => {
|
||
setCustomPresets(RagConfigPresets.getPresets());
|
||
}, []);
|
||
|
||
const handleScenarioChange = (scenarioKey: string) => {
|
||
setSelectedScenario(scenarioKey);
|
||
const scenario = RagConfigOptimizer.SCENARIOS[scenarioKey];
|
||
if (scenario) {
|
||
const newConfig = RagConfigOptimizer.applyScenario(currentConfig, scenario);
|
||
setCurrentConfig(newConfig);
|
||
onConfigChange(newConfig);
|
||
}
|
||
};
|
||
|
||
const handleConfigUpdate = (updates: Partial<RagGroundingConfig>) => {
|
||
const newConfig = { ...currentConfig, ...updates };
|
||
setCurrentConfig(newConfig);
|
||
onConfigChange(newConfig);
|
||
};
|
||
|
||
const handleSavePreset = () => {
|
||
const name = prompt('请输入预设名称:');
|
||
if (name && name.trim()) {
|
||
RagConfigPresets.savePreset(name.trim(), currentConfig);
|
||
setCustomPresets(RagConfigPresets.getPresets());
|
||
}
|
||
};
|
||
|
||
const handleLoadPreset = (presetName: string) => {
|
||
const preset = RagConfigPresets.getPreset(presetName);
|
||
if (preset) {
|
||
setCurrentConfig(preset);
|
||
onConfigChange(preset);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||
<div className="bg-white rounded-lg p-6 max-w-2xl w-full max-h-[90vh] overflow-y-auto">
|
||
<div className="flex justify-between items-center mb-6">
|
||
<h2 className="text-xl font-bold text-gray-800">RAG检索配置管理</h2>
|
||
{onClose && (
|
||
<button
|
||
onClick={onClose}
|
||
className="text-gray-500 hover:text-gray-700"
|
||
>
|
||
✕
|
||
</button>
|
||
)}
|
||
</div>
|
||
|
||
{/* 预设场景选择 */}
|
||
<div className="mb-6">
|
||
<h3 className="text-lg font-semibold mb-3">优化场景</h3>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||
{Object.entries(RagConfigOptimizer.SCENARIOS).map(([key, scenario]) => (
|
||
<button
|
||
key={key}
|
||
onClick={() => handleScenarioChange(key)}
|
||
className={`p-3 rounded-lg border text-left transition-colors ${
|
||
selectedScenario === key
|
||
? 'border-blue-500 bg-blue-50'
|
||
: 'border-gray-300 hover:border-gray-400'
|
||
}`}
|
||
>
|
||
<div className="font-medium text-gray-800">{scenario.name}</div>
|
||
<div className="text-sm text-gray-600 mt-1">{scenario.description}</div>
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 详细配置 */}
|
||
<div className="mb-6">
|
||
<h3 className="text-lg font-semibold mb-3">详细配置</h3>
|
||
<div className="space-y-4">
|
||
{/* 最大检索结果数量 */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
最大检索结果数量 (5-50)
|
||
</label>
|
||
<input
|
||
type="number"
|
||
min="5"
|
||
max="50"
|
||
value={currentConfig.max_retrieval_results || 20}
|
||
onChange={(e) => handleConfigUpdate({
|
||
max_retrieval_results: parseInt(e.target.value)
|
||
})}
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
/>
|
||
<p className="text-xs text-gray-500 mt-1">
|
||
更多结果可能包含更多信息,但也可能降低相关性
|
||
</p>
|
||
</div>
|
||
|
||
{/* 相关性阈值 */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
相关性阈值 (0.1-0.9)
|
||
</label>
|
||
<input
|
||
type="number"
|
||
min="0.1"
|
||
max="0.9"
|
||
step="0.1"
|
||
value={currentConfig.relevance_threshold || 0.4}
|
||
onChange={(e) => handleConfigUpdate({
|
||
relevance_threshold: parseFloat(e.target.value)
|
||
})}
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
/>
|
||
<p className="text-xs text-gray-500 mt-1">
|
||
较低的阈值会返回更多结果,较高的阈值会返回更相关的结果
|
||
</p>
|
||
</div>
|
||
|
||
{/* 搜索过滤器 */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
搜索过滤器 (可选)
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={currentConfig.search_filter || ''}
|
||
onChange={(e) => handleConfigUpdate({
|
||
search_filter: e.target.value || undefined
|
||
})}
|
||
placeholder="例如: category: ANY("服装", "搭配")"
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
/>
|
||
<p className="text-xs text-gray-500 mt-1">
|
||
使用Vertex AI Search过滤器语法限制搜索范围
|
||
</p>
|
||
</div>
|
||
|
||
{/* 包含摘要 */}
|
||
<div>
|
||
<label className="flex items-center">
|
||
<input
|
||
type="checkbox"
|
||
checked={currentConfig.include_summary || false}
|
||
onChange={(e) => handleConfigUpdate({
|
||
include_summary: e.target.checked
|
||
})}
|
||
className="mr-2"
|
||
/>
|
||
<span className="text-sm font-medium text-gray-700">包含结果摘要</span>
|
||
</label>
|
||
<p className="text-xs text-gray-500 mt-1">
|
||
启用后会包含检索结果的摘要信息,有助于AI理解上下文
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 配置说明 */}
|
||
<div className="mb-6 p-3 bg-blue-50 rounded-lg">
|
||
<h4 className="font-medium text-blue-800 mb-1">当前配置说明</h4>
|
||
<p className="text-sm text-blue-700">
|
||
{RagConfigOptimizer.getConfigExplanation(currentConfig)}
|
||
</p>
|
||
</div>
|
||
|
||
{/* 自定义预设管理 */}
|
||
<div className="mb-6">
|
||
<h3 className="text-lg font-semibold mb-3">自定义预设</h3>
|
||
<div className="flex gap-2 mb-3">
|
||
<button
|
||
onClick={handleSavePreset}
|
||
className="px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 transition-colors"
|
||
>
|
||
保存当前配置
|
||
</button>
|
||
</div>
|
||
{Object.keys(customPresets).length > 0 && (
|
||
<div className="space-y-2">
|
||
{Object.keys(customPresets).map((presetName) => (
|
||
<div key={presetName} className="flex items-center justify-between p-2 bg-gray-50 rounded">
|
||
<span className="text-sm font-medium">{presetName}</span>
|
||
<div className="flex gap-2">
|
||
<button
|
||
onClick={() => handleLoadPreset(presetName)}
|
||
className="px-3 py-1 text-xs bg-blue-500 text-white rounded hover:bg-blue-600"
|
||
>
|
||
加载
|
||
</button>
|
||
<button
|
||
onClick={() => {
|
||
RagConfigPresets.deletePreset(presetName);
|
||
setCustomPresets(RagConfigPresets.getPresets());
|
||
}}
|
||
className="px-3 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600"
|
||
>
|
||
删除
|
||
</button>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 操作按钮 */}
|
||
<div className="flex justify-end gap-3">
|
||
{onClose && (
|
||
<button
|
||
onClick={onClose}
|
||
className="px-4 py-2 text-gray-600 border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
|
||
>
|
||
关闭
|
||
</button>
|
||
)}
|
||
<button
|
||
onClick={() => {
|
||
onConfigChange(currentConfig);
|
||
onClose?.();
|
||
}}
|
||
className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors"
|
||
>
|
||
应用配置
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|