feat: skill unified

This commit is contained in:
qixinbo
2026-03-30 21:40:34 +08:00
parent 307ea0189e
commit 71e2b87583
3 changed files with 133 additions and 21 deletions
+37 -6
View File
@@ -53,6 +53,7 @@ const dedupeSkillsById = (skills: Skill[]): Skill[] => {
export function Skills() {
const { t } = useTranslation();
const [activeTab, setActiveTab] = useState<'skills' | 'mcp'>('skills');
const [sourceFilter, setSourceFilter] = useState<string>('all');
// Skills state
const [skills, setSkills] = useState<Skill[]>([]);
@@ -126,6 +127,14 @@ export function Skills() {
}
};
// Get unique sources for the filter dropdown
const uniqueSources = Array.from(new Set(skills.map(s => s.source))).filter(Boolean);
// Filtered skills
const filteredSkills = sourceFilter === 'all'
? skills
: skills.filter(skill => skill.source === sourceFilter);
const fetchMcpServers = async () => {
if (!currentProject) return;
setIsMcpLoading(true);
@@ -329,6 +338,19 @@ export function Skills() {
</div>
{activeTab === 'skills' ? (
<>
{uniqueSources.length > 0 && (
<Select value={sourceFilter} onValueChange={(val) => { if (val) setSourceFilter(val); }}>
<SelectTrigger className="w-[140px] h-9">
<SelectValue placeholder={t('filterBySource', '筛选来源')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">{t('allSources', '全部来源')}</SelectItem>
{uniqueSources.map(source => (
<SelectItem key={source} value={source}>{source}</SelectItem>
))}
</SelectContent>
</Select>
)}
<input
type="file"
ref={fileInputRef}
@@ -339,8 +361,17 @@ export function Skills() {
<Button
className="h-9 bg-[#ff4d29] hover:bg-[#ff4d29]/90 text-white gap-2 rounded-md px-3"
onClick={() => fileInputRef.current?.click()}
disabled={isLoading}
>
<Upload className="h-4 w-4" />{t('uploadSkill')}
{isLoading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Upload className="h-4 w-4" />}
{isLoading ? t('uploading', '上传中...') : t('uploadSkill')}
</Button>
<Button
onClick={() => setIsDialogOpen(true)}
className="h-9 bg-indigo-600 hover:bg-indigo-700 text-white gap-2 rounded-md px-3 shadow-sm"
>
<Plus className="h-4 w-4" />
{t('addNewSkill')}
</Button>
</>
) : (
@@ -393,7 +424,7 @@ export function Skills() {
</TableRow>
) : (
<>
{skills.map((skill, index) => (
{filteredSkills.map((skill, index) => (
<TableRow key={`${skill.id}_${index}`} className="group hover:bg-muted/50/50 transition-colors border-border">
<TableCell className="py-4 px-4 overflow-hidden">
<div className="flex items-start gap-3 min-w-0">
@@ -464,7 +495,7 @@ export function Skills() {
</TableCell>
</TableRow>
))}
{skills.length === 0 && (
{filteredSkills.length === 0 && (
<TableRow>
<TableCell colSpan={5} className="py-24 text-center">
<div className="flex flex-col items-center gap-3 text-muted-foreground">
@@ -517,11 +548,11 @@ export function Skills() {
</TableCell>
<TableCell className="py-4 px-4 text-muted-foreground text-sm truncate">
<div className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] md:text-xs font-medium whitespace-nowrap ${
mcp.status === 'connected'
!mcp.status || mcp.status === 'connected'
? 'bg-green-50 text-green-700 border border-green-100'
: mcp.status.startsWith('error')
? 'bg-red-50 text-red-700 border border-red-100'
: 'bg-muted/50 text-foreground/80 border border-border'
? 'bg-rose-50 text-rose-700 border border-rose-100'
: 'bg-amber-50 text-amber-700 border border-amber-100'
}`}
title={mcp.status}
>
+1 -1
View File
@@ -97,7 +97,7 @@ export function WebSearchConfig() {
<Label>{t('provider', 'Provider')}</Label>
<Select
value={config.provider}
onValueChange={(val) => setConfig({ ...config, provider: val })}
onValueChange={(val) => { if (val) setConfig({ ...config, provider: val }) }}
>
<SelectTrigger>
<SelectValue placeholder={t('selectProvider', 'Select a provider')} />