DocuThinker-AI-App

DocuThinker Frontend

Welcome to the Frontend of the DocuThinker application! This React-based frontend integrates with the DocuThinker backend, allowing users to upload documents, chat with an AI, and extract key insights from their documents. The frontend also provides various authentication functionalities such as registration, login, and password recovery.

Documents in many formats β€” PDF, Word (DOCX), Markdown, HTML, CSV/TSV, JSON, plain text, and a broad set of code/config files β€” are parsed in the browser: clean text is extracted for the AI and a faithful display rendering is produced for the viewer, while the original file is uploaded directly to Supabase Storage so the real document can be re-rendered later β€” for both live uploads and reopened history.

Table of Contents

Overview

The DocuThinker Frontend is built using React 18 and Material-UI to create a clean and responsive interface. It allows users to:

Backend: the app talks to the deployed backend at https://docuthinker-app-backend-api.vercel.app. The original file bytes are stored in Supabase Storage via a backend-minted signed-upload token.

Tech Stack

Area Technology
UI framework React 18 (functional components + hooks)
Component library Material-UI (MUI) v6 with Emotion styling
Font Poppins (@fontsource/poppins)
Routing react-router-dom v6
HTTP axios
Build tooling Create React App compiled via CRACO (craco.config.js)
Storage client @supabase/supabase-js (direct-to-Storage signed uploads)
Client-side extraction A single extractDocument() dispatcher over pdfjs-dist (PDF), mammoth (DOCX), DOMPurify (HTML), file.text() (Markdown/text/code), and a custom CSV/TSV→table + JSON pretty-printer
PDF text extraction pdfjs-dist (legacy build, line/paragraph reconstruction)
DOCX conversion mammoth β†’ plain text + display HTML
HTML sanitizing dompurify for the original-document viewer
Markdown & summary rendering react-markdown + remark-gfm / remark-math / rehype-katex (KaTeX math)
Drag-and-drop upload react-dropzone
Google Drive import gapi-script (Drive read-only)
Passkeys @simplewebauthn/browser
Analytics Google Analytics + @vercel/analytics / @vercel/speed-insights

Pages

Route Page Description
/ Landing Marketing / welcome page with light & dark variants.
/home Home Upload a document, then view the original + summary and run all AI tools. Shows a sign-in card when logged out.
/documents Documents Instant client-side search (by title or summary), sort (newest / oldest / title A–Z / Z–A), type filter (PDF / Word / Markdown / HTML / CSV / JSON / Text, each with its own icon + colored chip), paginated (5 per page), plus a spinner while a doc opens. Rename, delete, or re-open any document.
/profile Profile Avatar, email, account stats (days since joined, document count), social links, and a hero card. Sign-in gated.
/passkeys Passkeys WebAuthn management β€” add, rename, and delete passkeys (auth-only).
/how-to-use How to Use Step-by-step guide to every feature.
/login Login Email/password, Google OAuth, and β€œSign in with a passkey”.
/register Register Account creation, followed by an optional β€œcreate a passkey” prompt.
/forgot-password Forgot Password Password reset flow.

User Interfaces

The frontend consists of several pages and components that make up the user interface. Here are the main pages:

Landing Page

Landing Page

Document Upload Page

Document Upload Page

Document Upload Page - Document Uploaded

Document Upload Page - Document Uploaded

Home Page

Home Page

Home Page - Dark Mode

Home Page - Dark Mode

Chat Modal

Chat Modal

Document Analytics

Document Analytics

Documents Page

Documents Page

Profile Page

Profile Page

How To Use Page

How To Use Page

Passkeys Management Page

Passkeys Management Page

Privacy Policy Page

Privacy Policy Page

Terms of Service Page

Terms of Service Page

Login Page

Login Page

Registration Page

Registration Page

Reset Password Page

Forgot Password Page

Forgot Password Page

Key Features

Supported Upload Formats

The upload modal (components/UploadModal.js) accepts a wide range of formats. A single extractDocument() dispatcher inspects the file’s MIME type / extension and routes it to the right handler, returning clean text (for the AI), display HTML (for the viewer), and a resolved fileType. The original file is always stored as-is; the AI only ever sees the extracted text.

Format Extensions How it’s extracted Viewer rendering
PDF .pdf pdfjs-dist (line/paragraph reconstruction) Native <iframe> of the real PDF
Word .docx mammoth β†’ raw text + structured HTML Sanitized HTML
Markdown .md, .markdown file.text() react-markdown (GFM + KaTeX)
HTML .html, .htm file.text(), tags stripped for the AI DOMPurify-sanitized HTML
CSV / TSV .csv, .tsv file.text() β†’ parsed delimited rows HTML <table> (first row = header)
JSON .json file.text(), pretty-printed via JSON.stringify(…, 2) Monospace <pre>
Plain text .txt, .text, .log file.text() pre-wrap plaintext
Code / config .xml .yaml/.yml .js/.jsx/.mjs/.cjs .ts/.tsx .py .java .c/.cpp/.cc/.h/.hpp .cs .go .rs .rb .php .sql .sh/.bash .css/.scss/.less .ini/.toml/.conf/.env .kt .swift .r .lua .pl file.text() Monospace <pre>

Unsupported files surface a clear error listing the accepted formats; nothing is sent to the backend.

Document Upload & Storage Flow

The upload pipeline (components/UploadModal.js + utils/supabaseClient.js) does three things: extract text/HTML for the AI and viewer, store the original file, then ask the backend to summarize.

  1. Extract β€” extractDocument() dispatches on the file type: extractFromPdf (via pdfjs-dist) returns clean plaintext + reconstructed <p> HTML; extractFromDocx (via mammoth) returns raw text and structured HTML; Markdown/HTML/CSV/TSV/JSON/text/code are read with file.text() and turned into their display form (sanitized HTML, table, pretty-printed JSON, or monospace block). See Supported Upload Formats.
  2. Store the original file β€” the browser asks the backend to mint a short-lived signed upload token (POST /document-upload-url), then uploads the file bytes directly to Supabase Storage with supabase.storage.from(BUCKET).uploadToSignedUrl(...). This bypasses the serverless body-size limit, so large PDFs upload fine. If the direct upload can’t run (e.g. the frontend Supabase env vars aren’t set), it falls back to a through-backend multipart upload (POST /document-file). Storage is non-fatal: if it fails entirely, the app still summarizes and falls back to the HTML/text view.
  3. Summarize β€” the extracted text, display HTML, stored file path, and file type are sent to POST /upload, which returns the summary plus a signed fileUrl the viewer can render.
sequenceDiagram
    autonumber
    actor User
    participant Modal as UploadModal.js
    participant Backend as Backend API
    participant Storage as Supabase Storage

    User->>Modal: Drop / pick a file (PDF, DOCX, MD, HTML, CSV, JSON, text, code…)
    Modal->>Modal: extractDocument() β†’ text + display HTML
    Modal->>Backend: POST /document-upload-url (userId, fileName)
    Backend-->>Modal: { path, token } (signed upload)
    alt Supabase envs present
        Modal->>Storage: uploadToSignedUrl(path, token, file)
    else Fallback
        Modal->>Backend: POST /document-file (multipart)
    end
    Modal->>Backend: POST /upload (text, html, filePath, fileType)
    Backend-->>Modal: { summary, originalText, originalHtml, fileUrl, fileType }
    Modal->>User: Render Original + Summary

The Supabase browser client is created in utils/supabaseClient.js from REACT_APP_SUPABASE_* env vars. The public anon key cannot read the private bucket on its own β€” every upload is authorized by the server-minted signed token (the service_role key stays on the backend). When the env vars are absent the client is null, and callers transparently use the through-backend fallback.

Original Document Viewer

The Home page renders the left column based on the stored file type, so the viewer works identically for a fresh upload and a document reopened from history:

Source Rendering
PDF (fileType includes pdf and a signed fileUrl exists) Native <iframe> pointing at the signed Supabase URL β€” real, paginated PDF pages.
DOCX / HTML / CSV / TSV / code (any originalHtml present) The display HTML is sanitized with DOMPurify and styled for headings, bold, lists, tables, blockquotes, images, and <pre> code blocks.
Markdown Rendered with react-markdown (GFM tables + KaTeX math).
Anything else / no file Readable pre-wrap plaintext fallback.

While dragging the column splitter, a transparent overlay sits above the iframe so the PDF doesn’t swallow the mouse events and break the drag.

AI Tools

All AI tools run against the deployed backend. The document title is prepended as extra context (withTitle(...)) on non-persisted payloads, giving the model a stronger signal without polluting the stored text.

