diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac4db18 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +venv/ +.venv/ +__pycache__/ +*.pyc +.env +temp/ +chroma_db/ +node_modules/ \ No newline at end of file diff --git a/README.md b/README.md index 04caa50..0deb15a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,113 @@ -# scholarai -AI-powered research assistant designed to help students and scientists navigate large volumes of academic literature. Supports paper discovery, summarization, citation exploration, and question answering across research papers. Helps organize knowledge, identify trends, and accelerate literature review workflows. +# ScholarAI + +Open-source AI-powered research assistant by Alpha One Labs. +Ask research questions, summarise papers, discover literature, and generate reviews — +all powered by **Cloudflare Workers AI** (`@cf/meta/llama-3.1-8b-instruct`). + +--- + +## Project Structure + +``` +scholarai/ +├── src/ +│ └── worker.py # Single Python Worker — routing + AI logic +├── static/ +│ └── index.html # Full frontend (HTML/CSS/JS, no build step) +├── wrangler.jsonc # Cloudflare Workers config +├── package.json # npm scripts for Wrangler CLI +└── README.md +``` + +--- + +## Quick Start + +### Prerequisites + +- [Node.js](https://nodejs.org/) ≥ 18 +- A free [Cloudflare account](https://dash.cloudflare.com/sign-up) + +### 1. Install Wrangler + +```bash +npm install +npx wrangler login +``` + +### 2. Run locally + +```bash +npm run dev +``` + +Opens at `http://localhost:8787`. + +### 3. Deploy + +```bash +npm run deploy +``` + +--- + +## API Endpoints + +| Method | Endpoint | Description | +|--------|-----------------|-------------------------------| +| GET | `/` | Serves the frontend HTML | +| GET | `/api/health` | Health check | +| POST | `/api/ask` | Ask a research question | +| POST | `/api/summarize`| Summarise a paper | +| POST | `/api/discover` | Discover relevant papers | +| POST | `/api/review` | Generate a literature review | + +### `POST /api/ask` + +```json +{ "question": "What is transfer learning?", "context": "(optional excerpt)" } +``` + +### `POST /api/summarize` + +```json +{ "title": "...", "abstract": "...", "content": "..." } +``` + +At least one field required. + +### `POST /api/discover` + +```json +{ "query": "graph neural networks", "fields": ["ML", "biology"], "limit": 10 } +``` + +### `POST /api/review` + +```json +{ "topic": "self-supervised learning", "style": "comprehensive", "audience": "researchers" } +``` + +`style` options: `comprehensive`, `brief`, `systematic` + +--- + +## Contributing + +1. Fork the repo +2. Create a branch: `git checkout -b feat/your-change` +3. Commit and open a Pull Request with a clear description + +Please open an issue first for large changes. + +--- + +## License + +See [LICENSE](LICENSE). + +--- + +## Maintained by + +**Alpha One Labs** — open-source AI tooling for practical research workflows. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..cc920a8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1504 @@ +{ + "name": "scholarai", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "scholarai", + "version": "1.0.0", + "devDependencies": { + "wrangler": "^4.0.0" + } + }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.2.tgz", + "integrity": "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==", + "dev": true, + "license": "MIT OR Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.15.0.tgz", + "integrity": "sha512-EGYmJaGZKWl+X8tXxcnx4v2bOZSjQeNI5dWFeXivgX9+YCT69AkzHHwlNbVpqtEUTbew8eQurpyOpeN8fg00nw==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.24", + "workerd": "1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20260312.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260312.1.tgz", + "integrity": "sha512-HUAtDWaqUduS6yasV6+NgsK7qBpP1qGU49ow/Wb117IHjYp+PZPUGReDYocpB4GOMRoQlvdd4L487iFxzdARpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20260312.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260312.1.tgz", + "integrity": "sha512-DOn7TPTHSxJYfi4m4NYga/j32wOTqvJf/pY4Txz5SDKWIZHSTXFyGz2K4B+thoPWLop/KZxGoyTv7db0mk/qyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20260312.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260312.1.tgz", + "integrity": "sha512-TdkIh3WzPXYHuvz7phAtFEEvAxvFd30tHrm4gsgpw0R0F5b8PtoM3hfL2uY7EcBBWVYUBtkY2ahDYFfufnXw/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20260312.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260312.1.tgz", + "integrity": "sha512-kNauZhL569Iy94t844OMwa1zP6zKFiL3xiJ4tGLS+TFTEfZ3pZsRH6lWWOtkXkjTyCmBEOog0HSEKjIV4oAffw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20260312.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260312.1.tgz", + "integrity": "sha512-5dBrlSK+nMsZy5bYQpj8t9iiQNvCRlkm9GGvswJa9vVU/1BNO4BhJMlqOLWT24EmFyApZ+kaBiPJMV8847NDTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", + "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@poppinss/colors": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^4.1.5" + } + }, + "node_modules/@poppinss/dumper": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" + } + }, + "node_modules/@poppinss/exception": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", + "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@speed-highlight/core": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.14.tgz", + "integrity": "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/miniflare": { + "version": "4.20260312.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260312.0.tgz", + "integrity": "sha512-pieP2rfXynPT6VRINYaiHe/tfMJ4c5OIhqRlIdLF6iZ9g5xgpEmvimvIgMpgAdDJuFlrLcwDUi8MfAo2R6dt/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "sharp": "^0.34.5", + "undici": "7.18.2", + "workerd": "1.20260312.1", + "ws": "8.18.0", + "youch": "4.1.0-beta.10" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/undici": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.18.2.tgz", + "integrity": "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/unenv": { + "version": "2.0.0-rc.24", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3" + } + }, + "node_modules/workerd": { + "version": "1.20260312.1", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260312.1.tgz", + "integrity": "sha512-nNpPkw9jaqo79B+iBCOiksx+N62xC+ETIfyzofUEdY3cSOHJg6oNnVSHm7vHevzVblfV76c8Gr0cXHEapYMBEg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20260312.1", + "@cloudflare/workerd-darwin-arm64": "1.20260312.1", + "@cloudflare/workerd-linux-64": "1.20260312.1", + "@cloudflare/workerd-linux-arm64": "1.20260312.1", + "@cloudflare/workerd-windows-64": "1.20260312.1" + } + }, + "node_modules/wrangler": { + "version": "4.73.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.73.0.tgz", + "integrity": "sha512-VJXsqKDFCp6OtFEHXITSOR5kh95JOknwPY8m7RyQuWJQguSybJy43m4vhoCSt42prutTef7eeuw7L4V4xiynGw==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@cloudflare/kv-asset-handler": "0.4.2", + "@cloudflare/unenv-preset": "2.15.0", + "blake3-wasm": "2.1.5", + "esbuild": "0.27.3", + "miniflare": "4.20260312.0", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.24", + "workerd": "1.20260312.1" + }, + "bin": { + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20260312.1" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/youch": { + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" + } + }, + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f26d6f0 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "scholarai", + "version": "1.0.0", + "description": "AI-powered research assistant using Cloudflare Workers AI", + "private": true, + "scripts": { + "dev": "wrangler dev", + "deploy": "wrangler deploy", + "start": "wrangler dev" + }, + "devDependencies": { + "wrangler": "^4.0.0" + } +} \ No newline at end of file diff --git a/src/worker.py b/src/worker.py new file mode 100644 index 0000000..30e2716 --- /dev/null +++ b/src/worker.py @@ -0,0 +1,274 @@ +""" +ScholarAI - Cloudflare Python Worker +Correct entrypoint: WorkerEntrypoint class with async fetch() method. +Uses JS interop (from js import ...) for Response, Headers, URL. +""" + +import json +from js import Response, Headers, URL +from pyodide.ffi import to_js +from workers import WorkerEntrypoint + + +# --------------------------------------------------------------------------- +# Config +# --------------------------------------------------------------------------- +AI_MODEL = "@cf/meta/llama-3.1-8b-instruct" +MAX_TOKENS = 1024 + + +# --------------------------------------------------------------------------- +# Response helpers +# --------------------------------------------------------------------------- + +def _headers(content_type: str = "application/json") -> Headers: + h = Headers.new() + h.set("Content-Type", content_type) + h.set("Access-Control-Allow-Origin", "*") + h.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") + h.set("Access-Control-Allow-Headers", "Content-Type") + return h + + +def json_resp(data: dict, status: int = 200) -> Response: + return Response.new( + json.dumps(data), + to_js({"status": status, "headers": {"Content-Type": "application/json", + "Access-Control-Allow-Origin": "*"}}), + ) + + +def error_resp(message: str, status: int = 400) -> Response: + return json_resp({"ok": False, "error": message}, status) + + +def html_resp(html: str) -> Response: + return Response.new( + html, + to_js({"status": 200, "headers": {"Content-Type": "text/html; charset=utf-8"}}), + ) + + +def cors_resp() -> Response: + return Response.new( + "", + to_js({ + "status": 204, + "headers": { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type", + }, + }), + ) + + +# --------------------------------------------------------------------------- +# AI helper +# --------------------------------------------------------------------------- + +async def run_ai(env, system_prompt: str, user_prompt: str) -> str: + payload = to_js({ + "messages": [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt}, + ], + "max_tokens": MAX_TOKENS, + }) + result = await env.AI.run(AI_MODEL, payload) + text = getattr(result, "response", None) + if text is None: + text = str(result) + return text.strip() + + +# --------------------------------------------------------------------------- +# Body parser +# --------------------------------------------------------------------------- + +async def parse_body(request) -> dict: + try: + text = await request.text() + return json.loads(text) if text else {} + except Exception: + return {} + + +# --------------------------------------------------------------------------- +# HTML — served via ASSETS binding +# --------------------------------------------------------------------------- + +_HTML_CACHE: str | None = None + + +async def get_html(env) -> str: + global _HTML_CACHE + if _HTML_CACHE: + return _HTML_CACHE + try: + resp = await env.ASSETS.fetch("http://assets/index.html") + _HTML_CACHE = await resp.text() + return _HTML_CACHE + except Exception as e: + return ( + "" + "

