feat: 优化 MCP 页面
- 改进 MCP 页面展示和交互 - 优化样式 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, nextTick } from 'vue'
|
import { ref, nextTick, onMounted, computed } from 'vue'
|
||||||
|
import { useModelSettings } from './settings/useModelSettings'
|
||||||
|
|
||||||
interface MCPServer {
|
interface MCPServer {
|
||||||
id: number
|
id: number
|
||||||
@@ -94,13 +95,23 @@ const newSkillForm = ref({
|
|||||||
model: 'gpt-4o',
|
model: 'gpt-4o',
|
||||||
})
|
})
|
||||||
|
|
||||||
const availableModels = [
|
// 从 Model Settings 获取模型
|
||||||
{ name: 'gpt-4o', provider: 'OpenAI', icon: 'fa-openai' },
|
const { models, fetchModels } = useModelSettings()
|
||||||
{ name: 'gpt-4o-mini', provider: 'OpenAI', icon: 'fa-openai' },
|
|
||||||
{ name: 'claude-3-5-sonnet', provider: 'Anthropic', icon: 'fa-robot' },
|
// 过滤 chat 类型的模型用于下拉框
|
||||||
{ name: 'gemini-2.0-flash', provider: 'Google', icon: 'fa-google' },
|
const availableModels = computed(() => {
|
||||||
{ name: 'gpt-5', provider: 'OpenAI', icon: 'fa-openai' },
|
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 = () => {
|
const goToStep2 = () => {
|
||||||
if (!newSkillForm.value.name.trim()) return
|
if (!newSkillForm.value.name.trim()) return
|
||||||
@@ -199,8 +210,10 @@ const toggleFolder = (folder: FolderItem) => {
|
|||||||
// 新建文件夹
|
// 新建文件夹
|
||||||
const isCreatingFolder = ref(false)
|
const isCreatingFolder = ref(false)
|
||||||
const newFolderName = ref('')
|
const newFolderName = ref('')
|
||||||
const showNewFolderInput = () => {
|
const newFolderParentId = ref<string | null>(null)
|
||||||
|
const showNewFolderInput = (parentId: string | null = null) => {
|
||||||
newFolderName.value = ''
|
newFolderName.value = ''
|
||||||
|
newFolderParentId.value = parentId
|
||||||
isCreatingFolder.value = true
|
isCreatingFolder.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,17 +222,41 @@ const createFolder = () => {
|
|||||||
isCreatingFolder.value = false
|
isCreatingFolder.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fileTree.value.push({
|
|
||||||
|
const newFolder: FileItem = {
|
||||||
id: 'folder-' + Date.now(),
|
id: 'folder-' + Date.now(),
|
||||||
name: newFolderName.value,
|
name: newFolderName.value,
|
||||||
icon: 'fa-folder',
|
icon: 'fa-folder',
|
||||||
type: 'folder',
|
type: 'folder',
|
||||||
expanded: true,
|
expanded: true,
|
||||||
children: []
|
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
|
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 isCreatingFile = ref(false)
|
||||||
const newFileName = ref('')
|
const newFileName = ref('')
|
||||||
@@ -356,6 +393,17 @@ const editingFileType = ref('')
|
|||||||
// 选中文件/文件夹状态
|
// 选中文件/文件夹状态
|
||||||
// 鼠标悬停状态
|
// 鼠标悬停状态
|
||||||
const hoveringItem = ref<string | null>(null)
|
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) => {
|
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">
|
<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
|
<i class="fa-solid fa-brain mr-2 text-primary-cyan"></i>Select Model
|
||||||
</label>
|
</label>
|
||||||
<div class="grid grid-cols-2 gap-3">
|
<el-select
|
||||||
<div
|
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"
|
v-for="model in availableModels"
|
||||||
:key="model.name"
|
:key="model.name"
|
||||||
@click="newSkillForm.model = model.name"
|
:label="`${model.name} (${model.provider})`"
|
||||||
class="p-3 rounded-xl border-2 cursor-pointer transition-all hover:scale-105"
|
:value="model.name"
|
||||||
: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'"
|
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<i :class="['fa-solid', model.icon, 'text-lg']"></i>
|
<i :class="['fa-solid', model.icon, 'text-primary-cyan']"></i>
|
||||||
<div>
|
<span class="text-white">{{ model.name }}</span>
|
||||||
<div class="font-medium text-white text-sm">{{ model.name }}</div>
|
<span class="text-gray-500 text-sm">({{ model.provider }})</span>
|
||||||
<div class="text-xs text-gray-500">{{ model.provider }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</el-option>
|
||||||
</div>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1052,10 +1101,11 @@ const statusClass = (status: string) => {
|
|||||||
>
|
>
|
||||||
<!-- 文件夹标题 -->
|
<!-- 文件夹标题 -->
|
||||||
<div
|
<div
|
||||||
|
@click.stop="selectFolder(folder.id)"
|
||||||
@mouseenter="hoveringItem = folder.id"
|
@mouseenter="hoveringItem = folder.id"
|
||||||
@mouseleave="hoveringItem = null"
|
@mouseleave="hoveringItem = null"
|
||||||
class="flex items-center gap-2 px-2 py-1.5 cursor-pointer hover:bg-dark-700 group transition-colors"
|
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
|
<i
|
||||||
@click.stop="toggleFolder(folder)"
|
@click.stop="toggleFolder(folder)"
|
||||||
@@ -1068,6 +1118,8 @@ const statusClass = (status: string) => {
|
|||||||
<span
|
<span
|
||||||
class="text-sm text-gray-300 group-hover:text-white font-medium flex-1"
|
class="text-sm text-gray-300 group-hover:text-white font-medium flex-1"
|
||||||
>{{ folder.name }}</span>
|
>{{ folder.name }}</span>
|
||||||
|
<!-- 选中状态指示器 -->
|
||||||
|
<span v-if="selectedFolder === folder.id" class="text-xs text-primary-cyan">Selected</span>
|
||||||
<button
|
<button
|
||||||
@click.stop="deleteFolder(folder)"
|
@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"
|
class="text-red-400 hover:text-red-300 p-1 rounded hover:bg-dark-600 transition-colors opacity-0 group-hover:opacity-100"
|
||||||
|
|||||||
Reference in New Issue
Block a user