feat: 优化 MCP 页面

- 改进 MCP 页面展示和交互
- 优化样式

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 16:55:50 +08:00
parent 3cc1461be2
commit 6693fcaf38

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import { ref, nextTick } from 'vue'
import { ref, nextTick, onMounted, computed } from 'vue'
import { useModelSettings } from './settings/useModelSettings'
interface MCPServer {
id: number
@@ -94,13 +95,23 @@ const newSkillForm = ref({
model: 'gpt-4o',
})
const availableModels = [
{ name: 'gpt-4o', provider: 'OpenAI', icon: 'fa-openai' },
{ name: 'gpt-4o-mini', provider: 'OpenAI', icon: 'fa-openai' },
{ name: 'claude-3-5-sonnet', provider: 'Anthropic', icon: 'fa-robot' },
{ name: 'gemini-2.0-flash', provider: 'Google', icon: 'fa-google' },
{ name: 'gpt-5', provider: 'OpenAI', icon: 'fa-openai' },
]
// 从 Model Settings 获取模型
const { models, fetchModels } = useModelSettings()
// 过滤 chat 类型的模型用于下拉框
const availableModels = computed(() => {
return models.value
.filter((m: any) => m.model_type === 'chat')
.map((m: any) => ({
name: m.model,
provider: m.provider,
icon: 'fa-brain'
}))
})
onMounted(() => {
fetchModels()
})
const goToStep2 = () => {
if (!newSkillForm.value.name.trim()) return
@@ -199,8 +210,10 @@ const toggleFolder = (folder: FolderItem) => {
// 新建文件夹
const isCreatingFolder = ref(false)
const newFolderName = ref('')
const showNewFolderInput = () => {
const newFolderParentId = ref<string | null>(null)
const showNewFolderInput = (parentId: string | null = null) => {
newFolderName.value = ''
newFolderParentId.value = parentId
isCreatingFolder.value = true
}
@@ -209,17 +222,41 @@ const createFolder = () => {
isCreatingFolder.value = false
return
}
fileTree.value.push({
const newFolder: FileItem = {
id: 'folder-' + Date.now(),
name: newFolderName.value,
icon: 'fa-folder',
type: 'folder',
expanded: true,
children: []
})
}
if (newFolderParentId.value) {
// 添加到指定文件夹作为子文件夹
const parentFolder = findFolderById(fileTree.value, newFolderParentId.value)
if (parentFolder && parentFolder.children) {
parentFolder.children.push(newFolder)
}
} else {
// 添加到根目录
fileTree.value.push(newFolder)
}
isCreatingFolder.value = false
}
// 递归查找文件夹
const findFolderById = (folders: FileItem[], id: string): FileItem | null => {
for (const folder of folders) {
if (folder.id === id) return folder
if (folder.children) {
const found = findFolderById(folder.children, id)
if (found) return found
}
}
return null
}
// 新建文件
const isCreatingFile = ref(false)
const newFileName = ref('')
@@ -356,6 +393,17 @@ const editingFileType = ref('')
// 选中文件/文件夹状态
// 鼠标悬停状态
const hoveringItem = ref<string | null>(null)
// 当前选中的文件夹(用于创建子文件夹/子文件)
const selectedFolder = ref<string | null>(null)
// 选中文件夹
const selectFolder = (folderId: string) => {
if (selectedFolder.value === folderId) {
selectedFolder.value = null // 再次点击取消选中
} else {
selectedFolder.value = folderId
}
}
// 删除文件
const deleteFile = (file: FileItem, parentId: string | null) => {
@@ -944,25 +992,26 @@ const statusClass = (status: string) => {
<label class="block text-sm font-medium text-gray-300 mb-2">
<i class="fa-solid fa-brain mr-2 text-primary-cyan"></i>Select Model
</label>
<div class="grid grid-cols-2 gap-3">
<div
<el-select
v-model="newSkillForm.model"
placeholder="Select a model"
class="w-full"
size="large"
popper-class="dark-select-dropdown"
>
<el-option
v-for="model in availableModels"
:key="model.name"
@click="newSkillForm.model = model.name"
class="p-3 rounded-xl border-2 cursor-pointer transition-all hover:scale-105"
:class="newSkillForm.model === model.name
? 'border-primary-orange bg-dark-600 shadow-lg shadow-primary-orange/20'
: 'border-dark-500 bg-dark-700 hover:border-gray-500'"
:label="`${model.name} (${model.provider})`"
:value="model.name"
>
<div class="flex items-center gap-2">
<i :class="['fa-solid', model.icon, 'text-lg']"></i>
<div>
<div class="font-medium text-white text-sm">{{ model.name }}</div>
<div class="text-xs text-gray-500">{{ model.provider }}</div>
</div>
<i :class="['fa-solid', model.icon, 'text-primary-cyan']"></i>
<span class="text-white">{{ model.name }}</span>
<span class="text-gray-500 text-sm">({{ model.provider }})</span>
</div>
</div>
</div>
</el-option>
</el-select>
</div>
</div>
@@ -1052,10 +1101,11 @@ const statusClass = (status: string) => {
>
<!-- 文件夹标题 -->
<div
@click.stop="selectFolder(folder.id)"
@mouseenter="hoveringItem = folder.id"
@mouseleave="hoveringItem = null"
class="flex items-center gap-2 px-2 py-1.5 cursor-pointer hover:bg-dark-700 group transition-colors"
:class="{ 'bg-dark-700': hoveringItem === folder.id }"
:class="{ 'bg-dark-700': hoveringItem === folder.id || selectedFolder === folder.id }"
>
<i
@click.stop="toggleFolder(folder)"
@@ -1068,6 +1118,8 @@ const statusClass = (status: string) => {
<span
class="text-sm text-gray-300 group-hover:text-white font-medium flex-1"
>{{ folder.name }}</span>
<!-- 选中状态指示器 -->
<span v-if="selectedFolder === folder.id" class="text-xs text-primary-cyan">Selected</span>
<button
@click.stop="deleteFolder(folder)"
class="text-red-400 hover:text-red-300 p-1 rounded hover:bg-dark-600 transition-colors opacity-0 group-hover:opacity-100"