Skip to content

devagrawal09/powerchat

Repository files navigation

PowerChat

A real-time chat app with AI agents built with SolidStart, PowerSync, Neon, and Mastra.

Features

  • 💬 Real-time chat channels
  • 🤖 AI agents you can @mention in channels
  • ✨ Agent runs are triggered directly from message upload handling
  • 👥 Channel sidebar - view members and agents
  • 👤 Agent viewer - click agents to view their description and instructions
  • 📱 Offline-first with PowerSync local-first sync
  • ⚡ Instant UI updates via client-side writes
  • 🔐 Anonymous sessions (no signup required for MVP)

Tech Stack

  • Frontend: SolidStart + Solid Router
  • Database: Neon Postgres
  • Sync: PowerSync (Web SDK + Service)
  • AI: Mastra with OpenAI GPT-5
  • Auth: Anonymous cookie-based sessions

Setup

1. Install Dependencies

bun install

2. Database Setup

This app expects a Postgres database with the chat schema already created.

  • Tables used by the app: users, agents, channels, channel_members, messages, agent_runs
  • Seed/demo agents commonly used in development: Assistant, Analyst, Researcher, Writer
  • Schema source: src/db/schema/server.ts
  • Migrations: db/migrations generated by Drizzle via bun run db:generate

Set NEON_DATABASE_URL in .env.local (see Environment Variables below).

3. Configure PowerSync

PowerSync setup is split across a few files:

  • db/replication.sql - SQL for the Postgres replication role and publication
  • powersync/service.yaml - PowerSync service connection config
  • powersync/sync-config.yaml - sync rules used by the app
  • powersync/cli.yaml - PowerSync CLI project link metadata

You need to:

  • Enable logical replication in Neon
  • Run the SQL in db/replication.sql
  • Configure the PowerSync service connection in powersync/service.yaml
  • Deploy the sync rules from powersync/sync-config.yaml

The current sync rules subscribe each user to channels where they are a member and sync:

  • users
  • agents
  • channels
  • channel_members
  • messages
  • agent_runs

4. Environment Variables

Create a .env.local file with the following variables:

NEON_DATABASE_URL="postgresql://..."
POWERSYNC_SERVICE_URL=https://your-instance.powersync.com
POWERSYNC_JWT_SECRET=your-secret-min-32-chars
POWERSYNC_JWT_KID=your-key-id
OPENAI_API_KEY=sk-your-key
AI_MODEL=gpt-5

Notes:

  • POWERSYNC_JWT_SECRET should be a Base64URL-encoded secret.
  • POWERSYNC_JWT_KID is the key ID used in the JWT header.

Also add for client (Vite):

VITE_POWERSYNC_SERVICE_URL=https://your-instance.powersync.com

Optional variables supported by the repo:

ANTHROPIC_API_KEY=
FIRECRAWL_API_KEY=

5. Run Development Server

bun dev

6. Run Tests

bun run test

Visit http://localhost:3000

Usage

  1. Create a Channel: Use the form in the sidebar
  2. Invite an Agent: Use the agent invite UI in a channel
  3. Send Messages: Type in the input box
  4. Mention Agents: Use @AgentName in your message to trigger AI reply Agent execution starts after the message upload reaches the server, without blocking the client upload response.
  5. View Agent Details: Click on agents in the right sidebar to see their description and instructions

Architecture

Vertical Slice Architecture

PowerChat uses vertical slice architecture to organize features by domain rather than by technical layer. Each feature is a self-contained "slice" that includes all the code needed for that feature.

Slice Structure

Every slice lives in src/slices/{feature-name}/ and typically contains:

  • index.tsx - The component/hook implementation
  • index.test.tsx - Test file when present (see Testing below)

Query vs Mutation Slices

Slices are categorized by their primary responsibility:

Query Slices (read-only):

  • Fetch and display data
  • Use useQuery from ~/lib/powersync-solid for reactive local queries
  • Examples: channel-list, chat-messages, username-check, channel-header, channel-member-list, channel-agents-list, agent-viewer, mention-autocomplete

Mutation Slices (write operations):

  • Handle user actions that modify data
  • Use PowerSync writeTransaction or server actions
  • Examples: create-channel, chat-input, username-registration, channel-invite, create-agent, delete-channel

Key Principle: Each slice is either a query OR a mutation, never both. This ensures clear separation of concerns.

Slice Independence

Slices are completely independent - they never import or depend on other slices. This means:

  • Slices can be developed, tested, and refactored in isolation
  • No circular dependencies between features
  • Easy to understand what each slice does without reading other code
  • Route components orchestrate multiple slices together

Route Components Orchestrate Slices

Route components (src/routes/) compose multiple slices together:

// Example: src/routes/(chat).tsx
import { UsernameCheck } from "~/slices/username-check";
import { UsernameRegistration } from "~/slices/username-registration";
import { ChannelList } from "~/slices/channel-list";

export default function ChatLayout() {
  const usernameCheck = UsernameCheck(); // Query slice
  // ... conditionally render UsernameRegistration based on query state
  // ... render ChannelList and other slices
}

Testing

Most slices should have a colocated test file (index.test.tsx). Tests can be simple but should verify basic functionality:

  • Query slices: Test that data renders correctly, loading states work, and empty states are handled
  • Mutation slices: Test that user interactions trigger the correct mutations and callbacks

Test Structure

Tests use Vitest and @solidjs/testing-library:

import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@solidjs/testing-library";
import { MySlice } from "./index";

// Mock dependencies
vi.mock("~/lib/powersync-solid", () => ({
  useQuery: vi.fn(() => ({ data: [], isLoading: false })),
}));

describe("MySlice", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("renders correctly", () => {
    render(() => <MySlice prop="value" />);
    expect(screen.getByText("Expected text")).toBeInTheDocument();
  });
});

Running Tests

bun test

Test Philosophy

  • Keep tests simple - Focus on basic functionality, not edge cases
  • Mock external dependencies - Mock PowerSync queries, server actions, etc.
  • Test behavior, not implementation - Verify what users see and experience
  • Prefer colocated slice tests - Keep tests close to the slice and cover the primary behavior

Client-First Mutations

  • Messages are written to local PowerSync SQLite DB instantly
  • PowerSync queues uploads to the service
  • The upload handler writes validated mutations to Postgres with Drizzle
  • New user messages trigger agent runs from the upload path instead of a separate DB listener
  • Agent placeholder messages appear quickly, then streamed agent text is persisted in batches of roughly 20 chunks or on newline boundaries
  • Replies sync back automatically

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages