@syncagent/vue
v0.5.1
Published
SyncAgent Vue 3 SDK — composables and chat component
Downloads
552
Maintainers
Readme
@syncagent/vue
Vue 3 SDK for SyncAgent — composables for AI database chat in Vue 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/vue @syncagent/jsQuick Start
<script setup lang="ts">
import { ref } from "vue";
import { SyncAgentClient } from "@syncagent/js";
import { useSyncAgent } from "@syncagent/vue";
const client = new SyncAgentClient({
apiKey: import.meta.env.VITE_SYNCAGENT_KEY,
connectionString: import.meta.env.VITE_DATABASE_URL,
});
const { messages, isLoading, error, status, sendMessage, stop, reset } = useSyncAgent({ client });
const input = ref("");
function send() {
if (!input.value.trim()) return;
sendMessage(input.value);
input.value = "";
}
</script>
<template>
<div class="chat">
<div v-if="status" class="status">⏳ {{ status.label }}</div>
<div class="messages">
<div v-for="(msg, i) in messages" :key="i" :class="['message', msg.role]">
{{ msg.content }}
</div>
</div>
<div v-if="error" class="error">⚠️ {{ error.message }}</div>
<div class="input-row">
<input v-model="input" @keydown.enter="send" placeholder="Ask about your data..." :disabled="isLoading" />
<button @click="send" :disabled="isLoading || !input.trim()">Send</button>
<button v-if="isLoading" @click="stop">Stop</button>
<button @click="reset">Clear</button>
</div>
</div>
</template>Environment Variables
# .env (Vite)
VITE_SYNCAGENT_KEY=sa_your_api_key
VITE_DATABASE_URL=mongodb+srv://user:pass@cluster/db⚠️ Security note: In production, pass the connection string from your server/API route instead of exposing it in client-side env vars.
Multi-tenant SaaS
<script setup lang="ts">
import { computed } from "vue";
import { SyncAgentClient } from "@syncagent/js";
import { useSyncAgent } from "@syncagent/vue";
import { useAuth } from "@/composables/auth";
const { user } = useAuth();
const client = computed(() => new SyncAgentClient({
apiKey: import.meta.env.VITE_SYNCAGENT_KEY,
connectionString: import.meta.env.VITE_DATABASE_URL,
filter: { organizationId: user.value.orgId },
operations: user.value.isAdmin
? ["read", "create", "update", "delete"]
: ["read"],
}));
const { messages, isLoading, sendMessage } = useSyncAgent({
client: client.value,
context: { userId: user.value.id, page: "dashboard" },
});
</script>Custom Tools
<script setup lang="ts">
import { SyncAgentClient } from "@syncagent/js";
import { useSyncAgent } from "@syncagent/vue";
const client = new SyncAgentClient({
apiKey: import.meta.env.VITE_SYNCAGENT_KEY,
connectionString: import.meta.env.VITE_DATABASE_URL,
tools: {
sendNotification: {
description: "Send a push notification to a user",
inputSchema: {
userId: { type: "string", description: "User ID" },
message: { type: "string", description: "Notification message" },
},
execute: async ({ userId, message }) => {
await pushService.send(userId, message);
return { sent: true };
},
},
},
});
const { messages, sendMessage } = useSyncAgent({ client });
</script>Tools-only Mode
Use toolsOnly: true when you want the agent to only call your custom tools — no database access:
<script setup lang="ts">
import { SyncAgentClient } from "@syncagent/js";
import { useSyncAgent } from "@syncagent/vue";
const client = new SyncAgentClient({
apiKey: import.meta.env.VITE_SYNCAGENT_KEY,
toolsOnly: true,
tools: {
searchProducts: {
description: "Search products by name",
inputSchema: { query: { type: "string" } },
execute: async ({ query }) => {
const res = await fetch(`/api/products?q=${query}`);
return res.json();
},
},
},
});
const { messages, sendMessage } = useSyncAgent({ client });
</script>Customer Agent Mode
Use useCustomerChat to build customer support interfaces with Vue reactive state. This composable wraps the JS SDK's customerChat method, managing conversation state, escalation, and resolution as Vue Ref values.
<script setup lang="ts">
import { ref } from "vue";
import { SyncAgentClient } from "@syncagent/js";
import { useCustomerChat } from "@syncagent/vue";
const client = new SyncAgentClient({
apiKey: import.meta.env.VITE_SYNCAGENT_KEY,
connectionString: import.meta.env.VITE_DATABASE_URL,
externalUserId: "user_abc123",
});
const {
messages,
conversationId,
isLoading,
isEscalated,
isResolved,
error,
welcomeMessage,
sendMessage,
rateConversation,
reset,
} = useCustomerChat({
client,
onEscalated: () => {
console.log("Conversation escalated to a human agent");
},
onResolved: (id) => {
console.log(`Conversation ${id} resolved`);
},
});
const input = ref("");
function send() {
if (!input.value.trim()) return;
sendMessage(input.value);
input.value = "";
}
</script>
<template>
<div class="customer-chat">
<div v-if="welcomeMessage" class="welcome">{{ welcomeMessage }}</div>
<div class="messages">
<div v-for="(msg, i) in messages" :key="i" :class="['message', msg.role]">
{{ msg.content }}
</div>
</div>
<div v-if="isEscalated" class="notice">🙋 You've been connected to a human agent.</div>
<div v-if="isResolved" class="notice">
✅ Conversation resolved.
<button @click="rateConversation(5)">Rate ⭐⭐⭐⭐⭐</button>
</div>
<div v-if="error" class="error">⚠️ {{ error.message }}</div>
<div class="input-row">
<input v-model="input" @keydown.enter="send" placeholder="Ask a question..." :disabled="isLoading" />
<button @click="send" :disabled="isLoading || !input.trim()">Send</button>
<button @click="reset">Clear</button>
</div>
</div>
</template>Return Values
| Name | Type | Description |
|------|------|-------------|
| messages | Ref<Message[]> | Conversation message history |
| conversationId | Ref<string \| null> | Current conversation ID |
| isLoading | Ref<boolean> | True while awaiting a response |
| isEscalated | Ref<boolean> | True when escalated to a human agent |
| isResolved | Ref<boolean> | True when the conversation is resolved |
| error | Ref<Error \| null> | Last error, if any |
| welcomeMessage | Ref<string \| null> | Welcome message (first interaction only) |
| sendMessage | (content: string, metadata?: Record<string, any>) => Promise<void> | Send a customer message |
| rateConversation | (rating: number) => Promise<void> | Rate the conversation (1-5) |
| reset | () => void | Clear all state and start fresh |
UseCustomerChatOptions
| Option | Type | Description |
|--------|------|-------------|
| client | SyncAgentClient | Required. Must be configured with externalUserId |
| onEscalated | () => void | Called when the conversation is escalated to a human agent |
| onResolved | (conversationId: string) => void | Called when the conversation is resolved |
Guest Identification
When no externalUserId is provided, the composable activates a guest identification flow. Guests must identify themselves before sending messages. The composable exposes reactive refs and a method for managing this flow.
Guest Fields from useCustomerChat
| Name | Type | Description |
|------|------|-------------|
| isIdentified | Ref<boolean> | true when the guest has been identified (via form or stored identity) |
| guestIdentity | Ref<GuestIdentity \| null> | The current guest identity object, or null if not yet identified |
| identifyGuest | (data: { name: string; email: string; phone?: string }) => void | Submit guest identification data — validates, generates ID, persists, and transitions state |
Using the GuestIdentificationForm Component
The GuestIdentificationForm SFC provides a ready-made form with built-in validation. It emits a submit event with the complete GuestIdentity when the guest provides valid data.
<script setup lang="ts">
import { SyncAgentClient } from "@syncagent/js";
import { useCustomerChat, GuestIdentificationForm } from "@syncagent/vue";
import type { GuestIdentity, GuestFormConfig } from "@syncagent/vue";
const client = new SyncAgentClient({
apiKey: import.meta.env.VITE_SYNCAGENT_KEY,
});
const { messages, isIdentified, sendMessage, identifyGuest } = useCustomerChat({
client,
onGuestIdentified: (identity) => {
console.log("Guest identified:", identity.name, identity.email);
},
});
const formConfig: GuestFormConfig = {
title: "Welcome!",
subtitle: "Tell us a bit about yourself to get started.",
submitButtonText: "Start Chat",
namePlaceholder: "Your name",
emailPlaceholder: "[email protected]",
};
function handleGuestSubmit(identity: GuestIdentity) {
identifyGuest(identity);
}
</script>
<template>
<div class="chat">
<GuestIdentificationForm
v-if="!isIdentified"
:config="formConfig"
class-name="guest-form"
@submit="handleGuestSubmit"
/>
<div v-else>
<div v-for="(msg, i) in messages" :key="i" :class="msg.role">
{{ msg.content }}
</div>
<!-- chat input here -->
</div>
</div>
</template>Manual Guest Identification via Composable
You can build your own form and call identifyGuest directly:
<script setup lang="ts">
import { ref } from "vue";
import { SyncAgentClient } from "@syncagent/js";
import { useCustomerChat } from "@syncagent/vue";
const client = new SyncAgentClient({
apiKey: import.meta.env.VITE_SYNCAGENT_KEY,
});
const { isIdentified, guestIdentity, identifyGuest, error, messages, sendMessage } = useCustomerChat({
client,
onGuestIdentified: (identity) => {
console.log("Guest identified:", identity.guestId);
},
});
const name = ref("");
const email = ref("");
const phone = ref("");
function submitGuestForm() {
identifyGuest({
name: name.value,
email: email.value,
phone: phone.value || undefined,
});
}
</script>
<template>
<div>
<form v-if="!isIdentified" @submit.prevent="submitGuestForm">
<input v-model="name" placeholder="Name" required />
<input v-model="email" type="email" placeholder="Email" required />
<input v-model="phone" type="tel" placeholder="Phone (optional)" />
<button type="submit">Start Chat</button>
<p v-if="error" class="error">{{ error.message }}</p>
</form>
<div v-else>
<p>Welcome, {{ guestIdentity?.name }}!</p>
<div v-for="(msg, i) in messages" :key="i" :class="msg.role">
{{ msg.content }}
</div>
<!-- chat input here -->
</div>
</div>
</template>Note: If
identifyGuestis called with invalid data (empty name or invalid email), theerrorref is set with a validation message andisIdentifiedremainsfalse. Messages cannot be sent until the guest is identified.
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.
Legacy pattern (deprecated):
<script setup lang="ts">
import { SyncAgentClient } from "@syncagent/js";
import { useSyncAgent, useCustomerChat } from "@syncagent/vue";
const { db, support } = SyncAgentClient.createDual({
apiKey: import.meta.env.VITE_SYNCAGENT_KEY,
connectionString: import.meta.env.VITE_DATABASE_URL,
externalUserId: "customer_123",
});
// Database agent for admin queries
const { messages: adminMessages, sendMessage: adminSend } = useSyncAgent({ client: db });
// Customer agent for support
const { messages: supportMessages, sendMessage: supportSend } = useCustomerChat({ client: support });
</script>Before (deprecated):
<script setup lang="ts">
import { SyncAgentClient } from "@syncagent/js";
import { useSyncAgent, useCustomerChat } from "@syncagent/vue";
const { db, support } = SyncAgentClient.createDual({
apiKey: import.meta.env.VITE_SYNCAGENT_KEY,
connectionString: import.meta.env.VITE_DATABASE_URL,
externalUserId: "customer_123",
});
const { messages: adminMessages, sendMessage: adminSend } = useSyncAgent({ client: db });
const { messages: supportMessages, sendMessage: supportSend } = useCustomerChat({ client: support });
</script>After (recommended):
<script setup lang="ts">
import { SyncAgentClient } from "@syncagent/js";
import { useDualChat } from "@syncagent/vue";
const dualClient = SyncAgentClient.createDual({
apiKey: import.meta.env.VITE_SYNCAGENT_KEY,
connectionString: import.meta.env.VITE_DATABASE_URL,
externalUserId: "customer_123",
});
const { db, support } = useDualChat({ client: dualClient });
</script>Unified Dual Mode
The useDualChat() composable provides a single reactive interface for both database and customer chat on the same client instance. Pass externalUserId to a DualClient to enable both db (database agent) and support (customer agent) namespaces from one setup.
<script setup lang="ts">
import { ref } from "vue";
import { SyncAgentClient } from "@syncagent/js";
import { useDualChat } from "@syncagent/vue";
const dualClient = SyncAgentClient.createDual({
apiKey: import.meta.env.VITE_SYNCAGENT_KEY,
connectionString: import.meta.env.VITE_DATABASE_URL,
externalUserId: "user_abc123",
});
const { db, support } = useDualChat({ client: dualClient });
const dbInput = ref("");
const supportInput = ref("");
function sendDbMessage() {
if (!dbInput.value.trim()) return;
db.sendMessage(dbInput.value);
dbInput.value = "";
}
function sendSupportMessage() {
if (!supportInput.value.trim()) return;
support.sendMessage(supportInput.value);
supportInput.value = "";
}
</script>
<template>
<div class="dual-chat">
<!-- Database Agent -->
<div class="panel">
<h3>Database Agent</h3>
<div v-for="(msg, i) in db.messages" :key="'db-' + i" :class="msg.role">
{{ msg.content }}
</div>
<input v-model="dbInput" @keydown.enter="sendDbMessage" placeholder="Query your data..." />
<button @click="sendDbMessage" :disabled="db.isLoading">Send</button>
</div>
<!-- Customer Support -->
<div class="panel">
<h3>Customer Support</h3>
<div v-for="(msg, i) in support.messages" :key="'support-' + i" :class="msg.role">
{{ msg.content }}
</div>
<input v-model="supportInput" @keydown.enter="sendSupportMessage" placeholder="Ask support..." />
<button @click="sendSupportMessage" :disabled="support.isLoading">Send</button>
</div>
</div>
</template>DualChatReturn
All properties are Vue reactive refs.
db namespace
| Name | Type | Description |
|------|------|-------------|
| db.messages | Ref<Message[]> | Database agent conversation history |
| db.isLoading | Ref<boolean> | True while the database agent is streaming |
| db.error | Ref<Error \| null> | Last error from the database agent |
| db.status | Ref<{ step: string; label: string } \| null> | Live status (connecting, querying, thinking, done) |
| db.lastData | Ref<ToolData \| null> | Last DB query result |
| db.sendMessage | (content: string) => Promise<void> | Send a message to the database agent |
| db.stop | () => void | Abort the current database agent stream |
| db.reset | () => void | Clear database agent messages |
support namespace
| Name | Type | Description |
|------|------|-------------|
| support.messages | Ref<Message[]> | Customer support conversation history |
| support.conversationId | Ref<string \| null> | Current support conversation ID |
| support.isLoading | Ref<boolean> | True while awaiting a support response |
| support.isEscalated | Ref<boolean> | True when escalated to a human agent |
| support.isResolved | Ref<boolean> | True when the conversation is resolved |
| support.error | Ref<Error \| null> | Last error from the support agent |
| support.welcomeMessage | Ref<string \| null> | Welcome message (first interaction only) |
| support.sendMessage | (content: string, metadata?: Record<string, any>) => Promise<void> | Send a message to customer support |
| support.rateConversation | (rating: number) => Promise<void> | Rate the support conversation (1-5) |
| support.reset | () => void | Clear support state and start fresh |
UseDualChatOptions
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| client | DualClient | Yes | A client created via SyncAgentClient.createDual() with externalUserId |
| context | Record<string, any> | No | Extra context injected into every message for both agents |
| onData | (data: ToolData) => void | No | Called when the database agent tool returns data |
| onEscalated | () => void | No | Called when the support conversation is escalated to a human agent |
| onResolved | (conversationId: string) => void | No | Called when the support conversation is resolved |
Error Handling
If useDualChat() is called without a valid DualClient instance, it throws:
useDualChat: a valid DualClient instance is requiredEnsure you pass a client created via SyncAgentClient.createDual() with a valid externalUserId.
Customer Chat Component
The <SyncAgentCustomerChat> component is a pre-built, drop-in customer support chat widget. It handles the full conversation lifecycle: guest identification, messaging, escalation to human agents, real-time Pusher messages, satisfaction rating, and theming — all with zero custom UI required.
<script setup lang="ts">
import { SyncAgentCustomerChat } from "@syncagent/vue";
</script>
<template>
<SyncAgentCustomerChat
api-key="sa_your_api_key"
connection-string="your_connection_string"
/>
</template>A floating chat button appears in the bottom-right corner. Anonymous visitors see a guest identification form before chatting.
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| config | SyncAgentConfig | — | Pre-built configuration object. Takes priority over individual props. |
| apiKey | string | — | API key (required if no config). |
| connectionString | string | — | Database connection string (required if no config). |
| 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 the 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> | — | Extra metadata sent with every message. |
Emits
| Event | Payload | Description |
|-------|---------|-------------|
| escalated | — | Fired when the conversation is escalated to a human agent. |
| resolved | conversationId: string | Fired when the conversation is resolved. |
| guest-identified | identity: GuestIdentity | Fired when a guest completes identification. |
Code Examples
Config Object
<script setup lang="ts">
import { SyncAgentCustomerChat } from "@syncagent/vue";
const config = {
apiKey: "sa_your_api_key",
connectionString: import.meta.env.VITE_DATABASE_URL,
customerMode: true,
externalUserId: "user_abc123",
};
</script>
<template>
<SyncAgentCustomerChat :config="config" />
</template>Floating Mode (default)
<template>
<SyncAgentCustomerChat
api-key="sa_your_api_key"
connection-string="your_connection_string"
position="bottom-left"
:default-open="true"
title="Need Help?"
subtitle="We're here for you"
/>
</template>Inline Mode
Embed the chat inside your layout instead of a floating button:
<template>
<div style="height: 600px">
<SyncAgentCustomerChat
api-key="sa_your_api_key"
connection-string="your_connection_string"
mode="inline"
/>
</div>
</template>Dark Mode with Custom Accent Color
<template>
<SyncAgentCustomerChat
api-key="sa_your_api_key"
connection-string="your_connection_string"
:dark-mode="true"
accent-color="#8b5cf6"
/>
</template>Handling Emitted Events
<script setup lang="ts">
import { SyncAgentCustomerChat } from "@syncagent/vue";
import type { GuestIdentity } from "@syncagent/vue";
function onEscalated() {
console.log("Conversation escalated to a human agent");
}
function onResolved(conversationId: string) {
console.log("Conversation resolved:", conversationId);
}
function onGuestIdentified(identity: GuestIdentity) {
console.log("Guest identified:", identity.name, identity.email);
}
</script>
<template>
<SyncAgentCustomerChat
api-key="sa_your_api_key"
connection-string="your_connection_string"
@escalated="onEscalated"
@resolved="onResolved"
@guest-identified="onGuestIdentified"
/>
</template>useCustomerChat Composable
For developers who want to build a fully custom chat UI, the useCustomerChat composable provides all the reactive state and methods without any pre-built UI.
Options
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| client | SyncAgentClient | Yes | Client instance (configure with customerMode: true). |
| externalUserId | string | No | Authenticated user ID. Bypasses guest flow when provided. |
| onEscalated | () => void | No | Called when the conversation is escalated to a human agent. |
| onResolved | (conversationId: string) => void | No | Called when the conversation is resolved. |
| onGuestIdentified | (identity: GuestIdentity) => void | No | Called when a guest completes identification. |
Return Values
| Name | Type | Description |
|------|------|-------------|
| messages | Ref<Message[]> | Conversation message history. |
| conversationId | Ref<string \| null> | Current conversation ID (set after first message). |
| isLoading | Ref<boolean> | true while awaiting a response. |
| isEscalated | Ref<boolean> | true when escalated to a human agent. |
| isResolved | Ref<boolean> | true when the conversation is resolved. |
| error | Ref<Error \| null> | Last error, or null. |
| welcomeMessage | Ref<string \| null> | Welcome message (first interaction only). |
| isIdentified | Ref<boolean> | true when the user is identified (has externalUserId or completed guest form). |
| guestIdentity | Ref<GuestIdentity \| null> | The stored guest identity, or null. |
| 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). |
| identifyGuest | (data: { name: string; email: string; phone?: string }) => void | Submit guest identification data. |
| reset | () => void | Clear all state and start fresh. |
Custom UI Example
<script setup lang="ts">
import { ref } from "vue";
import { SyncAgentClient } from "@syncagent/js";
import { useCustomerChat } from "@syncagent/vue";
const client = new SyncAgentClient({
apiKey: import.meta.env.VITE_SYNCAGENT_KEY,
connectionString: import.meta.env.VITE_DATABASE_URL,
customerMode: true,
externalUserId: "user_abc123",
});
const {
messages,
isLoading,
isEscalated,
isResolved,
error,
sendMessage,
rateConversation,
} = useCustomerChat({ client });
const input = ref("");
function send() {
if (!input.value.trim()) return;
sendMessage(input.value);
input.value = "";
}
</script>
<template>
<div class="my-custom-chat">
<div v-for="(msg, i) in messages" :key="i" :class="msg.role">
{{ msg.content }}
</div>
<p v-if="isEscalated">You're connected to a human agent.</p>
<div v-if="isResolved">
<p>Conversation resolved!</p>
<button @click="rateConversation(5)">⭐ Rate 5/5</button>
</div>
<p v-if="error" class="error">{{ error.message }}</p>
<form @submit.prevent="send">
<input v-model="input" placeholder="Ask a question..." :disabled="isLoading" />
<button type="submit" :disabled="isLoading || !input.trim()">Send</button>
</form>
</div>
</template>Theming
The <SyncAgentCustomerChat> component uses the shared theme engine (computeTheme from @syncagent/js) to generate a complete WCAG-compliant color palette from two inputs:
accentColor— Any valid CSS color string (hex, rgb, hsl). Used for buttons, user message bubbles, focus outlines, and the header background. Defaults to#6366f1.darkMode— Whentrue, generates a dark color scheme. All text-on-background pairs maintain a minimum 4.5:1 contrast ratio.
All styles are applied inline, so no external CSS is required and no styles leak to or from your application. You can pass a className prop to add your own class for additional positioning or layout styles, and use the :style binding on a wrapper element for further customization.
<template>
<!-- Custom accent + dark mode -->
<SyncAgentCustomerChat
api-key="sa_your_api_key"
connection-string="your_connection_string"
accent-color="#ec4899"
:dark-mode="true"
class-name="my-chat-widget"
/>
</template>
<style>
.my-chat-widget {
/* Additional layout styles — component colors are handled internally */
margin: 16px;
}
</style>Accessibility
The <SyncAgentCustomerChat> component is built to meet WCAG 2.1 AA standards:
- ARIA roles: The container uses
role="region"with anaria-label. The message list usesrole="log"to convey its purpose to assistive technologies. - Live regions: New messages are announced via
aria-live="polite"witharia-relevant="additions", so screen readers announce incoming messages without interrupting the user. - Keyboard navigation: All interactive elements are reachable via Tab. Buttons activate with Enter or Space. Rating stars use a roving tabindex pattern with arrow key navigation.
- Focus management: When the view transitions from the guest form to the chat panel, focus moves to the message input within 100ms. This ensures keyboard users aren't stranded.
- Contrast requirements: The theme engine guarantees a minimum 4.5:1 contrast ratio for all text-on-background pairs and a minimum 3:1 contrast ratio for focus indicators against adjacent colors.
- Focus indicators: All interactive elements display a visible focus outline (minimum 2px width) with at least 3:1 contrast against adjacent colors.
API Reference
useSyncAgent(options)
| Option | Type | Description |
|--------|------|-------------|
| client | SyncAgentClient | Required. Create with new SyncAgentClient(config) |
| context | Record<string,any> | Extra context injected into every message |
| onData | (data: ToolData) => void | Called when a DB tool returns data |
Returns
| Name | Type | Description |
|------|------|-------------|
| messages | Readonly<Ref<Message[]>> | Conversation history |
| isLoading | Readonly<Ref<boolean>> | True while streaming |
| error | Readonly<Ref<Error\|null>> | Last error |
| status | Readonly<Ref<{step,label}\|null>> | Live status (connecting, querying, thinking, done) |
| lastData | Readonly<Ref<ToolData\|null>> | Last DB query result |
| sendMessage | (content: string) => Promise<void> | Send a message |
| stop | () => void | Abort current stream |
| reset | () => void | Clear all messages |
Supported Databases
| Database | Connection String Format |
|----------|------------------------|
| MongoDB | mongodb+srv://user:[email protected]/mydb |
| PostgreSQL | postgresql://user:pass@host:5432/mydb |
| MySQL | mysql://user:pass@host:3306/mydb |
| SQLite | /absolute/path/to/database.sqlite |
| SQL Server | Server=host,1433;Database=mydb;User Id=user;Password=pass;Encrypt=true; |
| Supabase | https://xxx.supabase.co\|your-anon-key |
Security
- Your database connection string is never stored on SyncAgent servers
- Passed at runtime, used once, immediately discarded
- API keys are hashed with bcrypt — raw keys are never stored
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
License
MIT
