|
|
@@ -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>
|