Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d08806f0c9 | ||
|
|
85ac73efcc | ||
|
|
7cc5a6b347 | ||
|
|
983e4d436d | ||
|
|
727826f1b1 | ||
|
|
386659109c | ||
|
|
bd9e8bf45e | ||
|
|
4e40530a8c | ||
|
|
ea69a350f4 | ||
|
|
18a4251714 |
@@ -108,6 +108,7 @@ services:
|
|||||||
- DJANGO_SUPERUSER_USERNAME=admin # default superuser name
|
- DJANGO_SUPERUSER_USERNAME=admin # default superuser name
|
||||||
- DJANGO_SUPERUSER_PASSWORD=password # default superuser password
|
- DJANGO_SUPERUSER_PASSWORD=password # default superuser password
|
||||||
- DJANGO_SUPERUSER_EMAIL=admin@example.com # default superuser email
|
- DJANGO_SUPERUSER_EMAIL=admin@example.com # default superuser email
|
||||||
|
- ACCOUNT_EMAIL_VERIFICATION=none # Determines the e-mail verification method during signup – choose one of "none", "optional", or "mandatory". Default is "optional". If you don't need to verify the email, you can set it to "none".
|
||||||
# If you want to use the email verification function, you need to configure the following parameters
|
# If you want to use the email verification function, you need to configure the following parameters
|
||||||
# - EMAIL_HOST=SMTP server address
|
# - EMAIL_HOST=SMTP server address
|
||||||
# - EMAIL_PORT=SMTP server port
|
# - EMAIL_PORT=SMTP server port
|
||||||
|
|||||||
98
components/MessageActions.vue
Normal file
98
components/MessageActions.vue
Normal 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>
|
||||||
@@ -1,17 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div
|
||||||
|
class="flex-grow-1 d-flex align-center justify-space-between"
|
||||||
|
>
|
||||||
<v-textarea
|
<v-textarea
|
||||||
v-model="message"
|
v-model="message"
|
||||||
:label="$t('writeAMessage')"
|
:label="$t('writeAMessage')"
|
||||||
:placeholder="hint"
|
:placeholder="hint"
|
||||||
rows="1"
|
:rows="rows"
|
||||||
|
max-rows="8"
|
||||||
:auto-grow="autoGrow"
|
:auto-grow="autoGrow"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:hide-details="true"
|
:hide-details="true"
|
||||||
append-inner-icon="send"
|
clearable
|
||||||
@keyup.enter.exact="enterOnly"
|
variant="outlined"
|
||||||
@click:appendInner="clickSendBtn"
|
@keydown.enter.exact="enterOnly"
|
||||||
></v-textarea>
|
></v-textarea>
|
||||||
|
<v-btn
|
||||||
|
:disabled="loading"
|
||||||
|
icon="send"
|
||||||
|
title="Send"
|
||||||
|
class="ml-3"
|
||||||
|
@click="clickSendBtn"
|
||||||
|
></v-btn>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -39,7 +51,7 @@ export default {
|
|||||||
message(val) {
|
message(val) {
|
||||||
const lines = val.split(/\r\n|\r|\n/).length;
|
const lines = val.split(/\r\n|\r|\n/).length;
|
||||||
if (lines > 8) {
|
if (lines > 8) {
|
||||||
this.rows = lines;
|
this.rows = 8;
|
||||||
this.autoGrow = false;
|
this.autoGrow = false;
|
||||||
} else {
|
} else {
|
||||||
this.rows = 1;
|
this.rows = 1;
|
||||||
@@ -65,7 +77,8 @@ export default {
|
|||||||
clickSendBtn () {
|
clickSendBtn () {
|
||||||
this.send()
|
this.send()
|
||||||
},
|
},
|
||||||
enterOnly () {
|
enterOnly (event) {
|
||||||
|
event.preventDefault();
|
||||||
if (!isMobile()) {
|
if (!isMobile()) {
|
||||||
this.send()
|
this.send()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,26 +15,23 @@
|
|||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-toolbar
|
<v-toolbar
|
||||||
dark
|
|
||||||
color="primary"
|
|
||||||
>
|
>
|
||||||
<v-btn
|
<v-btn
|
||||||
icon
|
icon
|
||||||
dark
|
|
||||||
@click="dialog = false"
|
@click="dialog = false"
|
||||||
>
|
>
|
||||||
<v-icon>close</v-icon>
|
<v-icon icon="close"></v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-toolbar-title>{{ $t('language') }}</v-toolbar-title>
|
<v-toolbar-title>{{ $t('language') }}</v-toolbar-title>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<!-- <v-toolbar-items>-->
|
<v-toolbar-items>
|
||||||
<!-- <v-btn-->
|
<v-btn
|
||||||
<!-- variant="text"-->
|
variant="text"
|
||||||
<!-- @click="dialog = false"-->
|
@click="dialog = false"
|
||||||
<!-- >-->
|
>
|
||||||
<!-- Save-->
|
Save
|
||||||
<!-- </v-btn>-->
|
</v-btn>
|
||||||
<!-- </v-toolbar-items>-->
|
</v-toolbar-items>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
<v-list
|
<v-list
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -65,6 +65,6 @@ sudo curl -L "https://raw.githubusercontent.com/WongSaang/chatgpt-ui/main/docker
|
|||||||
|
|
||||||
echo "Starting services..."
|
echo "Starting services..."
|
||||||
|
|
||||||
sudo APP_DOMAIN="${APP_DOMAIN}:${SERVER_PORT}" CLIENT_PORT=${CLIENT_PORT} SERVER_PORT=${SERVER_PORT} WSGI_PORT=${WSGI_PORT} docker-compose up --pull -d
|
sudo APP_DOMAIN="${APP_DOMAIN}:${SERVER_PORT}" CLIENT_PORT=${CLIENT_PORT} SERVER_PORT=${SERVER_PORT} WSGI_PORT=${WSGI_PORT} docker-compose up --pull always -d
|
||||||
|
|
||||||
echo "Done"
|
echo "Done"
|
||||||
@@ -106,6 +106,7 @@ services:
|
|||||||
- DJANGO_SUPERUSER_USERNAME=admin # 默认超级用户
|
- DJANGO_SUPERUSER_USERNAME=admin # 默认超级用户
|
||||||
- DJANGO_SUPERUSER_PASSWORD=password # 默认超级用户的密码
|
- DJANGO_SUPERUSER_PASSWORD=password # 默认超级用户的密码
|
||||||
- DJANGO_SUPERUSER_EMAIL=admin@example.com # 默认超级用户邮箱
|
- DJANGO_SUPERUSER_EMAIL=admin@example.com # 默认超级用户邮箱
|
||||||
|
- ACCOUNT_EMAIL_VERIFICATION=none # 邮箱验证方式,可选值: none, optional, mandatory. 默认为 optional。如果你不需要验证用户的邮箱,可以设置为 none。
|
||||||
# 如果您想使用电子邮件验证功能,需要配置以下参数:
|
# 如果您想使用电子邮件验证功能,需要配置以下参数:
|
||||||
# - EMAIL_HOST=SMTP server address
|
# - EMAIL_HOST=SMTP server address
|
||||||
# - EMAIL_PORT=SMTP server port
|
# - EMAIL_PORT=SMTP server port
|
||||||
|
|||||||
@@ -29,6 +29,11 @@
|
|||||||
"me": "Me",
|
"me": "Me",
|
||||||
"ai": "AI"
|
"ai": "AI"
|
||||||
},
|
},
|
||||||
|
"edit": "Edit",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"delete": "Delete",
|
||||||
|
"signOut": "Sign out",
|
||||||
"welcomeScreen": {
|
"welcomeScreen": {
|
||||||
"introduction1": "is an unofficial client for ChatGPT, but uses the official OpenAI API.",
|
"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.",
|
"introduction2": "You will need an OpenAI API Key before you can use this client.",
|
||||||
|
|||||||
@@ -29,6 +29,11 @@
|
|||||||
"me": "我",
|
"me": "我",
|
||||||
"ai": "AI"
|
"ai": "AI"
|
||||||
},
|
},
|
||||||
|
"edit": "编辑",
|
||||||
|
"copy": "复制",
|
||||||
|
"copied": "已复制",
|
||||||
|
"delete": "删除",
|
||||||
|
"signOut": "退出登录",
|
||||||
"welcomeScreen": {
|
"welcomeScreen": {
|
||||||
"introduction1": "是一个非官方的ChatGPT客户端,但使用OpenAI的官方API",
|
"introduction1": "是一个非官方的ChatGPT客户端,但使用OpenAI的官方API",
|
||||||
"introduction2": "在使用本客户端之前,您需要一个OpenAI API密钥。",
|
"introduction2": "在使用本客户端之前,您需要一个OpenAI API密钥。",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {useDisplay} from "vuetify";
|
import {useDisplay} from "vuetify";
|
||||||
|
|
||||||
const { $i18n } = useNuxtApp()
|
const { $i18n, $auth } = useNuxtApp()
|
||||||
const runtimeConfig = useRuntimeConfig()
|
const runtimeConfig = useRuntimeConfig()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
const drawer = ref(null)
|
const drawer = ref(null)
|
||||||
@@ -88,6 +88,15 @@ const drawerPermanent = computed(() => {
|
|||||||
return mdAndUp.value
|
return mdAndUp.value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const signOut = async () => {
|
||||||
|
const { data, error } = await useFetch('/api/account/logout/', {
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
if (!error.value) {
|
||||||
|
await $auth.logout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onNuxtReady(async () => {
|
onNuxtReady(async () => {
|
||||||
loadConversations()
|
loadConversations()
|
||||||
})
|
})
|
||||||
@@ -261,6 +270,14 @@ onNuxtReady(async () => {
|
|||||||
:title="$t('feedback')"
|
:title="$t('feedback')"
|
||||||
@click="feedback"
|
@click="feedback"
|
||||||
></v-list-item>
|
></v-list-item>
|
||||||
|
|
||||||
|
<v-list-item
|
||||||
|
rounded="xl"
|
||||||
|
prepend-icon="logout"
|
||||||
|
:title="$t('signOut')"
|
||||||
|
@click="signOut"
|
||||||
|
></v-list-item>
|
||||||
|
|
||||||
</v-list>
|
</v-list>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -281,24 +298,6 @@ onNuxtReady(async () => {
|
|||||||
@click="createNewConversion()"
|
@click="createNewConversion()"
|
||||||
></v-btn>
|
></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-app-bar>
|
||||||
|
|
||||||
<v-main>
|
<v-main>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default defineNuxtConfig({
|
|||||||
periodicSyncForUpdates: 20,
|
periodicSyncForUpdates: 20,
|
||||||
},
|
},
|
||||||
devOptions: {
|
devOptions: {
|
||||||
enabled: true,
|
enabled: false,
|
||||||
type: 'module',
|
type: 'module',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -83,7 +83,7 @@ export default defineNuxtConfig({
|
|||||||
nitro: {
|
nitro: {
|
||||||
devProxy: {
|
devProxy: {
|
||||||
"/api": {
|
"/api": {
|
||||||
target: "http://localhost:8000/api",
|
target: process.env.NUXT_DEV_SERVER ?? 'http://localhost:8000/api',
|
||||||
prependPath: true,
|
prependPath: true,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,15 @@ onNuxtReady(() => {
|
|||||||
elevation="0"
|
elevation="0"
|
||||||
>
|
>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
|
<div v-if="route.query.email_verification_required && route.query.email_verification_required === 'none'">
|
||||||
|
<h2 class="text-h4">Your registration is successful</h2>
|
||||||
|
<p class="mt-5">
|
||||||
|
You can now <NuxtLink to="/account/signin">login</NuxtLink> to your account.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
<h2 class="text-h4">Verify your email</h2>
|
<h2 class="text-h4">Verify your email</h2>
|
||||||
<p class="text-body-2 mt-5">
|
<p class="mt-5">
|
||||||
We've sent a verification email to <strong>{{ $auth.user.email }}</strong>. <br>
|
We've sent a verification email to <strong>{{ $auth.user.email }}</strong>. <br>
|
||||||
Please check your inbox and click the link to verify your email address.
|
Please check your inbox and click the link to verify your email address.
|
||||||
</p>
|
</p>
|
||||||
@@ -64,6 +71,7 @@ onNuxtReady(() => {
|
|||||||
{{ resent ? 'Resent' : 'Resend email'}}
|
{{ resent ? 'Resent' : 'Resend email'}}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ const submit = async () => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$auth.setUser(data.value.user)
|
$auth.setUser(data.value.user)
|
||||||
navigateTo('/account/onboarding')
|
navigateTo('/account/onboarding?email_verification_required='+data.value.email_verification_required)
|
||||||
}
|
}
|
||||||
|
|
||||||
submitting.value = false
|
submitting.value = false
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ definePageMeta({
|
|||||||
})
|
})
|
||||||
import {EventStreamContentType, fetchEventSource} from '@microsoft/fetch-event-source'
|
import {EventStreamContentType, fetchEventSource} from '@microsoft/fetch-event-source'
|
||||||
import { nextTick } from 'vue'
|
import { nextTick } from 'vue'
|
||||||
|
import MessageActions from "~/components/MessageActions.vue";
|
||||||
|
|
||||||
const { $i18n, $auth } = useNuxtApp()
|
const { $i18n, $auth } = useNuxtApp()
|
||||||
const runtimeConfig = useRuntimeConfig()
|
const runtimeConfig = useRuntimeConfig()
|
||||||
@@ -155,6 +156,10 @@ const usePrompt = (prompt) => {
|
|||||||
editor.value.usePrompt(prompt)
|
editor.value.usePrompt(prompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteMessage = (index) => {
|
||||||
|
currentConversation.value.messages.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -169,9 +174,16 @@ const usePrompt = (prompt) => {
|
|||||||
cols="12"
|
cols="12"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="d-flex"
|
class="d-flex align-center"
|
||||||
:class="message.is_bot ? 'justify-start mr-16' : 'justify-end ml-16'"
|
: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
|
<v-card
|
||||||
:color="message.is_bot ? '' : 'primary'"
|
:color="message.is_bot ? '' : 'primary'"
|
||||||
rounded="lg"
|
rounded="lg"
|
||||||
@@ -180,18 +192,14 @@ const usePrompt = (prompt) => {
|
|||||||
<v-card-text>
|
<v-card-text>
|
||||||
<MsgContent :content="message.message" />
|
<MsgContent :content="message.message" />
|
||||||
</v-card-text>
|
</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>
|
</v-card>
|
||||||
|
<MessageActions
|
||||||
|
v-if="message.is_bot"
|
||||||
|
:message="message"
|
||||||
|
:message-index="index"
|
||||||
|
:use-prompt="usePrompt"
|
||||||
|
:delete-message="deleteMessage"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|||||||
Reference in New Issue
Block a user