Skip to content

Latest commit

 

History

14 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rag Powered - AI PDF Workspace and Collaboration Hub

A full-stack web app for uploading, analyzing, summarizing, and collaborating on PDF contracts. Built with Google Gemini 3.6 Flash, pgvector, Supabase RLS, and Supabase Realtime.


Overview and key features

  • PDF validation and processing: Uploads are checked for application/pdf MIME type and a .pdf extension, on both the drag-and-drop frontend and the backend Multer middleware, then run through text extraction, sliding-window chunking, embedding, and an automatic Gemini summary.
  • Streaming RAG Q&A: Token-by-token answers over Server-Sent Events (SSE), grounded in the uploaded PDF.
  • Hybrid search: Filename match or vector similarity search (e.g. "employment contract," "liability cap").
  • Collaborative comments: Multi-threaded, synced live across open tabs via Supabase Realtime.
  • Permissions and share links: Owners generate public links or invite collaborators; comments show owner/collaborator badges.
  • Guest support: Guests on a share link can comment after giving a name (saved to localStorage); logged-in users skip that prompt.
  • Auth and recovery: Supabase Auth, bcrypt password hashing, JWT session cookies, password reset at /forgot-password.
  • Responsive UI: Next.js 16, Tailwind CSS, custom styling across mobile, tablet, and desktop.

Architecture and stack

                                 ┌───────────────────────────┐
                                 │   Next.js 16 Frontend     │
                                 │  Tailwind CSS / Lucide    │
                                 └─────────────┬─────────────┘
                                               │
                                       REST / SSE Stream
                                               │
                                               ▼
                                 ┌───────────────────────────┐
                                 │ Express.js Backend (Node) │
                                 │    TypeScript Services    │
                                 └──────┬─────────────┬──────┘
                                        │             │
                    Google GenAI SDK    │             │   Supabase Client
                  (Gemini 3.6 Flash)    │             │  (Postgres & Storage)
                                        ▼             ▼
                                 ┌────────────┐ ┌─────────────┐
                                 │ Gemini AI  │ │ Supabase DB │
                                 │ Models API │ │  pgvector   │
                                 └────────────┘ └─────────────┘
  • Frontend: Next.js 16 (App Router, Turbopack), React 19, Tailwind CSS, Lucide React, react-pdf, react-markdown.
  • Backend: Node.js, Express.js, TypeScript, @google/genai, @supabase/supabase-admin.
  • Database and storage: Supabase PostgreSQL (pgvector, HNSW indexes), Supabase Auth, Supabase Storage (pdfs bucket).

Project setup and installation

Prerequisites

  • Node.js v18+ and npm
  • A Supabase project with pgvector enabled
  • A Google Gemini API key

1. Environment variables

Backend (backend/.env):

PORT=4000
SUPABASE_URL=https://your-supabase-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your-supabase-service-role-key
GEMINI_API_KEY=your-gemini-api-key
GEMINI_MODEL=gemini-3.6-flash
EMBEDDING_MODEL=gemini-embedding-001
FRONTEND_URL=http://localhost:3000

Frontend (frontend/.env.local):

NEXT_PUBLIC_SUPABASE_URL=https://your-supabase-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key
NEXT_PUBLIC_BACKEND_URL=http://localhost:4000

2. Database schema

Run supabase/schema_combined.sql in the Supabase SQL editor to set up:

  • Extensions: vector, pgcrypto
  • Tables: pdfs, pdf_chunks, comments, pdf_shares, pdf_collaborators, chat_messages
  • Storage bucket: pdfs (public view policies enabled)
  • RPCs: match_pdf_chunks, search_user_pdfs

Note

Supabase Email Verification Note

By default, Supabase Auth may enforce email verification for new account signups.

  • For Testing & Demonstration: In your Supabase Dashboard, navigate to Authentication ➔ Providers ➔ Email and toggle off "Confirm email". This enables instant account sign-up and immediate login without waiting for an email verification link.
  • For Production with Email Verification: If email confirmation is enabled, ensure your Site URL and Redirect URLs in Supabase Auth Settings include http://localhost:3000 (for local dev) or your deployed frontend domain (e.g. https://your-app.vercel.app).

