2023-10-11 20:39:47 +08:00
|
|
|
|
<script setup lang='ts'>
|
2023-10-12 17:00:46 +08:00
|
|
|
|
import { computed, reactive, ref } from 'vue'
|
2023-10-11 20:39:47 +08:00
|
|
|
|
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
|
2023-10-12 17:00:46 +08:00
|
|
|
|
mp3?: Array<any>
|
2023-10-11 20:39:47 +08:00
|
|
|
|
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('复制失败')
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-12 16:06:59 +08:00
|
|
|
|
|
2023-10-12 19:04:09 +08:00
|
|
|
|
async function radioPlay() {
|
2023-10-12 17:00:46 +08:00
|
|
|
|
console.log('播放', props.mp3)
|
2023-10-12 19:04:09 +08:00
|
|
|
|
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)
|
|
|
|
|
}
|
2023-10-12 17:00:46 +08:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-12 19:04:09 +08:00
|
|
|
|
await promise()
|
|
|
|
|
|
|
|
|
|
// 监听WebSocket关闭事件
|
|
|
|
|
socket.onclose = (event: any) => {
|
|
|
|
|
console.log('连接已关闭: ', event)
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-12 19:07:13 +08:00
|
|
|
|
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触控采样率。"
|
|
|
|
|
|
|
|
|
|
}))
|
2023-10-12 19:04:09 +08:00
|
|
|
|
|
|
|
|
|
// 监听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()
|
2023-10-12 17:00:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const onAudioEnd = (index: any) => {
|
|
|
|
|
if (index + 1 < audioElements.length)
|
|
|
|
|
audioElements[index + 1].play()
|
2023-10-12 16:06:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建音频对象的数组
|
2023-10-12 17:00:46 +08:00
|
|
|
|
const audioElements = reactive([])
|
|
|
|
|
// 播放音频
|
|
|
|
|
const playAudio = () => {
|
|
|
|
|
// for (let i = 0; i < audioElements.length; i++)
|
|
|
|
|
audioElements[0].play()
|
|
|
|
|
}
|
2023-10-12 16:06:59 +08:00
|
|
|
|
|
2023-10-12 17:00:46 +08:00
|
|
|
|
// 暂停音频
|
|
|
|
|
const pauseAudio = () => {
|
|
|
|
|
for (let i = 0; i < audioElements.length; i++)
|
|
|
|
|
audioElements[i].pause()
|
|
|
|
|
}
|
2023-10-11 20:39:47 +08:00
|
|
|
|
</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
|
2023-10-12 19:04:09 +08:00
|
|
|
|
class=" items-end gap-1 mt-2"
|
2023-10-11 20:39:47 +08:00
|
|
|
|
:class="[inversion ? 'flex-row-reverse' : 'flex-row']"
|
|
|
|
|
>
|
|
|
|
|
<TextComponent
|
|
|
|
|
ref="textRef"
|
|
|
|
|
:inversion="inversion"
|
|
|
|
|
:error="error"
|
|
|
|
|
:text="text"
|
|
|
|
|
:loading="loading"
|
|
|
|
|
:as-raw-text="asRawText"
|
|
|
|
|
/>
|
2023-10-12 19:04:09 +08:00
|
|
|
|
<div class="flex-col btn">
|
2023-10-11 20:39:47 +08:00
|
|
|
|
<button
|
|
|
|
|
v-if="!inversion"
|
2023-10-12 19:04:09 +08:00
|
|
|
|
class="mr-2 transition text-neutral-300 hover:text-neutral-800 dark:hover:text-neutral-300"
|
|
|
|
|
@click="radioPlay"
|
2023-10-11 20:39:47 +08:00
|
|
|
|
>
|
2023-10-12 19:04:09 +08:00
|
|
|
|
播放
|
|
|
|
|
<!-- <SvgIcon icon="ri:restart-line" /> -->
|
2023-10-12 16:06:59 +08:00
|
|
|
|
</button>
|
2023-10-12 17:00:46 +08:00
|
|
|
|
<button
|
2023-10-12 16:06:59 +08:00
|
|
|
|
v-if="!inversion"
|
2023-10-12 19:04:09 +08:00
|
|
|
|
class="mr-2 transition text-neutral-300 hover:text-neutral-800 dark:hover:text-neutral-300"
|
|
|
|
|
@click="handleRegenerate"
|
2023-10-12 16:06:59 +08:00
|
|
|
|
>
|
2023-10-12 19:04:09 +08:00
|
|
|
|
<SvgIcon icon="ri:restart-line" />
|
2023-10-11 20:39:47 +08:00
|
|
|
|
</button>
|
|
|
|
|
<NDropdown
|
2023-10-12 19:04:09 +08:00
|
|
|
|
v-if="!inversion"
|
2023-10-11 20:39:47 +08:00
|
|
|
|
: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>
|
2023-10-12 19:04:09 +08:00
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.btn{
|
|
|
|
|
padding: 10px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|