Skip to content

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FINORA - COMPLETE BEGINNER GUIDE
================================

Last updated: 21 August 2026

This guide explains the project from the first browser click to the last database
write. You do not need backend experience to follow it. Read the QUICK START first
if you only want to run the app. Read the remaining sections to understand how it
works internally.


1. WHAT FINORA IS
-----------------

Finora is a full-stack document intelligence application. A user can upload a
PDF, DOC, DOCX, or TXT file, keep the original file in private object storage,
process its contents, search source-linked passages, review extracted fields, ask
questions with citations, edit an extraction template, and view workspace analytics.

The browser is not the source of truth. Users, workspaces, documents, processing
states, fields, schemas, activities, and assistant history are persisted by the
backend. The interface does not contain fake documents, fake AI answers, or fake
processing timers.


2. QUICK START IN VS CODE ON WINDOWS
------------------------------------

Prerequisites:

1. Install Node.js 22.13 or newer.
2. Install Visual Studio Code.
3. Open VS Code.
4. Choose File -> Open Folder.
5. Select:

   C:\Users\anubh\Desktop\Talk2PDF

6. In VS Code choose Terminal -> New Terminal.
7. Run these commands one at a time:

   npm install
   Copy-Item .env.example .env.local
   npm run db:migrate:local
   npm run dev

8. Wait until the terminal prints:

   Local: http://localhost:3000/

9. Open http://localhost:3000 in a browser.
10. Click "Open workspace" or go directly to:

   http://localhost:3000/workspace

Keep the terminal open. The development server stops when the terminal closes or
when you press Ctrl+C.


3. ENABLE FREE-TIER PDF AI PROCESSING
-------------------------------------

TXT files work locally without an AI key. They are really stored, split into
passages, indexed, and searchable. PDF understanding and the cited AI assistant can
use Google's Gemini API free tier. Google applies free usage limits, so this means
free within its current quota, not unlimited use forever.

Create a Gemini API key at:

   https://aistudio.google.com/api-keys

Sign in with Google, create an API key in a free project, and do not enable billing
unless you personally decide to upgrade later. Then open .env.local and set:

   AI_PROVIDER=gemini
   GEMINI_API_KEY=your_private_gemini_key
   GEMINI_MODEL=gemini-3.1-flash-lite
   GEMINI_EMBEDDING_MODEL=gemini-embedding-001

Then stop and restart npm run dev.

Important rules:

- Never add NEXT_PUBLIC_ in front of GEMINI_API_KEY. NEXT_PUBLIC values can be sent
  to the browser.
- Never paste the key into a chat, screenshot, source file, or Git commit. Put it
  only after GEMINI_API_KEY= in the private .env.local file.
- Never commit .env.local. The .gitignore already excludes it.
- If the key is missing, Finora deliberately reports a configuration error. It
  does not invent processing results or AI answers.
- Gemini mode supports PDF and TXT input. Convert DOC or DOCX files to PDF before
  uploading them. The optional paid OpenAI provider remains available for those
  formats if you configure AI_PROVIDER=openai and OPENAI_API_KEY.
- Google's free-tier terms say submitted content may be used to improve Google
  products. Do not use the free tier for confidential documents unless that policy
  is acceptable to you.


4. TECHNOLOGY USED
------------------

Frontend:

- React 19 renders the interactive interface.
- TypeScript checks the shapes of data and catches mistakes before runtime.
- vinext provides the Next.js-compatible application router on Vite.
- TanStack Query loads server data, caches it, handles errors, and refreshes it.
- Zustand stores small device/UI choices such as the active section and selected
  document. It does not replace the server database.
- React Hook Form and Zod validate editable forms.
- Framer Motion provides restrained interface animation.
- Lucide supplies accessible interface icons.

Backend and storage:

- Route handlers in app/api are the HTTP backend.
- A Cloudflare Worker runs the full application.
- D1 (SQLite) stores relational application records.
- R2 stores the original uploaded document bytes.
- Drizzle ORM creates typed SQL queries and migrations.
- unpdf supplies a serverless PDF.js build that extracts text separately from every
  PDF page inside Node.js and Cloudflare Workers.
- Gemini Generate Content reads PDF/TXT documents and produces strict,
  source-faithful structured output on the free tier.
- Gemini Embeddings creates vectors for semantic ranking on the free tier.
- OpenAI Responses and Embeddings remain an optional paid alternative.