3. Local development

Backend:

cd backend
npm install
npm run dev
# Server listening on http://localhost:4000

Frontend:

cd frontend
npm install
npm run dev
# App running on http://localhost:3000

Document ID and share link architecture

  • Document anchor (pdfId): On upload, PostgreSQL assigns a UUID v4 primary key (pdfs.id) that anchors pdf_chunks, comments, pdf_collaborators, chat_messages, and the storage path pdfs/<owner_id>/<rand>.pdf. Owners manage private controls at /pdf/[id].
  • Share tokens (share_token): Owners generate a cryptographically random 32-character hex token (ShareModal.tsx, via createOrGetShareToken in pdf.controller.ts), stored in pdf_shares.
  • Guest resolution: Visiting /share/[token] resolves the token to its pdf_id and a signed view URL. Guest requests carry an x-share-token header, checked by resolveShareOrAuth middleware, without exposing internal database IDs.

Access control and role hierarchy

The app implements role-based access control for document access via share links and email invitations:

User access level Access path Commenting Badge on comments
Document owner Document upload (/pdf/[id]) Unrestricted [ShieldCheck] Author Name (Owner) (indigo badge)
Invited collaborator Email invitation Unrestricted [User] Author Name (Collaborator) (emerald badge)
Registered user via share link Public share link (/share/[token]) Unrestricted, no guest prompt [User] Author Name (plain user badge)
Unregistered guest via share link Public share link (/share/[token]) Prompted for a guest name, saved in localStorage [User] Guest Name [Unregistered Guest] (amber badge)

🔐 Security Model & Capability-Based Access Control Trade-offs

Note

Identity-Based vs. Capability-Based Authorization The application balances two authorization paradigms:

  1. Identity-Based Auth (Private Owner Routes): /pdf/[id] routes enforce strict Supabase Auth JWT identity checks (auth.uid() = owner_id). Guessing /pdf/123 returns 401 Unauthorized.
  2. Capability-Based Auth (Public Share Links): /share/[token] implements capability security (similar to Figma, Google Drive, or Dropbox share links). Holding a valid, cryptographically unguessable token (encode(gen_random_bytes(16), 'hex')) grants view, comment, and chat capabilities for that specific PDF only. Reusing tokens across PDFs or attempting sequential ID enumeration is impossible.

Key Security Implementations & Trade-offs:

  • Unguessable Cryptographic Tokens: Share tokens are generated server-side using secure random bytes (gen_random_bytes(16)), ensuring links cannot be brute-forced or guessed.
  • Guest Identity Handshake: Unauthenticated guests accessing a share link are prompted for a display name before commenting (CommentSidebar.tsx). The name is stored in browser localStorage (guestSession.ts) and rendered with a distinct Unregistered Guest amber badge tag to clearly differentiate guest comments from verified owner/collaborator comments.
  • Scoped LLM Chat Endpoint: The streaming AI chat endpoint (/api/pdfs/:id/chat/stream) is strictly scoped to the target document. It requires a valid x-share-token or user JWT validated by resolveShareOrAuth.ts and queries vector embeddings filtered strictly by target_pdf_id—preventing it from acting as an arbitrary open LLM proxy.
  • Residual Risks & Future Production Mitigations:
    • Link Revocation & Expiration: pdf_shares includes an expires_at column structure allowing owners to configure time-bound link validity or revoke share access.
    • LLM Cost & Rate Limiting: Heavy requests can be capped using IP/token rate-limiting middleware (e.g. max 10 chat requests per minute per share token) to prevent token exhaustion.
    • Password Reset Integration: Self-service password recovery (resetPasswordForEmail()) is UI-structured at /forgot-password and would be fully integrated with custom SMTP mailer services given more time.

Approach to AI features

