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
npm install @sprout_ai_labs/sidekickImport Standalone Components
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
| Prop | Type | Default | Description |
|---|---|---|---|
messages | ChatMessage[] | [] | Array of chat messages to display |
isTyping | boolean | false | Show typing indicator |
currentThoughts | ThoughtStep[] | [] | Real-time thoughts during streaming |
currentChunkedMessage | string | '' | Streaming message content |
streamingAnimating | boolean | false | Enable shimmer animation effect |
Types
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
<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
| Prop | Type | Default | Description |
|---|---|---|---|
placeholder | string | 'Ask me anything...' | Default placeholder text |
placeholders | string[] | [] | Array of placeholders to cycle through |
disabled | boolean | false | Disable input |
showClearButton | boolean | true | Show clear button |
maxHeight | string | '120px' | Maximum input height |
minHeight | string | '42px' | Minimum input height |
autoFocus | boolean | false | Auto-focus on mount |
Events
| Event | Payload | Description |
|---|---|---|
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
<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 messageShift + 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
| Prop | Type | Default | Description |
|---|---|---|---|
steps | ThinkingStep[] | [] | Array of thinking steps |
duration | number | 0 | Thinking duration in seconds |
Types
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
<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
| Prop | Type | Default | Description |
|---|---|---|---|
steps | ThinkingStep[] | [] | Array of thinking steps |
duration | number | 0 | Thinking duration (used when completed) |
Types
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
<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:
<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 Type | Icon | Description |
|---|---|---|
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:
/* 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-iconCustom Styling
You can override styles by targeting these classes:
/* 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
// Generate unique IDs
const generateId = () => `msg-${Date.now()}-${Math.random()}`
// Add messages immutably
messages.value = [...messages.value, newMessage]2. Streaming Handling
// Always reset streaming state after completion
function completeStreaming() {
isTyping.value = false
streamingAnimating.value = false
currentThoughts.value = []
currentChunkedMessage.value = ''
}3. Error Handling
// Add error messages
messages.value.push({
id: generateId(),
sender: 'bot',
text: 'Sorry, something went wrong.',
isError: true
})4. Performance
// 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:
import type {
ChatMessage,
ChatState,
KBSource
} from '@sprout_ai_labs/sidekick'Comparison: Unified vs Standalone
| Feature | Unified Components | Standalone Components |
|---|---|---|
| Store | Built-in Pinia store | No store, you manage state |
| Setup | Plugin install | Direct import |
| State | Automatic | Manual via props |
| Flexibility | Less | More |
| Use Case | Quick setup, standard behavior | Custom state, existing apps |
Choose Unified for quick setup with built-in state management.
Choose Standalone for maximum control and custom state management.