Add File
This commit is contained in:
88
frontend/src/views/chat/typed.ts
Normal file
88
frontend/src/views/chat/typed.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { computed, onWatcherCleanup, unref, watch, ref } from 'vue'
|
||||
import type { Ref, VNode } from 'vue'
|
||||
|
||||
function isString(str: any): str is string {
|
||||
return typeof str === 'string'
|
||||
}
|
||||
|
||||
function useState<T, R = Ref<T>>(defaultStateValue?: T | (() => T)): [R, (val: T) => void] {
|
||||
const initValue: T =
|
||||
typeof defaultStateValue === 'function' ? (defaultStateValue as any)() : defaultStateValue
|
||||
|
||||
const innerValue = ref(initValue) as Ref<T>
|
||||
|
||||
function triggerChange(newValue: T) {
|
||||
innerValue.value = newValue
|
||||
}
|
||||
|
||||
return [innerValue as unknown as R, triggerChange]
|
||||
}
|
||||
/**
|
||||
* Return typed content and typing status when typing is enabled.
|
||||
* Or return content directly.
|
||||
*/
|
||||
const useTypedEffect = (
|
||||
content: Ref<VNode | object | string>,
|
||||
typingEnabled: Ref<boolean>,
|
||||
typingStep: Ref<number>,
|
||||
typingInterval: Ref<number>
|
||||
): [typedContent: Ref<VNode | object | string>, isTyping: Ref<boolean>] => {
|
||||
const [prevContent, setPrevContent] = useState<VNode | object | string>('')
|
||||
const [typingIndex, setTypingIndex] = useState<number>(1)
|
||||
|
||||
const mergedTypingEnabled = computed(() => typingEnabled.value && isString(content.value))
|
||||
|
||||
// Reset typing index when content changed
|
||||
watch(content, () => {
|
||||
const prevContentValue = unref(prevContent)
|
||||
setPrevContent(content.value)
|
||||
if (!mergedTypingEnabled.value && isString(content.value)) {
|
||||
setTypingIndex(content.value.length)
|
||||
} else if (
|
||||
isString(content.value) &&
|
||||
isString(prevContentValue) &&
|
||||
content.value.indexOf(prevContentValue) !== 0
|
||||
) {
|
||||
setTypingIndex(1)
|
||||
}
|
||||
})
|
||||
|
||||
// Start typing
|
||||
watch(
|
||||
[typingIndex, typingEnabled, content],
|
||||
() => {
|
||||
if (
|
||||
mergedTypingEnabled.value &&
|
||||
isString(content.value) &&
|
||||
unref(typingIndex) < content.value.length
|
||||
) {
|
||||
const id = setTimeout(() => {
|
||||
setTypingIndex(unref(typingIndex) + typingStep.value)
|
||||
}, typingInterval.value)
|
||||
|
||||
onWatcherCleanup(() => {
|
||||
clearTimeout(id)
|
||||
})
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const mergedTypingContent = computed(() =>
|
||||
mergedTypingEnabled.value && isString(content.value)
|
||||
? content.value.slice(0, unref(typingIndex))
|
||||
: content.value
|
||||
)
|
||||
|
||||
return [
|
||||
mergedTypingContent,
|
||||
computed(
|
||||
() =>
|
||||
mergedTypingEnabled.value &&
|
||||
isString(content.value) &&
|
||||
unref(typingIndex) < content.value.length
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
export default useTypedEffect
|
||||
Reference in New Issue
Block a user