<script setup lang="ts">import { useDocusI18n } from '../../../../app/composables/useDocusI18n' const { t } = useDocusI18n() const size = 4const dotSize = 2const gap = 2const totalDots = size * size const patterns = [ [[0], [1], [2], [3], [7], [11], [15], [14], [13], [12], [8], [4], [5], [6], [10], [9]], [[0, 4, 8, 12], [1, 5, 9, 13], [2, 6, 10, 14], [3, 7, 11, 15]], [[5, 6, 9, 10], [1, 4, 7, 8, 11, 14], [0, 3, 12, 15], [1, 4, 7, 8, 11, 14], [5, 6, 9, 10]], [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]], [[0], [3], [15], [12]], [[5, 6, 9, 10], [1, 2, 4, 7, 8, 11, 13, 14], [0, 3, 12, 15]], [[0], [1], [2], [3], [7], [6], [5], [4], [8], [9], [10], [11], [15], [14], [13], [12]], [[0], [1, 4], [2, 5, 8], [3, 6, 9, 12], [7, 10, 13], [11, 14], [15]],] const activeDots = ref<Set<number>>(new Set())let patternIndex = 0let stepIndex = 0 function nextStep() { const pattern = patterns[patternIndex] if (!pattern) return activeDots.value = new Set(pattern[stepIndex]) stepIndex++ if (stepIndex >= pattern.length) { stepIndex = 0 patternIndex = (patternIndex + 1) % patterns.length }} const statusMessages = computed(() => [ t('assistant.loading.thinking'), t('assistant.loading.searching'), t('assistant.loading.reading'), t('assistant.loading.analyzing'),])const currentIndex = ref(0)const displayedText = ref(statusMessages.value[0]!)const chars = 'abcdefghijklmnopqrstuvwxyz' function scramble(from: string, to: string) { const maxLength = Math.max(from.length, to.length) let frame = 0 const totalFrames = 15 const step = () => { frame++ let result = '' const progress = (frame / totalFrames) * maxLength for (let i = 0; i < maxLength; i++) { if (i < progress - 2) { result += to[i] || '' } else if (i < progress) { result += chars[Math.floor(Math.random() * chars.length)] } else { result += from[i] || '' } } displayedText.value = result if (frame < totalFrames) { requestAnimationFrame(step) } else { displayedText.value = to } } requestAnimationFrame(step)} let matrixInterval: ReturnType<typeof setInterval> | undefinedlet textInterval: ReturnType<typeof setInterval> | undefined onMounted(() => { nextStep() matrixInterval = setInterval(nextStep, 120) textInterval = setInterval(() => { const prev = displayedText.value currentIndex.value = (currentIndex.value + 1) % statusMessages.value.length scramble(prev, statusMessages.value[currentIndex.value]!) }, 3500)}) onUnmounted(() => { clearInterval(matrixInterval) clearInterval(textInterval)})</script> <template> <div class="flex items-center text-sm text-muted overflow-hidden"> <div class="shrink-0 mr-2 grid" :style="{ gridTemplateColumns: `repeat(${size}, 1fr)`, gap: `${gap}px`, width: `${size * dotSize + (size - 1) * gap}px`, height: `${size * dotSize + (size - 1) * gap}px`, }" > <span v-for="i in totalDots" :key="i" class="rounded-[0.5px] bg-current transition-opacity duration-100" :class="activeDots.has(i - 1) ? 'opacity-100' : 'opacity-20'" :style="{ width: `${dotSize}px`, height: `${dotSize}px` }" /> </div> <UChatShimmer :text="displayedText" /> </div></template>
<script setup lang="ts">
import { useDocusI18n } from '../../../../app/composables/useDocusI18n'
const { t } = useDocusI18n()
const size = 4
const dotSize = 2
const gap = 2
const totalDots = size * size
const patterns = [
[[0], [1], [2], [3], [7], [11], [15], [14], [13], [12], [8], [4], [5], [6], [10], [9]],
[[0, 4, 8, 12], [1, 5, 9, 13], [2, 6, 10, 14], [3, 7, 11, 15]],
[[5, 6, 9, 10], [1, 4, 7, 8, 11, 14], [0, 3, 12, 15], [1, 4, 7, 8, 11, 14], [5, 6, 9, 10]],
[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]],
[[0], [3], [15], [12]],
[[5, 6, 9, 10], [1, 2, 4, 7, 8, 11, 13, 14], [0, 3, 12, 15]],
[[0], [1], [2], [3], [7], [6], [5], [4], [8], [9], [10], [11], [15], [14], [13], [12]],
[[0], [1, 4], [2, 5, 8], [3, 6, 9, 12], [7, 10, 13], [11, 14], [15]],
]
const activeDots = ref<Set<number>>(new Set())
let patternIndex = 0
let stepIndex = 0
function nextStep() {
const pattern = patterns[patternIndex]
if (!pattern) return
activeDots.value = new Set(pattern[stepIndex])
stepIndex++
if (stepIndex >= pattern.length) {
stepIndex = 0
patternIndex = (patternIndex + 1) % patterns.length
}
}
const statusMessages = computed(() => [
t('assistant.loading.thinking'),
t('assistant.loading.searching'),
t('assistant.loading.reading'),
t('assistant.loading.analyzing'),
])
const currentIndex = ref(0)
const displayedText = ref(statusMessages.value[0]!)
const chars = 'abcdefghijklmnopqrstuvwxyz'
function scramble(from: string, to: string) {
const maxLength = Math.max(from.length, to.length)
let frame = 0
const totalFrames = 15
const step = () => {
frame++
let result = ''
const progress = (frame / totalFrames) * maxLength
for (let i = 0; i < maxLength; i++) {
if (i < progress - 2) {
result += to[i] || ''
}
else if (i < progress) {
result += chars[Math.floor(Math.random() * chars.length)]
}
else {
result += from[i] || ''
}
}
displayedText.value = result
if (frame < totalFrames) {
requestAnimationFrame(step)
}
else {
displayedText.value = to
}
}
requestAnimationFrame(step)
}
let matrixInterval: ReturnType<typeof setInterval> | undefined
let textInterval: ReturnType<typeof setInterval> | undefined
onMounted(() => {
nextStep()
matrixInterval = setInterval(nextStep, 120)
textInterval = setInterval(() => {
const prev = displayedText.value
currentIndex.value = (currentIndex.value + 1) % statusMessages.value.length
scramble(prev, statusMessages.value[currentIndex.value]!)
}, 3500)
})
onUnmounted(() => {
clearInterval(matrixInterval)
clearInterval(textInterval)
})
</script>
<template>
<div class="flex items-center text-sm text-muted overflow-hidden">
<div
class="shrink-0 mr-2 grid"
:style="{
gridTemplateColumns: `repeat(${size}, 1fr)`,
gap: `${gap}px`,
width: `${size * dotSize + (size - 1) * gap}px`,
height: `${size * dotSize + (size - 1) * gap}px`,
}"
>
<span
v-for="i in totalDots"
:key="i"
class="rounded-[0.5px] bg-current transition-opacity duration-100"
:class="activeDots.has(i - 1) ? 'opacity-100' : 'opacity-20'"
:style="{ width: `${dotSize}px`, height: `${dotSize}px` }"
/>
</div>
<UChatShimmer :text="displayedText" />
</div>
</template>