Compare commits
6 Commits
v2.5.0
...
chatwithfi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01ea5f599f | ||
|
|
6c2faf1039 | ||
|
|
31dc740554 | ||
|
|
7353614472 | ||
|
|
973338c9fb | ||
|
|
191409209b |
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
53
components/editorTools/FrugalMode.vue
Normal file
53
components/editorTools/FrugalMode.vue
Normal 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>
|
||||
99
components/editorTools/UploadFile.vue
Normal file
99
components/editorTools/UploadFile.vue
Normal 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>
|
||||
53
components/editorTools/WebSearch.vue
Normal file
53
components/editorTools/WebSearch.vue
Normal 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>
|
||||
@@ -10,3 +10,7 @@ export const useConversations = () => useState('conversations', () => [])
|
||||
export const useUser = () => useState('user', () => null)
|
||||
|
||||
export const useDrawer = () => useState('drawer', () => false)
|
||||
|
||||
export const useEnableWebSearch = () => useState('enableWebSearch', () => false)
|
||||
|
||||
export const useFrugalMode = () => useState('frugalMode', () => true)
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user