The frontend and backend are in one deployable project. Browser requests use /api,
so no separate Express server or CORS configuration is required.


5. IMPORTANT FOLDERS AND FILES
------------------------------

app/
  Page routes and API routes. app/page.tsx is the landing page.
  app/workspace/page.tsx is the signed-in product shell.

app/api/
  The real backend HTTP endpoints. Every protected endpoint creates an authenticated
  request context before reading or changing workspace data.

components/
  Shared visual building blocks: top bar, sidebar, status, upload dialog, command
  palette, and empty states.

features/
  Product areas grouped by purpose: dashboard, documents, search, assistant,
  schemas, analytics, settings, and the landing tour.

services/api-client.ts
  The one browser API gateway. UI components call this file instead of constructing
  unrelated fetch requests in many places.

stores/workspace-store.ts
  Short-lived interface state such as the current navigation section, theme,
  selected document, page number, and open dialogs.

types/index.ts
  Shared browser-side TypeScript contracts for documents, snapshots, fields,
  results, citations, and analytics.

server/auth.ts
  Turns trusted hosting headers or the local development identity into a user,
  workspace, membership, and role. It also checks document ownership and write roles.

server/ai.ts
  Analyzes compact document metadata, creates rate-aware grouped embeddings, and
  answers only from page-linked source passages through Gemini or OpenAI.

server/document-content.ts
  Uses unpdf to read every original PDF page locally, measures readable-page
  coverage, rejects image-only scans that need OCR, and produces searchable chunks.

server/text-chunking.ts
  Normalizes extracted text and splits long pages into overlapping passages so a
  fact crossing a chunk boundary is not silently lost.

server/documents.ts
  Runs actual document processing, persists pages/chunks/fields, changes job state,
  records audit activity, and saves actionable failures.

server/search.ts
  Loads only chunks from the active workspace, applies semantic similarity when
  embeddings exist, and uses honest keyword ranking when they do not.

server/runtime.ts
  Reads D1, R2, AI model configuration, and local identity from the runtime.

next.config.ts
  Raises vinext's multipart/Server Action request envelope from its 1 MB default to
  100 MB. The upload API still applies the stricter product rules: 25 MB per file
  and no more than 10 files. Without this framework setting, a 1.3 MB book can be
  rejected before Finora's upload route gets a chance to validate or store it.

server/serializers.ts
  Converts raw database rows into stable, human-friendly API responses.

db/schema.ts
  The complete relational data model.

drizzle/
  Generated SQL migrations. These files create the database in a repeatable way.

worker/index.ts
  The production Cloudflare Worker entry point. It serves application routes and
  image optimization.

.openai/hosting.json
  Declares the hosted project plus its DB and DOCUMENTS bindings.

wrangler.jsonc
  Defines matching local D1/R2 resources and the migration directory.

.env.example
  A safe template of environment variable names. It contains no secret value.

aboutme.txt
  A dated implementation and verification log, updated as backend work progresses.


6. DATABASE TABLES
------------------

users
  One row per authenticated person: ID, email, display name, timestamps.

workspaces
  A separate data boundary for a team. created_by records the creator.

memberships
  Connects users to workspaces and assigns owner, admin, reviewer, member, or viewer.

documents
  File metadata: workspace, uploader, R2 key, MIME type, size, detected type,
  processing state, pages, confidence, favorite flag, and failure information.

processing_jobs
  Real processing state: stage, progress, attempts, start/end times, and errors.

document_pages
  Readable text grouped by original page number.

document_chunks
  Smaller searchable passages with workspace, document, page, section, content,
  and an optional JSON-encoded embedding.

extraction_schemas
  Workspace extraction-template headers such as Invoice and its version.

schema_fields
  Ordered field definitions: name, type, required flag, and description.

extraction_fields
  Values found in one document, including confidence, page, provenance, and verifier.

activities
  Audit-friendly workspace events for uploads, processing, AI, and human review.

assistant_queries
  Successfully generated questions, answers, and their citations.

Foreign keys prevent orphan records. Workspace indexes make the actual application
queries efficient. Unique indexes stop duplicate memberships, page numbers, jobs,
schemas, and extracted field keys.


7. AUTHENTICATION AND AUTHORIZATION
-----------------------------------

In hosted production, the platform supplies trusted headers:

- oai-authenticated-user-id
- oai-authenticated-user-email
- oai-authenticated-user-full-name

The backend reads these headers; the browser is not allowed to choose a workspace
ID or user role. On the first request, the backend upserts the user. If that user has
no membership, it creates "My Workspace", owner membership, and a default invoice
schema.

