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 |
}
|