Skip to main content
Glama
ScrollingOutputLine.vue3.3 kB
<template> <div class="flex flex-row items-center"> <Icon v-if="props.prependSiLogo" name="logo-si" size="sm" :class="clsx('mr-xs self-start', !isActive && 'opacity-0')" /> <div class="leading-[1.5rem] tracking-tight"> <span :class=" clsx(!isLoader && isActive ? 'text-neutral-100' : 'text-neutral-400') " > {{ cropSentence ? croppedSentence : message }} </span> <div v-if="isActive" :class=" clsx( // Since this is an inline cursor like element, there's some finicky positioning required to make it look good 'w-xs h-sm inline-block relative top-0.5 left-0.5', 'bg-white animate-pulse [animation-duration:_1s]', ) " ></div> </div> </div> </template> <script lang="ts" setup> import { Icon } from "@si/vue-lib/design-system"; import { computed, onMounted, ref, watch } from "vue"; import clsx from "clsx"; import { sleep } from "@si/ts-lib/src/async-sleep"; const props = defineProps({ message: { type: String, required: true }, // On active lines, show SI logo before it. If not active, reserve some space for it for alignment. prependSiLogo: { type: Boolean }, // Shows SI logo to the left of the line (if enabled) and draws text in active colors. If false, color will be greyer isActive: { type: Boolean }, // When loader, will be shown in secondary greyer color, and without the initial delay isLoader: { type: Boolean }, // When the user goes out of the tab then comes back, we have an issue where multiple lines will be animating at the same time. // this is because this component is mounted only at that time. This flag prevents that by forcing animate only on the last line. isLastElement: { type: Boolean }, // Scroll the characters very fast, without the initial delay. fast: { type: Boolean }, }); const visibleCharCount = ref(-1); const croppedSentence = computed(() => props.message.slice(0, Math.max(visibleCharCount.value, 0)), ); // Fuse so we don't animate when message contents change after we're done rendering. const finishedRendering = ref(false); const cropSentence = computed( () => props.isLastElement && !finishedRendering.value, ); // Kick-off line animation const startWritingSentence = async () => { visibleCharCount.value = 0; }; onMounted(startWritingSentence); // Write character one at a time, enqueuing the next change with a watch that sleeps const INITIAL_DELAY_MS = 800; const CHAR_DELAY_MS = props.fast ? 15 : 35; const BASE_ELLIPSIS_DELAY_MS = 300; const ellipsisDelay = () => BASE_ELLIPSIS_DELAY_MS + Math.floor(Math.random() * 100); watch([visibleCharCount], async () => { const message = props.message ?? ""; if (visibleCharCount.value >= message.length) { finishedRendering.value = true; return; } const latestChar = message[visibleCharCount.value - 1] ?? ""; let delay = CHAR_DELAY_MS; // Add some extra delay on the initial character, except if the line is a loader if (!props.fast && !props.isLoader && visibleCharCount.value === 0) { delay = INITIAL_DELAY_MS; } else if (latestChar === ".") { delay = ellipsisDelay(); } await sleep(delay); visibleCharCount.value += 1; }); </script>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server