Skip to content

Commit 0172915

Browse files
Merge branch 'main' of https://github.com/maxkb-dev/maxkb
2 parents 9a69d23 + 1811a80 commit 0172915

File tree

16 files changed

+199
-57
lines changed

16 files changed

+199
-57
lines changed

apps/common/middleware/gzip.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# coding=utf-8
2+
"""
3+
@project: MaxKB
4+
@Author:虎
5+
@file: gzip.py
6+
@date:2025/2/27 10:03
7+
@desc:
8+
"""
9+
from django.utils.cache import patch_vary_headers
10+
from django.utils.deprecation import MiddlewareMixin
11+
from django.utils.regex_helper import _lazy_re_compile
12+
from django.utils.text import compress_sequence, compress_string
13+
14+
re_accepts_gzip = _lazy_re_compile(r"\bgzip\b")
15+
16+
17+
class GZipMiddleware(MiddlewareMixin):
18+
"""
19+
Compress content if the browser allows gzip compression.
20+
Set the Vary header accordingly, so that caches will base their storage
21+
on the Accept-Encoding header.
22+
"""
23+
24+
max_random_bytes = 100
25+
26+
def process_response(self, request, response):
27+
if request.method != 'GET' or request.path.startswith('/api'):
28+
return response
29+
# It's not worth attempting to compress really short responses.
30+
if not response.streaming and len(response.content) < 200:
31+
return response
32+
33+
# Avoid gzipping if we've already got a content-encoding.
34+
if response.has_header("Content-Encoding"):
35+
return response
36+
37+
patch_vary_headers(response, ("Accept-Encoding",))
38+
39+
ae = request.META.get("HTTP_ACCEPT_ENCODING", "")
40+
if not re_accepts_gzip.search(ae):
41+
return response
42+
43+
if response.streaming:
44+
if response.is_async:
45+
# pull to lexical scope to capture fixed reference in case
46+
# streaming_content is set again later.
47+
orignal_iterator = response.streaming_content
48+
49+
async def gzip_wrapper():
50+
async for chunk in orignal_iterator:
51+
yield compress_string(
52+
chunk,
53+
max_random_bytes=self.max_random_bytes,
54+
)
55+
56+
response.streaming_content = gzip_wrapper()
57+
else:
58+
response.streaming_content = compress_sequence(
59+
response.streaming_content,
60+
max_random_bytes=self.max_random_bytes,
61+
)
62+
# Delete the `Content-Length` header for streaming content, because
63+
# we won't know the compressed size until we stream it.
64+
del response.headers["Content-Length"]
65+
else:
66+
# Return the compressed content only if it's actually shorter.
67+
compressed_content = compress_string(
68+
response.content,
69+
max_random_bytes=self.max_random_bytes,
70+
)
71+
if len(compressed_content) >= len(response.content):
72+
return response
73+
response.content = compressed_content
74+
response.headers["Content-Length"] = str(len(response.content))
75+
76+
# If there is a strong ETag, make it weak to fulfill the requirements
77+
# of RFC 9110 Section 8.8.1 while also allowing conditional request
78+
# matches on ETags.
79+
etag = response.get("ETag")
80+
if etag and etag.startswith('"'):
81+
response.headers["ETag"] = "W/" + etag
82+
response.headers["Content-Encoding"] = "gzip"
83+
84+
return response

apps/dataset/serializers/document_serializers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1106,7 +1106,7 @@ def batch_save(self, instance_list: List[Dict], with_valid=True):
11061106
'order_by_query': QuerySet(Document).order_by('-create_time', 'id')
11071107
}, select_string=get_file_content(
11081108
os.path.join(PROJECT_DIR, "apps", "dataset", 'sql', 'list_document.sql')),
1109-
with_search_one=False), dataset_id
1109+
with_search_one=False), dataset_id
11101110

11111111
@staticmethod
11121112
def _batch_sync(document_id_list: List[str]):
@@ -1263,6 +1263,7 @@ def save_image(image_list):
12631263
exist_image_list = [str(i.get('id')) for i in
12641264
QuerySet(Image).filter(id__in=[i.id for i in image_list]).values('id')]
12651265
save_image_list = [image for image in image_list if not exist_image_list.__contains__(str(image.id))]
1266+
save_image_list = list({img.id: img for img in save_image_list}.values())
12661267
if len(save_image_list) > 0:
12671268
QuerySet(Image).bulk_create(save_image_list)
12681269

apps/smartdoc/settings/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
'django.contrib.sessions.middleware.SessionMiddleware',
5656
'django.middleware.common.CommonMiddleware',
5757
'django.contrib.messages.middleware.MessageMiddleware',
58-
'django.middleware.gzip.GZipMiddleware',
58+
'common.middleware.gzip.GZipMiddleware',
5959
'common.middleware.static_headers_middleware.StaticHeadersMiddleware',
6060
'common.middleware.cross_domain_middleware.CrossDomainMiddleware'
6161

