Spaces:
Running
Running
Commit
·
264f96c
1
Parent(s):
0d06a18
feat: enhance ChatSidebar with user ID editing functionality
Browse files- Added a dialog for editing the user ID, allowing users to update their ID for chat synchronization.
- Implemented validation to ensure the user ID is not empty before updating.
- Introduced a new Pencil icon in the dropdown menu for accessing the edit user ID feature.
- Updated the Messages component layout for improved responsiveness.
- Refactored ToolInvocation component for better icon usage and layout adjustments.
- components/chat-sidebar.tsx +75 -2
- components/messages.tsx +4 -4
- components/model-picker.tsx +35 -15
- components/tool-invocation.tsx +33 -38
- lib/user-id.ts +5 -0
components/chat-sidebar.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 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,
|
|
@@ -24,7 +24,7 @@ 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";
|
|
@@ -40,6 +40,16 @@ import {
|
|
| 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();
|
|
@@ -50,6 +60,8 @@ export function ChatSidebar() {
|
|
| 50 |
const [mcpSettingsOpen, setMcpSettingsOpen] = useState(false);
|
| 51 |
const { state } = useSidebar();
|
| 52 |
const isCollapsed = state === "collapsed";
|
|
|
|
|
|
|
| 53 |
|
| 54 |
// Initialize userId
|
| 55 |
useEffect(() => {
|
|
@@ -80,6 +92,22 @@ export function ChatSidebar() {
|
|
| 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) {
|
| 85 |
return null; // Or a loading spinner
|
|
@@ -307,6 +335,13 @@ export function ChatSidebar() {
|
|
| 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>
|
|
@@ -340,6 +375,44 @@ export function ChatSidebar() {
|
|
| 340 |
onOpenChange={setMcpSettingsOpen}
|
| 341 |
/>
|
| 342 |
</SidebarFooter>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
</Sidebar>
|
| 344 |
);
|
| 345 |
}
|
|
|
|
| 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 } from "lucide-react";
|
| 6 |
import {
|
| 7 |
Sidebar,
|
| 8 |
SidebarContent,
|
|
|
|
| 24 |
import Image from "next/image";
|
| 25 |
import { MCPServerManager, type MCPServer } 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";
|
|
|
|
| 40 |
DropdownMenuTrigger,
|
| 41 |
} from "@/components/ui/dropdown-menu";
|
| 42 |
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
| 43 |
+
import {
|
| 44 |
+
Dialog,
|
| 45 |
+
DialogContent,
|
| 46 |
+
DialogDescription,
|
| 47 |
+
DialogFooter,
|
| 48 |
+
DialogHeader,
|
| 49 |
+
DialogTitle,
|
| 50 |
+
} from "@/components/ui/dialog";
|
| 51 |
+
import { Input } from "@/components/ui/input";
|
| 52 |
+
import { Label } from "@/components/ui/label";
|
| 53 |
|
| 54 |
export function ChatSidebar() {
|
| 55 |
const router = useRouter();
|
|
|
|
| 60 |
const [mcpSettingsOpen, setMcpSettingsOpen] = useState(false);
|
| 61 |
const { state } = useSidebar();
|
| 62 |
const isCollapsed = state === "collapsed";
|
| 63 |
+
const [editUserIdOpen, setEditUserIdOpen] = useState(false);
|
| 64 |
+
const [newUserId, setNewUserId] = useState('');
|
| 65 |
|
| 66 |
// Initialize userId
|
| 67 |
useEffect(() => {
|
|
|
|
| 92 |
// Get active MCP servers status
|
| 93 |
const activeServersCount = selectedMcpServers.length;
|
| 94 |
|
| 95 |
+
// Handle user ID update
|
| 96 |
+
const handleUpdateUserId = () => {
|
| 97 |
+
if (!newUserId.trim()) {
|
| 98 |
+
toast.error("User ID cannot be empty");
|
| 99 |
+
return;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
updateUserId(newUserId.trim());
|
| 103 |
+
setUserId(newUserId.trim());
|
| 104 |
+
setEditUserIdOpen(false);
|
| 105 |
+
toast.success("User ID updated successfully");
|
| 106 |
+
|
| 107 |
+
// Refresh the page to reload chats with new user ID
|
| 108 |
+
window.location.reload();
|
| 109 |
+
};
|
| 110 |
+
|
| 111 |
// Show loading state if user ID is not yet initialized
|
| 112 |
if (!userId) {
|
| 113 |
return null; // Or a loading spinner
|
|
|
|
| 335 |
<Copy className="mr-2 h-4 w-4 hover:text-sidebar-accent" />
|
| 336 |
Copy User ID
|
| 337 |
</DropdownMenuItem>
|
| 338 |
+
<DropdownMenuItem onSelect={(e) => {
|
| 339 |
+
e.preventDefault();
|
| 340 |
+
setEditUserIdOpen(true);
|
| 341 |
+
}}>
|
| 342 |
+
<Pencil className="mr-2 h-4 w-4 hover:text-sidebar-accent" />
|
| 343 |
+
Edit User ID
|
| 344 |
+
</DropdownMenuItem>
|
| 345 |
</DropdownMenuGroup>
|
| 346 |
<DropdownMenuSeparator />
|
| 347 |
<DropdownMenuGroup>
|
|
|
|
| 375 |
onOpenChange={setMcpSettingsOpen}
|
| 376 |
/>
|
| 377 |
</SidebarFooter>
|
| 378 |
+
|
| 379 |
+
<Dialog open={editUserIdOpen} onOpenChange={(open) => {
|
| 380 |
+
setEditUserIdOpen(open);
|
| 381 |
+
if (open) {
|
| 382 |
+
setNewUserId(userId);
|
| 383 |
+
}
|
| 384 |
+
}}>
|
| 385 |
+
<DialogContent className="sm:max-w-[400px]">
|
| 386 |
+
<DialogHeader>
|
| 387 |
+
<DialogTitle>Edit User ID</DialogTitle>
|
| 388 |
+
<DialogDescription>
|
| 389 |
+
Update your user ID for chat synchronization. This will affect which chats are visible to you.
|
| 390 |
+
</DialogDescription>
|
| 391 |
+
</DialogHeader>
|
| 392 |
+
<div className="grid gap-4 py-4">
|
| 393 |
+
<div className="grid gap-2">
|
| 394 |
+
<Label htmlFor="userId">User ID</Label>
|
| 395 |
+
<Input
|
| 396 |
+
id="userId"
|
| 397 |
+
value={newUserId}
|
| 398 |
+
onChange={(e) => setNewUserId(e.target.value)}
|
| 399 |
+
placeholder="Enter your user ID"
|
| 400 |
+
/>
|
| 401 |
+
</div>
|
| 402 |
+
</div>
|
| 403 |
+
<DialogFooter>
|
| 404 |
+
<Button
|
| 405 |
+
variant="outline"
|
| 406 |
+
onClick={() => setEditUserIdOpen(false)}
|
| 407 |
+
>
|
| 408 |
+
Cancel
|
| 409 |
+
</Button>
|
| 410 |
+
<Button onClick={handleUpdateUserId}>
|
| 411 |
+
Save Changes
|
| 412 |
+
</Button>
|
| 413 |
+
</DialogFooter>
|
| 414 |
+
</DialogContent>
|
| 415 |
+
</Dialog>
|
| 416 |
</Sidebar>
|
| 417 |
);
|
| 418 |
}
|
components/messages.tsx
CHANGED
|
@@ -11,13 +11,13 @@ 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-
|
| 21 |
{messages.map((m, i) => (
|
| 22 |
<Message
|
| 23 |
key={i}
|
|
@@ -27,7 +27,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 |
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) => (
|
| 22 |
<Message
|
| 23 |
key={i}
|
|
|
|
| 27 |
status={status}
|
| 28 |
/>
|
| 29 |
))}
|
| 30 |
+
{/* <div className="h-1" ref={endRef} /> */}
|
| 31 |
</div>
|
| 32 |
</div>
|
| 33 |
);
|
components/model-picker.tsx
CHANGED
|
@@ -109,20 +109,20 @@ export const ModelPicker = ({ selectedModel, setSelectedModel }: ModelPickerProp
|
|
| 109 |
};
|
| 110 |
|
| 111 |
return (
|
| 112 |
-
<div className="absolute bottom-2 left-2">
|
| 113 |
<Select
|
| 114 |
value={validModelId}
|
| 115 |
onValueChange={handleModelChange}
|
| 116 |
defaultValue={validModelId}
|
| 117 |
>
|
| 118 |
<SelectTrigger
|
| 119 |
-
className="w-48 px-3 h-9 rounded-full group border-border/80 bg-background/80 backdrop-blur-sm hover:bg-muted/30 transition-all duration-200"
|
| 120 |
>
|
| 121 |
<SelectValue
|
| 122 |
placeholder="Select model"
|
| 123 |
-
className="text-xs font-medium flex items-center gap-2"
|
| 124 |
>
|
| 125 |
-
<div className="flex items-center gap-2">
|
| 126 |
{getProviderIcon(modelDetails[validModelId].provider)}
|
| 127 |
<TextMorph>{modelDetails[validModelId].name}</TextMorph>
|
| 128 |
</div>
|
|
@@ -130,12 +130,11 @@ export const ModelPicker = ({ selectedModel, setSelectedModel }: ModelPickerProp
|
|
| 130 |
</SelectTrigger>
|
| 131 |
<SelectContent
|
| 132 |
align="start"
|
| 133 |
-
className="bg-background/95 dark:bg-muted/95 backdrop-blur-sm border-border/80 rounded-lg overflow-hidden p-0"
|
| 134 |
-
style={{ width: "485px" }}
|
| 135 |
>
|
| 136 |
-
<div className="grid grid-cols-[170px_1fr] items-start
|
| 137 |
{/* Model selector column */}
|
| 138 |
-
<div className="border-r border-border/40 bg-muted/20 p-2">
|
| 139 |
<SelectGroup className="space-y-1">
|
| 140 |
{MODELS.map((id) => {
|
| 141 |
const modelId = id as modelID;
|
|
@@ -146,7 +145,7 @@ export const ModelPicker = ({ selectedModel, setSelectedModel }: ModelPickerProp
|
|
| 146 |
onMouseEnter={() => setHoveredModel(modelId)}
|
| 147 |
onMouseLeave={() => setHoveredModel(null)}
|
| 148 |
className={cn(
|
| 149 |
-
"!px-3 py-2 cursor-pointer rounded-md text-xs transition-colors duration-150",
|
| 150 |
"hover:bg-primary/5 hover:text-primary-foreground",
|
| 151 |
"focus:bg-primary/10 focus:text-primary focus:outline-none",
|
| 152 |
"data-[highlighted]:bg-primary/10 data-[highlighted]:text-primary",
|
|
@@ -154,11 +153,11 @@ export const ModelPicker = ({ selectedModel, setSelectedModel }: ModelPickerProp
|
|
| 154 |
)}
|
| 155 |
>
|
| 156 |
<div className="flex flex-col gap-0.5">
|
| 157 |
-
<div className="flex items-center gap-
|
| 158 |
{getProviderIcon(modelDetails[modelId].provider)}
|
| 159 |
<span className="font-medium truncate">{modelDetails[modelId].name}</span>
|
| 160 |
</div>
|
| 161 |
-
<span className="text-xs text-muted-foreground">
|
| 162 |
{modelDetails[modelId].provider}
|
| 163 |
</span>
|
| 164 |
</div>
|
|
@@ -168,8 +167,8 @@ export const ModelPicker = ({ selectedModel, setSelectedModel }: ModelPickerProp
|
|
| 168 |
</SelectGroup>
|
| 169 |
</div>
|
| 170 |
|
| 171 |
-
{/* Model details column */}
|
| 172 |
-
<div className="p-4 flex flex-col">
|
| 173 |
<div>
|
| 174 |
<div className="flex items-center gap-2 mb-1">
|
| 175 |
{getProviderIcon(currentModelDetails.provider)}
|
|
@@ -195,12 +194,12 @@ export const ModelPicker = ({ selectedModel, setSelectedModel }: ModelPickerProp
|
|
| 195 |
))}
|
| 196 |
</div>
|
| 197 |
|
| 198 |
-
<div className="text-xs text-foreground/90 leading-relaxed mb-3">
|
| 199 |
{currentModelDetails.description}
|
| 200 |
</div>
|
| 201 |
</div>
|
| 202 |
|
| 203 |
-
<div className="bg-muted/40 rounded-md p-2">
|
| 204 |
<div className="text-[10px] text-muted-foreground flex justify-between items-center">
|
| 205 |
<span>API Version:</span>
|
| 206 |
<code className="bg-background/80 px-2 py-0.5 rounded text-[10px] font-mono">
|
|
@@ -209,6 +208,27 @@ export const ModelPicker = ({ selectedModel, setSelectedModel }: ModelPickerProp
|
|
| 209 |
</div>
|
| 210 |
</div>
|
| 211 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
</div>
|
| 213 |
</SelectContent>
|
| 214 |
</Select>
|
|
|
|
| 109 |
};
|
| 110 |
|
| 111 |
return (
|
| 112 |
+
<div className="absolute bottom-2 left-2 z-10">
|
| 113 |
<Select
|
| 114 |
value={validModelId}
|
| 115 |
onValueChange={handleModelChange}
|
| 116 |
defaultValue={validModelId}
|
| 117 |
>
|
| 118 |
<SelectTrigger
|
| 119 |
+
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"
|
| 120 |
>
|
| 121 |
<SelectValue
|
| 122 |
placeholder="Select model"
|
| 123 |
+
className="text-xs font-medium flex items-center gap-1 sm:gap-2"
|
| 124 |
>
|
| 125 |
+
<div className="flex items-center gap-1 sm:gap-2">
|
| 126 |
{getProviderIcon(modelDetails[validModelId].provider)}
|
| 127 |
<TextMorph>{modelDetails[validModelId].name}</TextMorph>
|
| 128 |
</div>
|
|
|
|
| 130 |
</SelectTrigger>
|
| 131 |
<SelectContent
|
| 132 |
align="start"
|
| 133 |
+
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]"
|
|
|
|
| 134 |
>
|
| 135 |
+
<div className="grid grid-cols-1 sm:grid-cols-[120px_1fr] md:grid-cols-[170px_1fr] items-start">
|
| 136 |
{/* Model selector column */}
|
| 137 |
+
<div className="sm:border-r border-border/40 bg-muted/20 p-2">
|
| 138 |
<SelectGroup className="space-y-1">
|
| 139 |
{MODELS.map((id) => {
|
| 140 |
const modelId = id as modelID;
|
|
|
|
| 145 |
onMouseEnter={() => setHoveredModel(modelId)}
|
| 146 |
onMouseLeave={() => setHoveredModel(null)}
|
| 147 |
className={cn(
|
| 148 |
+
"!px-2 sm:!px-3 py-1.5 sm:py-2 cursor-pointer rounded-md text-xs transition-colors duration-150",
|
| 149 |
"hover:bg-primary/5 hover:text-primary-foreground",
|
| 150 |
"focus:bg-primary/10 focus:text-primary focus:outline-none",
|
| 151 |
"data-[highlighted]:bg-primary/10 data-[highlighted]:text-primary",
|
|
|
|
| 153 |
)}
|
| 154 |
>
|
| 155 |
<div className="flex flex-col gap-0.5">
|
| 156 |
+
<div className="flex items-center gap-1.5">
|
| 157 |
{getProviderIcon(modelDetails[modelId].provider)}
|
| 158 |
<span className="font-medium truncate">{modelDetails[modelId].name}</span>
|
| 159 |
</div>
|
| 160 |
+
<span className="text-[10px] sm:text-xs text-muted-foreground">
|
| 161 |
{modelDetails[modelId].provider}
|
| 162 |
</span>
|
| 163 |
</div>
|
|
|
|
| 167 |
</SelectGroup>
|
| 168 |
</div>
|
| 169 |
|
| 170 |
+
{/* Model details column - hidden on smallest screens, visible on sm+ */}
|
| 171 |
+
<div className="p-2 sm:p-3 md:p-4 flex flex-col sm:block hidden">
|
| 172 |
<div>
|
| 173 |
<div className="flex items-center gap-2 mb-1">
|
| 174 |
{getProviderIcon(currentModelDetails.provider)}
|
|
|
|
| 194 |
))}
|
| 195 |
</div>
|
| 196 |
|
| 197 |
+
<div className="text-xs text-foreground/90 leading-relaxed mb-3 hidden md:block">
|
| 198 |
{currentModelDetails.description}
|
| 199 |
</div>
|
| 200 |
</div>
|
| 201 |
|
| 202 |
+
<div className="bg-muted/40 rounded-md p-2 hidden md:block">
|
| 203 |
<div className="text-[10px] text-muted-foreground flex justify-between items-center">
|
| 204 |
<span>API Version:</span>
|
| 205 |
<code className="bg-background/80 px-2 py-0.5 rounded text-[10px] font-mono">
|
|
|
|
| 208 |
</div>
|
| 209 |
</div>
|
| 210 |
</div>
|
| 211 |
+
|
| 212 |
+
{/* Condensed model details for mobile only */}
|
| 213 |
+
<div className="p-3 sm:hidden border-t border-border/30">
|
| 214 |
+
<div className="flex flex-wrap gap-1 mb-2">
|
| 215 |
+
{currentModelDetails.capabilities.slice(0, 4).map((capability) => (
|
| 216 |
+
<span
|
| 217 |
+
key={capability}
|
| 218 |
+
className={cn(
|
| 219 |
+
"inline-flex items-center gap-1 text-[9px] px-1.5 py-0.5 rounded-full font-medium",
|
| 220 |
+
getCapabilityColor(capability)
|
| 221 |
+
)}
|
| 222 |
+
>
|
| 223 |
+
{getCapabilityIcon(capability)}
|
| 224 |
+
<span>{capability}</span>
|
| 225 |
+
</span>
|
| 226 |
+
))}
|
| 227 |
+
{currentModelDetails.capabilities.length > 4 && (
|
| 228 |
+
<span className="text-[10px] text-muted-foreground">+{currentModelDetails.capabilities.length - 4} more</span>
|
| 229 |
+
)}
|
| 230 |
+
</div>
|
| 231 |
+
</div>
|
| 232 |
</div>
|
| 233 |
</SelectContent>
|
| 234 |
</Select>
|
components/tool-invocation.tsx
CHANGED
|
@@ -6,11 +6,11 @@ import {
|
|
| 6 |
ChevronDownIcon,
|
| 7 |
ChevronUpIcon,
|
| 8 |
Loader2,
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
} from "lucide-react";
|
| 15 |
|
| 16 |
interface ToolInvocationProps {
|
|
@@ -46,11 +46,11 @@ export function ToolInvocation({
|
|
| 46 |
const getStatusIcon = () => {
|
| 47 |
if (state === "call") {
|
| 48 |
if (isLatestMessage && status !== "ready") {
|
| 49 |
-
return <Loader2 className="animate-spin h-
|
| 50 |
}
|
| 51 |
-
return <
|
| 52 |
}
|
| 53 |
-
return <
|
| 54 |
};
|
| 55 |
|
| 56 |
const formatContent = (content: any): string => {
|
|
@@ -70,31 +70,26 @@ export function ToolInvocation({
|
|
| 70 |
};
|
| 71 |
|
| 72 |
return (
|
| 73 |
-
<div className="flex flex-col
|
| 74 |
-
<div
|
| 75 |
-
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
| 77 |
</div>
|
| 78 |
-
<div className="flex
|
| 79 |
-
<span className="text-
|
| 80 |
-
|
| 81 |
-
</span>
|
| 82 |
-
<code className="px-2 py-1 text-xs font-mono rounded-md bg-muted text-muted-foreground">
|
| 83 |
-
{toolName}
|
| 84 |
-
</code>
|
| 85 |
</div>
|
| 86 |
<div className="flex items-center gap-2">
|
| 87 |
{getStatusIcon()}
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
<ChevronUpIcon className="h-4 w-4" />
|
| 94 |
-
) : (
|
| 95 |
-
<ChevronDownIcon className="h-4 w-4" />
|
| 96 |
-
)}
|
| 97 |
-
</button>
|
| 98 |
</div>
|
| 99 |
</div>
|
| 100 |
|
|
@@ -106,27 +101,27 @@ export function ToolInvocation({
|
|
| 106 |
exit="collapsed"
|
| 107 |
variants={variants}
|
| 108 |
transition={{ duration: 0.2 }}
|
| 109 |
-
className="space-y-3 pt-
|
| 110 |
>
|
| 111 |
{!!args && (
|
| 112 |
-
<div className="space-y-1
|
| 113 |
-
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
| 114 |
-
<
|
| 115 |
<span>Arguments</span>
|
| 116 |
</div>
|
| 117 |
-
<pre className="text-xs font-mono bg-
|
| 118 |
{formatContent(args)}
|
| 119 |
</pre>
|
| 120 |
</div>
|
| 121 |
)}
|
| 122 |
|
| 123 |
{!!result && (
|
| 124 |
-
<div className="space-y-1
|
| 125 |
-
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
| 126 |
-
<
|
| 127 |
<span>Result</span>
|
| 128 |
</div>
|
| 129 |
-
<pre className="text-xs font-mono bg-
|
| 130 |
{formatContent(result)}
|
| 131 |
</pre>
|
| 132 |
</div>
|
|
|
|
| 6 |
ChevronDownIcon,
|
| 7 |
ChevronUpIcon,
|
| 8 |
Loader2,
|
| 9 |
+
CheckCircle2,
|
| 10 |
+
TerminalSquare,
|
| 11 |
+
Code,
|
| 12 |
+
ArrowRight,
|
| 13 |
+
Circle,
|
| 14 |
} from "lucide-react";
|
| 15 |
|
| 16 |
interface ToolInvocationProps {
|
|
|
|
| 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 |
};
|
| 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 |
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>
|
lib/user-id.ts
CHANGED
|
@@ -15,4 +15,9 @@ export function getUserId(): string {
|
|
| 15 |
}
|
| 16 |
|
| 17 |
return userId;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
}
|
|
|
|
| 15 |
}
|
| 16 |
|
| 17 |
return userId;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
export function updateUserId(newUserId: string): void {
|
| 21 |
+
if (typeof window === 'undefined') return;
|
| 22 |
+
localStorage.setItem(USER_ID_KEY, newUserId);
|
| 23 |
}
|