Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ test-output
*.db*
.mastra

# NX cache
.nx/

# Vite cache
node_modules/.vite
**/.vite
Expand Down
2 changes: 2 additions & 0 deletions apps/agentic-chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@
"rehype-katex": "^7.0.1",
"remark-gfm": "^4.0.1",
"remark-math": "^6.0.0",
"shaders": "^2.5.109",
"sonner": "^2.0.7",
"tailwind-merge": "^3.2.0",
"three": "^0.184.0",
"viem": "*",
"wagmi": "^2.15.6",
"zod": "*",
Expand Down
2 changes: 1 addition & 1 deletion apps/agentic-chat/src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const Dashboard = () => {
<SidebarProvider>
{isSidebarLeftEnabled && <SidebarLeft />}
<SidebarInset className="h-dvh flex flex-col">
<header className="sticky top-0 h-12 flex-shrink-0 flex gap-2 bg-background z-10 px-2 items-center">
<header className="sticky top-0 h-12 flex-shrink-0 flex gap-2 bg-background/80 backdrop-blur-md border-b border-border z-10 px-2 items-center">
<div className="flex items-center gap-2">{isSidebarLeftEnabled && <SidebarTrigger />}</div>
<div className="ml-auto flex items-center gap-2">
<ExportChat />
Expand Down
107 changes: 107 additions & 0 deletions apps/agentic-chat/src/components/AuroraBackground.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { useEffect, useRef, useState } from 'react'

import { useIsMobile } from '@/hooks/use-mobile'

let cachedWebGLAvailable: boolean | null = null
function isWebGLAvailable(): boolean {
if (cachedWebGLAvailable !== null) return cachedWebGLAvailable
try {
const canvas = document.createElement('canvas')
const ctx = window.WebGLRenderingContext && (canvas.getContext('webgl2') ?? canvas.getContext('webgl'))
if (ctx) {
// Release the test context immediately so we don't exhaust the browser's limit
const ext = (ctx as WebGLRenderingContext).getExtension('WEBGL_lose_context')
ext?.loseContext()
}
cachedWebGLAvailable = !!ctx
} catch {
cachedWebGLAvailable = false
}
return cachedWebGLAvailable
}

function CSSFallback() {
return (
<div
className="absolute inset-0 w-full h-full"
style={{
background:
'radial-gradient(ellipse 80% 60% at 50% 0%, oklch(0.45 0.2 290 / 0.35) 0%, transparent 70%), radial-gradient(ellipse 60% 40% at 30% 20%, oklch(0.55 0.18 160 / 0.2) 0%, transparent 60%)',
}}
/>
)
}

function AuroraCanvas({ onError }: { onError: () => void }) {
const canvasRef = useRef<HTMLCanvasElement>(null)

useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return

let cleanup: (() => void) | null = null
let cancelled = false

import('shaders/js')
.then(({ createShader }) => {
if (cancelled) return Promise.resolve(null)
return createShader(
canvas,
{
components: [
{
id: 'aurora',
type: 'Aurora',
props: {
colorA: '#7B2FBE',
colorB: '#00CD98',
colorC: '#A855F7',
speed: 3.5,
waviness: 70,
intensity: 90,
curtainCount: 4,
rayDensity: 25,
height: 150,
balance: 40,
colorSpace: 'linear',
},
},
],
},
{ disableTelemetry: true }
)
})
.then(shader => {
if (!shader) return
if (cancelled) {
shader.destroy()
return
}
cleanup = () => shader.destroy()
})
.catch(err => {
console.error('[AuroraBackground] shader init failed, falling back to CSS:', err)
cleanup?.()
cleanup = null
if (!cancelled) onError()
})

return () => {
cancelled = true
cleanup?.()
}
}, [onError])

return <canvas ref={canvasRef} className="absolute inset-0 w-full h-full" style={{ display: 'block' }} />
}

export function AuroraBackground() {
const isMobile = useIsMobile()
const [shaderFailed, setShaderFailed] = useState(false)

if (isMobile || !isWebGLAvailable() || shaderFailed) {
return <CSSFallback />
}

return <AuroraCanvas onError={() => setShaderFailed(true)} />
}
10 changes: 6 additions & 4 deletions apps/agentic-chat/src/components/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useStreamPauseDetector } from '../hooks/useStreamPauseDetector'
import { useChatContext } from '../providers/ChatProvider'

import { AssistantMessage } from './AssistantMessage'
import { AuroraBackground } from './AuroraBackground'
import { Composer } from './Composer'
import { LoadingIndicator } from './LoadingIndicator'
import { Button } from './ui/Button'
Expand Down Expand Up @@ -93,8 +94,9 @@ export function Chat() {
{/* Messages viewport */}
<div className="flex-1 overflow-hidden">
{isEmpty ? (
<div className="flex h-full items-center justify-center">
<div className="text-lg text-foreground">How can I help you today?</div>
<div className="relative flex h-full items-center justify-center overflow-hidden">
<AuroraBackground />
<div className="relative z-10 text-lg text-foreground">How can I help you today?</div>
</div>
) : (
<Virtuoso
Expand All @@ -116,7 +118,7 @@ export function Chat() {

{/* Suggestions above composer - only shown when empty */}
{isEmpty && (
<div className="bg-background">
<div className="bg-background/80 backdrop-blur-md border-t border-border">
<div className="mx-auto flex max-w-2xl gap-2 px-4 py-3">
{WELCOME_SUGGESTIONS.map((suggestion, index) => (
<Button
Expand All @@ -134,7 +136,7 @@ export function Chat() {
)}

{/* Composer */}
<div className="bg-background">
<div className={isEmpty ? 'bg-background/80 backdrop-blur-md' : 'bg-background'}>
<div className="mx-auto max-w-2xl p-4">
<Composer />
</div>
Expand Down
3 changes: 3 additions & 0 deletions apps/agentic-chat/vite.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
export default defineConfig(() => ({
plugins: [react(), tailwindcss()],
optimizeDeps: {
include: ['three'],
},
envDir: resolve(__dirname, '../..'),
cacheDir: resolve(__dirname, '../../node_modules/.vite/agentic-chat'),
define: {
Expand Down
3 changes: 3 additions & 0 deletions apps/agentic-chat/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { defineConfig } from 'vite'

export default defineConfig(() => ({
plugins: [react(), tailwindcss()],
optimizeDeps: {
include: ['three'],
},
envDir: resolve(__dirname, '../..'),
cacheDir: resolve(__dirname, '../../node_modules/.vite/agentic-chat'),
define: {
Expand Down
6 changes: 6 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading