mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
feat: edit and delete MCP
This commit is contained in:
@@ -28,3 +28,21 @@ export type MCPConnection =
|
||||
| StdioMCPConnection
|
||||
| HTTPMCPConnection
|
||||
| SSEMCPConnection
|
||||
|
||||
|
||||
export interface MCPListItem{
|
||||
id: string;
|
||||
type: string;
|
||||
name: string;
|
||||
config: {
|
||||
cwd: string;
|
||||
env: Record<string, string>;
|
||||
args: string[];
|
||||
type: string;
|
||||
command: string;
|
||||
};
|
||||
active: boolean;
|
||||
user: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import type { PrimitiveProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { BadgeVariants } from '.'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import type { PrimitiveProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import type { BadgeVariants } from "."
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { Primitive } from "reka-ui"
|
||||
import { cn } from '#/lib/utils'
|
||||
import { badgeVariants } from '.'
|
||||
import { badgeVariants } from "."
|
||||
|
||||
const props = defineProps<PrimitiveProps & {
|
||||
variant?: BadgeVariants['variant']
|
||||
class?: HTMLAttributes['class']
|
||||
variant?: BadgeVariants["variant"]
|
||||
class?: HTMLAttributes["class"]
|
||||
}>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -196,10 +196,7 @@
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<section class="flex gap-4">
|
||||
<Label
|
||||
|
||||
for="airplane-mode"
|
||||
>开启</Label>
|
||||
<Label for="airplane-mode">开启</Label>
|
||||
<Switch
|
||||
id="airplane-mode"
|
||||
:model-value="componentField.modelValue"
|
||||
@@ -262,9 +259,10 @@ import {
|
||||
import z from 'zod'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { ref,inject } from 'vue'
|
||||
import { useMutation,useQueryCache } from '@pinia/colada'
|
||||
import { ref, inject, watch } from 'vue'
|
||||
import { useMutation, useQueryCache } from '@pinia/colada'
|
||||
import request from '@/utils/request'
|
||||
import { type MCPListItem as MCPType } from '@memoh/shared'
|
||||
|
||||
const validateSchema = toTypedSchema(z.object({
|
||||
name: z.string().min(1),
|
||||
@@ -273,9 +271,9 @@ const validateSchema = toTypedSchema(z.object({
|
||||
command: z.string().min(1),
|
||||
args: z.array(z.coerce.string().check(z.minLength(1))).min(1),
|
||||
env: z.looseObject({}),
|
||||
cwd:z.string().min(1)
|
||||
cwd: z.string().min(1)
|
||||
}),
|
||||
active:z.coerce.boolean()
|
||||
active: z.coerce.boolean()
|
||||
}))
|
||||
|
||||
const envList = ref<string[]>([])
|
||||
@@ -284,22 +282,41 @@ const form = useForm({
|
||||
})
|
||||
|
||||
|
||||
const queryCache=useQueryCache()
|
||||
const queryCache = useQueryCache()
|
||||
const { mutate: fetchMCP } = useMutation({
|
||||
mutation: (data: Parameters<(Parameters<typeof form.handleSubmit>)[0]>[0]) => request({
|
||||
url: '/mcp/',
|
||||
method: 'post',
|
||||
url: mcpEditData.value?.id ? `/mcp/${mcpEditData.value.id}` : '/mcp/',
|
||||
method: mcpEditData.value?.id ? 'put' : 'post',
|
||||
data
|
||||
}),
|
||||
onSettled:()=>queryCache.invalidateQueries({key:['mcp']})
|
||||
onSettled: () => queryCache.invalidateQueries({ key: ['mcp'] })
|
||||
})
|
||||
|
||||
const open = inject('open', ref(false))
|
||||
const mcpEditData = inject('mcpEditData', ref<{
|
||||
name: string,
|
||||
config: MCPType['config'],
|
||||
active: boolean
|
||||
id: string
|
||||
} | null>(null))
|
||||
|
||||
watch(open, () => {
|
||||
if (open.value && mcpEditData.value) {
|
||||
form.setValues(mcpEditData.value)
|
||||
}
|
||||
|
||||
if (!open.value) {
|
||||
mcpEditData.value = null
|
||||
}
|
||||
}, {
|
||||
immediate: true
|
||||
})
|
||||
|
||||
const open=inject('open',ref(false))
|
||||
const createMCP = form.handleSubmit(async (value) => {
|
||||
// console.log(value)
|
||||
try {
|
||||
console.log(mcpEditData.value)
|
||||
fetchMCP(value)
|
||||
open.value=false
|
||||
open.value = false
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ const table = useVueTable({
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableBody class="[&_td]:py-4!">
|
||||
<template v-if="table.getRowModel().rows?.length">
|
||||
<TableRow
|
||||
v-for="row in table.getRowModel().rows"
|
||||
|
||||
@@ -1,55 +1,111 @@
|
||||
<template>
|
||||
<section>
|
||||
<section class="[&_td:last-child]:w-40">
|
||||
<CreateMCP />
|
||||
<DataTable
|
||||
:columns="columns"
|
||||
:data="[]"
|
||||
:data="mcpFormatData"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useQuery } from '@pinia/colada'
|
||||
import { useQuery,useMutation,useQueryCache } from '@pinia/colada'
|
||||
import request from '@/utils/request'
|
||||
import { watch, h, provide,ref } from 'vue'
|
||||
import { watch, h, provide,ref, computed,reactive } from 'vue'
|
||||
import DataTable from '@/components/DataTable/index.vue'
|
||||
import CreateMCP from '@/components/CreateMCP/index.vue'
|
||||
import { type ColumnDef } from '@tanstack/vue-table'
|
||||
import {
|
||||
Badge,
|
||||
Button
|
||||
} from '@memoh/ui'
|
||||
import { type MCPListItem as MCPType } from '@memoh/shared'
|
||||
|
||||
const open=ref(false)
|
||||
provide('open',open)
|
||||
|
||||
const columns = [
|
||||
{
|
||||
accessorKey: 'modelId',
|
||||
header: () => h('div', { class: 'text-left py-4' }, 'Name'),
|
||||
cell({ row }) {
|
||||
return h('div', { class: 'text-left py-4' }, row.getValue('modelId'))
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'baseUrl',
|
||||
header: () => h('div', { class: 'text-left' }, 'Base Url'),
|
||||
},
|
||||
{
|
||||
accessorKey: 'apiKey',
|
||||
header: () => h('div', { class: 'text-left' }, 'Api Key'),
|
||||
},
|
||||
{
|
||||
accessorKey: 'clientType',
|
||||
header: () => h('div', { class: 'text-left' }, 'Client Type'),
|
||||
},
|
||||
const open = ref(false)
|
||||
const editMCPData = ref<{
|
||||
name: string,
|
||||
config: MCPType['config'],
|
||||
active: boolean
|
||||
id:string
|
||||
}|null>(null)
|
||||
provide('open', open)
|
||||
provide('mcpEditData',editMCPData)
|
||||
|
||||
const queryCache=useQueryCache()
|
||||
const { mutate:DeleteMCP}= useMutation({
|
||||
mutation: (id:string) => request({
|
||||
url: `/mcp/${id}`,
|
||||
method:'DELETE'
|
||||
}),
|
||||
onSettled() {
|
||||
queryCache.invalidateQueries({
|
||||
key:['mcp']
|
||||
})
|
||||
}
|
||||
})
|
||||
const columns:ColumnDef<MCPType>[] = [
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: () => h('div', { class: 'text-left' }, 'Name'),
|
||||
header: () => h('div', { class: 'text-left py-4' }, 'Name'),
|
||||
|
||||
},
|
||||
{
|
||||
accessorKey: 'type',
|
||||
header: () => h('div', { class: 'text-left' }, 'Type'),
|
||||
},
|
||||
{
|
||||
accessorKey: 'config.command',
|
||||
header: () => h('div', { class: 'text-left' }, 'Command'),
|
||||
},
|
||||
{
|
||||
accessorKey: 'config.cwd',
|
||||
header: () => h('div', { class: 'text-left' }, 'Cwd'),
|
||||
},
|
||||
{
|
||||
accessorKey: 'config.args',
|
||||
header: () => h('div', { class: 'text-left' }, 'Arguments'),
|
||||
cell: ({ row }) => h('div', {class:'flex gap-4'}, row.original.config.args.map((argTxt) => {
|
||||
return h(Badge, {
|
||||
variant:'default'
|
||||
},()=>argTxt)
|
||||
}))
|
||||
},
|
||||
{
|
||||
accessorKey: 'config.env',
|
||||
header: () => h('div', { class: 'text-left' }, 'Env'),
|
||||
cell: ({ row }) => h('div', { class: 'flex gap-4' }, Object.entries(row.original.config.env).map(([key,value]) => {
|
||||
return h(Badge, {
|
||||
variant: 'outline'
|
||||
}, ()=>`${key}:${value}`)
|
||||
}))
|
||||
},
|
||||
{
|
||||
accessorKey: 'control',
|
||||
header: () => h('div', { class: 'text-center' }, '操作'),
|
||||
|
||||
cell: ({ row }) => h('div', {class:'flex gap-2'}, [
|
||||
h(Button, {
|
||||
onClick() {
|
||||
editMCPData.value = {
|
||||
name: row.original.name,
|
||||
config: {...row.original.config},
|
||||
active: row.original.active,
|
||||
id:row.original.id
|
||||
}
|
||||
open.value=true
|
||||
}
|
||||
}, ()=>'编辑'),
|
||||
h(Button, {
|
||||
variant: 'destructive',
|
||||
async onClick() {
|
||||
try {
|
||||
await DeleteMCP(row.original.id)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
},()=>'删除')
|
||||
])
|
||||
}
|
||||
]
|
||||
|
||||
@@ -60,7 +116,8 @@ const { data: mcpData } = useQuery({
|
||||
})
|
||||
})
|
||||
|
||||
watch(mcpData, () => {
|
||||
console.log(mcpData.value?.data)
|
||||
const mcpFormatData = computed(() => {
|
||||
return mcpData.value?.data?.items??[]
|
||||
})
|
||||
|
||||
</script>
|
||||
@@ -96,7 +96,7 @@ const columns: ComputedRef<ColumnDef<ModelType>[]> = computed(() => [
|
||||
accessorKey: 'modelId',
|
||||
header: () => h('div', { class: 'text-left py-4' }, 'Name'),
|
||||
cell({ row }) {
|
||||
return h('div', { class: 'text-left py-4' }, row.getValue('modelId'))
|
||||
return h('div', { class: 'text-left' }, row.getValue('modelId'))
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user