Compare commits

..

6 Commits

Author SHA1 Message Date
Rafi
727826f1b1 Added a Sign-out button 2023-03-19 14:26:46 +08:00
Rafi
386659109c Added a new message action: delete 2023-03-19 13:49:12 +08:00
Rafi
bd9e8bf45e Optimize the editor and enhance the user experience. 2023-03-19 13:39:20 +08:00
Rafi
4e40530a8c Added a new message action: edit 2023-03-19 13:13:27 +08:00
Rafi
ea69a350f4 add environment variable NUXT_DEV_SERVER 2023-03-19 12:53:44 +08:00
Rafi
18a4251714 feat: Message actions 2023-03-17 18:27:07 +08:00
7 changed files with 177 additions and 49 deletions

View File

@@ -0,0 +1,98 @@
<script setup>
import copy from 'copy-to-clipboard'
const props = defineProps({
message: {
type: Object,
required: true
},
messageIndex: {
type: Number,
required: true
},
usePrompt: {
type: Function,
required: true
},
deleteMessage: {
type: Function,
required: true
}
})
const snackbar = ref(false)
const snackbarText = ref('')
const showSnackbar = (text) => {
snackbarText.value = text
snackbar.value = true
}
const copyMessage = () => {
copy(props.message.message)
showSnackbar('Copied!')
}
const editMessage = () => {
props.usePrompt(props.message.message)
}
const deleteMessage = async () => {
const { data, error } = await useAuthFetch(`/api/chat/messages/${props.message.id}/`, {
method: 'DELETE'
})
if (!error.value) {
this.$emit('deleteMessage', props.messageIndex)
showSnackbar('Deleted!')
}
showSnackbar('Delete failed')
}
</script>
<template>
<v-menu
>
<template v-slot:activator="{ props }">
<v-btn
v-bind="props"
icon
variant="text"
class="mx-1"
>
<v-icon icon="more_horiz"></v-icon>
</v-btn>
</template>
<v-list>
<v-list-item
@click="copyMessage()"
:title="$t('copy')"
prepend-icon="content_copy"
>
</v-list-item>
<v-list-item
@click="editMessage()"
:title="$t('edit')"
prepend-icon="edit"
>
</v-list-item>
<v-list-item
@click="deleteMessage()"
:title="$t('delete')"
prepend-icon="delete"
>
</v-list-item>
</v-list>
</v-menu>
<v-snackbar
v-model="snackbar"
location="top"
timeout="2000"
>
{{ snackbarText }}
</v-snackbar>
</template>
<style scoped>
</style>

View File

@@ -1,17 +1,29 @@
<template>
<div
class="flex-grow-1 d-flex align-center justify-space-between"
>
<v-textarea
v-model="message"
:label="$t('writeAMessage')"
:placeholder="hint"
rows="1"
:rows="rows"
max-rows="8"
:auto-grow="autoGrow"
:disabled="disabled"
:loading="loading"
:hide-details="true"
append-inner-icon="send"
@keyup.enter.exact="enterOnly"
@click:appendInner="clickSendBtn"
clearable
variant="outlined"
@keydown.enter.exact="enterOnly"
></v-textarea>
<v-btn
:disabled="loading"
icon="send"
title="Send"
class="ml-3"
@click="clickSendBtn"
></v-btn>
</div>
</template>
<script>
@@ -39,7 +51,7 @@ export default {
message(val) {
const lines = val.split(/\r\n|\r|\n/).length;
if (lines > 8) {
this.rows = lines;
this.rows = 8;
this.autoGrow = false;
} else {
this.rows = 1;
@@ -65,7 +77,8 @@ export default {
clickSendBtn () {
this.send()
},
enterOnly () {
enterOnly (event) {
event.preventDefault();
if (!isMobile()) {
this.send()
}

View File

@@ -29,6 +29,11 @@
"me": "Me",
"ai": "AI"
},
"edit": "Edit",
"copy": "Copy",
"copied": "Copied",
"delete": "Delete",
"signOut": "Sign out",
"welcomeScreen": {
"introduction1": "is an unofficial client for ChatGPT, but uses the official OpenAI API.",
"introduction2": "You will need an OpenAI API Key before you can use this client.",

View File

@@ -29,6 +29,11 @@
"me": "我",
"ai": "AI"
},
"edit": "编辑",
"copy": "复制",
"copied": "已复制",
"delete": "删除",
"signOut": "退出登录",
"welcomeScreen": {
"introduction1": "是一个非官方的ChatGPT客户端但使用OpenAI的官方API",
"introduction2": "在使用本客户端之前您需要一个OpenAI API密钥。",

View File

@@ -1,7 +1,7 @@
<script setup>
import {useDisplay} from "vuetify";
const { $i18n } = useNuxtApp()
const { $i18n, $auth } = useNuxtApp()
const runtimeConfig = useRuntimeConfig()
const colorMode = useColorMode()
const drawer = ref(null)
@@ -88,6 +88,15 @@ const drawerPermanent = computed(() => {
return mdAndUp.value
})
const signOut = async () => {
const { data, error } = await useFetch('/api/account/logout/', {
method: 'POST'
})
if (!error.value) {
await $auth.logout()
}
}
onNuxtReady(async () => {
loadConversations()
})
@@ -261,6 +270,14 @@ onNuxtReady(async () => {
:title="$t('feedback')"
@click="feedback"
></v-list-item>
<v-list-item
rounded="xl"
prepend-icon="logout"
:title="$t('signOut')"
@click="signOut"
></v-list-item>
</v-list>
</div>
</template>
@@ -281,24 +298,6 @@ onNuxtReady(async () => {
@click="createNewConversion()"
></v-btn>
<!-- <v-menu-->
<!-- >-->
<!-- <template v-slot:activator="{ props }">-->
<!-- <v-btn-->
<!-- v-bind="props"-->
<!-- icon="help_outline"-->
<!-- title="Feedback"-->
<!-- ></v-btn>-->
<!-- </template>-->
<!-- <v-list-->
<!-- >-->
<!-- <v-list-item-->
<!-- @click="feedback"-->
<!-- >-->
<!-- <v-list-item-title>{{ $t('feedback') }}</v-list-item-title>-->
<!-- </v-list-item>-->
<!-- </v-list>-->
<!-- </v-menu>-->
</v-app-bar>
<v-main>

View File

@@ -53,7 +53,7 @@ export default defineNuxtConfig({
periodicSyncForUpdates: 20,
},
devOptions: {
enabled: true,
enabled: false,
type: 'module',
}
},
@@ -83,7 +83,7 @@ export default defineNuxtConfig({
nitro: {
devProxy: {
"/api": {
target: "http://localhost:8000/api",
target: process.env.NUXT_DEV_SERVER ?? 'http://localhost:8000/api',
prependPath: true,
changeOrigin: true,
}

View File

@@ -6,6 +6,7 @@ definePageMeta({
})
import {EventStreamContentType, fetchEventSource} from '@microsoft/fetch-event-source'
import { nextTick } from 'vue'
import MessageActions from "~/components/MessageActions.vue";
const { $i18n, $auth } = useNuxtApp()
const runtimeConfig = useRuntimeConfig()
@@ -155,6 +156,10 @@ const usePrompt = (prompt) => {
editor.value.usePrompt(prompt)
}
const deleteMessage = (index) => {
currentConversation.value.messages.splice(index, 1)
}
</script>
<template>
@@ -169,9 +174,16 @@ const usePrompt = (prompt) => {
cols="12"
>
<div
class="d-flex"
:class="message.is_bot ? 'justify-start mr-16' : 'justify-end ml-16'"
class="d-flex align-center"
:class="message.is_bot ? 'justify-start' : 'justify-end'"
>
<MessageActions
v-if="!message.is_bot"
:message="message"
:message-index="index"
:use-prompt="usePrompt"
:delete-message="deleteMessage"
/>
<v-card
:color="message.is_bot ? '' : 'primary'"
rounded="lg"
@@ -180,18 +192,14 @@ const usePrompt = (prompt) => {
<v-card-text>
<MsgContent :content="message.message" />
</v-card-text>
<!-- <v-card-actions-->
<!-- v-if="message.is_bot"-->
<!-- >-->
<!-- <v-spacer></v-spacer>-->
<!-- <v-tooltip text="Copy">-->
<!-- <template v-slot:activator="{ props }">-->
<!-- <v-btn v-bind="props" icon="content_copy"></v-btn>-->
<!-- </template>-->
<!-- </v-tooltip>-->
<!-- </v-card-actions>-->
</v-card>
<MessageActions
v-if="message.is_bot"
:message="message"
:message-index="index"
:use-prompt="usePrompt"
:delete-message="deleteMessage"
/>
</div>
</v-col>
</v-row>