├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── README.md ├── eslint.config.mjs ├── nuxt.config.ts ├── package.json ├── pages └── index.vue ├── pnpm-lock.yaml ├── public ├── favicon.ico ├── image.svg ├── pdf-summarizer.png └── robots.txt ├── server ├── api │ └── index.ts ├── tsconfig.json └── utils │ └── pdf.ts ├── tsconfig.json └── wrangler.toml /.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .data 4 | .nuxt 5 | .nitro 6 | .cache 7 | dist 8 | 9 | # Node dependencies 10 | node_modules 11 | 12 | # Logs 13 | logs 14 | *.log 15 | 16 | # Misc 17 | .DS_Store 18 | .fleet 19 | .idea 20 | 21 | # Local env files 22 | .env 23 | .env.* 24 | !.env.example 25 | sample.pdf -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": "explicit" 5 | }, 6 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PDF Summarizer 2 | 3 | PDF Summarizer allows users to upload PDF files and generate summaries using AI models. 4 | 5 | You can deploy it with zero configuration on your Cloudflare account using NuxtHub: 6 | 7 | [![Deploy to NuxtHub](https://hub.nuxt.com/button.svg)](https://hub.nuxt.com/new?repo=awecode/pdf-summarizer) 8 | 9 | ## Demo 10 | 11 | [https://pdf-summarizer-ckg.pages.dev/](https://pdf-summarizer-ckg.pages.dev/) 12 | 13 | ## Features 14 | 15 | - Upload PDF files 16 | - Generate summaries using AI models 17 | - Configurable AI model selection 18 | 19 | 20 | ## Setup 21 | 22 | ``` 23 | git clone git@github.com:awecode/pdf-summarizer.git 24 | cd pdf-summarizer 25 | pnpm install 26 | pnpm dev 27 | ``` 28 | 29 | ## Configuration 30 | 31 | Create a `.env` file in the root directory with the following configuration: 32 | 33 | | Variable | Description | Default Value | 34 | |----------|-------------|---------------| 35 | | NUXT_PUBLIC_CF_WORKER_AI_MODEL | AI model to be used for summarization | '@cf/meta/llama-3.1-70b-instruct' | 36 | | NUXT_PUBLIC_MAX_CONTEXT_LENGTH | Maximum context length for the AI model | 112000 | 37 | | NUXT_SYSTEM_PROMPT | System prompt for the AI model | 'You are a helpful assistant that summarizes a text. Respond with the summary in around 100 words only and nothing else.' | 38 | | NUXT_CF_ACCOUNT_ID | Cloudflare account ID | '' | 39 | | NUXT_CF_API_TOKEN | Cloudflare API token | '' | 40 | 41 | 42 | Notes: 43 | - NUXT_CF_ACCOUNT_ID and NUXT_CF_API_TOKEN are required only when AI binding is not available. When deploying through NuxtHub or in a Cloudflare environment, you can utilize bindings instead of these tokens. Bindings are automatically set up when deploying via NuxtHub. 44 | - The summary may be empty if the uploaded file exceeds the model's input length. If the summary is empty, reduce the input context length from settings and try again. 45 | 46 | ## Technology Credits 47 | 48 | - [Nuxt 3](https://nuxt.com/) 49 | - [Vue.js](https://vuejs.org/) 50 | - [Cloudflare Workers AI](https://developers.cloudflare.com/workers-ai/) 51 | - [Unpdf](https://github.com/unjs/unpdf) 52 | - [PicoCSS](https://picocss.com/) 53 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import withNuxt from './.nuxt/eslint.config.mjs' 3 | 4 | export default withNuxt( 5 | // Your custom configs here 6 | ) 7 | -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | compatibilityDate: '2024-04-03', 3 | devtools: { enabled: true }, 4 | modules: ['@nuxt/eslint', 'nitro-cloudflare-dev', '@nuxthub/core'], 5 | hub: { 6 | ai: true 7 | }, 8 | eslint: { 9 | config: { 10 | stylistic: true, 11 | }, 12 | }, 13 | runtimeConfig: { 14 | cfAccountId: '', 15 | cfApiToken: '', 16 | public: { 17 | cfWorkerAiModel: '@cf/meta/llama-3.1-70b-instruct', 18 | maxContextLength: 112000, 19 | }, 20 | systemPrompt: 21 | 'You are a helpful assistant that summarizes a text. Respond with the summary in around 100 words only and nothing else.', 22 | }, 23 | app: { 24 | head: { 25 | title: 'AI Text Summarizer', 26 | meta: [ 27 | { charset: 'utf-8' }, 28 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 29 | { 30 | hid: 'description', 31 | name: 'description', 32 | content: 33 | 'An AI-powered text summarization tool using Cloudflare Workers AI', 34 | }, 35 | { name: 'format-detection', content: 'telephone=no' }, 36 | // Open Graph / Facebook 37 | { property: 'og:type', content: 'website' }, 38 | { property: 'og:title', content: 'AI Text Summarizer' }, 39 | { 40 | property: 'og:description', 41 | content: 42 | 'An AI-powered text summarization tool using Cloudflare Workers AI', 43 | }, 44 | { property: 'og:image', content: '/pdf-summarizer.png' }, 45 | // Twitter 46 | { name: 'twitter:card', content: 'summary_large_image' }, 47 | { name: 'twitter:title', content: 'AI Text Summarizer' }, 48 | { 49 | name: 'twitter:description', 50 | content: 51 | 'An AI-powered text summarization tool using Cloudflare Workers AI', 52 | }, 53 | { name: 'twitter:image', content: '/pdf-summarizer.png' }, 54 | ], 55 | link: [ 56 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, 57 | { 58 | rel: 'stylesheet', 59 | href: 'https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css', 60 | }, 61 | ], 62 | }, 63 | }, 64 | }) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-app", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "build": "nuxt build", 7 | "dev": "nuxt dev", 8 | "generate": "nuxt generate", 9 | "preview": "nuxt preview", 10 | "postinstall": "nuxt prepare" 11 | }, 12 | "dependencies": { 13 | "@cloudflare/workers-types": "^4.20240925.0", 14 | "@nuxt/eslint": "^0.5.7", 15 | "@nuxthub/core": "^0.7.24", 16 | "nuxt": "^3.13.0", 17 | "unpdf": "^0.11.0", 18 | "vue": "latest", 19 | "vue-router": "latest" 20 | }, 21 | "devDependencies": { 22 | "nitro-cloudflare-dev": "^0.1.6", 23 | "wrangler": "^3.78.12" 24 | }, 25 | "packageManager": "pnpm@9.4.0+sha1.9217c800d4ab947a7aee520242a7b70d64fc7638" 26 | } 27 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 97 | 98 | 175 | 176 | 256 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awecode/pdf-summarizer/20df868bccbb08ffe849d66aa5d6ca7d0695d683/public/favicon.ico -------------------------------------------------------------------------------- /public/image.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/pdf-summarizer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awecode/pdf-summarizer/20df868bccbb08ffe849d66aa5d6ca7d0695d683/public/pdf-summarizer.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /server/api/index.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler(async (event) => { 2 | const formData = await readFormData(event) 3 | const pdfFile = formData.get('pdf') 4 | 5 | if (!pdfFile || !(pdfFile instanceof File)) { 6 | throw createError({ 7 | statusCode: 400, 8 | message: 'No PDF file uploaded', 9 | }) 10 | } 11 | 12 | const config = useRuntimeConfig() 13 | const maxContextLength = formData.get('maxContextLength') || config.public.maxContextLength 14 | const aiModel = formData.get('aiModel')?.toString() || config.public.cfWorkerAiModel 15 | 16 | const text = await pdfToText(pdfFile) 17 | const parsedText = text.slice(0, Number(maxContextLength)) 18 | 19 | const messages = [ 20 | { 21 | role: 'system', 22 | content: config.systemPrompt, 23 | }, 24 | { 25 | role: 'user', 26 | content: parsedText, 27 | }, 28 | ] 29 | 30 | const ai = hubAI() 31 | const response = await ai.run(aiModel, { 32 | messages 33 | }) 34 | 35 | return {result: response} 36 | }) 37 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /server/utils/pdf.ts: -------------------------------------------------------------------------------- 1 | import { extractText, getDocumentProxy } from 'unpdf' 2 | 3 | export const pdfToText = async (pdfFile: File) => { 4 | const buffer = await pdfFile.arrayBuffer() 5 | const pdf = await getDocumentProxy(new Uint8Array(buffer)) 6 | const { text } = await extractText(pdf, { mergePages: true }) 7 | return text 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "pdf-summarizer" 2 | pages_build_output_dir = "./dist" 3 | 4 | compatibility_flags = ["nodejs_compat"] 5 | compatibility_date = "2024-09-23" 6 | 7 | [ai] 8 | binding = "AI" --------------------------------------------------------------------------------