For local development, .env.local provides LOCAL_DEV_USER_EMAIL and
LOCAL_DEV_USER_NAME. This avoids building a fake login screen while still exercising
the same persisted user/workspace/membership checks.

Every document-specific route verifies both the document ID and active workspace.
Viewer roles cannot upload, edit fields, change schemas, or retry processing.


8. COMPLETE UPLOAD AND PROCESSING FLOW
--------------------------------------

1. The user opens Upload documents.
2. The browser validates extension, size (25 MB maximum), and a maximum of 10 files.
3. services/api-client.ts sends multipart/form-data to the upload API. The 100 MB
   framework envelope lets ordinary document requests reach the application; the
   API then enforces the documented per-file and file-count rules.
4. The backend authenticates the request and checks write permission.
5. The backend validates again. Browser validation is helpful but never trusted.
6. A random document ID and workspace-scoped storage key are created.
7. Original bytes are written to the DOCUMENTS R2 bucket.
8. A document row, processing-job row, and upload activity are inserted in D1.
9. The processing service reads the bytes back from R2.
10. For PDF, unpdf reads every original page using its Cloudflare-compatible PDF.js
    engine. TXT is split by form-feed page boundaries. This extraction does not ask
    Gemini to summarize the file and therefore cannot silently shrink a whole book
    into a few AI-selected snippets.
11. Each original page is normalized and saved, including blank pages. Pages with
    text are divided into passages of about 1,600 characters with 240 characters of
    overlap. The overlap preserves sentences and facts near passage boundaries.
12. The backend checks extraction coverage. A mostly image-only/scanned PDF is not
    marked complete with missing content; it returns instructions to run OCR first.
13. Gemini or OpenAI receives a small spread of page samples only for classification,
    a concise description, and useful fields. That metadata response is never used
    as the full search index.
14. Gemini embeddings combine up to five neighboring passages under a safe character
    limit. One group vector is attached back to each member passage. This covers a
    book within far fewer free-tier embedding operations. If quota is unavailable,
    full keyword search remains usable and processing still stores every page.
15. Page rows are inserted in batches of 20 and chunk rows in batches of 10 so D1's
    SQL parameter limit is not exceeded by long books.
16. Retry clears old processing output and rebuilds the page/chunk/field set in small
    batches. A failed rebuild remains visibly Failed and can be retried safely.
17. Coverage below 80 percent or low-confidence fields puts the document in Review.
    Coverage below 50 percent fails with OCR guidance instead of false success.
18. The activity log records total pages, readable pages, coverage, characters,
    chunks, embedded chunks, and fields without storing document text in logs.
19. TanStack Query refreshes the workspace so the persisted result appears.

If processing throws, the original file remains stored, document/job rows become
failed, the exact safe message is saved, and the UI offers Retry processing. Retry
uses the same authorized pipeline and increments the attempt count.


9. SEARCH FLOW
--------------

1. The user enters at least two characters.
2. The backend restricts candidate chunks to the active workspace, selected document,
   and optional type. It evaluates every stored chunk; there is no old 300-chunk
   cutoff that could exclude later chapters.
3. With embeddings, it compares the query with grouped passage vectors by cosine
   similarity and combines semantic and lexical relevance.
4. Without embeddings, it counts honest query-term matches across the complete
   document. Common question words are removed and simple word endings are reduced
   so "hire", "hired", and similar forms rank more consistently. Free-tier quota
   failure never deletes the extracted page index.
5. Results include document ID/name, original page, section, passage, type, and score.
6. Clicking a result selects that document and opens its source page.


10. AI ASSISTANT AND CITATIONS
------------------------------

1. The question is validated (3 to 2,000 characters).
2. If a document is selected, workspace access to it is verified.
3. The backend classifies the question as focused, whole-document overview, or
   ending analysis. A focused question retrieves up to 12 relevant passages from
   every indexed page.
4. A broad prompt containing terms such as summarize, synopsis, overview, whole
   book, or main themes loads the complete ordered book index (within the provider's
   safe context budget) so the answer represents the whole work. Ending phrases
   such as "explain the ending," "final twist," "epilogue," "last chapter," and
   "what really happened" also load the complete book instead of relying on a few
   keyword matches.
5. Only permitted page-linked passages and the question are sent to the AI service.
   If a focused query has no lexical or semantic match at all, the assistant falls
   back to the complete selected-document context instead of failing prematurely.
   It also performs that full-context second pass when the focused answer contains
   no valid citation, protecting paraphrased questions from a weak first retrieval.
6. Strict output requires an answer and source indexes. By default, focused answers
   target 250 to 600 words, full summaries target 700 to 1,200 words, and ending
   explanations target 600 to 1,000 words. An explicit request for a brief answer
   overrides those targets.
7. Ending answers must walk through the decisive events, reveal or twist, motives,
   consequences, conflicting evidence, and any deliberate ambiguity. Confirmed
   source facts must be separated from interpretation.
8. If a cited answer is still below its mode's minimum depth, the backend makes one
   controlled rewrite request asking for a fuller explanation without repetition.
   Unsupported questions may remain short because the system must not pad a refusal
   with invented details.
9. The prompt may correct an incorrectly named role, but it may never invent an
   unsupported relationship. The provider has a 6,144-token output budget so it is
   not accidentally forced into a tiny response.
10. Invalid indexes are removed and citations are capped at 6 for focused answers or
    12 for overviews/endings so the interface remains useful.
11. The successful answer and citations are saved to assistant_queries.
12. Paragraph breaks from detailed answers are preserved on screen. Each source
    button opens the exact stored document page.

When the selected provider key is absent, the endpoint returns HTTP 503 with setup
guidance. There is no local fabricated answer fallback.


11. EXTRACTION AND SCHEMA EDITING
---------------------------------

Opening a document calls its detail endpoint, so the extracted fields always belong
to that selected document. Editing a value changes its provenance to modified.
Saving performs validated upserts keyed by document plus field key and creates an
activity record.

The Schema page edits an ordinary visual form. Saving validates 1 to 50 fields,
replaces the ordered Invoice field set, increments the schema version, and records
the activity. Returned IDs are the same IDs stored in D1.


12. ANALYTICS
-------------

Analytics are calculated from persisted workspace rows:

- processed documents
- completed/review success percentage
- average processing duration
- documents needing review
- average extraction confidence
- original storage bytes
- daily upload throughput

No chart number is hardcoded in the frontend.


13. API REFERENCE
-----------------

GET  /api/health
  Service readiness and whether AI is configured.

GET  /api/workspaces/current/snapshot
  Current user/workspace, documents, activity, pipeline, latest fields, and schema.

POST /api/workspaces/current/documents
  Validated multipart upload, R2 storage, processing, and persisted result.

GET  /api/workspaces/current/documents/:id
  One authorized document plus its own extracted fields.

GET  /api/workspaces/current/documents/:id/file
  Original R2 object with inline content headers and private no-store caching.

POST /api/workspaces/current/documents/:id/retry
  Authorized retry of a failed or reprocessable document.

PUT  /api/workspaces/current/documents/:id/extraction
  Validate and save reviewed/modified extraction fields.

GET  /api/workspaces/current/search?q=...
  Workspace-scoped semantic/keyword passage results.

POST /api/workspaces/current/assistant
  Source-constrained cited answer.

PUT  /api/workspaces/current/schemas/invoice
  Validate and save the Invoice extraction field template.

GET  /api/workspaces/current/analytics
  Live workspace metrics.

Errors use JSON with message and code. Unknown internal details are not leaked.


14. LOCAL DATA AND MIGRATIONS
-----------------------------

Local D1 and R2 emulator state is under .wrangler/. It is ignored by Git.

To apply new migrations after schema changes:

   npm run db:generate
   npm run db:migrate:local

Do not manually edit generated migration snapshots. Change db/schema.ts first and
generate the migration.

Deleting .wrangler removes local development data. That is destructive and cannot
be undone, so stop the server and back up anything important before doing it.


15. QUALITY CHECKS
------------------

Run:

   npx tsc --noEmit
   npm run lint
   npm test

npx tsc checks TypeScript. npm run lint checks React, unused values, and code
quality. npm test makes a production build, checks important rendered HTML, creates
a two-page PDF in memory, verifies that both pages are extracted separately, and
checks that long overlapping chunks retain their beginning and ending.

Manual primary journey:

1. Open /workspace.
2. Upload tests/fixtures/sample-agreement.txt.
3. Confirm Completed appears.
4. Open the document and confirm the original text loads.
5. Search for "termination" and open the result.
6. Add/edit an extracted field and save it.
7. Open Analytics and confirm persisted counts.
8. With GEMINI_API_KEY configured, ask a question and open its citation.
9. Without the key, confirm the assistant gives an actionable configuration error.


16. PRODUCTION HOSTING
----------------------

For a complete beginner-friendly launch sequence, access decisions, secret setup,
private-first deployment, smoke testing, security checks, monitoring, rollback, and
scaling plan, read HOSTING_ROADMAP.TXT in this project.

The project is prepared for OpenAI Sites/Cloudflare hosting through
.openai/hosting.json. Production needs:

- the hosted D1 binding named DB
- the hosted R2 binding named DOCUMENTS
- the D1 migration applied to the production database
- GEMINI_API_KEY stored as a server secret when free-tier AI features are wanted
- authenticated hosting enabled so trusted user headers are supplied

Do not upload/deploy source code or create paid resources merely by running the
local app. Publishing is a separate external action and should be done only after
the owner approves the target project and secret configuration.


17. CURRENT ARCHITECTURE BOUNDARIES
-----------------------------------

This repository originally had no PostgreSQL, Redis, BullMQ, Python service, or
WebSocket server to preserve. The working implementation uses its existing native
Sites/Cloudflare runtime: D1, R2, route handlers, and server-side AI-provider calls.

Processing currently completes inside the upload/retry request. Its stages are real
persisted job states, but it is not a detached queue worker. NEXT_PUBLIC_SOCKET_URL
is optional support for an external authenticated Socket.IO event source; the local
product refreshes server state after mutations and does not simulate socket events.

For very large production workloads, the deliberate next scaling step is a managed
queue/consumer that calls the same processing service and publishes authenticated
workspace events. D1 chunk embeddings are suitable for this portfolio-scale product;
a dedicated vector index is the next step for millions of passages.


18. TROUBLESHOOTING
-------------------

"npm is not recognized"
  Install Node.js, restart VS Code, and open a new terminal.

"No connection could be made to localhost:3000"
  Run npm run dev and keep that terminal open.

"no such table"
  Run npm run db:migrate:local, then restart npm run dev.

"AI document processing is not configured"
  Create a Gemini key, add GEMINI_API_KEY to .env.local, and restart the server.
  TXT search still works without the key.

"Gemini free-tier quota is exhausted"
  Wait for Google's quota window to reset, reduce requests, or choose a paid plan.
  Finora does not silently charge money or switch providers. PDF pages remain
  fully stored and keyword-searchable even when an embedding window is unavailable.

"Only N of M PDF pages contain extractable text"
  The PDF is probably a scan made from page images. Run OCR in Adobe Acrobat,
  Microsoft Lens, Google Drive, or another trusted OCR tool, export a searchable
  PDF, and upload it again. Finora refuses false completion when most page text
  cannot actually be read.

"Gemini mode currently supports PDF and TXT"
  Open the DOC/DOCX file in Word or Google Docs, export/download it as PDF, and
  upload that PDF.

"The document was not found or you do not have access"
  The selected ID is absent or belongs to another workspace. Return to Documents
  and open a file visible in the current workspace.

Upload rejected
  Use PDF, DOC, DOCX, or TXT; keep each file at or below 25 MB and each batch at or
  below 10 files. The whole multipart request must also stay below the 100 MB
  framework envelope, so split a very large batch into multiple uploads.

"Payload Too Large" or HTTP 413
  Restart npm run dev so the current next.config.ts is loaded. If the error remains,
  upload fewer files in one batch. The browser now displays this server reason
  instead of replacing it with a generic upload failure.

Changes do not appear
  Refresh once. In development, also check the terminal for a compilation/runtime
  error and restart npm run dev after changing environment values.


19. SECURITY CHECKLIST
----------------------

- Server secrets never use NEXT_PUBLIC prefixes.
- Protected routes authenticate before database or object reads.
- Document access is always workspace-scoped.
- Write operations enforce roles.
- File extension, count, and size are validated on server and client.
- R2 file responses are private and no-store.
- AI receives files from the server, not an exposed browser key.
- AI output is schema-constrained and citations are bounded to retrieved sources.
- Successful and failed important actions produce persisted activity records.
- No fake success fallback hides missing storage, database, authentication, or AI.


20. ONE-SENTENCE MENTAL MODEL
-----------------------------

The React interface sends authorized requests to the same Cloudflare application;
the backend stores file bytes in R2, structured truth in D1, uses the configured
Gemini or OpenAI provider only when its private key is present, and returns
source-linked persisted results for the interface to render.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages