Skip to content
Merged
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
116 changes: 116 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
format:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 11

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
cache-dependency-path: |
pnpm-lock.yaml
client/pnpm-lock.yaml
server/pnpm-lock.yaml

- name: Install root dependencies
run: pnpm install --frozen-lockfile

- name: Install client dependencies
working-directory: client
run: pnpm install --frozen-lockfile

- name: Install server dependencies
working-directory: server
run: pnpm install --frozen-lockfile

- name: Format check
run: pnpm format:check

lint-and-typecheck:
name: Lint and Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 11

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
cache-dependency-path: |
pnpm-lock.yaml
client/pnpm-lock.yaml
server/pnpm-lock.yaml

- name: Install root dependencies
run: pnpm install --frozen-lockfile

- name: Install client dependencies
working-directory: client
run: pnpm install --frozen-lockfile

- name: Install server dependencies
working-directory: server
run: pnpm install --frozen-lockfile

- name: Lint
run: pnpm lint

- name: Typecheck
run: pnpm typecheck

build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 11

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
cache-dependency-path: |
pnpm-lock.yaml
client/pnpm-lock.yaml
server/pnpm-lock.yaml

- name: Install root dependencies
run: pnpm install --frozen-lockfile

- name: Install client dependencies
working-directory: client
run: pnpm install --frozen-lockfile

- name: Install server dependencies
working-directory: server
run: pnpm install --frozen-lockfile

- name: Build
run: pnpm build

all-checks-pass:
name: All checks passed
runs-on: ubuntu-latest
needs: [format, lint-and-typecheck, build]
steps:
- run: echo "All checks passed"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ dist/
*.log
.DS_Store
coverage/
.pnpm-store/
documentation-samples/
7 changes: 7 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
dist
coverage
.pnpm-store
pnpm-lock.yaml
client/pnpm-lock.yaml
server/pnpm-lock.yaml
5 changes: 5 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100
}
45 changes: 24 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,27 @@ An AI powered documentation assistant built with RAG (Retrieval Augmented Genera

## Tech Stack

| Layer | Technology |
|-------|-----------|
| **Backend** | NestJS (TypeScript) |
| **Frontend** | React + Vite + TailwindCSS |
| **Vector DB / Cache / Storage** | Redis Stack |
| **LLM** | Groq (`llama-3.3-70b-versatile`) with Google AI (Gemini) fallback |
| **Embeddings** | Jina AI (`jina-embeddings-v3`, 1024 dimensions) |
| **Package Manager** | pnpm |
| Layer | Technology |
| ------------------------------- | ----------------------------------------------------------------- |
| **Backend** | NestJS (TypeScript) |
| **Frontend** | React + Vite + TailwindCSS |
| **Vector DB / Cache / Storage** | Redis Stack |
| **LLM** | Groq (`llama-3.3-70b-versatile`) with Google AI (Gemini) fallback |
| **Embeddings** | Jina AI (`jina-embeddings-v3`, 1024 dimensions) |
| **Package Manager** | pnpm |

## How It Works

### Ingestion

1. upload `.md` or `.txt` documentation files
2. split content into chunks by markdown headers and word count
3. generate 1024-dim embeddings via Jina AI
4. store chunks + embeddings in Redis vector index (`idx:docs`)
5. deduplicate via SHA-256 content hash

### Question Answering (RAG)

1. embed the user question via Jina AI (`retrieval.query` task)
2. check semantic cache (`idx:cache`) for a similar previously answered question
3. if cache miss, run KNN vector search to find top 5 relevant chunks
Expand Down Expand Up @@ -72,24 +74,25 @@ App runs at `http://localhost:5173`, backend at `http://localhost:3000`.

## API Endpoints

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/ingest` | POST | Upload documentation files (multipart) |
| `/api/ask` | POST | Ask a question `{ question: string }` |
| `/api/documents` | GET | List all uploaded documents |
| `/api/documents/:id` | DELETE | Delete a document and its chunks |
| Endpoint | Method | Description |
| -------------------- | ------ | -------------------------------------- |
| `/api/ingest` | POST | Upload documentation files (multipart) |
| `/api/ask` | POST | Ask a question `{ question: string }` |
| `/api/documents` | GET | List all uploaded documents |
| `/api/documents/:id` | DELETE | Delete a document and its chunks |

## Redis Data Model

| Key Pattern | Type | Description |
|-------------|------|-------------|
| `file:<hash>` | Hash | Document metadata (fileName, chunks, createdAt) |
| `doc:<hash>:<i>` | Hash | Document chunk with embedding vector |
| `cache:<id>` | Hash | Cached Q&A pair with embedding vector |
| `idx:docs` | Index | Vector index for document chunk search |
| `idx:cache` | Index | Vector index for semantic cache lookup |
| Key Pattern | Type | Description |
| ---------------- | ----- | ----------------------------------------------- |
| `file:<hash>` | Hash | Document metadata (fileName, chunks, createdAt) |
| `doc:<hash>:<i>` | Hash | Document chunk with embedding vector |
| `cache:<id>` | Hash | Cached Q&A pair with embedding vector |
| `idx:docs` | Index | Vector index for document chunk search |
| `idx:cache` | Index | Vector index for semantic cache lookup |

## UI preview (V1)

<img width="1512" height="844" alt="image" src="https://github.com/user-attachments/assets/0888b0c1-3195-4d73-832e-16b3323b0c4f" />

## License
Expand Down
28 changes: 28 additions & 0 deletions client/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';

export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'react-hooks/immutability': 'off',
'react-hooks/set-state-in-effect': 'off',
'react-hooks/exhaustive-deps': 'off',
},
},
);
2 changes: 1 addition & 1 deletion client/index.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
Expand Down
16 changes: 12 additions & 4 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,37 @@
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
"preview": "vite preview",
"lint": "eslint src --max-warnings 0",
"typecheck": "tsc --noEmit -p tsconfig.json"
},
"dependencies": {
"react-router-dom": "^6.22.0",
"react-markdown": "^9.0.1",
"remark-gfm": "^4.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.312.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^9.0.1",
"react-router-dom": "^6.22.0",
"remark-gfm": "^4.0.0",
"tailwind-merge": "^3.5.0"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@tailwindcss/typography": "^0.5.10",
"@types/node": "^25.5.0",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.17",
"eslint": "^10.6.0",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.3",
"globals": "^17.7.0",
"postcss": "^8.4.33",
"tailwindcss": "^3.4.1",
"typescript": "^5.3.3",
"typescript-eslint": "^8.62.1",
"vite": "^5.0.12"
}
}
Loading
Loading