mukaddamzaid commited on
Commit
7984c85
·
1 Parent(s): 5012205

feat: enhanced UI

Browse files

- Updated Providers component to manage sidebar state using local storage.
- Enhanced ChatSidebar with dropdown menu for user actions and improved layout.
- Modified MCPServerManager to include server descriptions and improved UI responsiveness.
- Adjusted Chat component layout for better spacing and usability.
- Added constants for sidebar state management in local storage.

app/api/chat/route.ts CHANGED
@@ -11,6 +11,7 @@ import { eq, and } from 'drizzle-orm';
11
 
12
  import { experimental_createMCPClient as createMCPClient, MCPTransport } from 'ai';
13
  import { Experimental_StdioMCPTransport as StdioMCPTransport } from 'ai/mcp-stdio';
 
14
 
15
  // Allow streaming responses up to 30 seconds
16
  export const maxDuration = 30;
@@ -75,8 +76,8 @@ export async function POST(req: Request) {
75
  isNewChat = true;
76
  }
77
 
78
- // Initialize tools with Composio tools
79
- let tools = { ...composioTools };
80
  const mcpClients: any[] = [];
81
 
82
  // Process each MCP server configuration
@@ -113,6 +114,23 @@ export async function POST(req: Request) {
113
  if (envVar.key) env[envVar.key] = envVar.value || '';
114
  });
115
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
  transport = new StdioMCPTransport({
118
  command: mcpServer.command,
@@ -209,6 +227,10 @@ export async function POST(req: Request) {
209
  // Step 2: Save all messages
210
  const dbMessages = convertToDBMessages(allMessages, id);
211
  await saveMessages({ messages: dbMessages });
 
 
 
 
212
  }
213
  });
214
 
 
11
 
12
  import { experimental_createMCPClient as createMCPClient, MCPTransport } from 'ai';
13
  import { Experimental_StdioMCPTransport as StdioMCPTransport } from 'ai/mcp-stdio';
14
+ import { spawn } from "child_process";
15
 
16
  // Allow streaming responses up to 30 seconds
17
  export const maxDuration = 30;
 
76
  isNewChat = true;
77
  }
78
 
79
+ // Initialize tools
80
+ let tools = {};
81
  const mcpClients: any[] = [];
82
 
83
  // Process each MCP server configuration
 
114
  if (envVar.key) env[envVar.key] = envVar.value || '';
115
  });
116
  }
117
+
118
+ // if python is passed in the command, install the python package mentioned in args after -m with subprocess or use regex to find the package name
119
+ if (mcpServer.command.includes('python3')) {
120
+ const packageName = mcpServer.args[mcpServer.args.indexOf('-m') + 1];
121
+ console.log("installing python package", packageName);
122
+ const subprocess = spawn('pip3', ['install', packageName]);
123
+ subprocess.on('close', (code: number) => {
124
+ if (code !== 0) {
125
+ console.error(`Failed to install python package: ${code}`);
126
+ }
127
+ });
128
+ // wait for the subprocess to finish
129
+ await new Promise((resolve, reject) => {
130
+ subprocess.on('close', resolve);
131
+ console.log("installed python package", packageName);
132
+ });
133
+ }
134
 
135
  transport = new StdioMCPTransport({
136
  command: mcpServer.command,
 
227
  // Step 2: Save all messages
228
  const dbMessages = convertToDBMessages(allMessages, id);
229
  await saveMessages({ messages: dbMessages });
230
+ // close all mcp clients
231
+ // for (const client of mcpClients) {
232
+ // await client.close();
233
+ // }
234
  }
235
  });
236
 
app/globals.css CHANGED
@@ -4,71 +4,6 @@
4
 
5
  @custom-variant dark (&:is(.dark *));
6
 
7
- @theme inline {
8
- --color-background: var(--background);
9
- --color-foreground: var(--foreground);
10
- --font-sans: Montserrat, sans-serif;
11
- --font-mono: Ubuntu Mono, monospace;
12
- --color-sidebar-ring: var(--sidebar-ring);
13
- --color-sidebar-border: var(--sidebar-border);
14
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
15
- --color-sidebar-accent: var(--sidebar-accent);
16
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
17
- --color-sidebar-primary: var(--sidebar-primary);
18
- --color-sidebar-foreground: var(--sidebar-foreground);
19
- --color-sidebar: var(--sidebar);
20
- --color-chart-5: var(--chart-5);
21
- --color-chart-4: var(--chart-4);
22
- --color-chart-3: var(--chart-3);
23
- --color-chart-2: var(--chart-2);
24
- --color-chart-1: var(--chart-1);
25
- --color-ring: var(--ring);
26
- --color-input: var(--input);
27
- --color-border: var(--border);
28
- --color-destructive-foreground: var(--destructive-foreground);
29
- --color-destructive: var(--destructive);
30
- --color-accent-foreground: var(--accent-foreground);
31
- --color-accent: var(--accent);
32
- --color-muted-foreground: var(--muted-foreground);
33
- --color-muted: var(--muted);
34
- --color-secondary-foreground: var(--secondary-foreground);
35
- --color-secondary: var(--secondary);
36
- --color-primary-foreground: var(--primary-foreground);
37
- --color-primary: var(--primary);
38
- --color-popover-foreground: var(--popover-foreground);
39
- --color-popover: var(--popover);
40
- --color-card-foreground: var(--card-foreground);
41
- --color-card: var(--card);
42
- --radius-sm: calc(var(--radius) - 4px);
43
- --radius-md: calc(var(--radius) - 2px);
44
- --radius-lg: var(--radius);
45
- --radius-xl: calc(var(--radius) + 4px);
46
- --font-serif: Merriweather, serif;
47
- --radius: 0.625rem;
48
- --tracking-tighter: calc(var(--tracking-normal) - 0.05em);
49
- --tracking-tight: calc(var(--tracking-normal) - 0.025em);
50
- --tracking-wide: calc(var(--tracking-normal) + 0.025em);
51
- --tracking-wider: calc(var(--tracking-normal) + 0.05em);
52
- --tracking-widest: calc(var(--tracking-normal) + 0.1em);
53
- --tracking-normal: var(--tracking-normal);
54
- --shadow-2xl: var(--shadow-2xl);
55
- --shadow-xl: var(--shadow-xl);
56
- --shadow-lg: var(--shadow-lg);
57
- --shadow-md: var(--shadow-md);
58
- --shadow: var(--shadow);
59
- --shadow-sm: var(--shadow-sm);
60
- --shadow-xs: var(--shadow-xs);
61
- --shadow-2xs: var(--shadow-2xs);
62
- --spacing: var(--spacing);
63
- --letter-spacing: var(--letter-spacing);
64
- --shadow-offset-y: var(--shadow-offset-y);
65
- --shadow-offset-x: var(--shadow-offset-x);
66
- --shadow-spread: var(--shadow-spread);
67
- --shadow-blur: var(--shadow-blur);
68
- --shadow-opacity: var(--shadow-opacity);
69
- --color-shadow-color: var(--shadow-color);
70
- }
71
-
72
  :root {
73
  --background: oklch(0.99 0.01 56.32);
74
  --foreground: oklch(0.34 0.01 2.77);
@@ -94,7 +29,6 @@
94
  --chart-3: oklch(0.88 0.08 54.93);
95
  --chart-4: oklch(0.82 0.11 40.89);
96
  --chart-5: oklch(0.64 0.13 32.07);
97
- --radius: 0.625rem;
98
  --sidebar: oklch(0.97 0.02 39.40);
99
  --sidebar-foreground: oklch(0.34 0.01 2.77);
100
  --sidebar-primary: oklch(0.74 0.16 34.71);
@@ -106,14 +40,7 @@
106
  --font-sans: Montserrat, sans-serif;
107
  --font-serif: Merriweather, serif;
108
  --font-mono: Ubuntu Mono, monospace;
109
- --shadow-color: hsl(0 0% 0%);
110
- --shadow-opacity: 0.09;
111
- --shadow-blur: 12px;
112
- --shadow-spread: -3px;
113
- --shadow-offset-x: 0px;
114
- --shadow-offset-y: 6px;
115
- --letter-spacing: 0em;
116
- --spacing: 0.25rem;
117
  --shadow-2xs: 0px 6px 12px -3px hsl(0 0% 0% / 0.04);
118
  --shadow-xs: 0px 6px 12px -3px hsl(0 0% 0% / 0.04);
119
  --shadow-sm: 0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 1px 2px -4px hsl(0 0% 0% / 0.09);
@@ -122,7 +49,6 @@
122
  --shadow-lg: 0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 4px 6px -4px hsl(0 0% 0% / 0.09);
123
  --shadow-xl: 0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 8px 10px -4px hsl(0 0% 0% / 0.09);
124
  --shadow-2xl: 0px 6px 12px -3px hsl(0 0% 0% / 0.22);
125
- --tracking-normal: 0em;
126
  }
127
 
