Skip to content

Commit 90ae2d8

Browse files
--wip-- [skip ci]
1 parent 2f8f2af commit 90ae2d8

File tree

15 files changed

+415
-977
lines changed

15 files changed

+415
-977
lines changed

app-config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface AppConfig {
1313
accent?: string;
1414
logoDark?: string;
1515
accentDark?: string;
16+
audioVisualizer?: 'bar' | 'radial' | 'aura' | 'wave';
1617

1718
// for LiveKit Cloud Sandbox
1819
sandboxId?: string;
@@ -34,6 +35,7 @@ export const APP_CONFIG_DEFAULTS: AppConfig = {
3435
logoDark: '/lk-logo-dark.svg',
3536
accentDark: '#1fd5f9',
3637
startButtonText: 'Start call',
38+
audioVisualizer: 'aura',
3739

3840
// for LiveKit Cloud Sandbox
3941
sandboxId: undefined,

app/ui/_components.tsx

Lines changed: 173 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
// useVoiceAssistant,
1313
} from '@livekit/components-react';
1414
import { MicrophoneIcon } from '@phosphor-icons/react/dist/ssr';
15-
import { useSession } from '@/components/app/session-provider';
1615
import { AgentControlBar } from '@/components/livekit/agent-control-bar/agent-control-bar';
1716
import { TrackControl } from '@/components/livekit/agent-control-bar/track-control';
1817
// import { TrackDeviceSelect } from '@/components/livekit/agent-control-bar/track-device-select';
@@ -46,6 +45,8 @@ import {
4645
} from '@/components/livekit/select';
4746
import { ShimmerText } from '@/components/livekit/shimmer-text';
4847
import { Toggle, toggleVariants } from '@/components/livekit/toggle';
48+
import { useConnection } from '@/hooks/useConnection';
49+
import { cn } from '@/lib/utils';
4950

5051
type toggleVariantsType = VariantProps<typeof toggleVariants>['variant'];
5152
type toggleVariantsSizeType = VariantProps<typeof toggleVariants>['size'];
@@ -61,13 +62,13 @@ type audioShaderVisualizerVariantsSizeType = VariantProps<
6162
>['size'];
6263

6364
export function useMicrophone() {
64-
const { startSession } = useSession();
65+
const { connect } = useConnection();
6566
const { localParticipant } = useLocalParticipant();
6667

6768
useEffect(() => {
68-
startSession();
69+
connect();
6970
localParticipant.setMicrophoneEnabled(true, undefined);
70-
}, [startSession, localParticipant]);
71+
}, [connect, localParticipant]);
7172
}
7273

