Skip to content

Instantly share code, notes, and snippets.

@ad22sinha
Created June 6, 2025 09:03
Show Gist options
  • Save ad22sinha/4d8d7f2d9b7c866152901a2cc4abbf02 to your computer and use it in GitHub Desktop.
Save ad22sinha/4d8d7f2d9b7c866152901a2cc4abbf02 to your computer and use it in GitHub Desktop.
src/components/dashboard/chatinterface.tsx
'use client';
import { useState, useRef, useEffect } from 'react';
import { PaperAirplaneIcon, ArrowPathIcon, XCircleIcon, XMarkIcon } from '@heroicons/react/24/solid';
import { ChatMessage } from '@/types/email';
interface EmailTag {
email: string;
isValid: boolean;
}
interface ChatInterfaceProps {
onSubmit: (prompt: string) => Promise<void>;
onSendToMany?: (emails: string[], subject?: string) => Promise<void>;
isGenerating: boolean;
messages: ChatMessage[];
currentEmailCardId?: string;
isNewChat?: boolean;
initialSubject?: string;
onCancelGeneration?: () => void;
}
// Added from Version 2
const typeText = (
text: string,
setPrompt: React.Dispatch<React.SetStateAction<string>>,
promptTextareaRef: React.RefObject<HTMLTextAreaElement>,
setIsTyping: React.Dispatch<React.SetStateAction<boolean>>,
onComplete?: () => void
) => {
let index = 0;
setPrompt(''); // Clear existing text
setIsTyping(true);
const typeInterval = setInterval(() => {
if (index < text.length) {
setPrompt(text.substring(0, index + 1));
index++;
} else {
clearInterval(typeInterval);
setIsTyping(false);
// Focus the textarea after typing is complete
setTimeout(() => {
promptTextareaRef.current?.focus();
// Call the completion callback after focusing
if (onComplete) {
onComplete();
}
}, 100);
}
}, 20); // Adjust speed here (lower = faster, higher = slower)
};
const ChatInterface = ({
onSubmit,
onSendToMany,
isGenerating,
messages,
currentEmailCardId,
isNewChat = false,
initialSubject = '',
onCancelGeneration
}: ChatInterfaceProps) => {
const [prompt, setPrompt] = useState('');
const [isSendMode, setIsSendMode] = useState(false);
const [emailTags, setEmailTags] = useState<EmailTag[]>([]);
const [currentEmail, setCurrentEmail] = useState('');
const [emailSubject, setEmailSubject] = useState(initialSubject);
const messagesEndRef = useRef<HTMLDivElement>(null);
const promptTextareaRef = useRef<HTMLTextAreaElement>(null);
const emailInputRef = useRef<HTMLInputElement>(null);
const [isTyping, setIsTyping] = useState(false); // isTyping state used by typeText and prompt examples
// useEffect for subject management from Version 1 (robust version)
useEffect(() => {
if (!isSendMode) {
if (initialSubject !== emailSubject) { // Only update if different
setEmailSubject(initialSubject);
}
}
}, [initialSubject, isSendMode, emailSubject]);
// useEffect for activateSendMode custom event from Version 1
useEffect(() => {
const activateSendModeHandler = (event: CustomEvent) => {
if (event.detail?.activate && !isSendMode) { // Ensure not already in send mode
setIsSendMode(true);
setPrompt('');
setEmailTags([]);
if (typeof event.detail.subject === 'string') {
setEmailSubject(event.detail.subject);
} else {
// If no event subject, use initialSubject (which defaults to '').
setEmailSubject(initialSubject);
}
setTimeout(() => emailInputRef.current?.focus(), 0);
}
};
document.addEventListener('activateSendMode', activateSendModeHandler as EventListener);
return () => {
document.removeEventListener('activateSendMode', activateSendModeHandler as EventListener);
};
}, [isSendMode, initialSubject]); // emailSubject removed as a dependency, as this effect sets it.
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const adjustTextareaHeight = () => {
if (promptTextareaRef.current) {
promptTextareaRef.current.style.height = 'auto';
const scrollHeight = promptTextareaRef.current.scrollHeight;
const minHeight = 40;
const maxHeight = 120;
const newHeight = Math.max(minHeight, Math.min(scrollHeight, maxHeight));
promptTextareaRef.current.style.height = newHeight + 'px';
promptTextareaRef.current.style.overflowY = scrollHeight > maxHeight ? 'auto' : 'hidden';
}
};
useEffect(() => {
adjustTextareaHeight();
}, [prompt]);
const isValidEmail = (email: string): boolean => {
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return emailRegex.test(email.trim());
};
// useEffect for /send command from Version 1
useEffect(() => {
if (!isSendMode && prompt.trim().toLowerCase().startsWith('/send')) { // Ensure not already in send mode
setIsSendMode(true);
const commandText = prompt.trim().substring(5).trim();
setPrompt('');
setEmailTags([]);
if (commandText) {
const potentialEmails = commandText.split(/[\s,]+/).filter(Boolean);
potentialEmails.forEach(emailStr => {
const cleanedPotentialEmail = emailStr.trim().replace(/,$/, '');
if (cleanedPotentialEmail && isValidEmail(cleanedPotentialEmail)) {
setEmailTags(prev => [...prev, { email: cleanedPotentialEmail, isValid: true }]);
}
});
}
// When entering send mode via /send, set subject from initialSubject (which defaults to '').
setEmailSubject(initialSubject);
setTimeout(() => emailInputRef.current?.focus(), 0);
}
}, [prompt, isSendMode, initialSubject]); // emailSubject removed as a dependency, as this effect sets it.
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (isSendMode) {
let finalEmailTags = [...emailTags];
if (currentEmail.trim()) {
const cleanedEmail = currentEmail.trim().replace(/,$/, '');
if (!finalEmailTags.some(tag => tag.email.toLowerCase() === cleanedEmail.toLowerCase())) {
finalEmailTags.push({ email: cleanedEmail, isValid: isValidEmail(cleanedEmail) });
}
setCurrentEmail(''); // Clear input after processing
}
const validEmails = finalEmailTags
.filter(tag => tag.isValid)
.map(tag => tag.email);
const uniqueValidEmails = Array.from(new Set(validEmails.map(email => email.toLowerCase()))).map(lowerEmail => {
return validEmails.find(e => e.toLowerCase() === lowerEmail)!;
});
if (uniqueValidEmails.length > 0 && emailSubject.trim() && onSendToMany) {
await onSendToMany(uniqueValidEmails, emailSubject);
// setEmailTags(finalEmailTags); // Update state if keeping tags after send
// exitSendMode(); // Optional: exit send mode after sending
} else if (uniqueValidEmails.length === 0) {
console.warn("No valid emails to send.");
emailInputRef.current?.focus();
} else if (!emailSubject.trim()) {
console.warn("Email subject is required.");
// Potentially focus the subject input here if it's empty
}
// Persist the tags that were just processed, including the one from currentEmail input
setEmailTags(finalEmailTags.filter(tag => !tag.email.toLowerCase().includes(currentEmail.trim().toLowerCase()) || isValidEmail(currentEmail.trim())));
return;
}
if (!prompt.trim()) return;
const currentPrompt = prompt;
setPrompt('');
setIsTyping(false); // Ensure isTyping is false after submission
if (promptTextareaRef.current) {
promptTextareaRef.current.style.height = '40px';
promptTextareaRef.current.style.overflowY = 'hidden';
}
onSubmit(currentPrompt);
};
const addEmailTag = (email: string) => {
if (!email.trim()) return;
const cleanedEmail = email.trim().replace(/,$/, '');
if (emailTags.some(tag => tag.email.toLowerCase() === cleanedEmail.toLowerCase())) {
setCurrentEmail('');
emailInputRef.current?.focus();
return;
}
const isValid = isValidEmail(cleanedEmail);
setEmailTags(prevTags => [...prevTags, { email: cleanedEmail, isValid }]);
setCurrentEmail('');
emailInputRef.current?.focus();
};
const removeEmailTag = (index: number) => {
setEmailTags(prevTags => prevTags.filter((_, i) => i !== index));
emailInputRef.current?.focus();
};
// exitSendMode from Version 1 (explicitly resets subject)
const exitSendMode = () => {
setIsSendMode(false);
setEmailTags([]);
setCurrentEmail('');
setEmailSubject(initialSubject); // Explicitly reset to initialSubject on exit
setTimeout(() => promptTextareaRef.current?.focus(), 0);
};
const clearInput = () => {
if (isSendMode) {
setCurrentEmail('');
emailInputRef.current?.focus();
} else {
setPrompt('');
setIsTyping(false);
if (promptTextareaRef.current) {
promptTextareaRef.current.style.height = '40px';
promptTextareaRef.current.style.overflowY = 'hidden';
promptTextareaRef.current.focus();
}
}
};
const formatTimestamp = (timestamp: string) => {
const date = new Date(timestamp);
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
};
// handleKeyDown from Version 1 (includes subject input handling)
const handleKeyDown = (e: React.KeyboardEvent) => {
if (isSendMode) {
if (e.target !== emailInputRef.current && e.target !== document.getElementById('emailSubject')) return;
if (e.target === emailInputRef.current) {
if (e.key === 'Enter' && !e.shiftKey && !currentEmail.trim() && emailTags.length > 0 && emailSubject.trim()) {
e.preventDefault();
handleSubmit(e as unknown as React.FormEvent);
return;
}
if ((e.key === 'Enter' && !e.shiftKey) || e.key === ',' || e.key === ' ') {
if (currentEmail.trim()) {
e.preventDefault();
addEmailTag(currentEmail);
} else if (e.key === ' ' || e.key === ',') {
e.preventDefault(); // Prevent space/comma if input is empty
}
return;
}
if (e.key === 'Backspace' && !currentEmail && emailTags.length > 0) {
e.preventDefault();
removeEmailTag(emailTags.length - 1);
return;
}
}
// Allow Enter on subject field to potentially submit form if conditions met
if (e.target === document.getElementById('emailSubject') && e.key === 'Enter' && !e.shiftKey) {
if (emailTags.length > 0 && emailSubject.trim()) {
e.preventDefault();
handleSubmit(e as unknown as React.FormEvent);
return;
}
}
if (e.key === 'Escape') {
e.preventDefault();
exitSendMode();
return;
}
return;
}
// Not in send mode (prompt input)
if (e.target !== promptTextareaRef.current) return;
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
if (prompt.trim()) {
handleSubmit(e as unknown as React.FormEvent);
}
}
};
return (
<div className="h-full overflow-hidden flex flex-col rounded-lg bg-white dark:bg-gray-900 shadow-sm">
<div className="flex-1 overflow-y-auto p-3 space-y-4 bg-gray-50 dark:bg-gray-800">
{messages.length === 0 ? (
<div className="flex flex-col items-center justify-center text-center text-gray-500 dark:text-gray-400 py-12">
<div className="w-16 h-16 bg-white dark:bg-gray-700 rounded-full flex items-center justify-center mb-4 shadow-sm border border-gray-100 dark:border-gray-600">
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" className="text-indigo-600 dark:text-indigo-400">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M12 5.75V18.25"></path>
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M18.25 12L5.75 12"></path>
</svg>
</div>
<p className="text-base font-medium text-gray-900 dark:text-white mb-2">Start a conversation</p>
<p className="text-sm max-w-sm text-gray-500 dark:text-gray-400">
Describe the email you want to create. Be specific about content, style, and purpose.
</p>
</div>
) : (
messages.map((message) => (
<div
key={message.id}
className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-[85%] rounded-2xl shadow-sm ${
message.isSystem
? 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 border border-gray-200 dark:border-gray-600'
: message.role === 'user'
? 'bg-indigo-600 text-white'
: 'bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200 border border-gray-100 dark:border-gray-600'
} animate-in fade-in-0 slide-in-from-${message.role === 'user' ? 'right' : 'left'}-2 duration-300`}
>
<div className="p-3">
<div className="text-sm whitespace-pre-wrap">{message.content}</div>
<div className={`text-[10px] mt-1.5 flex justify-end ${
message.role === 'user' ? 'text-indigo-200' : 'text-gray-400 dark:text-gray-500'
}`}>
{formatTimestamp(message.timestamp)}
</div>
</div>
</div>
</div>
))
)}
{isGenerating && !isSendMode && (
<div className="flex justify-start">
<div className="flex px-4 py-3 space-x-1 bg-white dark:bg-gray-700 rounded-2xl border border-gray-100 dark:border-gray-600 shadow-sm">
<div className="h-2 w-2 bg-indigo-600 dark:bg-indigo-400 rounded-full animate-bounce delay-0"></div>
<div className="h-2 w-2 bg-indigo-500 dark:bg-indigo-500 rounded-full animate-bounce delay-150"></div>
<div className="h-2 w-2 bg-indigo-400 dark:bg-indigo-600 rounded-full animate-bounce delay-300"></div>
</div>
</div>
)}
<div ref={messagesEndRef} className="h-2" />
</div>
<div className="flex-shrink-0 border-t border-gray-200 dark:border-gray-700 p-3 relative bg-white dark:bg-gray-900 backdrop-blur-md backdrop-saturate-150 sticky bottom-0">
<form onSubmit={handleSubmit} className="relative">
{isSendMode && (
<div className="mb-2 bg-white dark:bg-gray-800 p-3 rounded-lg border border-gray-200 dark:border-gray-700 flex flex-col shadow-sm">
<div className="flex items-center mb-2">
<div className="text-xs font-medium text-gray-800 dark:text-gray-200 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded-md">
Send to:
</div>
<button
type="button"
onClick={exitSendMode}
className="text-xs text-gray-500 dark:text-gray-400 hover:text-red-500 dark:hover:text-red-400 ml-auto flex items-center"
>
<XMarkIcon className="h-3.5 w-3.5 mr-1" />
Cancel
</button>
</div>
<div className="flex flex-wrap gap-2 min-h-[36px] p-2 rounded-md bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600">
{emailTags.map((tag, index) => (
<div
key={index}
className={`flex items-center text-sm px-2 py-0.5 rounded-md ${
tag.isValid
? 'bg-indigo-100 dark:bg-indigo-900/40 text-indigo-800 dark:text-indigo-300 border border-indigo-200 dark:border-indigo-800'
: 'bg-red-100 dark:bg-red-900/30 text-red-800 dark:text-red-300 border border-red-200 dark:border-red-800'
}`}
>
<span className="max-w-[150px] truncate font-medium">{tag.email}</span>
<button
type="button"
onClick={() => removeEmailTag(index)}
className="ml-1.5 text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 p-0.5 rounded-full hover:bg-gray-200/50 dark:hover:bg-gray-600/50"
>
<XMarkIcon className="h-3.5 w-3.5" />
</button>
</div>
))}
<input
ref={emailInputRef}
type="text"
value={currentEmail}
onChange={(e) => setCurrentEmail(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={emailTags.length === 0 ? "Type email addresses..." : ""}
className="flex-1 min-w-[150px] text-sm text-gray-900 dark:text-gray-100 outline-none bg-transparent dark:bg-transparent py-0.5"
autoFocus
/>
</div>
<div className="mt-3">
<label htmlFor="emailSubject" className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
Subject:
</label>
<input
type="text"
id="emailSubject" // Used by handleKeyDown
value={emailSubject}
onChange={(e) => setEmailSubject(e.target.value)}
onKeyDown={handleKeyDown} // Added from Version 1
placeholder="Enter email subject..."
className="w-full px-3 py-1.5 rounded-md bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 text-sm text-gray-900 dark:text-white focus:ring-1 focus:ring-indigo-300 dark:focus:ring-indigo-700 focus:border-indigo-500 dark:focus:border-indigo-500 outline-none"
/>
</div>
<div className="mt-2 text-xs text-gray-500 dark:text-gray-400">
Press <span className="px-1 py-0.5 bg-gray-100 dark:bg-gray-700 rounded text-gray-700 dark:text-gray-300 font-medium">Space</span>,
<span className="px-1 py-0.5 bg-gray-100 dark:bg-gray-700 rounded text-gray-700 dark:text-gray-300 font-medium mx-1">,</span>
or <span className="px-1 py-0.5 bg-gray-100 dark:bg-gray-700 rounded text-gray-700 dark:text-gray-300 font-medium ml-1">Enter</span>
to add each email. Press <span className="px-1 py-0.5 bg-gray-100 dark:bg-gray-700 rounded text-gray-700 dark:text-gray-300 font-medium mx-1">Enter</span>
when done to send.
</div>
</div>
)}
{!isSendMode && (
<textarea
ref={promptTextareaRef}
className="w-full border border-gray-200 dark:border-gray-700 rounded-xl py-2.5 px-3.5 pr-16 outline-none focus:border-indigo-500 dark:focus:border-indigo-400 focus:ring-2 focus:ring-indigo-100 dark:focus:ring-indigo-900/30 transition-all text-gray-900 dark:text-white resize-none text-sm shadow-sm bg-white dark:bg-gray-800"
placeholder={isNewChat
? "Describe the email you want to create..."
: "Ask for changes to your email..."}
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
disabled={isGenerating || isTyping} // Disable if AI is generating OR user is "typing" via example click
onKeyDown={handleKeyDown}
rows={1}
style={{
minHeight: "40px",
maxHeight: "120px",
overflowY: promptTextareaRef.current && promptTextareaRef.current.scrollHeight > 120 ? 'auto' : 'hidden'
}}
/>
)}
{!isSendMode && !isGenerating && (prompt.length > 0 || isTyping) && ( // Show clear button if user has typed, or if typeText is active
<button
type="button"
onClick={clearInput}
className="absolute right-14 bottom-2.5 p-1 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition-colors z-10"
aria-label="Clear input"
>
<XCircleIcon className="h-4 w-4" />
</button>
)}
{isSendMode ? (
<button
type="submit"
className={`absolute right-3 bottom-2 py-1 px-3 rounded-lg ${
(isGenerating || emailTags.filter(tag => tag.isValid).length === 0 || !emailSubject.trim())
? 'bg-gray-200 dark:bg-gray-700 text-gray-400 dark:text-gray-500 cursor-not-allowed'
: 'bg-indigo-600 text-white hover:bg-indigo-700 shadow-sm'
} transition-all duration-200 text-xs font-medium flex items-center z-10`}
disabled={isGenerating || emailTags.filter(tag => tag.isValid).length === 0 || !emailSubject.trim()}
aria-label="Send emails"
>
{isGenerating ? (
<>
<ArrowPathIcon className="h-3.5 w-3.5 mr-1 animate-spin" />
<span>Sending...</span>
</>
) : (
<>
<PaperAirplaneIcon className="h-3.5 w-3.5 mr-1" />
<span>Send ({emailTags.filter(tag => tag.isValid).length})</span>
</>
)}
</button>
) : (
(!isGenerating ? (
<button
type="submit"
className={`absolute right-3 bottom-2 p-1.5 rounded-lg ${
(!prompt.trim() || isTyping)
? 'text-gray-300 dark:text-gray-600 cursor-not-allowed'
: 'text-white bg-indigo-600 hover:bg-indigo-700 shadow-sm'
} transition-all duration-200 z-10`}
disabled={!prompt.trim() || isTyping}
aria-label="Send message"
>
<PaperAirplaneIcon className="h-4 w-4" />
</button>
) : (
<button
type="button"
onClick={onCancelGeneration}
className="absolute right-3 bottom-2 p-1.5 rounded-lg bg-red-100 dark:bg-red-900 text-red-600 dark:text-red-300 hover:bg-red-200 dark:hover:bg-red-800 shadow-sm transition-all duration-200 z-10 border border-red-200 dark:border-red-700 flex items-center"
aria-label="Cancel email generation"
>
<XMarkIcon className="h-4 w-4" />
</button>
))
)}
</form>
{/* Prompt examples - Redesigned for a more aesthetic and compact look */}
{!isSendMode && messages.length <= 1 && (
<div className="grid grid-cols-3 gap-1.5 mt-3 px-1 justify-center">
{[
{
icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth={1.5} viewBox="0 0 24 24">
<circle cx="12" cy="8" r="4" />
<path d="M4 20c0-4 16-4 16 0" />
</svg>
),
text: 'Welcome',
prompt: 'Create a welcoming email for new customers introducing our company and services',
tooltip: 'Generate a friendly welcome email for new customers'
},
{
icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth={1.5} viewBox="0 0 24 24">
<circle cx="9" cy="20" r="1" />
<circle cx="17" cy="20" r="1" />
<path d="M5 6h2l1 7h10l1-5H7" />
</svg>
),
text: 'E-commerce',
prompt: 'Write a promotional email for our latest product launch with a special discount offer',
tooltip: 'Create promotional emails with discounts and product launches'
},
{
icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth={1.5} viewBox="0 0 24 24">
<rect x="3" y="5" width="18" height="14" rx="2" />
<path d="M3 7l9 6 9-6" />
</svg>
),
text: 'Invite',
prompt: 'Create a professional invitation email for our upcoming webinar or event',
tooltip: 'Draft professional invitations for events and webinars'
},
{
icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth={1.5} viewBox="0 0 24 24">
<rect x="3" y="5" width="18" height="14" rx="2" />
<path d="M7 8h10M7 12h6" />
</svg>
),
text: 'Newsletter',
prompt: 'Draft a monthly newsletter with company updates and industry insights',
tooltip: 'Write engaging newsletters with updates and insights'
},
{
icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth={1.5} viewBox="0 0 24 24">
<rect x="6" y="4" width="12" height="16" rx="2" />
<path d="M9 8h6M9 12h6M9 16h2" />
</svg>
),
text: 'Invoice',
prompt: 'Write a polite invoice reminder email for overdue payments',
tooltip: 'Create polite payment reminders and invoice emails'
},
{
icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth={1.5} viewBox="0 0 24 24">
<path d="M6 7V6a6 6 0 1 1 12 0v1" />
<rect x="5" y="7" width="14" height="13" rx="2" />
</svg>
),
text: 'Cart',
prompt: 'Create a cart abandonment email to encourage customers to complete their purchase',
tooltip: 'Recover abandoned carts with persuasive follow-up emails'
}
].map((example, index) => (
<button
key={index}
type="button"
title={example.tooltip}
onClick={() => {
typeText(
example.prompt,
setPrompt,
promptTextareaRef,
setIsTyping,
() => { // This onComplete logic is from Version 2
setPrompt(''); // Clear the visual prompt
setIsTyping(false); // Ensure isTyping is reset
if (promptTextareaRef.current) { // Reset textarea style
promptTextareaRef.current.style.height = '40px';
promptTextareaRef.current.style.overflowY = 'hidden';
promptTextareaRef.current.focus();
}
onSubmit(example.prompt); // Submit the original example prompt
}
);
}}
className="flex items-center justify-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 transition-all duration-200 ease-in-out hover:border-indigo-300/70 dark:hover:border-indigo-600/70 hover:bg-indigo-50/70 dark:hover:bg-indigo-500/10 hover:text-indigo-600 dark:hover:text-indigo-400 hover:shadow-sm disabled:opacity-50 disabled:cursor-not-allowed disabled:shadow-none"
disabled={isTyping || isGenerating}
>
{example.icon}
<span>{example.text}</span>
</button>
))}
</div>
)}
{/* Hints section from Version 1 */}
{!isSendMode && (
<div className="flex items-center justify-between text-[10px] text-gray-500 dark:text-gray-400 mt-2 px-1">
<div className="flex items-center">
<span className="bg-gray-100 dark:bg-gray-700 px-1.5 py-0.5 rounded text-[10px] font-medium mr-1 border border-gray-200 dark:border-gray-600">
<span className="font-semibold">Enter</span>
</span>
to send
</div>
<div className="flex items-center">
<span className="bg-gray-100 dark:bg-gray-700 px-1.5 py-0.5 rounded text-[10px] font-medium mr-1 border border-gray-200 dark:border-gray-600">
<span className="font-semibold">Shift</span>+<span className="font-semibold">Enter</span>
</span>
for new line
</div>
<div className="flex items-center">
<span className="bg-gray-100 dark:bg-gray-700 px-1.5 py-0.5 rounded text-[10px] font-medium mr-1 border border-gray-200 dark:border-gray-600">/send</span>
to send to multiple
</div>
</div>
)}
</div>
</div>
);
};
export default ChatInterface;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment