TreeMap
무료 오픈소스 디스크 사용량 시각화 도구로, 사각형 트리맵·중복 파일 탐색·디스크 가득 예측·정리 시 파일을 휴지통으로만 이동한다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Upload PDF documents, then ask questions about them in a chat interface. Every
answer is grounded in the uploaded PDFs and cites the source document and
page number. Nothing is invented: if the answer isn't in your documents, the
app says so.
This is a working MVP, not a mockup - both the server and the client are
fully functional and covered by automated tests. It needs a free Postgres
database to run (see Install), but no paid services: a
demo mode covers the AI parts when no API key is configured, and the
automated test suite needs no external services at all.README 전문 보기 ↗Upload PDF documents, then ask questions about them in a chat interface. Every answer is grounded in the uploaded PDFs and cites the source document and page number. Nothing is invented: if the answer isn't in your documents, the app says so.
This is a working MVP, not a mockup - both the server and the client are fully functional and covered by automated tests. It needs a free Postgres database to run (see Install), but no paid services: a demo mode covers the AI parts when no API key is configured, and the automated test suite needs no external services at all.
Source: employee-handbook.pdf — Page 12. Clicking a citation opens that
exact page of the PDF alongside its extracted text.ai-document-app/
├── api/index.ts Vercel serverless function entry point (wraps server/src/app)
├── server/ Express + TypeScript API, Postgres database, PDF processing
└── client/ React + TypeScript + Vite dashboard and chat UI
Why this stack: the brief asked for the simplest reliable architecture,
not a distributed system - a single Express API is that, whether it runs as
a traditional long-running process or as a Vercel serverless function (both
are supported; see Deployment). The database is Postgres via
the plain pg driver: no ORM, one query dialect used everywhere (dev,
tests, and production), and it's the one piece of storage a serverless
deployment genuinely requires - see Deployment for why.
Uploaded PDF bytes go through a small storage adapter
(services/storage.ts) instead of being written to local disk directly:
locally that's still just disk, but on Vercel - where a serverless
function's filesystem doesn't persist between requests - it's Vercel Blob
instead. Everything else in the app (routes, retrieval, chat) is unaware of
which one is active.
Key server pieces (server/src/):
db/schema.sql - users, documents, pages, conversations,
messages tables (Postgres).services/pdfProcessor.ts - extracts text per PDF page (see below).services/storage.ts - saves/reads/deletes PDF bytes (local disk or
Vercel Blob).services/documentService.ts - upload/process/list/delete orchestration.services/retriever.ts - keyword search over stored pages.services/aiClient.ts - Claude question-answering + summarization, with a
built-in demo/mock fallback.routes/documents.ts, routes/chat.ts - the HTTP API.middleware/session.ts - anonymous per-browser user isolation.middleware/upload.ts - file validation (type, size); buffers uploads in
memory for the storage adapter to persist.Client pieces (client/src/):
App.tsx - responsive two-pane layout (documents sidebar + chat), with a
tab switcher on narrower/tablet screens.components/DocumentsPanel.tsx, DocumentRow.tsx, UploadButton.tsx -
the dashboard.components/ChatPanel.tsx, MessageBubble.tsx - "Ask your documents".components/PdfViewerModal.tsx - opens a cited page (PDF + extracted
text) when a citation is clicked.components/SummaryModal.tsx - per-document AI summary.There is no separate "vector database" service. See How AI retrieval works for why, and how the schema is already shaped to add one later without a rewrite.
Requires Node.js 20+ (developed and tested on Node 22) and a Postgres database - a free one from Neon or Vercel Postgres takes about two minutes to create and works fine for local dev too.
npm install # installs both workspaces (client + server)
cd server
cp .env.example .env # set DATABASE_URL at minimum; ANTHROPIC_API_KEY is optional
# Terminal 1
cd server
npm run dev # http://localhost:4000
# Terminal 2
cd client
npm run dev # http://localhost:5173
Open http://localhost:5173. The Vite dev server proxies /api/* requests to
the backend, so no CORS configuration is needed in development.
On first load, the dashboard is empty. Click Load sample documents to try the full flow immediately (see Demo mode), or upload your own PDFs.
All server configuration lives in server/.env (see server/.env.example):
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | Yes | Postgres connection string. Not needed when running tests - see Tests. |
PORT | No (default 4000) | API server port (local/traditional server only; Vercel ignores this). |
CLIENT_ORIGIN | No | Allowed CORS origin for cookies. Leave unset in production on Vercel (same-origin, so CORS is moot); defaults to http://localhost:5173 in spirit but really just reflects the request origin when unset. |
ANTHROPIC_API_KEY | No | Claude API key. If unset, the app runs in demo mode (see below) instead of failing. |
AI_MODEL | No (default claude-sonnet-5) | Claude model used for chat answers and summaries. |
BLOB_READ_WRITE_TOKEN | No | Vercel Blob token for storing PDFs. Required in a Vercel deployment; leave unset locally to use local disk instead. |
UPLOAD_DIR | No (default ./uploads) | Where uploaded PDFs are stored on disk when BLOB_READ_WRITE_TOKEN isn't set. |
MAX_FILE_SIZE_MB | No (default 20) | Per-file upload size limit. |
The AI API key is only ever read on the server (server/src/services/aiClient.ts)
and is never sent to or exposed in the frontend bundle.
The client has no required environment variables - it talks to the API at a
relative /api path, proxied in dev and served from the same origin (via
Vercel rewrites) in production.
POST /api/documents/upload (multer, buffered in
memory). The file is validated three ways: MIME type, .pdf extension,
and - the one that actually matters for security - its %PDF- magic
bytes, so a renamed non-PDF file is rejected even if the browser lied
about its MIME type.services/storage.ts (local disk, or Vercel
Blob when deployed there), and a documents row is created with
status = "processing". The upload request returns right here -
with the document still "processing" - without starting extraction.POST /api/documents/:id/process, which is what actually runs
extraction (see services/pdfProcessor.ts). This is a second HTTP
request rather than the server just kicking extraction off "in the
background" of the upload response, because on Vercel that background
work isn't reliably background: a serverless invocation isn't guaranteed
to keep running unawaited work after its response is sent, so an
unawaited processDocument() call inside /upload could end up
blocking - and occasionally timing out - the very response it was
supposed to let return immediately. A separate request gets its own
invocation with its own full time budget, genuinely decoupled from the
upload response. /process is idempotent (calling it again on an
already-processed document is a harmless no-op), so this is also safe
under retries.pdfjs-dist, one page at a time, but
where that parsing runs depends on the deployment target:
scripts/pdfExtractWorker.js). Running it out-of-process means a
malformed or hostile PDF can crash or hang that worker without taking
down the API server - defense in depth for parsing untrusted uploads.pdfjs-dist is ESM-only, and loading it in-process needs a genuine
dynamic import() at runtime - Vercel's Node runtime rejects a plain
require() of it outright - kept behind a small
new Function("s", "return import(s)") indirection specifically so
TypeScript's own CommonJS compilation (which is what Vercel actually
deploys here, not raw source) can't rewrite that back into
the same call it's working around. Both paths share the
same extraction logic () so there's exactly
one implementation of "given a PDF, return per-page text", not two to
keep in sync.This app uses retrieval-augmented generation: before asking Claude anything, the server finds the most relevant pages across your documents and puts only that text in the prompt, instructed to answer strictly from it.
Retrieval today (services/retriever.ts) is deliberately simple: it
tokenizes the question, scores each stored page by term-frequency overlap
(with a small bonus for an exact phrase match), and returns the top-scoring
pages. No embeddings, no vector database, no extra infrastructure - and for
the page counts this app deals with, it works well.
The schema and code are shaped so this can become real embedding-based
search later without changing anything upstream of retriever.ts: the
pages table already carries a spare embedding column, each page has a
stable (document_id, page_number) identity, and every caller only ever
consumes retrieveRelevantChunks()'s return shape - swapping its
implementation for a pgvector/embedding lookup is a one-file change.
The retrieved pages are then sent to Claude (services/aiClient.ts) with a
system prompt that:
(Source: <document> — Page <n>).Citations returned by the API always carry a real documentId and
pageNumber from the retrieved pages (never invented by the model), which
is what the client uses to open the exact cited page.
If ANTHROPIC_API_KEY is not set, the server automatically runs in demo
mode instead of failing:
[Demo mode - no AI API key configured]) instead of a
Claude-generated response.This means the entire app is testable end-to-end with zero external services or cost. Demo data is also kept clearly separate from anything you upload yourself:
is_demo in the database and shown with a Demo badge, but
behave like any other document (deletable, summarizable, searchable).npm run seed:demo (in server/) does the same thing from the command
line for a fixed CLI user, without starting the server.cd server
npm test
Tests need no external services and no DATABASE_URL - they run against
pg-mem, an in-memory Postgres-compatible
engine, so npm test is fully self-contained (see
src/__tests__/setupEnv.ts and src/db/index.ts).
24 automated tests (Jest + Supertest) cover:
/upload always
responds with the document still "processing", never starting
extraction itself (see How PDF processing works)/:id/process endpoint: extraction completing it, calling
it again being a harmless no-op, and it being scoped to the calling
session like every other document route.pdf-named file
whose content isn't actually a PDF (magic-byte check)The client was verified manually (and with a throwaway Playwright script) against a running server: upload, demo data loading, asking questions, clicking a citation to open the right PDF page, generating a summary, and the responsive tablet/mobile tab layout all work as described.
%PDF- magic bytes), and capped at MAX_FILE_SIZE_MB.access: "private" - reading one
back requires the store's token, not just its URL - and even so, the raw
Blob URL is never sent to the client; the only way to read a document's
file or text is through routes that check the requesting session's
userId against the document's owner, then fetch the bytes server-side.This repo deploys to Vercel as a single project: the client builds to a
static site, and the whole Express API is wrapped as one serverless
function (api/index.ts) that every /api/* request is routed to (see
vercel.json).
Vercel's serverless functions have no persistent local filesystem - different requests can hit different, ephemeral instances - so the app's storage layer is already built to not depend on one there: Postgres for the database (used everywhere, not just on Vercel) and Vercel Blob for PDF bytes (see Architecture). Steps:
vercel.json and the
root package.json workspaces - no framework preset needed.DATABASE_URL at any Postgres
you already have, e.g. a standalone Neon project). This sets
DATABASE_URL (or a similarly-named variable - copy its value into
DATABASE_URL if the integration uses a different name) as a project
environment variable automatically.BLOB_READ_WRITE_TOKEN automatically.ANTHROPIC_API_KEY under Project Settings → Environment
Variables to get real AI answers instead of demo mode (optional - the
app works in demo mode without it).server/src/db/schema.sql) is applied
automatically on first request (every statement is
CREATE ... IF NOT EXISTS) - no separate migration step to run.To validate the build locally before pushing (what was actually used to
verify this setup): npx vercel build after npx vercel link, which
produces .vercel/output with the static site and the bundled function
exactly as Vercel's build step would - useful for catching bundling issues
(missing dependencies, wrong paths) without waiting on a real deployment.
The server also runs as a plain long-running Node process, if you'd rather
not use serverless functions - in that case local disk works fine for PDF
storage (skip BLOB_READ_WRITE_TOKEN) since the filesystem persists there:
# Server
cd server
npm run build # compiles to dist/, also copies db/schema.sql
npm start # runs dist/index.js
# Client
cd client
npm run build # outputs static files to dist/
Any host that can (a) run a small Node process for the server and (b) serve
static files for the client works - for example, put the client's dist/
behind a static host or CDN and reverse-proxy /api/* to the server
process, or have the Express server itself serve client/dist as static
files (add express.static(...) and a catch-all route in
server/src/app.ts) to ship one process.
For this deployment mode specifically, also point UPLOAD_DIR at a
persistent volume (not an ephemeral container filesystem), and run behind
HTTPS so the session cookie's secure flag (set automatically when
VERCEL is set; set it similarly for your platform in
middleware/session.ts otherwise) actually applies.
import()require()scripts/pdfExtractCore.jspages table
(document_id, page_number, text), which is what lets citations and
the PDF viewer point at an exact page instead of "somewhere in the file".status = "ready" (or "error" with a message,
if extraction failed) and the dashboard picks up the change via polling.