/ src / renderer / components / pages / McpPromptPage.vue
McpPromptPage.vue
  1  <script setup lang="ts">
  2  import { useMessageStore } from '@/renderer/store/message'
  3  import { useLayoutStore } from '@/renderer/store/layout'
  4  import { usePromptStore } from '@/renderer/store/prompt'
  5  import { useSnackbarStore } from '@/renderer/store/snackbar'
  6  import { useRouter } from 'vue-router'
  7  import { ref } from 'vue'
  8  import { useRules } from '@/renderer/composables/useRules'
  9  
 10  const messageStore = useMessageStore()
 11  const promptStore = usePromptStore()
 12  const layoutStore = useLayoutStore()
 13  const snackbarStore = useSnackbarStore()
 14  
 15  const router = useRouter()
 16  
 17  const rules = useRules()
 18  
 19  const isValid = ref(true)
 20  
 21  const navigateTo = (route: string, screenValue: number) => {
 22    layoutStore.screen = screenValue
 23    router.push(route)
 24  }
 25  
 26  const handleApplyPrompt = async () => {
 27    try {
 28      const conversations = await promptStore.fetchSelect()
 29  
 30      if (!conversations || conversations.length === 0) {
 31        snackbarStore.showErrorMessage(
 32          'Prompt not found — possible MCP server issue or protocol mismatch, please contact the repo owner.'
 33        )
 34      }
 35      messageStore.initConversation(conversations)
 36      navigateTo('/chat', 1)
 37    } catch (error) {
 38      const errorMsg = error instanceof Error ? error.message : String(error)
 39      console.log(errorMsg)
 40      snackbarStore.showErrorMessage(errorMsg)
 41    }
 42  }
 43  </script>
 44  
 45  <template>
 46    <v-data-iterator
 47      :items="promptStore.promptList"
 48      :search="promptStore.search"
 49      items-per-page="-1"
 50      :loading="promptStore.loading"
 51      @update:options="promptStore.loadPrompts"
 52    >
 53      <template #header>
 54        <v-toolbar class="px-2" rounded="lg">
 55          <v-text-field
 56            v-model="promptStore.search"
 57            density="compact"
 58            placeholder="Search"
 59            prepend-inner-icon="mdi-magnify"
 60            variant="solo"
 61            clearable
 62            hide-details
 63          ></v-text-field>
 64        </v-toolbar>
 65      </template>
 66      <template #default="{ items }">
 67        <v-container class="pa-2" fluid>
 68          <v-row density="compact">
 69            <v-col
 70              v-for="item in items"
 71              :key="item.raw.title + ':' + item.raw.name"
 72              cols="auto"
 73              class="flex-fill"
 74            >
 75              <v-card border flat>
 76                <v-card-item :subtitle="item.raw.name" class="mb-2" :title="item.raw.title">
 77                  <template #append>
 78                    <v-btn
 79                      icon="mdi-lead-pencil"
 80                      size="small"
 81                      text="Read"
 82                      border
 83                      flat
 84                      @click="promptStore.select(item.raw)"
 85                    >
 86                    </v-btn>
 87                  </template>
 88                </v-card-item>
 89                <v-card-text>{{ item.raw.description }}</v-card-text>
 90              </v-card>
 91            </v-col>
 92          </v-row>
 93        </v-container>
 94      </template>
 95    </v-data-iterator>
 96  
 97    <v-dialog v-model="promptStore.promptSheet" max-width="80vw" max-height="80vh" scrollable>
 98      <v-card
 99        prepend-icon="mdi-account-cog-outline"
100        :title="$t('prompt.title') + ' - ' + promptStore.promptSelect.title"
101        :subtitle="promptStore.promptSelect.name"
102      >
103        <v-divider></v-divider>
104        <v-card-text class="pt-0">
105          <v-textarea
106            variant="plain"
107            :model-value="promptStore.promptSelect.description"
108            rows="1"
109            auto-grow
110            hide-details
111          ></v-textarea>
112          <v-form v-if="promptStore.promptSelect.arguments" ref="form" v-model="isValid" class="pt-2">
113            <v-textarea
114              v-for="argument in promptStore.promptSelect.arguments"
115              :key="argument.name"
116              v-model="argument.content"
117              :hint="argument.description"
118              class="mx-2 mt-3"
119              :base-color="argument.required ? 'secondary' : 'primary'"
120              :color="argument.required ? 'secondary' : 'primary'"
121              type="text"
122              variant="outlined"
123              :label="argument.name"
124              rows="1"
125              auto-grow
126              :rules="argument.required ? [rules.required] : []"
127            >
128            </v-textarea>
129          </v-form>
130        </v-card-text>
131        <v-divider></v-divider>
132        <v-card-actions>
133          <v-spacer></v-spacer>
134          <v-btn
135            class="my-2 mx-3 px-3"
136            variant="tonal"
137            :disabled="!isValid"
138            @click="handleApplyPrompt"
139          >
140            {{ $t('prompt.get') }}
141            <template #prepend>
142              <v-icon v-if="!isValid" icon="mdi-close-circle" color="error"></v-icon>
143              <v-icon v-else icon="mdi-check-circle" color="success"></v-icon>
144            </template>
145          </v-btn>
146        </v-card-actions>
147      </v-card>
148    </v-dialog>
149  </template>