Keinsaas Navigator

repository·main·Indexed 22 days ago

https://github.com/keinsaasforever/better-chatbot

An open-source, multi-AI chatbot platform (v1.26.0) that integrates LLMs from OpenAI, Anthropic, and Google. It features Model Context Protocol (MCP) support for browser automation via Playwright, visual workflow construction, custom agents, and built-in tools for web search (Exa AI), code execution, and data visualization. Supports deployment via Vercel, Docker Compose, and local installation with PostgreSQL and S3 storage options.

Tokens
26.6K
Snippets
86
Records
120
Agent score
78%

What's inside better-chatbot

  1. Create Visual Workflows as Custom Tools

    main

    You can build and publish custom visual workflows that act as callable tools in your chat conversations.

    Workflow Capabilities:

    • Node-based construction: Connect LLM nodes (for reasoning) and Tool nodes (for MCP execution).
    • Publishing: Once built, workflows can be invoked in chat using the @workflow_name syntax.
    • Automation: Chain complex, multi-step processes into reusable sequences.
  2. How the System Prompt Layering Works

    main

    The better-chatbot uses a multi-layered prompt system to construct the final context sent to the AI. This ensures that the assistant is personalized, project-aware, and tool-competent without overwhelming the context window with irrelevant data.

    The final context is a combination of four layers:

    1. Base System Prompt: Defines the core behavior of the better-chatbot.
    2. User Preferences: Incorporates your personal settings and communication style.
    3. Project Instructions: Adds context specific to the current project being worked on.
    4. MCP Customizations: Injects tool-specific instructions, but only when those tools are actually used.

    This layered approach provides efficiency (only relevant context is included), consistency (preferences apply globally), and intelligence (tool instructions activate on demand).

  3. How to perform multi-user testing

    main

    To test interactions between different users (e.g., sharing an agent from User A to User B), you must manually create new browser contexts for each user within a single test.

    Note: Because multi-user tests involve multiple browser instances interacting, you should set the test block to run sequentially using test.describe.configure({ mode: 'serial' }); to avoid race conditions during parallel execution.

    import { TEST_USERS } from '../constants/test-users';
    
    test.describe('Agent Sharing', () => {
      // Ensure tests in this block run one after another
      test.describe.configure({ mode: 'serial' });
    
      test('user sharing workflow', async ({ browser }) => {
        // User 1 setup
        const user1Context = await browser.newContext({
          storageState: TEST_USERS.editor.authFile,
        });
        const user1Page = await user1Context.newPage();
    
        // ... User 1 performs actions ...
    
        // User 2 setup
        const user2Context = await browser.newContext({
          storageState: TEST_USERS.editor2.authFile,
        });
        const user2Page = await user2Context.newPage();
    
        // ... User 2 interacts with shared agent ...
      });
    });
  4. Implement a Custom Storage Driver

    main

    To extend the project with a new storage backend (e.g., Cloudflare R2 or MinIO), follow these steps:

    1. Create a new driver file in src/lib/file-storage/ (e.g., r2-file-storage.ts).
    2. Implement the FileStorage interface defined in file-storage.interface.ts. The interface requires:
      • upload(): For server-side uploads.
      • download(): To retrieve files.
      • delete(): To remove files.
      • exists(): To check file existence.
      • getMetadata(): To fetch file details.
      • getSourceUrl(): To get the public URL.
      • createUploadUrl() (Optional): To generate presigned URLs for client-side uploads.
    3. Register your new driver in src/lib/file-storage/index.ts.
    4. Set FILE_STORAGE_TYPE to your new driver's identifier in your environment variables.
  5. Invoke Tools using @mentions and Presets

    main

    Keinsaas Navigator provides two ways to manage tool availability during a conversation:

    1. Tool Selection: Makes frequently used tools always available to the LLM across all chats. This is best for maintaining consistent context.
    2. Mentions (@): Temporarily binds specific tools for a single response by typing @toolname. This is more token-efficient and can improve accuracy because only the mentioned tools are sent to the LLM.

    You can also create tool presets by selecting specific MCP servers or tools, allowing you to switch between different toolsets instantly.

  6. Understand the MCP OAuth Flow

    main

    The application functions as an OAuth client, managing OAuth sessions within a PostgreSQL database. The flow is triggered during server startup when MCP clients attempt to connect.

    Key Lifecycle Stages:

    1. Connection Attempt: The MCPClient attempts to connect. The PgOAuthClientProvider checks the OAuthRepository (DB) for an existing token session.
    2. Authorization State: If no valid token session exists, the client enters an authorizing state, which triggers a prompt in the UI for user intervention.
    3. User Authorization: The user clicks 'Authorize' in the UI, which calls authorizeMcpClientAction(id) on the server. The server then provides an authorizationUrl to the client.
    4. OAuth Handshake: The client opens a popup to the OAuth Server for login/consent. Upon success, the server receives a callback at /api/mcp/oauth/callback?code&state.
    5. Session Completion: The application retrieves the session by state, calls finishAuth(code) on the client, and the provider saves the tokens via saveTokensAndCleanup(mcpServerId, state).
    6. Success: The client is refreshed, and the UI receives an MCP_OAUTH_SUCCESS postMessage.

    Security and Reliability Features:

    • Multi-instance Safety: Each authorization attempt uses a unique state. When tokens are successfully saved, any incomplete or stale sessions for that specific mcpServerId are automatically cleaned up.
    • Security Guard: If a redirect URI mismatch occurs, the system clears all active sessions and restarts the flow to prevent unauthorized access.
  7. Control Tool Usage with Tool Choice Mode

    main

    You can control how the LLM interacts with tools in any chat using Tool Choice Mode. Use the shortcut ⌘P to switch modes:

    • Auto: The model automatically decides when to call tools.
    • Manual: The model must ask for your permission before executing any tool call.
    • None: Tool usage is completely disabled.
  8. Create and Invoke Custom Agents

    main

    Custom agents are specialized AI assistants defined with specific system prompts and a curated set of available tools.

    Usage:

    • Definition: Define an agent by providing its system instructions and specific tool access (e.g., GitHub tools for a manager agent).
    • Invocation: Use the @agent_name syntax in chat to switch to that specialized assistant.

    Example: A @github_manager agent could be configured with issue/PR creation tools and project context to manage a repository.

  9. How to authenticate users in E2E tests

    main

    By default, tests run as unauthenticated users. To test features as an authenticated user, use test.use with the storageState option. The test suite provides pre-defined users in tests/constants/test-users.ts (admin, editor, editor2, and regular).

    Use test.use({ storageState: TEST_USERS.<role>.authFile }); within a describe or test block to apply the authentication state.

    import { TEST_USERS } from '../constants/test-users';
    
    test.describe('Agent Creation', () => {
      // This applies the editor authentication to all tests in this block
      test.use({ storageState: TEST_USERS.editor.authFile });
    
      test('should create agent', async ({ page }) => {
        // Test logic here
      });
    });
  10. Best Practices for Reliable E2E Tests

    main

    To ensure tests are stable and maintainable, follow these patterns:

    1. Use Stable Selectors

    Avoid fragile selectors like text content or CSS paths. Always use data-testid attributes.

    2. Use Robust Waiting Strategies

    Don't rely on arbitrary timeouts. Use Playwright's built-in waiting mechanisms:

    • page.waitForLoadState('networkidle'): Wait for network activity to settle.
    • page.waitForResponse(...): Wait for a specific API call to complete.
    • page.waitForURL(...): Wait for navigation to a specific URL.

    3. Generate Unique Test Data

    Use suffixes to prevent data collisions between parallel test runs.

    // ✅ Good: Stable selector
    await page.getByTestId('agent-name-input').fill('My Agent');
    
    // ✅ Good: Waiting for API response
    const responsePromise = page.waitForResponse(
      (response) => response.url().includes('/api/agent/') && response.request().method() === 'PUT'
    );
    await page.getByTestId('save-button').click();
    await responsePromise;
    
    // ✅ Good: Unique data
    const testSuffix = Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
    const agentName = `Test Agent ${testSuffix}`;
  11. Install and run locally

    main

    To run the application directly on your host machine, follow these steps. You can choose to run a local PostgreSQL instance via Docker or use an existing one.

    Option 1: Full Local Setup (with Dockerized Postgres)

    1. Install dependencies: pnpm i.
    2. Start PostgreSQL: pnpm docker:pg.
    3. Configure the .env file with your LLM API keys and the POSTGRES_URL.
    4. Build and start: pnpm build:local && pnpm start.
    5. For development with hot-reloading: pnpm dev.

    Option 2: Hybrid Setup (App local, DB in Docker)

    Use this if you want to run the app via pnpm but keep the database isolated in Docker.

    1. Start Postgres: docker compose -f docker/compose.yml up -d postgres.
    2. Apply migrations: pnpm db:migrate.
    3. Run the app: pnpm dev or pnpm build && pnpm start.
    # Full Local Setup
    pnpm i
    pnpm docker:pg
    pnpm build:local && pnpm start
    
    # Hybrid Setup
    docker compose -f docker/compose.yml up -d postgres
    pnpm db:migrate
    pnpm dev