groupchat/client/src/components/chat-sidebar.riot
Jason Tudisco 01258fa958 feat: complete GroupChat app with AI tool calling, search, fetch, and UI
Full-stack real-time group chat with Rust/Axum backend and Riot.js frontend.

Features:
- Auth (register/login/JWT), rooms, invites, WebSocket messaging
- AI responses via OpenRouter with tool calling (Brave Search + web fetch)
- Real-time tool usage indicators (searching/reading page)
- Collapsible tool results in message bubbles
- AI stats bar (model, tokens, speed, response time) persisted to DB
- Room soft-delete, /clear command, dynamic model fetching
- Markdown rendering with code highlighting and copy buttons

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 18:50:52 -06:00

201 lines
4.9 KiB
Plaintext

<chat-sidebar>
<aside class="sidebar">
<div class="sidebar-header">
<h2>GroupChat</h2>
<button class="btn btn-ghost btn-icon" onclick={props.cbCreateRoom} title="New Room">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
</svg>
</button>
</div>
<div class="room-list">
<div
each={room in props.rooms}
key={room.id}
class={'room-item ' + (room.id === props.activeRoomId ? 'active' : '')}
onclick={() => props.cbSelectRoom(room.id)}
>
<div class="room-avatar">
{room.name.charAt(0).toUpperCase()}
</div>
<div class="room-info">
<span class="room-name">{room.name}</span>
<span class="room-model">{room.model_id.split('/').pop()}</span>
</div>
</div>
<div if={!props.rooms || props.rooms.length === 0} class="empty-rooms">
<p>No rooms yet</p>
<button class="btn btn-primary btn-sm" onclick={props.cbCreateRoom}>Create one</button>
</div>
</div>
<div class="sidebar-footer">
<div class="user-info">
<div class="user-avatar">
{props.user?.display_name?.charAt(0).toUpperCase()}
</div>
<span class="user-name">{props.user?.display_name}</span>
</div>
<button class="btn btn-ghost btn-icon" onclick={props.cbLogout} title="Logout">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
<polyline points="16 17 21 12 16 7"/>
<line x1="21" y1="12" x2="9" y2="12"/>
</svg>
</button>
</div>
</aside>
<style>
.sidebar {
width: var(--sidebar-width);
min-width: var(--sidebar-width);
height: 100%;
background: var(--bg-secondary);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
}
.sidebar-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-md) var(--space-md);
border-bottom: 1px solid var(--border);
height: var(--header-height);
}
.sidebar-header h2 {
font-size: var(--text-lg);
font-weight: 600;
color: var(--accent);
}
.btn-icon {
width: 36px;
height: 36px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-md);
}
.room-list {
flex: 1;
overflow-y: auto;
padding: var(--space-sm);
}
.room-item {
display: flex;
align-items: center;
gap: var(--space-sm);
padding: var(--space-sm) var(--space-md);
border-radius: var(--radius-md);
cursor: pointer;
transition: background var(--transition-fast);
}
.room-item:hover {
background: var(--bg-hover);
}
.room-item.active {
background: var(--accent-subtle);
}
.room-avatar {
width: 36px;
height: 36px;
border-radius: var(--radius-md);
background: var(--bg-elevated);
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: var(--text-sm);
color: var(--accent);
flex-shrink: 0;
}
.room-info {
min-width: 0;
display: flex;
flex-direction: column;
}
.room-name {
font-size: var(--text-sm);
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.room-model {
font-size: var(--text-xs);
color: var(--text-muted);
}
.empty-rooms {
text-align: center;
padding: var(--space-xl);
color: var(--text-muted);
}
.empty-rooms p {
margin-bottom: var(--space-md);
font-size: var(--text-sm);
}
.btn-sm {
padding: var(--space-xs) var(--space-md);
font-size: var(--text-sm);
}
.sidebar-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-sm) var(--space-md);
border-top: 1px solid var(--border);
}
.user-info {
display: flex;
align-items: center;
gap: var(--space-sm);
min-width: 0;
}
.user-avatar {
width: 32px;
height: 32px;
border-radius: var(--radius-full);
background: var(--accent);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: var(--text-sm);
flex-shrink: 0;
}
.user-name {
font-size: var(--text-sm);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
<script>
export default {}
</script>
</chat-sidebar>