feat(stage-ui): added TimeSeriesChart component

This commit is contained in:
Neko Ayaka
2025-07-02 03:19:47 +08:00
parent 5573f2a448
commit c855047e04
8 changed files with 828 additions and 1 deletions
+1
View File
@@ -54,6 +54,7 @@
"@pixiv/three-vrm-animation": "^3.4.1",
"@pixiv/three-vrm-core": "^3.4.1",
"@proj-airi/ccc": "workspace:^",
"@proj-airi/chromatic": "^0.0.7",
"@proj-airi/drizzle-duckdb-wasm": "catalog:",
"@proj-airi/font-cjkfonts-allseto": "workspace:^",
"@proj-airi/font-xiaolai": "workspace:^",
@@ -0,0 +1,459 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
import TimeSeriesChart from './TimeSeriesChart.vue'
// Data arrays for different scenarios
const performanceHistory = ref<number[]>([])
const temperatureHistory = ref<number[]>([])
const activityHistory = ref<number[]>([])
const networkHistory = ref<number[]>([])
// Current values
const currentPerformance = ref(0.65)
const currentTemperature = ref(0.4)
const currentActivity = ref(0.2)
const currentNetwork = ref(0.8)
// Animation frame
let animationFrame: number | null = null
let frameCount = 0
const maxHistoryLength = 60
function animate() {
frameCount++
// Update performance data (oscillating with some noise)
const performanceBase = 0.6 + Math.sin(frameCount * 0.1) * 0.2
currentPerformance.value = Math.max(0, Math.min(1, performanceBase + (Math.random() - 0.5) * 0.1))
performanceHistory.value.push(currentPerformance.value)
if (performanceHistory.value.length > maxHistoryLength) {
performanceHistory.value.shift()
}
// Update temperature data (slow rise and fall)
const tempBase = 0.4 + Math.sin(frameCount * 0.05) * 0.3
currentTemperature.value = Math.max(0, Math.min(1, tempBase + (Math.random() - 0.5) * 0.05))
temperatureHistory.value.push(currentTemperature.value)
if (temperatureHistory.value.length > maxHistoryLength) {
temperatureHistory.value.shift()
}
// Update activity data (sudden spikes)
let activityBase = 0.1
if (frameCount % 30 === 0) { // Spike every 30 frames
activityBase = 0.8 + Math.random() * 0.2
}
currentActivity.value = Math.max(0, Math.min(1, currentActivity.value * 0.9 + activityBase * 0.1,
))
activityHistory.value.push(currentActivity.value)
if (activityHistory.value.length > maxHistoryLength) {
activityHistory.value.shift()
}
// Update network data (stepped changes)
if (frameCount % 15 === 0) {
currentNetwork.value = Math.random()
}
networkHistory.value.push(currentNetwork.value)
if (networkHistory.value.length > maxHistoryLength) {
networkHistory.value.shift()
}
animationFrame = requestAnimationFrame(animate)
}
onMounted(() => {
// Initialize with some data
for (let i = 0; i < 30; i++) {
performanceHistory.value.push(0.5 + Math.random() * 0.3)
temperatureHistory.value.push(0.3 + Math.random() * 0.2)
activityHistory.value.push(Math.random() * 0.4)
networkHistory.value.push(Math.random())
}
animate()
})
onUnmounted(() => {
if (animationFrame) {
cancelAnimationFrame(animationFrame)
}
})
// Static data for examples
const staticData1 = [0.2, 0.3, 0.5, 0.7, 0.6, 0.8, 0.9, 0.7, 0.5, 0.4, 0.6, 0.8, 0.9, 0.8, 0.6, 0.4, 0.3, 0.5, 0.7, 0.9]
const staticData2 = [0.1, 0.2, 0.1, 0.9, 0.8, 0.2, 0.1, 0.3, 0.2, 0.1, 0.8, 0.9, 0.2, 0.1, 0.4, 0.3, 0.2, 0.8, 0.9, 0.3]
const staticData3 = [0.5, 0.52, 0.48, 0.51, 0.49, 0.53, 0.47, 0.52, 0.48, 0.51, 0.49, 0.53, 0.47, 0.52, 0.48, 0.51, 0.49, 0.53, 0.47, 0.52]
// Custom formatters
const formatPercentage = (value: number) => `${(value * 100).toFixed(0)}%`
const formatTemperature = (value: number) => `${(value * 100).toFixed(0)}°C`
const formatDecimal = (value: number) => value.toFixed(3)
</script>
<template>
<Story
title="Time Series Chart"
group="gadgets"
:layout="{ type: 'grid', width: '100%' }"
>
<template #controls>
<ThemeColorsHueControl />
</template>
<Variant
id="real-time-monitoring"
title="Real-time Monitoring"
>
<div class="space-y-6">
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<!-- Performance Monitoring -->
<div class="rounded-xl px-3 py-2 shadow-md">
<TimeSeriesChart
:history="performanceHistory"
:current-value="currentPerformance"
:threshold="0.7"
:is-active="currentPerformance > 0.7"
title="System Performance"
subtitle="Real-time metrics"
active-label="High load"
active-legend-label="Above threshold"
inactive-legend-label="Normal operation"
threshold-label="Performance limit"
:format-value="formatPercentage"
:height="100"
/>
</div>
<!-- Temperature Monitoring -->
<div class="rounded-xl px-3 py-2 shadow-md">
<TimeSeriesChart
:history="temperatureHistory"
:current-value="currentTemperature"
:threshold="0.8"
:is-active="currentTemperature > 0.8"
title="Temperature Monitor"
subtitle="CPU temperature"
active-label="Overheating"
active-legend-label="Critical"
inactive-legend-label="Normal"
threshold-label="Critical temp"
:format-value="formatTemperature"
:colors-hue="10"
:height="100"
/>
</div>
</div>
</div>
</Variant>
<Variant
id="activity-detection"
title="Activity Detection"
>
<div class="space-y-6">
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<!-- Event Detection -->
<div class="rounded-xl px-3 py-2 shadow-md">
<TimeSeriesChart
:history="activityHistory"
:current-value="currentActivity"
:threshold="0.5"
:is-active="currentActivity > 0.5"
title="Event Detection"
subtitle="Real-time activity"
active-label="Event detected"
active-legend-label="Active events"
inactive-legend-label="Quiet"
threshold-label="Detection threshold"
:height="80"
/>
</div>
<!-- Network Quality -->
<div class="rounded-xl px-3 py-2 shadow-md">
<TimeSeriesChart
:history="networkHistory"
:current-value="currentNetwork"
:threshold="0.6"
:is-active="currentNetwork > 0.6"
title="Network Quality"
subtitle="Connection strength"
active-label="Strong signal"
active-legend-label="Good connection"
inactive-legend-label="Weak signal"
threshold-label="Quality threshold"
:colors-hue="240"
:height="80"
/>
</div>
</div>
</div>
</Variant>
<Variant
id="static-examples"
title="Static Data Examples"
>
<div class="space-y-6">
<div class="grid grid-cols-1 gap-6 md:grid-cols-3">
<!-- Success Rate -->
<div class="rounded-xl px-3 py-2 shadow-md">
<TimeSeriesChart
:history="staticData1"
:current-value="0.9"
:threshold="0.8"
:is-active="true"
title="Success Rate"
subtitle="Last 20 measurements"
active-label="Target met"
:format-value="formatPercentage"
:height="70"
/>
</div>
<!-- Error Spikes -->
<div class="rounded-xl px-3 py-2 shadow-md">
<TimeSeriesChart
:history="staticData2"
:current-value="0.3"
:threshold="0.5"
:is-active="false"
title="Error Rate"
subtitle="Sporadic failures"
active-label="High errors"
:colors-hue="15"
:format-value="formatPercentage"
:height="70"
/>
</div>
<!-- Stable Metrics -->
<div class="rounded-xl px-3 py-2 shadow-md">
<TimeSeriesChart
:history="staticData3"
:current-value="0.52"
:threshold="0.6"
:is-active="false"
title="Stable System"
subtitle="Low variance"
active-label="Threshold exceeded"
:colors-hue="160"
:format-value="formatDecimal"
:height="70"
/>
</div>
</div>
</div>
</Variant>
<Variant
id="configuration-options"
title="Configuration Options"
>
<div class="space-y-6">
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3 md:grid-cols-2">
<!-- Minimal Display -->
<div class="rounded-xl px-3 py-2 shadow-md">
<h3 class="mb-4 text-lg text-neutral-700 font-medium dark:text-neutral-300">
Minimal Chart
</h3>
<TimeSeriesChart
:history="staticData1"
:current-value="0.7"
:threshold="null"
:is-active="false"
:show-header="false"
:show-threshold="false"
:show-current-value="false"
:show-active-indicator="false"
:show-legend="false"
:height="50"
/>
<p class="mt-2 text-sm text-neutral-500">
Clean, minimal visualization
</p>
</div>
<!-- No Area Fill -->
<div class="rounded-xl px-3 py-2 shadow-md">
<h3 class="mb-4 text-lg text-neutral-700 font-medium dark:text-neutral-300">
Line Only
</h3>
<TimeSeriesChart
:history="staticData2"
:current-value="0.3"
:threshold="0.5"
:is-active="false"
:show-area="false"
:show-threshold-areas="false"
title="Line Chart"
:height="60"
/>
<p class="mt-2 text-sm text-neutral-500">
Line without fill areas
</p>
</div>
<!-- Custom Styling -->
<div class="rounded-xl px-3 py-2 shadow-md">
<h3 class="mb-4 text-lg text-neutral-700 font-medium dark:text-neutral-300">
Custom Style
</h3>
<TimeSeriesChart
:history="staticData1"
:current-value="0.8"
:threshold="0.6"
:is-active="true"
title="Custom Colors"
:colors-hue="280"
:line-width="3"
:active-indicator-size="6"
:height="60"
/>
<p class="mt-2 text-sm text-neutral-500">
Purple theme with thick line
</p>
</div>
</div>
</div>
</Variant>
<Variant
id="dashboard-layout"
title="Dashboard Layout"
>
<div class="space-y-6">
<div class="rounded-xl px-3 py-2 shadow-md">
<h3 class="mb-6 text-lg text-neutral-700 font-medium dark:text-neutral-300">
System Monitoring Dashboard
</h3>
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
<!-- Primary Metrics -->
<div class="space-y-4">
<h4 class="text-sm text-neutral-600 font-medium dark:text-neutral-400">
Primary Metrics
</h4>
<TimeSeriesChart
:history="performanceHistory"
:current-value="currentPerformance"
:threshold="0.7"
:is-active="currentPerformance > 0.7"
title="CPU Usage"
subtitle="Real-time utilization"
active-label="High load"
:show-legend="false"
:format-value="formatPercentage"
:height="60"
/>
<TimeSeriesChart
:history="temperatureHistory"
:current-value="currentTemperature"
:threshold="0.8"
:is-active="currentTemperature > 0.8"
title="Memory Usage"
subtitle="Available memory"
active-label="Low memory"
:show-legend="false"
:format-value="formatPercentage"
:colors-hue="10"
:height="60"
/>
</div>
<!-- Secondary Metrics -->
<div class="space-y-4">
<h4 class="text-sm text-neutral-600 font-medium dark:text-neutral-400">
Secondary Metrics
</h4>
<TimeSeriesChart
:history="activityHistory"
:current-value="currentActivity"
:threshold="0.5"
:is-active="currentActivity > 0.5"
title="Network I/O"
subtitle="Data transfer rate"
active-label="High traffic"
:show-legend="false"
:format-value="formatPercentage"
:colors-hue="240"
:height="60"
/>
<TimeSeriesChart
:history="networkHistory"
:current-value="currentNetwork"
:threshold="0.6"
:is-active="currentNetwork > 0.6"
title="Disk Usage"
subtitle="Storage utilization"
active-label="High usage"
:show-legend="false"
:format-value="formatPercentage"
:colors-hue="280"
:height="60"
/>
</div>
</div>
</div>
</div>
</Variant>
<Variant
id="special-applications"
title="Special Applications"
>
<div class="space-y-6">
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<!-- Audio/Voice Activity -->
<div class="rounded-xl px-3 py-2 shadow-md">
<h3 class="mb-4 text-lg text-neutral-700 font-medium dark:text-neutral-300">
Voice Activity Detection
</h3>
<TimeSeriesChart
:history="activityHistory"
:current-value="currentActivity"
:threshold="0.5"
:is-active="currentActivity > 0.5"
title="Voice Activity"
subtitle="Speech detection"
active-label="Speaking"
active-legend-label="Voice detected"
inactive-legend-label="Silence"
threshold-label="Speech threshold"
:colors-hue="120"
:height="90"
/>
</div>
<!-- Anomaly Detection -->
<div class="rounded-xl px-3 py-2 shadow-md">
<h3 class="mb-4 text-lg text-neutral-700 font-medium dark:text-neutral-300">
Anomaly Detection
</h3>
<TimeSeriesChart
:history="staticData2"
:current-value="0.3"
:threshold="0.4"
:is-active="false"
title="Anomaly Score"
subtitle="System health"
active-label="Anomaly detected"
active-legend-label="Unusual behavior"
inactive-legend-label="Normal operation"
threshold-label="Alert threshold"
:colors-hue="30"
:height="90"
/>
</div>
</div>
</div>
</Variant>
</Story>
</template>
@@ -0,0 +1,345 @@
<script setup lang="ts">
import type { Oklch } from '@proj-airi/chromatic'
import { chromaticPaletteFrom } from '@proj-airi/chromatic'
import { useElementBounding } from '@vueuse/core'
import { computed, inject, ref, toRef, watch } from 'vue'
import { chromaticHue as hue } from '../../constants'
import { chromaticHueDefault as hueDefault } from '../../constants/theme'
interface Props {
history: number[] // Array of values (normalized 0-1)
currentValue: number // Current value (0-1)
threshold?: number | null // Threshold value (0-1)
isActive: boolean // Whether current state is "active"
title?: string // Chart title
colorsHue?: number // Optional hue for colors (default from theme)
lineColor?: string // Line color (default from theme)
thresholdColor?: string // Threshold color (default from theme)
activeColor?: string // Active state color (default from theme)
inactiveColor?: string // Inactive state color (default from theme)
subtitle?: string // Chart subtitle
activeLabel?: string // Label when active
activeLegendLabel?: string // Legend label for active state
inactiveLegendLabel?: string // Legend label for inactive state
thresholdLabel?: string // Label for threshold
height?: number // Chart height in pixels
lineWidth?: number // Line stroke width
chartHeight?: number // Internal chart height for calculations
minDataPoints?: number // Minimum points needed to show chart
precision?: number // Value display precision
unit?: string // Value unit
showHeader?: boolean // Show title/subtitle
showThreshold?: boolean // Show threshold zone
showArea?: boolean // Show filled area under curve
showThresholdAreas?: boolean // Show highlighted threshold areas
showCurrentValue?: boolean // Show floating current value
showActiveIndicator?: boolean // Show active state indicator
showLegend?: boolean // Show legend
formatValue?: (value: number) => string // Custom value formatter
}
const props = withDefaults(defineProps<Props>(), {
threshold: null,
title: 'Time Series',
subtitle: 'Recent data',
activeLabel: 'Active',
activeLegendLabel: 'Active state',
inactiveLegendLabel: 'Inactive state',
thresholdLabel: 'Threshold',
height: 80,
lineWidth: 1.5,
minDataPoints: 5,
precision: 0,
unit: '%',
showHeader: true,
showThreshold: true,
showArea: true,
showThresholdAreas: true,
showCurrentValue: true,
showActiveIndicator: true,
showLegend: true,
})
// Use the actual display height for all calculations
const chartHeight = computed(() => props.height)
const timeSeriesChartRef = ref<HTMLDivElement>()
const chromaticHue = inject(hue, hueDefault)
const chromaticHueOrDefault = toRef(() => props.colorsHue || chromaticHue || hueDefault)
const chromaticShades = computed(() => chromaticPaletteFrom(chromaticHueOrDefault.value))
const timeSeriesChartContainerBounding = useElementBounding(timeSeriesChartRef, { windowResize: true })
watch([chromaticHueOrDefault, timeSeriesChartRef], () => {
if (timeSeriesChartRef.value) {
timeSeriesChartRef.value.style.setProperty('--chromatic-hue', chromaticHueOrDefault.value.toString())
}
}, { immediate: true })
const lineColorProps = toRef(() => props.lineColor)
const lineColor = computed(() => {
if (!lineColorProps.value) {
return chromaticShades.value.shadeBy(500).toHex()
}
return lineColorProps.value
})
const thresholdColorProps = toRef(() => props.thresholdColor)
const thresholdColor = computed(() => {
if (!thresholdColorProps.value) {
const color = chromaticShades.value.shadeBy(500).withAlpha(0.1).color as Oklch
return `oklch(${color.l} ${color.c} ${color.h} / ${color.alpha})`
}
return thresholdColorProps.value
})
const activeColorProps = toRef(() => props.activeColor)
const activeColor = computed(() => {
if (!activeColorProps.value) {
return chromaticShades.value.shadeBy(600).toHex()
}
return activeColorProps.value
})
const inactiveColorProps = toRef(() => props.inactiveColor)
const inactiveColor = computed(() => {
if (!inactiveColorProps.value) {
return chromaticShades.value.shadeBy(400).toHex()
}
return inactiveColorProps.value
})
// Generate unique IDs for SVG patterns
const componentId = Math.random().toString(36).substring(2, 9)
const gridPatternId = `grid-${componentId}`
const areaGradientId = `area-gradient-${componentId}`
const thresholdGradientId = `threshold-gradient-${componentId}`
const normalizedThreshold = computed(() =>
props.threshold !== null ? Math.max(0, Math.min(1, props.threshold)) : 0,
)
// Calculate threshold line Y position
const thresholdLineY = computed(() => {
if (props.threshold === null)
return 0
return chartHeight.value - (normalizedThreshold.value * chartHeight.value)
})
// Create smooth curve path
const smoothPath = computed(() => {
const history = props.history
if (history.length < 2)
return ''
const width = timeSeriesChartContainerBounding.width.value
const height = chartHeight.value
let path = `M0,${height - (history[0] * height)}`
for (let i = 1; i < history.length; i++) {
const x = (i / (history.length - 1)) * width
const y = height - (history[i] * height)
if (i === 1) {
path += ` Q${x / 2},${height - (history[0] * height)} ${x},${y}`
}
else {
const prevX = ((i - 1) / (history.length - 1)) * width
const cpX = (prevX + x) / 2
const prevY = height - (history[i - 1] * height)
path += ` Q${cpX},${prevY} ${x},${y}`
}
}
return path
})
// Create filled area path
const dataAreaPath = computed(() => {
const history = props.history
if (history.length < 2)
return ''
const width = timeSeriesChartContainerBounding.width.value
const height = chartHeight.value
let path = `M0,${height} L0,${height - (history[0] * height)}`
for (let i = 1; i < history.length; i++) {
const x = (i / (history.length - 1)) * width
const y = height - (history[i] * height)
if (i === 1) {
path += ` Q${x / 2},${height - (history[0] * height)} ${x},${y}`
}
else {
const prevX = ((i - 1) / (history.length - 1)) * width
const cpX = (prevX + x) / 2
const prevY = height - (history[i - 1] * height)
path += ` Q${cpX},${prevY} ${x},${y}`
}
}
path += ` L${width},${height} Z`
return path
})
</script>
<template>
<div v-if="history.length > minDataPoints" ref="timeSeriesChartRef" class="time-series-chart space-y-3">
<div v-if="showHeader" class="flex items-center justify-between">
<div class="text-sm font-medium">
{{ title }}
</div>
<div class="text-xs text-neutral-500">
{{ subtitle }}
</div>
</div>
<!-- Chart Visualization -->
<div
class="relative overflow-hidden border border-neutral-200 rounded-lg from-neutral-50 to-neutral-100 bg-gradient-to-b dark:border-neutral-800 dark:from-neutral-800 dark:to-neutral-900"
:style="{ height: `${chartHeight}px` }"
>
<svg class="h-full w-full">
<!-- Background grid (subtle) -->
<defs>
<pattern :id="gridPatternId" width="20" height="10" patternUnits="userSpaceOnUse">
<path d="M 20 0 L 0 0 0 10" fill="none" stroke="rgb(156 163 175 / 0.1)" stroke-width="0.5" />
</pattern>
<!-- Gradient for the filled area -->
<linearGradient :id="areaGradientId" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" :style="`stop-color:${lineColor};stop-opacity:0.3`" />
<stop offset="50%" :style="`stop-color:${lineColor};stop-opacity:0.15`" />
<stop offset="100%" :style="`stop-color:${lineColor};stop-opacity:0.05`" />
</linearGradient>
<!-- Gradient for threshold areas -->
<linearGradient :id="thresholdGradientId" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" :style="`stop-color:${activeColor};stop-opacity:0.3`" />
<stop offset="50%" :style="`stop-color:${activeColor};stop-opacity:0.15`" />
<stop offset="100%" :style="`stop-color:${activeColor};stop-opacity:0.05`" />
</linearGradient>
<!-- Below threshold gradient -->
<linearGradient id="below-threshold-gradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" :style="`stop-color:${thresholdColor};stop-opacity:0.2`" />
<stop offset="50%" :style="`stop-color:${thresholdColor};stop-opacity:0.1`" />
<stop offset="100%" :style="`stop-color:${thresholdColor};stop-opacity:0.05`" />
</linearGradient>
</defs>
<!-- Background grid -->
<rect width="100%" height="100%" :fill="`url(#${gridPatternId})`" />
<!-- Below threshold area with gradient -->
<rect
v-if="showThreshold && threshold !== null"
x="0"
:y="thresholdLineY"
width="100%"
:height="chartHeight - thresholdLineY"
:fill="thresholdColor"
class="transition-all duration-300"
/>
<!-- Threshold line -->
<line
v-if="showThreshold && threshold !== null"
x1="0"
:y1="thresholdLineY"
x2="100%"
:y2="thresholdLineY"
:stroke="thresholdColor"
stroke-width="1.5"
stroke-dasharray="4,4"
:fill="thresholdColor"
class="transition-all duration-300"
/>
<!-- Data area (filled under curve) -->
<path
v-if="dataAreaPath && showArea"
:d="dataAreaPath"
:fill="`url(#${areaGradientId})`"
class="transition-all duration-75"
/>
<!-- Main data curve -->
<path
v-if="smoothPath"
:d="smoothPath"
fill="none"
:stroke="lineColor"
:stroke-width="lineWidth"
stroke-linecap="round"
stroke-linejoin="round"
class="drop-shadow-sm transition-all duration-75"
/>
</svg>
<!-- Floating current value -->
<div
v-if="showCurrentValue"
class="absolute right-2 top-2 border border-neutral-200 rounded-md bg-white px-2 py-1 shadow-sm transition-all duration-200 dark:border-neutral-700 dark:bg-neutral-800"
:class="isActive ? `bg-primary-50 dark:bg-primary-900 border-primary-200 dark:border-primary-800` : ''"
>
<div class="text-xs font-medium" :class="isActive ? 'text-primary-700 dark:text-primary-300' : 'text-neutral-600 dark:text-neutral-400'">
{{ formatValue ? formatValue(currentValue) : `${(currentValue * 100).toFixed(precision)}${unit}` }}
</div>
</div>
<!-- Active state indicator -->
<Transition name="fade">
<div
v-if="isActive && showActiveIndicator"
class="absolute left-2 top-2 flex items-center gap-1.5 border border-primary-200 rounded-md bg-primary-50 px-2 py-1 dark:border-primary-800 dark:bg-primary-900"
>
<div class="h-1.5 w-1.5 animate-pulse rounded-full bg-primary-500" />
<span class="text-xs text-primary-700 font-medium dark:text-primary-300">{{ activeLabel }}</span>
</div>
</Transition>
</div>
<!-- Legend -->
<div v-if="showLegend" class="flex flex-wrap items-center justify-between text-xs text-neutral-500">
<div class="flex items-center gap-3">
<span class="flex items-center gap-1 text-nowrap">
<div class="h-2 w-2 rounded-full" :style="{ backgroundColor: activeColor }" />
{{ activeLegendLabel }}
</span>
<span class="flex items-center gap-1 text-nowrap">
<div class="h-2 w-2 rounded-full" :style="{ backgroundColor: inactiveColor }" />
{{ inactiveLegendLabel }}
</span>
</div>
<span v-if="threshold !== null" class="text-nowrap">{{ thresholdLabel }}: {{ (threshold * 100).toFixed(0) }}%</span>
</div>
</div>
</template>
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.fade-enter-to,
.fade-leave-from {
opacity: 1;
}
</style>
@@ -3,3 +3,4 @@ export { default as AudioSpectrumVisualizer } from './AudioSpectrumVisualizer.vu
export { default as LevelMeter } from './LevelMeter.vue'
export { default as TestDummyMarker } from './TestDummyMarker.vue'
export { default as ThresholdMeter } from './ThresholdMeter.vue'
export { default as TimeSeriesChart } from './TimeSeriesChart.vue'
+2
View File
@@ -1,4 +1,6 @@
export const llmInferenceEndToken = '<|llm_inference_end|>'
export * from './emotions'
export * from './inject'
export * from './prompts/system-v2'
export * from './theme'
@@ -0,0 +1,3 @@
import type { InjectionKey } from 'vue'
export const chromaticHue: InjectionKey<number> = Symbol('@proj-airi/chromatic-hue')
+1
View File
@@ -0,0 +1 @@
export const chromaticHueDefault = 220.44
+16 -1
View File
@@ -990,6 +990,9 @@ importers:
'@proj-airi/ccc':
specifier: workspace:^
version: link:../ccc
'@proj-airi/chromatic':
specifier: ^0.0.7
version: 0.0.7
'@proj-airi/drizzle-duckdb-wasm':
specifier: 'catalog:'
version: 0.4.28(drizzle-orm@0.44.2(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(web-worker@1.5.0)
@@ -4234,6 +4237,9 @@ packages:
'@polka/url@1.0.0-next.24':
resolution: {integrity: sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==}
'@proj-airi/chromatic@0.0.7':
resolution: {integrity: sha512-4uxJhnbdJ6vRWeivuVy4z20cTwHbH+armAHqZNJozaspCtwCyN1AMkvJmj6+4BuuQO1iwQ6KvSHrNcNUGkOtZg==}
'@proj-airi/drizzle-duckdb-wasm@0.4.28':
resolution: {integrity: sha512-1Y+7YdFbVgY9vUUAzm+a5QfSLnJkyhqxTENqZQbFmfCRn3rQO4KPLvu/l6HBg7GtLtlIYVIIOG37QiRexuofGw==}
peerDependencies:
@@ -5769,6 +5775,9 @@ packages:
'@xsai/shared@0.3.0-beta.5':
resolution: {integrity: sha512-0vDW0fkEcFhfd3uG2OEMkoqq/ghh62KJUokMUwaAPKLNcVNAVcFRoslH9lKjdHJziSZ/b59f5rpUEjU3DHooZQ==}
'@xsai/shared@0.3.0-beta.6':
resolution: {integrity: sha512-EeqkD4+ijKKbxr9Jy9kRnJOcPBErWL8TK2wOdxwhVQuh7VbrYQwaheGjOz9g5OWwM6A0vCje/+TyHz+tURhhqw==}
'@xsai/stream-text@0.3.0-beta.5':
resolution: {integrity: sha512-o0SxwzCA2F1zKcONZJX7qrk+PnKhzldV0jRqQ+NdBzaFLmg/clfqeENlbuwmfIrV5ck0BfPEMTo4gNBfxhsGSQ==}
@@ -14880,6 +14889,10 @@ snapshots:
'@polka/url@1.0.0-next.24': {}
'@proj-airi/chromatic@0.0.7':
dependencies:
culori: 4.0.2
'@proj-airi/drizzle-duckdb-wasm@0.4.28(drizzle-orm@0.44.2(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(web-worker@1.5.0)':
dependencies:
'@date-fns/tz': 1.2.0
@@ -16826,7 +16839,7 @@ snapshots:
'@xsai-ext/shared-providers@0.2.2':
dependencies:
'@xsai/shared': 0.3.0-beta.5
'@xsai/shared': 0.3.0-beta.6
'@xsai-ext/shared-providers@0.3.0-beta.5':
dependencies:
@@ -16880,6 +16893,8 @@ snapshots:
'@xsai/shared@0.3.0-beta.5': {}
'@xsai/shared@0.3.0-beta.6': {}
'@xsai/stream-text@0.3.0-beta.5':
dependencies:
'@xsai/shared-chat': 0.3.0-beta.5