Skip to content

Commit 3f20e19

Browse files
committed
refactor(frontend): update chat topbar with badge component and mock chat history for improved functionality
1 parent dfbcd5d commit 3f20e19

File tree

6 files changed

+17708
-14108
lines changed

6 files changed

+17708
-14108
lines changed

frontend/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"@radix-ui/react-scroll-area": "^1.2.0",
3434
"@radix-ui/react-select": "^2.1.1",
3535
"@radix-ui/react-separator": "^1.1.1",
36-
"@radix-ui/react-slot": "^1.1.1",
36+
"@radix-ui/react-slot": "^1.1.2",
3737
"@radix-ui/react-tabs": "^1.1.2",
3838
"@radix-ui/react-tooltip": "^1.1.6",
3939
"@radix-ui/react-visually-hidden": "^1.1.1",

frontend/src/components/chat/chat-bottombar.tsx

+179-31
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,40 @@
11
'use client';
2-
3-
import React, { useEffect, useRef } from 'react';
2+
import React, { useEffect, useRef, useState } from 'react';
43
import { motion, AnimatePresence } from 'framer-motion';
54
import TextareaAutosize from 'react-textarea-autosize';
6-
import { Send, X } from 'lucide-react';
7-
import useChatStore from '@/hooks/useChatStore';
8-
import { Button } from '../ui/button';
5+
import { PaperclipIcon, Send, X } from 'lucide-react';
6+
import { cn } from '@/lib/utils';
97
import { ChatProps } from './chat-panel';
8+
import Image from 'next/image';
9+
import {
10+
Tooltip,
11+
TooltipContent,
12+
TooltipProvider,
13+
TooltipTrigger,
14+
} from '@/components/ui/tooltip';
1015