1. Models used

  • Text generation and summarization: gemini-3.6-flash via the @google/genai SDK, chosen for fast inference and a large context window.
  • Embeddings: gemini-embedding-001, configured for an explicit 768-dimension output (outputDimensionality: 768).

2. Prompt strategy

The RAG prompt (chat.service.ts) sets a few rules: answer only from retrieved PDF excerpts, no outside knowledge, to keep hallucinations down; if nothing relevant is found, reply "I couldn't find that in this document"; and the last 5 turns (recentTurns) are injected into the system prompt so the model can resolve follow-ups like "what about the second clause?"

3. Handling long PDFs and vector retrieval

Important

Long PDF Ingestion, Summarization & RAG Strategy

Handling long documents (50+ page contracts or manuals) efficiently requires a multi-stage pipeline:

  1. Format validation: MIME type and .pdf extension checked client-side (UploadDropzone.tsx) and server-side (upload.ts).
  2. Text extraction: PDF buffers parsed asynchronously with @cyber2024/pdf-parse.
  3. Sliding-window chunking: Text split into ~500-token chunks with a 50-token overlap (chunking.service.ts) so sentences don't get cut off at boundaries.
  4. Vector storage: Each chunk embedded into a 768-dim vector via gemini-embedding-001, stored in pdf_chunks via pgvector.
  5. Vector search (RPC): Queries embedded on the fly and matched against pdf_chunks by cosine distance (match_pdf_chunks RPC), returning the top 6 passages.
  6. Background processing: Embedding and summarization run in background promises, so the upload response returns immediately instead of waiting on them.

Database schema, row-level security, and RPC functions

The platform runs on Supabase PostgreSQL with pgvector, anchored around a central document UUID (pdfId) with cascading integrity (ON DELETE CASCADE).


1. Core schema (6 tables)

Table Keys and constraints Purpose
public.pdfs id (UUID v4), owner_id → auth.users(id) Uploaded PDF metadata: filename, storage_path, full_text, summary, upload_date.
public.pdf_chunks id (UUID v4), pdf_id → pdfs(id) Passage text (content, chunk_index, token_count) and embedding vector(768), indexed via HNSW (pdf_chunks_embedding_hnsw_idx).
public.comments id (UUID v4), pdf_id → pdfs(id), parent_comment_id → comments(id) Discussion threads and nested replies, with authenticated author_id → auth.users(id) or a guest_name.
public.pdf_shares id (UUID v4), pdf_id (UNIQUE) → pdfs(id) One-to-one share link access: share_token UNIQUE, permission, expires_at.
public.pdf_collaborators id (UUID v4), pdf_id → pdfs(id), user_id → auth.users(id) Invited collaborators and their role (viewer, commenter, editor).
public.chat_messages id (UUID v4), pdf_id → pdfs(id) Multi-turn chat history: session_id, role (user/assistant), content.

2. Row-level security and access control

  • RLS is enabled on every public table (ALTER TABLE ... ENABLE ROW LEVEL SECURITY).
  • For view, comment, and chat operations, policies check either document ownership (auth.uid() = owner_id) or collaborator membership in pdf_collaborators.
  • pdf_shares allows SELECT for public token resolution; guest requests carry an x-share-token header, validated by backend middleware (resolveShareOrAuth.ts).

3. Stored procedures (RPC functions)

A. RAG passage retrieval (match_pdf_chunks)

Ranks pdf_chunks by cosine distance (1 - (c.embedding <=> query_embedding)) and returns the top 6 passages for AI context grounding (embeding.service.ts):

CREATE OR REPLACE FUNCTION match_pdf_chunks(
    query_embedding vector(768), target_pdf_id UUID, match_count INT DEFAULT 6
)
RETURNS TABLE (id UUID, pdf_id UUID, chunk_index INT, content TEXT, similarity FLOAT)
LANGUAGE plpgsql AS $$
BEGIN
    RETURN QUERY
    SELECT c.id, c.pdf_id, c.chunk_index, c.content,
           1 - (c.embedding <=> query_embedding) AS similarity
    FROM pdf_chunks c WHERE c.pdf_id = target_pdf_id
    ORDER BY c.embedding <=> query_embedding ASC LIMIT match_count;
