dataV-xunfei/src/views/chat/components/Message/index.vue

225 lines
6.0 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang='ts'>
import { computed, reactive, ref } from 'vue'
import { NDropdown, useMessage } from 'naive-ui'
import AvatarComponent from './Avatar.vue'
import TextComponent from './Text.vue'
import { SvgIcon } from '@/components/common'
import { useIconRender } from '@/hooks/useIconRender'
import { t } from '@/locales'
import { useBasicLayout } from '@/hooks/useBasicLayout'
import { copyToClip } from '@/utils/copy'
interface Props {
dateTime?: string
text?: string
mp3?: Array<any>
inversion?: boolean
error?: boolean
loading?: boolean
}
interface Emit {
(ev: 'regenerate'): void
(ev: 'delete'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
const { isMobile } = useBasicLayout()
const { iconRender } = useIconRender()
const message = useMessage()
const textRef = ref<HTMLElement>()
const asRawText = ref(props.inversion)
const messageRef = ref<HTMLElement>()
const options = computed(() => {
const common = [
{
label: t('chat.copy'),
key: 'copyText',
icon: iconRender({ icon: 'ri:file-copy-2-line' }),
},
{
label: t('common.delete'),
key: 'delete',
icon: iconRender({ icon: 'ri:delete-bin-line' }),
},
]
if (!props.inversion) {
common.unshift({
label: asRawText.value ? t('chat.preview') : t('chat.showRawText'),
key: 'toggleRenderType',
icon: iconRender({ icon: asRawText.value ? 'ic:outline-code-off' : 'ic:outline-code' }),
})
}
return common
})
function handleSelect(key: 'copyText' | 'delete' | 'toggleRenderType') {
switch (key) {
case 'copyText':
handleCopy()
return
case 'toggleRenderType':
asRawText.value = !asRawText.value
return
case 'delete':
emit('delete')
}
}
function handleRegenerate() {
messageRef.value?.scrollIntoView()
emit('regenerate')
}
async function handleCopy() {
try {
await copyToClip(props.text || '')
message.success('复制成功')
}
catch {
message.error('复制失败')
}
}
async function radioPlay() {
console.log('播放', props.mp3)
const socket = new WebSocket('wss://chat.lihaink.cn/zhanti/tts');
const promise = () => {
return new Promise((resolve, reject) => {
// 监听WebSocket连接打开事件
socket.onopen = () => {
console.log('socket已连接')
resolve(null)
}
})
}
await promise()
// 监听WebSocket关闭事件
socket.onclose = (event: any) => {
console.log('连接已关闭: ', event)
}
socket.send(JSON.stringify( {
            "data": "快科技10月12日消息不宣而发的华为Mate 60在上架官方商城后直到现在都处于一机难求的状态。由于Mate 60系列的爆火华为也是将明年的预计手机出货量翻倍到了7000万台。 华为Mate 60的热销对同期上市的iPhone15产生了很大的冲击。不仅对华为品牌起到提振效果也对上游国内相关企业产生了积极的影响。 其中华为Mate 60采用了6.69英寸的OLED柔性屏幕分辨率为FHD+ 2688×1216。Mate 60 Pro则采用了6.82英寸的四曲面屏幕分辨率为FHD+ 2720 × 1260。这两款手机屏幕都支持1-120Hz LTPO自适应刷新率、1440Hz高频PWM调光以300 Hz触控采样率。"
        }))
// 监听WebSocket接收消息事件
socket.onmessage = (event: any) => {
const msg = JSON.parse(event.data);
console.log(msg.mp3);
}
// for (let i = 0; i < props.mp3.length; i++) {
// const a = new Audio(props.mp3[i])
// a.addEventListener('ended', () => {
// onAudioEnd(i)
// })
// audioElements.push(a)
// }
// playAudio()
}
const onAudioEnd = (index: any) => {
if (index + 1 < audioElements.length)
audioElements[index + 1].play()
}
// 创建音频对象的数组
const audioElements = reactive([])
// 播放音频
const playAudio = () => {
// for (let i = 0; i < audioElements.length; i++)
audioElements[0].play()
}
// 暂停音频
const pauseAudio = () => {
for (let i = 0; i < audioElements.length; i++)
audioElements[i].pause()
}
</script>
<template>
<div
ref="messageRef"
class="flex w-full mb-6 overflow-hidden"
:class="[{ 'flex-row-reverse': inversion }]"
>
<div
class="flex items-center justify-center flex-shrink-0 h-8 overflow-hidden rounded-full basis-8"
:class="[inversion ? 'ml-2' : 'mr-2']"
>
<AvatarComponent :image="inversion" />
</div>
<div class="overflow-hidden text-sm " :class="[inversion ? 'items-end' : 'items-start']">
<p class="text-xs text-[#b4bbc4]" :class="[inversion ? 'text-right' : 'text-left']">
{{ dateTime }}
</p>
<div
class=" items-end gap-1 mt-2"
:class="[inversion ? 'flex-row-reverse' : 'flex-row']"
>
<TextComponent
ref="textRef"
:inversion="inversion"
:error="error"
:text="text"
:loading="loading"
:as-raw-text="asRawText"
/>
<div class="flex-col btn">
<button
v-if="!inversion"
class="mr-2 transition text-neutral-300 hover:text-neutral-800 dark:hover:text-neutral-300"
@click="radioPlay"
>
播放
<!-- <SvgIcon icon="ri:restart-line" /> -->
</button>
<button
v-if="!inversion"
class="mr-2 transition text-neutral-300 hover:text-neutral-800 dark:hover:text-neutral-300"
@click="handleRegenerate"
>
<SvgIcon icon="ri:restart-line" />
</button>
<NDropdown
v-if="!inversion"
:trigger="isMobile ? 'click' : 'hover'"
:placement="!inversion ? 'right' : 'left'"
:options="options"
@select="handleSelect"
>
<button class="transition text-neutral-300 hover:text-neutral-800 dark:hover:text-neutral-200">
<SvgIcon icon="ri:more-2-fill" />
</button>
</NDropdown>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.btn{
padding: 10px;
}
</style>