diff --git a/apps/common/middleware/gzip.py b/apps/common/middleware/gzip.py new file mode 100644 index 000000000..229ec09b3 --- /dev/null +++ b/apps/common/middleware/gzip.py @@ -0,0 +1,84 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎 + @file: gzip.py + @date:2025/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 diff --git a/apps/dataset/serializers/document_serializers.py b/apps/dataset/serializers/document_serializers.py index b8c013418..963c033d4 100644 --- a/apps/dataset/serializers/document_serializers.py +++ b/apps/dataset/serializers/document_serializers.py @@ -1106,7 +1106,7 @@ class DocumentSerializers(ApiMixin, serializers.Serializer): 'order_by_query': QuerySet(Document).order_by('-create_time', 'id') }, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "dataset", 'sql', 'list_document.sql')), - with_search_one=False), dataset_id + with_search_one=False), dataset_id @staticmethod 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 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 = list({img.id: img for img in save_image_list}.values()) if len(save_image_list) > 0: QuerySet(Image).bulk_create(save_image_list) diff --git a/apps/smartdoc/settings/base.py b/apps/smartdoc/settings/base.py index cda860b76..b804c637e 100644 --- a/apps/smartdoc/settings/base.py +++ b/apps/smartdoc/settings/base.py @@ -55,7 +55,7 @@ MIDDLEWARE = [ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.gzip.GZipMiddleware', + 'common.middleware.gzip.GZipMiddleware', 'common.middleware.static_headers_middleware.StaticHeadersMiddleware', 'common.middleware.cross_domain_middleware.CrossDomainMiddleware' diff --git a/ui/src/components/ai-chat/ExecutionDetailDialog.vue b/ui/src/components/ai-chat/ExecutionDetailDialog.vue index 4f97c0085..76917fc9d 100644 --- a/ui/src/components/ai-chat/ExecutionDetailDialog.vue +++ b/ui/src/components/ai-chat/ExecutionDetailDialog.vue @@ -454,6 +454,7 @@ :data="paragraph.metadata" :content="paragraph.page_content" :index="paragraphIndex" + :score="paragraph.metadata?.relevance_score" /> diff --git a/ui/src/components/ai-chat/component/ParagraphCard.vue b/ui/src/components/ai-chat/component/ParagraphCard.vue index de8010deb..82fbd13cd 100644 --- a/ui/src/components/ai-chat/component/ParagraphCard.vue +++ b/ui/src/components/ai-chat/component/ParagraphCard.vue @@ -9,10 +9,10 @@ -
{{ data.similarity?.toFixed(3) }}
+
{{ score?.toFixed(3) || data.similarity?.toFixed(3) }}
- + {{ $t('chat.tip.stopAnswer') }} - + {{ $t('chat.tip.answerLoading') }} diff --git a/ui/src/components/ai-chat/component/control/index.vue b/ui/src/components/ai-chat/component/control/index.vue index daeeaeae0..6c7eefa76 100644 --- a/ui/src/components/ai-chat/component/control/index.vue +++ b/ui/src/components/ai-chat/component/control/index.vue @@ -17,27 +17,41 @@ import { ref, nextTick, onMounted } from 'vue' import { t } from '@/locales' const isOpen = ref(false) const eventVal = ref({}) + function getSelection() { const selection = window.getSelection() - if (selection && selection.anchorNode == null) { - return null + if (selection) { + 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 text && text.substring(selection.anchorOffset, selection.focusOffset) + return undefined } + /** * 打开控制台 * @param event */ const openControl = (event: any) => { const c = getSelection() - isOpen.value = false if (c) { - nextTick(() => { - eventVal.value = event - isOpen.value = true - }) + if (!isOpen.value) { + nextTick(() => { + eventVal.value = event + isOpen.value = true + }) + } else { + clearSelectedText() + isOpen.value = false + } event.preventDefault() + } else { + isOpen.value = false } } diff --git a/ui/src/components/ai-chat/component/prologue-content/index.vue b/ui/src/components/ai-chat/component/prologue-content/index.vue index 3cf7a467e..0ed86b625 100644 --- a/ui/src/components/ai-chat/component/prologue-content/index.vue +++ b/ui/src/components/ai-chat/component/prologue-content/index.vue @@ -6,7 +6,7 @@
- +
-
+
- - + + +
@@ -53,17 +47,8 @@
- - + + +
- {{ chatRecord.problem_text }} + {{ chatRecord.problem_text }}
@@ -140,6 +125,16 @@ onMounted(() => {}) .question-content { display: flex; 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 { height: 43px; @@ -163,5 +158,44 @@ onMounted(() => {}) 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%; + } + } } diff --git a/ui/src/components/ai-chat/component/user-form/index.vue b/ui/src/components/ai-chat/component/user-form/index.vue index b2c89a0e4..980ad8663 100644 --- a/ui/src/components/ai-chat/component/user-form/index.vue +++ b/ui/src/components/ai-chat/component/user-form/index.vue @@ -7,7 +7,7 @@ class="mb-16" style="padding: 0 24px" > - +
-
+
-

+

{{ currentChatName }}

diff --git a/ui/src/views/function-lib/component/FunctionFormDrawer.vue b/ui/src/views/function-lib/component/FunctionFormDrawer.vue index 4ff1030c1..2b74e052b 100644 --- a/ui/src/views/function-lib/component/FunctionFormDrawer.vue +++ b/ui/src/views/function-lib/component/FunctionFormDrawer.vue @@ -14,6 +14,7 @@ label-position="top" require-asterisk-position="right" v-loading="loading" + @submit.prevent > { // console.log(replyNodeFormRef.value.validate()) - return Promise.all([ + let ps = [ replyNodeFormRef.value?.validate(), - ...nodeCascaderRef.value.map((item: any) => item.validate()), - ...nodeCascaderRef2.value.map((item: any) => item.validate()) - ]).catch((err: any) => { + ...nodeCascaderRef.value.map((item: any) => item.validate()) + ] + 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 }) }) }