feat(web): 优化工具管理界面,重构搜索和分类筛选功能,提升用户体验
This commit is contained in:
parent
6078518577
commit
ef0f5ca233
@ -45,6 +45,7 @@
|
||||
- 新增内置Skills reporter,并移除内置 Agent reporter,数据库报表将由 Skills 完成
|
||||
- 重构内置 Skills 安装机制:内置 skill 改为在管理页以“未安装”状态展示,支持按需安装、基于 `version + content_hash` 的更新提示与覆盖确认,并对已安装内置 skill 禁止在线文件编辑
|
||||
- 新增知识库 PDF、图片的预览功能
|
||||
- 优化扩展页工具列表筛选区:将“全部分类”筛选收纳为搜索框右侧的紧凑下拉入口,并复用扩展页侧栏工具条样式,避免影响其他管理组件布局
|
||||
|
||||
<!-- 添加到这里 -->
|
||||
|
||||
|
||||
@ -30,6 +30,20 @@
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
|
||||
.sidebar-toolbar {
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border-bottom: 1px solid var(--gray-150);
|
||||
|
||||
.search-box {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.search-box {
|
||||
padding: 8px 12px 0;
|
||||
|
||||
@ -76,6 +90,34 @@
|
||||
// }
|
||||
}
|
||||
|
||||
.sidebar-tool {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
border-color: var(--gray-150);
|
||||
background-color: var(--gray-0);
|
||||
color: var(--gray-500);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
box-shadow: none;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: var(--gray-700);
|
||||
border-color: var(--gray-200);
|
||||
background-color: var(--gray-0);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--main-color);
|
||||
border-color: var(--main-color);
|
||||
background-color: color-mix(in srgb, var(--main-color) 8%, var(--gray-0));
|
||||
}
|
||||
}
|
||||
|
||||
.list-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
@ -37,13 +37,14 @@
|
||||
<span class="item-name">{{ skill.name }}</span>
|
||||
</div>
|
||||
<div class="item-status">
|
||||
<a-button
|
||||
<button
|
||||
v-if="skill.status === 'update_available'"
|
||||
size="small"
|
||||
type="button"
|
||||
class="skill-inline-action skill-inline-action-secondary"
|
||||
@click.stop="handleUpdateBuiltin(skill)"
|
||||
>
|
||||
更新
|
||||
</a-button>
|
||||
</button>
|
||||
<span
|
||||
v-else-if="skill.statusLabel"
|
||||
class="status-chip"
|
||||
@ -92,9 +93,13 @@
|
||||
<span class="item-name">{{ skill.name }}</span>
|
||||
</div>
|
||||
<div class="item-status">
|
||||
<a-button size="small" type="primary" @click.stop="handleInstallBuiltin(skill)">
|
||||
<button
|
||||
type="button"
|
||||
class="skill-inline-action skill-inline-action-primary"
|
||||
@click.stop="handleInstallBuiltin(skill)"
|
||||
>
|
||||
安装
|
||||
</a-button>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-details item-details-inline">
|
||||
@ -144,43 +149,40 @@
|
||||
>
|
||||
{{ currentSkillStatusLabel }}
|
||||
</span>
|
||||
<a-button
|
||||
<button
|
||||
v-if="currentSkill.is_builtin_spec && currentSkill.status === 'not_installed'"
|
||||
type="primary"
|
||||
size="small"
|
||||
type="button"
|
||||
@click="handleInstallBuiltin(currentSkill)"
|
||||
class="lucide-icon-btn"
|
||||
class="lucide-icon-btn skill-panel-action skill-panel-action-primary"
|
||||
>
|
||||
<span>安装</span>
|
||||
</a-button>
|
||||
<a-button
|
||||
</button>
|
||||
<button
|
||||
v-if="currentSkill.is_builtin_spec && currentSkill.status === 'update_available'"
|
||||
size="small"
|
||||
type="button"
|
||||
@click="handleUpdateBuiltin(currentSkill)"
|
||||
class="lucide-icon-btn"
|
||||
class="lucide-icon-btn skill-panel-action skill-panel-action-secondary"
|
||||
>
|
||||
<span>更新</span>
|
||||
</a-button>
|
||||
<a-button
|
||||
</button>
|
||||
<button
|
||||
v-if="isInstalledSkill"
|
||||
size="small"
|
||||
type="button"
|
||||
@click="handleExport"
|
||||
class="lucide-icon-btn"
|
||||
class="lucide-icon-btn skill-panel-action skill-panel-action-secondary"
|
||||
>
|
||||
<Download :size="14" />
|
||||
<span>导出</span>
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="isInstalledSkill && !isBuiltinInstalledSkill"
|
||||
size="small"
|
||||
danger
|
||||
ghost
|
||||
</button>
|
||||
<button
|
||||
v-if="isInstalledSkill"
|
||||
type="button"
|
||||
@click="confirmDeleteSkill"
|
||||
class="lucide-icon-btn"
|
||||
class="lucide-icon-btn skill-panel-action skill-panel-action-danger"
|
||||
>
|
||||
<Trash2 :size="14" />
|
||||
<span>删除</span>
|
||||
</a-button>
|
||||
<span>{{ isBuiltinInstalledSkill ? '卸载' : '删除' }}</span>
|
||||
</button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
@ -476,6 +478,10 @@ const isBuiltinInstalledSkill = computed(() => {
|
||||
return !!(isInstalledSkill.value && (currentSkill.value?.is_builtin || currentSkill.value?.installed_record))
|
||||
})
|
||||
|
||||
const currentSkillDeleteActionText = computed(() => {
|
||||
return isBuiltinInstalledSkill.value ? '卸载' : '删除'
|
||||
})
|
||||
|
||||
const currentSkillStatusLabel = computed(() => {
|
||||
const skill = currentSkill.value
|
||||
if (!skill) return ''
|
||||
@ -777,22 +783,26 @@ const handleCreateNode = async () => {
|
||||
|
||||
const confirmDeleteSkill = () => {
|
||||
if (!currentSkill.value || !isInstalledSkill.value) return
|
||||
const actionText = currentSkillDeleteActionText.value
|
||||
const detailText = isBuiltinInstalledSkill.value
|
||||
? '卸载后会移除已安装文件和数据库记录,但仍可从“未安装 Skills”中重新安装。'
|
||||
: '删除后无法恢复,所有文件和配置将永久消失。'
|
||||
Modal.confirm({
|
||||
title: `彻底删除技能「${currentSkill.value.slug}」?`,
|
||||
content: '删除后无法恢复,所有文件和配置将永久消失。',
|
||||
okText: '确认删除',
|
||||
title: `确认${actionText}技能「${currentSkill.value.slug}」?`,
|
||||
content: detailText,
|
||||
okText: `确认${actionText}`,
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await skillApi.deleteSkill(currentSkill.value.slug)
|
||||
message.success('已删除')
|
||||
message.success(`已${actionText}`)
|
||||
currentSkill.value = null
|
||||
treeData.value = []
|
||||
resetFileState()
|
||||
await fetchSkills()
|
||||
} catch {
|
||||
message.error('删除失败')
|
||||
message.error(`${actionText}失败`)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -975,6 +985,121 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.skill-inline-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 999px;
|
||||
min-width: 52px;
|
||||
height: 24px;
|
||||
padding: 0 9px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
box-shadow: none;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
transition:
|
||||
background-color 0.18s ease,
|
||||
border-color 0.18s ease,
|
||||
color 0.18s ease;
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
|
||||
&.skill-inline-action-primary {
|
||||
border-color: transparent;
|
||||
background: var(--main-600);
|
||||
color: var(--main-0);
|
||||
}
|
||||
|
||||
&.skill-inline-action-secondary {
|
||||
border-color: var(--main-100);
|
||||
background: var(--main-30);
|
||||
color: var(--main-700);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&.skill-inline-action-primary:hover,
|
||||
&.skill-inline-action-primary:focus {
|
||||
background: var(--main-700);
|
||||
color: var(--main-0);
|
||||
}
|
||||
|
||||
&.skill-inline-action-secondary:hover,
|
||||
&.skill-inline-action-secondary:focus {
|
||||
border-color: var(--main-200);
|
||||
background: var(--main-50);
|
||||
color: var(--main-800);
|
||||
}
|
||||
}
|
||||
|
||||
.skill-panel-action {
|
||||
min-height: 30px;
|
||||
padding: 0 12px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 999px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
box-shadow: none;
|
||||
font-weight: 500;
|
||||
transition:
|
||||
background-color 0.18s ease,
|
||||
border-color 0.18s ease,
|
||||
color 0.18s ease;
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&.skill-panel-action-primary {
|
||||
border-color: transparent;
|
||||
background: var(--main-600);
|
||||
color: var(--main-0);
|
||||
}
|
||||
|
||||
&.skill-panel-action-primary:hover,
|
||||
&.skill-panel-action-primary:focus {
|
||||
background: var(--main-700);
|
||||
color: var(--main-0);
|
||||
}
|
||||
|
||||
&.skill-panel-action-secondary {
|
||||
border-color: var(--gray-200);
|
||||
background: var(--gray-25);
|
||||
color: var(--gray-700);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: var(--gray-300);
|
||||
color: var(--gray-900);
|
||||
background: var(--gray-0);
|
||||
}
|
||||
}
|
||||
|
||||
&.skill-panel-action-danger {
|
||||
border-color: var(--color-error-100);
|
||||
background: var(--color-error-50);
|
||||
color: var(--color-error-700);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: var(--color-error-100);
|
||||
background: var(--color-error-100);
|
||||
color: var(--color-error-900);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel-status-chip {
|
||||
|
||||
@ -6,29 +6,33 @@
|
||||
<div class="layout-wrapper" :class="{ 'content-loading': loading }">
|
||||
<!-- 左侧:工具列表 -->
|
||||
<div class="sidebar-list">
|
||||
<div class="search-box">
|
||||
<a-input
|
||||
v-model:value="searchQuery"
|
||||
placeholder="搜索工具..."
|
||||
allow-clear
|
||||
class="search-input"
|
||||
>
|
||||
<template #prefix><Search :size="14" class="text-muted" /></template>
|
||||
</a-input>
|
||||
</div>
|
||||
<!-- 分类筛选 -->
|
||||
<div class="category-filter">
|
||||
<a-select
|
||||
v-model:value="selectedCategory"
|
||||
placeholder="全部分类"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
>
|
||||
<a-select-option value="">全部分类</a-select-option>
|
||||
<a-select-option v-for="cat in categories" :key="cat" :value="cat">
|
||||
{{ categoryLabels[cat] || cat }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<div class="sidebar-toolbar">
|
||||
<div class="search-box">
|
||||
<a-input
|
||||
v-model:value="searchQuery"
|
||||
placeholder="搜索工具..."
|
||||
allow-clear
|
||||
class="search-input"
|
||||
>
|
||||
<template #prefix><Search :size="14" class="text-muted" /></template>
|
||||
</a-input>
|
||||
</div>
|
||||
|
||||
<a-tooltip :title="`当前分类:${currentCategoryLabel}`">
|
||||
<a-dropdown trigger="click">
|
||||
<a-button class="sidebar-tool category-trigger" :class="{ active: !!selectedCategory }">
|
||||
<SlidersHorizontal :size="14" />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu :selectedKeys="[selectedCategory || 'all']" @click="handleCategorySelect">
|
||||
<a-menu-item key="all">全部分类</a-menu-item>
|
||||
<a-menu-item v-for="cat in categories" :key="cat">
|
||||
{{ categoryLabels[cat] || cat }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="list-container">
|
||||
@ -143,7 +147,7 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { Search, Wrench, Tag, Tags, FileText, List } from 'lucide-vue-next'
|
||||
import { Search, Wrench, Tag, Tags, FileText, List, SlidersHorizontal } from 'lucide-vue-next'
|
||||
import { toolApi } from '@/apis/tool_api'
|
||||
import { getToolIcon } from '@/components/ToolCallingResult/toolRegistry'
|
||||
|
||||
@ -189,6 +193,10 @@ const filteredTools = computed(() => {
|
||||
return result
|
||||
})
|
||||
|
||||
const currentCategoryLabel = computed(
|
||||
() => categoryLabels[selectedCategory.value] || (selectedCategory.value ? selectedCategory.value : '全部分类')
|
||||
)
|
||||
|
||||
const fetchTools = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
@ -209,6 +217,10 @@ const selectTool = (record) => {
|
||||
currentTool.value = record
|
||||
}
|
||||
|
||||
const handleCategorySelect = ({ key }) => {
|
||||
selectedCategory.value = key === 'all' ? '' : key
|
||||
}
|
||||
|
||||
onMounted(fetchTools)
|
||||
|
||||
// 暴露方法给父组件
|
||||
@ -220,11 +232,6 @@ defineExpose({
|
||||
<style scoped lang="less">
|
||||
@import '@/assets/css/extensions.less';
|
||||
|
||||
.category-filter {
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--gray-150);
|
||||
}
|
||||
|
||||
.list-item {
|
||||
.item-details {
|
||||
.item-tags {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user