Tool What it does
Summary Generated on upload; rendered as Markdown with GFM tables + KaTeX math.
Key Ideas Extracts the most important points.
Discussion Points Prompts for debate / group analysis.
Bullet-Point Summary A concise bulleted digest.
Change Language Re-renders the summary in any of ~45 languages.
Sentiment Analysis A positive/neutral/negative meter. Cached per document in localStorage (content-addressed key) so revisits and history switches don’t recompute.
Document Analytics A client-side stats modal: a Flesch readability gauge, top-word bars, word-length and sentence-length distributions, punctuation analysis, lexical diversity, reading & speaking time, and animated counters β€” all computed in the browser.
Generate Recommendations Actionable next steps based on the content.
Rewrite Content Rewrites the whole document β€” or just a highlighted passage β€” in a chosen tone/style.
Refine Summary Re-summarizes with custom instructions.
Voice Chat Upload or record audio (mic-recorder-to-mp3) and talk to the AI.
AI Chat Ask questions grounded in the document. Shows a friendly greeting when the thread is empty, and prepends the document title as context so the model has a stronger signal (also aware of today’s date).

File Structure

Here is the complete file structure for the DocuThinker Frontend. The frontend is located under DocuThinker-AI-App/frontend:

DocuThinker-AI-App/
β”œβ”€β”€ frontend/
β”‚   β”œβ”€β”€ public/
β”‚   β”‚   β”œβ”€β”€ index.html                     # Main HTML template
β”‚   β”‚   β”œβ”€β”€ manifest.json                  # Manifest for PWA settings
β”‚   β”‚   └── pdf.worker.min.mjs             # Local pdf.js worker (served for client-side extraction)
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ assets/                        # Static assets like images and fonts
β”‚   β”‚   β”‚   └── logo.png                   # App logo or images
β”‚   β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”‚   β”œβ”€β”€ ChatModal.js               # AI chat modal
β”‚   β”‚   β”‚   β”œβ”€β”€ Spinner.js                 # Loading spinner component
β”‚   β”‚   β”‚   β”œβ”€β”€ UploadModal.js             # Upload + client-side extraction + Supabase storage
β”‚   β”‚   β”‚   β”œβ”€β”€ GoogleDriveFileSelectorModal.js  # Google Drive file picker
β”‚   β”‚   β”‚   β”œβ”€β”€ PasskeyPromptModal.js      # Post-sign-up "create a passkey" modal
β”‚   β”‚   β”‚   β”œβ”€β”€ useErrorToast.js           # Reusable error-toast hook
β”‚   β”‚   β”‚   β”œβ”€β”€ Navbar.js                  # Navigation bar (orange-accent hover, Account dropdown, mobile drawer)
β”‚   β”‚   β”‚   β”œβ”€β”€ Footer.js                  # Footer component
β”‚   β”‚   β”‚   └── GoogleAnalytics.js         # Google Analytics integration component
β”‚   β”‚   β”œβ”€β”€ pages/
β”‚   β”‚   β”‚   β”œβ”€β”€ Home.js                    # Upload + original/summary viewer + all AI tools
β”‚   β”‚   β”‚   β”œβ”€β”€ DocumentsPage.js           # History: search / sort / filter / paginate / rename / delete
β”‚   β”‚   β”‚   β”œβ”€β”€ Profile.js                 # Avatar, email, stats, social links, hero card
β”‚   β”‚   β”‚   β”œβ”€β”€ LandingPage.js             # Welcome and information page
β”‚   β”‚   β”‚   β”œβ”€β”€ Login.js                   # Login page (incl. "Sign in with a passkey")
β”‚   β”‚   β”‚   β”œβ”€β”€ Register.js                # Registration page
β”‚   β”‚   β”‚   β”œβ”€β”€ Passkeys.js                # Passkey management page (add/rename/delete)
β”‚   β”‚   β”‚   β”œβ”€β”€ ForgotPassword.js          # Forgot password page
β”‚   β”‚   β”‚   β”œβ”€β”€ PrivacyPolicy.js           # Privacy Policy page (dark-mode-aware)
β”‚   β”‚   β”‚   β”œβ”€β”€ TermsOfService.js          # Terms of Service page (dark-mode-aware)
β”‚   β”‚   β”‚   β”œβ”€β”€ NotFoundPage.js            # 404 page (dark-mode-aware)
β”‚   β”‚   β”‚   └── HowToUse.js                # Page explaining how to use the app
β”‚   β”‚   β”œβ”€β”€ utils/
β”‚   β”‚   β”‚   β”œβ”€β”€ auth.js                    # Event-driven client session helper
β”‚   β”‚   β”‚   β”œβ”€β”€ api.js                     # Centralized API base URL
β”‚   β”‚   β”‚   β”œβ”€β”€ supabaseClient.js          # Browser Supabase client (REACT_APP_SUPABASE_*)
β”‚   β”‚   β”‚   └── passkeys.js                # WebAuthn (passkey) client helpers
β”‚   β”‚   β”œβ”€β”€ App.js                         # Main App component
β”‚   β”‚   β”œβ”€β”€ index.js                       # Entry point for the React app
β”‚   β”‚   β”œβ”€β”€ App.css                        # Global CSS 1
β”‚   β”‚   β”œβ”€β”€ index.css                      # Global CSS 2
β”‚   β”‚   β”œβ”€β”€ reportWebVitals.js             # Web Vitals reporting
β”‚   β”‚   β”œβ”€β”€ styles.css                     # Custom styles for different components
β”‚   β”‚   └── config.js                      # Configuration file for environment variables
β”‚   β”œβ”€β”€ craco.config.js                    # CRACO config (Node polyfill fallbacks for the browser bundle)
β”‚   β”œβ”€β”€ .env                               # Environment variables file (REACT_APP_* only)
β”‚   β”œβ”€β”€ package.json                       # Project dependencies and scripts
β”‚   β”œβ”€β”€ README.md                          # This README file
β”‚   └── package-lock.json                  # Lock file for dependencies