ui/src/components/ai-chat/ExecutionDetailDialog.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@
454454
:data="paragraph.metadata"
455455
:content="paragraph.page_content"
456456
:index="paragraphIndex"
457+
:score="paragraph.metadata?.relevance_score"
457458
/>
458459
</template>
459460
</template>

ui/src/components/ai-chat/component/ParagraphCard.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
<template #icon>
1010
<AppAvatar class="mr-12 avatar-light" :size="22"> {{ index + 1 + '' }}</AppAvatar>
1111
</template>
12-
<div class="active-button primary">{{ data.similarity?.toFixed(3) }}</div>
12+
<div class="active-button primary">{{ score?.toFixed(3) || data.similarity?.toFixed(3) }}</div>
1313
<template #description>
1414
<el-scrollbar height="150">
15-
<MdPreview ref="editorRef" editorId="preview-only" :modelValue="content" noImgZoomIn/>
15+
<MdPreview ref="editorRef" editorId="preview-only" :modelValue="content" noImgZoomIn />
1616
</el-scrollbar>
1717
</template>
1818
<template #footer>
@@ -69,6 +69,10 @@ const props = defineProps({
6969
index: {
7070
type: Number,
7171
default: 0
72+
},
73+
score: {
74+
type: Number,
75+
default: 0
7276
}
7377
})
7478
const isMetaObject = computed(() => typeof props.data.meta === 'object')

ui/src/components/ai-chat/component/answer-content/index.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<LogoIcon v-else height="32px" width="32px" />
77
</div>
88
<div class="content" @mouseup="openControl">
9-
<el-card shadow="always" class="dialog-card mb-8">
9+
<el-card shadow="always" class="mb-8 border-r-8">
1010
<MdRenderer
1111
v-if="
1212
(chatRecord.write_ed === undefined || chatRecord.write_ed === true) &&
@@ -27,10 +27,10 @@
2727
:send-message="chatMessage"
2828
></MdRenderer>
2929
</template>
30-
<span v-else-if="chatRecord.is_stop" shadow="always" class="dialog-card">
30+
<span v-else-if="chatRecord.is_stop" shadow="always">
3131
{{ $t('chat.tip.stopAnswer') }}
3232
</span>
33-
<span v-else shadow="always" class="dialog-card">
33+
<span v-else shadow="always">
3434
{{ $t('chat.tip.answerLoading') }} <span class="dotting"></span>
3535
</span>
3636
<!-- 知识来源 -->

