Inspiration
I read across a lot of genres, decades, and translations, and I kept noticing the same ideas popping up in books that had nothing else in common. A Japanese confessional novel and a Russian murder mystery ended up circling the same kind of alienation. A dystopian farm story and a Jazz Age tragedy were both quietly about class. Every "concept map" tool I found was built for nonfiction, study guides, textbook chapters, business book summaries. Fiction, where the meaning is usually implied instead of stated outright, was kind of left out. I wanted something that could read across my whole library without needing me to highlight a single passage or upload full text.
What it does
Litgraph builds a personal concept map out of your reading. You can add books one at a time through a Google Books search, or bulk import your actual Goodreads export. An LLM pulls out 3 to 5 core themes per book, grounded in real Wikipedia plot or theme sections when it can find them, instead of just guessing off the title. Every book has a sourceConfidence flag so you can tell which extractions are well grounded and which are basically best effort. Themes get normalized across your whole library using embedding similarity, so "Alienation" from one book and "Isolation and Alienation" from another collapse into the same node instead of sitting there as two accidental duplicates. All of it renders as an explorable, force directed graph, with books and concepts as two different kinds of nodes. Click a theme and you see every book that shares it, plus a rationale grounded in what actually happens in that specific book.
How we built it
Next.js 16 with the App Router and TypeScript, deployed on Vercel, Prisma 7 over Neon Postgres, Tailwind for the UI. I wanted the design to feel quiet and editorial, closer to Apple Books or Linear than the glowing node, purple gradient look a lot of hackathon AI projects end up with. The extraction pipeline chains together OpenAI's gpt-4o-mini for structured concept extraction, text-embedding-3-small for normalization, Google Books for metadata, and the Wikipedia REST and Action APIs for grounding text, all in one request per book. The graph itself uses react-force-graph-2d, custom rendered so concept node size reflects how many books actually share that theme. Instead of building full accounts, every visitor gets their own private library tied to an anonymous cookie. Enough to keep people's data separate without building a whole auth system.
Challenges we ran into
Probably the one I'm proudest of solving: Wikipedia section headings lie to you. A heading like "Major Themes" is often just a stub with barely any text of its own, and the real content is sitting in subsections underneath it that aren't listed separately. My first version was grounding books in almost nothing and I didn't realize it until I actually looked at what was getting pulled. Fixing it meant using each section's byte offsets to grab its whole subtree instead of just its own little intro paragraph. Google Books search had a similar issue. intitle:spring snow only scopes the first word to the title field, so the rest quietly turns into a loose full catalog search. I didn't catch that until I tested a two word title and got nothing back. And more than once, Google's own API just returned a 503 with no warning, which I only found by actually adding server side logging instead of assuming my own code was the problem.
Accomplishments that we're proud of
Getting the concept-merging threshold right without just picking a number that sounded reasonable. Rather than guess, I extracted real themes from actual books, computed similarity across every pair of concepts, and looked for where genuine matches naturally separated from genuinely different ideas. I deliberately included test cases on both sides of that line: Camus's The Stranger to make sure "Absurdism" and "Existentialism" stayed distinct despite being closely related.
What we learned
The biggest thing I learned is that "it didn't throw an error" and "it worked" are not the same claim, and I'd been treating them as interchangeable without realizing it. Almost every real bug tonight was a function returning successfully with the wrong answer, without crashing. This changes how I'll approach building in the future, I'll log the boundary conditions before I need them, not after something looks wrong.
What's next for litgraph
A manual way to set a cover image for the rare book Google just doesn't have art for. An LLM tie break as a third layer in normalization, for the genuinely ambiguous cases embeddings alone can't sort out. Actual entity resolution across different editions, so two printings of the same book stop showing up as separate nodes. And if this ever grows past a hackathon project, real accounts. The anonymous per browser scoping worked fine for tonight, but it's the first thing I'd want to replace.
Built With
- d3.js
- google-books-api
- neon
- next.js
- node.js
- openai
- papaparse
- postgresql
- prisma
- react
- react-force-graph
- tailwindcss
- typescript
- vercel
- wikipedia-api
Log in or sign up for Devpost to join the conversation.