Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ A collection of examples and demos for people building the web with Netlify
- Nuxt 4 with advanced caching - [Site](https://example-nuxt-4-caching.netlify.app/), [Code](https://github.com/netlify/examples/tree/main/examples/frameworks/nuxt-4-caching)
- Nuxt AI chat starter - [Site](https://example-nuxt-chat.netlify.app/), [Code](https://github.com/netlify/examples/tree/main/examples/frameworks/nuxt-chat)
- Prerender Extension Demo - [Code](https://github.com/netlify/examples/tree/main/examples/prerender-extension)
- Raindrop SmartBucket - [Site](https://smartbucket-demo-do-not-remove.netlify.app/), [Code](https://github.com/netlify/examples/tree/main/examples/smartbucket)
- Raindrop SmartMemory - [Site](https://smartmemory-demo-do-not-remove.netlify.app/), [Code](https://github.com/netlify/examples/tree/main/examples/smartmemory)
- Raindrop SmartSQL - [Site](https://smartsql-demo-do-not-remove.netlify.app/), [Code](https://github.com/netlify/examples/tree/main/examples/smartsql)
- Serverless functions, hello world — [Site](https://example-functions-hello-world.netlify.app/), [Code](https://github.com/netlify/examples/tree/main/examples/serverless/functions-hello-world)
- Supabase with Astro - [Site](https://supabase-astro-example.netlify.app/), [Code](https://github.com/netlify/examples/tree/main/examples/supabase-astro)
- Triple Buzzer (AI Model Comparison Game) - [Site](https://triple-buzzer.netlify.app), [Code](https://github.com/netlify/examples/tree/main/examples/triple-buzzer)
Expand Down
110 changes: 110 additions & 0 deletions examples/smartbucket/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# SmartBucket

[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/netlify/examples&subdirectory=smartbucket)

Semantic document search on Netlify. SmartBucket automatically chunks, embeds, and indexes documents for natural language retrieval.

## Netlify Primitives in Action

- **Netlify Functions** - Serverless document upload and search endpoints
- **Environment Variables** - Automatic configuration via Raindrop integration
- **Raindrop SmartBucket** - Semantic document search with automatic embeddings
- **Serverless Architecture** - Scalable file processing without infrastructure

## How It Works

1. Drag and drop a document (PDF, TXT, JSON, MD) into the upload zone
2. Click upload - document is automatically chunked and embedded
3. Switch to "Search" tab (wait 1-2 minutes for indexing)
4. Ask natural language questions about your documents
5. SmartBucket returns semantically relevant chunks with relevance scores
6. Results show matched text, source file, and similarity percentage

## Clone and Deploy

[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/netlify/examples&subdirectory=smartbucket)

Clicking this button will:
1. Clone this example to a new Netlify project
2. Prompt you to add the Raindrop integration (auto-configures environment variables)
3. Deploy your SmartBucket app instantly

## Local Development

### Prerequisites
- Node.js 18+ installed
- Netlify CLI installed (`npm install -g netlify-cli`)

### Setup

1. Clone the repository:
```bash
git clone https://github.com/netlify/examples.git
cd examples/smartbucket
```

2. Install dependencies:
```bash
npm install
```

3. Link to your Netlify project:
```bash
netlify link
```
This connects your local environment to the deployed site and pulls environment variables.

4. Run the development server:
```bash
netlify dev
```

### Environment Variables

The following are automatically set when you add the Raindrop integration:
- `RAINDROP_API_KEY` - Your Raindrop API authentication key
- `RAINDROP_SMARTBUCKET_NAME` - SmartBucket instance identifier

No manual configuration needed!

## Tech Stack

- **[Netlify Functions](https://docs.netlify.com/functions/overview/)** - Serverless API endpoints
- **[Raindrop SmartBucket](https://docs.liquidmetal.ai/)** - Semantic document search
- **[@liquidmetal-ai/lm-raindrop](https://www.npmjs.com/package/@liquidmetal-ai/lm-raindrop)** - Raindrop JavaScript SDK
- **Vector Embeddings** - Automatic document chunking and semantic indexing
- **Vanilla JavaScript** - Frontend with no framework dependencies

## Project Structure

```
smartbucket/
├── netlify.toml # Netlify configuration
├── netlify/functions/
│ ├── upload.js # Handles document uploads
│ └── search.js # Handles semantic search
└── public/
├── index.html # Tab interface (Upload/Search)
├── style.css # Styling
└── app.js # Client-side logic
```

## Features

### Upload Interface
- Drag-and-drop file upload
- Click to browse files
- Supported formats: PDF, TXT, JSON, MD
- File size display
- Upload status feedback

### Search Interface
- Natural language queries
- Relevance scores (0-100%)
- Result highlighting
- Source file attribution
- Visual score indicators

## More Examples

Check out more examples in the [Netlify examples repository](https://github.com/netlify/examples).
8 changes: 8 additions & 0 deletions examples/smartbucket/netlify.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[build]
publish = "public"
functions = "netlify/functions"

[[redirects]]
from = "/api/*"
to = "/.netlify/functions/:splat"
status = 200
77 changes: 77 additions & 0 deletions examples/smartbucket/netlify/functions/search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Raindrop } from '@liquidmetal-ai/lm-raindrop';
import crypto from 'crypto';

export const handler = async (event, context) => {
// Only allow POST requests
if (event.httpMethod !== 'POST') {
return {
statusCode: 405,
body: JSON.stringify({ error: 'Method not allowed' }),
};
}

try {
const { query } = JSON.parse(event.body);

if (!query) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'Search query is required' }),
};
}

// Initialize Raindrop client
const client = new Raindrop({
apiKey: process.env.RAINDROP_API_KEY,
});

const bucketLocation = {
bucket: {
name: process.env.RAINDROP_SMARTBUCKET_NAME,
version: process.env.RAINDROP_APPLICATION_VERSION,
applicationName: process.env.RAINDROP_APPLICATION_NAME,
}
};

// Perform semantic search
const response = await client.query.chunkSearch({
bucketLocations: [bucketLocation],
input: query,
requestId: crypto.randomUUID(),
});

// Format results
const results = response.results?.map(chunk => ({
text: chunk.text,
score: Math.round(chunk.score * 100),
fileName: chunk.source?.object || 'Unknown',
bucketName: chunk.source?.bucket?.bucketName || 'Unknown',
chunkId: chunk.chunkSignature,
type: chunk.type,
})) || [];

return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
results,
count: results.length,
query: query,
}),
};
} catch (error) {
console.error('Search error:', error);
return {
statusCode: 500,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
error: 'Failed to search documents',
details: error.message
}),
};
}
};
81 changes: 81 additions & 0 deletions examples/smartbucket/netlify/functions/upload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Raindrop } from '@liquidmetal-ai/lm-raindrop';

export const handler = async (event, context) => {
// Only allow POST requests
if (event.httpMethod !== 'POST') {
return {
statusCode: 405,
body: JSON.stringify({ error: 'Method not allowed' }),
};
}

try {
const { fileName, fileContent, contentType } = JSON.parse(event.body);

if (!fileName || !fileContent) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'fileName and fileContent are required' }),
};
}