ScholarAI

" + f"

Frontend load error: {e}

" + "" + ) + + +# --------------------------------------------------------------------------- +# Route handlers +# --------------------------------------------------------------------------- + +async def handle_health(request, env) -> Response: + return json_resp({"ok": True, "service": "scholarai", "model": AI_MODEL}) + + +async def handle_ask(request, env) -> Response: + body = await parse_body(request) + question = str(body.get("question") or "").strip() + context = str(body.get("context") or "").strip() + + if not question: + return error_resp("'question' is required.") + + system_prompt = ( + "You are ScholarAI, an expert academic research assistant. " + "Answer research questions clearly and accurately. " + "If paper context is provided, base your answer on it. " + "If unsure, say so rather than guessing." + ) + ctx_block = f"\n\nContext from uploaded paper:\n{context[:10000]}" if context else "" + user_prompt = f"Question: {question}{ctx_block}" + + try: + answer = await run_ai(env, system_prompt, user_prompt) + return json_resp({"ok": True, "answer": answer}) + except Exception as e: + return error_resp(f"AI error: {e}", 500) + + +async def handle_summarize(request, env) -> Response: + body = await parse_body(request) + title = str(body.get("title") or "").strip() + abstract = str(body.get("abstract") or "").strip() + content = str(body.get("content") or "").strip() + + if not (title or abstract or content): + return error_resp("At least one of 'title', 'abstract', or 'content' is required.") + + parts = [] + if title: parts.append(f"Title: {title}") + if abstract: parts.append(f"Abstract: {abstract}") + if content: parts.append(f"Content:\n{content[:10000]}") + + system_prompt = ( + "You are ScholarAI, an expert at summarising research papers. " + "Produce a structured summary: main contribution, problem, " + "methodology, key findings, and significance." + ) + user_prompt = "Summarise this paper:\n\n" + "\n\n".join(parts) + + try: + summary = await run_ai(env, system_prompt, user_prompt) + return json_resp({"ok": True, "title": title, "summary": summary}) + except Exception as e: + return error_resp(f"AI error: {e}", 500) + + +async def handle_discover(request, env) -> Response: + body = await parse_body(request) + query = str(body.get("query") or "").strip() + fields = body.get("fields") or [] + limit = int(body.get("limit") or 10) + + if not query: + return error_resp("'query' is required.") + + field_ctx = f" in the fields of {', '.join(fields)}" if fields else "" + system_prompt = ( + "You are ScholarAI, an expert academic research assistant. " + "Suggest relevant papers, key concepts, and research directions." + ) + user_prompt = ( + f"Suggest up to {limit} relevant academic papers{field_ctx} for:\n\n" + f"Query: {query}\n\n" + "For each paper: title, likely authors/year, one-line relevance. " + "Then list 3-5 key concepts and 2-3 related queries." + ) + + try: + results = await run_ai(env, system_prompt, user_prompt) + return json_resp({"ok": True, "query": query, "results": results}) + except Exception as e: + return error_resp(f"AI error: {e}", 500) + + +async def handle_review(request, env) -> Response: + body = await parse_body(request) + topic = str(body.get("topic") or "").strip() + style = str(body.get("style") or "comprehensive").strip() + audience = str(body.get("audience") or "researchers").strip() + context = str(body.get("context") or "").strip() + + if not topic: + return error_resp("'topic' is required.") + + ctx_block = f"\n\nPaper content for reference:\n{context[:10000]}" if context else "" + system_prompt = ( + "You are ScholarAI, an expert at writing academic literature reviews. " + "Generate well-structured, rigorous literature reviews." + ) + user_prompt = ( + f"Write a {style} literature review on '{topic}' for {audience}.{ctx_block}\n\n" + "Structure: Introduction, Background, State of the Art, " + "Key Methodologies, Major Findings, Open Challenges, Future Directions." + ) + + try: + review = await run_ai(env, system_prompt, user_prompt) + return json_resp({"ok": True, "topic": topic, "style": style, "review": review}) + except Exception as e: + return error_resp(f"AI error: {e}", 500) + + +# --------------------------------------------------------------------------- +# Router table +# --------------------------------------------------------------------------- + +ROUTES = { + "GET /api/health": handle_health, + "POST /api/ask": handle_ask, + "POST /api/summarize": handle_summarize, + "POST /api/discover": handle_discover, + "POST /api/review": handle_review, +} + + +# --------------------------------------------------------------------------- +# Entrypoint — MUST be a WorkerEntrypoint subclass with async fetch() +# Cloudflare Python Workers require this exact pattern. +# --------------------------------------------------------------------------- + +class Default(WorkerEntrypoint): + + async def fetch(self, request) -> Response: + method = request.method.upper() + path = URL.new(str(request.url)).pathname + + # CORS preflight + if method == "OPTIONS": + return cors_resp() + + # Serve HTML for root path + if method == "GET" and path in ("/", "/index.html"): + return html_resp(await get_html(self.env)) + + # API routes + handler = ROUTES.get(f"{method} {path}") + if handler is None: + return error_resp(f"Not found: {method} {path}", 404) + + return await handler(request, self.env) \ No newline at end of file diff --git a/static/images/logo.png b/static/images/logo.png new file mode 100755 index 0000000..29d0cbf Binary files /dev/null and b/static/images/logo.png differ diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..a00fddb --- /dev/null +++ b/static/index.html @@ -0,0 +1,564 @@ + + + + + + ScholarAI | Alpha One Labs + + + + + + + + + + + + + +
+ + +
+

