├── LICENSE ├── README.md ├── data ├── .DS_Store ├── .gitignore ├── convert.js ├── fancy_pdf │ ├── contract-1.pdf │ ├── contract-2.pdf │ ├── contract-3.pdf │ ├── contract-4.pdf │ ├── contract-5.pdf │ ├── contract-6.pdf │ ├── invoice-1-1.pdf │ ├── invoice-1-2.pdf │ ├── invoice-1-3.pdf │ ├── invoice-2-1.pdf │ ├── invoice-2-2.pdf │ ├── invoice-2-3.pdf │ ├── invoice-3-1.pdf │ ├── invoice-3-2.pdf │ ├── invoice-3-3.pdf │ ├── invoice-4-1.pdf │ ├── invoice-4-2.pdf │ ├── invoice-4-3.pdf │ ├── invoice-5-1.pdf │ ├── invoice-5-2.pdf │ ├── invoice-5-3.pdf │ ├── invoice-6-1.pdf │ ├── invoice-6-2.pdf │ └── invoice-6-3.pdf ├── markdown │ ├── contract-1.md │ ├── contract-2.md │ ├── contract-3.md │ ├── contract-4.md │ ├── contract-5.md │ ├── contract-6.md │ ├── invoice-1-1.md │ ├── invoice-1-2.md │ ├── invoice-1-3.md │ ├── invoice-2-1.md │ ├── invoice-2-2.md │ ├── invoice-2-3.md │ ├── invoice-3-1.md │ ├── invoice-3-2.md │ ├── invoice-3-3.md │ ├── invoice-4-1.md │ ├── invoice-4-2.md │ ├── invoice-4-3.md │ ├── invoice-5-1.md │ ├── invoice-5-2.md │ ├── invoice-5-3.md │ ├── invoice-6-1.md │ ├── invoice-6-2.md │ └── invoice-6-3.md ├── package-lock.json ├── package.json ├── pdf │ ├── .DS_Store │ ├── contract-1.pdf │ ├── contract-2.pdf │ ├── contract-3.pdf │ ├── contract-4.pdf │ ├── contract-5.pdf │ ├── contract-6.pdf │ ├── invoice-1-1.pdf │ ├── invoice-1-2.pdf │ └── invoice-1-3.pdf └── split_markdown.js └── frontend ├── .gitignore ├── README.md ├── components.json ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── file.svg ├── globe.svg ├── logo.png ├── next.svg ├── vercel.svg └── window.svg ├── src ├── app │ ├── api │ │ ├── auth │ │ │ └── validate │ │ │ │ └── route.ts │ │ ├── contracts │ │ │ ├── list │ │ │ │ └── route.ts │ │ │ ├── setname │ │ │ │ └── route.ts │ │ │ └── upload │ │ │ │ └── route.ts │ │ ├── initialize │ │ │ └── route.ts │ │ └── invoices │ │ │ ├── reconcile │ │ │ └── route.ts │ │ │ ├── status │ │ │ └── route.ts │ │ │ └── upload │ │ │ └── route.ts │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ └── page.tsx ├── components │ ├── ContractUpload.tsx │ ├── ContractsList.tsx │ ├── InvoiceUpload.tsx │ ├── Login.tsx │ ├── ReconciledInvoicesList.tsx │ └── ui │ │ ├── alert.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── collapsible.tsx │ │ └── input.tsx ├── context │ └── InvoicesContext.tsx └── lib │ └── utils.ts ├── tailwind.config.ts └── tsconfig.json /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) Laurie Voss 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LlamaIndex Invoice Reconciler 2 | 3 | This is a full-stack web app showing how to use LlamaCloud with the LlamaIndex.TS framework to produce a real-world application, in this case matching the terms of contracts against incoming invoices from the contracted companies, aka "invoice reconciliation". 4 | 5 | ## User flow 6 | 7 | 1. The user uploads one or more contracts to the app 8 | 2. These are indexed and made searchable by the app 9 | 3. The user uploads one or more invoices to the app 10 | 4. The app determines which contract each invoice applies to 11 | 5. It examines the terms of the contract and the invoice details and flags any discrepancies 12 | 6. It shows the user the results 13 | 14 | ## Under the hood 15 | 16 | 1. The user "logs in" by providing a LlamaCloud API key. This is used to create a LlamaCloud index. (The API key is stored in localStorage, as are other settings. This is not a good practice, but it kept the code simple for a demonstration) 17 | 18 | 2. Uploaded invoices are uploaded to the LlamaCloud index, which parses them and then stores the parsed chunks in a vector database. The LlamaCloud index also stores the parsed documents and original file names in the metadata, which we use to display them on the frontend. 19 | 20 | 3. When a user uploads an invoice, we parse it with LlamaParse. Then an LLM call extracts the name of the company that issued the invoice, and runs a retrieval query again the LlamaCloud index with that name. 21 | 22 | 4. The chunk with the highest match to the name is taken as the contract that the invoice applies to. We retrieve the entire parsed contract from the index. 23 | 24 | 5. A further LLM call compares the entire parsed contract against the full parsed invoice, and asks it to flag discrepancies. Because this is a complex task, we use OpenAI's o3-mini model for this. 25 | 26 | 6. The parsed invoices and any discrepancies are returned to the frontend. 27 | 28 | ## Running the app 29 | 30 | The frontend is a vanilla Next.js application. It requires a `.env.local` file with a single variable, your `OPENAI_API_KEY`. Everything else is handled by LlamaCloud and credentials are stored on the client (insecurely!). You can run the app as normal: 31 | 32 | ```bash 33 | npm install 34 | npm run dev 35 | ``` 36 | -------------------------------------------------------------------------------- /data/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/.DS_Store -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /data/convert.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const markdownpdf = require('markdown-pdf'); 4 | 5 | // Create directories if they don't exist 6 | const markdownDir = './markdown'; 7 | const pdfDir = './pdf'; 8 | 9 | if (!fs.existsSync(markdownDir)) { 10 | fs.mkdirSync(markdownDir); 11 | console.log('Created markdown directory'); 12 | } 13 | 14 | if (!fs.existsSync(pdfDir)) { 15 | fs.mkdirSync(pdfDir); 16 | console.log('Created pdf directory'); 17 | } 18 | 19 | // Get all markdown files 20 | const markdownFiles = fs.readdirSync(markdownDir) 21 | .filter(file => file.endsWith('.md')); 22 | 23 | if (markdownFiles.length === 0) { 24 | console.log('No markdown files found in the markdown directory'); 25 | process.exit(0); 26 | } 27 | 28 | // Convert each markdown file to PDF 29 | markdownFiles.forEach(file => { 30 | const inputPath = path.join(markdownDir, file); 31 | const outputPath = path.join(pdfDir, file.replace('.md', '.pdf')); 32 | 33 | console.log(`Converting ${file} to PDF...`); 34 | 35 | markdownpdf() 36 | .from(inputPath) 37 | .to(outputPath, () => { 38 | console.log(`Successfully converted ${file} to PDF`); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /data/fancy_pdf/contract-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/contract-1.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/contract-2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/contract-2.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/contract-3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/contract-3.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/contract-4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/contract-4.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/contract-5.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/contract-5.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/contract-6.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/contract-6.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-1-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-1-1.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-1-2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-1-2.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-1-3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-1-3.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-2-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-2-1.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-2-2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-2-2.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-2-3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-2-3.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-3-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-3-1.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-3-2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-3-2.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-3-3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-3-3.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-4-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-4-1.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-4-2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-4-2.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-4-3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-4-3.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-5-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-5-1.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-5-2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-5-2.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-5-3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-5-3.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-6-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-6-1.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-6-2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-6-2.pdf -------------------------------------------------------------------------------- /data/fancy_pdf/invoice-6-3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/fancy_pdf/invoice-6-3.pdf -------------------------------------------------------------------------------- /data/markdown/contract-1.md: -------------------------------------------------------------------------------- 1 | **AGREEMENT DATE:** JANUARY 5, 2025 2 | 3 | **PARTIES:** 4 | - **SUPPLIER:** Premium Flour Mills, Inc., 1234 Grain Road, Mill Valley, CA 94941 5 | - **BUYER:** Sunshine Bakery LLC, 567 Main Street, San Francisco, CA 94110 6 | 7 | **TERMS AND CONDITIONS:** 8 | 9 | 1. **PRODUCTS AND PRICING:** 10 | - All-Purpose Flour: $32.75 / 50 lb bag 11 | - Whole Wheat Flour: $36.50 / 50 lb bag 12 | - Bread Flour: $34.25 / 50 lb bag 13 | - Pastry Flour: $38.95 / 50 lb bag 14 | - Organic Rye Flour: $42.75 / 50 lb bag 15 | 16 | 2. **VOLUME DISCOUNTS:** 17 | - 10-24 bags: 3% discount 18 | - 25-49 bags: 5% discount 19 | - 50+ bags: 8% discount 20 | 21 | 3. **DELIVERY:** Weekly delivery on Mondays before 10:00 AM. 22 | 23 | 4. **PAYMENT TERMS:** Net 30 days from invoice date. 24 | 25 | 5. **MINIMUM ORDER:** 10 bags total per order. 26 | 27 | 6. **CONTRACT PERIOD:** January 15, 2025 to January 14, 2026. 28 | 29 | 7. **PRICE ADJUSTMENT:** Prices fixed for first 6 months. Thereafter, maximum 4% increase with 30 days written notice. 30 | 31 | 8. **QUALITY STANDARDS:** All products must meet or exceed FDA and USDA standards for food grade flour. 32 | 33 | 9. **RETURNS:** Defective products can be returned within 7 days of delivery. 34 | 35 | 10. **TERMINATION:** 60 days written notice by either party. 36 | -------------------------------------------------------------------------------- /data/markdown/contract-2.md: -------------------------------------------------------------------------------- 1 | This AGREEMENT is made on this 15th day of December, 2024 2 | 3 | **BETWEEN:** 4 | Fresh Farms Dairy Cooperative ("Supplier") 5 | 789 Pasture Lane, Petaluma, CA 94952 6 | 7 | **AND:** 8 | Sunshine Bakery LLC ("Buyer") 9 | 567 Main Street, San Francisco, CA 94110 10 | 11 | **WHEREBY:** 12 | 13 | **ARTICLE 1: PRODUCTS** 14 | Supplier shall provide the following products: 15 | - Butter, Unsalted, Grade AA: $4.85/lb 16 | - Butter, Salted, Grade AA: $4.75/lb 17 | - Heavy Cream (40%): $3.95/quart 18 | - Whole Milk: $3.25/gallon 19 | - Buttermilk: $3.65/quart 20 | - Cream Cheese: $2.95/lb 21 | 22 | **ARTICLE 2: ORDERING** 23 | 2.1 Orders must be placed by 2:00 PM for next-day delivery. 24 | 2.2 Minimum order quantity is $200 per delivery. 25 | 26 | **ARTICLE 3: DELIVERY** 27 | 3.1 Deliveries shall be made Monday, Wednesday, and Friday between 5:00 AM and 7:00 AM. 28 | 3.2 Products must be maintained at appropriate temperatures during transport. 29 | 3.3 Buyer shall provide access to delivery point and inspect products immediately upon receipt. 30 | 31 | **ARTICLE 4: PAYMENT** 32 | 4.1 Payment terms are Net 15 days from delivery. 33 | 4.2 Late payments subject to 1.5% interest per month. 34 | 4.3 Recurring late payments may result in modified payment terms requiring advance payment. 35 | 36 | **ARTICLE 5: QUALITY & RETURNS** 37 | 5.1 All products guaranteed fresh upon delivery. 38 | 5.2 Any quality issues must be reported within 24 hours. 39 | 5.3 Credit for defective products issued within 7 business days. 40 | 41 | **ARTICLE 6: CONTRACT PERIOD** 42 | This agreement commences on January 1, 2025 and continues through December 31, 2025, with automatic renewal for additional one-year periods unless terminated. 43 | 44 | **ARTICLE 7: PRICE ADJUSTMENTS** 45 | Supplier reserves the right to adjust prices quarterly with 15 days advance notice, based on market conditions. 46 | -------------------------------------------------------------------------------- /data/markdown/contract-3.md: -------------------------------------------------------------------------------- 1 | **Contract No:** SWS-2025-048 2 | **Effective Date:** February 1, 2025 3 | 4 | This Supply Agreement (the "Agreement") is entered into by and between: 5 | 6 | **SweetWay Sugar Suppliers, Inc.** 7 | 456 Sugar Mill Drive 8 | Sacramento, CA 95814 9 | (hereinafter referred to as "SUPPLIER") 10 | 11 | and 12 | 13 | **Sunshine Bakery LLC** 14 | 567 Main Street 15 | San Francisco, CA 94110 16 | (hereinafter referred to as "BUYER") 17 | 18 | **I. PRODUCTS & PRICING SCHEDULE** 19 | 20 | | PRODUCT CODE | DESCRIPTION | PACKAGING | PRICE (USD) | 21 | |--------------|-------------|-----------|-------------| 22 | | GRS-50 | Granulated White Sugar | 50 lb bag | $38.50 | 23 | | BRS-50 | Brown Sugar, Light | 50 lb bag | $42.75 | 24 | | BRS-50D | Brown Sugar, Dark | 50 lb bag | $43.95 | 25 | | PWS-25 | Powdered Sugar, 10X | 25 lb bag | $26.50 | 26 | | HNY-5 | Wildflower Honey | 5 gal bucket | $187.50 | 27 | | MSY-1 | Maple Syrup, Grade A | 1 gal jug | $64.95 | 28 | 29 | **II. ORDERING PROCEDURES** 30 | A. Purchase orders must be submitted via SUPPLIER's online portal or by email to orders@sweetway.com. 31 | B. Orders received by 12:00 PM PST will be processed the same business day. 32 | C. BUYER shall provide a rolling 4-week forecast of anticipated requirements on a monthly basis. 33 | 34 | **III. DELIVERY & LOGISTICS** 35 | A. Standard delivery schedule shall be every two weeks. 36 | B. Expedited deliveries available with 48-hour notice at additional cost of $75.00 per delivery. 37 | C. Title and risk of loss pass to BUYER upon delivery. 38 | D. BUYER shall inspect all products within 24 hours of delivery and notify SUPPLIER of any discrepancies. 39 | 40 | **IV. PAYMENT TERMS** 41 | A. Payment due Net 45 days from invoice date. 42 | B. 2% discount available for payments made within 10 days. 43 | C. Payments shall be made by check or ACH transfer. 44 | 45 | **V. MINIMUM PURCHASE COMMITMENT** 46 | BUYER agrees to purchase a minimum of $1,500.00 of product monthly during the term of this Agreement. 47 | 48 | **VI. TERM & TERMINATION** 49 | A. This Agreement shall remain in effect for one (1) year from the Effective Date. 50 | B. Either party may terminate with 90 days written notice. 51 | C. SUPPLIER may adjust prices with 30 days written notice, not more than once per quarter. 52 | 53 | **VII. QUALITY ASSURANCE** 54 | A. All products shall conform to FDA standards and SUPPLIER's published specifications. 55 | B. SUPPLIER shall provide Certificates of Analysis upon request. 56 | C. BUYER may reject non-conforming products within 5 business days of delivery. 57 | -------------------------------------------------------------------------------- /data/markdown/contract-4.md: -------------------------------------------------------------------------------- 1 | ## CONTRACT #4: PACKAGING SUPPLY AGREEMENT 2 | 3 | **CONTRACT NUMBER:** PKG-SB-2025-033 4 | **EFFECTIVE DATE:** January 15, 2025 5 | 6 | **PARTIES:** 7 | 8 | **SUPPLIER:** 9 | EcoPack Solutions, Inc. 10 | 1750 Green Way 11 | Oakland, CA 94612 12 | Tax ID: 27-5431982 13 | 14 | **CUSTOMER:** 15 | Sunshine Bakery LLC 16 | 567 Main Street 17 | San Francisco, CA 94110 18 | Tax ID: 83-9217654 19 | 20 | ### SECTION 1: PRODUCTS AND PRICING 21 | 22 | | Item # | Description | Unit | Price | 23 | |--------|-------------|------|-------| 24 | | EP-CB8 | Cake Box, 8" Compostable | Each | $0.85 | 25 | | EP-CB10 | Cake Box, 10" Compostable | Each | $1.15 | 26 | | EP-CB12 | Cake Box, 12" Compostable | Each | $1.45 | 27 | | EP-CC6 | Cupcake Container, 6-Pack | Each | $0.75 | 28 | | EP-CC12 | Cupcake Container, 12-Pack | Each | $1.25 | 29 | | EP-PB-S | Paper Bag, Small | Pack/100 | $18.50 | 30 | | EP-PB-M | Paper Bag, Medium | Pack/100 | $22.75 | 31 | | EP-PB-L | Paper Bag, Large | Pack/100 | $27.95 | 32 | | EP-WT | Wax Tissue Paper | Pack/500 | $32.50 | 33 | | EP-SB-AS | Sandwich Box, Assorted Sizes | Pack/50 | $37.50 | 34 | 35 | ### SECTION 2: GENERAL TERMS 36 | 37 | 1. **ORDERING** 38 | - Minimum order value: $300.00 39 | - Orders must be placed via online portal or email to orders@ecopack.com 40 | - Order cutoff: 3 PM for next-day processing 41 | 42 | 2. **VOLUME DISCOUNTS** 43 | - Orders $1,000-$2,499: 5% discount 44 | - Orders $2,500-$4,999: 8% discount 45 | - Orders $5,000+: 12% discount 46 | 47 | 3. **DELIVERY** 48 | - Standard delivery: Within 5 business days 49 | - Rush delivery (48 hours): Additional $75 fee 50 | - Free delivery for orders over $750 51 | - Delivery fee for orders under $750: $45 52 | 53 | 4. **CUSTOM PRINTING** 54 | - Setup fee: $250 (one-time) 55 | - Minimum order: 1,000 units 56 | - Additional cost: +25% over standard pricing 57 | - Lead time: 3 weeks 58 | 59 | 5. **PAYMENT TERMS** 60 | - Net 20 days from invoice date 61 | - 2% discount for payment within 10 days 62 | - Late payment penalty: 1.5% per month 63 | 64 | 6. **CONTRACT DURATION** 65 | - Initial term: 12 months 66 | - Auto-renewal for 12-month periods unless cancelled 67 | - 60-day written notice required for termination 68 | 69 | 7. **PRICE ADJUSTMENTS** 70 | - Fixed pricing for initial 6 months 71 | - Adjustments limited to 6% maximum per quarter thereafter 72 | - 30-day written notice required 73 | 74 | 8. **QUALITY & RETURNS** 75 | - Defective products may be returned within 10 days of receipt 76 | - Credit or replacement issued within 7 business days 77 | - Return shipping at Supplier's expense for defective items -------------------------------------------------------------------------------- /data/markdown/contract-5.md: -------------------------------------------------------------------------------- 1 | **AGREEMENT DATE:** February 1, 2025 2 | 3 | This Fruit Supply Agreement (the "Agreement") is made by and between: 4 | 5 | **SUPPLIER:** 6 | Golden State Fruit Farms 7 | 825 Orchard Road 8 | Watsonville, CA 95076 9 | ("GSFF") 10 | 11 | **BUYER:** 12 | Sunshine Bakery LLC 13 | 567 Main Street 14 | San Francisco, CA 94110 15 | ("Bakery") 16 | 17 | ### 1. PURPOSE 18 | This Agreement establishes the terms and conditions under which GSFF will supply fresh, frozen, and processed fruits to Bakery for use in bakery products. 19 | 20 | ### 2. PRODUCTS & PRICING 21 | 22 | **FRESH FRUIT (Seasonal Pricing)** 23 | - Strawberries, Organic: Market Price + 10% (Current: $4.25/lb) 24 | - Blueberries, Organic: Market Price + 10% (Current: $6.75/lb) 25 | - Raspberries, Organic: Market Price + 10% (Current: $7.95/lb) 26 | - Blackberries, Organic: Market Price + 10% (Current: $6.50/lb) 27 | - Apples, Assorted Varieties: $1.85/lb 28 | - Peaches (June-Sept): $2.75/lb 29 | - Cherries (May-July): $4.95/lb 30 | 31 | **FROZEN FRUIT (Fixed Pricing)** 32 | - Strawberries, IQF: $3.85/lb 33 | - Blueberries, IQF: $4.95/lb 34 | - Mixed Berries, IQF: $4.50/lb 35 | - Peach Slices, IQF: $3.75/lb 36 | - Cherry Halves, IQF: $5.25/lb 37 | 38 | **PROCESSED FRUIT (Fixed Pricing)** 39 | - Berry Puree, Sweetened: $6.50/quart 40 | - Berry Puree, Unsweetened: $5.95/quart 41 | - Apple Filling: $4.25/quart 42 | - Cherry Filling: $7.50/quart 43 | - Fruit Jam, Assorted: $8.95/quart 44 | 45 | ### 3. ORDERING & DELIVERY 46 | 47 | **Fresh Fruit:** 48 | - Orders placed by 1:00 PM will be delivered next morning 49 | - Minimum order: $200 50 | - Delivery days: Monday, Wednesday, Friday 51 | - Delivery time: 7:00 AM - 9:00 AM 52 | 53 | **Frozen & Processed Fruit:** 54 | - Orders placed by 3:00 PM will be delivered within 2 business days 55 | - Minimum order: $350 56 | - Delivery days: Tuesday, Thursday 57 | - Delivery time: 8:00 AM - 12:00 PM 58 | 59 | ### 4. QUALITY STANDARDS 60 | - All fresh fruit shall meet or exceed USDA Grade A standards 61 | - Organic certification documentation available upon request 62 | - Product temperatures maintained at appropriate levels during transport 63 | - Shelf life guarantees: Fresh (3 days), Frozen (6 months), Processed (3 months) 64 | 65 | ### 5. PAYMENT TERMS 66 | - Payment due Net 14 days from delivery 67 | - 3% discount for payment within 7 days 68 | - Late payment fee: 2% of outstanding balance 69 | - Recurring late payments may result in modified terms or suspension of deliveries 70 | 71 | ### 6. SEASONAL PRICE ADJUSTMENTS 72 | - Fresh fruit prices updated weekly based on market conditions 73 | - Price updates communicated via email each Friday 74 | - Frozen and processed fruit prices revised quarterly 75 | - 14-day notice provided for frozen/processed price changes 76 | 77 | ### 7. TERM & TERMINATION 78 | - Initial term: 12 months from Agreement Date 79 | - Automatic renewal for successive 6-month periods 80 | - Either party may terminate with 30 days written notice 81 | - Breach of payment terms constitutes grounds for immediate termination 82 | 83 | ### 8. MISCELLANEOUS 84 | - Force majeure provisions apply for crop failures or natural disasters 85 | - Disputes resolved through binding arbitration in San Francisco, CA 86 | - This Agreement constitutes the entire understanding between the parties 87 | -------------------------------------------------------------------------------- /data/markdown/contract-6.md: -------------------------------------------------------------------------------- 1 | ## CONTRACT #6: EQUIPMENT MAINTENANCE AGREEMENT 2 | 3 | **CONTRACT NUMBER:** BEM-2025-042 4 | **EFFECTIVE DATE:** January 1, 2025 5 | 6 | **SERVICE PROVIDER:** 7 | Bakery Equipment Masters 8 | 1875 Industrial Blvd. 9 | San Leandro, CA 94577 10 | Tel: (510) 555-4321 11 | Email: service@bakeryequipmentmasters.com 12 | 13 | **CLIENT:** 14 | Sunshine Bakery LLC 15 | 567 Main Street 16 | San Francisco, CA 94110 17 | Tel: (415) 555-9876 18 | Email: operations@sunshinebakery.com 19 | 20 | This Equipment Maintenance Agreement ("Agreement") is entered into by and between Bakery Equipment Masters ("Provider") and Sunshine Bakery LLC ("Client") for the maintenance and servicing of bakery equipment. 21 | 22 | ### 1. COVERED EQUIPMENT 23 | 24 | | Equipment | Model | Serial Number | Purchase Date | 25 | |-----------|-------|---------------|---------------| 26 | | Commercial Mixer | KitchenPro KPM-80 | KP8045627 | 06/15/2023 | 27 | | Convection Oven | BakeRight BR-2200 | BR267489 | 03/22/2024 | 28 | | Proof Box | ProofMaster PM-500 | PM521334 | 06/15/2023 | 29 | | Dough Sheeter | RollRight RR-30 | RR30789 | 11/05/2022 | 30 | | Walk-in Refrigerator | CoolPro CP-800 | CP842651 | 03/22/2024 | 31 | | Deck Oven | StoneOven SO-3T | SO378954 | 11/05/2022 | 32 | 33 | ### 2. MAINTENANCE SERVICES 34 | 35 | **2.1 Preventive Maintenance:** 36 | - Quarterly inspections and maintenance of all covered equipment 37 | - Scheduled dates: January, April, July, October (15th-30th) 38 | - Services include: 39 | * Cleaning of critical components 40 | * Lubrication of moving parts 41 | * Calibration and adjustment 42 | * Safety checks 43 | * Performance testing 44 | 45 | **2.2 Emergency Repairs:** 46 | - Response time: Within 4 hours during business days 47 | - After-hours response: Within 8 hours 48 | - Weekend/Holiday response: Within 12 hours 49 | 50 | **2.3 Parts Replacement:** 51 | - Regular wear parts included in contract 52 | - Major components billed separately (with prior approval) 53 | - 15% discount on all parts not covered by warranty 54 | 55 | **2.4 Equipment Training:** 56 | - Annual staff training session (up to 3 hours) 57 | - Additional training available at $125/hour 58 | 59 | ### 3. SERVICE FEES 60 | 61 | **3.1 Maintenance Plan Options:** 62 | - Standard Plan (Selected): $575/month 63 | * Includes quarterly maintenance 64 | * Emergency repair labor 65 | * Travel expenses 66 | * Regular wear parts 67 | 68 | - Premium Plan: $875/month 69 | * Includes all Standard Plan features 70 | * Priority emergency response (2 hours) 71 | * Annual deep cleaning 72 | * All parts replacement (except major components) 73 | 74 | **3.2 Additional Charges:** 75 | - After-hours emergency calls: $125/hour surcharge 76 | - Non-covered equipment service: $150/hour + parts 77 | - Equipment operator error: $150/hour + parts 78 | 79 | ### 4. PAYMENT TERMS 80 | 81 | **4.1 Monthly Fee:** 82 | - $575.00 due on the 1st of each month 83 | - Late fee: 5% after 5 days 84 | - Suspension of services: After 15 days of non-payment 85 | 86 | **4.2 Additional Services:** 87 | - Billed separately from monthly maintenance fee 88 | - Payment terms: Net 15 days 89 | - Itemized invoice provided for all services 90 | 91 | ### 5. TERM AND TERMINATION 92 | 93 | **5.1 Initial Term:** 94 | - 12 months from Effective Date 95 | 96 | **5.2 Renewal:** 97 | - Automatic renewal for successive 12-month periods 98 | - 60-day written notice required to terminate at end of term 99 | 100 | **5.3 Early Termination:** 101 | - 90-day written notice required 102 | - Early termination fee: 3 months of service fees 103 | 104 | ### 6. GENERAL PROVISIONS 105 | 106 | **6.1 Warranty:** 107 | Provider warrants all service work for 90 days 108 | 109 | **6.2 Liability:** 110 | Provider's liability limited to cost of services provided 111 | 112 | **6.3 Insurance:** 113 | Provider shall maintain appropriate insurance coverage 114 | 115 | **6.4 Venue:** 116 | Governing law: State of California -------------------------------------------------------------------------------- /data/markdown/invoice-1-1.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #PFM-2025-112 2 | **Premium Flour Mills, Inc.** 3 | 1234 Grain Road 4 | Mill Valley, CA 94941 5 | Tax ID: 12-3456789 6 | 7 | **BILL TO:** 8 | Sunshine Bakery LLC 9 | 567 Main Street 10 | San Francisco, CA 94110 11 | 12 | **DATE:** February 7, 2025 13 | **TERMS:** Net 30 14 | **DUE DATE:** March 9, 2025 15 | 16 | **ITEMS:** 17 | - 12 × All-Purpose Flour (50 lb) @ $32.75 = $393.00 18 | - 8 × Bread Flour (50 lb) @ $34.25 = $274.00 19 | - 5 × Whole Wheat Flour (50 lb) @ $36.50 = $182.50 20 | - 4 × Pastry Flour (50 lb) @ $38.95 = $155.80 21 | 22 | **SUBTOTAL:** $1,005.30 23 | **DISCOUNT (5%):** -$50.27 24 | **TOTAL DUE:** $955.03 25 | 26 | Please make checks payable to Premium Flour Mills, Inc. 27 | For questions concerning this invoice, contact accounting@premiumflour.com or call (415) 555-1234. 28 | -------------------------------------------------------------------------------- /data/markdown/invoice-1-2.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #PFM-2025-136 2 | **Premium Flour Mills, Inc.** 3 | 1234 Grain Road 4 | Mill Valley, CA 94941 5 | Tax ID: 12-3456789 6 | 7 | **BILL TO:** 8 | Sunshine Bakery LLC 9 | 567 Main Street 10 | San Francisco, CA 94110 11 | 12 | **DATE:** March 7, 2025 13 | **TERMS:** Net 30 14 | **DUE DATE:** April 6, 2025 15 | 16 | **ITEMS:** 17 | - 15 × All-Purpose Flour (50 lb) @ $32.75 = $491.25 18 | - 10 × Bread Flour (50 lb) @ $34.25 = $342.50 19 | - 8 × Whole Wheat Flour (50 lb) @ $36.50 = $292.00 20 | - 5 × Pastry Flour (50 lb) @ $39.95 = $199.75 21 | - 2 × Organic Rye Flour (50 lb) @ $42.75 = $85.50 22 | 23 | **SUBTOTAL:** $1,411.00 24 | **DISCOUNT (5%):** -$70.55 25 | **SHIPPING:** $45.00 26 | **TOTAL DUE:** $1,385.45 27 | 28 | Please make checks payable to Premium Flour Mills, Inc. 29 | For questions concerning this invoice, contact accounting@premiumflour.com or call (415) 555-1234. 30 | -------------------------------------------------------------------------------- /data/markdown/invoice-1-3.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #PFM-2025-158 2 | **Premium Flour Mills, Inc.** 3 | 1234 Grain Road 4 | Mill Valley, CA 94941 5 | Tax ID: 12-3456789 6 | 7 | **BILL TO:** 8 | Sunshine Bakery LLC 9 | 567 Main Street 10 | San Francisco, CA 94110 11 | 12 | **DATE:** April 4, 2025 13 | **TERMS:** Net 30 14 | **DUE DATE:** May 4, 2025 15 | 16 | **ITEMS:** 17 | - 18 × All-Purpose Flour (50 lb) @ $32.75 = $589.50 18 | - 12 × Bread Flour (50 lb) @ $34.25 = $411.00 19 | - 6 × Whole Wheat Flour (50 lb) @ $36.50 = $219.00 20 | - 4 × Pastry Flour (50 lb) @ $38.95 = $155.80 21 | - 2 × Organic Rye Flour (50 lb) @ $42.75 = $85.50 22 | 23 | **SUBTOTAL:** $1,460.80 24 | **DISCOUNT (5%):** -$73.04 25 | **SHIPPING:** $0.00 26 | **TOTAL DUE:** $1,387.76 27 | 28 | Please make checks payable to Premium Flour Mills, Inc. 29 | For questions concerning this invoice, contact accounting@premiumflour.com or call (415) 555-1234. 30 | -------------------------------------------------------------------------------- /data/markdown/invoice-2-1.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #FFD-25-0342 2 | **FRESH FARMS DAIRY COOPERATIVE** 3 | 789 Pasture Lane 4 | Petaluma, CA 94952 5 | Office: (707) 555-5678 6 | 7 | **Invoice:** #FFD-25-0342 8 | **Date:** Jan 19, 2025 9 | **Customer ID:** SUN-223 10 | 11 | **Ship To/Bill To:** 12 | Sunshine Bakery LLC 13 | 567 Main Street 14 | San Francisco, CA 94110 15 | (415) 555-9876 16 | 17 | **Order Date:** Jan 18, 2025 18 | **Delivery Date:** Jan 19, 2025 19 | **Payment Due:** Feb 3, 2025 20 | 21 | **ITEMS:** 22 | - Butter, Unsalted: 35 lbs @ $4.85 = $169.75 23 | - Heavy Cream: 24 qts @ $3.95 = $94.80 24 | - Cream Cheese: 15 lbs @ $2.95 = $44.25 25 | - Buttermilk: 12 qts @ $3.65 = $43.80 26 | 27 | **Subtotal:** $352.60 28 | **Delivery:** $15.00 29 | **Tax:** $0.00 30 | **TOTAL:** $367.60 31 | 32 | Comments: Regular bi-weekly order. Increased butter quantity per customer request. 33 | 34 | PAYMENT TERMS: Net 15 Days 35 | Please reference invoice number with payment. 36 | Questions? Contact our accounting department: accounting@freshfarmsdairy.com 37 | -------------------------------------------------------------------------------- /data/markdown/invoice-2-2.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #FFD-25-0396 2 | **FRESH FARMS DAIRY COOPERATIVE** 3 | 789 Pasture Lane 4 | Petaluma, CA 94952 5 | Office: (707) 555-5678 6 | 7 | **Invoice:** #FFD-25-0396 8 | **Date:** Feb 2, 2025 9 | **Customer ID:** SUN-223 10 | 11 | **Ship To/Bill To:** 12 | Sunshine Bakery LLC 13 | 567 Main Street 14 | San Francisco, CA 94110 15 | (415) 555-9876 16 | 17 | **Order Date:** Feb 1, 2025 18 | **Delivery Date:** Feb 2, 2025 19 | **Payment Due:** Feb 17, 2025 20 | 21 | **ITEMS:** 22 | - Butter, Unsalted: 40 lbs @ $4.85 = $194.00 23 | - Butter, Salted: 10 lbs @ $4.75 = $47.50 24 | - Heavy Cream: 30 qts @ $3.95 = $118.50 25 | - Cream Cheese: 18 lbs @ $2.95 = $53.10 26 | - Buttermilk: 15 qts @ $3.65 = $54.75 27 | - Whole Milk: 10 gal @ $3.25 = $32.50 28 | 29 | **Subtotal:** $500.35 30 | **Delivery:** $0.00 31 | **Tax:** $0.00 32 | **TOTAL:** $500.35 33 | 34 | Comments: Regular bi-weekly order. First delivery with new whole milk product. 35 | 36 | PAYMENT TERMS: Net 15 Days 37 | Please reference invoice number with payment. 38 | Questions? Contact our accounting department: accounting@freshfarmsdairy.com 39 | -------------------------------------------------------------------------------- /data/markdown/invoice-2-3.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #FFD-25-0428 2 | **FRESH FARMS DAIRY COOPERATIVE** 3 | 789 Pasture Lane 4 | Petaluma, CA 94952 5 | Office: (707) 555-5678 6 | 7 | **Invoice:** #FFD-25-0428 8 | **Date:** Feb 16, 2025 9 | **Customer ID:** SUN-223 10 | 11 | **Ship To/Bill To:** 12 | Sunshine Bakery LLC 13 | 567 Main Street 14 | San Francisco, CA 94110 15 | (415) 555-9876 16 | 17 | **Order Date:** Feb 15, 2025 18 | **Delivery Date:** Feb 16, 2025 19 | **Payment Due:** Mar 3, 2025 20 | 21 | **ITEMS:** 22 | - Butter, Unsalted: 45 lbs @ $4.85 = $218.25 23 | - Butter, Salted: 15 lbs @ $4.75 = $71.25 24 | - Heavy Cream: 35 qts @ $3.95 = $138.25 25 | - Cream Cheese: 20 lbs @ $2.95 = $59.00 26 | - Buttermilk: 12 qts @ $3.75 = $45.00 27 | - Whole Milk: 12 gal @ $3.25 = $39.00 28 | 29 | **Subtotal:** $570.75 30 | **Delivery:** $0.00 31 | **Tax:** $0.00 32 | **TOTAL:** $570.75 33 | 34 | Comments: Price change on buttermilk noted - increased from $3.65 to $3.75 per quart. 35 | 36 | PAYMENT TERMS: Net 15 Days 37 | Please reference invoice number with payment. 38 | Questions? Contact our accounting department: accounting@freshfarmsdairy.com 39 | -------------------------------------------------------------------------------- /data/markdown/invoice-3-1.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #SWS-0456-25 2 | **SweetWay Sugar Suppliers, Inc.** 3 | 456 Sugar Mill Drive 4 | Sacramento, CA 95814 5 | Tel: (916) 555-2468 6 | Email: billing@sweetway.com 7 | www.sweetwaysugars.com 8 | 9 | **INVOICE #:** SWS-0456-25 10 | **DATE ISSUED:** March 12, 2025 11 | **CUSTOMER ID:** SUN-789 12 | **PO #:** SB-2025-087 13 | 14 | **BILL TO:** 15 | Sunshine Bakery LLC 16 | Attn: Accounts Payable 17 | 567 Main Street 18 | San Francisco, CA 94110 19 | 20 | **SHIP TO:** 21 | Sunshine Bakery LLC 22 | 567 Main Street 23 | San Francisco, CA 94110 24 | (415) 555-9876 25 | 26 | **ORDER DETAILS:** 27 | Order Date: March 10, 2025 28 | Ship Date: March 12, 2025 29 | Ship Via: SweetWay Fleet 30 | Terms: Net 45 31 | 32 | **PRODUCT DETAILS:** 33 | - GRS-50: Granulated White Sugar - 12 Bags @ $38.50 = $462.00 34 | - BRS-50: Brown Sugar, Light - 6 Bags @ $42.75 = $256.50 35 | - PWS-25: Powdered Sugar, 10X - 8 Bags @ $26.50 = $212.00 36 | - HNY-5: Wildflower Honey - 2 Buckets @ $187.50 = $375.00 37 | 38 | **Merchandise Total:** $1,305.50 39 | **Delivery Charge:** $0.00 40 | **California State Tax (0%):** $0.00 41 | **INVOICE TOTAL:** $1,305.50 42 | 43 | **PAYMENT DUE DATE:** April 26, 2025 44 | 45 | PAYMENT OPTIONS: 46 | - Check payable to SweetWay Sugar Suppliers, Inc. 47 | - ACH Transfer: Routing #121000358, Account #7568921 48 | - For questions regarding this invoice, please contact: accounting@sweetway.com or call (916) 555-2470 49 | -------------------------------------------------------------------------------- /data/markdown/invoice-3-2.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #SWS-0512-25 2 | **SweetWay Sugar Suppliers, Inc.** 3 | 456 Sugar Mill Drive 4 | Sacramento, CA 95814 5 | Tel: (916) 555-2468 6 | Email: billing@sweetway.com 7 | www.sweetwaysugars.com 8 | 9 | **INVOICE #:** SWS-0512-25 10 | **DATE ISSUED:** March 26, 2025 11 | **CUSTOMER ID:** SUN-789 12 | **PO #:** SB-2025-099 13 | 14 | **BILL TO:** 15 | Sunshine Bakery LLC 16 | Attn: Accounts Payable 17 | 567 Main Street 18 | San Francisco, CA 94110 19 | 20 | **SHIP TO:** 21 | Sunshine Bakery LLC 22 | 567 Main Street 23 | San Francisco, CA 94110 24 | (415) 555-9876 25 | 26 | **ORDER DETAILS:** 27 | Order Date: March 25, 2025 28 | Ship Date: March 26, 2025 29 | Ship Via: SweetWay Fleet 30 | Terms: Net 45 31 | 32 | **PRODUCT DETAILS:** 33 | - GRS-50: Granulated White Sugar - 15 Bags @ $38.50 = $577.50 34 | - BRS-50: Brown Sugar, Light - 8 Bags @ $42.75 = $342.00 35 | - BRS-50D: Brown Sugar, Dark - 4 Bags @ $43.95 = $175.80 36 | - PWS-25: Powdered Sugar, 10X - 10 Bags @ $26.50 = $265.00 37 | - MSY-1: Maple Syrup, Grade A - 3 Jugs @ $64.95 = $194.85 38 | 39 | **Merchandise Total:** $1,555.15 40 | **Delivery Charge:** $0.00 41 | **California State Tax (0%):** $0.00 42 | **INVOICE TOTAL:** $1,555.15 43 | 44 | **PAYMENT DUE DATE:** May 10, 2025 45 | 46 | PAYMENT OPTIONS: 47 | - Check payable to SweetWay Sugar Suppliers, Inc. 48 | - ACH Transfer: Routing #121000358, Account #7568921 49 | - For questions regarding this invoice, please contact: accounting@sweetway.com or call (916) 555-2470 50 | -------------------------------------------------------------------------------- /data/markdown/invoice-3-3.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #SWS-0587-25 2 | **SweetWay Sugar Suppliers, Inc.** 3 | 456 Sugar Mill Drive 4 | Sacramento, CA 95814 5 | Tel: (916) 555-2468 6 | Email: billing@sweetway.com 7 | www.sweetwaysugars.com 8 | 9 | **INVOICE #:** SWS-0587-25 10 | **DATE ISSUED:** April 9, 2025 11 | **CUSTOMER ID:** SUN-789 12 | **PO #:** SB-2025-112 13 | 14 | **BILL TO:** 15 | Sunshine Bakery LLC 16 | Attn: Accounts Payable 17 | 567 Main Street 18 | San Francisco, CA 94110 19 | 20 | **SHIP TO:** 21 | Sunshine Bakery LLC 22 | 567 Main Street 23 | San Francisco, CA 94110 24 | (415) 555-9876 25 | 26 | **ORDER DETAILS:** 27 | Order Date: April 8, 2025 28 | Ship Date: April 9, 2025 29 | Ship Via: SweetWay Fleet 30 | Terms: Net 45 31 | 32 | **PRODUCT DETAILS:** 33 | - GRS-50: Granulated White Sugar - 20 Bags @ $39.25 = $785.00 34 | - BRS-50: Brown Sugar, Light - 10 Bags @ $43.50 = $435.00 35 | - BRS-50D: Brown Sugar, Dark - 5 Bags @ $44.75 = $223.75 36 | - PWS-25: Powdered Sugar, 10X - 12 Bags @ $27.25 = $327.00 37 | - HNY-5: Wildflower Honey - 3 Buckets @ $195.00 = $585.00 38 | 39 | **Merchandise Total:** $2,355.75 40 | **Delivery Charge:** $0.00 41 | **California State Tax (0%):** $0.00 42 | **INVOICE TOTAL:** $2,355.75 43 | 44 | **PAYMENT DUE DATE:** May 24, 2025 45 | 46 | **NOTICE:** Price increases effective April 1, 2025 as per contract terms. 47 | 48 | PAYMENT OPTIONS: 49 | - Check payable to SweetWay Sugar Suppliers, Inc. 50 | - ACH Transfer: Routing #121000358, Account #7568921 51 | - For questions regarding this invoice, please contact: accounting@sweetway.com or call (916) 555-2470 52 | -------------------------------------------------------------------------------- /data/markdown/invoice-4-1.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #ECOS-2025-1234 2 | **EcoPack Solutions, Inc.** 3 | 1750 Green Way 4 | Oakland, CA 94612 5 | Tel: (510) 555-7890 6 | www.ecopacksolutions.com 7 | 8 | **INVOICE TO:** 9 | Sunshine Bakery LLC 10 | 567 Main Street 11 | San Francisco, CA 94110 12 | 13 | **INVOICE NUMBER:** ECOS-2025-1234 14 | **DATE:** February 10, 2025 15 | **CUSTOMER ID:** SB-2207 16 | **ORDER NUMBER:** 45678 17 | **PAYMENT TERMS:** Net 20 days 18 | 19 | **SHIP TO:** 20 | Sunshine Bakery LLC 21 | 567 Main Street 22 | San Francisco, CA 94110 23 | 24 | **ITEMS:** 25 | - EP-CB8: Cake Box, 8" Compostable - 250 units @ $0.85 = $212.50 26 | - EP-CB10: Cake Box, 10" Compostable - 175 units @ $1.15 = $201.25 27 | - EP-CC12: Cupcake Container, 12-Pack - 200 units @ $1.25 = $250.00 28 | - EP-PB-M: Paper Bag, Medium - 3 packs @ $22.75 = $68.25 29 | - EP-PB-L: Paper Bag, Large - 2 packs @ $27.95 = $55.90 30 | - EP-WT: Wax Tissue Paper - 4 packs @ $32.50 = $130.00 31 | 32 | **Subtotal:** $917.90 33 | **Volume Discount (5%):** -$45.90 34 | **Shipping:** $0.00 35 | **Tax (8.5%):** $74.12 36 | **TOTAL DUE:** $946.12 37 | 38 | **DUE DATE:** March 2, 2025 39 | 40 | Please make checks payable to: EcoPack Solutions, Inc. 41 | For questions about this invoice, contact accounting@ecopack.com or call (510) 555-7895 -------------------------------------------------------------------------------- /data/markdown/invoice-4-2.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #ECOS-2025-1346 2 | **EcoPack Solutions, Inc.** 3 | 1750 Green Way 4 | Oakland, CA 94612 5 | Tel: (510) 555-7890 6 | www.ecopacksolutions.com 7 | 8 | **INVOICE TO:** 9 | Sunshine Bakery LLC 10 | 567 Main Street 11 | San Francisco, CA 94110 12 | 13 | **INVOICE NUMBER:** ECOS-2025-1346 14 | **DATE:** March 5, 2025 15 | **CUSTOMER ID:** SB-2207 16 | **ORDER NUMBER:** 46012 17 | **PAYMENT TERMS:** Net 20 days 18 | 19 | **SHIP TO:** 20 | Sunshine Bakery LLC 21 | 567 Main Street 22 | San Francisco, CA 94110 23 | 24 | **ITEMS:** 25 | - EP-CB8: Cake Box, 8" Compostable - 300 units @ $0.85 = $255.00 26 | - EP-CB10: Cake Box, 10" Compostable - 200 units @ $1.15 = $230.00 27 | - EP-CB12: Cake Box, 12" Compostable - 100 units @ $1.45 = $145.00 28 | - EP-CC6: Cupcake Container, 6-Pack - 150 units @ $0.75 = $112.50 29 | - EP-CC12: Cupcake Container, 12-Pack - 250 units @ $1.25 = $312.50 30 | - EP-PB-S: Paper Bag, Small - 2 packs @ $18.50 = $37.00 31 | - EP-PB-M: Paper Bag, Medium - 4 packs @ $22.75 = $91.00 32 | - EP-WT: Wax Tissue Paper - 5 packs @ $32.50 = $162.50 33 | - EP-SB-AS: Sandwich Box, Assorted Sizes - 3 packs @ $37.50 = $112.50 34 | 35 | **Subtotal:** $1,458.00 36 | **Volume Discount (5%):** -$72.90 37 | **Shipping:** $0.00 38 | **Tax (8.5%):** $117.74 39 | **TOTAL DUE:** $1,502.84 40 | 41 | **DUE DATE:** March 25, 2025 42 | 43 | Please make checks payable to: EcoPack Solutions, Inc. 44 | For questions about this invoice, contact accounting@ecopack.com or call (510) 555-7895 -------------------------------------------------------------------------------- /data/markdown/invoice-4-3.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #ECOS-2025-1487 2 | **EcoPack Solutions, Inc.** 3 | 1750 Green Way 4 | Oakland, CA 94612 5 | Tel: (510) 555-7890 6 | www.ecopacksolutions.com 7 | 8 | **INVOICE TO:** 9 | Sunshine Bakery LLC 10 | 567 Main Street 11 | San Francisco, CA 94110 12 | 13 | **INVOICE NUMBER:** ECOS-2025-1487 14 | **DATE:** April 2, 2025 15 | **CUSTOMER ID:** SB-2207 16 | **ORDER NUMBER:** 46587 17 | **PAYMENT TERMS:** Net 20 days 18 | 19 | **SHIP TO:** 20 | Sunshine Bakery LLC 21 | 567 Main Street 22 | San Francisco, CA 94110 23 | 24 | **ITEMS:** 25 | - EP-CB8: Cake Box, 8" Compostable - 350 units @ $0.90 = $315.00 26 | - EP-CB10: Cake Box, 10" Compostable - 225 units @ $1.22 = $274.50 27 | - EP-CB12: Cake Box, 12" Compostable - 125 units @ $1.54 = $192.50 28 | - EP-CC6: Cupcake Container, 6-Pack - 175 units @ $0.80 = $140.00 29 | - EP-CC12: Cupcake Container, 12-Pack - 275 units @ $1.32 = $363.00 30 | - EP-PB-S: Paper Bag, Small - 3 packs @ $19.50 = $58.50 31 | - EP-PB-M: Paper Bag, Medium - 5 packs @ $24.10 = $120.50 32 | - EP-PB-L: Paper Bag, Large - 4 packs @ $29.65 = $118.60 33 | - EP-WT: Wax Tissue Paper - 6 packs @ $34.50 = $207.00 34 | - EP-SB-AS: Sandwich Box, Assorted Sizes - 4 packs @ $39.75 = $159.00 35 | 36 | **Subtotal:** $1,948.60 37 | **Volume Discount (5%):** -$97.43 38 | **Shipping:** $0.00 39 | **Tax (8.5%):** $157.35 40 | **TOTAL DUE:** $2,008.52 41 | 42 | **NOTICE:** Price adjustment effective April 1, 2025 per Section 7 of contract. 43 | 44 | **DUE DATE:** April 22, 2025 45 | 46 | Please make checks payable to: EcoPack Solutions, Inc. 47 | For questions about this invoice, contact accounting@ecopack.com or call (510) 555-7895 -------------------------------------------------------------------------------- /data/markdown/invoice-5-1.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #GSFF-2025-0237 2 | **GOLDEN STATE FRUIT FARMS** 3 | 825 Orchard Road 4 | Watsonville, CA 95076 5 | Phone: (831) 555-9876 6 | Email: accounting@goldenstatefruitfarms.com 7 | 8 | **BILL TO:** 9 | Sunshine Bakery LLC 10 | 567 Main Street 11 | San Francisco, CA 94110 12 | 13 | **INVOICE #:** GSFF-2025-0237 14 | **DATE:** February 15, 2025 15 | **TERMS:** Net 14 days 16 | **DUE DATE:** March 1, 2025 17 | **PO #:** SB-F-0215 18 | 19 | **FRESH FRUIT DELIVERY:** 20 | - Strawberries, Organic: 15 lbs @ $4.25 = $63.75 21 | - Blueberries, Organic: 10 lbs @ $6.75 = $67.50 22 | - Apples, Gala: 25 lbs @ $1.85 = $46.25 23 | 24 | **FROZEN FRUIT DELIVERY:** 25 | - Strawberries, IQF: 20 lbs @ $3.85 = $77.00 26 | - Blueberries, IQF: 15 lbs @ $4.95 = $74.25 27 | - Mixed Berries, IQF: 25 lbs @ $4.50 = $112.50 28 | 29 | **PROCESSED FRUIT DELIVERY:** 30 | - Berry Puree, Unsweetened: 8 quarts @ $5.95 = $47.60 31 | - Apple Filling: 10 quarts @ $4.25 = $42.50 32 | 33 | **SUBTOTAL:** $531.35 34 | **DELIVERY FEE:** $0.00 35 | **TAX:** $0.00 36 | **TOTAL DUE:** $531.35 37 | 38 | Payment Options: 39 | - Check payable to: Golden State Fruit Farms 40 | - ACH Transfer (details on attached remittance advice) 41 | - Credit Card: Call (831) 555-9876 ext. 2 42 | 43 | For questions about this invoice, please contact: 44 | Jessica Martinez, Accounts Receivable 45 | Phone: (831) 555-9876 ext. 3 46 | Email: ar@goldenstatefruitfarms.com -------------------------------------------------------------------------------- /data/markdown/invoice-5-2.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #GSFF-2025-0298 2 | **GOLDEN STATE FRUIT FARMS** 3 | 825 Orchard Road 4 | Watsonville, CA 95076 5 | Phone: (831) 555-9876 6 | Email: accounting@goldenstatefruitfarms.com 7 | 8 | **BILL TO:** 9 | Sunshine Bakery LLC 10 | 567 Main Street 11 | San Francisco, CA 94110 12 | 13 | **INVOICE #:** GSFF-2025-0298 14 | **DATE:** March 5, 2025 15 | **TERMS:** Net 14 days 16 | **DUE DATE:** March 19, 2025 17 | **PO #:** SB-F-0304 18 | 19 | **FRESH FRUIT DELIVERY:** 20 | - Strawberries, Organic: 20 lbs @ $3.95 = $79.00 21 | - Blueberries, Organic: 12 lbs @ $6.50 = $78.00 22 | - Raspberries, Organic: 8 lbs @ $7.95 = $63.60 23 | - Apples, Mixed Varieties: 30 lbs @ $1.85 = $55.50 24 | 25 | **FROZEN FRUIT DELIVERY:** 26 | - Strawberries, IQF: 25 lbs @ $3.85 = $96.25 27 | - Blueberries, IQF: 18 lbs @ $4.95 = $89.10 28 | - Mixed Berries, IQF: 20 lbs @ $4.50 = $90.00 29 | 30 | **PROCESSED FRUIT DELIVERY:** 31 | - Berry Puree, Sweetened: 5 quarts @ $6.50 = $32.50 32 | - Berry Puree, Unsweetened: 10 quarts @ $5.95 = $59.50 33 | - Apple Filling: 12 quarts @ $4.25 = $51.00 34 | 35 | **SUBTOTAL:** $694.45 36 | **DELIVERY FEE:** $0.00 37 | **TAX:** $0.00 38 | **TOTAL DUE:** $694.45 39 | 40 | **NOTE:** Fresh berry pricing has been adjusted based on seasonal availability. 41 | 42 | Payment Options: 43 | - Check payable to: Golden State Fruit Farms 44 | - ACH Transfer (details on attached remittance advice) 45 | - Credit Card: Call (831) 555-9876 ext. 2 46 | 47 | For questions about this invoice, please contact: 48 | Jessica Martinez, Accounts Receivable 49 | Phone: (831) 555-9876 ext. 3 50 | Email: ar@goldenstatefruitfarms.com -------------------------------------------------------------------------------- /data/markdown/invoice-5-3.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #GSFF-2025-0357 2 | **GOLDEN STATE FRUIT FARMS** 3 | 825 Orchard Road 4 | Watsonville, CA 95076 5 | Phone: (831) 555-9876 6 | Email: accounting@goldenstatefruitfarms.com 7 | 8 | **BILL TO:** 9 | Sunshine Bakery LLC 10 | 567 Main Street 11 | San Francisco, CA 94110 12 | 13 | **INVOICE #:** GSFF-2025-0357 14 | **DATE:** March 26, 2025 15 | **TERMS:** Net 14 days 16 | **DUE DATE:** April 9, 2025 17 | **PO #:** SB-F-0325 18 | 19 | **FRESH FRUIT DELIVERY:** 20 | - Strawberries, Organic: 25 lbs @ $3.75 = $93.75 21 | - Blueberries, Organic: 15 lbs @ $6.25 = $93.75 22 | - Raspberries, Organic: 12 lbs @ $7.50 = $90.00 23 | - Blackberries, Organic: 8 lbs @ $6.25 = $50.00 24 | - Apples, Mixed Varieties: 35 lbs @ $1.85 = $64.75 25 | 26 | **FROZEN FRUIT DELIVERY:** 27 | - Strawberries, IQF: 30 lbs @ $3.85 = $115.50 28 | - Blueberries, IQF: 20 lbs @ $4.95 = $99.00 29 | - Mixed Berries, IQF: 25 lbs @ $4.50 = $112.50 30 | - Cherry Halves, IQF: 10 lbs @ $5.25 = $52.50 31 | 32 | **PROCESSED FRUIT DELIVERY:** 33 | - Berry Puree, Sweetened: 8 quarts @ $6.50 = $52.00 34 | - Berry Puree, Unsweetened: 12 quarts @ $5.95 = $71.40 35 | - Apple Filling: 15 quarts @ $4.25 = $63.75 36 | - Fruit Jam, Assorted: 6 quarts @ $8.95 = $53.70 37 | 38 | **SUBTOTAL:** $1,012.60 39 | **DELIVERY FEE:** $0.00 40 | **TAX:** $0.00 41 | **TOTAL DUE:** $1,012.60 42 | 43 | **NOTE:** Fresh berry pricing has been adjusted based on seasonal availability. 44 | 45 | Payment Options: 46 | - Check payable to: Golden State Fruit Farms 47 | - ACH Transfer (details on attached remittance advice) 48 | - Credit Card: Call (831) 555-9876 ext. 2 49 | 50 | For questions about this invoice, please contact: 51 | Jessica Martinez, Accounts Receivable 52 | Phone: (831) 555-9876 ext. 3 53 | Email: ar@goldenstatefruitfarms.com -------------------------------------------------------------------------------- /data/markdown/invoice-6-1.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #BEM-2025-0127 2 | **BAKERY EQUIPMENT MASTERS** 3 | 1875 Industrial Blvd. 4 | San Leandro, CA 94577 5 | Tel: (510) 555-4321 6 | www.bakeryequipmentmasters.com 7 | 8 | **INVOICE TO:** 9 | Sunshine Bakery LLC 10 | 567 Main Street 11 | San Francisco, CA 94110 12 | 13 | **INVOICE #:** BEM-2025-0127 14 | **DATE:** January 31, 2025 15 | **CUSTOMER ID:** SB-0042 16 | **CONTRACT #:** BEM-2025-042 17 | 18 | **BILL DESCRIPTION:** 19 | Monthly Equipment Maintenance - January 2025 20 | 21 | **REGULAR MAINTENANCE SERVICES:** 22 | - Standard Plan Monthly Fee: $575.00 23 | 24 | **ADDITIONAL SERVICES:** 25 | - Emergency Repair (1/17/25): Convection Oven Thermostat 26 | * After-hours service (2 hours @ $125 surcharge): $250.00 27 | * Thermostat replacement part: $185.00 28 | * Parts discount (15%): -$27.75 29 | 30 | **SUBTOTAL:** $982.25 31 | **TAX (Parts Only, 8.5%):** $13.37 32 | **TOTAL DUE:** $995.62 33 | 34 | **PAYMENT TERMS:** Net 15 days 35 | **DUE DATE:** February 15, 2025 36 | 37 | Please make checks payable to Bakery Equipment Masters 38 | For electronic payment options, contact accounting@bakeryequipmentmasters.com 39 | 40 | Service Notes: 41 | - Quarterly preventive maintenance completed January 22, 2025 42 | - Convection oven thermostat replaced during emergency service 43 | - All equipment tested and operating within specifications 44 | - Next scheduled maintenance: April 15-30, 2025 -------------------------------------------------------------------------------- /data/markdown/invoice-6-2.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #BEM-2025-0218 2 | **BAKERY EQUIPMENT MASTERS** 3 | 1875 Industrial Blvd. 4 | San Leandro, CA 94577 5 | Tel: (510) 555-4321 6 | www.bakeryequipmentmasters.com 7 | 8 | **INVOICE TO:** 9 | Sunshine Bakery LLC 10 | 567 Main Street 11 | San Francisco, CA 94110 12 | 13 | **INVOICE #:** BEM-2025-0218 14 | **DATE:** February 28, 2025 15 | **CUSTOMER ID:** SB-0042 16 | **CONTRACT #:** BEM-2025-042 17 | 18 | **BILL DESCRIPTION:** 19 | Monthly Equipment Maintenance - February 2025 20 | 21 | **REGULAR MAINTENANCE SERVICES:** 22 | - Standard Plan Monthly Fee: $575.00 23 | 24 | **ADDITIONAL SERVICES:** 25 | - Dough Sheeter Belt Replacement 26 | * Regular wear part (covered under plan): $0.00 27 | * Labor (covered under plan): $0.00 28 | 29 | - Staff Training Session (2/15/25) 30 | * Equipment operation best practices (3 hours): $0.00 31 | * Additional training materials: $45.00 32 | 33 | **SUBTOTAL:** $620.00 34 | **TAX (on materials, 8.5%):** $3.83 35 | **TOTAL DUE:** $623.83 36 | 37 | **PAYMENT TERMS:** Net 15 days 38 | **DUE DATE:** March 15, 2025 39 | 40 | Please make checks payable to Bakery Equipment Masters 41 | For electronic payment options, contact accounting@bakeryequipmentmasters.com 42 | 43 | Service Notes: 44 | - Dough sheeter belt replaced as preventive measure 45 | - Annual staff training session completed 46 | - All equipment operating within specifications 47 | - Next scheduled maintenance: April 15-30, 2025 -------------------------------------------------------------------------------- /data/markdown/invoice-6-3.md: -------------------------------------------------------------------------------- 1 | ## INVOICE #BEM-2025-0327 2 | **BAKERY EQUIPMENT MASTERS** 3 | 1875 Industrial Blvd. 4 | San Leandro, CA 94577 5 | Tel: (510) 555-4321 6 | www.bakeryequipmentmasters.com 7 | 8 | **INVOICE TO:** 9 | Sunshine Bakery LLC 10 | 567 Main Street 11 | San Francisco, CA 94110 12 | 13 | **INVOICE #:** BEM-2025-0327 14 | **DATE:** March 31, 2025 15 | **CUSTOMER ID:** SB-0042 16 | **CONTRACT #:** BEM-2025-042 17 | 18 | **BILL DESCRIPTION:** 19 | Monthly Equipment Maintenance - March 2025 20 | 21 | **REGULAR MAINTENANCE SERVICES:** 22 | - Standard Plan Monthly Fee: $575.00 23 | 24 | **ADDITIONAL SERVICES:** 25 | - Emergency Repair (3/12/25): Walk-in Refrigerator 26 | * Compressor issue diagnosis (covered under plan): $0.00 27 | * Compressor replacement (major component): $1,250.00 28 | * Parts discount (15%): -$187.50 29 | * Labor (covered under plan): $0.00 30 | 31 | - Deck Oven Element Replacement (3/25/25) 32 | * Heating element (regular wear part): $0.00 33 | * Labor (covered under plan): $0.00 34 | 35 | **SUBTOTAL:** $1,637.50 36 | **TAX (Parts Only, 8.5%):** $90.31 37 | **TOTAL DUE:** $1,727.81 38 | 39 | **PAYMENT TERMS:** Net 15 days 40 | **DUE DATE:** April 15, 2025 41 | 42 | Please make checks payable to Bakery Equipment Masters 43 | For electronic payment options, contact accounting@bakeryequipmentmasters.com 44 | 45 | Service Notes: 46 | - Walk-in refrigerator compressor replaced 47 | - Deck oven heating element replaced as preventive measure 48 | - All equipment tested and operating within specifications 49 | - Next scheduled maintenance: April 15-30, 2025 -------------------------------------------------------------------------------- /data/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-to-pdf", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "markdown-to-pdf", 9 | "version": "1.0.0", 10 | "dependencies": { 11 | "markdown-pdf": "^11.0.0" 12 | } 13 | }, 14 | "node_modules/ajv": { 15 | "version": "6.12.6", 16 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 17 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 18 | "license": "MIT", 19 | "dependencies": { 20 | "fast-deep-equal": "^3.1.1", 21 | "fast-json-stable-stringify": "^2.0.0", 22 | "json-schema-traverse": "^0.4.1", 23 | "uri-js": "^4.2.2" 24 | }, 25 | "funding": { 26 | "type": "github", 27 | "url": "https://github.com/sponsors/epoberezkin" 28 | } 29 | }, 30 | "node_modules/argparse": { 31 | "version": "1.0.10", 32 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 33 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 34 | "license": "MIT", 35 | "dependencies": { 36 | "sprintf-js": "~1.0.2" 37 | } 38 | }, 39 | "node_modules/asn1": { 40 | "version": "0.2.6", 41 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", 42 | "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", 43 | "license": "MIT", 44 | "dependencies": { 45 | "safer-buffer": "~2.1.0" 46 | } 47 | }, 48 | "node_modules/assert-plus": { 49 | "version": "1.0.0", 50 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 51 | "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", 52 | "license": "MIT", 53 | "engines": { 54 | "node": ">=0.8" 55 | } 56 | }, 57 | "node_modules/async": { 58 | "version": "1.5.2", 59 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", 60 | "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", 61 | "license": "MIT" 62 | }, 63 | "node_modules/asynckit": { 64 | "version": "0.4.0", 65 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 66 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 67 | "license": "MIT" 68 | }, 69 | "node_modules/autolinker": { 70 | "version": "3.16.2", 71 | "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-3.16.2.tgz", 72 | "integrity": "sha512-JiYl7j2Z19F9NdTmirENSUUIIL/9MytEWtmzhfmsKPCp9E+G35Y0UNCMoM9tFigxT59qSc8Ml2dlZXOCVTYwuA==", 73 | "license": "MIT", 74 | "dependencies": { 75 | "tslib": "^2.3.0" 76 | } 77 | }, 78 | "node_modules/aws-sign2": { 79 | "version": "0.7.0", 80 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 81 | "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", 82 | "license": "Apache-2.0", 83 | "engines": { 84 | "node": "*" 85 | } 86 | }, 87 | "node_modules/aws4": { 88 | "version": "1.13.2", 89 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", 90 | "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", 91 | "license": "MIT" 92 | }, 93 | "node_modules/balanced-match": { 94 | "version": "1.0.2", 95 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 96 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 97 | "license": "MIT" 98 | }, 99 | "node_modules/bcrypt-pbkdf": { 100 | "version": "1.0.2", 101 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 102 | "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", 103 | "license": "BSD-3-Clause", 104 | "dependencies": { 105 | "tweetnacl": "^0.14.3" 106 | } 107 | }, 108 | "node_modules/brace-expansion": { 109 | "version": "1.1.11", 110 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 111 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 112 | "license": "MIT", 113 | "dependencies": { 114 | "balanced-match": "^1.0.0", 115 | "concat-map": "0.0.1" 116 | } 117 | }, 118 | "node_modules/buffer-crc32": { 119 | "version": "0.2.13", 120 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", 121 | "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", 122 | "license": "MIT", 123 | "engines": { 124 | "node": "*" 125 | } 126 | }, 127 | "node_modules/buffer-from": { 128 | "version": "1.1.2", 129 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 130 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 131 | "license": "MIT" 132 | }, 133 | "node_modules/caseless": { 134 | "version": "0.12.0", 135 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 136 | "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", 137 | "license": "Apache-2.0" 138 | }, 139 | "node_modules/combined-stream": { 140 | "version": "1.0.8", 141 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 142 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 143 | "license": "MIT", 144 | "dependencies": { 145 | "delayed-stream": "~1.0.0" 146 | }, 147 | "engines": { 148 | "node": ">= 0.8" 149 | } 150 | }, 151 | "node_modules/commander": { 152 | "version": "3.0.2", 153 | "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", 154 | "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", 155 | "license": "MIT" 156 | }, 157 | "node_modules/concat-map": { 158 | "version": "0.0.1", 159 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 160 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 161 | "license": "MIT" 162 | }, 163 | "node_modules/concat-stream": { 164 | "version": "1.6.2", 165 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 166 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 167 | "engines": [ 168 | "node >= 0.8" 169 | ], 170 | "license": "MIT", 171 | "dependencies": { 172 | "buffer-from": "^1.0.0", 173 | "inherits": "^2.0.3", 174 | "readable-stream": "^2.2.2", 175 | "typedarray": "^0.0.6" 176 | } 177 | }, 178 | "node_modules/core-util-is": { 179 | "version": "1.0.3", 180 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 181 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", 182 | "license": "MIT" 183 | }, 184 | "node_modules/dashdash": { 185 | "version": "1.14.1", 186 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 187 | "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", 188 | "license": "MIT", 189 | "dependencies": { 190 | "assert-plus": "^1.0.0" 191 | }, 192 | "engines": { 193 | "node": ">=0.10" 194 | } 195 | }, 196 | "node_modules/debug": { 197 | "version": "2.6.9", 198 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 199 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 200 | "license": "MIT", 201 | "dependencies": { 202 | "ms": "2.0.0" 203 | } 204 | }, 205 | "node_modules/delayed-stream": { 206 | "version": "1.0.0", 207 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 208 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 209 | "license": "MIT", 210 | "engines": { 211 | "node": ">=0.4.0" 212 | } 213 | }, 214 | "node_modules/duplexer": { 215 | "version": "0.1.2", 216 | "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", 217 | "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", 218 | "license": "MIT" 219 | }, 220 | "node_modules/ecc-jsbn": { 221 | "version": "0.1.2", 222 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 223 | "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", 224 | "license": "MIT", 225 | "dependencies": { 226 | "jsbn": "~0.1.0", 227 | "safer-buffer": "^2.1.0" 228 | } 229 | }, 230 | "node_modules/es6-promise": { 231 | "version": "4.2.8", 232 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", 233 | "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", 234 | "license": "MIT" 235 | }, 236 | "node_modules/extend": { 237 | "version": "3.0.2", 238 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 239 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", 240 | "license": "MIT" 241 | }, 242 | "node_modules/extract-zip": { 243 | "version": "1.7.0", 244 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", 245 | "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", 246 | "license": "BSD-2-Clause", 247 | "dependencies": { 248 | "concat-stream": "^1.6.2", 249 | "debug": "^2.6.9", 250 | "mkdirp": "^0.5.4", 251 | "yauzl": "^2.10.0" 252 | }, 253 | "bin": { 254 | "extract-zip": "cli.js" 255 | } 256 | }, 257 | "node_modules/extsprintf": { 258 | "version": "1.3.0", 259 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 260 | "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", 261 | "engines": [ 262 | "node >=0.6.0" 263 | ], 264 | "license": "MIT" 265 | }, 266 | "node_modules/fast-deep-equal": { 267 | "version": "3.1.3", 268 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 269 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 270 | "license": "MIT" 271 | }, 272 | "node_modules/fast-json-stable-stringify": { 273 | "version": "2.1.0", 274 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 275 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 276 | "license": "MIT" 277 | }, 278 | "node_modules/fd-slicer": { 279 | "version": "1.1.0", 280 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", 281 | "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", 282 | "license": "MIT", 283 | "dependencies": { 284 | "pend": "~1.2.0" 285 | } 286 | }, 287 | "node_modules/forever-agent": { 288 | "version": "0.6.1", 289 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 290 | "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", 291 | "license": "Apache-2.0", 292 | "engines": { 293 | "node": "*" 294 | } 295 | }, 296 | "node_modules/form-data": { 297 | "version": "2.3.3", 298 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 299 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 300 | "license": "MIT", 301 | "dependencies": { 302 | "asynckit": "^0.4.0", 303 | "combined-stream": "^1.0.6", 304 | "mime-types": "^2.1.12" 305 | }, 306 | "engines": { 307 | "node": ">= 0.12" 308 | } 309 | }, 310 | "node_modules/fs-extra": { 311 | "version": "1.0.0", 312 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", 313 | "integrity": "sha512-VerQV6vEKuhDWD2HGOybV6v5I73syoc/cXAbKlgTC7M/oFVEtklWlp9QH2Ijw3IaWDOQcMkldSPa7zXy79Z/UQ==", 314 | "license": "MIT", 315 | "dependencies": { 316 | "graceful-fs": "^4.1.2", 317 | "jsonfile": "^2.1.0", 318 | "klaw": "^1.0.0" 319 | } 320 | }, 321 | "node_modules/fs.realpath": { 322 | "version": "1.0.0", 323 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 324 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 325 | "license": "ISC" 326 | }, 327 | "node_modules/getpass": { 328 | "version": "0.1.7", 329 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 330 | "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", 331 | "license": "MIT", 332 | "dependencies": { 333 | "assert-plus": "^1.0.0" 334 | } 335 | }, 336 | "node_modules/glob": { 337 | "version": "7.2.3", 338 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 339 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 340 | "deprecated": "Glob versions prior to v9 are no longer supported", 341 | "license": "ISC", 342 | "dependencies": { 343 | "fs.realpath": "^1.0.0", 344 | "inflight": "^1.0.4", 345 | "inherits": "2", 346 | "minimatch": "^3.1.1", 347 | "once": "^1.3.0", 348 | "path-is-absolute": "^1.0.0" 349 | }, 350 | "engines": { 351 | "node": "*" 352 | }, 353 | "funding": { 354 | "url": "https://github.com/sponsors/isaacs" 355 | } 356 | }, 357 | "node_modules/graceful-fs": { 358 | "version": "4.2.11", 359 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 360 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 361 | "license": "ISC" 362 | }, 363 | "node_modules/har-schema": { 364 | "version": "2.0.0", 365 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 366 | "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", 367 | "license": "ISC", 368 | "engines": { 369 | "node": ">=4" 370 | } 371 | }, 372 | "node_modules/har-validator": { 373 | "version": "5.1.5", 374 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", 375 | "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", 376 | "deprecated": "this library is no longer supported", 377 | "license": "MIT", 378 | "dependencies": { 379 | "ajv": "^6.12.3", 380 | "har-schema": "^2.0.0" 381 | }, 382 | "engines": { 383 | "node": ">=6" 384 | } 385 | }, 386 | "node_modules/hasha": { 387 | "version": "2.2.0", 388 | "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", 389 | "integrity": "sha512-jZ38TU/EBiGKrmyTNNZgnvCZHNowiRI4+w/I9noMlekHTZH3KyGgvJLmhSgykeAQ9j2SYPDosM0Bg3wHfzibAQ==", 390 | "license": "MIT", 391 | "dependencies": { 392 | "is-stream": "^1.0.1", 393 | "pinkie-promise": "^2.0.0" 394 | }, 395 | "engines": { 396 | "node": ">=0.10.0" 397 | } 398 | }, 399 | "node_modules/highlight.js": { 400 | "version": "10.7.3", 401 | "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", 402 | "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", 403 | "license": "BSD-3-Clause", 404 | "engines": { 405 | "node": "*" 406 | } 407 | }, 408 | "node_modules/http-signature": { 409 | "version": "1.2.0", 410 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 411 | "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", 412 | "license": "MIT", 413 | "dependencies": { 414 | "assert-plus": "^1.0.0", 415 | "jsprim": "^1.2.2", 416 | "sshpk": "^1.7.0" 417 | }, 418 | "engines": { 419 | "node": ">=0.8", 420 | "npm": ">=1.3.7" 421 | } 422 | }, 423 | "node_modules/inflight": { 424 | "version": "1.0.6", 425 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 426 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 427 | "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", 428 | "license": "ISC", 429 | "dependencies": { 430 | "once": "^1.3.0", 431 | "wrappy": "1" 432 | } 433 | }, 434 | "node_modules/inherits": { 435 | "version": "2.0.4", 436 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 437 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 438 | "license": "ISC" 439 | }, 440 | "node_modules/is-stream": { 441 | "version": "1.1.0", 442 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 443 | "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", 444 | "license": "MIT", 445 | "engines": { 446 | "node": ">=0.10.0" 447 | } 448 | }, 449 | "node_modules/is-typedarray": { 450 | "version": "1.0.0", 451 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 452 | "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", 453 | "license": "MIT" 454 | }, 455 | "node_modules/isarray": { 456 | "version": "1.0.0", 457 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 458 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", 459 | "license": "MIT" 460 | }, 461 | "node_modules/isexe": { 462 | "version": "2.0.0", 463 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 464 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 465 | "license": "ISC" 466 | }, 467 | "node_modules/isstream": { 468 | "version": "0.1.2", 469 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 470 | "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", 471 | "license": "MIT" 472 | }, 473 | "node_modules/jsbn": { 474 | "version": "0.1.1", 475 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 476 | "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", 477 | "license": "MIT" 478 | }, 479 | "node_modules/json-schema": { 480 | "version": "0.4.0", 481 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", 482 | "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", 483 | "license": "(AFL-2.1 OR BSD-3-Clause)" 484 | }, 485 | "node_modules/json-schema-traverse": { 486 | "version": "0.4.1", 487 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 488 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 489 | "license": "MIT" 490 | }, 491 | "node_modules/json-stringify-safe": { 492 | "version": "5.0.1", 493 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 494 | "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", 495 | "license": "ISC" 496 | }, 497 | "node_modules/jsonfile": { 498 | "version": "2.4.0", 499 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", 500 | "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", 501 | "license": "MIT", 502 | "optionalDependencies": { 503 | "graceful-fs": "^4.1.6" 504 | } 505 | }, 506 | "node_modules/jsprim": { 507 | "version": "1.4.2", 508 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", 509 | "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", 510 | "license": "MIT", 511 | "dependencies": { 512 | "assert-plus": "1.0.0", 513 | "extsprintf": "1.3.0", 514 | "json-schema": "0.4.0", 515 | "verror": "1.10.0" 516 | }, 517 | "engines": { 518 | "node": ">=0.6.0" 519 | } 520 | }, 521 | "node_modules/kew": { 522 | "version": "0.7.0", 523 | "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", 524 | "integrity": "sha512-IG6nm0+QtAMdXt9KvbgbGdvY50RSrw+U4sGZg+KlrSKPJEwVE5JVoI3d7RWfSMdBQneRheeAOj3lIjX5VL/9RQ==", 525 | "license": "Apache-2.0" 526 | }, 527 | "node_modules/klaw": { 528 | "version": "1.3.1", 529 | "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", 530 | "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", 531 | "license": "MIT", 532 | "optionalDependencies": { 533 | "graceful-fs": "^4.1.9" 534 | } 535 | }, 536 | "node_modules/markdown-pdf": { 537 | "version": "11.0.0", 538 | "resolved": "https://registry.npmjs.org/markdown-pdf/-/markdown-pdf-11.0.0.tgz", 539 | "integrity": "sha512-h75sQdlJeTDWB/Q3U39iHBlwGDU9oCoZ4fsv/7bB/fK8/ergDK2r8CPrEKFg0DqT8coA+d8EhUB2+i1UNBaDag==", 540 | "license": "MIT", 541 | "dependencies": { 542 | "commander": "^3.0.0", 543 | "duplexer": "^0.1.1", 544 | "extend": "^3.0.2", 545 | "highlight.js": "^10.0.0", 546 | "phantomjs-prebuilt": "^2.1.3", 547 | "remarkable": "^2.0.0", 548 | "stream-from-to": "^1.4.2", 549 | "through2": "^3.0.1", 550 | "tmp": "^0.1.0" 551 | }, 552 | "bin": { 553 | "markdown-pdf": "bin/markdown-pdf" 554 | }, 555 | "engines": { 556 | "node": ">=0.10.0" 557 | } 558 | }, 559 | "node_modules/mime-db": { 560 | "version": "1.52.0", 561 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 562 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 563 | "license": "MIT", 564 | "engines": { 565 | "node": ">= 0.6" 566 | } 567 | }, 568 | "node_modules/mime-types": { 569 | "version": "2.1.35", 570 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 571 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 572 | "license": "MIT", 573 | "dependencies": { 574 | "mime-db": "1.52.0" 575 | }, 576 | "engines": { 577 | "node": ">= 0.6" 578 | } 579 | }, 580 | "node_modules/minimatch": { 581 | "version": "3.1.2", 582 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 583 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 584 | "license": "ISC", 585 | "dependencies": { 586 | "brace-expansion": "^1.1.7" 587 | }, 588 | "engines": { 589 | "node": "*" 590 | } 591 | }, 592 | "node_modules/minimist": { 593 | "version": "1.2.8", 594 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 595 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 596 | "license": "MIT", 597 | "funding": { 598 | "url": "https://github.com/sponsors/ljharb" 599 | } 600 | }, 601 | "node_modules/mkdirp": { 602 | "version": "0.5.6", 603 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", 604 | "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", 605 | "license": "MIT", 606 | "dependencies": { 607 | "minimist": "^1.2.6" 608 | }, 609 | "bin": { 610 | "mkdirp": "bin/cmd.js" 611 | } 612 | }, 613 | "node_modules/ms": { 614 | "version": "2.0.0", 615 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 616 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", 617 | "license": "MIT" 618 | }, 619 | "node_modules/oauth-sign": { 620 | "version": "0.9.0", 621 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 622 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", 623 | "license": "Apache-2.0", 624 | "engines": { 625 | "node": "*" 626 | } 627 | }, 628 | "node_modules/once": { 629 | "version": "1.4.0", 630 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 631 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 632 | "license": "ISC", 633 | "dependencies": { 634 | "wrappy": "1" 635 | } 636 | }, 637 | "node_modules/path-is-absolute": { 638 | "version": "1.0.1", 639 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 640 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 641 | "license": "MIT", 642 | "engines": { 643 | "node": ">=0.10.0" 644 | } 645 | }, 646 | "node_modules/pend": { 647 | "version": "1.2.0", 648 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", 649 | "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", 650 | "license": "MIT" 651 | }, 652 | "node_modules/performance-now": { 653 | "version": "2.1.0", 654 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 655 | "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", 656 | "license": "MIT" 657 | }, 658 | "node_modules/phantomjs-prebuilt": { 659 | "version": "2.1.16", 660 | "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz", 661 | "integrity": "sha512-PIiRzBhW85xco2fuj41FmsyuYHKjKuXWmhjy3A/Y+CMpN/63TV+s9uzfVhsUwFe0G77xWtHBG8xmXf5BqEUEuQ==", 662 | "deprecated": "this package is now deprecated", 663 | "hasInstallScript": true, 664 | "license": "Apache-2.0", 665 | "dependencies": { 666 | "es6-promise": "^4.0.3", 667 | "extract-zip": "^1.6.5", 668 | "fs-extra": "^1.0.0", 669 | "hasha": "^2.2.0", 670 | "kew": "^0.7.0", 671 | "progress": "^1.1.8", 672 | "request": "^2.81.0", 673 | "request-progress": "^2.0.1", 674 | "which": "^1.2.10" 675 | }, 676 | "bin": { 677 | "phantomjs": "bin/phantomjs" 678 | } 679 | }, 680 | "node_modules/pinkie": { 681 | "version": "2.0.4", 682 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", 683 | "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", 684 | "license": "MIT", 685 | "engines": { 686 | "node": ">=0.10.0" 687 | } 688 | }, 689 | "node_modules/pinkie-promise": { 690 | "version": "2.0.1", 691 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", 692 | "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", 693 | "license": "MIT", 694 | "dependencies": { 695 | "pinkie": "^2.0.0" 696 | }, 697 | "engines": { 698 | "node": ">=0.10.0" 699 | } 700 | }, 701 | "node_modules/process-nextick-args": { 702 | "version": "2.0.1", 703 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 704 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", 705 | "license": "MIT" 706 | }, 707 | "node_modules/progress": { 708 | "version": "1.1.8", 709 | "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", 710 | "integrity": "sha512-UdA8mJ4weIkUBO224tIarHzuHs4HuYiJvsuGT7j/SPQiUJVjYvNDBIPa0hAorduOfjGohB/qHWRa/lrrWX/mXw==", 711 | "engines": { 712 | "node": ">=0.4.0" 713 | } 714 | }, 715 | "node_modules/psl": { 716 | "version": "1.15.0", 717 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", 718 | "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", 719 | "license": "MIT", 720 | "dependencies": { 721 | "punycode": "^2.3.1" 722 | }, 723 | "funding": { 724 | "url": "https://github.com/sponsors/lupomontero" 725 | } 726 | }, 727 | "node_modules/punycode": { 728 | "version": "2.3.1", 729 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 730 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 731 | "license": "MIT", 732 | "engines": { 733 | "node": ">=6" 734 | } 735 | }, 736 | "node_modules/qs": { 737 | "version": "6.5.3", 738 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", 739 | "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", 740 | "license": "BSD-3-Clause", 741 | "engines": { 742 | "node": ">=0.6" 743 | } 744 | }, 745 | "node_modules/readable-stream": { 746 | "version": "2.3.8", 747 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", 748 | "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", 749 | "license": "MIT", 750 | "dependencies": { 751 | "core-util-is": "~1.0.0", 752 | "inherits": "~2.0.3", 753 | "isarray": "~1.0.0", 754 | "process-nextick-args": "~2.0.0", 755 | "safe-buffer": "~5.1.1", 756 | "string_decoder": "~1.1.1", 757 | "util-deprecate": "~1.0.1" 758 | } 759 | }, 760 | "node_modules/remarkable": { 761 | "version": "2.0.1", 762 | "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-2.0.1.tgz", 763 | "integrity": "sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==", 764 | "license": "MIT", 765 | "dependencies": { 766 | "argparse": "^1.0.10", 767 | "autolinker": "^3.11.0" 768 | }, 769 | "bin": { 770 | "remarkable": "bin/remarkable.js" 771 | }, 772 | "engines": { 773 | "node": ">= 6.0.0" 774 | } 775 | }, 776 | "node_modules/request": { 777 | "version": "2.88.2", 778 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", 779 | "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", 780 | "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", 781 | "license": "Apache-2.0", 782 | "dependencies": { 783 | "aws-sign2": "~0.7.0", 784 | "aws4": "^1.8.0", 785 | "caseless": "~0.12.0", 786 | "combined-stream": "~1.0.6", 787 | "extend": "~3.0.2", 788 | "forever-agent": "~0.6.1", 789 | "form-data": "~2.3.2", 790 | "har-validator": "~5.1.3", 791 | "http-signature": "~1.2.0", 792 | "is-typedarray": "~1.0.0", 793 | "isstream": "~0.1.2", 794 | "json-stringify-safe": "~5.0.1", 795 | "mime-types": "~2.1.19", 796 | "oauth-sign": "~0.9.0", 797 | "performance-now": "^2.1.0", 798 | "qs": "~6.5.2", 799 | "safe-buffer": "^5.1.2", 800 | "tough-cookie": "~2.5.0", 801 | "tunnel-agent": "^0.6.0", 802 | "uuid": "^3.3.2" 803 | }, 804 | "engines": { 805 | "node": ">= 6" 806 | } 807 | }, 808 | "node_modules/request-progress": { 809 | "version": "2.0.1", 810 | "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", 811 | "integrity": "sha512-dxdraeZVUNEn9AvLrxkgB2k6buTlym71dJk1fk4v8j3Ou3RKNm07BcgbHdj2lLgYGfqX71F+awb1MR+tWPFJzA==", 812 | "license": "MIT", 813 | "dependencies": { 814 | "throttleit": "^1.0.0" 815 | } 816 | }, 817 | "node_modules/rimraf": { 818 | "version": "2.7.1", 819 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 820 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 821 | "deprecated": "Rimraf versions prior to v4 are no longer supported", 822 | "license": "ISC", 823 | "dependencies": { 824 | "glob": "^7.1.3" 825 | }, 826 | "bin": { 827 | "rimraf": "bin.js" 828 | } 829 | }, 830 | "node_modules/safe-buffer": { 831 | "version": "5.1.2", 832 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 833 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 834 | "license": "MIT" 835 | }, 836 | "node_modules/safer-buffer": { 837 | "version": "2.1.2", 838 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 839 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 840 | "license": "MIT" 841 | }, 842 | "node_modules/series-stream": { 843 | "version": "1.0.1", 844 | "resolved": "https://registry.npmjs.org/series-stream/-/series-stream-1.0.1.tgz", 845 | "integrity": "sha512-4bATV1VVzG+Mgwzjvnts/yr1JDflogCZo+tnPlF+F4zBLQgCcF58r6a4EZxWskse0Jz9wD7nEJ3jI2OmAdQiUg==", 846 | "license": "ISC" 847 | }, 848 | "node_modules/sprintf-js": { 849 | "version": "1.0.3", 850 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 851 | "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", 852 | "license": "BSD-3-Clause" 853 | }, 854 | "node_modules/sshpk": { 855 | "version": "1.18.0", 856 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", 857 | "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", 858 | "license": "MIT", 859 | "dependencies": { 860 | "asn1": "~0.2.3", 861 | "assert-plus": "^1.0.0", 862 | "bcrypt-pbkdf": "^1.0.0", 863 | "dashdash": "^1.12.0", 864 | "ecc-jsbn": "~0.1.1", 865 | "getpass": "^0.1.1", 866 | "jsbn": "~0.1.0", 867 | "safer-buffer": "^2.0.2", 868 | "tweetnacl": "~0.14.0" 869 | }, 870 | "bin": { 871 | "sshpk-conv": "bin/sshpk-conv", 872 | "sshpk-sign": "bin/sshpk-sign", 873 | "sshpk-verify": "bin/sshpk-verify" 874 | }, 875 | "engines": { 876 | "node": ">=0.10.0" 877 | } 878 | }, 879 | "node_modules/stream-from-to": { 880 | "version": "1.4.3", 881 | "resolved": "https://registry.npmjs.org/stream-from-to/-/stream-from-to-1.4.3.tgz", 882 | "integrity": "sha512-924UPDggaWjtnsFFHv9tF2TX3fbsEDaj0ZjJoMLXjTPXsSTkLeWtNoaeqA+LzRu+0BvThmChMwCcW23jGlOl0w==", 883 | "license": "MIT", 884 | "dependencies": { 885 | "async": "^1.5.2", 886 | "concat-stream": "^1.4.7", 887 | "mkdirp": "^0.5.0", 888 | "series-stream": "^1.0.1" 889 | } 890 | }, 891 | "node_modules/string_decoder": { 892 | "version": "1.1.1", 893 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 894 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 895 | "license": "MIT", 896 | "dependencies": { 897 | "safe-buffer": "~5.1.0" 898 | } 899 | }, 900 | "node_modules/throttleit": { 901 | "version": "1.0.1", 902 | "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", 903 | "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", 904 | "license": "MIT", 905 | "funding": { 906 | "url": "https://github.com/sponsors/sindresorhus" 907 | } 908 | }, 909 | "node_modules/through2": { 910 | "version": "3.0.2", 911 | "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", 912 | "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", 913 | "license": "MIT", 914 | "dependencies": { 915 | "inherits": "^2.0.4", 916 | "readable-stream": "2 || 3" 917 | } 918 | }, 919 | "node_modules/tmp": { 920 | "version": "0.1.0", 921 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", 922 | "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", 923 | "license": "MIT", 924 | "dependencies": { 925 | "rimraf": "^2.6.3" 926 | }, 927 | "engines": { 928 | "node": ">=6" 929 | } 930 | }, 931 | "node_modules/tough-cookie": { 932 | "version": "2.5.0", 933 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", 934 | "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", 935 | "license": "BSD-3-Clause", 936 | "dependencies": { 937 | "psl": "^1.1.28", 938 | "punycode": "^2.1.1" 939 | }, 940 | "engines": { 941 | "node": ">=0.8" 942 | } 943 | }, 944 | "node_modules/tslib": { 945 | "version": "2.8.1", 946 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 947 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 948 | "license": "0BSD" 949 | }, 950 | "node_modules/tunnel-agent": { 951 | "version": "0.6.0", 952 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 953 | "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", 954 | "license": "Apache-2.0", 955 | "dependencies": { 956 | "safe-buffer": "^5.0.1" 957 | }, 958 | "engines": { 959 | "node": "*" 960 | } 961 | }, 962 | "node_modules/tweetnacl": { 963 | "version": "0.14.5", 964 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 965 | "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", 966 | "license": "Unlicense" 967 | }, 968 | "node_modules/typedarray": { 969 | "version": "0.0.6", 970 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 971 | "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", 972 | "license": "MIT" 973 | }, 974 | "node_modules/uri-js": { 975 | "version": "4.4.1", 976 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 977 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 978 | "license": "BSD-2-Clause", 979 | "dependencies": { 980 | "punycode": "^2.1.0" 981 | } 982 | }, 983 | "node_modules/util-deprecate": { 984 | "version": "1.0.2", 985 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 986 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 987 | "license": "MIT" 988 | }, 989 | "node_modules/uuid": { 990 | "version": "3.4.0", 991 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", 992 | "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", 993 | "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", 994 | "license": "MIT", 995 | "bin": { 996 | "uuid": "bin/uuid" 997 | } 998 | }, 999 | "node_modules/verror": { 1000 | "version": "1.10.0", 1001 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 1002 | "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", 1003 | "engines": [ 1004 | "node >=0.6.0" 1005 | ], 1006 | "license": "MIT", 1007 | "dependencies": { 1008 | "assert-plus": "^1.0.0", 1009 | "core-util-is": "1.0.2", 1010 | "extsprintf": "^1.2.0" 1011 | } 1012 | }, 1013 | "node_modules/verror/node_modules/core-util-is": { 1014 | "version": "1.0.2", 1015 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 1016 | "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", 1017 | "license": "MIT" 1018 | }, 1019 | "node_modules/which": { 1020 | "version": "1.3.1", 1021 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 1022 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 1023 | "license": "ISC", 1024 | "dependencies": { 1025 | "isexe": "^2.0.0" 1026 | }, 1027 | "bin": { 1028 | "which": "bin/which" 1029 | } 1030 | }, 1031 | "node_modules/wrappy": { 1032 | "version": "1.0.2", 1033 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1034 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 1035 | "license": "ISC" 1036 | }, 1037 | "node_modules/yauzl": { 1038 | "version": "2.10.0", 1039 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", 1040 | "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", 1041 | "license": "MIT", 1042 | "dependencies": { 1043 | "buffer-crc32": "~0.2.3", 1044 | "fd-slicer": "~1.1.0" 1045 | } 1046 | } 1047 | } 1048 | } 1049 | -------------------------------------------------------------------------------- /data/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-to-pdf", 3 | "version": "1.0.0", 4 | "description": "Convert markdown files to PDF", 5 | "main": "convert.js", 6 | "scripts": { 7 | "convert": "node convert.js" 8 | }, 9 | "dependencies": { 10 | "markdown-pdf": "^11.0.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /data/pdf/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/pdf/.DS_Store -------------------------------------------------------------------------------- /data/pdf/contract-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/pdf/contract-1.pdf -------------------------------------------------------------------------------- /data/pdf/contract-2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/pdf/contract-2.pdf -------------------------------------------------------------------------------- /data/pdf/contract-3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/pdf/contract-3.pdf -------------------------------------------------------------------------------- /data/pdf/contract-4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/pdf/contract-4.pdf -------------------------------------------------------------------------------- /data/pdf/contract-5.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/pdf/contract-5.pdf -------------------------------------------------------------------------------- /data/pdf/contract-6.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/pdf/contract-6.pdf -------------------------------------------------------------------------------- /data/pdf/invoice-1-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/pdf/invoice-1-1.pdf -------------------------------------------------------------------------------- /data/pdf/invoice-1-2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/pdf/invoice-1-2.pdf -------------------------------------------------------------------------------- /data/pdf/invoice-1-3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/data/pdf/invoice-1-3.pdf -------------------------------------------------------------------------------- /data/split_markdown.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | function splitMarkdownFile(inputFile, outputDir) { 5 | // Create output directory if it doesn't exist 6 | if (!fs.existsSync(outputDir)) { 7 | fs.mkdirSync(outputDir, { recursive: true }); 8 | } 9 | 10 | // Read the input file 11 | const content = fs.readFileSync(inputFile, 'utf8'); 12 | 13 | // Split content by page separator 14 | const pages = content.split('\n---\n'); 15 | 16 | // Process each page 17 | pages.forEach((page, index) => { 18 | // Skip empty pages 19 | if (!page.trim()) { 20 | return; 21 | } 22 | 23 | // Extract title from the page content 24 | const titleMatch = page.match(/^#\s+(.+)$/m); 25 | let filename; 26 | 27 | if (titleMatch) { 28 | const title = titleMatch[1] 29 | .toLowerCase() 30 | .replace(/\s+/g, '-') 31 | .replace(/[^a-z0-9-]/g, ''); 32 | filename = `${String(index + 1).padStart(2, '0')}-${title}.md`; 33 | } else { 34 | filename = `${String(index + 1).padStart(2, '0')}-page.md`; 35 | } 36 | 37 | // Write the page to a new file 38 | const outputPath = path.join(outputDir, filename); 39 | fs.writeFileSync(outputPath, page.trim()); 40 | }); 41 | 42 | console.log(`Successfully split markdown file into individual files in ${outputDir}`); 43 | } 44 | 45 | // Main execution 46 | const inputFile = 'markdown/supplier-contracts-invoices-additional.md'; 47 | const outputDir = 'markdown/split'; 48 | splitMarkdownFile(inputFile, outputDir); 49 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /frontend/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | /* config options here */ 4 | }; 5 | 6 | module.exports = nextConfig; 7 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@llamaindex/openai": "^0.3.4", 13 | "@radix-ui/react-collapsible": "^1.1.8", 14 | "@radix-ui/react-slot": "^1.2.0", 15 | "@tailwindcss/typography": "^0.5.16", 16 | "animate.css": "^4.1.1", 17 | "class-variance-authority": "^0.7.1", 18 | "clsx": "^2.1.1", 19 | "fumadocs-ui": "^15.2.10", 20 | "lucide-react": "^0.344.0", 21 | "next": "14.1.0", 22 | "react": "^18.2.0", 23 | "react-dom": "^18.2.0", 24 | "react-markdown": "^10.1.0", 25 | "remark-gfm": "^4.0.1", 26 | "tailwind-merge": "^2.6.0", 27 | "tailwindcss-animate": "^1.0.7" 28 | }, 29 | "devDependencies": { 30 | "@types/node": "^20", 31 | "@types/react": "^18", 32 | "@types/react-dom": "^18", 33 | "autoprefixer": "^10.4.21", 34 | "postcss": "^8.4.35", 35 | "postcss-import": "^16.1.0", 36 | "tailwindcss": "^3.4.1", 37 | "typescript": "^5" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-import': {}, 4 | 'tailwindcss/nesting': {}, 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /frontend/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/frontend/public/logo.png -------------------------------------------------------------------------------- /frontend/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/api/auth/validate/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | 3 | export async function POST(request: Request) { 4 | const { apiKey } = await request.json(); 5 | 6 | if (!apiKey || apiKey.trim() === '') { 7 | return NextResponse.json({ keyStatus: 'invalid' }, { status: 401 }); 8 | } 9 | 10 | try { 11 | const response = await fetch('https://api.cloud.llamaindex.ai/api/v1/projects/current', { 12 | headers: { 13 | 'Authorization': `Bearer ${apiKey}`, 14 | 'Content-Type': 'application/json' 15 | } 16 | }); 17 | 18 | if (response.status === 200) { 19 | const data = await response.json(); 20 | return NextResponse.json({ 21 | keyStatus: 'valid', 22 | project_id: data.id, 23 | organization_id: data.organization_id 24 | }); 25 | } 26 | 27 | return NextResponse.json({ keyStatus: 'invalid' }, { status: 401 }); 28 | } catch (error) { 29 | console.error('Error validating API key:', error); 30 | return NextResponse.json({ keyStatus: 'invalid' }, { status: 401 }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/app/api/contracts/list/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | 3 | export async function GET(request: NextRequest) { 4 | try { 5 | const apiKey = request.headers.get('X-API-Key'); 6 | const organizationId = request.headers.get('X-Organization-Id'); 7 | const projectId = request.headers.get('X-Project-Id'); 8 | const indexId = request.headers.get('X-Index-Id'); 9 | 10 | if (!apiKey || !organizationId || !projectId || !indexId) { 11 | return NextResponse.json( 12 | { error: 'Missing required headers' }, 13 | { status: 400 } 14 | ); 15 | } 16 | 17 | const response = await fetch( 18 | `https://api.cloud.llamaindex.ai/api/v1/pipelines/${indexId}/documents`, 19 | { 20 | headers: { 21 | 'Authorization': `Bearer ${apiKey}`, 22 | 'X-Organization-Id': organizationId, 23 | 'X-Project-Id': projectId, 24 | }, 25 | } 26 | ); 27 | 28 | if (!response.ok) { 29 | const error = await response.json(); 30 | return NextResponse.json( 31 | { error: 'Failed to fetch documents', details: error }, 32 | { status: response.status } 33 | ); 34 | } 35 | 36 | const data = await response.json(); 37 | 38 | // Transform the response into our expected format 39 | const contracts = data.map((doc: any) => ({ 40 | id: doc.id, 41 | friendlyName: doc.metadata?.friendlyName || null, 42 | fileName: doc.metadata?.file_name || 'Unknown file', 43 | markdown: doc.text 44 | })); 45 | 46 | return NextResponse.json(contracts); 47 | } catch (error) { 48 | console.error('Error in contracts list:', error); 49 | return NextResponse.json( 50 | { error: 'Internal server error' }, 51 | { status: 500 } 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /frontend/src/app/api/contracts/setname/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | import { OpenAI } from '@llamaindex/openai'; 3 | 4 | export async function GET(req: NextRequest) { 5 | try { 6 | // Check required headers 7 | const apiKey = req.headers.get('X-API-Key'); 8 | const organizationId = req.headers.get('X-Organization-Id'); 9 | const projectId = req.headers.get('X-Project-Id'); 10 | const indexId = req.headers.get('X-Index-Id'); 11 | 12 | if (!apiKey || !organizationId || !projectId || !indexId) { 13 | return NextResponse.json( 14 | { error: 'Missing required headers' }, 15 | { status: 400 } 16 | ); 17 | } 18 | 19 | // Get contract ID from query parameter 20 | const { searchParams } = new URL(req.url); 21 | const contractId = searchParams.get('contract_id'); 22 | 23 | if (!contractId) { 24 | return NextResponse.json( 25 | { error: 'Missing contract_id query parameter' }, 26 | { status: 400 } 27 | ); 28 | } 29 | 30 | // Fetch document from LlamaIndex API 31 | const response = await fetch( 32 | `https://api.cloud.llamaindex.ai/api/v1/pipelines/${indexId}/documents/${contractId}`, 33 | { 34 | headers: { 35 | 'Authorization': `Bearer ${apiKey}`, 36 | 'X-Organization-Id': organizationId, 37 | 'X-Project-Id': projectId, 38 | }, 39 | } 40 | ); 41 | 42 | if (!response.ok) { 43 | const error = await response.json(); 44 | return NextResponse.json( 45 | { error: 'Failed to fetch document', details: error }, 46 | { status: response.status } 47 | ); 48 | } 49 | 50 | const data = await response.json(); 51 | 52 | console.log("Stored contract data:", data); 53 | 54 | const contractText = data.text; 55 | 56 | const llm = new OpenAI({ 57 | apiKey: process.env.OPENAI_API_KEY, 58 | model: 'gpt-4o', 59 | }); 60 | 61 | const nameResponse = await llm.complete({ 62 | prompt: `Read the following contract and come up with a friendly name for it, 63 | probably based on the name of the company issuing the contract. 64 | ${contractText} 65 | Respond with the name as a string only, no other text, no preamble.`, 66 | }); 67 | 68 | const friendlyName = nameResponse.text.trim(); 69 | 70 | // Update the document with the friendly name 71 | const updateResponse = await fetch( 72 | `https://api.cloud.llamaindex.ai/api/v1/pipelines/${indexId}/documents`, 73 | { 74 | method: 'PUT', 75 | headers: { 76 | 'Authorization': `Bearer ${apiKey}`, 77 | 'X-Organization-Id': organizationId, 78 | 'X-Project-Id': projectId, 79 | 'Content-Type': 'application/json', 80 | }, 81 | body: JSON.stringify([{ 82 | text: contractText, 83 | metadata: { 84 | ...data.metadata, 85 | friendlyName: friendlyName, 86 | document_id: contractId 87 | }, 88 | id: contractId 89 | }]) 90 | } 91 | ); 92 | 93 | if (!updateResponse.ok) { 94 | const error = await updateResponse.json(); 95 | return NextResponse.json( 96 | { error: 'Failed to update document', details: error }, 97 | { status: updateResponse.status } 98 | ); 99 | } 100 | 101 | const updateData = await updateResponse.json(); 102 | console.log('Update response:', updateData); 103 | 104 | return NextResponse.json({ 105 | success: true, 106 | message: 'Contract name updated successfully', 107 | data 108 | }); 109 | 110 | } catch (error) { 111 | console.error('Error in setname endpoint:', error); 112 | return NextResponse.json( 113 | { error: 'Internal server error' }, 114 | { status: 500 } 115 | ); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /frontend/src/app/api/contracts/upload/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | 3 | export async function POST(req: NextRequest) { 4 | try { 5 | // Check API key 6 | const apiKey = req.headers.get('X-API-Key'); 7 | const indexId = req.headers.get('X-Index-ID'); 8 | const projectId = req.headers.get('X-Project-ID'); 9 | const organizationId = req.headers.get('X-Organization-ID'); 10 | 11 | console.log('Received headers:', { 12 | apiKey: apiKey ? 'present' : 'missing', 13 | indexId: indexId ? 'present' : 'missing', 14 | projectId: projectId ? 'present' : 'missing', 15 | organizationId: organizationId ? 'present' : 'missing' 16 | }); 17 | 18 | if (!apiKey || !indexId || !projectId || !organizationId) { 19 | return NextResponse.json({ error: 'Missing required headers' }, { status: 401 }); 20 | } 21 | 22 | // Parse the multipart form data 23 | const formData = await req.formData(); 24 | const files: File[] = []; 25 | let index = 0; 26 | while (formData.has(`file_${index}`)) { 27 | const file = formData.get(`file_${index}`) as File; 28 | if (file) { 29 | files.push(file); 30 | } 31 | index++; 32 | } 33 | 34 | console.log(`Found ${files.length} files to upload`); 35 | 36 | if (files.length === 0) { 37 | return NextResponse.json({ error: 'No files provided' }, { status: 400 }); 38 | } 39 | 40 | // Step 1: Upload each file to get file IDs 41 | const fileIds = await Promise.all( 42 | files.map(async (file) => { 43 | console.log(`Uploading file: ${file.name}`); 44 | const uploadFormData = new FormData(); 45 | uploadFormData.append('upload_file', file); 46 | 47 | const url = `https://api.cloud.llamaindex.ai/api/v1/files?project_id=${projectId}&organization_id=${organizationId}&external_file_id=${encodeURIComponent(file.name)}`; 48 | console.log('Upload URL:', url); 49 | 50 | const response = await fetch(url, { 51 | method: 'POST', 52 | headers: { 53 | 'Authorization': `Bearer ${apiKey}`, 54 | }, 55 | body: uploadFormData, 56 | }); 57 | 58 | console.log(`File upload response status: ${response.status}`); 59 | if (!response.ok) { 60 | const errorText = await response.text(); 61 | console.error(`File upload error: ${errorText}`); 62 | throw new Error(`Failed to upload file ${file.name}: ${errorText}`); 63 | } 64 | 65 | const data = await response.json(); 66 | console.log(`File upload response:`, data); 67 | return data.id; 68 | }) 69 | ); 70 | 71 | console.log('All files uploaded, file IDs:', fileIds); 72 | 73 | // Step 2: Associate files with pipeline 74 | const pipelineUrl = `https://api.cloud.llamaindex.ai/api/v1/pipelines/${indexId}/files`; 75 | console.log('Pipeline URL:', pipelineUrl); 76 | const fileObjects = fileIds.map(id => ({ file_id: id })); 77 | console.log('Request body:', fileObjects); 78 | 79 | const pipelineResponse = await fetch(pipelineUrl, { 80 | method: 'PUT', 81 | headers: { 82 | 'Authorization': `Bearer ${apiKey}`, 83 | 'Content-Type': 'application/json', 84 | }, 85 | body: JSON.stringify(fileObjects), 86 | }); 87 | 88 | console.log(`Pipeline response status: ${pipelineResponse.status}`); 89 | if (!pipelineResponse.ok) { 90 | const errorText = await pipelineResponse.text(); 91 | console.error(`Pipeline error: ${errorText}`); 92 | throw new Error(`Failed to associate files with pipeline: ${errorText}`); 93 | } 94 | 95 | const result = await pipelineResponse.json(); 96 | console.log('Pipeline response:', result); 97 | return NextResponse.json(result); 98 | } catch (error) { 99 | console.error('Error processing upload:', error); 100 | return NextResponse.json({ 101 | error: error instanceof Error ? error.message : 'Internal server error processing upload' 102 | }, { status: 500 }); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /frontend/src/app/api/initialize/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | 3 | export async function POST(req: NextRequest) { 4 | try { 5 | // Check required headers 6 | const apiKey = req.headers.get('X-API-Key'); 7 | const organizationId = req.headers.get('X-Organization-Id'); 8 | const projectId = req.headers.get('X-Project-Id'); 9 | 10 | if (!apiKey) { 11 | return NextResponse.json({ error: 'API key is required' }, { status: 401 }); 12 | } 13 | if (!organizationId) { 14 | return NextResponse.json({ error: 'Organization ID is required' }, { status: 401 }); 15 | } 16 | if (!projectId) { 17 | return NextResponse.json({ error: 'Project ID is required' }, { status: 401 }); 18 | } 19 | 20 | const headers = { 21 | 'Authorization': `Bearer ${apiKey}`, 22 | 'Content-Type': 'application/json' 23 | }; 24 | 25 | // Call LlamaIndex API to check for existing pipeline 26 | const pipelineResponse = await fetch('https://api.cloud.llamaindex.ai/api/v1/pipelines?pipeline_name=Invoicer Index', { 27 | headers 28 | }); 29 | 30 | if (pipelineResponse.ok) { 31 | const pipelineData = await pipelineResponse.json(); 32 | if (pipelineData.length > 0) { 33 | // Return the ID of the first matching pipeline 34 | return NextResponse.json({ 35 | index_id: pipelineData[0].id 36 | }); 37 | } 38 | } 39 | 40 | // If no pipeline found, look for embedding configs 41 | const embeddingResponse = await fetch('https://api.cloud.llamaindex.ai/api/v1/embedding-model-configs', { 42 | headers 43 | }); 44 | 45 | let embeddingConfigId: string | null = null; 46 | if (embeddingResponse.ok) { 47 | const embeddingData = await embeddingResponse.json(); 48 | if (embeddingData.length > 0) { 49 | embeddingConfigId = embeddingData[0].id; 50 | } 51 | } 52 | 53 | if (!embeddingConfigId) { 54 | // Create a new embedding config 55 | const createEmbeddingResponse = await fetch('https://api.cloud.llamaindex.ai/api/v1/embedding-model-configs', { 56 | method: 'POST', 57 | headers, 58 | body: JSON.stringify({ 59 | name: "Invoicer embedding", 60 | embedding_config: { 61 | type: "OPENAI_EMBEDDING", 62 | component: { 63 | model_name: "text-embedding-3-small", 64 | embed_batch_size: 10, 65 | num_workers: 0, 66 | additional_kwargs: {}, 67 | api_key: process.env.OPENAI_API_KEY, 68 | max_retries: 10, 69 | timeout: 60, 70 | default_headers: {}, 71 | reuse_client: true, 72 | dimensions: 1536, 73 | azure_endpoint: "string", 74 | azure_deployment: "string", 75 | class_name: "OpenAIEmbedding" 76 | } 77 | } 78 | }) 79 | }); 80 | 81 | if (createEmbeddingResponse.ok) { 82 | const createdConfig = await createEmbeddingResponse.json(); 83 | embeddingConfigId = createdConfig.id; 84 | } else { 85 | console.error('Failed to create embedding config:', await createEmbeddingResponse.text()); 86 | return NextResponse.json({ 87 | error: 'Failed to create embedding config' 88 | }, { status: 500 }); 89 | } 90 | } 91 | 92 | // Create a new pipeline using the embedding config 93 | const createPipelineResponse = await fetch(`https://api.cloud.llamaindex.ai/api/v1/pipelines?project_id=${projectId}&organization_id=${organizationId}`, { 94 | method: 'POST', 95 | headers, 96 | body: JSON.stringify({ 97 | embedding_model_config_id: embeddingConfigId, 98 | data_sink_id: null, 99 | name: "Invoicer Index", 100 | transform_config: { mode: "auto" } 101 | }) 102 | }); 103 | 104 | if (createPipelineResponse.ok) { 105 | const createdPipeline = await createPipelineResponse.json(); 106 | return NextResponse.json({ 107 | index_id: createdPipeline.id 108 | }); 109 | } else { 110 | console.error('Failed to create pipeline:', await createPipelineResponse.text()); 111 | return NextResponse.json({ 112 | error: 'Failed to create pipeline' 113 | }, { status: 500 }); 114 | } 115 | } catch (error) { 116 | console.error('Error in initialize endpoint:', error); 117 | return NextResponse.json({ 118 | error: 'Internal server error during initialization' 119 | }, { status: 500 }); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /frontend/src/app/api/invoices/reconcile/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | import { OpenAI } from '@llamaindex/openai'; 3 | 4 | export async function GET(request: NextRequest) { 5 | const llm = new OpenAI({ 6 | apiKey: process.env.OPENAI_API_KEY, 7 | model: 'o3-mini', 8 | }); 9 | 10 | try { 11 | // Extract headers 12 | const apiKey = request.headers.get('X-API-Key'); 13 | const indexId = request.headers.get('X-Index-ID'); 14 | const projectId = request.headers.get('X-Project-ID'); 15 | const organizationId = request.headers.get('X-Organization-ID'); 16 | 17 | // Extract job_id from query parameters 18 | const { searchParams } = new URL(request.url); 19 | const jobId = searchParams.get('job_id'); 20 | 21 | if (!apiKey) { 22 | return NextResponse.json( 23 | { error: 'API key is required' }, 24 | { status: 401 } 25 | ); 26 | } 27 | 28 | if (!jobId) { 29 | return NextResponse.json( 30 | { error: 'job_id is required' }, 31 | { status: 400 } 32 | ); 33 | } 34 | 35 | if (!organizationId) { 36 | return NextResponse.json( 37 | { error: 'Organization ID is required' }, 38 | { status: 400 } 39 | ); 40 | } 41 | 42 | if (!indexId) { 43 | return NextResponse.json( 44 | { error: 'Index ID is required' }, 45 | { status: 400 } 46 | ); 47 | } 48 | 49 | if (!projectId) { 50 | return NextResponse.json( 51 | { error: 'Project ID is required' }, 52 | { status: 400 } 53 | ); 54 | } 55 | 56 | // Fetch the parsed document from LlamaParse API 57 | console.log(`Fetching parsed document for job: ${jobId}`); 58 | 59 | const llamaIndexApiUrl = `https://api.cloud.llamaindex.ai/api/v1/parsing/job/${jobId}/result/markdown`; 60 | const queryParams = new URLSearchParams(); 61 | queryParams.append('organization_id', organizationId); 62 | const finalUrl = `${llamaIndexApiUrl}?${queryParams}` 63 | 64 | const parsedDocResponse = await fetch(finalUrl, { 65 | method: 'GET', 66 | headers: { 67 | 'Authorization': `Bearer ${apiKey}`, 68 | 'Content-Type': 'application/json', 69 | }, 70 | }); 71 | 72 | if (!parsedDocResponse.ok) { 73 | console.error(`Error fetching parsed document: ${parsedDocResponse.status}`); 74 | const errorText = await parsedDocResponse.text(); 75 | console.error(`Error response: ${errorText}`); 76 | return NextResponse.json( 77 | { error: 'Failed to fetch parsed document' }, 78 | { status: parsedDocResponse.status } 79 | ); 80 | } 81 | 82 | const parsedData = await parsedDocResponse.json(); 83 | const markdown = parsedData.markdown; 84 | 85 | if (!markdown) { 86 | return NextResponse.json( 87 | { error: 'No markdown content found in parsed document' }, 88 | { status: 404 } 89 | ); 90 | } 91 | 92 | console.log('Successfully retrieved parsed document'); 93 | 94 | // extract just the company name from the markdown 95 | const nameResponse = await llm.complete({ 96 | prompt: `Extract the company name from the following markdown: ${markdown} 97 | Return ONLY the name of the company as a string, with no other text. 98 | Don't use markdown.` 99 | }) 100 | 101 | const companyName = nameResponse.text.trim(); 102 | console.log('Successfully extracted company name:', companyName); 103 | 104 | // Call the retriever API to get matching document 105 | console.log('Calling retriever API to find a contract that matches this company name'); 106 | 107 | const retrieverApiUrl = `https://api.cloud.llamaindex.ai/api/v1/retrievers/retrieve?project_id=${projectId}&organization_id=${organizationId}`; 108 | 109 | const retrieverPayload = { 110 | mode: "full", 111 | query: companyName, 112 | pipelines: [ 113 | { 114 | name: "Contract Matching Pipeline", 115 | description: "Find matching contract for invoice reconciliation", 116 | pipeline_id: indexId 117 | } 118 | ] 119 | }; 120 | 121 | const retrieverResponse = await fetch(retrieverApiUrl, { 122 | method: 'POST', 123 | headers: { 124 | 'Authorization': `Bearer ${apiKey}`, 125 | 'Content-Type': 'application/json', 126 | }, 127 | body: JSON.stringify(retrieverPayload) 128 | }); 129 | 130 | if (!retrieverResponse.ok) { 131 | console.error(`Error calling retriever API: ${retrieverResponse.status}`); 132 | const errorText = await retrieverResponse.text(); 133 | console.error(`Error response: ${errorText}`); 134 | return NextResponse.json( 135 | { error: 'Failed to find matching document' }, 136 | { status: retrieverResponse.status } 137 | ); 138 | } 139 | 140 | const retrieverData = await retrieverResponse.json(); 141 | 142 | if (!retrieverData.nodes || retrieverData.nodes.length === 0) { 143 | return NextResponse.json( 144 | { error: 'No matching documents found' }, 145 | { status: 404 } 146 | ); 147 | } 148 | 149 | //console.log("Retriever data:", JSON.stringify(retrieverData, null, 2)); 150 | 151 | // Extract document ID from the first node 152 | const firstNode = retrieverData.nodes[0]; 153 | if (!firstNode.node || !firstNode.node.metadata || !firstNode.node.metadata.document_id) { 154 | return NextResponse.json( 155 | { error: 'Document ID not found in response' }, 156 | { status: 404 } 157 | ); 158 | } 159 | 160 | const documentId = firstNode.node.metadata.document_id; 161 | console.log(`Found matching contract with ID: ${documentId}`); 162 | 163 | // Fetch the full document text 164 | console.log(`Fetching full contract text for document ID: ${documentId}`); 165 | 166 | const documentApiUrl = `https://api.cloud.llamaindex.ai/api/v1/pipelines/${indexId}/documents/${documentId}`; 167 | 168 | const documentResponse = await fetch(documentApiUrl, { 169 | method: 'GET', 170 | headers: { 171 | 'Authorization': `Bearer ${apiKey}`, 172 | 'Content-Type': 'application/json', 173 | }, 174 | }); 175 | 176 | if (!documentResponse.ok) { 177 | console.error(`Error fetching document text: ${documentResponse.status}`); 178 | const errorText = await documentResponse.text(); 179 | console.error(`Error response: ${errorText}`); 180 | return NextResponse.json( 181 | { error: 'Failed to fetch contract document' }, 182 | { status: documentResponse.status } 183 | ); 184 | } 185 | 186 | const documentData = await documentResponse.json(); 187 | 188 | if (!documentData.text) { 189 | return NextResponse.json( 190 | { error: 'No text content found in contract document' }, 191 | { status: 404 } 192 | ); 193 | } 194 | 195 | const contractText = documentData.text; 196 | console.log('Successfully retrieved contract text'); 197 | 198 | const llmResponse = await llm.complete({ 199 | prompt: ` 200 | Your job is to reconcile an invoice against a contract. 201 | The contract text is this: 202 | 203 | ${contractText} 204 | 205 | The invoice text is this: 206 | 207 | ${markdown} 208 | 209 | Your output should be a JSON object with the following fields: 210 | - status: "success" or "failure". To succeed, the invoice should 211 | have a set of line items that match the terms of the contract in 212 | terms of item names, price, and quantities. 213 | - If the invoice is successful, also include total_due. 214 | - No matter what, include due_date: in the format YYYY-MM-DD. 215 | 216 | If there is a problem with the invoice, you should return "failure" 217 | and an additional key: 218 | - errors: an array of strings describing problems with the invoice. 219 | Each error should be a very short description of a single problem. 220 | 221 | Return ONLY JSON, with no preamble or postamble and no other text. 222 | Do not include markdown formatting of any kind. 223 | ` 224 | }) 225 | 226 | const reconciliationResult = JSON.parse(llmResponse.text); 227 | 228 | // Add documentId to the reconciliation result 229 | reconciliationResult.contractId = documentId; 230 | 231 | // add the markdown to the reconciliation result 232 | reconciliationResult.markdown = markdown; 233 | 234 | // Come up with a friendly name for the contract 235 | const friendlyNameResponse = await llm.complete({ 236 | prompt: `Come up with a very brief name for this invoice, 237 | in the format "Invoice " 238 | where is some identifier for the invoice, and 239 | is the name of the billing company. 240 | Here's the contract: 241 | ${contractText} 242 | Return ONLY the name as a string, with no other text. Don't use Markdown. 243 | ` 244 | }) 245 | 246 | const friendlyName = friendlyNameResponse.text.trim(); 247 | reconciliationResult.friendlyName = friendlyName; 248 | 249 | console.log("Reconciliation result:", JSON.stringify(reconciliationResult, null, 2)); 250 | 251 | // Return success response with the enhanced reconciliation result 252 | return NextResponse.json(reconciliationResult); 253 | } catch (error) { 254 | console.error('Error in reconciliation process:', error); 255 | return NextResponse.json( 256 | { error: 'Failed to start reconciliation process' }, 257 | { status: 500 } 258 | ); 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /frontend/src/app/api/invoices/status/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | 3 | export async function GET(req: NextRequest) { 4 | try { 5 | // Check API key 6 | const apiKey = req.headers.get('X-API-Key'); 7 | 8 | if (!apiKey) { 9 | return NextResponse.json({ error: 'Missing API key' }, { status: 401 }); 10 | } 11 | 12 | // Get job_id from query parameters 13 | const url = new URL(req.url); 14 | const jobId = url.searchParams.get('job_id'); 15 | 16 | if (!jobId) { 17 | return NextResponse.json({ error: 'Missing job_id parameter' }, { status: 400 }); 18 | } 19 | 20 | // Call LlamaIndex API to check job status 21 | const statusUrl = `https://api.cloud.llamaindex.ai/api/v1/parsing/job/${jobId}`; 22 | 23 | const statusResponse = await fetch(statusUrl, { 24 | method: 'GET', 25 | headers: { 26 | 'Authorization': `Bearer ${apiKey}`, 27 | }, 28 | }); 29 | 30 | if (!statusResponse.ok) { 31 | const errorText = await statusResponse.text(); 32 | return NextResponse.json({ 33 | error: `Failed to check job status: ${errorText}` 34 | }, { status: statusResponse.status }); 35 | } 36 | 37 | const statusData = await statusResponse.json(); 38 | return NextResponse.json(statusData); 39 | 40 | } catch (error) { 41 | console.error('Error checking job status:', error); 42 | return NextResponse.json({ 43 | error: error instanceof Error ? error.message : 'Internal server error checking job status' 44 | }, { status: 500 }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /frontend/src/app/api/invoices/upload/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | 3 | export async function POST(req: NextRequest) { 4 | try { 5 | // Check API key 6 | const apiKey = req.headers.get('X-API-Key'); 7 | const indexId = req.headers.get('X-Index-ID'); 8 | const projectId = req.headers.get('X-Project-ID'); 9 | const organizationId = req.headers.get('X-Organization-ID'); 10 | 11 | console.log('Received headers:', { 12 | apiKey: apiKey ? 'present' : 'missing', 13 | indexId: indexId ? 'present' : 'missing', 14 | projectId: projectId ? 'present' : 'missing', 15 | organizationId: organizationId ? 'present' : 'missing' 16 | }); 17 | 18 | if (!apiKey || !indexId || !projectId || !organizationId) { 19 | return NextResponse.json({ error: 'Missing required headers' }, { status: 401 }); 20 | } 21 | 22 | // Parse form data from the request 23 | const formData = await req.formData(); 24 | 25 | // Extract files, first check for file_0, file_1, etc. 26 | let files: File[] = []; 27 | 28 | let index = 0; 29 | while (formData.has(`file_${index}`)) { 30 | const file = formData.get(`file_${index}`) as File; 31 | if (file instanceof File) { 32 | files.push(file); 33 | } 34 | index++; 35 | } 36 | 37 | console.log(`Found ${files.length} files with file_N format`); 38 | 39 | // If no files found, try generic 'file' entries 40 | if (files.length === 0) { 41 | const formFiles = formData.getAll('file'); 42 | files = formFiles.filter(f => f instanceof File) as File[]; 43 | console.log(`Found ${files.length} files with 'file' format`); 44 | } 45 | 46 | if (files.length === 0) { 47 | return NextResponse.json({ error: 'No files provided' }, { status: 400 }); 48 | } 49 | 50 | // Upload each file to LlamaIndex API and collect job IDs 51 | const uploadedFiles: Array<{fileName: string, job_id: string}> = []; 52 | 53 | for (const file of files) { 54 | console.log(`Processing file: ${file.name}`); 55 | 56 | const uploadFormData = new FormData(); 57 | uploadFormData.append('file', file); 58 | 59 | const uploadUrl = `https://api.cloud.llamaindex.ai/api/v1/parsing/upload?organization_id=${organizationId}&project_id=${projectId}`; 60 | 61 | const uploadResponse = await fetch(uploadUrl, { 62 | method: 'POST', 63 | headers: { 64 | 'Authorization': `Bearer ${apiKey}`, 65 | }, 66 | body: uploadFormData, 67 | }); 68 | 69 | if (!uploadResponse.ok) { 70 | const errorText = await uploadResponse.text(); 71 | console.error(`Failed to upload file ${file.name}: ${errorText}`); 72 | throw new Error(`Failed to upload file: ${errorText}`); 73 | } 74 | 75 | const responseData = await uploadResponse.json(); 76 | console.log('Upload response:', responseData); 77 | 78 | if (responseData.id) { 79 | uploadedFiles.push({ 80 | fileName: file.name, 81 | job_id: responseData.id 82 | }); 83 | } 84 | } 85 | 86 | console.log('Successfully uploaded files:', uploadedFiles); 87 | 88 | return NextResponse.json({ 89 | message: 'Files uploaded successfully', 90 | files: uploadedFiles 91 | }); 92 | } catch (error) { 93 | console.error('Error processing invoice upload:', error); 94 | return NextResponse.json({ 95 | error: error instanceof Error ? error.message : 'Internal server error processing invoice upload' 96 | }, { status: 500 }); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /frontend/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-llama/invoice-reconciler/67932384a48ca7e2968bedd2e9eec16eaf08a362/frontend/src/app/favicon.ico -------------------------------------------------------------------------------- /frontend/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @custom-variant dark (&:is(.dark *)); 6 | 7 | :root { 8 | --background: hsl(0, 0%, 100%); 9 | --foreground: hsl(240, 10%, 3.9%); 10 | --card: hsl(0, 0%, 100%); 11 | --card-foreground: hsl(240, 10%, 3.9%); 12 | --popover: hsl(0, 0%, 100%); 13 | --popover-foreground: hsl(240, 10%, 3.9%); 14 | --primary: hsl(240, 5.9%, 10%); 15 | --primary-foreground: hsl(0, 0%, 98%); 16 | --secondary: hsl(240, 4.8%, 95.9%); 17 | --secondary-foreground: hsl(240, 5.9%, 10%); 18 | --muted: hsl(240, 4.8%, 95.9%); 19 | --muted-foreground: hsl(240, 3.8%, 46.1%); 20 | --accent: hsl(240, 4.8%, 95.9%); 21 | --accent-foreground: hsl(240, 5.9%, 10%); 22 | --destructive: hsl(0, 84.2%, 60.2%); 23 | --destructive-foreground: hsl(0, 0%, 98%); 24 | --border: hsl(240, 5.9%, 90%); 25 | --input: hsl(240, 5.9%, 90%); 26 | --ring: hsl(240, 5.9%, 10%); 27 | --radius: 0.5rem; 28 | 29 | --sidebar-background: hsl(0, 0%, 98%); 30 | --sidebar-foreground: hsl(240, 5.3%, 26.1%); 31 | --sidebar-primary: hsl(240, 5.9%, 10%); 32 | --sidebar-primary-foreground: hsl(0, 0%, 98%); 33 | --sidebar-accent: hsl(240, 4.8%, 95.9%); 34 | --sidebar-accent-foreground: hsl(240, 5.9%, 10%); 35 | --sidebar-border: hsl(220, 13%, 91%); 36 | --sidebar-ring: hsl(217.2, 91.2%, 59.8%); 37 | 38 | --chart-1: hsl(12, 76%, 61%); 39 | --chart-2: hsl(173, 58%, 39%); 40 | --chart-3: hsl(197, 37%, 24%); 41 | --chart-4: hsl(43, 74%, 66%); 42 | --chart-5: hsl(27, 87%, 67%); 43 | } 44 | 45 | .dark { 46 | --background: hsl(240, 10%, 3.9%); 47 | --foreground: hsl(0, 0%, 98%); 48 | --card: hsl(240, 10%, 3.9%); 49 | --card-foreground: hsl(0, 0%, 98%); 50 | --popover: hsl(240, 10%, 3.9%); 51 | --popover-foreground: hsl(0, 0%, 98%); 52 | --primary: hsl(0, 0%, 98%); 53 | --primary-foreground: hsl(240, 5.9%, 10%); 54 | --secondary: hsl(240, 3.7%, 15.9%); 55 | --secondary-foreground: hsl(0, 0%, 98%); 56 | --muted: hsl(240, 3.7%, 15.9%); 57 | --muted-foreground: hsl(240, 5%, 64.9%); 58 | --accent: hsl(240, 3.7%, 15.9%); 59 | --accent-foreground: hsl(0, 0%, 98%); 60 | --destructive: hsl(0, 62.8%, 30.6%); 61 | --destructive-foreground: hsl(0, 0%, 98%); 62 | --border: hsl(240, 3.7%, 15.9%); 63 | --input: hsl(240, 3.7%, 15.9%); 64 | --ring: hsl(240, 4.9%, 83.9%); 65 | --chart-1: hsl(220, 70%, 50%); 66 | --chart-2: hsl(160, 60%, 45%); 67 | --chart-3: hsl(30, 80%, 55%); 68 | --chart-4: hsl(280, 65%, 60%); 69 | --chart-5: hsl(340, 75%, 55%); 70 | 71 | --sidebar-background: hsl(240, 5.9%, 10%); 72 | --sidebar-foreground: hsl(240, 4.8%, 95.9%); 73 | --sidebar-primary: hsl(224.3, 76.3%, 48%); 74 | --sidebar-primary-foreground: hsl(0, 0%, 100%); 75 | --sidebar-accent: hsl(240, 3.7%, 15.9%); 76 | --sidebar-accent-foreground: hsl(240, 4.8%, 95.9%); 77 | --sidebar-border: hsl(240, 3.7%, 15.9%); 78 | --sidebar-ring: hsl(217.2, 91.2%, 59.8%); 79 | } 80 | 81 | .filepond--credits { 82 | display: none; 83 | } 84 | 85 | .selectPrimitive__trigger[data-placeholder] span { 86 | color: #9297a0; 87 | /* Todo: fix color attributes, imports */ 88 | } 89 | 90 | #pdf-controls { 91 | z-index: 1000; 92 | } 93 | 94 | .background-gradient { 95 | background-color: #fff; 96 | background-image: 97 | radial-gradient(at 21% 11%, rgba(186, 186, 233, 0.53) 0, transparent 50%), 98 | radial-gradient(at 85% 0, hsla(46, 57%, 78%, 0.52) 0, transparent 50%), 99 | radial-gradient(at 91% 36%, rgba(194, 213, 255, 0.68) 0, transparent 50%), 100 | radial-gradient(at 8% 40%, rgba(251, 218, 239, 0.46) 0, transparent 50%); 101 | } 102 | 103 | .custom-markdown.prose { 104 | max-width: 100%; 105 | } 106 | 107 | /** 108 | * Sidebar 109 | */ 110 | [data-sidebar="menu"] { 111 | @apply gap-1; 112 | } 113 | 114 | [data-sidebar="content"] { 115 | @apply gap-2; 116 | } 117 | 118 | #proxy-renderer, 119 | #msdoc-renderer { 120 | height: 100%; 121 | } 122 | -------------------------------------------------------------------------------- /frontend/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ 6 | subsets: ["latin"], 7 | variable: "--font-inter", 8 | }); 9 | 10 | export const metadata: Metadata = { 11 | title: "Invoice Reconciler", 12 | description: "Another great LlamaIndex demo", 13 | }; 14 | 15 | export default function RootLayout({ 16 | children, 17 | }: Readonly<{ 18 | children: React.ReactNode; 19 | }>) { 20 | return ( 21 | 22 | 23 | {children} 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState, useEffect } from 'react'; 4 | import Image from 'next/image'; 5 | import Login from '@/components/Login'; 6 | import ContractUpload from '@/components/ContractUpload'; 7 | import ContractsList from '@/components/ContractsList'; 8 | import { Button } from '@/components/ui/button'; 9 | import { Alert, AlertDescription } from '@/components/ui/alert'; 10 | import { InvoicesProvider } from '@/context/InvoicesContext'; 11 | 12 | export default function Home() { 13 | const [isLoggedIn, setIsLoggedIn] = useState(false); 14 | const [initMessage, setInitMessage] = useState(''); 15 | 16 | const initializeIndex = async () => { 17 | try { 18 | const apiKey = localStorage.getItem('apiKey'); 19 | const organizationId = localStorage.getItem('organization_id'); 20 | const projectId = localStorage.getItem('project_id'); 21 | 22 | const response = await fetch('/api/initialize', { 23 | method: 'POST', 24 | headers: { 25 | 'Content-Type': 'application/json', 26 | 'X-API-Key': apiKey || '', 27 | 'X-Organization-Id': organizationId || '', 28 | 'X-Project-Id': projectId || '', 29 | }, 30 | }); 31 | 32 | if (response.ok) { 33 | const data = await response.json(); 34 | localStorage.setItem('index_id', data.index_id); 35 | setInitMessage('Index initialized'); 36 | setTimeout(() => setInitMessage(''), 3000); 37 | } 38 | } catch (err) { 39 | console.error('Error initializing index:', err); 40 | } 41 | }; 42 | 43 | useEffect(() => { 44 | const storedKey = localStorage.getItem('apiKey'); 45 | if (storedKey) { 46 | setIsLoggedIn(true); 47 | initializeIndex(); 48 | } 49 | }, []); 50 | 51 | const handleLogin = () => { 52 | setIsLoggedIn(true); 53 | initializeIndex(); 54 | }; 55 | 56 | const handleLogout = () => { 57 | localStorage.removeItem('apiKey'); 58 | localStorage.removeItem('project_id'); 59 | localStorage.removeItem('organization_id'); 60 | localStorage.removeItem('index_id'); 61 | setIsLoggedIn(false); 62 | }; 63 | 64 | return ( 65 | <> 66 |
67 |
68 | Invoice Reconciler Logo 74 |
75 |

