Stop repeating yourself β let AI find it for you
When writing a PhD thesis over months or years, the same ideas inevitably appear in multiple chapters, sometimes word-for-word, sometimes paraphrased. RepeatRadar finds these repetitions semantically (not just exact matches), scores them by severity, and lets you fix them with one click using Gemini AI.
- π Upload PDF or DOCX β supports any academic document format
- π§ Semantic detection β finds repetitions even when phrased differently
- π― Smart scoring β π΄ High / π‘ Medium / π’ Low based on similarity, length, and signpost keywords
- π Three fix strategies β rewrite, shorten to recall sentence, or replace with cross-reference
- β Accept / Reject / Edit β you stay in control, AI just suggests
- π₯ Export fixes β download a
.txtchangelog to apply to your Word/LaTeX document - β‘ Caching β embeddings saved to disk, analysis is instant on repeat runs
- π° 100% free β uses Gemini free tier, no paid API needed
PDF/DOCX Upload
β
LangChain RecursiveCharacterTextSplitter β tagged chunks
β
Gemini gemini-embedding-001 β FAISS FlatIP index (cached)
β
FAISS search (top-K) β cosine similarity pairs
β
Filter: skip preamble / references / math / adjacent chunks
β
Score: similarity + length + signpost keywords β π΄/π‘/π’
β
Streamlit UI β side-by-side pair view
β
Fix on demand β Gemini Flash rewrite β Accept / Reject
| Upload | Analyse | Review & Fix |
|---|---|---|
| Drag & drop PDF/DOCX | Real-time embedding progress bar | Side-by-side pair comparison with fix panel |
- Python 3.11+
- Free Gemini API key β Get one here
git clone https://github.com/bazzal99/RepeatRadar.git
cd RepeatRadarcp .env.example .env
# Open .env and add your Gemini API keypip install -r requirements.txt
pip install streamlit==1.40.0 requests==2.32.3cd backend
uvicorn app.main:app --reload --port 8080cd frontend
streamlit run app.pyhttp://localhost:8501
RepeatRadar's duplicate-detection pipeline (ingest β embed β detect β score) is also
exposed as a Model Context Protocol (MCP) tool (mcp_server/server.py), so any
MCP-compatible client can call find_duplicates(file_path, doc_id=None) directly on a
PDF or DOCX β no need to run the Streamlit UI. No changes were made to
backend/app/services/*; the MCP server only imports and calls the functions that
already exist there.
Tool exposed: find_duplicates(file_path, doc_id=None) β returns a JSON report:
total pairs found, counts per severity (high/medium/low), and the pairs
themselves (chunk text truncated for readability).
cd mcp_server
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
export GOOGLE_API_KEY="your-gemini-api-key" # Windows: $env:GOOGLE_API_KEY="..."Why a separate
requirements.txt? ThemcpSDK requirespydantic>=2.12.0, while the FastAPI backend above is pinned topydantic==2.8.2. Merging them into one file would force an untested pydantic upgrade on the core app, somcp_server/keeps its own isolated environment on purpose.
python server.pyA silent hang with no error means it started correctly β it's waiting for a client
over stdio. Ctrl+C to stop.
Add to your claude_desktop_config.json (Settings β Developer β Local MCP servers β
Edit Config in Claude Desktop):
{
"mcpServers": {
"repeatradar": {
"command": "/absolute/path/to/RepeatRadar/mcp_server/venv/bin/python",
"args": ["/absolute/path/to/RepeatRadar/mcp_server/server.py"],
"env": { "GOOGLE_API_KEY": "your-gemini-api-key" }
}
}
}On Windows, use venv\Scripts\python.exe and escaped backslashes (\\) in the paths.
Restart Claude Desktop, then ask something like:
"Use repeatradar to find duplicates in C:\path\to\my_thesis.pdf"
Claude will call find_duplicates directly and report back the flagged pairs.
First run on a new file embeds and caches it (mcp_server/faiss_store/<doc_id>.index);
subsequent calls on the same file reuse the cache.
- Path errors on the local server: the server sets its own working directory
(
os.chdirto its own folder) at startup specifically because MCP clients may launch it from an arbitrarycwdβ if you fork this, keep that line. - Tool not visible in a chat: some Claude Desktop builds require enabling the connector per-conversation β check the "+" / Context panel near the message box.
- "File not found": the path must be a real path on the machine running the server, not an uploaded/attached file in the chat β the server can't reach files that only exist inside the conversation.
cp .env.example .env
# Add your API key to .env
cd infra
docker compose up -d- Frontend: http://localhost:8501
- API docs: http://localhost:8080/docs
Edit RepeatRadar/.env:
# Embedding key (gemini-embedding-001)
GOOGLE_API_KEY=your_key_here
# Optional: separate key for fix agent to avoid quota conflicts
GOOGLE_API_KEY_GENERATION=your_second_key_here
# Detection sensitivity (0.92 = recommended, lower = more pairs found)
SIMILARITY_THRESHOLD=0.92
# Chunk size for splitting (characters)
CHUNK_SIZE=400
CHUNK_OVERLAP=80Tip: Use two separate Gemini API keys β one for embeddings and one for the fix agent. This prevents quota conflicts since embedding and generation share the same free tier limits.
RepeatRadar/
βββ backend/
β βββ app/
β β βββ api/routes/
β β β βββ documents.py # Upload & chunk endpoints
β β β βββ analysis.py # Embed, analyse, fix, accept endpoints
β β βββ core/
β β β βββ config.py # Settings from .env
β β βββ services/
β β β βββ ingestor.py # PDF/DOCX β LangChain chunks
β β β βββ embedder.py # Gemini embeddings + FAISS cache
β β β βββ detector.py # FAISS search + pair filtering
β β β βββ scorer.py # π΄/π‘/π’ scoring logic
β β β βββ fixer.py # Gemini Flash rewriting agent
β β βββ main.py
β βββ Dockerfile
βββ frontend/
β βββ app.py # Full Streamlit UI
β βββ Dockerfile
βββ mcp_server/
β βββ server.py # MCP tool wrapping the detection pipeline
β βββ manifest.json # Desktop Extension (.mcpb) manifest
β βββ requirements.txt # Separate env: mcp SDK needs pydantic>=2.12.0
βββ infra/
β βββ docker-compose.yml
βββ .env.example
βββ requirements.txt
βββ LICENSE
βββ README.md
Each flagged pair is scored on three signals:
| Signal | Criteria | Points |
|---|---|---|
| Similarity | β₯ 0.95 (near-identical) | +2 |
| β₯ 0.90 (very similar) | +1 | |
| β₯ 0.92 threshold | 0 | |
| Length | Both chunks β₯ 200 chars | +2 |
| One chunk β₯ 200 chars | +1 | |
| Signpost | "as introduced in", "recall that", etc. | β2 |
Final score β severity:
- π΄ High (4-5 pts) β fix strongly recommended
- π‘ Medium (2-3 pts) β review recommended
- π’ Low (0-1 pts) β likely intentional, shown collapsed
| Strategy | What it does | Best for |
|---|---|---|
| Rewrite | Different phrasing, same meaning | Same-chapter repetition |
| Shorten | Compress to one recall sentence | Intro repeated in Conclusion |
| Reference | Replace with pointer to original | When repetition adds no value |
| Layer | Technology |
|---|---|
| LLM + Embeddings | Google Gemini (free tier) |
| Vector Index | FAISS FlatIP |
| Chunking | LangChain RecursiveCharacterTextSplitter |
| API | FastAPI + Uvicorn |
| Frontend | Streamlit |
| PDF parsing | pypdf |
| DOCX parsing | python-docx |
| Containers | Docker Compose |
Mohammad Bazzal β ML Engineer | PhD Candidate in Telecommunications
IMT Atlantique, Lab-STICC, Brest, France
This project is licensed under the MIT License β see the LICENSE file for details.