Open Source · Privacy-First · Cloudflare Workers AI

+

ScholarAI

+

+ An open-source research assistant by Alpha One Labs. Ask research questions, summarise papers, discover literature — powered by Cloudflare Workers AI. +

+ +
+ + +
+
+ +

Research Assistant

+ + +
+
+
+
📄
+
+
Upload your research paper
+
+ PDF is read locally in your browser — nothing is uploaded to any server +
+
+
+
+ + + +
+
+ + + + + +
+
+ + +
+ + + + +
+ + +
+

Ask a Research Question

+ + + + + +
+
+
Answer
+
No answer yet.
+
+
+ + +
+

Summarise a Paper

+ + + + + + + +
+
+
Summary
+
No summary yet.
+
+
+ + +
+

Discover Papers

+ + + + + +
+
+
Results
+
No results yet.
+
+
+ + +
+

Generate a Literature Review

+ + +
+
+ + +
+
+ + +
+
+ +
+
+
Literature Review
+
No review yet.
+
+
+ +
+
+ + +
+
+ +

How to Use ScholarAI

+
+

Upload your PDF research paper using the panel above the tabs — it stays in your browser, nothing is sent to a server.

+

The text is extracted automatically. It will be injected as context into Ask, Summarise, and Review.

+

Pick a tool tab: Ask a question, Summarise, Discover new papers, or Generate a Review.

