This commit is contained in:
liqiang-fit2cloud 2025-02-27 10:41:04 +08:00
commit 01729157b7
16 changed files with 199 additions and 57 deletions

View File

@ -0,0 +1,84 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file gzip.py
@date2025/2/27 10:03
@desc:
"""
from django.utils.cache import patch_vary_headers
from django.utils.deprecation import MiddlewareMixin
from django.utils.regex_helper import _lazy_re_compile
from django.utils.text import compress_sequence, compress_string
re_accepts_gzip = _lazy_re_compile(r"\bgzip\b")
class GZipMiddleware(MiddlewareMixin):
"""
Compress content if the browser allows gzip compression.
Set the Vary header accordingly, so that caches will base their storage
on the Accept-Encoding header.
"""
max_random_bytes = 100
def process_response(self, request, response):
if request.method != 'GET' or request.path.startswith('/api'):
return response
# It's not worth attempting to compress really short responses.
if not response.streaming and len(response.content) < 200:
return response
# Avoid gzipping if we've already got a content-encoding.
if response.has_header("Content-Encoding"):
return response
patch_vary_headers(response, ("Accept-Encoding",))
ae = request.META.get("HTTP_ACCEPT_ENCODING", "")
if not re_accepts_gzip.search(ae):
return response
if response.streaming:
if response.is_async:
# pull to lexical scope to capture fixed reference in case
# streaming_content is set again later.
orignal_iterator = response.streaming_content
async def gzip_wrapper():
async for chunk in orignal_iterator:
yield compress_string(
chunk,
max_random_bytes=self.max_random_bytes,
)
response.streaming_content = gzip_wrapper()
else:
response.streaming_content = compress_sequence(
response.streaming_content,
max_random_bytes=self.max_random_bytes,
)
# Delete the `Content-Length` header for streaming content, because
# we won't know the compressed size until we stream it.
del response.headers["Content-Length"]
else:
# Return the compressed content only if it's actually shorter.
compressed_content = compress_string(
response.content,
max_random_bytes=self.max_random_bytes,
)
if len(compressed_content) >= len(response.content):
return response
response.content = compressed_content
response.headers["Content-Length"] = str(len(response.content))
# If there is a strong ETag, make it weak to fulfill the requirements
# of RFC 9110 Section 8.8.1 while also allowing conditional request
# matches on ETags.
etag = response.get("ETag")
if etag and etag.startswith('"'):
response.headers["ETag"] = "W/" + etag
response.headers["Content-Encoding"] = "gzip"
return response

View File

@ -1106,7 +1106,7 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
'order_by_query': QuerySet(Document).order_by('-create_time', 'id') 'order_by_query': QuerySet(Document).order_by('-create_time', 'id')
}, select_string=get_file_content( }, select_string=get_file_content(
os.path.join(PROJECT_DIR, "apps", "dataset", 'sql', 'list_document.sql')), os.path.join(PROJECT_DIR, "apps", "dataset", 'sql', 'list_document.sql')),
with_search_one=False), dataset_id with_search_one=False), dataset_id
@staticmethod @staticmethod
def _batch_sync(document_id_list: List[str]): def _batch_sync(document_id_list: List[str]):
@ -1263,6 +1263,7 @@ def save_image(image_list):
exist_image_list = [str(i.get('id')) for i in exist_image_list = [str(i.get('id')) for i in
QuerySet(Image).filter(id__in=[i.id for i in image_list]).values('id')] QuerySet(Image).filter(id__in=[i.id for i in image_list]).values('id')]
save_image_list = [image for image in image_list if not exist_image_list.__contains__(str(image.id))] save_image_list = [image for image in image_list if not exist_image_list.__contains__(str(image.id))]
save_image_list = list({img.id: img for img in save_image_list}.values())
if len(save_image_list) > 0: if len(save_image_list) > 0:
QuerySet(Image).bulk_create(save_image_list) QuerySet(Image).bulk_create(save_image_list)

View File

@ -55,7 +55,7 @@ MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.gzip.GZipMiddleware', 'common.middleware.gzip.GZipMiddleware',
'common.middleware.static_headers_middleware.StaticHeadersMiddleware', 'common.middleware.static_headers_middleware.StaticHeadersMiddleware',
'common.middleware.cross_domain_middleware.CrossDomainMiddleware' 'common.middleware.cross_domain_middleware.CrossDomainMiddleware'

View File

@ -454,6 +454,7 @@
:data="paragraph.metadata" :data="paragraph.metadata"
:content="paragraph.page_content" :content="paragraph.page_content"
:index="paragraphIndex" :index="paragraphIndex"
:score="paragraph.metadata?.relevance_score"
/> />
</template> </template>
</template> </template>

View File

@ -9,10 +9,10 @@
<template #icon> <template #icon>
<AppAvatar class="mr-12 avatar-light" :size="22"> {{ index + 1 + '' }}</AppAvatar> <AppAvatar class="mr-12 avatar-light" :size="22"> {{ index + 1 + '' }}</AppAvatar>
</template> </template>
<div class="active-button primary">{{ data.similarity?.toFixed(3) }}</div> <div class="active-button primary">{{ score?.toFixed(3) || data.similarity?.toFixed(3) }}</div>
<template #description> <template #description>
<el-scrollbar height="150"> <el-scrollbar height="150">
<MdPreview ref="editorRef" editorId="preview-only" :modelValue="content" noImgZoomIn/> <MdPreview ref="editorRef" editorId="preview-only" :modelValue="content" noImgZoomIn />
</el-scrollbar> </el-scrollbar>
</template> </template>
<template #footer> <template #footer>
@ -69,6 +69,10 @@ const props = defineProps({
index: { index: {
type: Number, type: Number,
default: 0 default: 0
},
score: {
type: Number,
default: 0
} }
}) })
const isMetaObject = computed(() => typeof props.data.meta === 'object') const isMetaObject = computed(() => typeof props.data.meta === 'object')

View File

@ -6,7 +6,7 @@
<LogoIcon v-else height="32px" width="32px" /> <LogoIcon v-else height="32px" width="32px" />
</div> </div>
<div class="content" @mouseup="openControl"> <div class="content" @mouseup="openControl">
<el-card shadow="always" class="dialog-card mb-8"> <el-card shadow="always" class="mb-8 border-r-8">
<MdRenderer <MdRenderer
v-if=" v-if="
(chatRecord.write_ed === undefined || chatRecord.write_ed === true) && (chatRecord.write_ed === undefined || chatRecord.write_ed === true) &&
@ -27,10 +27,10 @@
:send-message="chatMessage" :send-message="chatMessage"
></MdRenderer> ></MdRenderer>
</template> </template>
<span v-else-if="chatRecord.is_stop" shadow="always" class="dialog-card"> <span v-else-if="chatRecord.is_stop" shadow="always">
{{ $t('chat.tip.stopAnswer') }} {{ $t('chat.tip.stopAnswer') }}
</span> </span>
<span v-else shadow="always" class="dialog-card"> <span v-else shadow="always">
{{ $t('chat.tip.answerLoading') }} <span class="dotting"></span> {{ $t('chat.tip.answerLoading') }} <span class="dotting"></span>
</span> </span>
<!-- 知识来源 --> <!-- 知识来源 -->

View File

@ -17,27 +17,41 @@ import { ref, nextTick, onMounted } from 'vue'
import { t } from '@/locales' import { t } from '@/locales'
const isOpen = ref<boolean>(false) const isOpen = ref<boolean>(false)
const eventVal = ref<any>({}) const eventVal = ref<any>({})
function getSelection() { function getSelection() {
const selection = window.getSelection() const selection = window.getSelection()
if (selection && selection.anchorNode == null) { if (selection) {
return null if (selection.rangeCount === 0) return undefined
const range = selection.getRangeAt(0)
const fragment = range.cloneContents() //
const div = document.createElement('div')
div.appendChild(fragment)
if (div.textContent) {
return div.textContent.trim()
}
} }
const text = selection?.anchorNode?.textContent return undefined
return text && text.substring(selection.anchorOffset, selection.focusOffset)
} }
/** /**
* 打开控制台 * 打开控制台
* @param event * @param event
*/ */
const openControl = (event: any) => { const openControl = (event: any) => {
const c = getSelection() const c = getSelection()
isOpen.value = false
if (c) { if (c) {
nextTick(() => { if (!isOpen.value) {
eventVal.value = event nextTick(() => {
isOpen.value = true eventVal.value = event
}) isOpen.value = true
})
} else {
clearSelectedText()
isOpen.value = false
}
event.preventDefault() event.preventDefault()
} else {
isOpen.value = false
} }
} }

View File

@ -6,7 +6,7 @@
<LogoIcon v-else height="32px" width="32px" /> <LogoIcon v-else height="32px" width="32px" />
</div> </div>
<div class="content" v-if="prologue"> <div class="content" v-if="prologue">
<el-card shadow="always" class="dialog-card" style="--el-card-padding: 10px 16px 12px"> <el-card shadow="always" class="border-r-8" style="--el-card-padding: 10px 16px 12px">
<MdRenderer <MdRenderer
:source="prologue" :source="prologue"
:send-message="sendMessage" :send-message="sendMessage"

View File

@ -1,20 +1,14 @@
<template> <template>
<!-- 问题内容 --> <!-- 问题内容 -->
<div class="question-content item-content mb-16 lighter"> <div class="question-content item-content mb-16 lighter">
<div class="content mr-16"> <div
class="content mr-12 p-12-16 border-r-8"
:class="document_list.length >= 2 ? 'media_2' : `media_${document_list.length}`"
>
<div class="text break-all pre-wrap"> <div class="text break-all pre-wrap">
<div class="mb-8" v-if="document_list.length"> <div class="mb-8" v-if="document_list.length">
<el-row :gutter="10"> <el-space wrap class="w-full media-file-width">
<el-col <template v-for="(item, index) in document_list" :key="index">
v-for="(item, index) in document_list"
:key="index"
:xs="24"
:sm="props.type === 'debug-ai-chat' ? 24 : 12"
:md="props.type === 'debug-ai-chat' ? 24 : 12"
:lg="props.type === 'debug-ai-chat' ? 24 : 12"
:xl="props.type === 'debug-ai-chat' ? 24 : 12"
class="mb-8 w-full"
>
<el-card shadow="never" style="--el-card-padding: 8px" class="download-file cursor"> <el-card shadow="never" style="--el-card-padding: 8px" class="download-file cursor">
<div class="download-button flex align-center" @click="downloadFile(item)"> <div class="download-button flex align-center" @click="downloadFile(item)">
<el-icon class="mr-4"> <el-icon class="mr-4">
@ -29,8 +23,8 @@
</div> </div>
</div> </div>
</el-card> </el-card>
</el-col> </template>
</el-row> </el-space>
</div> </div>
<div class="mb-8" v-if="image_list.length"> <div class="mb-8" v-if="image_list.length">
<el-space wrap> <el-space wrap>
@ -53,17 +47,8 @@
</el-space> </el-space>
</div> </div>
<div class="mb-8" v-if="audio_list.length"> <div class="mb-8" v-if="audio_list.length">
<el-row :gutter="10"> <el-space wrap>
<el-col <template v-for="(item, index) in audio_list" :key="index">
v-for="(item, index) in audio_list"
:key="index"
:xs="24"
:sm="props.type === 'debug-ai-chat' ? 24 : 12"
:md="props.type === 'debug-ai-chat' ? 24 : 12"
:lg="props.type === 'debug-ai-chat' ? 24 : 12"
:xl="props.type === 'debug-ai-chat' ? 24 : 12"
class="mb-8"
>
<div class="file cursor border-r-4" v-if="item.url"> <div class="file cursor border-r-4" v-if="item.url">
<audio <audio
:src="item.url" :src="item.url"
@ -72,10 +57,10 @@
class="border-r-4" class="border-r-4"
/> />
</div> </div>
</el-col> </template>
</el-row> </el-space>
</div> </div>
{{ chatRecord.problem_text }} <span> {{ chatRecord.problem_text }}</span>
</div> </div>
</div> </div>
<div class="avatar"> <div class="avatar">
@ -140,6 +125,16 @@ onMounted(() => {})
.question-content { .question-content {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
padding-left: var(--padding-left);
width: 100%;
box-sizing: border-box;
.content {
background: #d6e2ff;
padding-left: 16px;
padding-right: 16px;
}
.download-file { .download-file {
height: 43px; height: 43px;
@ -163,5 +158,44 @@ onMounted(() => {})
display: none; display: none;
} }
} }
.media-file-width {
:deep(.el-space__item) {
min-width: 40% !important;
flex-grow: 1;
}
}
.media_2 {
flex: 1;
}
.media_0 {
flex: inherit;
}
.media_1 {
width: 50%;
}
}
@media only screen and (max-width: 768px) {
.question-content {
.media-file-width {
:deep(.el-space__item) {
min-width: 100% !important;
}
}
.media_1 {
width: 100%;
}
}
}
.debug-ai-chat {
.question-content {
.media-file-width {
:deep(.el-space__item) {
min-width: 100% !important;
}
}
.media_1 {
width: 100%;
}
}
} }
</style> </style>

View File

@ -7,7 +7,7 @@
class="mb-16" class="mb-16"
style="padding: 0 24px" style="padding: 0 24px"
> >
<el-card shadow="always" class="dialog-card" style="--el-card-padding: 16px 8px"> <el-card shadow="always" class="border-r-8" style="--el-card-padding: 16px 8px">
<div <div
class="flex align-center cursor w-full" class="flex align-center cursor w-full"
style="padding: 0 8px" style="padding: 0 8px"

View File

@ -17,15 +17,12 @@
.content { .content {
padding-left: var(--padding-left); padding-left: var(--padding-left);
padding-right: var(--padding-left);
:deep(ol) { :deep(ol) {
margin-left: 16px !important; margin-left: 16px !important;
} }
} }
.text {
padding: 6px 0;
}
} }
&__operate { &__operate {
background: #f3f7f9; background: #f3f7f9;

View File

@ -1,5 +1,5 @@
<template> <template>
<div ref="aiChatRef" class="ai-chat" :class="type == 'log' ? 'chart-log' : ''"> <div ref="aiChatRef" class="ai-chat" :class="type">
<UserForm <UserForm
v-model:api_form_data="api_form_data" v-model:api_form_data="api_form_data"
v-model:form_data="form_data" v-model:form_data="form_data"

View File

@ -311,6 +311,9 @@ h5 {
.border-r-4 { .border-r-4 {
border-radius: 4px; border-radius: 4px;
} }
.border-r-8 {
border-radius: 8px;
}
.border-t-dashed { .border-t-dashed {
border-top: 1px dashed var(--el-border-color); border-top: 1px dashed var(--el-border-color);
@ -761,4 +764,4 @@ h5 {
.responsive-dialog { .responsive-dialog {
width: 90% !important; width: 90% !important;
} }
} }

View File

@ -101,7 +101,7 @@
</div> </div>
<div class="chat-pc__right"> <div class="chat-pc__right">
<div class="right-header border-b mb-24 p-16-24 flex-between"> <div class="right-header border-b mb-24 p-16-24 flex-between">
<h4 class="ellipsis-1" style="width: 70%"> <h4 class="ellipsis-1" style="width: 66%">
{{ currentChatName }} {{ currentChatName }}
</h4> </h4>

View File

@ -14,6 +14,7 @@
label-position="top" label-position="top"
require-asterisk-position="right" require-asterisk-position="right"
v-loading="loading" v-loading="loading"
@submit.prevent
> >
<el-form-item <el-form-item
:label="$t('views.functionLib.functionForm.form.functionName.label')" :label="$t('views.functionLib.functionForm.form.functionName.label')"

View File

@ -209,11 +209,14 @@ const nodeCascaderRef = ref()
const nodeCascaderRef2 = ref() const nodeCascaderRef2 = ref()
const validate = async () => { const validate = async () => {
// console.log(replyNodeFormRef.value.validate()) // console.log(replyNodeFormRef.value.validate())
return Promise.all([ let ps = [
replyNodeFormRef.value?.validate(), replyNodeFormRef.value?.validate(),
...nodeCascaderRef.value.map((item: any) => item.validate()), ...nodeCascaderRef.value.map((item: any) => item.validate())
...nodeCascaderRef2.value.map((item: any) => item.validate()) ]
]).catch((err: any) => { if (nodeCascaderRef2.value) {
ps = [...ps, ...nodeCascaderRef.value.map((item: any) => item.validate())]
}
return Promise.all(ps).catch((err: any) => {
return Promise.reject({ node: props.nodeModel, errMessage: err }) return Promise.reject({ node: props.nodeModel, errMessage: err })
}) })
} }