@syncagent/react
v0.6.0
Published
SyncAgent React SDK — AI database chat widget & hooks
Downloads
917
Maintainers
Readme
@syncagent/react
React SDK for SyncAgent — drop-in AI database chat widget and hooks for React apps.
Works with MongoDB, PostgreSQL, MySQL, SQLite, SQL Server, and Supabase.
Get Your API Key
- Sign up for a free account
- Go to your Dashboard → New Project → choose your database type
- Copy your API key (starts with
sa_)
Every new project gets a 14-day trial with 500 free requests — no credit card required. After the trial, you get 100 free requests/month on the Free plan.
Install
npm install @syncagent/react @syncagent/jsQuick Start
import { SyncAgentChat } from "@syncagent/react";
export default function App() {
return (
<SyncAgentChat
config={{
apiKey: "sa_your_api_key",
connectionString: process.env.DATABASE_URL,
}}
/>
);
}A floating chat button appears in the bottom-right corner. Your users can now query your database in plain English.
<SyncAgentChat> Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| config | SyncAgentConfig | Required* | API key, connection string, tools, filter, operations |
| mode | "floating" \| "inline" | "floating" | Floating FAB or embedded inline panel |
| position | "bottom-right" \| "bottom-left" | "bottom-right" | FAB position (floating mode only) |
| defaultOpen | boolean | false | Start with the panel open |
| title | string | "SyncAgent" | Header title |
| subtitle | string | "AI Database Assistant" | Header subtitle |
| placeholder | string | "Ask anything..." | Input placeholder |
| welcomeMessage | string | "Hi! I can query..." | Empty state message |
| accentColor | string | "#10b981" | Brand color for header, FAB, send button |
| suggestions | string[] | 3 defaults | Quick-start suggestion chips |
| persistKey | string | — | localStorage key for conversation persistence |
| context | Record<string, any> | — | Extra context injected into every message |
| filter | Record<string, any> | — | Mandatory query filter for multi-tenancy |
| operations | ("read"\|"create"\|"update"\|"delete")[] | — | Restrict operations for this session |
| onReaction | (idx, reaction, content) => void | — | Called when user reacts 👍/👎 |
| onData | (data: ToolData) => void | — | Called when a DB tool returns structured data |
*config is required unless wrapped in <SyncAgentProvider>.
Inline Mode
Embed the chat inside your layout instead of a floating button:
<div style={{ height: 600 }}>
<SyncAgentChat
config={{ apiKey: "...", connectionString: "..." }}
mode="inline"
/>
</div>Custom UI with useSyncAgent
Build your own chat UI with full control:
import { SyncAgentProvider, useSyncAgent } from "@syncagent/react";
export default function App() {
return (
<SyncAgentProvider config={{ apiKey: "...", connectionString: "..." }}>
<MyChat />
</SyncAgentProvider>
);
}
function MyChat() {
const { messages, isLoading, error, status, lastData, sendMessage, stop, reset } = useSyncAgent();
return (
<div>
{status && <div>⏳ {status.label}</div>}
{messages.map((msg, i) => (
<div key={i}><strong>{msg.role}:</strong> {msg.content}</div>
))}
<button onClick={() => sendMessage("Show all users")}>Ask</button>
<button onClick={stop}>Stop</button>
<button onClick={reset}>Clear</button>
</div>
);
}useSyncAgent Returns
| Return | Type | Description |
|--------|------|-------------|
| messages | Message[] | Full conversation history |
| isLoading | boolean | true while streaming |
| error | Error \| null | Last error |
| status | { step, label } \| null | Live status while agent is working |
| lastData | ToolData \| null | Last structured data from a DB tool |
| sendMessage | (content: string) => void | Send a user message |
| stop | () => void | Abort the current stream |
| reset | () => void | Clear all messages |
Features
- Auto page detection — detects current page, record ID, and query params from the URL
- Live status — shows
● Querying users...while the agent works - Markdown rendering — tables, code blocks, bold, italic, lists
- Streaming — blinking cursor while text streams in
- Copy button — on every AI response
- Reactions — 👍/👎 on AI messages
- Conversation persistence — saves history to localStorage
- Suggestion chips — configurable quick-start prompts
- Export CSV — download tables as CSV
- Bar charts — auto-renders aggregation results
- Resize handle — drag to resize the floating panel
- Mobile responsive — full-width on small screens
- Dark mode — respects
prefers-color-scheme
Multi-tenant SaaS
Pass filter to scope every agent operation to the current user's organization. Enforced server-side.
<SyncAgentChat
config={{
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
filter: { organizationId: currentUser.orgId },
operations: currentUser.isAdmin
? ["read", "create", "update", "delete"]
: ["read"],
}}
/>Custom Tools
Give the agent capabilities beyond your database:
<SyncAgentChat
config={{
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
tools: {
createInvoice: {
description: "Create a Stripe invoice for a customer",
inputSchema: {
customerId: { type: "string", description: "Stripe customer ID" },
amount: { type: "number", description: "Amount in cents" },
},
execute: async ({ customerId, amount }) => {
const inv = await stripe.invoices.create({ customer: customerId });
return { invoiceId: inv.id };
},
},
},
}}
/>Tools-only Mode
Use the agent with only your custom tools — no database access:
<SyncAgentChat
config={{
apiKey: "sa_your_key",
toolsOnly: true,
tools: {
searchProducts: {
description: "Search products by name",
inputSchema: { query: { type: "string", description: "Search query" } },
execute: async ({ query }) => {
const res = await fetch(`/api/products?q=${query}`);
return res.json();
},
},
},
}}
/>Customer Agent Mode
Use the useCustomerChat hook to build customer-facing support interfaces powered by SyncAgent's customer agent pipeline — including persona, flows, knowledge base, escalation, and AI fallback.
import { SyncAgentProvider, useCustomerChat } from "@syncagent/react";
function App() {
return (
<SyncAgentProvider
config={{
apiKey: "sa_your_api_key",
connectionString: process.env.DATABASE_URL,
externalUserId: currentUser.id,
}}
>
<CustomerChat />
</SyncAgentProvider>
);
}
function CustomerChat() {
const {
messages,
conversationId,
isLoading,
isEscalated,
isResolved,
error,
welcomeMessage,
sendMessage,
rateConversation,
reset,
} = useCustomerChat({
onEscalated: () => console.log("Escalated to human agent"),
onResolved: (id) => console.log("Conversation resolved:", id),
});
return (
<div>
{welcomeMessage && <p>{welcomeMessage}</p>}
{messages.map((msg, i) => (
<div key={i}>
<strong>{msg.role}:</strong> {msg.content}
</div>
))}
{isEscalated && <p>You've been connected to a human agent.</p>}
{isResolved && (
<div>
<p>Conversation resolved!</p>
<button onClick={() => rateConversation(5)}>⭐ Rate 5/5</button>
</div>
)}
{error && <p>Error: {error.message}</p>}
<button onClick={() => sendMessage("I need help with my order")}>
Send
</button>
<button onClick={reset}>New Conversation</button>
</div>
);
}useCustomerChat Return Values
| Return | Type | Description |
|--------|------|-------------|
| messages | Message[] | Full conversation history (user and assistant messages) |
| conversationId | string \| null | Current conversation ID, set after first message |
| isLoading | boolean | true while waiting for a response |
| isEscalated | boolean | true when conversation has been escalated to a human agent |
| isResolved | boolean | true when the conversation has been resolved |
| error | Error \| null | Last error encountered, or null |
| welcomeMessage | string \| null | Welcome message returned on first interaction |
| sendMessage | (content: string, metadata?: Record<string, any>) => Promise<void> | Send a message to the customer agent |
| rateConversation | (rating: number) => Promise<void> | Rate the conversation (1-5). Throws if no active conversation. |
| reset | () => void | Clear all state and start a new conversation |
UseCustomerChatOptions
| Field | Type | Description |
|-------|------|-------------|
| client | SyncAgentClient? | Optional client instance. If omitted, uses the client from SyncAgentProvider context. |
| onEscalated | () => void? | Called when the conversation is escalated to a human agent |
| onResolved | (conversationId: string) => void? | Called when the conversation is resolved |
Client resolution:
useCustomerChatfirst checks for aclientpassed directly in options. If none is provided, it falls back to the client from the nearest<SyncAgentProvider>. If neither is available, it throws an error prompting you to provide one.
Guest Identification
When no externalUserId is provided in the config, the customer chat enters guest mode — anonymous visitors must identify themselves before sending messages. The useCustomerChat hook exposes guest identification state and a method to submit guest data programmatically.
Hook Fields
| Return | Type | Description |
|--------|------|-------------|
| isIdentified | boolean | true if the user has an externalUserId or has completed guest identification |
| guestIdentity | GuestIdentity \| null | The stored guest identity object, or null if not yet identified |
| identifyGuest | (data: { name: string; email: string; phone?: string }) => void | Submit guest form data — validates, generates a guest ID, persists to localStorage, and transitions to identified state |
Using GuestIdentificationForm
The GuestIdentificationForm component provides a ready-made form with built-in validation, accessibility attributes, and customizable text:
import { useCustomerChat, GuestIdentificationForm } from "@syncagent/react";
function CustomerChat() {
const {
messages,
isIdentified,
identifyGuest,
sendMessage,
} = useCustomerChat({
onGuestIdentified: (identity) => {
console.log("Guest identified:", identity.guestId);
},
});
if (!isIdentified) {
return (
<GuestIdentificationForm
onSubmit={(identity) => identifyGuest(identity)}
config={{
title: "Welcome!",
subtitle: "Tell us a bit about yourself",
submitButtonText: "Start Chat",
namePlaceholder: "Your name",
emailPlaceholder: "[email protected]",
phonePlaceholder: "Phone (optional)",
}}
className="my-guest-form"
/>
);
}
return (
<div>
{messages.map((msg, i) => (
<div key={i}><strong>{msg.role}:</strong> {msg.content}</div>
))}
<button onClick={() => sendMessage("Hello!")}>Send</button>
</div>
);
}GuestIdentificationFormProps
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| onSubmit | (identity: GuestIdentity) => void | Yes | Called with the complete GuestIdentity (including generated guestId) on valid submission |
| config | GuestFormConfig | No | Customize title, subtitle, button text, and placeholder strings |
| className | string | No | Custom CSS class applied to the form container |
Manual Guest Identification
If you prefer to build your own form UI, use the identifyGuest method directly:
import { useState } from "react";
import { useCustomerChat } from "@syncagent/react";
function CustomGuestForm() {
const { isIdentified, identifyGuest, error } = useCustomerChat();
const [name, setName] = useState("");
const [email, setEmail] = useState("");
if (isIdentified) return null;
return (
<form
onSubmit={(e) => {
e.preventDefault();
identifyGuest({ name, email });
}}
>
<input value={name} onChange={(e) => setName(e.target.value)} placeholder="Name" />
<input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" />
<button type="submit">Continue</button>
{error && <p>{error.message}</p>}
</form>
);
}The identifyGuest method validates the input using validateGuestForm from @syncagent/js. If validation fails, it sets the error state with a descriptive message. On success, it generates a deterministic guest identifier from the email, persists the identity to localStorage, and sets isIdentified to true.
Dual Mode (Database + Customer Agent)
⚠️ Deprecated:
createDual()will be removed in a future major version. UseuseDualChat()instead — passexternalUserIddirectly to enable both modes on a single instance.
Before (deprecated):
import { SyncAgentClient } from "@syncagent/js";
import { SyncAgentProvider, useCustomerChat, useSyncAgent } from "@syncagent/react";
const { db, support } = SyncAgentClient.createDual({
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
externalUserId: currentUser.id,
});
function AdminChat() {
return <SyncAgentProvider config={db}><AdminPanel /></SyncAgentProvider>;
}
function CustomerWidget() {
const chat = useCustomerChat({ client: support });
return <div>{/* customer chat UI */}</div>;
}After (recommended):
import { SyncAgentProvider, useDualChat } from "@syncagent/react";
function App() {
return (
<SyncAgentProvider
config={{
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
externalUserId: currentUser.id,
}}
>
<DualChatUI />
</SyncAgentProvider>
);
}
function DualChatUI() {
const { db, support } = useDualChat();
// Use db.sendMessage(), support.sendMessage(), etc.
}Legacy pattern (deprecated):
import { SyncAgentClient } from "@syncagent/js";
import { SyncAgentProvider, useCustomerChat, useSyncAgent } from "@syncagent/react";
const { db, support } = SyncAgentClient.createDual({
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
externalUserId: currentUser.id,
});
// Admin panel — database agent
function AdminChat() {
return <SyncAgentProvider config={db}><AdminPanel /></SyncAgentProvider>;
}
// Customer widget — support agent
function CustomerWidget() {
const chat = useCustomerChat({ client: support });
return <div>{/* customer chat UI */}</div>;
}Customer Chat Component
The <SyncAgentCustomerChat> component is a pre-built, drop-in customer support chat widget. It provides a complete experience including guest identification, real-time messaging with AI and human agents, escalation display, and satisfaction rating — all as a single component.
import { SyncAgentCustomerChat } from "@syncagent/react";
export default function App() {
return (
<SyncAgentCustomerChat
apiKey="sa_your_api_key"
connectionString={process.env.DATABASE_URL!}
/>
);
}<SyncAgentCustomerChat> Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| config | SyncAgentConfig | — | Server config object. When provided, apiKey/connectionString are ignored. |
| apiKey | string | — | API key for authentication (ignored if config provided) |
| connectionString | string | — | Database connection string (ignored if config provided) |
| externalUserId | string | — | Authenticated user ID — skips guest form when provided |
| mode | "floating" \| "inline" | "floating" | Floating toggle button or embedded inline panel |
| position | "bottom-right" \| "bottom-left" | "bottom-right" | Position of the floating widget (floating mode only) |
| defaultOpen | boolean | false | Whether the floating panel starts open (floating mode only) |
| title | string | "Customer Support" | Header title text (max 100 chars) |
| subtitle | string | "How can we help you?" | Header subtitle text (max 200 chars) |
| placeholder | string | "Type your message..." | Input placeholder text (max 150 chars) |
| welcomeMessage | string | — | Initial welcome message displayed before any interaction |
| accentColor | string | "#6366f1" | Primary accent color (hex, rgb, or hsl) |
| darkMode | boolean | false | Enable dark mode color scheme |
| className | string | — | Additional CSS class on root container |
| style | CSSProperties | — | Inline styles merged onto root container |
| guestForm | GuestFormConfig | — | Custom guest form configuration (title, subtitle, placeholders, button text) |
| pusherKey | string | — | Pusher app key for real-time human agent messages |
| pusherCluster | string | "us2" | Pusher cluster |
| metadata | Record<string, any> | — | Custom metadata attached to conversations |
| onEscalated | () => void | — | Called when conversation is escalated to a human agent |
| onResolved | (conversationId: string) => void | — | Called when conversation is resolved |
| onGuestIdentified | (identity: GuestIdentity) => void | — | Called after guest form submission |
Basic Usage
<SyncAgentCustomerChat
apiKey="sa_your_api_key"
connectionString={process.env.DATABASE_URL!}
/>Using a Config Object
import { SyncAgentCustomerChat } from "@syncagent/react";
// Config from server (e.g., Next.js createCustomerServerConfig)
<SyncAgentCustomerChat config={serverConfig} />Floating Mode (default)
<SyncAgentCustomerChat
apiKey="sa_your_api_key"
connectionString={process.env.DATABASE_URL!}
mode="floating"
position="bottom-right"
defaultOpen={false}
/>Inline Mode
Embed the chat inside your layout — the panel fills its parent container and is always visible:
<div style={{ height: 600, width: 400 }}>
<SyncAgentCustomerChat
apiKey="sa_your_api_key"
connectionString={process.env.DATABASE_URL!}
mode="inline"
/>
</div>Dark Mode with Custom Accent Color
<SyncAgentCustomerChat
apiKey="sa_your_api_key"
connectionString={process.env.DATABASE_URL!}
darkMode
accentColor="#8b5cf6"
/>Custom Guest Form
<SyncAgentCustomerChat
apiKey="sa_your_api_key"
connectionString={process.env.DATABASE_URL!}
guestForm={{
title: "Welcome!",
subtitle: "Tell us about yourself to get started",
submitButtonText: "Start Chat",
namePlaceholder: "Your name",
emailPlaceholder: "[email protected]",
phonePlaceholder: "+1 (555) 000-0000",
}}
onGuestIdentified={(identity) => {
console.log("Guest identified:", identity.guestId);
}}
/>useCustomerChat Hook
For full control over the UI, use the useCustomerChat hook directly to build a custom chat interface:
import { SyncAgentProvider, useCustomerChat } from "@syncagent/react";
function App() {
return (
<SyncAgentProvider config={{ apiKey: "sa_...", connectionString: "..." }}>
<CustomChat />
</SyncAgentProvider>
);
}
function CustomChat() {
const {
messages,
isLoading,
isEscalated,
isResolved,
isIdentified,
identifyGuest,
sendMessage,
rateConversation,
error,
welcomeMessage,
} = useCustomerChat({
onEscalated: () => console.log("Escalated"),
onResolved: (id) => console.log("Resolved:", id),
onGuestIdentified: (identity) => console.log("Guest:", identity),
});
if (!isIdentified) {
return (
<form onSubmit={(e) => {
e.preventDefault();
identifyGuest({ name: "Jane", email: "[email protected]" });
}}>
<button type="submit">Identify</button>
</form>
);
}
return (
<div>
{welcomeMessage && <p>{welcomeMessage}</p>}
{messages.map((msg, i) => (
<div key={i}><strong>{msg.role}:</strong> {msg.content}</div>
))}
{isEscalated && <p>Connected to a human agent</p>}
{isResolved && <button onClick={() => rateConversation(5)}>⭐ Rate</button>}
{error && <p>Error: {error.message}</p>}
<button onClick={() => sendMessage("Hello!")} disabled={isLoading}>
Send
</button>
</div>
);
}UseCustomerChatOptions
| Option | Type | Description |
|--------|------|-------------|
| client | SyncAgentClient | Optional client instance. If omitted, uses the client from SyncAgentProvider context. |
| externalUserId | string | When provided, the hook considers the user pre-authenticated (bypasses guest flow). |
| onEscalated | () => void | Called when the conversation is escalated to a human agent. |
| onResolved | (conversationId: string) => void | Called when the conversation is resolved. |
| onGuestIdentified | (identity: GuestIdentity) => void | Called when a guest completes identification. |
useCustomerChat Return Values
| Return | Type | Description |
|--------|------|-------------|
| messages | Message[] | Full conversation history |
| conversationId | string \| null | Current conversation ID, set after first message |
| isLoading | boolean | true while waiting for a response |
| isEscalated | boolean | true when conversation has been escalated to a human agent |
| isResolved | boolean | true when the conversation has been resolved |
| error | Error \| null | Last error encountered |
| welcomeMessage | string \| null | Welcome message returned on first interaction |
| sendMessage | (content: string, metadata?: Record<string, any>) => Promise<void> | Send a message to the customer agent |
| rateConversation | (rating: number) => Promise<void> | Rate the conversation (1–5). Throws if no active conversation. |
| reset | () => void | Clear all state and start a new conversation |
| isIdentified | boolean | true if the user has an externalUserId or has completed guest identification |
| guestIdentity | GuestIdentity \| null | The stored guest identity object |
| identifyGuest | (data: { name: string; email: string; phone?: string }) => void | Submit guest form data — validates, generates a guest ID, persists to localStorage |
Theming
The <SyncAgentCustomerChat> component uses a built-in theme engine to generate a complete color palette from two inputs:
accentColor— any valid CSS color (hex, rgb, hsl). Used for buttons, user message bubbles, focus outlines, and the floating toggle button. Default:"#6366f1".darkMode— whentrue, renders a dark color scheme with light text on dark backgrounds.
The theme engine (computeTheme() from @syncagent/js) automatically adjusts all derived colors to meet WCAG AA contrast requirements:
- Text contrast: All text-on-background pairs maintain a minimum 4.5:1 contrast ratio
- Focus indicators: Focus outlines maintain a minimum 3:1 contrast ratio against adjacent colors
- Accent contrast: The accent color against the background maintains a minimum 3:1 contrast ratio
All styles are applied inline — no external CSS is required and no styles leak to or from your application.
// Light mode with custom accent
<SyncAgentCustomerChat accentColor="#059669" />
// Dark mode with default accent
<SyncAgentCustomerChat darkMode />
// Dark mode with custom accent
<SyncAgentCustomerChat darkMode accentColor="#f59e0b" />Accessibility
The <SyncAgentCustomerChat> component is built with WCAG AA compliance:
ARIA Roles and Labels:
- The chat container uses
role="region"witharia-label="Customer chat" - The message list uses
role="log"to indicate a chronological message stream - All interactive elements have descriptive ARIA labels
Keyboard Navigation:
Tabmoves focus through all interactive elements (buttons, inputs, rating stars)EnterorSpaceactivates buttons and submits forms- Arrow keys navigate the satisfaction rating stars
Focus Management:
- When the guest form transitions to the chat panel, focus moves to the message input within 100ms
- All interactive elements have visible focus indicators with a minimum 2px outline width
- Focus indicator contrast meets the 3:1 minimum ratio against adjacent colors
Screen Reader Support:
- New messages are announced via an ARIA live region (
aria-live="polite") - Loading states, errors, and escalation status changes are communicated to assistive technology
Contrast Requirements:
- All text maintains a minimum 4.5:1 contrast ratio against its background
- Focus indicators maintain a minimum 3:1 contrast ratio
- These guarantees hold in both light and dark mode, regardless of the accent color chosen
Unified Dual Mode
The useDualChat() hook provides a single interface for managing both database agent and customer support agent conversations from within a <SyncAgentProvider>. When your config includes externalUserId, both modes are available through namespaced db and support objects.
import { SyncAgentProvider, useDualChat } from "@syncagent/react";
function App() {
return (
<SyncAgentProvider
config={{
apiKey: "sa_your_api_key",
connectionString: process.env.DATABASE_URL,
externalUserId: currentUser.id,
}}
>
<DualChatUI />
</SyncAgentProvider>
);
}
function DualChatUI() {
const { db, support } = useDualChat({
context: { tenant: currentUser.orgId },
onData: (data) => console.log("DB tool result:", data),
onEscalated: () => console.log("Escalated to human"),
onResolved: (id) => console.log("Resolved:", id),
});
return (
<div>
{/* Database agent panel */}
<section>
<h2>Admin Database Chat</h2>
{db.messages.map((msg, i) => (
<div key={i}><strong>{msg.role}:</strong> {msg.content}</div>
))}
{db.isLoading && <p>Loading...</p>}
{db.error && <p>Error: {db.error.message}</p>}
<button onClick={() => db.sendMessage("Show all orders")}>
Query DB
</button>
<button onClick={db.stop}>Stop</button>
<button onClick={db.reset}>Clear</button>
</section>
{/* Customer support panel */}
<section>
<h2>Customer Support</h2>
{support.welcomeMessage && <p>{support.welcomeMessage}</p>}
{support.messages.map((msg, i) => (
<div key={i}><strong>{msg.role}:</strong> {msg.content}</div>
))}
{support.isLoading && <p>Loading...</p>}
{support.isEscalated && <p>Connected to a human agent.</p>}
{support.isResolved && (
<button onClick={() => support.rateConversation(5)}>⭐ Rate</button>
)}
{support.error && <p>Error: {support.error.message}</p>}
<button onClick={() => support.sendMessage("I need help")}>
Send
</button>
<button onClick={support.reset}>New Conversation</button>
</section>
</div>
);
}Error Handling
If useDualChat() is called outside of a <SyncAgentProvider>, it throws:
useDualChat must be used within a <SyncAgentProvider>Ensure your component is wrapped in a <SyncAgentProvider> with a valid config that includes externalUserId.
DualChatReturn
The useDualChat() hook returns an object with two namespaces: db for database agent state and support for customer agent state.
db Properties
| Property | Type | Description |
|----------|------|-------------|
| messages | Message[] | Full database agent conversation history |
| isLoading | boolean | true while the database agent is streaming |
| error | Error \| null | Last error from the database agent, or null |
| status | { step: string; label: string } \| null | Live status while the agent is working (e.g., querying) |
| lastData | ToolData \| null | Last structured data returned by a DB tool |
| sendMessage | (content: string) => void | Send a message to the database agent |
| stop | () => void | Abort the current database agent stream |
| reset | () => void | Clear all database agent messages and state |
support Properties
| Property | Type | Description |
|----------|------|-------------|
| messages | Message[] | Full customer support conversation history |
| conversationId | string \| null | Current conversation ID, set after first message |
| isLoading | boolean | true while waiting for a support response |
| isEscalated | boolean | true when conversation has been escalated to a human agent |
| isResolved | boolean | true when the conversation has been resolved |
| error | Error \| null | Last error from the support agent, or null |
| welcomeMessage | string \| null | Welcome message returned on first interaction |
| sendMessage | (content: string, metadata?: Record<string, any>) => Promise<void> | Send a message to the customer support agent |
| rateConversation | (rating: number) => Promise<void> | Rate the conversation (1-5). Throws if no active conversation. |
| reset | () => void | Clear all support state and start a new conversation |
UseDualChatOptions
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| context | Record<string, any> | No | Extra context injected into every database agent message |
| onData | (data: ToolData) => void | No | Called when a DB tool returns structured data |
| onEscalated | () => void | No | Called when the customer conversation is escalated to a human agent |
| onResolved | (conversationId: string) => void | No | Called when the customer conversation is resolved |
Customization Options
All config options from @syncagent/js work here too:
<SyncAgentChat
config={{
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
systemInstruction: "You are a friendly sales assistant for Acme Corp.",
language: "French",
confirmWrites: true,
maxResults: 10,
sensitiveFields: ["ssn", "salary", "creditCard"],
onBeforeToolCall: (name, args) => { console.log(`[Audit] ${name}`, args); return true; },
onAfterToolCall: (name, args, result) => { analytics.track("tool_call", { tool: name }); },
}}
/>Conversation Persistence
<SyncAgentChat
config={{ apiKey: "...", connectionString: "..." }}
persistKey={currentUser.id}
/>History saves to localStorage under sa_chat_{persistKey}. The "New" button clears it.
Context & Auto Page Detection
The SDK automatically detects the current page from window.location — zero config needed:
URL: /dashboard/orders/ord_123?tab=details
Auto-detected:
currentPage: "orders"
currentPath: "/dashboard/orders/ord_123"
currentRecordId: "ord_123"
param_tab: "details"Pass additional context:
<SyncAgentChat
config={{ apiKey: "...", connectionString: "..." }}
context={{ userId: currentUser.id, userRole: "admin" }}
/>Vanilla JS Widget
No npm required — drop a script tag into any HTML page:
<script src="https://syncagentdev.vercel.app/api/v1/widget"></script>
<script>
SyncAgent.init({
apiKey: "sa_your_key",
connectionString: "your_database_url",
position: "right",
accentColor: "#10b981",
title: "AI Assistant",
persistKey: "my-app",
});
</script>Security
- Your database connection string is never stored on SyncAgent servers
- Passed at runtime, used once, immediately discarded
- API keys are hashed with bcrypt
- Never expose your connection string in client-side code — use server components or API routes
Plans & Pricing
| Plan | Requests/mo | Collections | Price | |------|-------------|-------------|-------| | Free (+ 14-day trial) | 100 (500 during trial) | 5 | GH₵0 | | Starter | 5,000 | 20 | GH₵150/mo | | Pro | 50,000 | Unlimited | GH₵500/mo | | Enterprise | Unlimited | Unlimited | Custom |
Resources
TypeScript Types
The following types are exported from @syncagent/react for use in your TypeScript projects:
| Type | Description | Import |
|------|-------------|--------|
| DualChatReturn | Return type of the useDualChat() hook containing namespaced db and support objects | import { DualChatReturn } from "@syncagent/react" |
| UseDualChatOptions | Options interface for configuring the useDualChat() hook (context, callbacks) | import { UseDualChatOptions } from "@syncagent/react" |
import { DualChatReturn, UseDualChatOptions } from "@syncagent/react";License
MIT
