Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 3 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
name: CI

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
Expand All @@ -15,19 +13,13 @@ jobs:
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
redis:
image: redis:7
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
--health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
Expand All @@ -37,7 +29,7 @@ jobs:
- run: pip install -r requirements.txt
- run: pip install ruff pytest pytest-asyncio pytest-cov
- name: Lint
run: ruff check api/ tests/ --ignore E501
run: ruff check api/ tests/ --ignore E501,E712 --per-file-ignores "api/models/*:F821"
- name: Type check
run: pip install mypy types-redis && mypy api/ --ignore-missing-imports
- name: Test
Expand Down
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
![Next.js](https://img.shields.io/badge/Next.js-16.2-000000)
![Claude](https://img.shields.io/badge/Claude-Sonnet-blueviolet)
![CI](https://github.com/ChunkyTortoise/chatbot-widget/actions/workflows/ci.yml/badge.svg)
[![Dashboard](https://img.shields.io/badge/Dashboard-Live-brightgreen)](https://chatbot-widget-dashboard.vercel.app)

# Chatbot Widget SaaS

Expand Down Expand Up @@ -203,18 +202,17 @@ The dashboard includes a [live playground](/dashboard/app/playground/page.tsx) f

*Required for production. API runs without these in development mode.

## Live Demo
## Demo (Self-Host)

- **Dashboard**: [chatbot-widget-dashboard.vercel.app](https://chatbot-widget-dashboard.vercel.app) — "Try Demo" button on the login page (no account needed)
- **Widget demo page**: `GET /demo` — portfolio-quality showcase with live widget, copy-paste embed snippet, and cold-start indicator
Screenshots below are from a local self-hosted run. See [Self-Hosting](#self-hosting) for full setup.

### Dashboard Login (with demo button)
![Dashboard Login](docs/screenshots/dashboard-login.png)

### Demo Page
![Demo Page](docs/screenshots/demo-page.png)

Run locally:
Run the widget demo locally:
```bash
DEMO_MODE=true uvicorn api.main:app --reload
# then open http://localhost:8000/demo
Expand Down
2 changes: 2 additions & 0 deletions api/models/chatbot.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import uuid
from sqlalchemy import String, Text, Boolean, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
Expand Down
2 changes: 2 additions & 0 deletions api/models/conversation.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import uuid
from sqlalchemy import String, Integer, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
Expand Down
4 changes: 3 additions & 1 deletion api/models/document_chunk.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import uuid
from sqlalchemy import String, Text, Integer, ForeignKey, Index
from sqlalchemy import Text, Integer, ForeignKey, Index
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.dialects.postgresql import UUID
from pgvector.sqlalchemy import Vector
Expand Down
2 changes: 2 additions & 0 deletions api/models/knowledge_doc.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import uuid
from sqlalchemy import String, Text, Integer, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
Expand Down
2 changes: 2 additions & 0 deletions api/models/message.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import uuid
from sqlalchemy import String, Text, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
Expand Down
3 changes: 2 additions & 1 deletion api/models/user.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import uuid
from __future__ import annotations

from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Base, UUIDMixin, TimestampMixin
Expand Down
2 changes: 1 addition & 1 deletion api/routes/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, cast, Date, text
from sqlalchemy import select, func, text
from api.dependencies import get_db
from api.auth.middleware import get_current_user, CurrentUser
from api.models.chatbot import Chatbot
Expand Down
2 changes: 1 addition & 1 deletion api/routes/chatbots.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update
from api.dependencies import get_db, get_admin_key, get_admin_or_owner
from api.auth.middleware import get_optional_user, get_current_user, CurrentUser
from api.auth.middleware import get_current_user, CurrentUser
from api.models.chatbot import Chatbot
from api.schemas.chatbot import (
ChatbotCreate,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_analytics.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest
import uuid
from unittest.mock import AsyncMock, MagicMock, patch
from unittest.mock import AsyncMock, MagicMock
from datetime import datetime, timezone, timedelta
from jose import jwt
from httpx import AsyncClient, ASGITransport
Expand Down
3 changes: 1 addition & 2 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import pytest
import uuid
from unittest.mock import patch, AsyncMock
from unittest.mock import AsyncMock
from datetime import datetime, timezone, timedelta
from jose import jwt
from httpx import AsyncClient, ASGITransport
Expand Down
1 change: 0 additions & 1 deletion tests/test_billing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from api.main import app
from api.dependencies import get_db, get_redis
from api.billing.quota import (
get_message_count,
increment_message_count,
check_quota,
get_plan_limit,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ async def fake_redis_get(key):
mock_redis.expire = AsyncMock()

with patch("api.services.chat_service.similarity_search", return_value=[]):
response = await client.post(
await client.post(
f"/api/v1/chat/{chatbot_id}",
json={"message": "Hello", "session_id": "test-session"},
)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_chatbot_quota.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest
import uuid
from unittest.mock import AsyncMock, MagicMock, patch
from unittest.mock import AsyncMock, MagicMock
from datetime import datetime, timezone, timedelta
from jose import jwt
from httpx import AsyncClient, ASGITransport
Expand Down
3 changes: 1 addition & 2 deletions tests/test_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import pytest
from pathlib import Path
from httpx import AsyncClient
from api.main import app


class TestDemoUI:
Expand Down Expand Up @@ -104,7 +103,7 @@ async def test_chat_with_demo_id_in_demo_mode(
) -> None:
"""POST /chat/demo should work when DEMO_MODE=true."""
response = await demo_client.post(
f"/api/v1/chat/demo",
"/api/v1/chat/demo",
json={"message": "Hello", "session_id": "test-session"},
)
# Note: This may fail if no LLM API key is configured, but the routing should work
Expand Down
2 changes: 1 addition & 1 deletion tests/test_documents.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest
import uuid
from io import BytesIO
from unittest.mock import AsyncMock, MagicMock, patch
from unittest.mock import MagicMock
from datetime import datetime, timezone, timedelta
from jose import jwt

Expand Down
1 change: 0 additions & 1 deletion tests/test_health.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

@pytest.mark.asyncio
async def test_health_endpoint(client, mock_db, mock_redis):
from sqlalchemy import text
mock_db.execute = pytest.importorskip # will be called
mock_db.execute = __import__("unittest.mock", fromlist=["AsyncMock"]).AsyncMock()

Expand Down
2 changes: 1 addition & 1 deletion tests/test_kb_quota.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest
import uuid
from unittest.mock import AsyncMock, MagicMock
from api.billing.kb_quota import check_knowledge_base_quota, KB_LIMITS, KBQuotaResult
from api.billing.kb_quota import check_knowledge_base_quota, KB_LIMITS


@pytest.fixture
Expand Down
2 changes: 1 addition & 1 deletion tests/test_websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from starlette.testclient import TestClient
from fastapi import FastAPI, WebSocket, Depends
from fastapi import FastAPI
from api.routes.chat import router
from api.dependencies import get_db, get_redis

Expand Down
Loading