Commit 748ae56c authored by nick zheng's avatar nick zheng

feat: 应用添加知识库检索方式及调试页面展示知识库详情

parent eaf56b16
...@@ -22,6 +22,7 @@ export default [ ...@@ -22,6 +22,7 @@ export default [
Recordable: 'readonly', Recordable: 'readonly',
ViteEnv: 'readonly', ViteEnv: 'readonly',
AnyObject: 'readonly', AnyObject: 'readonly',
KnowledgeContentResultItem: 'readonly',
ConversationMessageItem: 'readonly', ConversationMessageItem: 'readonly',
ConversationMessageItemInfo: 'readonly', ConversationMessageItemInfo: 'readonly',
MittEvents: 'readonly', MittEvents: 'readonly',
......
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
name="viewport" name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/> />
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_4711453_kteyuqickp.css" /> <link rel="stylesheet" href="//at.alicdn.com/t/c/font_4711453_m4bkcrjzo7h.css" />
<link rel="preload" href="https://gsst-poe-sit.gz.bcebos.com/front/SourceHanSansCN-Medium.otf" as="font" crossorigin="anonymous" />
<title>Model Link</title> <title>Model Link</title>
<script src="/tinymce/tinymce.min.js" type="module" referrerpolicy="origin"></script> <script src="/tinymce/tinymce.min.js" type="module" referrerpolicy="origin"></script>
</head> </head>
......
...@@ -92,7 +92,7 @@ function handleConfirm() { ...@@ -92,7 +92,7 @@ function handleConfirm() {
> >
<template #header> <template #header>
<slot v-if="slots.header" name="header" /> <slot v-if="slots.header" name="header" />
<div v-else class="text-[18px] leading-none">{{ title }}</div> <div v-else class="font-family-medium text-[18px] leading-none">{{ title }}</div>
</template> </template>
<div> <div>
......
...@@ -9,6 +9,7 @@ interface ResponseData { ...@@ -9,6 +9,7 @@ interface ResponseData {
message: string message: string
reasoningContent: string reasoningContent: string
function: { name: string } function: { name: string }
knowledgeContentResult: KnowledgeContentResultItem[]
} }
const { t } = i18n.global const { t } = i18n.global
......
...@@ -98,9 +98,9 @@ common_module: ...@@ -98,9 +98,9 @@ common_module:
voice: 'Voice' voice: 'Voice'
sound: 'Sound' sound: 'Sound'
voice_auto_play: 'Voice auto play' voice_auto_play: 'Voice auto play'
start_playing: 'play' start_playing: 'Play'
stop_playing: 'stop' stop_playing: 'Stop'
unplayable: 'unplayable' unplayable: 'Unplayable'
unplayable_tip: 'The voice setting do not match the model output language' unplayable_tip: 'The voice setting do not match the model output language'
response_error: 'Response error' response_error: 'Response error'
agent_exception: 'Agent exception, please try again later!' agent_exception: 'Agent exception, please try again later!'
...@@ -145,6 +145,8 @@ common_module: ...@@ -145,6 +145,8 @@ common_module:
download: 'Download' download: 'Download'
download_success: 'Download Successfully' download_success: 'Download Successfully'
download_fail: 'Download failed' download_fail: 'Download failed'
unfold: 'Unfold'
fold: 'Fold'
dialogue_module: dialogue_module:
continue_question_message: 'You can keep asking questions' continue_question_message: 'You can keep asking questions'
...@@ -316,6 +318,29 @@ personal_space_module: ...@@ -316,6 +318,29 @@ personal_space_module:
knowledge: 'Knowledge' knowledge: 'Knowledge'
knowledge_base: 'Knowledge base' knowledge_base: 'Knowledge base'
knowledge_base_desc: 'Reference text data, tabular knowledge data (including FAQ questions, multi-column index questions) and web data to achieve knowledge base questions and answers. The application can be associated with a maximum of 5 knowledge bases. Please fill in the detailed description of the knowledge base to improve the accuracy of questions and answers' knowledge_base_desc: 'Reference text data, tabular knowledge data (including FAQ questions, multi-column index questions) and web data to achieve knowledge base questions and answers. The application can be associated with a maximum of 5 knowledge bases. Please fill in the detailed description of the knowledge base to improve the accuracy of questions and answers'
advanced_setting: 'Advanced setting'
knowledge_base_setting: 'Knowledge base setting'
reply: 'Reply'
reply_to_superclass_question: 'Reply to superclass question'
default_reply: 'Default reply'
custom_reply: 'Custom reply'
please_enter_custom_content: 'Please enter custom content'
knowledge_base_retrieval_strategy: 'Knowledge base retrieval strategy'
mix: 'Mix'
mix_desc: 'Keyword retrieval strategy and semantic retrieval strategy are used to retrieve knowledge base and return slices with high relevance. It is recommended to be used in the scenario where both keywords and semantic understanding are required.'
keyword: 'Keyword'
keyword_desc: 'The key word retrieval strategy is used to retrieve the knowledge base and return the slices with high correlation to the keywords in the question. It is recommended for scenarios that require an exact match with the keyword in the question.'
semantics: 'Semantics'
semantics_desc: 'The semantic retrieval strategy is used to retrieve the knowledge base and return the slice content that matches the meaning of the question. It is recommended for use in scenarios where the semantic relevance of questions and the relevance of intent recognition need to be matched.'
retrieval_strategy_setting: 'Retrieval strategy setting'
retrieval_strategy_setting_desc: 'Set the minimum similarity and the number of results returned'
minimum_similarity: 'Minimum similarity'
minimum_similarity_desc: 'In the knowledge base retrieval process, the similarity between the input question and the knowledge base slice is calculated, and the fragment that is higher or equal to the similar fraction is returned. The similarity subthreshold needs to be adjusted to obtain the most relevant filtered fragments. Too low may cause the returned fragments to be irrelevant to the problem. The optimal threshold can be adjusted after several tests.'
number_of_results_returned: 'Number of results returned'
number_of_results_returned_desc: 'The slice content matching the question is returned from the knowledge base. The larger the value, the more slices are returned. The total number of characters returned to the slice should not exceed the maximum number of context characters for the selected model.'
back_chunk_content: 'Back chunk content'
from_knowledge_base: 'From knowledge base'
score: 'Score'
upload_file: 'Upload file' upload_file: 'Upload file'
upload_file_desc: 'Enable the user to upload files for chat, support TXT, MD, PDF, DOC, DOCX format files' upload_file_desc: 'Enable the user to upload files for chat, support TXT, MD, PDF, DOC, DOCX format files'
dialogue: 'Dialogue' dialogue: 'Dialogue'
......
...@@ -144,6 +144,8 @@ common_module: ...@@ -144,6 +144,8 @@ common_module:
download: '下载' download: '下载'
download_success: '下载成功' download_success: '下载成功'
download_fail: '下载失败' download_fail: '下载失败'
unfold : '展开'
fold: '收起'
dialogue_module: dialogue_module:
continue_question_message: '你可以继续提问' continue_question_message: '你可以继续提问'
...@@ -314,6 +316,29 @@ personal_space_module: ...@@ -314,6 +316,29 @@ personal_space_module:
knowledge: '知识' knowledge: '知识'
knowledge_base: '知识库' knowledge_base: '知识库'
knowledge_base_desc: '引用文本数据、表格型知识数据(含FAQ问答,多列索引问答)以及网页数据,实现知识库问答,应用最多可关联5个知识库,请详细填写知识库描述信息以提高问答准确率' knowledge_base_desc: '引用文本数据、表格型知识数据(含FAQ问答,多列索引问答)以及网页数据,实现知识库问答,应用最多可关联5个知识库,请详细填写知识库描述信息以提高问答准确率'
advanced_setting: '高级设置'
knowledge_base_setting: '知识库设置'
reply: '回复'
reply_to_superclass_question: '超纲问题回复'
default_reply: '默认回复'
custom_reply: '自定义回复'
please_enter_custom_content: '请输入自定义内容'
knowledge_base_retrieval_strategy: '知识库检索策略'
mix: '混合检索'
mix_desc: '使用关键词检索策略和语义检索策略检索知识库,并返回相关性高的切片内容。推荐在需要兼顾提问关键词和提问语义理解的场景下使用。'
keyword: '关键词检索'
keyword_desc: '使用关键词检索策略检索知识库,返回与提问中关键字相关度高的切片内容。推荐在需要与提问里关键字精准匹配的场景中使用。'
semantics: '语义检索'
semantics_desc: '使用语义检索策略检索知识库,返回与提问含义相匹配的切片内容。推荐在需要匹配提问语义相关性和意图识别相关性的场景中使用。'
retrieval_strategy_setting: '检索策略设置'
retrieval_strategy_setting_desc: '配置最低相似度与匹配结果返回数量'
minimum_similarity: '最低相似度'
minimum_similarity_desc: '在知识库检索过程中,用于计算输入的提问与知识库切片的相似度,返回高于或等于相似分的片段。此相似分阈值需要调整后得到经过滤的最相关片段,过低可能会导致返回片段与问题不相关,可经过多次测试后调整最佳阈值。'
number_of_results_returned: '匹配结果返回数量'
number_of_results_returned_desc: '从知识库中返回与提问相匹配的切片内容,数值越大返回的切片越多。返回切片的总字符不应超过所选模型的最大上下文字符数。'
back_chunk_content: '返回切片内容'
from_knowledge_base: '所属知识库'
score: '匹配分'
upload_file: '上传文件' upload_file: '上传文件'
upload_file_desc: '开启后支持用户上传文件进行对话聊天, 支持TXT、MD、PDF、DOC、DOCX格式的文件' upload_file_desc: '开启后支持用户上传文件进行对话聊天, 支持TXT、MD、PDF、DOC、DOCX格式的文件'
dialogue: '对话' dialogue: '对话'
......
...@@ -144,6 +144,8 @@ common_module: ...@@ -144,6 +144,8 @@ common_module:
download: '下載' download: '下載'
download_success: '下載成功' download_success: '下載成功'
download_fail: '下載失敗' download_fail: '下載失敗'
unfold : '展開'
fold: '收起'
dialogue_module: dialogue_module:
continue_question_message: '你可以繼續提問' continue_question_message: '你可以繼續提問'
...@@ -314,6 +316,29 @@ personal_space_module: ...@@ -314,6 +316,29 @@ personal_space_module:
knowledge: '知識' knowledge: '知識'
knowledge_base: '知識庫' knowledge_base: '知識庫'
knowledge_base_desc: '引用文本數據、表格型知識數據(含FAQ問答,多列索引問答)以及網頁數據,實現知識庫問答,應用最多可關聯5個知識庫,請詳細填寫知識庫描述信息以提高問答準確率' knowledge_base_desc: '引用文本數據、表格型知識數據(含FAQ問答,多列索引問答)以及網頁數據,實現知識庫問答,應用最多可關聯5個知識庫,請詳細填寫知識庫描述信息以提高問答準確率'
advanced_setting: '高級設置'
knowledge_base_setting: '知識庫設置'
reply: '回覆'
reply_to_superclass_question: '超綱問題回覆'
default_reply: '默認回覆'
custom_reply: '自定義回覆'
please_enter_custom_content: '請輸入自定義內容'
knowledge_base_retrieval_strategy: '知識庫檢索策略'
mix: '混合檢索'
mix_desc: '使用關鍵詞檢索策略和語義檢索策略檢索知識庫,並返回相關性高的切片內容。推薦在需要兼顧提問關鍵詞和提問語義理解的場景下使用。'
keyword: '關鍵詞檢索'
keyword_desc: '使用關鍵詞檢索策略檢索知識庫,返回與提問中關鍵字相關度高的切片內容。推薦在需要與提問裏關鍵字精準匹配的場景中使用。'
semantics: '語義檢索'
semantics_desc: '使用語義檢索策略檢索知識庫,返回與提問含義相匹配的切片內容。推薦在需要匹配提問語義相關性和意圖識別相關性的場景中使用。'
retrieval_strategy_setting: '檢索策略設置'
retrieval_strategy_setting_desc: '配置最低相似度與匹配結果返回數量'
minimum_similarity: '最低相似度'
minimum_similarity_desc: '在知識庫檢索過程中,用於計算輸入的提問與知識庫切片的相似度,返回高於或等於相似分的片段。此相似分閾值需要調整後得到經過濾的最相關片段,過低可能會導致返回片段與問題不相關,可經過多次測試後調整最佳閾值。'
number_of_results_returned: '匹配結果返回數量'
number_of_results_returned_desc: '從知識庫中返回與提問相匹配的切片內容,數值越大返回的切片越多。返回切片的總字符不應超過所選模型的最大上下文字符數。'
back_chunk_content: '返回切片內容'
from_knowledge_base: '所屬知識庫'
score: '匹配分'
upload_file: '上傳文件' upload_file: '上傳文件'
upload_file_desc: '開啓後支持用户上傳文件進行對話聊天, 支持TXT、MD、PDF、DOC、DOCX格式的文件' upload_file_desc: '開啓後支持用户上傳文件進行對話聊天, 支持TXT、MD、PDF、DOC、DOCX格式的文件'
dialogue: '對話' dialogue: '對話'
......
...@@ -28,6 +28,11 @@ export function defaultPersonalAppConfigState(): PersonalAppConfigState { ...@@ -28,6 +28,11 @@ export function defaultPersonalAppConfigState(): PersonalAppConfigState {
knowledgeConfig: { knowledgeConfig: {
knowledgeIds: [], knowledgeIds: [],
isDocumentParsing: 'N', isDocumentParsing: 'N',
knowledgeResponseType: 'default-ai',
knowledgeCustomResponse: [],
knowledgeSearchType: 'MIX',
knowledgeSimilarity: 0.4,
knowledgeNResult: 3,
}, },
commModelConfig: { commModelConfig: {
largeModel: '文心4.0 (8K)', largeModel: '文心4.0 (8K)',
......
...@@ -29,6 +29,11 @@ export interface PersonalAppConfigState { ...@@ -29,6 +29,11 @@ export interface PersonalAppConfigState {
knowledgeConfig: { knowledgeConfig: {
knowledgeIds: number[] //知识库ID knowledgeIds: number[] //知识库ID
isDocumentParsing: 'Y' | 'N' //是否开启文档解析 Y-开启 N-关闭 isDocumentParsing: 'Y' | 'N' //是否开启文档解析 Y-开启 N-关闭
knowledgeResponseType: 'default-ai' | 'custom' //超纲问题回复 default-ai-默认回复 custom-自定义回复
knowledgeCustomResponse: string[] //knowledgeCustomResponse
knowledgeSearchType: 'MIX' | 'KEY_WORD' | 'SEMANTICS' //知识库检索策略
knowledgeSimilarity: number //最低相似度 0.01-0.99
knowledgeNResult: number //知识库返回结果数 1-10
} }
commModelConfig: { commModelConfig: {
largeModel: string //大模型 largeModel: string //大模型
......
...@@ -35,3 +35,11 @@ body { ...@@ -35,3 +35,11 @@ body {
font-weight: normal; font-weight: normal;
src: url('https://gsst-poe-sit.gz.bcebos.com/front/SourceHanSansCN-Regular.otf'); src: url('https://gsst-poe-sit.gz.bcebos.com/front/SourceHanSansCN-Regular.otf');
} }
@font-face {
font-family: 'SourceHanSansCN-Medium';
font-style: normal;
font-weight: normal;
src: url('https://gsst-poe-sit.gz.bcebos.com/front/SourceHanSansCN-Medium.otf');
font-display: swap;
}
...@@ -152,12 +152,12 @@ const handleContentEdit = throttle( ...@@ -152,12 +152,12 @@ const handleContentEdit = throttle(
class="py-[10px] pr-[14px] text-end" class="py-[10px] pr-[14px] text-end"
> >
<i <i
class="iconfont icon-edit-document hover:text-theme-color mr-[14px] transform cursor-pointer text-[12px]" class="iconfont icon-copy1 hover:text-theme-color mr-[14px] transform cursor-pointer text-[14px]"
@click="handleContentEdit" @click="handleContentCopy"
></i> ></i>
<i <i
class="iconfont icon-copy1 hover:text-theme-color transform cursor-pointer text-[14px]" class="iconfont icon-edit-document hover:text-theme-color transform cursor-pointer text-[14px]"
@click="handleContentCopy" @click="handleContentEdit"
></i> ></i>
</div> </div>
......
...@@ -96,6 +96,7 @@ function messageItemFactory() { ...@@ -96,6 +96,7 @@ function messageItemFactory() {
pluginName: '', pluginName: '',
imageUrl: '', imageUrl: '',
reasoningContent: '', reasoningContent: '',
knowledgeContentResult: [],
} as MessageItemInterface } as MessageItemInterface
} }
...@@ -191,6 +192,18 @@ function handleQuestionSubmit() { ...@@ -191,6 +192,18 @@ function handleQuestionSubmit() {
return return
} }
// 知识库
if (data.knowledgeContentResult) {
emit(
'updateMessageItem',
answerMessageId,
{ knowledgeContentResult: data.knowledgeContentResult },
modelIndex,
)
emit('messageListScrollToBottom')
return
}
// 回复内容 // 回复内容
if (data.message) { if (data.message) {
messageContent += data.message messageContent += data.message
......
<script setup lang="ts">
import { computed, ref } from 'vue'
import { SlidingVertical } from '@icon-park/vue-next'
import { throttle } from 'lodash-es'
import { useI18n } from 'vue-i18n'
import HitKnowledgeItem from '@/views/personal-space/personal-app-setting/components/agent-config/agent-preview/components/hit-knowledge-item.vue'
interface Props {
drawerToRef: string
maxHeight: number
knowledgeContentResult: KnowledgeContentResultItem[]
}
const props = defineProps<Props>()
const { t } = useI18n()
const isShowDrawer = defineModel<boolean>('isShow', { required: true })
const drawerHeight = ref(300)
const isResizing = ref(false) // 是否允许拖拽
const startY = ref(0) // 鼠标按下时的Y坐标
const startHeight = ref(0) // 初始高度
const MIN_HEIGHT = 80 // 最低高度
const maxHeight = computed(() => {
return props.maxHeight - 80 || 80
})
function handleStartResize(event: MouseEvent) {
isResizing.value = true
startY.value = event.clientY
startHeight.value = drawerHeight.value
window.addEventListener('mousemove', onMouseMove)
window.addEventListener('mouseup', stopResize)
}
const onMouseMove = throttle((event) => {
if (isResizing.value) {
const deltaY = event.clientY - startY.value
let newHeight = startHeight.value - deltaY
if (newHeight < MIN_HEIGHT) {
newHeight = MIN_HEIGHT
} else if (newHeight > maxHeight.value) {
newHeight = maxHeight.value
}
drawerHeight.value = newHeight
}
}, 16)
const stopResize = () => {
isResizing.value = false
window.removeEventListener('mousemove', onMouseMove)
window.removeEventListener('mouseup', stopResize)
}
</script>
<template>
<n-drawer
v-model:show="isShowDrawer"
placement="bottom"
:height="drawerHeight"
:show-mask="'transparent'"
:trap-focus="false"
:block-scroll="false"
class="shadow-[0_0_20px_rgba(0,4,65,0.07)]! mx-[25px]! rounded-[10px]! relative"
:to="drawerToRef"
>
<SlidingVertical
theme="outline"
size="18"
fill="#333"
:stroke-width="3"
class="absolute left-1/2 top-0 -translate-x-1/2 -translate-y-1/2 cursor-ns-resize"
@mousedown="handleStartResize"
/>
<n-drawer-content
:title="t('personal_space_module.agent_module.agent_setting_module.agent_config_module.back_chunk_content')"
header-class="border-b-0! p-5! pt-6! text-[16px]! font-family-medium!"
body-content-class="px-5! py-0!"
footer-class="border-t-0! py-2!"
:native-scrollbar="false"
closable
>
<div class="flex flex-col gap-[18px]">
<HitKnowledgeItem
v-for="(knowledgeContentResultItem, index) in knowledgeContentResult"
:key="index"
:content-result-item="knowledgeContentResultItem"
/>
</div>
<template #footer> </template>
</n-drawer-content>
</n-drawer>
</template>
...@@ -12,6 +12,10 @@ interface Props { ...@@ -12,6 +12,10 @@ interface Props {
const props = defineProps<Props>() const props = defineProps<Props>()
const emit = defineEmits<{
showKnowledgeResult: []
}>()
const { t } = useI18n() const { t } = useI18n()
const isShowReasoningContent = ref(true) const isShowReasoningContent = ref(true)
...@@ -60,24 +64,18 @@ function handleShowReasoningContentSwitch() { ...@@ -60,24 +64,18 @@ function handleShowReasoningContentSwitch() {
</div> </div>
<n-collapse-transition :show="isShowReasoningContent"> <n-collapse-transition :show="isShowReasoningContent">
<div class="my-[14px] border-l-[1px] border-solid border-l-[#ccc] p-[13px]"> <div
<div> v-show="messageItem.reasoningContent"
<img class="my-[14px] border-l-[1px] border-solid border-l-[#ccc] p-[13px]"
v-if="!messageItem.reasoningContent && !messageItem.content" >
src="@/assets/images/home/bubble-loading.gif" <MarkdownRender
alt="bubble-loading" :raw-text-content="
/> messageItem.reasoningContent
<template v-else> ? messageItem.reasoningContent
<MarkdownRender : t('common_module.dialogue_module.empty_message_content')
:raw-text-content=" "
messageItem.reasoningContent color="#999"
? messageItem.reasoningContent />
: t('common_module.dialogue_module.empty_message_content')
"
color="#999"
/>
</template>
</div>
</div> </div>
</n-collapse-transition> </n-collapse-transition>
</template> </template>
...@@ -85,44 +83,9 @@ function handleShowReasoningContentSwitch() { ...@@ -85,44 +83,9 @@ function handleShowReasoningContentSwitch() {
{{ props.messageItem.nickName }} {{ props.messageItem.nickName }}
</div> </div>
<div
class="min-h-[21px] min-w-[10px] max-w-full flex-wrap rounded-[10px] border border-[#9EA3FF] px-[15px] py-[12px] text-justify"
:class="{
'bg-[#777EF9]': isAssistant,
'text-[#fff]': isAssistant,
'!min-w-[80px]': messageItem.isTextContentLoading,
}"
>
<img
v-show="!isAssistant && messageItem.imageUrl"
:src="messageItem.imageUrl"
class="max-h-[120px]! mb-[12px] rounded-[10px] object-contain"
/>
<div v-if="messageItem.isTextContentLoading" class="flex h-[21px] w-[30px] items-center justify-center">
<MessageBubbleLoading :active-color="isAssistant ? '#fff' : '#192338'" />
</div>
<div v-else>
<MarkdownRender
:raw-text-content="
messageItem.content ? messageItem.content : t('common_module.dialogue_module.empty_message_content')
"
:color="isAssistant ? '#fff' : '#192338'"
/>
<div
v-show="isAssistant && messageItem.isAnswerResponseLoading"
class="mt-2.5 flex h-[21px] w-[30px] items-center justify-center"
>
<MessageBubbleLoading :active-color="isAssistant ? '#fff' : '#192338'" />
</div>
</div>
</div>
<div <div
v-show="isAssistant && messageItem.pluginName" v-show="isAssistant && messageItem.pluginName"
class="mt-[7px] flex items-center gap-[5px] font-['Microsoft_YaHei_UI'] text-[#999]" class="mb-[7px] flex items-center gap-[5px] font-['Microsoft_YaHei_UI'] text-[#999]"
> >
<div <div
v-show="messageItem.isTextContentLoading" v-show="messageItem.isTextContentLoading"
...@@ -138,6 +101,54 @@ function handleShowReasoningContentSwitch() { ...@@ -138,6 +101,54 @@ function handleShowReasoningContentSwitch() {
}} }}
</span> </span>
</div> </div>
<div class="flex min-w-[40px] max-w-full flex-col items-start overflow-hidden">
<div
class="min-h-[21px] w-full flex-wrap rounded-[10px] border border-[#9EA3FF] px-[15px] py-[12px] text-justify"
:class="{
'bg-[#777EF9]': isAssistant,
'text-[#fff]': isAssistant,
'!min-w-[80px]': messageItem.isTextContentLoading,
}"
>
<img
v-show="!isAssistant && messageItem.imageUrl"
:src="messageItem.imageUrl"
class="max-h-[120px]! mb-[12px] rounded-[10px] object-contain"
/>
<div v-if="messageItem.isTextContentLoading" class="flex h-[21px] w-full items-center justify-center">
<MessageBubbleLoading :active-color="isAssistant ? '#fff' : '#192338'" />
</div>
<div v-else>
<MarkdownRender
:raw-text-content="
messageItem.content ? messageItem.content : t('common_module.dialogue_module.empty_message_content')
"
:color="isAssistant ? '#fff' : '#192338'"
/>
<div
v-show="isAssistant && messageItem.isAnswerResponseLoading"
class="mt-2.5 flex h-[21px] w-[30px] items-center justify-center"
>
<MessageBubbleLoading :active-color="isAssistant ? '#fff' : '#192338'" />
</div>
</div>
</div>
<div class="flex justify-between py-[2px]">
<div
v-show="isAssistant && messageItem.knowledgeContentResult.length"
class="flex-center rounded-theme h-8 cursor-pointer gap-[5px] px-[14px] font-['Microsoft_YaHei_UI'] text-[#0B7DFF] hover:opacity-80"
@click="emit('showKnowledgeResult')"
>
<i class="iconfont icon-knowledge" />
<span>{{ t('common_module.knowledge') }}</span>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -10,6 +10,10 @@ interface Props { ...@@ -10,6 +10,10 @@ interface Props {
defineProps<Props>() defineProps<Props>()
const emit = defineEmits<{
showKnowledgeResult: [knowledgeContentResult: KnowledgeContentResultItem[]]
}>()
defineExpose({ defineExpose({
scrollToBottom, scrollToBottom,
}) })
...@@ -30,7 +34,12 @@ function scrollToBottom() { ...@@ -30,7 +34,12 @@ function scrollToBottom() {
<n-scrollbar ref="scrollbarRef" class="min-h-full" content-class="min-h-full flex"> <n-scrollbar ref="scrollbarRef" class="min-h-full" content-class="min-h-full flex">
<div class="flex flex-col-reverse overflow-hidden"> <div class="flex flex-col-reverse overflow-hidden">
<div class="pr-[10px]"> <div class="pr-[10px]">
<MessageItem v-for="[key, messageItem] in messageList" :key="key" :message-item="messageItem" /> <MessageItem
v-for="[key, messageItem] in messageList"
:key="key"
:message-item="messageItem"
@show-knowledge-result="emit('showKnowledgeResult', messageItem.knowledgeContentResult)"
/>
</div> </div>
</div> </div>
</n-scrollbar> </n-scrollbar>
......
<script setup lang="ts"> <script setup lang="ts">
import { computed, h, nextTick, ref, useTemplateRef } from 'vue' import { computed, h, nextTick, ref, useTemplateRef, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { SelectOption } from 'naive-ui' import { SelectOption } from 'naive-ui'
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { useElementSize } from '@vueuse/core'
import { useSystemLanguageStore } from '@/store/modules/system-language' import { useSystemLanguageStore } from '@/store/modules/system-language'
import { MultiModelDialogueItem } from '../types' import { MultiModelDialogueItem } from '../types'
import ModelSetting from './model-setting.vue' import ModelSetting from './model-setting.vue'
import MessageList from './message-list.vue' import MessageList from './message-list.vue'
import HitKnowledgeContent from './hit-knowledge-content.vue'
interface Props { interface Props {
modelDialogueItem: MultiModelDialogueItem modelDialogueItem: MultiModelDialogueItem
...@@ -32,6 +34,10 @@ const modelConfig = useVModel(props, 'modelDialogueItem', emit) ...@@ -32,6 +34,10 @@ const modelConfig = useVModel(props, 'modelDialogueItem', emit)
const messageListRef = useTemplateRef<InstanceType<typeof MessageList>>('messageListRef') const messageListRef = useTemplateRef<InstanceType<typeof MessageList>>('messageListRef')
const modelDialogueRef = ref<HTMLElement | null>(null)
const { height } = useElementSize(modelDialogueRef)
defineExpose({ defineExpose({
scrollToBottom, scrollToBottom,
}) })
...@@ -67,6 +73,13 @@ const isEnLanguage = computed(() => { ...@@ -67,6 +73,13 @@ const isEnLanguage = computed(() => {
return systemLanguageStore.currentLanguageInfo.key === 'en' return systemLanguageStore.currentLanguageInfo.key === 'en'
}) })
watch(
() => props.modelDialogueItem.messageList.size,
(newVal) => {
newVal && (isShowKnowledgeContentResult.value = false)
},
)
// 选择更多操作 // 选择更多操作
function handleSelectMoreOption(key: string) { function handleSelectMoreOption(key: string) {
switch (key) { switch (key) {
...@@ -92,12 +105,21 @@ function scrollToBottom() { ...@@ -92,12 +105,21 @@ function scrollToBottom() {
messageListRef.value?.scrollToBottom() messageListRef.value?.scrollToBottom()
}) })
} }
const isShowKnowledgeContentResult = ref(false)
const currentKnowledgeContentResult = ref<KnowledgeContentResultItem[]>([])
function handleShowKnowledgeResult(knowledgeContentResult: KnowledgeContentResultItem[]) {
isShowKnowledgeContentResult.value = true
currentKnowledgeContentResult.value = knowledgeContentResult
}
</script> </script>
<template> <template>
<div <div
:id="modelDialogueItem.id" :id="modelDialogueItem.id"
class="border-inactive-border-color flex flex-col overflow-hidden border-r px-6 last-of-type:border-none" ref="modelDialogueRef"
class="border-inactive-border-color relative flex flex-col overflow-hidden border-r px-6 last-of-type:border-none"
> >
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-5"> <div class="flex items-center gap-5">
...@@ -173,7 +195,19 @@ function scrollToBottom() { ...@@ -173,7 +195,19 @@ function scrollToBottom() {
<span class="text-font-color select-none">{{ t('multi_model_dialogue_module.please_select_model') }}</span> <span class="text-font-color select-none">{{ t('multi_model_dialogue_module.please_select_model') }}</span>
</div> </div>
<MessageList v-else ref="messageListRef" :message-list="modelDialogueItem.messageList" /> <MessageList
v-else
ref="messageListRef"
:message-list="modelDialogueItem.messageList"
@show-knowledge-result="handleShowKnowledgeResult"
/>
<HitKnowledgeContent
v-model:is-show="isShowKnowledgeContentResult"
:drawer-to-ref="`#${modelDialogueItem.id}`"
:max-height="height"
:knowledge-content-result="currentKnowledgeContentResult"
/>
</div> </div>
</template> </template>
......
...@@ -36,6 +36,7 @@ export interface MessageItemInterface { ...@@ -36,6 +36,7 @@ export interface MessageItemInterface {
pluginName?: string pluginName?: string
imageUrl?: string imageUrl?: string
reasoningContent: string reasoningContent: string
knowledgeContentResult: KnowledgeContentResultItem[]
} }
export interface LargeModelItem { export interface LargeModelItem {
......
...@@ -9,6 +9,7 @@ interface ResponseData { ...@@ -9,6 +9,7 @@ interface ResponseData {
message: string message: string
reasoningContent: string reasoningContent: string
function: { name: string } function: { name: string }
knowledgeContentResult: KnowledgeContentResultItem[]
} }
const { t } = i18n.global const { t } = i18n.global
......
...@@ -4,7 +4,7 @@ import AgentPreview from './agent-preview/agent-preview.vue' ...@@ -4,7 +4,7 @@ import AgentPreview from './agent-preview/agent-preview.vue'
</script> </script>
<template> <template>
<div class="flex h-full w-full flex-1"> <div id="agent-config-target" class="relative flex h-full w-full flex-1 overflow-hidden">
<AgentSetting /> <AgentSetting />
<AgentPreview /> <AgentPreview />
......
...@@ -149,6 +149,8 @@ function messageItemFactory(): ConversationMessageItem { ...@@ -149,6 +149,8 @@ function messageItemFactory(): ConversationMessageItem {
pluginName: '', pluginName: '',
imageUrl: '', imageUrl: '',
reasoningContent: '', reasoningContent: '',
modelName: '',
knowledgeContentResult: [],
} }
} }
...@@ -233,6 +235,7 @@ function handleMessageSend() { ...@@ -233,6 +235,7 @@ function handleMessageSend() {
isAnswerResponseLoading: true, isAnswerResponseLoading: true,
isVoiceLoading: true, isVoiceLoading: true,
isVoiceEnabled, isVoiceEnabled,
modelName: personalAppConfigStore.commModelConfig.largeModel || '',
}) })
emit('updatePageScroll') emit('updatePageScroll')
...@@ -256,18 +259,6 @@ function handleMessageSend() { ...@@ -256,18 +259,6 @@ function handleMessageSend() {
reasoningContent += data.reasoningContent reasoningContent += data.reasoningContent
emit('updateSpecifyMessageItem', latestAssistantMessageKey, { reasoningContent: reasoningContent }) emit('updateSpecifyMessageItem', latestAssistantMessageKey, { reasoningContent: reasoningContent })
emit('updatePageScroll') emit('updatePageScroll')
assistantFullAnswerContent.value = (assistantFullAnswerContent.value + data.reasoningContent).replace(
/\^\[[\d\\[\]-]+?\]\^/g,
'',
)
if (!sentenceExtractCheckEnabled.value && isVoiceEnabled) {
sentenceExtract(latestAssistantMessageKey)
sentenceExtractCheckEnabled.value = true
messageAudioLoading.value = true
}
return return
} }
...@@ -278,6 +269,13 @@ function handleMessageSend() { ...@@ -278,6 +269,13 @@ function handleMessageSend() {
return return
} }
// 知识库
if (data.knowledgeContentResult) {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, {
knowledgeContentResult: data.knowledgeContentResult,
})
}
// 回复消息 // 回复消息
if (data.message) { if (data.message) {
replyTextContent += data.message replyTextContent += data.message
......
<script setup lang="ts">
import { nextTick, onMounted, ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
interface Props {
contentResultItem: KnowledgeContentResultItem
}
defineProps<Props>()
const { t } = useI18n()
const knowledgeContentRef = useTemplateRef('knowledgeContentRef')
const isContentExceed = ref(false)
const isExpandContent = ref(false)
const ELLIPSIS_HIGHT = 100
onMounted(() => {
nextTick(() => {
if (knowledgeContentRef.value) {
isContentExceed.value = knowledgeContentRef.value?.offsetHeight > ELLIPSIS_HIGHT
}
})
})
function handleClickExpand() {
isExpandContent.value = !isExpandContent.value
}
</script>
<template>
<div
class="rounded-theme border bg-white px-[15px] py-4"
:class="isExpandContent ? 'border-theme-color' : 'border-inactive-border-color'"
>
<div class="flex-center text-gray-font-color font-family-medium flex-shrink-0 justify-between gap-[10px]">
<div class="flex flex-1 items-center gap-[5px]">
<span class="flex-shrink-0">
{{ t('personal_space_module.agent_module.agent_setting_module.agent_config_module.from_knowledge_base') }}:
</span>
<n-ellipsis class="w-full!">
{{ contentResultItem.knowledgeName }}
</n-ellipsis>
</div>
<div class="flex flex-shrink-0 items-center gap-[5px]">
<span>{{ t('personal_space_module.agent_module.agent_setting_module.agent_config_module.score') }}:</span>
<span>{{ (Math.floor(contentResultItem.score * 100) / 100).toFixed(2) }}</span>
</div>
</div>
<div class="text-font-color relative mt-3 leading-4">
<span
ref="knowledgeContentRef"
class="whitespace-pre-wrap break-all leading-4"
:class="isContentExceed && !isExpandContent ? `line-clamp-6 max-h-[100px] overflow-y-hidden` : ''"
>
{{ contentResultItem.content }}
</span>
<div
v-show="isContentExceed"
class="text-gray-font-color flex-center mt-4 h-[16px] w-[72px] cursor-pointer gap-[5px] bg-white pr-2 leading-4 hover:text-[rgba(153,153,153,0.8)]"
:class="isExpandContent ? 'ml-auto items-baseline' : 'flex w-full justify-end'"
@click="handleClickExpand"
>
<span class="inline-block leading-4">
{{ isExpandContent ? t('common_module.fold') : t('common_module.unfold') }}
</span>
<i
class="iconfont icon-left rotate-270 text-center text-xs leading-4"
:class="{ '!rotate-90': isExpandContent }"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import HitKnowledgeItem from './hit-knowledge-item.vue'
interface Props {
knowledgeContentResult: KnowledgeContentResultItem[]
}
defineProps<Props>()
const { t } = useI18n()
const isShowDrawer = defineModel<boolean>('isShowDrawer', { required: true })
</script>
<template>
<n-drawer
v-model:show="isShowDrawer"
:width="430"
:show-mask="'transparent'"
:trap-focus="false"
:block-scroll="false"
class="shadow-[0_0_20px_rgba(0,4,65,0.07)]! rounded-[10px]!"
to="#agent-config-target"
>
<n-drawer-content
:title="t('personal_space_module.agent_module.agent_setting_module.agent_config_module.back_chunk_content')"
header-class="border-b-0! p-5! pt-6! text-[16px]! font-family-medium!"
body-content-class="px-5! py-0!"
footer-class="border-t-0! py-2!"
:native-scrollbar="false"
closable
>
<div class="flex flex-col gap-[18px]">
<HitKnowledgeItem
v-for="(knowledgeContentResultItem, index) in knowledgeContentResult"
:key="index"
:content-result-item="knowledgeContentResultItem"
/>
</div>
<template #footer></template>
</n-drawer-content>
</n-drawer>
</template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, nextTick, watch } from 'vue' import { computed, nextTick, ref, watch } from 'vue'
import MessageItem from './message-item.vue' import MessageItem from './message-item.vue'
import ContinueQuestion from './continue-question.vue' import ContinueQuestion from './continue-question.vue'
import { useScroll } from '@/composables/useScroll' import { useScroll } from '@/composables/useScroll'
import { useBackBottom } from '@/composables/useBackBottom' import { useBackBottom } from '@/composables/useBackBottom'
import HitKnowledge from './hit-knowledge.vue'
interface Props { interface Props {
messageList: Map<string, ConversationMessageItem> messageList: Map<string, ConversationMessageItem>
...@@ -24,6 +25,9 @@ const { scrollRef, scrollToBottom } = useScroll() ...@@ -24,6 +25,9 @@ const { scrollRef, scrollToBottom } = useScroll()
const { visible, clickBackBottom, throttleScrollContainer } = useBackBottom(scrollRef, scrollToBottom) const { visible, clickBackBottom, throttleScrollContainer } = useBackBottom(scrollRef, scrollToBottom)
const isShowKnowledgeContent = ref(false)
const currentKnowledgeContentResult = ref<KnowledgeContentResultItem[]>([])
const isShowContinueQuestion = computed(() => { const isShowContinueQuestion = computed(() => {
return ( return (
props.continuousQuestionStatus === 'default' && props.continuousQuestionStatus === 'default' &&
...@@ -49,6 +53,11 @@ function handleScrollToBottom() { ...@@ -49,6 +53,11 @@ function handleScrollToBottom() {
!visible.value && scrollToBottom() !visible.value && scrollToBottom()
}) })
} }
function handleShowKnowledgeResult(knowledgeContentResult: KnowledgeContentResultItem[]) {
isShowKnowledgeContent.value = true
currentKnowledgeContentResult.value = knowledgeContentResult
}
</script> </script>
<template> <template>
...@@ -61,6 +70,7 @@ function handleScrollToBottom() { ...@@ -61,6 +70,7 @@ function handleScrollToBottom() {
:message-item="messageItem" :message-item="messageItem"
@audio-play="() => $emit('audioPlay', messageItem)" @audio-play="() => $emit('audioPlay', messageItem)"
@audio-pause="() => $emit('audioPause')" @audio-pause="() => $emit('audioPause')"
@show-knowledge-result="handleShowKnowledgeResult(messageItem.knowledgeContentResult)"
/> />
</div> </div>
...@@ -75,5 +85,10 @@ function handleScrollToBottom() { ...@@ -75,5 +85,10 @@ function handleScrollToBottom() {
> >
<i class="iconfont icon-left rotate-270 text-sm" /> <i class="iconfont icon-left rotate-270 text-sm" />
</div> </div>
<HitKnowledge
v-model:is-show-drawer="isShowKnowledgeContent"
:knowledge-content-result="currentKnowledgeContentResult!"
/>
</main> </main>
</template> </template>
...@@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n' ...@@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n'
import { Plus, RightOne } from '@icon-park/vue-next' import { Plus, RightOne } from '@icon-park/vue-next'
import { fetchGetKnowledgeListByKdIds } from '@/apis/knowledge' import { fetchGetKnowledgeListByKdIds } from '@/apis/knowledge'
import AssociatedKnowledgeModal from './associated-knowledge-modal.vue' import AssociatedKnowledgeModal from './associated-knowledge-modal.vue'
import AgentKnowledgeSettingModal, { KnowledgeConfigType } from './agent-knowledge-setting-modal.vue'
import { KnowledgeItem } from '@/views/personal-space/personal-knowledge/types' import { KnowledgeItem } from '@/views/personal-space/personal-knowledge/types'
import { PersonalAppConfigState } from '@/store/types/personal-app-config' import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { KnowledgeTypeIcon } from '@/enums/knowledge' import { KnowledgeTypeIcon } from '@/enums/knowledge'
...@@ -16,6 +17,7 @@ const isShowAssociatedKnowledgeModel = ref(false) ...@@ -16,6 +17,7 @@ const isShowAssociatedKnowledgeModel = ref(false)
const knowledgeConfigExpandedNames = ref<string[]>([]) const knowledgeConfigExpandedNames = ref<string[]>([])
const selectKnowledgeList = ref<KnowledgeItem[]>([]) const selectKnowledgeList = ref<KnowledgeItem[]>([])
const hoverKdId = ref(0) const hoverKdId = ref(0)
const isShowAgentKnowledgeSettingModal = ref(false)
watch( watch(
() => knowledgeConfig.value.knowledgeIds, () => knowledgeConfig.value.knowledgeIds,
...@@ -88,6 +90,12 @@ function handleCloseAssociatedKnowledgeModal() { ...@@ -88,6 +90,12 @@ function handleCloseAssociatedKnowledgeModal() {
function handleUpdateDocumentParsing(value: boolean) { function handleUpdateDocumentParsing(value: boolean) {
knowledgeConfig.value.isDocumentParsing = value ? 'Y' : 'N' knowledgeConfig.value.isDocumentParsing = value ? 'Y' : 'N'
} }
function handleUpdateKnowledgeConfig(newKnowledgeConfig: KnowledgeConfigType) {
knowledgeConfig.value = { ...knowledgeConfig.value, ...newKnowledgeConfig }
isShowAgentKnowledgeSettingModal.value = false
window.$message.success(t('common_module.save_success_message'))
}
</script> </script>
<template> <template>
...@@ -112,6 +120,13 @@ function handleUpdateDocumentParsing(value: boolean) { ...@@ -112,6 +120,13 @@ function handleUpdateDocumentParsing(value: boolean) {
class="my-[13px]!" class="my-[13px]!"
> >
<template #header-extra> <template #header-extra>
<div
class="text-theme-color mr-2 cursor-pointer border-r pr-3 text-xs hover:opacity-80"
@click="isShowAgentKnowledgeSettingModal = true"
>
{{ t('personal_space_module.agent_module.agent_setting_module.agent_config_module.advanced_setting') }}
</div>
<NTooltip trigger="hover"> <NTooltip trigger="hover">
<template #trigger> <template #trigger>
<Plus <Plus
...@@ -202,4 +217,9 @@ function handleUpdateDocumentParsing(value: boolean) { ...@@ -202,4 +217,9 @@ function handleUpdateDocumentParsing(value: boolean) {
:modal-title="t('common_module.add_knowledge')" :modal-title="t('common_module.add_knowledge')"
@close="handleCloseAssociatedKnowledgeModal" @close="handleCloseAssociatedKnowledgeModal"
/> />
<AgentKnowledgeSettingModal
v-model:is-show-modal="isShowAgentKnowledgeSettingModal"
@confirm="handleUpdateKnowledgeConfig"
/>
</template> </template>
...@@ -143,6 +143,7 @@ function messageItemFactory(): ConversationMessageItem { ...@@ -143,6 +143,7 @@ function messageItemFactory(): ConversationMessageItem {
pluginName: '', pluginName: '',
imageUrl: '', imageUrl: '',
reasoningContent: '', reasoningContent: '',
knowledgeContentResult: [],
} }
} }
...@@ -226,18 +227,6 @@ function handleMessageSend() { ...@@ -226,18 +227,6 @@ function handleMessageSend() {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, { reasoningContent }) emit('updateSpecifyMessageItem', latestAssistantMessageKey, { reasoningContent })
emit('updatePageScroll') emit('updatePageScroll')
assistantFullAnswerContent.value = (assistantFullAnswerContent.value + data.reasoningContent).replace(
/\^\[[\d\\[\]-]+?\]\^/g,
'',
)
if (!sentenceExtractCheckEnabled.value && props.isEnableVoice) {
sentenceExtract(latestAssistantMessageKey)
sentenceExtractCheckEnabled.value = true
messageAudioLoading.value = true
}
return return
} }
......
This diff is collapsed.
declare interface KnowledgeContentResultItem {
content: string
score: number
knowledgeName: string
documentName: string
}
declare interface ConversationMessageItem { declare interface ConversationMessageItem {
timestamp: number timestamp: number
role: 'user' | 'assistant' role: 'user' | 'assistant'
...@@ -12,4 +19,6 @@ declare interface ConversationMessageItem { ...@@ -12,4 +19,6 @@ declare interface ConversationMessageItem {
pluginName?: string pluginName?: string
imageUrl?: string imageUrl?: string
reasoningContent: string reasoningContent: string
modelName?: string
knowledgeContentResult: KnowledgeContentResultItem[]
} }
...@@ -144,6 +144,8 @@ declare namespace I18n { ...@@ -144,6 +144,8 @@ declare namespace I18n {
download: string download: string
download_success: string download_success: string
download_fail: string download_fail: string
unfold: string
fold: string
dialogue_module: { dialogue_module: {
continue_question_message: string continue_question_message: string
...@@ -314,6 +316,29 @@ declare namespace I18n { ...@@ -314,6 +316,29 @@ declare namespace I18n {
knowledge: string knowledge: string
knowledge_base: string knowledge_base: string
knowledge_base_desc: string knowledge_base_desc: string
advanced_setting: string
knowledge_base_setting: string
reply: string
reply_to_superclass_question: string
default_reply: string
custom_reply: string
please_enter_custom_content: string
knowledge_base_retrieval_strategy: string
mix: string
mix_desc: string
keyword: string
keyword_desc: string
semantics: string
semantics_desc: string
retrieval_strategy_setting: string
retrieval_strategy_setting_desc: string
minimum_similarity: string
minimum_similarity_desc: string
number_of_results_returned: string
number_of_results_returned_desc: string
back_chunk_content: string
from_knowledge_base: string
score: string
upload_file: string upload_file: string
upload_file_desc: string upload_file_desc: string
dialogue: string dialogue: string
......
...@@ -80,6 +80,9 @@ export default defineConfig({ ...@@ -80,6 +80,9 @@ export default defineConfig({
training: 'infinite', training: 'infinite',
}, },
}, },
fontFamily: {
'family-medium': 'SourceHanSansCN-Medium',
},
}, },
shortcuts: { shortcuts: {
'flex-center': 'flex items-center justify-center', 'flex-center': 'flex items-center justify-center',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment