Files
aigen/components/InfoTip.vue
T
TowstyandCursor 78b158471b Add ACE-Step 1.5 and YuE on Music, and put Video/Image/Music cards back.
Recover finished Comfy audio when queueing hangs so a completed track is not stuck on Queueing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 07:42:54 -05:00

110 lines
3.1 KiB
Vue

<template>
<div
ref="root"
class="relative inline-flex shrink-0"
@mouseenter="open"
@mouseleave="scheduleClose"
@focusin="open"
@focusout="onFocusOut"
>
<button
type="button"
class="flex h-6 w-6 items-center justify-center rounded-full border border-white/15 text-zinc-400 hover:border-amber-300/50 hover:text-amber-200"
:aria-label="ariaLabel"
:aria-expanded="show"
@click.prevent.stop="show = !show"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="h-3.5 w-3.5" aria-hidden="true">
<circle cx="12" cy="12" r="10" />
<path d="M12 16v-4M12 8h.01" stroke-linecap="round" />
</svg>
</button>
<Teleport to="body">
<div
v-if="show"
ref="panel"
class="fixed z-[80] max-h-[min(20rem,50vh)] w-[min(28rem,calc(100vw-1.5rem))] overflow-y-auto overscroll-contain whitespace-pre-wrap break-words rounded-xl border border-white/10 bg-zinc-950 p-3 text-xs leading-relaxed text-zinc-300 shadow-2xl"
:style="panelStyle"
@mouseenter="open"
@mouseleave="scheduleClose"
@click.stop
>
{{ text }}
</div>
</Teleport>
</div>
</template>
<script setup lang="ts">
defineProps<{
text: string
ariaLabel: string
}>()
const root = ref<HTMLElement | null>(null)
const panel = ref<HTMLElement | null>(null)
const show = ref(false)
const panelStyle = ref<Record<string, string>>({})
let closeTimer: ReturnType<typeof setTimeout> | null = null
function place() {
const el = root.value
if (!el) return
const rect = el.getBoundingClientRect()
const width = Math.min(448, window.innerWidth - 24)
const maxHeight = Math.min(320, window.innerHeight * 0.5)
let left = rect.right - width
left = Math.min(Math.max(12, left), window.innerWidth - width - 12)
const below = rect.bottom + 8
const above = rect.top - 8 - maxHeight
const top = below + maxHeight <= window.innerHeight - 12 ? below : Math.max(12, above)
panelStyle.value = {
top: `${Math.round(top)}px`,
left: `${Math.round(left)}px`,
width: `${Math.round(width)}px`
}
}
function open() {
if (closeTimer) {
clearTimeout(closeTimer)
closeTimer = null
}
show.value = true
nextTick(place)
}
function close() {
show.value = false
}
function scheduleClose() {
if (closeTimer) clearTimeout(closeTimer)
closeTimer = setTimeout(close, 160)
}
function onFocusOut(event: FocusEvent) {
const next = event.relatedTarget as Node | null
if (root.value?.contains(next) || panel.value?.contains(next)) return
scheduleClose()
}
watch(show, (value) => {
if (typeof window === 'undefined') return
if (value) {
window.addEventListener('resize', place)
window.addEventListener('scroll', place, true)
return
}
window.removeEventListener('resize', place)
window.removeEventListener('scroll', place, true)
})
onBeforeUnmount(() => {
if (closeTimer) clearTimeout(closeTimer)
if (typeof window === 'undefined') return
window.removeEventListener('resize', place)
window.removeEventListener('scroll', place, true)
})
</script>