Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/PDFGenPdfLib #660

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions export/pdf/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<!-- START_INFOCARD -->

# AI-Powered PDF Report Generator

**Automatically generate and upload AI-analyzed PDF reports from Flatfile data**

The `AI-Powered PDF Report Generator` is a Flatfile Listener plugin that creates comprehensive PDF reports with AI-driven analysis when a job is ready. It fetches sheet data, utilizes Anthropic's AI for in-depth analysis, generates a visually appealing PDF report, and seamlessly uploads it back to Flatfile.

**Event Type:**
`listener.on('job:ready')`

**Supported field types:**
All field types are supported as the plugin processes entire sheets.

<!-- END_INFOCARD -->

## Features

- Automatic PDF report generation when a job is ready
- AI-powered data analysis using Anthropic's Claude model
- Dynamic PDF creation with data summaries and placeholders for charts
- Seamless upload of generated reports back to Flatfile
- Error handling and logging for robust operation

## Installation

1. Install the required dependencies:

```bash
npm install @flatfile/listener @flatfile/api pdf-lib @anthropic-ai/sdk
```

2. Set up the necessary environment variables:

```
FLATFILE_API_KEY=your_flatfile_api_key
ANTHROPIC_API_KEY=your_anthropic_api_key
FLATFILE_SPACE_ID=your_flatfile_space_id
FLATFILE_ENVIRONMENT_ID=your_flatfile_environment_id
```

3. Import and use the plugin in your Flatfile configuration.

## Example Usage

```javascript
import listener from './path-to-plugin';

// Use the listener in your Flatfile configuration
export default {
name: "My Flatfile Project",
plugins: [listener],
// ... other configuration options
};
```

## Configuration

The plugin requires the following environment variables to be set:

- `FLATFILE_API_KEY`: Your Flatfile API key
- `ANTHROPIC_API_KEY`: Your Anthropic API key
- `FLATFILE_SPACE_ID`: The ID of your Flatfile space
- `FLATFILE_ENVIRONMENT_ID`: The ID of your Flatfile environment

Ensure these are properly set in your environment before running the plugin.

## Behavior

1. When a `job:ready` event is triggered, the plugin fetches the sheet data from Flatfile.
2. It then sends the first 10 rows of data to Anthropic's AI for analysis.
3. A PDF report is generated, including:
- A title
- A summary of the total records
- A placeholder for charts or graphs
- The AI-generated analysis
4. The PDF is temporarily saved and then uploaded back to Flatfile.
5. The temporary PDF file is deleted after successful upload.

In case of any errors during this process, they are logged, and the job is marked as failed with an error message.

This plugin provides a powerful way to automatically generate insightful reports from your Flatfile data, enhancing data analysis and visualization capabilities.
71 changes: 71 additions & 0 deletions export/pdf/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"name": "@flatfile/plugin-export-pdf",
"version": "0.0.0",
"url": "https://github.com/FlatFilers/flatfile-plugins/tree/main/export/pdf",
"description": "A Flatfile plugin for generating PDF reports from a Flatfile Workbook",
"registryMetadata": {
"category": "export"
},
"engines": {
"node": ">= 16"
},
"browser": {
"./dist/index.cjs": "./dist/index.browser.cjs",
"./dist/index.mjs": "./dist/index.browser.mjs"
},
"exports": {
"types": "./dist/index.d.ts",
"node": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"browser": {
"require": "./dist/index.browser.cjs",
"import": "./dist/index.browser.mjs"
},
"default": "./dist/index.mjs"
},
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"source": "./src/index.ts",
"types": "./dist/index.d.ts",
"files": [
"dist/**"
],
"scripts": {
"build": "rollup -c",
"build:watch": "rollup -c --watch",
"build:prod": "NODE_ENV=production rollup -c",
"check": "tsc ./**/*.ts --noEmit --esModuleInterop",
"test": "jest src/*.spec.ts --detectOpenHandles",
"test:unit": "jest src/*.spec.ts --testPathIgnorePatterns=.*\\.e2e\\.spec\\.ts$ --detectOpenHandles",
"test:e2e": "jest src/*.e2e.spec.ts --detectOpenHandles"
},
"keywords": [
"flatfile-plugins",
"category-export"
],
"author": "Flatfile, Inc.",
"repository": {
"type": "git",
"url": "https://github.com/FlatFilers/flatfile-plugins.git",
"directory": "export/pdf"
},
"license": "ISC",
"dependencies": {
"@anthropic-ai/sdk": "^0.29.0",
"@flatfile/api": "^1.9.19",
"@flatfile/plugin-space-configure": "0.6.1",
"@flatfile/util-common": "^1.4.1",
"pdf-lib": "^1.17.1"
},
"peerDependencies": {
"@flatfile/listener": "^1.0.5"
},
"devDependencies": {
"@flatfile/hooks": "^1.5.0",
"@flatfile/rollup-config": "^0.1.1",
"@types/node": "^22.7.4",
"typescript": "^5.6.2"
}
}
3 changes: 3 additions & 0 deletions export/pdf/rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { buildConfig } from '@flatfile/rollup-config'

export default buildConfig({})
30 changes: 30 additions & 0 deletions export/pdf/src/export.pdf.analyze.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Anthropic from '@anthropic-ai/sdk'
import { TextBlock } from '@anthropic-ai/sdk/resources'

