Skip to content

Commit 1cbc394

Browse files
init
1 parent 8bef257 commit 1cbc394

21 files changed

Lines changed: 1118 additions & 458 deletions

web/.env.example

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
NEXT_PUBLIC_ROBOT_IDENTITY=
2-
NEXT_PUBLIC_LIVEKIT_URL=
3-
NEXT_PUBLIC_LIVEKIT_TOKEN=
2+
NEXT_PUBLIC_ROBOT_LIVEKIT_URL=
3+
NEXT_PUBLIC_ROBOT_LIVEKIT_TOKEN=
4+
NEXT_PUBLIC_ROVER_IDENTITY=
5+
NEXT_PUBLIC_ROVER_LIVEKIT_URL=
6+
NEXT_PUBLIC_ROVER_LIVEKIT_TOKEN=

web/README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,15 @@ A browser-based teleoperation interface for controlling the pan/tilt robot via L
2424
Create a `.env.local` file in the `web/` directory:
2525

2626
```bash
27-
# Required: LiveKit server URL and a pre-generated participant token
28-
NEXT_PUBLIC_LIVEKIT_URL=wss://<your-livekit-server>
29-
NEXT_PUBLIC_LIVEKIT_TOKEN=<your-participant-token>
27+
# Robot (arm) — LiveKit credentials and participant identity
3028
NEXT_PUBLIC_ROBOT_IDENTITY=pt_robot
29+
NEXT_PUBLIC_ROBOT_LIVEKIT_URL=wss://<your-livekit-server>
30+
NEXT_PUBLIC_ROBOT_LIVEKIT_TOKEN=<your-participant-token>
31+
32+
# Rover — LiveKit credentials and participant identity
33+
NEXT_PUBLIC_ROVER_IDENTITY=rover-1
34+
NEXT_PUBLIC_ROVER_LIVEKIT_URL=wss://<your-livekit-server>
35+
NEXT_PUBLIC_ROVER_LIVEKIT_TOKEN=<your-participant-token>
3136
```
3237

3338
For local development with the token generation API:

web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"react": "19.2.4",
2525
"react-dom": "19.2.4",
2626
"shadcn": "^4.1.0",
27+
"sonner": "^2.0.7",
2728
"tailwind-merge": "^3.5.0",
2829
"tw-animate-css": "^1.4.0"
2930
},

web/pnpm-lock.yaml

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/src/app/globals.css

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
@import 'tailwindcss';
2-
@import 'tw-animate-css';
3-
@import 'shadcn/tailwind.css';
1+
@import "tailwindcss";
2+
@import "tw-animate-css";
3+
@import "shadcn/tailwind.css";
44

55
@custom-variant dark (&:is(.dark *));
66

@@ -9,14 +9,14 @@
99
--color-foreground: var(--foreground);
1010

