Skip to content
Merged

Dev #32

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSIjM0I4MkY2Ij48cGF0aCBkPSJNMTIuNDggMy41MmExIDEgMCAwIDAtMSAxdjIuOTJhMSAxIDAgMCAwIC41Mi44OGw1LjQ0IDMuMTRhMSAxIDAgMCAwIDEuNS0uODdWNy41MmExIDEgMCAwIDAtLjUyLS44OGwtNS40NC0zLjE0YTEgMSAwIDAgMC0uNSAweiNNNS4wOCA3LjUyYTEgMSAwIDAgMC0uNTIuODh2Mi45MmExIDEgMCAwIDAgLjUyLjg4bDUuNDQgMy4xNGExIDEgMCAwIDAgMS41LS44N1YxMS40YTEgMSAwIDAgMC0uNTItLjg4TDYuNTggNy41MmExIDEgMCAwIDAtMS41IDB6TTEyIDE0LjVsLTUuNDQgMy4xNGExIDEgMCAwIDAtLjUyLjg4djIuOTJhMSAxIDAgMCAwIDEuNS44N2w1LjQ0LTMuMTRhMSAxIDAgMCAwIC41Mi0uODh2LTIuOTJhMSAxIDAgMCAwLTEuNS0uODd6Ii8+PC9zdmc+" />
<link rel="icon" type="image/webp" href="/src/assets/logo.webp" />
<meta name="theme-color" content="#3B82F6" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Atomic</title>
Expand Down
1 change: 0 additions & 1 deletion frontend/src/assets/logo.svg

This file was deleted.

Binary file added frontend/src/assets/logo.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 28 additions & 23 deletions frontend/src/components/ChatInterface.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ const ChatInterface = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const { user, isAuthenticated } = useSelector((state) => state.auth);
const sidebarRef = useRef(null);


