feat: 优化输入框placeholder动态显示逻辑,支持基于知识库和网络搜索状态的个性化提示

This commit is contained in:
wizardchen
2025-12-22 12:11:09 +08:00
committed by lyingbug
parent 58ae0d8350
commit 1ac663fc3f
8 changed files with 146 additions and 14 deletions
+21 -1
View File
@@ -121,6 +121,26 @@ const { t } = useI18n();
const displayedKbs = computed(() => selectedKbs.value.slice(0, 2));
const remainingCount = computed(() => Math.max(0, selectedKbs.value.length - 2));
// 根据不同状态组合计算输入框的 placeholder
const inputPlaceholder = computed(() => {
const hasKnowledge = allSelectedItems.value.length > 0;
const hasWebSearch = isWebSearchEnabled.value && isWebSearchConfigured.value;
if (hasKnowledge && hasWebSearch) {
// 有知识库 + 有网络搜索
return t('input.placeholderKbAndWeb');
} else if (hasKnowledge) {
// 有知识库 + 无网络搜索
return t('input.placeholderWithContext');
} else if (hasWebSearch) {
// 无知识库 + 有网络搜索
return t('input.placeholderWebOnly');
} else {
// 无知识库 + 无网络搜索(纯模型对话)
return t('input.placeholder');
}
});
// 加载知识库列表
const loadKnowledgeBases = async () => {
try {
@@ -1185,7 +1205,7 @@ onBeforeRouteUpdate((to, from, next) => {
<t-textarea
ref="textareaRef"
v-model="query"
:placeholder="allSelectedItems.length > 0 ? $t('input.placeholderWithContext') : $t('input.placeholder')"
:placeholder="inputPlaceholder"
name="description"
:autosize="true"
@keydown="onKeydown"
+3 -1
View File
@@ -748,8 +748,10 @@ export default {
},
input: {
addModel: 'Add Model',
placeholder: 'Ask questions based on the knowledge base',
placeholder: 'Ask questions directly to the model',
placeholderWithContext: 'Enter your question, will answer based on selected knowledge bases/files above',
placeholderWebOnly: 'Enter your question, will answer with web search',
placeholderKbAndWeb: 'Enter your question, will answer based on knowledge base and web search',
agentMode: 'Agent Mode',
normalMode: 'Normal Mode',
normalModeDesc: 'Knowledge base RAG Q&A',
+3 -1
View File
@@ -1671,8 +1671,10 @@ export default {
},
input: {
addModel: 'Добавить модель',
placeholder: 'Задайте вопрос на основе базы знаний',
placeholder: 'Задайте вопрос напрямую модели',
placeholderWithContext: 'Введите вопрос, ответ будет основан на выбранных выше базах знаний/файлах',
placeholderWebOnly: 'Введите вопрос, ответ будет основан на веб-поиске',
placeholderKbAndWeb: 'Введите вопрос, ответ будет основан на базе знаний и веб-поиске',
agentMode: 'Agent режим',
normalMode: 'Обычный режим',
normalModeDesc: 'RAG-вопросы и ответы по базе знаний',
+3 -1
View File
@@ -1427,8 +1427,10 @@ export default {
},
input: {
addModel: "添加模型",
placeholder: "基于知识库提问",
placeholder: "直接向模型提问",
placeholderWithContext: "输入问题,将基于上方选中的知识库/文件回答",
placeholderWebOnly: "输入问题,将结合网络搜索回答",
placeholderKbAndWeb: "输入问题,将基于知识库和网络搜索回答",
agentMode: "Agent 模式",
normalMode: "普通模式",
normalModeDesc: "基于知识库的 RAG 问答",
+4 -1
View File
@@ -32,6 +32,7 @@ export const useMenuStore = defineStore('menuStore', () => {
const isFirstSession = ref(false)
const firstQuery = ref('')
const firstMentionedItems = ref<any[]>([])
const applyMenuTranslations = () => {
menuArr.forEach(item => {
@@ -90,14 +91,16 @@ export const useMenuStore = defineStore('menuStore', () => {
isFirstSession.value = payload
}
const changeFirstQuery = (payload: string) => {
const changeFirstQuery = (payload: string, mentionedItems: any[] = []) => {
firstQuery.value = payload
firstMentionedItems.value = mentionedItems
}
return {
menuArr,
isFirstSession,
firstQuery,
firstMentionedItems,
clearMenuArr,
updatemenuArr,
updataMenuChildren,
@@ -1,6 +1,23 @@
<template>
<div class="bot_msg">
<div style="display: flex;flex-direction: column; gap:8px">
<!-- 显示@的知识库和文件(非 Agent 模式下显示) -->
<div v-if="!session.isAgentMode && mentionedItems && mentionedItems.length > 0" class="mentioned_items">
<span
v-for="item in mentionedItems"
:key="item.id"
class="mentioned_tag"
:class="[
item.type === 'kb' ? (item.kb_type === 'faq' ? 'faq-tag' : 'kb-tag') : 'file-tag'
]"
>
<span class="tag_icon">
<t-icon v-if="item.type === 'kb'" :name="item.kb_type === 'faq' ? 'chat-bubble-help' : 'folder'" />
<t-icon v-else name="file" />
</span>
<span class="tag_name">{{ item.name }}</span>
</span>
</div>
<docInfo :session="session"></docInfo>
<AgentStreamDisplay :session="session" :user-query="userQuery" v-if="session.isAgentMode"></AgentStreamDisplay>
<deepThink :deepSession="session" v-if="session.showThink && !session.isAgentMode"></deepThink>
@@ -75,6 +92,11 @@ const props = defineProps({
required: false,
default: ''
},
mentionedItems: {
type: Array,
required: false,
default: () => []
},
isFirstEnter: {
type: Boolean,
required: false
@@ -249,6 +271,75 @@ onBeforeUnmount(() => {
<style lang="less" scoped>
@import '../../../components/css/markdown.less';
// @提及的知识库和文件标签样式
.mentioned_items {
display: flex;
flex-wrap: wrap;
gap: 6px;
justify-content: flex-start;
max-width: 100%;
margin-bottom: 2px;
}
.mentioned_tag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 8px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
max-width: 200px;
cursor: default;
transition: all 0.15s;
background: var(--td-bg-color-secondarycontainer, #f3f3f3);
border: 1px solid transparent;
color: var(--td-text-color-primary, #333);
/* KB - Document (Greenish tint) */
&.kb-tag {
background: rgba(16, 185, 129, 0.08);
color: #059669;
.tag_icon {
color: #10b981;
}
}
/* KB - FAQ (Blueish tint) */
&.faq-tag {
background: rgba(0, 82, 217, 0.08);
color: #0052d9;
.tag_icon {
color: #0052d9;
}
}
/* File (Orange tint) */
&.file-tag {
background: rgba(237, 123, 47, 0.08);
color: #e65100;
.tag_icon {
color: #ed7b2f;
}
}
.tag_icon {
font-size: 14px;
display: flex;
align-items: center;
}
.tag_name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: currentColor;
}
}
// 内容包装器 - 与 Agent 模式的 answer 样式一致
.content-wrapper {
background: #ffffff;
+15 -3
View File
@@ -7,7 +7,7 @@
<usermsg :content="session.content" :mentioned_items="session.mentioned_items"></usermsg>
</div>
<div v-if="session.role == 'assistant'">
<botmsg :content="session.content" :session="session" :user-query="getUserQuery(id)" @scroll-bottom="scrollToBottom"
<botmsg :content="session.content" :session="session" :user-query="getUserQuery(id)" :mentioned-items="getUserMentionedItems(id)" @scroll-bottom="scrollToBottom"
:isFirstEnter="isFirstEnter"></botmsg>
</div>
</div>
@@ -61,7 +61,7 @@ const useSettingsStoreInstance = useSettingsStore();
const uiStore = useUIStore();
const { navigateToKnowledgeBaseList } = useKnowledgeBaseCreationNavigation();
const { t } = useI18n();
const { menuArr, isFirstSession, firstQuery } = storeToRefs(usemenuStore);
const { menuArr, isFirstSession, firstQuery, firstMentionedItems } = storeToRefs(usemenuStore);
const { output, onChunk, isStreaming, isLoading, error, startStream, stopStream } = useStream();
const route = useRoute();
const router = useRouter();
@@ -93,6 +93,18 @@ const getUserQuery = (index) => {
}
return '';
};
// 获取用户消息的 mentioned_items(用于 bot 消息显示)
const getUserMentionedItems = (index) => {
if (index <= 0) {
return [];
}
const previous = messagesList[index - 1];
if (previous && previous.role === 'user') {
return previous.mentioned_items || [];
}
return [];
};
watch([() => route.params], (newvalue) => {
isFirstEnter.value = true;
if (newvalue[0].chatid) {
@@ -790,7 +802,7 @@ onMounted(async () => {
checkmenuTitle(session_id.value)
if (firstQuery.value) {
scrollLock.value = true;
sendMsg(firstQuery.value);
sendMsg(firstQuery.value, '', firstMentionedItems.value || []);
usemenuStore.changeFirstQuery('');
} else {
scrollLock.value = false;
+6 -6
View File
@@ -39,11 +39,11 @@ const uiStore = useUIStore();
const { t } = useI18n();
const { navigateToKnowledgeBaseList } = useKnowledgeBaseCreationNavigation();
const sendMsg = (value: string) => {
createNewSession(value);
const sendMsg = (value: string, modelId: string, mentionedItems: any[]) => {
createNewSession(value, mentionedItems);
}
async function createNewSession(value: string) {
async function createNewSession(value: string, mentionedItems: any[] = []) {
const selectedKbs = settingsStore.settings.selectedKnowledgeBases || [];
const selectedFiles = settingsStore.settings.selectedFiles || [];
@@ -63,7 +63,7 @@ async function createNewSession(value: string) {
try {
const res = await createSessions(sessionData);
if (res.data && res.data.id) {
await navigateToSession(res.data.id, value);
await navigateToSession(res.data.id, value, mentionedItems);
} else {
console.error('[createChat] Failed to create session');
MessagePlugin.error(t('createChat.messages.createFailed'));
@@ -74,7 +74,7 @@ async function createNewSession(value: string) {
}
}
const navigateToSession = async (sessionId: string, value: string) => {
const navigateToSession = async (sessionId: string, value: string, mentionedItems: any[]) => {
const now = new Date().toISOString();
let obj = {
title: t('createChat.newSessionTitle'),
@@ -87,7 +87,7 @@ const navigateToSession = async (sessionId: string, value: string) => {
};
usemenuStore.updataMenuChildren(obj);
usemenuStore.changeIsFirstSession(true);
usemenuStore.changeFirstQuery(value);
usemenuStore.changeFirstQuery(value, mentionedItems);
router.push(`/platform/chat/${sessionId}`);
}