1111
--font-sans:
12-
var(--font-lk-sans), ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
13-
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
12+
var(--font-lk-sans), ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
13+
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
1414
--font-mono:
15-
var(--font-lk-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
16-
'Courier New', monospace;
15+
var(--font-lk-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
16+
"Courier New", monospace;
1717
--font-display:
18-
var(--font-lk-display), ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
19-
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
18+
var(--font-lk-display), ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
19+
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
2020

2121
--color-sidebar-ring: var(--sidebar-ring);
2222
--color-sidebar-border: var(--sidebar-border);
@@ -80,6 +80,8 @@
8080
--foreground: oklch(var(--color-fgAccent1));
8181
--card: oklch(var(--color-fgAccent1) / 0.15);
8282
--card-foreground: oklch(var(--color-fgAccent1));
83+
--popover: oklch(var(--color-fgAccent1) / 0.15);
84+
--popover-foreground: oklch(var(--color-fgAccent1));
8385
--primary: oklch(var(--color-bgAccent1));
8486
--primary-foreground: oklch(var(--color-fgAccent1));
8587
--secondary: oklch(var(--color-bg2));
@@ -97,7 +99,7 @@
9799
--chart-3: oklch(var(--color-chart3));
98100
--chart-4: oklch(var(--color-chart4));
99101
--chart-5: oklch(var(--color-chart5));
100-
--radius: 0.625rem;
102+
--radius: 0.333rem;
101103
--sidebar: oklch(var(--color-bg2));
102104
--sidebar-foreground: oklch(var(--color-fg1));
103105
--sidebar-primary: oklch(var(--color-bgAccent1));

web/src/app/layout.tsx

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
import './globals.css';
1+
import "./globals.css";
22

3-
import { cn } from '@/lib/utils';
4-
import type { Metadata } from 'next';
5-
import Image from 'next/image';
3+
import { cn } from "@/lib/utils";
4+
import type { Metadata } from "next";
5+
import Image from "next/image";
66

7-
import logo from '../../public/logo.svg';
8-
import { displayFont, monoFont, sansFont } from '@/lib/fonts';
7+
import logo from "../../public/logo.svg";
8+
import { displayFont, monoFont, sansFont } from "@/lib/fonts";
9+
import { Toaster } from "@/components/ui/toaster";
910

1011
export const metadata: Metadata = {
11-
title: 'TeleOps UI',
12-
description: 'Robot teleoperation interface',
12+
title: "TeleOps UI",
13+
description: "Robot teleoperation interface",
1314
};
1415

1516
export default function RootLayout({
@@ -20,15 +21,20 @@ export default function RootLayout({
2021
return (
2122
<html
2223
lang="en"
23-
className={cn(sansFont.variable, displayFont.variable, monoFont.variable, 'dark')}
24+
className={cn(
25+
sansFont.variable,
26+
displayFont.variable,
27+
monoFont.variable,
28+
"dark",
29+
)}
2430
>
2531
<body
26-
className="min-h-screen overflow-hidden bg-size-[33dvh_33dvh] bg-center font-sans"
32+
className="min-h-screen overflow-hidden bg-size-[33dvh_33dvh] bg-center font-mono"
2733
style={{
2834
backgroundImage: [
29-
'linear-gradient(0deg, color-mix(in oklch, var(--primary-foreground) 10%, transparent) 1px, transparent 1px)',
30-
'linear-gradient(90deg, color-mix(in oklch, var(--primary-foreground) 10%, transparent) 1px, transparent 1px)',
31-
].join(', '),
35+
"linear-gradient(0deg, color-mix(in oklch, var(--primary-foreground) 10%, transparent) 1px, transparent 1px)",
36+
"linear-gradient(90deg, color-mix(in oklch, var(--primary-foreground) 10%, transparent) 1px, transparent 1px)",
37+
].join(", "),
3238
}}
3339
>
3440
{/* Header */}
@@ -40,6 +46,7 @@ export default function RootLayout({
4046
</header>
4147

4248
{children}
49+
<Toaster />
4350
</body>
4451
</html>
4552
);

web/src/app/page.tsx

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,102 @@
1-
'use client';
1+
"use client";
22

3-
import { App } from '@/components/app';
3+
import Link from "next/link";
4+
import { PowerIcon, TriangleAlertIcon } from "lucide-react";
45

5-
import { TokenSource } from 'livekit-client';
6-
import { SessionProvider, useSession } from '@livekit/components-react';
6+
import { Button } from "@/components/ui/button";
7+
import { motion, AnimatePresence } from "motion/react";
78

8-
const TOKEN_SOURCE = TokenSource.literal({
9-
serverUrl: process.env.NEXT_PUBLIC_LIVEKIT_URL as string,
10-
participantToken: process.env.NEXT_PUBLIC_LIVEKIT_TOKEN as string,
11-
});
9+
const armConfigured = Boolean(
10+
process.env.NEXT_PUBLIC_ROBOT_IDENTITY &&
11+
process.env.NEXT_PUBLIC_ROBOT_LIVEKIT_URL &&
12+
process.env.NEXT_PUBLIC_ROBOT_LIVEKIT_TOKEN,
13+
);
1214

13-
export default function Home() {
14-
const session = useSession(TOKEN_SOURCE);
15+
const roverConfigured = Boolean(
16+
process.env.NEXT_PUBLIC_ROVER_IDENTITY &&
17+
process.env.NEXT_PUBLIC_ROVER_LIVEKIT_URL &&
18+
process.env.NEXT_PUBLIC_ROVER_LIVEKIT_TOKEN,
19+
);
20+
21+
function EmptyState() {
22+
return (
23+
<div className="border-input bg-card flex max-w-md flex-col items-center gap-3 rounded-lg border px-6 py-8 text-center backdrop-blur-lg">
24+
<TriangleAlertIcon className="text-foreground size-12" />
25+
<h1 className="font-mono text-sm uppercase tracking-wide">
26+
No robots configured
27+
</h1>
28+
<p className="text-muted-foreground font-mono text-xs leading-relaxed">
29+
Define{" "}
30+
<code className="bg-card text-foreground rounded px-1 py-0.5">
31+
NEXT_PUBLIC_ROBOT_*
32+
</code>{" "}
33+
and/or{" "}
34+
<code className="bg-card text-foreground rounded px-1 py-0.5">
35+
NEXT_PUBLIC_ROVER_*
36+
</code>{" "}
37+
env vars in{" "}
38+
<code className="bg-card text-foreground rounded px-1 py-0.5">
39+
.env.local
40+
</code>{" "}
41+
and restart the dev server.
42+
</p>
43+
</div>
44+
);
45+
}
1546

47+
export default function Home() {
1648
return (
17-
<SessionProvider session={session}>
18-
<App />
19-
</SessionProvider>
49+
<AnimatePresence>
50+
{!armConfigured && !roverConfigured ? (
51+
<motion.div
52+
initial={{ opacity: 0, scale: 0.9 }}
53+
animate={{ opacity: 1, scale: 1 }}
54+
exit={{ opacity: 0, scale: 0.9 }}
55+
className="relative grid h-dvh place-items-center"
56+
>
57+
<EmptyState />
58+
</motion.div>
59+
) : (
60+
<motion.div
61+
initial={{ opacity: 0 }}
62+
animate={{ opacity: 1 }}
63+
exit={{ opacity: 0 }}
64+
className="relative grid h-dvh place-items-center gap-4"
65+
>
66+
<div className="bg-card border-input rounded-lg border p-8 backdrop-blur-lg w-full max-w-sm">
67+
<h1 className="text-2xl uppercase tracking-wide mb-4">
68+
TeleOp Web UI
69+
</h1>
70+
<p className="uppercase mb-2">Connect to:</p>
71+
<div className="flex gap-2 justify-center">
72+
{armConfigured && (
73+
<Link href="/robot" className="grow">
74+
<Button
75+
size="lg"
76+
variant="outline"
77+
className="font-mono uppercase w-full"
78+
>
79+
<PowerIcon className="size-4 justify-self-start" />
80+
<span className="grow text-left">Robot</span>
81+
</Button>
82+
</Link>
83+
)}
84+
{roverConfigured && (
85+
<Link href="/rover" className="grow">
86+
<Button
87+
size="lg"
88+
variant="outline"
89+
className="font-mono uppercase w-full"
90+
>
91+
<PowerIcon className="size-4 justify-self-start" />
92+
<span className="grow text-left">Rover</span>
93+
</Button>
94+
</Link>
95+
)}
96+
</div>
97+
</div>
98+
</motion.div>
99+
)}
100+
</AnimatePresence>
20101
);
21102
}

0 commit comments

Comments
 (0)