npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@syncagent/vue

v0.5.1

Published

SyncAgent Vue 3 SDK — composables and chat component

Downloads

552

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.

npm version License: MIT

Get Your API Key

  1. Sign up for a free account
  2. Go to your DashboardNew Project → choose your database type
  3. 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/js

Quick 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 identifyGuest is called with invalid data (empty name or invalid email), the error ref is set with a validation message and isIdentified remains false. Messages cannot be sent until the guest is identified.

Dual Mode (Database + Customer Agent)

⚠️ Deprecated: createDual() will be removed in a future major version. Use useDualChat() instead — pass externalUserId directly 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 required

Ensure 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 — When true, 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 an aria-label. The message list uses role="log" to convey its purpose to assistive technologies.
  • Live regions: New messages are announced via aria-live="polite" with aria-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 |

View full pricing →

Resources

License

MIT