105 lines
		
	
	
		
			2.2 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
		
		
			
		
	
	
			105 lines
		
	
	
		
			2.2 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
|  | <template> | ||
|  |   <div class="message-stream"> | ||
|  |     {{ displayedContent }}<span v-if="!isFinished" class="blinking-cursor" /> | ||
|  |   </div> | ||
|  | </template> | ||
|  | 
 | ||
|  | <script lang="ts"> | ||
|  | import vue from "../adapter-vue"; | ||
|  | import { customerServicePayloadType } from "../interface"; | ||
|  | 
 | ||
|  | const { ref, watchEffect, onBeforeUnmount, onMounted } = vue; | ||
|  | 
 | ||
|  | interface Props { | ||
|  |   payload: customerServicePayloadType; | ||
|  | } | ||
|  | 
 | ||
|  | export default { | ||
|  |   props: { | ||
|  |     payload: { | ||
|  |       type: Object as () => customerServicePayloadType, | ||
|  |       default: () => ({}), | ||
|  |     }, | ||
|  |   }, | ||
|  |   setup(props: Props) { | ||
|  |     const content = ref<string>(""); | ||
|  |     const displayedContent = ref<string>(""); | ||
|  |     const isFinished = ref<boolean>(false); | ||
|  |     let intervalId: number | null = null; | ||
|  |     let currentIndex = 0; | ||
|  | 
 | ||
|  |     const updateDisplayedContent = () => { | ||
|  |       if (intervalId) { | ||
|  |         window.clearInterval(intervalId); | ||
|  |       } | ||
|  |       intervalId = window.setInterval(() => { | ||
|  |         if (currentIndex < content.value.length) { | ||
|  |           displayedContent.value += content.value[currentIndex]; | ||
|  |           currentIndex++; | ||
|  |         } else { | ||
|  |           window.clearInterval(intervalId!); | ||
|  |           intervalId = null; | ||
|  |         } | ||
|  |       }, 50); | ||
|  |     }; | ||
|  | 
 | ||
|  |     onMounted(() => { | ||
|  |       content.value = props?.payload?.chunks?.join("") ?? ""; | ||
|  |       displayedContent.value = content.value; | ||
|  |       currentIndex = content.value.length; | ||
|  |     }); | ||
|  | 
 | ||
|  |     watchEffect(() => { | ||
|  |       const newContent = props?.payload?.chunks?.join("") ?? ""; | ||
|  |       if (newContent.length > currentIndex) { | ||
|  |         content.value = newContent; | ||
|  |         updateDisplayedContent(); | ||
|  |       } | ||
|  |     }); | ||
|  | 
 | ||
|  |     watchEffect(() => { | ||
|  |       isFinished.value = props?.payload?.isFinished === 1; | ||
|  |     }); | ||
|  | 
 | ||
|  |     onBeforeUnmount(() => { | ||
|  |       if (intervalId) { | ||
|  |         window.clearInterval(intervalId); | ||
|  |       } | ||
|  |     }); | ||
|  | 
 | ||
|  |     return { | ||
|  |       content, | ||
|  |       props, | ||
|  |       isFinished, | ||
|  |       displayedContent, | ||
|  |     }; | ||
|  |   }, | ||
|  | }; | ||
|  | </script> | ||
|  | <style lang="scss" scoped> | ||
|  | .message-stream { | ||
|  |   word-break: break-all; | ||
|  |   font-size: 14px; | ||
|  | 
 | ||
|  |   .blinking-cursor { | ||
|  |     display: inline-block; | ||
|  |     width: 1px; | ||
|  |     height: 16px; | ||
|  |     background-color: black; | ||
|  |     animation: blink 1s step-end infinite; | ||
|  |     vertical-align: sub; | ||
|  |   } | ||
|  | 
 | ||
|  |   @keyframes blink { | ||
|  |     0%, | ||
|  |     100% { | ||
|  |       background-color: transparent; | ||
|  |     } | ||
|  | 
 | ||
|  |     50% { | ||
|  |       background-color: black; | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | </style> |