feat(AgentConfigSidebar): 新增工具配置项的刷新和跳转功能,优化用户交互体验

This commit is contained in:
Wenjie Zhang 2026-03-06 11:46:32 +08:00
parent 85d0867080
commit 3df0fce4f6
2 changed files with 180 additions and 13 deletions

View File

@ -148,16 +148,40 @@
<div v-if="getConfigOptions(value).length <= 5" class="multi-select-cards">
<div class="multi-select-label">
<span>已选择 {{ getSelectedCount(key) }} </span>
<a-button
type="link"
size="small"
class="clear-btn"
@click="clearSelection(key)"
v-if="getSelectedCount(key) > 0"
>
清空
</a-button>
<div class="label-actions">
<a-button
type="link"
size="small"
class="clear-btn"
@click="clearSelection(key)"
v-if="getSelectedCount(key) > 0"
>
清空
</a-button>
<template v-if="isToolsKind(value.template_metadata?.kind)">
<a-divider type="vertical" />
<a-button
type="link"
size="small"
@click="refreshConfigOptions(key, value.template_metadata.kind)"
class="action-btn"
>
<RotateCw :size="12" />
刷新
</a-button>
<a-button
type="link"
size="small"
@click="navigateToConfigPage(value.template_metadata.kind)"
class="action-btn"
>
<Settings :size="12" />
配置
</a-button>
</template>
</div>
</div>
<div class="options-grid">
<div
v-for="option in getConfigOptions(value)"
@ -322,6 +346,28 @@
<Search :size="16" class="search-icon" />
</template>
</a-input>
<template v-if="isToolsKind(currentConfigKind)">
<a-button
type="text"
size="small"
@click="refreshConfigOptions(currentConfigKey, currentConfigKind)"
class="modal-action-btn"
title="刷新列表"
>
<RotateCw :size="14" />
刷新
</a-button>
<a-button
type="text"
size="small"
@click="navigateToConfigPage(currentConfigKind)"
class="modal-action-btn"
title="跳转配置"
>
<Settings :size="14" />
配置
</a-button>
</template>
</div>
<div class="selection-list">
@ -367,7 +413,8 @@
<script setup>
import { ref, computed, nextTick, watch } from 'vue'
import { message, Modal } from 'ant-design-vue'
import { X, Trash2, Check, Plus, Search, Star } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
import { X, Trash2, Check, Plus, Search, Star, RotateCw, Settings } from 'lucide-vue-next'
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue'
import { useAgentStore } from '@/stores/agent'
import { useUserStore } from '@/stores/user'
@ -395,6 +442,7 @@ const emit = defineEmits(['close'])
const agentStore = useAgentStore()
const userStore = useUserStore()
const databaseStore = useDatabaseStore()
const router = useRouter()
watch(
() => props.isOpen,
@ -476,11 +524,15 @@ const segmentedOptions = computed(() => {
return options
})
const loadLiveSkillOptions = async () => {
const loadLiveSkillOptions = async (force = false) => {
if (!userStore.isAdmin) {
liveSkillOptions.value = []
return
}
//
if (!force && liveSkillOptions.value.length > 0) {
return
}
try {
const result = await skillApi.listSkills()
const rows = result?.data || []
@ -494,11 +546,15 @@ const loadLiveSkillOptions = async () => {
}
}
const loadToolOptions = async () => {
const loadToolOptions = async (force = false) => {
if (!userStore.isAdmin) {
toolOptionsFromApi.value = []
return
}
//
if (!force && toolOptionsFromApi.value.length > 0) {
return
}
try {
const result = await toolApi.getTools()
toolOptionsFromApi.value = (result?.data || []).map((item) => ({
@ -511,6 +567,61 @@ const loadToolOptions = async () => {
}
}
//
const isToolsKind = (kind) => {
return ['knowledges', 'tools', 'mcps', 'skills'].includes(kind)
}
//
const refreshConfigOptions = async (key, kind) => {
try {
switch (kind) {
case 'knowledges':
await databaseStore.loadDatabases(true)
message.success('知识库列表已刷新')
break
case 'tools':
await loadToolOptions(true)
message.success('工具列表已刷新')
break
case 'skills':
await loadLiveSkillOptions(true)
message.success('Skills 列表已刷新')
break
case 'mcps':
// MCP store
message.info('请在 MCP 管理页面刷新')
break
}
} catch (error) {
console.error('刷新配置选项失败:', error)
message.error('刷新失败')
}
}
//
const navigateToConfigPage = (kind) => {
//
closeSelectionModal()
//
setTimeout(() => {
switch (kind) {
case 'knowledges':
router.push('/database')
break
case 'tools':
router.push({ path: '/extensions', query: { tab: 'tools' } })
break
case 'mcps':
router.push({ path: '/extensions', query: { tab: 'mcp' } })
break
case 'skills':
router.push({ path: '/extensions', query: { tab: 'skills' } })
break
}
}, 100)
}
//
const getConfigOptions = (value) => {
if (value?.template_metadata?.kind === 'tools') {
@ -557,6 +668,11 @@ const getOptionDescription = (option) => {
return null
}
const currentConfigKind = computed(() => {
if (!currentConfigKey.value) return null
return configurableItems.value[currentConfigKey.value]?.template_metadata?.kind
})
const filteredOptions = computed(() => {
if (!currentConfigKey.value) return []
const key = currentConfigKey.value
@ -1167,6 +1283,27 @@ const confirmDeleteConfig = async () => {
margin-bottom: 12px;
font-size: 12px;
color: var(--gray-600);
.label-actions {
display: flex;
align-items: center;
gap: 4px;
.action-btn {
font-size: 12px;
color: var(--gray-600);
display: flex;
align-items: center;
gap: 2px;
padding: 2px 6px;
height: auto;
line-height: 1;
&:hover {
color: var(--main-color);
}
}
}
}
.options-grid {
@ -1238,8 +1375,12 @@ const confirmDeleteConfig = async () => {
.selection-modal-content {
.selection-search {
margin-bottom: 16px;
display: flex;
gap: 8px;
align-items: center;
.search-input {
flex: 1;
border-radius: 8px;
border: 1px solid var(--gray-300);
height: 36px;
@ -1265,6 +1406,19 @@ const confirmDeleteConfig = async () => {
border-color: var(--gray-400);
}
}
.modal-action-btn {
display: flex;
align-items: center;
gap: 4px;
font-size: 13px;
color: var(--gray-600);
white-space: nowrap;
&:hover {
color: var(--main-color);
}
}
}
.selection-list {

View File

@ -65,14 +65,27 @@
</template>
<script setup>
import { ref } from 'vue'
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { Upload, RotateCw, Plus } from 'lucide-vue-next'
import SkillsManagerComponent from '@/components/SkillsManagerComponent.vue'
import ToolsManagerComponent from '@/components/ToolsManagerComponent.vue'
import McpServersComponent from '@/components/McpServersComponent.vue'
const route = useRoute()
const activeTab = ref('tools')
const skillsRef = ref(null)
// query
watch(
() => route.query,
(query) => {
if (query.tab && ['tools', 'skills', 'mcp'].includes(query.tab)) {
activeTab.value = query.tab
}
},
{ immediate: true }
)
const toolsRef = ref(null)
const mcpRef = ref(null)