END; $$;

B. Semantic search across a user's PDFs (search_user_pdfs)

Aggregates the maximum chunk similarity per document for a given user (search.controller.ts):

CREATE OR REPLACE FUNCTION search_user_pdfs(
    query_embedding vector(768), user_id UUID, match_count INT DEFAULT 10
)
RETURNS TABLE (id UUID, filename TEXT, summary TEXT, max_similarity FLOAT)
LANGUAGE plpgsql AS $$
BEGIN
    RETURN QUERY
    SELECT p.id, p.filename, p.summary,
           MAX(1 - (c.embedding <=> query_embedding)) AS max_similarity
    FROM pdfs p JOIN pdf_chunks c ON c.pdf_id = p.id
    WHERE p.owner_id = user_id
    GROUP BY p.id, p.filename, p.summary
    ORDER BY max_similarity DESC LIMIT match_count;
END; $$;

Supabase infrastructure And RAG engine

Covers Supabase Auth, the pdfs storage bucket, PostgreSQL with Realtime sync, pgvector embeddings, and the RAG chat pipeline with SSE streaming.

1. Authentication (Supabase Auth)

  • Password storage: Credentials and password hashes live in PostgreSQL's private auth.users schema, hashed with bcrypt (cost factor 10). Never stored in application databases or logs.
  • JWT sessions: HMAC-SHA256 signed JWT access tokens (1-hour lifespan) passed via Authorization: Bearer, paired with HTTP-only refresh cookies handled by Next.js SSR middleware (middleware.ts).
  • Recovery: signUp() email redirection (emailRedirectTo) and single-use password reset tokens (resetPasswordForEmail()) in forgot-password/page.tsx.

2. Document storage (pdfs bucket)

  • Raw PDFs upload to the private pdfs bucket at pdfs/<owner_id>/<rand>_<timestamp>.pdf (storage.service.ts).
  • Access goes through short-lived signed URLs (supabaseAdmin.storage.from("pdfs").createSignedUrl(filepath, 3600)), so the viewer can render documents without exposing public bucket access.

3. Relational database and realtime sync

  • Tables public.pdfs, public.comments, public.pdf_collaborators, public.pdf_shares, and public.chat_messages are all anchored to the document UUID with ON DELETE CASCADE.
  • Supabase Realtime channels (comments-realtime-${pdfId}) listen for INSERT/UPDATE/DELETE events and push comment updates to all open tabs instantly (CommentSidebar.tsx).

4. RAG vector storage and semantic search (pgvector)

  • Chunks from sliding-window extraction are embedded via gemini-embedding-001 and stored in pdf_chunks.embedding (vector(768)).
  • An HNSW cosine-distance index (pdf_chunks_embedding_hnsw_idx) speeds up approximate nearest-neighbor lookups.
  • Two RPCs handle retrieval: match_pdf_chunks ranks by cosine distance (1 - (embedding <=> query_embedding)) and returns the top 6 passages for RAG grounding (embeding.service.ts); search_user_pdfs aggregates max similarity across chunks for semantic search over a user's PDFs (search.controller.ts).

5. AI chat: RAG engine and SSE streaming

Chat answers are grounded in the current PDF, with multi-turn memory and word-by-word streaming over SSE.

Flow:

[User Input in UI]
       │
       ▼
1. ChatPanel.tsx (POST /api/pdfs/:id/chat/stream)
       │
       ▼
2. resolveShareOrAuth.ts (Validates User JWT or x-share-token)
       │
       ▼
3. chat.controller.ts (Sets SSE Headers: text/event-stream)
       │
       ▼
