AI-powered text summarization with real-time streaming.
Paste any text and watch a structured, markdown-formatted summary materialize word by word -- powered by GPT-4o-mini and delivered through Server-Sent Events.
Coming soon -- screenshots of the summarization interface, streaming in action, and dark mode.
| Feature | Description |
|---|---|
| Real-time streaming | SSE-based progressive text rendering -- summary appears word by word |
| Markdown output | Structured summaries with headers, bold highlights, and bullet points via @tailwindcss/typography |
| Prompt injection prevention | Multi-layer defense: system prompt isolation, pattern detection, special character ratio checks |
| Input validation | 10--50,000 character range enforced on both client and server |
| Dark mode | System-aware theme switching with Tailwind |
| Copy to clipboard | One-click copy of the generated summary |
| Example text loader | Pre-loaded sample text for instant demo |
| CORS security | Allowlist restricted to localhost and *.vercel.app origins |
POST /api/summarize
┌──────────────┐ HTTPS ┌──────────────────┐ OpenAI SDK ┌─────────────┐
│ │ ───────────> │ │ ──────────────> │ │
│ Next.js │ │ FastAPI │ │ OpenAI │
│ React 19 │ <─────────── │ Python 3.11 │ <────────────── │ GPT-4o-mini│
│ │ SSE stream │ │ stream chunks │ │
└──────────────┘ └──────────────────┘ └─────────────┘
Port 3000 Port 8000 External API
- User pastes text into the React form
- Client-side validation enforces length and format constraints
POST /api/summarizesends the text to FastAPI- Server-side validation runs prompt injection detection and input sanitization
- FastAPI streams a chat completion request to OpenAI (GPT-4o-mini, temperature 0.3)
- Tokens are accumulated, formatted with markdown post-processing, then re-streamed as SSE chunks
- The frontend renders the summary progressively with
@tailwindcss/typography
| Decision | Rationale |
|---|---|
| FastAPI middleware layer | Keeps the OpenAI API key server-side, centralizes validation, enables logging and rate limiting without exposing secrets to the browser |
| SSE over WebSockets | Summarization is unidirectional (server to client). SSE is simpler, has native browser support via EventSource, and avoids the connection management overhead of WebSockets |
| GPT-4o-mini | Optimal cost-quality tradeoff for summarization ($0.15/1M input tokens, sub-2s latency) |
| Markdown post-processing | The LLM output is reformatted server-side (format_markdown_with_breaks) to ensure consistent heading spacing and bullet structure before streaming |
| Layer | Technology | Purpose |
|---|---|---|
| Frontend | Next.js 16, React 19, TypeScript 5.6 | App Router, server/client components |
| Styling | Tailwind CSS, @tailwindcss/typography |
Utility-first CSS, prose rendering |
| Backend | FastAPI 0.115, Python 3.11, Pydantic 2.9 | Async API, streaming, validation |
| AI | OpenAI SDK, GPT-4o-mini | Chat completions with streaming |
| Testing | Jest, React Testing Library, Playwright, Pytest | Unit, component, E2E, API tests |
| CI/CD | GitHub Actions | Lint, test, build, deploy |
| Hosting | Vercel (frontend), Render (backend) | Edge deployment, managed Python |
- Node.js 18+ and npm (or pnpm)
- Python 3.11+
- An OpenAI API key
git clone https://github.com/willianpinho/smart-summary-app.git
cd smart-summary-app
# Create environment file
cp .env.example .env # or create manuallyAdd your API key to .env:
OPENAI_API_KEY=sk-...
cd backend
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
python main.pyThe API will be available at http://localhost:8000 with interactive docs at http://localhost:8000/docs.
cd frontend
npm install
npm run devOpen http://localhost:3000 in your browser.
| Variable | Location | Required | Default | Description |
|---|---|---|---|---|
OPENAI_API_KEY |
Backend .env |
Yes | -- | OpenAI API key for GPT-4o-mini |
NEXT_PUBLIC_API_URL |
Frontend .env |
No | http://localhost:8000 |
Backend API base URL |
ALLOWED_ORIGINS |
Backend .env |
No | -- | Comma-separated additional CORS origins |
The project maintains 62 tests across three layers with 95%+ backend coverage.
cd backend
source venv/bin/activate
pytest -v # Run all tests
pytest --cov=main --cov-report=term-missing # With coverage reportCovers: endpoint responses, input validation, prompt injection prevention, SSE streaming, error handling.
cd frontend
npm test # Run all unit tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage reportCovers: component rendering, form validation, streaming display, snapshot regression.
cd frontend
npx playwright install # First time only
npm run e2e # Headless
npm run e2e:ui # Interactive UI modeCovers: full summarization flow, error states, clipboard copy, dark mode, responsive layout.
Health check.
{ "status": "healthy", "service": "Smart Summary API", "version": "1.0.0" }Detailed health check including OpenAI configuration status.
{ "status": "healthy", "openai_configured": true, "service": "Smart Summary API" }Streams a markdown-formatted summary via Server-Sent Events.
Request:
{ "text": "Your long text here (10-50,000 characters)..." }Response (text/event-stream):
data: ## Over
data: view\n\nTh
data: is is a su
data: mmary...
data: [DONE]
Error codes:
| Status | Reason |
|---|---|
422 |
Validation error -- text too short, too long, or suspicious patterns detected |
500 |
Internal server error (logged server-side, generic message returned) |
smart-summary-app/
├── backend/
│ ├── main.py # FastAPI application (routes, validation, streaming)
│ ├── test_main.py # 24 Pytest tests
│ └── requirements.txt
│
├── frontend/
│ ├── app/ # Next.js App Router
│ │ ├── page.tsx # Home page
│ │ ├── layout.tsx # Root layout with metadata
│ │ └── globals.css # Tailwind base styles
│ ├── components/
│ │ ├── SummaryForm.tsx # Text input form with validation
│ │ ├── SummaryDisplay.tsx # Streaming summary renderer
│ │ ├── FormattedSummary.tsx# Markdown prose component
│ │ └── StreamingProgress.tsx # Progress indicator
│ ├── lib/
│ │ └── config.ts # API URL configuration
│ ├── __tests__/ # Jest + RTL unit tests
│ ├── e2e/ # Playwright E2E tests
│ ├── playwright.config.ts
│ ├── jest.config.js
│ └── tailwind.config.js
│
├── .github/workflows/ # CI/CD pipeline
├── vercel.json # Vercel deployment config
├── render.yaml # Render deployment config
└── README.md
The frontend deploys automatically on push to main via Vercel's GitHub integration. Configuration lives in vercel.json.
Set the build-time environment variable:
NEXT_PUBLIC_API_URL=https://your-backend.onrender.com
The backend deploys automatically via render.yaml. Set the following environment variable in the Render dashboard:
OPENAI_API_KEY=sk-...
Every push and pull request triggers the GitHub Actions workflow:
- Lint -- ESLint (frontend), formatting checks
- Test -- Pytest (backend), Jest (frontend), Playwright (E2E)
- Build -- Next.js production build
- Deploy -- Automatic on
mainvia Vercel and Render
- Prompt injection prevention -- System prompt uses strict role separation; user text is treated as content, never as instructions. Pattern detection flags suspicious inputs.
- Input sanitization -- Special character ratio analysis, repeated pattern detection, and length bounds enforced via Pydantic validators on the server.
- API key isolation -- The OpenAI key lives exclusively on the backend. The frontend never touches it.
- CORS allowlist -- Only
localhostand*.vercel.apporigins are permitted. Additional origins can be added via theALLOWED_ORIGINSenv var. - Error opacity -- Detailed errors are logged server-side; clients receive generic messages only.
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature - Write tests for your changes
- Ensure all checks pass:
pytest+npm test+npm run e2e - Open a pull request
All PRs must pass CI (lint, test, build) before merging.