// Validate file type
const allowedExtensions = ['.pdf', '.txt', '.json', '.md'];
const fileExt = '.' + fileName.split('.').pop().toLowerCase();

if (!allowedExtensions.includes(fileExt)) {
return {
statusCode: 400,
body: JSON.stringify({
error: 'Unsupported file type. Only PDF, TXT, JSON, and MD files are allowed.'
}),
};
}

// Initialize Raindrop client
const client = new Raindrop({
apiKey: process.env.RAINDROP_API_KEY,
});

const bucketLocation = {
bucket: {
name: process.env.RAINDROP_SMARTBUCKET_NAME,
version: process.env.RAINDROP_APPLICATION_VERSION,
applicationName: process.env.RAINDROP_APPLICATION_NAME,
}
};

// Upload to SmartBucket (fileContent is already base64 from client)
const response = await client.bucket.put({
bucketLocation: bucketLocation,
content: fileContent,
contentType: contentType || 'application/pdf',
key: fileName,
});

return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
success: true,
message: 'File uploaded successfully',
fileName: fileName,
bucket: response.bucket,
}),
};
} catch (error) {
console.error('Upload error:', error);
return {
statusCode: 500,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
error: 'Failed to upload file',
details: error.message
}),
};
}
};
26 changes: 26 additions & 0 deletions examples/smartbucket/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "smartbucket",
"version": "1.0.0",
"description": "Semantic document search using Raindrop SmartBucket on Netlify",
"type": "module",
"keywords": [
"netlify",
"raindrop",
"ai",
"serverless",
"smartbucket",
"semantic-search"
],
"license": "MIT",
"scripts": {
"dev": "netlify dev",
"build": "echo 'No build step required'",
"deploy": "netlify deploy --prod"
},
"dependencies": {
"@liquidmetal-ai/lm-raindrop": "^0.6.42"
},
"devDependencies": {
"netlify-cli": "^17.0.0"
}
}
Loading