+

Hit the action button — the AI responds in seconds using your paper as context.

+

All processing is stateless — no data is stored between sessions.

+
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..335c5ef --- /dev/null +++ b/static/style.css @@ -0,0 +1,566 @@ + /* ── Reset & Root ─────────────────────────────────────────── */ + *, *::before, *::after { box-sizing: border-box; } + html { scroll-behavior: smooth; } + + :root { + --bg: #0b1220; + --bg-soft: #101a2f; + --panel: rgba(15, 23, 42, 0.78); + --panel-border: rgba(148, 163, 184, 0.16); + --text: #e5e7eb; + --muted: #9fb0c6; + --input-bg: #0f172a; + --input-border: #334155; + --primary: #3b82f6; + --primary-h: #2563eb; + --accent: #14b8a6; + --danger: #dc2626; + --danger-h: #b91c1c; + --success: #22c55e; + --section-max: 860px; + --nav-h: 62px; + } + + body { + margin: 0; + font-family: 'Sora', sans-serif; + color: var(--text); + background: + radial-gradient(circle at 0% 0%, rgba(20,184,166,.16), transparent 35%), + radial-gradient(circle at 100% 0%, rgba(249,115,22,.14), transparent 30%), + var(--bg); + min-height: 100vh; + } + + /* ── Utilities ────────────────────────────────────────────── */ + .hidden { display: none !important; } + .w-full { width: 100%; } + .m-0 { margin: 0; } + + /* ── Navbar ───────────────────────────────────────────────── */ + .navbar { + position: fixed; + inset: 0 0 auto 0; + z-index: 50; + height: var(--nav-h); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 28px; + background: rgba(11,18,32,.88); + backdrop-filter: blur(14px); + border-bottom: 1px solid var(--panel-border); + } + + .logo-pill { + display: inline-flex; + align-items: center; + gap: 10px; + text-decoration: none; + } + + .logo-img { + width: 34px; height: 34px; + border-radius: 8px; + object-fit: contain; + } + + .brand-text { + color: #f8fafc; + font-weight: 700; + font-size: .95rem; + } + + .nav-links { + display: flex; + align-items: center; + gap: 2px; + } + + .nav-links a { + color: var(--muted); + text-decoration: none; + font-size: .85rem; + font-weight: 500; + padding: 6px 11px; + border-radius: 7px; + transition: color .15s, background .15s; + } + + .nav-links a:hover { color: #f8fafc; background: rgba(255,255,255,.06); } + + .nav-cta { + background: var(--primary) !important; + color: #fff !important; + margin-left: 6px; + } + + .nav-cta:hover { background: var(--primary-h) !important; } + + .menu-btn { + display: none; + border: 1px solid var(--panel-border); + background: rgba(255,255,255,.05); + color: #f8fafc; + border-radius: 8px; + padding: 7px 13px; + cursor: pointer; + font-size: 1rem; + } + + .mobile-menu { + position: fixed; + right: 16px; + top: calc(var(--nav-h) + 8px); + z-index: 60; + width: 200px; + border-radius: 12px; + border: 1px solid var(--panel-border); + background: rgba(11,18,32,.97); + box-shadow: 0 16px 32px rgba(2,6,23,.5); + padding: 8px; + } + + .mobile-menu a { + display: block; + color: #dbe6f5; + text-decoration: none; + padding: 10px 12px; + border-radius: 8px; + font-size: .9rem; + } + + .mobile-menu a:hover { background: #18233b; } + + /* ── Page wrapper ─────────────────────────────────────────── */ + .page { padding-top: var(--nav-h); } + + /* ── Section shared ───────────────────────────────────────── */ + .section-wrap { + width: min(var(--section-max), calc(100% - 40px)); + margin: 0 auto; + padding: 80px 0; + } + + .section-label { + display: inline-block; + font-size: .72rem; + font-weight: 700; + letter-spacing: .8px; + text-transform: uppercase; + color: var(--accent); + margin-bottom: 8px; + } + + .section-title { + font-size: clamp(1.4rem, 3vw, 1.9rem); + font-weight: 700; + color: #f8fafc; + margin: 0 0 28px; + letter-spacing: -.3px; + } + + /* ── Hero ─────────────────────────────────────────────────── */ + .hero { + min-height: calc(100vh - var(--nav-h)); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + padding: 60px 24px; + } + + .hero-chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 5px 14px; + border-radius: 999px; + font-size: .75rem; + font-weight: 600; + letter-spacing: .5px; + text-transform: uppercase; + border: 1px solid rgba(20,184,166,.35); + background: rgba(20,184,166,.07); + color: var(--accent); + margin-bottom: 20px; + } + + .hero h1 { + margin: 0 0 18px; + font-size: clamp(2.6rem, 7vw, 4.2rem); + font-weight: 700; + color: #f8fafc; + line-height: 1.08; + letter-spacing: -1.5px; + } + + .hero h1 span { + background: linear-gradient(90deg, #3b82f6 30%, #14b8a6); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + } + + .hero-desc { + max-width: 560px; + margin: 0 auto 36px; + color: var(--muted); + font-size: 1.05rem; + line-height: 1.75; + } + + .hero-actions { + display: flex; + gap: 12px; + flex-wrap: wrap; + justify-content: center; + } + + /* ── Buttons ──────────────────────────────────────────────── */ + .btn { + display: inline-block; + padding: 10px 22px; + border: none; + border-radius: 8px; + background: var(--primary); + color: #fff; + font: 600 .9rem/1 'Sora', sans-serif; + cursor: pointer; + text-decoration: none; + transition: background .15s, transform .1s, opacity .15s; + } + + .btn:hover { background: var(--primary-h); transform: translateY(-1px); } + .btn:active { transform: none; } + .btn:disabled { opacity: .55; cursor: not-allowed; transform: none; } + + .btn-ghost { + background: transparent; + border: 1px solid var(--panel-border); + color: #dbe6f5; + } + + .btn-ghost:hover { background: rgba(255,255,255,.06); } + + .btn-danger { background: var(--danger); } + .btn-danger:hover { background: var(--danger-h); } + + /* ── Cards ────────────────────────────────────────────────── */ + .card { + background: var(--panel); + border: 1px solid var(--panel-border); + border-radius: 14px; + padding: 22px; + box-shadow: 0 8px 24px rgba(2,6,23,.28); + } + + .card-title { + margin: 0 0 16px; + font-size: .88rem; + font-weight: 700; + color: #f8fafc; + text-transform: uppercase; + letter-spacing: .5px; + display: flex; + align-items: center; + gap: 8px; + } + + .card-title::before { + content: ''; + display: block; + width: 3px; height: 15px; + border-radius: 2px; + background: var(--primary); + flex-shrink: 0; + } + + /* ── Inputs ───────────────────────────────────────────────── */ + .input, textarea, input[type="text"] { + width: 100%; + padding: 10px 12px; + border: 1px solid var(--input-border); + border-radius: 8px; + font: .9rem/1.5 'Sora', sans-serif; + color: #e2e8f0; + background: var(--input-bg); + margin-bottom: 12px; + outline: none; + transition: border-color .15s; + display: block; + resize: vertical; + } + + .input:focus, textarea:focus { border-color: var(--primary); } + + /* ── Tab system ───────────────────────────────────────────── */ + .tab-bar { + display: flex; + gap: 4px; + margin-bottom: 20px; + border-bottom: 1px solid var(--panel-border); + padding-bottom: 0; + } + + .tab-btn { + padding: 8px 16px; + border: none; + background: transparent; + color: var(--muted); + font: 600 .82rem/1 'Sora', sans-serif; + cursor: pointer; + border-bottom: 2px solid transparent; + margin-bottom: -1px; + border-radius: 6px 6px 0 0; + transition: color .15s, border-color .15s; + letter-spacing: .3px; + text-transform: uppercase; + } + + .tab-btn:hover { color: #f8fafc; } + .tab-btn.active { color: var(--primary); border-bottom-color: var(--primary); } + + .tab-panel { display: none; } + .tab-panel.active { display: block; } + + /* ── Ask tab ──────────────────────────────────────────────── */ + #answerBox { + white-space: pre-wrap; + background: #020617; + padding: 16px; + border-radius: 10px; + border: 1px solid #1e293b; + min-height: 130px; + color: #e2e8f0; + font: .88rem/1.65 'JetBrains Mono', monospace; + margin: 0; + } + + /* ── Status bar ───────────────────────────────────────────── */ + .status-bar { + margin-top: 10px; + font-size: .83rem; + min-height: 20px; + color: #64748b; + } + + .status-bar.ok { color: var(--success); } + .status-bar.err { color: #f87171; } + .status-bar.inf { color: #60a5fa; } + + /* ── Workspace meta row ───────────────────────────────────── */ + .meta-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + flex-wrap: wrap; + padding: 12px 16px; + border-radius: 10px; + background: rgba(255,255,255,.025); + border: 1px solid var(--panel-border); + margin-bottom: 20px; + } + + .meta-row p { margin: 0; color: var(--muted); font-size: .84rem; } + + /* ── How-to steps ─────────────────────────────────────────── */ + #how-to { border-top: 1px solid var(--panel-border); } + + .how-steps { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); + gap: 14px; + counter-reset: steps; + } + + .how-step { + counter-increment: steps; + padding: 20px; + border-radius: 12px; + border: 1px solid var(--panel-border); + background: rgba(255,255,255,.02); + } + + .how-step::before { + content: counter(steps, decimal-leading-zero); + display: block; + font: 700 1.5rem/1 'JetBrains Mono', monospace; + color: var(--primary); + opacity: .65; + margin-bottom: 10px; + } + + .how-step p { margin: 0; color: #cbd5e1; font-size: .88rem; line-height: 1.6; } + + /* ── Footer ───────────────────────────────────────────────── */ + .site-footer { + border-top: 1px solid var(--panel-border); + background: rgba(11,18,32,.9); + } + + .footer-inner { + width: min(var(--section-max), calc(100% - 40px)); + margin: 0 auto; + padding: 48px 0 32px; + display: grid; + grid-template-columns: 1fr auto; + gap: 40px; + align-items: start; + } + + .footer-brand .brand-text { display: block; margin-bottom: 8px; font-size: 1rem; } + .footer-brand p { margin: 0; color: var(--muted); font-size: .84rem; line-height: 1.6; max-width: 280px; } + + .footer-nav-label { + font-size: .7rem; + font-weight: 700; + letter-spacing: .7px; + text-transform: uppercase; + color: var(--muted); + margin: 0 0 8px; + } + + .footer-nav { display: flex; flex-direction: column; gap: 6px; } + + .footer-link { color: #94a3b8; text-decoration: none; font-size: .86rem; transition: color .15s; } + .footer-link:hover { color: var(--accent); } + + .footer-bottom { + width: min(var(--section-max), calc(100% - 40px)); + margin: 0 auto; + padding: 16px 0; + border-top: 1px solid var(--panel-border); + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; + } + + .footer-bottom p { margin: 0; color: #475569; font-size: .78rem; } + + .footer-bottom-links { display: flex; gap: 16px; } + .footer-bottom-links a { color: #475569; text-decoration: none; font-size: .78rem; transition: color .15s; } + .footer-bottom-links a:hover { color: var(--muted); } + + /* ── PDF Upload Panel ────────────────────────────────────── */ + .pdf-panel { + margin-bottom: 20px; + border-radius: 14px; + border: 1.5px dashed var(--panel-border); + background: rgba(15,23,42,.5); + padding: 20px 22px; + transition: border-color .2s, background .2s; + } + + .pdf-panel.has-pdf { + border-style: solid; + border-color: var(--accent); + background: rgba(20,184,166,.06); + } + + .pdf-panel.dragging { + border-color: var(--primary); + background: rgba(59,130,246,.08); + } + + .pdf-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; + } + + .pdf-label { + display: flex; + align-items: center; + gap: 10px; + font-size: .88rem; + font-weight: 600; + color: #f8fafc; + } + + .pdf-icon { + width: 32px; height: 32px; + border-radius: 8px; + background: rgba(59,130,246,.15); + display: flex; + align-items: center; + justify-content: center; + font-size: 1rem; + flex-shrink: 0; + } + + .pdf-icon.loaded { background: rgba(20,184,166,.18); } + + .pdf-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; } + + .btn-sm { + padding: 7px 14px; + font-size: .8rem; + border-radius: 7px; + } + + .pdf-meta { + margin-top: 12px; + padding: 10px 14px; + border-radius: 8px; + background: rgba(255,255,255,.04); + border: 1px solid var(--panel-border); + font-size: .82rem; + color: var(--muted); + display: flex; + gap: 16px; + flex-wrap: wrap; + } + + .pdf-meta span { display: flex; align-items: center; gap: 5px; } + + .pdf-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 999px; + font-size: .72rem; + font-weight: 700; + letter-spacing: .3px; + background: rgba(20,184,166,.15); + color: var(--accent); + border: 1px solid rgba(20,184,166,.3); + } + + .pdf-inject-note { + margin-top: 10px; + font-size: .78rem; + color: var(--accent); + opacity: .8; + } + + /* file input hidden, label acts as button */ + #pdfFileInput { display: none; } + + /* ── Spinner ──────────────────────────────────────────────── */ + @keyframes spin { to { transform: rotate(360deg); } } + + .spinner { + display: inline-block; + width: 14px; height: 14px; + border: 2px solid rgba(255,255,255,.2); + border-top-color: #fff; + border-radius: 50%; + animation: spin .6s linear infinite; + vertical-align: middle; + margin-right: 6px; + } + + /* ── Responsive ───────────────────────────────────────────── */ + @media (max-width: 680px) { + .menu-btn { display: block; } + .nav-links { display: none; } + .footer-inner { grid-template-columns: 1fr; gap: 24px; } + .how-steps { grid-template-columns: 1fr; } + .hero { padding: 48px 20px; } + .tab-btn { font-size: .75rem; padding: 7px 11px; } + } \ No newline at end of file diff --git a/wrangler.jsonc b/wrangler.jsonc new file mode 100644 index 0000000..090b2ec --- /dev/null +++ b/wrangler.jsonc @@ -0,0 +1,20 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "scholarai", + "main": "src/worker.py", + "compatibility_date": "2025-11-02", + "compatibility_flags": [ + "python_workers", + "python_dedicated_snapshot" + ], + "ai": { + "binding": "AI" + }, + "assets": { + "binding": "ASSETS", + "directory": "./static" + }, + "observability": { + "enabled": true + } +}