Skip to content

Commit 624caa4

Browse files
feat: implement quick-refine action toolbar for AI responses
1 parent 6d9331d commit 624caa4

File tree

4 files changed

+118
-70
lines changed

4 files changed

+118
-70
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React from "react";
2+
import { Scissors, Briefcase, List, RotateCcw } from "lucide-react";
3+
4+
interface QuickRefineToolbarProps {
5+
onRefine: (prompt: string) => void;
6+
}
7+
8+
const QuickRefineToolbar: React.FC<QuickRefineToolbarProps> = ({ onRefine }) => {
9+
const actions = [
10+
{
11+
id: "shorten",
12+
label: "Shorten",
13+
icon: <Scissors className="h-3 w-3" />,
14+
prompt: "Summarize the previous response to be much shorter."
15+
},
16+
{
17+
id: "pro",
18+
label: "Professional",
19+
icon: <Briefcase className="h-3 w-3" />,
20+
prompt: "Rewrite the previous response in a professional, formal tone."
21+
},
22+
{
23+
id: "list",
24+
label: "Listify",
25+
icon: <List className="h-3 w-3" />,
26+
prompt: "Convert the previous response into a bulleted list."
27+
},
28+
{
29+
id: "retry",
30+
label: "Retry",
31+
icon: <RotateCcw className="h-3 w-3" />,
32+
prompt: "Regenerate the previous response."
33+
},
34+
];
35+
36+
return (
37+
<div className="flex flex-wrap gap-2 mt-2 opacity-0 group-hover:opacity-100 transition-opacity duration-300 ease-in-out justify-start">
38+
{actions.map((action) => (
39+
<button
40+
key={action.id}
41+
onClick={() => onRefine(action.prompt)}
42+
className="flex items-center gap-1 px-2 py-1 text-[10px] font-medium bg-white hover:bg-purple-50 border border-gray-200 rounded-md transition-all shadow-sm text-gray-600 hover:text-purple-700 hover:border-purple-200"
43+
>
44+
{action.icon}
45+
{action.label}
46+
</button>
47+
))}
48+
</div>
49+
);
50+
};
51+
52+
export default QuickRefineToolbar;
Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,61 @@
11
import { type Message } from "@/redux/chatSlice";
22
import { CheckCheckIcon, CheckIcon } from "lucide-react";
3-
43
import React from "react";
4+
import QuickRefineToolbar from "./QuickRefineToolbar";
5+
6+
interface MessageItemProps {
7+
message: Message;
8+
onSendMessage?: (msg: string) => void;
9+
}
510

6-
export default function MessageItem({ message }: { message: Message }) {
11+
export default function MessageItem({ message, onSendMessage }: MessageItemProps) {
712
return (
813
<>
914
{message.isSent ? (
10-
<div className="flex justify-end">
11-
<div className="bg-purple-700 text-white rounded-lg p-3 max-w-md">
12-
<p>{message.message}</p>
15+
/* User Messages */
16+
<div className="flex justify-end mb-4">
17+
<div className="bg-purple-700 text-white rounded-lg p-3 max-w-md shadow-sm">
18+
<p className="text-sm">{message.message}</p>
1319
<div className="flex justify-end items-center text-xs mt-1 text-gray-200">
1420
<span className="mr-1">
1521
{new Date(message.createdAt).toLocaleTimeString([], {
1622
hour: "2-digit",
1723
minute: "2-digit",
1824
})}
1925
</span>
20-
{message.status === "sent" && (
21-
<span>
22-
<CheckIcon className="h-3 w-3 inline" />
23-
</span>
24-
)}
26+
{message.status === "sent" && <CheckIcon className="h-3 w-3 inline" />}
2527
{message.status === "delivered" && (
26-
<span>
27-
<CheckCheckIcon className="h-3 w-3 inline text-gray-300/70" />
28-
</span>
28+
<CheckCheckIcon className="h-3 w-3 inline text-gray-300/70" />
2929
)}
3030
{message.status === "seen" && (
31-
<span>
32-
<CheckCheckIcon className="h-3 w-3 inline text-blue-300" />
33-
</span>
31+
<CheckCheckIcon className="h-3 w-3 inline text-blue-300" />
3432
)}
3533
</div>
3634
</div>
3735
</div>
3836
) : (
39-
<div className="flex justify-start">
40-
<div className="bg-gray-100 rounded-lg p-3 max-w-md">
41-
<p>{message.message}</p>
42-
<div className="flex justify-end items-center text-xs mt-1 text-gray-500">
43-
<span>
44-
{new Date(message.createdAt).toLocaleTimeString([], {
45-
hour: "2-digit",
46-
minute: "2-digit",
47-
})}
48-
</span>
37+
/* AI Messages - Added 'group' for hover logic */
38+
<div className="flex justify-start mb-4 group">
39+
<div className="flex flex-col items-start max-w-md">
40+
<div className="bg-gray-100 rounded-lg p-3 shadow-sm border border-gray-200">
41+
<p className="text-sm text-gray-800">{message.message}</p>
42+
<div className="flex justify-end items-center text-xs mt-1 text-gray-500">
43+
<span>
44+
{new Date(message.createdAt).toLocaleTimeString([], {
45+
hour: "2-digit",
46+
minute: "2-digit",
47+
})}
48+
</span>
49+
</div>
4950
</div>
51+
52+
{/* Toolbar only appears for AI responses on hover */}
53+
{onSendMessage && (
54+
<QuickRefineToolbar onRefine={onSendMessage} />
55+
)}
5056
</div>
5157
</div>
5258
)}
5359
</>
5460
);
55-
}
61+
}

