feat(ux): epic UX design improvements across agents, chat, notes, and i18n
Comprehensive UI/UX updates including agent card redesign, chat container improvements, note editor enhancements, memory echo notifications, and updated translations for all 15 locales. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,19 +14,22 @@ import { useLanguage } from '@/lib/i18n'
|
||||
interface ChatContainerProps {
|
||||
initialConversations: any[]
|
||||
notebooks: any[]
|
||||
webSearchAvailable?: boolean
|
||||
}
|
||||
|
||||
export function ChatContainer({ initialConversations, notebooks }: ChatContainerProps) {
|
||||
export function ChatContainer({ initialConversations, notebooks, webSearchAvailable }: ChatContainerProps) {
|
||||
const { t, language } = useLanguage()
|
||||
const [conversations, setConversations] = useState(initialConversations)
|
||||
const [currentId, setCurrentId] = useState<string | null>(null)
|
||||
const [selectedNotebook, setSelectedNotebook] = useState<string | undefined>(undefined)
|
||||
const [webSearchEnabled, setWebSearchEnabled] = useState(false)
|
||||
const [historyMessages, setHistoryMessages] = useState<UIMessage[]>([])
|
||||
const [isLoadingHistory, setIsLoadingHistory] = useState(false)
|
||||
|
||||
// Prevents the useEffect from loading an empty conversation
|
||||
// when we just created one via createConversation()
|
||||
const skipHistoryLoad = useRef(false)
|
||||
const scrollRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const transport = useRef(new DefaultChatTransport({
|
||||
api: '/api/chat',
|
||||
@@ -129,6 +132,7 @@ export function ChatContainer({ initialConversations, notebooks }: ChatContainer
|
||||
conversationId: convId,
|
||||
notebookId: notebookId || selectedNotebook || undefined,
|
||||
language,
|
||||
webSearch: webSearchEnabled,
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -139,6 +143,7 @@ export function ChatContainer({ initialConversations, notebooks }: ChatContainer
|
||||
setMessages([])
|
||||
setHistoryMessages([])
|
||||
setSelectedNotebook(undefined)
|
||||
setWebSearchEnabled(false)
|
||||
}
|
||||
|
||||
const handleDeleteConversation = async (id: string) => {
|
||||
@@ -158,6 +163,13 @@ export function ChatContainer({ initialConversations, notebooks }: ChatContainer
|
||||
? messages
|
||||
: historyMessages
|
||||
|
||||
// Auto-scroll to bottom when messages change
|
||||
useEffect(() => {
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight
|
||||
}
|
||||
}, [displayMessages])
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex overflow-hidden bg-white dark:bg-[#1a1c22]">
|
||||
<ChatSidebar
|
||||
@@ -169,7 +181,7 @@ export function ChatContainer({ initialConversations, notebooks }: ChatContainer
|
||||
/>
|
||||
|
||||
<div className="flex-1 flex flex-col h-full overflow-hidden">
|
||||
<div className="flex-1 overflow-y-auto scrollbar-hide pb-6 w-full flex justify-center">
|
||||
<div ref={scrollRef} className="flex-1 overflow-y-auto scrollbar-hide pb-6 w-full flex justify-center">
|
||||
<ChatMessages messages={displayMessages} isLoading={isLoading || isLoadingHistory} />
|
||||
</div>
|
||||
|
||||
@@ -180,6 +192,9 @@ export function ChatContainer({ initialConversations, notebooks }: ChatContainer
|
||||
isLoading={isLoading}
|
||||
notebooks={notebooks}
|
||||
currentNotebookId={selectedNotebook || null}
|
||||
webSearchEnabled={webSearchEnabled}
|
||||
onToggleWebSearch={() => setWebSearchEnabled(prev => !prev)}
|
||||
webSearchAvailable={webSearchAvailable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import { Send, BookOpen, X } from 'lucide-react'
|
||||
import { Send, BookOpen, X, Globe } from 'lucide-react'
|
||||
import { getNotebookIcon } from '@/lib/notebook-icon'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
@@ -21,9 +21,12 @@ interface ChatInputProps {
|
||||
isLoading?: boolean
|
||||
notebooks: any[]
|
||||
currentNotebookId?: string | null
|
||||
webSearchEnabled?: boolean
|
||||
onToggleWebSearch?: () => void
|
||||
webSearchAvailable?: boolean
|
||||
}
|
||||
|
||||
export function ChatInput({ onSend, isLoading, notebooks, currentNotebookId }: ChatInputProps) {
|
||||
export function ChatInput({ onSend, isLoading, notebooks, currentNotebookId, webSearchEnabled, onToggleWebSearch, webSearchAvailable }: ChatInputProps) {
|
||||
const { t } = useLanguage()
|
||||
const [input, setInput] = useState('')
|
||||
const [selectedNotebook, setSelectedNotebook] = useState<string | undefined>(currentNotebookId || undefined)
|
||||
@@ -76,8 +79,8 @@ export function ChatInput({ onSend, isLoading, notebooks, currentNotebookId }: C
|
||||
<div className="flex items-center justify-between px-3 pb-3 pt-1">
|
||||
{/* Context Selector */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Select
|
||||
value={selectedNotebook || 'global'}
|
||||
<Select
|
||||
value={selectedNotebook || 'global'}
|
||||
onValueChange={(val) => setSelectedNotebook(val === 'global' ? undefined : val)}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-auto min-w-[130px] rounded-full bg-white dark:bg-[#1a1c22] border-slate-200 dark:border-white/10 shadow-sm text-xs font-medium gap-2 ring-offset-transparent focus:ring-0 focus:ring-offset-0 hover:bg-slate-50 dark:hover:bg-[#252830] transition-colors">
|
||||
@@ -96,12 +99,28 @@ export function ChatInput({ onSend, isLoading, notebooks, currentNotebookId }: C
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
|
||||
{selectedNotebook && (
|
||||
<Badge variant="secondary" className="text-[10px] bg-primary/10 text-primary border-none rounded-full px-2.5 h-6 font-semibold tracking-wide">
|
||||
{t('chat.active')}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{webSearchAvailable && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onToggleWebSearch}
|
||||
className={cn(
|
||||
"h-8 rounded-full border shadow-sm text-xs font-medium gap-1.5 flex items-center px-3 transition-all duration-200",
|
||||
webSearchEnabled
|
||||
? "bg-primary/10 text-primary border-primary/30 hover:bg-primary/20"
|
||||
: "bg-white dark:bg-[#1a1c22] border-slate-200 dark:border-white/10 text-slate-500 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-[#252830]"
|
||||
)}
|
||||
>
|
||||
<Globe className="h-3.5 w-3.5" />
|
||||
{webSearchEnabled && t('chat.webSearch')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Send Button */}
|
||||
|
||||
Reference in New Issue
Block a user