// console.log(user);
Expand Down Expand Up @@ -100,9 +99,8 @@ const ChatInterface = () => {
const handleResize = () => {
if (window.innerWidth >= 768) {
setSidebarOpen(true);
} else {
setSidebarOpen(false);
}
// Don't auto-close sidebar on mobile resize (keyboard opening)
};

window.addEventListener('resize', handleResize);
Expand Down Expand Up @@ -151,6 +149,9 @@ const ChatInterface = () => {
setNewChatTitle("");
setIsCreatingNewChat(false);
setIsCreatingFirstChat(false);
if (window.innerWidth < 768) {
setSidebarOpen(false);
}
};

const handleFirstChatSubmit = (e) => {
Expand All @@ -160,6 +161,9 @@ const ChatInterface = () => {
setNewChatTitle("");
setIsCreatingFirstChat(false);
setIsCreatingNewChat(false);
if (window.innerWidth < 768) {
setSidebarOpen(false);
}
};

const handleTitleChange = (e) => {
Expand All @@ -171,27 +175,29 @@ const ChatInterface = () => {

const handleHistoryClick = (id) => {
dispatch(setActiveChatId(id));
if (window.innerWidth < 768) setSidebarOpen(false);
if (window.innerWidth < 768) {
setSidebarOpen(false);
}
};

const handleCreateNewChat = () => {
setIsCreatingNewChat(true);
// Don't close sidebar when creating new chat
};

// Add click outside handler back
useEffect(() => {
const handleClickOutside = (event) => {
// Don't close sidebar if clicking on form elements
if (event.target.closest('input') || event.target.closest('textarea') || event.target.closest('select') || event.target.closest('button')) {
// Don't close sidebar if clicking on form elements or sidebar itself
if (event.target.closest('.sidebar') || event.target.closest('input') || event.target.closest('textarea') || event.target.closest('select')) {
return;
}

if (sidebarRef.current && !sidebarRef.current.contains(event.target)) {
if (window.innerWidth < 768 && sidebarOpen) {
setSidebarOpen(false);
}
// Close sidebar when clicking outside
if (window.innerWidth < 768 && sidebarOpen) {
setSidebarOpen(false);
}
};

document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
Expand Down Expand Up @@ -234,7 +240,7 @@ const ChatInterface = () => {

return (
<div className="chat-container">
<aside ref={sidebarRef} className={`sidebar ${sidebarOpen ? "open" : ""}`}>
<aside className={`sidebar ${sidebarOpen ? "open" : ""}`}>
<div className="sidebar-header">
<div className="logo-container">
<LogoIcon />
Expand All @@ -247,7 +253,7 @@ const ChatInterface = () => {
<div className="sidebar-top"> <nav className="main-nav"> <button className="new-thread-btn" onClick={handleCreateNewChat} disabled={!isAuthenticated}> <Icon path={<path d="M12 5v14m-7-7h14" />} /> <span>New Chat</span> </button> </nav> </div>
<div className="library">
<div className="library-header"> <h3>History</h3> </div>
{isCreatingNewChat && (<div className="new-chat-form-container" onClick={(e) => e.stopPropagation()}> <form onSubmit={handleNewChatSubmit} className={`new-chat-form ${titleError ? "error" : ""}`} onClick={(e) => e.stopPropagation()}> <input type="text" placeholder="New chat title..." value={newChatTitle} onChange={handleTitleChange} onBlur={() => !newChatTitle && setIsCreatingNewChat(false)} onClick={(e) => e.stopPropagation()} autoFocus/> <button type="submit" className="submit-new-chat-btn" disabled={!!titleError} onClick={(e) => e.stopPropagation()}> <Icon path={titleError ? (<path d="M18 6L6 18M6 6l12 12" />) : (<path d="M20 6L9 17l-5-5" />)} /> </button> </form> {titleError && (<p className="title-error-warning">{titleError}</p>)} </div>)}
{isCreatingNewChat && (<div className="new-chat-form-container"> <form onSubmit={handleNewChatSubmit} className={`new-chat-form ${titleError ? "error" : ""}`}> <input type="text" placeholder="New chat title..." value={newChatTitle} onChange={handleTitleChange} onBlur={() => !newChatTitle && setIsCreatingNewChat(false)} autoFocus/> <button type="submit" className="submit-new-chat-btn" disabled={!!titleError}> <Icon path={titleError ? (<path d="M18 6L6 18M6 6l12 12" />) : (<path d="M20 6L9 17l-5-5" />)} /> </button> </form> {titleError && (<p className="title-error-warning">{titleError}</p>)} </div>)}
<ul> {isAuthenticated ? (chats.length > 0 ? (chats.map((item) => (<li key={item._id} className={item._id === activeChatId ? "active" : ""} > <a href="#" onClick={(e) => { e.preventDefault(); handleHistoryClick(item._id); }}> <span>{item.title}</span> </a> </li>))) : (<li> <a href="#" className="no-chats"> <span>No chats found</span> </a> </li>)) : (<li> <a href="#" className="no-chats"> <span>Login to see history</span> </a> </li>)} </ul>
</div>
<div className="sidebar-bottom"> <div className="user-profile"> <div className="user-info"> <Icon path={<> <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /> <circle cx="12" cy="7" r="4" /> </>} /> <span>{user?.fullName?.firstName || "Guest User"}</span> </div> <div className={`credits-container ${isCreditsVisible ? 'show-text' : ''} ${creditsLoading ? 'loading' : ''} ${credits === 0 ? 'zero-credits' : ''}`} onClick={handleCreditsClick} > <span>{creditsLoading ? <div className="loading-spinner"></div> : (isAuthenticated ? `Credits: ${credits}` : (isCreditsVisible ? 'Login First' : 'Credits: 0'))}</span> </div> </div> </div>
Expand All @@ -256,7 +262,7 @@ const ChatInterface = () => {
<main className="main-content">
<header className="main-header">
<div className="header-left">
<button className="header-hamburger" onClick={() => setSidebarOpen((prev) => !prev)} >
<button className="header-hamburger" onClick={() => setSidebarOpen(true)} >
<Icon path={<> <path d="M3 12h18" /> <path d="M3 6h18" /> <path d="M3 18h18" /> </>} />
</button>
{/* <div className="header-logo">
Expand Down Expand Up @@ -302,18 +308,17 @@ const ChatInterface = () => {
<>
<h2>Start Your First Chat</h2>
<p>Create your first chat to begin your conversation with Atomic.</p>
<div className="first-chat-form-container" onClick={(e) => e.stopPropagation()}>
<form onSubmit={handleFirstChatSubmit} className={`first-chat-form ${titleError ? "error" : ""}`} onClick={(e) => e.stopPropagation()}>
<div className="first-chat-form-container">
<form onSubmit={handleFirstChatSubmit} className={`first-chat-form ${titleError ? "error" : ""}`}>
<input
type="text"
placeholder="Untitled Chat"
value={newChatTitle}
onChange={handleTitleChange}
onBlur={() => !newChatTitle && setIsCreatingFirstChat(false)}
onClick={(e) => e.stopPropagation()}
autoFocus
/>
<button type="submit" className="submit-first-chat-btn" disabled={!!titleError} onClick={(e) => e.stopPropagation()}>
<button type="submit" className="submit-first-chat-btn" disabled={!!titleError}>
<Icon path={titleError ? <path d="M18 6L6 18M6 6l12 12" /> : <path d="M20 6L9 17l-5-5" />} />
</button>
</form>
Expand Down Expand Up @@ -341,14 +346,14 @@ const ChatInterface = () => {
</div>
</section>

<section className="chat-input-area" onClick={(e) => e.stopPropagation()}>
<form className="input-form" onSubmit={handleSendMessage} onClick={(e) => e.stopPropagation()}>
<div className="input-wrapper"> <textarea ref={textareaRef} rows="1" placeholder={!isAuthenticated ? "Login to chat" : (characterLoading ? "Changing character..." : (!activeChatId ? "Make chat first then send message" : "Ask anything..."))} value={inputValue} onChange={(e) => setInputValue(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSendMessage(e); } }} onClick={(e) => e.stopPropagation()} disabled={characterLoading || !isAuthenticated || !activeChatId} /> </div>
<section className="chat-input-area">
<form className="input-form" onSubmit={handleSendMessage}>
<div className="input-wrapper"> <textarea ref={textareaRef} rows="1" placeholder={!isAuthenticated ? "Login to chat" : (characterLoading ? "Changing character..." : (!activeChatId ? "Make chat first then send message" : "Ask anything..."))} value={inputValue} onChange={(e) => setInputValue(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSendMessage(e); } }} disabled={characterLoading || !isAuthenticated || !activeChatId} /> </div>
<div className="input-footer">
<div className="input-footer-left"> <select name="model" value={character} className={`model-selector ${characterLoading ? 'loading' : ''}`} onChange={handleChangeCharacter} onClick={(e) => e.stopPropagation()} disabled={characterLoading || !isAuthenticated || !activeChatId}> <option value="jahnvi">Jahnvi</option> <option value="atomic">Atomic</option> <option value="chandni">Chandni</option>
<div className="input-footer-left"> <select name="model" value={character} className={`model-selector ${characterLoading ? 'loading' : ''}`} onChange={handleChangeCharacter} disabled={characterLoading || !isAuthenticated || !activeChatId}> <option value="jahnvi">Jahnvi</option> <option value="atomic">Atomic</option> <option value="chandni">Chandni</option>
<option value="bhaiya"> Harsh Bhaiya</option>
</select> <div className={`char-counter ${inputValue.length > MAX_PROMPT_CHARS ? "error" : ""}`} > {MAX_PROMPT_CHARS - inputValue.length} / 1400 </div> </div>
<div className="input-footer-right"> <button type="submit" className="send-button" disabled={!inputValue.trim() || inputValue.length > MAX_PROMPT_CHARS || !activeChatId || characterLoading || !isAuthenticated} onClick={(e) => e.stopPropagation()}> <Icon path={<> <line x1="12" y1="19" x2="12" y2="5" /> <polyline points="5 12 12 5 19 12" /> </>} /> </button> </div>
<div className="input-footer-right"> <button type="submit" className="send-button" disabled={!inputValue.trim() || inputValue.length > MAX_PROMPT_CHARS || !activeChatId || characterLoading || !isAuthenticated}> <Icon path={<> <line x1="12" y1="19" x2="12" y2="5" /> <polyline points="5 12 12 5 19 12" /> </>} /> </button> </div>
</div>
</form>
</section>
Expand Down
67 changes: 63 additions & 4 deletions frontend/src/styles/ChatInterface.css
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,19 @@
border: none;
padding: 8px;
cursor: pointer;
width: 40px;
height: 40px;
align-items: center;
justify-content: center;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.sidebar-close-btn .icon {
margin: 0;
stroke: var(--text-secondary);
width: 20px;
height: 20px;
}
.sidebar-close-btn:hover .icon {
stroke: var(--text-primary);
Expand All @@ -120,6 +129,9 @@
cursor: pointer;
font-size: 14px;
transition: box-shadow 0.3s;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.new-thread-btn:hover {
/* background-color: #03172200; */
Expand Down Expand Up @@ -487,12 +499,21 @@ a:hover .icon {
display: none; /* Hidden on desktop */
background: none;
border: none;
padding: 4px;
padding: 8px;
cursor: pointer;
width: 40px;
height: 40px;
align-items: center;
justify-content: center;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.header-hamburger .icon {
margin: 0;
stroke: var(--text-primary);
width: 20px;
height: 20px;
}

.header-title-wrapper {
Expand Down Expand Up @@ -528,6 +549,9 @@ a:hover .icon {
font-weight: 500;
font-size: 14px;
transition: box-shadow 0.3s;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}

.share-btn:hover {
Expand Down Expand Up @@ -614,6 +638,9 @@ a:hover .icon {
justify-content: center;
opacity: 0;
transition: opacity 0.2s ease-in-out;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.chat-turn:hover .copy-btn { opacity: 1; }
.copy-btn .icon { margin: 0; width: 18px; height: 18px; stroke: var(--text-secondary); }
Expand All @@ -630,6 +657,9 @@ a:hover .icon {
align-items: center;
justify-content: center;
border-radius: 6px;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.icon-button:hover { background-color: var(--background-secondary); }
.icon-button .icon { margin: 0; }
Expand Down Expand Up @@ -715,6 +745,9 @@ a:hover .icon {
cursor: pointer;
border: none;
transition: box-shadow 0.3s;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.input-form .send-button:hover { box-shadow: 0 0 8px var(--glow-color); }
.input-form .send-button .icon { stroke: white; margin: 0; }
Expand Down Expand Up @@ -776,6 +809,9 @@ a:hover .icon {
align-items: center;
justify-content: center;
cursor: pointer;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.new-chat-form.error .submit-new-chat-btn { background-color: var(--error-color); }
.submit-new-chat-btn:disabled { opacity: 0.6; cursor: not-allowed; }
Expand All @@ -791,6 +827,9 @@ a:hover .icon {
font-size: 13px;
cursor: pointer;
transition: all 0.3s ease;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.model-selector:focus { outline: none; border-color: var(--accent-color); }

Expand Down Expand Up @@ -861,12 +900,18 @@ a:hover .icon {

/* Mobile theme toggle adjustments */
.header-right .theme-toggle-button {
padding: 4px 8px;
gap: 4px;
padding: 8px;
gap: 0;
width: 40px;
height: 40px;
border-radius: 50% !important;
justify-content: center;
min-width: 40px;
min-height: 40px;
}

.header-right .theme-toggle-text {
font-size: 11px;
display: none;
}

.header-right .theme-icon {
Expand Down Expand Up @@ -1006,6 +1051,9 @@ a:hover .icon {
gap: 8px;
transition: color 0.3s ease;
margin-top: 16px;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}

/* .login-to-chat-btn::after {
Expand Down Expand Up @@ -1068,6 +1116,11 @@ a:hover .icon {
color: var(--text-primary);
z-index: auto;
box-shadow: none;
width: auto;
height: auto;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}

.header-right .theme-toggle-button:hover {
Expand Down Expand Up @@ -1191,6 +1244,9 @@ a:hover .icon {
justify-content: center;
cursor: pointer;
transition: opacity 0.3s ease;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}

.submit-first-chat-btn:hover {
Expand Down Expand Up @@ -1223,6 +1279,9 @@ a:hover .icon {
gap: 8px;
transition: all 0.3s ease;
margin-top: 16px;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}

.create-first-chat-btn:hover {
Expand Down