1116
export default function ChatBottombar({
1217
messages,
1318
input,
1419
handleInputChange,
1520
handleSubmit,
16-
stop,
1721
formRef,
1822
setInput,
1923
}: ChatProps) {
20-
const [isMobile, setIsMobile] = React.useState(false);
24+
const [isMobile, setIsMobile] = useState(false);
25+
const [isFocused, setIsFocused] = useState(false);
26+
const [attachments, setAttachments] = useState<File[]>([]);
2127
const inputRef = useRef<HTMLTextAreaElement>(null);
22-
const base64Images = useChatStore((state) => state.base64Images);
23-
const setBase64Images = useChatStore((state) => state.setBase64Images);
24-
const env = process.env.NODE_ENV;
28+
const fileInputRef = useRef<HTMLInputElement>(null);
2529

2630
useEffect(() => {
2731
const checkScreenWidth = () => {
2832
setIsMobile(window.innerWidth <= 768);
2933
};
30-
3134
// Initial check
3235
checkScreenWidth();
33-
3436
// Event listener for screen width changes
3537
window.addEventListener('resize', checkScreenWidth);
36-
3738
// Cleanup the event listener on component unmount
3839
return () => {
3940
window.removeEventListener('resize', checkScreenWidth);
@@ -43,53 +44,200 @@ export default function ChatBottombar({
4344
const handleKeyPress = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
4445
if (e.key === 'Enter' && !e.shiftKey) {
4546
e.preventDefault();
46-
handleSubmit(e as unknown as React.FormEvent<HTMLFormElement>);
47+
submitWithAttachments(e as unknown as React.FormEvent<HTMLFormElement>);
48+
}
49+
};
50+
51+
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
52+
if (e.target.files && e.target.files.length > 0) {
53+
const fileArray = Array.from(e.target.files);
54+
setAttachments((prev) => [...prev, ...fileArray]);
4755
}
4856
};
4957

58+
const handleFileSelect = () => {
59+
fileInputRef.current?.click();
60+
};
61+
62+
const removeAttachment = (index: number) => {
63+
setAttachments((prev) => prev.filter((_, i) => i !== index));
64+
};
65+
66+
const submitWithAttachments = (e: React.FormEvent<HTMLFormElement>) => {
67+
// Here you would normally handle attachments with your form submission
68+
// For this example, we'll just clear them after submission
69+
handleSubmit(e);
70+
setAttachments([]);
71+
};
72+
5073
useEffect(() => {
5174
if (inputRef.current) {
5275
inputRef.current.focus();
5376
}
5477
}, []);
5578

5679
return (
57-
<div className="px-4 pb-4 pt-2">
80+
<div className="px-4 pb-4 pt-2 bg-white dark:bg-[#151718]">
5881
<motion.div
59-
initial={{ y: 20, opacity: 0 }}
82+
initial={{ y: 10, opacity: 0 }}
6083
animate={{ y: 0, opacity: 1 }}
61-
transition={{ duration: 0.3 }}
62-
className="rounded-xl border bg-background shadow-sm"
84+
transition={{ duration: 0.2 }}
85+
className={cn(
86+
'relative border shadow-sm rounded-lg overflow-hidden',
87+
isFocused
88+
? 'ring-1 ring-blue-500 border-blue-500'
89+
: 'border-gray-200 hover:border-gray-300 dark:border-zinc-700 dark:hover:border-zinc-600',
90+
'bg-white dark:bg-[#1e1e1e]'
91+
)}
6392
>
64-
{/* Image attachments removed */}
93+
{/* Attachments preview */}
94+
<AnimatePresence>
95+
{attachments.length > 0 && (
96+
<motion.div
97+
initial={{ height: 0, opacity: 0 }}
98+
animate={{ height: 'auto', opacity: 1 }}
99+
exit={{ height: 0, opacity: 0 }}
100+
className="flex flex-wrap gap-2 p-2 border-b border-gray-100 dark:border-zinc-800"
101+
>
102+
{attachments.map((file, index) => (
103+
<motion.div
104+
key={`${file.name}-${index}`}
105+
initial={{ scale: 0.8, opacity: 0 }}
106+
animate={{ scale: 1, opacity: 1 }}
107+
exit={{ scale: 0.8, opacity: 0 }}
108+
className="relative group"
109+
>
110+
<div className="w-16 h-16 rounded border overflow-hidden bg-gray-50 dark:bg-zinc-800">
111+
{file.type.startsWith('image/') ? (
112+
<div className="relative w-full h-full">
113+
<Image
114+
src={URL.createObjectURL(file)}
115+
alt={file.name}
116+
fill
117+
className="object-cover"
118+
/>
119+
</div>
120+
) : (
121+
<div className="w-full h-full flex items-center justify-center text-xs text-gray-500 dark:text-zinc-400 p-1 overflow-hidden">
122+
{file.name}
123+
</div>
124+
)}
125+
</div>
126+
<button
127+
type="button"
128+
onClick={() => removeAttachment(index)}
129+
className="absolute -top-1 -right-1 size-5 bg-red-500 rounded-full flex items-center justify-center text-white opacity-0 group-hover:opacity-100 transition-opacity"
130+
>
131+
<X className="size-3" />
132+
</button>
133+
</motion.div>
134+
))}
135+
</motion.div>
136+
)}
137+
</AnimatePresence>
65138

66139
<form
67140
ref={formRef}
68-
onSubmit={handleSubmit}
69-
className="flex items-end w-full"
141+
onSubmit={submitWithAttachments}
142+
className="flex items-center w-full"
70143
>
144+
{/* Hidden file input */}
145+
<input
146+
type="file"
147+
ref={fileInputRef}
148+
onChange={handleFileChange}
149+
className="hidden"
150+
multiple
151+
/>
152+
153+
{/* Left icons with tooltips */}
154+
<div className="flex items-center ml-2">
155+
<TooltipProvider>
156+
<Tooltip>
157+
<TooltipTrigger asChild>
158+
<button
159+
type="button"
160+
className="p-1.5 text-gray-400 dark:text-zinc-400 rounded-md cursor-not-allowed opacity-50"
161+
aria-label="Attach file (not available)"
162+
disabled
163+
>
164+
<PaperclipIcon className="h-4 w-4" />
165+
</button>
166+
</TooltipTrigger>
167+
<TooltipContent side="top">
168+
<p>Feature not available yet</p>
169+
</TooltipContent>
170+
</Tooltip>
171+
</TooltipProvider>
172+
173+
<TooltipProvider>
174+
<Tooltip>
175+
<TooltipTrigger asChild>
176+
<button
177+
type="button"
178+
className="p-1.5 text-gray-400 dark:text-zinc-400 rounded-md cursor-not-allowed opacity-50"
179+
aria-label="Record (not available)"
180+
disabled
181+
>
182+
<svg
183+
xmlns="http://www.w3.org/2000/svg"
184+
className="h-4 w-4"
185+
viewBox="0 0 24 24"
186+
fill="none"
187+
stroke="currentColor"
188+
strokeWidth="2"
189+
strokeLinecap="round"
190+
strokeLinejoin="round"
191+
>
192+
<circle cx="12" cy="12" r="10"></circle>
193+
<circle cx="12" cy="12" r="4"></circle>
194+
</svg>
195+
</button>
196+
</TooltipTrigger>
197+
<TooltipContent side="top">
198+
<p>Feature not available yet</p>
199+
</TooltipContent>
200+
</Tooltip>
201+
</TooltipProvider>
202+
</div>
203+
204+
{/* Text input */}
71205
<div className="relative flex-1 flex items-center">
72206
<TextareaAutosize
73207
autoComplete="off"
74208
value={input}
75209
ref={inputRef}
76210
onKeyDown={handleKeyPress}
77211
onChange={handleInputChange}
212+
onFocus={() => setIsFocused(true)}
213+
onBlur={() => setIsFocused(false)}
78214
name="message"
79-
placeholder="Message CodeFox..."
80-
className="resize-none px-4 py-3 w-full focus:outline-none bg-transparent text-sm placeholder:text-muted-foreground"
215+
placeholder="Message Agent..."
216+
className="resize-none px-2 py-2.5 w-full focus:outline-none bg-transparent text-gray-800 dark:text-zinc-200 text-sm placeholder:text-gray-400 dark:placeholder:text-zinc-400"
81217
maxRows={5}
82218
/>
83219
</div>
84220

85-
<Button
86-
type="submit"
87-
size="icon"
88-
className="h-9 w-9 rounded-lg mr-2 mb-1 bg-primary hover:bg-primary/90 text-primary-foreground"
89-
disabled={!input.trim()}
90-
>
91-
<Send className="h-5 w-5" />
92-
</Button>
221+
{/* Right side - feedback & send */}
222+
<div className="flex items-center mr-2 gap-2">
223+
<div className="text-sm text-gray-400 dark:text-zinc-400">
224+
<span>Have feedback?</span>
225+
</div>
226+
227+
<button
228+
type="submit"
229+
className={cn(
230+
'h-7 w-7 rounded-md flex items-center justify-center',
231+
input.trim() || attachments.length > 0
232+
? 'bg-gray-100 hover:bg-gray-200 text-gray-700 dark:bg-zinc-700 dark:hover:bg-zinc-600 dark:text-zinc-200'
233+
: 'bg-gray-50 text-gray-300 cursor-not-allowed dark:bg-zinc-800 dark:text-zinc-500'
234+
)}
235+
disabled={!input.trim() && attachments.length === 0}
236+
aria-label="Send message"
237+
>
238+
<Send className="h-3.5 w-3.5" />
239+
</button>
240+
</div>
93241
</form>
94242
</motion.div>
95243
</div>

0 commit comments

Comments
 (0)