Skip to content

Standalone Components API ​

The standalone components provide a flexible, store-independent way to build chat interfaces. These components accept data via props and communicate through events, making them perfect for integrating into existing applications with custom state management.

Overview ​

Standalone components are designed for maximum flexibility:

  • ✅ No store dependencies - Manage state however you want
  • ✅ Props-based - Pass data directly to components
  • ✅ Event-driven - Handle user interactions via events
  • ✅ Fully styled - Beautiful UI out of the box
  • ✅ TypeScript support - Full type safety

Installation ​

bash
npm install @sprout_ai_labs/sidekick

Import Standalone Components ​

typescript
import {
  SidekickMessages,
  SidekickChatInput,
  SidekickCollapsibleThought,
  SidekickCollapsibleThoughtStreaming
} from '@sprout_ai_labs/sidekick'

// Don't forget the CSS
import '@sprout_ai_labs/sidekick/dist/sprout-sidekick.css'

SidekickMessages ​

Displays a list of chat messages with support for user messages, bot responses, thinking processes, and streaming.

Props ​

PropTypeDefaultDescription
messagesChatMessage[][]Array of chat messages to display
isTypingbooleanfalseShow typing indicator
currentThoughtsThoughtStep[][]Real-time thoughts during streaming
currentChunkedMessagestring''Streaming message content
streamingAnimatingbooleanfalseEnable shimmer animation effect

Types ​

typescript
interface ChatMessage {
  id: string
  sender: 'user' | 'bot' | 'ticket'
  text: string
  isError?: boolean
  thoughtSteps?: ThoughtStep[]
  thinkingDuration?: number
  kbSources?: KBSource[]
  analyticsId?: string
}

interface ThoughtStep {
  event: string
  message: string
  agent_name?: string
  agent_id?: string
  tool_details?: Record<string, unknown>
  completed: boolean
}

interface KBSource {
  id: string
  title: string
  url?: string
  snippet?: string
}

Usage Example ​

vue
<template>
  <div class="messages-container">
    <SidekickMessages
      :messages="messages"
      :is-typing="isTyping"
      :current-thoughts="currentThoughts"
      :current-chunked-message="currentChunkedMessage"
      :streaming-animating="streamingAnimating"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { SidekickMessages } from '@sprout_ai_labs/sidekick'
import type { ChatMessage } from '@sprout_ai_labs/sidekick'

const messages = ref<ChatMessage[]>([
  {
    id: '1',
    sender: 'user',
    text: 'How do I use standalone components?'
  },
  {
    id: '2',
    sender: 'bot',
    text: 'Standalone components are easy to use! Just pass data via props.',
    thoughtSteps: [
      { message: 'Analyzing query', completed: true, event: 'process' },
      { message: 'Searching docs', completed: true, event: 'tool_call' }
    ],
    thinkingDuration: 3
  }
])

const isTyping = ref(false)
const currentThoughts = ref([])
const currentChunkedMessage = ref('')
const streamingAnimating = ref(false)
</script>

Features ​

  • Auto-scroll: Automatically scrolls to the latest message
  • Markdown rendering: Supports markdown in bot messages
  • Thought processes: Shows collapsible thinking steps
  • Knowledge sources: Displays KB sources with citations
  • Error handling: Displays error messages with appropriate styling
  • Streaming support: Shows real-time message streaming with animations

SidekickChatInput ​

A beautiful, auto-expanding text input for chat messages with placeholder animation support.

Props ​

PropTypeDefaultDescription
placeholderstring'Ask me anything...'Default placeholder text
placeholdersstring[][]Array of placeholders to cycle through
disabledbooleanfalseDisable input
showClearButtonbooleantrueShow clear button
maxHeightstring'120px'Maximum input height
minHeightstring'42px'Minimum input height
autoFocusbooleanfalseAuto-focus on mount

Events ​

EventPayloadDescription
submit(message: string)Fired when user submits a message
input(value: string)Fired on input change
clear()Fired when clear button is clicked

Usage Example ​

vue
<template>
  <SidekickChatInput
    :placeholder="placeholder"
    :placeholders="placeholderOptions"
    :disabled="isLoading"
    :show-clear-button="true"
    :auto-focus="true"
    @submit="handleSubmit"
    @input="handleInput"
    @clear="handleClear"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { SidekickChatInput } from '@sprout_ai_labs/sidekick'

const placeholder = ref('Ask me anything...')
const placeholderOptions = ref([
  'What can you help with?',
  'Ask a question...',
  'Need assistance?'
])
const isLoading = ref(false)

