-
Notifications
You must be signed in to change notification settings - Fork 8
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
base: main
Are you sure you want to change the base?
feat/PDFGenPdfLib #660
Changes from all commits
5b2b1b3
99821bc
165a0b9
4c1e3f1
7622373
91096b2
a3d324c
e1c156a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. |
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" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { buildConfig } from '@flatfile/rollup-config' | ||
|
||
export default buildConfig({}) |
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[], | ||
ANTHROPIC_API_KEY: string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid passing the API key directly as a function parameter. Passing sensitive data like |
||
): 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Limit data sent to the AI API to prevent performance issues and protect sensitive information. Converting the entire
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
|
||
} |
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() | ||
} |
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
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './export.pdf.plugin' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a specific type instead of
any[]
forsheetData
.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 ofsheetData
to improve code reliability and readability.