Read Less. Know More.
Précis is a fast, focused AI-powered article summarizer. Paste any article URL and receive a clean, three-paragraph summary in seconds — powered by Google Gemini, with no account required.
- Overview
- Features
- Tech Stack
- Architecture
- Getting Started
- Environment Variables
- Available Scripts
- How It Works
- AI Integration
- State Management
- Testing
- CI/CD
- Performance
- Deployment
- Screenshots
- Contributing
- License
The modern web produces more information than any person can consume. Précis solves that directly — it acts as an intelligent reading layer between you and any article on the internet. Paste a URL, hit summarize, and within seconds you have the core ideas distilled into three focused paragraphs.
Who it's for:
- Researchers & students who need to scan dozens of sources quickly
- Developers & engineers who want to stay current without reading every blog post in full
- Content curators & journalists evaluating articles for relevance before committing reading time
- Busy professionals who want signal without noise
There are no accounts, no subscriptions, and no friction. Summaries are cached locally, so repeat lookups are instant, and your last 20 articles are always one click away in the history sidebar.
| Feature | Description |
|---|---|
| Instant AI Summaries | Submits any HTTP/HTTPS URL to Google Gemini and returns a structured three-paragraph summary |
| Smart Local Cache | Stores summaries in localStorage keyed by URL — identical requests never hit the API twice |
| Article History | Sidebar persists up to 20 recent summaries across sessions; entries can be selected, copied, or deleted |
| One-Click Copy | Copies the summary or source URL to clipboard with a visual tick feedback that auto-resets |
| Skeleton Loading | Animated placeholder cards replace content while the API request is in flight |
| Graceful Error Handling | API failures surface a descriptive, recoverable error state — never a silent crash |
| Feature | Description |
|---|---|
| Strict TypeScript | No implicit any, strict mode enabled across the entire codebase |
| Zod Validation | Runtime schema validation on all URL inputs before any network call is made |
| Feature-Based Architecture | Code organized by product feature, not by file type, for clear boundaries and scalability |
| Path Aliases | @features, @hooks, @lib, @store aliases eliminate relative import hell |
| Code Splitting | React, Router, and Redux loaded as separate chunks for a fast initial paint |
| Full Test Coverage | Hook, validator, and component tests via Vitest + Testing Library |
| Feature | Description |
|---|---|
| Dark Mode | Class-based Tailwind dark theme toggled manually and persisted to localStorage |
| Responsive Layout | Mobile-first design tested across sm, md, and lg breakpoints |
| ARIA-Compliant | aria-label, aria-live, aria-invalid, aria-describedby, role="alert" throughout |
| Semantic HTML | Proper landmark elements (nav, main, footer) and heading hierarchy |
| Custom Typography | Satoshi and Inter typefaces via Fontshare and Google Fonts |
| Layer | Technology | Purpose |
|---|---|---|
| Framework | React 18.3 | UI rendering with concurrent features |
| Language | TypeScript 5.2 | End-to-end type safety, strict mode |
| Bundler | Vite 5.3 | Sub-second HMR in dev, optimized production builds |
| Routing | React Router 6.24 | Client-side routing with lazy-loaded pages |
| State | Redux Toolkit 2.2 | Global state with structured slice architecture |
| Data Fetching | RTK Query (via Redux Toolkit) | API state, caching, and normalized request lifecycle |
| Styling | Tailwind CSS 3.4 | Utility-first CSS with class-based dark mode |
| Validation | Zod 4.4 | Runtime schema validation at form boundaries |
| AI Provider | Google Gemini API | Article summarization via url_context tool |
| Testing | Vitest 4.1 + Testing Library | Unit and component tests, v8 coverage |
| Linting | ESLint 8 + TypeScript ESLint | Static analysis and a11y enforcement |
| Formatting | Prettier 3.8 | Consistent code style (100-char width, single quotes) |
| CI/CD | GitHub Actions | Automated type-check, lint, test, and build on every push |
| Icons | react-icons 5.2 | Bootstrap icon set (sun, moon, GitHub, LinkedIn, etc.) |
précis/
├── .github/
│ └── workflows/
│ └── ci.yml # GitHub Actions CI pipeline
│
├── public/
│ ├── avatar.png
│ ├── favicon.ico
│ └── favicon.svg
│
├── src/
│ ├── assets/ # SVG icons and logo exports
│ │
│ ├── components/ # Globally shared UI components
│ │ ├── ErrorBoundary.tsx # Top-level crash recovery
│ │ └── Spinner.tsx # Configurable loading spinner (sm/md/lg)
│ │
│ ├── features/ # Self-contained product feature modules
│ │ ├── home/ # Landing page
│ │ │ ├── HomePage.tsx
│ │ │ └── components/
│ │ │ ├── LandingText.tsx # Hero section and CTA
│ │ │ ├── Features.tsx # Feature grid (6 cards)
│ │ │ └── Testimonials.tsx # Social proof section
│ │ │
│ │ └── summarize/ # Core summarizer feature
│ │ ├── SummarizePage.tsx
│ │ ├── components/
│ │ │ ├── UrlForm.tsx # URL input and submission
│ │ │ ├── SummaryResult.tsx # Summary display + tests
│ │ │ └── ArticleHistory.tsx # History sidebar
│ │ └── hooks/
│ │ └── useArticleSummarizer.ts # All summarizer logic
│ │
│ ├── hooks/ # Reusable custom hooks
│ │ ├── useTheme.ts # Dark mode toggle + persistence
│ │ └── useLocalStorage.ts # Generic typed localStorage hook + tests
│ │
│ ├── layout/ # App shell components
│ │ ├── Layout.tsx # Root layout wrapper
│ │ ├── Header.tsx # Fixed nav with theme toggle
│ │ └── Footer.tsx # Footer with social links
│ │
│ ├── lib/ # Utilities and application constants
│ │ ├── constants.ts # API config, storage keys, UI config
│ │ └── validators.ts # Zod URL schema + tests
│ │
│ ├── store/ # Redux store and RTK Query API
│ │ ├── store.ts
│ │ └── api/
│ │ └── articleApi.ts # RTK Query endpoint definitions
│ │
│ ├── types/
│ │ └── article.ts # Article, SummaryApiResponse, error types
│ │
│ ├── test/
│ │ └── setup.ts # Vitest global setup (jest-dom)
│ │
│ ├── App.tsx # Router setup with lazy-loaded pages
│ ├── App.css # Global Tailwind layers + component classes
│ └── main.tsx # React DOM entry point
│
├── .env.example # Environment variable template
├── vite.config.ts # Bundler config (chunks, sourcemaps, aliases)
├── vitest.config.ts # Test runner config
├── tailwind.config.js # Tailwind config (dark mode, custom fonts, keyframes)
├── tsconfig.app.json # TypeScript strict config (ES2020 target)
└── .github/workflows/ci.yml # CI pipeline
Feature-based modules — Code lives with the feature it belongs to. useArticleSummarizer, UrlForm, SummaryResult, and ArticleHistory all live inside src/features/summarize/. This avoids the "components soup" anti-pattern and keeps boundaries explicit.
Hook-first logic — useArticleSummarizer absorbs all summarizer orchestration (validation, cache lookup, API trigger, history management, copy feedback). Components stay thin and declarative.
RTK Query lazy queries — The useLazyGetSummaryQuery hook is used intentionally so the API call fires only on explicit user submission, not on mount or URL change.
localStorage as the database — For a client-only app at this scope, localStorage is the right tradeoff. The useLocalStorage<T> hook provides a typed, error-safe abstraction over it.
- Node.js 18 or higher
- npm 9 or higher
- A Google Gemini API key — get one free at aistudio.google.com
git clone https://github.com/Engraya/QuickSum-AI.git
cd QuickSum-AI
npm installcp .env.example .envOpen .env and set your Gemini API key:
VITE_GEMINI_API_KEY=your_gemini_api_key_herenpm run devOpen http://localhost:5173 in your browser.
npm run build # Type-check + bundle → dist/
npm run preview # Serve dist/ locally to verify| Variable | Required | Description |
|---|---|---|
VITE_GEMINI_API_KEY |
Yes | Google Gemini API key used to call the summarization endpoint |
All variables are prefixed with VITE_ to be exposed to the browser by Vite. Never commit your .env file — it is listed in .gitignore.
.env.example
# Google Gemini API — https://aistudio.google.com/app/apikey
VITE_GEMINI_API_KEY=| Script | Command | Description |
|---|---|---|
dev |
vite |
Start dev server with Hot Module Replacement |
build |
tsc -b && vite build |
Type-check then bundle for production |
preview |
vite preview |
Locally preview the production build |
lint |
eslint . --ext ts,tsx --max-warnings 0 |
Static analysis (fails on any warning) |
lint:fix |
eslint . --ext ts,tsx --fix |
Auto-fix linting issues |
format |
prettier --write "src/**/*.{ts,tsx,css}" |
Format all source files |
test |
vitest run |
Run full test suite once (CI mode) |
test:watch |
vitest |
Run tests in watch mode |
test:coverage |
vitest run --coverage |
Generate v8 coverage report in coverage/ |
test:ui |
vitest --ui |
Launch Vitest's interactive browser dashboard |
User Input → Zod Validation → Cache Lookup → Gemini API → Display + Cache + History
Step-by-step flow:
- Input — The user pastes an article URL into the
UrlFormcomponent. - Validate —
useArticleSummarizerruns the URL through the Zod schema. Invalid URLs surface a human-readable error immediately, before any network call. - Cache check — The hook checks
localStoragefor an existing summary at that URL key. If found, the result is surfaced instantly with zero latency. - API call — On a cache miss, RTK Query fires a
POSTto the Gemini API using theuseLazyGetSummaryQueryhook. A skeleton loading state is shown while the request is in flight. - Gemini processing — Gemini's
url_contexttool fetches the article from the URL and extracts its content. The model then generates a three-paragraph summary. - Persist & display — The response is displayed in
SummaryResult, stored inlocalStoragefor future lookups, and prepended to the article history sidebar (capped at 20 items).
Provider: Google Gemini API
Model: gemini-3-flash-preview
Base URL: https://generativelanguage.googleapis.com/v1beta/
Auth: API key passed as a query parameter (?key=<VITE_GEMINI_API_KEY>)
POST /models/gemini-3-flash-preview:generateContent?key={API_KEY}
Request body:
{
"contents": [
{
"parts": [
{
"text": "Provide a concise summary of the article at this URL in 3 paragraphs: <url>"
}
]
}
],
"tools": [{ "url_context": {} }]
}Response shape:
{
"candidates": [
{
"content": {
"parts": [{ "text": "Paragraph one...\n\nParagraph two...\n\nParagraph three..." }]
}
}
]
}The url_context tool instructs Gemini to browse the article URL directly rather than relying solely on the prompt text. This means the model fetches, reads, and understands the full article content before generating the summary — making it effective across paywalled previews, long-form journalism, and technical documentation alike.
Précis uses a two-layer state model:
RTK Query (server/async state)
articleApislice handles all Gemini API interactionuseLazyGetSummaryQuery()gives manual control over when requests fire- RTK Query's built-in deduplication prevents duplicate in-flight requests
- Normalized cache keyed by URL
Hook-local state (UI/ephemeral state)
useArticleSummarizermanages URL input, validation errors, the active article, copy feedback timers, and historyuseLocalStorage<T>provides a typed, error-safe abstraction forlocalStoragereads and writesuseThemehandles dark mode toggle and persistence
This separation keeps Redux lean — it owns only what crosses component boundaries — while complex feature logic stays co-located in custom hooks.
Tests live next to the code they cover (validators.test.ts, useLocalStorage.test.ts, SummaryResult.test.tsx).
Run tests:
npm run test # Single run (CI mode)
npm run test:watch # Watch mode for development
npm run test:coverage # Coverage report → coverage/index.html
npm run test:ui # Interactive Vitest UI in browser| Module | Tests | What's Covered |
|---|---|---|
lib/validators.ts |
6 | Valid http/https URLs, empty strings, bare domains, FTP URLs, error messages |
hooks/useLocalStorage.ts |
5 | Read, write, persist across re-renders, functional updater, error recovery |
features/summarize/SummaryResult.tsx |
5 | Loading skeleton, error state, success render, empty state, generic error fallback |
Test setup: jsdom for DOM simulation, @testing-library/jest-dom matchers, @testing-library/user-event for realistic interaction simulation.
Every push and pull request to main, master, or develop triggers the GitHub Actions pipeline.
Pipeline: .github/workflows/ci.yml
Push / PR
│
├── 1. Type Check tsc -b --noEmit
├── 2. Lint eslint (zero warnings enforced)
├── 3. Test vitest run --coverage
└── 4. Build vite build → dist/
- Runtime: Node.js 20 on
ubuntu-latest - Caching: npm dependencies cached between runs
- API key is injected as a GitHub Actions secret for the build step
| Optimization | Implementation |
|---|---|
| Code splitting | React, React Router, and Redux Toolkit loaded as separate Vite manual chunks — reduces initial bundle size |
| Route-level lazy loading | HomePage and SummarizePage loaded with React.lazy + Suspense — pages only download when first visited |
| API response caching | Summaries stored in localStorage — zero-latency repeat lookups, zero redundant API calls |
| RTK Query deduplication | Concurrent identical requests collapse into a single in-flight call |
| Sourcemaps | Enabled for production to support error tracing without exposing raw source |
| Tailwind CSS purging | Vite + Tailwind removes unused utility classes at build time, keeping CSS minimal |
Précis builds to a fully static dist/ directory and can be deployed to any static host.
npm i -g vercel
vercel --prodSet VITE_GEMINI_API_KEY in your Vercel project's Environment Variables dashboard.
npm run build
# Deploy the dist/ folder via Netlify CLI or drag-and-drop
netlify deploy --prod --dir distnpm run build
# Push dist/ to the gh-pages branch using gh-pages or your preferred methodnpm run build
# Serve dist/ with any static file server (nginx, serve, etc.)
npx serve distBuild output: dist/ — a static bundle of HTML, CSS, and JS with no server-side requirements.
Screenshots coming soon. To contribute, open a PR with images placed in
public/screenshots/.
| View | Preview |
|---|---|
| Landing Page | ![]() |
| Summarizer — Input | ![]() |
| Summarizer — Result | ![]() |
| History Sidebar | ![]() |
| Dark Mode | ![]() |
| Mobile View | ![]() |
Based on the current architecture, natural next steps include:
- User accounts & cloud sync — Supabase or Clerk auth to persist history across devices
- Bulk summarization — Accept multiple URLs and return a digest
- Export options — Download summaries as PDF or Markdown
- Browser extension — Summarize the current tab with one click
- Reading time estimates — Display estimated time saved per summary
- Custom prompt control — Let users adjust summary length, tone, or focus
- RSS / newsletter integration — Summarize feeds automatically on a schedule
- i18n / localization — Multi-language support for non-English articles and UI
Contributions are welcome. Please follow these steps:
- Fork the repository
- Create a branch for your feature or fix:
git checkout -b feat/your-feature-name
- Write your changes — ensure all tests pass and no new lint warnings are introduced:
npm run lint npm run test npm run build - Commit with a clear, descriptive message:
git commit -m "feat: add export to Markdown" - Push and open a Pull Request against
main
Guidelines:
- Keep PRs focused — one feature or fix per PR
- Add or update tests for any changed logic
- Maintain the existing code style (Prettier + ESLint configs are already set up)
- Update this README if your change affects setup, usage, or architecture
This project is licensed under the MIT License.