export async function analyzeDataWithAI(
sheetData: any[],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use a specific type instead of any[] for sheetData.

Using any[] defeats TypeScript's type safety, which can lead to potential runtime errors. Consider defining an interface or type that accurately represents the structure of sheetData to improve code reliability and readability.

ANTHROPIC_API_KEY: string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid passing the API key directly as a function parameter.

Passing sensitive data like ANTHROPIC_API_KEY as a parameter may expose it unintentionally and poses security risks. It's a best practice to access API keys from secure sources such as environment variables within the function.

): Promise<TextBlock> {
const dataDescription = JSON.stringify(sheetData)

const anthropic = new Anthropic({
apiKey: ANTHROPIC_API_KEY,
})

const prompt = `Given the following dataset: ${dataDescription}
Comment on lines +8 to +14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Limit data sent to the AI API to prevent performance issues and protect sensitive information.

Converting the entire sheetData to a JSON string and including it in the prompt can:

  • Exceed API token limits, leading to errors or truncated responses.
  • Reduce performance due to large payloads.
  • Potentially expose sensitive or personal data contained within sheetData.

Consider summarizing the data, extracting key features, or sending a representative sample instead of the entire dataset.


Please provide a concise analysis of this data, including:
1. A summary of the main features or columns in the dataset.
2. Any notable patterns or trends you can identify.
3. Potential insights or recommendations based on this data.

Limit your response to 3-4 paragraphs.`

const response = await anthropic.messages.create({
max_tokens: 1024,
messages: [{ role: 'user', content: prompt }],
model: 'claude-3-opus-20240229',
})

return response.content[0] as TextBlock
bangarang marked this conversation as resolved.
Show resolved Hide resolved
}
96 changes: 96 additions & 0 deletions export/pdf/src/export.pdf.generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { FlatfileEvent } from '@flatfile/listener'
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib'
import { analyzeDataWithAI } from './export.pdf.analyze'

export async function generatePDFReport(
sheetData: any[],
event: FlatfileEvent
bangarang marked this conversation as resolved.
Show resolved Hide resolved
) {
const pdfDoc = await PDFDocument.create()
const page = pdfDoc.addPage()
const { height } = page.getSize()
const font = await pdfDoc.embedFont(StandardFonts.Helvetica)
const anthropicApiKey =
process.env.ANTHROPIC_API_KEY || (await event.secrets('ANTHROPIC_API_KEY'))
bangarang marked this conversation as resolved.
Show resolved Hide resolved

if (!anthropicApiKey) {
throw new Error('Anthropic API key is not set')
}
// Add title
page.drawText('Data Analysis Report', {
x: 50,
y: height - 50,
size: 24,
font,
color: rgb(0, 0, 0),
})

// Add data summary
let yOffset = height - 100
page.drawText(`Total Records: ${sheetData.length}`, {
x: 50,
y: yOffset,
size: 12,
font,
color: rgb(0, 0, 0),
})

// Placeholder for chart/graph
yOffset -= 30
page.drawRectangle({
x: 50,
y: yOffset - 200,
width: 300,
height: 200,
borderColor: rgb(0, 0, 0),
borderWidth: 1,
})
page.drawText('Chart Placeholder', {
x: 175,
y: yOffset - 100,
size: 12,
font,
color: rgb(0.5, 0.5, 0.5),
})

// AI analysis
yOffset -= 250
page.drawText('AI Analysis:', {
x: 50,
y: yOffset,
size: 14,
font,
color: rgb(0, 0, 0),
})
yOffset -= 20

const aiAnalysis = await analyzeDataWithAI(sheetData, anthropicApiKey)
console.log('aiAnalysis', aiAnalysis)
const words = aiAnalysis.text.split(' ')
bangarang marked this conversation as resolved.
Show resolved Hide resolved
let line = ''
for (const word of words) {
if ((line + word).length > 70) {
page.drawText(line, {
x: 50,
y: yOffset,
size: 10,
font,
color: rgb(0, 0, 0),
})
yOffset -= 15
line = ''
}
line += (line ? ' ' : '') + word
}
bangarang marked this conversation as resolved.
Show resolved Hide resolved
if (line) {
page.drawText(line, {
x: 50,
y: yOffset,
size: 10,
font,
color: rgb(0, 0, 0),
})
}

return pdfDoc.save()
}
47 changes: 47 additions & 0 deletions export/pdf/src/export.pdf.plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import api from '@flatfile/api'
import type { FlatfileEvent, FlatfileListener } from '@flatfile/listener'
import { jobHandler } from '@flatfile/plugin-job-handler'
import * as fs from 'fs'
import { generatePDFReport } from './export.pdf.generate'

export function exportPDF() {
return function (listener: FlatfileListener) {
listener.use(
jobHandler('sheet:generatePDFReport', async (event: FlatfileEvent) => {
const { sheetId, spaceId, environmentId } = event.context

if (!sheetId) {
throw new Error('Sheet ID is missing from the event context')
}

const { data: records } = await api.records.get(sheetId, {
pageSize: 50,
})
bangarang marked this conversation as resolved.
Show resolved Hide resolved

const pdfBytes = await generatePDFReport(records.records, event)
bangarang marked this conversation as resolved.
Show resolved Hide resolved

const fileName = `report_${sheetId}_${Date.now()}.pdf`

const tempFilePath = `/tmp/${fileName}`
fs.writeFileSync(tempFilePath, Buffer.from(pdfBytes))
const fileStream = fs.createReadStream(tempFilePath)

bangarang marked this conversation as resolved.
Show resolved Hide resolved
try {
await api.files.upload(fileStream, {
spaceId,
environmentId,
})
fs.unlinkSync(tempFilePath)

return {
info: 'PDF report generated',
}
} catch (error) {
fs.unlinkSync(tempFilePath)
console.error('Failed to upload PDF report', error)
throw new Error('Failed to upload PDF report')
}
bangarang marked this conversation as resolved.
Show resolved Hide resolved
})
)
}
}
bangarang marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions export/pdf/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './export.pdf.plugin'
Loading
Loading