7374
interface ContainerProps {
@@ -569,9 +570,7 @@ export const COMPONENTS = {
569570
// shape
570571
const [shape, setShape] = useState(1.0);
571572
// color scale
572-
const [colorScale, setColorScale] = useState(0.1);
573-
// color position
574-
const [colorPosition, setColorPosition] = useState(0.15);
573+
const [colorShift, setColorShift] = useState(0.3);
575574

576575
const sizes = ['icon', 'sm', 'md', 'lg', 'xl'];
577576
const states = [
@@ -582,9 +581,17 @@ export const COMPONENTS = {
582581
'thinking',
583582
'speaking',
584583
] as AgentState[];
585-
584+
const colors: [number, number, number][] = [
585+
[31.0 / 255, 213.0 / 255, 249.0 / 255], // LiveKit Blue
586+
[0.0, 0.0, 1.0], // Blue
587+
[0.0, 1.0, 0.0], // Green
588+
[1.0, 0.0, 0.0], // Red
589+
[1.0, 0.0, 1.0], // Purple
590+
];
591+
592+
const [rgbColor, setRgbColor] = useState<[number, number, number]>(colors[0]);
586593
const [size, setSize] = useState<audioShaderVisualizerVariantsSizeType>('lg');
587-
const [state, setState] = useState<AgentState>(states[0]);
594+
const [state, setState] = useState<AgentState>(states[1]);
588595

589596
const { microphoneTrack, localParticipant } = useLocalParticipant();
590597
const micTrackRef = useMemo<TrackReferenceOrPlaceholder | undefined>(() => {
@@ -599,10 +606,24 @@ export const COMPONENTS = {
599606

600607
useMicrophone();
601608

602-
const fields = [
603-
['color position', colorPosition, setColorPosition, 0, 1, 0.01],
604-
['color scale', colorScale, setColorScale, 0, 1, 0.01],
605-
] as const;
609+
const handleColorChange = (e: React.ChangeEvent<HTMLInputElement>) => {
610+
const hexColor = e.target.value;
611+
612+
try {
613+
const rgbColor = hexColor.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);
614+
615+
if (rgbColor) {
616+
const [, r, g, b] = rgbColor;
617+
const color = [r, g, b].map((c) => parseInt(c, 16) / 255);
618+
619+
setRgbColor(color as [number, number, number]);
620+
}
621+
} catch (error) {
622+
console.error(error);
623+
}
624+
};
625+
626+
const fields = [['color shift', colorShift, setColorShift, 0, 1, 0.01]] as const;
606627

607628
return (
608629
<Container componentName="AudioShaderVisualizer">
@@ -649,10 +670,10 @@ export const COMPONENTS = {
649670
size={size}
650671
state={state}
651672
shape={shape}
652-
colorScale={colorScale}
653-
colorPosition={colorPosition}
673+
rgbColor={rgbColor}
674+
colorShift={colorShift}
654675
audioTrack={micTrackRef!}
655-
className="mx-auto bg-black"
676+
className="mx-auto"
656677
/>
657678
</div>
658679

@@ -671,6 +692,42 @@ export const COMPONENTS = {
671692
</div>
672693

673694
<div className="grid grid-cols-2 gap-4">
695+
<div>
696+
<StoryTitle>Color</StoryTitle>
697+
<div className="flex items-center gap-2">
698+
{colors.map((color) => (
699+
<div
700+
key={color.join(',')}
701+
onClick={() => setRgbColor(color)}
702+
style={{ backgroundColor: `rgb(${color.map((c) => c * 255).join(',')})` }}
703+
className={cn(
704+
'h-4 w-4 cursor-pointer rounded-full',
705+
rgbColor.toString() === color.toString() &&
706+
'ring-muted-foreground ring-offset-background ring-1 ring-offset-2'
707+
)}
708+
/>
709+
))}
710+
711+
<Button
712+
type="button"
713+
size="sm"
714+
className="relative"
715+
onClick={() => setRgbColor(colors[0])}
716+
>
717+
<span className="text-muted-foreground text-xs">Pick a color</span>
718+
<span
719+
className="inline-block size-4 rounded-full"
720+
style={{ backgroundColor: `rgb(${rgbColor.map((c) => c * 255).join(',')})` }}
721+
/>
722+
<input
723+
type="color"
724+
onChange={handleColorChange}
725+
className="absolute inset-0 m-0 block h-full w-full opacity-0"
726+
/>
727+
</Button>
728+
</div>
729+
</div>
730+
674731
{fields.map(([name, value, setValue, min = 0.1, max = 10, step = 0.1]) => {
675732
return (
676733
<div key={name}>
@@ -698,6 +755,10 @@ export const COMPONENTS = {
698755
AudioOscilloscopeVisualizer: () => {
699756
// shape
700757
const [shape, setShape] = useState(1.0);
758+
// line width
759+
const [lineWidth, setLineWidth] = useState(2.5);
760+
// smoothing
761+
const [smoothing, setSmoothing] = useState(0.0);
701762

702763
const sizes = ['icon', 'sm', 'md', 'lg', 'xl'];
703764
const states = [
@@ -708,9 +769,18 @@ export const COMPONENTS = {
708769
'thinking',
709770
'speaking',
710771
] as AgentState[];
772+
const colors: [number, number, number][] = [
773+
[31.0 / 255, 213.0 / 255, 249.0 / 255], // LiveKit Blue
774+
[0.0, 0.0, 1.0], // Blue
775+
[0.0, 1.0, 0.0], // Green
776+
[1.0, 0.0, 0.0], // Red
777+
[1.0, 0.0, 1.0], // Purple
778+
];
711779

712-
const [size, setSize] = useState<audioShaderVisualizerVariantsSizeType>('lg');
713-
const [state, setState] = useState<AgentState>(states[0]);
780+
const [rgbColor, setRgbColor] = useState<[number, number, number]>(colors[0]);
781+
782+
const [size, setSize] = useState<audioShaderVisualizerVariantsSizeType>('xl');
783+
const [state, setState] = useState<AgentState>(states[1]);
714784

715785
const { microphoneTrack, localParticipant } = useLocalParticipant();
716786
const micTrackRef = useMemo<TrackReferenceOrPlaceholder | undefined>(() => {
@@ -725,6 +795,28 @@ export const COMPONENTS = {
725795

726796
useMicrophone();
727797

798+
const handleColorChange = (e: React.ChangeEvent<HTMLInputElement>) => {
799+
const hexColor = e.target.value;
800+
801+
try {
802+
const rgbColor = hexColor.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);
803+
804+
if (rgbColor) {
805+
const [, r, g, b] = rgbColor;
806+
const color = [r, g, b].map((c) => parseInt(c, 16) / 255);
807+
808+
setRgbColor(color as [number, number, number]);
809+
}
810+
} catch (error) {
811+
console.error(error);
812+
}
813+
};
814+
815+
const fields = [
816+
['line width', lineWidth, setLineWidth, 1, 10, 1],
817+
['smoothing', smoothing, setSmoothing, 0, 10, 0.1],
818+
] as const;
819+
728820
return (
729821
<Container componentName="AudioShaderVisualizer">
730822
<div className="flex gap-4">
@@ -769,6 +861,9 @@ export const COMPONENTS = {
769861
<AudioOscilloscopeVisualizer
770862
size={size}
771863
state={state}
864+
rgbColor={rgbColor}
865+
lineWidth={lineWidth}
866+
smoothing={smoothing}
772867
audioTrack={micTrackRef!}
773868
className="mx-auto"
774869
/>
@@ -787,6 +882,66 @@ export const COMPONENTS = {
787882
</Button>
788883
))}
789884
</div>
885+
886+
<div className="grid grid-cols-2 gap-4">
887+
<div>
888+
<StoryTitle>Color</StoryTitle>
889+
<div className="flex items-center gap-2">
890+
{colors.map((color) => (
891+
<div
892+
key={color.join(',')}
893+
onClick={() => setRgbColor(color)}
894+
style={{ backgroundColor: `rgb(${color.map((c) => c * 255).join(',')})` }}
895+
className={cn(
896+
'h-4 w-4 cursor-pointer rounded-full',
897+
rgbColor.toString() === color.toString() &&
898+
'ring-muted-foreground ring-offset-background ring-1 ring-offset-2'
899+
)}
900+
/>
901+
))}
902+
903+
<Button
904+
type="button"
905+
size="sm"
906+
onClick={() => setRgbColor(colors[0])}
907+
className="relative"
908+
>
909+
<span className="text-muted-foreground text-xs">Pick a color</span>
910+
<span
911+
className="inline-block size-4 rounded-full"
912+
style={{ backgroundColor: `rgb(${rgbColor.map((c) => c * 255).join(',')})` }}
913+
/>
914+
<input
915+
type="color"
916+
onChange={handleColorChange}
917+
className="absolute inset-0 m-0 block h-full w-full opacity-0"
918+
/>
919+
</Button>
920+
</div>
921+
</div>
922+
923+
<div>
924+
{fields.map(([name, value, setValue, min = 0.1, max = 10, step = 0.1]) => {
925+
return (
926+
<div key={name}>
927+
<div className="flex items-center justify-between">
928+
<StoryTitle>{name}</StoryTitle>
929+
<div className="text-muted-foreground mb-2 text-xs">{String(value)}</div>
930+
</div>
931+
<input
932+
type="range"
933+
value={String(value)}
934+
min={min}
935+
max={max}
936+
step={step}
937+
onChange={(e) => setValue(parseFloat(e.target.value))}
938+
className="w-full"
939+
/>
940+
</div>
941+
);
942+
})}
943+
</div>
944+
</div>
790945
</Container>
791946
);
792947
},

components/app/session-view.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export const SessionView = ({
108108
</div>
109109

110110
{/* Tile Layout */}
111-
<TileLayout chatOpen={chatOpen} />
111+
<TileLayout chatOpen={chatOpen} appConfig={appConfig} />
112112

113113
{/* Bottom */}
114114
<MotionBottom

0 commit comments

Comments
 (0)