4. chat.service.ts ───► Save User Turn (chat_messages Table)
       │
       ├──► Fetch Recent History (Last 5 Turns from chat_messages)
       │
       ├──► retriveRelevantChunks() (gemini-embedding-001 + match_pdf_chunks RPC)
       │
       ├──► buildRagPrompt() (Injects Context + Memory + System Grounding Rules)
       │
       └──► ai.models.generateContentStream() (gemini-3.6-flash Model API)
               │
               ▼ (Pipes SSE data: {"text": "..."} tokens)
5. ChatPanel.tsx (Accumulates tokens & renders Markdown + Auto-Scrolls)

Key components:

Layer / file Function / handler Purpose
UI stream reader (ChatPanel.tsx) handleSendMessage() Reads SSE chunks via getReader(), updates token state, auto-scrolls.
Auth guard (resolveShareOrAuth.ts) Middleware Validates Supabase Auth JWT or x-share-token.
SSE controller (chat.controller.ts) streamQuestion() Sets SSE headers (text/event-stream) and starts the streaming pipeline.
RAG orchestrator (chat.service.ts) processStreamChatQuestion() Coordinates history, vector retrieval, grounded prompt, and Gemini streaming.
History manager (chat.service.ts) getRecentChatTurns() Retrieves the last 5 turns from chat_messages for conversation memory.
Vector retriever (embeding.service.ts) retriveRelevantChunks() Embeds the query and calls match_pdf_chunks for the top 6 passages.
Prompt builder (chat.service.ts) buildRagPrompt() Assembles system rules, document context, and chat history into one prompt.

System prompt, in chat.service.ts:

You are an expert AI Legal & Document Assistant grounded strictly in the provided PDF context.

--- STRICT GROUNDING INSTRUCTIONS ---
1. Answer the user's question ONLY using the facts contained within the provided CONTEXT.
2. Do NOT extrapolate, speculate, or use external knowledge.
3. If the answer cannot be found in the provided CONTEXT, clearly respond:
   "I couldn't find information about that in this document."
4. Format your answer in clean Markdown with concise bullet points where appropriate.

--- CONTEXT FROM DOCUMENT ---
[Chunk 1 text...]
[Chunk 2 text...]

--- RECENT CONVERSATION HISTORY ---
User: What is the termination notice period?
Assistant: The termination notice period is 30 business days as stated in Clause 14.2.

--- USER QUESTION ---
What about the penalty fees for early termination?

Persistence (chat_messages), defined in supabase/schema_combined.sql:

CREATE TABLE public.chat_messages (
    id uuid PRIMARY KEY DEFAULT gen_random_policy_uuid(),
    pdf_id uuid REFERENCES public.pdfs(id) ON DELETE CASCADE,
    session_id text NOT NULL,
    role text NOT NULL CHECK (role IN ('user', 'assistant')),
    content text NOT NULL,
    created_at timestamptz DEFAULT now()
);

-- Fast lookup index for document session history
CREATE INDEX if not exists idx_chat_messages_pdf_session 
ON public.chat_messages(pdf_id, session_id, created_at);

Trade-offs and blockers along the way

  1. Gemini embedding model deprecation: models/text-embedding-004 on v1beta started returning HTTP 404 errors. Fixed by switching to gemini-embedding-001 with an explicit 768-dimension output, matching the vector(768) schema.
  2. PDF canvas resize flicker: The standard react-pdf canvas triggered continuous ResizeObserver loops, flickering on window resize. Fixed with a debounced resize listener (80ms delay, 8px threshold) and fixed container overflow scrolling in PdfViewer.tsx.
  3. Guest vs. authenticated handshake: Share links were prompting already-logged-in users for a guest name. Fixed by adding a session check (supabase.auth.getUser()) in CommentSidebar.tsx: registered users post under author_id, unauthenticated guests still get the name prompt via localStorage.
  4. Chat auto-scroll: scrollIntoView({ behavior: "smooth" }) kept getting interrupted by frequent streaming updates. Fixed by setting scrollTop = scrollHeight directly on a DOM ref (chatContainerRef) in ChatPanel.tsx during token generation.
  5. Self-Service Password Recovery Flow: While the UI page structure (/forgot-password) and Supabase resetPasswordForEmail() handlers exist, configuring custom SMTP mail server credentials for outbound password reset emails was deprioritized due to the 4-day timeline. Given more time, full production SMTP dispatch and password update callbacks would be fully connected.