ui/src/components/ai-chat/component/control/index.vue

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,41 @@ import { ref, nextTick, onMounted } from 'vue'
1717
import { t } from '@/locales'
1818
const isOpen = ref<boolean>(false)
1919
const eventVal = ref<any>({})
20+
2021
function getSelection() {
2122
const selection = window.getSelection()
22-
if (selection && selection.anchorNode == null) {
23-
return null
23+
if (selection) {
24+
if (selection.rangeCount === 0) return undefined
25+
const range = selection.getRangeAt(0)
26+
const fragment = range.cloneContents() // 克隆选区内容
27+
const div = document.createElement('div')
28+
div.appendChild(fragment)
29+
if (div.textContent) {
30+
return div.textContent.trim()
31+
}
2432
}
25-
const text = selection?.anchorNode?.textContent
26-
return text && text.substring(selection.anchorOffset, selection.focusOffset)
33+
return undefined
2734
}
35+
2836
/**
2937
* 打开控制台
3038
* @param event
3139
*/
3240
const openControl = (event: any) => {
3341
const c = getSelection()
34-
isOpen.value = false
3542
if (c) {
36-
nextTick(() => {
37-
eventVal.value = event
38-
isOpen.value = true
39-
})
43+
if (!isOpen.value) {
44+
nextTick(() => {
45+
eventVal.value = event
46+
isOpen.value = true
47+
})
48+
} else {
49+
clearSelectedText()
50+
isOpen.value = false
51+
}
4052
event.preventDefault()
53+
} else {
54+
isOpen.value = false
4155
}
4256
}
4357

ui/src/components/ai-chat/component/prologue-content/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<LogoIcon v-else height="32px" width="32px" />
77
</div>
88
<div class="content" v-if="prologue">
9-
<el-card shadow="always" class="dialog-card" style="--el-card-padding: 10px 16px 12px">
9+
<el-card shadow="always" class="border-r-8" style="--el-card-padding: 10px 16px 12px">
1010
<MdRenderer
1111
:source="prologue"
1212
:send-message="sendMessage"

ui/src/components/ai-chat/component/question-content/index.vue

Lines changed: 62 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
<template>
22
<!-- 问题内容 -->
33
<div class="question-content item-content mb-16 lighter">
4-
<div class="content mr-16">
4+
<div
5+
class="content mr-12 p-12-16 border-r-8"
6+
:class="document_list.length >= 2 ? 'media_2' : `media_${document_list.length}`"
7+
>
58
<div class="text break-all pre-wrap">
69
<div class="mb-8" v-if="document_list.length">
7-
<el-row :gutter="10">
8-
<el-col
9-
v-for="(item, index) in document_list"
10-
:key="index"
11-
:xs="24"
12-
:sm="props.type === 'debug-ai-chat' ? 24 : 12"
13-
:md="props.type === 'debug-ai-chat' ? 24 : 12"
14-
:lg="props.type === 'debug-ai-chat' ? 24 : 12"
15-
:xl="props.type === 'debug-ai-chat' ? 24 : 12"
16-
class="mb-8 w-full"
17-
>
10+
<el-space wrap class="w-full media-file-width">
11+
<template v-for="(item, index) in document_list" :key="index">
1812
<el-card shadow="never" style="--el-card-padding: 8px" class="download-file cursor">
1913
<div class="download-button flex align-center" @click="downloadFile(item)">
2014
<el-icon class="mr-4">
@@ -29,8 +23,8 @@
2923
</div>
3024
</div>
3125
</el-card>
32-
</el-col>
33-
</el-row>
26+
</template>
27+
</el-space>
3428
</div>
3529
<div class="mb-8" v-if="image_list.length">
3630
<el-space wrap>
@@ -53,17 +47,8 @@
5347
</el-space>
5448
</div>
5549
<div class="mb-8" v-if="audio_list.length">
56-
<el-row :gutter="10">
57-
<el-col
58-
v-for="(item, index) in audio_list"
59-
:key="index"
60-
:xs="24"
61-
:sm="props.type === 'debug-ai-chat' ? 24 : 12"
62-
:md="props.type === 'debug-ai-chat' ? 24 : 12"
63-
:lg="props.type === 'debug-ai-chat' ? 24 : 12"
64-
:xl="props.type === 'debug-ai-chat' ? 24 : 12"
65-
class="mb-8"
66-
>
50+
<el-space wrap>
51+
<template v-for="(item, index) in audio_list" :key="index">
6752
<div class="file cursor border-r-4" v-if="item.url">
6853
<audio
6954
:src="item.url"
@@ -72,10 +57,10 @@
7257
class="border-r-4"
7358
/>
7459
</div>
75-
</el-col>
76-
</el-row>
60+
</template>
61+
</el-space>
7762
</div>
78-
{{ chatRecord.problem_text }}
63+
<span> {{ chatRecord.problem_text }}</span>
7964
</div>
8065
</div>
8166
<div class="avatar">
@@ -140,6 +125,16 @@ onMounted(() => {})
140125
.question-content {
141126
display: flex;
142127
justify-content: flex-end;
128+
padding-left: var(--padding-left);
129+
width: 100%;
130+
box-sizing: border-box;
131+
132+
.content {
133+
background: #d6e2ff;
134+
padding-left: 16px;
135+
padding-right: 16px;
136+
137+
}
143138
144139
.download-file {
145140
height: 43px;
@@ -163,5 +158,44 @@ onMounted(() => {})
163158
display: none;
164159
}
165160
}
161+
.media-file-width {
162+
:deep(.el-space__item) {
163+
min-width: 40% !important;
164+
flex-grow: 1;
165+
}
166+
}
167+
.media_2 {
168+
flex: 1;
169+
}
170+
.media_0 {
171+
flex: inherit;
172+
}
173+
.media_1 {
174+
width: 50%;
175+
}
176+
}
177+
@media only screen and (max-width: 768px) {
178+
.question-content {
179+
.media-file-width {
180+
:deep(.el-space__item) {
181+
min-width: 100% !important;
182+
}
183+
}
184+
.media_1 {
185+
width: 100%;
186+
}
187+
}
188+
}
189+
.debug-ai-chat {
190+
.question-content {
191+
.media-file-width {
192+
:deep(.el-space__item) {
193+
min-width: 100% !important;
194+
}
195+
}
196+
.media_1 {
197+
width: 100%;
198+
}
199+
}
166200
}
167201
</style>

ui/src/components/ai-chat/component/user-form/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
class="mb-16"
88
style="padding: 0 24px"
99
>
10-
<el-card shadow="always" class="dialog-card" style="--el-card-padding: 16px 8px">
10+
<el-card shadow="always" class="border-r-8" style="--el-card-padding: 16px 8px">
1111
<div
1212
class="flex align-center cursor w-full"
1313
style="padding: 0 8px"

0 commit comments

Comments
 (0)