mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-28 23:32:48 +00:00
Merge branch 'main' of https://github.com/maxkb-dev/maxkb
This commit is contained in:
commit
01729157b7
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -454,6 +454,7 @@
|
|||
:data="paragraph.metadata"
|
||||
:content="paragraph.page_content"
|
||||
:index="paragraphIndex"
|
||||
:score="paragraph.metadata?.relevance_score"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@
|
|||
<template #icon>
|
||||
<AppAvatar class="mr-12 avatar-light" :size="22"> {{ index + 1 + '' }}</AppAvatar>
|
||||
</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>
|
||||
<el-scrollbar height="150">
|
||||
<MdPreview ref="editorRef" editorId="preview-only" :modelValue="content" noImgZoomIn/>
|
||||
<MdPreview ref="editorRef" editorId="preview-only" :modelValue="content" noImgZoomIn />
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
<template #footer>
|
||||
|
|
@ -69,6 +69,10 @@ const props = defineProps({
|
|||
index: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
score: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
const isMetaObject = computed(() => typeof props.data.meta === 'object')
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<LogoIcon v-else height="32px" width="32px" />
|
||||
</div>
|
||||
<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
|
||||
v-if="
|
||||
(chatRecord.write_ed === undefined || chatRecord.write_ed === true) &&
|
||||
|
|
@ -27,10 +27,10 @@
|
|||
:send-message="chatMessage"
|
||||
></MdRenderer>
|
||||
</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') }}
|
||||
</span>
|
||||
<span v-else shadow="always" class="dialog-card">
|
||||
<span v-else shadow="always">
|
||||
{{ $t('chat.tip.answerLoading') }} <span class="dotting"></span>
|
||||
</span>
|
||||
<!-- 知识来源 -->
|
||||
|
|
|
|||
|
|
@ -17,27 +17,41 @@ import { ref, nextTick, onMounted } from 'vue'
|
|||
import { t } from '@/locales'
|
||||
const isOpen = ref<boolean>(false)
|
||||
const eventVal = ref<any>({})
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<LogoIcon v-else height="32px" width="32px" />
|
||||
</div>
|
||||
<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
|
||||
:source="prologue"
|
||||
:send-message="sendMessage"
|
||||
|
|
|
|||
|
|
@ -1,20 +1,14 @@
|
|||
<template>
|
||||
<!-- 问题内容 -->
|
||||
<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="mb-8" v-if="document_list.length">
|
||||
<el-row :gutter="10">
|
||||
<el-col
|
||||
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-space wrap class="w-full media-file-width">
|
||||
<template v-for="(item, index) in document_list" :key="index">
|
||||
<el-card shadow="never" style="--el-card-padding: 8px" class="download-file cursor">
|
||||
<div class="download-button flex align-center" @click="downloadFile(item)">
|
||||
<el-icon class="mr-4">
|
||||
|
|
@ -29,8 +23,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
</el-space>
|
||||
</div>
|
||||
<div class="mb-8" v-if="image_list.length">
|
||||
<el-space wrap>
|
||||
|
|
@ -53,17 +47,8 @@
|
|||
</el-space>
|
||||
</div>
|
||||
<div class="mb-8" v-if="audio_list.length">
|
||||
<el-row :gutter="10">
|
||||
<el-col
|
||||
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"
|
||||
>
|
||||
<el-space wrap>
|
||||
<template v-for="(item, index) in audio_list" :key="index">
|
||||
<div class="file cursor border-r-4" v-if="item.url">
|
||||
<audio
|
||||
:src="item.url"
|
||||
|
|
@ -72,10 +57,10 @@
|
|||
class="border-r-4"
|
||||
/>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
</el-space>
|
||||
</div>
|
||||
{{ chatRecord.problem_text }}
|
||||
<span> {{ chatRecord.problem_text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="avatar">
|
||||
|
|
@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
class="mb-16"
|
||||
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
|
||||
class="flex align-center cursor w-full"
|
||||
style="padding: 0 8px"
|
||||
|
|
|
|||
|
|
@ -17,15 +17,12 @@
|
|||
|
||||
.content {
|
||||
padding-left: var(--padding-left);
|
||||
padding-right: var(--padding-left);
|
||||
|
||||
:deep(ol) {
|
||||
margin-left: 16px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
padding: 6px 0;
|
||||
}
|
||||
}
|
||||
&__operate {
|
||||
background: #f3f7f9;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div ref="aiChatRef" class="ai-chat" :class="type == 'log' ? 'chart-log' : ''">
|
||||
<div ref="aiChatRef" class="ai-chat" :class="type">
|
||||
<UserForm
|
||||
v-model:api_form_data="api_form_data"
|
||||
v-model:form_data="form_data"
|
||||
|
|
|
|||
|
|
@ -311,6 +311,9 @@ h5 {
|
|||
.border-r-4 {
|
||||
border-radius: 4px;
|
||||
}
|
||||
.border-r-8 {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.border-t-dashed {
|
||||
border-top: 1px dashed var(--el-border-color);
|
||||
|
|
@ -761,4 +764,4 @@ h5 {
|
|||
.responsive-dialog {
|
||||
width: 90% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@
|
|||
</div>
|
||||
<div class="chat-pc__right">
|
||||
<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 }}
|
||||
</h4>
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
v-loading="loading"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item
|
||||
:label="$t('views.functionLib.functionForm.form.functionName.label')"
|
||||
|
|
|
|||
|
|
@ -209,11 +209,14 @@ const nodeCascaderRef = ref()
|
|||
const nodeCascaderRef2 = ref()
|
||||
const validate = async () => {
|
||||
// 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 })
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue