-
Notifications
You must be signed in to change notification settings - Fork 281
chore: improve LK UI #295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
chore: improve LK UI #295
Changes from all commits
8a23dd0
3b0cdce
72805d5
cee2b84
7292bb6
3c1e820
abd8ea4
7f284a5
2f8f2af
90ae2d8
51ced70
eeb2315
7a4a73e
e3c900a
eded239
48492fb
ea71b77
cd54234
319cd40
e007697
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| 'use client'; | ||
|
|
||
| import Link from 'next/link'; | ||
| import { useVoiceAssistant } from '@livekit/components-react'; | ||
| import { AgentControlBar } from '@/components/livekit/agent-control-bar/agent-control-bar'; | ||
| import { AudioBarVisualizer } from '@/components/livekit/audio-visualizer/audio-bar-visualizer/audio-bar-visualizer'; | ||
| import { Button } from '@/components/livekit/button'; | ||
| import { ChatEntry } from '@/components/livekit/chat-entry'; | ||
| import { useMicrophone } from '@/hooks/useMicrophone'; | ||
|
|
||
| export default function Page() { | ||
| const { state, audioTrack } = useVoiceAssistant(); | ||
|
|
||
| useMicrophone(); | ||
|
|
||
| return ( | ||
| <> | ||
| <header className="grid h-96 place-content-center space-y-6 text-center"> | ||
| <h1 className="flex items-baseline justify-center gap-2 text-5xl"> | ||
| <svg | ||
| height="48" | ||
| viewBox="0 0 123 28" | ||
| fill="none" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| className="text-foreground" | ||
| > | ||
| <path | ||
| d="M4.7 0H0v27.6h17v-4H4.7V0ZM24.8 12.5h-4.5v15h4.5v-15ZM38.2 27 32.4 8H28l6 19.6h8.6l6-19.6H44l-5.8 19ZM59.8 7.6c-5.9 0-9.6 4.2-9.6 10.2 0 6 3.6 10.2 9.6 10.2 4.6 0 8-2 9.2-6.2h-4.6c-.7 1.9-2 3-4.5 3-2.8 0-4.7-2-5-5.7h14.4l.1-1.4c0-6.1-3.8-10.1-9.6-10.1Zm-5 8.4c.5-3.6 2.4-5.2 5-5.2 2.9 0 4.7 2 5 5.2h-10ZM96 0h-5.9L78.7 12.6V0H74v27.6h4.7v-14l12.6 14h6L84.1 13 96.1 0ZM104 8h-4.6v15h4.5V8ZM20.3 8h-4.6v4.5h4.6V8ZM108.5 23h-4.6v4.6h4.6V23ZM122 23h-4.5v4.6h4.6V23ZM122 12.5V8h-4.5V0H113v8h-4.6v4.5h4.6V23h4.5V12.5h4.6Z" | ||
| fill="currentColor" | ||
| /> | ||
| </svg> | ||
| <span className="font-extralight tracking-tighter">UI</span> | ||
| </h1> | ||
| <p className="text-lg text-pretty"> | ||
| A set of Open Source UI components for | ||
| <br /> | ||
| building beautiful voice experiences. | ||
| </p> | ||
| <div className="flex justify-center gap-4"> | ||
| <Button variant="primary" asChild> | ||
| <Link href="/ui/components">View components</Link> | ||
| </Button> | ||
| <Button variant="ghost" asChild> | ||
| <Link href="https://docs.livekit.io/agents/start/frontend/">Read our docs</Link> | ||
| </Button> | ||
| </div> | ||
| </header> | ||
|
|
||
| <main className="mx-auto max-w-5xl space-y-8"> | ||
| <div className="grid grid-cols-1 gap-4 md:grid-cols-2"> | ||
| <div className="border-border bg-background h-96 rounded-3xl border p-8"> | ||
| <div className="flex h-full flex-col gap-4"> | ||
| <div className="grid flex-1 grow place-content-center"> | ||
| <AudioBarVisualizer state={state} audioTrack={audioTrack!} /> | ||
| </div> | ||
| <AgentControlBar | ||
| className="w-full" | ||
| controls={{ | ||
| leave: true, | ||
| chat: true, | ||
| camera: true, | ||
| microphone: true, | ||
| screenShare: true, | ||
| }} | ||
| /> | ||
| </div> | ||
| </div> | ||
| <div className="border-border bg-background h-96 rounded-3xl border p-8"> | ||
| <div className="flex h-full flex-col gap-4"> | ||
| <div className="flex-1 grow"> | ||
| <ChatEntry | ||
| locale="en-US" | ||
| name="User" | ||
| message="Hello, how are you?" | ||
| messageOrigin="local" | ||
| timestamp={1761096559966} | ||
| /> | ||
| <ChatEntry | ||
| locale="en-US" | ||
| name="Agent" | ||
| message="I am good, how about you?" | ||
| messageOrigin="remote" | ||
| timestamp={1761096569216} | ||
| /> | ||
| </div> | ||
| <AgentControlBar | ||
| className="w-full" | ||
| controls={{ | ||
| leave: true, | ||
| chat: true, | ||
| camera: true, | ||
| microphone: true, | ||
| screenShare: true, | ||
| }} | ||
| /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </main> | ||
| </> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| THIS IS NOT PART OF THE MAIN APPLICATION CODE. | ||
|
|
||
| This folder contains code for testing and previewing LiveKit's UI component library in isolation. | ||
|
|
||
| ## Getting started | ||
|
|
||
| To run the development server, run the following command: | ||
|
|
||
| ```bash | ||
| npm run dev | ||
| ``` | ||
|
|
||
| Then, navigate to `http://localhost:3000/ui` to see the components. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| 'use client'; | ||
|
|
||
| import dynamic from 'next/dynamic'; | ||
| import { redirect, useParams } from 'next/navigation'; | ||
|
|
||
| export default function Page() { | ||
| const { slug = [] } = useParams(); | ||
| const [componentName] = slug; | ||
| const ComponentDemo = dynamic(() => import(`@/components/demos/${componentName}`)); | ||
|
|
||
| if (!ComponentDemo) { | ||
| return redirect('/ui'); | ||
| } | ||
|
|
||
| return ( | ||
| <> | ||
| <h1 className="text-foreground mb-8 text-5xl font-bold">{componentName}</h1> | ||
| <ComponentDemo /> | ||
| </> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import { SideNav } from '@/components/docs/side-nav'; | ||
| import { getComponentNames } from '@/lib/components'; | ||
|
|
||
| interface LayoutProps { | ||
| children: React.ReactNode; | ||
| } | ||
|
|
||
| export default function Layout({ children }: LayoutProps) { | ||
| const componentNames = getComponentNames(); | ||
|
|
||
| return ( | ||
| <div className="grid grid-cols-1 gap-8 md:grid-cols-[100px_1fr_100px]"> | ||
| <aside className="sticky top-0 hidden py-10 md:block"> | ||
| <div className="flex flex-col gap-2"> | ||
| <SideNav componentNames={componentNames} /> | ||
| </div> | ||
| </aside> | ||
|
|
||
| <div className="space-y-8 py-8"> | ||
| <main className="mx-auto max-w-3xl space-y-8">{children}</main> | ||
| </div> | ||
|
|
||
| <aside className="sticky top-0 hidden md:block"></aside> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change | |||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,31 @@ | |||||||||||||||||||||||||||||
| import Link from 'next/link'; | |||||||||||||||||||||||||||||
| import { getComponentNames } from '@/lib/components'; | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| export default function Page() { | |||||||||||||||||||||||||||||
| const componentNames = getComponentNames(); | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| return ( | |||||||||||||||||||||||||||||
| <> | |||||||||||||||||||||||||||||
| <h2 id="components" className="mb-8 text-4xl font-bold tracking-tighter"> | |||||||||||||||||||||||||||||
| Components | |||||||||||||||||||||||||||||
| </h2> | |||||||||||||||||||||||||||||
| <p className="text-muted-foreground text-balance"> | |||||||||||||||||||||||||||||
| Build beautiful voice experiences with our components. | |||||||||||||||||||||||||||||
| </p> | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| <div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3"> | |||||||||||||||||||||||||||||
| {componentNames | |||||||||||||||||||||||||||||
| .sort((a, b) => a.localeCompare(b)) | |||||||||||||||||||||||||||||
| .map((componentName) => ( | |||||||||||||||||||||||||||||
| <Link | |||||||||||||||||||||||||||||
| href={`/ui/components/${componentName}`} | |||||||||||||||||||||||||||||
| key={componentName} | |||||||||||||||||||||||||||||
Check failureCode scanning / CodeQL Stored cross-site scripting High
Stored cross-site scripting vulnerability due to
stored value Error loading related location Loading
Copilot AutofixAI 9 days ago The best way to fix the problem is to ensure that all untrusted inputs rendered in the URL context are safely encoded. Specifically, when constructing the Files to change:
No code changes necessary in
Suggested changeset
1
app/ui/components/page.tsx
Copilot is powered by AI and may make mistakes. Always verify output.
Positive FeedbackNegative Feedback
Refresh and try again.
|
|||||||||||||||||||||||||||||
| className="font-semibold underline-offset-4 hover:underline focus:underline" | |||||||||||||||||||||||||||||
| > | |||||||||||||||||||||||||||||
| {componentName} | |||||||||||||||||||||||||||||
| </Link> | |||||||||||||||||||||||||||||
| ))} | |||||||||||||||||||||||||||||
| </div> | |||||||||||||||||||||||||||||
| </> | |||||||||||||||||||||||||||||
| ); | |||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||
Check failure
Code scanning / CodeQL
Stored cross-site scripting High
Copilot Autofix
AI 9 days ago
The best fix is to ensure that only valid, expected component names are used, and that they are sanitized before inclusion in URLs and as visible text. Specifically:
A robust approach is to update
getComponentNames()inlib/components.tsxso it only returns component names that match a safe pattern (e.g.,/^[a-zA-Z0-9_-]+$/).Make the change directly in
lib/components.tsx.No changes are needed in
app/ui/components/page.tsx, as the source of truth will now be validated and only return safe values.