function handleSubmit(message: string) {
  console.log('User submitted:', message)
  // Send message to your API
  sendMessage(message)
}

function handleInput(value: string) {
  console.log('User is typing:', value)
}

function handleClear() {
  console.log('Input cleared')
}

async function sendMessage(message: string) {
  isLoading.value = true
  // Your API call logic here
  isLoading.value = false
}
</script>

Features ​

  • Auto-expanding: Grows as user types (up to maxHeight)
  • Animated placeholders: Cycles through placeholder text with typing animation
  • Keyboard shortcuts:
    • Enter - Submit message
    • Shift + Enter - New line
  • Clear button: Quick way to clear input
  • Paste handling: Smart paste with formatting cleanup
  • Disabled state: Visual feedback when disabled

SidekickCollapsibleThought ​

Displays completed AI thinking processes in a collapsible format.

Props ​

PropTypeDefaultDescription
stepsThinkingStep[][]Array of thinking steps
durationnumber0Thinking duration in seconds

Types ​

typescript
interface ThinkingStep {
  text: string           // The step message
  completed: boolean     // Whether step is complete
  event?: string         // Event type: 'tool_call', 'observation', 'process', etc.
  agent_name?: string    // Optional agent name
  tool_details?: Record<string, unknown>  // Optional tool metadata
}

Usage Example ​

vue
<template>
  <SidekickCollapsibleThought
    :steps="thoughtSteps"
    :duration="thinkingDuration"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { SidekickCollapsibleThought } from '@sprout_ai_labs/sidekick'

const thoughtSteps = ref([
  {
    text: 'Analyzing user query for intent',
    completed: true,
    event: 'process'
  },
  {
    text: 'Using Document Retriever Tool to search knowledge base',
    completed: true,
    event: 'tool_call'
  },
  {
    text: 'Found 12 relevant documents matching criteria',
    completed: true,
    event: 'observation'
  },
  {
    text: 'Synthesizing information from sources',
    completed: true,
    event: 'process'
  }
])

const thinkingDuration = ref(5)
</script>

Features ​

  • Collapsed by default: Keeps UI clean
  • Smart header: Shows "Thought for X seconds" or last step message
  • Event icons: Different icons for different event types
  • Markdown stripping: Header text strips markdown for clean display
  • Truncation: Long headers truncate to 60 characters
  • See more/less: Long steps can be expanded
  • All steps completed: Shows checkmarks for all steps

SidekickCollapsibleThoughtStreaming ​

Displays real-time AI thinking processes with animations during streaming.

Props ​

PropTypeDefaultDescription
stepsThinkingStep[][]Array of thinking steps
durationnumber0Thinking duration (used when completed)

Types ​

typescript
interface ThinkingStep {
  message: string        // Step message (note: 'message' not 'text')
  completed: boolean     // Last step should be false during streaming
  event?: string         
  agent_name?: string
  tool_details?: Record<string, unknown>
}

Usage Example ​

vue
<template>
  <SidekickCollapsibleThoughtStreaming
    :steps="liveThoughts"
    :duration="streamingDuration"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { SidekickCollapsibleThoughtStreaming } from '@sprout_ai_labs/sidekick'

const liveThoughts = ref([
  {
    message: 'Initializing request',
    completed: true,
    event: 'start'
  },
  {
    message: 'Analyzing context and generating response',
    completed: false,  // Last step incomplete during streaming
    event: 'process'
  }
])

const streamingDuration = ref(0)

// As you receive streaming updates:
function handleStreamingUpdate(newThought: any) {
  if (newThought) {
    liveThoughts.value.push({
      message: newThought.message,
      completed: newThought.completed,
      event: newThought.event
    })
  }
}
</script>

Features ​

  • Shimmer animation: Animated text in header during streaming
  • Loading spinner: Shows on last incomplete step
  • Cycling loaders: When collapsed, cycles through predefined messages
  • Real-time updates: Updates as new thoughts arrive
  • Smooth transitions: Animated state changes
  • Completion state: Shows final "Thought for X seconds" when done

Complete Chat Interface Example ​

Here's a complete example combining all standalone components:

vue
<template>
  <div class="chat-interface">
    <!-- Messages Area -->
    <div class="messages-wrapper">
      <SidekickMessages
        :messages="messages"
        :is-typing="isTyping"
        :current-thoughts="currentThoughts"
        :current-chunked-message="currentChunkedMessage"
        :streaming-animating="streamingAnimating"
      />
    </div>

    <!-- Input Area -->
    <div class="input-wrapper">
      <SidekickChatInput
        placeholder="Ask me anything..."
        :placeholders="['What can you help with?', 'Ask a question...']"
        :disabled="isLoading"
        @submit="handleSubmit"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import {
  SidekickMessages,
  SidekickChatInput,
  type ChatMessage
} from '@sprout_ai_labs/sidekick'

