feat:修复知识库问题

v3.2
panyy 2026-07-01 11:27:06 +08:00
parent c142297f41
commit 4c9ac22a7f
21 changed files with 297 additions and 25 deletions

View File

@ -40,6 +40,8 @@ def get_embedding_id(knowledge_id_list):
_("The vector model of the associated knowledge base is inconsistent and the segmentation cannot be recalled."))
if len(knowledge_list) == 0:
raise Exception(_("The knowledge base setting is wrong, please reset the knowledge base"))
if knowledge_list[0].embedding_model_id is None:
raise Exception(_("The knowledge base setting is wrong, please reset the knowledge base"))
return knowledge_list[0].embedding_model_id

View File

@ -30,6 +30,8 @@ def get_embedding_id(dataset_id_list):
raise Exception("关联知识库的向量模型不一致,无法召回分段。")
if len(dataset_list) == 0:
raise Exception("知识库设置错误,请重新设置知识库")
if dataset_list[0].embedding_model_id is None:
raise Exception("Knowledge base setting error, please reset the knowledge base")
return dataset_list[0].embedding_model_id

View File

@ -13,7 +13,8 @@ from django.db.models import QuerySet
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from application.models import ApplicationAccessToken, ChatUserType, Application, ApplicationVersion
from application.models import ApplicationAccessToken, ChatUserType, Application, ApplicationVersion, \
ApplicationKnowledgeMapping
from application.models.application_api_key import ApplicationApiKey
from application.serializers.application import ApplicationSerializerModel
from common.auth.common import ChatUserToken, ChatAuthentication
@ -23,6 +24,7 @@ from common.constants.cache_version import Cache_Version
from common.database_model_manage.database_model_manage import DatabaseModelManage
from common.exception.app_exception import NotFound404, AppUnauthorizedFailed
from common.utils.rsa_util import get_key_pair_by_sql
from knowledge.models import Knowledge, Problem
class AnonymousAuthenticationSerializer(serializers.Serializer):
@ -173,6 +175,61 @@ class ApplicationProfileSerializer(serializers.Serializer):
_v = getattr(application_version, version_field)
setattr(application, app_field, _v)
@staticmethod
def get_work_flow_knowledge_id_list(work_flow):
knowledge_id_list = []
knowledge_keys = {
'knowledge_id',
'knowledge_id_list',
'dataset_id',
'dataset_id_list',
'all_knowledge_id_list',
}
def collect(data):
if isinstance(data, dict):
for key, value in data.items():
if key in knowledge_keys:
if isinstance(value, list):
knowledge_id_list.extend([str(item) for item in value if item])
elif value:
knowledge_id_list.append(str(value))
else:
collect(value)
elif isinstance(data, list):
for item in data:
collect(item)
collect(work_flow or {})
return list(dict.fromkeys(knowledge_id_list))
@staticmethod
def get_suggested_questions(application):
knowledge_id_list = [str(knowledge_id) for knowledge_id in QuerySet(ApplicationKnowledgeMapping).filter(
application_id=application.id
).values_list('knowledge_id', flat=True)]
knowledge_id_list.extend(ApplicationProfileSerializer.get_work_flow_knowledge_id_list(application.work_flow))
knowledge_id_list = list(dict.fromkeys(knowledge_id_list))
knowledge_id_list = [str(knowledge_id) for knowledge_id in QuerySet(Knowledge).filter(
id__in=knowledge_id_list
).values_list('id', flat=True)]
problem_query_set = QuerySet(Problem)
if len(knowledge_id_list) > 0:
problem_query_set = problem_query_set.filter(knowledge_id__in=knowledge_id_list)
else:
problem_query_set = problem_query_set.filter(knowledge__workspace_id=application.workspace_id)
problem_list = problem_query_set.exclude(content='').order_by(
'-hit_num', '-create_time'
).values_list('content', flat=True)
result = []
for content in problem_list:
content = (content or '').strip()
if content and content not in result:
result.append(content)
if len(result) >= 3:
break
return result
def profile(self, with_valid=True):
if with_valid:
self.is_valid()
@ -219,6 +276,7 @@ class ApplicationProfileSerializer(serializers.Serializer):
'show_user_avatar': application_setting.show_user_avatar,
'float_location': application_setting.float_location,
'chat_background': application_setting.chat_background}
suggested_questions = self.get_suggested_questions(application)
base_node = [node for node in ((application.work_flow or {}).get('nodes', []) or []) if
node.get('id') == 'base-node']
return {**ApplicationSerializerModel(application).data,
@ -232,6 +290,7 @@ class ApplicationProfileSerializer(serializers.Serializer):
'file_upload_enable': application.file_upload_enable,
'file_upload_setting': application.file_upload_setting,
'work_flow': {'nodes': base_node} if base_node else None,
'suggested_questions': suggested_questions,
'show_source': application_access_token.show_source,
'show_exec': application_access_token.show_exec,
'language': application_access_token.language,

View File

@ -119,6 +119,8 @@ def get_embedding_model_by_knowledge_id_list(knowledge_id_list: List):
raise Exception(_('The knowledge base is inconsistent with the vector model'))
if len(knowledge_list) == 0:
raise Exception(_('Knowledge base setting error, please reset the knowledge base'))
if knowledge_list[0].embedding_model_id is None or knowledge_list[0].embedding_model is None:
raise Exception(_('Knowledge base setting error, please reset the knowledge base'))
default_params = get_model_default_params(knowledge_list[0].embedding_model)
@ -130,6 +132,8 @@ def get_embedding_model_by_knowledge_id_list(knowledge_id_list: List):
def get_embedding_model_by_knowledge_id(knowledge_id: str):
knowledge = QuerySet(Knowledge).select_related('embedding_model').filter(id=knowledge_id).first()
if knowledge is None or knowledge.embedding_model_id is None or knowledge.embedding_model is None:
raise Exception(_('Knowledge base setting error, please reset the knowledge base'))
default_params = get_model_default_params(knowledge.embedding_model)
@ -138,6 +142,8 @@ def get_embedding_model_by_knowledge_id(knowledge_id: str):
def get_embedding_model_by_knowledge(knowledge):
if knowledge is None or knowledge.embedding_model_id is None or knowledge.embedding_model is None:
raise Exception(_('Knowledge base setting error, please reset the knowledge base'))
default_params = get_model_default_params(knowledge.embedding_model)
return ModelManage.get_model(str(knowledge.embedding_model_id),
@ -146,6 +152,8 @@ def get_embedding_model_by_knowledge(knowledge):
def get_embedding_model_id_by_knowledge_id(knowledge_id):
knowledge = QuerySet(Knowledge).select_related('embedding_model').filter(id=knowledge_id).first()
if knowledge is None or knowledge.embedding_model_id is None:
raise Exception(_('Knowledge base setting error, please reset the knowledge base'))
return str(knowledge.embedding_model_id)
@ -155,6 +163,8 @@ def get_embedding_model_id_by_knowledge_id_list(knowledge_id_list: List):
raise Exception(_('The knowledge base is inconsistent with the vector model'))
if len(knowledge_list) == 0:
raise Exception(_('Knowledge base setting error, please reset the knowledge base'))
if knowledge_list[0].embedding_model_id is None:
raise Exception(_('Knowledge base setting error, please reset the knowledge base'))
return str(knowledge_list[0].embedding_model_id)

View File

@ -1702,9 +1702,11 @@ class DocumentSerializers(serializers.Serializer):
source_file.save(file_content)
file.seek(0)
parsed_documents = DocumentSerializers.Split(
split_serializer = DocumentSerializers.Split(
data={'workspace_id': self.data.get('workspace_id'), 'knowledge_id': self.data.get('knowledge_id')}
).file_to_paragraph(file, None, None, 4096)
)
split_serializer.is_valid(instance={'file': [file]}, raise_exception=True)
parsed_documents = split_serializer.file_to_paragraph(file, None, None, 4096)
paragraphs = []
for parsed_document in parsed_documents:
paragraphs.extend(parsed_document.get('content', []))

View File

@ -47,6 +47,8 @@ def get_model(model, **kwargs):
@param model: model 数据库Model实例对象
@return: 模型实例
"""
if model is None:
raise Exception(_("Model does not exist"))
return get_model_(model.provider, model.model_type, model.model_name, model.credential, str(model.id), **kwargs)
@ -105,6 +107,8 @@ def is_valid_credential(provider, model_type, model_name, model_credential: Dict
def get_model_by_id(_id, workspace_id):
if _id is None:
raise Exception(_("Model does not exist"))
model = QuerySet(Model).filter(id=_id).first()
# 归还链接到连接池
connection.close()
@ -116,6 +120,9 @@ def get_model_by_id(_id, workspace_id):
return model
def get_model_default_params(model):
if model is None:
raise Exception(_("Model does not exist"))
def convert_to_int(value):
if isinstance(value, str):
try:

View File

@ -43,11 +43,48 @@ const showUserAvatar = computed(() => {
return props.application.show_user_avatar == undefined ? true : props.application.show_user_avatar
})
const applicationName = computed(() => {
return props.application?.name || props.application?.application_name || 'XXX'
})
const replaceApplicationName = (text: string) => {
return (text || '').replaceAll('XXX', applicationName.value)
}
const defaultQuestions = computed(() => {
const source = replaceApplicationName(props.application?.prologue || '')
const questionList = Array.from(source.matchAll(/-\s+(.+)/g))
.map((match) => match[1]?.trim())
.filter((item) => item)
if (questionList.length) {
return questionList.slice(0, 3)
}
return [
`${applicationName.value}主要功能有什么?`,
`${applicationName.value}如何收费?`,
'需要转人工服务',
]
})
const suggestedQuestions = computed(() => {
const questions = props.application?.suggested_questions
return Array.isArray(questions)
? questions.map((item) => `${item || ''}`.trim()).filter((item) => item).slice(0, 3)
: []
})
const toQuickQuestion = (match: string, offset: number, input: string) => {
return `<quick_question>${match.replace('- ', '')}</quick_question>`
}
const prologue = computed(() => {
const temp = props.available ? props.application?.prologue : t('chat.tip.prologueMessage')
const temp = props.available
? [
`您好,我是${applicationName.value},您可以向我提出以下或其它问题。`,
...(suggestedQuestions.value.length ? suggestedQuestions.value : defaultQuestions.value).map(
(item) => `<quick_question>${item}</quick_question>`,
),
].join('\n')
: t('chat.tip.prologueMessage')
if (temp) {
const tag_list = [
/<html_rander>[\d\D]*?<\/html_rander>/g,

View File

@ -5,15 +5,20 @@
<template v-for="(item, index) in md_view_list" :key="index">
<div
v-if="item.type === 'question'"
@click="
sendMessage && type !== 'log' ? sendMessage(item.content, 'new') : (content: string) => {}
"
@click="sendQuestion(item.content)"
class="problem-button mt-4 mb-4"
:class="sendMessage && type !== 'log' ? 'cursor' : 'disabled'"
>
<el-space :size="8" alignment="flex-start">
<AppIcon iconName="app-edit" class="color-primary" style="margin-top: 3px"></AppIcon>
{{ item.content }}
<AppIcon
iconName="app-edit"
class="color-primary quick-question-edit"
style="margin-top: 3px"
@click.stop="setInputQuestion(item.content)"
></AppIcon>
<span>
{{ item.content }}
</span>
</el-space>
</div>
<HtmlRander v-else-if="item.type === 'html_rander'" :source="item.content"></HtmlRander>
@ -49,6 +54,7 @@ import EchartsRander from './EchartsRander.vue'
import FormRander from './FormRander.vue'
import ReasoningRander from './ReasoningRander.vue'
import { nanoid } from 'nanoid'
import bus from '@/bus'
config({
markdownItConfig(md) {
md.renderer.rules.image = (tokens, idx, options, env, self) => {
@ -88,6 +94,16 @@ const props = withDefaults(
},
)
const editorRef = ref()
const sendQuestion = (question: string) => {
if (props.sendMessage && props.type !== 'log') {
props.sendMessage(question, 'new')
}
}
const setInputQuestion = (question: string) => {
if (props.type !== 'log') {
bus.emit('chat-input', question)
}
}
const md_view_list = computed(() => {
const temp_source = props.source
return split_form_rander(

View File

@ -128,6 +128,24 @@
@close="closeSingleSelectDialog"
width="500px"
>
<div v-if="pendingPermissionChange" class="permission-confirm-info mb-16">
<div>{{ resourceTypeLabel }}{{ targetResourceName }}</div>
<div>
{{ $t('views.userManage.userForm.nick_name.label') }}{{
pendingPermissionChange.row?.nick_name || '-'
}}
</div>
<div>
{{ $t('views.login.loginForm.username.label') }}{{
pendingPermissionChange.row?.username || '-'
}}
</div>
<div>
{{ $t('views.model.modelForm.permissionType.label') }}{{
getPermissionLabel(pendingPermissionChange.val)
}}
</div>
</div>
<el-radio-group v-if="isFolder" v-model="authAllChildren" class="radio-block">
<el-radio :value="false">
<p class="color-text-primary lighter">
@ -326,6 +344,8 @@ watch(drawerVisible, (bool) => {
searchType.value = 'nick_name'
searchForm.value = { nick_name: '', username: '', permission: undefined }
permissionData.value = []
targetResource.value = null
folderData.value = null
paginationConfig.current_page = 1
paginationConfig.total = 0
multipleSelection.value = []
@ -336,6 +356,7 @@ watch(drawerVisible, (bool) => {
const loading = ref(false)
const targetId = ref('')
const folderData = ref<any>(null)
const targetResource = ref<any>(null)
const permissionData = ref<any[]>([])
const searchType = ref('nick_name')
const searchForm = ref<any>({
@ -374,6 +395,32 @@ const singleSelectDialogVisible = ref(false)
const pendingPermissionChange = ref<{ val: any; row: any } | null>(null)
const radioPermission = ref('')
const authAllChildren = ref(false)
const resourceTypeLabelMap: Record<string, string> = {
APPLICATION: t('views.application.title'),
KNOWLEDGE: t('views.knowledge.title'),
TOOL: t('views.tool.title'),
MODEL: t('views.model.title'),
}
const resourceTypeLabel = computed(() => {
return props.isFolder
? '文件夹'
: resourceTypeLabelMap[props.type] || t('views.system.resourceAuthorization.title')
})
const targetResourceName = computed(() => {
return targetResource.value?.name || folderData.value?.name || targetId.value || '-'
})
const getPermissionLabel = (permission: string) => {
return (
getFolderPermissionOptions().find((item) => item.value === permission)?.label ||
permission ||
'-'
)
}
function openMulConfigureDialog() {
if (multipleSelection.value.length === 0) {
return
@ -458,9 +505,10 @@ const getPermissionList = () => {
})
}
const open = (id: string, folder_data?: any) => {
const open = (id: string, resource_data?: any) => {
targetId.value = id
folderData.value = folder_data
folderData.value = props.isFolder ? resource_data : null
targetResource.value = resource_data || null
drawerVisible.value = true
getPermissionList()
}

View File

@ -7,10 +7,10 @@
<h1 class="header-title">AI-RAG</h1>
</div>
<div class="header-right">
<router-link v-if="isSystemManagement" to="/application" class="back-link">
<button v-if="isSystemManagement" class="back-link" type="button" @click="goBackWorkspace">
<span class="back-icon">&larr;</span>
{{menuText.back}}
</router-link>
</button>
</div>
</div>
</div>
@ -18,10 +18,11 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import ChinaMobileIcon from '@/components/china-mobile-icon/index.vue'
const { locale } = useI18n({ useScope: 'global' })
const SYSTEM_ENTRY_FROM_KEY = 'system_entry_from'
const menuText = computed(() => {
if (locale.value === 'en-US') {
return {
@ -40,11 +41,21 @@ const menuText = computed(() => {
const route = useRoute()
const router = useRouter()
const isSystemManagement = computed(() => {
const path = route.path
return path.startsWith('/system') || path.startsWith('/admin/system')
return path.startsWith('/system') || path === '/operate' || path.startsWith('/admin/system')
})
const isValidReturnPath = (path?: string | null) => {
return Boolean(path && !path.startsWith('/system') && path !== '/operate')
}
const goBackWorkspace = () => {
const fromPath = sessionStorage.getItem(SYSTEM_ENTRY_FROM_KEY)
router.push(isValidReturnPath(fromPath) ? fromPath! : '/knowledge')
}
</script>
<style lang="scss" scoped>
@ -91,9 +102,10 @@ const isSystemManagement = computed(() => {
align-items: center;
padding: 6px 12px;
border-radius: 6px;
text-decoration: none;
color: #1890ff;
border: 1px solid #1890ff;
background: transparent;
cursor: pointer;
}
.back-icon {

View File

@ -13,6 +13,25 @@ import useStore from '@/stores'
import { routes } from '@/router/routes'
import { getFirstAvailableMenuPath, resolveMenuIdByPath } from '@/utils/menu-setting'
NProgress.configure({ showSpinner: false, speed: 500, minimum: 0.3 })
const SYSTEM_ENTRY_FROM_KEY = 'system_entry_from'
const isSystemRoutePath = (path: string) => {
return path.startsWith('/system') || path === '/operate'
}
const shouldRecordSystemEntryFrom = (
to: RouteLocationNormalized,
from: RouteLocationNormalized,
) => {
return (
isSystemRoutePath(to.path) &&
!isSystemRoutePath(from.path) &&
from.name &&
from.path !== '/' &&
from.path !== '/login'
)
}
const router = createRouter({
history: createWebHistory(window.MaxKB?.prefix ? window.MaxKB?.prefix : import.meta.env.BASE_URL),
routes: routes,
@ -22,6 +41,9 @@ const router = createRouter({
router.beforeEach(
async (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
NProgress.start()
if (shouldRecordSystemEntryFrom(to, from)) {
sessionStorage.setItem(SYSTEM_ENTRY_FROM_KEY, from.fullPath)
}
if (to.name === '404') {
next()
return

View File

@ -387,7 +387,7 @@ const CopyApplicationDialogRef = ref()
const ResourceAuthorizationDrawerRef = ref()
function openAuthorization(item: any) {
ResourceAuthorizationDrawerRef.value.open(item.id)
ResourceAuthorizationDrawerRef.value.open(item.id, item)
}
const MoveToDialogRef = ref()

View File

@ -448,7 +448,7 @@ const paginationConfig = reactive({
const ResourceAuthorizationDrawerRef = ref()
function openAuthorization(item: any) {
ResourceAuthorizationDrawerRef.value.open(item.id)
ResourceAuthorizationDrawerRef.value.open(item.id, item)
}
const MoveToDialogRef = ref()

View File

@ -181,7 +181,7 @@ const MoreFilledPermission = (id: any) => {
const ResourceAuthorizationDrawerRef = ref()
function openAuthorization(item: any) {
ResourceAuthorizationDrawerRef.value.open(item.id)
ResourceAuthorizationDrawerRef.value.open(item.id, item)
}
const downModel = ref<Model>()

View File

@ -354,7 +354,7 @@ const MoreFilledPermission = () => {
const ResourceAuthorizationDrawerRef = ref()
function openAuthorization(item: any) {
ResourceAuthorizationDrawerRef.value.open(item.id)
ResourceAuthorizationDrawerRef.value.open(item.id, item)
}
const apiInputParams = ref([])

View File

@ -349,7 +349,7 @@ const paginationConfig = reactive({
const ResourceAuthorizationDrawerRef = ref()
function openAuthorization(item: any) {
ResourceAuthorizationDrawerRef.value.open(item.id)
ResourceAuthorizationDrawerRef.value.open(item.id, item)
}
const exportKnowledge = (item: any) => {

View File

@ -299,7 +299,7 @@ const MoreFilledPermission = () => {
const ResourceAuthorizationDrawerRef = ref()
function openAuthorization(item: any) {
ResourceAuthorizationDrawerRef.value.open(item.id)
ResourceAuthorizationDrawerRef.value.open(item.id, item)
}
const deleteModel = (row: any) => {

View File

@ -404,7 +404,7 @@ const MoreFilledPermission = (row: any) => {
const ResourceAuthorizationDrawerRef = ref()
function openAuthorization(item: any) {
ResourceAuthorizationDrawerRef.value.open(item.id)
ResourceAuthorizationDrawerRef.value.open(item.id, item)
}
function exportTool(row: any) {

View File

@ -150,12 +150,15 @@ import { ComplexPermission } from '@/utils/permission/type'
import { getPermissionOptions } from '@/views/system/resource-authorization/constant'
import useStore from '@/stores'
import { TreeToFlatten } from '@/utils/array'
import { MsgConfirm } from '@/utils/message'
const { model, user } = useStore()
const route = useRoute()
const props = defineProps<{
data: any[]
type: string
resourceLabel?: string
currentMember?: any
getData?: () => void
}>()
const emit = defineEmits(['submitPermissions'])
@ -170,6 +173,7 @@ watch(
defaultExpandKeys.value = props.data?.length > 0 ? [props.data[0]?.id] : []
isComputedFirst.value = false
}
refreshPermissionSnapshot(newData || [])
},
{ immediate: true },
)
@ -329,6 +333,41 @@ const filteredData = computed(() => {
const multipleSelection = ref<any[]>([])
const selectObj: any = {}
const permissionSnapshot = ref<Record<string, string>>({})
const refreshPermissionSnapshot = (data: any[]) => {
const snapshot: Record<string, string> = {}
TreeToFlatten(data || []).forEach((item: any) => {
snapshot[item.id] = item.permission
})
permissionSnapshot.value = snapshot
}
const escapeHtml = (value: any) => {
return String(value ?? '-')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
}
const getPermissionLabel = (value: string, row: any) => {
return getRowPermissionOptions(row).find((item) => item.value === value)?.label || value
}
const getConfirmDescription = (value: string, row: any) => {
const resourceLabel = props.resourceLabel || '资源'
const memberName = props.currentMember?.nick_name || props.currentMember?.username || '-'
return `
<div style="line-height: 1.8;">
<div>${escapeHtml(resourceLabel)}${escapeHtml(row?.name)}</div>
<div>姓名${escapeHtml(memberName)}</div>
<div>操作权限${escapeHtml(getPermissionLabel(value, row))}</div>
</div>
`
}
const selectAll = (selection: any[]) => {
multipleSelection.value = selection
}
@ -384,7 +423,17 @@ function closeDialog() {
multipleTableRef.value?.clearSelection()
}
function submitPermissions(value: string, row: any) {
async function submitPermissions(value: string, row: any) {
const previousPermission = permissionSnapshot.value[row.id] || 'NOT_AUTH'
try {
await MsgConfirm('确认修改资源权限', getConfirmDescription(value, row), {
dangerouslyUseHTMLString: true,
type: 'warning',
})
} catch {
row.permission = previousPermission
return
}
const obj = [
{
target_id: row.id,

View File

@ -65,6 +65,8 @@
<PermissionTable
:data="treeData"
:type="activeData.type"
:resource-label="activeData.label"
:current-member="currentMember"
ref="PermissionTableRef"
:getData="getPermissionList"
@submitPermissions="submitPermissions"
@ -213,6 +215,10 @@ const treeData = computed(() => {
return toTree(permissionData.value, 'folder_id')
})
const currentMember = computed(() => {
return memberList.value.find((item: any) => item.id === currentUser.value)
})
function clickMemberHandle(item: any) {
currentUser.value = item.id
currentType.value = item.type

View File

@ -326,7 +326,7 @@ const MoreFieldPermission = (id: any) => {
const ResourceAuthorizationDrawerRef = ref()
function openAuthorization(item: any) {
ResourceAuthorizationDrawerRef.value.open(item.id)
ResourceAuthorizationDrawerRef.value.open(item.id, item)
}
const InitParamDrawerRef = ref()