@@ -12,7 +12,6 @@ import {
1212 // useVoiceAssistant,
1313} from '@livekit/components-react' ;
1414import { MicrophoneIcon } from '@phosphor-icons/react/dist/ssr' ;
15- import { useSession } from '@/components/app/session-provider' ;
1615import { AgentControlBar } from '@/components/livekit/agent-control-bar/agent-control-bar' ;
1716import { 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' ;
4746import { ShimmerText } from '@/components/livekit/shimmer-text' ;
4847import { Toggle , toggleVariants } from '@/components/livekit/toggle' ;
48+ import { useConnection } from '@/hooks/useConnection' ;
49+ import { cn } from '@/lib/utils' ;
4950
5051type toggleVariantsType = VariantProps < typeof toggleVariants > [ 'variant' ] ;
5152type toggleVariantsSizeType = VariantProps < typeof toggleVariants > [ 'size' ] ;
@@ -61,13 +62,13 @@ type audioShaderVisualizerVariantsSizeType = VariantProps<
6162> [ 'size' ] ;
6263
6364export 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
7374interface 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 - 9 a - f A - F ] { 2 } ) ( [ 0 - 9 a - f A - F ] { 2 } ) ( [ 0 - 9 a - f A - 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 - 9 a - f A - F ] { 2 } ) ( [ 0 - 9 a - f A - F ] { 2 } ) ( [ 0 - 9 a - f A - 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 } ,
0 commit comments