// State
const messages = ref<ChatMessage[]>([])
const isTyping = ref(false)
const currentThoughts = ref([])
const currentChunkedMessage = ref('')
const streamingAnimating = ref(false)
const isLoading = ref(false)

// Handle message submission
async function handleSubmit(message: string) {
  // Add user message
  messages.value.push({
    id: Date.now().toString(),
    sender: 'user',
    text: message
  })
  
  // Start streaming
  isTyping.value = true
  streamingAnimating.value = true
  isLoading.value = true
  
  try {
    // Your API call here
    await streamResponse(message)
  } finally {
    isTyping.value = false
    streamingAnimating.value = false
    isLoading.value = false
  }
}

async function streamResponse(userMessage: string) {
  // Implement your streaming logic here
  // Update currentThoughts and currentChunkedMessage as data arrives
  
  // Example:
  currentThoughts.value = [
    { message: 'Processing request', completed: true, event: 'start' },
    { message: 'Generating response', completed: false, event: 'process' }
  ]
  
  // Simulate streaming
  const response = 'This is a simulated response...'
  for (let i = 0; i < response.length; i++) {
    currentChunkedMessage.value += response[i]
    await new Promise(resolve => setTimeout(resolve, 30))
  }
  
  // Complete the message
  messages.value.push({
    id: Date.now().toString(),
    sender: 'bot',
    text: currentChunkedMessage.value,
    thoughtSteps: currentThoughts.value.map(t => ({ ...t, completed: true })),
    thinkingDuration: 3
  })
  
  // Reset streaming state
  currentThoughts.value = []
  currentChunkedMessage.value = ''
}
</script>

<style scoped>
.chat-interface {
  height: 100vh;
  display: flex;
  flex-direction: column;
}

.messages-wrapper {
  flex: 1;
  overflow-y: auto;
  background: #f9fafb;
  padding: 1rem;
}

.input-wrapper {
  border-top: 1px solid #e5e7eb;
  background: white;
  padding: 1rem;
}
</style>

Event Type Icons ​

The collapsible thought components automatically map event types to icons:

Event TypeIconDescription
tool_call🔧Tool/function calls
observation🔍Search results, observations
process⚙️Processing, thinking
route_to🔀Routing to agents
start▶️Starting actions
Default✓Completed steps

Styling ​

All standalone components come pre-styled with the Sidekick design system. CSS classes use the scp- prefix to avoid conflicts:

css
/* Main containers */
.scp-messages
.scp-message-bubble
.scp-user-message-text
.scp-ai-message-text

/* Input */
.sidekick-chat-input-container
.sidekick-chat-input-editable

/* Thoughts */
.collapsible-thought
.thought-header
.thought-steps
.step-icon

Custom Styling ​

You can override styles by targeting these classes:

css
/* Example: Customize message bubble */
.scp-user-message-text {
  background: #your-brand-color !important;
}

/* Example: Customize input */
.sidekick-chat-input-editable {
  font-size: 16px;
  padding: 12px;
}

Best Practices ​

1. Message Management ​

typescript
// Generate unique IDs
const generateId = () => `msg-${Date.now()}-${Math.random()}`

// Add messages immutably
messages.value = [...messages.value, newMessage]

2. Streaming Handling ​

typescript
// Always reset streaming state after completion
function completeStreaming() {
  isTyping.value = false
  streamingAnimating.value = false
  currentThoughts.value = []
  currentChunkedMessage.value = ''
}

3. Error Handling ​

typescript
// Add error messages
messages.value.push({
  id: generateId(),
  sender: 'bot',
  text: 'Sorry, something went wrong.',
  isError: true
})

4. Performance ​

typescript
// Limit message history if needed
if (messages.value.length > 100) {
  messages.value = messages.value.slice(-50)
}

TypeScript Support ​

All standalone components are fully typed. Import types from the package:

typescript
import type {
  ChatMessage,
  ChatState,
  KBSource
} from '@sprout_ai_labs/sidekick'

Comparison: Unified vs Standalone ​

FeatureUnified ComponentsStandalone Components
StoreBuilt-in Pinia storeNo store, you manage state
SetupPlugin installDirect import
StateAutomaticManual via props
FlexibilityLessMore
Use CaseQuick setup, standard behaviorCustom state, existing apps

Choose Unified for quick setup with built-in state management.
Choose Standalone for maximum control and custom state management.

Released under the MIT License.