diff --git a/packages/views/agents/components/skill-add-dialog.tsx b/packages/views/agents/components/skill-add-dialog.tsx index 6a14495a6a..05d663f0de 100644 --- a/packages/views/agents/components/skill-add-dialog.tsx +++ b/packages/views/agents/components/skill-add-dialog.tsx @@ -1,7 +1,7 @@ "use client"; import { useState } from "react"; -import { FileText } from "lucide-react"; +import { FileText, Search } from "lucide-react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import type { Agent } from "@multica/core/types"; @@ -20,6 +20,7 @@ import { DialogHeader, DialogTitle, } from "@multica/ui/components/ui/dialog"; +import { Input } from "@multica/ui/components/ui/input"; import { useT } from "../../i18n"; /** @@ -46,11 +47,27 @@ export function SkillAddDialog({ const qc = useQueryClient(); const { data: workspaceSkills = [] } = useQuery(skillListOptions(wsId)); const [saving, setSaving] = useState(false); + const [query, setQuery] = useState(""); const agentSkillIds = new Set(agent.skills.map((s) => s.id)); const availableSkills = workspaceSkills.filter( (s) => !agentSkillIds.has(s.id), ); + const trimmedQuery = query.trim().toLowerCase(); + const filteredSkills = trimmedQuery + ? availableSkills.filter((s) => { + const name = s.name.toLowerCase(); + const description = s.description?.toLowerCase() ?? ""; + return ( + name.includes(trimmedQuery) || description.includes(trimmedQuery) + ); + }) + : availableSkills; + + const handleOpenChange = (v: boolean) => { + if (!v) setQuery(""); + onOpenChange(v); + }; const handleAdd = async (skillId: string) => { setSaving(true); @@ -58,7 +75,7 @@ export function SkillAddDialog({ const newIds = [...agent.skills.map((s) => s.id), skillId]; await api.setAgentSkills(agent.id, { skill_ids: newIds }); qc.invalidateQueries({ queryKey: workspaceKeys.agents(wsId) }); - onOpenChange(false); + handleOpenChange(false); } catch (e) { toast.error(e instanceof Error ? e.message : t(($) => $.tab_body.skills.add_failed_toast)); } finally { @@ -66,8 +83,11 @@ export function SkillAddDialog({ } }; + const showSearch = availableSkills.length > 0; + const noMatch = showSearch && filteredSkills.length === 0; + return ( - + {t(($) => $.tab_body.skills.add_dialog_title)} @@ -75,8 +95,21 @@ export function SkillAddDialog({ {t(($) => $.tab_body.skills.add_dialog_description)} + {showSearch && ( +
+ + setQuery(e.target.value)} + placeholder={t(($) => $.tab_body.skills.add_dialog_search_placeholder)} + aria-label={t(($) => $.tab_body.skills.add_dialog_search_placeholder)} + className="pl-7" + /> +
+ )}
- {availableSkills.map((skill) => ( + {filteredSkills.map((skill) => (
- diff --git a/packages/views/locales/en/agents.json b/packages/views/locales/en/agents.json index 900324c5e2..327755cad5 100644 --- a/packages/views/locales/en/agents.json +++ b/packages/views/locales/en/agents.json @@ -259,7 +259,9 @@ "remove_failed_toast": "Failed to remove skill", "add_dialog_title": "Add skill", "add_dialog_description": "Select a workspace skill to assign to this agent.", + "add_dialog_search_placeholder": "Search skills", "add_dialog_empty": "All workspace skills are already assigned.", + "add_dialog_no_match": "No skills match your search.", "add_dialog_cancel": "Cancel", "add_failed_toast": "Failed to add skill" }, diff --git a/packages/views/locales/zh-Hans/agents.json b/packages/views/locales/zh-Hans/agents.json index 87dc8d99b3..516b96450e 100644 --- a/packages/views/locales/zh-Hans/agents.json +++ b/packages/views/locales/zh-Hans/agents.json @@ -255,7 +255,9 @@ "remove_failed_toast": "移除 skill 失败", "add_dialog_title": "添加 skill", "add_dialog_description": "选择一个工作区 skill 分配给该智能体。", + "add_dialog_search_placeholder": "搜索 skill", "add_dialog_empty": "全部工作区 skill 都已分配。", + "add_dialog_no_match": "没有匹配的 skill。", "add_dialog_cancel": "取消", "add_failed_toast": "添加 skill 失败" },