Key Folders

Client-side Auth (src/utils/auth.js)

Session state lives in localStorage under two keys (token, userId) and is broadcast through a custom "auth-change" event plus the native cross-tab storage event. Screens subscribe via onAuthChange(handler) and re-render the instant the keys change β€” there is no polling.

sequenceDiagram
    autonumber
    actor User
    participant Login as pages/Login.js
    participant Auth as utils/auth.js
    participant LS as localStorage
    participant Navbar as components/Navbar.js
    participant OtherTab as Other tab

    User->>Login: Submit credentials
    Login->>Auth: setAuth(customToken, userId)
    Auth->>LS: setItem(token), setItem(userId)
    Auth-->>Navbar: dispatchEvent("auth-change")
    Navbar->>Navbar: setIsLoggedIn(isAuthenticated())
    Auth-->>OtherTab: native "storage" event<br/>(cross-tab only)
    OtherTab->>OtherTab: re-render with logged-in state

    User->>Navbar: Click Sign out
    Navbar->>Auth: clearAuth()
    Auth->>LS: removeItem(token), removeItem(userId)
    Auth-->>Navbar: dispatchEvent("auth-change")
    Navbar->>Navbar: setIsLoggedIn(false)

Public surface:

Export Use
isAuthenticated() Sync !!localStorage.getItem("userId")
setAuth(token, userId) Write both keys + emit
clearAuth() Remove both keys + emit
onAuthChange(handler) Subscribe (same tab + cross-tab); returns unsubscribe

Notable refactors landed in the latest PR

flowchart LR
    subgraph PR["What the PR changes"]
        A[utils/auth.js<br/>new utility]
        B[Login.js<br/>setAuth on success]
        C[Profile.js<br/>clearAuth on signout]
        D[Navbar.js<br/>onAuthChange subscribe]
    end

    A --> B
    A --> C
    A --> D

    B -.->|emit| D
    C -.->|emit| D
    D -.->|read| LS[(localStorage<br/>token + userId)]
    B -.->|write| LS
    C -.->|remove| LS

Passkeys (WebAuthn)

Passwordless sign-in is implemented with @simplewebauthn/browser, wrapped in src/utils/passkeys.js. The browser library and the backend’s @simplewebauthn/server are a matched pair: the server emits the options JSON consumed here, and the response JSON produced here is verified verbatim by the server.

Surface What it does
pages/Login.js β€œSign in with a passkey” button β€” discoverable (usernameless) or email-scoped. On success it calls the same setAuth(token, userId) as password login.
components/PasskeyPromptModal.js Shown right after sign-up to invite the user to enroll their first passkey (a styled modal, never a native alert/prompt).
pages/Passkeys.js Account-only page (guarded by RequireAuth) to add, rename, and delete multiple passkeys, with β€œSynced / This device” badges and themed dialogs.
components/Navbar.js When signed in, the Logout button becomes an Account dropdown β†’ Passkeys + Log Out (Log Out stays destructive-red); the mobile drawer gets a Passkeys entry.

