尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Chat with your documents. Every answer shows the exact page it came from.
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Chat with your documents. Every answer cites its page.
Upload PDFs, ask questions in English or German, and jump straight to the passage behind every answer.
Live demo: citewise-rouge.vercel.app
CiteWise is a document-research app built around verifiable answers. Instead of returning a block of text you have to trust, it will show the exact page of the exact document each answer came from, so you can check it in one click.
Status: early development. Authentication, the database schema, the dashboard, PDF upload, PDF processing, chat with page citations, saved chat history and a PDF viewer that opens at the cited page with the passage highlighted are done. Workspaces, an evaluation set and deployment are next. See the roadmap for what is built and what is planned.
The screenshots use made-up documents.
| Area | Status | Details |
|---|---|---|
| Sign up / sign in / sign out | Done | Email and password through Supabase Auth |
| Route protection | Done | Logged-out visitors are redirected to /login |
| Database schema | Done | Documents, chunks with page numbers and embeddings, chats, messages, workspaces |
| Row Level Security | Done | Every table is locked to its owner |
| Private file storage | Done | A private documents bucket, PDF only, 10 MB limit, per-user folders |
| Vector search function | Done | match_chunks() finds the closest passages by meaning |
| PDF upload | Done | Drag and drop or file picker, PDF only, up to 10 MB each, up to 5 at once, straight to private storage |
| PDF processing | Done | Text extracted page by page, split into overlapping chunks, embedded with Gemini (768 dimensions) and saved with page numbers. Status moves Uploaded, Processing, Ready or Failed, with a retry button |
| Chat with page citations | Done | Streamed answers from a Gemini chat model, grounded only in your documents (choose which documents to search; with several, answers name each document and keep their facts separate), with [n] citation badges and expandable source passages. Says "I couldn't find that in your documents" instead of guessing. Works across English and German |
| PDF viewer with highlighted citations | Done | "View in PDF" on any cited passage opens the document beside the chat at the exact page, with the passage highlighted. Page controls, open in a new tab, full screen on phones. The PDF is fetched from private storage and drawn in the browser with PDF.js |
| Usage limits | Done | Per-user caps on questions and processed documents, with friendly messages, to protect the shared AI quota |
| Saved chat history | Done | Every chat is saved with its sources and opens at its own address (/chat/<id>), with a short AI-written title. Recent chats are listed in the sidebar by date, with delete; follow-up questions use the saved conversation |
| Semantic search | Verified | match_chunks() returns the right page for questions in English and German |
| Document library | Done | List with status badges, summary counts, search by name and delete |
| Dashboard layout | Done | Sidebar navigation, page header, responsive top bar on phones |
| Light, dark and system themes | Done | Remembered per browser, applied before first paint so there is no flash |
| Login UI | Done | Responsive, password-strength hint |
Each upload is processed automatically: you can watch it go from Uploaded to Processing to Ready, with a page count. Scanned PDFs are read with OCR (up to 15 scanned pages). Failed documents show a clear reason and a retry button. Open Chat to ask questions about all your ready documents, or just one, and see which pages each answer came from. Chats are saved automatically, so you can leave and come back to them from the Recent chats list.
Phase 1: core
Phase 2: what sets it apart
Phase 3: advanced
| Layer | Choice |
|---|---|
| Framework | Next.js 16 (App Router, Cache Components) and React 19 |
| Language | TypeScript |
| Styling | Tailwind CSS 4 with a two-color theme (warm neutral and emerald) |
| Auth, database, storage | Supabase (Postgres, pgvector, Auth, Storage) |
| PDF text | unpdf, a serverless-friendly pdf.js build |
| AI orchestration | LangChain.js: text splitter, Gemini embeddings and chat model, prompt templates and a custom vector store. LangGraph.js: the question-answering workflow |
| AI models | Google Gemini: gemini-embedding-001 for embeddings (768 dimensions) and gemini-3.5-flash-lite for answers (set GEMINI_CHAT_MODEL to change) |
| PDF viewer | PDF.js (pdfjs-dist), loaded only when a PDF is opened |
| Markdown | react-markdown renders answers safely |
There is no separate backend. The server logic lives in Next.js route handlers and server actions, so the whole app deploys as one project (for example on Vercel).
CiteWise is a RAG (retrieval-augmented generation) app. Two pipelines share one database.
Neither is an AI model. They are toolkits around one (Google Gemini).
retrieve, then
generate, then generateTitle only for a new chat. It runs the steps in order, takes the branch and
streams the answer as it is written. New steps (for example "decide whether to search at all") can be added
as nodes.flowchart LR
A[PDF upload] --> B[(Private storage)]
B --> C["unpdf<br/>text per page"]
C --> D["LangChain<br/>RecursiveCharacterTextSplitter<br/>1000 chars, 200 overlap,<br/>page number in metadata"]
D --> E["LangChain<br/>GoogleGenerativeAIEmbeddings<br/>gemini-embedding-001, 768 dims"]
E --> F[("SupabaseChunkStore<br/>Postgres + pgvector")]
SupabaseChunkStore (source) is a custom LangChain
VectorStore over our own chunks table and match_chunks() SQL function. It runs with the
signed-in user's Supabase client, so row level security applies to every read and write.flowchart LR
S((start)) --> R["retrieve<br/>embed the question, search each document,<br/>keep passages above a similarity threshold"]
R --> G["generate<br/>ChatPromptTemplate + ChatGoogleGenerativeAI,<br/>tokens streamed"]
G -- "new chat" --> T["generateTitle"]
G -- "existing chat" --> E((end))
T --> E
StateGraph (source) with typed
state (question, history, selected documents, sources, answer, title).retrieve searches each selected document separately when there are two to five, so one long
document cannot crowd out the others. generate answers only from the numbered excerpts, cites them
as [1], [2], names the document when several are involved, and replies "I couldn't find that in
your documents" instead of guessing.streamMode: ["updates", "messages"]. The API route turns those events into
newline-delimited JSON (sources, tokens, done, title) for the browser, and saves the question, answer
and cited sources to Postgres.auth.users
|
+-- workspaces (name)
+-- documents (title, storage_path, page_count, language, status, summary,
| suggested_questions, workspace_id)
| +-- chunks (page_number, chunk_index, content, embedding vector(768),
| full-text search column)
+-- chats (title, workspace_id)
+-- messages (role, content, sources jsonb)
match_chunks(query_embedding, match_count, filter_document_ids, filter_workspace_id)
runs with the caller's permissions, so search results respect RLS automatically.<user_id>/... in a private bucket, with matching policies.The full schema is in supabase/migrations/0001_init.sql.
git clone https://github.com/Zulqarnainhayder/citewise.git
cd citewise
npm install
supabase/migrations/0001_init.sql, and run it.Copy .env.example to .env.local and fill in three values:
GOOGLE_API_KEY= # Google AI Studio key (server only)
NEXT_PUBLIC_SUPABASE_URL= # Project URL
NEXT_PUBLIC_SUPABASE_ANON_KEY= # Publishable (anon) key, safe for the browser
GEMINI_CHAT_MODEL is optional. The app never uses the Supabase secret (service role) key: every
database and storage call runs as the signed-in user, so row level security protects the data. Keep
that key out of the project entirely. .env* files are git-ignored (except .env.example), so never
commit real keys.
npm run dev
Open http://localhost:3000. You will be sent to the login page.
Automated tests. npm test runs 100+ unit tests with Vitest. They cover the logic that
needs no network: the highlight matcher, the citation parser, the page-aware splitter, request validation,
the usage limits, the prompt, file checks and the evaluation scorer itself. GitHub Actions
(.github/workflows/ci.yml) runs lint, the type check, the tests and a production build on every
push and pull request.
Browser tests. npm run e2e runs Playwright tests in e2e/: sign-in, the document
library, workspaces, settings, the theme, and (with real Gemini calls) uploading a PDF, asking a question,
opening the cited page, refusing an unanswerable question, and reading a scanned PDF with OCR. Each test creates
a temporary user and deletes it afterwards. CI runs them in a second job when these repository secrets exist:
NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, GOOGLE_API_KEY;
without them the job skips itself. The free Gemini tier allows about 500 chat requests per model per day, which
is why the AI tests are few.
Accuracy evaluation. eval/ is a small benchmark that asks the real app 49 questions about four
fictional documents (two similar English contracts with different numbers, a German handbook and a resume)
and scores every answer automatically. The questions include reworded ones, ones that need a small
calculation or comparison, ones that mix several documents, ones in the other language, a "trap" about a fact
that exists only in a different document, an attempt to make the model ignore its rules, and questions the
documents cannot answer.
| Measure | Result |
|---|---|
| Correct answer and correct cited page | 49/49 (100%) |
| Right page found by search | 40/40 |
| Answer has the correct fact | 40/40 |
| Cites the right page | 40/40 |
| Unanswerable questions correctly refused | 8/8 |
| Time to first word (median / slowest 5%) | 4012 ms / 12352 ms |
One run with hybrid search on gemini-3.1-flash-lite, because the usual gemini-3.5-flash-lite daily free quota was used up. The same questions with meaning-only search scored 48/49 (one server error, not a retrieval miss), so this run does not show hybrid is more accurate. Full per-question results are in eval/RESULTS.md. Read the score with its limits in mind:
the documents are short and fictional, it is a single run, and the scoring is automatic string matching (the scorer
has its own tests that feed it wrong answers and check that it rejects them). It shows the pipeline works as
designed, not how it performs on long, messy real documents. I also checked the app with scripted real-browser
runs (sign-in, upload, chat, PDF viewer, phone layout); those are not in CI yet.
CiteWise deploys as a single Next.js project. These are the steps that were used for the live demo:
vercel link and vercel deploy --prod).
Vercel detects Next.js; no build settings are needed.GOOGLE_API_KEY (mark it Sensitive),
NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY. Do not add the Supabase secret key: the
app never uses it.supabase/migrations/0001_init.sql in the
Supabase SQL Editor (once).localhost.vercel.json runs the server functions in fra1
(Frankfurt) because the Supabase project is in Europe. Functions default to the US East coast, which made
every database call cross the Atlantic: moving them cut the first chat token from about 3 seconds to
about 1.2 seconds. Change the region to match your own Supabase project.Pushes to main deploy automatically once the repository is connected.
The frontend is component-based: pages stay thin, features own their components and hooks, and shared pieces live in one place.
src/
app/ Thin pages that only compose components
page.tsx Documents dashboard
login/page.tsx Sign in / sign up
chat/page.tsx New chat
chat/[chatId]/page.tsx A saved chat
api/documents/[id]/process/ POST route that processes an uploaded PDF
api/chat/ POST route: retrieve passages, stream a cited answer
layout.tsx, globals.css Root layout, theme script, Tailwind theme tokens
components/
ui/ Button, Card, Alert, Spinner, SearchInput
layout/ AppShell, Sidebar, NavItem, AccountCard,
MobileHeader, PageHeader, PageContainer
theme/ ThemeToggle, ThemeButton, useTheme
icons.tsx, Logo.tsx
features/
auth/ Server actions + AuthForm, TextField, BrandPanel, ...
documents/ Hooks (useDocuments, useUploader) + Dropzone,
DocumentList, DocumentItem, DocumentStats, ...
chat/ useChat hook, ChatPanel, MessageList, AnswerText,
SourceChips, Composer + server/ (LangGraph workflow in
graph/, prompt, persist, validate)
viewer/ PdfViewerPanel, PdfPage (canvas + highlight overlay),
ViewerProvider, usePdfDocument, findHighlight
processing/ Server-only ingestion: extractPages, splitPages
(LangChain splitter), processDocument
lib/
langchain/ Model factories (Gemini chat + embeddings) and
SupabaseChunkStore, a custom LangChain VectorStore
limits/ Per-user usage limits
supabase/ Browser client, server client, useSupabase hook
theme.ts Theme storage and the no-flash init script
proxy.ts Session refresh and route protection
supabase/
migrations/
0001_init.sql Schema, RLS policies, search function, storage bucket
eval/ Accuracy benchmark: documents, questions, scorer, results
docs/screenshots/ Images used in this README
.github/workflows/ci.yml Lint, type check, tests and build on every push
Unit tests sit next to the code they test (*.test.ts).
The two theme colors are defined once in src/app/globals.css:
brand (slate-navy) for structure and text, and accent (emerald) for actions and
highlights. Changing those values re-themes the whole app.
Dark mode is class-based (a dark class on <html>), so the user can choose Light,
Dark or System. The choice is stored in localStorage and applied by a small inline
script before first paint.
CHAT_LIMIT_PER_MINUTE, CHAT_LIMIT_PER_DAY and
DOCUMENT_LIMIT_PER_DAY. A blocked request gets a clear message and a 429 status.eval/)supabase/migrations/0002_hybrid_search.sql)Inspired by the "chat with your PDF" pattern from the O'Reilly book Learning LangChain. CiteWise is an independent project and shares no code with it.
No license has been chosen yet.