Add frequently used prompt function.

This commit is contained in:
Rafi
2023-03-09 17:39:45 +08:00
parent 8340edbf40
commit 052f5299a0
4 changed files with 237 additions and 5 deletions

View File

@@ -65,7 +65,6 @@ const highlightCode = () => {
} }
watchEffect(() => { watchEffect(() => {
console.log('content changed', props.content)
contentHtml.value = props.content ? marked(props.content) : '' contentHtml.value = props.content ? marked(props.content) : ''
if (props.content && props.content.endsWith('```')) { if (props.content && props.content.endsWith('```')) {
nextTick(() => { nextTick(() => {

View File

@@ -2,13 +2,12 @@
<v-textarea <v-textarea
v-model="message" v-model="message"
:label="$t('writeAMessage')" :label="$t('writeAMessage')"
:placeholder="$t('writeAMessage') + '...'" :placeholder="hint"
rows="1" rows="1"
:auto-grow="autoGrow" :auto-grow="autoGrow"
:disabled="disabled" :disabled="disabled"
:loading="loading" :loading="loading"
:hint="hint" :hide-details="true"
:hide-details="loading"
append-inner-icon="send" append-inner-icon="send"
@keyup.enter.exact="enterOnly" @keyup.enter.exact="enterOnly"
@click:appendInner="clickSendBtn" @click:appendInner="clickSendBtn"
@@ -60,6 +59,9 @@ export default {
} }
this.message = "" this.message = ""
}, },
usePrompt(prompt) {
this.message = prompt
},
clickSendBtn () { clickSendBtn () {
this.send() this.send()
}, },

224
components/Prompt.vue Normal file
View File

@@ -0,0 +1,224 @@
<script setup>
const menu = ref(false)
const prompts = ref([])
const editingPrompt = ref(null)
const newPrompt = ref('')
const submittingNewPrompt = ref(false)
const promptInputErrorMessage = ref('')
const loadingPrompts = ref(false)
const deletingPromptIndex = ref(null)
const props = defineProps({
usePrompt: {
type: Function,
required: true
}
})
const addPrompt = async () => {
if (!newPrompt.value) {
promptInputErrorMessage.value = 'Please enter a prompt'
return
}
submittingNewPrompt.value = true
const { data, error } = await useAuthFetch('/api/chat/prompts/', {
method: 'POST',
body: JSON.stringify({
prompt: newPrompt.value
})
})
if (!error.value) {
prompts.value.push(data.value)
newPrompt.value = ''
}
submittingNewPrompt.value = false
}
const editPrompt = (index) => {
editingPrompt.value = Object.assign({}, prompts.value[index])
}
const updatePrompt = async (index) => {
editingPrompt.value.updating = true
const { data, error } = await useAuthFetch(`/api/chat/prompts/${editingPrompt.value.id}/`, {
method: 'PUT',
body: JSON.stringify({
prompt: editingPrompt.value.prompt
})
})
if (!error.value) {
prompts.value[index] = editingPrompt.value
}
editingPrompt.value.updating = false
editingPrompt.value = null
}
const cancelEditPrompt = () => {
editingPrompt.value = null
}
const deletePrompt = async (index) => {
deletingPromptIndex.value = index
const { data, error } = await useAuthFetch(`/api/chat/prompts/${prompts.value[index].id}/`, {
method: 'DELETE'
})
deletingPromptIndex.value = null
if (!error.value) {
prompts.value.splice(index, 1)
}
}
const loadPrompts = async () => {
loadingPrompts.value = true
const { data, error } = await useAuthFetch('/api/chat/prompts/')
if (!error.value) {
prompts.value = data.value
}
loadingPrompts.value = false
}
const selectPrompt = (prompt) => {
props.usePrompt(prompt.prompt)
menu.value = false
}
onMounted( () => {
loadPrompts()
})
</script>
<template>
<div>
<v-menu
v-model="menu"
:close-on-content-click="false"
>
<template v-slot:activator="{ props }">
<v-btn
v-bind="props"
icon="speaker_notes"
title="Common prompts"
class="mr-3"
></v-btn>
</template>
<v-container>
<v-card
min-width="300"
max-width="500"
>
<v-card-title>
<span class="headline">Frequently prompts</span>
</v-card-title>
<v-divider></v-divider>
<v-list>
<v-list-item v-show="loadingPrompts">
<v-list-item-title class="d-flex justify-center">
<v-progress-circular indeterminate></v-progress-circular>
</v-list-item-title>
</v-list-item>
<template
v-for="(prompt, idx) in prompts"
:key="prompt.id"
>
<v-list-item
active-color="primary"
rounded="xl"
v-if="editingPrompt && editingPrompt.id === prompt.id"
>
<v-textarea
rows="2"
v-model="editingPrompt.prompt"
:loading="editingPrompt.updating"
variant="underlined"
hide-details
density="compact"
>
<template v-slot:append>
<div class="d-flex flex-column">
<v-btn
icon="done"
variant="text"
:loading="editingPrompt.updating"
@click="updatePrompt(idx)"
>
</v-btn>
<v-btn
icon="close"
variant="text"
@click="cancelEditPrompt()"
>
</v-btn>
</div>
</template>
</v-textarea>
</v-list-item>
<v-list-item
v-if="!editingPrompt || editingPrompt.id !== prompt.id"
rounded="xl"
active-color="primary"
@click="selectPrompt(prompt)"
>
<v-list-item-title>{{ prompt.prompt }}</v-list-item-title>
<template v-slot:append>
<v-btn
icon="edit"
size="small"
variant="text"
@click="editPrompt(idx)"
>
</v-btn>
<v-btn
icon="delete"
size="small"
variant="text"
:loading="deletingPromptIndex === idx"
@click="deletePrompt(idx)"
>
</v-btn>
</template>
</v-list-item>
</template>
<v-list-item
active-color="primary"
>
<div
class="pt-3"
>
<v-textarea
rows="2"
v-model="newPrompt"
label="Add a new prompt"
variant="outlined"
density="compact"
:error-messages="promptInputErrorMessage"
@update:modelValue="promptInputErrorMessage = ''"
clearable
>
</v-textarea>
</div>
</v-list-item>
<v-list-item>
<v-btn
variant="text"
block
:loading="submittingNewPrompt"
@click="addPrompt()"
>
<v-icon icon="add"></v-icon>
Add prompt
</v-btn>
</v-list-item>
</v-list>
</v-card>
</v-container>
</v-menu>
</div>
</template>
<style scoped>
</style>

View File

@@ -1,4 +1,6 @@
<script setup> <script setup>
import Prompt from "~/components/Prompt.vue";
definePageMeta({ definePageMeta({
middleware: ["auth"] middleware: ["auth"]
}) })
@@ -140,6 +142,10 @@ const showSnackbar = (text) => {
snackbar.value = true snackbar.value = true
} }
const editor = ref(null)
const usePrompt = (prompt) => {
editor.value.usePrompt(prompt)
}
</script> </script>
@@ -188,6 +194,7 @@ const showSnackbar = (text) => {
<Welcome v-else /> <Welcome v-else />
<v-footer app class="d-flex flex-column"> <v-footer app class="d-flex flex-column">
<div class="px-md-16 w-100 d-flex align-center"> <div class="px-md-16 w-100 d-flex align-center">
<Prompt v-show="!fetchingResponse" :use-prompt="usePrompt" />
<v-btn <v-btn
v-show="fetchingResponse" v-show="fetchingResponse"
icon="close" icon="close"
@@ -195,7 +202,7 @@ const showSnackbar = (text) => {
class="mr-3" class="mr-3"
@click="stop" @click="stop"
></v-btn> ></v-btn>
<MsgEditor :send-message="send" :disabled="fetchingResponse" :loading="fetchingResponse" /> <MsgEditor ref="editor" :send-message="send" :disabled="fetchingResponse" :loading="fetchingResponse" />
</div> </div>
<div class="px-4 py-2 text-disabled text-caption font-weight-light text-center w-100"> <div class="px-4 py-2 text-disabled text-caption font-weight-light text-center w-100">