/ src / renderer / components / pages / McpDxtPage.vue
McpDxtPage.vue
  1  <script setup lang="ts">
  2  import { ref, watch } from 'vue'
  3  import { FileTransfer } from '@/renderer/utils'
  4  import { useMcpStore } from '@/renderer/store/mcp'
  5  const mcpStore = useMcpStore()
  6  
  7  const props = defineProps({
  8    modelValue: {
  9      type: Boolean,
 10      required: true
 11    }
 12  })
 13  
 14  const emit = defineEmits(['update:modelValue'])
 15  
 16  const internalDialog = ref(props.modelValue)
 17  
 18  const files = ref([] as File[])
 19  const loading = ref(false)
 20  
 21  watch(files, (val) => {
 22    console.log(val)
 23  })
 24  
 25  const filterFiles = () => {
 26    files.value = files.value.filter((file) => {
 27      return file.name.toLowerCase().endsWith('.mcpb')
 28    })
 29  }
 30  
 31  const processFiles = async () => {
 32    try {
 33      loading.value = true
 34      console.log(files)
 35      const fileList = files.value
 36      files.value = []
 37  
 38      const promises = fileList.map(async (file) => {
 39        const arrayBuffer = await file.arrayBuffer()
 40        return FileTransfer.request({
 41          name: file.name,
 42          data: arrayBuffer
 43        })
 44      })
 45  
 46      await Promise.all(promises)
 47  
 48      await FileTransfer.response(fileList.length)
 49  
 50      await window.dxtManifest?.refresh()
 51      await window.mcpServers?.refresh()
 52      mcpStore.version++
 53    } finally {
 54      loading.value = false
 55      closeDialog()
 56    }
 57  }
 58  
 59  watch(
 60    () => props.modelValue,
 61    (newVal) => {
 62      internalDialog.value = newVal
 63    }
 64  )
 65  
 66  watch(internalDialog, (newVal) => {
 67    emit('update:modelValue', newVal)
 68  })
 69  
 70  const closeDialog = () => {
 71    files.value = []
 72    internalDialog.value = false
 73  }
 74  </script>
 75  
 76  <template>
 77    <v-dialog v-model="internalDialog" persistent max-width="80vw" max-height="80vh" scrollable>
 78      <v-card :title="$t('dxt.title')">
 79        <template #append>
 80          <v-btn
 81            icon="mdi-close-box"
 82            rounded="lg"
 83            color="error"
 84            variant="text"
 85            @click="closeDialog"
 86          ></v-btn>
 87        </template>
 88        <v-divider></v-divider>
 89        <v-card-text>
 90          <slot></slot>
 91          <v-card variant="flat" :loading="loading">
 92            <v-file-upload
 93              v-model="files"
 94              :disabled="loading"
 95              color="light-grey"
 96              class="mb-2"
 97              density="compact"
 98              accept=".mcpb"
 99              clearable
100              show-size
101              multiple
102              scrim="primary"
103              @change="filterFiles"
104            >
105              <template #icon>
106                <v-icon class="mb-2" size="x-small" icon="mdi-upload"></v-icon>
107              </template>
108              <template #title>
109                <div class="text-grey text-h6"> .MCPB {{ $t('mcp.file') }} </div>
110              </template>
111            </v-file-upload>
112          </v-card>
113        </v-card-text>
114  
115        <v-card-actions>
116          <v-spacer></v-spacer>
117  
118          <v-btn
119            variant="plain"
120            rounded="lg"
121            :disabled="files.length === 0"
122            icon="mdi-delete-outline"
123            color="error"
124            @click="files.length = 0"
125          ></v-btn>
126  
127          <v-btn
128            variant="plain"
129            rounded="lg"
130            :disabled="files.length === 0"
131            icon="mdi-content-save-plus"
132            color="success"
133            @click="processFiles()"
134          ></v-btn>
135        </v-card-actions>
136      </v-card>
137    </v-dialog>
138  </template>