feat(AgentConfigSidebar): 新增工具配置项的刷新和跳转功能,优化用户交互体验
This commit is contained in:
parent
85d0867080
commit
3df0fce4f6
@ -148,16 +148,40 @@
|
|||||||
<div v-if="getConfigOptions(value).length <= 5" class="multi-select-cards">
|
<div v-if="getConfigOptions(value).length <= 5" class="multi-select-cards">
|
||||||
<div class="multi-select-label">
|
<div class="multi-select-label">
|
||||||
<span>已选择 {{ getSelectedCount(key) }} 项</span>
|
<span>已选择 {{ getSelectedCount(key) }} 项</span>
|
||||||
<a-button
|
<div class="label-actions">
|
||||||
type="link"
|
<a-button
|
||||||
size="small"
|
type="link"
|
||||||
class="clear-btn"
|
size="small"
|
||||||
@click="clearSelection(key)"
|
class="clear-btn"
|
||||||
v-if="getSelectedCount(key) > 0"
|
@click="clearSelection(key)"
|
||||||
>
|
v-if="getSelectedCount(key) > 0"
|
||||||
清空
|
>
|
||||||
</a-button>
|
清空
|
||||||
|
</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>
|
||||||
|
|
||||||
<div class="options-grid">
|
<div class="options-grid">
|
||||||
<div
|
<div
|
||||||
v-for="option in getConfigOptions(value)"
|
v-for="option in getConfigOptions(value)"
|
||||||
@ -322,6 +346,28 @@
|
|||||||
<Search :size="16" class="search-icon" />
|
<Search :size="16" class="search-icon" />
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</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>
|
||||||
|
|
||||||
<div class="selection-list">
|
<div class="selection-list">
|
||||||
@ -367,7 +413,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, nextTick, watch } from 'vue'
|
import { ref, computed, nextTick, watch } from 'vue'
|
||||||
import { message, Modal } from 'ant-design-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 ModelSelectorComponent from '@/components/ModelSelectorComponent.vue'
|
||||||
import { useAgentStore } from '@/stores/agent'
|
import { useAgentStore } from '@/stores/agent'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
@ -395,6 +442,7 @@ const emit = defineEmits(['close'])
|
|||||||
const agentStore = useAgentStore()
|
const agentStore = useAgentStore()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const databaseStore = useDatabaseStore()
|
const databaseStore = useDatabaseStore()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.isOpen,
|
() => props.isOpen,
|
||||||
@ -476,11 +524,15 @@ const segmentedOptions = computed(() => {
|
|||||||
return options
|
return options
|
||||||
})
|
})
|
||||||
|
|
||||||
const loadLiveSkillOptions = async () => {
|
const loadLiveSkillOptions = async (force = false) => {
|
||||||
if (!userStore.isAdmin) {
|
if (!userStore.isAdmin) {
|
||||||
liveSkillOptions.value = []
|
liveSkillOptions.value = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 如果不是强制刷新且已有数据,则跳过
|
||||||
|
if (!force && liveSkillOptions.value.length > 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const result = await skillApi.listSkills()
|
const result = await skillApi.listSkills()
|
||||||
const rows = result?.data || []
|
const rows = result?.data || []
|
||||||
@ -494,11 +546,15 @@ const loadLiveSkillOptions = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadToolOptions = async () => {
|
const loadToolOptions = async (force = false) => {
|
||||||
if (!userStore.isAdmin) {
|
if (!userStore.isAdmin) {
|
||||||
toolOptionsFromApi.value = []
|
toolOptionsFromApi.value = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 如果不是强制刷新且已有数据,则跳过
|
||||||
|
if (!force && toolOptionsFromApi.value.length > 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const result = await toolApi.getTools()
|
const result = await toolApi.getTools()
|
||||||
toolOptionsFromApi.value = (result?.data || []).map((item) => ({
|
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) => {
|
const getConfigOptions = (value) => {
|
||||||
if (value?.template_metadata?.kind === 'tools') {
|
if (value?.template_metadata?.kind === 'tools') {
|
||||||
@ -557,6 +668,11 @@ const getOptionDescription = (option) => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const currentConfigKind = computed(() => {
|
||||||
|
if (!currentConfigKey.value) return null
|
||||||
|
return configurableItems.value[currentConfigKey.value]?.template_metadata?.kind
|
||||||
|
})
|
||||||
|
|
||||||
const filteredOptions = computed(() => {
|
const filteredOptions = computed(() => {
|
||||||
if (!currentConfigKey.value) return []
|
if (!currentConfigKey.value) return []
|
||||||
const key = currentConfigKey.value
|
const key = currentConfigKey.value
|
||||||
@ -1167,6 +1283,27 @@ const confirmDeleteConfig = async () => {
|
|||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--gray-600);
|
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 {
|
.options-grid {
|
||||||
@ -1238,8 +1375,12 @@ const confirmDeleteConfig = async () => {
|
|||||||
.selection-modal-content {
|
.selection-modal-content {
|
||||||
.selection-search {
|
.selection-search {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.search-input {
|
.search-input {
|
||||||
|
flex: 1;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid var(--gray-300);
|
border: 1px solid var(--gray-300);
|
||||||
height: 36px;
|
height: 36px;
|
||||||
@ -1265,6 +1406,19 @@ const confirmDeleteConfig = async () => {
|
|||||||
border-color: var(--gray-400);
|
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 {
|
.selection-list {
|
||||||
|
|||||||
@ -65,14 +65,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<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 { Upload, RotateCw, Plus } from 'lucide-vue-next'
|
||||||
import SkillsManagerComponent from '@/components/SkillsManagerComponent.vue'
|
import SkillsManagerComponent from '@/components/SkillsManagerComponent.vue'
|
||||||
import ToolsManagerComponent from '@/components/ToolsManagerComponent.vue'
|
import ToolsManagerComponent from '@/components/ToolsManagerComponent.vue'
|
||||||
import McpServersComponent from '@/components/McpServersComponent.vue'
|
import McpServersComponent from '@/components/McpServersComponent.vue'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
const activeTab = ref('tools')
|
const activeTab = ref('tools')
|
||||||
const skillsRef = ref(null)
|
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 toolsRef = ref(null)
|
||||||
const mcpRef = ref(null)
|
const mcpRef = ref(null)
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user