PHPackages                             nahid-hasan/ai-notes - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. nahid-hasan/ai-notes

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

nahid-hasan/ai-notes
====================

AI-powered voice notes with semantic search for Laravel

v1.0(2mo ago)12PHPPHP ^8.2

Since Apr 5Pushed 2mo agoCompare

[ Source](https://github.com/nahidprince7/ollama-notes-laravel-package)[ Packagist](https://packagist.org/packages/nahid-hasan/ai-notes)[ RSS](/packages/nahid-hasan-ai-notes/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (4)Versions (3)Used By (0)

🧠 ollama-notes-laravel-package (ai-notes)
=========================================

[](#-ollama-notes-laravel-package-ai-notes)

[![Latest Version on Packagist](https://camo.githubusercontent.com/d089441ab71726d70014b893b6040cd0166e429c2e53c832ab5273160f7f106d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6e616869642f6c61726176656c2d61692d6e6f7465732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/nahid-hasan/ai-notes)[![Total Downloads](https://camo.githubusercontent.com/3e33682b9ab67bee4db8fd76ed867b5b8a7cb618b6c17268e65ce72bed5da22e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6e616869642f6c61726176656c2d61692d6e6f7465732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/nahid-hasan/ai-notes)[![License](https://camo.githubusercontent.com/d2cf060799ca5d4bfe4b8643a1197021a064a156dbe7acfb962f0b81daee513c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6e616869642f6c61726176656c2d61692d6e6f7465732e7376673f7374796c653d666c61742d737175617265)](LICENSE)[![PHP Version](https://camo.githubusercontent.com/453d3230d18331ac71d5630896cd4636482e3766afa89a8119e72d599d98d889/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f6e616869642f6c61726176656c2d61692d6e6f7465732e7376673f7374796c653d666c61742d737175617265)](composer.json)

**Turn your Laravel app into an AI-powered memory system.**

Record a voice note or write text. Search it later using natural language — not keywords. No OpenAI account needed. Runs 100% locally and free with Ollama + Whisper.

```
// Store a voice note
AINote::fromAudio($request->file('audio'));

// Store a text note
AINote::fromText("Met with client Sarah about renewing the contract.");

// Search semantically — finds related notes even with different wording
AINote::search("contract renewal discussion");

// Ask a question across all your notes
AINote::ask("What did I promise the client?");
```

---

Features
--------

[](#features)

- 🎙️ **Voice notes** — upload audio, Whisper transcribes it automatically
- ✍️ **Text notes** — store any text, summarized and embedded instantly
- 🔍 **Semantic search** — find notes by meaning, not just keywords (pgvector)
- 🤖 **Ask questions** — RAG-powered Q&amp;A across your entire note history
- ⚡ **Queue-first** — all AI processing runs async via Laravel queues
- 🔌 **Swappable drivers** — Ollama (free/local) or OpenAI (paid)
- 🐳 **Docker included** — full docker-compose setup out of the box
- 🧪 **Fake provider** — test without any AI services running

---

Requirements
------------

[](#requirements)

RequirementVersionPHP^8.2Laravel^11.0PostgreSQL^14 + pgvector extensionRedisAny> **For the free local AI stack** (recommended): Docker is required to run Ollama and Whisper containers.
> **For OpenAI**: just an API key, no Docker needed for AI services.

---

Installation
------------

[](#installation)

### 1. Install the package

[](#1-install-the-package)

```
composer require nahid-hasan/ai-notes
```

### 2. Run the install command

[](#2-run-the-install-command)

```
php artisan ai-notes:install
```

This publishes the config file and migrations.

### 3. Run migrations

[](#3-run-migrations)

```
php artisan migrate
```

> This creates the `ai_notes` table and enables the `pgvector` extension automatically.

---

Setup: Free Local Stack (Recommended)
-------------------------------------

[](#setup-free-local-stack-recommended)

This uses **Ollama** (summarization + embeddings) and **Whisper** (transcription) — both running in Docker, zero cost, zero API keys.

### Step 1 — Add Docker services

[](#step-1--add-docker-services)

Copy this into your `docker-compose.yml`:

```
services:
  ollama:
    image: ollama/ollama:latest
    container_name: ai_notes_ollama
    restart: unless-stopped
    ports:
      - "11434:11434"
    volumes:
      - ollama_data:/root/.ollama
    networks:
      - your_network

  whisper:
    image: your-whisper-image  # see full docker setup in docs
    container_name: ai_notes_whisper
    ports:
      - "9000:9000"
    networks:
      - your_network

volumes:
  ollama_data:
```

> For the complete docker-compose.yml with Whisper Dockerfile and Flask server, see [Docker Setup](#docker-setup) below.

### Step 2 — Pull the AI models

[](#step-2--pull-the-ai-models)

```
# Pull once — these stay cached in the ollama_data volume
docker exec ai_notes_ollama ollama pull llama3.2
docker exec ai_notes_ollama ollama pull nomic-embed-text
```

> First pull is ~2-4GB. After that, restarts are instant.

### Step 3 — Configure your `.env`

[](#step-3--configure-your-env)

```
AI_DRIVER=ollama

OLLAMA_BASE_URL=http://ollama:11434
OLLAMA_CHAT_MODEL=llama3.2
OLLAMA_EMBED_MODEL=nomic-embed-text

WHISPER_BASE_URL=http://whisper:9000

DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=your_db
DB_USERNAME=your_user
DB_PASSWORD=your_password

QUEUE_CONNECTION=redis
REDIS_HOST=redis
```

### Step 4 — Start the queue worker

[](#step-4--start-the-queue-worker)

```
php artisan queue:work
```

That's it. You're running fully local AI.

---

Setup: OpenAI (Paid Alternative)
--------------------------------

[](#setup-openai-paid-alternative)

If you prefer OpenAI and already have an API key:

```
AI_DRIVER=openai
OPENAI_API_KEY=sk-...
OPENAI_CHAT_MODEL=gpt-4o-mini
OPENAI_EMBED_MODEL=text-embedding-3-small
```

No Ollama or Whisper needed. Everything else works identically.

> **Note:** When switching from Ollama (768 dims) to OpenAI (1536 dims), you need to re-run migrations with the correct dimension. Set `AI_NOTES_VECTOR_DIM=1536` in `.env` before migrating.

---

Usage
-----

[](#usage)

### Storing Notes

[](#storing-notes)

**From audio (voice note):**

```
use AINote;

// In a controller
public function store(Request $request)
{
    $request->validate([
        'audio' => 'required|file|mimes:wav,mp3,mp4,webm|max:51200'
    ]);

    $note = AINote::fromAudio(
        $request->file('audio'),
        userId: auth()->id()   // optional — omit for single-user apps
    );

    // Note is queued for processing. Returns immediately.
    return response()->json([
        'note_id' => $note->id,
        'status'  => $note->status,  // "pending"
    ], 202);
}
```

**From text:**

```
$note = AINote::fromText(
    "Had a call with John. He wants the proposal by Friday.",
    userId: auth()->id(),
    title: "John call"  // optional
);
```

---

### Checking Processing Status

[](#checking-processing-status)

AI processing is async. Poll the status or use a webhook/event:

```
use Nahid\AINotesPackage\Models\AINote;

$note = AINote::find($noteId);

echo $note->status;
// "pending"    → queued, not started
// "processing" → AI is working on it
// "done"       → transcription, summary, and embedding ready
// "failed"     → something went wrong, check logs
```

---

### Searching Notes

[](#searching-notes)

```
// Basic search
$results = AINote::search("payment discussion");

// Limit results
$results = AINote::search("contract renewal", 3);

// Scoped to a user
$results = AINote::search("invoice delay", 5, auth()->id());

// Each result has a `distance` score — lower = more relevant (0.0 to 1.0)
foreach ($results as $note) {
    echo $note->summary;
    echo $note->distance;  // e.g. 0.31 = very relevant
}
```

**How distance scores work:**

DistanceMeaning0.0 – 0.3Highly relevant0.3 – 0.5Related / probably useful0.5 – 0.7Loosely related0.7 – 1.0Not really related---

### Asking Questions (RAG)

[](#asking-questions-rag)

```
// Ask a question — searches relevant notes and generates an answer
$answer = AINote::ask("What did I say about the Q4 budget?");
echo $answer;

// Scoped to a user
$answer = AINote::ask("What promises did I make to clients?", userId: auth()->id());

// Control how many notes are used as context (default: 3)
$answer = AINote::ask("Summarize my week", contextLimit: 5, userId: auth()->id());
```

---

### Working with the Model Directly

[](#working-with-the-model-directly)

```
use Nahid\AINotesPackage\Models\AINote;

// All done notes
AINote::done()->get();

// All pending notes
AINote::pending()->get();

// Notes for a specific user
AINote::where('user_id', $userId)->done()->latest()->get();

// Retry a failed note
$note = AINote::find($id);
$note->update(['status' => 'pending']);
\Nahid\AINotesPackage\Jobs\ProcessNote::dispatch($note);
```

---

Complete API Reference
----------------------

[](#complete-api-reference)

### `AINote::fromAudio(UploadedFile $file, ?int $userId = null): AINote`

[](#ainotefromaudiouploadedfile-file-int-userid--null-ainote)

Upload an audio file. Queues transcription → summarization → embedding. Returns the note immediately with `status = "pending"`.

### `AINote::fromText(string $text, ?int $userId = null, ?string $title = null): AINote`

[](#ainotefromtextstring-text-int-userid--null-string-title--null-ainote)

Store a text note. Queues summarization → embedding. Returns the note immediately with `status = "pending"`.

### `AINote::search(string $query, int $limit = 5, ?int $userId = null): Collection`

[](#ainotesearchstring-query-int-limit--5-int-userid--null-collection)

Semantic search across all `done` notes. Returns an Eloquent Collection sorted by relevance. Each result includes a `distance` attribute.

### `AINote::ask(string $question, int $contextLimit = 3, ?int $userId = null): string`

[](#ainoteaskstring-question-int-contextlimit--3-int-userid--null-string)

RAG-powered question answering. Finds the most relevant notes, builds context, and asks the AI to answer your question based on them. Returns a plain string answer.

---

Configuration
-------------

[](#configuration)

Publish and edit `config/ai-notes.php`:

```
return [
    // 'ollama' (free/local) or 'openai' (paid)
    'driver' => env('AI_DRIVER', 'ollama'),

    // Set false to process synchronously (useful in tests)
    'queue' => env('AI_NOTES_QUEUE', true),

    'ollama' => [
        'base_url'    => env('OLLAMA_BASE_URL', 'http://localhost:11434'),
        'chat_model'  => env('OLLAMA_CHAT_MODEL', 'llama3.2'),
        'embed_model' => env('OLLAMA_EMBED_MODEL', 'nomic-embed-text'),
    ],

    'whisper' => [
        'base_url' => env('WHISPER_BASE_URL', 'http://localhost:9000'),
    ],

    'openai' => [
        'api_key'     => env('OPENAI_API_KEY'),
        'chat_model'  => env('OPENAI_CHAT_MODEL', 'gpt-4o-mini'),
        'embed_model' => env('OPENAI_EMBED_MODEL', 'text-embedding-3-small'),
    ],

    'database' => [
        // 768 for Ollama nomic-embed-text
        // 1536 for OpenAI text-embedding-3-small
        'vector_dimension' => env('AI_NOTES_VECTOR_DIM', 768),
    ],
];
```

---

Docker Setup
------------

[](#docker-setup)

Full `docker-compose.yml` for a new project using this package:

```
version: '3.9'

services:
  app:
    build:
      context: .
      dockerfile: docker/php/Dockerfile
    volumes:
      - .:/var/www
    networks:
      - app_network
    depends_on:
      - postgres
      - redis
      - ollama

  nginx:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - .:/var/www
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
    networks:
      - app_network

  postgres:
    image: pgvector/pgvector:pg16   # bind(AIProvider::class, fn() => FakeAIProvider::make());
config(['ai-notes.queue' => false]); // process synchronously

// Now AINote works without Ollama or Whisper running
$note = AINote::fromText("Test note content");
expect($note->status)->toBe('done');
```

**Customise fake responses:**

```
$fake = FakeAIProvider::make()
    ->withTranscription("Custom transcription text")
    ->withSummary("Custom summary");

app()->bind(AIProvider::class, fn() => $fake);
```

---

Custom AI Driver
----------------

[](#custom-ai-driver)

Implement the `AIProvider` contract to use any AI provider:

```
use Nahid\AINotesPackage\Contracts\AIProvider;

class MistralProvider implements AIProvider
{
    public function transcribe(string $audioPath): string
    {
        // your transcription logic
    }

    public function summarize(string $text): string
    {
        // your summarization logic
    }

    public function embed(string $text): array
    {
        // your embedding logic — must return float[]
    }
}
```

Register it in a service provider:

```
use Nahid\AINotesPackage\Contracts\AIProvider;

$this->app->bind(AIProvider::class, fn() => new MistralProvider());
```

---

Troubleshooting
---------------

[](#troubleshooting)

**`SQLSTATE: type "vector" does not exist`**
You're using plain PostgreSQL without pgvector. Use the `pgvector/pgvector:pg16` Docker image instead of `postgres:16`.

**Notes stuck on `pending` status**
Your queue worker isn't running. Start it:

```
php artisan queue:work
# or in Docker:
docker exec -it your_queue_container php artisan queue:work
```

**Ollama returns empty embeddings**
The model isn't pulled yet:

```
docker exec ai_notes_ollama ollama pull nomic-embed-text
```

**Whisper is slow on first request**
Normal — it loads the model into memory on first call (~10-15 seconds). Subsequent calls are fast.

**`distance` is always high (&gt;0.8) for all results**
Your notes were embedded with a different model than your search query. If you switched from Ollama to OpenAI (or vice versa), re-process your notes:

```
// Reset all notes to re-embed them
Nahid\AINotesPackage\Models\AINote::query()->update(['status' => 'pending', 'embedding' => null]);
// Then reprocess each one via the job
```

**Notes have `status = failed`**
Check `storage/logs/laravel.log` for the error. Common causes: Ollama not running, Whisper unreachable, or audio file format not supported by Whisper.

---

Roadmap
-------

[](#roadmap)

- Livewire/Vue UI component
- Auto-tagging via AI
- Vector DB drivers (Pinecone, Weaviate, Qdrant)
- Realtime recording via browser MediaRecorder API
- Team/multi-tenant support
- Note export (PDF, Markdown)
- Scheduled note digests ("summarize my week")

---

Contributing
------------

[](#contributing)

Contributions are welcome. Please:

1. Fork the repo
2. Create a feature branch: `git checkout -b feature/my-feature`
3. Write tests for your changes
4. Submit a PR against `main`

---

License
-------

[](#license)

The MIT License. See [LICENSE](LICENSE) for details.

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance87

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

65d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/78085363?v=4)[Nahid Hasan](/maintainers/nahidprince7)[@nahidprince7](https://github.com/nahidprince7)

---

Top Contributors

[![nahidprince7](https://avatars.githubusercontent.com/u/78085363?v=4)](https://github.com/nahidprince7 "nahidprince7 (8 commits)")

---

Tags

laravel-frameworkollamaopen-sourceopenaipgvectorscalesemantic-searchwhisper-ai

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/nahid-hasan-ai-notes/health.svg)

```
[![Health](https://phpackages.com/badges/nahid-hasan-ai-notes/health.svg)](https://phpackages.com/packages/nahid-hasan-ai-notes)
```

###  Alternatives

[backpack/crud

Quickly build admin interfaces using Laravel, Bootstrap and JavaScript.

3.4k3.6M217](/packages/backpack-crud)[grumpydictator/firefly-iii

Firefly III: a personal finances manager.

23.6k69.4k](/packages/grumpydictator-firefly-iii)[unopim/unopim

UnoPim Laravel PIM

10.1k2.2k](/packages/unopim-unopim)[backpack/basset

Dead-simple way to load CSS or JS assets only once per page, when using Laravel 10+.

206923.1k10](/packages/backpack-basset)[firefly-iii/data-importer

Firefly III Data Import Tool.

7905.8k](/packages/firefly-iii-data-importer)[nickurt/laravel-akismet

Akismet for Laravel 11.x/12.x/13.x

98145.2k3](/packages/nickurt-laravel-akismet)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
