Skip to content

Commit db1a7ad

Browse files
committed
Layout: Polishing layout & animation 80%
1 parent 5c600a3 commit db1a7ad

28 files changed

+648
-194
lines changed

.env.example

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1 @@
1-
# OpenAI API Configuration
2-
VITE_OPENAI_API_KEY=your_openai_api_key_here
3-
4-
# Default Model Configuration
5-
VITE_DEFAULT_MODEL=gpt-3.5-turbo
6-
7-
# Add more AI provider API keys below as you integrate them
8-
# Example:
9-
# VITE_ANTHROPIC_API_KEY=your_anthropic_api_key_here
10-
# VITE_GOOGLE_AI_API_KEY=your_google_ai_api_key_here
1+
# Nothing to configure for now

src/components/chat/ChatHistoryList.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ export const ChatHistoryList: React.FC<ChatHistoryListProps> = ({
389389
ref={sidebarRef}
390390
onMouseEnter={handleSidebarMouseEnter}
391391
onMouseLeave={handleSidebarMouseLeave}
392-
className={`flex flex-col h-full border-r border-gray-200 major-area-bg-color transition-all duration-300 ease-in-out ${isCollapsed ? 'w-0' : 'w-64'} relative`}
392+
className={`flex flex-col h-full frame-right-border major-area-bg-color transition-all duration-300 ease-in-out ${isCollapsed ? 'w-0' : 'w-64'} relative`}
393393
onDragOver={(e) => handleDragOver(e, 'root', 'root')}
394394
onDrop={(e) => handleDrop(e, 'root', 'root')}
395395
>
@@ -473,7 +473,7 @@ export const ChatHistoryList: React.FC<ChatHistoryListProps> = ({
473473
className={`flex items-center justify-between w-full ${isCollapsed ? 'px-0 py-3 flex-col' : 'px-2 py-2'}
474474
text-left transition-colors duration-150 cursor-pointer
475475
${dropTargetId === (item as ConversationFolder).folderId && dropTargetType === 'folder' ?
476-
'bg-blue-100 border-2 border-blue-300 rounded-md' : 'border-2 border-transparent hover:bg-gray-100'}`}
476+
'bg-blue-100 border-2 border-blue-300 rounded-md' : 'conversation-folder-item conversation-folder-item-text'}`}
477477
onDragOver={(e) => handleDragOver(e, (item as ConversationFolder).folderId, 'folder')}
478478
onDragLeave={handleDragLeave}
479479
onDrop={(e) => handleDrop(e, (item as ConversationFolder).folderId, 'folder')}
@@ -488,15 +488,15 @@ export const ChatHistoryList: React.FC<ChatHistoryListProps> = ({
488488
>
489489
<button className="flex items-center justify-center w-6 h-6">
490490
{expandedFolders[(item as ConversationFolder).folderId] ?
491-
<ChevronDown size={16} className="text-gray-500" /> :
492-
<ChevronRight size={16} className="text-gray-500" />
491+
<ChevronDown size={16} /> :
492+
<ChevronRight size={16} />
493493
}
494494
</button>
495495
<Folder fill={(item as ConversationFolder).colorFlag} size={16} className="flex-shrink-0 mr-1 text-gray-600" />
496-
<span className="font-medium truncate select-none">{(item as ConversationFolder).folderName}</span>
496+
<span className="font-semibold truncate select-none">{(item as ConversationFolder).folderName}</span>
497497
</div>
498498
<div onClick={(e) => handleMenuClick(e, (item as ConversationFolder).folderId, 'folder')}>
499-
<MoreVertical size={16} className="flex-shrink-0 text-gray-400 hover:text-gray-600" />
499+
<MoreVertical size={16} className="flex-shrink-0" />
500500
</div>
501501
</>
502502
)}
@@ -515,7 +515,7 @@ export const ChatHistoryList: React.FC<ChatHistoryListProps> = ({
515515

516516
{/* Folder contents */}
517517
{!isCollapsed && expandedFolders[(item as ConversationFolder).folderId] && (
518-
<ul className="pl-6 mt-1 mr-2">
518+
<ul className="pl-6 mr-2">
519519
{conversationsByFolder[(item as ConversationFolder).folderId]?.map((conversation) => (
520520
<li key={conversation.conversationId} className="relative">
521521
{editingId === conversation.conversationId ? (
@@ -538,7 +538,7 @@ export const ChatHistoryList: React.FC<ChatHistoryListProps> = ({
538538
onDragOver={(e) => handleDragOver(e, (item as ConversationFolder).folderId, 'folder')}
539539
onDrop={(e) => handleDrop(e, (item as ConversationFolder).folderId, 'folder')}
540540
onClick={() => onSelectConversation(conversation.conversationId)}
541-
className={`flex items-center justify-between w-full px-3 py-2 text-left conversation-item-border ${
541+
className={`flex items-center justify-between w-full px-3 py-2 text-left conversation-item-border transition-all duration-100 cursor-pointer ${
542542
activeConversationId === conversation.conversationId
543543
? 'conversation-selected-item-bg-color conversation-selected-item-text-color'
544544
: 'conversation-item-bg-color conversation-item-text-color'
@@ -594,7 +594,7 @@ export const ChatHistoryList: React.FC<ChatHistoryListProps> = ({
594594
onDragStart={(e) => handleDragStart(e, { id: (item as Conversation).conversationId, type: 'conversation' })}
595595
onDragEnd={handleDragEnd}
596596
onClick={() => onSelectConversation((item as Conversation).conversationId)}
597-
className={`flex flex-1 items-center justify-between mx-2 conversation-item-border px-3 py-2 text-left transition-all duration-100 ${
597+
className={`flex flex-1 items-center justify-between mx-2 conversation-item-border px-3 py-2 text-left transition-all duration-100 cursor-pointer ${
598598
activeConversationId === (item as Conversation).conversationId
599599
? 'conversation-selected-item-bg-color conversation-selected-item-text-color'
600600
: 'conversation-item-bg-color conversation-item-text-color'

src/components/chat/ChatMessageArea.tsx

Lines changed: 86 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import React, { useState, FormEvent, useRef, useEffect } from 'react';
22
import { Conversation, Message } from '../../types/chat';
3-
import { Send, Square, Copy, RotateCcw, Pencil, Loader2, Globe } from 'lucide-react';
3+
import { Send, Square, Copy, Pencil, Loader2, Globe, RefreshCw, Check, X } from 'lucide-react';
44
import MarkdownContent from './MarkdownContent';
55
import MessageToolboxMenu, { ToolboxAction } from '../ui/MessageToolboxMenu';
66
import { MessageHelper } from '../../services/message-helper';
77
import { DatabaseIntegrationService } from '../../services/database-integration';
88
import { SettingsService } from '../../services/settings-service';
99
import { ChatService } from '../../services/chat-service';
1010
import { AIServiceCapability } from '../../types/capabilities';
11+
import ProviderIcon from '../ui/ProviderIcon';
1112

1213
interface ChatMessageAreaProps {
1314
activeConversation: Conversation | null;
@@ -34,8 +35,10 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
3435
selectedProvider,
3536
selectedModel,
3637
}) => {
37-
const [input, setInput] = useState('');
38+
const [inputValue, setInput] = useState('');
39+
const inputRef = useRef<HTMLTextAreaElement>(null);
3840
const messagesEndRef = useRef<HTMLDivElement>(null);
41+
const editingContentRef = useRef<HTMLTextAreaElement>(null);
3942
const [hoveredMessageId, setHoveredMessageId] = useState<string | null>(null);
4043
const [editingMessageId, setEditingMessageId] = useState<string | null>(null);
4144
const [editingContent, setEditingContent] = useState('');
@@ -50,9 +53,11 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
5053
}, 50);
5154

5255
if(activeConversation) {
53-
const messagesList = MessageHelper.mapMessagesTreeToList(activeConversation);
56+
const messagesList = MessageHelper.mapMessagesTreeToList(activeConversation, false);
5457
setMessagesList(messagesList);
5558
}
59+
60+
handleCancelEdit();
5661
}, [activeConversation, activeConversation?.messages]);
5762

5863
// Load web search status on component mount
@@ -77,9 +82,9 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
7782
const handleSubmit = (e: FormEvent) => {
7883
e.preventDefault();
7984

80-
if (!input.trim() || isLoading) return;
85+
if (!inputValue.trim() || isLoading) return;
8186

82-
onSendMessage(input);
87+
onSendMessage(inputValue);
8388

8489
setInput('');
8590
};
@@ -172,7 +177,7 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
172177
updatedFatherMessage.conversationId
173178
);
174179

175-
setMessagesList(MessageHelper.mapMessagesTreeToList(activeConversation));
180+
setMessagesList(MessageHelper.mapMessagesTreeToList(activeConversation, false));
176181
};
177182

178183
const getCurrentMessageIndex = (messageId: string) => {
@@ -211,8 +216,8 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
211216
}
212217
};
213218

214-
const getModelName = (modelID: string) => {
215-
const providerSettings = SettingsService.getInstance().getProviderSettings(selectedProvider);
219+
const getModelName = (modelID: string, provider: string) => {
220+
const providerSettings = SettingsService.getInstance().getProviderSettings(provider);
216221
if(!providerSettings.models) return modelID;
217222

218223
const model = providerSettings.models?.find(m => m.modelId === modelID);
@@ -272,12 +277,14 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
272277
// },
273278
{
274279
id: 'regenerate',
275-
icon: RotateCcw,
280+
icon: RefreshCw,
276281
label: 'Regenerate',
277282
onClick: () => handleRegenerateResponse(message.messageId),
278283
}
279284
];
280-
285+
286+
if(message.role === 'system') return null;
287+
281288
return (
282289
<div
283290
key={message.messageId}
@@ -286,44 +293,61 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
286293
onMouseLeave={() => setHoveredMessageId(null)}
287294
>
288295
{isEditing && isUserMessage ? (
289-
<div className="w-[80%]">
290-
<textarea
296+
<form
297+
onSubmit={handleSendEditedMessage}
298+
onClick={() => {
299+
editingContentRef.current?.focus();
300+
}}
301+
onFocus={() => {
302+
editingContentRef.current?.focus();
303+
}}
304+
onKeyDown={(e) => {
305+
if(e.key === 'Escape') {
306+
handleCancelEdit();
307+
}
308+
}}
309+
className="w-[80%] px-3 py-2 input-border rounded-lg cursor-text transition-all duration-200"
310+
>
311+
<textarea
312+
ref={editingContentRef}
291313
value={editingContent}
292314
onChange={(e) => setEditingContent(e.target.value)}
293-
className="w-full px-3 py-2 border border-blue-400 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
315+
className="w-full rounded-lg resize-none focus:outline-none"
294316
rows={3}
295-
autoFocus
317+
autoFocus={true}
296318
/>
297319
<div className="flex justify-end mt-2 space-x-2">
298320
<button
321+
type="button"
299322
onClick={handleCancelEdit}
300-
className="px-3 py-1 text-sm text-gray-600 bg-gray-100 rounded hover:bg-gray-200"
323+
className="p-2 text-sm text-red-400 transition-all duration-200 rounded-md hover:text-red-500 message-icon"
301324
>
302-
Cancel
325+
<X size={18} />
303326
</button>
304-
<button
305-
onClick={handleSendEditedMessage}
306-
className="px-3 py-1 text-sm text-white bg-blue-500 rounded hover:bg-blue-600"
327+
<button
328+
type="submit"
329+
className="p-2 text-sm transition-all duration-200 rounded-md message-icon"
307330
disabled={!editingContent.trim()}
308331
>
309-
Send
332+
<Check size={18} />
310333
</button>
311334
</div>
312-
</div>
335+
</form>
313336
) : (
314337
<>
315338
{!isUserMessage &&
316339
<div className="flex items-center flex-1 gap-2 px-2 mb-4 justify-left">
317-
<span className="text-sm text-gray-500">{getModelName(message.model)}</span>
318-
<span className="text-sm text-gray-500 bg-gray-200 rounded-full px-2 py-0.5">{message.provider}</span>
340+
<ProviderIcon providerName={message.provider} className="w-4 h-4 mr-2" />
341+
<span className="text-sm message-model-tag">{getModelName(message.model, message.provider)}</span>
342+
<span className="px-3 py-1 text-xs font-medium message-provider-tag">{message.provider}</span>
319343
</div>
320344
}
321345

322346
<div
323347
className={`max-w-[80%] rounded-lg p-3 ${
324348
isUserMessage
325-
? 'bg-blue-500 text-white rounded-tr-none'
326-
: 'bg-gray-200 text-gray-800 rounded-tl-none'
349+
? 'message-user rounded-tr-none'
350+
: 'message-assistant rounded-tl-none'
327351
}`}
328352
>
329353
{isUserMessage ? (
@@ -377,19 +401,42 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
377401
</div>
378402

379403
{/* Input form */}
380-
<form onSubmit={handleSubmit} className="flex flex-col gap-2 px-4 pt-4 pb-2 m-2 mb-4 border border-gray-200 rounded-lg">
404+
<form onSubmit={handleSubmit}
405+
onClick={() => {
406+
inputRef.current?.focus();
407+
}}
408+
onFocus={() => {
409+
inputRef.current?.focus();
410+
}}
411+
className="flex flex-col gap-2 px-4 pt-3 pb-2 m-2 mb-4 transition-all duration-200 rounded-lg input-border cursor-text"
412+
>
381413
<div className="flex space-x-2">
382-
<input
383-
type="text"
384-
value={input}
385-
onChange={(e) => setInput(e.target.value)}
414+
<textarea
415+
ref={inputRef}
416+
value={inputValue}
417+
onChange={(e) => {
418+
setInput(e.target.value);
419+
// Adjust height based on content
420+
const textarea = e.target;
421+
textarea.style.height = 'auto'; // Reset height
422+
423+
// Calculate new height based on scrollHeight, with min and max constraints
424+
const minHeight = 38; // Approx height for 1 row
425+
const maxHeight = 38 * 3; // Approx height for 3 rows
426+
427+
const newHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);
428+
textarea.style.height = `${newHeight}px`;
429+
}}
386430
placeholder="Type your message..."
387-
className="flex-1 px-4 py-2 border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500"
431+
className="flex-1 px-2 pt-1 pb-2 resize-none focus:outline-none"
388432
disabled={isLoading}
389-
/>
433+
inputMode='text'
434+
rows={1}
435+
style={{ minHeight: '38px', maxHeight: '114px', height: '38px', overflow: 'auto' }}
436+
></textarea>
390437
</div>
391438

392-
<div className="flex flex-row items-center justify-between px-2">
439+
<div className="flex flex-row items-center justify-between px-1">
393440
{
394441
isWebSearchAllowed ? (
395442
<button
@@ -422,18 +469,19 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
422469
<button
423470
type="button"
424471
onClick={handleStopStreaming}
425-
className="flex items-center justify-center w-10 h-10 text-white bg-red-500 rounded-full hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500"
472+
className="flex items-center justify-center w-10 h-10 transition-all duration-200 rounded-full conversation-stop-button focus:outline-none"
426473
aria-label="Stop response"
427474
title="Stop response"
428475
>
429-
<Square size={20} />
476+
<Square size={20} fill="currentColor" />
430477
</button>
431478
) : (
432479
<button
433480
type="submit"
434-
disabled={isLoading || !input.trim()}
435-
className="flex items-center justify-center w-10 h-10 text-white bg-blue-500 rounded-full hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50"
436-
aria-label="Send message"
481+
disabled={isLoading || !inputValue.trim()}
482+
className="flex items-center justify-center w-10 h-10 transition-all duration-200 rounded-full conversation-send-button focus:outline-none disabled:cursor-not-allowed"
483+
aria-label={isLoading || !inputValue.trim() ? 'Cannot send message' : 'Send message'}
484+
title={isLoading || !inputValue.trim() ? 'Cannot send message' : 'Send message'}
437485
>
438486
<Send size={20} />
439487
</button>

src/components/core/DatabaseInitializer.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,18 @@ const DatabaseInitializer: React.FC<DatabaseInitializerProps> = ({ children }) =
6161
);
6262
}
6363

64-
return <>{children}</>;
64+
return (
65+
<>
66+
<div className={`fixed flex inset-0 z-50 items-center justify-center w-screen h-screen bg-white
67+
${isInitialized ? 'fade-out' : ''}`}>
68+
<div className="flex flex-col items-center text-center">
69+
<div className="w-16 h-16 border-4 border-t-4 border-blue-500 border-opacity-50 rounded-full animate-bounce" />
70+
<p className="mt-4 text-lg text-gray-600">Initializing database...</p>
71+
</div>
72+
</div>
73+
{children}
74+
</>
75+
);
6576
};
6677

6778
export default DatabaseInitializer;

src/components/layout/MainLayout.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,14 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
5555
<div className="flex flex-col flex-1 bg-main-background-color">
5656
{/* Main content */}
5757

58-
<div className='flex flex-col flex-1 overflow-hidden major-area-border major-area-bg-color'>
58+
<div className='relative flex flex-col flex-1 overflow-hidden major-area-border major-area-bg-color'>
59+
<div className="flex-1 overflow-auto">
60+
{children}
61+
</div>
62+
5963
<SettingsPage
6064
isOpen={showSettings}
6165
/>
62-
63-
{!showSettings &&
64-
<div className="flex-1 overflow-auto">
65-
{children}
66-
</div>
67-
}
6866
</div>
6967

7068
{/* <BottomBar loadedModels={loadedModels} /> */}

0 commit comments

Comments
 (0)