ai助理
parent
a747231358
commit
687fa6cffb
|
|
@ -11,6 +11,14 @@ body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.van-button--primary{
|
||||||
|
background-color: #3662FE !important;
|
||||||
|
}
|
||||||
|
.van-floating-bubble{
|
||||||
|
background: transparent !important;
|
||||||
|
width: 140px !important;
|
||||||
|
height: 140px !important;
|
||||||
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 7px;
|
width: 7px;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg t="1692247991456" class="icon" viewBox="0 0 1024 1024" version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" p-id="7389" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
width="200" height="200">
|
||||||
|
<path
|
||||||
|
d="M513 19.7c-272.9 0-495 222.1-495 495 0 86 22.4 170.6 64.7 244.9 8.6 15.1-4.4 43.8-18.3 74.2-17.9 39.5-25.1 59.9-17.7 68.5 2.2 2.9 7.3 4.4 15.5 4.4 14.5 0 35.7-4.7 56.3-9.2 21.6-4.8 42.1-9.3 56.9-9.3 8.7 0 14.5 1.5 18.3 4.8 89.1 75.2 202.5 116.7 319.3 116.7 272.9 0 495-222 495-495 0-272.9-222-495-495-495z m0 922.7c-100.9 0-198.9-35.8-275.8-100.8-16.1-13.6-37-20.6-61.8-20.6-10.8 0-22.1 1.3-33.7 3.2 11.9-32.1 17.8-65.8-0.5-97.9-36.6-64.1-55.9-137.3-55.9-211.6C85.3 278.9 277.2 87 513 87c235.9 0 427.8 191.9 427.8 427.7 0 235.8-191.9 427.7-427.8 427.7z"
|
||||||
|
fill="currentColor" p-id="7390"></path>
|
||||||
|
<path
|
||||||
|
d="M445.2 323.5c18.5 0 33.6 15.1 33.6 33.6v315.2c0 18.5-15.1 33.6-33.6 33.6s-33.6-15.1-33.6-33.6V357.1c-0.1-18.5 15.1-33.6 33.6-33.6zM580.9 373.6c18.5 0 33.6 15.1 33.6 33.6v215c0 18.5-15.1 33.6-33.6 33.6s-33.6-15.1-33.6-33.6v-215c0-18.5 15.2-33.6 33.6-33.6zM716.7 449.3c18.5 0 33.6 15.1 33.6 33.6v63.5c0 18.5-15.1 33.6-33.6 33.6s-33.6-15.1-33.6-33.6v-63.5c0-18.4 15.1-33.6 33.6-33.6zM309.4 449.3c18.5 0 33.6 15.1 33.6 33.6v63.5c0 18.5-15.1 33.6-33.6 33.6s-33.6-15.1-33.6-33.6v-63.5c-0.1-18.4 15.1-33.6 33.6-33.6z"
|
||||||
|
fill="currentColor" p-id="7391"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1,79 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<van-floating-bubble
|
||||||
|
v-model:offset="offsetc"
|
||||||
|
axis="xy"
|
||||||
|
icon="chat"
|
||||||
|
magnetic="x"
|
||||||
|
:gap="0"
|
||||||
|
@click="onClick"
|
||||||
|
@offset-change="onOffsetChange"
|
||||||
|
>
|
||||||
|
<div ref="floatref" class="w-full h-full flex items-center justify-center">
|
||||||
|
<div class="ai-float-btn">
|
||||||
|
<SvgIcon class="text-white" name="生活助手"></SvgIcon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</van-floating-bubble>
|
||||||
|
|
||||||
|
<van-popup teleport="body" v-model:show="show">
|
||||||
|
<div class="relative">
|
||||||
|
<div class="absolute right-20px top-30px">
|
||||||
|
<SvgIcon @click="show=false" class="text-white" name="close"></SvgIcon>
|
||||||
|
</div>
|
||||||
|
<AiAssistant :content="content"></AiAssistant>
|
||||||
|
</div>
|
||||||
|
</van-popup>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRect } from '@vant/use'
|
||||||
|
import AiAssistant from './ai-assistant.vue'
|
||||||
|
const floatref = ref(null)
|
||||||
|
const props = defineProps({
|
||||||
|
offset: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const offsetc = ref({ x: 0, y: 0 })
|
||||||
|
const show = ref(false)
|
||||||
|
const onOffsetChange = (offset) => {
|
||||||
|
console.log(offset)
|
||||||
|
}
|
||||||
|
const setOffset = () => {
|
||||||
|
const { offset } = props
|
||||||
|
const { width,height } = useRect(floatref.value)
|
||||||
|
offsetc.value = {
|
||||||
|
x: offset?.x ?? window.innerWidth - width,
|
||||||
|
y: offset?.y ?? height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onClick = () => {
|
||||||
|
show.value = true
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
setOffset()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.ai-float {
|
||||||
|
background: #3662fe;
|
||||||
|
box-shadow: 0px 12px 26px 0px rgba(32, 69, 201, 0.86);
|
||||||
|
}
|
||||||
|
.ai-float-btn {
|
||||||
|
width: 94px;
|
||||||
|
height: 94px;
|
||||||
|
background: #3662fe;
|
||||||
|
box-shadow: 0px 12px 26px 0px rgba(32, 69, 201, 0.86);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,28 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="fixed w-86 h-full z-999" :style="styleObj">
|
<div class="w-full z-999" :style="styleObj">
|
||||||
<!-- <vue-draggable-resizable
|
<div class="w-full bg-[#161718] p-28px" ref="floatWindow">
|
||||||
:x="0"
|
|
||||||
:y="0"
|
|
||||||
:z="999"
|
|
||||||
:resizable="true"
|
|
||||||
w="auto"
|
|
||||||
h="auto"
|
|
||||||
> -->
|
|
||||||
<div class="w-86 bg-[#161718] p-3" ref="floatWindow">
|
|
||||||
<div class="flex text-white items-center justify-between">
|
<div class="flex text-white items-center justify-between">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<Avatar />
|
<Avatar />
|
||||||
<span class="font-bold text-lg ml-4">海兔AI智慧助理</span>
|
<span class="font-bold text-27px ml-26px">海兔AI智慧助理</span>
|
||||||
</div>
|
</div>
|
||||||
<SvgIcon
|
|
||||||
class="text-white text-xl cursor-pointer"
|
|
||||||
name="close"
|
|
||||||
></SvgIcon>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="border-2px border-[#A6A8AF] p-4 mt-4">
|
<div class="border-1px border-[#A6A8AF] p-32px mt-30px">
|
||||||
<div class="grid grid-cols-3 gap-x-2.5 options">
|
<div class="grid grid-cols-3 gap-x-21px options">
|
||||||
<div
|
<div
|
||||||
class="border-2px border-[#414548] h-6.5 text-center text-sm leading-6.5 text-white cursor-pointer"
|
class="border-1px border-[#414548] h-52px text-center text-27px leading-52px text-white cursor-pointer"
|
||||||
:class="{ active: optionIndex === index }"
|
:class="{ active: optionIndex === index }"
|
||||||
@click="changeOption(index)"
|
@click="changeOption(index)"
|
||||||
v-for="(item, index) in options"
|
v-for="(item, index) in options"
|
||||||
|
|
@ -31,30 +19,32 @@
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full h-2px bg-[#3662FE] my-2.5"></div>
|
<div class="w-full h-4px bg-[#3662FE] my-20px"></div>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<SvgIcon
|
<SvgIcon
|
||||||
v-if="loading"
|
v-if="loading"
|
||||||
@click="handleStop"
|
@click="handleStop"
|
||||||
class="text-white text-xl ml-2 cursor-pointer"
|
class="text-white text-44px ml-20px cursor-pointer"
|
||||||
name="pause"
|
name="pause"
|
||||||
></SvgIcon>
|
></SvgIcon>
|
||||||
<SvgIcon class="text-white text-xl ml-2" name="right-arrow"></SvgIcon>
|
<SvgIcon
|
||||||
|
class="text-white text-44px ml-20px"
|
||||||
|
name="right-arrow"
|
||||||
|
></SvgIcon>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5 h-97 -mx-4">
|
<div class="mt-10px h-600px">
|
||||||
<ScrollContainer ref="scrollRefAi">
|
<ScrollContainer class="!h-full" ref="scrollRefAi">
|
||||||
<div class="px-4">
|
<div class="px-0">
|
||||||
<div class="text-base text-white opacity-40 leading-6">
|
<div class="text-27px text-white opacity-40 leading-34px">
|
||||||
<div class="text-center" v-if="contentLoading">
|
<div class="text-center" v-if="contentLoading">
|
||||||
<a-spin size="small" />
|
<van-loading type="spinner" size="24"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
{{ contenText }}
|
{{ contenText }}
|
||||||
</div>
|
</div>
|
||||||
<!-- 大自然是人类赖以生存发展的基本条件,尊重自然、顺应自然、保护自然是全面建设社会主义现代化国家的内在要求大自然是人类赖以生存发展的基本条件,尊重自然、顺应自然、保护自然是全面建设社会主义现代化国家是全面建设社会主义现代化国家的内在要求 -->
|
|
||||||
</div>
|
</div>
|
||||||
<Message
|
<Message
|
||||||
class="my-4"
|
class="my-20px"
|
||||||
v-for="(item, index) in dataSources"
|
v-for="(item, index) in dataSources"
|
||||||
:key="index"
|
:key="index"
|
||||||
:text="item.text"
|
:text="item.text"
|
||||||
|
|
@ -64,31 +54,30 @@
|
||||||
</div>
|
</div>
|
||||||
</ScrollContainer>
|
</ScrollContainer>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex border-1px border-[#414548] h-88px mt-20px">
|
||||||
<a-input
|
<van-field
|
||||||
@pressEnter="sendMessage"
|
@pressEnter="sendMessage"
|
||||||
v-model:value="prompt"
|
v-model="prompt"
|
||||||
size="small"
|
size="small"
|
||||||
class="flex-1 text-sm rounded-r-none rounded-4px bg-[#414548] bg-opacity-40 text-white placeholder-[#FFFFFF40] border-[#414548]"
|
:border="false"
|
||||||
|
class="cu-field-ai"
|
||||||
placeholder="向我提问有关文本的任何问题"
|
placeholder="向我提问有关文本的任何问题"
|
||||||
></a-input>
|
></van-field>
|
||||||
<a-button
|
<van-button
|
||||||
@click="sendMessage"
|
@click="sendMessage"
|
||||||
type="primary"
|
type="primary"
|
||||||
class="rounded-r-4px rounded-l-none px-6 h-full !w-19"
|
class="!rounded-r-4px !rounded-l-none px-6 h-full !w-155px"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<SvgIcon class="text-white text-xl" name="send"></SvgIcon>
|
<SvgIcon class="text-white text-34px" name="send"></SvgIcon>
|
||||||
</template>
|
</template>
|
||||||
</a-button>
|
</van-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="opacity-40 text-sm text-center mt-4">
|
<div class="opacity-40 text-27px text-center mt-40px">
|
||||||
文案仅供参考,使用前请核实,风险自负
|
文案仅供参考,使用前请核实,风险自负
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- </vue-draggable-resizable> -->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -100,8 +89,6 @@ import { ref, computed, nextTick } from 'vue'
|
||||||
import http from '@/io/request'
|
import http from '@/io/request'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { useAiChat } from '@/stores/aichat'
|
import { useAiChat } from '@/stores/aichat'
|
||||||
import VueDraggableResizable from 'vue-draggable-resizable/src/components/vue-draggable-resizable.vue'
|
|
||||||
import 'vue-draggable-resizable/dist/VueDraggableResizable.css'
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
top: {
|
top: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
@ -183,7 +170,7 @@ const changeOption = (index) => {
|
||||||
autoMessage()
|
autoMessage()
|
||||||
}
|
}
|
||||||
const currentOption = computed(() => {
|
const currentOption = computed(() => {
|
||||||
if(optionIndex.value === null) return null
|
if (optionIndex.value === null) return null
|
||||||
return options[optionIndex.value] ?? null
|
return options[optionIndex.value] ?? null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -193,11 +180,13 @@ const dataSources = computed(() => {
|
||||||
|
|
||||||
const prompt = ref('')
|
const prompt = ref('')
|
||||||
|
|
||||||
const autoMessage = async ()=>{
|
const autoMessage = async () => {
|
||||||
|
const message = replacePlaceholder(
|
||||||
const message = replacePlaceholder(currentOption.value.value, truncateRichText(props.content, 4000))
|
currentOption.value.value,
|
||||||
|
truncateRichText(props.content, 4000)
|
||||||
|
)
|
||||||
|
|
||||||
if(loading.value) return
|
if (loading.value) return
|
||||||
contentLoading.value = true
|
contentLoading.value = true
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
|
|
@ -233,26 +222,24 @@ const autoMessage = async ()=>{
|
||||||
}
|
}
|
||||||
const errorMessage = error?.errmsg ?? '好像出错了,请稍后再试。'
|
const errorMessage = error?.errmsg ?? '好像出错了,请稍后再试。'
|
||||||
contenText.value = errorMessage
|
contenText.value = errorMessage
|
||||||
}finally{
|
} finally {
|
||||||
contentLoading.value = false
|
contentLoading.value = false
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function truncateRichText(richText, maxLength) {
|
function truncateRichText(richText, maxLength) {
|
||||||
// 去除标签
|
// 去除标签
|
||||||
const plainText = richText.replace(/<[^>]+>/g, '');
|
const plainText = richText.replace(/<[^>]+>/g, '')
|
||||||
|
|
||||||
// 截取最多 maxLength 个字符
|
// 截取最多 maxLength 个字符
|
||||||
const truncatedText = plainText.substring(0, maxLength);
|
const truncatedText = plainText.substring(0, maxLength)
|
||||||
|
|
||||||
return truncatedText;
|
return truncatedText
|
||||||
}
|
}
|
||||||
|
|
||||||
function replacePlaceholder(originalText, replacement) {
|
function replacePlaceholder(originalText, replacement) {
|
||||||
return originalText.replace('{0}', replacement);
|
return originalText.replace('{0}', replacement)
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendMessage = async () => {
|
const sendMessage = async () => {
|
||||||
|
|
@ -290,8 +277,6 @@ const sendMessage = async () => {
|
||||||
const { responseText } = xhr
|
const { responseText } = xhr
|
||||||
if (xhr.status == 200) {
|
if (xhr.status == 200) {
|
||||||
const arr = parseEventMessages(responseText)
|
const arr = parseEventMessages(responseText)
|
||||||
const { conversation_id, message_id } = arr[0]
|
|
||||||
|
|
||||||
const msg = arr.reduce((acc, item) => {
|
const msg = arr.reduce((acc, item) => {
|
||||||
return acc + item.text
|
return acc + item.text
|
||||||
}, '')
|
}, '')
|
||||||
|
|
@ -370,15 +355,26 @@ function scrollToBottomIfAtBottom() {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
:deep(.ant-input-group) {
|
.cu-field-ai {
|
||||||
.ant-input-group-addon {
|
height: 100% !important;
|
||||||
height: 100%;
|
background-color: #1e1f21;
|
||||||
// display: inline-block;
|
color: #fff;
|
||||||
&:last-child {
|
border: none;
|
||||||
padding: 0;
|
border-radius: 4px;
|
||||||
|
height: 52px;
|
||||||
|
font-size: 27px;
|
||||||
|
line-height: 52px;
|
||||||
|
padding: 0 10px;
|
||||||
|
input {
|
||||||
|
color: white !important;
|
||||||
|
&::placeholder {
|
||||||
|
color: #ffffff41 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.van-field__body {
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.options {
|
.options {
|
||||||
.active {
|
.active {
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,10 @@
|
||||||
:data="item"
|
:data="item"
|
||||||
></CardItem>
|
></CardItem>
|
||||||
</div>
|
</div>
|
||||||
|
<AiAssistant />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
@ -34,6 +37,7 @@ import bg01 from '@/assets/images/insights_01.jpg'
|
||||||
import bg02 from '@/assets/images/insights_02.jpg'
|
import bg02 from '@/assets/images/insights_02.jpg'
|
||||||
import bg03 from '@/assets/images/insights_03.jpg'
|
import bg03 from '@/assets/images/insights_03.jpg'
|
||||||
import bg04 from '@/assets/images/insights_04.jpg'
|
import bg04 from '@/assets/images/insights_04.jpg'
|
||||||
|
import AiAssistant from '@/views/chat/components/ai-assistant-float.vue'
|
||||||
const list = [
|
const list = [
|
||||||
{
|
{
|
||||||
icon: '法律法规',
|
icon: '法律法规',
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ import 'swiper/css/navigation'
|
||||||
|
|
||||||
const modules = [Mousewheel, Navigation, EffectCoverflow]
|
const modules = [Mousewheel, Navigation, EffectCoverflow]
|
||||||
|
|
||||||
const pagetSize = 100
|
const pagetSize = 20
|
||||||
const weekNewsPage = ref(1)
|
const weekNewsPage = ref(1)
|
||||||
const weekNewsList = ref([])
|
const weekNewsList = ref([])
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue