Appearance
Chat Sidebar System
Distinction: This document describes the app-level global sidebar launcher (
ChatBubble/GnosariAppChatSidebar/useChatSidebar()) — a floating button + slide-in panel widget planned for the Gnosari owner app. This is not the same as the in-chatChatSidebarcomponent (the owner conversation/teams history panel rendered insideGnosariChat). The in-chatChatSidebaris an owner-only authenticated feature controlled by internal pages; it has no operator-configurable theme or agent config surface. Seeapp/CLAUDE.mdfor the auth-gate gotcha.
Status: The app-level sidebar launcher described below (
ChatBubble,GnosariAppChatSidebar,useChatSidebar) is a planned feature. The components and composable are not yet implemented in the codebase. This document describes the intended API for when the feature ships.
The Chat Sidebar System will provide a globally accessible chat interface that can be triggered from any page — perfect for always-available AI assistance.
Overview
The sidebar system consists of two main components:
- Chat Bubble: Floating action button (usually bottom-right corner)
- Chat Sidebar: Slide-in panel with full chat interface
Key Features:
- ✅ Globally accessible from any page
- ✅ Smooth animations with backdrop blur
- ✅ Fully responsive (mobile and desktop)
- ✅ Keyboard accessible (ESC to close, ARIA support)
- ✅ Session persistence across page navigations
- ✅ Customizable appearance and position
Quick Start
The chat sidebar is already integrated in the default layout - just click the floating chat bubble!
Default Setup
Location: Bottom-right corner Appearance: Floating circular button with chat icon Behavior: Click to open sidebar, click backdrop or ESC to close
Pre-configured:
vue
<!-- Already in layouts/default.vue -->
<ChatBubble
position="bottom-right"
size="lg"
color="primary"
tooltip-text="Chat with AI Assistant"
agent-identifier="agent_setup_assistant"
/>Components
1. ChatBubble
Location: ~/components/ui/ChatBubble.vue
Purpose: Floating action button that opens the chat sidebar
Props
| Prop | Type | Default | Description |
|---|---|---|---|
position | 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | 'bottom-right' | Screen corner position |
size | 'sm' | 'md' | 'lg' | 'lg' | Button size |
color | 'primary' | 'blue' | 'purple' | 'green' | 'orange' | 'primary' | Button color theme |
icon | string | 'i-heroicons-chat-bubble-left-right' | Icon to display |
tooltipText | string | 'Chat' | Tooltip shown on hover |
showTooltip | boolean | true | Show/hide tooltip |
showBadge | boolean | false | Show notification badge |
badgeContent | string | number | - | Badge content (e.g., "3" for 3 messages) |
agentIdentifier | string | - | Default agent to open chat with |
Features
Visual Effects:
- Smooth pulse animation when idle
- Hover scale transformation (grows slightly)
- Tooltip on hover with customizable text
- Notification badge support (for unread messages)
- Gradient background matching color theme
Accessibility:
- ARIA label for screen readers
- Keyboard navigation support
- Focus visible indicator
- Role and state attributes
Behavior:
- Automatically hides when sidebar is open
- Re-appears when sidebar closes
- Maintains z-index to stay on top
Example Usage
Basic:
vue
<ChatBubble
position="bottom-right"
agent-identifier="support-agent"
/>With Notification:
vue
<ChatBubble
position="bottom-left"
size="md"
color="blue"
:show-badge="true"
badge-content="5"
tooltip-text="5 new messages"
agent-identifier="notifications-agent"
/>Custom Icon:
vue
<ChatBubble
position="top-right"
icon="i-heroicons-question-mark-circle"
tooltip-text="Get Help"
color="green"
agent-identifier="help-agent"
/>2. GnosariAppChatSidebar
Location: ~/components/chat/GnosariAppChatSidebar.vue
Purpose: Sidebar container with full chat interface
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | 'Chat' | Sidebar header title |
subtitle | string | - | Optional subtitle |
width | 'sm' | 'md' | 'lg' | 'xl' | 'full' | 'lg' | Sidebar width |
closeOnBackdrop | boolean | true | Close when clicking backdrop |
closeOnEscape | boolean | true | Close with ESC key |
hideWelcome | boolean | true | Hide welcome modal in chat |
showMinimize | boolean | false | Show minimize button |
showStatus | boolean | true | Show connection status |
showFooter | boolean | true | Show "Powered by Gnosari" footer |
Events
| Event | Payload | Description |
|---|---|---|
ready | - | Chat initialized and ready |
error | Error | Error occurred |
message-sent | string | User sent message |
closed | - | Sidebar closed |
Width Sizes
| Size | Width | Best For |
|---|---|---|
sm | 400px | Mobile-optimized, minimal space |
md | 500px | Standard chat width |
lg | 640px | Recommended default (shows more context) |
xl | 800px | Wide conversations, multiple agents |
full | 100vw | Full-screen takeover (mobile) |
Features
Animations:
- Smooth slide-in from right edge
- Backdrop fade-in with blur effect
- Transition duration: 300ms ease-out
Interactions:
- Click backdrop to close (configurable)
- Press ESC to close (configurable)
- Drag header to reposition (future feature)
Accessibility:
- Focus trap when open (Tab stays within sidebar)
- ARIA modal attributes
- Screen reader announcements
- Keyboard navigation
Example Usage
Basic:
vue
<GnosariAppChatSidebar
title="Support Assistant"
width="lg"
/>Custom Configuration:
vue
<GnosariAppChatSidebar
title="AI Helper"
subtitle="Available 24/7"
width="xl"
:close-on-backdrop="false"
:show-footer="false"
@ready="handleChatReady"
@message-sent="trackMessage"
@closed="handleChatClosed"
/>Global State Management
useChatSidebar() Composable
Location: ~/composables/features/useChatSidebar.ts
Purpose: Global state for sidebar visibility and configuration
State
typescript
interface ChatSidebarState {
isOpen: boolean // Sidebar visibility
agentIdentifier: string // Current agent ID
autoMessage?: string // Auto-send message on open
sessionId?: string // Active session ID
}Methods
| Method | Parameters | Description |
|---|---|---|
open() | config: { agentIdentifier?, autoMessage? } | Open sidebar with agent |
close() | - | Close sidebar |
toggle() | - | Toggle open/closed |
setSessionId() | sessionId: string | Set active session |
clearSession() | - | Clear session data |
openChatFullscreen() | - | Open in new tab (preserve session) |
Computed Properties
| Property | Type | Description |
|---|---|---|
isOpen | ComputedRef<boolean> | Sidebar visibility state |
agentIdentifier | ComputedRef<string> | Current agent identifier |
autoMessage | ComputedRef<string | undefined> | Auto-message if set |
sessionId | ComputedRef<string | undefined> | Active session ID |
Usage Patterns
Opening the Sidebar
From Any Component
vue
<template>
<UButton @click="openChat">
Ask AI Assistant
</UButton>
</template>
<script setup lang="ts">
const chatSidebar = useChatSidebar()
const openChat = () => {
chatSidebar.open({
agentIdentifier: 'support-agent'
})
}
</script>With Auto-Message
Automatically send a message when opening:
vue
<template>
<UButton @click="askAboutPricing">
Ask About Pricing
</UButton>
</template>
<script setup lang="ts">
const chatSidebar = useChatSidebar()
const askAboutPricing = () => {
chatSidebar.open({
agentIdentifier: 'sales-agent',
autoMessage: 'What are your pricing plans?'
})
}
</script>From Multiple Agents
Different buttons open different agents:
vue
<template>
<div class="flex gap-4">
<UButton @click="openSupport">Support</UButton>
<UButton @click="openSales">Sales</UButton>
<UButton @click="openHelp">Help</UButton>
</div>
</template>
<script setup lang="ts">
const chatSidebar = useChatSidebar()
const openSupport = () => chatSidebar.open({ agentIdentifier: 'support' })
const openSales = () => chatSidebar.open({ agentIdentifier: 'sales' })
const openHelp = () => chatSidebar.open({ agentIdentifier: 'help' })
</script>Closing the Sidebar
vue
<script setup lang="ts">
const chatSidebar = useChatSidebar()
// Programmatically close
const closeChat = () => {
chatSidebar.close()
}
// Also closes via:
// - Click backdrop
// - Press ESC key
// - Click X button in header
</script>Toggling Sidebar
vue
<template>
<UButton @click="chatSidebar.toggle()">
{{ chatSidebar.isOpen.value ? 'Close' : 'Open' }} Chat
</UButton>
</template>
<script setup lang="ts">
const chatSidebar = useChatSidebar()
</script>Session Persistence
The sidebar maintains conversation continuity across page navigations.
How It Works
- Session Created: When user sends first message
- Session ID Saved: Stored in state and localStorage
- Page Navigation: User navigates to different page
- Session Preserved: When sidebar reopens, session resumes
- Conversation Continues: Full message history restored
Implementation
Automatic Handling:
The composable handles persistence automatically:
typescript
// On session creation
onSessionCreated: (data) => {
setSessionId(data.sessionId)
localStorage.setItem('chat_session_id', data.sessionId)
}
// On initialization
const savedSessionId = localStorage.getItem('chat_session_id')
if (savedSessionId) {
config.sessionId = savedSessionId // Resume session
}Manual Control:
vue
<script setup lang="ts">
const chatSidebar = useChatSidebar()
// Clear session (start fresh conversation)
const startNewChat = () => {
chatSidebar.clearSession()
chatSidebar.open({ agentIdentifier: 'support' })
}
// Get current session ID
const currentSession = computed(() => chatSidebar.sessionId.value)
</script>Opening in Fullscreen
Transition from sidebar to full-screen tab while preserving session:
vue
<template>
<UButton @click="chatSidebar.openChatFullscreen()">
Open in New Tab
</UButton>
</template>
<script setup lang="ts">
const chatSidebar = useChatSidebar()
// Opens: /chat?sessionId=ui_1704067200000_abc123
// Full conversation history loads in new tab
</script>Customization
Position Variants
Place chat bubble in different screen corners:
vue
<!-- Bottom-right (default) -->
<ChatBubble position="bottom-right" />
<!-- Bottom-left -->
<ChatBubble position="bottom-left" />
<!-- Top-right -->
<ChatBubble position="top-right" />
<!-- Top-left -->
<ChatBubble position="top-left" />Size Variants
Adjust button size:
vue
<!-- Small (48px) -->
<ChatBubble size="sm" />
<!-- Medium (56px) -->
<ChatBubble size="md" />
<!-- Large (64px, default) -->
<ChatBubble size="lg" />Color Themes
Match your brand colors:
vue
<!-- Primary (purple gradient) -->
<ChatBubble color="primary" />
<!-- Blue -->
<ChatBubble color="blue" />
<!-- Purple -->
<ChatBubble color="purple" />
<!-- Green -->
<ChatBubble color="green" />
<!-- Orange -->
<ChatBubble color="orange" />Sidebar Widths
Choose appropriate width:
vue
<!-- Narrow (mobile-friendly) -->
<GnosariAppChatSidebar width="sm" />
<!-- Standard -->
<GnosariAppChatSidebar width="md" />
<!-- Recommended default -->
<GnosariAppChatSidebar width="lg" />
<!-- Extra wide -->
<GnosariAppChatSidebar width="xl" />
<!-- Fullscreen -->
<GnosariAppChatSidebar width="full" />Advanced Features
Notification Badge
Show unread message count:
vue
<template>
<ChatBubble
:show-badge="hasUnread"
:badge-content="unreadCount"
tooltip-text="`${unreadCount} new messages`"
/>
</template>
<script setup lang="ts">
const unreadCount = ref(3)
const hasUnread = computed(() => unreadCount.value > 0)
// Update count when new messages arrive
watch(newMessageEvent, () => {
unreadCount.value++
})
// Clear count when sidebar opens
watch(() => chatSidebar.isOpen.value, (isOpen) => {
if (isOpen) unreadCount.value = 0
})
</script>Event Tracking
Track user interactions:
vue
<template>
<GnosariAppChatSidebar
@ready="trackChatReady"
@message-sent="trackMessage"
@closed="trackClose"
/>
</template>
<script setup lang="ts">
const trackChatReady = () => {
analytics.track('Chat Opened')
}
const trackMessage = (message: string) => {
analytics.track('Chat Message Sent', {
message_length: message.length,
agent: chatSidebar.agentIdentifier.value
})
}
const trackClose = () => {
analytics.track('Chat Closed', {
duration: calculateDuration(),
messages_sent: messageCount.value
})
}
</script>Conditional Display
Show chat bubble only in certain conditions:
vue
<template>
<ChatBubble
v-if="shouldShowChat"
:agent-identifier="currentAgent"
/>
</template>
<script setup lang="ts">
const route = useRoute()
const auth = useAuth()
const shouldShowChat = computed(() => {
// Show only on specific pages
if (!route.path.startsWith('/dashboard')) return false
// Show only for logged-in users
if (!auth.isAuthenticated.value) return false
// Show only during business hours
const hour = new Date().getHours()
if (hour < 9 || hour > 17) return false
return true
})
const currentAgent = computed(() => {
// Different agents for different pages
if (route.path.includes('billing')) return 'billing-support'
if (route.path.includes('technical')) return 'tech-support'
return 'general-support'
})
</script>Responsive Behavior
Mobile
On small screens (< 640px):
- Chat Bubble: Slightly smaller, bottom-right
- Sidebar: Full-width (100vw), slides from bottom
- Backdrop: Darker overlay (60% opacity)
- Close: Swipe down to close (future feature)
Tablet
On medium screens (640px - 1024px):
- Chat Bubble: Standard size
- Sidebar: 500px width, slides from right
- Backdrop: Standard overlay
Desktop
On large screens (> 1024px):
- Chat Bubble: Full size, customizable position
- Sidebar: Up to 800px width, slides from right
- Backdrop: Subtle blur effect
Accessibility
Keyboard Navigation
| Key | Action |
|---|---|
| Tab | Navigate within sidebar |
| Shift+Tab | Navigate backwards |
| ESC | Close sidebar |
| Enter | Send message (from input) |
| Shift+Enter | New line (in message input) |
Screen Readers
- ARIA Labels: All interactive elements labeled
- Live Regions: New messages announced
- Focus Management: Focus trapped in sidebar when open
- Roles: Proper semantic roles (dialog, button, etc.)
Focus Indicators
- Visible outlines on keyboard focus
- Skip to input link for quick access
- Restore focus to trigger button when closing
Best Practices
Agent Selection
Choose appropriate agents for different contexts:
✅ Good:
- Support pages → Support Agent
- Pricing pages → Sales Agent
- Docs pages → Documentation Agent
❌ Avoid:
- Random agent on all pages
- Same agent for all contextsAuto-Message Usage
Use auto-messages thoughtfully:
✅ Good:
openChat({
agentIdentifier: 'sales',
autoMessage: 'I'm interested in the Enterprise plan'
})
❌ Avoid:
openChat({
autoMessage: 'Hi' // Too generic
})Session Management
Provide clear session controls:
✅ Good:
- Show "Start New Chat" button
- Display current session info
- Persist sessions across pages
❌ Avoid:
- Force new session on every open
- Lose session on page refresh
- No way to start fresh conversationTroubleshooting
Sidebar Not Opening
Problem: Click chat bubble, nothing happens
Solutions:
- Check console for JavaScript errors
- Verify
useChatSidebar()is initialized in layout - Ensure
GnosariAppChatSidebarcomponent exists in layout - Check if
agentIdentifieris valid
Session Not Persisting
Problem: Conversation lost on page navigation
Solutions:
- Check localStorage is enabled
- Verify
setSessionId()is called on session creation - Check localStorage key:
chat_session_id - Ensure sessionId passed to chat component
Sidebar Behind Content
Problem: Sidebar appears under other elements
Solutions:
- Check z-index hierarchy
- Ensure sidebar has
z-50or higher - Remove conflicting positioned elements
- Use browser DevTools to inspect stacking context
Animation Glitches
Problem: Sidebar animation stutters or jumps
Solutions:
- Check for CSS conflicts
- Disable GPU acceleration if issues persist
- Reduce backdrop blur on low-end devices
- Test with
transition: noneto isolate issue
What's Next?
- Widget Embedding: Embed chat sidebar on external websites
- Multi-Agent Sidebar: Switch between agents in sidebar
- Advanced Theming: Custom CSS for sidebar styling
- Mobile Swipe: Swipe gestures for opening/closing