Skip to content

Repository files navigation

Echo logo

Echo

The B2B AI‑Powered Customer Support Platform

Drop one <script> tag on any website and give your customers a 24/7 AI support agent — over chat and voice — grounded in your own knowledge base, with a real‑time operator dashboard for seamless human takeover.

CICodeQLLicense: MITNode.jspnpmTypeScriptNext.jsTurborepoTailwind CSSConvexClerkGoogle GeminiVapiSentry

Product Overview · Documentation · Architecture · Report Bug · Request Feature


Table of Contents


What is Echo?

Echo is a multi‑tenant, AI‑powered customer support platform that companies embed on their websites to deflect support tickets and delight customers — without staffing a 24/7 team.

A visitor clicks the chat launcher on a customer's site and talks to an AI agent that:

  • Answers from your knowledge base, not from generic training data — every answer is grounded in documents you upload (retrieval‑augmented generation, or RAG).
  • Escalates to a human the moment a customer is frustrated or explicitly asks for one.
  • Resolves and closes conversations when the customer is satisfied.
  • Talks, literally — the same widget can place a live voice call to an AI voice agent (powered by Vapi) or surface a real phone number.

Behind the scenes, your support operators work from a real‑time dashboard: a filterable inbox of every conversation, a full chat view with AI‑assisted reply drafting, and a rich contact panel showing exactly who they're talking to (device, browser, location, session history).

Echo is built as a production‑grade Turborepo monorepo — three deployable apps and a shared, fully‑typed Convex backend — so the entire system (widget → backend → dashboard) shares one source of truth end to end.

flowchart LR
subgraph Customer["🌐 Your Customer's Website"]
V["Website Visitor"]
E["Echo Embed Script<br/>(one &lt;script&gt; tag)"]
end
subgraph Echo["⚡ Echo Platform"]
W["Widget App<br/>(chat · voice · contact)"]
B["Convex Backend<br/>(real-time · typed)"]
AI["AI Agent + RAG<br/>(Gemini)"]
D["Operator Dashboard"]
end
V --> E --> W <-->|"real-time"| B
B <--> AI
B <-->|"live sync"| D
O["🧑‍💼 Support Operator"] --> D
style Echo fill:#eef4ff,stroke:#377ff6
style Customer fill:#f7f7f8,stroke:#bbb
Loading

Why Echo

Pain todayHow Echo solves it
Support agents answer the same questions all dayAn AI agent deflects repetitive questions 24/7, grounded in your docs
Chatbots hallucinate and invent policiesRAG means answers come only from your uploaded knowledge base — "I don't know, want a human?" beats a confident wrong answer
Customers get stuck with a botAutomatic escalation on frustration; instant human takeover from the dashboard
Voice support needs a call centerOne click launches an AI voice agent or reveals a real phone line
Integrating a widget means a sprintA single <script> tag. Copy‑paste snippets for HTML, React, Next.js, and JavaScript
No visibility into who you're helpingA contact panel with device, OS, browser, country, and session metadata for every visitor
Rolling your own multi‑tenant SaaS is hardClerk organizations, per‑org data isolation, plan‑based feature gating, and billing are built in

Feature Tour

🧩 Embeddable Widget & Loader

  • One‑tag install — a dependency‑free Vite/IIFE loader (apps/embed) injects a floating launcher button and an organization‑scoped <iframe> onto any page.
  • window.EchoWidget APIinit(), show(), hide(), destroy() for programmatic control, plus postMessage‑based close/resize coordination between host page and widget.
  • Configurabledata-organization-id and data-position (bottom-right / bottom-left) attributes on the script tag.
  • Copy‑paste snippets — the dashboard's Integrations page generates ready‑to‑paste embed code for HTML, React, Next.js, and JavaScript.

