Compare commits

...

15 Commits

Author SHA1 Message Date
Rafi
01ea5f599f feat(attachment): Support message attachments 2023-04-19 23:28:48 +08:00
Rafi
6c2faf1039 feat(editor): Add a file upload component. 2023-04-19 18:11:07 +08:00
Rafi
31dc740554 feat(editor): Encapsulate the tools in the editor toolbar as independent components. 2023-04-19 16:52:41 +08:00
Rafi
7353614472 fix(signup): some translation quoting error 2023-04-19 08:05:04 +08:00
Rafi
973338c9fb Temporarily remove the invitation code input in the registration because the backend does not support it yet. 2023-04-19 07:50:36 +08:00
Rafi
191409209b feat(md) Display the borders of the table. 2023-04-18 23:10:09 +08:00
Rafi
04c52cba88 feat(msg card): Optimize message content layout and support rendering mathematical formulas. 2023-04-18 19:59:32 +08:00
Rafi
1d2ebb30bb feat(msg card): Optimize message content layout and support rendering mathematical formulas. 2023-04-18 19:55:23 +08:00
Rafi
53d639a9f6 feat(lang): Added French translation 2023-04-18 18:15:51 +08:00
Rafi
47951851c5 fix(gen_title) add openaiApiKey to request body 2023-04-18 14:25:21 +08:00
Rafi
f1b5f8cf3c Add env var DEBUG to docker-compose.yml 2023-04-18 11:08:29 +08:00
Rafi
e6a8868f6c docs: Frugal Mode Control 2023-04-18 10:44:35 +08:00
Rafi
e023a13bbc feat(editor): Make the editor automatically focus after selecting a prompt. 2023-04-18 10:37:16 +08:00
Rafi
69dacca6c5 feat(nuxt): Upgrade Nuxt to version 3.4. 2023-04-18 10:29:27 +08:00
Rafi
76b865646c feat(settings): Add independent plugin to put loading system configuration in the lifecycle hook "app:created". 2023-04-18 10:29:09 +08:00
22 changed files with 2360 additions and 228 deletions

View File

@@ -1,12 +1,3 @@
<script setup>
onNuxtReady(() => {
fetchSystemSettings()
// api key
const apiKey = useApiKey()
apiKey.value = getStoredApiKey()
})
</script>
<template>
<NuxtLayout>
<NuxtLoadingIndicator />

View File

@@ -1,13 +1,14 @@
<script setup>
import {EventStreamContentType, fetchEventSource} from '@microsoft/fetch-event-source'
import {useEnableWebSearch, useFrugalMode} from "~/composables/states";
const { $i18n } = useNuxtApp()
const { $i18n, $settings } = useNuxtApp()
const runtimeConfig = useRuntimeConfig()
const currentModel = useCurrentModel()
const openaiApiKey = useApiKey()
const fetchingResponse = ref(false)
const messageQueue = []
const frugalMode = ref(true)
const attachment = ref(null)
let isProcessingQueue = false
const props = defineProps({
@@ -51,10 +52,16 @@ const abortFetch = () => {
}
fetchingResponse.value = false
}
const enableWebSearch = useEnableWebSearch()
const frugalMode = useFrugalMode()
const fetchReply = async (message) => {
ctrl = new AbortController()
let webSearchParams = {}
if (enableWebSearch.value) {
webSearchParams['web_search'] = {
ua: navigator.userAgent,
@@ -63,10 +70,10 @@ const fetchReply = async (message) => {
}
const data = Object.assign({}, currentModel.value, {
openaiApiKey: enableCustomApiKey.value ? openaiApiKey.value : null,
openaiApiKey: $settings.open_api_key_setting === 'True' ? openaiApiKey.value : null,
message: message,
conversationId: props.conversation.id,
frugalMode: frugalMode.value
frugalMode: $settings.open_frugal_mode_control === 'True' && frugalMode.value
}, webSearchParams)
try {
@@ -145,10 +152,20 @@ const send = (message) => {
if (props.conversation.messages.length === 0) {
addConversation(props.conversation)
}
props.conversation.messages.push({message: message})
fetchReply(message)
let newMessage = {
id: null,
is_bot: false,
message: message
}
if (attachment.value) {
newMessage.attachments = [attachment.value]
attachment.value = null
}
props.conversation.messages.push(newMessage)
fetchReply(newMessage)
scrollChatWindow()
}
const stop = () => {
abortFetch()
}
@@ -169,17 +186,9 @@ const deleteMessage = (index) => {
props.conversation.messages.splice(index, 1)
}
const settings = useSettings()
const enableWebSearch = ref(false)
const showWebSearchToggle = computed(() => {
return settings.value && settings.value.open_web_search && settings.value.open_web_search === 'True'
})
const enableCustomApiKey = computed(() => {
return settings.value && settings.value.open_api_key_setting && settings.value.open_api_key_setting === 'True'
})
const updateAttachment = (file) => {
attachment.value = file
}
onNuxtReady(() => {
currentModel.value = getCurrentModel()
@@ -244,6 +253,20 @@ onNuxtReady(() => {
class="footer"
>
<div class="px-md-16 w-100 d-flex flex-column">
<div
v-if="attachment"
class="mb-2"
>
<v-chip
closable
color="teal"
label
@click:close="attachment = null"
>
<v-icon start icon="attach_file"></v-icon>
{{ attachment.original_name }}
</v-chip>
</div>
<div class="d-flex align-center">
<v-btn
v-show="fetchingResponse"
@@ -258,46 +281,10 @@ onNuxtReady(() => {
density="comfortable"
color="transparent"
>
<Prompt v-show="!fetchingResponse" :use-prompt="usePrompt" />
<v-switch
v-if="showWebSearchToggle"
v-model="enableWebSearch"
inline
hide-details
color="primary"
:label="$t('webSearch')"
></v-switch>
<v-spacer></v-spacer>
<v-switch
v-model="frugalMode"
inline
hide-details
color="primary"
:label="$t('frugalMode')"
></v-switch>
<v-dialog
transition="dialog-bottom-transition"
width="auto"
>
<template v-slot:activator="{ props }">
<v-icon
color="grey"
v-bind="props"
icon="help_outline"
></v-icon>
</template>
<template v-slot:default="{ isActive }">
<v-card>
<v-toolbar
color="primary"
:title="$t('frugalMode')"
></v-toolbar>
<v-card-text>
{{ $t('frugalModeTip') }}
</v-card-text>
</v-card>
</template>
</v-dialog>
<Prompt :use-prompt="usePrompt" />
<EditorToolsUploadFile :update-attachment="updateAttachment" />
<EditorToolsWebSearch v-if="$settings.open_web_search === 'True'" />
<EditorToolsFrugalMode v-if="$settings.open_frugal_mode_control === 'True'" />
</v-toolbar>
</div>
</v-footer>

View File

@@ -2,6 +2,7 @@
import hljs from "highlight.js"
import MarkdownIt from 'markdown-it'
import copy from 'copy-to-clipboard'
import mathjax3 from 'markdown-it-mathjax3'
const md = new MarkdownIt({
@@ -11,6 +12,7 @@ const md = new MarkdownIt({
return `<pre class="hljs-code-container my-3"><div class="hljs-code-header d-flex align-center justify-space-between bg-grey-darken-3 pa-1"><span class="pl-2 text-caption">${language}</span><button class="hljs-copy-button" data-copied="false">Copy</button></div><code class="hljs language-${language}">${hljs.highlight(code, { language: language, ignoreIllegals: true }).value}</code></pre>`
},
})
md.use(mathjax3)
const props = defineProps({
message: {
@@ -67,10 +69,50 @@ onMounted(() => {
v-html="contentHtml"
class="chat-msg-content pa-3"
></div>
<template
v-if="message.attachments && message.attachments.length > 0"
>
<v-divider class="mx-4"></v-divider>
<v-card-text class="d-flex justify-space-between">
<v-chip
label
>
<v-icon start icon="attach_file"></v-icon>
{{ message.attachments[0].original_name }}
</v-chip>
</v-card-text>
</template>
</v-card>
</template>
<style>
.chat-msg-content {
font-size: 0.875rem !important;
font-weight: 400;
line-height: 1.25rem;
}
.chat-msg-content p,
.chat-msg-content table,
.chat-msg-content ul,
.chat-msg-content ol,
.chat-msg-content h1,
.chat-msg-content h2,
.chat-msg-content h3,
.chat-msg-content h4,
.chat-msg-content h5,
.chat-msg-content h6 {
margin-bottom: 1rem;
}
.chat-msg-content table {
width: 100%;
border-collapse: collapse;
border-radius: .5rem;
}
.chat-msg-content table th,
.chat-msg-content table td {
padding: .5rem 1rem;
border: 1px solid gray;
}
.chat-msg-content ol, .chat-msg-content ul {
padding-left: 2em;
}
@@ -89,4 +131,10 @@ onMounted(() => {
.hljs-copy-button:active{border-color:#ffffff66}
.hljs-copy-button[data-copied="true"]{text-indent:0;width:auto;background-image:none}
@media(prefers-reduced-motion){.hljs-copy-button{transition:none}}
/*MathJax*/
.MathJax svg {
max-width: 100%;
overflow: auto;
}
</style>

View File

@@ -48,8 +48,11 @@ const send = () => {
message.value = ""
}
const textArea = ref()
const usePrompt = (prompt) => {
message.value = prompt
textArea.value.focus()
}
const clickSendBtn = () => {
@@ -73,6 +76,7 @@ defineExpose({
class="flex-grow-1 d-flex align-center justify-space-between"
>
<v-textarea
ref="textArea"
v-model="message"
:label="$t('writeAMessage')"
:placeholder="hint"

View File

@@ -3,7 +3,7 @@ import { useDisplay } from 'vuetify'
import {useDrawer} from "../composables/states";
const route = useRoute()
const { $i18n } = useNuxtApp()
const { $i18n, $settings } = useNuxtApp()
const colorMode = useColorMode()
const {mdAndUp} = useDisplay()
const drawerPermanent = computed(() => {
@@ -88,11 +88,6 @@ const loadConversations = async () => {
loadingConversations.value = false
}
const settings = useSettings()
const showApiKeySetting = computed(() => {
return settings.value && settings.value.open_api_key_setting && settings.value.open_api_key_setting === 'True'
})
const signOut = async () => {
const { data, error } = await useFetch('/api/account/logout/', {
method: 'POST'
@@ -277,7 +272,7 @@ const drawer = useDrawer()
</v-dialog>
<ApiKeyDialog
v-if="showApiKeySetting"
v-if="$settings.open_api_key_setting === 'True'"
/>
<ModelParameters/>

View File

@@ -0,0 +1,53 @@
<script setup>
import {useFrugalMode} from "~/composables/states";
const menu = ref(false)
const frugalMode = useFrugalMode()
</script>
<template>
<v-menu
v-model="menu"
:close-on-content-click="false"
>
<template v-slot:activator="{ props }">
<v-btn
v-bind="props"
icon
>
<v-icon
icon="network_wifi_2_bar"
:color="frugalMode ? '' : 'grey'"
></v-icon>
</v-btn>
</template>
<v-container>
<v-card
min-width="300"
max-width="500"
>
<v-card-title>
<span class="headline">{{ $t('frugalMode') }}</span>
</v-card-title>
<v-divider></v-divider>
<v-card-text>
<p>{{ $t('frugalModeTip') }}</p>
<v-switch
v-model="frugalMode"
inline
hide-details
color="primary"
:label="$t('frugalMode')"
></v-switch>
</v-card-text>
</v-card>
</v-container>
</v-menu>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,99 @@
<script setup>
const { $i18n } = useNuxtApp()
const dialog = ref(false)
const uploading = ref(false)
const file = ref(null)
const errorMsg = ref('')
const props = defineProps({
updateAttachment: {
type: Function,
required: true
}
})
const fileChanged = () => {
errorMsg.value = ''
}
const upload = async () => {
if (file.value.files.length < 1){
return;
}
const fileObj = file.value.files[0]
uploading.value = true
const formData = new FormData()
formData.append('file', fileObj)
const { data, error } = await useAuthFetch('/api/chat/upload/', {
method: 'POST',
body: formData,
})
if (error.value) {
errorMsg.value = $i18n.t('Failed to upload file')
} else {
dialog.value = false
props.updateAttachment(data.value.attachment)
}
uploading.value = false
}
</script>
<template>
<v-dialog
v-model="dialog"
width="auto"
>
<template v-slot:activator="{ props }">
<v-btn
v-bind="props"
icon
>
<v-icon
icon="description"
></v-icon>
</v-btn>
</template>
<v-card
>
<v-card-title>
<span class="headline">{{ $t('Insert file') }}</span>
</v-card-title>
<v-divider></v-divider>
<v-card-text>
{{ $t('Currently, only PDF files are supported.') }}
</v-card-text>
<v-card-text>
<v-file-input
ref="file"
show-size
accept=".pdf"
clearable
:loading="uploading"
:disabled="uploading"
:label="$t('Please select a PDF file')"
:error-messages="errorMsg"
@change="fileChanged"
></v-file-input>
<div
class="d-flex justify-center"
>
<v-btn
color="primary"
:loading="uploading"
@click="upload"
>{{ $t('Insert') }}</v-btn>
</div>
</v-card-text>
</v-card>
</v-dialog>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,53 @@
<script setup>
import {useEnableWebSearch} from "~/composables/states";
const menu = ref(false)
const enableWebSearch = useEnableWebSearch()
</script>
<template>
<v-menu
v-model="menu"
:close-on-content-click="false"
>
<template v-slot:activator="{ props }">
<v-btn
v-bind="props"
icon
>
<v-icon
icon="travel_explore"
:color="enableWebSearch ? '' : 'grey'"
></v-icon>
</v-btn>
</template>
<v-container>
<v-card
min-width="300"
max-width="500"
>
<v-card-title>
<span class="headline">{{ $t('webSearch') }}</span>
</v-card-title>
<v-divider></v-divider>
<v-card-text>
<v-switch
v-model="enableWebSearch"
inline
hide-details
color="primary"
:label="$t('webSearch')"
></v-switch>
</v-card-text>
</v-card>
</v-container>
</v-menu>
</template>
<style scoped>
</style>

View File

@@ -7,8 +7,10 @@ export const useApiKey = () => useState('apiKey', () => getStoredApiKey())
export const useConversations = () => useState('conversations', () => [])
export const useSettings = () => useState('settings', () => {})
export const useUser = () => useState('user', () => null)
export const useDrawer = () => useState('drawer', () => false)
export const useEnableWebSearch = () => useState('enableWebSearch', () => false)
export const useFrugalMode = () => useState('frugalMode', () => true)

View File

@@ -20,6 +20,7 @@ services:
platform: linux/x86_64
image: wongsaang/chatgpt-ui-wsgi-server:latest
environment:
- DEBUG=${DEBUG:-False} # Whether to enable debug mode, default False
- APP_DOMAIN=${APP_DOMAIN:-localhost:9000}
- SERVER_WORKERS=3 # The number of worker processes for handling requests.
- WORKER_TIMEOUT=180 # Workers silent for more than this many seconds are killed and restarted. default 180s

View File

@@ -78,3 +78,7 @@ After deployment, there is an `open_registration` setting under `Chat->Settings`
## Web Search Function Control
This feature is disabled by default. You can enable it in the admin panel under `Chat->Settings`. There is a setting called `open_web_search`, set its value to `True`.
## Frugal Mode Control
This feature is enabled by default. You can disable it in the `Chat->Settings` section of the management backend. There is a setting called `open_frugal_mode_control` in Settings. Set its value to `False`.

View File

@@ -79,3 +79,7 @@ backend-wsgi-server:
## 网页搜索功能控制
该功能默认处于关闭状态,你可以在管理后台的 `Chat->Settings` 中开启它,在 Settings 中有一个 `open_web_search` 的设置项,把它的值设置为 `True`
## 节俭模式控制
该功能默认处于开启状态,你可以在管理后台的 `Chat->Settings` 中关闭它,在 Settings 中有一个 `open_frugal_mode_control` 的设置项,把它的值设置为 `False`

96
lang/fr-FR.json Normal file
View File

@@ -0,0 +1,96 @@
{
"signIn":"Se connecter",
"signUp":"S'inscrire",
"username":"Nom d'utilisateur",
"password":"Mot de passe",
"Username is required":"Nom d'utilisateur requis",
"Password is required":"Mot de passe requis",
"Create your account":"Créer votre compte",
"createAccount":"Créer un compte",
"email":"E-mail",
"Sign in instead":"S'identifier à la place",
"Please enter your username":"Veuillez saisir votre nom d'utilisateur",
"Username must be at least 4 characters":"Le nom d'utilisateur doit comporter au moins 4 caractères",
"Please enter your e-mail address":"Veuillez saisir votre adresse e-mail",
"E-mail address must be valid":"L'adresse e-mail doit être valide",
"Please enter your password":"Veuillez saisir votre mot de passe",
"Password must be at least 8 characters":"Le mot de passe doit comporter au moins 8 caractères",
"Please confirm your password":"Veuillez confirmer votre mot de passe",
"welcomeTo": "Bienvenue à",
"language": "Langue",
"setApiKey": "Définir la clé API",
"setOpenAIApiKey": "Définir la clé API OpenAI",
"openAIApiKey": "Clé API OpenAI",
"getAKey": "Obtenir une clé",
"openAIModels": "Modèles OpenAI",
"aboutTheModels": "À propos des modèles",
"saveAndClose": "Enregistrer et fermer",
"pleaseSelectAtLeastOneModelDot": "Veuillez sélectionner au moins un modèle.",
"writeAMessage": "Écrire un message",
"frequentlyPrompts": "Prompts fréquents",
"addPrompt": "Ajouter un prompt",
"titlePrompt": "Titre",
"addNewPrompt": "Ajouter un nouveau prompt",
"pressEnterToSendYourMessageOrShiftEnterToAddANewLine": "Appuyez sur Entrée pour envoyer votre message ou sur Maj+Entrée pour ajouter une nouvelle ligne",
"lightMode": "Mode clair",
"darkMode": "Mode sombre",
"followSystem": "Suivre le système",
"themeMode": "Mode thème",
"feedback": "Commentaires",
"newConversation": "Nouvelle conversation",
"defaultConversationTitle": "Sans titre",
"clearConversations": "Effacer les conversations",
"modelParameters": "Paramètres du modèle",
"model": "Modèle",
"temperature": "Température",
"topP": "Top P",
"frequencyPenalty": "Pénalité de fréquence",
"presencePenalty": "Pénalité de présence",
"maxTokens": "Nombre maximal de jetons",
"roles": {
"me": "Moi",
"ai": "IA"
},
"edit": "Modifier",
"copy": "Copier",
"copied": "Copié",
"delete": "Supprimer",
"signOut": "Déconnexion",
"resetPassword": "Réinitialiser le mot de passe",
"submit": "Soumettre",
"agree": "Accepter",
"newPassword": "Nouveau mot de passe",
"currentPassword": "Mot de passe actuel",
"confirmPassword": "Confirmer le mot de passe",
"yourPasswordHasBeenReset": "Votre mot de passe a été réinitialisé",
"nowYouNeedToSignInAgain": "Vous devez maintenant vous reconnecter",
"webSearch": "Recherche Web",
"webSearchDefaultPrompt": "Résultats de la recherche Web : \n\n[résultats_web]\nDate actuelle : [date_actuelle]\n\nInstructions : Utilisez les résultats de la recherche Web fournis pour rédiger une réponse complète à la question donnée. Assurez-vous de citer les résultats en utilisant la notation [nombre] après la référence. Si les résultats de recherche fournis font référence à plusieurs sujets avec le même nom, rédigez des réponses distinctes pour chaque sujet. \nQuestion : [question]",
"genTitlePrompt": "Générer un titre court pour le contenu suivant, pas plus de 10 mots. \n\nContenu : ",
"maxTokenTips1": "La longueur maximale du contexte pour le modèle actuel est de",
"maxTokenTips2": "jeton, ce qui inclut la longueur du prompt et la longueur du texte généré. Le paramètre Max Tokens ici fait référence à la longueur du texte généré. Vous devriez donc laisser de l'espace pour votre prompt et ne pas le régler trop grand ou à la limite maximale.",
"frugalMode": "Mode éco",
"frugalModeTip": "Activez le mode frugal, le client n'enverra pas les messages historiques à ChatGPT, ce qui peut économiser la consommation de jetons. Si vous souhaitez que ChatGPT comprenne le contexte de la conversation, veuillez désactiver le mode frugal.",
"welcomeScreen": {
"introduction1": "est un client non officiel pour ChatGPT, mais utilise l'API officielle d'OpenAI.",
"introduction2": "Vous aurez besoin d'une clé API OpenAI avant de pouvoir utiliser ce client.",
"examples": {
"title": "Exemples",
"item1": "\"Expliquez l'informatique quantique en termes simples\"",
"item2": "\"Avez-vous des idées créatives pour l'anniversaire d'un enfant de 10 ans?\"",
"item3": "\"Comment faire une requête HTTP en JavaScript?\""
},
"capabilities": {
"title": "Fonctionnalités",
"item1": "Se souvient de ce que l'utilisateur a dit précédemment dans la conversation",
"item2": "Permet à l'utilisateur de fournir des corrections de suivi",
"item3": "Entraîné à refuser les demandes inappropriées"
},
"limitations": {
"title": "Limitations",
"item1": "Peut occasionnellement générer des informations incorrectes",
"item2": "Peut occasionnellement produire des instructions dangereuses ou du contenu biaisé",
"item3": "Connaissance limitée du monde et des événements après 2021"
}
}
}

View File

@@ -26,7 +26,7 @@
"to your account.":"你的账号了。",
"welcomeTo": "欢迎来到",
"language": "语言",
"setApiKey": "设置API密钥",
"setApiKey": "API 密钥",
"setOpenAIApiKey": "设置OpenAI的API密钥",
"openAIApiKey": "OpenAI的API密钥",
"getAKey": "获取钥匙",

View File

@@ -59,6 +59,12 @@ export default defineNuxtConfig({
iso: 'ru-RU',
name: 'Русский',
file: 'ru-RU.json',
},
{
code: 'fr',
iso: 'fr-FR',
name: 'Français',
file: 'fr-FR.json',
}
],
lazy: true,

View File

@@ -11,10 +11,11 @@
},
"devDependencies": {
"@kevinmarrec/nuxt-pwa": "^0.17.0",
"@nuxt/devtools": "^0.4.0",
"@nuxtjs/color-mode": "^3.2.0",
"@nuxtjs/i18n": "^8.0.0-beta.9",
"material-design-icons-iconfont": "^6.7.0",
"nuxt": "^3.3.3",
"nuxt": "^3.4.0",
"vuepress": "^2.0.0-beta.61"
},
"dependencies": {
@@ -24,6 +25,7 @@
"http-proxy-middleware": "3.0.0-beta.1",
"is-mobile": "^3.1.1",
"markdown-it": "^13.0.1",
"markdown-it-mathjax3": "^4.3.2",
"nanoid": "^4.0.1",
"vuetify": "^3.0.6"
},

View File

@@ -47,9 +47,9 @@ onNuxtReady(() => {
>
<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>
<h2 class="text-h4">{{$t('Your registration is successful')}}</h2>
<p class="mt-5">
{{$('You can now')}} <NuxtLink to="/account/signin">{{$('signIn')}}</NuxtLink> {{$t('to your account.')}}
{{$t('You can now')}} <NuxtLink to="/account/signin">{{$t('signIn')}}</NuxtLink> {{$t('to your account.')}}
</p>
</div>
<div v-else>

View File

@@ -150,14 +150,14 @@ const handleFieldUpdate = (field) => {
clearable
></v-text-field>
<v-text-field
v-model="formData.code"
:rules="formRules.code"
:label="$t('invitation code')"
variant="underlined"
@keyup.enter="submit"
clearable
></v-text-field>
<!-- <v-text-field-->
<!-- v-model="formData.code"-->
<!-- :rules="formRules.code"-->
<!-- :label="$t('invitation code')"-->
<!-- variant="underlined"-->
<!-- @keyup.enter="submit"-->
<!-- clearable-->
<!-- ></v-text-field>-->
</v-form>

6
plugins/initApiKey.js Normal file
View File

@@ -0,0 +1,6 @@
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('app:created', async () => {
const apiKey = useApiKey()
apiKey.value = getStoredApiKey()
})
})

24
plugins/settings.js Normal file
View File

@@ -0,0 +1,24 @@
const transformData = (list) => {
const result = {};
for (let i = 0; i < list.length; i++) {
const item = list[i];
result[item.name] = item.value;
}
return result;
}
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('app:created', async () => {
let settings = {}
const { data, error } = await useAuthFetch('/api/chat/settings/', {
method: 'GET',
})
if (!error.value) {
settings = transformData(data.value)
}
nuxtApp.provide('settings', settings)
})
})

View File

@@ -24,12 +24,14 @@ export const addConversation = (conversation) => {
export const genTitle = async (conversationId) => {
const { $i18n } = useNuxtApp()
const { $i18n, $settings } = useNuxtApp()
const openaiApiKey = useApiKey()
const { data, error } = await useAuthFetch('/api/gen_title/', {
method: 'POST',
body: {
conversationId: conversationId,
prompt: $i18n.t('genTitlePrompt')
prompt: $i18n.t('genTitlePrompt'),
openaiApiKey: $settings.open_api_key_setting === 'True' ? openaiApiKey.value : null,
}
})
if (!error.value) {
@@ -44,25 +46,6 @@ export const genTitle = async (conversationId) => {
return null
}
const transformData = (list) => {
const result = {};
for (let i = 0; i < list.length; i++) {
const item = list[i];
result[item.name] = item.value;
}
return result;
}
export const fetchSystemSettings = async () => {
const { data, error } = await useAuthFetch('/api/chat/settings/', {
method: 'GET',
})
if (!error.value) {
const settings = useSettings()
settings.value = transformData(data.value)
}
}
export const fetchUser = async () => {
return useMyFetch('/api/account/user/')
}

2008
yarn.lock

File diff suppressed because it is too large Load Diff