Codebase breakdown

Database structure and Supabase migrations (./supabase)

The supabase directory has two paths into the schema: a single consolidated file for fast setup, and ordered migration files for tracking incremental changes.

supabase/
├── schema_combined.sql        # Consolidated multi-table schema, RLS policies & RPC functions
└── migrations/
    ├── 0001_extensions.sql            # pgvector & uuid-ossp PostgreSQL extensions
    ├── 0002_tables.sql                # Relational tables & HNSW vector index
    ├── 0003_rls_policies.sql          # Row-Level Security (RLS) policies
    ├── 0004_storage_bucket.sql        # Private Supabase Storage bucket initialization
    ├── 0005_rpc_match.sql             # match_pdf_chunks RPC for RAG passage retrieval
    ├── 0006_alter_pdfs_table.sql      # Schema migration altering pdfs table columns
    ├── 0007_alter_comments_table.sql  # Schema migration altering comments table columns
    └── 0008_rpc_search_user_pdfs.sql  # search_user_pdfs RPC for global semantic search

Backend (./backend)

Express.js and TypeScript, split into routes → middleware → controllers → services → config.

backend/
└── src/
    ├── index.ts                          # Entry point: imports server.ts, starts the Express server on PORT 4000
    ├── server.ts                         # Configures CORS, body parsers (json, urlencoded), registers routes under /api, mounts error handlers
    ├── config/
    │   ├── gemini.ts                     # Instantiates the GoogleGenAI client with GEMINI_API_KEY; exports GEMINI_MODEL and EMBEDDING_MODEL constants
    │   └── supabase.ts                   # Initializes supabaseAdmin with SUPABASE_SERVICE_ROLE_KEY for server-side DB/storage ops that bypass RLS
    ├── controllers/
    │   ├── chat.controller.ts            # Exposes askQuestion, streamQuestion (SSE), getChatHistory. Resolves guest vs. user session IDs
    │   ├── collaborator.controller.ts    # Exposes addCollaborator, listCollaborators, removeCollaborator
    │   ├── comment.controller.ts         # Exposes listComments, addComment; resolves author metadata (Owner, Collaborator, Guest)
    │   ├── pdf.controller.ts             # Handles uploadPdf, listPdf, getPdfDetails, createOrGetShareToken, getSharedPdfLookup; triggers background extraction/chunking/embedding/summary
    │   └── search.controller.ts          # Exposes semanticSearch; generates query embeddings, calls search_user_pdfs RPC with a text fallback
    ├── middleware/
    │   ├── errorHandler.ts               # Catches unhandled errors, formats JSON error responses with proper status codes
    │   ├── requireAuth.ts                # Validates Authorization: Bearer <token> JWTs via Supabase Auth, attaches req.user
    │   ├── resolveShareOrAuth.ts         # Permits access with either a valid user JWT or a valid x-share-token
    │   └── upload.ts                     # Configures multer memory storage for single-file PDF uploads up to 25MB
    ├── routes/
    │   ├── chat.routes.ts                # Maps /api/pdfs/:id/chat, /:id/chat/stream, /:id/chat/history
    │   ├── collaborator.routes.ts        # Maps /api/pdfs/:id/collaborators and /:id/collaborators/:userId
    │   ├── comment.routes.ts             # Maps /api/pdfs/:id/comments
    │   ├── pdf.routes.ts                 # Maps /api/pdfs/upload, /api/pdfs, /:id, /:id/share, /shared-lookup
    │   └── search.routes.ts              # Maps /api/search/semantic
    ├── services/
    │   ├── chat.service.ts               # Vector retrieval (retriveRelevantChunks), builds grounded RAG prompts with memory, streams tokens via SSE
    │   ├── chunking.service.ts           # Sliding-window chunking (500 tokens, 50-token overlap)
    │   ├── embeding.service.ts           # Calls gemini-embedding-001 (768 dims), stores embeddings in pdf_chunks, runs similarity queries
    │   ├── pdfExtraction.service.ts      # Converts PDF buffers to text with @cyber2024/pdf-parse
    │   ├── storage.service.ts            # Uploads PDFs to the Supabase pdfs bucket, generates signed view URLs
    │   └── summary.service.ts            # Sends full PDF text to gemini-3.6-flash for an executive summary on upload
    └── types/
        └── index.ts                      # Defines AuthRequest and other Express type extensions