🤖 AI Support Agent

  • Grounded answers (RAG) — powered by @convex-dev/agent + @convex-dev/rag, using Google Gemini 2.5 Flash for chat and gemini-embedding-001 for embeddings.
  • Tool‑calling agent — the agent autonomously invokes three tools:
    • searchTool — semantic search over the organization's knowledge base with a dedicated result‑interpretation pass (never fabricates; offers a human when it can't find an answer).
    • escalateConversationTool — flags the conversation for a human on frustration or explicit request.
    • resolveConversationTool — closes the conversation when the customer is done.
  • Per‑conversation memory — each conversation maps to an agent thread with full, paginated message history.
  • Centralized prompt engineering — identity, conversation flow, tone, and interpretation prompts live in system/ai/constants.

📚 Knowledge Base (RAG)

  • Multi‑format ingestion — upload PDF, CSV, and TXT files; Gemini extracts text from images, PDFs, and HTML.
  • Organization‑namespaced — each org's embeddings live in an isolated RAG namespace; no cross‑tenant search leakage.
  • Content‑hash deduplication — re‑uploading unchanged content is a no‑op.
  • Managed in the dashboard — a paginated file table with drag‑and‑drop upload and delete, backed by AWS‑storage‑aware size resolution.

📞 Voice AI (Vapi)

  • Live web voice calls — the widget's voice screen streams a real‑time transcript with a speaking/listening indicator and start/end controls (useVapi).
  • Secure by design — private Vapi keys are encrypted in AWS Secrets Manager; the widget only ever receives the public key via a dedicated action.
  • Phone numbers — the contact screen offers copy‑to‑clipboard and tap‑to‑call for a configured business line.
  • Dashboard management — connect Vapi, then browse your assistants and phone numbers in tabbed tables.

🎛️ Operator Dashboard

  • Conversations inbox — resizable, infinitely‑scrolling, status‑filterable (unresolved / escalated / resolved) list, with country‑flag avatars derived from the visitor's timezone.
  • Live chat view — reply to any conversation through a full AI Elements chat UI; sending a reply auto‑escalates the conversation so the AI steps back.
  • AI "Enhance" — one click rewrites an operator's draft into a polished, professional response (Gemini).
  • Contact panel — avatar, email, "Send Email," and collapsible device / location / session metadata (browser, OS, screen, viewport, cookies, language, timezone, UTC offset) parsed with bowser.
  • Status control — cycle a conversation through unresolved → escalated → resolved with a single button.

🔐 Multi‑Tenancy & Authentication

  • Clerk organizations — every user belongs to an organization; all data is partitioned by organizationId.
  • Guarded routing — Clerk middleware protects the dashboard, redirects org‑less users to an org‑selection flow, and AuthGuard + OrganizationGuard enforce it in the React tree.
  • Convex ↔ Clerk bridgeConvexProviderWithClerk passes Clerk JWTs to Convex so every backend function can trust ctx.auth.
  • Public vs. private vs. system — backend functions are cleanly separated: public (widget‑facing, unauthenticated, scoped by org ID), private (dashboard, Clerk‑identity gated), and system/internal (never exposed to clients).

💳 Billing & Plan Gating

  • Clerk‑powered pricing — an organization‑scoped PricingTable on the billing page.
  • Server‑side gating — Knowledge Base, Voice Assistant, and Widget Customization pages check auth().has({ plan: "pro" }) and render a PremiumFeatureOverlay upsell when the org isn't on Pro.
  • Subscription webhook — a signature‑verified (svix) Clerk webhook syncs subscription status and adjusts the org's seat limit (5 seats when active, 1 otherwise).
  • Feature enforcement — AI auto‑response, message enhancement, and file uploads all require an active subscription.

🛠️ Platform & Developer Experience

  • Turborepo monorepo — topological task orchestration with caching across three apps and five packages.
  • End‑to‑end type safety — Convex generates a fully‑typed API consumed by both Next.js apps.
  • Real‑time everywhere — Convex live queries mean the dashboard updates the instant a visitor sends a message.
  • Error monitoring — Sentry across client, server, and edge runtimes with session replay and tracing.
  • Quality gates — ESLint 9, Prettier, TypeScript strict mode, CodeQL, Dependabot, and PR‑title validation in CI.

System Architecture

Echo is a workspace dependency graph: two Next.js apps and one Vite loader consume shared packages, all built on a single Convex backend that owns the schema and every server function.

flowchart TB
subgraph apps["apps/"]
embed["<b>embed</b><br/>Vite IIFE loader<br/>:3002 (dev)"]
widget["<b>widget</b><br/>Next.js — visitor UI<br/>:3001"]
web["<b>web</b><br/>Next.js — operator dashboard<br/>:3000"]
end
subgraph packages["packages/"]
backend["<b>@workspace/backend</b><br/>Convex: schema + functions"]
ui["<b>@workspace/ui</b><br/>shadcn/ui + AI Elements"]
math["<b>@workspace/math</b>"]
eslintcfg["<b>eslint-config</b>"]
tscfg["<b>typescript-config</b>"]
end
subgraph external["External Services"]
clerk["Clerk<br/>(auth · orgs · billing)"]
gemini["Google Gemini<br/>(chat · embeddings)"]
vapi["Vapi<br/>(voice)"]
aws["AWS Secrets Manager<br/>(encrypted keys)"]
sentry["Sentry<br/>(monitoring)"]
end
embed -.->|"injects iframe"| widget
widget --> backend
web --> backend
widget --> ui
web --> ui
web --> clerk
backend --> clerk
backend --> gemini
backend --> vapi
backend --> aws
web --> sentry
style apps fill:#eef4ff,stroke:#377ff6
style packages fill:#f0fff4,stroke:#3FB62F
style external fill:#fff7ed,stroke:#f59e0b
Loading

Request‑path summary

