mukaddamzaid commited on
Commit
d8c725b
·
1 Parent(s): 6d663cf

ui updates and added anthropic models for testing

Browse files
ai/providers.ts CHANGED
@@ -1,9 +1,8 @@
1
- import { xai } from "@ai-sdk/xai";
2
  import { openai } from "@ai-sdk/openai";
3
  import { google } from "@ai-sdk/google";
4
  import { groq } from "@ai-sdk/groq";
5
  import { customProvider, wrapLanguageModel, extractReasoningMiddleware } from "ai";
6
- import { cohere } from "@ai-sdk/cohere";
7
  export interface ModelInfo {
8
  provider: string;
9
  name: string;
@@ -14,35 +13,41 @@ export interface ModelInfo {
14
 
15
  const middleware = extractReasoningMiddleware({
16
  tagName: 'think',
17
- separator: '\n',
18
  });
19
 
20
  const languageModels = {
 
21
  "gpt-4.1-mini": openai("gpt-4.1-mini"),
22
- "gemini-2-flash": google("gemini-2.0-flash-001"),
23
  "qwen-qwq": wrapLanguageModel(
24
  {
25
- model: groq("qwen-qwq-32b"),
26
  middleware
27
  }
28
  ),
29
- "command-a": cohere('command-a-03-2025')
30
  };
31
 
32
  export const modelDetails: Record<keyof typeof languageModels, ModelInfo> = {
 
 
 
 
 
 
 
33
  "gpt-4.1-mini": {
34
  provider: "OpenAI",
35
  name: "GPT-4.1 Mini",
36
  description: "Compact version of OpenAI's GPT-4.1 with good balance of capabilities, including vision.",
37
  apiVersion: "gpt-4.1-mini",
38
- capabilities: [ "Balance", "Creative", "Vision"]
39
  },
40
- "gemini-2-flash": {
41
- provider: "Google",
42
- name: "Gemini 2 Flash",
43
- description: "Latest version of Google's Gemini 2 with strong reasoning and coding capabilities.",
44
- apiVersion: "gemini-2-flash",
45
- capabilities: ["Balance", "Efficient", "Agentic"]
46
  },
47
  "qwen-qwq": {
48
  provider: "Groq",
@@ -51,13 +56,6 @@ export const modelDetails: Record<keyof typeof languageModels, ModelInfo> = {
51
  apiVersion: "qwen-qwq",
52
  capabilities: ["Reasoning", "Efficient", "Agentic"]
53
  },
54
- "command-a": {
55
- provider: "Cohere",
56
- name: "Command A",
57
- description: "Latest version of Cohere's Command A with strong reasoning and coding capabilities.",
58
- apiVersion: "command-a-03-2025",
59
- capabilities: ["Smart", "Fast", "Reasoning"]
60
- }
61
  };
62
 
63
  export const model = customProvider({
 
 
1
  import { openai } from "@ai-sdk/openai";
2
  import { google } from "@ai-sdk/google";
3
  import { groq } from "@ai-sdk/groq";
4
  import { customProvider, wrapLanguageModel, extractReasoningMiddleware } from "ai";
5
+ import { anthropic } from "@ai-sdk/anthropic";
6
  export interface ModelInfo {
7
  provider: string;
8
  name: string;
 
13
 
14
  const middleware = extractReasoningMiddleware({
15
  tagName: 'think',
 
16
  });
17
 
18
  const languageModels = {
19
+ "gemini-2.5-flash": google("gemini-2.5-flash-preview-04-17"),
20
  "gpt-4.1-mini": openai("gpt-4.1-mini"),
21
+ "claude-3-7-sonnet": anthropic('claude-3-7-sonnet-20250219'),
22
  "qwen-qwq": wrapLanguageModel(
23
  {
24
+ model: groq("qwen-qwq-32b"),
25
  middleware
26
  }
27
  ),
 
28
  };
29
 
30
  export const modelDetails: Record<keyof typeof languageModels, ModelInfo> = {
31
+ "gemini-2.5-flash": {
32
+ provider: "Google",
33
+ name: "Gemini 2.5 Flash",
34
+ description: "Latest version of Google's Gemini 2.5 Flash with strong reasoning and coding capabilities.",
35
+ apiVersion: "gemini-2.5-flash-preview-04-17",
36
+ capabilities: ["Balance", "Efficient", "Agentic"]
37
+ },
38
  "gpt-4.1-mini": {
39
  provider: "OpenAI",
40
  name: "GPT-4.1 Mini",
41
  description: "Compact version of OpenAI's GPT-4.1 with good balance of capabilities, including vision.",
42
  apiVersion: "gpt-4.1-mini",
43
+ capabilities: ["Balance", "Creative", "Vision"]
44
  },
45
+ "claude-3-7-sonnet": {
46
+ provider: "Anthropic",
47
+ name: "Claude 3.7 Sonnet",
48
+ description: "Latest version of Anthropic's Claude 3.7 Sonnet with strong reasoning and coding capabilities.",
49
+ apiVersion: "claude-3-7-sonnet-20250219",
50
+ capabilities: ["Reasoning", "Efficient", "Agentic"]
51
  },
52
  "qwen-qwq": {
53
  provider: "Groq",
 
56
  apiVersion: "qwen-qwq",
57
  capabilities: ["Reasoning", "Efficient", "Agentic"]
58
  },
 
 
 
 
 
 
 
59
  };
60
 
61
  export const model = customProvider({
ai/tools.ts DELETED
@@ -1,11 +0,0 @@
1
- import { VercelAIToolSet } from "composio-core";
2
-
3
- const toolset = new VercelAIToolSet();
4
-
5
- export const composioTools = await toolset.getTools(
6
- {
7
- apps: [
8
- "tavily",
9
- ]
10
- }
11
- );
 
 
 
 
 
 
 
 
 
 
 
 
app/api/chat/route.ts CHANGED
@@ -113,8 +113,17 @@ export async function POST(req: Request) {
113
  });
114
  }
115
 
 
 
 
 
 
 
 
 
 
116
  // 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
117
- if (mcpServer.command.includes('python3')) {
118
  const packageName = mcpServer.args[mcpServer.args.indexOf('-m') + 1];
119
  console.log("installing python package", packageName);
120
  const subprocess = spawn('pip3', ['install', packageName]);
@@ -195,6 +204,19 @@ export async function POST(req: Request) {
195
  messages,
196
  tools,
197
  maxSteps: 20,
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  onError: (error) => {
199
  console.error(JSON.stringify(error, null, 2));
200
  },
 
113
  });
114
  }
115
 
116
+ // Check for uvx pattern and transform to python3 -m uv run
117
+ if (mcpServer.command === 'uvx') {
118
+ console.log("Detected uvx pattern, transforming to python3 -m uv run");
119
+ mcpServer.command = 'python3';
120
+ // Get the tool name (first argument)
121
+ const toolName = mcpServer.args[0];
122
+ // Replace args with the new pattern
123
+ mcpServer.args = ['-m', 'uv', 'run', toolName, ...mcpServer.args.slice(1)];
124
+ }
125
  // 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
126
+ else if (mcpServer.command.includes('python3')) {
127
  const packageName = mcpServer.args[mcpServer.args.indexOf('-m') + 1];
128
  console.log("installing python package", packageName);
129
  const subprocess = spawn('pip3', ['install', packageName]);
 
204
  messages,
205
  tools,
206
  maxSteps: 20,
207
+ providerOptions: {
208
+ google: {
209
+ thinkingConfig: {
210
+ thinkingBudget: 0,
211
+ },
212
+ },
213
+ anthropic: {
214
+ thinking: {
215
+ type: 'enabled',
216
+ budgetTokens: 12000
217
+ },
218
+ }
219
+ },
220
  onError: (error) => {
221
  console.error(JSON.stringify(error, null, 2));
222
  },
components/chat-sidebar.tsx CHANGED
@@ -1,8 +1,8 @@
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, Pencil, Github } from "lucide-react";
6
  import {
7
  Sidebar,
8
  SidebarContent,
@@ -25,8 +25,6 @@ import Image from "next/image";
25
  import { MCPServerManager } from "./mcp-server-manager";
26
  import { ThemeToggle } from "./theme-toggle";
27
  import { getUserId, updateUserId } 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";
@@ -51,6 +49,8 @@ import {
51
  import { Input } from "@/components/ui/input";
52
  import { Label } from "@/components/ui/label";
53
  import { useMCP } from "@/lib/context/mcp-context";
 
 
54
 
55
  export function ChatSidebar() {
56
  const router = useRouter();
@@ -115,6 +115,23 @@ export function ChatSidebar() {
115
  return null; // Or a loading spinner
116
  }
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  return (
119
  <Sidebar className="shadow-sm bg-background/80 dark:bg-background/40 backdrop-blur-md" collapsible="icon">
120
  <SidebarHeader className="p-4 border-b border-border/40">
@@ -144,12 +161,7 @@ export function ChatSidebar() {
144
  )}>
145
  <SidebarMenu>
146
  {isLoading ? (
147
- <div className={`flex items-center justify-center py-4 ${isCollapsed ? "" : "px-4"}`}>
148
- <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
149
- {!isCollapsed && (
150
- <span className="ml-2 text-xs text-muted-foreground">Loading...</span>
151
- )}
152
- </div>
153
  ) : chats.length === 0 ? (
154
  <div className={`flex items-center justify-center py-3 ${isCollapsed ? "" : "px-4"}`}>
155
  {isCollapsed ? (
@@ -164,50 +176,60 @@ export function ChatSidebar() {
164
  )}
165
  </div>
166
  ) : (
167
- chats.map((chat) => (
168
- <SidebarMenuItem key={chat.id}>
169
- <SidebarMenuButton
170
- asChild
171
- tooltip={isCollapsed ? chat.title : undefined}
172
- data-active={pathname === `/chat/${chat.id}`}
173
- className={cn(
174
- "transition-all hover:bg-primary/10 active:bg-primary/15",
175
- pathname === `/chat/${chat.id}` ? "bg-secondary/60 hover:bg-secondary/60" : ""
176
- )}
177
  >
178
- <Link
179
- href={`/chat/${chat.id}`}
180
- className="flex items-center justify-between w-full gap-1"
181
- >
182
- <div className="flex items-center min-w-0 overflow-hidden flex-1 pr-2">
183
- <MessageSquare className={cn(
184
- "h-4 w-4 flex-shrink-0",
185
- pathname === `/chat/${chat.id}` ? "text-foreground" : "text-muted-foreground"
186
- )} />
187
- {!isCollapsed && (
188
- <span className={cn(
189
- "ml-2 truncate text-sm",
190
- pathname === `/chat/${chat.id}` ? "text-foreground font-medium" : "text-foreground/80"
191
- )} title={chat.title}>
192
- {chat.title.length > 18 ? `${chat.title.slice(0, 18)}...` : chat.title}
193
- </span>
194
  )}
195
- </div>
196
- {!isCollapsed && (
197
- <Button
198
- variant="ghost"
199
- size="icon"
200
- className="h-6 w-6 text-muted-foreground hover:text-foreground flex-shrink-0"
201
- onClick={(e) => handleDeleteChat(chat.id, e)}
202
- title="Delete chat"
203
  >
204
- <Trash2 className="h-3.5 w-3.5" />
205
- </Button>
206
- )}
207
- </Link>
208
- </SidebarMenuButton>
209
- </SidebarMenuItem>
210
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  )}
212
  </SidebarMenu>
213
  </SidebarGroupContent>
@@ -265,18 +287,23 @@ export function ChatSidebar() {
265
 
266
  <SidebarFooter className="p-4 border-t border-border/40 mt-auto">
267
  <div className={`flex flex-col ${isCollapsed ? "items-center" : ""} gap-3`}>
268
- <Button
269
- variant="default"
270
- className={cn(
271
- "w-full bg-primary text-primary-foreground hover:bg-primary/90",
272
- isCollapsed ? "w-8 h-8 p-0" : ""
273
- )}
274
- onClick={handleNewChat}
275
- title={isCollapsed ? "New Chat" : undefined}
276
  >
277
- <PlusCircle className={`${isCollapsed ? "" : "mr-2"} h-4 w-4`} />
278
- {!isCollapsed && <span>New Chat</span>}
279
- </Button>
 
 
 
 
 
 
 
 
 
 
280
 
281
  <DropdownMenu modal={false}>
282
  <DropdownMenuTrigger asChild>
 
1
  "use client";
2
 
3
+ import { useState, useEffect } from "react";
4
  import { useRouter, usePathname } from "next/navigation";
5
+ import { MessageSquare, PlusCircle, Trash2, ServerIcon, Settings, Sparkles, ChevronsUpDown, Copy, Pencil, Github } from "lucide-react";
6
  import {
7
  Sidebar,
8
  SidebarContent,
 
25
  import { MCPServerManager } from "./mcp-server-manager";
26
  import { ThemeToggle } from "./theme-toggle";
27
  import { getUserId, updateUserId } from "@/lib/user-id";
 
 
28
  import { useChats } from "@/lib/hooks/use-chats";
29
  import { cn } from "@/lib/utils";
30
  import Link from "next/link";
 
49
  import { Input } from "@/components/ui/input";
50
  import { Label } from "@/components/ui/label";
51
  import { useMCP } from "@/lib/context/mcp-context";
52
+ import { Skeleton } from "@/components/ui/skeleton";
53
+ import { AnimatePresence, motion } from "motion/react";
54
 
55
  export function ChatSidebar() {
56
  const router = useRouter();
 
115
  return null; // Or a loading spinner
116
  }
117
 
118
+ // Create chat loading skeletons
119
+ const renderChatSkeletons = () => {
120
+ return Array(3).fill(0).map((_, index) => (
121
+ <SidebarMenuItem key={`skeleton-${index}`}>
122
+ <div className={`flex items-center gap-2 px-3 py-2 ${isCollapsed ? "justify-center" : ""}`}>
123
+ <Skeleton className="h-4 w-4 rounded-full" />
124
+ {!isCollapsed && (
125
+ <>
126
+ <Skeleton className="h-4 w-full max-w-[180px]" />
127
+ <Skeleton className="h-5 w-5 ml-auto rounded-md flex-shrink-0" />
128
+ </>
129
+ )}
130
+ </div>
131
+ </SidebarMenuItem>
132
+ ));
133
+ };
134
+
135
  return (
136
  <Sidebar className="shadow-sm bg-background/80 dark:bg-background/40 backdrop-blur-md" collapsible="icon">
137
  <SidebarHeader className="p-4 border-b border-border/40">
 
161
  )}>
162
  <SidebarMenu>
163
  {isLoading ? (
164
+ renderChatSkeletons()
 
 
 
 
 
165
  ) : chats.length === 0 ? (
166
  <div className={`flex items-center justify-center py-3 ${isCollapsed ? "" : "px-4"}`}>
167
  {isCollapsed ? (
 
176
  )}
177
  </div>
178
  ) : (
179
+ <AnimatePresence initial={false}>
180
+ {chats.map((chat) => (
181
+ <motion.div
182
+ key={chat.id}
183
+ initial={{ opacity: 0, height: 0, y: -10 }}
184
+ animate={{ opacity: 1, height: "auto", y: 0 }}
185
+ exit={{ opacity: 0, height: 0 }}
186
+ transition={{ duration: 0.2 }}
 
 
187
  >
188
+ <SidebarMenuItem>
189
+ <SidebarMenuButton
190
+ asChild
191
+ tooltip={isCollapsed ? chat.title : undefined}
192
+ data-active={pathname === `/chat/${chat.id}`}
193
+ className={cn(
194
+ "transition-all hover:bg-primary/10 active:bg-primary/15",
195
+ pathname === `/chat/${chat.id}` ? "bg-secondary/60 hover:bg-secondary/60" : ""
 
 
 
 
 
 
 
 
196
  )}
197
+ >
198
+ <Link
199
+ href={`/chat/${chat.id}`}
200
+ className="flex items-center justify-between w-full gap-1"
 
 
 
 
201
  >
202
+ <div className="flex items-center min-w-0 overflow-hidden flex-1 pr-2">
203
+ <MessageSquare className={cn(
204
+ "h-4 w-4 flex-shrink-0",
205
+ pathname === `/chat/${chat.id}` ? "text-foreground" : "text-muted-foreground"
206
+ )} />
207
+ {!isCollapsed && (
208
+ <span className={cn(
209
+ "ml-2 truncate text-sm",
210
+ pathname === `/chat/${chat.id}` ? "text-foreground font-medium" : "text-foreground/80"
211
+ )} title={chat.title}>
212
+ {chat.title.length > 18 ? `${chat.title.slice(0, 18)}...` : chat.title}
213
+ </span>
214
+ )}
215
+ </div>
216
+ {!isCollapsed && (
217
+ <Button
218
+ variant="ghost"
219
+ size="icon"
220
+ className="h-6 w-6 text-muted-foreground hover:text-foreground flex-shrink-0"
221
+ onClick={(e) => handleDeleteChat(chat.id, e)}
222
+ title="Delete chat"
223
+ >
224
+ <Trash2 className="h-3.5 w-3.5" />
225
+ </Button>
226
+ )}
227
+ </Link>
228
+ </SidebarMenuButton>
229
+ </SidebarMenuItem>
230
+ </motion.div>
231
+ ))}
232
+ </AnimatePresence>
233
  )}
234
  </SidebarMenu>
235
  </SidebarGroupContent>
 
287
 
288
  <SidebarFooter className="p-4 border-t border-border/40 mt-auto">
289
  <div className={`flex flex-col ${isCollapsed ? "items-center" : ""} gap-3`}>
290
+ <motion.div
291
+ whileHover={{ scale: 1.02 }}
292
+ whileTap={{ scale: 0.98 }}
 
 
 
 
 
293
  >
294
+ <Button
295
+ variant="default"
296
+ className={cn(
297
+ "w-full bg-primary text-primary-foreground hover:bg-primary/90",
298
+ isCollapsed ? "w-8 h-8 p-0" : ""
299
+ )}
300
+ onClick={handleNewChat}
301
+ title={isCollapsed ? "New Chat" : undefined}
302
+ >
303
+ <PlusCircle className={`${isCollapsed ? "" : "mr-2"} h-4 w-4`} />
304
+ {!isCollapsed && <span>New Chat</span>}
305
+ </Button>
306
+ </motion.div>
307
 
308
  <DropdownMenu modal={false}>
309
  <DropdownMenuTrigger asChild>
components/markdown.tsx CHANGED
@@ -7,7 +7,7 @@ import { cn } from "@/lib/utils";
7
 
8
  const components: Partial<Components> = {
9
  pre: ({ children, ...props }) => (
10
- <pre className="overflow-x-auto rounded-lg bg-zinc-100 dark:bg-zinc-800/50 p-4 my-2 text-sm" {...props}>
11
  {children}
12
  </pre>
13
  ),
@@ -18,7 +18,7 @@ const components: Partial<Components> = {
18
  if (isInline) {
19
  return (
20
  <code
21
- className="px-1.5 py-0.5 rounded-md bg-zinc-100 dark:bg-zinc-800/50 text-zinc-700 dark:text-zinc-300 text-[0.9em] font-mono"
22
  {...props}
23
  >
24
  {children}
@@ -32,22 +32,22 @@ const components: Partial<Components> = {
32
  );
33
  },
34
  ol: ({ node, children, ...props }) => (
35
- <ol className="list-decimal list-outside ml-4 space-y-1 my-2" {...props}>
36
  {children}
37
  </ol>
38
  ),
39
  ul: ({ node, children, ...props }) => (
40
- <ul className="list-disc list-outside ml-4 space-y-1 my-2" {...props}>
41
  {children}
42
  </ul>
43
  ),
44
  li: ({ node, children, ...props }) => (
45
- <li className="leading-relaxed" {...props}>
46
  {children}
47
  </li>
48
  ),
49
  p: ({ node, children, ...props }) => (
50
- <p className="leading-7" {...props}>
51
  {children}
52
  </p>
53
  ),
@@ -63,7 +63,7 @@ const components: Partial<Components> = {
63
  ),
64
  blockquote: ({ node, children, ...props }) => (
65
  <blockquote
66
- className="border-l-2 border-zinc-200 dark:border-zinc-700 pl-4 my-3 italic text-zinc-600 dark:text-zinc-400"
67
  {...props}
68
  >
69
  {children}
@@ -72,7 +72,7 @@ const components: Partial<Components> = {
72
  a: ({ node, children, ...props }) => (
73
  // @ts-expect-error error
74
  <Link
75
- className="text-blue-500 hover:underline hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors"
76
  target="_blank"
77
  rel="noreferrer"
78
  {...props}
@@ -81,72 +81,72 @@ const components: Partial<Components> = {
81
  </Link>
82
  ),
83
  h1: ({ node, children, ...props }) => (
84
- <h1 className="text-3xl font-semibold mt-6 mb-4 text-zinc-800 dark:text-zinc-200" {...props}>
85
  {children}
86
  </h1>
87
  ),
88
  h2: ({ node, children, ...props }) => (
89
- <h2 className="text-2xl font-semibold mt-6 mb-3 text-zinc-800 dark:text-zinc-200" {...props}>
90
  {children}
91
  </h2>
92
  ),
93
  h3: ({ node, children, ...props }) => (
94
- <h3 className="text-xl font-semibold mt-6 mb-3 text-zinc-800 dark:text-zinc-200" {...props}>
95
  {children}
96
  </h3>
97
  ),
98
  h4: ({ node, children, ...props }) => (
99
- <h4 className="text-lg font-semibold mt-6 mb-2 text-zinc-800 dark:text-zinc-200" {...props}>
100
  {children}
101
  </h4>
102
  ),
103
  h5: ({ node, children, ...props }) => (
104
- <h5 className="text-base font-semibold mt-6 mb-2 text-zinc-800 dark:text-zinc-200" {...props}>
105
  {children}
106
  </h5>
107
  ),
108
  h6: ({ node, children, ...props }) => (
109
- <h6 className="text-sm font-semibold mt-6 mb-2 text-zinc-800 dark:text-zinc-200" {...props}>
110
  {children}
111
  </h6>
112
  ),
113
  table: ({ node, children, ...props }) => (
114
- <div className="my-4 overflow-x-auto">
115
- <table className="min-w-full divide-y divide-zinc-200 dark:divide-zinc-700" {...props}>
116
  {children}
117
  </table>
118
  </div>
119
  ),
120
  thead: ({ node, children, ...props }) => (
121
- <thead className="bg-zinc-50 dark:bg-zinc-800/50" {...props}>
122
  {children}
123
  </thead>
124
  ),
125
  tbody: ({ node, children, ...props }) => (
126
- <tbody className="divide-y divide-zinc-200 dark:divide-zinc-700 bg-white dark:bg-transparent" {...props}>
127
  {children}
128
  </tbody>
129
  ),
130
  tr: ({ node, children, ...props }) => (
131
- <tr className="transition-colors hover:bg-zinc-50 dark:hover:bg-zinc-800/30" {...props}>
132
  {children}
133
  </tr>
134
  ),
135
  th: ({ node, children, ...props }) => (
136
  <th
137
- className="px-4 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider"
138
  {...props}
139
  >
140
  {children}
141
  </th>
142
  ),
143
  td: ({ node, children, ...props }) => (
144
- <td className="px-4 py-3 text-sm" {...props}>
145
  {children}
146
  </td>
147
  ),
148
  hr: ({ node, ...props }) => (
149
- <hr className="my-4 border-zinc-200 dark:border-zinc-700" {...props} />
150
  ),
151
  };
152
 
 
7
 
8
  const components: Partial<Components> = {
9
  pre: ({ children, ...props }) => (
10
+ <pre className="overflow-x-auto rounded-lg bg-zinc-100 dark:bg-zinc-800/50 black:bg-zinc-800/50 p-2.5 my-1.5 text-sm" {...props}>
11
  {children}
12
  </pre>
13
  ),
 
18
  if (isInline) {
19
  return (
20
  <code
21
+ className="px-1 py-0.5 rounded-md bg-zinc-100 dark:bg-zinc-800/50 black:bg-zinc-800/50 text-zinc-700 dark:text-zinc-300 black:text-zinc-300 text-[0.9em] font-mono"
22
  {...props}
23
  >
24
  {children}
 
32
  );
33
  },
34
  ol: ({ node, children, ...props }) => (
35
+ <ol className="list-decimal list-outside ml-4 space-y-0.5 my-1.5" {...props}>
36
  {children}
37
  </ol>
38
  ),
39
  ul: ({ node, children, ...props }) => (
40
+ <ul className="list-disc list-outside ml-4 space-y-0.5 my-1.5" {...props}>
41
  {children}
42
  </ul>
43
  ),
44
  li: ({ node, children, ...props }) => (
45
+ <li className="leading-normal" {...props}>
46
  {children}
47
  </li>
48
  ),
49
  p: ({ node, children, ...props }) => (
50
+ <p className="leading-relaxed my-1" {...props}>
51
  {children}
52
  </p>
53
  ),
 
63
  ),
64
  blockquote: ({ node, children, ...props }) => (
65
  <blockquote
66
+ className="border-l-2 border-zinc-200 dark:border-zinc-700 black:border-zinc-700 pl-3 my-1.5 italic text-zinc-600 dark:text-zinc-400 black:text-zinc-400"
67
  {...props}
68
  >
69
  {children}
 
72
  a: ({ node, children, ...props }) => (
73
  // @ts-expect-error error
74
  <Link
75
+ className="text-blue-500 hover:underline hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 black:text-blue-400 black:hover:text-blue-300 transition-colors"
76
  target="_blank"
77
  rel="noreferrer"
78
  {...props}
 
81
  </Link>
82
  ),
83
  h1: ({ node, children, ...props }) => (
84
+ <h1 className="text-2xl font-semibold mt-3 mb-1.5 text-zinc-800 dark:text-zinc-200 black:text-zinc-200" {...props}>
85
  {children}
86
  </h1>
87
  ),
88
  h2: ({ node, children, ...props }) => (
89
+ <h2 className="text-xl font-semibold mt-2.5 mb-1.5 text-zinc-800 dark:text-zinc-200 black:text-zinc-200" {...props}>
90
  {children}
91
  </h2>
92
  ),
93
  h3: ({ node, children, ...props }) => (
94
+ <h3 className="text-lg font-semibold mt-2 mb-1 text-zinc-800 dark:text-zinc-200 black:text-zinc-200" {...props}>
95
  {children}
96
  </h3>
97
  ),
98
  h4: ({ node, children, ...props }) => (
99
+ <h4 className="text-base font-semibold mt-2 mb-1 text-zinc-800 dark:text-zinc-200 black:text-zinc-200" {...props}>
100
  {children}
101
  </h4>
102
  ),
103
  h5: ({ node, children, ...props }) => (
104
+ <h5 className="text-sm font-semibold mt-2 mb-1 text-zinc-800 dark:text-zinc-200 black:text-zinc-200" {...props}>
105
  {children}
106
  </h5>
107
  ),
108
  h6: ({ node, children, ...props }) => (
109
+ <h6 className="text-xs font-semibold mt-2 mb-0.5 text-zinc-800 dark:text-zinc-200 black:text-zinc-200" {...props}>
110
  {children}
111
  </h6>
112
  ),
113
  table: ({ node, children, ...props }) => (
114
+ <div className="my-1.5 overflow-x-auto">
115
+ <table className="min-w-full divide-y divide-zinc-200 dark:divide-zinc-700 black:divide-zinc-700" {...props}>
116
  {children}
117
  </table>
118
  </div>
119
  ),
120
  thead: ({ node, children, ...props }) => (
121
+ <thead className="bg-zinc-50 dark:bg-zinc-800/50 black:bg-zinc-800/50" {...props}>
122
  {children}
123
  </thead>
124
  ),
125
  tbody: ({ node, children, ...props }) => (
126
+ <tbody className="divide-y divide-zinc-200 dark:divide-zinc-700 black:divide-zinc-700 bg-white dark:bg-transparent black:bg-transparent" {...props}>
127
  {children}
128
  </tbody>
129
  ),
130
  tr: ({ node, children, ...props }) => (
131
+ <tr className="transition-colors hover:bg-zinc-50 dark:hover:bg-zinc-800/30 black:hover:bg-zinc-800/30" {...props}>
132
  {children}
133
  </tr>
134
  ),
135
  th: ({ node, children, ...props }) => (
136
  <th
137
+ className="px-3 py-1.5 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 black:text-zinc-400 uppercase tracking-wider"
138
  {...props}
139
  >
140
  {children}
141
  </th>
142
  ),
143
  td: ({ node, children, ...props }) => (
144
+ <td className="px-3 py-1.5 text-sm" {...props}>
145
  {children}
146
  </td>
147
  ),
148
  hr: ({ node, ...props }) => (
149
+ <hr className="my-1.5 border-zinc-200 dark:border-zinc-700 black:border-zinc-700" {...props} />
150
  ),
151
  };
152
 
components/message.tsx CHANGED
@@ -6,7 +6,7 @@ import { memo, useCallback, useEffect, useState } from "react";
6
  import equal from "fast-deep-equal";
7
  import { Markdown } from "./markdown";
8
  import { cn } from "@/lib/utils";
9
- import { ChevronDownIcon, ChevronUpIcon, LightbulbIcon } from "lucide-react";
10
  import { SpinnerIcon } from "./icons";
11
  import { ToolInvocation } from "./tool-invocation";
12
  import { CopyButton } from "./copy-button";
@@ -37,51 +37,82 @@ export function ReasoningMessagePart({
37
  }, [isReasoning, memoizedSetIsExpanded]);
38
 
39
  return (
40
- <div className="flex flex-col py-1">
41
  {isReasoning ? (
42
- <div className="flex items-center gap-2 text-zinc-500 dark:text-zinc-400">
43
- <div className="animate-spin">
 
 
 
 
44
  <SpinnerIcon />
45
  </div>
46
- <div className="text-sm">Thinking...</div>
47
  </div>
48
  ) : (
49
- <div className="flex items-center gap-2 group">
50
- <div className="flex items-center gap-2">
51
- <LightbulbIcon className="h-4 w-4 text-zinc-400" />
52
- <div className="text-sm text-zinc-600 dark:text-zinc-300">Reasoning</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  </div>
54
- <button
55
- className={cn(
56
- "cursor-pointer rounded-md p-1 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors opacity-0 group-hover:opacity-100",
57
- {
58
- "opacity-100 bg-zinc-100 dark:bg-zinc-800": isExpanded,
59
- },
60
- )}
61
- onClick={() => setIsExpanded(!isExpanded)}
62
- >
63
  {isExpanded ? (
64
- <ChevronDownIcon className="h-3.5 w-3.5" />
65
  ) : (
66
- <ChevronUpIcon className="h-3.5 w-3.5" />
67
  )}
68
- </button>
69
- </div>
70
  )}
71
 
72
  <AnimatePresence initial={false}>
73
  {isExpanded && (
74
  <motion.div
75
  key="reasoning"
76
- className="text-sm text-zinc-600 dark:text-zinc-400 flex flex-col gap-3 border-l-2 pl-4 mt-2 border-zinc-200 dark:border-zinc-700 overflow-hidden"
 
 
 
 
77
  initial={{ height: 0, opacity: 0 }}
78
  animate={{ height: "auto", opacity: 1 }}
79
  exit={{ height: 0, opacity: 0 }}
80
  transition={{ duration: 0.2, ease: "easeInOut" }}
81
  >
 
 
 
82
  {part.details.map((detail, detailIndex) =>
83
  detail.type === "text" ? (
84
- <Markdown key={detailIndex}>{detail.text}</Markdown>
 
 
85
  ) : (
86
  "<redacted>"
87
  ),
 
6
  import equal from "fast-deep-equal";
7
  import { Markdown } from "./markdown";
8
  import { cn } from "@/lib/utils";
9
+ import { ChevronDownIcon, ChevronUpIcon, LightbulbIcon, BrainIcon } from "lucide-react";
10
  import { SpinnerIcon } from "./icons";
11
  import { ToolInvocation } from "./tool-invocation";
12
  import { CopyButton } from "./copy-button";
 
37
  }, [isReasoning, memoizedSetIsExpanded]);
38
 
39
  return (
40
+ <div className="flex flex-col mb-2 group">
41
  {isReasoning ? (
42
+ <div className={cn(
43
+ "flex items-center gap-2.5 rounded-full py-1.5 px-3",
44
+ "bg-indigo-50/50 dark:bg-indigo-900/10 text-indigo-700 dark:text-indigo-300",
45
+ "border border-indigo-200/50 dark:border-indigo-700/20 w-fit"
46
+ )}>
47
+ <div className="animate-spin h-3.5 w-3.5">
48
  <SpinnerIcon />
49
  </div>
50
+ <div className="text-xs font-medium tracking-tight">Thinking...</div>
51
  </div>
52
  ) : (
53
+ <button
54
+ onClick={() => setIsExpanded(!isExpanded)}
55
+ className={cn(
56
+ "flex items-center justify-between w-full",
57
+ "rounded-md py-2 px-3 mb-0.5",
58
+ "bg-muted/50 border border-border/60 hover:border-border/80",
59
+ "transition-all duration-150 cursor-pointer",
60
+ isExpanded ? "bg-muted border-primary/20" : ""
61
+ )}
62
+ >
63
+ <div className="flex items-center gap-2.5">
64
+ <div className={cn(
65
+ "flex items-center justify-center w-6 h-6 rounded-full",
66
+ "bg-amber-50 dark:bg-amber-900/20",
67
+ "text-amber-600 dark:text-amber-400 ring-1 ring-amber-200 dark:ring-amber-700/30",
68
+ )}>
69
+ <LightbulbIcon className="h-3.5 w-3.5" />
70
+ </div>
71
+ <div className="text-sm font-medium text-foreground flex items-center gap-1.5">
72
+ Reasoning
73
+ <span className="text-xs text-muted-foreground font-normal">
74
+ (click to {isExpanded ? "hide" : "view"})
75
+ </span>
76
+ </div>
77
  </div>
78
+ <div className={cn(
79
+ "flex items-center justify-center",
80
+ "rounded-full p-0.5 w-5 h-5",
81
+ "text-muted-foreground hover:text-foreground",
82
+ "bg-background/80 border border-border/50",
83
+ "transition-colors",
84
+ )}>
 
 
85
  {isExpanded ? (
86
+ <ChevronDownIcon className="h-3 w-3" />
87
  ) : (
88
+ <ChevronUpIcon className="h-3 w-3" />
89
  )}
90
+ </div>
91
+ </button>
92
  )}
93
 
94
  <AnimatePresence initial={false}>
95
  {isExpanded && (
96
  <motion.div
97
  key="reasoning"
98
+ className={cn(
99
+ "text-sm text-muted-foreground flex flex-col gap-2",
100
+ "pl-3.5 ml-0.5 mt-1",
101
+ "border-l border-amber-200/50 dark:border-amber-700/30"
102
+ )}
103
  initial={{ height: 0, opacity: 0 }}
104
  animate={{ height: "auto", opacity: 1 }}
105
  exit={{ height: 0, opacity: 0 }}
106
  transition={{ duration: 0.2, ease: "easeInOut" }}
107
  >
108
+ <div className="text-xs text-muted-foreground/70 pl-1 font-medium">
109
+ The assistant&apos;s thought process:
110
+ </div>
111
  {part.details.map((detail, detailIndex) =>
112
  detail.type === "text" ? (
113
+ <div key={detailIndex} className="px-2 py-1.5 bg-muted/10 rounded-md border border-border/30">
114
+ <Markdown>{detail.text}</Markdown>
115
+ </div>
116
  ) : (
117
  "<redacted>"
118
  ),
components/messages.tsx CHANGED
@@ -11,11 +11,12 @@ export const Messages = ({
11
  isLoading: boolean;
12
  status: "error" | "submitted" | "streaming" | "ready";
13
  }) => {
14
- // const [containerRef, endRef] = useScrollToBottom();
 
15
  return (
16
  <div
17
  className="h-full overflow-y-auto no-scrollbar"
18
- // ref={containerRef}
19
  >
20
  <div className="max-w-lg sm:max-w-3xl mx-auto py-4">
21
  {messages.map((m, i) => (
@@ -27,7 +28,7 @@ export const Messages = ({
27
  status={status}
28
  />
29
  ))}
30
- {/* <div className="h-1" ref={endRef} /> */}
31
  </div>
32
  </div>
33
  );
 
11
  isLoading: boolean;
12
  status: "error" | "submitted" | "streaming" | "ready";
13
  }) => {
14
+ const [containerRef, endRef] = useScrollToBottom();
15
+
16
  return (
17
  <div
18
  className="h-full overflow-y-auto no-scrollbar"
19
+ ref={containerRef}
20
  >
21
  <div className="max-w-lg sm:max-w-3xl mx-auto py-4">
22
  {messages.map((m, i) => (
 
28
  status={status}
29
  />
30
  ))}
31
+ <div className="h-1" ref={endRef} />
32
  </div>
33
  </div>
34
  );
components/model-picker.tsx CHANGED
@@ -34,7 +34,7 @@ export const ModelPicker = ({ selectedModel, setSelectedModel }: ModelPickerProp
34
  // Function to get the appropriate icon for each provider
35
  const getProviderIcon = (provider: string) => {
36
  switch (provider.toLowerCase()) {
37
- case 'xai':
38
  return <Sparkles className="h-3 w-3 text-yellow-500" />;
39
  case 'openai':
40
  return <Zap className="h-3 w-3 text-green-500" />;
@@ -122,25 +122,25 @@ export const ModelPicker = ({ selectedModel, setSelectedModel }: ModelPickerProp
122
  defaultValue={validModelId}
123
  >
124
  <SelectTrigger
125
- className="max-w-[150px] sm:max-w-none sm:w-48 px-2 sm:px-3 h-8 sm:h-9 rounded-full group border-border/80 bg-background/80 backdrop-blur-sm hover:bg-muted/30 transition-all duration-200"
126
  >
127
  <SelectValue
128
  placeholder="Select model"
129
- className="text-xs font-medium flex items-center gap-1 sm:gap-2"
130
  >
131
  <div className="flex items-center gap-1 sm:gap-2">
132
  {getProviderIcon(modelDetails[validModelId].provider)}
133
- <TextMorph>{modelDetails[validModelId].name}</TextMorph>
134
  </div>
135
  </SelectValue>
136
  </SelectTrigger>
137
  <SelectContent
138
  align="start"
139
- className="bg-background/95 dark:bg-muted/95 backdrop-blur-sm border-border/80 rounded-lg overflow-hidden p-0 w-[280px] sm:w-[350px] md:w-[485px]"
140
  >
141
- <div className="grid grid-cols-1 sm:grid-cols-[120px_1fr] md:grid-cols-[170px_1fr] items-start">
142
  {/* Model selector column */}
143
- <div className="sm:border-r border-border/40 bg-muted/20 p-2">
144
  <SelectGroup className="space-y-1">
145
  {MODELS.map((id) => {
146
  const modelId = id as modelID;
 
34
  // Function to get the appropriate icon for each provider
35
  const getProviderIcon = (provider: string) => {
36
  switch (provider.toLowerCase()) {
37
+ case 'anthropic':
38
  return <Sparkles className="h-3 w-3 text-yellow-500" />;
39
  case 'openai':
40
  return <Zap className="h-3 w-3 text-green-500" />;
 
122
  defaultValue={validModelId}
123
  >
124
  <SelectTrigger
125
+ className="max-w-[200px] sm:max-w-fit sm:w-56 px-2 sm:px-3 h-8 sm:h-9 rounded-full group border-primary/20 bg-primary/5 hover:bg-primary/10 dark:bg-primary/10 dark:hover:bg-primary/20 transition-all duration-200 ring-offset-background focus:ring-2 focus:ring-primary/30 focus:ring-offset-2"
126
  >
127
  <SelectValue
128
  placeholder="Select model"
129
+ className="text-xs font-medium flex items-center gap-1 sm:gap-2 text-primary dark:text-primary-foreground"
130
  >
131
  <div className="flex items-center gap-1 sm:gap-2">
132
  {getProviderIcon(modelDetails[validModelId].provider)}
133
+ <span className="font-medium truncate">{modelDetails[validModelId].name}</span>
134
  </div>
135
  </SelectValue>
136
  </SelectTrigger>
137
  <SelectContent
138
  align="start"
139
+ className="bg-background/95 dark:bg-muted/95 backdrop-blur-sm border-border/80 rounded-lg overflow-hidden p-0 w-[280px] sm:w-[350px] md:w-[515px]"
140
  >
141
+ <div className="grid grid-cols-1 sm:grid-cols-[120px_1fr] md:grid-cols-[200px_1fr] items-start">
142
  {/* Model selector column */}
143
+ <div className="sm:border-r border-border/40 bg-muted/20 p-0 pr-1">
144
  <SelectGroup className="space-y-1">
145
  {MODELS.map((id) => {
146
  const modelId = id as modelID;
components/theme-toggle.tsx CHANGED
@@ -8,7 +8,7 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
8
  import { cn } from "@/lib/utils"
9
 
10
  export function ThemeToggle({ className, ...props }: React.ComponentProps<typeof Button>) {
11
- const { theme, setTheme } = useTheme()
12
 
13
  return (
14
  <DropdownMenu>
@@ -28,7 +28,7 @@ export function ThemeToggle({ className, ...props }: React.ComponentProps<typeof
28
  <DropdownMenuContent align="end">
29
  <DropdownMenuItem onSelect={() => setTheme("dark")}>
30
  <Flame className="mr-2 h-4 w-4" />
31
- <span>Sunset</span>
32
  </DropdownMenuItem>
33
  <DropdownMenuItem onSelect={() => setTheme("light")}>
34
  <Sun className="mr-2 h-4 w-4" />
@@ -36,7 +36,12 @@ export function ThemeToggle({ className, ...props }: React.ComponentProps<typeof
36
  </DropdownMenuItem>
37
  <DropdownMenuItem onSelect={() => setTheme("black")}>
38
  <CircleDashed className="mr-2 h-4 w-4" />
39
- <span>Dark</span>
 
 
 
 
 
40
  </DropdownMenuItem>
41
  </DropdownMenuContent>
42
  </DropdownMenu>
 
8
  import { cn } from "@/lib/utils"
9
 
10
  export function ThemeToggle({ className, ...props }: React.ComponentProps<typeof Button>) {
11
+ const { setTheme } = useTheme()
12
 
13
  return (
14
  <DropdownMenu>
 
28
  <DropdownMenuContent align="end">
29
  <DropdownMenuItem onSelect={() => setTheme("dark")}>
30
  <Flame className="mr-2 h-4 w-4" />
31
+ <span>Dark</span>
32
  </DropdownMenuItem>
33
  <DropdownMenuItem onSelect={() => setTheme("light")}>
34
  <Sun className="mr-2 h-4 w-4" />
 
36
  </DropdownMenuItem>
37
  <DropdownMenuItem onSelect={() => setTheme("black")}>
38
  <CircleDashed className="mr-2 h-4 w-4" />
39
+ <span>Black</span>
40
+ </DropdownMenuItem>
41
+ {/* sunset theme */}
42
+ <DropdownMenuItem onSelect={() => setTheme("sunset")}>
43
+ <Sun className="mr-2 h-4 w-4" />
44
+ <span>Sunset</span>
45
  </DropdownMenuItem>
46
  </DropdownMenuContent>
47
  </DropdownMenu>
components/tool-invocation.tsx CHANGED
@@ -12,6 +12,7 @@ import {
12
  ArrowRight,
13
  Circle,
14
  } from "lucide-react";
 
15
 
16
  interface ToolInvocationProps {
17
  toolName: string;
@@ -46,11 +47,21 @@ export function ToolInvocation({
46
  const getStatusIcon = () => {
47
  if (state === "call") {
48
  if (isLatestMessage && status !== "ready") {
49
- return <Loader2 className="animate-spin h-3.5 w-3.5 text-muted-foreground/70" />;
50
  }
51
- return <Circle className="h-3.5 w-3.5 fill-destructive/20 text-destructive/70" />;
52
  }
53
- return <CheckCircle2 size={14} className="text-success/90" />;
 
 
 
 
 
 
 
 
 
 
54
  };
55
 
56
  const formatContent = (content: any): string => {
@@ -70,26 +81,37 @@ export function ToolInvocation({
70
  };
71
 
72
  return (
73
- <div className="flex flex-col mb-3 bg-muted/30 rounded-lg border border-border/40 overflow-hidden">
 
 
 
 
74
  <div
75
- className="flex items-center gap-2 px-3 py-2 cursor-pointer hover:bg-muted/50 transition-colors"
 
 
 
76
  onClick={() => setIsExpanded(!isExpanded)}
77
  >
78
- <div className="flex items-center justify-center w-5 h-5">
79
- <TerminalSquare className="h-3.5 w-3.5 text-primary/70" />
80
  </div>
81
  <div className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground flex-1">
82
- <span className="text-foreground/80">{toolName}</span>
83
  <ArrowRight className="h-3 w-3 text-muted-foreground/50" />
84
- <span className="text-foreground/60">{state === "call" ? "Running" : "Completed"}</span>
 
 
85
  </div>
86
- <div className="flex items-center gap-2">
87
  {getStatusIcon()}
88
- {isExpanded ? (
89
- <ChevronUpIcon className="h-3.5 w-3.5 text-muted-foreground/70" />
90
- ) : (
91
- <ChevronDownIcon className="h-3.5 w-3.5 text-muted-foreground/70" />
92
- )}
 
 
93
  </div>
94
  </div>
95
 
@@ -101,27 +123,33 @@ export function ToolInvocation({
101
  exit="collapsed"
102
  variants={variants}
103
  transition={{ duration: 0.2 }}
104
- className="space-y-2 p-3 pt-1 border-t border-border/30"
105
  >
106
  {!!args && (
107
- <div className="space-y-1">
108
- <div className="flex items-center gap-1.5 text-xs text-muted-foreground/70">
109
  <Code className="h-3 w-3" />
110
- <span>Arguments</span>
111
  </div>
112
- <pre className="text-xs font-mono bg-background/50 p-3 rounded-md overflow-x-auto">
 
 
 
113
  {formatContent(args)}
114
  </pre>
115
  </div>
116
  )}
117
 
118
  {!!result && (
119
- <div className="space-y-1">
120
  <div className="flex items-center gap-1.5 text-xs text-muted-foreground/70">
121
  <ArrowRight className="h-3 w-3" />
122
- <span>Result</span>
123
  </div>
124
- <pre className="text-xs font-mono bg-background/50 p-3 rounded-md overflow-x-auto max-h-[300px] overflow-y-auto">
 
 
 
125
  {formatContent(result)}
126
  </pre>
127
  </div>
 
12
  ArrowRight,
13
  Circle,
14
  } from "lucide-react";
15
+ import { cn } from "@/lib/utils";
16
 
17
  interface ToolInvocationProps {
18
  toolName: string;
 
47
  const getStatusIcon = () => {
48
  if (state === "call") {
49
  if (isLatestMessage && status !== "ready") {
50
+ return <Loader2 className="animate-spin h-3.5 w-3.5 text-primary/70" />;
51
  }
52
+ return <Circle className="h-3.5 w-3.5 fill-muted-foreground/10 text-muted-foreground/70" />;
53
  }
54
+ return <CheckCircle2 size={14} className="text-primary/90" />;
55
+ };
56
+
57
+ const getStatusClass = () => {
58
+ if (state === "call") {
59
+ if (isLatestMessage && status !== "ready") {
60
+ return "text-primary";
61
+ }
62
+ return "text-muted-foreground";
63
+ }
64
+ return "text-primary";
65
  };
66
 
67
  const formatContent = (content: any): string => {
 
81
  };
82
 
83
  return (
84
+ <div className={cn(
85
+ "flex flex-col mb-2 rounded-md border border-border/50 overflow-hidden",
86
+ "bg-gradient-to-b from-background to-muted/30 backdrop-blur-sm",
87
+ "transition-all duration-200 hover:border-border/80 group"
88
+ )}>
89
  <div
90
+ className={cn(
91
+ "flex items-center gap-2.5 px-3 py-2 cursor-pointer transition-colors",
92
+ "hover:bg-muted/20"
93
+ )}
94
  onClick={() => setIsExpanded(!isExpanded)}
95
  >
96
+ <div className="flex items-center justify-center rounded-full w-5 h-5 bg-primary/5 text-primary">
97
+ <TerminalSquare className="h-3.5 w-3.5" />
98
  </div>
99
  <div className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground flex-1">
100
+ <span className="text-foreground font-semibold tracking-tight">{toolName}</span>
101
  <ArrowRight className="h-3 w-3 text-muted-foreground/50" />
102
+ <span className={cn("font-medium", getStatusClass())}>
103
+ {state === "call" ? (isLatestMessage && status !== "ready" ? "Running" : "Waiting") : "Completed"}
104
+ </span>
105
  </div>
106
+ <div className="flex items-center gap-2 opacity-70 group-hover:opacity-100 transition-opacity">
107
  {getStatusIcon()}
108
+ <div className="bg-muted/30 rounded-full p-0.5 border border-border/30">
109
+ {isExpanded ? (
110
+ <ChevronUpIcon className="h-3 w-3 text-foreground/70" />
111
+ ) : (
112
+ <ChevronDownIcon className="h-3 w-3 text-foreground/70" />
113
+ )}
114
+ </div>
115
  </div>
116
  </div>
117
 
 
123
  exit="collapsed"
124
  variants={variants}
125
  transition={{ duration: 0.2 }}
126
+ className="space-y-2 px-3 pb-3"
127
  >
128
  {!!args && (
129
+ <div className="space-y-1.5">
130
+ <div className="flex items-center gap-1.5 text-xs text-muted-foreground/70 pt-1.5">
131
  <Code className="h-3 w-3" />
132
+ <span className="font-medium">Arguments</span>
133
  </div>
134
+ <pre className={cn(
135
+ "text-xs font-mono p-2.5 rounded-md overflow-x-auto",
136
+ "border border-border/40 bg-muted/10"
137
+ )}>
138
  {formatContent(args)}
139
  </pre>
140
  </div>
141
  )}
142
 
143
  {!!result && (
144
+ <div className="space-y-1.5">
145
  <div className="flex items-center gap-1.5 text-xs text-muted-foreground/70">
146
  <ArrowRight className="h-3 w-3" />
147
+ <span className="font-medium">Result</span>
148
  </div>
149
+ <pre className={cn(
150
+ "text-xs font-mono p-2.5 rounded-md overflow-x-auto max-h-[300px] overflow-y-auto",
151
+ "border border-border/40 bg-muted/10"
152
+ )}>
153
  {formatContent(result)}
154
  </pre>
155
  </div>
instrumentation.ts DELETED
@@ -1,13 +0,0 @@
1
- import { registerOTel } from '@vercel/otel'
2
-
3
- export function register() {
4
- registerOTel({
5
- serviceName: 'next-app',
6
- instrumentations: [
7
- {
8
- name: 'ai-instrumentation',
9
- include: [/^\/api\/ai/]
10
- }
11
- ]
12
- })
13
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/hooks/use-scroll-to-bottom.tsx CHANGED
@@ -6,27 +6,73 @@ export function useScrollToBottom(): [
6
  ] {
7
  const containerRef = useRef<HTMLDivElement>(null);
8
  const endRef = useRef<HTMLDivElement>(null);
 
9
 
10
  useEffect(() => {
11
  const container = containerRef.current;
12
  const end = endRef.current;
13
 
14
- if (container && end) {
15
- const observer = new MutationObserver(() => {
16
- end.scrollIntoView({ behavior: 'instant', block: 'end' });
17
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
- observer.observe(container, {
20
- childList: true,
21
- subtree: true,
22
- attributes: true,
23
- characterData: true,
 
 
 
 
 
 
 
 
 
24
  });
25
 
26
- return () => observer.disconnect();
27
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  }, []);
29
 
30
- // @ts-expect-error error
31
- return [containerRef, endRef];
32
  }
 
6
  ] {
7
  const containerRef = useRef<HTMLDivElement>(null);
8
  const endRef = useRef<HTMLDivElement>(null);
9
+ const isUserScrollingRef = useRef(false);
10
 
11
  useEffect(() => {
12
  const container = containerRef.current;
13
  const end = endRef.current;
14
 
15
+ if (!container || !end) return;
16
+
17
+ // Initial scroll to bottom
18
+ setTimeout(() => {
19
+ end.scrollIntoView({ behavior: 'instant', block: 'end' });
20
+ }, 100);
21
+
22
+ // Track if user has manually scrolled up
23
+ const handleScroll = () => {
24
+ if (!container) return;
25
+
26
+ const { scrollTop, scrollHeight, clientHeight } = container;
27
+ const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
28
+
29
+ // If user is scrolled up, mark as manually scrolling
30
+ isUserScrollingRef.current = distanceFromBottom > 100;
31
+ };
32
+
33
+ // Handle mutations
34
+ const observer = new MutationObserver((mutations) => {
35
+ if (!container || !end) return;
36
 
37
+ // Check if mutation is related to expand/collapse
38
+ const isToggleSection = mutations.some(mutation => {
39
+ // Check if the target or parent is a motion-div (expanded content)
40
+ let target = mutation.target as HTMLElement;
41
+ let isExpand = false;
42
+
43
+ while (target && target !== container) {
44
+ if (target.classList?.contains('motion-div')) {
45
+ isExpand = true;
46
+ break;
47
+ }
48
+ target = target.parentElement as HTMLElement;
49
+ }
50
+ return isExpand;
51
  });
52
 
53
+ // Don't scroll for expand/collapse actions
54
+ if (isToggleSection) return;
55
+
56
+ // Only auto-scroll if user hasn't manually scrolled up
57
+ if (!isUserScrollingRef.current) {
58
+ // For new messages, use smooth scrolling
59
+ end.scrollIntoView({ behavior: 'smooth', block: 'end' });
60
+ }
61
+ });
62
+
63
+ observer.observe(container, {
64
+ childList: true,
65
+ subtree: true,
66
+ });
67
+
68
+ // Add scroll event listener
69
+ container.addEventListener('scroll', handleScroll);
70
+
71
+ return () => {
72
+ observer.disconnect();
73
+ container.removeEventListener('scroll', handleScroll);
74
+ };
75
  }, []);
76
 
77
+ return [containerRef, endRef] as [RefObject<HTMLDivElement>, RefObject<HTMLDivElement>];
 
78
  }
package-lock.json DELETED
The diff for this file is too large to render. See raw diff
 
package.json CHANGED
@@ -13,18 +13,13 @@
13
  "db:studio": "drizzle-kit studio"
14
  },
15
  "dependencies": {
 
16
  "@ai-sdk/cohere": "^1.2.9",
17
  "@ai-sdk/google": "^1.2.12",
18
  "@ai-sdk/groq": "^1.2.8",
19
  "@ai-sdk/openai": "^1.3.16",
20
  "@ai-sdk/react": "^1.2.9",
21
- "@ai-sdk/xai": "^1.2.13",
22
- "@auth/core": "^0.38.0",
23
- "@auth/drizzle-adapter": "^1.8.0",
24
  "@neondatabase/serverless": "^1.0.0",
25
- "@opentelemetry/api-logs": "^0.200.0",
26
- "@opentelemetry/instrumentation": "^0.200.0",
27
- "@opentelemetry/sdk-logs": "^0.200.0",
28
  "@radix-ui/react-accordion": "^1.2.7",
29
  "@radix-ui/react-avatar": "^1.1.6",
30
  "@radix-ui/react-dialog": "^1.1.10",
@@ -39,13 +34,11 @@
39
  "@tanstack/react-query": "^5.74.4",
40
  "@vercel/otel": "^1.11.0",
41
  "ai": "^4.3.9",
42
- "autoevals": "^0.0.127",
43
- "braintrust": "^0.0.198",
44
  "class-variance-authority": "^0.7.1",
45
  "clsx": "^2.1.1",
46
- "composio-core": "^0.5.31",
47
  "drizzle-orm": "^0.42.0",
48
  "fast-deep-equal": "^3.1.3",
 
49
  "groq-sdk": "^0.19.0",
50
  "lucide-react": "^0.488.0",
51
  "motion": "^12.7.3",
 
13
  "db:studio": "drizzle-kit studio"
14
  },
15
  "dependencies": {
16
+ "@ai-sdk/anthropic": "^1.2.10",
17
  "@ai-sdk/cohere": "^1.2.9",
18
  "@ai-sdk/google": "^1.2.12",
19
  "@ai-sdk/groq": "^1.2.8",
20
  "@ai-sdk/openai": "^1.3.16",
21
  "@ai-sdk/react": "^1.2.9",
 
 
 
22
  "@neondatabase/serverless": "^1.0.0",
 
 
 
23
  "@radix-ui/react-accordion": "^1.2.7",
24
  "@radix-ui/react-avatar": "^1.1.6",
25
  "@radix-ui/react-dialog": "^1.1.10",
 
34
  "@tanstack/react-query": "^5.74.4",
35
  "@vercel/otel": "^1.11.0",
36
  "ai": "^4.3.9",
 
 
37
  "class-variance-authority": "^0.7.1",
38
  "clsx": "^2.1.1",
 
39
  "drizzle-orm": "^0.42.0",
40
  "fast-deep-equal": "^3.1.3",
41
+ "framer-motion": "^12.7.4",
42
  "groq-sdk": "^0.19.0",
43
  "lucide-react": "^0.488.0",
44
  "motion": "^12.7.3",
pnpm-lock.yaml CHANGED
The diff for this file is too large to render. See raw diff