Frontend (./frontend)

Next.js 16 (App Router) and Tailwind CSS, organized into pages, components, and helper libraries.

frontend/
└── src/
    ├── middleware.ts                          # Runs updateSession for route protection and cookie refreshing
    ├── app/
    │   ├── globals.css                        # Tailwind import, glassmorphism utilities (glass-panel, glass-card, glass-input), animations
    │   ├── layout.tsx                         # Root HTML structure and font config
    │   ├── page.tsx                           # / redirects to /dashboard
    │   ├── (auth)/
    │   │   ├── login/page.tsx                 # Login route
    │   │   ├── signup/page.tsx                # Signup route
    │   │   └── forgot-password/page.tsx       # Password reset route
    │   ├── (dashboard)/
    │   │   ├── layout.tsx                     # Sticky header nav, logo, profile display, sign-out button
    │   │   └── dashboard/page.tsx             # Upload dropzone, filename/semantic search bar, grid of PDF cards
    │   ├── pdf/[id]/
    │   │   └── page.tsx                       # Owner workspace: 3-panel layout (viewer, comments, AI chat), Share/Summary modals, mobile tab switcher
    │   └── share/[token]/
    │       └── page.tsx                       # Public guest workspace for /share/[token]: viewing, guest comments, AI chat
    ├── components/
    │   ├── auth/
    │   │   ├── LoginForm.tsx                  # Email/password fields, loading state, error messaging, Supabase auth
    │   │   └── SignupForm.tsx                 # Registration form with client-side validation
    │   ├── dashboard/
    │   │   ├── PdfCard.tsx                    # Title, timestamp, 3-line summary preview, "View Full" trigger, workspace link
    │   │   ├── SearchBar.tsx                  # Filename filtering plus AI semantic search toggle
    │   │   └── UploadDropzone.tsx             # Drag-and-drop upload: validation, progress, background-processing notices
    │   ├── pdf-workspace/
    │   │   ├── ChatPanel.tsx                  # SSE token streaming, Markdown rendering, auto-scroll pinning
    │   │   ├── CommentSidebar.tsx             # Multi-threaded discussion, guest name prompts, Realtime sync, 4-second fallback polling
    │   │   ├── CommentThread.tsx              # Role badges (Owner, Collaborator, Guest), timestamps, nested replies
    │   │   ├── PdfViewer.tsx                  # react-pdf canvas viewer: page nav, zoom, debounced resizing
    │   │   ├── ShareModal.tsx                 # Generates share links, invites collaborators by email
    │   │   └── SummaryModal.tsx               # Full Markdown AI summary with copy-to-clipboard
    │   └── ui/
    │       ├── Button.tsx                     # Variants (primary, secondary, outline, ghost), sizes, loading spinners
    │       └── Input.tsx                      # Styled text input
    └── lib/
        ├── api.ts                             # fetchApi wrapper attaching the Supabase JWT as Authorization: Bearer <token>
        ├── guestSession.ts                    # Manages guest session IDs (guest_<rand>_<time>) and names in localStorage
        ├── types.ts                           # Shared types: PdfDocument, CommentItem, ChatMessage, Collaborator
        └── supabase/
            ├── client.ts                      # Browser client singleton (createBrowserClient)
            ├── middleware.ts                  # Refreshes expired auth cookies, redirects unauthenticated users to /login
            └── server.ts                      # Server client helper (createServerClient) for Server Components/Actions

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages