// ExpertsTab - 专家系统配置(来源目录 + 已发现专家 + 编辑/创建模态框) import { useState, useEffect, useCallback } from 'react'; import { Loader2, Pencil, Trash2 } from 'lucide-react'; import { Toggle, SectionCard, SourceEditor } from '../ui'; import { AddButton } from '../shared'; import { ExpertModal, expertToDraft, emptyExpertDraft, type ExpertDraft } from '../modals/ExpertModal'; import { listExperts, toggleExpert, deleteExpert, } from '../../../api/experts'; import type { TabProps, ToastProps } from '../shared'; import type { KnownSource, ExpertListResponse } from '../types'; type Props = TabProps & ToastProps; const EXPERT_KNOWN_SOURCES: KnownSource[] = [ { key: 'user', label: '用户专家', description: '~/.picobot/experts' }, { key: 'project', label: '项目专家', description: '.picobot/experts' }, ]; export function ExpertsTab({ config, update, setToast }: Props) { const [expertList, setExpertList] = useState(null); const [expertListLoading, setExpertListLoading] = useState(false); const [editingExpert, setEditingExpert] = useState(null); const fetchExpertList = useCallback(async () => { setExpertListLoading(true); const data = await listExperts(); if (data) setExpertList(data); setExpertListLoading(false); }, []); useEffect(() => { fetchExpertList(); }, [fetchExpertList]); const handleToggle = async (name: string, currentlyEnabled: boolean) => { if (!expertList) return; const prevList = expertList; setExpertList({ ...expertList, experts: expertList.experts.map((e) => e.name === name ? { ...e, disabled_in_scopes: currentlyEnabled ? ['project'] : [] } : e, ), }); try { const resp = await toggleExpert(name, 'project', !currentlyEnabled); const data = await resp.json(); if (!resp.ok || !data.success) { setExpertList(prevList); setToast(data.error || '切换专家状态失败'); return; } setExpertList({ ...prevList, experts: prevList.experts.map((e) => e.name === name ? { ...e, disabled_in_scopes: data.disabled_in_scopes || [] } : e, ), }); } catch { setExpertList(prevList); setToast('网络错误,切换专家状态失败'); } }; const handleDelete = async (name: string) => { if (!confirm(`确定删除专家 "${name}" 吗?此操作将删除对应文件。`)) return; try { const resp = await deleteExpert(name, 'project'); const data = await resp.json(); if (!resp.ok || !data.success) { setToast(data.error || '删除专家失败'); return; } setToast('专家已删除'); fetchExpertList(); } catch { setToast('网络错误,删除专家失败'); } }; return (
启用专家系统 update('experts', { ...config.experts, enabled: v })} />
update('experts', { ...config.experts, sources: v })} knownSources={EXPERT_KNOWN_SOURCES} examplePaths={['D:\\my-experts', '/home/user/shared-experts']} /> {expertList && expertList.experts_system_enabled && ( {expertListLoading && expertList.experts.length === 0 ? (
加载中...
) : expertList.experts.length === 0 ? (

未发现任何专家

) : (
{expertList.experts.map((expert) => { const isEnabled = expert.disabled_in_scopes.length === 0; return (
{expert.name} {expert.source}

{expert.description}

handleToggle(expert.name, isEnabled)} />
); })}
)}
)} setEditingExpert(emptyExpertDraft())} /> {editingExpert && ( setEditingExpert(null)} onSaved={() => { setEditingExpert(null); fetchExpertList(); }} setToast={setToast} /> )}
); }