diff --git a/README.md b/README.md index 2ce57ac..c628ecf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# GovDoc SecureFlow - -**AI-powered government document processing with intelligent routing, classification, and multi-role workflow.** +
+ GovDoc SecureFlow Logo +

GovDoc SecureFlow

+

AI-powered government document processing with intelligent routing, classification, and multi-role workflow.

+
![Python](https://img.shields.io/badge/python-3.12-blue) ![React](https://img.shields.io/badge/react-19-61dafb) @@ -53,6 +55,51 @@ The result: faster processing, fewer routing errors, built-in audit trails, and --- +## Screenshots + +Overview of the main UI flows. **Click an image** to open the full-size file in the repository. + + + + + + + + + + + + + + + + + + + + + + +
Homepage DashboardUpload & Status Tracking
+ + Homepage Dashboard + + + + Upload and document status tracking + +
AI Classification & AnalysisConsultation Workflow
+ + AI classification and analysis + + + + Consultation workflow between departments + +
+ +--- + ## Architecture ``` diff --git a/api/app/api/v1/endpoints/demo.py b/api/app/api/v1/endpoints/demo.py index 3eaa465..83c500b 100644 --- a/api/app/api/v1/endpoints/demo.py +++ b/api/app/api/v1/endpoints/demo.py @@ -1,4 +1,7 @@ +from pathlib import Path + from fastapi import APIRouter, Depends, HTTPException +from fastapi.responses import FileResponse from sqlalchemy.orm import Session from app.api import deps from app.api.deps import require_action, CurrentRole @@ -9,6 +12,39 @@ router = APIRouter(prefix="/demo", tags=["demo"]) +# api/app/api/v1/endpoints/demo.py → parents[5] = repo root (govdoc/) +_PROJECT_ROOT = Path(__file__).resolve().parents[5] +_DEMO_SAMPLE_FILES = frozenset( + { + "sample-cong-van-dong-nai.pdf", + "sample-bao-cao-dong-nai.pdf", + } +) + + +def _demo_sample_pdf_dir() -> Path: + """Prefer Docker-bundled PDFs; fall back to web/public/demo in local dev.""" + docker_dir = Path("/app/demo-sample-pdfs") + if docker_dir.is_dir() and any(docker_dir.glob("*.pdf")): + return docker_dir + return _PROJECT_ROOT / "web" / "public" / "demo" + + +@router.get("/sample-files/{filename}") +async def download_demo_sample_file(filename: str) -> FileResponse: + """Serve bundled demo PDFs via the API so downloads work through /api/ (nginx proxy) with correct MIME type.""" + if filename not in _DEMO_SAMPLE_FILES: + raise HTTPException(status_code=404, detail="Unknown sample file") + path = _demo_sample_pdf_dir() / filename + if not path.is_file(): + raise HTTPException(status_code=404, detail="Sample file not available on server") + return FileResponse( + path, + media_type="application/pdf", + filename=filename, + content_disposition_type="attachment", + ) + @router.get("/scenarios", response_model=list[DemoScenarioOut]) async def list_scenarios(db: Session = Depends(deps.get_db)): diff --git a/api/tests/unit/test_demo_sample_files.py b/api/tests/unit/test_demo_sample_files.py new file mode 100644 index 0000000..bb28456 --- /dev/null +++ b/api/tests/unit/test_demo_sample_files.py @@ -0,0 +1,23 @@ +"""Demo sample PDF downloads — served via API for reliable MIME and proxy routing.""" + +import pytest +from fastapi.testclient import TestClient + +from app.main import app + + +@pytest.mark.unit +def test_demo_sample_file_returns_pdf() -> None: + with TestClient(app) as client: + r = client.get("/api/v1/demo/sample-files/sample-cong-van-dong-nai.pdf") + assert r.status_code == 200 + assert r.headers.get("content-type", "").startswith("application/pdf") + assert len(r.content) > 10_000 + assert r.content[:4] == b"%PDF" + + +@pytest.mark.unit +def test_demo_sample_file_unknown_name_404() -> None: + with TestClient(app) as client: + r = client.get("/api/v1/demo/sample-files/not-a-real-file.pdf") + assert r.status_code == 404 diff --git a/deploy/Dockerfile.api b/deploy/Dockerfile.api index 1800108..7da2691 100644 --- a/deploy/Dockerfile.api +++ b/deploy/Dockerfile.api @@ -22,6 +22,10 @@ RUN pip install --no-cache-dir -r /app/api/requirements.txt COPY api/ /app/api/ COPY data/ /app/demo-data/ +# Demo PDFs for /api/v1/demo/sample-files/* (same files as web/public/demo — web static can still serve /demo/) +RUN mkdir -p /app/demo-sample-pdfs +COPY web/public/demo/sample-cong-van-dong-nai.pdf web/public/demo/sample-bao-cao-dong-nai.pdf /app/demo-sample-pdfs/ + # Ensure Python can find the 'app' package inside api/ ENV PYTHONPATH=/app/api diff --git a/docs/logo.svg b/docs/logo.svg new file mode 100644 index 0000000..ae85612 --- /dev/null +++ b/docs/logo.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/screenshots/1.homepage.png b/docs/screenshots/1.homepage.png new file mode 100644 index 0000000..ae4b9d5 Binary files /dev/null and b/docs/screenshots/1.homepage.png differ diff --git a/docs/screenshots/2.upload-status.png b/docs/screenshots/2.upload-status.png new file mode 100644 index 0000000..44ba648 Binary files /dev/null and b/docs/screenshots/2.upload-status.png differ diff --git a/docs/screenshots/3.classification.png b/docs/screenshots/3.classification.png new file mode 100644 index 0000000..b440ca6 Binary files /dev/null and b/docs/screenshots/3.classification.png differ diff --git a/docs/screenshots/4.consultantion.png b/docs/screenshots/4.consultantion.png new file mode 100644 index 0000000..fbf5955 Binary files /dev/null and b/docs/screenshots/4.consultantion.png differ diff --git a/web/nginx.conf b/web/nginx.conf index f1b9a3a..2bb03ec 100644 --- a/web/nginx.conf +++ b/web/nginx.conf @@ -9,6 +9,12 @@ server { root /usr/share/nginx/html; index index.html; + # Bundled demo PDFs live under /demo/ — must be real files in the image, never the SPA + # fallback (otherwise try_files serves index.html and browsers report a failed download). + location ^~ /demo/ { + try_files $uri =404; + } + # Proxy API calls to the FastAPI backend location /api/ { proxy_pass http://api:8000/api/; diff --git a/web/public/demo/sample-bao-cao-dong-nai.pdf b/web/public/demo/sample-bao-cao-dong-nai.pdf new file mode 100644 index 0000000..d74b423 Binary files /dev/null and b/web/public/demo/sample-bao-cao-dong-nai.pdf differ diff --git a/web/public/demo/sample-cong-van-dong-nai.pdf b/web/public/demo/sample-cong-van-dong-nai.pdf new file mode 100644 index 0000000..8027d98 Binary files /dev/null and b/web/public/demo/sample-cong-van-dong-nai.pdf differ diff --git a/web/src/components/demo-walkthrough.tsx b/web/src/components/demo-walkthrough.tsx new file mode 100644 index 0000000..b0eb2a9 --- /dev/null +++ b/web/src/components/demo-walkthrough.tsx @@ -0,0 +1,60 @@ +import { Link } from 'react-router-dom'; +import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui-card'; +import { Sparkles } from 'lucide-react'; + +/** Shown only on the dashboard — home links here via `/dashboard#demo-walkthrough`. */ +export function DemoWalkthrough() { + return ( + + + + + Demo walkthrough + +

+ Downloadable PDF samples (sourced from the same incoming + corpus as ./data/incoming/) are on{' '} + + Intake → Demo sample files + + . Your full local pack under ./data may + contain additional PDFs for stress tests (not committed to git). +

+
+ +
    +
  1. + Optional for workshops: on the{' '} + + home page + + , use Seed Baseline Data to reset stored documents to a known starting + queue so a guided session stays in sync—figures below are computed from those records like any other + deployment. +
  2. +
  3. + Set Active Role to Intake Clerk, open{' '} + + Intake + + , and upload a PDF from the demo downloads. +
  4. +
  5. + Switch to Department Reviewer →{' '} + + Review + {' '} + to validate AI routing and workflow actions. +
  6. +
  7. + Select Supervisor in the header and return here for aggregate metrics + (trend badges are illustrative in this MVP). +
  8. +
+
+
+ ); +} diff --git a/web/src/lib/demo-samples.ts b/web/src/lib/demo-samples.ts new file mode 100644 index 0000000..345a0e6 --- /dev/null +++ b/web/src/lib/demo-samples.ts @@ -0,0 +1,30 @@ +/** Demo PDFs are served by GET `/api/v1/demo/sample-files/{filename}` so nginx proxies them with correct headers. */ + +import { API_BASE_URL } from '@/lib/api'; + +export type DemoSampleFile = { + filename: string; + title: string; + /** Corresponding path in the local full data pack (not in git). */ + dataPackSource: string; + description: string; +}; + +export function demoSampleFileUrl(filename: string): string { + return `${API_BASE_URL}/demo/sample-files/${encodeURIComponent(filename)}`; +} + +export const DEMO_SAMPLE_FILES: DemoSampleFile[] = [ + { + filename: 'sample-cong-van-dong-nai.pdf', + title: 'Công văn (Đồng Nai — mẫu)', + dataPackSource: 'data/incoming/cong-van/cong-van_dong-nai_mau-cong-van-di.pdf', + description: 'Official-style letter PDF from the incoming corpus.', + }, + { + filename: 'sample-bao-cao-dong-nai.pdf', + title: 'Báo cáo (Đồng Nai — văn thư)', + dataPackSource: 'data/incoming/bao-cao/bao-cao_dong-nai_cong-tac-van-thu-luu-tru.pdf', + description: 'Periodic report PDF suitable for extraction and routing demos.', + }, +]; diff --git a/web/src/pages/DashboardPage.tsx b/web/src/pages/DashboardPage.tsx index eb62bdc..441f07f 100644 --- a/web/src/pages/DashboardPage.tsx +++ b/web/src/pages/DashboardPage.tsx @@ -3,6 +3,7 @@ import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui-card'; import { BarChart3, PieChart, TrendingUp, Users, FileCheck, Layers } from 'lucide-react'; import { apiGet } from '@/lib/api'; import { useRole } from '@/hooks/use-role'; +import { DemoWalkthrough } from '@/components/demo-walkthrough'; type DashboardMetrics = { total_received: number; @@ -87,6 +88,8 @@ export default function DashboardPage() { + +
{stats.map((stat) => ( diff --git a/web/src/pages/HomePage.tsx b/web/src/pages/HomePage.tsx index aacc967..2218ff7 100644 --- a/web/src/pages/HomePage.tsx +++ b/web/src/pages/HomePage.tsx @@ -1,7 +1,7 @@ import { useRole } from '@/hooks/use-role'; import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui-card'; import { Badge } from '@/components/ui-badge'; -import { ArrowRight, CheckCircle2, Clock, AlertCircle, FileUp, ListChecks, MessageSquare, LayoutDashboard, Database, Loader2 } from 'lucide-react'; +import { ArrowRight, CheckCircle2, Clock, AlertCircle, FileUp, ListChecks, MessageSquare, LayoutDashboard, Database, Loader2, Sparkles } from 'lucide-react'; import { Link } from 'react-router-dom'; import { useState } from 'react'; import { apiPost } from '@/lib/api'; @@ -34,7 +34,7 @@ export default function HomePage() {

Automated document intake, AI-driven triage, and secure administrative workflow for government departments.

-
+
- {seedResult && {seedResult}} + + + Demo walkthrough + + {seedResult && {seedResult}}
diff --git a/web/src/pages/IntakePage.tsx b/web/src/pages/IntakePage.tsx index 70b276d..ad07821 100644 --- a/web/src/pages/IntakePage.tsx +++ b/web/src/pages/IntakePage.tsx @@ -11,10 +11,13 @@ import { BrainCircuit, ArrowRight, ListChecks, + Download, + FileText, } from 'lucide-react'; import { useEffect, useRef, useState } from 'react'; import { Link } from 'react-router-dom'; import { uploadDocument } from '@/lib/api'; +import { DEMO_SAMPLE_FILES, demoSampleFileUrl } from '@/lib/demo-samples'; // --------------------------------------------------------------------------- // Stepper model @@ -283,31 +286,71 @@ export default function IntakePage() { - - - Intake Guidelines - - -
-

1. Verify Origin

-

Ensure the document is from an authorized sender or department.

-
-
-

2. Upload & Classify

-

- Text extraction and AI classification (doc type, urgency, department) run - automatically on upload. +

+ + + Demo sample files +

+ PDFs below match files under ./data/incoming/ in the + full data pack; they are bundled here for one-click demos.

-
-
-

3. Review AI Output

-

- Open the Review queue to validate AI routing suggestions and take workflow - actions. -

-
- - + + +
    + {DEMO_SAMPLE_FILES.map((f) => ( +
  • +
    +
    +
    + + {f.title} +
    +

    {f.description}

    +

    + Source in data pack: {f.dataPackSource} +

    +
    + + + Get + +
    +
  • + ))} +
+
+ + + + + Intake Guidelines + + +
+

1. Verify Origin

+

Ensure the document is from an authorized sender or department.

+
+
+

2. Upload & Classify

+

+ Text extraction and AI classification (doc type, urgency, department) run + automatically on upload. +

+
+
+

3. Review AI Output

+

+ Open the Review queue to validate AI routing suggestions and take workflow + actions. +

+
+
+
+
);