docs: 补充 AI 打字机输出、小程序灵感卡片、脚本主页布局等设计文档和计划
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
export const useTypewriterStream = ({ interval = 24, step = 1 } = {}) => {
|
||||
const visibleText = ref('')
|
||||
const targetText = ref('')
|
||||
const received = ref(false)
|
||||
const backendDone = ref(false)
|
||||
const failed = ref(false)
|
||||
let timer = null
|
||||
let waiters = []
|
||||
|
||||
const isWaiting = computed(() => !received.value && !backendDone.value && !failed.value)
|
||||
const isStreaming = computed(() => received.value && (visibleText.value.length < targetText.value.length || !backendDone.value))
|
||||
const isDraining = computed(() => backendDone.value && visibleText.value.length < targetText.value.length)
|
||||
const isDone = computed(() => backendDone.value && visibleText.value.length >= targetText.value.length && !failed.value)
|
||||
|
||||
const stopTimer = () => {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
|
||||
const resolveWaiters = () => {
|
||||
if (!waiters.length) return
|
||||
const currentWaiters = waiters
|
||||
waiters = []
|
||||
currentWaiters.forEach(resolve => resolve(visibleText.value))
|
||||
}
|
||||
|
||||
const isFullyRendered = () => {
|
||||
return backendDone.value && visibleText.value.length >= targetText.value.length
|
||||
}
|
||||
|
||||
const tick = () => {
|
||||
if (visibleText.value.length < targetText.value.length) {
|
||||
const nextLength = Math.min(targetText.value.length, visibleText.value.length + step)
|
||||
visibleText.value = targetText.value.slice(0, nextLength)
|
||||
return
|
||||
}
|
||||
if (backendDone.value || failed.value) {
|
||||
stopTimer()
|
||||
if (isFullyRendered() || failed.value) {
|
||||
resolveWaiters()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ensureTimer = () => {
|
||||
if (!timer) {
|
||||
timer = setInterval(tick, interval)
|
||||
}
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
stopTimer()
|
||||
visibleText.value = ''
|
||||
targetText.value = ''
|
||||
received.value = false
|
||||
backendDone.value = false
|
||||
failed.value = false
|
||||
resolveWaiters()
|
||||
}
|
||||
|
||||
const push = (nextText = '') => {
|
||||
const next = String(nextText || '')
|
||||
if (next.length < visibleText.value.length) {
|
||||
reset()
|
||||
}
|
||||
received.value = true
|
||||
targetText.value = next
|
||||
ensureTimer()
|
||||
}
|
||||
|
||||
const finish = (finalText) => {
|
||||
if (typeof finalText === 'string') {
|
||||
targetText.value = finalText
|
||||
}
|
||||
backendDone.value = true
|
||||
if (targetText.value.length > visibleText.value.length) {
|
||||
ensureTimer()
|
||||
} else {
|
||||
stopTimer()
|
||||
resolveWaiters()
|
||||
}
|
||||
}
|
||||
|
||||
const fail = (message) => {
|
||||
failed.value = true
|
||||
backendDone.value = true
|
||||
if (!visibleText.value && message) {
|
||||
visibleText.value = message
|
||||
targetText.value = message
|
||||
}
|
||||
stopTimer()
|
||||
resolveWaiters()
|
||||
}
|
||||
|
||||
const waitForDone = () => {
|
||||
if (isFullyRendered() || failed.value) {
|
||||
return Promise.resolve(visibleText.value)
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
waiters.push(resolve)
|
||||
ensureTimer()
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
visibleText,
|
||||
targetText,
|
||||
isWaiting,
|
||||
isStreaming,
|
||||
isDraining,
|
||||
isDone,
|
||||
push,
|
||||
finish,
|
||||
waitForDone,
|
||||
fail,
|
||||
reset,
|
||||
dispose: stopTimer
|
||||
}
|
||||
}
|
||||
|
||||
export default useTypewriterStream
|
||||
Reference in New Issue
Block a user