feat: add LiveKit styles and enhance meeting page UI with improved layout and components
Some checks failed
GBCI / build (push) Failing after 33s

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-04-27 09:25:48 -03:00
parent a3c67a3bcc
commit 22eaff3899
7 changed files with 536 additions and 52 deletions

View file

@ -0,0 +1,33 @@
name: GBCI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: gbo
steps:
- name: Disable SSL verification (temporary)
run: git config --global http.sslVerify false
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies and build
run: |
pnpm i
pnpm run build
- name: Deploy to production
run: |
sudo rm -rf /opt/gbo/websites/next.generalbots.online/*
sudo cp -r ./out/. /opt/gbo/websites/next.generalbots.online/

View file

@ -2,6 +2,7 @@
import { usePathname, useRouter } from 'next/navigation';
import { Button } from '../src/components/ui/button';
import Image from 'next/image';
const examples = [
{ name: "Chat", href: "/chat" },
@ -23,16 +24,26 @@ export function Nav() {
return (
<div className="examples-nav-container">
<div className="examples-nav-inner">
{examples.map((example) => (
<Button
key={example.href}
onClick={() => router.push(example.href)}
className={`example-button ${pathname === example.href ? 'active' : ''}`}
>
{example.name}
</Button>
))}
<div className="examples-nav-inner" style={{ display: 'flex', alignItems: 'center' }}>
<Image
src={"/images/generalbots-logo.svg"}
alt="Logo"
width={92}
height={56}
className="logo"
style={{ marginLeft: '10px' , marginRight: '10px' , marginTop: '3px'}} // Add some spacing between logo and buttons
/>
<div style={{ display: 'flex' }}>
{examples.map((example) => (
<Button
key={example.href}
onClick={() => router.push(example.href)}
className={`example-button ${pathname === example.href ? 'active' : ''}`}
>
{example.name}
</Button>
))}
</div>
</div>
</div>
);

View file

@ -9,6 +9,7 @@ import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card } from '@/components/ui/card'
import { ScrollArea } from '@/components/ui/scroll-area'
import '@livekit/components-styles';
import { Avatar, AvatarFallback } from '@/components/ui/avatar'
import { Mic, MicOff, Video, VideoOff, Send, Bot } from 'lucide-react'
@ -28,6 +29,7 @@ export default function MeetingPage({ params }: { params: { room: string } }) {
const processorRef = useRef<ScriptProcessorNode>()
const participantsRef = useRef<Map<string, MediaStreamAudioSourceNode>>(new Map())
// Connect to LiveKit room
const connectToRoom = async () => {
try {
@ -137,27 +139,32 @@ export default function MeetingPage({ params }: { params: { room: string } }) {
useEffect(() => {
if (isConnected) {
// Setup code if needed
}
}, [isConnected])
if (!isConnected) {
return (
<div className="flex flex-col items-center justify-center min-h-screen p-4">
<Card className="w-full max-w-md p-6">
<h1 className="text-2xl font-bold mb-4">Join Meeting</h1>
<div className="flex flex-col items-center justify-center min-h-screen p-4 bg-gradient-to-br from-gray-50 to-gray-100">
<Card className="w-full max-w-md p-6 shadow-lg rounded-lg border-0">
<h1 className="text-2xl font-bold mb-6 text-center text-gray-800">Join Meeting</h1>
<div className="space-y-4">
<Input
placeholder="Your Name"
value={name}
onChange={(e) => setName(e.target.value)}
className="border-gray-300 focus:border-primary focus:ring-1 focus:ring-primary"
/>
<Input
placeholder="Room Name"
value={roomName}
onChange={(e) => setRoomName(e.target.value)}
className="border-gray-300 focus:border-primary focus:ring-1 focus:ring-primary"
/>
<Button className="w-full" onClick={connectToRoom}>
<Button
className="w-full bg-primary hover:bg-primary/90 transition-colors duration-200"
onClick={connectToRoom}
>
Join
</Button>
</div>
@ -167,82 +174,101 @@ export default function MeetingPage({ params }: { params: { room: string } }) {
}
return (
<div className="flex flex-col h-screen">
<LiveKitRoom
<div className="flex flex-col h-screen bg-gray-50">
<LiveKitRoom data-lk-theme="default"
token={token}
serverUrl={process.env.NEXT_PUBLIC_LIVEKIT_URL}
connect={true}
audio={micEnabled}
video={cameraEnabled}
className="flex-1"
>
<div className="flex flex-1">
{/* Video Area */}
<div className="flex-1 bg-gray-800 relative">
<VideoConference />
<div className="absolute bottom-4 left-4 flex gap-2">
<div className="flex flex-1 h-full">
{/* Video Area - Enhanced with better contrast and controls */}
<div className="flex-1 bg-gray-900 relative overflow-hidden">
<VideoConference className="absolute inset-0" />
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 flex gap-2 bg-gray-800/80 backdrop-blur-sm p-2 rounded-full">
<Button
variant={micEnabled ? 'default' : 'outline'}
variant={micEnabled ? 'primary' : 'outline'}
size="icon"
onClick={() => setMicEnabled(!micEnabled)}
className="rounded-full h-10 w-10 hover:bg-gray-700 transition-colors"
>
{micEnabled ? <Mic className="h-4 w-4" /> : <MicOff className="h-4 w-4" />}
{micEnabled ? <Mic className="h-5 w-5" /> : <MicOff className="h-5 w-5" />}
</Button>
<Button
variant={cameraEnabled ? 'default' : 'outline'}
variant={cameraEnabled ? 'primary' : 'outline'}
size="icon"
onClick={() => setCameraEnabled(!cameraEnabled)}
className="rounded-full h-10 w-10 hover:bg-gray-700 transition-colors"
>
{cameraEnabled ? <Video className="h-4 w-4" /> : <VideoOff className="h-4 w-4" />}
{cameraEnabled ? <Video className="h-5 w-5" /> : <VideoOff className="h-5 w-5" />}
</Button>
</div>
</div>
{/* Chat/Transcript Area */}
<div className="w-80 border-l flex flex-col bg-background">
<ScrollArea className="flex-1 p-4">
{/* Chat/Transcript Area - Improved readability and interaction */}
<div className="w-80 border-l flex flex-col bg-white shadow-lg">
<ScrollArea className="flex-1 p-4 space-y-3">
{messages.map((msg, i) => (
<div key={i} className={`mb-3 ${msg.isBot ? 'bg-muted/50' : ''} p-2 rounded`}>
<div className="flex items-center gap-2 mb-1">
<div
key={i}
className={`p-3 rounded-lg ${msg.isBot ? 'bg-blue-50 border border-blue-100' : 'bg-gray-50 border border-gray-100'}`}
>
<div className="flex items-center gap-2 mb-1.5">
{msg.isBot ? (
<Avatar className="h-6 w-6 bg-primary">
<AvatarFallback className="bg-primary">
<Bot className="h-3 w-3" />
<Avatar className="h-7 w-7 bg-blue-500">
<AvatarFallback className="bg-blue-500 text-white">
<Bot className="h-4 w-4" />
</AvatarFallback>
</Avatar>
) : (
<Avatar className="h-6 w-6">
<AvatarFallback>{msg.sender[0]}</AvatarFallback>
<Avatar className="h-7 w-7 bg-gray-500">
<AvatarFallback className="text-white">
{msg.sender[0].toUpperCase()}
</AvatarFallback>
</Avatar>
)}
<span className="font-medium">{msg.sender}</span>
<span className="font-medium text-sm text-gray-800">{msg.sender}</span>
</div>
<p className="text-sm">{msg.text}</p>
<p className="text-sm text-gray-700 pl-9">{msg.text}</p>
</div>
))}
{botTyping && (
<div className="mb-3 bg-muted/50 p-2 rounded">
<div className="flex items-center gap-2 mb-1">
<Avatar className="h-6 w-6 bg-primary">
<AvatarFallback className="bg-primary">
<Bot className="h-3 w-3" />
<div className="p-3 rounded-lg bg-blue-50 border border-blue-100">
<div className="flex items-center gap-2 mb-1.5">
<Avatar className="h-7 w-7 bg-blue-500">
<AvatarFallback className="bg-blue-500 text-white">
<Bot className="h-4 w-4" />
</AvatarFallback>
</Avatar>
<span className="font-medium">AI Assistant</span>
<span className="font-medium text-sm text-gray-800">AI Assistant</span>
</div>
<div className="flex space-x-1 pl-9">
<div className="w-2 h-2 rounded-full bg-gray-400 animate-bounce"></div>
<div className="w-2 h-2 rounded-full bg-gray-400 animate-bounce" style={{ animationDelay: '0.2s' }}></div>
<div className="w-2 h-2 rounded-full bg-gray-400 animate-bounce" style={{ animationDelay: '0.4s' }}></div>
</div>
<p className="text-sm text-muted-foreground">Typing...</p>
</div>
)}
</ScrollArea>
<div className="p-4 border-t">
<div className="flex gap-2 mb-2">
{/* Message Input - Enhanced with better visual feedback */}
<div className="p-4 border-t border-gray-200 bg-white">
<div className="flex gap-2">
<Input
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
placeholder="Type a message"
placeholder="Type a message..."
onKeyDown={(e) => e.key === 'Enter' && sendMessage()}
className="flex-1 border-gray-300 focus:border-primary focus:ring-1 focus:ring-primary rounded-full px-4"
/>
<Button size="icon" onClick={sendMessage}>
<Button
size="icon"
onClick={sendMessage}
className="rounded-full bg-primary hover:bg-primary/90 transition-colors"
disabled={!inputMessage.trim()}
>
<Send className="h-4 w-4" />
</Button>
</div>

