@standardagents/cli 0.10.1-next.bbd142a → 0.11.0-next.99fb790

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.txt ADDED
@@ -0,0 +1,48 @@
1
+ PROPRIETARY SOFTWARE LICENSE
2
+
3
+ Copyright (c) 2024-2025 FormKit Inc. All Rights Reserved.
4
+
5
+ UNLICENSED - DO NOT USE
6
+
7
+ This software and associated documentation files (the "Software") are the sole
8
+ and exclusive property of FormKit Inc. ("FormKit").
9
+
10
+ USE RESTRICTIONS
11
+
12
+ The Software is UNLICENSED and proprietary. NO PERMISSION is granted to use,
13
+ copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
14
+ the Software, or to permit persons to whom the Software is furnished to do so,
15
+ under any circumstances, without prior written authorization from FormKit Inc.
16
+
17
+ UNAUTHORIZED USE PROHIBITED
18
+
19
+ Any use of this Software without a valid, written license agreement signed by
20
+ authorized officers of FormKit Inc. is strictly prohibited and constitutes
21
+ unauthorized use and infringement of FormKit's intellectual property rights.
22
+
23
+ LICENSING INQUIRIES
24
+
25
+ Organizations interested in licensing this Software should contact:
26
+ enterprise@formkit.com
27
+
28
+ A written license agreement must be executed before any use of this Software
29
+ is authorized.
30
+
31
+ NO WARRANTY
32
+
33
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
36
+ FORMKIT INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
37
+ AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
38
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
39
+
40
+ GOVERNING LAW
41
+
42
+ This license shall be governed by and construed in accordance with the laws
43
+ of the jurisdiction in which FormKit Inc. is incorporated, without regard to
44
+ its conflict of law provisions.
45
+
46
+ FormKit Inc.
47
+ https://formkit.com
48
+ enterprise@formkit.com
@@ -0,0 +1,24 @@
1
+ import type { Metadata } from 'next'
2
+ import { Inter } from 'next/font/google'
3
+ import '../../src/index.css'
4
+
5
+ const inter = Inter({ subsets: ['latin'] })
6
+
7
+ export const metadata: Metadata = {
8
+ title: 'Chat',
9
+ description: 'Standard Agents Chat',
10
+ }
11
+
12
+ export default function RootLayout({
13
+ children,
14
+ }: {
15
+ children: React.ReactNode
16
+ }) {
17
+ return (
18
+ <html lang="en" className="dark">
19
+ <body className={`${inter.className} bg-[#0a0a0b] text-zinc-100 antialiased`}>
20
+ {children}
21
+ </body>
22
+ </html>
23
+ )
24
+ }
@@ -0,0 +1,21 @@
1
+ 'use client'
2
+
3
+ import { AgentBuilderProvider } from '@standardagents/react'
4
+ import { ThemeProvider } from '../../src/hooks/useTheme'
5
+ import { AuthProvider } from '../../src/hooks/useAuth'
6
+ import App from '../../src/App'
7
+
8
+ // SDK endpoint - uses rewrites so just /api
9
+ const SDK_ENDPOINT = '/api'
10
+
11
+ export default function Page() {
12
+ return (
13
+ <ThemeProvider>
14
+ <AuthProvider>
15
+ <AgentBuilderProvider config={{ endpoint: SDK_ENDPOINT }}>
16
+ <App />
17
+ </AgentBuilderProvider>
18
+ </AuthProvider>
19
+ </ThemeProvider>
20
+ )
21
+ }
@@ -0,0 +1,6 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+ /// <reference path="./.next/types/routes.d.ts" />
4
+
5
+ // NOTE: This file should not be edited
6
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -0,0 +1,15 @@
1
+ import type { NextConfig } from 'next'
2
+
3
+ const nextConfig: NextConfig = {
4
+ async rewrites() {
5
+ const builderUrl = process.env.NEXT_PUBLIC_AGENTBUILDER_URL || 'http://localhost:5173'
6
+ return [
7
+ {
8
+ source: '/api/:path*',
9
+ destination: `${builderUrl}/api/:path*`,
10
+ },
11
+ ]
12
+ },
13
+ }
14
+
15
+ export default nextConfig
@@ -0,0 +1,5 @@
1
+ export default {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["../src/*"]
23
+ }
24
+ },
25
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "../src/**/*.ts", "../src/**/*.tsx"],
26
+ "exclude": ["node_modules"]
27
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@standardagents/chat",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev:vite": "vite --config vite/vite.config.ts",
8
+ "build:vite": "vite build --config vite/vite.config.ts",
9
+ "dev:next": "cd next && next dev --port 5175",
10
+ "build:next": "cd next && next build"
11
+ },
12
+ "dependencies": {
13
+ "@standardagents/react": "workspace:*",
14
+ "next": "^15.1.0",
15
+ "react": "^19.0.0",
16
+ "react-dom": "^19.0.0",
17
+ "react-markdown": "^10.1.0",
18
+ "react-shiki": "^0.9.1"
19
+ },
20
+ "devDependencies": {
21
+ "@tailwindcss/postcss": "^4.0.0",
22
+ "@tailwindcss/vite": "^4.0.0",
23
+ "@types/node": "^22.0.0",
24
+ "@types/react": "^19.0.0",
25
+ "@types/react-dom": "^19.0.0",
26
+ "@vitejs/plugin-react": "^4.3.0",
27
+ "postcss": "^8.5.0",
28
+ "tailwindcss": "^4.0.0",
29
+ "typescript": "^5.9.0",
30
+ "vite": "^6.0.0"
31
+ }
32
+ }
@@ -0,0 +1,130 @@
1
+ import { useState, useCallback, useEffect } from 'react'
2
+ import { ThreadProvider } from '@standardagents/react'
3
+ import { Sidebar } from './components/Sidebar'
4
+ import { Chat } from './components/Chat'
5
+ import { EmptyState } from './components/EmptyState'
6
+ import { Login } from './components/Login'
7
+ import { useThreads, type Thread } from './hooks/useThreads'
8
+ import { useAuth } from './hooks/useAuth'
9
+
10
+ export default function App() {
11
+ const { isAuthenticated, isLoading: authLoading } = useAuth()
12
+
13
+ // Show loading state while checking auth
14
+ if (authLoading) {
15
+ return (
16
+ <div className="h-screen bg-[var(--bg-primary)] flex items-center justify-center">
17
+ <div className="text-[var(--text-secondary)]">Loading...</div>
18
+ </div>
19
+ )
20
+ }
21
+
22
+ // Show login if not authenticated
23
+ if (!isAuthenticated) {
24
+ return <Login />
25
+ }
26
+
27
+ return <AuthenticatedApp />
28
+ }
29
+
30
+ function AuthenticatedApp() {
31
+ const { threads, loading, refreshThreads, deleteThread, updateThreadTitle } = useThreads()
32
+ const [currentThreadId, setCurrentThreadId] = useState<string | null>(() => {
33
+ // Check URL for thread ID on initial load
34
+ const params = new URLSearchParams(window.location.search)
35
+ return params.get('thread')
36
+ })
37
+ const [selectedAgentId, setSelectedAgentId] = useState<string>('')
38
+ const [sidebarOpen, setSidebarOpen] = useState(true)
39
+
40
+ // Update URL when thread changes
41
+ useEffect(() => {
42
+ const url = new URL(window.location.href)
43
+ if (currentThreadId) {
44
+ url.searchParams.set('thread', currentThreadId)
45
+ } else {
46
+ url.searchParams.delete('thread')
47
+ }
48
+ window.history.replaceState({}, '', url.toString())
49
+ }, [currentThreadId])
50
+
51
+ const handleNewThread = useCallback(() => {
52
+ setCurrentThreadId(null)
53
+ }, [])
54
+
55
+ const handleSelectThread = useCallback((threadId: string) => {
56
+ setCurrentThreadId(threadId)
57
+ }, [])
58
+
59
+ const handleThreadCreated = useCallback((thread: Thread) => {
60
+ refreshThreads()
61
+ setCurrentThreadId(thread.id)
62
+ }, [refreshThreads])
63
+
64
+ const handleAgentChange = useCallback((agentId: string) => {
65
+ setSelectedAgentId(agentId)
66
+ }, [])
67
+
68
+ const handleDeleteThread = useCallback(async (threadId: string) => {
69
+ await deleteThread(threadId)
70
+ if (currentThreadId === threadId) {
71
+ setCurrentThreadId(null)
72
+ }
73
+ }, [deleteThread, currentThreadId])
74
+
75
+ return (
76
+ <div className="h-screen bg-[var(--bg-primary)]">
77
+ <Sidebar
78
+ threads={threads}
79
+ currentThreadId={currentThreadId}
80
+ onSelectThread={handleSelectThread}
81
+ onNewThread={handleNewThread}
82
+ onDeleteThread={handleDeleteThread}
83
+ isOpen={sidebarOpen}
84
+ onToggle={() => setSidebarOpen(!sidebarOpen)}
85
+ loading={loading}
86
+ />
87
+
88
+ {/* Main content with animated margin */}
89
+ <main
90
+ className={`
91
+ h-full flex flex-col min-w-0 relative
92
+ transition-[margin] duration-300 ease-out
93
+ ${sidebarOpen ? 'ml-72' : 'ml-0'}
94
+ `}
95
+ >
96
+ {/* Expand sidebar button - shown when collapsed */}
97
+ <button
98
+ onClick={() => setSidebarOpen(true)}
99
+ className={`
100
+ fixed top-[5.5px] left-3 z-30 p-2 rounded-lg hover:bg-[var(--bg-hover)]
101
+ transition-all duration-300 ease-out
102
+ ${sidebarOpen ? 'opacity-0 pointer-events-none -translate-x-2' : 'opacity-100 translate-x-0'}
103
+ `}
104
+ title="Expand sidebar"
105
+ >
106
+ {/* ChatGPT drawer icon */}
107
+ <svg className="w-5 h-5 text-[var(--text-secondary)]" viewBox="0 0 20 20" fill="currentColor">
108
+ <path d="M6.835 4c-.451.004-.82.012-1.137.038-.386.032-.659.085-.876.162l-.2.086c-.44.224-.807.564-1.063.982l-.103.184c-.126.247-.206.562-.248 1.076-.043.523-.043 1.19-.043 2.135v2.664c0 .944 0 1.612.043 2.135.042.515.122.829.248 1.076l.103.184c.256.418.624.758 1.063.982l.2.086c.217.077.49.13.876.162.316.026.685.034 1.136.038zm11.33 7.327c0 .922 0 1.654-.048 2.243-.043.522-.125.977-.305 1.395l-.082.177a4 4 0 0 1-1.473 1.593l-.276.155c-.465.237-.974.338-1.57.387-.59.048-1.322.048-2.244.048H7.833c-.922 0-1.654 0-2.243-.048-.522-.042-.977-.126-1.395-.305l-.176-.082a4 4 0 0 1-1.594-1.473l-.154-.275c-.238-.466-.34-.975-.388-1.572-.048-.589-.048-1.32-.048-2.243V8.663c0-.922 0-1.654.048-2.243.049-.597.15-1.106.388-1.571l.154-.276a4 4 0 0 1 1.594-1.472l.176-.083c.418-.18.873-.263 1.395-.305.589-.048 1.32-.048 2.243-.048h4.334c.922 0 1.654 0 2.243.048.597.049 1.106.15 1.571.388l.276.154a4 4 0 0 1 1.473 1.594l.082.176c.18.418.262.873.305 1.395.048.589.048 1.32.048 2.243zm-10 4.668h4.002c.944 0 1.612 0 2.135-.043.514-.042.829-.122 1.076-.248l.184-.103c.418-.256.758-.624.982-1.063l.086-.2c.077-.217.13-.49.162-.876.043-.523.043-1.19.043-2.135V8.663c0-.944 0-1.612-.043-2.135-.032-.386-.085-.659-.162-.876l-.086-.2a2.67 2.67 0 0 0-.982-1.063l-.184-.103c-.247-.126-.562-.206-1.076-.248-.523-.043-1.19-.043-2.135-.043H8.164L8.165 4z" />
109
+ </svg>
110
+ </button>
111
+
112
+ {currentThreadId ? (
113
+ <ThreadProvider key={currentThreadId} threadId={currentThreadId} live useWorkblocks>
114
+ <Chat
115
+ thread={threads.find(t => t.id === currentThreadId)}
116
+ onUpdateTitle={(title) => updateThreadTitle(currentThreadId, title)}
117
+ sidebarOpen={sidebarOpen}
118
+ />
119
+ </ThreadProvider>
120
+ ) : (
121
+ <EmptyState
122
+ selectedAgentId={selectedAgentId}
123
+ onAgentChange={handleAgentChange}
124
+ onThreadCreated={handleThreadCreated}
125
+ />
126
+ )}
127
+ </main>
128
+ </div>
129
+ )
130
+ }
@@ -0,0 +1,102 @@
1
+ 'use client'
2
+
3
+ import { useState, useEffect, useRef } from 'react'
4
+
5
+ interface Agent {
6
+ id: string
7
+ name: string
8
+ title?: string
9
+ }
10
+
11
+ interface AgentSelectorProps {
12
+ selectedAgentId: string
13
+ onSelectAgent: (agentId: string) => void
14
+ }
15
+
16
+ export function AgentSelector({ selectedAgentId, onSelectAgent }: AgentSelectorProps) {
17
+ const [agents, setAgents] = useState<Agent[]>([])
18
+ const [loading, setLoading] = useState(true)
19
+ const [isOpen, setIsOpen] = useState(false)
20
+ const dropdownRef = useRef<HTMLDivElement>(null)
21
+
22
+ useEffect(() => {
23
+ const fetchAgents = async () => {
24
+ try {
25
+ const response = await fetch('/api/agents')
26
+ if (!response.ok) throw new Error('Failed to fetch agents')
27
+ const data = await response.json()
28
+ const agentList = data.agents || []
29
+ setAgents(agentList)
30
+ if (!selectedAgentId && agentList.length > 0) {
31
+ onSelectAgent(agentList[0].id)
32
+ }
33
+ } catch (err) {
34
+ console.error('Failed to load agents:', err)
35
+ } finally {
36
+ setLoading(false)
37
+ }
38
+ }
39
+ fetchAgents()
40
+ }, [selectedAgentId, onSelectAgent])
41
+
42
+ useEffect(() => {
43
+ const handleClickOutside = (e: MouseEvent) => {
44
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
45
+ setIsOpen(false)
46
+ }
47
+ }
48
+ document.addEventListener('mousedown', handleClickOutside)
49
+ return () => document.removeEventListener('mousedown', handleClickOutside)
50
+ }, [])
51
+
52
+ const selectedAgent = agents.find(a => a.id === selectedAgentId)
53
+
54
+ if (loading) {
55
+ return (
56
+ <div className="inline-flex items-center gap-2 px-4 py-2 bg-zinc-800 rounded-full text-sm text-zinc-400">
57
+ Loading agents...
58
+ </div>
59
+ )
60
+ }
61
+
62
+ if (agents.length === 0) {
63
+ return (
64
+ <div className="inline-flex items-center gap-2 px-4 py-2 bg-zinc-800 rounded-full text-sm text-zinc-400">
65
+ No agents available
66
+ </div>
67
+ )
68
+ }
69
+
70
+ return (
71
+ <div ref={dropdownRef} className="relative inline-block">
72
+ <button
73
+ onClick={() => setIsOpen(!isOpen)}
74
+ className="inline-flex items-center gap-2 px-4 py-2 bg-zinc-800 hover:bg-zinc-700 rounded-full text-sm transition-colors"
75
+ >
76
+ <span className="text-zinc-300">{selectedAgent?.title || selectedAgent?.name || 'Select agent'}</span>
77
+ <svg className={`w-4 h-4 text-zinc-500 transition-transform ${isOpen ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
78
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
79
+ </svg>
80
+ </button>
81
+
82
+ {isOpen && (
83
+ <div className="absolute top-full mt-2 left-1/2 -translate-x-1/2 min-w-[200px] bg-zinc-800 border border-zinc-700 rounded-xl shadow-xl overflow-hidden z-50">
84
+ {agents.map((agent) => (
85
+ <button
86
+ key={agent.id}
87
+ onClick={() => {
88
+ onSelectAgent(agent.id)
89
+ setIsOpen(false)
90
+ }}
91
+ className={`w-full px-4 py-3 text-left text-sm hover:bg-zinc-700 transition-colors ${
92
+ agent.id === selectedAgentId ? 'bg-zinc-700 text-white' : 'text-zinc-300'
93
+ }`}
94
+ >
95
+ {agent.title || agent.name}
96
+ </button>
97
+ ))}
98
+ </div>
99
+ )}
100
+ </div>
101
+ )
102
+ }
@@ -0,0 +1,134 @@
1
+ 'use client'
2
+
3
+ import { useState, useCallback, useRef, useEffect } from 'react'
4
+ import { useThread } from '@standardagents/react'
5
+ import { MessageList } from './MessageList'
6
+ import { MessageInput } from './MessageInput'
7
+ import { type Thread, getThreadTitle } from '../hooks/useThreads'
8
+
9
+ interface ChatProps {
10
+ thread?: Thread
11
+ onUpdateTitle?: (title: string) => void
12
+ sidebarOpen?: boolean
13
+ }
14
+
15
+ export function Chat({ thread, onUpdateTitle, sidebarOpen = true }: ChatProps) {
16
+ const { status } = useThread()
17
+ const [isEditingTitle, setIsEditingTitle] = useState(false)
18
+ const [titleValue, setTitleValue] = useState('')
19
+ const inputRef = useRef<HTMLInputElement>(null)
20
+
21
+ const agentName = thread?.agent?.title || thread?.agent?.name || 'Agent'
22
+ const threadTitle = thread ? getThreadTitle(thread) : 'Untitled'
23
+
24
+ // Focus input when entering edit mode
25
+ useEffect(() => {
26
+ if (isEditingTitle && inputRef.current) {
27
+ inputRef.current.focus()
28
+ inputRef.current.select()
29
+ }
30
+ }, [isEditingTitle])
31
+
32
+ const handleStartEdit = useCallback(() => {
33
+ const currentTitle = thread ? getThreadTitle(thread) : ''
34
+ setTitleValue(currentTitle === 'Untitled' ? '' : currentTitle)
35
+ setIsEditingTitle(true)
36
+ }, [thread])
37
+
38
+ const handleSaveTitle = useCallback(() => {
39
+ const trimmed = titleValue.trim()
40
+ if (trimmed && onUpdateTitle) {
41
+ onUpdateTitle(trimmed)
42
+ }
43
+ setIsEditingTitle(false)
44
+ }, [titleValue, onUpdateTitle])
45
+
46
+ const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
47
+ if (e.key === 'Enter') {
48
+ handleSaveTitle()
49
+ } else if (e.key === 'Escape') {
50
+ setIsEditingTitle(false)
51
+ }
52
+ }, [handleSaveTitle])
53
+
54
+ // Connection status LED color
55
+ const ledColor = status === 'connected'
56
+ ? 'bg-emerald-500'
57
+ : status === 'connecting' || status === 'reconnecting'
58
+ ? 'bg-yellow-500 animate-pulse'
59
+ : 'bg-red-500'
60
+
61
+ return (
62
+ <div className="flex-1 flex flex-col w-full">
63
+ {/* Header bar - frosted glass effect, fixed on scroll */}
64
+ <div className={`
65
+ sticky top-0 z-20 h-[47px] flex items-center border-b border-[var(--border-primary)] bg-[var(--bg-primary)]/60 backdrop-blur-md
66
+ transition-[padding] duration-300 ease-out
67
+ ${sidebarOpen ? 'px-4' : 'pl-14 pr-4'}
68
+ `}>
69
+ <div className="flex items-center justify-between w-full">
70
+ {/* Editable title on the left */}
71
+ <div className="flex items-center gap-2 min-w-0">
72
+ {isEditingTitle ? (
73
+ <input
74
+ ref={inputRef}
75
+ type="text"
76
+ value={titleValue}
77
+ onChange={(e) => setTitleValue(e.target.value)}
78
+ onBlur={handleSaveTitle}
79
+ onKeyDown={handleKeyDown}
80
+ className="text-sm font-medium text-[var(--text-primary)] bg-transparent outline-none w-full max-w-xs"
81
+ placeholder="Enter title..."
82
+ />
83
+ ) : (
84
+ <>
85
+ <span className="text-sm font-medium text-[var(--text-primary)] truncate max-w-xs">
86
+ {threadTitle}
87
+ </span>
88
+ <button
89
+ onClick={handleStartEdit}
90
+ className="p-1 text-[var(--text-muted)] hover:text-[var(--text-secondary)] transition-colors shrink-0"
91
+ title="Edit title"
92
+ >
93
+ <svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
94
+ <path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" />
95
+ <path d="m15 5 4 4" />
96
+ </svg>
97
+ </button>
98
+ </>
99
+ )}
100
+ </div>
101
+
102
+ {/* Agent name and connection status on the right */}
103
+ <div className="flex items-center gap-3 ml-4">
104
+ <span className="text-sm text-[var(--text-tertiary)] hidden sm:inline">{agentName}</span>
105
+ <div
106
+ className={`w-2 h-2 rounded-full ${ledColor}`}
107
+ title={status === 'connected' ? 'Connected' : status === 'connecting' || status === 'reconnecting' ? 'Connecting...' : 'Disconnected'}
108
+ />
109
+ </div>
110
+ </div>
111
+ </div>
112
+
113
+ <div
114
+ className="flex-1 overflow-y-auto"
115
+ style={{
116
+ maskImage: 'linear-gradient(to bottom, black calc(100% - 80px), transparent 100%)',
117
+ WebkitMaskImage: 'linear-gradient(to bottom, black calc(100% - 80px), transparent 100%)',
118
+ }}
119
+ >
120
+ <div className="max-w-3xl mx-auto w-full pb-20">
121
+ <MessageList />
122
+ </div>
123
+ </div>
124
+
125
+ <div className="sticky bottom-0 z-20 pb-4 px-4 pointer-events-none">
126
+ {/* Opaque blocker behind input - covers bottom half of input area down */}
127
+ <div className="absolute bottom-0 left-0 right-0 h-12 bg-[var(--bg-primary)] pointer-events-none" />
128
+ <div className="relative max-w-3xl mx-auto w-full pointer-events-auto">
129
+ <MessageInput />
130
+ </div>
131
+ </div>
132
+ </div>
133
+ )
134
+ }