Generate a title and summary for any text or document, then annotate the result with sentiment, part-of-speech tagging and named-entity recognition.
Django REST Framework backend, React + Vite frontend. Summarization runs through the Groq API; the linguistic analysis runs locally with spaCy and NLTK.
Licence: GNU AGPL-3.0-or-later — if you run a modified version as a network service, you must offer its source to users.
- Paste text, or upload a
.txt,.docxor.pdfdocument. - AI-generated title and summary (Groq).
- Sentiment classification — positive / negative / neutral (NLTK VADER).
- Part-of-speech tagging with colour-coded output (NLTK).
- Named-entity recognition rendered with spaCy displaCy.
- A "Connect Us" contact form protected by reCAPTCHA v2.
Requires Python 3.12+ and Node 22+. No database server is needed — the default configuration uses SQLite.
cd backend
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
python manage.py download_nlp_data # fetches the NLTK corpora
python manage.py migrate
cp .env.example .env # set GROQ_API_KEY
DJANGO_DEBUG=true python manage.py runserverThe API is then on http://localhost:8000/textsummery/.
With DJANGO_DEBUG=true the backend starts with no configuration at all;
everything except GROQ_API_KEY has a working development default. Without a
Groq key the app runs but process_text returns 503.
cd frontend
npm install
cp .env.example .env # optional; defaults point at localhost:8000
npm run devOpen http://localhost:5173.
cp .env.example .env # set DJANGO_SECRET_KEY and GROQ_API_KEY
docker compose up --buildFrontend on http://localhost:8080, backend on http://localhost:8000.
Both images are multi-stage, run as a non-root user, and serve through
gunicorn and nginx respectively — no development servers.
All backend variables are read in backend/textsummerizer/settings.py.
See backend/.env.example for the annotated file.
| Variable | Default | Description |
|---|---|---|
DJANGO_SECRET_KEY |
— | Required when DJANGO_DEBUG=false; the app refuses to start without it. A development-only fallback is used when debug is on. |
DJANGO_DEBUG |
false |
Never true in production. |
DJANGO_ALLOWED_HOSTS |
localhost,127.0.0.1,[::1] when debug |
Comma-separated. Required when debug is off. |
CORS_ALLOWED_ORIGINS |
http://localhost:5173 |
Comma-separated browser origins. |
CSRF_TRUSTED_ORIGINS |
same as CORS_ALLOWED_ORIGINS |
Comma-separated. |
GROQ_API_KEY |
— | Required when debug is off. Get one at console.groq.com/keys. |
GROQ_MODEL |
openai/gpt-oss-20b |
Model used for title + summary. |
RECAPTCHA_SECRET_KEY |
empty | When empty the contact form rejects all submissions. |
RECAPTCHA_VERIFY_URL |
Google's siteverify URL | Override only for testing. |
DB_ENGINE |
django.db.backends.sqlite3 |
Set to the postgresql/mysql backend to use a server. |
DB_NAME |
backend/db.sqlite3 |
File path for SQLite; database name otherwise. |
DB_USER, DB_PASSWORD, DB_HOST, DB_PORT |
empty | Required when DB_ENGINE is not SQLite. |
IP_HASH_SALT |
DJANGO_SECRET_KEY |
Salt for hashing client IPs before storage. |
ENABLE_REQUEST_LOGGING |
true |
Set false to stop persisting a row per request. |
MAX_UPLOAD_SIZE |
5242880 (5 MiB) |
Rejected above this size. |
API_THROTTLE_RATE |
30/minute |
DRF anonymous throttle rate. |
LOG_LEVEL |
INFO |
Root logger level. |
SECURE_SSL_REDIRECT |
true |
Only applied when debug is off. |
SECURE_HSTS_SECONDS |
31536000 |
Only applied when debug is off. |
Vite inlines VITE_* variables into the bundle at build time, so they are
public by design — never put a secret in them.
| Variable | Default | Description |
|---|---|---|
VITE_API_URL |
http://localhost:8000/textsummery |
Backend base URL, including the /textsummery prefix. |
VITE_RECAPTCHA_SITE_KEY |
empty | Public reCAPTCHA v2 site key. When empty the widget is not rendered and the form shows a notice. |
All endpoints are mounted under the /textsummery/ prefix
(backend/textsummerizer/urls.py).
| Method | Endpoint | Description |
|---|---|---|
GET |
/textsummery/health/ |
Liveness probe. Returns {"status": "ok"}. |
POST |
/textsummery/process_text/ |
Summarize and analyse text or an uploaded file. |
POST |
/textsummery/submit_form/ |
Submit the "Connect Us" contact form. |
Django's admin is also mounted, at /admin/.
Send either text (JSON) or file (multipart). Supplying neither is
a 400.
curl -X POST http://localhost:8000/textsummery/process_text/ \
-H 'Content-Type: application/json' \
-d '{"text": "Your long text here."}'curl -X POST http://localhost:8000/textsummery/process_text/ \
-F 'file=@document.pdf'200 response
{
"title": "Generated title",
"summary": "Generated summary.",
"sentiment": "positive",
"pos_result": "<p>…colour-coded HTML…</p>",
"ner_result": "<div>…displaCy HTML…</div>"
}pos_result and ner_result are HTML fragments intended for direct
rendering. All user-derived tokens are HTML-escaped server-side before the
markup is built.
| Status | When |
|---|---|
400 |
No text or file; unsupported extension; file over MAX_UPLOAD_SIZE; unreadable/corrupt document. |
429 |
Anonymous throttle rate exceeded. |
500 |
Groq returned a malformed response, or the spaCy model / NLTK data is missing on the server. |
503 |
Groq unreachable, rate limiting, or rejecting our credentials. |
curl -X POST http://localhost:8000/textsummery/submit_form/ \
-H 'Content-Type: application/json' \
-d '{
"first_name": "Ada", "last_name": "Lovelace",
"company": "Analytical Engines", "email": "ada@example.com",
"project": ["Text Summarizer"], "message": "Hello.",
"recaptcha_token": "<token from the widget>"
}'Returns 201 with {"message": "Form submitted successfully"}, or 400 with
per-field errors. project must contain at least one entry. The captcha token
is verified against Google before anything is stored, and is never echoed back
in a response.
Deployers should read this before putting the app in front of real users.
| What | Where | Notes |
|---|---|---|
| Submitted text + sentiment + timestamp | UserSummerizerLog table |
One row per summarization request. Disable with ENABLE_REQUEST_LOGGING=false. |
| Salted SHA-256 hash of the client IP | UserSummerizerLog.ip_hash |
The raw address is not stored. Salt comes from IP_HASH_SALT. |
| Contact form fields (name, company, email, message) | TextSummerizerFormSubmission table |
Submitted deliberately by the user. |
Submitted text is also transmitted to Groq for summarization; their handling is governed by Groq's own terms.
There is no automatic retention limit — deleting old rows is the deployer's responsibility. Earlier versions of this project additionally wrote every submission to CSV files on disk; that duplicate store has been removed.
text-summarizer/
├── backend/
│ ├── textApp/ # Django app
│ │ ├── management/commands/
│ │ │ └── download_nlp_data.py
│ │ ├── migrations/
│ │ ├── utils/
│ │ │ ├── entity.py # NER + POS HTML rendering
│ │ │ ├── file_parser.py # .txt / .docx / .pdf extraction
│ │ │ ├── privacy.py # IP hashing
│ │ │ ├── sentiment_check.py # VADER sentiment
│ │ │ └── summarizer.py # Groq client
│ │ ├── models.py
│ │ ├── serializers.py # validation
│ │ ├── services.py # business logic
│ │ ├── tests.py
│ │ └── views.py # transport only
│ ├── textsummerizer/ # project config
│ ├── .env.example
│ ├── Dockerfile
│ ├── requirements.txt
│ └── requirements-dev.txt
├── frontend/
│ ├── src/
│ │ ├── components/
│ │ │ ├── connect/ # contact modal
│ │ │ └── summarize/ # input, summary, analysis panels
│ │ ├── constants/
│ │ ├── hooks/
│ │ ├── pages/
│ │ ├── services/
│ │ └── test/
│ ├── .env.example
│ ├── Dockerfile
│ └── nginx.conf
├── .github/workflows/ci.yml
├── docker-compose.yml
├── CONTRIBUTING.md
├── SECURITY.md
└── LICENSE
The package textsummerizer and the app textApp are misspelled and
non-PEP8. They are kept deliberately: both names are written into the
migration history and INSTALLED_APPS, so renaming them would orphan the
django_migrations rows of every existing deployment. The lint warning is
suppressed in backend/.pylintrc with that reasoning.
See CONTRIBUTING.md for the exact commands CI runs.
# Backend
cd backend
python -m pylint textApp textsummerizer manage.py
python manage.py test
# Frontend
cd frontend
npm run lint
npm test
npm run build- Not multi-tenant and has no authentication. Every endpoint is public.
The only abuse control is a DRF anonymous throttle (
30/minuteby default), keyed on IP. - Summarization requires a Groq account and sends your text to a third party. There is no local/offline summarization fallback.
- English only. The spaCy model (
en_core_web_sm), the POS tagger and the VADER lexicon are all English-specific. - Sentiment is lexicon-based, computed as the mean of per-sentence scores. It does not detect sarcasm, negation scope or domain-specific tone.
- Sentiment, POS and NER describe the generated summary, not the original input text.
pos_result/ner_resultare raw HTML rendered withdangerouslySetInnerHTML. This is safe because the backend escapes all user-derived tokens, but any fork changing that rendering must preserve the escaping.- PDF extraction is text-layer only. Scanned/image PDFs yield nothing; there is no OCR.
- Uploads are capped at 5 MiB and processed synchronously in the request — there is no job queue, so long documents block a worker.
- The contact form needs both reCAPTCHA halves configured, otherwise every submission is rejected.
- SQLite by default. Fine for evaluation and light use; switch to PostgreSQL or MySQL for concurrent write load.