Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ export interface AppConfig {
audioVisualizerType?: 'bar' | 'wave' | 'grid' | 'radial' | 'aura';
audioVisualizerColor?: `#${string}`;
audioVisualizerColorDark?: `#${string}`;
audioVisualizerColorShift?: number;
audioVisualizerBarCount?: number;
audioVisualizerGridRowCount?: number;
audioVisualizerGridColumnCount?: number;
audioVisualizerRadialBarCount?: number;
audioVisualizerRadialRadius?: number;
audioVisualizerAuraColorShift?: number;
audioVisualizerWaveLineWidth?: number;

// agent dispatch configuration
Expand All @@ -49,9 +49,10 @@ export const APP_CONFIG_DEFAULTS: AppConfig = {
startButtonText: 'Start call',

// optional: audio visualization configuration
// audioVisualizerType: 'bar',
// audioVisualizerColor: '#002cf2',
// audioVisualizerColorDark: '#1fd5f9',
// audioVisualizerType: 'bar',
// audioVisualizerColorShift: 0.3,
// audioVisualizerBarCount: 5,
// audioVisualizerType: 'radial',
// audioVisualizerRadialBarCount: 24,
Expand All @@ -62,7 +63,6 @@ export const APP_CONFIG_DEFAULTS: AppConfig = {
// audioVisualizerType: 'wave',
// audioVisualizerWaveLineWidth: 3,
// audioVisualizerType: 'aura',
// audioVisualizerAuraColorShift: 0.3,

// agent dispatch configuration
agentName: process.env.AGENT_NAME ?? undefined,
Expand Down
22 changes: 13 additions & 9 deletions components/agents-ui/agent-audio-visualizer-aura.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ import { ReactShaderToy } from '@/components/agents-ui/react-shader-toy';
import { useAgentAudioVisualizerAura } from '@/hooks/agents-ui/use-agent-audio-visualizer-aura';
import { cn } from '@/lib/shadcn/utils';

// Originally developed for Unicorn Studio
// https://unicorn.studio
//
// Licensed under the Polyform Non-Resale License 1.0.0
// https://polyformproject.org/licenses/non-resale/1.0.0/
//
// © 2026 UNCRN LLC

const DEFAULT_COLOR = '#1FD5F9';

function hexToRgb(hexColor: string) {
Expand Down Expand Up @@ -236,10 +244,10 @@ interface AuraShaderProps {
blur?: number;

/**
* Color of the aura
* Color of the aura in hexidecimal format.
* @default '#1FD5F9'
*/
color?: string;
color?: `#${string}`;

/**
* Color variation across layers (0-1)
Expand Down Expand Up @@ -361,10 +369,10 @@ export interface AgentAudioVisualizerAuraProps {
*/
state?: AgentState;
/**
* The color of the aura in hex format.
* The color of the aura in hexidecimal format.
* @defaultValue '#1FD5F9'
*/
color?: string;
color?: `#${string}`;
/**
* The color shift of the aura.
* @defaultValue 0.05
Expand Down Expand Up @@ -428,11 +436,7 @@ export function AgentAudioVisualizerAura({
amplitude={amplitude}
frequency={frequency}
brightness={brightness}
className={cn(
AgentAudioVisualizerAuraVariants({ size }),
'overflow-hidden rounded-full',
className
)}
className={cn(AgentAudioVisualizerAuraVariants({ size }), className)}
{...props}
/>
);
Expand Down
9 changes: 8 additions & 1 deletion components/agents-ui/agent-audio-visualizer-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function cloneSingleChild(
export const AgentAudioVisualizerBarElementVariants = cva(
[
'rounded-full transition-colors duration-250 ease-linear',
'bg-transparent data-[lk-highlighted=true]:bg-current',
'bg-current/10 data-[lk-highlighted=true]:bg-current',
],
{
variants: {
Expand Down Expand Up @@ -93,6 +93,10 @@ export interface AgentAudioVisualizerBarProps {
* @defaultValue 'connecting'
*/
state?: AgentState;
/**
* The color of the bars in hexidecimal format.
*/
color?: `#${string}`;
/**
* The number of bars to display in the visualizer.
* If not provided, defaults based on size: 3 for 'icon'/'sm', 5 for others.
Expand Down Expand Up @@ -132,10 +136,12 @@ export interface AgentAudioVisualizerBarProps {
export function AgentAudioVisualizerBar({
size = 'md',
state = 'connecting',
color,
barCount,
audioTrack,
className,
children,
style,
...props
}: AgentAudioVisualizerBarProps &
VariantProps<typeof AgentAudioVisualizerBarVariants> &
Expand Down Expand Up @@ -192,6 +198,7 @@ export function AgentAudioVisualizerBar({
return (
<div
data-lk-state={state}
style={{ ...style, color } as CSSProperties}
className={cn(AgentAudioVisualizerBarVariants({ size }), className)}
{...props}
>
Expand Down
9 changes: 8 additions & 1 deletion components/agents-ui/agent-audio-visualizer-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ export type AgentAudioVisualizerGridProps = GridOptions & {
* @defaultValue 'connecting'
*/
state?: AgentState;
/**
* The color of the grid cells in hexidecimal format.
*/
color?: `#${string}`;
/**
* The audio track to visualize. Can be a local/remote audio track or a track reference.
*/
Expand Down Expand Up @@ -235,6 +239,7 @@ export function AgentAudioVisualizerGrid({
size = 'md',
state = 'connecting',
radius,
color,
rowCount: _rowCount = 5,
columnCount: _columnCount = 5,
interval = 100,
Expand Down Expand Up @@ -266,7 +271,9 @@ export function AgentAudioVisualizerGrid({
<div
data-lk-state={state}
className={cn(AgentAudioVisualizerGridVariants({ size }), className)}
style={{ ...style, gridTemplateColumns: `repeat(${columnCount}, 1fr)` }}
style={
{ ...style, gridTemplateColumns: `repeat(${columnCount}, 1fr)`, color } as CSSProperties
}
{...props}
>
{items.map((idx) => (
Expand Down
11 changes: 9 additions & 2 deletions components/agents-ui/agent-audio-visualizer-radial.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { type ComponentProps, useMemo } from 'react';
import { type CSSProperties, type ComponentProps, useMemo } from 'react';
import { type VariantProps, cva } from 'class-variance-authority';
import { type LocalAudioTrack, type RemoteAudioTrack } from 'livekit-client';
import {
Expand All @@ -19,7 +19,7 @@ export const AgentAudioVisualizerRadialVariants = cva(
'**:data-lk-index:rounded-full **:data-lk-index:transition-colors **:data-lk-index:duration-150 **:data-lk-index:ease-linear **:data-lk-index:data-[lk-highlighted=true]:bg-current',
'has-data-[lk-state=connecting]:**:data-lk-index:duration-300',
'has-data-[lk-state=initializing]:**:data-lk-index:duration-300',
'has-data-[lk-state=listening]:**:data-lk-index:duration-300 has-data-[lk-state=listening]:**:data-lk-index:duration-300',
'has-data-[lk-state=listening]:**:data-lk-index:duration-300',
'has-data-[lk-state=thinking]:animate-spin has-data-[lk-state=thinking]:[animation-duration:5s] has-data-[lk-state=thinking]:**:data-lk-index:bg-current',
],
{
Expand Down Expand Up @@ -52,6 +52,10 @@ export interface AgentAudioVisualizerRadialProps {
* @defaultValue 'connecting'
*/
state?: AgentState;
/**
* The color of the radial bars in hexidecimal format.
*/
color?: `#${string}`;
/**
* The radius (distance from center) for the radial bars.
* If not provided, defaults based on size.
Expand Down Expand Up @@ -93,10 +97,12 @@ export interface AgentAudioVisualizerRadialProps {
export function AgentAudioVisualizerRadial({
size = 'md',
state = 'connecting',
color,
radius,
barCount,
audioTrack,
className,
style,
...props
}: AgentAudioVisualizerRadialProps &
ComponentProps<'div'> &
Expand Down Expand Up @@ -175,6 +181,7 @@ export function AgentAudioVisualizerRadial({
<div
data-lk-state={state}
className={cn(AgentAudioVisualizerRadialVariants({ size }), 'relative', className)}
style={{ ...style, color } as CSSProperties}
{...props}
>
{bands.map((band, idx) => {
Expand Down
57 changes: 49 additions & 8 deletions components/agents-ui/agent-audio-visualizer-wave.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function hexToRgb(hexColor: string) {

return color;
}
} catch (error) {
} catch {
console.error(
`Invalid hex color '${hexColor}'.\nFalling back to default color '${DEFAULT_COLOR}'.`
);
Expand All @@ -44,6 +44,23 @@ float luma(vec3 color) {
return dot(color, vec3(0.299, 0.587, 0.114));
}

// RGB to HSV
vec3 rgb2hsv(vec3 c) {
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}

// HSV to RGB
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

// Bell curve function for attenuation from center with rounded top
float bellCurve(float distanceFromCenter, float maxDistance) {
float normalizedDistance = distanceFromCenter / maxDistance;
Expand Down Expand Up @@ -104,9 +121,18 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
// Solid line with smooth edges using minimum distance
float line = smoothstep(lineWidthUV + smoothingUV, lineWidthUV - smoothingUV, minDist);

// Calculate color position based on x position for gradient effect
float colorPos = x;
vec3 color = uColor;
if(abs(uColorShift) > 0.01) {
// Keep the center 50% at base color, then ramp shift across outer 25% on each side.
float centerBandHalfWidth = 0.2;
float edgeBandWidth = 0.5;
float distanceFromCenter = abs(x - centerX);
float edgeFactor = clamp((distanceFromCenter - centerBandHalfWidth) / edgeBandWidth, 0.0, 1.0);
vec3 hsv = rgb2hsv(color);
// Hue shift is zero in the center band and strongest at far edges.
hsv.x = fract(hsv.x + edgeFactor * uColorShift * 0.3);
color = hsv2rgb(hsv);
}

// Apply line intensity
color *= line;
Expand Down Expand Up @@ -142,10 +168,15 @@ interface WaveShaderProps {
*/
frequency?: number;
/**
* Color of the oscilloscope
* Color of the oscilloscope in hexidecimal format.
* @default '#1FD5F9'
*/
color?: string;
color?: `#${string}`;
/**
* Hue shift amount applied toward the outside of the wave. Center remains at the base color.
* @default 0.05
*/
colorShift?: number;
/**
* Mix of the oscilloscope
* @default 1.0
Expand All @@ -166,6 +197,7 @@ interface WaveShaderProps {
function WaveShader({
speed = 10,
color = '#1FD5F9',
colorShift = 0.05,
mix = 1.0,
amplitude = 0.02,
frequency = 20.0,
Expand All @@ -190,6 +222,7 @@ function WaveShader({
uLineWidth: { type: '1f', value: lineWidth },
uSmoothing: { type: '1f', value: blur },
uColor: { type: '3fv', value: rgbColor },
uColorShift: { type: '1f', value: colorShift },
}}
onError={(error) => {
console.error('Shader error:', error);
Expand Down Expand Up @@ -232,10 +265,15 @@ export interface AgentAudioVisualizerWaveProps {
*/
state?: AgentState;
/**
* The color of the wave in hex format.
* The color of the wave in hexidecimal format.
* @defaultValue '#1FD5F9'
*/
color?: string;
color?: `#${string}`;
/**
* The color shift of the wave. Higher values increase hue variation toward the edges.
* @defaultValue 0.05
*/
colorShift?: number;
/**
* The line width of the wave in pixels.
* @defaultValue 2.0
Expand Down Expand Up @@ -268,6 +306,7 @@ export interface AgentAudioVisualizerWaveProps {
* size="lg"
* state="speaking"
* color="#1FD5F9"
* colorShift={0.3}
* lineWidth={2}
* blur={0.5}
* audioTrack={audioTrack}
Expand All @@ -278,6 +317,7 @@ export function AgentAudioVisualizerWave({
size = 'lg',
state = 'speaking',
color,
colorShift = 0.05,
lineWidth,
blur,
audioTrack,
Expand Down Expand Up @@ -311,14 +351,15 @@ export function AgentAudioVisualizerWave({
data-lk-state={state}
speed={speed}
color={color}
colorShift={colorShift}
mix={opacity}
amplitude={amplitude}
frequency={frequency}
lineWidth={_lineWidth}
blur={blur}
className={cn(
AgentAudioVisualizerWaveVariants({ size }),
'mask-[linear-gradient(90deg,transparent_0%,black_20%,black_80%,transparent_100%)]',
'overflow-hidden rounded-full',
className
)}
{...props}
Expand Down
Loading