Frontend/src/components/chat/messages-list.tsx

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ export default function MessagesList({ messages }: { messages: Message[] }) {
1414
(state: RootState) => state.chat.selectedChatId
1515
);
1616

17+
const { markMessageAsSeen, sendMessage } = useChat();
18+
1719
useEffect(() => {
1820
setLastMarkedAsSeen(new Date().toISOString());
1921
}, [selectedChatId]);
2022

21-
const { markMessageAsSeen } = useChat();
22-
2323
useEffect(() => {
2424
const unseenMessages = messages.filter(
2525
(message) =>
@@ -33,56 +33,43 @@ export default function MessagesList({ messages }: { messages: Message[] }) {
3333
});
3434
setLastMarkedAsSeen(new Date().toISOString());
3535
}
36-
}, [messages]);
36+
}, [messages, lastMarkedAsSeen, markMessageAsSeen]);
3737

3838
return (
39-
<>
39+
<div className="flex flex-col w-full px-4">
4040
{messages.length > 0 ? (
4141
<>
4242
{messages.reduce((acc: JSX.Element[], message, index, array) => {
43-
// Add date separator for first message
43+
// Date Separator Logic
44+
const messageDate = parseISO(message.createdAt);
4445
if (index === 0) {
45-
const firstDate = parseISO(message.createdAt);
4646
acc.push(
47-
<div
48-
key={`date-first-${message.id}`}
49-
className="flex justify-center my-4"
50-
>
47+
<div key={`date-first-${message.id}`} className="flex justify-center my-4">
5148
<div className="bg-gray-100 px-3 py-1 rounded-full text-sm text-gray-500">
52-
{format(firstDate, "PPP")}
49+
{format(messageDate, "PPP")}
5350
</div>
5451
</div>
5552
);
5653
}
5754

58-
// Add the message component
59-
acc.push(<MessageItem key={message.id} message={message} />);
55+
// Push Message Item
56+
acc.push(
57+
<MessageItem
58+
key={message.id}
59+
message={message}
60+
onSendMessage={sendMessage}
61+
/>
62+
);
6063

61-
// Check if the next message is from a different date
64+
// Date separator for subsequent messages
6265
if (index < array.length - 1) {
63-
const currentDate = parseISO(message.createdAt);
6466
const nextDate = parseISO(array[index + 1].createdAt);
65-
66-
// Check if dates are different
67-
if (
68-
!isEqual(
69-
new Date(
70-
currentDate.getFullYear(),
71-
currentDate.getMonth(),
72-
currentDate.getDate()
73-
),
74-
new Date(
75-
nextDate.getFullYear(),
76-
nextDate.getMonth(),
77-
nextDate.getDate()
78-
)
79-
)
80-
) {
67+
if (!isEqual(
68+
new Date(messageDate.setHours(0,0,0,0)),
69+
new Date(nextDate.setHours(0,0,0,0))
70+
)) {
8171
acc.push(
82-
<div
83-
key={`date-${message.id}`}
84-
className="flex justify-center my-4"
85-
>
72+
<div key={`date-${array[index+1].id}`} className="flex justify-center my-4">
8673
<div className="bg-gray-100 px-3 py-1 rounded-full text-sm text-gray-500">
8774
{format(nextDate, "PPP")}
8875
</div>
@@ -95,10 +82,10 @@ export default function MessagesList({ messages }: { messages: Message[] }) {
9582
}, [])}
9683
</>
9784
) : (
98-
<div className="flex justify-center items-center h-full">
99-
<p className="text-gray-500">No messages yet</p>
85+
<div className="flex justify-center items-center h-64">
86+
<p className="text-gray-400 italic">No messages yet. Start a conversation!</p>
10087
</div>
10188
)}
102-
</>
89+
</div>
10390
);
104-
}
91+
}

Frontend/src/components/ui/input.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
import React from "react";
2-
import { cn } from "../../lib/utils";
1+
import * as React from "react";
2+
import { cn } from "@/lib/utils";
33

4-
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
4+
export interface InputProps
5+
extends React.InputHTMLAttributes<HTMLInputElement> {}
6+
7+
const Input = React.forwardRef<HTMLInputElement, InputProps>(
58
({ className, type, ...props }, ref) => {
69
return (
710
<input
811
type={type}
912
className={cn(
10-
13+
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
1114
className
1215
)}
1316
ref={ref}

0 commit comments

Comments
 (0)