Invoice Reconciler

76 |
77 | 78 | {isLoggedIn ? ( 79 | <> 80 |
81 | 88 |
89 | {initMessage && ( 90 |
91 | 92 | {initMessage} 93 | 94 |
95 | )} 96 |
97 | 98 | 99 | 100 | 101 |
102 | 103 | ) : ( 104 |
105 | 106 |
107 | )} 108 | 109 | ); 110 | } 111 | -------------------------------------------------------------------------------- /frontend/src/components/ContractUpload.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState, useCallback } from 'react'; 4 | import { Button } from "@/components/ui/button"; 5 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 6 | import { Alert, AlertDescription } from "@/components/ui/alert"; 7 | import { cn } from "@/lib/utils"; 8 | import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; 9 | import { ChevronDown, ChevronRight } from "lucide-react"; 10 | 11 | export default function ContractUpload({ className }: { className?: string }) { 12 | const [files, setFiles] = useState([]); 13 | const [uploading, setUploading] = useState(false); 14 | const [error, setError] = useState(''); 15 | const [success, setSuccess] = useState(''); 16 | const [isOpen, setIsOpen] = useState(false); 17 | 18 | const handleDrop = useCallback((e: React.DragEvent) => { 19 | e.preventDefault(); 20 | const droppedFiles = Array.from(e.dataTransfer.files); 21 | setFiles(prev => [...prev, ...droppedFiles]); 22 | }, []); 23 | 24 | const handleFileChange = (e: React.ChangeEvent) => { 25 | if (e.target.files) { 26 | const selectedFiles = Array.from(e.target.files); 27 | setFiles(prev => [...prev, ...selectedFiles]); 28 | } 29 | }; 30 | 31 | const removeFile = (index: number) => { 32 | setFiles(prev => prev.filter((_, i) => i !== index)); 33 | }; 34 | 35 | const handleSubmit = async (e: React.FormEvent) => { 36 | e.preventDefault(); 37 | if (files.length === 0) return; 38 | 39 | setUploading(true); 40 | setError(''); 41 | setSuccess(''); 42 | 43 | try { 44 | const formData = new FormData(); 45 | files.forEach((file, index) => { 46 | formData.append(`file_${index}`, file); 47 | }); 48 | const fileNames = files.map(file => file.name); 49 | formData.append('fileNames', JSON.stringify(fileNames)); 50 | 51 | const apiKey = localStorage.getItem('apiKey'); 52 | const indexId = localStorage.getItem('index_id'); 53 | const projectId = localStorage.getItem('project_id'); 54 | const organizationId = localStorage.getItem('organization_id'); 55 | 56 | const response = await fetch('/api/contracts/upload', { 57 | method: 'POST', 58 | headers: { 59 | 'X-API-Key': apiKey || '', 60 | 'X-Index-ID': indexId || '', 61 | 'X-Project-ID': projectId || '', 62 | 'X-Organization-ID': organizationId || '', 63 | }, 64 | body: formData, 65 | }); 66 | 67 | if (response.ok) { 68 | setSuccess('Files uploaded successfully'); 69 | setFiles([]); 70 | } else { 71 | const data = await response.json(); 72 | setError(data.error || 'Failed to upload files'); 73 | } 74 | } catch (err) { 75 | setError('Error uploading files'); 76 | } finally { 77 | setUploading(false); 78 | } 79 | }; 80 | 81 | return ( 82 | 83 | 84 | 85 | 86 | 90 | 91 | 92 | 93 | 94 |
95 |
e.preventDefault()} 98 | onDrop={handleDrop} 99 | onClick={() => document.getElementById('fileInput')?.click()} 100 | > 101 |
102 |

Drag and drop files here, or click to select

103 |

Upload multiple contract files

104 |
105 | 112 |
113 | 114 | {files.length > 0 && ( 115 |
116 |

Selected files:

117 |
    118 | {files.map((file, index) => ( 119 |
  • 120 | {file.name} 121 | 128 |
  • 129 | ))} 130 |
131 |
132 | )} 133 | 134 | {error && ( 135 | 136 | {error} 137 | 138 | )} 139 | 140 | {success && ( 141 | 142 | {success} 143 | 144 | )} 145 | 146 | 153 |
154 |
155 |
156 |
157 |
158 | ); 159 | } 160 | -------------------------------------------------------------------------------- /frontend/src/components/ContractsList.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState, useEffect } from 'react'; 4 | import { useInvoices } from '@/context/InvoicesContext'; 5 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 6 | import { Alert, AlertDescription } from "@/components/ui/alert"; 7 | import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; 8 | import { ChevronDown, ChevronRight } from "lucide-react"; 9 | import InvoiceUpload from './InvoiceUpload'; 10 | import ReconciledInvoicesList from './ReconciledInvoicesList'; 11 | import { Button } from "@/components/ui/button"; 12 | import ReactMarkdown from 'react-markdown'; 13 | import remarkGfm from 'remark-gfm'; 14 | 15 | interface Contract { 16 | id: string; 17 | friendlyName: string | null; 18 | fileName: string; 19 | markdown: string; 20 | } 21 | 22 | export default function ContractsList() { 23 | const [contracts, setContracts] = useState([]); 24 | const [error, setError] = useState(''); 25 | const [openContractId, setOpenContractId] = useState(null); 26 | const { reconciledInvoices } = useInvoices(); 27 | 28 | useEffect(() => { 29 | const fetchContracts = async () => { 30 | try { 31 | const apiKey = localStorage.getItem('apiKey'); 32 | const indexId = localStorage.getItem('index_id'); 33 | const projectId = localStorage.getItem('project_id'); 34 | const organizationId = localStorage.getItem('organization_id'); 35 | 36 | // Skip API call if any required ID is missing 37 | if (!apiKey || !indexId || !projectId || !organizationId) { 38 | return; 39 | } 40 | 41 | const response = await fetch('/api/contracts/list', { 42 | headers: { 43 | 'X-API-Key': apiKey || '', 44 | 'X-Index-ID': indexId || '', 45 | 'X-Project-ID': projectId || '', 46 | 'X-Organization-ID': organizationId || '', 47 | }, 48 | }); 49 | 50 | if (response.ok) { 51 | const data = await response.json(); 52 | setContracts(data); 53 | 54 | // Call setname endpoint for contracts with empty friendly names 55 | data.forEach((contract: Contract) => { 56 | if (!contract.friendlyName) { 57 | fetch(`/api/contracts/setname?contract_id=${contract.id}`, { 58 | headers: { 59 | 'X-API-Key': localStorage.getItem('apiKey') || '', 60 | 'X-Index-ID': localStorage.getItem('index_id') || '', 61 | 'X-Project-ID': localStorage.getItem('project_id') || '', 62 | 'X-Organization-ID': localStorage.getItem('organization_id') || '', 63 | }, 64 | }).catch(err => { 65 | console.error('Error setting contract name:', err); 66 | }); 67 | } 68 | }); 69 | } else { 70 | setError('Failed to fetch contracts'); 71 | } 72 | } catch (err) { 73 | setError('Error fetching contracts'); 74 | } 75 | }; 76 | 77 | // Initial fetch 78 | fetchContracts(); 79 | 80 | // Set up polling 81 | const intervalId = setInterval(fetchContracts, 5000); 82 | 83 | // Cleanup interval on unmount 84 | return () => clearInterval(intervalId); 85 | }, []); 86 | 87 | console.log("reconciledInvoices", reconciledInvoices); 88 | 89 | return ( 90 |
91 | {error && ( 92 | 93 | {error} 94 | 95 | )} 96 | 97 | {contracts.map(contract => ( 98 | 99 | 100 |
101 | {contract.friendlyName || "Unnamed contract"} 102 | {contract.fileName} 103 |
104 |
105 | 106 | setOpenContractId(isOpen ? contract.id : null)}> 107 | 108 | 112 | 113 | 114 |
115 | {contract.markdown} 116 |
117 |
118 |
119 | {reconciledInvoices[contract.id] && ( 120 |
121 |

Reconciled Invoices:

122 | invoice.invoice_data)} 124 | /> 125 |
126 | )} 127 |
128 |
129 | ))} 130 | 131 |
132 | ); 133 | } 134 | -------------------------------------------------------------------------------- /frontend/src/components/InvoiceUpload.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState, useCallback, useEffect, useRef } from 'react'; 4 | import { Button } from "@/components/ui/button"; 5 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 6 | import { Alert, AlertDescription } from "@/components/ui/alert"; 7 | import { Loader2, CheckCircle2, XCircle } from "lucide-react"; 8 | import { useInvoices } from '@/context/InvoicesContext'; 9 | 10 | interface UploadedFile { 11 | fileName: string; 12 | job_id: string; 13 | status: 'pending' | 'success' | 'error'; 14 | reconciling?: boolean; // Track reconciliation status 15 | } 16 | 17 | export default function InvoiceUpload() { 18 | const { addReconciledInvoice } = useInvoices(); 19 | const [files, setFiles] = useState([]); 20 | const [uploading, setUploading] = useState(false); 21 | const [error, setError] = useState(''); 22 | const [success, setSuccess] = useState(''); 23 | const [uploadedFiles, setUploadedFiles] = useState([]); 24 | 25 | // Ref to track files that have started reconciliation to prevent duplicates 26 | const reconciliationStartedRef = useRef>(new Set()); 27 | 28 | const handleDrop = useCallback((e: React.DragEvent) => { 29 | e.preventDefault(); 30 | const droppedFiles = Array.from(e.dataTransfer.files); 31 | setFiles(prev => [...prev, ...droppedFiles]); 32 | }, []); 33 | 34 | const handleFileChange = (e: React.ChangeEvent) => { 35 | if (e.target.files) { 36 | const selectedFiles = Array.from(e.target.files); 37 | setFiles(prev => [...prev, ...selectedFiles]); 38 | } 39 | }; 40 | 41 | const removeFile = (index: number) => { 42 | setFiles(prev => prev.filter((_, i) => i !== index)); 43 | }; 44 | 45 | // Poll job status for each pending file 46 | useEffect(() => { 47 | if (uploadedFiles.length === 0) return; 48 | 49 | const pendingFiles = uploadedFiles.filter(file => file.status === 'pending'); 50 | if (pendingFiles.length === 0) return; 51 | 52 | const apiKey = localStorage.getItem('apiKey'); 53 | if (!apiKey) return; 54 | 55 | const intervals: NodeJS.Timeout[] = []; 56 | 57 | pendingFiles.forEach(file => { 58 | const interval = setInterval(async () => { 59 | try { 60 | const response = await fetch(`/api/invoices/status?job_id=${file.job_id}`, { 61 | headers: { 62 | 'X-API-Key': apiKey 63 | } 64 | }); 65 | 66 | if (response.ok) { 67 | const data = await response.json(); 68 | 69 | if (data.status === 'SUCCESS') { 70 | // Check if reconciliation has already been started for this file 71 | if (!reconciliationStartedRef.current.has(file.job_id)) { 72 | reconciliationStartedRef.current.add(file.job_id); 73 | startReconciliation(file.job_id); 74 | } 75 | 76 | setUploadedFiles(prev => { 77 | return prev.map(f => { 78 | if (f.job_id === file.job_id) { 79 | return { ...f, status: 'success' as const }; 80 | } 81 | return f; 82 | }); 83 | }); 84 | 85 | clearInterval(interval); 86 | } else if (data.status === 'ERROR' || data.status === 'CANCELLED') { 87 | setUploadedFiles(prev => 88 | prev.map(f => 89 | f.job_id === file.job_id ? { ...f, status: 'error' } : f 90 | ) 91 | ); 92 | clearInterval(interval); 93 | } 94 | } else { 95 | // If our endpoint returns an error, mark as error 96 | setUploadedFiles(prev => 97 | prev.map(f => 98 | f.job_id === file.job_id ? { ...f, status: 'error' } : f 99 | ) 100 | ); 101 | clearInterval(interval); 102 | } 103 | } catch (err) { 104 | console.error(`Error polling status for job ${file.job_id}:`, err); 105 | } 106 | }, 3000); // Poll every 3 seconds 107 | 108 | intervals.push(interval); 109 | }); 110 | 111 | // Clean up all intervals when the component unmounts 112 | return () => { 113 | intervals.forEach(interval => clearInterval(interval)); 114 | }; 115 | }, [uploadedFiles]); 116 | 117 | // Function to start the reconciliation process 118 | const startReconciliation = async (jobId: string) => { 119 | const apiKey = localStorage.getItem('apiKey'); 120 | const indexId = localStorage.getItem('index_id'); 121 | const projectId = localStorage.getItem('project_id'); 122 | const organizationId = localStorage.getItem('organization_id'); 123 | 124 | if (!apiKey) return; 125 | 126 | // Update the file status to show reconciling 127 | setUploadedFiles(prev => 128 | prev.map(f => 129 | f.job_id === jobId ? { ...f, reconciling: true } : f 130 | ) 131 | ); 132 | 133 | try { 134 | const response = await fetch(`/api/invoices/reconcile?job_id=${jobId}`, { 135 | method: 'GET', 136 | headers: { 137 | 'X-API-Key': apiKey, 138 | 'X-Index-ID': indexId || '', 139 | 'X-Project-ID': projectId || '', 140 | 'X-Organization-ID': organizationId || '', 141 | } 142 | }); 143 | 144 | if (response.ok) { 145 | const data = await response.json(); 146 | // Add the reconciled invoice data to the context 147 | addReconciledInvoice(data.contractId, data); 148 | console.log(`Reconciled invoice added for contract ${data.contractId}`); 149 | 150 | // Update the file status to show reconciliation is complete 151 | setUploadedFiles(prev => 152 | prev.map(f => 153 | f.job_id === jobId ? { ...f, reconciling: false } : f 154 | ) 155 | ); 156 | } 157 | } catch (err) { 158 | console.error(`Error starting reconciliation for job ${jobId}:`, err); 159 | // Optionally mark reconciliation as failed 160 | setUploadedFiles(prev => 161 | prev.map(f => 162 | f.job_id === jobId ? { ...f, reconciling: false } : f 163 | ) 164 | ); 165 | // Remove from the tracking set on error 166 | reconciliationStartedRef.current.delete(jobId); 167 | } 168 | }; 169 | 170 | // Reset reconciliation tracking when unmounting 171 | useEffect(() => { 172 | return () => { 173 | reconciliationStartedRef.current.clear(); 174 | }; 175 | }, []); 176 | 177 | const handleSubmit = async (e: React.FormEvent) => { 178 | e.preventDefault(); 179 | if (files.length === 0) return; 180 | 181 | setUploading(true); 182 | setError(''); 183 | setSuccess(''); 184 | 185 | try { 186 | const formData = new FormData(); 187 | files.forEach((file, index) => { 188 | formData.append(`file_${index}`, file); 189 | }); 190 | const fileNames = files.map(file => file.name); 191 | formData.append('fileNames', JSON.stringify(fileNames)); 192 | 193 | const apiKey = localStorage.getItem('apiKey'); 194 | const indexId = localStorage.getItem('index_id'); 195 | const projectId = localStorage.getItem('project_id'); 196 | const organizationId = localStorage.getItem('organization_id'); 197 | 198 | const response = await fetch('/api/invoices/upload', { 199 | method: 'POST', 200 | headers: { 201 | 'X-API-Key': apiKey || '', 202 | 'X-Index-ID': indexId || '', 203 | 'X-Project-ID': projectId || '', 204 | 'X-Organization-ID': organizationId || '', 205 | }, 206 | body: formData, 207 | }); 208 | 209 | if (response.ok) { 210 | const data = await response.json(); 211 | if (data.files && Array.isArray(data.files)) { 212 | // Add new files to the tracking list with pending status 213 | const newFiles = data.files.map((file: {fileName: string, job_id: string}) => ({ 214 | ...file, 215 | status: 'pending' as const 216 | })); 217 | 218 | setUploadedFiles(prev => [...prev, ...newFiles]); 219 | } 220 | 221 | setSuccess('Invoices uploaded successfully'); 222 | setFiles([]); 223 | } else { 224 | const data = await response.json(); 225 | setError(data.error || 'Failed to upload invoices'); 226 | } 227 | } catch (err) { 228 | setError('Error uploading invoices'); 229 | } finally { 230 | setUploading(false); 231 | } 232 | }; 233 | 234 | // Get status icon component 235 | const StatusIcon = ({ status }: { status: string }) => { 236 | switch (status) { 237 | case 'pending': 238 | return ( 239 |
240 | 🟡 241 | 242 |
243 | ); 244 | case 'success': 245 | return ( 246 |
247 | 🟢 248 | 249 |
250 | ); 251 | case 'error': 252 | return ( 253 |
254 | 🔴 255 | 256 |
257 | ); 258 | default: 259 | return ( 260 |
261 | 🟡 262 | 263 |
264 | ); 265 | } 266 | }; 267 | 268 | return ( 269 | 270 | 271 | Upload Invoices 272 | 273 | Drag and drop or select files to upload 274 | 275 | 276 | 277 |
278 |
e.preventDefault()} 281 | onDrop={handleDrop} 282 | onClick={() => document.getElementById('invoiceFileInput')?.click()} 283 | > 284 |
285 |