ConsumerTalks toAuthorization
Widget (visitor)convex/public/* functionsUnauthenticated; scoped by organizationId + contact‑session validation
Dashboard (operator)convex/private/* functionsClerk identity via ctx.auth, org‑matched
Convex internalsconvex/system/* functionsinternal* — never callable from a client
Clerkconvex/http.ts (/clerk-webhook)svix signature verification

For a deep dive, see docs/architecture.md.


How It Works — Core Flows

1. Widget Bootstrap & Session Lifecycle

When the widget loads, it runs a multi‑step state machine (Jotai atoms) before showing a chat surface — validating the organization, restoring or creating a contact session, loading widget settings, and probing for voice capability.

sequenceDiagram
autonumber
participant Host as Host Website
participant Embed as Embed Script
participant Widget as Widget (iframe)
participant CVX as Convex (public)
Host->>Embed: load <script data-organization-id="org_…">
Embed->>Host: inject launcher button + iframe
Note over Widget: Screen = "loading"
Widget->>CVX: organizations.validate(orgId)
CVX-->>Widget: { valid: true }
Widget->>CVX: contactSessions.validate(sessionId?)
alt session valid (localStorage)
CVX-->>Widget: { valid: true }
Note over Widget: → "selection"
else no / expired session
Note over Widget: → "auth" (collect name + email)
end
Widget->>CVX: widgetSettings.getByOrganizationId(orgId)
CVX-->>Widget: greeting + suggestions
Widget->>CVX: secrets.getVapiSecrets(orgId)
CVX-->>Widget: { publicApiKey } | null
Note over Widget: Voice options shown only if key present
Loading

Screens:loading → error | auth | selection, then chat, voice, contact, or inbox. Contact sessions carry a 24‑hour TTL that auto‑refreshes whenever the visitor is active, so long conversations never expire mid‑chat.

2. AI Conversation & RAG Pipeline

A visitor's message triggers the AI agent — but only when the conversation is unresolved and the organization has an active subscription. The agent decides whether to search the knowledge base, answer, escalate, or resolve.

sequenceDiagram
autonumber
participant Visitor
participant Widget
participant Msgs as public/messages.create
participant Agent as supportAgent (Gemini)
participant RAG as Knowledge Base (RAG)
participant DB as Convex DB
Visitor->>Widget: "How do I reset my password?"
Widget->>Msgs: create(threadId, prompt, sessionId)
Msgs->>DB: refresh contact session TTL
Msgs->>DB: check subscription = active?
alt active & conversation unresolved
Msgs->>Agent: generateText(prompt, tools)
Agent->>RAG: searchTool("reset password")
RAG->>RAG: embed query → vector search (org namespace)
RAG->>Agent: top matches + interpretation
Agent-->>Widget: grounded answer
else escalate / resolve
Agent->>DB: escalateConversationTool / resolveConversationTool
Agent-->>Widget: "Connecting you to a human…"
else no subscription
Note over Msgs: message saved, AI stays silent
end
Loading

The searchTool runs a two‑stage RAG: a vector search over the org's namespace, then a second Gemini pass (SEARCH_INTERPRETER_PROMPT) that turns raw matches into a faithful, conversational answer — or a "I couldn't find that, want a human?" fallback.

3. Human Operator Takeover

Operators watch a live inbox and can jump into any conversation. Replying instantly escalates the conversation so the AI stops auto‑responding.

sequenceDiagram
autonumber
participant Op as Operator (Dashboard)
participant Priv as private/messages
participant DB as Convex DB
participant Widget as Visitor Widget
Note over Op: Inbox streams conversations in real time
Op->>Priv: getMany(threadId) — load history
Op->>Priv: enhanceResponse(draft) — optional AI polish
Priv-->>Op: refined reply
Op->>Priv: create(conversationId, prompt)
Priv->>DB: if unresolved → status = "escalated"
Priv->>DB: saveMessage(role: assistant, operator)
DB-->>Widget: message appears live
Loading

4. Voice Calls (Vapi)

If the organization has connected Vapi and configured an assistant, the widget's selection screen surfaces a "Start voice call" option.

sequenceDiagram
autonumber
participant Visitor
participant Widget
participant CVX as public/secrets.getVapiSecrets
participant AWS as AWS Secrets Manager
participant Vapi
Widget->>CVX: getVapiSecrets(orgId)
CVX->>AWS: decrypt tenant/{org}/vapi
AWS-->>CVX: { publicApiKey, privateApiKey }
CVX-->>Widget: { publicApiKey } // public only
Visitor->>Widget: "Start call"
Widget->>Vapi: vapi.start(assistantId) with publicApiKey
Vapi-->>Widget: live transcript + speaking events
Loading

5. Billing & Subscription Gating

Plans are managed by Clerk; a signed webhook keeps Convex in sync and drives feature access.

sequenceDiagram
autonumber
participant Clerk
participant HTTP as convex/http.ts (/clerk-webhook)
participant Sys as system/subscriptions
participant DB as Convex DB
participant Guard as Dashboard page
Clerk->>HTTP: POST subscription.updated (svix-signed)
HTTP->>HTTP: verify svix signature
HTTP->>Clerk: set maxAllowedMemberships (5 active / 1 inactive)
HTTP->>Sys: upsert(orgId, status)
Sys->>DB: subscriptions table
Note over Guard: auth().has({ plan: "pro" })
Guard-->>Guard: gated view or PremiumFeatureOverlay
Loading

Data Model

All business tables are partitioned by organizationId and indexed for the access patterns above. Convex generates fully‑typed accessors from this schema.

erDiagram
ORGANIZATION ||--o{ CONTACT_SESSION : "has visitors"
ORGANIZATION ||--o{ CONVERSATION : "owns"
ORGANIZATION ||--o| WIDGET_SETTINGS : "configures"
ORGANIZATION ||--o| SUBSCRIPTION : "billed by"
ORGANIZATION ||--o{ PLUGIN : "connects"
CONTACT_SESSION ||--o{ CONVERSATION : "starts"
CONVERSATION ||--|| AGENT_THREAD : "maps to (threadId)"
CONTACT_SESSION {
string name
string email
string organizationId
number expiresAt
object metadata "browser · device · locale"
}
CONVERSATION {
string threadId
string organizationId
id contactSessionId
enum status "unresolved · escalated · resolved"
}
WIDGET_SETTINGS {
string organizationId
string greetMessage
object defaultSuggestions
object vapiSettings "assistantId · phoneNumber"
}
SUBSCRIPTION {
string organizationId
string status
}
PLUGIN {
string organizationId
enum service "vapi"
string secretName "→ AWS Secrets Manager"
}
Loading
TablePurposeKey indexes
contactSessionsA widget visitor's identity + captured browser metadata; 24h TTLby_organization_id, by_expires_at
conversationsLinks a contact session to an agent thread with a resolution statusby_organization_id, by_thread_id, by_contact_session_id, by_status_and_organization_id
widgetSettingsPer‑org greeting, quick‑reply suggestions, Vapi assistant/phone selectionby_organization_id
subscriptionsMirror of the org's Clerk subscription statusby_organization_id
pluginsRecords that an org connected a service (Vapi); points to its AWS secret nameby_organization_id, by_organization_id_and_service
usersScaffold table

Agent threads, messages, and RAG embeddings are managed by the @convex-dev/agent and @convex-dev/rag Convex components (registered in convex.config.ts), keyed by threadId and per‑org namespaces respectively.

See docs/data-model.md for full field‑level detail.


Tech Stack

LayerTechnology
MonorepoTurborepo 2 · pnpm 10 workspaces
Web frameworkNext.js 16 (App Router, RSC, Turbopack) · React 19
LanguageTypeScript 5 (strict)
StylingTailwind CSS 4 · shadcn/ui · Radix UI · AI Elements
BackendConvex (real‑time DB, queries/mutations/actions, HTTP actions, components)
Auth & OrgsClerk (organizations, JWT, billing, PricingTable)
AI — chatGoogle Gemini 2.5 Flash via @ai-sdk/google + @convex-dev/agent
AI — RAG@convex-dev/rag · gemini-embedding-001 embeddings
VoiceVapi (@vapi-ai/web client, @vapi-ai/server-sdk)
SecretsAWS Secrets Manager (@aws-sdk/client-secrets-manager)
Webhookssvix signature verification
Client stateJotai (+ jotai-family)
FormsReact Hook Form + Zod
Widget loaderVite (IIFE library build)
MonitoringSentry (client · server · edge, session replay)
ToolingESLint 9 · Prettier 3 · Turbo · CodeQL · Dependabot · GitHub Actions

Monorepo Structure

echo/
├── apps/
│ ├── web/ # Operator dashboard (Next.js, :3000)
│ │ ├── app/
│ │ │ ├── (auth)/ # sign-in · sign-up · org-selection
│ │ │ ├── (dashboard)/ # conversations · files · plugins · customization · billing · integrations
│ │ │ ├── icon.svg # Echo favicon
│ │ │ └── layout.tsx # Clerk + Convex providers, metadata
│ │ ├── modules/ # feature modules (auth, dashboard, files, plugins, customization, billing, integrations)
│ │ ├── proxy.ts # Clerk middleware (route protection + org redirect)
│ │ ├── instrumentation*.ts # Sentry init
│ │ └── next.config.ts # withSentryConfig
│ │
│ ├── widget/ # Embeddable visitor widget (Next.js, :3001)
│ │ ├── app/ # renders <WidgetView organizationId=…>
│ │ ├── modules/widget/
│ │ │ ├── atoms/ # Jotai state machine
│ │ │ ├── screens/ # loading · auth · selection · chat · voice · contact · inbox · error
│ │ │ ├── hooks/use-vapi.ts # Vapi web client
│ │ │ └── constants · types
│ │ └── public/widget.js # built embed loader (served at /widget.js)
│ │
│ └── embed/ # Standalone widget loader (Vite IIFE, :3002)
│ ├── embed.ts # launcher + iframe + window.EchoWidget API
│ ├── config.ts · icons.ts
│ └── demo.html · landing.html # playground + smoke test
│
├── packages/
│ ├── backend/convex/ # Convex backend (the system of record)
│ │ ├── schema.ts # all tables + indexes
│ │ ├── convex.config.ts # registers agent + rag components
│ │ ├── http.ts # /clerk-webhook (svix)
│ │ ├── auth.config.ts # Clerk JWT provider
│ │ ├── public/ # widget-facing (unauthenticated)
│ │ ├── private/ # dashboard (Clerk-identity gated)
│ │ ├── system/ # internal + AI (agent, rag, tools, prompts)
│ │ └── lib/ # secrets · text extraction
│ ├── ui/ # shadcn/ui + Radix + AI Elements design system
│ ├── math/ # shared utility package
│ ├── eslint-config/ # base · next · react-internal presets
│ └── typescript-config/ # base · nextjs · react-library presets
│
├── docs/ # 📚 full documentation suite (see below)
├── CHANGELOG.md · CONTRIBUTING.md · SECURITY.md · CODE_OF_CONDUCT.md · LICENSE
└── turbo.json · pnpm-workspace.yaml

Feature modules follow a consistent shape — ui/views, ui/components, ui/layouts, plus atoms, hooks, constants, schemas, and types — keeping domain logic out of the Next.js routing layer.


Getting Started

Prerequisites

ToolVersionNotes
Node.js>= 20fnm recommended
pnpm>= 10.33npm install -g pnpm (repo pins pnpm@10.33.4)
GitLatestgit-scm.com

You will also need free/developer accounts for: Convex, Clerk, Google AI (Gemini), and — for the optional voice + secrets features — Vapi and AWS.

1. Clone & Install

git clone https://github.com/RISHII7/echo.git
cdecho
pnpm install

2. Provision External Services

ServiceWhat to createUsed for
ConvexA project (pnpm --filter backend dev provisions a dev deployment)Database, functions, HTTP actions
ClerkAn application with Organizations enabled, a JWT template named convex, and (for billing) a Pro planAuth, multi‑tenancy, billing, webhooks
Google AI StudioAn API keyGemini chat + embeddings
Vapi(optional)An assistant + public/private API keysVoice calls
AWS(optional)An IAM user with secretsmanager:CreateSecret, PutSecretValue, GetSecretValueEncrypted plugin credentials
Sentry(optional)A project + auth tokenError monitoring

Convex functions run on Convex's servers, so secrets they need (CLERK_SECRET_KEY, CLERK_WEBHOOK_SECRET, GOOGLE_GENERATIVE_AI_API_KEY, AWS_*) must be set in the Convex dashboard (or via npx convex env set), not only in .env.local.

3. Environment Variables

apps/web/.env.local

VariableDescription
NEXT_PUBLIC_CONVEX_URLConvex deployment URL (https://<deployment>.convex.cloud)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYClerk publishable key (pk_test_…)
CLERK_SECRET_KEYClerk secret key (sk_test_…)

apps/widget/.env.local

VariableDescription
NEXT_PUBLIC_CONVEX_URLSame Convex deployment URL as the web app

packages/backend — set in the Convex dashboard (auto‑managed .env.local for CLI):

VariableDescription
CONVEX_DEPLOYMENTConvex deployment identifier (auto)
CLERK_JWT_ISSUER_DOMAINClerk JWT issuer URL for Convex auth
CLERK_SECRET_KEYUsed by the /clerk-webhook HTTP action and Vapi/org actions
CLERK_WEBHOOK_SECRETClerk webhook signing secret (verified via svix)
GOOGLE_GENERATIVE_AI_API_KEYGemini chat + embeddings
AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEYAWS Secrets Manager (voice/plugins)

apps/web/.env.sentry-build-plugin(optional, gitignored)

VariableDescription
SENTRY_AUTH_TOKENUploads source maps on build

4. Run the Stack

# Everything (Convex + web + widget) via Turborepo
pnpm dev
# …or individually
pnpm --filter backend dev # Convex dev server (provisions deployment on first run)
pnpm --filter web dev # Dashboard → http://localhost:3000
pnpm --filter widget dev # Widget → http://localhost:3001
pnpm --filter embed dev # Embed demo → http://localhost:3002/demo.html

Open the dashboard, create an organization, upload a document under Knowledge Base, then open the widget with your org ID:

http://localhost:3001/?organizationId=org_XXXXXXXXXXXXXXXXXXXX

A full, screenshot‑guided walkthrough lives in docs/setup.md.


Backend Function Reference

Convex functions are grouped by trust boundary. Public functions are callable from the unauthenticated widget (and validate org/session themselves); private functions require a Clerk identity; system functions are internal* and only callable from other Convex functions.

public/ — widget-facing (unauthenticated, org-scoped)
FunctionKindDescription
organizations.validateactionVerifies an org ID exists in Clerk
contactSessions.createmutationCreates a visitor session (name, email, browser metadata, 24h TTL)
contactSessions.validatemutationChecks a stored session is present and unexpired
conversations.createmutationRefreshes session, seeds an agent thread with the greeting, creates a conversation
conversations.getOnequeryFetches a conversation (ownership‑verified by session)
conversations.getManyqueryPaginated conversation list for a contact session, with last message
messages.createactionSaves a visitor message; triggers the AI agent when unresolved + subscribed
messages.getManyqueryPaginated thread messages
widgetSettings.getByOrganizationIdqueryGreeting + suggestions + Vapi settings for the widget
secrets.getVapiSecretsactionReturns only the Vapi public key for the org
private/ — dashboard (Clerk-identity gated)
FunctionKindDescription
conversations.getManyqueryOrg‑scoped, status‑filterable, paginated inbox with contact + last message
conversations.getOnequerySingle conversation with its contact session
conversations.updateStatusmutationSet unresolved / escalated / resolved
messages.createmutationOperator reply (auto‑escalates the conversation)
messages.getManyqueryThread history for the dashboard chat view
messages.enhanceResponseactionGemini rewrite of an operator draft (subscription‑gated)
contactSessions.getOneByConversationIdqueryContact record behind a conversation (contact panel)
files.addFileactionExtract → embed → index a file into the org's RAG namespace (subscription‑gated)
files.deleteFilemutationRemove a knowledge‑base entry + its storage blob
files.listqueryPaginated knowledge‑base file table
plugins.getOne / plugins.removequery / mutationPlugin connection state
secrets.upsertmutationSchedules encrypted storage of service credentials
vapi.getAssistants / vapi.getPhoneNumbersactionList Vapi resources via the server SDK
widgetSettings.getOne / widgetSettings.upsertquery / mutationRead/write widget configuration
system/ — internal + AI
ModuleDescription
ai/agents/supportAgentThe Gemini‑backed @convex-dev/agent instance + system prompt
ai/ragThe @convex-dev/rag instance (gemini-embedding-001)
ai/tools/{search,escalateConversation,resolveConversation}Agent tools
ai/constantsSupport‑agent, search‑interpreter, and enhancement prompts
subscriptions.{upsert,getByOrganizationId}Subscription mirror
plugins.{upsert,getByOrganizationIdAndService}Plugin registry
secrets.upsertWrites to AWS Secrets Manager + records the plugin
contactSessions.{refresh,getOne} · conversations.{escalate,resolve,getByThreadId}Session TTL + conversation state

Full signatures and argument validators are documented in docs/backend-api.md.


Embedding the Widget

Once an organization exists, embedding Echo on any website is one tag:

<scriptsrc="https://YOUR_WIDGET_HOST/widget.js"
data-organization-id="org_XXXXXXXXXXXXXXXXXXXX"
data-position="bottom-right"
></script>

The loader injects a floating launcher and an organization‑scoped iframe, and exposes a programmatic API:

// Reinitialize with new config, or control visibilitywindow.EchoWidget.init({organizationId: "org_…",position: "bottom-left"})window.EchoWidget.show()window.EchoWidget.hide()window.EchoWidget.destroy()

The dashboard's Integrations page generates copy‑paste snippets for HTML, React, Next.js, and JavaScript. See docs/embedding.md.


Deployment

Echo has four deployable units. A typical production topology:

UnitRecommended hostNotes
Convex backendConvex Cloud (npx convex deploy)Set all backend env vars in the Convex dashboard; register the Clerk webhook at <site>/clerk-webhook
Dashboard (apps/web)Vercel (root apps/web)Add NEXT_PUBLIC_* + Clerk keys
Widget (apps/widget)Vercel (root apps/widget)Serves /widget.js; add NEXT_PUBLIC_CONVEX_URL
Embed loader (apps/embed)Any static/CDN host, or bundled into the widget's public/Build with pnpm --filter embed build

Before going live, replace the hard‑coded http://localhost:3001 in the embed loader / integration snippets with your production widget host (via VITE_WIDGET_URL for the embed build and the *_SCRIPT constants in apps/web/modules/integrations/constants).

Step‑by‑step deployment (including the Clerk webhook and Convex env setup) is in docs/deployment.md.


Scripts & Tooling

pnpm dev # Run all apps + Convex (Turborepo)
pnpm build # Build every app and package in topological order
pnpm lint # ESLint across all workspaces
pnpm typecheck # tsc --noEmit across all workspaces
pnpm format # Prettier write across all workspaces
pnpm --filter web dev # Target one workspace
pnpm --filter backend dev # Convex only
  • Branching — Git Flow: feature/* → PR to developrelease/* → PR to main.
  • CommitsConventional Commits; PR titles validated in CI.
  • CI — format, lint, typecheck, build, CodeQL, and PR‑title checks on every pull request.

Documentation

The docs/ directory contains the full, diagram‑rich documentation set:

DocumentWhat's inside
Product OverviewNon‑technical, client‑facing tour of what Echo does and why it's valuable
ArchitectureSystem design, trust boundaries, request paths, component diagrams
Data ModelEvery table, field, index, and relationship (with ERD)
Authentication & Multi‑TenancyClerk ↔ Convex bridge, guards, org isolation
AI Agent & RAGAgent, tools, prompts, embeddings, and the retrieval pipeline
Conversation FlowsEnd‑to‑end message, escalation, and resolution sequences
Widget & EmbedState machine, screens, and the embed loader
Voice (Vapi)Connection, secret handling, and the call lifecycle
Billing & SubscriptionsPlans, gating, and the webhook
Backend API ReferenceEvery Convex function, with args and behavior
Setup GuideStep‑by‑step local environment
Deployment GuideProduction rollout

Start at docs/README.md.


Roadmap

  • Framework‑specific embed snippets (React/Next.js component wrappers)
  • Widget disconnect/reconnect UX polish for the Vapi plugin
  • Environment‑driven widget host (remove localhost from shipped snippets)
  • Analytics: deflection rate, CSAT, resolution time
  • Team roles & permissions beyond organization membership

Contributing

Contributions are welcome! Please read CONTRIBUTING.md and the Code of Conduct before opening a pull request. In short: branch from develop, follow Conventional Commits, and make sure pnpm lint, pnpm typecheck, and pnpm build pass.

Security

Please read SECURITY.md before reporting a vulnerability. Do not open a public issue for security vulnerabilities.

Changelog

All notable changes are documented in CHANGELOG.md, following Keep a Changelog and Semantic Versioning.

License

Distributed under the MIT License. See LICENSE for details.


Echo

Echo — AI‑powered customer support that actually knows your product.

Made with precision by Rishikesh Palande

About

AI-powered customer support platform — embed a chat & voice widget on any site with one script tag. RAG-grounded AI agent, real-time operator dashboard, Vapi voice, Clerk billing. Next.js 16, Convex, Turborepo.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages