Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35d4292d29 | ||
|
|
3992121b71 | ||
|
|
d08806f0c9 | ||
|
|
85ac73efcc | ||
|
|
7cc5a6b347 | ||
|
|
983e4d436d | ||
|
|
727826f1b1 | ||
|
|
386659109c | ||
|
|
bd9e8bf45e | ||
|
|
4e40530a8c | ||
|
|
ea69a350f4 | ||
|
|
18a4251714 | ||
|
|
878fda0054 |
@@ -108,6 +108,7 @@ services:
|
||||
- DJANGO_SUPERUSER_USERNAME=admin # default superuser name
|
||||
- DJANGO_SUPERUSER_PASSWORD=password # default superuser password
|
||||
- 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
|
||||
# - EMAIL_HOST=SMTP server address
|
||||
# - EMAIL_PORT=SMTP server port
|
||||
|
||||
1
app.vue
1
app.vue
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<VitePwaManifest />
|
||||
<NuxtLoadingIndicator />
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
|
||||
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>
|
||||
191
components/ModelParameters.vue
Normal file
191
components/ModelParameters.vue
Normal file
@@ -0,0 +1,191 @@
|
||||
<script setup>
|
||||
const dialog = ref(false)
|
||||
const currentModel = useCurrentModel()
|
||||
const availableModels = [
|
||||
DEFAULT_MODEL.name
|
||||
]
|
||||
|
||||
watch(currentModel, (newVal, oldVal) => {
|
||||
saveCurrentModel(newVal)
|
||||
}, { deep: true })
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-dialog
|
||||
v-model="dialog"
|
||||
persistent
|
||||
>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
rounded="xl"
|
||||
prepend-icon="tune"
|
||||
:title="$t('modelParameters')"
|
||||
></v-list-item>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-toolbar
|
||||
density="compact"
|
||||
>
|
||||
<v-toolbar-title>{{ $t('modelParameters') }}</v-toolbar-title>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<v-btn icon="close" @click="dialog = false"></v-btn>
|
||||
</v-toolbar>
|
||||
<v-card-text>
|
||||
<v-select
|
||||
v-model="currentModel.name"
|
||||
:label="$t('model')"
|
||||
:items="availableModels"
|
||||
variant="underlined"
|
||||
></v-select>
|
||||
|
||||
<v-row
|
||||
no-gutters
|
||||
>
|
||||
<v-col cols="12">
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<v-list-subheader>{{ $t('temperature') }}</v-list-subheader>
|
||||
<v-text-field
|
||||
v-model="currentModel.temperature"
|
||||
hide-details
|
||||
single-line
|
||||
density="compact"
|
||||
type="number"
|
||||
max="1"
|
||||
step="0.01"
|
||||
style="width: 100px"
|
||||
class="flex-grow-0"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-slider
|
||||
v-model="currentModel.temperature"
|
||||
:max="1"
|
||||
:step="0.01"
|
||||
hide-details
|
||||
>
|
||||
</v-slider>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row
|
||||
no-gutters
|
||||
>
|
||||
<v-col cols="12">
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<v-list-subheader>{{ $t('maxTokens') }}</v-list-subheader>
|
||||
<v-text-field
|
||||
v-model="currentModel.max_tokens"
|
||||
hide-details
|
||||
single-line
|
||||
density="compact"
|
||||
type="number"
|
||||
max="2048"
|
||||
step="1"
|
||||
style="width: 100px"
|
||||
class="flex-grow-0"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-slider
|
||||
v-model="currentModel.max_tokens"
|
||||
:max="2048"
|
||||
:step="1"
|
||||
hide-details
|
||||
>
|
||||
</v-slider>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row
|
||||
no-gutters
|
||||
>
|
||||
<v-col cols="12">
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<v-list-subheader>{{ $t('topP') }}</v-list-subheader>
|
||||
<v-text-field
|
||||
v-model="currentModel.top_p"
|
||||
hide-details
|
||||
single-line
|
||||
density="compact"
|
||||
type="number"
|
||||
max="1"
|
||||
step="0.01"
|
||||
style="width: 100px"
|
||||
class="flex-grow-0"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-slider
|
||||
v-model="currentModel.top_p"
|
||||
:max="1"
|
||||
:step="0.01"
|
||||
hide-details
|
||||
>
|
||||
</v-slider>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row no-gutters>
|
||||
<v-col cols="12">
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<v-list-subheader>{{ $t('frequencyPenalty') }}</v-list-subheader>
|
||||
<v-text-field
|
||||
v-model="currentModel.frequency_penalty"
|
||||
hide-details
|
||||
single-line
|
||||
density="compact"
|
||||
type="number"
|
||||
max="2"
|
||||
step="0.01"
|
||||
style="width: 100px"
|
||||
class="flex-grow-0"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-slider
|
||||
v-model="currentModel.frequency_penalty"
|
||||
:max="2"
|
||||
:step="0.01"
|
||||
hide-details
|
||||
></v-slider>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row no-gutters>
|
||||
<v-col cols="12">
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<v-list-subheader>{{ $t('presencePenalty') }}</v-list-subheader>
|
||||
<v-text-field
|
||||
v-model="currentModel.presence_penalty"
|
||||
hide-details
|
||||
single-line
|
||||
density="compact"
|
||||
type="number"
|
||||
max="2"
|
||||
step="0.01"
|
||||
style="width: 100px"
|
||||
class="flex-grow-0"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-slider
|
||||
v-model="currentModel.presence_penalty"
|
||||
:max="2"
|
||||
:step="0.01"
|
||||
hide-details
|
||||
></v-slider>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,17 +1,29 @@
|
||||
<template>
|
||||
<v-textarea
|
||||
v-model="message"
|
||||
:label="$t('writeAMessage')"
|
||||
:placeholder="hint"
|
||||
rows="1"
|
||||
:auto-grow="autoGrow"
|
||||
:disabled="disabled"
|
||||
:loading="loading"
|
||||
:hide-details="true"
|
||||
append-inner-icon="send"
|
||||
@keyup.enter.exact="enterOnly"
|
||||
@click:appendInner="clickSendBtn"
|
||||
></v-textarea>
|
||||
<div
|
||||
class="flex-grow-1 d-flex align-center justify-space-between"
|
||||
>
|
||||
<v-textarea
|
||||
v-model="message"
|
||||
:label="$t('writeAMessage')"
|
||||
:placeholder="hint"
|
||||
:rows="rows"
|
||||
max-rows="8"
|
||||
:auto-grow="autoGrow"
|
||||
:disabled="disabled"
|
||||
:loading="loading"
|
||||
:hide-details="true"
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -15,26 +15,23 @@
|
||||
</template>
|
||||
<v-card>
|
||||
<v-toolbar
|
||||
dark
|
||||
color="primary"
|
||||
>
|
||||
<v-btn
|
||||
icon
|
||||
dark
|
||||
@click="dialog = false"
|
||||
>
|
||||
<v-icon>close</v-icon>
|
||||
<v-icon icon="close"></v-icon>
|
||||
</v-btn>
|
||||
<v-toolbar-title>{{ $t('language') }}</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<!-- <v-toolbar-items>-->
|
||||
<!-- <v-btn-->
|
||||
<!-- variant="text"-->
|
||||
<!-- @click="dialog = false"-->
|
||||
<!-- >-->
|
||||
<!-- Save-->
|
||||
<!-- </v-btn>-->
|
||||
<!-- </v-toolbar-items>-->
|
||||
<v-toolbar-items>
|
||||
<v-btn
|
||||
variant="text"
|
||||
@click="dialog = false"
|
||||
>
|
||||
Save
|
||||
</v-btn>
|
||||
</v-toolbar-items>
|
||||
</v-toolbar>
|
||||
<v-list
|
||||
>
|
||||
|
||||
@@ -65,6 +65,6 @@ sudo curl -L "https://raw.githubusercontent.com/WongSaang/chatgpt-ui/main/docker
|
||||
|
||||
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"
|
||||
@@ -22,6 +22,7 @@ services:
|
||||
- DJANGO_SUPERUSER_USERNAME=admin # default superuser name
|
||||
- DJANGO_SUPERUSER_PASSWORD=password # default superuser password
|
||||
- DJANGO_SUPERUSER_EMAIL=admin@example.com # default superuser email
|
||||
- ACCOUNT_EMAIL_VERIFICATION=${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
|
||||
# - EMAIL_HOST=SMTP server address
|
||||
# - EMAIL_PORT=SMTP server port
|
||||
|
||||
@@ -106,6 +106,7 @@ services:
|
||||
- DJANGO_SUPERUSER_USERNAME=admin # 默认超级用户
|
||||
- DJANGO_SUPERUSER_PASSWORD=password # 默认超级用户的密码
|
||||
- DJANGO_SUPERUSER_EMAIL=admin@example.com # 默认超级用户邮箱
|
||||
- ACCOUNT_EMAIL_VERIFICATION=none # 邮箱验证方式,可选值: none, optional, mandatory. 默认为 optional。如果你不需要验证用户的邮箱,可以设置为 none。
|
||||
# 如果您想使用电子邮件验证功能,需要配置以下参数:
|
||||
# - EMAIL_HOST=SMTP server address
|
||||
# - EMAIL_PORT=SMTP server port
|
||||
|
||||
@@ -18,10 +18,22 @@
|
||||
"feedback": "Feedback",
|
||||
"newConversation": "New conversation",
|
||||
"clearConversations": "Clear conversations",
|
||||
"modelParameters": "Model Parameters",
|
||||
"model": "Model",
|
||||
"temperature": "Temperature",
|
||||
"topP": "Top P",
|
||||
"frequencyPenalty": "Frequency Penalty",
|
||||
"presencePenalty": "Presence Penalty",
|
||||
"maxTokens": "Max Tokens",
|
||||
"roles": {
|
||||
"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.",
|
||||
|
||||
@@ -18,10 +18,22 @@
|
||||
"feedback": "反馈",
|
||||
"newConversation": "新的对话",
|
||||
"clearConversations": "清除对话",
|
||||
"modelParameters": "模型参数",
|
||||
"model": "模型",
|
||||
"temperature": "Temperature",
|
||||
"topP": "Top P",
|
||||
"frequencyPenalty": "Frequency Penalty",
|
||||
"presencePenalty": "Presence Penalty",
|
||||
"maxTokens": "Max Tokens",
|
||||
"roles": {
|
||||
"me": "我",
|
||||
"ai": "AI"
|
||||
},
|
||||
"edit": "编辑",
|
||||
"copy": "复制",
|
||||
"copied": "已复制",
|
||||
"delete": "删除",
|
||||
"signOut": "退出登录",
|
||||
"welcomeScreen": {
|
||||
"introduction1": "是一个非官方的ChatGPT客户端,但使用OpenAI的官方API",
|
||||
"introduction2": "在使用本客户端之前,您需要一个OpenAI API密钥。",
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
@@ -228,6 +237,8 @@ onNuxtReady(async () => {
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<ModelParameters/>
|
||||
|
||||
<v-menu
|
||||
>
|
||||
<template v-slot:activator="{ props }">
|
||||
@@ -259,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>
|
||||
@@ -279,72 +298,12 @@ 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>
|
||||
<NuxtPage/>
|
||||
</v-main>
|
||||
|
||||
<div>
|
||||
<div
|
||||
v-if="$pwa?.offlineReady || $pwa?.needRefresh"
|
||||
class="pwa-toast"
|
||||
role="alert"
|
||||
>
|
||||
<div class="message">
|
||||
<span v-if="$pwa.offlineReady">
|
||||
App ready to work offline
|
||||
</span>
|
||||
<span v-else>
|
||||
New content available, click on reload button to update.
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
v-if="$pwa.needRefresh"
|
||||
@click="$pwa.updateServiceWorker()"
|
||||
>
|
||||
Reload
|
||||
</button>
|
||||
<button @click="$pwa.cancelPrompt()">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="$pwa?.showInstallPrompt && !$pwa?.offlineReady && !$pwa?.needRefresh"
|
||||
class="pwa-toast"
|
||||
role="alert"
|
||||
>
|
||||
<div class="message">
|
||||
<span>
|
||||
Install PWA
|
||||
</span>
|
||||
</div>
|
||||
<button @click="$pwa.install()">
|
||||
Install
|
||||
</button>
|
||||
<button @click="$pwa.cancelInstall()">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
@@ -360,26 +319,4 @@ onNuxtReady(async () => {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.pwa-toast {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: 16px;
|
||||
padding: 12px;
|
||||
border: 1px solid #8885;
|
||||
border-radius: 4px;
|
||||
z-index: 1;
|
||||
text-align: left;
|
||||
box-shadow: 3px 4px 5px 0 #8885;
|
||||
}
|
||||
.pwa-toast .message {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.pwa-toast button {
|
||||
border: 1px solid #8885;
|
||||
outline: none;
|
||||
margin-right: 5px;
|
||||
border-radius: 2px;
|
||||
padding: 3px 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -25,36 +25,18 @@ export default defineNuxtConfig({
|
||||
'highlight.js/styles/panda-syntax-dark.css',
|
||||
],
|
||||
modules: [
|
||||
'@vite-pwa/nuxt',
|
||||
'@kevinmarrec/nuxt-pwa',
|
||||
'@nuxtjs/color-mode',
|
||||
'@nuxtjs/i18n',
|
||||
],
|
||||
pwa: {
|
||||
registerType: 'autoUpdate',
|
||||
manifest: {
|
||||
name: appName,
|
||||
short_name: appName,
|
||||
icons: [
|
||||
{
|
||||
src: 'icon-black.svg',
|
||||
sizes: '900x900',
|
||||
purpose: 'any maskable',
|
||||
}
|
||||
],
|
||||
description: 'A ChatGPT web Client'
|
||||
},
|
||||
workbox: {
|
||||
navigateFallback: '/',
|
||||
globPatterns: ['**/*.{js,css,html,png,svg,ico}'],
|
||||
},
|
||||
client: {
|
||||
installPrompt: true,
|
||||
// you don't need to include this: only for testing purposes
|
||||
// if enabling periodic sync for update use 1 hour or so (periodicSyncForUpdates: 3600)
|
||||
periodicSyncForUpdates: 20,
|
||||
},
|
||||
devOptions: {
|
||||
enabled: true,
|
||||
type: 'module',
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
i18n: {
|
||||
@@ -83,7 +65,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,
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"postinstall": "nuxt prepare"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kevinmarrec/nuxt-pwa": "^0.17.0",
|
||||
"@nuxtjs/color-mode": "^3.2.0",
|
||||
"@nuxtjs/i18n": "^8.0.0-beta.9",
|
||||
"material-design-icons-iconfont": "^6.7.0",
|
||||
@@ -15,7 +16,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"@vite-pwa/nuxt": "^0.0.7",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"highlight.js": "^11.7.0",
|
||||
"is-mobile": "^3.1.1",
|
||||
|
||||
@@ -45,24 +45,32 @@ onNuxtReady(() => {
|
||||
elevation="0"
|
||||
>
|
||||
<div class="text-center">
|
||||
<h2 class="text-h4">Verify your email</h2>
|
||||
<p class="text-body-2 mt-5">
|
||||
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.
|
||||
</p>
|
||||
<p v-if="errorMsg"
|
||||
class="text-red"
|
||||
>{{ errorMsg }}</p>
|
||||
<v-btn
|
||||
variant="text"
|
||||
class="mt-5"
|
||||
color="primary"
|
||||
:loading="sending"
|
||||
@click="resendEmail"
|
||||
:disabled="resent"
|
||||
>
|
||||
{{ resent ? 'Resent' : 'Resend email'}}
|
||||
</v-btn>
|
||||
<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>
|
||||
<p class="mt-5">
|
||||
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.
|
||||
</p>
|
||||
<p v-if="errorMsg"
|
||||
class="text-red"
|
||||
>{{ errorMsg }}</p>
|
||||
<v-btn
|
||||
variant="text"
|
||||
class="mt-5"
|
||||
color="primary"
|
||||
:loading="sending"
|
||||
@click="resendEmail"
|
||||
:disabled="resent"
|
||||
>
|
||||
{{ resent ? 'Resent' : 'Resend email'}}
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
@@ -75,7 +75,7 @@ const submit = async () => {
|
||||
}
|
||||
} else {
|
||||
$auth.setUser(data.value.user)
|
||||
navigateTo('/account/onboarding')
|
||||
navigateTo('/account/onboarding?email_verification_required='+data.value.email_verification_required)
|
||||
}
|
||||
|
||||
submitting.value = false
|
||||
|
||||
@@ -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()
|
||||
@@ -51,6 +52,14 @@ const abortFetch = () => {
|
||||
}
|
||||
const fetchReply = async (message, parentMessageId) => {
|
||||
ctrl = new AbortController()
|
||||
|
||||
const data = Object.assign({}, currentModel.value, {
|
||||
openaiApiKey: openaiApiKey.value,
|
||||
message: message,
|
||||
parentMessageId: parentMessageId,
|
||||
conversationId: currentConversation.value.id
|
||||
})
|
||||
|
||||
try {
|
||||
await fetchEventSource('/api/conversation/', {
|
||||
signal: ctrl.signal,
|
||||
@@ -59,13 +68,7 @@ const fetchReply = async (message, parentMessageId) => {
|
||||
'accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: currentModel.value,
|
||||
openaiApiKey: openaiApiKey.value,
|
||||
message: message,
|
||||
parentMessageId: parentMessageId,
|
||||
conversationId: currentConversation.value.id
|
||||
}),
|
||||
body: JSON.stringify(data),
|
||||
onopen(response) {
|
||||
if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
|
||||
return;
|
||||
@@ -153,6 +156,10 @@ const usePrompt = (prompt) => {
|
||||
editor.value.usePrompt(prompt)
|
||||
}
|
||||
|
||||
const deleteMessage = (index) => {
|
||||
currentConversation.value.messages.splice(index, 1)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -167,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"
|
||||
@@ -178,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>
|
||||
|
||||
BIN
public/icon.png
Normal file
BIN
public/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -1,6 +1,15 @@
|
||||
|
||||
export const STORAGE_KEY = {
|
||||
OPENAI_MODELS: 'openai_models',
|
||||
CURRENT_OPENAI_MODEL: 'current_openai_model',
|
||||
MODELS: 'models',
|
||||
CURRENT_MODEL: 'current_model',
|
||||
OPENAI_API_KEY: 'openai_api_key',
|
||||
}
|
||||
|
||||
export const DEFAULT_MODEL = {
|
||||
name: 'gpt-3.5-turbo',
|
||||
frequency_penalty: 0.0,
|
||||
presence_penalty: 0.0,
|
||||
max_tokens: 1000,
|
||||
temperature: 0.7,
|
||||
top_p: 1.0
|
||||
}
|
||||
@@ -11,32 +11,28 @@ const set = (key, val) => {
|
||||
localStorage.setItem(key, JSON.stringify(val))
|
||||
}
|
||||
|
||||
const DEFAULT_OPENAI_MODEL = 'text-davinci-003'
|
||||
|
||||
export const setModels = (val) => {
|
||||
const models = useModels()
|
||||
set(STORAGE_KEY.OPENAI_MODELS, val)
|
||||
set(STORAGE_KEY.MODELS, val)
|
||||
models.value = val
|
||||
}
|
||||
|
||||
export const getStoredModels = () => {
|
||||
let models = get(STORAGE_KEY.OPENAI_MODELS)
|
||||
let models = get(STORAGE_KEY.MODELS)
|
||||
if (!models) {
|
||||
models = [DEFAULT_OPENAI_MODEL]
|
||||
models = [DEFAULT_MODEL]
|
||||
}
|
||||
return models
|
||||
}
|
||||
|
||||
export const setCurrentModel = (val) => {
|
||||
const model = useCurrentModel()
|
||||
set(STORAGE_KEY.CURRENT_OPENAI_MODEL, val)
|
||||
model.value = val
|
||||
export const saveCurrentModel = (val) => {
|
||||
set(STORAGE_KEY.CURRENT_MODEL, val)
|
||||
}
|
||||
|
||||
export const getCurrentModel = () => {
|
||||
let model = get(STORAGE_KEY.CURRENT_OPENAI_MODEL)
|
||||
let model = get(STORAGE_KEY.CURRENT_MODEL)
|
||||
if (!model) {
|
||||
model = DEFAULT_OPENAI_MODEL
|
||||
model = DEFAULT_MODEL
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user