feat:【antd】【ai】chat 增加联网搜索的迁移
This commit is contained in:
@@ -57,6 +57,7 @@ const conversationInAbortController = ref<any>(); // 对话进行中 abort 控
|
||||
const inputTimeout = ref<any>(); // 处理输入中回车的定时器
|
||||
const prompt = ref<string>(); // prompt
|
||||
const enableContext = ref<boolean>(true); // 是否开启上下文
|
||||
const enableWebSearch = ref<boolean>(false); // 是否开启联网搜索
|
||||
// 接收 Stream 消息
|
||||
const receiveMessageFullText = ref('');
|
||||
const receiveMessageDisplayedText = ref('');
|
||||
@@ -353,6 +354,7 @@ async function doSendMessageStream(userMessage: AiChatMessageApi.ChatMessage) {
|
||||
userMessage.content,
|
||||
conversationInAbortController.value,
|
||||
enableContext.value,
|
||||
enableWebSearch.value,
|
||||
async (res: any) => {
|
||||
const { code, data, msg } = JSON.parse(res.data);
|
||||
if (code !== 0) {
|
||||
@@ -590,9 +592,15 @@ onMounted(async () => {
|
||||
placeholder="问我任何问题...(Shift+Enter 换行,按下 Enter 发送)"
|
||||
></textarea>
|
||||
<div class="flex justify-between pb-0 pt-1">
|
||||
<div class="flex items-center">
|
||||
<Switch v-model:checked="enableContext" />
|
||||
<span class="ml-1 text-sm text-gray-400">上下文</span>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center">
|
||||
<Switch v-model:checked="enableContext" size="small" />
|
||||
<span class="ml-1 text-sm text-gray-400">上下文</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<Switch v-model:checked="enableWebSearch" size="small" />
|
||||
<span class="ml-1 text-sm text-gray-400">联网搜索</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
|
||||
@@ -18,6 +18,7 @@ import { deleteChatMessage } from '#/api/ai/chat/message';
|
||||
import { MarkdownView } from '#/components/markdown-view';
|
||||
|
||||
import MessageKnowledge from './knowledge.vue';
|
||||
import MessageWebSearch from './web-search.vue';
|
||||
|
||||
const props = defineProps({
|
||||
conversation: {
|
||||
@@ -136,6 +137,10 @@ onMounted(async () => {
|
||||
:content="item.content"
|
||||
/>
|
||||
<MessageKnowledge v-if="item.segments" :segments="item.segments" />
|
||||
<MessageWebSearch
|
||||
v-if="item.webSearchPages"
|
||||
:web-search-pages="item.webSearchPages"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-row">
|
||||
<Button
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
<script setup lang="ts">
|
||||
import type { AiChatMessageApi } from '#/api/ai/chat/message';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useVbenDrawer } from '@vben/common-ui';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
defineProps<{
|
||||
webSearchPages?: AiChatMessageApi.WebSearchPage[];
|
||||
}>();
|
||||
|
||||
const isExpanded = ref(false); // 默认收起
|
||||
const selectedResult = ref<AiChatMessageApi.WebSearchPage | null>(null); // 选中的搜索结果
|
||||
const iconLoadError = ref<Record<number, boolean>>({}); // 记录图标加载失败
|
||||
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
title: '联网搜索详情',
|
||||
closable: true,
|
||||
footer: true,
|
||||
onCancel() {
|
||||
drawerApi.close();
|
||||
},
|
||||
onConfirm() {
|
||||
if (selectedResult.value?.url) {
|
||||
window.open(selectedResult.value.url, '_blank');
|
||||
}
|
||||
drawerApi.close();
|
||||
},
|
||||
});
|
||||
|
||||
/** 切换展开/收起 */
|
||||
function toggleExpanded() {
|
||||
isExpanded.value = !isExpanded.value;
|
||||
}
|
||||
|
||||
/** 点击搜索结果 */
|
||||
function handleClick(result: AiChatMessageApi.WebSearchPage) {
|
||||
selectedResult.value = result;
|
||||
drawerApi.open();
|
||||
}
|
||||
|
||||
/** 图标加载失败处理 */
|
||||
function handleIconError(index: number) {
|
||||
iconLoadError.value[index] = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="webSearchPages && webSearchPages.length > 0" class="mt-2.5">
|
||||
<!-- 标题栏:可点击展开/收起 -->
|
||||
<div
|
||||
class="mb-2 flex cursor-pointer items-center justify-between text-sm text-gray-600 transition-colors hover:text-blue-500"
|
||||
@click="toggleExpanded"
|
||||
>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<IconifyIcon icon="lucide:search" :size="14" />
|
||||
<span>联网搜索结果 ({{ webSearchPages.length }} 条)</span>
|
||||
</div>
|
||||
<IconifyIcon
|
||||
:icon="isExpanded ? 'lucide:chevron-up' : 'lucide:chevron-down'"
|
||||
class="text-xs transition-transform duration-200"
|
||||
:size="12"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 可展开的搜索结果列表 -->
|
||||
<div
|
||||
v-show="isExpanded"
|
||||
class="flex flex-col gap-2 transition-all duration-200 ease-in-out"
|
||||
>
|
||||
<div
|
||||
v-for="(page, index) in webSearchPages"
|
||||
:key="index"
|
||||
class="cursor-pointer rounded-md bg-white p-2.5 transition-all hover:bg-blue-50"
|
||||
@click="handleClick(page)"
|
||||
>
|
||||
<div class="flex items-start gap-2">
|
||||
<!-- 网站图标 -->
|
||||
<div class="mt-0.5 h-4 w-4 flex-shrink-0">
|
||||
<img
|
||||
v-if="page.icon && !iconLoadError[index]"
|
||||
:src="page.icon"
|
||||
:alt="page.name"
|
||||
class="h-full w-full rounded-sm object-contain"
|
||||
@error="handleIconError(index)"
|
||||
/>
|
||||
<IconifyIcon
|
||||
v-else
|
||||
icon="lucide:link"
|
||||
class="h-full w-full text-gray-600"
|
||||
/>
|
||||
</div>
|
||||
<!-- 内容区域 -->
|
||||
<div class="min-w-0 flex-1">
|
||||
<!-- 网站名称 -->
|
||||
<div class="mb-1 truncate text-xs text-gray-400">
|
||||
{{ page.name }}
|
||||
</div>
|
||||
<!-- 主标题 -->
|
||||
<div
|
||||
class="mb-1 line-clamp-2 text-sm font-medium leading-snug text-blue-600"
|
||||
>
|
||||
{{ page.title }}
|
||||
</div>
|
||||
<!-- 描述 -->
|
||||
<div class="mb-1 line-clamp-2 text-xs leading-snug text-gray-600">
|
||||
{{ page.snippet }}
|
||||
</div>
|
||||
<!-- URL -->
|
||||
<div class="truncate text-xs text-green-700">
|
||||
{{ page.url }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 联网搜索详情 Drawer -->
|
||||
<Drawer class="w-[600px]" cancel-text="关闭" confirm-text="访问原文">
|
||||
<div v-if="selectedResult">
|
||||
<!-- 标题区域 -->
|
||||
<div class="mb-4 flex items-start gap-3">
|
||||
<div class="mt-0.5 h-6 w-6 flex-shrink-0">
|
||||
<img
|
||||
v-if="selectedResult.icon"
|
||||
:src="selectedResult.icon"
|
||||
:alt="selectedResult.name"
|
||||
class="h-full w-full rounded-sm object-contain"
|
||||
/>
|
||||
<IconifyIcon
|
||||
v-else
|
||||
icon="lucide:link"
|
||||
class="h-full w-full text-gray-600"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="mb-2 text-lg font-bold text-gray-900">
|
||||
{{ selectedResult.title }}
|
||||
</div>
|
||||
<div class="mb-1 text-sm text-gray-500">
|
||||
{{ selectedResult.name }}
|
||||
</div>
|
||||
<div class="break-all text-sm text-green-700">
|
||||
{{ selectedResult.url }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 内容区域 -->
|
||||
<div class="space-y-4">
|
||||
<!-- 简短描述 -->
|
||||
<div>
|
||||
<div class="mb-2 text-sm font-semibold text-gray-900">简短描述</div>
|
||||
<div
|
||||
class="rounded-lg bg-gray-50 p-3 text-sm leading-relaxed text-gray-700"
|
||||
>
|
||||
{{ selectedResult.snippet }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 内容摘要 -->
|
||||
<div v-if="selectedResult.summary">
|
||||
<div class="mb-2 text-sm font-semibold text-gray-900">内容摘要</div>
|
||||
<div
|
||||
class="max-h-[50vh] overflow-y-auto whitespace-pre-wrap rounded-lg bg-gray-50 p-3 text-sm leading-relaxed text-gray-900"
|
||||
>
|
||||
{{ selectedResult.summary }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user