Spaces:
Running
Running
| import type { ChatSession } from '../../lib/contracts'; | |
| import { conversationPreview } from '../../lib/conversations'; | |
| interface ConversationListProps { | |
| conversations: ChatSession[]; | |
| activeConversationId: string; | |
| onNewConversation: () => void; | |
| onOpenConversation: (conversationId: string) => void; | |
| onOpenConversationSettings: (conversationId: string) => void; | |
| onDeleteConversation: (conversationId: string) => void; | |
| } | |
| export function ConversationList({ | |
| conversations, | |
| activeConversationId, | |
| onNewConversation, | |
| onOpenConversation, | |
| onOpenConversationSettings, | |
| onDeleteConversation, | |
| }: ConversationListProps) { | |
| return ( | |
| <main className="chat-surface conversation-library"> | |
| <header className="chat-header conversation-list-header"> | |
| <div> | |
| <h2>Chats</h2> | |
| <p>Saved only in this browser</p> | |
| </div> | |
| <button className="new-chat-button" type="button" onClick={onNewConversation}>New chat</button> | |
| </header> | |
| <section className="conversation-list" aria-label="Saved chats"> | |
| {conversations.length === 0 ? ( | |
| <div className="conversation-list-empty"> | |
| <h3>No saved chats</h3> | |
| <p>Your first prompt creates a local session automatically.</p> | |
| <button type="button" onClick={onNewConversation}>Start a chat</button> | |
| </div> | |
| ) : conversations.map((conversation) => ( | |
| <article className={`conversation-row${conversation.id === activeConversationId ? ' is-current' : ''}`} key={conversation.id}> | |
| <button className="conversation-open" type="button" onClick={() => onOpenConversation(conversation.id)}> | |
| <span className="conversation-title-line"> | |
| <strong>{conversation.title}</strong> | |
| {conversation.id === activeConversationId && <small>Current</small>} | |
| </span> | |
| <span>{conversationPreview(conversation.messages)}</span> | |
| <small>{conversation.modelId.replace('bonsai-', 'Bonsai ')} · {formatConversationTime(conversation.updatedAt)}</small> | |
| </button> | |
| <div className="conversation-actions"> | |
| <button type="button" onClick={() => onOpenConversationSettings(conversation.id)}>Settings</button> | |
| <button | |
| className="conversation-delete" | |
| type="button" | |
| aria-label={`Delete chat: ${conversation.title}`} | |
| onClick={() => onDeleteConversation(conversation.id)} | |
| > | |
| Delete | |
| </button> | |
| </div> | |
| </article> | |
| ))} | |
| </section> | |
| </main> | |
| ); | |
| } | |
| function formatConversationTime(timestamp: number): string { | |
| return new Intl.DateTimeFormat('en', { | |
| month: 'short', | |
| day: 'numeric', | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| }).format(new Date(timestamp)); | |
| } | |