[前端+后端+RAG] 检索范围切换(当前文件/整个知识库);联网搜索功能(SearXNG);搜索结果带网络链接;修复RAG检索source格式不匹配bug

This commit is contained in:
2026-04-07 15:02:54 +08:00
parent a5110da4e8
commit e1e5d4f30d
6 changed files with 158 additions and 36 deletions

View File

@@ -9,16 +9,27 @@
<!-- 文字窗口-->
<div>
<div class="tool-bar">
<div class="label">
<!-- <img src="../assets/images/writing/start.png">
<div>AI写作助手</div>-->
</div>
<div class="label"></div>
<div class="clean" @click="cleanChat">
<img src="../assets/images/writing/brush.png">
<div>清除对话</div>
</div>
</div>
<div class="search-scope" v-if="selectedFile">
<div class="scope-label">检索范围</div>
<div class="scope-option" :class="{ active: searchScope === 'file' }" @click="searchScope = 'file'"
:title="'仅在「' + selectedFile.fileName + '」中检索'">
<span class="scope-icon">📄</span>
<span class="scope-name">{{ selectedFile.fileName }}</span>
</div>
<div class="scope-option" :class="{ active: searchScope === 'kb' }" @click="searchScope = 'kb'"
:title="'在「' + selectedFile.folderName + '」知识库的所有文件中检索'">
<span class="scope-icon">📁</span>
<span class="scope-name">{{ selectedFile.folderName }}</span>
</div>
</div>
<div class="text-box">
<div class="quote-box" v-if="quoteMsg">
<div class="vertical-line"></div>
@@ -34,10 +45,17 @@
@input="handleInput"
@keydown.enter="keyDown"
placeholder="请输入你想提的问题字数不能超过1000字"/>
<div>
<img v-if="textarea&&!sendStatus" style="width: 38px" src="../assets/images/writing/send-blue.png" @click="send('','0')">
<img v-if="!textarea&&!sendStatus" src="../assets/images/writing/send-gray.png">
<img v-if="sendStatus" src="../assets/images/chat/stopChat.png" @click="handleStop"></img>
<div class="text-box-bottom">
<div class="web-search-toggle" :class="{ active: webSearchEnabled }" @click="webSearchEnabled = !webSearchEnabled"
:title="webSearchEnabled ? '联网搜索已开启,点击关闭' : '开启联网搜索,从互联网获取最新信息'">
<span class="ws-icon">🌐</span>
<span class="ws-text">联网搜索</span>
</div>
<div class="send-btn">
<img v-if="textarea&&!sendStatus" style="width: 38px" src="../assets/images/writing/send-blue.png" @click="send('','0')">
<img v-if="!textarea&&!sendStatus" src="../assets/images/writing/send-gray.png">
<img v-if="sendStatus" src="../assets/images/chat/stopChat.png" @click="handleStop">
</div>
</div>
</div>
@@ -76,6 +94,8 @@ const clearQuote = () => {
}
//const title = inject('aiboxTitle');
const searchScope = ref<'file' | 'kb'>('file');
const webSearchEnabled = ref(false);
const textarea = ref("");
const firstChat = ref(true);
const sendStatus = ref(false);
@@ -198,12 +218,13 @@ const getFetchChatAPIProcess = async (type: string) => {
headers: headers,
signal: controller.signal,
body: JSON.stringify({
fileNames: [selectedFile.value?.embeddingId],
fileNames: searchScope.value === 'file' ? [selectedFile.value?.embeddingId] : [],
conversationId: conversationId.value,
promptName: "default",
knowledgeBaseIdList: [selectedFile.value?.folderId],
chatType: type,
quote: quoteMsg.value
quote: quoteMsg.value,
webSearch: webSearchEnabled.value
}),
}
);
@@ -372,6 +393,7 @@ const loadChatHistory = async () => {
// 监听文件切换,重新加载对话历史
watch(() => selectedFile.value?.fileId, () => {
searchScope.value = 'file';
loadChatHistory();
});
@@ -434,7 +456,7 @@ const handleStop = async () => {
<style lang="less" scoped>
.message-content {
height: calc(100% - 290px);
height: calc(100% - 320px);
overflow-y: auto;
padding: 20px;
@@ -505,28 +527,93 @@ const handleStop = async () => {
}
}
.search-scope {
display: flex;
align-items: center;
padding: 4px 12px;
gap: 6px;
.scope-label {
font-size: 13px;
color: #333;
flex-shrink: 0;
}
.scope-option {
display: flex;
align-items: center;
gap: 5px;
padding: 6px 14px;
border-radius: 14px;
border: 1px solid #E0E0E0;
cursor: pointer;
font-size: 13px;
color: #666;
transition: all 0.2s;
max-width: 45%;
overflow: hidden;
&:hover { border-color: #004EA0; color: #004EA0; }
&.active { border-color: #004EA0; color: #fff; background: #004EA0; }
.scope-icon { font-size: 14px; flex-shrink: 0; }
.scope-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}
}
.text-box {
//width: 100%;
height: 190px;
background: #FFFFFF;
border-radius: 8px;
border: 1px solid #D5DDFF;
margin: 12px 20px 12px 20px;
display: flex;
flex-direction: column;
.box-textarea {
outline: none;
border: none;
resize: none;
width: 100%;
height: calc(100% - 54px);
flex: 1;
padding: 16px;
line-height: 24px;
border-radius: 8px;
}
.text-box-bottom {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 12px;
.web-search-toggle {
display: flex;
align-items: center;
gap: 5px;
padding: 6px 14px;
border-radius: 14px;
border: 1px solid #E0E0E0;
cursor: pointer;
font-size: 13px;
color: #999;
transition: all 0.2s;
user-select: none;
&:hover { border-color: #10a37f; color: #10a37f; }
&.active { border-color: #10a37f; color: #fff; background: #10a37f; }
.ws-icon { font-size: 14px; }
.ws-text { font-size: 13px; }
}
.send-btn {
img { width: 38px; cursor: pointer; }
}
}
img {
cursor:pointer;
float: right;
margin-right: 16px;
cursor: pointer;
}
.quote-box {