128
  .dark {
@@ -132,7 +58,7 @@
132
  --card-foreground: oklch(0.94 0.01 51.32);
133
  --popover: oklch(0.32 0.02 341.45);
134
  --popover-foreground: oklch(0.94 0.01 51.32);
135
- --primary: oklch(0.74 0.16 34.71);
136
  --primary-foreground: oklch(1.00 0 0);
137
  --secondary: oklch(0.36 0.02 342.27);
138
  --secondary-foreground: oklch(0.94 0.01 51.32);
@@ -140,7 +66,7 @@
140
  --muted-foreground: oklch(0.84 0.02 52.63);
141
  --accent: oklch(0.83 0.11 58.00);
142
  --accent-foreground: oklch(0.26 0.02 352.40);
143
- --destructive: oklch(0.61 0.21 22.24);
144
  --destructive-foreground: oklch(1.00 0 0);
145
  --border: oklch(0.36 0.02 342.27);
146
  --input: oklch(0.36 0.02 342.27);
@@ -152,24 +78,16 @@
152
  --chart-5: oklch(0.64 0.13 32.07);
153
  --sidebar: oklch(0.26 0.02 352.40);
154
  --sidebar-foreground: oklch(0.94 0.01 51.32);
155
- --sidebar-primary: oklch(0.74 0.16 34.71);
156
  --sidebar-primary-foreground: oklch(1.00 0 0);
157
- --sidebar-accent: oklch(0.83 0.11 58.00);
158
- --sidebar-accent-foreground: oklch(0.26 0.02 352.40);
159
  --sidebar-border: oklch(0.36 0.02 342.27);
160
  --sidebar-ring: oklch(0.74 0.16 34.71);
161
- --radius: 0.625rem;
162
  --font-sans: Montserrat, sans-serif;
163
  --font-serif: Merriweather, serif;
164
  --font-mono: Ubuntu Mono, monospace;
165
- --shadow-color: hsl(0 0% 0%);
166
- --shadow-opacity: 0.09;
167
- --shadow-blur: 12px;
168
- --shadow-spread: -3px;
169
- --shadow-offset-x: 0px;
170
- --shadow-offset-y: 6px;
171
- --letter-spacing: 0em;
172
- --spacing: 0.25rem;
173
  --shadow-2xs: 0px 6px 12px -3px hsl(0 0% 0% / 0.04);
174
  --shadow-xs: 0px 6px 12px -3px hsl(0 0% 0% / 0.04);
175
  --shadow-sm: 0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 1px 2px -4px hsl(0 0% 0% / 0.09);
@@ -180,6 +98,59 @@
180
  --shadow-2xl: 0px 6px 12px -3px hsl(0 0% 0% / 0.22);
181
  }
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  @layer base {
184
  * {
185
  @apply border-border outline-ring/50;
 
4
 
5
  @custom-variant dark (&:is(.dark *));
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  :root {
8
  --background: oklch(0.99 0.01 56.32);
9
  --foreground: oklch(0.34 0.01 2.77);
 
29
  --chart-3: oklch(0.88 0.08 54.93);
30
  --chart-4: oklch(0.82 0.11 40.89);
31
  --chart-5: oklch(0.64 0.13 32.07);
 
32
  --sidebar: oklch(0.97 0.02 39.40);
33
  --sidebar-foreground: oklch(0.34 0.01 2.77);
34
  --sidebar-primary: oklch(0.74 0.16 34.71);
 
40
  --font-sans: Montserrat, sans-serif;
41
  --font-serif: Merriweather, serif;
42
  --font-mono: Ubuntu Mono, monospace;
43
+ --radius: 0.625rem;
 
 
 
 
 
 
 
44
  --shadow-2xs: 0px 6px 12px -3px hsl(0 0% 0% / 0.04);
45
  --shadow-xs: 0px 6px 12px -3px hsl(0 0% 0% / 0.04);
46
  --shadow-sm: 0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 1px 2px -4px hsl(0 0% 0% / 0.09);
 
49
  --shadow-lg: 0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 4px 6px -4px hsl(0 0% 0% / 0.09);
50
  --shadow-xl: 0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 8px 10px -4px hsl(0 0% 0% / 0.09);
51
  --shadow-2xl: 0px 6px 12px -3px hsl(0 0% 0% / 0.22);
 
52
  }
53
 
54
  .dark {
 
58
  --card-foreground: oklch(0.94 0.01 51.32);
59
  --popover: oklch(0.32 0.02 341.45);
60
  --popover-foreground: oklch(0.94 0.01 51.32);
61
+ --primary: oklch(0.57 0.15 35.26);
62
  --primary-foreground: oklch(1.00 0 0);
63
  --secondary: oklch(0.36 0.02 342.27);
64
  --secondary-foreground: oklch(0.94 0.01 51.32);
 
66
  --muted-foreground: oklch(0.84 0.02 52.63);
67
  --accent: oklch(0.83 0.11 58.00);
68
  --accent-foreground: oklch(0.26 0.02 352.40);
69
+ --destructive: oklch(0.51 0.16 20.19);
70
  --destructive-foreground: oklch(1.00 0 0);
71
  --border: oklch(0.36 0.02 342.27);
72
  --input: oklch(0.36 0.02 342.27);
 
78
  --chart-5: oklch(0.64 0.13 32.07);
79
  --sidebar: oklch(0.26 0.02 352.40);
80
  --sidebar-foreground: oklch(0.94 0.01 51.32);
81
+ --sidebar-primary: oklch(0.47 0.08 34.31);
82
  --sidebar-primary-foreground: oklch(1.00 0 0);
83
+ --sidebar-accent: oklch(0.67 0.09 56.00);
84
+ --sidebar-accent-foreground: oklch(0.26 0.01 353.48);
85
  --sidebar-border: oklch(0.36 0.02 342.27);
86
  --sidebar-ring: oklch(0.74 0.16 34.71);
 
87
  --font-sans: Montserrat, sans-serif;
88
  --font-serif: Merriweather, serif;
89
  --font-mono: Ubuntu Mono, monospace;
90
+ --radius: 0.625rem;
 
 
 
 
 
 
 
91
  --shadow-2xs: 0px 6px 12px -3px hsl(0 0% 0% / 0.04);
92
  --shadow-xs: 0px 6px 12px -3px hsl(0 0% 0% / 0.04);
93
  --shadow-sm: 0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 1px 2px -4px hsl(0 0% 0% / 0.09);
 
98
  --shadow-2xl: 0px 6px 12px -3px hsl(0 0% 0% / 0.22);
99
  }
100
 
101
+ @theme inline {
102
+ --color-background: var(--background);
103
+ --color-foreground: var(--foreground);
104
+ --color-card: var(--card);
105
+ --color-card-foreground: var(--card-foreground);
106
+ --color-popover: var(--popover);
107
+ --color-popover-foreground: var(--popover-foreground);
108
+ --color-primary: var(--primary);
109
+ --color-primary-foreground: var(--primary-foreground);
110
+ --color-secondary: var(--secondary);
111
+ --color-secondary-foreground: var(--secondary-foreground);
112
+ --color-muted: var(--muted);
113
+ --color-muted-foreground: var(--muted-foreground);
114
+ --color-accent: var(--accent);
115
+ --color-accent-foreground: var(--accent-foreground);
116
+ --color-destructive: var(--destructive);
117
+ --color-destructive-foreground: var(--destructive-foreground);
118
+ --color-border: var(--border);
119
+ --color-input: var(--input);
120
+ --color-ring: var(--ring);
121
+ --color-chart-1: var(--chart-1);
122
+ --color-chart-2: var(--chart-2);
123
+ --color-chart-3: var(--chart-3);
124
+ --color-chart-4: var(--chart-4);
125
+ --color-chart-5: var(--chart-5);
126
+ --color-sidebar: var(--sidebar);
127
+ --color-sidebar-foreground: var(--sidebar-foreground);
128
+ --color-sidebar-primary: var(--sidebar-primary);
129
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
130
+ --color-sidebar-accent: var(--sidebar-accent);
131
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
132
+ --color-sidebar-border: var(--sidebar-border);
133
+ --color-sidebar-ring: var(--sidebar-ring);
134
+
135
+ --font-sans: var(--font-sans);
136
+ --font-mono: var(--font-mono);
137
+ --font-serif: var(--font-serif);
138
+
139
+ --radius-sm: calc(var(--radius) - 4px);
140
+ --radius-md: calc(var(--radius) - 2px);
141
+ --radius-lg: var(--radius);
142
+ --radius-xl: calc(var(--radius) + 4px);
143
+
144
+ --shadow-2xs: var(--shadow-2xs);
145
+ --shadow-xs: var(--shadow-xs);
146
+ --shadow-sm: var(--shadow-sm);
147
+ --shadow: var(--shadow);
148
+ --shadow-md: var(--shadow-md);
149
+ --shadow-lg: var(--shadow-lg);
150
+ --shadow-xl: var(--shadow-xl);
151
+ --shadow-2xl: var(--shadow-2xl);
152
+ }
153
+
154
  @layer base {
155
  * {
156
  @apply border-border outline-ring/50;
app/providers.tsx CHANGED
@@ -1,10 +1,12 @@
1
  "use client";
2
 
3
- import { ReactNode } from "react";
4
  import { ThemeProvider } from "@/components/theme-provider";
5
  import { SidebarProvider } from "@/components/ui/sidebar";
6
  import { Toaster } from "sonner";
7
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
 
 
8
 
9
  // Create a client
10
  const queryClient = new QueryClient({
@@ -17,6 +19,11 @@ const queryClient = new QueryClient({
17
  });
18
 
19
  export function Providers({ children }: { children: ReactNode }) {
 
 
 
 
 
20
  return (
21
  <QueryClientProvider client={queryClient}>
22
  <ThemeProvider
@@ -25,7 +32,7 @@ export function Providers({ children }: { children: ReactNode }) {
25
  enableSystem
26
  disableTransitionOnChange
27
  >
28
- <SidebarProvider defaultOpen={true}>
29
  {children}
30
  <Toaster position="top-center" richColors />
31
  </SidebarProvider>
 
1
  "use client";
2
 
3
+ import { ReactNode, useEffect, useState } from "react";
4
  import { ThemeProvider } from "@/components/theme-provider";
5
  import { SidebarProvider } from "@/components/ui/sidebar";
6
  import { Toaster } from "sonner";
7
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
8
+ import { useLocalStorage } from "@/lib/hooks/use-local-storage";
9
+ import { STORAGE_KEYS } from "@/lib/constants";
10
 
11
  // Create a client
12
  const queryClient = new QueryClient({
 
19
  });
20
 
21
  export function Providers({ children }: { children: ReactNode }) {
22
+ const [sidebarOpen, setSidebarOpen] = useLocalStorage<boolean>(
23
+ STORAGE_KEYS.SIDEBAR_STATE,
24
+ true
25
+ );
26
+
27
  return (
28
  <QueryClientProvider client={queryClient}>
29
  <ThemeProvider
 
32
  enableSystem
33
  disableTransitionOnChange
34
  >
35
+ <SidebarProvider defaultOpen={sidebarOpen} open={sidebarOpen} onOpenChange={setSidebarOpen}>
36
  {children}
37
  <Toaster position="top-center" richColors />
38
  </SidebarProvider>
components/chat-sidebar.tsx CHANGED
@@ -1,8 +1,8 @@
1
  "use client";
2
 
3
- import { useState, useEffect } from "react";
4
  import { useRouter, usePathname } from "next/navigation";
5
- import { MessageSquare, PlusCircle, Trash2, ServerIcon, Settings, Loader2, Sparkles } from "lucide-react";
6
  import {
7
  Sidebar,
8
  SidebarContent,
@@ -15,24 +15,31 @@ import {
15
  SidebarMenuButton,
16
  SidebarMenuItem,
17
  SidebarMenuBadge,
18
- SidebarSeparator,
19
  useSidebar
20
  } from "@/components/ui/sidebar";
21
  import { Separator } from "@/components/ui/separator";
22
  import { Button } from "@/components/ui/button";
23
  import { Badge } from "@/components/ui/badge";
24
  import { toast } from "sonner";
25
- import { type Chat } from "@/lib/db/schema";
26
  import Image from "next/image";
27
  import { MCPServerManager, type MCPServer } from "./mcp-server-manager";
28
  import { ThemeToggle } from "./theme-toggle";
29
- import { useTheme } from "next-themes";
30
  import { getUserId } from "@/lib/user-id";
31
  import { useLocalStorage } from "@/lib/hooks/use-local-storage";
32
  import { STORAGE_KEYS } from "@/lib/constants";
33
  import { useChats } from "@/lib/hooks/use-chats";
34
  import { cn } from "@/lib/utils";
35
  import Link from "next/link";
 
 
 
 
 
 
 
 
 
 
36
 
37
  export function ChatSidebar() {
38
  const router = useRouter();
@@ -72,7 +79,6 @@ export function ChatSidebar() {
72
 
73
  // Get active MCP servers status
74
  const activeServersCount = selectedMcpServers.length;
75
- const multipleServersActive = activeServersCount > 1;
76
 
77
  // Show loading state if user ID is not yet initialized
78
  if (!userId) {
@@ -94,15 +100,15 @@ export function ChatSidebar() {
94
  </div>
95
  </SidebarHeader>
96
 
97
- <SidebarContent className="pt-4">
98
- <SidebarGroup>
99
  <SidebarGroupLabel className={cn(
100
- "px-4 mb-1 text-xs font-medium text-muted-foreground/80 uppercase tracking-wider",
101
  isCollapsed ? "sr-only" : ""
102
  )}>
103
  Chats
104
  </SidebarGroupLabel>
105
- <SidebarGroupContent>
106
  <SidebarMenu>
107
  {isLoading ? (
108
  <div className={`flex items-center justify-center py-4 ${isCollapsed ? "" : "px-4"}`}>
@@ -112,20 +118,15 @@ export function ChatSidebar() {
112
  )}
113
  </div>
114
  ) : chats.length === 0 ? (
115
- <div className={`flex flex-col items-center gap-2 py-6 ${isCollapsed ? "" : "px-4"}`}>
116
  {isCollapsed ? (
117
- <div className="flex h-7 w-7 items-center justify-center rounded-full bg-secondary/60">
118
- <Sparkles className="h-3 w-3 text-secondary-foreground" />
119
  </div>
120
  ) : (
121
- <div className="flex flex-col items-center text-center py-4">
122
- <div className="flex h-10 w-10 items-center justify-center rounded-full bg-secondary/60 mb-3">
123
- <Sparkles className="h-5 w-5 text-secondary-foreground" />
124
- </div>
125
- <span className="text-sm font-medium text-foreground/90">No chats yet</span>
126
- <span className="text-xs text-muted-foreground mt-1 max-w-[200px]">
127
- Start a new conversation below
128
- </span>
129
  </div>
130
  )}
131
  </div>
@@ -137,7 +138,7 @@ export function ChatSidebar() {
137
  tooltip={isCollapsed ? chat.title : undefined}
138
  data-active={pathname === `/chat/${chat.id}`}
139
  className={cn(
140
- "transition-all hover:bg-secondary/50 active:bg-secondary/70",
141
  pathname === `/chat/${chat.id}` ? "bg-secondary/60 hover:bg-secondary/60" : ""
142
  )}
143
  >
@@ -179,15 +180,15 @@ export function ChatSidebar() {
179
  </SidebarGroupContent>
180
  </SidebarGroup>
181
 
182
- <div className="relative my-3">
183
  <div className="absolute inset-x-0">
184
  <Separator className="w-full h-px bg-border/40" />
185
  </div>
186
  </div>
187
 
188
- <SidebarGroup>
189
  <SidebarGroupLabel className={cn(
190
- "px-4 mb-1 text-xs font-medium text-muted-foreground/80 uppercase tracking-wider",
191
  isCollapsed ? "sr-only" : ""
192
  )}>
193
  MCP Servers
@@ -205,7 +206,7 @@ export function ChatSidebar() {
205
  >
206
  <ServerIcon className={cn(
207
  "h-4 w-4 flex-shrink-0",
208
- activeServersCount > 0 ? "text-green-500" : "text-muted-foreground"
209
  )} />
210
  {!isCollapsed && (
211
  <span className="flex-grow text-sm text-foreground/80">MCP Servers</span>
@@ -244,18 +245,90 @@ export function ChatSidebar() {
244
  {!isCollapsed && <span>New Chat</span>}
245
  </Button>
246
 
247
- <div className={`flex ${isCollapsed ? "flex-col" : ""} gap-2 ${isCollapsed ? "items-center" : "justify-between items-center"}`}>
248
- <Button
249
- variant="ghost"
250
- size="icon"
251
- className="h-8 w-8 rounded-md hover:bg-secondary/50 text-muted-foreground hover:text-foreground"
252
- onClick={() => setMcpSettingsOpen(true)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  >
254
- <Settings className="h-4 w-4" />
255
- <span className="sr-only">MCP Settings</span>
256
- </Button>
257
- <ThemeToggle className="hover:bg-secondary/50 text-muted-foreground hover:text-foreground" />
258
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  </div>
260
 
261
  <MCPServerManager
 
1
  "use client";
2
 
3
+ import { useState, useEffect, useRef } from "react";
4
  import { useRouter, usePathname } from "next/navigation";
5
+ import { MessageSquare, PlusCircle, Trash2, ServerIcon, Settings, Loader2, Sparkles, ChevronsUpDown, UserIcon, Copy } from "lucide-react";
6
  import {
7
  Sidebar,
8
  SidebarContent,
 
15
  SidebarMenuButton,
16
  SidebarMenuItem,
17
  SidebarMenuBadge,
 
18
  useSidebar
19
  } from "@/components/ui/sidebar";
20
  import { Separator } from "@/components/ui/separator";
21
  import { Button } from "@/components/ui/button";
22
  import { Badge } from "@/components/ui/badge";
23
  import { toast } from "sonner";
 
24
  import Image from "next/image";
25
  import { MCPServerManager, type MCPServer } from "./mcp-server-manager";
26
  import { ThemeToggle } from "./theme-toggle";
 
27
  import { getUserId } from "@/lib/user-id";
28
  import { useLocalStorage } from "@/lib/hooks/use-local-storage";
29
  import { STORAGE_KEYS } from "@/lib/constants";
30
  import { useChats } from "@/lib/hooks/use-chats";
31
  import { cn } from "@/lib/utils";
32
  import Link from "next/link";
33
+ import {
34
+ DropdownMenu,
35
+ DropdownMenuContent,
36
+ DropdownMenuGroup,
37
+ DropdownMenuItem,
38
+ DropdownMenuLabel,
39
+ DropdownMenuSeparator,
40
+ DropdownMenuTrigger,
41
+ } from "@/components/ui/dropdown-menu";
42
+ import { Avatar, AvatarFallback } from "@/components/ui/avatar";
43
 
44
  export function ChatSidebar() {
45
  const router = useRouter();
 
79
 
80
  // Get active MCP servers status
81
  const activeServersCount = selectedMcpServers.length;
 
82
 
83
  // Show loading state if user ID is not yet initialized
84
  if (!userId) {
 
100
  </div>
101
  </SidebarHeader>
102
 
103
+ <SidebarContent className="flex flex-col h-[calc(100vh-8rem)]">
104
+ <SidebarGroup className="flex-1 min-h-0">
105
  <SidebarGroupLabel className={cn(
106
+ "px-4 text-xs font-medium text-muted-foreground/80 uppercase tracking-wider",
107
  isCollapsed ? "sr-only" : ""
108
  )}>
109
  Chats
110
  </SidebarGroupLabel>
111
+ <SidebarGroupContent className="overflow-y-auto pt-1">
112
  <SidebarMenu>
113
  {isLoading ? (
114
  <div className={`flex items-center justify-center py-4 ${isCollapsed ? "" : "px-4"}`}>
 
118
  )}
119
  </div>
120
  ) : chats.length === 0 ? (
121
+ <div className={`flex items-center justify-center py-3 ${isCollapsed ? "" : "px-4"}`}>
122
  {isCollapsed ? (
123
+ <div className="flex h-6 w-6 items-center justify-center rounded-md border border-border/50 bg-background/50">
124
+ <MessageSquare className="h-3 w-3 text-muted-foreground" />
125
  </div>
126
  ) : (
127
+ <div className="flex items-center gap-3 w-full px-3 py-2 rounded-md border border-dashed border-border/50 bg-background/50">
128
+ <MessageSquare className="h-4 w-4 text-muted-foreground" />
129
+ <span className="text-xs text-muted-foreground font-normal">No conversations yet</span>
 
 
 
 
 
130
  </div>
131
  )}
132
  </div>
 
138
  tooltip={isCollapsed ? chat.title : undefined}
139
  data-active={pathname === `/chat/${chat.id}`}
140
  className={cn(
141
+ "transition-all hover:bg-primary/10 active:bg-primary/15",
142
  pathname === `/chat/${chat.id}` ? "bg-secondary/60 hover:bg-secondary/60" : ""
143
  )}
144
  >
 
180
  </SidebarGroupContent>
181
  </SidebarGroup>
182
 
183
+ <div className="relative my-0">
184
  <div className="absolute inset-x-0">
185
  <Separator className="w-full h-px bg-border/40" />
186
  </div>
187
  </div>
188
 
189
+ <SidebarGroup className="flex-shrink-0">
190
  <SidebarGroupLabel className={cn(
191
+ "px-4 pt-0 text-xs font-medium text-muted-foreground/80 uppercase tracking-wider",
192
  isCollapsed ? "sr-only" : ""
193
  )}>
194
  MCP Servers
 
206
  >
207
  <ServerIcon className={cn(
208
  "h-4 w-4 flex-shrink-0",
209
+ activeServersCount > 0 ? "text-primary" : "text-muted-foreground"
210
  )} />
211
  {!isCollapsed && (
212
  <span className="flex-grow text-sm text-foreground/80">MCP Servers</span>
 
245
  {!isCollapsed && <span>New Chat</span>}
246
  </Button>
247
 
248
+ <DropdownMenu modal={false}>
249
+ <DropdownMenuTrigger asChild>
250
+ {isCollapsed ? (
251
+ <Button
252
+ variant="ghost"
253
+ className="w-8 h-8 p-0 flex items-center justify-center"
254
+ >
255
+ <Avatar className="h-6 w-6 rounded-lg bg-secondary/60">
256
+ <AvatarFallback className="rounded-lg text-xs font-medium text-secondary-foreground">
257
+ {userId.substring(0, 2).toUpperCase()}
258
+ </AvatarFallback>
259
+ </Avatar>
260
+ </Button>
261
+ ) : (
262
+ <Button
263
+ variant="outline"
264
+ className="w-full justify-between font-normal bg-transparent border border-border/60 shadow-none px-2 h-10 hover:bg-secondary/50"
265
+ >
266
+ <div className="flex items-center gap-2">
267
+ <Avatar className="h-7 w-7 rounded-lg bg-secondary/60">
268
+ <AvatarFallback className="rounded-lg text-sm font-medium text-secondary-foreground">
269
+ {userId.substring(0, 2).toUpperCase()}
270
+ </AvatarFallback>
271
+ </Avatar>
272
+ <div className="grid text-left text-sm leading-tight">
273
+ <span className="truncate font-medium text-foreground/90">User ID</span>
274
+ <span className="truncate text-xs text-muted-foreground">{userId.substring(0, 16)}...</span>
275
+ </div>
276
+ </div>
277
+ <ChevronsUpDown className="h-4 w-4 text-muted-foreground" />
278
+ </Button>
279
+ )}
280
+ </DropdownMenuTrigger>
281
+ <DropdownMenuContent
282
+ className="w-56 rounded-lg"
283
+ side={isCollapsed ? "top" : "top"}
284
+ align={isCollapsed ? "start" : "end"}
285
+ sideOffset={8}
286
  >
287
+ <DropdownMenuLabel className="p-0 font-normal">
288
+ <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
289
+ <Avatar className="h-8 w-8 rounded-lg bg-secondary/60">
290
+ <AvatarFallback className="rounded-lg text-sm font-medium text-secondary-foreground">
291
+ {userId.substring(0, 2).toUpperCase()}
292
+ </AvatarFallback>
293
+ </Avatar>
294
+ <div className="grid flex-1 text-left text-sm leading-tight">
295
+ <span className="truncate font-semibold text-foreground/90">User ID</span>
296
+ <span className="truncate text-xs text-muted-foreground">{userId}</span>
297
+ </div>
298
+ </div>
299
+ </DropdownMenuLabel>
300
+ <DropdownMenuSeparator />
301
+ <DropdownMenuGroup>
302
+ <DropdownMenuItem onSelect={(e) => {
303
+ e.preventDefault();
304
+ navigator.clipboard.writeText(userId);
305
+ toast.success("User ID copied to clipboard");
306
+ }}>
307
+ <Copy className="mr-2 h-4 w-4 hover:text-sidebar-accent" />
308
+ Copy User ID
309
+ </DropdownMenuItem>
310
+ </DropdownMenuGroup>
311
+ <DropdownMenuSeparator />
312
+ <DropdownMenuGroup>
313
+ <DropdownMenuItem onSelect={(e) => {
314
+ e.preventDefault();
315
+ setMcpSettingsOpen(true);
316
+ }}>
317
+ <Settings className="mr-2 h-4 w-4 hover:text-sidebar-accent" />
318
+ MCP Settings
319
+ </DropdownMenuItem>
320
+ <DropdownMenuItem onSelect={(e) => e.preventDefault()}>
321
+ <div className="flex items-center justify-between w-full">
322
+ <div className="flex items-center">
323
+ <Sparkles className="mr-2 h-4 w-4 hover:text-sidebar-accent" />
324
+ Theme
325
+ </div>
326
+ <ThemeToggle className="h-6 w-6" />
327
+ </div>
328
+ </DropdownMenuItem>
329
+ </DropdownMenuGroup>
330
+ </DropdownMenuContent>
331
+ </DropdownMenu>
332
  </div>
333
 
334
  <MCPServerManager
components/chat.tsx CHANGED
@@ -182,7 +182,7 @@ export default function Chat() {
182
  const isLoading = status === "streaming" || status === "submitted" || isLoadingChat;
183
 
184
  return (
185
- <div className="h-dvh flex flex-col justify-center py-4 w-full max-w-3xl">
186
  {messages.length === 0 && !isLoadingChat ? (
187
  <div className="max-w-xl mx-auto w-full">
188
  <ProjectOverview />
@@ -203,12 +203,12 @@ export default function Chat() {
203
  </div>
204
  ) : (
205
  <>
206
- <div className="flex-1 overflow-y-auto min-h-0">
207
  <Messages messages={messages} isLoading={isLoading} status={status} />
208
  </div>
209
  <form
210
  onSubmit={handleFormSubmit}
211
- className="mt-4 w-full max-w-2xl mx-auto"
212
  >
213
  <Textarea
214
  selectedModel={selectedModel}
 
182
  const isLoading = status === "streaming" || status === "submitted" || isLoadingChat;
183
 
184
  return (
185
+ <div className="h-dvh flex flex-col justify-center w-full max-w-3xl mx-auto px-4 sm:px-6 md:py-4">
186
  {messages.length === 0 && !isLoadingChat ? (
187
  <div className="max-w-xl mx-auto w-full">
188
  <ProjectOverview />
 
203
  </div>
204
  ) : (
205
  <>
206
+ <div className="flex-1 overflow-y-auto min-h-0 pb-2">
207
  <Messages messages={messages} isLoading={isLoading} status={status} />
208
  </div>
209
  <form
210
  onSubmit={handleFormSubmit}
211
+ className="mt-2 w-full mx-auto mb-4"
212
  >
213
  <Textarea
214
  selectedModel={selectedModel}
components/mcp-server-manager.tsx CHANGED
@@ -67,6 +67,7 @@ export interface MCPServer {
67
  args?: string[];
68
  env?: KeyValuePair[];
69
  headers?: KeyValuePair[];
 
70
  }
71
 
72
  interface MCPServerManagerProps {
@@ -398,36 +399,44 @@ export const MCPServerManager = ({
398
  </DialogHeader>
399
 
400
  {view === 'list' ? (
401
- <div className="flex-1 overflow-hidden flex flex-col pb-14">
402
  {servers.length > 0 ? (
403
  <div className="flex-1 overflow-hidden flex flex-col">
404
  <div className="flex-1 overflow-hidden flex flex-col">
405
- <div className="flex items-center justify-between mb-2">
406
  <h3 className="text-sm font-medium">Available Servers</h3>
407
  <span className="text-xs text-muted-foreground">
408
  Select multiple servers to combine their tools
409
  </span>
410
  </div>
411
- <div className="overflow-y-auto pr-1 flex-1 gap-2 flex flex-col">
412
- {servers.map((server) => {
 
 
 
 
 
 
 
 
413
  const isActive = selectedServers.includes(server.id);
414
  return (
415
  <div
416
  key={server.id}
417
  className={`
418
- relative flex flex-col p-3 rounded-lg transition-colors
419
  border ${isActive
420
  ? 'border-primary bg-primary/10'
421
  : 'border-border hover:border-primary/30 hover:bg-primary/5'}
422
  `}
423
  >
424
  {/* Server Header with Type Badge and Delete Button */}
425
- <div className="flex items-center justify-between mb-1.5">
426
  <div className="flex items-center gap-2">
427
  {server.type === 'sse' ? (
428
- <Globe className="h-4 w-4 text-primary flex-shrink-0" />
429
  ) : (
430
- <Terminal className="h-4 w-4 text-primary flex-shrink-0" />
431
  )}
432
  <h4 className="text-sm font-medium truncate max-w-[220px]">{server.name}</h4>
433
  {hasAdvancedConfig(server) && (
@@ -437,7 +446,7 @@ export const MCPServerManager = ({
437
  )}
438
  </div>
439
  <div className="flex items-center gap-2">
440
- <span className="text-xs px-1.5 py-0.5 rounded-full bg-secondary text-secondary-foreground">
441
  {server.type.toUpperCase()}
442
  </span>
443
  <button
@@ -458,7 +467,7 @@ export const MCPServerManager = ({
458
  </div>
459
 
460
  {/* Server Details */}
461
- <p className="text-xs text-muted-foreground mb-2 truncate">
462
  {server.type === 'sse'
463
  ? server.url
464
  : `${server.command} ${server.args?.join(' ')}`
@@ -468,7 +477,7 @@ export const MCPServerManager = ({
468
  {/* Action Button */}
469
  <Button
470
  size="sm"
471
- className="w-full mt-0.5 gap-1.5"
472
  variant={isActive ? "default" : "outline"}
473
  onClick={() => toggleServer(server.id)}
474
  >
@@ -870,14 +879,14 @@ export const MCPServerManager = ({
870
  )}
871
 
872
  {/* Persistent fixed footer with buttons */}
873
- <div className="absolute bottom-0 left-0 right-0 p-4 bg-background border-t border-border flex justify-between">
874
  {view === 'list' ? (
875
  <>
876
  <Button
877
  variant="outline"
878
  onClick={clearAllServers}
879
  size="sm"
880
- className="gap-1.5"
881
  disabled={selectedServers.length === 0}
882
  >
883
  <X className="h-3.5 w-3.5" />
 
67
  args?: string[];
68
  env?: KeyValuePair[];
69
  headers?: KeyValuePair[];
70
+ description?: string;
71
  }
72
 
73
  interface MCPServerManagerProps {
 
399
  </DialogHeader>
400
 
401
  {view === 'list' ? (
402
+ <div className="flex-1 overflow-hidden flex flex-col">
403
  {servers.length > 0 ? (
404
  <div className="flex-1 overflow-hidden flex flex-col">
405
  <div className="flex-1 overflow-hidden flex flex-col">
406
+ <div className="flex items-center justify-between mb-3">
407
  <h3 className="text-sm font-medium">Available Servers</h3>
408
  <span className="text-xs text-muted-foreground">
409
  Select multiple servers to combine their tools
410
  </span>
411
  </div>
412
+ <div className="overflow-y-auto pr-1 flex-1 gap-2.5 flex flex-col pb-16">
413
+ {servers
414
+ .sort((a, b) => {
415
+ const aActive = selectedServers.includes(a.id);
416
+ const bActive = selectedServers.includes(b.id);
417
+ if (aActive && !bActive) return -1;
418
+ if (!aActive && bActive) return 1;
419
+ return 0;
420
+ })
421
+ .map((server) => {
422
  const isActive = selectedServers.includes(server.id);
423
  return (
424
  <div
425
  key={server.id}
426
  className={`
427
+ relative flex flex-col p-3.5 rounded-xl transition-colors
428
  border ${isActive
429
  ? 'border-primary bg-primary/10'
430
  : 'border-border hover:border-primary/30 hover:bg-primary/5'}
431
  `}
432
  >
433
  {/* Server Header with Type Badge and Delete Button */}
434
+ <div className="flex items-center justify-between mb-2">
435
  <div className="flex items-center gap-2">
436
  {server.type === 'sse' ? (
437
+ <Globe className={`h-4 w-4 ${isActive ? 'text-primary' : 'text-muted-foreground'} flex-shrink-0`} />
438
  ) : (
439
+ <Terminal className={`h-4 w-4 ${isActive ? 'text-primary' : 'text-muted-foreground'} flex-shrink-0`} />
440
  )}
441
  <h4 className="text-sm font-medium truncate max-w-[220px]">{server.name}</h4>
442
  {hasAdvancedConfig(server) && (
 
446
  )}
447
  </div>
448
  <div className="flex items-center gap-2">
449
+ <span className="text-xs px-2 py-0.5 rounded-full bg-secondary text-secondary-foreground">
450
  {server.type.toUpperCase()}
451
  </span>
452
  <button
 
467
  </div>
468
 
469
  {/* Server Details */}
470
+ <p className="text-xs text-muted-foreground mb-2.5 truncate">
471
  {server.type === 'sse'
472
  ? server.url
473
  : `${server.command} ${server.args?.join(' ')}`
 
477
  {/* Action Button */}
478
  <Button
479
  size="sm"
480
+ className="w-full gap-1.5 hover:text-black hover:dark:text-white rounded-lg"
481
  variant={isActive ? "default" : "outline"}
482
  onClick={() => toggleServer(server.id)}
483
  >
 
879
  )}
880
 
881
  {/* Persistent fixed footer with buttons */}
882
+ <div className="absolute bottom-0 left-0 right-0 p-4 bg-background border-t border-border flex justify-between z-10">
883
  {view === 'list' ? (
884
  <>
885
  <Button
886
  variant="outline"
887
  onClick={clearAllServers}
888
  size="sm"
889
+ className="gap-1.5 hover:text-black hover:dark:text-white"
890
  disabled={selectedServers.length === 0}
891
  >
892
  <X className="h-3.5 w-3.5" />
components/theme-toggle.tsx CHANGED
@@ -16,8 +16,8 @@ export function ThemeToggle({ className, ...props }: React.ComponentProps<typeof
16
  className={`rounded-md h-8 w-8 ${className}`}
17
  {...props}
18
  >
19
- <Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
20
- <Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
21
  <span className="sr-only">Toggle theme</span>
22
  </Button>
23
  )
 
16
  className={`rounded-md h-8 w-8 ${className}`}
17
  {...props}
18
  >
19
+ <Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0 hover:text-sidebar-accent" />
20
+ <Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100 hover:text-sidebar-accent" />
21
  <span className="sr-only">Toggle theme</span>
22
  </Button>
23
  )
components/ui/avatar.tsx ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ function Avatar({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
12
+ return (
13
+ <AvatarPrimitive.Root
14
+ data-slot="avatar"
15
+ className={cn(
16
+ "relative flex size-8 shrink-0 overflow-hidden rounded-full",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ )
22
+ }
23
+
24
+ function AvatarImage({
25
+ className,
26
+ ...props
27
+ }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
28
+ return (
29
+ <AvatarPrimitive.Image
30
+ data-slot="avatar-image"
31
+ className={cn("aspect-square size-full", className)}
32
+ {...props}
33
+ />
34
+ )
35
+ }
36
+
37
+ function AvatarFallback({
38
+ className,
39
+ ...props
40
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
41
+ return (
42
+ <AvatarPrimitive.Fallback
43
+ data-slot="avatar-fallback"
44
+ className={cn(
45
+ "bg-muted flex size-full items-center justify-center rounded-full",
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ )
51
+ }
52
+
53
+ export { Avatar, AvatarImage, AvatarFallback }
components/ui/dropdown-menu.tsx ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5
+ import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ function DropdownMenu({
10
+ ...props
11
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
12
+ return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
13
+ }
14
+
15
+ function DropdownMenuPortal({
16
+ ...props
17
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
18
+ return (
19
+ <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
20
+ )
21
+ }
22
+
23
+ function DropdownMenuTrigger({
24
+ ...props
25
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
26
+ return (
27
+ <DropdownMenuPrimitive.Trigger
28
+ data-slot="dropdown-menu-trigger"
29
+ {...props}
30
+ />
31
+ )
32
+ }
33
+
34
+ function DropdownMenuContent({
35
+ className,
36
+ sideOffset = 4,
37
+ ...props
38
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
39
+ return (
40
+ <DropdownMenuPrimitive.Portal>
41
+ <DropdownMenuPrimitive.Content
42
+ data-slot="dropdown-menu-content"
43
+ sideOffset={sideOffset}
44
+ className={cn(
45
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ </DropdownMenuPrimitive.Portal>
51
+ )
52
+ }
53
+
54
+ function DropdownMenuGroup({
55
+ ...props
56
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
57
+ return (
58
+ <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
59
+ )
60
+ }
61
+
62
+ function DropdownMenuItem({
63
+ className,
64
+ inset,
65
+ variant = "default",
66
+ ...props
67
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
68
+ inset?: boolean
69
+ variant?: "default" | "destructive"
70
+ }) {
71
+ return (
72
+ <DropdownMenuPrimitive.Item
73
+ data-slot="dropdown-menu-item"
74
+ data-inset={inset}
75
+ data-variant={variant}
76
+ className={cn(
77
+ "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
78
+ className
79
+ )}
80
+ {...props}
81
+ />
82
+ )
83
+ }
84
+
85
+ function DropdownMenuCheckboxItem({
86
+ className,
87
+ children,
88
+ checked,
89
+ ...props
90
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
91
+ return (
92
+ <DropdownMenuPrimitive.CheckboxItem
93
+ data-slot="dropdown-menu-checkbox-item"
94
+ className={cn(
95
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
96
+ className
97
+ )}
98
+ checked={checked}
99
+ {...props}
100
+ >
101
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
102
+ <DropdownMenuPrimitive.ItemIndicator>
103
+ <CheckIcon className="size-4" />
104
+ </DropdownMenuPrimitive.ItemIndicator>
105
+ </span>
106
+ {children}
107
+ </DropdownMenuPrimitive.CheckboxItem>
108
+ )
109
+ }
110
+
111
+ function DropdownMenuRadioGroup({
112
+ ...props
113
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
114
+ return (
115
+ <DropdownMenuPrimitive.RadioGroup
116
+ data-slot="dropdown-menu-radio-group"
117
+ {...props}
118
+ />
119
+ )
120
+ }
121
+
122
+ function DropdownMenuRadioItem({
123
+ className,
124
+ children,
125
+ ...props
126
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
127
+ return (
128
+ <DropdownMenuPrimitive.RadioItem
129
+ data-slot="dropdown-menu-radio-item"
130
+ className={cn(
131
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
132
+ className
133
+ )}
134
+ {...props}
135
+ >
136
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
137
+ <DropdownMenuPrimitive.ItemIndicator>
138
+ <CircleIcon className="size-2 fill-current" />
139
+ </DropdownMenuPrimitive.ItemIndicator>
140
+ </span>
141
+ {children}
142
+ </DropdownMenuPrimitive.RadioItem>
143
+ )
144
+ }
145
+
146
+ function DropdownMenuLabel({
147
+ className,
148
+ inset,
149
+ ...props
150
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
151
+ inset?: boolean
152
+ }) {
153
+ return (
154
+ <DropdownMenuPrimitive.Label
155
+ data-slot="dropdown-menu-label"
156
+ data-inset={inset}
157
+ className={cn(
158
+ "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
159
+ className
160
+ )}
161
+ {...props}
162
+ />
163
+ )
164
+ }
165
+
166
+ function DropdownMenuSeparator({
167
+ className,
168
+ ...props
169
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
170
+ return (
171
+ <DropdownMenuPrimitive.Separator
172
+ data-slot="dropdown-menu-separator"
173
+ className={cn("bg-border -mx-1 my-1 h-px", className)}
174
+ {...props}
175
+ />
176
+ )
177
+ }
178
+
179
+ function DropdownMenuShortcut({
180
+ className,
181
+ ...props
182
+ }: React.ComponentProps<"span">) {
183
+ return (
184
+ <span
185
+ data-slot="dropdown-menu-shortcut"
186
+ className={cn(
187
+ "text-muted-foreground ml-auto text-xs tracking-widest",
188
+ className
189
+ )}
190
+ {...props}
191
+ />
192
+ )
193
+ }
194
+
195
+ function DropdownMenuSub({
196
+ ...props
197
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
198
+ return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
199
+ }
200
+
201
+ function DropdownMenuSubTrigger({
202
+ className,
203
+ inset,
204
+ children,
205
+ ...props
206
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
207
+ inset?: boolean
208
+ }) {
209
+ return (
210
+ <DropdownMenuPrimitive.SubTrigger
211
+ data-slot="dropdown-menu-sub-trigger"
212
+ data-inset={inset}
213
+ className={cn(
214
+ "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
215
+ className
216
+ )}
217
+ {...props}
218
+ >
219
+ {children}
220
+ <ChevronRightIcon className="ml-auto size-4" />
221
+ </DropdownMenuPrimitive.SubTrigger>
222
+ )
223
+ }
224
+
225
+ function DropdownMenuSubContent({
226
+ className,
227
+ ...props
228
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
229
+ return (
230
+ <DropdownMenuPrimitive.SubContent
231
+ data-slot="dropdown-menu-sub-content"
232
+ className={cn(
233
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
234
+ className
235
+ )}
236
+ {...props}
237
+ />
238
+ )
239
+ }
240
+
241
+ export {
242
+ DropdownMenu,
243
+ DropdownMenuPortal,
244
+ DropdownMenuTrigger,
245
+ DropdownMenuContent,
246
+ DropdownMenuGroup,
247
+ DropdownMenuLabel,
248
+ DropdownMenuItem,
249
+ DropdownMenuCheckboxItem,
250
+ DropdownMenuRadioGroup,
251
+ DropdownMenuRadioItem,
252
+ DropdownMenuSeparator,
253
+ DropdownMenuShortcut,
254
+ DropdownMenuSub,
255
+ DropdownMenuSubTrigger,
256
+ DropdownMenuSubContent,
257
+ }
components/ui/popover.tsx ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as PopoverPrimitive from "@radix-ui/react-popover"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ function Popover({
9
+ ...props
10
+ }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
11
+ return <PopoverPrimitive.Root data-slot="popover" {...props} />
12
+ }
13
+
14
+ function PopoverTrigger({
15
+ ...props
16
+ }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
17
+ return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
18
+ }
19
+
20
+ function PopoverContent({
21
+ className,
22
+ align = "center",
23
+ sideOffset = 4,
24
+ ...props
25
+ }: React.ComponentProps<typeof PopoverPrimitive.Content>) {
26
+ return (
27
+ <PopoverPrimitive.Portal>
28
+ <PopoverPrimitive.Content
29
+ data-slot="popover-content"
30
+ align={align}
31
+ sideOffset={sideOffset}
32
+ className={cn(
33
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
34
+ className
35
+ )}
36
+ {...props}
37
+ />
38
+ </PopoverPrimitive.Portal>
39
+ )
40
+ }
41
+
42
+ function PopoverAnchor({
43
+ ...props
44
+ }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
45
+ return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
46
+ }
47
+
48
+ export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
lib/constants.ts CHANGED
@@ -5,5 +5,6 @@
5
  // Local storage keys
6
  export const STORAGE_KEYS = {
7
  MCP_SERVERS: "mcp-servers",
8
- SELECTED_MCP_SERVERS: "selected-mcp-servers"
 
9
  };
 
5
  // Local storage keys
6
  export const STORAGE_KEYS = {
7
  MCP_SERVERS: "mcp-servers",
8
+ SELECTED_MCP_SERVERS: "selected-mcp-servers",
9
+ SIDEBAR_STATE: "sidebar-state"
10
  };
package.json CHANGED
@@ -23,8 +23,11 @@
23
  "@opentelemetry/instrumentation": "^0.200.0",
24
  "@opentelemetry/sdk-logs": "^0.200.0",
25
  "@radix-ui/react-accordion": "^1.2.7",
 
26
  "@radix-ui/react-dialog": "^1.1.10",
 
27
  "@radix-ui/react-label": "^2.1.3",
 
28
  "@radix-ui/react-scroll-area": "^1.2.5",
29
  "@radix-ui/react-select": "^2.1.7",
30
  "@radix-ui/react-separator": "^1.1.4",
 
23
  "@opentelemetry/instrumentation": "^0.200.0",
24
  "@opentelemetry/sdk-logs": "^0.200.0",
25
  "@radix-ui/react-accordion": "^1.2.7",
26
+ "@radix-ui/react-avatar": "^1.1.6",
27
  "@radix-ui/react-dialog": "^1.1.10",
28
+ "@radix-ui/react-dropdown-menu": "^2.1.11",
29
  "@radix-ui/react-label": "^2.1.3",
30
+ "@radix-ui/react-popover": "^1.1.10",
31
  "@radix-ui/react-scroll-area": "^1.2.5",
32
  "@radix-ui/react-select": "^2.1.7",
33
  "@radix-ui/react-separator": "^1.1.4",
pnpm-lock.yaml CHANGED
@@ -38,12 +38,21 @@ importers:
38
  '@radix-ui/react-accordion':
39
  specifier: ^1.2.7
40
 
 
 
41
  '@radix-ui/react-dialog':
42
  specifier: ^1.1.10
43
 
 
 
44
  '@radix-ui/react-label':
45
  specifier: ^2.1.3
46
 
 
 
47
  '@radix-ui/react-scroll-area':
48
  specifier: ^1.2.5
49
@@ -1135,6 +1144,19 @@ packages:
1135
  '@types/react-dom':
1136
  optional: true
1137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1138
  '@radix-ui/[email protected]':
1139
  resolution: {integrity: sha512-zGFsPcFJNdQa/UNd6MOgF40BS054FIGj32oOWBllixz42f+AkQg3QJ1YT9pw7vs+Ai+EgWkh839h69GEK8oH2A==}
1140
  peerDependencies:
@@ -1240,6 +1262,19 @@ packages:
1240
  '@types/react-dom':
1241
  optional: true
1242
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1243
  '@radix-ui/[email protected]':
1244
  resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==}
1245
  peerDependencies:
@@ -1297,6 +1332,32 @@ packages:
1297
  '@types/react-dom':
1298
  optional: true
1299
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1300
  '@radix-ui/[email protected]':
1301
  resolution: {integrity: sha512-iNb9LYUMkne9zIahukgQmHlSBp9XWGeQQ7FvUGNk45ywzOb6kQa+Ca38OphXlWDiKvyneo9S+KSJsLfLt8812A==}
1302
  peerDependencies:
@@ -1388,6 +1449,19 @@ packages:
1388
  '@types/react-dom':
1389
  optional: true
1390
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1391
  '@radix-ui/[email protected]':
1392
  resolution: {integrity: sha512-VyLjxI8/gXYn+Wij1FLpXjZp6Z/uNklUFQQ75tOpJNESeNaZ2kCRfjiEDmHgWmLeUPeJGwrqbgRmcdFjtYEkMA==}
1393
  peerDependencies:
@@ -1494,6 +1568,15 @@ packages:
1494
  '@types/react':
1495
  optional: true
1496
 
 
 
 
 
 
 
 
 
 
1497
  '@radix-ui/[email protected]':
1498
  resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
1499
  peerDependencies:
@@ -5204,6 +5287,19 @@ snapshots:
5204
  '@types/react': 19.1.2
5205
  '@types/react-dom': 19.1.2(@types/[email protected])
5206
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5207
5208
  dependencies:
5209
  '@radix-ui/primitive': 1.1.2
@@ -5310,6 +5406,21 @@ snapshots:
5310
  '@types/react': 19.1.2
5311
  '@types/react-dom': 19.1.2(@types/[email protected])
5312
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5313
5314
  dependencies:
5315
  react: 19.1.0
@@ -5354,6 +5465,55 @@ snapshots:
5354
  '@types/react': 19.1.2
5355
  '@types/react-dom': 19.1.2(@types/[email protected])
5356
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5357
5358
  dependencies:
5359
  '@floating-ui/react-dom': 2.1.2([email protected]([email protected]))([email protected])
@@ -5438,6 +5598,23 @@ snapshots:
5438
  '@types/react': 19.1.2
5439
  '@types/react-dom': 19.1.2(@types/[email protected])
5440
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5441
5442
  dependencies:
5443
  '@radix-ui/number': 1.1.1
@@ -5555,6 +5732,13 @@ snapshots:
5555
  optionalDependencies:
5556
  '@types/react': 19.1.2
5557
 
 
 
 
 
 
 
 
5558
5559
  dependencies:
5560
  react: 19.1.0
 
38
  '@radix-ui/react-accordion':
39
  specifier: ^1.2.7
40
41
+ '@radix-ui/react-avatar':
42
+ specifier: ^1.1.6
43
44
  '@radix-ui/react-dialog':
45
  specifier: ^1.1.10
46
47
+ '@radix-ui/react-dropdown-menu':
48
+ specifier: ^2.1.11
49
50
  '@radix-ui/react-label':
51
  specifier: ^2.1.3
52
53
+ '@radix-ui/react-popover':
54
+ specifier: ^1.1.10
55
56
  '@radix-ui/react-scroll-area':
57
  specifier: ^1.2.5
58
 
1144
  '@types/react-dom':
1145
  optional: true
1146
 
1147
+ '@radix-ui/[email protected]':
1148
+ resolution: {integrity: sha512-YDduxvqNMHzTQWNqja7Z/XTyFc8UOP98/ePjJTFa1vqILPlTPcQaVa1YyQMiQl4SFQPA9Y/zj1dHBgMlE5G/ow==}
1149
+ peerDependencies:
1150
+ '@types/react': '*'
1151
+ '@types/react-dom': '*'
1152
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1153
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1154
+ peerDependenciesMeta:
1155
+ '@types/react':
1156
+ optional: true
1157
+ '@types/react-dom':
1158
+ optional: true
1159
+
1160
  '@radix-ui/[email protected]':
1161
  resolution: {integrity: sha512-zGFsPcFJNdQa/UNd6MOgF40BS054FIGj32oOWBllixz42f+AkQg3QJ1YT9pw7vs+Ai+EgWkh839h69GEK8oH2A==}
1162
  peerDependencies:
 
1262
  '@types/react-dom':
1263
  optional: true
1264
 
1265
+ '@radix-ui/[email protected]':
1266
+ resolution: {integrity: sha512-wbPE3cFBfLl+S+LCxChWQGX0k14zUxgvep1HEnLhJ9mNhjyO3ETzRviAeKZ3XomT/iVRRZAWFsnFZ3N0wI8OmA==}
1267
+ peerDependencies:
1268
+ '@types/react': '*'
1269
+ '@types/react-dom': '*'
1270
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1271
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1272
+ peerDependenciesMeta:
1273
+ '@types/react':
1274
+ optional: true
1275
+ '@types/react-dom':
1276
+ optional: true
1277
+
1278
  '@radix-ui/[email protected]':
1279
  resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==}
1280
  peerDependencies:
 
1332
  '@types/react-dom':
1333
  optional: true
1334
 
1335
+ '@radix-ui/[email protected]':
1336
+ resolution: {integrity: sha512-sbFI4Qaw02J0ogmR9tOMsSqsdrGNpUanlPYAqTE2JJafow8ecHtykg4fSTjNHBdDl4deiKMK+RhTEwyVhP7UDA==}
1337
+ peerDependencies:
1338
+ '@types/react': '*'
1339
+ '@types/react-dom': '*'
1340
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1341
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1342
+ peerDependenciesMeta:
1343
+ '@types/react':
1344
+ optional: true
1345
+ '@types/react-dom':
1346
+ optional: true
1347
+
1348
+ '@radix-ui/[email protected]':
1349
+ resolution: {integrity: sha512-IZN7b3sXqajiPsOzKuNJBSP9obF4MX5/5UhTgWNofw4r1H+eATWb0SyMlaxPD/kzA4vadFgy1s7Z1AEJ6WMyHQ==}
1350
+ peerDependencies:
1351
+ '@types/react': '*'
1352
+ '@types/react-dom': '*'
1353
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1354
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1355
+ peerDependenciesMeta:
1356
+ '@types/react':
1357
+ optional: true
1358
+ '@types/react-dom':
1359
+ optional: true
1360
+
1361
  '@radix-ui/[email protected]':
1362
  resolution: {integrity: sha512-iNb9LYUMkne9zIahukgQmHlSBp9XWGeQQ7FvUGNk45ywzOb6kQa+Ca38OphXlWDiKvyneo9S+KSJsLfLt8812A==}
1363
  peerDependencies:
 
1449
  '@types/react-dom':
1450
  optional: true
1451
 
1452
+ '@radix-ui/[email protected]':
1453
+ resolution: {integrity: sha512-C6oAg451/fQT3EGbWHbCQjYTtbyjNO1uzQgMzwyivcHT3GKNEmu1q3UuREhN+HzHAVtv3ivMVK08QlC+PkYw9Q==}
1454
+ peerDependencies:
1455
+ '@types/react': '*'
1456
+ '@types/react-dom': '*'
1457
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1458
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1459
+ peerDependenciesMeta:
1460
+ '@types/react':
1461
+ optional: true
1462
+ '@types/react-dom':
1463
+ optional: true
1464
+
1465
  '@radix-ui/[email protected]':
1466
  resolution: {integrity: sha512-VyLjxI8/gXYn+Wij1FLpXjZp6Z/uNklUFQQ75tOpJNESeNaZ2kCRfjiEDmHgWmLeUPeJGwrqbgRmcdFjtYEkMA==}
1467
  peerDependencies:
 
1568
  '@types/react':
1569
  optional: true
1570
 
1571
+ '@radix-ui/[email protected]':
1572
+ resolution: {integrity: sha512-23RkSm7jSZ8+rtfdSJTi/2D+p9soPbtnoG/tPf08egYCDr6p8X83hrcmW77p7MJ8kJYWNXwruuPTPp1TwIIH4g==}
1573
+ peerDependencies:
1574
+ '@types/react': '*'
1575
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1576
+ peerDependenciesMeta:
1577
+ '@types/react':
1578
+ optional: true
1579
+
1580
  '@radix-ui/[email protected]':
1581
  resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
1582
  peerDependencies:
 
5287
  '@types/react': 19.1.2
5288
  '@types/react-dom': 19.1.2(@types/[email protected])
5289
 
5290
5291
+ dependencies:
5292
+ '@radix-ui/react-context': 1.1.2(@types/[email protected])([email protected])
5293
5294
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/[email protected])([email protected])
5295
+ '@radix-ui/react-use-is-hydrated': 0.0.0(@types/[email protected])([email protected])
5296
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/[email protected])([email protected])
5297
+ react: 19.1.0
5298
+ react-dom: 19.1.0([email protected])
5299
+ optionalDependencies:
5300
+ '@types/react': 19.1.2
5301
+ '@types/react-dom': 19.1.2(@types/[email protected])
5302
+
5303
5304
  dependencies:
5305
  '@radix-ui/primitive': 1.1.2
 
5406
  '@types/react': 19.1.2
5407
  '@types/react-dom': 19.1.2(@types/[email protected])
5408
 
5409
5410
+ dependencies:
5411
+ '@radix-ui/primitive': 1.1.2
5412
+ '@radix-ui/react-compose-refs': 1.1.2(@types/[email protected])([email protected])
5413
+ '@radix-ui/react-context': 1.1.2(@types/[email protected])([email protected])
5414
+ '@radix-ui/react-id': 1.1.1(@types/[email protected])([email protected])
5415
5416
5417
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/[email protected])([email protected])
5418
+ react: 19.1.0
5419
+ react-dom: 19.1.0([email protected])
5420
+ optionalDependencies:
5421
+ '@types/react': 19.1.2
5422
+ '@types/react-dom': 19.1.2(@types/[email protected])
5423
+
5424
5425
  dependencies:
5426
  react: 19.1.0
 
5465
  '@types/react': 19.1.2
5466
  '@types/react-dom': 19.1.2(@types/[email protected])
5467
 
5468
5469
+ dependencies:
5470
+ '@radix-ui/primitive': 1.1.2
5471
+ '@radix-ui/react-collection': 1.1.4(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
5472
+ '@radix-ui/react-compose-refs': 1.1.2(@types/[email protected])([email protected])
5473
+ '@radix-ui/react-context': 1.1.2(@types/[email protected])([email protected])
5474
+ '@radix-ui/react-direction': 1.1.1(@types/[email protected])([email protected])
5475
+ '@radix-ui/react-dismissable-layer': 1.1.7(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
5476
+ '@radix-ui/react-focus-guards': 1.1.2(@types/[email protected])([email protected])
5477
+ '@radix-ui/react-focus-scope': 1.1.4(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
5478
+ '@radix-ui/react-id': 1.1.1(@types/[email protected])([email protected])
5479
5480
5481
5482
5483
+ '@radix-ui/react-roving-focus': 1.1.7(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
5484
+ '@radix-ui/react-slot': 1.2.0(@types/[email protected])([email protected])
5485
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/[email protected])([email protected])
5486
+ aria-hidden: 1.2.4
5487
+ react: 19.1.0
5488
+ react-dom: 19.1.0([email protected])
5489
+ react-remove-scroll: 2.6.3(@types/[email protected])([email protected])
5490
+ optionalDependencies:
5491
+ '@types/react': 19.1.2
5492
+ '@types/react-dom': 19.1.2(@types/[email protected])
5493
+
5494
5495
+ dependencies:
5496
+ '@radix-ui/primitive': 1.1.2
5497
+ '@radix-ui/react-compose-refs': 1.1.2(@types/[email protected])([email protected])
5498
+ '@radix-ui/react-context': 1.1.2(@types/[email protected])([email protected])
5499
+ '@radix-ui/react-dismissable-layer': 1.1.7(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
5500
+ '@radix-ui/react-focus-guards': 1.1.2(@types/[email protected])([email protected])
5501
+ '@radix-ui/react-focus-scope': 1.1.4(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
5502
+ '@radix-ui/react-id': 1.1.1(@types/[email protected])([email protected])
5503
5504
5505
5506
5507
+ '@radix-ui/react-slot': 1.2.0(@types/[email protected])([email protected])
5508
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/[email protected])([email protected])
5509
+ aria-hidden: 1.2.4
5510
+ react: 19.1.0
5511
+ react-dom: 19.1.0([email protected])
5512
+ react-remove-scroll: 2.6.3(@types/[email protected])([email protected])
5513
+ optionalDependencies:
5514
+ '@types/react': 19.1.2
5515
+ '@types/react-dom': 19.1.2(@types/[email protected])
5516
+
5517
5518
  dependencies:
5519
  '@floating-ui/react-dom': 2.1.2([email protected]([email protected]))([email protected])
 
5598
  '@types/react': 19.1.2
5599
  '@types/react-dom': 19.1.2(@types/[email protected])
5600
 
5601
5602
+ dependencies:
5603
+ '@radix-ui/primitive': 1.1.2
5604
+ '@radix-ui/react-collection': 1.1.4(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
5605
+ '@radix-ui/react-compose-refs': 1.1.2(@types/[email protected])([email protected])
5606
+ '@radix-ui/react-context': 1.1.2(@types/[email protected])([email protected])
5607
+ '@radix-ui/react-direction': 1.1.1(@types/[email protected])([email protected])
5608
+ '@radix-ui/react-id': 1.1.1(@types/[email protected])([email protected])
5609
5610
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/[email protected])([email protected])
5611
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/[email protected])([email protected])
5612
+ react: 19.1.0
5613
+ react-dom: 19.1.0([email protected])
5614
+ optionalDependencies:
5615
+ '@types/react': 19.1.2
5616
+ '@types/react-dom': 19.1.2(@types/[email protected])
5617
+
5618
5619
  dependencies:
5620
  '@radix-ui/number': 1.1.1
 
5732
  optionalDependencies:
5733
  '@types/react': 19.1.2
5734
 
5735
5736
+ dependencies:
5737
+ react: 19.1.0
5738
+ use-sync-external-store: 1.5.0([email protected])
5739
+ optionalDependencies:
5740
+ '@types/react': 19.1.2
5741
+
5742
5743
  dependencies:
5744
  react: 19.1.0
railpack.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://schema.railpack.com",
3
+ "provider": "node",
4
+ "buildAptPackages": [
5
+ "git",
6
+ "curl"
7
+ ],
8
+ "packages": {
9
+ "node": "22",
10
+ "python": "3.12.7"
11
+ },
12
+ "deploy": {
13
+ "aptPackages": [
14
+ "git",
15
+ "curl"
16
+ ]
17
+ }
18
+ }