Skip to content

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-chat ChatSidebar component (the owner conversation/teams history panel rendered inside GnosariChat). The in-chat ChatSidebar is an owner-only authenticated feature controlled by internal pages; it has no operator-configurable theme or agent config surface. See app/CLAUDE.md for 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:

  1. Chat Bubble: Floating action button (usually bottom-right corner)
  2. 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

PropTypeDefaultDescription
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
iconstring'i-heroicons-chat-bubble-left-right'Icon to display
tooltipTextstring'Chat'Tooltip shown on hover
showTooltipbooleantrueShow/hide tooltip
showBadgebooleanfalseShow notification badge
badgeContentstring | number-Badge content (e.g., "3" for 3 messages)
agentIdentifierstring-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

PropTypeDefaultDescription
titlestring'Chat'Sidebar header title
subtitlestring-Optional subtitle
width'sm' | 'md' | 'lg' | 'xl' | 'full''lg'Sidebar width
closeOnBackdropbooleantrueClose when clicking backdrop
closeOnEscapebooleantrueClose with ESC key
hideWelcomebooleantrueHide welcome modal in chat
showMinimizebooleanfalseShow minimize button
showStatusbooleantrueShow connection status
showFooterbooleantrueShow "Powered by Gnosari" footer

Events

EventPayloadDescription
ready-Chat initialized and ready
errorErrorError occurred
message-sentstringUser sent message
closed-Sidebar closed

Width Sizes

SizeWidthBest For
sm400pxMobile-optimized, minimal space
md500pxStandard chat width
lg640pxRecommended default (shows more context)
xl800pxWide conversations, multiple agents
full100vwFull-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

MethodParametersDescription
open()config: { agentIdentifier?, autoMessage? }Open sidebar with agent
close()-Close sidebar
toggle()-Toggle open/closed
setSessionId()sessionId: stringSet active session
clearSession()-Clear session data
openChatFullscreen()-Open in new tab (preserve session)

Computed Properties

PropertyTypeDescription
isOpenComputedRef<boolean>Sidebar visibility state
agentIdentifierComputedRef<string>Current agent identifier
autoMessageComputedRef<string | undefined>Auto-message if set
sessionIdComputedRef<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

  1. Session Created: When user sends first message
  2. Session ID Saved: Stored in state and localStorage
  3. Page Navigation: User navigates to different page
  4. Session Preserved: When sidebar reopens, session resumes
  5. 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" />

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

KeyAction
TabNavigate within sidebar
Shift+TabNavigate backwards
ESCClose sidebar
EnterSend message (from input)
Shift+EnterNew 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 contexts

Auto-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 conversation

Troubleshooting

Problem: Click chat bubble, nothing happens

Solutions:

  1. Check console for JavaScript errors
  2. Verify useChatSidebar() is initialized in layout
  3. Ensure GnosariAppChatSidebar component exists in layout
  4. Check if agentIdentifier is valid

Session Not Persisting

Problem: Conversation lost on page navigation

Solutions:

  1. Check localStorage is enabled
  2. Verify setSessionId() is called on session creation
  3. Check localStorage key: chat_session_id
  4. Ensure sessionId passed to chat component

Problem: Sidebar appears under other elements

Solutions:

  1. Check z-index hierarchy
  2. Ensure sidebar has z-50 or higher
  3. Remove conflicting positioned elements
  4. Use browser DevTools to inspect stacking context

Animation Glitches

Problem: Sidebar animation stutters or jumps

Solutions:

  1. Check for CSS conflicts
  2. Disable GPU acceleration if issues persist
  3. Reduce backdrop blur on low-end devices
  4. Test with transition: none to 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