200
package-lock.json generated
View file

@ -9,6 +9,8 @@
"version": "0.1.0",
"dependencies": {
"@hookform/resolvers": "^3.9.1",
"@livekit/components-react": "^2.9.3",
"@livekit/components-styles": "^1.1.5",
"@next/font": "^14.2.15",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.6",
@ -48,9 +50,11 @@
"cmdk": "^1.1.1",
"date-fns": "^2.30.0",
"jotai": "^2.12.2",
"livekit-client": "^2.11.3",
"lucide-react": "0.454.0",
"nativewind": "2.0.10",
"next": "^15.2.4",
"next-themes": "^0.4.6",
"postcss": "8.4.35",
"react": "18.3.1",
"react-day-picker": "^8.10.1",
@ -1800,6 +1804,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/@bufbuild/protobuf": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.1.tgz",
"integrity": "sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==",
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
"node_modules/@emnapi/runtime": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.0.tgz",
@ -3188,6 +3198,83 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@livekit/components-core": {
"version": "0.12.4",
"resolved": "https://registry.npmjs.org/@livekit/components-core/-/components-core-0.12.4.tgz",
"integrity": "sha512-a/GkK8XFULPhXoSKxuXEU62gwTAYJ83DP5/vlRzwESEY+rsoiw2NvvPZtDCU17yyd/5QBIF9VdDjB9ZZF0dOfQ==",
"license": "Apache-2.0",
"dependencies": {
"@floating-ui/dom": "1.6.13",
"loglevel": "1.9.1",
"rxjs": "7.8.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"livekit-client": "^2.11.1",
"tslib": "^2.6.2"
}
},
"node_modules/@livekit/components-core/node_modules/rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/@livekit/components-react": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/@livekit/components-react/-/components-react-2.9.3.tgz",
"integrity": "sha512-gE1sEE57BkBz3+TQHrOXVDVwVMwV5wtIYokdrfd7vshh22/PtWWj3vON9wzYLFRKx98L6QyAzyh7W9EWu3Lj9Q==",
"license": "Apache-2.0",
"dependencies": {
"@livekit/components-core": "0.12.4",
"clsx": "2.1.1",
"usehooks-ts": "3.1.1"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@livekit/krisp-noise-filter": "^0.2.12",
"livekit-client": "^2.11.1",
"react": ">=18",
"react-dom": ">=18",
"tslib": "^2.6.2"
},
"peerDependenciesMeta": {
"@livekit/krisp-noise-filter": {
"optional": true
}
}
},
"node_modules/@livekit/components-styles": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@livekit/components-styles/-/components-styles-1.1.5.tgz",
"integrity": "sha512-SocIPcwm18S28zVruvJcmiHfbUIwGTfxGbUOIp1Db78EON/iJ2v7B2g/xD5sr+c7jXoV1DNUPl2qQiFW2S9dbw==",
"license": "Apache-2.0",
"engines": {
"node": ">=18"
}
},
"node_modules/@livekit/mutex": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@livekit/mutex/-/mutex-1.1.1.tgz",
"integrity": "sha512-EsshAucklmpuUAfkABPxJNhzj9v2sG7JuzFDL4ML1oJQSV14sqrpTYnsaOudMAw9yOaW53NU3QQTlUQoRs4czw==",
"license": "Apache-2.0"
},
"node_modules/@livekit/protocol": {
"version": "1.36.1",
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.36.1.tgz",
"integrity": "sha512-nN3QnITAQ5yXk7UKfotH7CRWIlEozNWeKVyFJ0/+dtSzvWP/ib+10l1DDnRYi3A1yICJOGAKFgJ5d6kmi1HCUA==",
"license": "Apache-2.0",
"dependencies": {
"@bufbuild/protobuf": "^1.10.0"
}
},
"node_modules/@next/env": {
"version": "15.2.4",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.4.tgz",
@ -8387,9 +8474,7 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.8.x"
}
@ -10424,6 +10509,36 @@
"uc.micro": "^1.0.1"
}
},
"node_modules/livekit-client": {
"version": "2.11.3",
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.11.3.tgz",
"integrity": "sha512-WEyn3PMi/nBA096VvNN8ZHqfoJ/+s0klCPdQzzYtyBT7A5B0zHfzDy5YgaxXD817LxJEKjpVFwgQ4ddNmTAL4A==",
"license": "Apache-2.0",
"dependencies": {
"@livekit/mutex": "1.1.1",
"@livekit/protocol": "1.36.1",
"events": "^3.3.0",
"loglevel": "^1.9.2",
"sdp-transform": "^2.15.0",
"ts-debounce": "^4.0.0",
"tslib": "2.8.1",
"typed-emitter": "^2.1.0",
"webrtc-adapter": "^9.0.1"
}
},
"node_modules/livekit-client/node_modules/loglevel": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz",
"integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==",
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
},
"funding": {
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/loglevel"
}
},
"node_modules/loader-runner": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
@ -10454,6 +10569,19 @@
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"license": "MIT"
},
"node_modules/loglevel": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz",
"integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
},
"funding": {
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/loglevel"
}
},
"node_modules/longest-streak": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
@ -11555,6 +11683,16 @@
}
}
},
"node_modules/next-themes": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
"integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
}
},
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@ -13365,6 +13503,21 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/sdp": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz",
"integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==",
"license": "MIT"
},
"node_modules/sdp-transform": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.15.0.tgz",
"integrity": "sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==",
"license": "MIT",
"bin": {
"sdp-verify": "checker.js"
}
},
"node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
@ -14123,6 +14276,12 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/ts-debounce": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/ts-debounce/-/ts-debounce-4.0.0.tgz",
"integrity": "sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==",
"license": "MIT"
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@ -14152,6 +14311,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/typed-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz",
"integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==",
"license": "MIT",
"optionalDependencies": {
"rxjs": "*"
}
},
"node_modules/typescript": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
@ -14452,6 +14620,21 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/usehooks-ts": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.1.tgz",
"integrity": "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA==",
"license": "MIT",
"dependencies": {
"lodash.debounce": "^4.0.8"
},
"engines": {
"node": ">=16.15.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc"
}
},
"node_modules/username-sync": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/username-sync/-/username-sync-1.0.3.tgz",
@ -14838,6 +15021,19 @@
"node": ">=0.4.0"
}
},
"node_modules/webrtc-adapter": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-9.0.3.tgz",
"integrity": "sha512-5fALBcroIl31OeXAdd1YUntxiZl1eHlZZWzNg3U4Fn+J9/cGL3eT80YlrsWGvj2ojuz1rZr2OXkgCzIxAZ7vRQ==",
"license": "BSD-3-Clause",
"dependencies": {
"sdp": "^3.2.0"
},
"engines": {
"node": ">=6.0.0",
"npm": ">=3.10.0"
}
},
"node_modules/whatwg-fetch": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz",

View file

@ -11,6 +11,7 @@
"dependencies": {
"@hookform/resolvers": "^3.9.1",
"@livekit/components-react": "^2.9.3",
"@livekit/components-styles": "^1.1.5",
"@next/font": "^14.2.15",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.6",

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 51 KiB

File diff suppressed because one or more lines are too long