feat(ui-loading-screens): updated boot log

This commit is contained in:
Neko Ayaka
2025-06-02 21:27:14 +08:00
parent c9e9ee4eda
commit 8931d54a67
3 changed files with 174 additions and 53 deletions
@@ -9,8 +9,8 @@ const props = withDefaults(defineProps<{
glitchAmplitude?: string
}>(), {
blinkSpeed: '2s',
flashFrequency: '0.005s',
glitchOffset: '4px',
flashFrequency: '0.06s',
glitchOffset: '1px',
blurAmount: '0.8px',
glitchAmplitude: '20deg',
})
@@ -1,5 +1,5 @@
<template>
<div class="glitch-content">
<div class="glitch-content" whitespace-pre-wrap>
<slot />
</div>
</template>
@@ -28,13 +28,13 @@
}
.glitch-content::before {
left: calc(var(--glitch-offset) * -0.1);
color: rgb(52, 0, 0);
left: calc(var(--glitch-offset) * -2);
color: rgba(255, 0, 0, 0.05);
}
.glitch-content::after {
left: calc(var(--glitch-offset) * 0.1);
color: rgb(0, 56, 56);
left: calc(var(--glitch-offset) * 1);
color: rgba(0, 0, 255, 0.2);
}
@keyframes glitch-skew {
@@ -1,7 +1,9 @@
<script setup lang="ts">
import type { Ref } from 'vue'
import { Rive } from '@rive-app/canvas-lite'
import { useDark } from '@vueuse/core'
import { onMounted, ref, watch } from 'vue'
import { breakpointsTailwind, useBreakpoints, useDark } from '@vueuse/core'
import { computed, onMounted, ref, watch } from 'vue'
import CircleFadeInAnimation from './assets/circle_blink_in_-_loading_(@proj-airi).riv?url'
import CRT from './CRT.vue'
@@ -11,6 +13,7 @@ interface WriteLineOptions {
renderSpeed?: number
pending?: boolean
onPendingCheck?: () => Promise<boolean>
withoutTimestamp?: boolean
}
interface ConsoleEntry {
@@ -21,6 +24,7 @@ interface ConsoleEntry {
error?: string
isTyping?: boolean
targetContent?: string
class?: string
}
const step = ref<string>('')
@@ -44,82 +48,194 @@ const PENDING_FRAMES = [
interface BootMessage {
template: string
typingSpeed?: number
withoutTimestamp?: boolean
pending?: boolean
onPendingCheck?: () => Promise<boolean>
class?: string
}
const bootMessages: BootMessage[] = [
const breakpoints = useBreakpoints(breakpointsTailwind)
const isDeviceSm = computed(() => breakpoints.smaller('sm').value)
/**
* Stick Letters
*
* From @link{https://www.asciiart.eu/text-to-ascii-art}
*/
const wideAsciiArt = computed(() => (`
__ __ __ __ ___ __ ___ __
|__) |__) / \\ | |__ / \` | /\\ | |__) |
| | \\ \\__/ \\__/ |___ \\__, | /~~\\ | | \\ |
`))
const narrowAsciiArt = computed(() => (`
__ __
|__)_ _ . _ _|_ /\\ ||__)|
| | (_)|(-(_|_ /--\\|| \\ |
/
`))
const projectAIRIAsciiArt = computed(() => {
if (isDeviceSm.value)
return narrowAsciiArt.value
return wideAsciiArt.value
})
const projectAIRIMetadata = `
Project AIRI team from Moeru AI (https://moeru.ai) and other contributors
Open sourced on https://github.com/moeru-ai/airi
`
const bootMessages = computed<BootMessage[]>(() => [
...projectAIRIAsciiArt.value.split('\n').map(line => ({
template: line,
typingSpeed: 1,
withoutTimestamp: true,
})),
{
template: `Project AIRI version ${import.meta.env.VITE_AIRI_VERSION || '1.0.0'} ${import.meta.env.VITE_AIRI_COMMIT || '0240602'}`,
typingSpeed: 15,
template: `Project AIRI version ${import.meta.env.VITE_AIRI_VERSION || '1.0.0'} @ ${import.meta.env.VITE_AIRI_COMMIT || '0240602'} build`,
typingSpeed: 5,
withoutTimestamp: true,
},
...projectAIRIMetadata.trim().split('\n').map(line => ({
template: line,
typingSpeed: 1,
withoutTimestamp: true,
})),
{
template: '',
typingSpeed: 1,
withoutTimestamp: true,
},
{
template: 'Command line: BOOT_IMAGE=/boot/airi.moeru.ai root=UUID=airi-system',
typingSpeed: 10,
template: '',
typingSpeed: 1,
withoutTimestamp: true,
},
{
template: 'Command line: BOOT_IMAGE=/boot/airi.moeru.ai root=UUID=io.github.moeru-ai.airi',
typingSpeed: 1,
},
{
template: 'moeru-ai/NPU: Initialized power management',
typingSpeed: 20,
typingSpeed: 1,
},
{
template: 'Initializing AIRI subsystems...',
typingSpeed: 30,
typingSpeed: 1,
pending: true,
onPendingCheck: () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, 5000)
})
},
},
{
template: 'Loading initial ramdisk...',
typingSpeed: 25,
typingSpeed: 1,
pending: true,
onPendingCheck: () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, 5000)
})
},
},
{
template: 'Starting system services',
typingSpeed: 20,
template: 'Starting system services...',
typingSpeed: 1,
onPendingCheck: () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, 5000)
})
},
},
{
template: 'AIRI core services initialized',
typingSpeed: 20,
typingSpeed: 1,
},
{
template: 'Neural processing units detected',
typingSpeed: 20,
typingSpeed: 50,
},
{
template: 'Quantum interface online',
typingSpeed: 15,
typingSpeed: 1,
},
{
template: 'Consciousness waking up...',
typingSpeed: 40,
typingSpeed: 50,
pending: true,
onPendingCheck: () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, 5000)
})
},
},
{
template: 'Consciousness fully awakened',
typingSpeed: 35,
typingSpeed: 1,
},
{
template: 'Initiating Speech Recognition subsystem',
typingSpeed: 25,
template: 'Initiating Speech Recognition subsystem...',
typingSpeed: 50,
pending: true,
onPendingCheck: () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, 5000)
})
},
},
{
template: 'Speech recognition online',
typingSpeed: 25,
typingSpeed: 1,
},
{
template: 'Initiating Speaker voice',
typingSpeed: 25,
template: 'Initiating Speaker voice...',
typingSpeed: 50,
pending: true,
onPendingCheck: () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, 5000)
})
},
},
{
template: 'Speaker voice line tuned',
typingSpeed: 25,
typingSpeed: 1,
},
{
template: 'Initiating Vision downlink',
typingSpeed: 25,
template: 'Initiating Vision downlink...',
typingSpeed: 50,
pending: true,
onPendingCheck: () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, 5000)
})
},
},
{
template: 'Vision downlink offline',
typingSpeed: 25,
typingSpeed: 1,
},
{
template: 'AIRI ready',
typingSpeed: 50,
typingSpeed: 1,
},
]
])
const riveCanvas = ref<HTMLCanvasElement>()
const rive = ref<Rive>()
@@ -149,7 +265,7 @@ function handleUpdateDone(value: boolean) {
// Method to update typing speed for a specific message
function setMessageTypingSpeed(index: number, speed: number) {
if (index >= 0 && index < bootMessages.length && speed > 0) {
if (index >= 0 && index < bootMessages.value.length && speed > 0) {
bootMessages[index].typingSpeed = speed
}
}
@@ -168,19 +284,19 @@ function setTimeSpeed(multiplier: number) {
}
}
async function typeCharacters(entry: ConsoleEntry, text: string, speed: number) {
entry.isTyping = true
entry.targetContent = text
async function typeCharacters(entry: Ref<ConsoleEntry>, text: string, speed: number) {
entry.value.isTyping = true
entry.value.targetContent = text
for (let i = 0; i <= text.length; i++) {
if (!entry.isTyping)
if (!entry.value.isTyping)
break // Allow for interruption
entry.content = text.slice(0, i)
entry.value.content = text.slice(0, i)
await new Promise(resolve => setTimeout(resolve, speed))
}
entry.isTyping = false
entry.content = text // Ensure final state is complete
entry.value.isTyping = false
entry.value.content = text // Ensure final state is complete
}
async function writeLine<T extends any[]>(
@@ -191,7 +307,7 @@ async function writeLine<T extends any[]>(
? args.pop() as WriteLineOptions
: {}
const { renderSpeed = defaultTypingSpeed.value, pending = false, onPendingCheck } = options
const { renderSpeed = defaultTypingSpeed.value, pending = false, onPendingCheck, withoutTimestamp = false } = options
const entryId = ++currentEntryId.value
const timestamp = getTimestamp()
@@ -200,20 +316,20 @@ async function writeLine<T extends any[]>(
typeof arg === 'object' ? JSON.stringify(arg) : String(arg),
)
const message = format.replace(/%[sdjo]/g, () => formattedArgs.shift() || '')
const timestampStr = `[${formatTimestamp(timestamp)}] `
const timestampStr = withoutTimestamp ? '' : `[${formatTimestamp(timestamp)}] `
const fullLine = timestampStr + message
// Create initial entry
const entry: ConsoleEntry = {
const entry = ref<ConsoleEntry>({
id: entryId,
content: '',
timestamp,
status: pending ? 'pending' : undefined,
isTyping: false,
targetContent: fullLine,
}
})
consoleEntries.value.push(entry)
consoleEntries.value.push(entry.value)
// Handle pending animation and check
if (pending && onPendingCheck) {
@@ -265,8 +381,13 @@ onMounted(async () => {
})
// Boot sequence
for (const message of bootMessages) {
await writeLine(message.template, { renderSpeed: message.typingSpeed || defaultTypingSpeed.value })
for (const message of bootMessages.value) {
await writeLine(message.template, {
renderSpeed: message.typingSpeed || defaultTypingSpeed.value,
pending: message.pending,
onPendingCheck: message.onPendingCheck,
withoutTimestamp: message.withoutTimestamp,
})
}
})
@@ -299,9 +420,9 @@ defineExpose({
<div w="[min(200px,50dvw)]" h="[min(200px,50dvw)]" mx-auto flex justify-center overflow-hidden filter="blur-0.5px">
<canvas ref="riveCanvas" object-contain />
</div>
<CRT ref="crtRef">
<CRTLine v-for="entry in consoleEntries" :key="entry.id">
{{ entry.content }}
<CRT ref="crtRef" class="text-base <lg:text-base <md:text-sm">
<CRTLine v-for="entry in consoleEntries" :key="entry.id" :data-text="entry.content" :class="entry.class">
{{ !!entry.content ? entry.content : '&nbsp;' }}
</CRTLine>
</CRT>
</div>