utils/passkeys.js exposes isPasskeySupported(), registerPasskey(), authenticateWithPasskey(), listPasskeys(), renamePasskey(), and deletePasskey(). The backend origin comes from utils/api.js (REACT_APP_API_BASE_URL, falling back to the deployed backend).

Prerequisites

Before you begin, ensure you have the following installed on your machine:

Installation

To get started, follow these steps:

  1. Clone the repository:

    git clone https://github.com/hoangsonww/DocuThinker-AI-App.git
    cd DocuThinker-AI-App/frontend
    
  2. Install dependencies: Using npm:

    npm install
    

    or using Yarn:

    yarn install
    

Environment Variables

Create an .env file in the frontend/ directory. Every variable must be prefixed with REACT_APP_ β€” Create React App only exposes REACT_APP_* variables to the bundle, and they are baked in at build time, so you must rebuild after changing them (and never put secrets like a Supabase service_role key here β€” only browser-safe public values).

# --- Backend (optional; defaults to the deployed backend) ---
REACT_APP_BACKEND_URL=http://localhost:3000          # Backend URL for API requests
REACT_APP_API_BASE_URL=http://localhost:3000         # Backend origin for passkey/API calls

# --- Supabase Storage (original-document upload flow) ---
REACT_APP_SUPABASE_URL=https://<project>.supabase.co # Supabase project URL
REACT_APP_SUPABASE_ANON_KEY=<public-anon-key>        # Public anon key (browser-safe)
REACT_APP_SUPABASE_BUCKET=docuthinker                # Storage bucket name (default: docuthinker)

# --- Google Drive import (optional) ---
REACT_APP_GOOGLE_DRIVE_API_KEY=<api-key>             # Google API key with Drive API enabled
REACT_APP_GOOGLE_DRIVE_CLIENT_ID=<oauth-client-id>   # Google OAuth client ID

# --- Analytics (optional) ---
REACT_APP_GOOGLE_ANALYTICS_ID=G-XXXXXX               # Google Analytics ID

Notes

Running the App

The app is built with Create React App via CRACO β€” the npm scripts call craco start / craco build (defined in package.json), and craco.config.js adds the Node polyfill fallbacks (crypto, stream, buffer, util, vm) that the browser bundle needs.

  1. Start the development server:

    npm start      # alias: npm run dev  β†’ runs `craco start`
    

    or if using yarn:

    yarn start
    
  2. Open your browser and navigate to http://localhost:3000 (or the port CRA assigns / the PORT you configured).

  3. Build for production:

    npm run build  # runs `craco build` β†’ outputs to build/
    

Scripts

Here are the most important scripts available in package.json:

Script Command Description
npm start / npm run dev craco start Starts the app in development mode.
npm run build craco build Builds the production bundle into build/.
npm test jest Runs the test suite.
npm run test:watch jest --watch Runs tests in watch mode.
npm run test:coverage jest --coverage Runs tests with a coverage report.

Testing

The test runner is jest with jest-environment-jsdom. Both are declared explicitly in package.json (no transitive-only resolution). The npm test script runs jest directly, which does not watch by default:

npm test                 # single run
npm run test:watch       # watch mode
npm run test:coverage    # with a coverage report

There are six suites under src/__tests__/ covering structure, package metadata, source content, deps, runtime basics, and README presence.

Screenshots

Here are some screenshots of the DocuThinker Frontend:

Landing Page

[Placeholder for Landing Page Screenshot - Centered]

Document Upload

[Placeholder for Document Upload Screenshot - Centered]

Login Page

[Placeholder for Login Page Screenshot - Centered]

Note: Replace the placeholders with actual screenshots once you have them. You can take screenshots using your browser or a screenshot tool.

Deployment

Deploying to Vercel

To deploy the app to Vercel, follow these steps:

  1. Create an account on Vercel if you don’t have one.
  2. Install the Vercel CLI:
    npm install -g vercel
    
  3. Link your project:
    vercel
    
  4. Deploy the project:
    vercel --prod
    

You can also configure the project in Vercel’s dashboard and trigger deployments from your GitHub repository.

Contributing

We welcome contributions from the community! If you’d like to contribute, please follow these steps:

  1. Fork the repository.
  2. Create a new branch:
    git checkout -b feature/your-feature
    
  3. Make your changes and commit them:
    git commit -m "Add your feature"
    
  4. Push the changes to your forked repository:
    git push origin feature/your-feature
    
  5. Open a pull request to the main repository.

License

This project is licensed under the MIT License. See the LICENSE file for details.


Happy coding! πŸš€