Compare commits

...

6 Commits

8 changed files with 285 additions and 65 deletions

View File

@@ -1,5 +1,6 @@
<script setup>
import {EventStreamContentType, fetchEventSource} from '@microsoft/fetch-event-source'
import {useEnableWebSearch, useFrugalMode} from "~/composables/states";
const { $i18n, $settings } = useNuxtApp()
const runtimeConfig = useRuntimeConfig()
@@ -7,7 +8,7 @@ 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,
@@ -66,7 +73,7 @@ const fetchReply = async (message) => {
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,8 +186,9 @@ const deleteMessage = (index) => {
props.conversation.messages.splice(index, 1)
}
const enableWebSearch = ref(false)
const updateAttachment = (file) => {
attachment.value = file
}
onNuxtReady(() => {
currentModel.value = getCurrentModel()
@@ -235,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"
@@ -249,53 +281,10 @@ onNuxtReady(() => {
density="comfortable"
color="transparent"
>
<Prompt v-show="!fetchingResponse" :use-prompt="usePrompt" />
<v-switch
v-if="$settings.open_web_search === 'True'"
v-model="enableWebSearch"
inline
hide-details
color="primary"
:label="$t('webSearch')"
></v-switch>
<v-spacer></v-spacer>
<div
v-if="$settings.open_frugal_mode_control === 'True'"
class="d-flex align-center"
>
<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"
class="ml-3"
></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>
</div>
<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

@@ -13,7 +13,6 @@ const md = new MarkdownIt({
},
})
md.use(mathjax3)
// md.use(mk)
const props = defineProps({
message: {
@@ -70,6 +69,19 @@ 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>
@@ -91,6 +103,16 @@ onMounted(() => {
.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;
}

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

@@ -9,4 +9,8 @@ export const useConversations = () => useState('conversations', () => [])
export const useUser = () => useState('user', () => null)
export const useDrawer = () => useState('drawer', () => false)
export const useDrawer = () => useState('drawer', () => false)
export const useEnableWebSearch = () => useState('enableWebSearch', () => false)
export const useFrugalMode = () => useState('frugalMode', () => true)

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>