feat: 添加支持 modelscope 安装的说明

This commit is contained in:
Wenjie Zhang 2026-06-03 22:35:32 +08:00
parent fafbc5c9ee
commit e94d293737
2 changed files with 207 additions and 120 deletions

View File

@ -294,71 +294,87 @@
<a href="https://skills.sh/" target="_blank" rel="noopener noreferrer" <a href="https://skills.sh/" target="_blank" rel="noopener noreferrer"
>skills.sh</a >skills.sh</a
> >
查询开源 skills 查询开源 skills 也支持 ModelScope 单个 Skill
地址每次仅限安装一个`https://modelscope.cn/skills/&lt;skill-id&gt;` Skill
ID 可在
<a href="https://modelscope.cn/skills" target="_blank" rel="noopener noreferrer"
>ModelScope Skill 市场</a
>
进入详情后从地址栏获取
</div> </div>
<!-- 仓库技能分页多选列表 --> <!-- 仓库技能分页多选列表 -->
<div v-if="remoteSkillOptions.length" class="skills-list-section"> <div v-if="remoteSkillOptions.length" class="skills-list-section">
<div class="list-operations-bar"> <template v-if="hasSingleRepoSkill">
<div class="op-buttons"> <div class="single-remote-skill-card">
<a-button size="small" type="link" @click="handleRepoSelectAll" <div class="single-remote-skill-name">{{ singleRepoSkill.name }}</div>
>全选</a-button <div class="single-remote-skill-meta">
> {{ singleRepoSkill.description || '暂无描述' }}
<a-button size="small" type="link" @click="handleRepoSelectInvert" </div>
>反选</a-button
>
<a-button size="small" type="link" @click="handleRepoSelectNone"
>清空</a-button
>
</div> </div>
<a-input </template>
v-model:value="repoFilterKeyword" <template v-else>
placeholder="本地过滤检索..." <div class="list-operations-bar">
size="small" <div class="op-buttons">
style="width: 180px" <a-button size="small" type="link" @click="handleRepoSelectAll"
allow-clear >全选</a-button
/> >
</div> <a-button size="small" type="link" @click="handleRepoSelectInvert"
<div class="skills-list-viewport"> >反选</a-button
<a-list >
size="small" <a-button size="small" type="link" @click="handleRepoSelectNone"
:pagination="{ pageSize: 5, size: 'small', showSizeChanger: false }" >清空</a-button
:data-source="filteredRepoSkills" >
class="remote-skills-list-container" </div>
> <a-input
<template #renderItem="{ item }"> v-model:value="repoFilterKeyword"
<a-list-item> placeholder="本地过滤检索..."
<div class="skill-list-item-content"> size="small"
<div class="skill-name-col"> style="width: 180px"
<a-checkbox allow-clear
:checked="selectedRepoSkills.includes(item.name)" />
@change=" </div>
(e) => handleToggleRepoSkill(item.name, e.target.checked) <div class="skills-list-viewport">
" <a-list
:disabled="installingRemoteSkill" size="small"
> :pagination="{ pageSize: 5, size: 'small', showSizeChanger: false }"
<span class="skill-item-name">{{ item.name }}</span> :data-source="filteredRepoSkills"
</a-checkbox> class="remote-skills-list-container"
>
<template #renderItem="{ item }">
<a-list-item>
<div class="skill-list-item-content">
<div class="skill-name-col">
<a-checkbox
:checked="selectedRepoSkills.includes(item.name)"
@change="
(e) => handleToggleRepoSkill(item.name, e.target.checked)
"
:disabled="installingRemoteSkill"
>
<span class="skill-item-name">{{ item.name }}</span>
</a-checkbox>
</div>
<div class="skill-desc-col">
<a-tooltip
:title="item.description || '暂无描述'"
placement="topLeft"
>
<span class="skill-item-desc">{{
item.description || '暂无描述'
}}</span>
</a-tooltip>
</div>
</div> </div>
<div class="skill-desc-col"> </a-list-item>
<a-tooltip </template>
:title="item.description || '暂无描述'" </a-list>
placement="topLeft" </div>
> <div class="remote-skill-summary">
<span class="skill-item-desc">{{ 已选 {{ selectedRepoSkills.length }} / 共发现
item.description || '暂无描述' {{ remoteSkillOptions.length }} skills
}}</span> </div>
</a-tooltip> </template>
</div>
</div>
</a-list-item>
</template>
</a-list>
</div>
<div class="remote-skill-summary">
已选 {{ selectedRepoSkills.length }} / 共发现
{{ remoteSkillOptions.length }} skills
</div>
</div> </div>
</a-form> </a-form>
</div> </div>
@ -392,65 +408,86 @@
<!-- 搜索结果列表 --> <!-- 搜索结果列表 -->
<div v-if="searchedSkills.length" class="skills-list-section"> <div v-if="searchedSkills.length" class="skills-list-section">
<div class="list-operations-bar"> <template v-if="hasSingleSearchedSkill">
<div class="op-buttons"> <div class="single-remote-skill-card">
<a-button size="small" type="link" @click="handleSearchSelectAll" <div class="single-remote-skill-header">
>全选</a-button <div class="single-remote-skill-name">
> {{ singleSearchedSkill.name }}
<a-button size="small" type="link" @click="handleSearchSelectInvert" </div>
>反选</a-button <a-tag
> v-if="singleSearchedSkill.installs"
<a-button size="small" type="link" @click="handleSearchSelectNone" color="blue"
>清空</a-button class="skill-item-installs"
> >
{{ singleSearchedSkill.installs }}
</a-tag>
</div>
<div class="single-remote-skill-meta">
{{ singleSearchedSkill.source }}
</div>
</div> </div>
</div> </template>
<div class="skills-list-viewport"> <template v-else>
<a-list <div class="list-operations-bar">
size="small" <div class="op-buttons">
:pagination="{ pageSize: 5, size: 'small', showSizeChanger: false }" <a-button size="small" type="link" @click="handleSearchSelectAll"
:data-source="searchedSkills" >全选</a-button
class="remote-skills-list-container" >
> <a-button size="small" type="link" @click="handleSearchSelectInvert"
<template #renderItem="{ item }"> >反选</a-button
<a-list-item> >
<div class="search-skill-item-row"> <a-button size="small" type="link" @click="handleSearchSelectNone"
<div class="skill-name-col"> >清空</a-button
<a-checkbox >
:checked=" </div>
selectedSearchSkills.some( </div>
(s) => s.name === item.name && s.source === item.source <div class="skills-list-viewport">
) <a-list
" size="small"
@change="(e) => handleToggleSearchSkill(item, e.target.checked)" :pagination="{ pageSize: 5, size: 'small', showSizeChanger: false }"
:disabled="installingRemoteSkill" :data-source="searchedSkills"
> class="remote-skills-list-container"
<span class="skill-item-name">{{ item.name }}</span> >
</a-checkbox> <template #renderItem="{ item }">
<a-list-item>
<div class="search-skill-item-row">
<div class="skill-name-col">
<a-checkbox
:checked="
selectedSearchSkills.some(
(s) => s.name === item.name && s.source === item.source
)
"
@change="(e) => handleToggleSearchSkill(item, e.target.checked)"
:disabled="installingRemoteSkill"
>
<span class="skill-item-name">{{ item.name }}</span>
</a-checkbox>
</div>
<div class="skill-repo-col">
<a-tooltip :title="item.source" placement="topLeft">
<span class="skill-item-repo">{{ item.source }}</span>
</a-tooltip>
</div>
<div class="skill-install-col">
<a-tag
v-if="item.installs"
color="blue"
class="skill-item-installs"
>
{{ item.installs }}
</a-tag>
</div>
</div> </div>
<div class="skill-repo-col"> </a-list-item>
<a-tooltip :title="item.source" placement="topLeft"> </template>
<span class="skill-item-repo">{{ item.source }}</span> </a-list>
</a-tooltip> </div>
</div> <div class="remote-skill-summary">
<div class="skill-install-col"> 已选择 {{ selectedSearchSkills.length }} / 共找到
<a-tag {{ searchedSkills.length }} skills
v-if="item.installs" </div>
color="blue" </template>
class="skill-item-installs"
>
{{ item.installs }}
</a-tag>
</div>
</div>
</a-list-item>
</template>
</a-list>
</div>
<div class="remote-skill-summary">
已选择 {{ selectedSearchSkills.length }} / 共找到
{{ searchedSkills.length }} skills
</div>
</div> </div>
</a-form> </a-form>
</div> </div>
@ -587,11 +624,15 @@ const remoteInstallForm = reactive({
const remoteSkillOptions = ref([]) const remoteSkillOptions = ref([])
const repoFilterKeyword = ref('') const repoFilterKeyword = ref('')
const selectedRepoSkills = ref([]) const selectedRepoSkills = ref([])
const hasSingleRepoSkill = computed(() => remoteSkillOptions.value.length === 1)
const singleRepoSkill = computed(() => remoteSkillOptions.value[0] || null)
const searchKeyword = ref('') const searchKeyword = ref('')
const searchingRemoteSkills = ref(false) const searchingRemoteSkills = ref(false)
const searchedSkills = ref([]) const searchedSkills = ref([])
const selectedSearchSkills = ref([]) const selectedSearchSkills = ref([])
const hasSingleSearchedSkill = computed(() => searchedSkills.value.length === 1)
const singleSearchedSkill = computed(() => searchedSkills.value[0] || null)
const repoHistory = ref([]) const repoHistory = ref([])
const allowedSkillAccessLevels = ref(['user']) const allowedSkillAccessLevels = ref(['user'])
@ -1049,12 +1090,17 @@ const handleListRemoteSkills = async () => {
try { try {
const result = await skillApi.listRemoteSkills(source) const result = await skillApi.listRemoteSkills(source)
remoteSkillOptions.value = result?.data || [] remoteSkillOptions.value = result?.data || []
selectedRepoSkills.value = [] selectedRepoSkills.value =
remoteSkillOptions.value.length === 1 ? [remoteSkillOptions.value[0].name] : []
if (!remoteSkillOptions.value.length) { if (!remoteSkillOptions.value.length) {
message.warning('未发现可安装的 Skills') message.warning('未发现可安装的 Skills')
return return
} }
message.success(`已发现 ${remoteSkillOptions.value.length} 个 Skills`) if (remoteSkillOptions.value.length === 1) {
message.success('已发现 1 个 Skill已自动选中')
} else {
message.success(`已发现 ${remoteSkillOptions.value.length} 个 Skills`)
}
// //
let history = [...repoHistory.value] let history = [...repoHistory.value]
@ -1112,9 +1158,11 @@ const handleSearchRemoteSkills = async () => {
try { try {
const result = await skillApi.searchRemoteSkills(query) const result = await skillApi.searchRemoteSkills(query)
searchedSkills.value = result?.data || [] searchedSkills.value = result?.data || []
selectedSearchSkills.value = [] selectedSearchSkills.value = searchedSkills.value.length === 1 ? [...searchedSkills.value] : []
if (!searchedSkills.value.length) { if (!searchedSkills.value.length) {
message.warning('未搜索到相关的 Skills') message.warning('未搜索到相关的 Skills')
} else if (searchedSkills.value.length === 1) {
message.success('搜索到 1 个 Skill已自动选中')
} else { } else {
message.success(`搜索到 ${searchedSkills.value.length} 个 Skills`) message.success(`搜索到 ${searchedSkills.value.length} 个 Skills`)
} }
@ -1627,6 +1675,45 @@ defineExpose({
} }
} }
.single-remote-skill-card {
border: 1px solid var(--gray-150);
border-radius: 6px;
background: var(--gray-0);
padding: 12px;
}
.single-remote-skill-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
min-width: 0;
.ant-tag {
flex-shrink: 0;
margin-inline-end: 0;
}
}
.single-remote-skill-name {
line-height: 20px;
font-weight: 600;
color: var(--gray-900);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.single-remote-skill-meta {
margin-top: 2px;
font-size: 12px;
line-height: 18px;
color: var(--gray-500);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.skill-list-item-content { .skill-list-item-content {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -283,7 +283,7 @@ const normalizedTags = computed(() => {
} }
&-mini { &-mini {
padding: 12px 14px; padding: 16px 14px;
gap: 0; gap: 0;
} }