Procházet zdrojové kódy

feat: Codex 官方技能目录支持搜索与筛选

- 技能与插件页 Codex 页签:按名称和描述搜索,按"全部 / 已安装 / 可安装"筛选,
  显示匹配数量
- 页面副标题改为"每个引擎接入各自厂商的官方技能仓库"

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Jerry Liang před 1 týdnem
rodič
revize
e6d38a4e70
1 změnil soubory, kde provedl 25 přidání a 2 odebrání
  1. 25 2
      apps/admin/src/views/PluginsView.vue

+ 25 - 2
apps/admin/src/views/PluginsView.vue

@@ -11,6 +11,16 @@ const OFFICIAL_SOURCE = 'anthropics/skills'
 const tab = ref<'claude' | 'codex'>('claude')
 const codex = ref<CodexSkillCatalogDto | null>(null)
 const codexBusy = ref<string | null>(null)
+const codexSearch = ref('')
+const codexFilter = ref<'all' | 'installed' | 'installable'>('all')
+const codexShown = computed(() => {
+  const q = codexSearch.value.trim().toLowerCase()
+  return (codex.value?.skills ?? []).filter(s =>
+    (!q || `${s.name} ${s.description}`.toLowerCase().includes(q))
+    && (codexFilter.value === 'all'
+      || (codexFilter.value === 'installed' && s.installed)
+      || (codexFilter.value === 'installable' && !s.installed && s.license === 'open')))
+})
 
 async function loadCodex() {
   codex.value = await admin.codexSkills.catalog().catch(err => (toast(errorMessage(err), 'error'), null))
@@ -94,7 +104,7 @@ onMounted(() => {
 
 <template>
   <div class="h-full flex flex-col">
-    <PageHeader title="技能与插件" subtitle="通过 Claude Code 官方插件机制安装和管理,AgentServer 不复制、不修改插件内容">
+    <PageHeader title="技能与插件" subtitle="每个引擎接入各自厂商的官方技能仓库,只提供给对应的引擎">
       <button type="button" class="btn-secondary" :disabled="loading" @click="load(true); loadCodex()"><span class="codicon codicon-refresh" />刷新</button>
     </PageHeader>
     <div class="flex-none flex gap-1 px-6 border-b border-b-solid border-border">
@@ -123,8 +133,20 @@ onMounted(() => {
             <span class="codicon" :class="codexBusy === 'refresh' ? 'codicon-loading codicon-modifier-spin' : 'codicon-cloud-download'" />{{ codex?.sha ? '更新目录' : '获取技能目录' }}
           </button>
         </div>
+        <div v-if="codex?.skills.length" class="flex items-center gap-3">
+          <div class="relative flex-1 max-w-100">
+            <span class="codicon codicon-search absolute left-2 top-1/2 -translate-y-1/2 text-fg-muted text-[13px]" />
+            <input v-model="codexSearch" class="field pl-7" placeholder="搜索技能名称或描述" aria-label="搜索技能">
+          </div>
+          <select v-model="codexFilter" class="field w-32" aria-label="筛选">
+            <option value="all">全部</option>
+            <option value="installed">已安装</option>
+            <option value="installable">可安装</option>
+          </select>
+          <span class="hint">{{ codexShown.length }} / {{ codex.skills.length }}</span>
+        </div>
         <div class="card divide-y divide-solid divide-border">
-          <div v-for="s in codex?.skills" :key="s.name" class="flex items-start gap-3 px-4 py-2.5">
+          <div v-for="s in codexShown" :key="s.name" class="flex items-start gap-3 px-4 py-2.5">
             <div class="flex-1 min-w-0">
               <div class="flex items-center gap-2">
                 <span class="font-500">{{ s.name }}</span>
@@ -142,6 +164,7 @@ onMounted(() => {
             </button>
           </div>
           <div v-if="codex && !codex.skills.length" class="px-4 py-3 hint">点击"获取技能目录"从官方仓库读取可用技能</div>
+          <div v-else-if="codex && !codexShown.length" class="px-4 py-3 hint">没有匹配的技能</div>
         </div>
         <div v-if="codex?.unmanaged.length" class="hint">Codex 技能目录中还有不是由 AgentServer 安装的技能:{{ codex.unmanaged.join('、') }}</div>
       </div>