Drag and drop files here, or click to select

286 |

Upload multiple invoice files

287 |
288 | 295 |
296 | 297 | {files.length > 0 && ( 298 |
299 |

Selected files:

300 |
    301 | {files.map((file, index) => ( 302 |
  • 303 | {file.name} 304 | 311 |
  • 312 | ))} 313 |
314 |
315 | )} 316 | 317 | {uploadedFiles.length > 0 && ( 318 |
319 |

Processing status:

320 |
321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | {uploadedFiles.map((file, index) => ( 331 | 332 | 335 | 338 | 352 | 353 | ))} 354 | 355 |
Invoice NameParsingReconciling
333 | {file.fileName} 334 | 336 | 337 | 339 | {file.reconciling ? ( 340 |
341 | 🔄 342 | reconciling... 343 | 344 |
345 | ) : file.status === 'success' ? ( 346 |
347 | 348 | Reconciled 349 |
350 | ) : null} 351 |
356 |
357 |
358 | )} 359 | 360 | {error && ( 361 | 362 | {error} 363 | 364 | )} 365 | 366 | {success && ( 367 | 368 | {success} 369 | 370 | )} 371 | 372 | 379 |
380 |
381 |
382 | ); 383 | } 384 | -------------------------------------------------------------------------------- /frontend/src/components/Login.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | import { Button } from "@/components/ui/button"; 5 | import { Input } from "@/components/ui/input"; 6 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 7 | import { Alert, AlertDescription } from "@/components/ui/alert"; 8 | 9 | interface LoginProps { 10 | onLoginSuccess: () => void; 11 | } 12 | 13 | export default function Login({ onLoginSuccess }: LoginProps) { 14 | const [apiKey, setApiKey] = useState(''); 15 | const [error, setError] = useState(''); 16 | 17 | const handleSubmit = async (e: React.FormEvent) => { 18 | e.preventDefault(); 19 | setError(''); 20 | 21 | try { 22 | const response = await fetch('/api/auth/validate', { 23 | method: 'POST', 24 | headers: { 25 | 'Content-Type': 'application/json', 26 | }, 27 | body: JSON.stringify({ apiKey }), 28 | }); 29 | 30 | const data = await response.json(); 31 | 32 | if (data.keyStatus === 'valid') { 33 | localStorage.setItem('apiKey', apiKey); 34 | if (data.project_id) { 35 | localStorage.setItem('project_id', data.project_id); 36 | } 37 | if (data.organization_id) { 38 | localStorage.setItem('organization_id', data.organization_id); 39 | } 40 | onLoginSuccess(); 41 | } else { 42 | setError('Invalid API key'); 43 | } 44 | } catch (err) { 45 | setError('Error validating API key'); 46 | } 47 | }; 48 | 49 | return ( 50 |
51 | 52 | 53 | Enter your API key 54 | 55 | This key will be stored in localStorage for demo purposes 56 | 57 | 58 | 59 |
60 |
61 | setApiKey(e.target.value)} 66 | required 67 | /> 68 |
69 | 70 | {error && ( 71 | 72 | {error} 73 | 74 | )} 75 | 76 | 79 |
80 |
81 |
82 |
83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /frontend/src/components/ReconciledInvoicesList.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | import { ChevronDown, ChevronUp, ChevronRight } from 'lucide-react'; 5 | import { Card, CardContent } from "@/components/ui/card"; 6 | import ReactMarkdown from 'react-markdown'; 7 | import remarkGfm from 'remark-gfm'; 8 | 9 | interface ReconciledInvoice { 10 | status: 'success' | 'failure'; 11 | friendlyName: string; 12 | due_date: string; 13 | total_due?: number; 14 | errors?: string[]; 15 | markdown?: string; 16 | } 17 | 18 | interface ReconciledInvoicesListProps { 19 | invoices: ReconciledInvoice[]; 20 | } 21 | 22 | export default function ReconciledInvoicesList({ invoices }: ReconciledInvoicesListProps) { 23 | // Sort invoices by due_date, oldest first 24 | const sortedInvoices = [...invoices].sort((a, b) => 25 | new Date(a.due_date).getTime() - new Date(b.due_date).getTime() 26 | ); 27 | 28 | return ( 29 |
30 | {sortedInvoices.map((invoice, index) => ( 31 | 32 | ))} 33 |
34 | ); 35 | } 36 | 37 | function InvoiceItem({ invoice }: { invoice: ReconciledInvoice }) { 38 | const [isExpanded, setIsExpanded] = useState(false); 39 | const [showMarkdown, setShowMarkdown] = useState(false); 40 | 41 | return ( 42 | 43 | 44 |
45 |
46 |
{invoice.friendlyName}
47 |
48 | Due: {new Date(invoice.due_date).toLocaleDateString()} 49 |
50 | {invoice.markdown && ( 51 | 58 | )} 59 |
60 | 61 |
62 | {invoice.status === 'success' && invoice.total_due && ( 63 |
64 | ${invoice.total_due.toFixed(2)} 65 |
66 | )} 67 |
68 | {invoice.status === 'success' ? '🟢' : '🔴'} 69 |
70 | {invoice.status === 'failure' && ( 71 | 77 | )} 78 |
79 |
80 | 81 | {showMarkdown && invoice.markdown && ( 82 |
83 | {invoice.markdown} 84 |
85 | )} 86 | 87 | {isExpanded && invoice.errors && invoice.errors.length > 0 && ( 88 |
89 |
    90 | {invoice.errors.map((error, index) => ( 91 |
  • 92 | {error} 93 |
  • 94 | ))} 95 |
96 |
97 | )} 98 |
99 |
100 | ); 101 | } 102 | -------------------------------------------------------------------------------- /frontend/src/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const alertVariants = cva( 7 | "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-card text-card-foreground", 12 | destructive: 13 | "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", 14 | }, 15 | }, 16 | defaultVariants: { 17 | variant: "default", 18 | }, 19 | } 20 | ) 21 | 22 | function Alert({ 23 | className, 24 | variant, 25 | ...props 26 | }: React.ComponentProps<"div"> & VariantProps) { 27 | return ( 28 |
34 | ) 35 | } 36 | 37 | function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { 38 | return ( 39 |
47 | ) 48 | } 49 | 50 | function AlertDescription({ 51 | className, 52 | ...props 53 | }: React.ComponentProps<"div">) { 54 | return ( 55 |
63 | ) 64 | } 65 | 66 | export { Alert, AlertTitle, AlertDescription } 67 | -------------------------------------------------------------------------------- /frontend/src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", 16 | outline: 17 | "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", 20 | ghost: 21 | "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", 22 | link: "text-primary underline-offset-4 hover:underline", 23 | }, 24 | size: { 25 | default: "h-9 px-4 py-2 has-[>svg]:px-3", 26 | sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", 27 | lg: "h-10 rounded-md px-6 has-[>svg]:px-4", 28 | icon: "size-9", 29 | }, 30 | }, 31 | defaultVariants: { 32 | variant: "default", 33 | size: "default", 34 | }, 35 | } 36 | ) 37 | 38 | function Button({ 39 | className, 40 | variant, 41 | size, 42 | asChild = false, 43 | ...props 44 | }: React.ComponentProps<"button"> & 45 | VariantProps & { 46 | asChild?: boolean 47 | }) { 48 | const Comp = asChild ? Slot : "button" 49 | 50 | return ( 51 | 56 | ) 57 | } 58 | 59 | export { Button, buttonVariants } 60 | -------------------------------------------------------------------------------- /frontend/src/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Card({ className, ...props }: React.ComponentProps<"div">) { 6 | return ( 7 |
15 | ) 16 | } 17 | 18 | function CardHeader({ className, ...props }: React.ComponentProps<"div">) { 19 | return ( 20 |
28 | ) 29 | } 30 | 31 | function CardTitle({ className, ...props }: React.ComponentProps<"div">) { 32 | return ( 33 |
38 | ) 39 | } 40 | 41 | function CardDescription({ className, ...props }: React.ComponentProps<"div">) { 42 | return ( 43 |
48 | ) 49 | } 50 | 51 | function CardAction({ className, ...props }: React.ComponentProps<"div">) { 52 | return ( 53 |
61 | ) 62 | } 63 | 64 | function CardContent({ className, ...props }: React.ComponentProps<"div">) { 65 | return ( 66 |
71 | ) 72 | } 73 | 74 | function CardFooter({ className, ...props }: React.ComponentProps<"div">) { 75 | return ( 76 |
81 | ) 82 | } 83 | 84 | export { 85 | Card, 86 | CardHeader, 87 | CardFooter, 88 | CardTitle, 89 | CardAction, 90 | CardDescription, 91 | CardContent, 92 | } 93 | -------------------------------------------------------------------------------- /frontend/src/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 5 | 6 | const Collapsible = CollapsiblePrimitive.Root 7 | 8 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 9 | 10 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 11 | 12 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 13 | -------------------------------------------------------------------------------- /frontend/src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Input({ className, type, ...props }: React.ComponentProps<"input">) { 6 | return ( 7 | 18 | ) 19 | } 20 | 21 | export { Input } 22 | -------------------------------------------------------------------------------- /frontend/src/context/InvoicesContext.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { createContext, useContext, useState, ReactNode } from 'react'; 4 | 5 | interface ReconciledInvoice { 6 | contract_id: string; 7 | invoice_data: any; // Replace with actual invoice data type 8 | } 9 | 10 | interface InvoicesContextType { 11 | reconciledInvoices: Record; 12 | addReconciledInvoice: (contractId: string, invoiceData: any) => void; 13 | } 14 | 15 | const InvoicesContext = createContext(undefined); 16 | 17 | export function InvoicesProvider({ children }: { children: ReactNode }) { 18 | const [reconciledInvoices, setReconciledInvoices] = useState>({}); 19 | 20 | const addReconciledInvoice = (contractId: string, invoiceData: any) => { 21 | setReconciledInvoices(prev => ({ 22 | ...prev, 23 | [contractId]: [...(prev[contractId] || []), { contract_id: contractId, invoice_data: invoiceData }] 24 | })); 25 | }; 26 | 27 | return ( 28 | 29 | {children} 30 | 31 | ); 32 | } 33 | 34 | export function useInvoices() { 35 | const context = useContext(InvoicesContext); 36 | if (context === undefined) { 37 | throw new Error('useInvoices must be used within an InvoicesProvider'); 38 | } 39 | return context; 40 | } 41 | -------------------------------------------------------------------------------- /frontend/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /frontend/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | darkMode: ["class", "media"], 5 | content: [ 6 | "./src/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./packages/**/src/**/*.{js,ts,jsx,tsx,mdx}", 8 | "./packages/docs/content/**/*.{js,ts,jsx,tsx,mdx}", 9 | ], 10 | theme: { 11 | extend: { 12 | backgroundImage: { 13 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 14 | "gradient-conic": 15 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 16 | "gradient-linear": 17 | "linear-gradient(176.57deg, #F4E0D9 15.72%, #F98BE7 39.65%, #64DFFC 62.5%, #8399F3 84.28%)", 18 | }, 19 | colors: { 20 | "dark-code-bg": "#0b0e14", 21 | border: "var(--border)", 22 | input: "var(--input)", 23 | ring: "var(--ring)", 24 | background: "var(--background)", 25 | foreground: "var(--foreground)", 26 | primary: { 27 | DEFAULT: "var(--primary)", 28 | foreground: "var(--primary-foreground)", 29 | }, 30 | secondary: { 31 | DEFAULT: "var(--secondary)", 32 | foreground: "var(--secondary-foreground)", 33 | }, 34 | destructive: { 35 | DEFAULT: "var(--destructive)", 36 | foreground: "var(--destructive-foreground)", 37 | }, 38 | muted: { 39 | DEFAULT: "var(--muted)", 40 | foreground: "var(--muted-foreground)", 41 | }, 42 | accent: { 43 | DEFAULT: "var(--accent)", 44 | foreground: "var(--accent-foreground)", 45 | }, 46 | popover: { 47 | DEFAULT: "var(--popover)", 48 | foreground: "var(--popover-foreground)", 49 | }, 50 | card: { 51 | DEFAULT: "var(--card)", 52 | foreground: "var(--card-foreground)", 53 | }, 54 | chart: { 55 | "1": "var(--chart-1)", 56 | "2": "var(--chart-2)", 57 | "3": "var(--chart-3)", 58 | "4": "var(--chart-4)", 59 | "5": "var(--chart-5)", 60 | }, 61 | sidebar: { 62 | DEFAULT: "var(--sidebar-background)", 63 | foreground: "var(--sidebar-foreground)", 64 | primary: "var(--sidebar-primary)", 65 | accent: "var(--sidebar-accent)", 66 | border: "var(--sidebar-border)", 67 | ring: "var(--sidebar-ring)", 68 | "primary-foreground": "var(--sidebar-primary-foreground)", 69 | "accent-foreground": "var(--sidebar-accent-foreground)", 70 | }, 71 | }, 72 | borderRadius: { 73 | lg: "var(--radius)", 74 | md: "calc(var(--radius) - 2px)", 75 | sm: "calc(var(--radius) - 4px)", 76 | }, 77 | keyframes: { 78 | "accordion-down": { 79 | from: { 80 | height: "0", 81 | }, 82 | to: { 83 | height: "var(--radix-accordion-content-height)", 84 | }, 85 | }, 86 | "accordion-up": { 87 | from: { 88 | height: "var(--radix-accordion-content-height)", 89 | }, 90 | to: { 91 | height: "0", 92 | }, 93 | }, 94 | }, 95 | fontFamily: { 96 | sans: ["var(--font-inter)"], 97 | }, 98 | fontSize: { 99 | "2xs": "0.625rem", 100 | "2-5xs": "0.6875rem", 101 | "2-75xs": "0.7rem", 102 | "3xs": "0.5rem", 103 | xxl: "12rem", 104 | }, 105 | animation: { 106 | "accordion-down": "accordion-down 0.2s ease-out", 107 | "accordion-up": "accordion-up 0.2s ease-out", 108 | }, 109 | }, 110 | }, 111 | }; 112 | 113 | export default config; 114 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | --------------------------------------------------------------------------------