guideants
v0.6.14
Published
Framework-agnostic web component for a published guides chat UI with server-side proxy support
Maintainers
Readme
GuideAnts Chat Component
A framework-agnostic web component for embedding AI-powered conversations from published GuideAnts Notebooks into any website or application.
About GuideAnts
GuideAnts is an AI-powered notebook platform that enables teams to create and share intelligent assistants. This component allows you to embed conversations from published guides directly into your website.
- Application: go.guideants.ai - Create and manage your guides
- Website: www.guideants.ai - Learn more about GuideAnts
Installation
npm install guideantsQuick Start
Basic Usage (IIFE)
<!DOCTYPE html>
<html>
<head>
<title>GuideAnts Chat</title>
</head>
<body>
<!-- Include the component -->
<script src="node_modules/guideants/dist/guideants-chat.iife.js"></script>
<!-- Use the component (api-base-url is optional, defaults to production) -->
<guideants-chat
pub-id="your-published-guide-id">
</guideants-chat>
<!-- Or specify a custom API URL -->
<guideants-chat
api-base-url="https://api.guideants.ai"
pub-id="your-published-guide-id">
</guideants-chat>
</body>
</html>ES Module Usage
<script type="module">
import 'guideants/dist/guideants-chat.es.js';
// Component is now available (api-base-url defaults to production)
document.body.innerHTML = `
<guideants-chat
pub-id="your-published-guide-id">
</guideants-chat>
`;
</script>React Usage
import { useEffect } from 'react';
import 'guideants/dist/guideants-chat.es.js';
function App() {
useEffect(() => {
// Component is registered globally
}, []);
return (
<guideants-chat
pub-id="your-published-guide-id"
/>
);
}TypeScript Support
The package includes full TypeScript definitions:
import type { GuideantsChatElement } from 'guideants';
const chatElement = document.querySelector('guideants-chat') as GuideantsChatElement;
chatElement.setAuthToken('your-token');
chatElement.addEventListener('wf-complete', (e) => {
console.log('Conversation complete', e.detail);
});Prerequisites
Before using this component, you need:
- A GuideAnts account - Sign up at go.guideants.ai
- A published guide - Create a guide in GuideAnts Notebooks and publish it to get a
pub-id
The component connects directly to GuideAnts' API to power conversations.
Creating Guides
GuideAnts Notebooks includes "The Guide Guide" - an AI assistant that helps you create guides. Simply start a conversation with The Guide Guide and it will walk you through the process of creating, configuring, and publishing your guide.
For more information about creating guides, visit go.guideants.ai.
Attributes
Required Attributes
pub-id(string) - The published guide identifier (GUID) from GuideAnts Notebooks- Obtained when you publish a guide in the GuideAnts application
- Example:
"d4a1d138-e283-4cfc-9418-f389375143b9"
Optional Attributes
api-base-url(string, optional) - Base URL of the GuideAnts API server (no trailing slash)- Defaults to
"https://api.guideants.ai"(production) if not provided - Can also be set via
VITE_DEFAULT_API_BASE_URLenvironment variable at build time
- Defaults to
proxy-url(string, optional) - URL of your proxy server (no trailing slash)- When set, all API calls are routed through this proxy instead of directly to GuideAnts
- Use for secure API key management (see Server-Side Proxy section)
- Example:
"https://your-server.com/api/chat"
auth-token(string) - Bearer token for authenticated requests- Used for published guides that require authentication via webhook
- Can be set programmatically via
setAuthToken()method - See the Authentication section below for details
display-mode("full"|"last-turn") - Controls conversation display"full"(default) - Shows all conversation turns"last-turn"- Shows only the most recent turn
enable-turn-navigation(boolean) - Enable turn navigation controls- Only effective when
display-mode="last-turn" - Shows previous/next/first/last buttons
- Only effective when
collapsible(boolean) - Enable collapsible conversation UI- When enabled, users can collapse/expand the conversation area
command-mode(boolean) – Command Mode (stateless)- Each message starts a new conversation thread
- Undo and Reset controls are disabled/hidden
- Useful for command palette or one‑shot interactions
conversation-starters-enabled(boolean) – Show guide-authored suggestions when no messages exist- Defaults to off; when enabled, the component will render any starters returned by the published guide
- You can override the list via
setConversationStarters()if you prefer custom prompts
attachments-enabled(boolean) – Enable clipboard paste events and attachment APIs- When enabled, pasting a file into the textarea fires
wf-attachment-paste - Hosts are responsible for uploading files and providing Notebook file IDs before sending
- When enabled, pasting a file into the textarea fires
speech-to-text-enabled(boolean) – Enable microphone recording for voice input- When enabled, a microphone button appears in the input area
- Audio is recorded and sent to the server for transcription using Azure Speech Service
- Transcribed text is automatically inserted into the input field
- Auto-stops after 2 seconds of silence (configurable)
- Works in browsers that support MediaRecorder API
camera-enabled(boolean) – Enable camera capture for still images- Requires
attachments-enabled="true"to be set - When enabled, a camera button appears in the input area
- Opens a modal with live camera preview for capturing photos
- Supports switching between front/back cameras on mobile devices
- Captured images are automatically uploaded and added as attachments
- Requires
input-label(string, optional) – Custom label text for the input field- Defaults to
"Message" - Set to empty string
""to hide the label entirely (zero height) - Example:
input-label="Your question"
- Defaults to
input-placeholder(string, optional) – Custom placeholder text for the input field- Defaults to
"Type your message..." - Example:
input-placeholder="Ask me anything..."
- Defaults to
Programmatic API
The component exposes methods for programmatic control:
const chat = document.querySelector('guideants-chat');
// Configure the component programmatically
// api-base-url defaults to https://api.guideants.ai, so you only need to set pub-id
chat.setPubId('your-published-guide-id');
// Optionally override the API URL if needed
// chat.setApiBaseUrl('https://api.guideants.ai');
// Set authentication token (if required by your published guide)
chat.setAuthToken('your-token-here');
// Get current token
const token = chat.getAuthToken();
// Get current pub ID
const pubId = chat.getPubId();
// Register a tool handler for client-side tool execution
chat.registerTool('my-tool', async (call) => {
// Execute tool logic
return {
toolCallId: call.id,
name: call.function.name,
content: JSON.stringify({ result: 'success' })
};
});
// Set stream event callback for advanced monitoring
chat.setStreamEventCallback((event) => {
console.log('Stream event:', event.type, event.data);
});
// Register a context provider to inject additional context with each message
chat.setContextProvider(() => {
const now = new Date();
return `Current time: ${now.toLocaleString()}\nPage URL: ${window.location.href}`;
});Complete API reference
Initialization and configuration
setPubId(pubId: string)/getPubId(): string | nullsetApiBaseUrl(url: string)/getApiBaseUrl(): string | null(defaults tohttps://api.guideants.ai)setProxyUrl(url: string | null)/getProxyUrl(): string | null(for server-side proxy)setAuthToken(token: string | null)/getAuthToken(): string | nullsetCommandMode(enabled: boolean)/getCommandMode(): booleantestAuthentication(): Promise<PublishedGuideConfig>(validates token and loads config)getPublishedGuideConfig(): PublishedGuideConfig | nullgetResolvedIds(): { projectId: string | null; notebookId: string | null; guideId: string | null }getConversationId(): string | nullgetAuthError(): { code: string; message: string } | nullclearConversation(): void
Streaming and tool calling
registerTool(toolName: string, handler: ToolHandler): voidsubmitExternalToolResults(results: Array<{ toolCallId: string; name: string; content: string }>): Promise<void>beginResumeStream(): void(prepare UI to resume after tool calls)ingestStreamEvent(eventType: string, data: any): void(feed external SSE events)setStreamEventCallback(fn?: (event: { type: string; data: unknown }) => void): voidsetContextProvider(provider?: (() => Promise<string | null> | string | null) | null): void(provide additional context for each message)
Display and navigation
setDisplayMode(mode: 'full' | 'last-turn')/getDisplayMode(): 'full' | 'last-turn'setTurnNavigationEnabled(enabled: boolean)/getTurnNavigationEnabled(): booleangetTurnCount(): numbergetCurrentTurnIndex(): number | null(last-turn mode)goToTurn(index: number),goToPreviousTurn(),goToNextTurn()
Collapsible conversation
setCollapsible(enabled: boolean)/getCollapsible(): booleancollapse(),expand(),toggleCollapse(),getIsCollapsed(): boolean
Conversation starters
setConversationStarters(starters: string[]): void/getConversationStarters(): string[]setConversationStartersEnabled(enabled: boolean): void/getConversationStartersEnabled(): boolean
Attachments & clipboard
setAttachmentsEnabled(enabled: boolean): void/getAttachmentsEnabled(): booleansetConversationStartersEnabled(enabled: boolean): void/getConversationStartersEnabled(): boolean
Speech-to-text
setSpeechToTextEnabled(enabled: boolean): void/getSpeechToTextEnabled(): booleanisRecording(): boolean- Check if currently recordingstartRecording(): Promise<void>- Programmatically start recordingstopRecording(): Promise<string | null>- Stop and transcribe (returns text or null)cancelRecording(): void- Cancel without transcribing
Camera capture
setCameraEnabled(enabled: boolean): void/getCameraEnabled(): booleanisCameraOpen(): boolean- Check if camera modal is openopenCamera(): Promise<void>- Programmatically open cameracaptureImage(): void- Capture current framecloseCamera(): void- Close camera modal
Input customization
setInputLabel(label: string): void/getInputLabel(): stringsetInputPlaceholder(placeholder: string): void/getInputPlaceholder(): string
Events (prefixed with wf-): stream-start, token, assistant_message, message, complete, usage, cancelled, error, undo-complete, undo-error, restart, auth-error, turn-navigation, collapsed, expanded, turns-hidden, conversation-starter-selected.
Events
The component fires custom events prefixed with wf-:
wf-stream-start
Fired when a message is submitted and streaming begins.
chat.addEventListener('wf-stream-start', () => {
console.log('Streaming started');
});wf-token
Fired for each token received during streaming.
chat.addEventListener('wf-token', (e) => {
console.log('Token:', e.detail.contentDelta);
});wf-complete
Fired when streaming completes successfully.
chat.addEventListener('wf-complete', (e) => {
console.log('Conversation complete');
});wf-error
Fired when an error occurs.
chat.addEventListener('wf-error', (e) => {
console.error('Error:', e.detail.message, e.detail.error);
});wf-auth-error
Fired when authentication fails.
chat.addEventListener('wf-auth-error', (e) => {
console.error('Auth error:', e.detail.code, e.detail.message);
});Other Events
wf-assistant_message- Assistant message updatewf-message- Final persisted messagewf-usage- Token usage metricswf-cancelled- Stream cancelledwf-undo-complete- Undo operation completedwf-undo-error- Undo operation failedwf-restart- Conversation restartedwf-turn-navigation- Turn navigation changedwf-collapsed- Conversation collapsedwf-expanded- Conversation expandedwf-turns-hidden- Turns hidden in last-turn mode
wf-attachment-paste
(Deprecated) Previously emitted when files were pasted. The component now handles uploads internally when attachments-enabled is true.
wf-conversation-starter-selected
Fired when the user clicks one of the starter buttons shown in the empty state.
Useful for analytics or to prefill other UI.
chat.addEventListener('wf-conversation-starter-selected', (e) => {
console.log('Starter selected:', e.detail.prompt);
});Client-Side Tool Calling
The component supports client-side tool execution, allowing your published guides to call functions that run in the browser. This enables interactive features like DOM manipulation, local storage, browser APIs, and integration with your application's functionality.
How It Works
- Server requests a tool call - When the assistant needs to execute a client-side tool, it sends an
external_tool_callevent - Component pauses streaming - The component automatically pauses and waits for tool execution
- Your handler executes - Your registered tool handler runs in the browser
- Results are submitted - The component automatically submits results back to the server
- Streaming resumes - The conversation continues automatically after tool execution
Registering Tool Handlers
Register tool handlers using the registerTool() method. The handler receives the tool call and must return a result:
const chat = document.querySelector('guideants-chat');
// Register a tool handler
chat.registerTool('SetColor', async (call) => {
// Parse tool arguments
const args = typeof call.function.arguments === 'string'
? JSON.parse(call.function.arguments || '{}')
: (call.function.arguments || {});
// Execute the tool (e.g., change page background color)
const color = args.color || 'white';
document.body.style.backgroundColor = color;
// Return result
return {
toolCallId: call.id,
name: 'SetColor',
content: JSON.stringify({
status: 'ok',
appliedColor: color
})
};
});Tool Handler Signature
type ToolHandler = (call: {
id: string; // Unique tool call ID
function: {
name: string; // Tool name (matches registration)
arguments: string | object; // Tool arguments (may be JSON string or object)
}
}) => Promise<{
toolCallId: string; // Must match call.id
name: string; // Tool name
content: string; // Result content (JSON string recommended)
} | null>; // Return null to skip this tool callComplete Example
const chat = document.querySelector('guideants-chat');
chat.setPubId('your-published-guide-id');
// Register multiple tools
chat.registerTool('SetColor', async (call) => {
const args = typeof call.function.arguments === 'string'
? JSON.parse(call.function.arguments || '{}')
: (call.function.arguments || {});
const color = args.color || 'white';
document.body.style.backgroundColor = color;
return {
toolCallId: call.id,
name: 'SetColor',
content: JSON.stringify({ status: 'ok', color })
};
});
chat.registerTool('GetLocalStorage', async (call) => {
const args = typeof call.function.arguments === 'string'
? JSON.parse(call.function.arguments || '{}')
: (call.function.arguments || {});
const key = args.key;
const value = localStorage.getItem(key);
return {
toolCallId: call.id,
name: 'GetLocalStorage',
content: JSON.stringify({ key, value })
};
});
chat.registerTool('ShowNotification', async (call) => {
const args = typeof call.function.arguments === 'string'
? JSON.parse(call.function.arguments || '{}')
: (call.function.arguments || {});
// Use browser notification API
if ('Notification' in window && Notification.permission === 'granted') {
new Notification(args.title || 'Notification', {
body: args.message || '',
icon: args.icon
});
}
return {
toolCallId: call.id,
name: 'ShowNotification',
content: JSON.stringify({ status: 'shown' })
};
});Error Handling
If a tool handler throws an error or returns an error result, the component will:
- Log the error to the console
- Submit an error result to the server
- Continue the conversation (the assistant can handle the error)
chat.registerTool('RiskyOperation', async (call) => {
try {
// Perform operation
const result = await performOperation();
return {
toolCallId: call.id,
name: 'RiskyOperation',
content: JSON.stringify({ status: 'success', result })
};
} catch (error) {
// Return error result - assistant can handle it
return {
toolCallId: call.id,
name: 'RiskyOperation',
content: JSON.stringify({
status: 'error',
error: error.message
})
};
}
});Monitoring Tool Calls
Built-in Console Logging
The component automatically logs tool call activity to the browser console for debugging:
[guideants-chat] Tool calls received: [{id: "...", name: "SetColor", arguments: {...}}]
[guideants-chat] Executing tool: SetColor {color: "red"}
[guideants-chat] Tool result for SetColor: {"status":"ok","appliedColor":"red"}
[guideants-chat] Submitting tool results: 1 result(s)This logging is always active and helps diagnose tool call issues during development.
Stream Event Callback
For programmatic monitoring, you can use the stream event callback:
chat.setStreamEventCallback((event) => {
if (event.type === 'external_tool_call') {
const toolCalls = event.data?.toolCalls || [];
console.log('Tool calls requested:', toolCalls);
// You can inspect tool calls before they're executed
toolCalls.forEach(call => {
console.log(`Tool: ${call.function?.name}`, call.function?.arguments);
});
}
});Tool Call Flow
User sends message
↓
Assistant processes (streaming tokens)
↓
Assistant requests tool call → external_tool_call event
↓
Component pauses streaming
↓
Your handler executes (async)
↓
Component submits results automatically
↓
Streaming resumes (assistant continues)
↓
Conversation completesBest Practices
- Validate arguments - Always validate and sanitize tool arguments
- Handle errors gracefully - Return error results instead of throwing
- Use JSON for content - Return structured JSON strings for better parsing
- Register tools early - Register all tools before the first message
- Match tool names exactly - Tool names must match what the server expects
- Keep handlers fast - Long-running operations should show progress to users
Server-Side Tool Configuration
For client-side tools to work, your published guide must be configured with tools that have clientHandled: true in the server configuration. The server will send external_tool_call events for these tools instead of executing them server-side.
Context Providers
The component supports injecting additional context information into each conversation message. This allows you to provide dynamic, page-specific context that the assistant can use when responding. Context providers work alongside server-side context options defined in your guide.
How It Works
- You register a context provider - A function that returns context information
- Component invokes provider - Before each message is sent, the provider is called
- Context is included - The context is added as a system message to the conversation
- Assistant receives context - The assistant can use this information in its responses
Registering a Context Provider
Register a context provider using the setContextProvider() method. The provider can be synchronous or asynchronous:
const chat = document.querySelector('guideants-chat');
// Synchronous provider
chat.setContextProvider(() => {
const now = new Date();
return `Current time: ${now.toLocaleString()}\nPage URL: ${window.location.href}`;
});
// Asynchronous provider
chat.setContextProvider(async () => {
const user = await getCurrentUser();
const session = await getSessionInfo();
return `User: ${user.name}\nSession ID: ${session.id}\nRole: ${user.role}`;
});
// Clear the provider (no context will be sent)
chat.setContextProvider(null);Context Provider Signature
type ContextProvider = () => Promise<string | null> | string | null;The provider should return:
- A string - Will be included as context (empty strings are ignored)
- null or undefined - No context will be included for this message
- Promise<string | null> - Async providers are supported
When Context is Invoked
The context provider is invoked once when a conversation starts, not on every turn. This design:
- Preserves token caching - System messages remain stable across turns, allowing the LLM to cache tokens efficiently
- Maintains consistency - Context doesn't change mid-conversation, preventing confusion
- Improves performance - Context is computed once per conversation, not on every message
Context is captured when:
- New conversation created - When
ensureConversation()creates a new conversation (first message or afterclearConversation()) - Command mode - Each message in command mode starts a new conversation, so context is captured fresh for each message
- After conversation cleared - When
clearConversation()is called, the next message will capture new context
Context is NOT captured when:
- Subsequent turns - Once a conversation has context, it's reused for all turns in that conversation
- Resuming after tools - When submitting tool results, the existing conversation context is reused
Context in Multi-Turn Conversations
Important: Context is captured once at conversation start and reused for all turns in that conversation:
- Stable context - The same context is used for all turns in a conversation thread
- Context is prepended - On the server, your context is added as a system message at the beginning of the message list
- Token efficiency - Stable system messages allow the LLM to cache tokens, reducing costs and improving performance
Example flow in a 3-turn conversation:
Conversation starts (context captured: "Time: 10:00, Page: /dashboard")
Turn 1:
[Server context] [Client context: "Time: 10:00, Page: /dashboard"] [Instructions]
[User: "Hello"]
[Assistant: "Hi there!"]
Turn 2:
[Server context] [Client context: "Time: 10:00, Page: /dashboard"] [Instructions] ← Same context
[User: "Hello"] ← Previous turn
[Assistant: "Hi there!"] ← Previous turn
[User: "What's the time?"]
[Assistant: "It's 10:00"]
Turn 3:
[Server context] [Client context: "Time: 10:00, Page: /dashboard"] [Instructions] ← Same context
[User: "Hello"] ← Turn 1
[Assistant: "Hi there!"] ← Turn 1
[User: "What's the time?"] ← Turn 2
[Assistant: "It's 10:00"] ← Turn 2
[User: "Thanks"]
[Assistant: "You're welcome"]Updating context mid-conversation: If you need to update facts or state during a conversation, use registered tools with appropriate agent definitions. The context provider is intended for initial conversation setup, not dynamic updates.
Message Ordering
When context is provided, system messages are added in this order:
- Server-side context options (from guide definition, with resolved placeholders)
- Client-provided context (from your provider callback)
- Assistant instructions (the guide's system prompt)
- Conversation history (previous messages)
This ensures your dynamic context is available alongside the guide's static context.
Example Use Cases
Inject local time and timezone:
chat.setContextProvider(() => {
const now = new Date();
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
return `Local Time: ${now.toLocaleString()}\nTimezone: ${timezone}\nUTC: ${now.toISOString()}`;
});Inject user session information:
chat.setContextProvider(async () => {
const session = await getSession();
return `Session ID: ${session.id}\nUser Role: ${session.role}\nEnvironment: ${session.env}`;
});Inject page-specific context:
chat.setContextProvider(() => {
const url = new URL(window.location.href);
return `Page: ${url.pathname}\nQuery: ${url.search}\nReferrer: ${document.referrer}`;
});Inject application state:
chat.setContextProvider(() => {
const state = getAppState();
return `Current View: ${state.currentView}\nSelected Item: ${state.selectedId}\nMode: ${state.mode}`;
});Error Handling
If a context provider throws an error or returns an invalid value:
- The error is logged to the console
- The message is sent without client context (server context and instructions still apply)
- The conversation continues normally
chat.setContextProvider(async () => {
try {
const data = await fetchUserData();
return `User Data: ${JSON.stringify(data)}`;
} catch (error) {
console.error('Failed to load context:', error);
// Return null to skip context for this message
return null;
}
});Best Practices
- Keep context concise - Long context strings consume tokens. Since context is stable across turns, it's included in the cached token window (depending on the model's support for cached tokens)
- Use structured format - Use key-value pairs or simple formatting for clarity
- Handle errors gracefully - Return
nullon errors rather than throwing - Capture stable information - Context is captured once per conversation, so include information that should remain constant throughout the conversation (e.g., user role, page context, initial state)
- Consider performance - Async providers should complete quickly, but they only run once per conversation
- Command mode considerations - In command mode, each message starts a new conversation, so context is captured fresh for each message. This is ideal for command-palette style interactions
- For dynamic updates - If you need to update facts or state during a conversation, use registered tools with appropriate agent definitions. Don't rely on context provider for mid-conversation updates
- Include timestamps - If including time-sensitive information, consider including a timestamp so the assistant knows when the context was captured
Server-Side Context Options
In addition to client-provided context, your published guide can define server-side context options in the guide configuration. These are resolved server-side and can include placeholders like [@currentDate] that are automatically replaced. Client-provided context is added after server context, allowing you to supplement or override guide-defined context with dynamic, page-specific information.
Conversation Starters
- Published guides can define starter prompts. They are now returned by
/api/published/guides/{pubId}and exposed to the component. - Opt in with
conversation-starters-enabled="true"orsetConversationStartersEnabled(true). - Override at runtime with
setConversationStarters([...])if you want a custom list. - When enabled, the component shows a friendly empty-state with buttons. Selecting one fills the textarea and emits
wf-conversation-starter-selected.
<guideants-chat
pub-id="..."
conversation-starters-enabled="true">
</guideants-chat>chat.setConversationStarters([
'Summarize this guide for a beginner.',
'Outline the first three actions I should take.',
]);
chat.addEventListener('wf-conversation-starter-selected', (e) => {
analytics.track('starter_selected', e.detail);
});Attachments & Clipboard
- Attachments are fully supported and handled internally by the component.
- Turn on paste detection with
attachments-enabled="true"orsetAttachmentsEnabled(true). - When enabled, users can paste images, audio, or text files directly into the input area.
- The component automatically uploads files to the GuideAnts API and attaches them to the message.
- Visual feedback (upload progress, preview chips) is built-in.
<guideants-chat
pub-id="..."
attachments-enabled="true">
</guideants-chat>Speech-to-Text
Enable voice input with server-side transcription powered by Azure Speech Service.
Basic Usage
<guideants-chat
pub-id="..."
speech-to-text-enabled="true">
</guideants-chat>How It Works
- Click the microphone button - Listening starts, button shows stop icon and duration
- Speak your message - Audio is captured via MediaRecorder API
- Stop listening - Click stop or wait for auto-stop after 2 seconds of silence
- Automatic transcription - Audio is sent to server, transcribed text is inserted into input
Programmatic Control
const chat = document.querySelector('guideants-chat');
// Enable/disable
chat.setSpeechToTextEnabled(true);
// Check state
if (chat.isRecording()) {
console.log('Currently recording...');
}
// Programmatic recording
await chat.startRecording();
// ... user speaks ...
const text = await chat.stopRecording();
console.log('Transcribed:', text);
// Cancel without transcribing
chat.cancelRecording();Features
- Auto-stop on silence - Recording automatically stops after 2 seconds of silence (after speech is detected)
- Visual feedback - Pulsing red indicator during recording, spinner during transcription
- Error handling - Graceful handling of permission denied, no microphone, transcription failures
- Browser support - Works in Chrome, Edge, Firefox, Safari (any browser with MediaRecorder API)
Requirements
- HTTPS (required for microphone access in most browsers)
- User must grant microphone permission when prompted
Camera Capture
Enable still image capture from device cameras. Captured images are uploaded and added as message attachments.
Basic Usage
<guideants-chat
pub-id="..."
attachments-enabled="true"
camera-enabled="true">
</guideants-chat>Note:
camera-enabledrequiresattachments-enabled="true"to function.
How It Works
- Click the camera button - Camera modal opens with live preview
- Position your shot - Use camera switch button on mobile to toggle front/back camera
- Capture - Click the capture button to take a photo
- Review - Preview the captured image, retake if needed
- Confirm - Image is uploaded and added as an attachment to your message
Programmatic Control
const chat = document.querySelector('guideants-chat');
// Enable/disable
chat.setCameraEnabled(true);
chat.setAttachmentsEnabled(true); // Required
// Check state
if (chat.isCameraOpen()) {
console.log('Camera modal is open');
}
// Programmatic control
await chat.openCamera();
chat.captureImage();
chat.closeCamera();Features
- Camera switching - Toggle between front and back cameras on devices with multiple cameras
- Live preview - Real-time video feed before capturing
- Review mode - Preview captured image with retake option before uploading
- Automatic upload - Captured images are automatically uploaded to GuideAnts
- Mobile-friendly - Optimized for both desktop and mobile devices
Requirements
- HTTPS (required for camera access in all browsers)
- User must grant camera permission when prompted
attachments-enabled="true"must be set
Full-Featured Configuration
Enable all input modalities for maximum user flexibility:
<guideants-chat
pub-id="your-published-guide-id"
attachments-enabled="true"
speech-to-text-enabled="true"
camera-enabled="true"
conversation-starters-enabled="true">
</guideants-chat>This configuration enables:
- Text input (always available)
- File attachments via paste/drag-drop
- Voice input via microphone
- Photo capture via camera
- Conversation starter suggestions
Styling & Theming
The component uses Shadow DOM for style encapsulation. You can style the host element and customize the appearance using CSS custom properties.
Basic Styling
guideants-chat {
display: block;
width: 100%;
max-width: 800px;
height: 600px;
border: 1px solid #e5e7eb;
border-radius: 8px;
}Theming with CSS Custom Properties
The component supports extensive theming via CSS custom properties. Below are example themes you can implement in your app (the component does not include built‑in themes):
Default Theme (Blue)
guideants-chat {
--wf-primary-color: #3b82f6;
--wf-primary-hover: #2563eb;
--wf-danger-color: #dc2626;
--wf-danger-hover: #b91c1c;
--wf-border-color: #e5e7eb;
--wf-bg-user: #dbeafe;
--wf-bg-assistant: #f3f4f6;
--wf-bg-control-bar: #f3f4f6;
--wf-bg-container: #ffffff;
--wf-text-primary: #111827;
--wf-text-secondary: #6b7280;
--wf-border-radius: 0.375rem;
--wf-border-radius-lg: 0.5rem;
}Dark Mode Theme
guideants-chat.theme-dark {
--wf-primary-color: #60a5fa;
--wf-primary-hover: #3b82f6;
--wf-danger-color: #f87171;
--wf-danger-hover: #ef4444;
--wf-border-color: #374151;
--wf-bg-user: #1e3a5f;
--wf-bg-assistant: #374151;
--wf-bg-control-bar: #1f2937;
--wf-bg-container: #111827;
--wf-text-primary: #f3f4f6;
--wf-text-secondary: #9ca3af;
--wf-text-error: #f87171;
--wf-border-radius: 0.375rem;
--wf-border-radius-lg: 0.5rem;
}Playful Theme (Pink/Purple)
guideants-chat.theme-playful {
--wf-primary-color: #ec4899;
--wf-primary-hover: #db2777;
--wf-danger-color: #f97316;
--wf-danger-hover: #ea580c;
--wf-border-color: #fce7f3;
--wf-bg-user: #fce7f3;
--wf-bg-assistant: #faf5ff;
--wf-bg-control-bar: #fdf4ff;
--wf-bg-container: #ffffff;
--wf-text-primary: #831843;
--wf-text-secondary: #9333ea;
--wf-border-radius: 16px;
--wf-border-radius-lg: 20px;
--wf-font-family: 'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', cursive;
}Available CSS Custom Properties
- Colors:
--wf-primary-color,--wf-primary-hover,--wf-danger-color,--wf-danger-hover - Borders:
--wf-border-color,--wf-border-radius,--wf-border-radius-lg - Backgrounds:
--wf-bg-user,--wf-bg-assistant,--wf-bg-control-bar,--wf-bg-container - Text:
--wf-text-primary,--wf-text-secondary,--wf-text-error - Typography:
--wf-font-family
Applying Themes
Apply themes using your own CSS classes (the component does not register these classes itself):
<!-- Default theme -->
<guideants-chat class="theme-default" pub-id="..."></guideants-chat>
<!-- Dark mode -->
<guideants-chat class="theme-dark" pub-id="..."></guideants-chat>
<!-- Playful theme -->
<guideants-chat class="theme-playful" pub-id="..."></guideants-chat>Or switch themes dynamically:
const chat = document.querySelector('guideants-chat');
chat.classList.remove('theme-default');
chat.classList.add('theme-dark');For advanced styling with CSS parts, see the Styling Guide for details.
Responsive Control Bar
The control bar (containing Restart, Undo, Send, and turn navigation buttons) uses CSS container queries to adapt to its container width rather than the viewport width. This ensures proper responsive behavior when the component is embedded in containers of any size.
| Container Width | Button Size | Description | |-----------------|-------------|-------------| | < 300px | 22px height | Tiny - minimal padding, 10px font | | ≥ 300px | 26px height | Small - compact layout, 11px font | | ≥ 400px | 30px height | Medium - comfortable spacing, 12px font | | ≥ 540px | 38px height | Full - standard size, 14px font |
This container-responsive approach means the component works correctly whether embedded in a narrow sidebar (300px) or a full-width layout (1200px+), regardless of the browser viewport size.
Features
- ✅ Framework-agnostic - Works with React, Angular, Vue, or vanilla JavaScript
- ✅ TypeScript support - Full type definitions included
- ✅ Streaming responses - Real-time token streaming via Server-Sent Events
- ✅ Markdown rendering - Rich markdown support with syntax highlighting
- ✅ Mermaid diagrams - Automatic rendering of Mermaid diagrams
- ✅ Media support - Images, videos, and audio playback
- ✅ Tool integration - Support for client-side tool execution
- ✅ Authentication - Support for authenticated published guides
- ✅ Container-responsive design - Control bar adapts to container width using CSS container queries
- ✅ Accessible - Built with accessibility in mind
- ✅ Conversation starters - Optional empty-state suggestions driven by published guides
- ✅ Clipboard attachments - Opt-in paste detection so hosts can collect files before sending
- ✅ Context providers - Inject dynamic, page-specific context into conversations
- ✅ Speech-to-text - Voice input with server-side transcription and auto-stop on silence
- ✅ Camera capture - Still image capture from device camera with live preview
Browser Support
- Chrome/Edge (latest)
- Firefox (latest)
- Safari (latest)
- Any browser that supports Custom Elements v1
Programmatic Configuration Example
const chat = document.querySelector('guideants-chat');
// Configure via API (api-base-url defaults to production)
chat.setPubId('your-published-guide-id');
// Optionally override API URL if needed
// chat.setApiBaseUrl('https://api.guideants.ai');
// Optional: Set auth token if your guide requires authentication
const token = await authService.getToken();
chat.setAuthToken(token);
// Optional: Enable Command Mode (each message is a new thread)
chat.setCommandMode(true);
// Optional: Register a context provider to inject dynamic context
chat.setContextProvider(() => {
const now = new Date();
return `Current time: ${now.toLocaleString()}\nPage: ${window.location.pathname}`;
});
// Test authentication and get guide config
try {
const config = await chat.testAuthentication();
console.log('Guide config:', config);
} catch (err) {
console.error('Auth failed:', err);
}Authentication
Published guides support two authentication modes:
Anonymous Access (Default)
By default, published guides allow anonymous access. No authentication token is required:
<guideants-chat pub-id="your-published-guide-id"></guideants-chat>Webhook Authentication
For secure published guides, you can configure webhook-based authentication. When a published guide has an AuthValidationWebhookUrl configured, the component will:
- Require an
auth-tokenattribute or call tosetAuthToken() - Send the token to your webhook for validation on each request
- Handle authentication errors and token refresh
Setting Up Webhook Authentication
1. Configure your published guide with a webhook URL in GuideAnts Notebooks.
2. Implement your webhook endpoint that validates tokens:
// Example webhook endpoint (Express.js)
app.post('/api/validate-chat-token', async (req, res) => {
const { token, publishedGuideId, projectId, notebookId } = req.body;
// Validate token using your authentication system
const user = await myAuthSystem.validateToken(token);
if (!user) {
return res.status(401).json({
valid: false,
error: 'Invalid token'
});
}
// Return success with user identity
res.json({
valid: true,
userIdentity: user.email // or user.id, user.username, etc.
});
});3. Pass the token to the component:
<guideants-chat
pub-id="your-published-guide-id"
auth-token="user-token-here">
</guideants-chat>Or programmatically:
const chat = document.querySelector('guideants-chat');
chat.setAuthToken('user-token-here');4. Handle authentication errors:
chat.addEventListener('wf-auth-error', async (e) => {
const { code, message, requiresAuth } = e.detail;
if (code === 'invalid_token') {
// Token expired or invalid - refresh it
const newToken = await refreshToken();
chat.setAuthToken(newToken);
} else if (code === 'authentication_required') {
// User needs to log in
redirectToLogin();
}
});Webhook Contract
Your webhook endpoint should:
- Accept
POSTrequests - Receive JSON body:
{ token, publishedGuideId, projectId, notebookId } - Return
200 OKwith{ valid: true, userIdentity: "..." }for valid tokens - Return
401or403with{ valid: false, error: "..." }for invalid tokens - Respond within the configured timeout (default: 5 seconds)
The component automatically handles token validation, error responses, and retries.
Component Architecture
This component connects directly to GuideAnts' API (https://api.guideants.ai) to:
- Create and manage conversations
- Stream assistant responses in real-time (Server-Sent Events)
- Handle authentication for protected guides via webhook validation
- Support undo operations and conversation management
- Execute client-side tools when registered
All conversations are powered by your published guides in GuideAnts Notebooks. The component handles all API communication automatically - you just need to provide the pub-id of your published guide.
Media handling
- Assistant messages can include media and links. The component:
- Rewrites relative and authenticated file URLs to published, anonymous URLs when possible
- Downloads authenticated media via blob URLs when needed and respects filename headers
- Provides optional fullscreen images (enable via
enableImageFullscreenin the markdown viewer)
Server-Side Proxy
For secure API key management, you can route requests through your own server using the proxy middleware included in this package. This keeps your API key secure on the server side instead of exposing it in client-side code.
Why Use a Proxy?
API keys in client-side code are inherently insecure:
- Visible in page source / dev tools
- Exposed in network traffic
- Can be extracted and abused
A server-side proxy holds the API key securely and forwards requests to GuideAnts.
Architecture
┌────────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐
│ Browser │ │ Your Server │ │ GuideAnts API │
│ │ │ │ │ │
│ <guideants-chat │────▶│ Proxy Middleware │────▶│ api.guideants.ai │
│ proxy-url="..." │ HTTP│ - Injects API key │ HTTP│ │
│ pub-id="..."> │ SSE │ - Forwards requests │ SSE │ │
└────────────────────┘ └──────────────────────┘ └─────────────────────┘Client Configuration
Use the proxy-url attribute instead of (or in addition to) api-base-url:
<guideants-chat
proxy-url="https://your-server.com/api/chat"
pub-id="your-published-guide-id">
</guideants-chat>Or configure programmatically:
const chat = document.querySelector('guideants-chat');
chat.setProxyUrl('https://your-server.com/api/chat');
chat.setPubId('your-published-guide-id');Node.js / Express Proxy
The package includes Express middleware for Node.js servers:
import express from 'express';
import { createExpressProxy } from 'guideants/proxy/express';
const app = express();
// Your own auth middleware first
app.use('/api/chat', myAuthMiddleware);
// Then the GuideAnts proxy
app.use('/api/chat', createExpressProxy({
apiKey: process.env.GUIDEANTS_API_KEY!,
// Optional: Custom target URL (defaults to https://api.guideants.ai)
targetBaseUrl: 'https://api.guideants.ai',
// Optional: Logging
logger: {
info: (msg, meta) => console.log(`[GuideAnts] ${msg}`, meta),
error: (msg, meta) => console.error(`[GuideAnts] ${msg}`, meta),
},
// Optional: Metrics
metrics: {
onRequest: ({ method, path, pubId }) => {
myMetrics.increment('guideants.request', { method, pubId });
},
onResponse: ({ status, durationMs }) => {
myMetrics.histogram('guideants.response_time', durationMs);
}
}
}));
app.listen(3000);.NET Core Proxy
For ASP.NET Core applications, use the Guideants.Proxy NuGet package:
dotnet add package Guideants.Proxyusing Guideants.Proxy;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Map the proxy (can be combined with RequireAuthorization)
app.MapGuideantsProxy("/api/chat", options =>
{
options.ApiKey = builder.Configuration["Guideants:ApiKey"]!;
options.Cache = app.Services.GetService<IDistributedCache>();
options.Logger = app.Services.GetRequiredService<ILogger<Program>>();
options.TransformRequest = async (context, request) =>
{
var user = context.User.Identity?.Name;
if (user != null)
{
// Inject user context
}
};
})
.RequireAuthorization("MyPolicy");
app.Run();Proxy Options
| Option | Type | Description |
|--------|------|-------------|
| apiKey | string | Required. Your GuideAnts API key |
| targetBaseUrl | string | Target API URL (default: https://api.guideants.ai) |
| cache | object | Optional cache for guide configuration |
| logger | object | Optional logging hooks |
| metrics | object | Optional metrics hooks |
| transformRequest | function | Optional request transformation |
Security Notes
- Never expose API keys in client-side code - Use the proxy to keep them secure
- The proxy strips Authorization headers - It injects its own API key header
- Add your own authentication - The proxy is non-opinionated about auth
- Use HTTPS in production - Protect data in transit
Changelog
0.6.7 (IN PROGRESS - NOT WORKING)
- Added:
input-labelattribute to customize the input field label (defaults to "Message"); set to empty string to hide label entirely - Added:
input-placeholderattribute to customize the input field placeholder (defaults to "Type your message...") - Added:
setInputLabel()/getInputLabel()programmatic API methods - Added:
setInputPlaceholder()/getInputPlaceholder()programmatic API methods
KNOWN ISSUE: The input-label attribute does not work. When set to empty string input-label="", the label still shows "Message". The input-placeholder attribute works correctly.
Files modified:
src/chat/GuideantsChatState.ts:54-56- AddedinputLabelandinputPlaceholderstate propertiessrc/chat/Template.ts:7- Addedid="wf-input-label"to the label elementsrc/chat/GuideantsChatView.ts:57,145-146,671-689- AddedinputLabelElproperty, binding, andupdateInputUI()methodsrc/chat/GuideantsChatController.ts:88-98,140-147,696-712- Attribute handling, setters/getters, sync in connectedCallbacksrc/GuideantsChat.ts:42-43,299-314- observedAttributes and public APIsrc/types/index.d.ts:143-153,604-628- TypeScript definitions
Debugging findings:
el.state.inputLabel = 'TEST'works - direct state assignment OKel.setInputLabel('')does NOT work - state ends up as "Message"el.shadowRoot.querySelector('#wf-input-label')finds element - DOM is correctel.getAttribute('input-label')returns''- attribute IS set correctlyinput-placeholderworks with identical code pattern
Attempted fixes that did not resolve the issue:
- Changed
newValue || 'Message'tonewValue !== null ? newValue : 'Message' - Removed
setAttributecall from setters to prevent attributeChangedCallback re-triggering - Added explicit
getAttribute()sync inconnectedCallback - Made
updateInputUI()re-query DOM if references are null
Suspected root cause: Unknown. Either attributeChangedCallback receives null for empty strings, or some other code path resets the state to "Message".
0.6.6
- Fixed: Attribute synchronization timing - attributes set before
connectedCallback(e.g.,speech-to-text-enabled,camera-enabled,command-mode) now properly initialize their UI state when the DOM is ready
0.6.5
- Initial public release with full feature set
License
Apache 2.0
Support
- Application: go.guideants.ai - Create and manage your guides
- Website: www.guideants.ai - Learn more about GuideAnts
Author
dougware
