diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/.gitignore b/use-cases/pritesh2564u/dealer-agreement-redline/.gitignore new file mode 100644 index 00000000..ea4a3afb --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/.gitignore @@ -0,0 +1,28 @@ +# Environment +.env +.env.* + +# Python +__pycache__/ +*.py[cod] +*.pyo +.venv/ +venv/ + +# Node +node_modules/ +dist/ +build/ + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log + +backend/uploads/ \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/README.md b/use-cases/pritesh2564u/dealer-agreement-redline/README.md new file mode 100644 index 00000000..ba9f0b19 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/README.md @@ -0,0 +1,1276 @@ +# Dealer Agreement Redline Desk + +A contract-redlining workflow built with FastAPI, PostgreSQL, and the SuperDocs API. + +The application compares a dealer-returned agreement against an approved master agreement and a negotiation playbook, detects deviations, classifies them, and routes them through a human-in-the-loop approval process before applying redlines and exporting the final document. + +--- + +## Current Status + +### Backend + +- [x] SuperDocs API integration +- [x] API key configuration +- [x] Dealer agreement upload +- [x] Master agreement attachment upload +- [x] Negotiation playbook attachment upload +- [x] DOCX section extraction +- [x] Agreement deviation detection +- [x] Playbook-based classification +- [x] Redline review persistence +- [x] Finding persistence +- [x] Human approve/reject workflow +- [x] Apply approved redlines through SuperDocs +- [x] Export final DOCX +- [x] Duplicate apply protection +- [x] Review-state validation +- [x] Backend compilation verification +- [x] End-to-end workflow verification + +### Frontend + +- [ ] React frontend +- [ ] Review dashboard +- [ ] Review summary +- [ ] Finding cards +- [ ] Master vs dealer comparison +- [ ] Approve/reject controls +- [ ] Apply approved changes +- [ ] Export DOCX + +--- + +## Architecture + +```text +┌─────────────────┐ +│ React UI │ +│ (Frontend) │ +└─────────┬────────┘ + │ HTTP + ▼ +┌─────────────────┐ +│ FastAPI Backend │ +└─────────┬────────┘ + │ + ┌──────┴──────┐ + ▼ ▼ +``` + +The frontend communicates only with the FastAPI backend. + +The SuperDocs API key remains server-side and is never exposed to the browser. + +## Workflow + +``` +Dealer Agreement + │ + ▼ +Create SuperDocs Session + │ + ▼ +Upload Dealer Agreement + │ + ├── Master Agreement → Attachment + │ + └── Negotiation Playbook → Attachment + │ + ▼ +Local Agreement Analysis + │ + ├── Extract sections + ├── Detect deviations + └── Classify against playbook + │ + ▼ +Create Redline Review + │ + ▼ +Human Review + │ + ├── Approve finding + └── Reject finding + │ + ▼ +All Findings Reviewed + │ + ▼ +Apply Approved Findings + │ + ▼ +SuperDocs Updates Active Document + │ + ▼ +Export Final DOCX +``` + +--- + +## Redline Classification + +Each detected deviation is classified into one of three categories: + +``` +PRE-APPROVED +ESCALATE +REFUSE +``` + +**PRE-APPROVED** +The requested change falls within the limits defined by the negotiation playbook. + +**ESCALATE** +The requested change requires human review or approval. + +**REFUSE** +The requested change exceeds the limits defined by the negotiation playbook and should not be accepted. + +The classification is determined by the backend playbook classifier. + +--- + +## Human Review + +Every detected deviation is persisted as a redline finding. + +The reviewer can make one of two decisions: + +``` +approve +reject +``` + +Only findings with: + +``` +decision = approve +``` + +are sent to SuperDocs for application. + +Rejected findings are not applied to the document. + +A finding cannot be reviewed more than once. + +A completed or applied review cannot receive additional decisions. + +--- + +## Review Status + +A review progresses through the following states: + +``` +pending + │ + ▼ +in_review + │ + ▼ +completed + │ + ▼ +applied +``` + +**pending** +The review has been created but human review has not started. + +**in_review** +At least one finding has been reviewed, but pending findings remain. + +**completed** +All findings have received a human decision. + +**applied** +All approved findings have been sent to SuperDocs and applied to the active document. + +--- + +## Database + +The backend uses PostgreSQL. + +### `redline_reviews` + +Stores the overall redline review. + +Fields: + +``` +id +session_id +status +created_at +updated_at +``` + +### `redline_findings` + +Stores individual deviations found during the analysis. + +Fields: + +``` +id +review_id +section +master_text +dealer_text +deviation_type +similarity +classification +reason +counter_position +decision +created_at +``` + +Relationship: + +``` +RedlineReview + │ + └── RedlineFinding + ├── Finding 1 + ├── Finding 2 + ├── Finding 3 + └── ... +``` + +`review_id` references the parent review. + +Deleting a review cascades to its findings. + +--- + +## Backend Structure + +``` +backend/ +├── app/ +│ ├── api/ +│ │ ├── __init__.py +│ │ ├── poc.py +│ │ ├── redline.py +│ │ └── superdocs.py +│ │ +│ ├── db/ +│ │ ├── base.py +│ │ ├── init_db.py +│ │ └── session.py +│ │ +│ ├── models/ +│ │ └── redline_review.py +│ │ +│ ├── services/ +│ │ ├── agreement_parser.py +│ │ ├── deviation_analyzer.py +│ │ ├── playbook_classifier.py +│ │ ├── redline_service.py +│ │ └── superdocs_redline.py +│ │ +│ ├── superdocs/ +│ │ ├── __init__.py +│ │ └── client.py +│ │ +│ ├── config.py +│ └── main.py +│ +├── .env +└── requirements.txt +``` + +`.env` is local-only and must never be committed. + +--- + +## Test Data + +The project contains three DOCX files used by the redline workflow: + +``` +test-data/ +├── dealer-agreement.docx +├── master-agreement.docx +└── negotiation-playbook.docx +``` + +### Dealer Agreement + +The dealer-returned agreement contains four relevant deviations: + +``` +Section 4.2 — Payment Terms +Section 7.1 — Agreement Term +Section 9.3 — Territory +Section 12.1 — Termination +``` + +### Master Agreement + +The master agreement provides the approved baseline language. + +### Negotiation Playbook + +The negotiation playbook defines the allowed ranges and escalation/refusal rules. + +--- + +## Current Example Analysis + +For the provided test agreements, the backend produces: + +``` +Section 4.2 — Payment Terms +→ ESCALATE + +Section 7.1 — Agreement Term +→ REFUSE + +Section 9.3 — Territory +→ ESCALATE + +Section 12.1 — Termination +→ REFUSE +``` + +Summary: + +```json +{ + "total": 4, + "pre_approved": 0, + "escalate": 2, + "refuse": 2 +} +``` + +A human reviewer can approve the two escalation findings and reject the two refusal findings. + +The resulting workflow is: + +``` +Payment Terms +→ APPROVE +→ APPLY + +Agreement Term +→ REJECT +→ IGNORE + +Territory +→ APPROVE +→ APPLY + +Termination +→ REJECT +→ IGNORE +``` + +Only the two approved findings are sent to SuperDocs. + +--- + +## SuperDocs Integration + +The backend contains a dedicated `SuperDocsClient`. + +The client supports: + +- API key authentication +- API key verification +- Document upload +- Attachment upload +- Chat +- Human-in-the-loop approval +- Document export + +### SuperDocs Authentication + +The SuperDocs API uses the `Authorization` header: + +```http +Authorization: Bearer sk_YOUR_API_KEY +``` + +The API key is stored in the backend environment: + +```env +SUPERDOCS_API_KEY=sk_YOUR_API_KEY +``` + +The browser never receives this key. + +### API Key Verification + +SuperDocs provides: + +```http +GET /v1/sessions +``` + +as a safe way to verify an API key. + +The backend's SuperDocs client uses: + +``` +GET /v1/sessions +``` + +for verification. + +A successful `200` response confirms that the API key is valid. + +--- + +## Documents vs Attachments + +SuperDocs provides two different ways to load files. + +### Active Document + +The dealer agreement is uploaded using: + +``` +/v1/documents/upload +``` + +This makes it the active editable document. + +### Attachments + +The master agreement and negotiation playbook are uploaded using: + +``` +/v1/attachments/upload +``` + +These provide reference material for the workflow. + +The distinction is: + +``` +Dealer Agreement +→ Active editable document + +Master Agreement +→ Reference attachment + +Negotiation Playbook +→ Reference attachment +``` + +Attachments are useful as supporting context, while the document upload is used for the document that will actually be edited. + +--- + +## SuperDocs Session + +A unique SuperDocs session is created for each redline analysis. + +The same `session_id` is persisted in `RedlineReview`. + +This connects the application review to the SuperDocs active document: + +``` +RedlineReview + │ + └── session_id + │ + ▼ + SuperDocs Session + │ + ▼ + Active Dealer Document +``` + +When `/apply` is called, the backend uses the stored session ID to modify the correct active SuperDocs document. + +--- + +## SuperDocs Chat + +The backend uses SuperDocs chat/document editing capabilities to apply approved counter-positions. + +Only human-approved findings are passed to the application service. + +The frontend does not communicate directly with the SuperDocs chat API. + +--- + +## Human-in-the-Loop + +The application has its own human review layer. + +The workflow is: + +``` +AI / Backend Analysis + │ + ▼ +Redline Findings + │ + ▼ +Human Decision + ┌───┴───┐ + │ │ +Approve Reject + │ │ + ▼ ▼ + Apply Ignore +``` + +SuperDocs also supports human-in-the-loop approval for document changes. + +For the tested workflow, SuperDocs returned the applied edits as: + +``` +status: auto_approved +``` + +with: + +``` +requires_approval: null +pending_changes: null +``` + +Therefore, no additional SuperDocs approval call was required for the tested changes. + +The application does not artificially call the SuperDocs approval endpoint when SuperDocs reports that there are no pending changes requiring approval. + +--- + +## Applied Changes + +For the tested review, two findings were approved. + +SuperDocs returned: + +``` +2 sections edited, rest untouched +``` + +### Section 4.2 — Payment Terms + +Original dealer language: + +``` +Dealer payments are due within 60 days of invoice. +``` + +Applied language: + +``` +Dealer payments are due within 30 days of invoice. +Any request for 60-day terms must be escalated for approval. +``` + +### Section 9.3 — Territory + +Original dealer language: + +``` +Dealer shall operate within the assigned territory +and may sell to customers outside the territory. +``` + +Applied language: + +``` +Dealer shall operate within the assigned territory. +Expansion of territory is prohibited unless approved +through escalation. +``` + +The rejected findings remained unchanged. + +--- + +## API Endpoints + +### 1. Analyze Agreement + +```http +POST /redline/analyze +``` + +Creates a SuperDocs session, uploads the required documents, analyzes the agreements, and persists a redline review. + +Example: + +```powershell +curl -X POST http://localhost:8000/redline/analyze +``` + +Response includes: + +``` +review_id +session_id +dealer_document +master_attachment +playbook_attachment +master_sections +dealer_sections +playbook_sections +deviations +summary +``` + +### 2. Get Review + +```http +GET /redline/reviews/{review_id} +``` + +Returns: + +- review status +- SuperDocs session ID +- timestamps +- all findings +- classification +- reason +- counter-position +- human decision + +Example: + +```powershell +curl http://localhost:8000/redline/reviews/REVIEW_ID +``` + +### 3. Decide Finding + +```http +POST /redline/reviews/{review_id}/findings/{finding_id}/decision +``` + +Approve: + +```json +{ + "decision": "approve" +} +``` + +Reject: + +```json +{ + "decision": "reject" +} +``` + +The backend normalizes the decision using: + +``` +strip() +lower() +``` + +Therefore: + +``` +APPROVE +Approve + approve +``` + +are normalized to: + +``` +approve +``` + +Only `approve` and `reject` are accepted. + +### 4. Apply Approved Findings + +```http +POST /redline/reviews/{review_id}/apply +``` + +This endpoint: + +1. Loads the review. +2. Checks that the review has not already been applied. +3. Checks that no findings remain pending. +4. Loads all findings. +5. Passes approved findings to the SuperDocs redline service. +6. Applies approved changes to the active document. +7. Updates the review status to `applied`. + +Example: + +```powershell +curl -X POST http://localhost:8000/redline/reviews/REVIEW_ID/apply +``` + +A review cannot be applied twice. + +Attempting to apply an already-applied review returns HTTP `409`. + +### 5. Export + +```http +GET /redline/reviews/{review_id}/export +``` + +Exports the currently applied SuperDocs document. + +The generated file is: + +``` +dealer-agreement-redlined.docx +``` + +Example: + +```powershell +curl -X GET http://localhost:8000/redline/reviews/REVIEW_ID/export -o dealer-agreement-redlined.docx +``` + +Export is blocked until the review status is: + +``` +applied +``` + +--- + +## Validation Rules + +The backend currently validates: + +- Required test files exist. +- Review exists before review operations. +- Finding exists before a decision is made. +- Finding belongs to the requested review. +- Finding cannot be decided twice. +- Completed reviews cannot receive new decisions. +- Applied reviews cannot receive new decisions. +- Pending findings prevent applying. +- Already-applied reviews cannot be applied again. +- Export requires an applied review. +- SuperDocs API errors are handled by the backend. +- Invalid decisions return HTTP `400`. +- Missing reviews/findings return HTTP `404`. +- Invalid workflow state returns HTTP `409`. +- SuperDocs failures return HTTP `502`. + +--- + +## Error Responses + +### Invalid Decision + +```json +{ + "detail": "Decision must be either 'approve' or 'reject'." +} +``` + +HTTP: `400 Bad Request` + +### Review Not Found + +```json +{ + "detail": "Redline review not found" +} +``` + +HTTP: `404 Not Found` + +### Finding Already Reviewed + +```json +{ + "detail": "This finding has already been reviewed." +} +``` + +HTTP: `409 Conflict` + +### Pending Findings + +```json +{ + "detail": "2 finding(s) still require review." +} +``` + +HTTP: `409 Conflict` + +### Review Already Applied + +```json +{ + "detail": "This review has already been applied." +} +``` + +HTTP: `409 Conflict` + +### Export Before Apply + +```json +{ + "detail": "The review must be applied before exporting." +} +``` + +HTTP: `409 Conflict` + +### SuperDocs Error + +SuperDocs failures are returned as: + +``` +502 Bad Gateway +``` + +--- + +## Environment Variables + +Create: + +``` +backend/.env +``` + +Example: + +```env +DATABASE_URL=postgresql+asyncpg://postgres:password@localhost:5432/dealer_redline +SUPERDOCS_API_KEY=sk_YOUR_API_KEY +``` + +Never commit `.env`. + +The `.env` file contains secrets and must remain local. + +--- + +## Backend Setup + +From the project root: + +```powershell +cd backend +``` + +Create a virtual environment: + +```powershell +python -m venv .venv +``` + +Activate it on Windows PowerShell: + +```powershell +.\venv\Scripts\Activate.ps1 +``` + +Install dependencies: + +```powershell +pip install -r requirements.txt +``` + +Start the backend: + +```powershell +uvicorn app.main:app --reload +``` + +The API runs at: + +``` +http://127.0.0.1:8000 +``` + +Swagger documentation: + +``` +http://127.0.0.1:8000/docs +``` + +--- + +## Backend Verification + +Python compilation can be checked with: + +```powershell +python -m compileall app +``` + +A successful result finishes without syntax errors. + +The current backend has been verified through the following workflow: + +``` +POST /redline/analyze + ↓ +GET /redline/reviews/{review_id} + ↓ +POST /redline/reviews/{review_id}/findings/{finding_id}/decision + ↓ +remaining_findings = 0 + ↓ +POST /redline/reviews/{review_id}/apply + ↓ +status = applied + ↓ +GET /redline/reviews/{review_id}/export + ↓ +DOCX successfully exported +``` + +The duplicate-apply protection was also tested successfully. + +Attempting to apply an already-applied review returns HTTP `409`. + +--- + +## Backend Test Results + +The following functionality has been tested successfully: + +### Agreement Analysis + +- ✓ Master agreement parsed +- ✓ Dealer agreement parsed +- ✓ Negotiation playbook parsed +- ✓ Deviations detected +- ✓ Deviations classified + +### Database + +- ✓ Redline review persisted +- ✓ Redline findings persisted +- ✓ Review retrieved +- ✓ Finding decisions persisted + +### Human Review + +- ✓ Finding approve +- ✓ Finding reject +- ✓ Pending finding count +- ✓ Review transitions to completed +- ✓ Duplicate decision prevented + +### SuperDocs + +- ✓ Dealer document uploaded +- ✓ Master attachment uploaded +- ✓ Playbook attachment uploaded +- ✓ Approved redlines applied +- ✓ Rejected redlines left unchanged +- ✓ Final document exported + +### Workflow Protection + +- ✓ Duplicate apply prevented +- ✓ Apply blocked while findings are pending +- ✓ Decision blocked after review completion +- ✓ Decision blocked after review application +- ✓ Export blocked before apply + +--- + +## Frontend + +The frontend is maintained separately from the backend. + +The frontend directory is: + +``` +frontend/ +``` + +The frontend will be built using React and TypeScript with Vite. + +Planned structure: + +``` +frontend/ +├── package.json +├── vite.config.ts +├── tsconfig.json +├── index.html +└── src/ + ├── main.tsx + ├── App.tsx + ├── api/ + │ └── redline.ts + ├── components/ + │ ├── ReviewSummary.tsx + │ ├── FindingCard.tsx + │ └── ReviewActions.tsx + └── types/ + └── redline.ts +``` + +### Planned Frontend Workflow + +``` +Start Review + │ + ▼ +POST /redline/analyze + │ + ▼ +Review Dashboard + │ + ├── View summary + ├── View master language + ├── View dealer language + ├── View classification + ├── View reason + └── View counter-position + │ + ▼ +Approve / Reject findings + │ + ▼ +All findings reviewed + │ + ▼ +Apply Approved Changes + │ + ▼ +Export DOCX +``` + +### Planned Frontend UI + +``` +┌────────────────────────────────────────────────────────────┐ +│ Dealer Agreement Redline │ +│ Review and approve contract changes │ +├────────────────────────────────────────────────────────────┤ +│ │ +│ Review Summary │ +│ │ +│ ┌─────┐ ┌──────────┐ ┌────────┐ ┌──────────┐ │ +│ │ 4 │ │ 2 │ │ 2 │ │ 0 │ │ +│ │Total│ │Escalate │ │Refuse │ │Reviewed │ │ +│ └─────┘ └──────────┘ └────────┘ └──────────┘ │ +│ │ +├────────────────────────────────────────────────────────────┤ +│ │ +│ Section 4.2 — Payment Terms ESCALATE │ +│ │ +│ Master: │ +│ Dealer payments are due within 30 days of invoice. │ +│ │ +│ Dealer: │ +│ Dealer payments are due within 60 days of invoice. │ +│ │ +│ Reason: │ +│ Payment terms exceed the 45-day pre-approved limit. │ +│ │ +│ Counter-position: │ +│ Maintain the standard 30-day payment term or │ +│ escalate the requested 60-day term for approval. │ +│ │ +│ [ Reject ] [ Approve ] │ +│ │ +├────────────────────────────────────────────────────────────┤ +│ [ Apply Approved Changes ] │ +│ [ Export DOCX ] │ +└────────────────────────────────────────────────────────────┘ +``` + +### Frontend API Flow + +The frontend will consume the following backend APIs: + +``` +React + │ + ├── POST /redline/analyze + │ + ├── GET /redline/reviews/{review_id} + │ + ├── POST /redline/reviews/{review_id}/findings/{finding_id}/decision + │ + ├── POST /redline/reviews/{review_id}/apply + │ + └── GET /redline/reviews/{review_id}/export + │ + ▼ + FastAPI + │ + ├── PostgreSQL + │ + └── SuperDocs API +``` + +The frontend never calls: + +``` +https://api.superdocs.app +``` + +directly. + +--- + +## Security + +The SuperDocs API key is server-side only. + +The frontend must never contain: + +``` +SUPERDOCS_API_KEY +``` + +The frontend must never make direct requests to: + +``` +https://api.superdocs.app +``` + +All SuperDocs operations must go through the FastAPI backend. + +The `.env` file must never be committed to Git. + +--- + +## Git Ignore + +The repository should ignore local environment files and generated files: + +```gitignore +# Environment +.env +.env.* + +# Python +__pycache__/ +*.py[cod] +.venv/ +venv/ + +# Node +node_modules/ +dist/ + +# IDE +.vscode/ +.idea/ + +# Generated export +dealer-agreement-redlined.docx +``` + +Do not use a blanket: + +```gitignore +*.docx +``` + +rule. + +The test DOCX files under `test-data/` are required project files and should remain tracked: + +``` +test-data/ +├── dealer-agreement.docx +├── master-agreement.docx +└── negotiation-playbook.docx +``` + +--- + +## Project Directory + +The current project structure is: + +``` +dealer-agreement-redline/ +│ +├── backend/ +│ ├── app/ +│ ├── .env +│ └── requirements.txt +│ +├── frontend/ +│ +├── test-data/ +│ ├── dealer-agreement.docx +│ ├── master-agreement.docx +│ └── negotiation-playbook.docx +│ +├── .gitignore +└── README.md +``` + +--- + +## Development Notes + +This project is a focused proof of concept for the dealer-agreement redline workflow. + +The backend currently provides the complete functional workflow: + +``` +Analyze + ↓ +Persist Review + ↓ +Review Findings + ↓ +Approve / Reject + ↓ +Apply Approved Changes + ↓ +Export DOCX +``` + +The backend is complete and tested before starting frontend implementation. + +The frontend will consume the existing backend APIs and provide the user-facing review interface. + +The SuperDocs API key remains completely server-side. + +--- + +## Current Milestone + +### Task 2 Backend + +``` +STATUS: COMPLETE +``` + +Completed: + +- ✓ SuperDocs integration +- ✓ Document uploads +- ✓ Attachment uploads +- ✓ Agreement parsing +- ✓ Deviation analysis +- ✓ Playbook classification +- ✓ PostgreSQL persistence +- ✓ Human review +- ✓ Approve/reject decisions +- ✓ Apply approved changes +- ✓ Export DOCX +- ✓ Workflow validation +- ✓ Duplicate apply protection +- ✓ Backend verification + +### Next + +- Build the React frontend (dashboard, finding cards, comparison view, approve/reject controls, apply and export actions). diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/README_updated.md b/use-cases/pritesh2564u/dealer-agreement-redline/README_updated.md new file mode 100644 index 00000000..21400e14 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/README_updated.md @@ -0,0 +1,288 @@ +# Dealer Agreement Redline Desk + +A contract-redlining workflow built with FastAPI, PostgreSQL, and the SuperDocs API. + +The application compares a dealer-returned agreement against an approved master agreement and a negotiation playbook, detects deviations, classifies them, and routes them through a human-in-the-loop approval process before applying redlines and exporting the final document. + +--- + +## Current Status + +### Backend + +- [x] SuperDocs API integration +- [x] API key configuration +- [x] Dealer agreement upload +- [x] Master agreement attachment upload +- [x] Negotiation playbook attachment upload +- [x] DOCX section extraction +- [x] Agreement deviation detection +- [x] Playbook-based classification +- [x] Redline review persistence +- [x] Finding persistence +- [x] Human approve/reject workflow +- [x] Apply approved redlines through SuperDocs +- [x] Export final DOCX +- [x] Duplicate apply protection +- [x] Review-state validation +- [x] Backend compilation verification +- [x] End-to-end workflow verification + +### Frontend + +The frontend is implemented with React, TypeScript, Vite, Tailwind CSS, and Lucide React icons. + +It provides the complete user-facing workflow: + +- Dynamic upload of dealer, master, and playbook DOCX files +- Review summary +- Finding classification badges +- Collapsible finding cards +- Master vs dealer comparison +- Reason and counter-position display +- Approve/reject controls +- Review progress +- Apply approved changes +- Export final DOCX +- Loading, success, and error states +- Fixed header +- Responsive layout +- Lucide icons + +### Frontend Structure + +```text +frontend/ +├── package.json +├── vite.config.ts +├── tsconfig.json +├── index.html +└── src/ + ├── main.tsx + ├── App.tsx + ├── api/ + │ └── redline.ts + ├── components/ + │ ├── DocumentUpload.tsx + │ ├── EmptyState.tsx + │ ├── FindingCard.tsx + │ ├── FindingList.tsx + │ ├── Header.tsx + │ └── SuccessMessage.tsx + └── types/ + └── redline.ts +``` + +### Frontend Workflow + +```text +Upload Documents + │ + ▼ +POST /redline/analyze + │ + ▼ +Review Dashboard + │ + ├── Summary + ├── Classification + ├── Master language + ├── Dealer language + ├── Reason + └── Counter-position + │ + ▼ +Approve / Reject findings + │ + ▼ +All findings reviewed + │ + ▼ +Apply Approved Changes + │ + ▼ +Export DOCX +``` + +### Finding UX + +Findings are collapsed by default so reviews with many deviations remain compact. + +The collapsed card shows the finding number, section, classification, and decision status. Expanding a card reveals the master text, dealer text, reason, counter-position, and approve/reject actions. + +### Frontend Development + +From the frontend directory: + +```powershell +npm install +npm run dev +``` + +The Vite development server runs at: + +```text +http://localhost:5173 +``` + +Production build: + +```powershell +npm run build +``` + +The frontend communicates only with the FastAPI backend. It never receives or exposes the SuperDocs API key. + +## Security + +The SuperDocs API key is server-side only. + +The frontend must never contain: + +``` +SUPERDOCS_API_KEY +``` + +The frontend must never make direct requests to: + +``` +https://api.superdocs.app +``` + +All SuperDocs operations must go through the FastAPI backend. + +The `.env` file must never be committed to Git. + +--- + +## Git Ignore + +The repository should ignore local environment files and generated files: + +```gitignore +# Environment +.env +.env.* + +# Python +__pycache__/ +*.py[cod] +.venv/ +venv/ + +# Node +node_modules/ +dist/ + +# IDE +.vscode/ +.idea/ + +# Generated export +dealer-agreement-redlined.docx +``` + +Do not use a blanket: + +```gitignore +*.docx +``` + +rule. + +The test DOCX files under `test-data/` are required project files and should remain tracked: + +``` +test-data/ +├── dealer-agreement.docx +├── master-agreement.docx +└── negotiation-playbook.docx +``` + +--- + +## Project Directory + +The current project structure is: + +``` +dealer-agreement-redline/ +│ +├── backend/ +│ ├── app/ +│ ├── .env +│ └── requirements.txt +│ +├── frontend/ +│ +├── test-data/ +│ ├── dealer-agreement.docx +│ ├── master-agreement.docx +│ └── negotiation-playbook.docx +│ +├── .gitignore +└── README.md +``` + +--- + +## Development Notes + +This project is a focused proof of concept for the dealer-agreement redline workflow. + +The backend currently provides the complete functional workflow: + +``` +Analyze + ↓ +Persist Review + ↓ +Review Findings + ↓ +Approve / Reject + ↓ +Apply Approved Changes + ↓ +Export DOCX +``` + +The backend is complete and tested before starting frontend implementation. + +The frontend will consume the existing backend APIs and provide the user-facing review interface. + +The SuperDocs API key remains completely server-side. + +--- + +## Current Milestone + +### Task 2 + +```text +STATUS: COMPLETE +``` + +Completed: + +- ✓ SuperDocs integration +- ✓ Dynamic dealer/master/playbook uploads +- ✓ Dealer document loaded into the SuperDocs session +- ✓ Agreement parsing +- ✓ Deviation analysis +- ✓ Playbook classification +- ✓ PostgreSQL persistence +- ✓ Human review +- ✓ Approve/reject decisions +- ✓ Apply approved changes +- ✓ Export DOCX +- ✓ Workflow validation +- ✓ Duplicate apply protection +- ✓ Responsive React frontend +- ✓ Collapsible findings +- ✓ Fixed header +- ✓ Lucide icons +- ✓ User-friendly loading/success/error states +- ✓ End-to-end workflow verification +- ✓ Backend compilation verification + +The complete Task 2 workflow is implemented and tested. diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/__init__.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/__init__.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/poc.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/poc.py new file mode 100644 index 00000000..d6b838b9 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/poc.py @@ -0,0 +1,142 @@ +import uuid +from pathlib import Path + +from fastapi import APIRouter, HTTPException + +from app.superdocs.client import ( + SuperDocsClient, + SuperDocsError, +) + + +router = APIRouter( + prefix="/poc", + tags=["poc"], +) + + +TEST_DATA = ( + Path(__file__).resolve().parents[3] + / "test-data" +) + + +@router.post("/dealer-redline") +async def dealer_redline_poc(): + """ + End-to-end SuperDocs POC. + + Current flow: + 1. Upload dealer agreement as active document. + 2. Upload master agreement as attachment. + 3. Upload negotiation playbook as attachment. + 4. Ask SuperDocs to analyze the agreement. + 5. Return the raw chat response. + + We intentionally do not approve or export yet. + First we inspect the exact proposed-change response. + """ + + dealer_file = ( + TEST_DATA / "dealer-agreement.docx" + ) + + master_file = ( + TEST_DATA / "master-agreement.docx" + ) + + playbook_file = ( + TEST_DATA / "negotiation-playbook.docx" + ) + + for file in ( + dealer_file, + master_file, + playbook_file, + ): + if not file.exists(): + raise HTTPException( + status_code=404, + detail=f"Test file not found: {file}", + ) + + client = SuperDocsClient() + + session_id = str(uuid.uuid4()) + + try: + # -------------------------------------------------- + # 1. Upload dealer agreement as active document. + # -------------------------------------------------- + + dealer_document = ( + await client.upload_document( + file_path=dealer_file, + session_id=session_id, + ) + ) + + # -------------------------------------------------- + # 2. Upload master agreement as reference. + # -------------------------------------------------- + + master_attachment = ( + await client.upload_attachment( + file_path=master_file, + session_id=session_id, + ) + ) + + # -------------------------------------------------- + # 3. Upload negotiation playbook as reference. + # -------------------------------------------------- + + playbook_attachment = ( + await client.upload_attachment( + file_path=playbook_file, + session_id=session_id, + ) + ) + + # -------------------------------------------------- + # 4. Ask SuperDocs to analyze the dealer agreement. + # -------------------------------------------------- + + instruction = """ +Compare the active dealer agreement against the approved +master agreement and evaluate all substantive deviations +using the negotiation playbook. + +For every deviation: + +1. Identify the agreement section. +2. Describe the substantive change. +3. Explain the difference between the master language + and dealer language. +4. Determine whether the change is PRE-APPROVED, + ESCALATE, or REFUSE according to the playbook. +5. Draft a concise counter-position. + +Do not make any final changes to the document yet. + +Return the proposed changes for human review. +""".strip() + + chat_response = await client.chat( + session_id=session_id, + message=instruction, + ) + + return { + "session_id": session_id, + "dealer_document": dealer_document, + "master_attachment": master_attachment, + "playbook_attachment": playbook_attachment, + "chat_response": chat_response, + } + + except SuperDocsError as exc: + raise HTTPException( + status_code=502, + detail=str(exc), + ) \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/redline.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/redline.py new file mode 100644 index 00000000..ef20e320 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/redline.py @@ -0,0 +1,629 @@ +import shutil +import uuid +from pathlib import Path +from uuid import UUID + +from fastapi import ( + APIRouter, + Depends, + File, + HTTPException, + UploadFile, +) +from fastapi.responses import Response +from pydantic import BaseModel +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.db.session import get_db +from app.models.redline_review import ( + RedlineFinding, + RedlineReview, +) +from app.services.redline_service import ( + analyze_agreements, + create_redline_review, +) +from app.services.superdocs_redline import ( + apply_approved_redlines, +) +from app.superdocs.client import ( + SuperDocsClient, + SuperDocsError, +) + + +router = APIRouter( + prefix="/redline", + tags=["redline"], +) + + +UPLOAD_DIR = ( + Path(__file__).resolve().parents[2] + / "uploads" +) + + +ALLOWED_EXTENSIONS = { + ".docx", +} + + +class FindingDecisionRequest(BaseModel): + decision: str + + +def validate_docx_file( + file: UploadFile, + field_name: str, +) -> None: + if not file.filename: + raise HTTPException( + status_code=400, + detail=f"{field_name} is required.", + ) + + extension = Path( + file.filename + ).suffix.lower() + + if extension not in ALLOWED_EXTENSIONS: + raise HTTPException( + status_code=400, + detail=( + f"{field_name} must be a DOCX file." + ), + ) + + +async def save_upload( + file: UploadFile, + destination: Path, +) -> None: + destination.parent.mkdir( + parents=True, + exist_ok=True, + ) + + with destination.open("wb") as output: + shutil.copyfileobj( + file.file, + output, + ) + + +@router.post("/analyze") +async def analyze_redline( + dealer_file: UploadFile = File(...), + master_file: UploadFile = File(...), + playbook_file: UploadFile = File(...), + db: AsyncSession = Depends(get_db), +): + """ + Analyze uploaded dealer, master, and playbook + documents. + + The dealer agreement is also loaded into a + SuperDocs session so that the same session can + later be used for applying approved redlines + and exporting the updated document. + """ + + # -------------------------------------------------- + # Validate uploaded files + # -------------------------------------------------- + + validate_docx_file( + dealer_file, + "dealer_file", + ) + + validate_docx_file( + master_file, + "master_file", + ) + + validate_docx_file( + playbook_file, + "playbook_file", + ) + + # -------------------------------------------------- + # Create temporary upload directory + # -------------------------------------------------- + + request_id = uuid.uuid4() + + request_upload_dir = ( + UPLOAD_DIR / str(request_id) + ) + + dealer_path = ( + request_upload_dir + / "dealer-agreement.docx" + ) + + master_path = ( + request_upload_dir + / "master-agreement.docx" + ) + + playbook_path = ( + request_upload_dir + / "negotiation-playbook.docx" + ) + + try: + # -------------------------------------------------- + # Save uploaded documents temporarily + # -------------------------------------------------- + + await save_upload( + dealer_file, + dealer_path, + ) + + await save_upload( + master_file, + master_path, + ) + + await save_upload( + playbook_file, + playbook_path, + ) + + # -------------------------------------------------- + # Analyze documents locally + # -------------------------------------------------- + + analysis = analyze_agreements( + master_path=master_path, + dealer_path=dealer_path, + playbook_path=playbook_path, + ) + + # -------------------------------------------------- + # Create one SuperDocs session for this review + # -------------------------------------------------- + + session_id = str(uuid.uuid4()) + + client = SuperDocsClient() + + # -------------------------------------------------- + # Load dealer agreement into SuperDocs + # + # This is important because the same session is + # later used by apply and export. + # -------------------------------------------------- + + try: + dealer_document = ( + await client.upload_document( + file_path=dealer_path, + session_id=session_id, + ) + ) + + except SuperDocsError as exc: + raise HTTPException( + status_code=502, + detail=( + "SuperDocs document upload failed: " + f"{exc}" + ), + ) + + # -------------------------------------------------- + # Create database review + # -------------------------------------------------- + + review = await create_redline_review( + db=db, + session_id=session_id, + analysis=analysis, + ) + + # -------------------------------------------------- + # Return analysis + SuperDocs document information + # -------------------------------------------------- + + return { + "review_id": str(review.id), + "session_id": session_id, + "dealer_document": dealer_document, + **analysis, + } + + except HTTPException: + raise + + except Exception as exc: + raise HTTPException( + status_code=500, + detail=str(exc), + ) + + finally: + # -------------------------------------------------- + # Delete temporary uploaded files + # -------------------------------------------------- + + shutil.rmtree( + request_upload_dir, + ignore_errors=True, + ) + + +@router.get("/reviews/{review_id}") +async def get_redline_review( + review_id: UUID, + db: AsyncSession = Depends(get_db), +): + result = await db.execute( + select(RedlineReview).where( + RedlineReview.id == review_id + ) + ) + + review = result.scalar_one_or_none() + + if review is None: + raise HTTPException( + status_code=404, + detail="Redline review not found", + ) + + result = await db.execute( + select(RedlineFinding) + .where( + RedlineFinding.review_id == review_id + ) + .order_by(RedlineFinding.created_at) + ) + + findings = result.scalars().all() + + return { + "review_id": str(review.id), + "session_id": review.session_id, + "status": review.status, + "created_at": review.created_at, + "updated_at": review.updated_at, + "findings": [ + { + "id": str(finding.id), + "section": finding.section, + "master_text": finding.master_text, + "dealer_text": finding.dealer_text, + "deviation_type": finding.deviation_type, + "similarity": finding.similarity, + "classification": finding.classification, + "reason": finding.reason, + "counter_position": finding.counter_position, + "decision": finding.decision, + } + for finding in findings + ], + } + + +@router.post( + "/reviews/{review_id}/findings/{finding_id}/decision" +) +async def decide_redline_finding( + review_id: UUID, + finding_id: UUID, + payload: FindingDecisionRequest, + db: AsyncSession = Depends(get_db), +): + # -------------------------------------------------- + # Validate decision + # -------------------------------------------------- + + if payload.decision not in { + "approve", + "reject", + }: + raise HTTPException( + status_code=400, + detail=( + "Decision must be either " + "'approve' or 'reject'." + ), + ) + + # -------------------------------------------------- + # Verify review exists + # -------------------------------------------------- + + result = await db.execute( + select(RedlineReview).where( + RedlineReview.id == review_id + ) + ) + + review = result.scalar_one_or_none() + + if review is None: + raise HTTPException( + status_code=404, + detail="Redline review not found", + ) + + # -------------------------------------------------- + # Find finding + # -------------------------------------------------- + + result = await db.execute( + select(RedlineFinding).where( + RedlineFinding.id == finding_id, + RedlineFinding.review_id == review_id, + ) + ) + + finding = result.scalar_one_or_none() + + if finding is None: + raise HTTPException( + status_code=404, + detail="Redline finding not found", + ) + + # -------------------------------------------------- + # Prevent duplicate decisions + # -------------------------------------------------- + + if finding.decision != "pending": + raise HTTPException( + status_code=409, + detail=( + "This finding has already been " + "reviewed." + ), + ) + + finding.decision = payload.decision + + # -------------------------------------------------- + # Check remaining pending findings + # -------------------------------------------------- + + result = await db.execute( + select(RedlineFinding).where( + RedlineFinding.review_id == review_id, + RedlineFinding.decision == "pending", + ) + ) + + remaining = result.scalars().all() + + if remaining: + review.status = "in_review" + else: + review.status = "completed" + + await db.commit() + await db.refresh(finding) + + return { + "review_id": str(review_id), + "finding_id": str(finding_id), + "decision": finding.decision, + "review_status": review.status, + "remaining_findings": len(remaining), + } + + +@router.post( + "/reviews/{review_id}/apply" +) +async def apply_review( + review_id: UUID, + db: AsyncSession = Depends(get_db), +): + # -------------------------------------------------- + # Get review + # -------------------------------------------------- + + result = await db.execute( + select(RedlineReview).where( + RedlineReview.id == review_id + ) + ) + + review = result.scalar_one_or_none() + + if review is None: + raise HTTPException( + status_code=404, + detail="Redline review not found", + ) + + # -------------------------------------------------- + # Prevent duplicate apply + # -------------------------------------------------- + + if review.status == "applied": + raise HTTPException( + status_code=409, + detail=( + "This review has already been applied." + ), + ) + + # -------------------------------------------------- + # Make sure all findings were reviewed + # -------------------------------------------------- + + result = await db.execute( + select(RedlineFinding).where( + RedlineFinding.review_id == review_id, + RedlineFinding.decision == "pending", + ) + ) + + pending = result.scalars().all() + + if pending: + raise HTTPException( + status_code=409, + detail=( + f"{len(pending)} finding(s) " + "still require review." + ), + ) + + # -------------------------------------------------- + # Load findings + # -------------------------------------------------- + + result = await db.execute( + select(RedlineFinding) + .where( + RedlineFinding.review_id == review_id + ) + .order_by(RedlineFinding.created_at) + ) + + findings = result.scalars().all() + + finding_data = [ + { + "id": str(finding.id), + "section": finding.section, + "dealer_text": finding.dealer_text, + "counter_position": finding.counter_position, + "decision": finding.decision, + "classification": finding.classification, + } + for finding in findings + ] + + # -------------------------------------------------- + # Apply approved redlines in SuperDocs + # -------------------------------------------------- + + client = SuperDocsClient() + + try: + result = await apply_approved_redlines( + client=client, + session_id=review.session_id, + findings=finding_data, + ) + + except Exception as exc: + raise HTTPException( + status_code=502, + detail=f"SuperDocs error: {exc}", + ) + + # -------------------------------------------------- + # Mark review as applied + # -------------------------------------------------- + + review.status = "applied" + + await db.commit() + + return { + "review_id": str(review_id), + "status": review.status, + **result, + } + + +@router.post( + "/reviews/{review_id}/export" +) +async def export_review( + review_id: UUID, + db: AsyncSession = Depends(get_db), +): + # -------------------------------------------------- + # Get review + # -------------------------------------------------- + + result = await db.execute( + select(RedlineReview).where( + RedlineReview.id == review_id + ) + ) + + review = result.scalar_one_or_none() + + if review is None: + raise HTTPException( + status_code=404, + detail="Redline review not found", + ) + + # -------------------------------------------------- + # Export only after apply + # -------------------------------------------------- + + if review.status != "applied": + raise HTTPException( + status_code=409, + detail=( + "The review must be applied " + "before it can be exported." + ), + ) + + # -------------------------------------------------- + # Export updated document from SuperDocs + # + # The review.session_id is the same session used + # during /analyze to load the dealer document. + # -------------------------------------------------- + + client = SuperDocsClient() + + filename = ( + "dealer-agreement-redlined.docx" + ) + + try: + document_bytes = ( + await client.export_document( + session_id=review.session_id, + filename=filename, + ) + ) + + except SuperDocsError as exc: + raise HTTPException( + status_code=502, + detail=( + f"SuperDocs export failed: {exc}" + ), + ) + + except Exception as exc: + raise HTTPException( + status_code=502, + detail=( + f"SuperDocs export failed: {exc}" + ), + ) + + # -------------------------------------------------- + # Return DOCX + # -------------------------------------------------- + + return Response( + content=document_bytes, + media_type=( + "application/vnd.openxmlformats-" + "officedocument.wordprocessingml.document" + ), + headers={ + "Content-Disposition": ( + f'attachment; filename="{filename}"' + ), + }, + ) \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/superdocs.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/superdocs.py new file mode 100644 index 00000000..9d7d3272 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/superdocs.py @@ -0,0 +1,32 @@ +from fastapi import APIRouter, HTTPException + +from app.superdocs.client import ( + SuperDocsClient, + SuperDocsError, +) + + +router = APIRouter( + prefix="/superdocs", + tags=["superdocs"], +) + + +@router.get("/health") +async def superdocs_health(): + client = SuperDocsClient() + + try: + sessions = await client.verify_key() + + return { + "status": "ok", + "superdocs": "connected", + "sessions": sessions, + } + + except SuperDocsError as exc: + raise HTTPException( + status_code=502, + detail=str(exc), + ) \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/config.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/config.py new file mode 100644 index 00000000..7bea1ca0 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/config.py @@ -0,0 +1,18 @@ +from functools import lru_cache + +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + superdocs_api_key: str + + model_config = SettingsConfigDict( + env_file=".env", + env_file_encoding="utf-8", + extra="ignore", + ) + + +@lru_cache +def get_settings() -> Settings: + return Settings() \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/db/base.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/db/base.py new file mode 100644 index 00000000..1c2dcc40 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/db/base.py @@ -0,0 +1,5 @@ +from sqlalchemy.orm import DeclarativeBase + + +class Base(DeclarativeBase): + pass \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/db/init_db.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/db/init_db.py new file mode 100644 index 00000000..87e1281c --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/db/init_db.py @@ -0,0 +1,14 @@ +from app.db.base import Base +from app.db.session import engine + +from app.models.redline_review import ( + RedlineReview, + RedlineFinding, +) + + +async def init_db(): + async with engine.begin() as connection: + await connection.run_sync( + Base.metadata.create_all + ) \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/db/session.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/db/session.py new file mode 100644 index 00000000..830045f6 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/db/session.py @@ -0,0 +1,37 @@ +import os + +from dotenv import load_dotenv +from sqlalchemy.ext.asyncio import ( + AsyncSession, + async_sessionmaker, + create_async_engine, +) + + +load_dotenv() + + +DATABASE_URL = os.getenv("DATABASE_URL") + +if not DATABASE_URL: + raise RuntimeError( + "DATABASE_URL environment variable is not set" + ) + + +engine = create_async_engine( + DATABASE_URL, + echo=False, +) + + +AsyncSessionLocal = async_sessionmaker( + engine, + class_=AsyncSession, + expire_on_commit=False, +) + + +async def get_db(): + async with AsyncSessionLocal() as session: + yield session \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/main.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/main.py new file mode 100644 index 00000000..7630d171 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/main.py @@ -0,0 +1,33 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from app.api.poc import router as poc_router +from app.api.redline import router as redline_router +from app.api.superdocs import router as superdocs_router + + +app = FastAPI( + title="Dealer Agreement Redline", + version="1.0.0", +) + +app.add_middleware( + CORSMiddleware, + allow_origins=[ + "http://localhost:5173", + ], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +app.include_router(poc_router) +app.include_router(redline_router) +app.include_router(superdocs_router) + + +@app.get("/health") +async def health(): + return { + "status": "ok", + } \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/models/redline_review.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/models/redline_review.py new file mode 100644 index 00000000..f70b3df0 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/models/redline_review.py @@ -0,0 +1,114 @@ +from datetime import datetime +from uuid import UUID, uuid4 + +from sqlalchemy import DateTime, ForeignKey, String, Text, func +from sqlalchemy.dialects.postgresql import UUID as PG_UUID +from sqlalchemy.orm import Mapped, mapped_column + +from app.db.base import Base + + +class RedlineReview(Base): + __tablename__ = "redline_reviews" + + id: Mapped[UUID] = mapped_column( + PG_UUID(as_uuid=True), + primary_key=True, + default=uuid4, + ) + + session_id: Mapped[str] = mapped_column( + String(255), + nullable=False, + index=True, + ) + + status: Mapped[str] = mapped_column( + String(50), + nullable=False, + default="pending", + ) + + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + server_default=func.now(), + nullable=False, + ) + + updated_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + server_default=func.now(), + onupdate=func.now(), + nullable=False, + ) + + +class RedlineFinding(Base): + __tablename__ = "redline_findings" + + id: Mapped[UUID] = mapped_column( + PG_UUID(as_uuid=True), + primary_key=True, + default=uuid4, + ) + + review_id: Mapped[UUID] = mapped_column( + PG_UUID(as_uuid=True), + ForeignKey( + "redline_reviews.id", + ondelete="CASCADE", + ), + nullable=False, + index=True, + ) + + section: Mapped[str] = mapped_column( + String(255), + nullable=False, + ) + + master_text: Mapped[str | None] = mapped_column( + Text, + nullable=True, + ) + + dealer_text: Mapped[str | None] = mapped_column( + Text, + nullable=True, + ) + + deviation_type: Mapped[str] = mapped_column( + String(50), + nullable=False, + ) + + similarity: Mapped[float | None] = mapped_column( + nullable=True, + ) + + classification: Mapped[str] = mapped_column( + String(50), + nullable=False, + ) + + reason: Mapped[str] = mapped_column( + Text, + nullable=False, + ) + + counter_position: Mapped[str] = mapped_column( + Text, + nullable=False, + ) + + decision: Mapped[str] = mapped_column( + String(50), + nullable=False, + default="pending", + ) + + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + server_default=func.now(), + nullable=False, + ) \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/__init__.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/agreement_parser.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/agreement_parser.py new file mode 100644 index 00000000..5a70343b --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/agreement_parser.py @@ -0,0 +1,44 @@ +from pathlib import Path + +from docx import Document + + +def extract_docx_sections(file_path: str | Path) -> list[dict]: + path = Path(file_path) + + if not path.exists(): + raise FileNotFoundError( + f"Document not found: {path}" + ) + + document = Document(path) + + sections: list[dict] = [] + current_section: dict | None = None + + for paragraph in document.paragraphs: + text = paragraph.text.strip() + + if not text: + continue + + # Our test documents use Heading 2 for sections. + if paragraph.style.name.startswith("Heading 2"): + if current_section: + sections.append(current_section) + + current_section = { + "section": text, + "text": "", + } + + elif current_section: + if current_section["text"]: + current_section["text"] += "\n" + + current_section["text"] += text + + if current_section: + sections.append(current_section) + + return sections \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/deviation_analyzer.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/deviation_analyzer.py new file mode 100644 index 00000000..f8a730bd --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/deviation_analyzer.py @@ -0,0 +1,69 @@ +from difflib import SequenceMatcher + + +def normalize(text: str) -> str: + return " ".join(text.lower().split()) + + +def analyze_deviations( + master_sections: list[dict], + dealer_sections: list[dict], +) -> list[dict]: + + master_by_section = { + item["section"]: item["text"] + for item in master_sections + } + + dealer_by_section = { + item["section"]: item["text"] + for item in dealer_sections + } + + deviations: list[dict] = [] + + for section, dealer_text in dealer_by_section.items(): + master_text = master_by_section.get(section) + + if master_text is None: + deviations.append( + { + "section": section, + "master_text": None, + "dealer_text": dealer_text, + "deviation_type": "added_section", + } + ) + continue + + if normalize(master_text) == normalize(dealer_text): + continue + + similarity = SequenceMatcher( + None, + normalize(master_text), + normalize(dealer_text), + ).ratio() + + deviations.append( + { + "section": section, + "master_text": master_text, + "dealer_text": dealer_text, + "deviation_type": "modified_section", + "similarity": round(similarity, 3), + } + ) + + for section, master_text in master_by_section.items(): + if section not in dealer_by_section: + deviations.append( + { + "section": section, + "master_text": master_text, + "dealer_text": None, + "deviation_type": "removed_section", + } + ) + + return deviations \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/playbook_classifier.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/playbook_classifier.py new file mode 100644 index 00000000..07ba1eb3 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/playbook_classifier.py @@ -0,0 +1,210 @@ +import re + + +def extract_section_number(section: str) -> str | None: + """ + Extract the numeric section identifier. + + Examples: + Section 7.1 — AgreementTerm -> 7.1 + Section 9.3— Territory -> 9.3 + Section 12.1 — Termination -> 12.1 + """ + match = re.search( + r"section\s+(\d+(?:\.\d+)*)", + section, + re.IGNORECASE, + ) + + if not match: + return None + + return match.group(1) + + +def classify_deviation( + deviation: dict, +) -> dict: + + section = deviation["section"] + dealer_text = ( + deviation.get("dealer_text") or "" + ).lower() + + section_number = extract_section_number(section) + + classification = "ESCALATE" + reason = "The change requires human review." + counter_position = ( + "Maintain the approved master agreement language." + ) + + # -------------------------------------------------- + # Section 4.2 — Payment Terms + # -------------------------------------------------- + + if section_number == "4.2": + + match = re.search( + r"(\d+)\s*days", + dealer_text, + ) + + if match: + days = int(match.group(1)) + + if days <= 45: + classification = "PRE-APPROVED" + reason = ( + "The playbook permits payment terms " + "up to 45 days." + ) + counter_position = ( + f"Accept payment terms of {days} days." + ) + + else: + classification = "ESCALATE" + reason = ( + "The dealer requested payment terms " + "above the 45-day pre-approved limit." + ) + counter_position = ( + "Maintain the standard 30-day payment " + "term or escalate the requested " + f"{days}-day term for approval." + ) + + # -------------------------------------------------- + # Section 7.1 — Agreement Term + # -------------------------------------------------- + + elif section_number == "7.1": + + year_match = re.search( + r"(\d+)\s*years?", + dealer_text, + ) + + if year_match: + years = int(year_match.group(1)) + + else: + word_to_number = { + "one": 1, + "two": 2, + "three": 3, + "four": 4, + "five": 5, + "six": 6, + "seven": 7, + "eight": 8, + "nine": 9, + "ten": 10, + "eleven": 11, + "twelve": 12, + "thirteen": 13, + "fourteen": 14, + "fifteen": 15, + } + + years = None + + for word, value in word_to_number.items(): + if re.search( + rf"\b{word}\s+years?\b", + dealer_text, + ): + years = value + break + + if years is not None: + + if years <= 7: + classification = "PRE-APPROVED" + + reason = ( + "The playbook permits agreement terms " + "up to 7 years." + ) + + counter_position = ( + f"Accept the {years}-year agreement term." + ) + + else: + classification = "REFUSE" + + reason = ( + "The requested agreement term exceeds " + "the 7-year playbook limit." + ) + + counter_position = ( + "Maintain the approved five-year " + "initial agreement term." + ) + + # -------------------------------------------------- + # Section 9.3 — Territory + # -------------------------------------------------- + + elif section_number == "9.3": + + if ( + "outside" in dealer_text + or "expand" in dealer_text + ): + classification = "ESCALATE" + reason = ( + "The dealer requests an expansion " + "of territory rights." + ) + counter_position = ( + "Maintain the assigned territory unless " + "the requested expansion is approved " + "through escalation." + ) + + # -------------------------------------------------- + # Section 12.1 — Termination + # -------------------------------------------------- + + elif section_number == "12.1": + + match = re.search( + r"(\d+)\s*days", + dealer_text, + ) + + if match: + days = int(match.group(1)) + + if days > 60: + classification = "REFUSE" + reason = ( + "The requested termination notice " + "period exceeds the 60-day limit." + ) + counter_position = ( + "Maintain the approved 30-day " + "termination notice period." + ) + + else: + classification = "ESCALATE" + reason = ( + "The termination notice change " + "requires review." + ) + counter_position = ( + "Review the requested termination " + "notice period before acceptance." + ) + + return { + **deviation, + "classification": classification, + "reason": reason, + "counter_position": counter_position, + } \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/redline_service.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/redline_service.py new file mode 100644 index 00000000..b26ac613 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/redline_service.py @@ -0,0 +1,115 @@ +from pathlib import Path + +from app.services.agreement_parser import ( + extract_docx_sections, +) +from app.services.deviation_analyzer import ( + analyze_deviations, +) +from app.services.playbook_classifier import ( + classify_deviation, +) + +from uuid import UUID + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models.redline_review import ( + RedlineFinding, + RedlineReview, +) + + +def analyze_agreements( + master_path: str | Path, + dealer_path: str | Path, + playbook_path: str | Path, +) -> dict: + + master_sections = extract_docx_sections( + master_path + ) + + dealer_sections = extract_docx_sections( + dealer_path + ) + + playbook_sections = extract_docx_sections( + playbook_path + ) + + deviations = analyze_deviations( + master_sections=master_sections, + dealer_sections=dealer_sections, + ) + + classified = [ + classify_deviation(deviation) + for deviation in deviations + ] + + return { + "master_sections": master_sections, + "dealer_sections": dealer_sections, + "playbook_sections": playbook_sections, + "deviations": classified, + "summary": { + "total": len(classified), + "pre_approved": sum( + 1 + for item in classified + if item["classification"] + == "PRE-APPROVED" + ), + "escalate": sum( + 1 + for item in classified + if item["classification"] + == "ESCALATE" + ), + "refuse": sum( + 1 + for item in classified + if item["classification"] + == "REFUSE" + ), + }, + } + +async def create_redline_review( + db: AsyncSession, + session_id: str, + analysis: dict, +) -> RedlineReview: + + review = RedlineReview( + session_id=session_id, + status="pending", + ) + + db.add(review) + + await db.flush() + + for deviation in analysis["deviations"]: + finding = RedlineFinding( + review_id=review.id, + section=deviation["section"], + master_text=deviation.get("master_text"), + dealer_text=deviation.get("dealer_text"), + deviation_type=deviation["deviation_type"], + similarity=deviation.get("similarity"), + classification=deviation["classification"], + reason=deviation["reason"], + counter_position=deviation["counter_position"], + decision="pending", + ) + + db.add(finding) + + await db.commit() + + await db.refresh(review) + + return review + diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/superdocs_redline.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/superdocs_redline.py new file mode 100644 index 00000000..afa662a5 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/superdocs_redline.py @@ -0,0 +1,64 @@ +from app.superdocs.client import SuperDocsClient + + +async def apply_approved_redlines( + client: SuperDocsClient, + session_id: str, + findings: list[dict], +): + approved = [ + finding + for finding in findings + if finding["decision"] == "approve" + ] + + if not approved: + return { + "applied": False, + "message": "No approved findings to apply.", + } + + instructions = [] + + for finding in approved: + instructions.append( + f""" +Section: {finding["section"]} + +Current dealer language: +{finding["dealer_text"]} + +Approved counter-position: +{finding["counter_position"]} + +Human decision: +APPROVED +""".strip() + ) + + message = f""" +Update the active dealer agreement using the +human-approved redline decisions below. + +IMPORTANT: +- Only modify the sections explicitly listed below. +- Preserve all other document content. +- Do not modify formatting unnecessarily. +- Apply the approved counter-position as the + replacement language. +- Do not invent additional changes. + +Approved redlines: + +{"\n\n---\n\n".join(instructions)} +""".strip() + + response = await client.chat( + session_id=session_id, + message=message, + ) + + return { + "applied": True, + "chat_response": response, + } \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/superdocs/__init__.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/superdocs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/superdocs/client.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/superdocs/client.py new file mode 100644 index 00000000..982c9ca5 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/superdocs/client.py @@ -0,0 +1,289 @@ +from pathlib import Path +from typing import Any + +import httpx + +from app.config import get_settings + + +class SuperDocsError(Exception): + """Raised when the SuperDocs API returns an error.""" + + +class SuperDocsClient: + BASE_URL = "https://api.superdocs.app/v1" + + def __init__(self) -> None: + settings = get_settings() + + self.api_key = settings.superdocs_api_key + + self.headers = { + "Authorization": f"Bearer {self.api_key}", + } + + async def verify_key(self) -> Any: + async with httpx.AsyncClient( + base_url=self.BASE_URL, + timeout=30.0, + ) as client: + response = await client.get( + "/sessions", + headers=self.headers, + ) + + if response.is_error: + raise SuperDocsError( + f"SuperDocs authentication failed: " + f"{response.status_code} {response.text}" + ) + + return response.json() + + async def upload_document( + self, + file_path: str | Path, + session_id: str, + ) -> dict[str, Any]: + + path = Path(file_path) + + if not path.exists(): + raise FileNotFoundError( + f"Document not found: {path}" + ) + + async with httpx.AsyncClient( + base_url=self.BASE_URL, + timeout=120.0, + ) as client: + + with path.open("rb") as file: + response = await client.post( + "/documents/upload", + headers=self.headers, + files={ + "file": ( + path.name, + file, + self._content_type(path), + ) + }, + data={ + "session_id": session_id, + }, + ) + + self._raise_for_error(response) + + return response.json() + + async def upload_attachment( + self, + file_path: str | Path, + session_id: str, + ) -> dict[str, Any]: + + path = Path(file_path) + + if not path.exists(): + raise FileNotFoundError( + f"Attachment not found: {path}" + ) + + async with httpx.AsyncClient( + base_url=self.BASE_URL, + timeout=120.0, + ) as client: + + with path.open("rb") as file: + response = await client.post( + "/attachments/upload", + headers=self.headers, + files={ + "file": ( + path.name, + file, + self._content_type(path), + ) + }, + data={ + "session_id": session_id, + }, + ) + + self._raise_for_error(response) + + return response.json() + + async def get_job( + self, + job_id: str, + ) -> dict[str, Any]: + + async with httpx.AsyncClient( + base_url=self.BASE_URL, + timeout=30.0, + ) as client: + + response = await client.get( + f"/jobs/{job_id}", + headers=self.headers, + ) + + self._raise_for_error(response) + + return response.json() + + async def chat( + self, + *, + session_id: str, + message: str, + document_html: str | None = None, + document_id: str | None = None, + approval_mode: str | None = None, + response_mode: str | None = None, + ) -> dict[str, Any]: + + payload: dict[str, Any] = { + "message": message, + "session_id": session_id, + } + + if document_html is not None: + payload["document_html"] = document_html + + if document_id is not None: + payload["document_id"] = document_id + + if approval_mode is not None: + payload["approval_mode"] = approval_mode + + if response_mode is not None: + payload["response_mode"] = response_mode + + async with httpx.AsyncClient( + base_url=self.BASE_URL, + timeout=180.0, + ) as client: + + response = await client.post( + "/chat", + headers={ + **self.headers, + "Content-Type": "application/json", + }, + json=payload, + ) + + self._raise_for_error(response) + + return response.json() + + async def approve_change( + self, + *, + session_id: str, + job_id: str, + change_id: str, + approved: bool, + feedback: str | None = None, + ) -> dict[str, Any]: + + payload: dict[str, Any] = { + "job_id": job_id, + "change_id": change_id, + "approved": approved, + } + + if feedback: + payload["feedback"] = feedback + + async with httpx.AsyncClient( + base_url=self.BASE_URL, + timeout=120.0, + ) as client: + + response = await client.post( + f"/chat/{session_id}/approve", + headers={ + **self.headers, + "Content-Type": "application/json", + }, + json=payload, + ) + + self._raise_for_error(response) + + return response.json() + + async def export_document( + self, + *, + session_id: str, + filename: str, + ) -> bytes: + + payload = { + "session_id": session_id, + "format": "docx", + "options": { + "filename": filename, + }, + } + + async with httpx.AsyncClient( + base_url=self.BASE_URL, + timeout=180.0, + ) as client: + + response = await client.post( + "/documents/export", + headers={ + **self.headers, + "Content-Type": "application/json", + }, + json=payload, + ) + + self._raise_for_error(response) + + return response.content + + @staticmethod + def _content_type(path: Path) -> str: + suffix = path.suffix.lower() + + mapping = { + ".pdf": "application/pdf", + ".docx": ( + "application/vnd.openxmlformats-" + "officedocument.wordprocessingml.document" + ), + ".doc": "application/msword", + ".txt": "text/plain", + ".html": "text/html", + ".htm": "text/html", + ".md": "text/markdown", + ".rtf": "application/rtf", + } + + return mapping.get( + suffix, + "application/octet-stream", + ) + + @staticmethod + def _raise_for_error( + response: httpx.Response, + ) -> None: + + if response.is_success: + return + + raise SuperDocsError( + f"SuperDocs API error " + f"{response.status_code}: " + f"{response.text}" + ) \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/backend/requirements.txt b/use-cases/pritesh2564u/dealer-agreement-redline/backend/requirements.txt new file mode 100644 index 00000000..6f69f070 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/backend/requirements.txt @@ -0,0 +1,9 @@ +fastapi +uvicorn[standard] +httpx +pydantic +pydantic-settings +python-dotenv +python-multipart +sqlalchemy +asyncpg \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/.gitignore b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/README.md b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/README.md new file mode 100644 index 00000000..c3001356 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/README.md @@ -0,0 +1,75 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) + +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) + +``` diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/eslint.config.js b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/eslint.config.js new file mode 100644 index 00000000..ef614d25 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/eslint.config.js @@ -0,0 +1,22 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + globals: globals.browser, + }, + }, +]) diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/index.html b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/index.html new file mode 100644 index 00000000..0fca6f04 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + frontend + + +
+ + + diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/package-lock.json b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/package-lock.json new file mode 100644 index 00000000..f6aebf6b --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/package-lock.json @@ -0,0 +1,3561 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@tailwindcss/vite": "^4.3.3", + "axios": "^1.19.0", + "lucide-react": "^1.33.0", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "tailwindcss": "^4.3.3" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^24.13.3", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.4", + "eslint": "^10.8.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.3", + "globals": "^17.7.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.65.0", + "vite": "^8.2.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.146.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.146.0.tgz", + "integrity": "sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.5.tgz", + "integrity": "sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.5.tgz", + "integrity": "sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.5.tgz", + "integrity": "sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.5.tgz", + "integrity": "sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.5.tgz", + "integrity": "sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.5.tgz", + "integrity": "sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.5.tgz", + "integrity": "sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.5.tgz", + "integrity": "sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.5.tgz", + "integrity": "sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.5.tgz", + "integrity": "sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.5.tgz", + "integrity": "sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.5.tgz", + "integrity": "sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.5.tgz", + "integrity": "sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.5.tgz", + "integrity": "sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.5.tgz", + "integrity": "sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", + "integrity": "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/type-utils": "8.67.0", + "@typescript-eslint/utils": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.67.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz", + "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", + "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.67.0", + "@typescript-eslint/types": "^8.67.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", + "integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", + "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", + "integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/utils": "8.67.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz", + "integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", + "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.67.0", + "@typescript-eslint/tsconfig-utils": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", + "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", + "integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.0.tgz", + "integrity": "sha512-qd2BzUBehkov86WFhg0JkEFEYyCLG9uPCe6qWTY/kRlss9OvJrOF2UbIWT7p+8IzZHkEu0DNGHc4HSv+JdDLsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": "^0.145.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "oxc-transform-react": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.15", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.15.tgz", + "integrity": "sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.411", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.411.tgz", + "integrity": "sha512-gglkxzokjHfawpGxq75XdBV2/l3BAPzrsMs70qgaZdTW5rpV1tC4MdgJVP9fN126bODA4ZJQkn1wryEzJyQXIg==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.8.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.1.tgz", + "integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.4.tgz", + "integrity": "sha512-7bqTKz7T0r+HKWFarNXByDE9/5+73wI2ru+M3zuqGbR7s/b/5/pQJXZoufWlrngqGqoZto73ZkGumCdLxk+4rw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.11.0.tgz", + "integrity": "sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.33.0.tgz", + "integrity": "sha512-MTRwMy0ZlL8Ur/vOAiJ9XGHE+kFPC7brq6MxAm0GiGXEBj0qy0jA/pG4N675oSzciO/UCdX8T+5yUQdmDeTLxg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/rolldown": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.5.tgz", + "integrity": "sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.146.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.5", + "@rolldown/binding-android-arm64": "1.2.5", + "@rolldown/binding-darwin-arm64": "1.2.5", + "@rolldown/binding-darwin-x64": "1.2.5", + "@rolldown/binding-freebsd-x64": "1.2.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.5", + "@rolldown/binding-linux-arm64-gnu": "1.2.5", + "@rolldown/binding-linux-arm64-musl": "1.2.5", + "@rolldown/binding-linux-ppc64-gnu": "1.2.5", + "@rolldown/binding-linux-s390x-gnu": "1.2.5", + "@rolldown/binding-linux-x64-gnu": "1.2.5", + "@rolldown/binding-linux-x64-musl": "1.2.5", + "@rolldown/binding-openharmony-arm64": "1.2.5", + "@rolldown/binding-win32-arm64-msvc": "1.2.5", + "@rolldown/binding-win32-x64-msvc": "1.2.5" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.67.0.tgz", + "integrity": "sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.67.0", + "@typescript-eslint/parser": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/utils": "8.67.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", + "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.26", + "rolldown": "~1.2.4", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.4.0 || ^0.5.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/vite/node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/package.json b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/package.json new file mode 100644 index 00000000..0f39a215 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/package.json @@ -0,0 +1,34 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@tailwindcss/vite": "^4.3.3", + "axios": "^1.19.0", + "lucide-react": "^1.33.0", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "tailwindcss": "^4.3.3" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^24.13.3", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.4", + "eslint": "^10.8.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.3", + "globals": "^17.7.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.65.0", + "vite": "^8.2.0" + } +} diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/public/favicon.svg b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/public/favicon.svg new file mode 100644 index 00000000..6893eb13 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/public/icons.svg b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/public/icons.svg new file mode 100644 index 00000000..e9522193 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/App.css b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/App.css new file mode 100644 index 00000000..8c99abd3 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/App.css @@ -0,0 +1,3 @@ +#root { + min-height: 100vh; +} \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/App.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/App.tsx new file mode 100644 index 00000000..edf21e9d --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/App.tsx @@ -0,0 +1,293 @@ +import { useState } from "react"; + +import { + analyzeRedline, + applyReview, + decideFinding, + exportReview, + getRedlineReview, +} from "./api/redline"; + +import type { AnalyzeResponse, RedlineReview } from "./types/redline"; + +import ActionBar from "./components/ActionBar"; +import EmptyState from "./components/EmptyState"; +import FindingList from "./components/FindingList"; +import Header from "./components/Header"; +import LoadingState from "./components/LoadingState"; +import ReviewProgress from "./components/ReviewProgress"; +import ReviewSummary from "./components/ReviewSummary"; +import StatusBadge from "./components/StatusBadge"; +import SuccessMessage from "./components/SuccessMessage"; + +function App() { + const [analysis, setAnalysis] = useState(null); + + const [review, setReview] = useState(null); + + const [loading, setLoading] = useState(false); + + const [processingFindingId, setProcessingFindingId] = useState< + string | null + >(null); + + const [applying, setApplying] = useState(false); + + const [exporting, setExporting] = useState(false); + + const [error, setError] = useState(null); + + const [success, setSuccess] = useState(null); + + async function handleAnalyze( + dealerFile: File, + masterFile: File, + playbookFile: File, + ) { + setLoading(true); + setError(null); + setSuccess(null); + + try { + const result = await analyzeRedline( + dealerFile, + masterFile, + playbookFile, + ); + + setAnalysis(result); + + const reviewResult = await getRedlineReview(result.review_id); + + setReview(reviewResult); + + setSuccess("Agreement analysis completed successfully."); + } catch (err) { + console.error(err); + + setError( + err instanceof Error + ? err.message + : "Failed to analyze the agreement.", + ); + } finally { + setLoading(false); + } + } + + async function handleDecision( + findingId: string, + decision: "approve" | "reject", + ) { + if (!review) { + return; + } + + setProcessingFindingId(findingId); + setError(null); + setSuccess(null); + + try { + const result = await decideFinding( + review.review_id, + findingId, + decision, + ); + + setReview((current) => { + if (!current) { + return current; + } + + return { + ...current, + status: result.review_status, + findings: current.findings.map((finding) => + finding.id === findingId + ? { + ...finding, + decision: result.decision, + } + : finding, + ), + }; + }); + + setSuccess( + decision === "approve" + ? "Finding approved successfully." + : "Finding rejected successfully.", + ); + } catch (err) { + console.error(err); + + setError( + err instanceof Error + ? err.message + : "Failed to save the finding decision.", + ); + } finally { + setProcessingFindingId(null); + } + } + + async function handleApply() { + if (!review) { + return; + } + + if (review.status !== "completed") { + return; + } + + setApplying(true); + setError(null); + setSuccess(null); + + try { + const result = await applyReview(review.review_id); + + setReview((current) => { + if (!current) { + return current; + } + + return { + ...current, + status: result.status, + }; + }); + + setSuccess("Approved changes have been applied successfully."); + } catch (err) { + console.error(err); + + setError( + err instanceof Error + ? err.message + : "Failed to apply the approved changes.", + ); + } finally { + setApplying(false); + } + } + + async function handleExport() { + if (!review) { + return; + } + + if (review.status !== "applied") { + return; + } + + setExporting(true); + setError(null); + setSuccess(null); + + try { + const blob = await exportReview(review.review_id); + + const url = window.URL.createObjectURL(blob); + + const link = document.createElement("a"); + + link.href = url; + link.download = "dealer-agreement-redlined.docx"; + + document.body.appendChild(link); + + link.click(); + + link.remove(); + + window.URL.revokeObjectURL(url); + + setSuccess("Redlined DOCX exported successfully."); + } catch (err) { + console.error(err); + + setError( + err instanceof Error + ? err.message + : "Failed to export the document.", + ); + } finally { + setExporting(false); + } + } + + const reviewedCount = + review?.findings.filter((finding) => finding.decision !== "pending") + .length ?? 0; + + return ( +
+
+ +
+ {success && ( + setSuccess(null)} + /> + )} + + {error && ( +
+ {error} +
+ )} + + {!analysis && !loading && ( + + )} + + {loading && } + + {analysis && review && !loading && ( +
+
+
+
+

+ Review +

+ +

+ Agreement Findings +

+
+ + +
+
+ + + + + + + + +
+ )} +
+
+ ); +} + +export default App; diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/api/redline.ts b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/api/redline.ts new file mode 100644 index 00000000..17e9bc91 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/api/redline.ts @@ -0,0 +1,116 @@ +import type { + AnalyzeResponse, + ApplyResponse, + DecisionResponse, + RedlineReview, +} from "../types/redline"; + +const API_BASE_URL = "http://localhost:8000"; + +async function handleResponse(response: Response): Promise { + if (!response.ok) { + let message = `Request failed with status ${response.status}`; + + try { + const data = await response.json(); + + if (data.detail) { + message = data.detail; + } + } catch { + // Keep default error message. + } + + throw new Error(message); + } + + return response.json() as Promise; +} + +export async function analyzeRedline( + dealerFile: File, + masterFile: File, + playbookFile: File, +): Promise { + const formData = new FormData(); + + formData.append("dealer_file", dealerFile); + + formData.append("master_file", masterFile); + + formData.append("playbook_file", playbookFile); + + const response = await fetch(`${API_BASE_URL}/redline/analyze`, { + method: "POST", + body: formData, + }); + + return handleResponse(response); +} + +export async function getRedlineReview( + reviewId: string, +): Promise { + const response = await fetch(`${API_BASE_URL}/redline/reviews/${reviewId}`); + + return handleResponse(response); +} + +export async function decideFinding( + reviewId: string, + findingId: string, + decision: "approve" | "reject", +): Promise { + const response = await fetch( + `${API_BASE_URL}/redline/reviews/${reviewId}/findings/${findingId}/decision`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + decision, + }), + }, + ); + + return handleResponse(response); +} + +export async function applyReview(reviewId: string): Promise { + const response = await fetch( + `${API_BASE_URL}/redline/reviews/${reviewId}/apply`, + { + method: "POST", + }, + ); + + return handleResponse(response); +} + +export async function exportReview(reviewId: string): Promise { + const response = await fetch( + `${API_BASE_URL}/redline/reviews/${reviewId}/export`, + { + method: "POST", + }, + ); + + if (!response.ok) { + let message = "Failed to export the document."; + + try { + const data = await response.json(); + + if (data.detail) { + message = data.detail; + } + } catch { + // Response was not JSON. + } + + throw new Error(message); + } + + return response.blob(); +} diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ActionBar.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ActionBar.tsx new file mode 100644 index 00000000..009d0db7 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ActionBar.tsx @@ -0,0 +1,61 @@ +interface ActionBarProps { + reviewStatus: string; + applying: boolean; + exporting: boolean; + onApply: () => void; + onExport: () => void; +} + +function ActionBar({ + reviewStatus, + applying, + exporting, + onApply, + onExport, +}: ActionBarProps) { + const canApply = reviewStatus === "completed"; + + const canExport = reviewStatus === "applied"; + + return ( +
+
+
+

+ Review Actions +

+ +

+ {canApply + ? "All findings have been reviewed." + : canExport + ? "Changes have been applied." + : "Review all findings before applying changes."} +

+
+ +
+ + + +
+
+
+ ); +} + +export default ActionBar; diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ClassificationBadge.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ClassificationBadge.tsx new file mode 100644 index 00000000..30774250 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ClassificationBadge.tsx @@ -0,0 +1,25 @@ +import type { Classification } from "../types/redline"; + +interface ClassificationBadgeProps { + classification: Classification; +} + +const classes: Record = { + "PRE-APPROVED": "bg-emerald-50 text-emerald-700 border-emerald-200", + + ESCALATE: "bg-amber-50 text-amber-700 border-amber-200", + + REFUSE: "bg-red-50 text-red-700 border-red-200", +}; + +function ClassificationBadge({ classification }: ClassificationBadgeProps) { + return ( + + {classification} + + ); +} + +export default ClassificationBadge; diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ComparisonBlock.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ComparisonBlock.tsx new file mode 100644 index 00000000..80fe972f --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ComparisonBlock.tsx @@ -0,0 +1,18 @@ +interface ComparisonBlockProps { + label: string; + text: string; +} + +function ComparisonBlock({ label, text }: ComparisonBlockProps) { + return ( +
+

+ {label} +

+ +

{text}

+
+ ); +} + +export default ComparisonBlock; diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/DocumentUpload.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/DocumentUpload.tsx new file mode 100644 index 00000000..327c0aae --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/DocumentUpload.tsx @@ -0,0 +1,155 @@ +import { useState } from "react"; + +interface DocumentUploadProps { + onAnalyze: (dealerFile: File, masterFile: File, playbookFile: File) => void; + + loading: boolean; +} + +interface FilePickerProps { + label: string; + description: string; + file: File | null; + onChange: (file: File | null) => void; + disabled: boolean; +} + +function FilePicker({ + label, + description, + file, + onChange, + disabled, +}: FilePickerProps) { + function handleChange(event: React.ChangeEvent) { + const selectedFile = event.target.files?.[0] ?? null; + + onChange(selectedFile); + } + + return ( +
+
+

{label}

+ +

{description}

+
+ + +
+ ); +} + +function DocumentUpload({ onAnalyze, loading }: DocumentUploadProps) { + const [dealerFile, setDealerFile] = useState(null); + + const [masterFile, setMasterFile] = useState(null); + + const [playbookFile, setPlaybookFile] = useState(null); + + const canAnalyze = + dealerFile !== null && + masterFile !== null && + playbookFile !== null && + !loading; + + function handleAnalyze() { + if (!dealerFile || !masterFile || !playbookFile) { + return; + } + + onAnalyze(dealerFile, masterFile, playbookFile); + } + + return ( +
+
+

+ Document Setup +

+ +

+ Upload agreement documents +

+ +

+ Upload the dealer agreement, approved master agreement, and + negotiation playbook to begin the redline review. +

+
+ +
+ + + + + +
+ +
+ +
+
+ ); +} + +export default DocumentUpload; diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/EmptyState.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/EmptyState.tsx new file mode 100644 index 00000000..4ccf7068 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/EmptyState.tsx @@ -0,0 +1,28 @@ +import DocumentUpload from "./DocumentUpload"; + +interface EmptyStateProps { + loading: boolean; + + onAnalyze: (dealerFile: File, masterFile: File, playbookFile: File) => void; +} + +function EmptyState({ loading, onAnalyze }: EmptyStateProps) { + return ( +
+
+

+ Start a new redline review +

+ +

+ Provide the three documents required to compare and review + the dealer agreement. +

+
+ + +
+ ); +} + +export default EmptyState; diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/FindingCard.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/FindingCard.tsx new file mode 100644 index 00000000..e88b9312 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/FindingCard.tsx @@ -0,0 +1,184 @@ +import { Check, ChevronDown, ChevronRight, X } from "lucide-react"; +import { useState } from "react"; + +import type { FindingDecision, RedlineFinding } from "../types/redline"; + +import ClassificationBadge from "./ClassificationBadge"; +import ComparisonBlock from "./ComparisonBlock"; + +interface FindingCardProps { + finding: RedlineFinding; + index: number; + loading?: boolean; + onDecision?: (decision: "approve" | "reject") => void; +} + +function FindingCard({ + finding, + index, + loading = false, + onDecision, +}: FindingCardProps) { + const [expanded, setExpanded] = useState( + index === 0 && finding.decision === "pending", + ); + + const decision: FindingDecision = finding.decision; + + const reviewed = decision !== "pending"; + + return ( +
+ {/* Collapsed header */} + + + {/* Expanded content */} + {expanded && ( +
+
+ + + +
+ +
+

+ Reason +

+ +

+ {finding.reason} +

+
+ +
+

+ Counter-position +

+ +

+ {finding.counter_position} +

+
+ + {!reviewed && ( +
+

+ Review this finding before applying changes. +

+ +
+ + + +
+
+ )} + + {reviewed && ( +
+

+ {decision === "approve" ? ( + <> + + Finding approved + + ) : ( + <> + + Finding rejected + + )} +

+
+ )} +
+ )} +
+ ); +} + +export default FindingCard; diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/FindingList.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/FindingList.tsx new file mode 100644 index 00000000..13a12d50 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/FindingList.tsx @@ -0,0 +1,52 @@ +import type { RedlineFinding } from "../types/redline"; + +import FindingCard from "./FindingCard"; + +interface FindingListProps { + findings: RedlineFinding[]; + processingFindingId?: string | null; + onDecision: (findingId: string, decision: "approve" | "reject") => void; +} + +function FindingList({ + findings, + processingFindingId, + onDecision, +}: FindingListProps) { + return ( +
+
+
+

+ Findings +

+ +

+ Expand a finding to review its details. +

+
+ + + {findings.length}{" "} + {findings.length === 1 ? "finding" : "findings"} + +
+ +
+ {findings.map((finding, index) => ( + + onDecision(finding.id, decision) + } + /> + ))} +
+
+ ); +} + +export default FindingList; diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/Header.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/Header.tsx new file mode 100644 index 00000000..7c751b51 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/Header.tsx @@ -0,0 +1,27 @@ +import { FileText } from "lucide-react"; + +function Header() { + return ( +
+
+
+
+ +
+ +
+

+ Dealer Agreement Redline +

+ +

+ Review and approve contract changes +

+
+
+
+
+ ); +} + +export default Header; diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/LoadingState.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/LoadingState.tsx new file mode 100644 index 00000000..18ba3d99 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/LoadingState.tsx @@ -0,0 +1,18 @@ +function LoadingState() { + return ( +
+
+ +

+ Analyzing agreement +

+ +

+ Comparing the dealer agreement against the master agreement and + playbook. +

+
+ ); +} + +export default LoadingState; diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ReviewProgress.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ReviewProgress.tsx new file mode 100644 index 00000000..94700d72 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ReviewProgress.tsx @@ -0,0 +1,39 @@ +interface ReviewProgressProps { + total: number; + reviewed: number; +} + +function ReviewProgress({ total, reviewed }: ReviewProgressProps) { + const percentage = total === 0 ? 0 : Math.round((reviewed / total) * 100); + + return ( +
+
+
+

+ Review Progress +

+ +

+ {reviewed} of {total} findings reviewed +

+
+ + + {percentage}% + +
+ +
+
+
+
+ ); +} + +export default ReviewProgress; diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ReviewSummary.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ReviewSummary.tsx new file mode 100644 index 00000000..13a7b4b2 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ReviewSummary.tsx @@ -0,0 +1,22 @@ +import type { ReviewSummary as ReviewSummaryType } from "../types/redline"; +import SummaryCard from "./SummaryCard"; + +interface ReviewSummaryProps { + summary: ReviewSummaryType; +} + +function ReviewSummary({ summary }: ReviewSummaryProps) { + return ( +
+ + + + + + + +
+ ); +} + +export default ReviewSummary; diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/StatusBadge.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/StatusBadge.tsx new file mode 100644 index 00000000..e02220d3 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/StatusBadge.tsx @@ -0,0 +1,34 @@ +import type { ReviewStatus } from "../types/redline"; + +interface StatusBadgeProps { + status: ReviewStatus; +} + +const styles: Record = { + pending: "bg-slate-100 text-slate-600 border-slate-200", + + in_review: "bg-amber-50 text-amber-700 border-amber-200", + + completed: "bg-blue-50 text-blue-700 border-blue-200", + + applied: "bg-emerald-50 text-emerald-700 border-emerald-200", +}; + +const labels: Record = { + pending: "Pending", + in_review: "In Review", + completed: "Ready to Apply", + applied: "Applied", +}; + +function StatusBadge({ status }: StatusBadgeProps) { + return ( + + {labels[status]} + + ); +} + +export default StatusBadge; diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/SuccessMessage.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/SuccessMessage.tsx new file mode 100644 index 00000000..04815bc6 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/SuccessMessage.tsx @@ -0,0 +1,31 @@ +import { CheckCircle2, X } from "lucide-react"; + +interface SuccessMessageProps { + message: string; + onDismiss?: () => void; +} + +function SuccessMessage({ message, onDismiss }: SuccessMessageProps) { + return ( +
+
+ + +

{message}

+
+ + {onDismiss && ( + + )} +
+ ); +} + +export default SuccessMessage; diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/SummaryCard.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/SummaryCard.tsx new file mode 100644 index 00000000..5bef9f90 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/SummaryCard.tsx @@ -0,0 +1,18 @@ +interface SummaryCardProps { + label: string; + value: number; +} + +function SummaryCard({ label, value }: SummaryCardProps) { + return ( +
+

{label}

+ +

+ {value} +

+
+ ); +} + +export default SummaryCard; diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/index.css b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/index.css new file mode 100644 index 00000000..82af602b --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/index.css @@ -0,0 +1,33 @@ +@import "tailwindcss"; + +:root { + font-family: + Inter, + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; + + color: #111827; + background: #f8fafc; + font-synthesis: none; + text-rendering: optimizeLegibility; +} + +* { + box-sizing: border-box; +} + +html, +body, +#root { + min-height: 100%; + margin: 0; +} + +body { + min-width: 320px; + background: #f8fafc; +} \ No newline at end of file diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/main.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/main.tsx new file mode 100644 index 00000000..17cb9016 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/main.tsx @@ -0,0 +1,11 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; + +import "./index.css"; +import App from "./App"; + +createRoot(document.getElementById("root")!).render( + + + , +); diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/types/redline.ts b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/types/redline.ts new file mode 100644 index 00000000..2a236267 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/types/redline.ts @@ -0,0 +1,133 @@ +export type Classification = "PRE-APPROVED" | "ESCALATE" | "REFUSE"; + +export type FindingDecision = "pending" | "approve" | "reject"; + +export type ReviewStatus = "pending" | "in_review" | "completed" | "applied"; + +export interface AgreementSection { + section: string; + text: string; +} + +export interface Deviation { + section: string; + master_text: string; + dealer_text: string; + deviation_type: string; + similarity: number; + classification: Classification; + reason: string; + counter_position: string; +} + +export interface ReviewSummary { + total: number; + pre_approved: number; + escalate: number; + refuse: number; +} + +export interface AnalyzeResponse { + review_id: string; + session_id: string; + + dealer_document?: { + html: string; + session_id: string; + filename: string; + chunks_count: number; + version_id: string; + document_id: string; + }; + + master_attachment?: { + job_id: string; + filename: string; + status: string; + message: string; + }; + + playbook_attachment?: { + job_id: string; + filename: string; + status: string; + message: string; + }; + + master_sections: AgreementSection[]; + dealer_sections: AgreementSection[]; + playbook_sections: AgreementSection[]; + deviations: Deviation[]; + summary: ReviewSummary; +} + +export interface RedlineFinding extends Deviation { + id: string; + decision: FindingDecision; +} + +export interface RedlineReview { + review_id: string; + session_id: string; + status: ReviewStatus; + created_at: string; + updated_at: string; + findings: RedlineFinding[]; +} + +export interface FindingDecisionRequest { + decision: "approve" | "reject"; +} + +export interface FindingDecisionResponse { + review_id: string; + finding_id: string; + decision: FindingDecision; + review_status: ReviewStatus; + remaining_findings: number; +} + +export interface ApplyReviewResponse { + review_id: string; + status: "applied"; + applied: boolean; + chat_response?: { + response?: string; + session_id?: string; + document_changes?: { + updated_html?: string; + version_id?: string; + changes_summary?: string; + requires_approval?: boolean | null; + pending_changes?: unknown; + changes?: unknown[]; + chunk_diffs?: unknown; + concurrent_merges?: unknown; + focused_document_id?: string; + }; + usage?: { + ops_charged?: number; + monthly_used?: number; + monthly_remaining?: number; + }; + }; +} + +export interface ApiError { + detail: string; +} + +export interface DecisionResponse { + review_id: string; + finding_id: string; + decision: "approve" | "reject"; + review_status: ReviewStatus; + remaining_findings: number; +} + +export interface ApplyResponse { + review_id: string; + status: "applied"; + applied: boolean; + chat_response: unknown; +} diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/tsconfig.app.json b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/tsconfig.app.json new file mode 100644 index 00000000..6830b6f7 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023", "DOM"], + "module": "esnext", + "types": ["vite/client"], + "allowArbitraryExtensions": true, + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/tsconfig.json b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/tsconfig.node.json b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/tsconfig.node.json new file mode 100644 index 00000000..8455dcbc --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/tsconfig.node.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "module": "nodenext", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/frontend/vite.config.ts b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/vite.config.ts new file mode 100644 index 00000000..1f4caca4 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; + +export default defineConfig({ + plugins: [react(), tailwindcss()], +}); diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/test-data/dealer-agreement.docx b/use-cases/pritesh2564u/dealer-agreement-redline/test-data/dealer-agreement.docx new file mode 100644 index 00000000..398ac0ac Binary files /dev/null and b/use-cases/pritesh2564u/dealer-agreement-redline/test-data/dealer-agreement.docx differ diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/test-data/master-agreement.docx b/use-cases/pritesh2564u/dealer-agreement-redline/test-data/master-agreement.docx new file mode 100644 index 00000000..f37070cf Binary files /dev/null and b/use-cases/pritesh2564u/dealer-agreement-redline/test-data/master-agreement.docx differ diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/test-data/negotiation-playbook.docx b/use-cases/pritesh2564u/dealer-agreement-redline/test-data/negotiation-playbook.docx new file mode 100644 index 00000000..8bae9fc3 Binary files /dev/null and b/use-cases/pritesh2564u/dealer-agreement-redline/test-data/negotiation-playbook.docx differ