diff --git a/.env.example b/.env.example index 274aae3..9858632 100644 --- a/.env.example +++ b/.env.example @@ -22,7 +22,7 @@ LOG_LEVEL=debug DB_CONNECTION=pgsql DB_HOST=127.0.0.1 -DB_PORT=3306 +DB_PORT=5432 DB_DATABASE=laravel DB_USERNAME=root DB_PASSWORD= diff --git a/README.md b/README.md new file mode 100644 index 0000000..c1f5813 --- /dev/null +++ b/README.md @@ -0,0 +1,125 @@ +# Embedd HUB + +Embedd HUB is a local-first Laravel workbench for exploring text embeddings. It lets users generate vectors, search similar stored phrases, and compare phrase proximity with Ollama and PostgreSQL vector storage. + +## Features + +- Generate embeddings for words, phrases, or multiline text. +- Store vectors in PostgreSQL with vector indexes when using pgvector. +- Search existing embeddings by semantic similarity. +- Compare multiple phrases with a pairwise similarity matrix and ranked pairs. +- Pick embedding models from the live Ollama `/api/tags` response. +- Show clear Ollama online/offline status before running embedding actions. +- Track per-user usage on the dashboard. + +## Stack + +- Laravel 13 +- Livewire 4 single-file components +- Flux UI 2 +- Laravel AI SDK +- PostgreSQL with vector storage +- Ollama for local embedding generation +- Tailwind CSS 4 and Vite +- Pest 4 + +## Requirements + +- PHP 8.4 +- Composer +- Node.js and npm +- PostgreSQL 18 or another PostgreSQL version with vector extension support +- Ollama running locally or on a reachable host + +## Setup + +Install PHP and JavaScript dependencies: + +```bash +composer install +npm install +``` + +Create the environment file and app key: + +```bash +cp .env.example .env +php artisan key:generate +``` + +Configure PostgreSQL in `.env`: + +```dotenv +DB_CONNECTION=pgsql +DB_HOST=127.0.0.1 +DB_PORT=5432 +DB_DATABASE=embedd_hub +DB_USERNAME=postgres +DB_PASSWORD= +``` + +Configure Ollama: + +```dotenv +OLLAMA_URL=http://localhost:11434 +OLLAMA_EMBEDDINGS_DIMENSIONS=768 +``` + +The dimensions must match the embedding model you plan to use. Set this before running migrations so the vector column is created with the correct size. + +Run migrations and build assets: + +```bash +php artisan migrate +npm run build +``` + +## Ollama + +Start Ollama and pull at least one embedding-capable model: + +```bash +ollama serve +ollama pull nomic-embed-text +``` + +Embedd HUB does not use a configured default model. The tool pages call the Ollama API and list the models returned from `/api/tags`. If Ollama cannot return that model list, embedding actions are disabled and the UI shows an offline status. + +## Development + +For Laravel Herd, the app is available at: + +```text +https://embedd-hub.test +``` + +Common commands: + +```bash +npm run dev +php artisan test --compact +vendor/bin/pint --dirty --format agent +``` + +## Project Map + +- `app/Services/EmbeddingWorkbench.php` - embedding generation, search, comparison, and Ollama model discovery. +- `app/Concerns/InteractsWithEmbeddingWorkbench.php` - shared Livewire state for model selection and availability. +- `resources/views/pages/embeddings` - the Embed, Search, and Compare pages. +- `resources/views/dashboard.blade.php` - usage dashboard. +- `database/migrations/*embedding*` - vector storage and usage event tables. +- `public/favicon.svg` - source SVG favicon asset. It is intentionally not referenced in templates. + +## Testing + +Run the full suite: + +```bash +php artisan test --compact +``` + +Run only embedding tool tests: + +```bash +php artisan test --compact tests/Feature/EmbeddingToolsTest.php +``` diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png index c2efef6..6a41216 100644 Binary files a/public/apple-touch-icon.png and b/public/apple-touch-icon.png differ diff --git a/public/favicon.ico b/public/favicon.ico index 236fadb..0e4dbff 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/favicon.svg b/public/favicon.svg index e4e710e..d499f3e 100644 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -1,3 +1,8 @@ - - + + + + + + + diff --git a/resources/views/partials/head.blade.php b/resources/views/partials/head.blade.php index 8e9a491..aa6238b 100644 --- a/resources/views/partials/head.blade.php +++ b/resources/views/partials/head.blade.php @@ -5,10 +5,6 @@ {{ filled($title ?? null) ? $title.' - '.config('app.name', 'Laravel') : config('app.name', 'Laravel') }} - - {{ __('Welcome') }} - {{ config('app.name', 'Laravel') }} - - - - + {{ config('app.name', 'Embedd HUB') }} - Local embedding workbench @fonts - - - + @vite(['resources/css/app.css', 'resources/js/app.js']) - -
- @if (Route::has('login')) -
+ +
+
+
+

+ {{ __('Ollama, pgvector, and Livewire in one local workflow') }} +

+ +

+ {{ config('app.name', 'Embedd HUB') }} +

+ +

+ {{ __('Generate embeddings, search stored phrases, and compare semantic proximity with a local Ollama provider and PostgreSQL vector storage.') }} +

+ +
+ @auth + + {{ __('Open embedding tools') }} - - -
  • - - - - - - - Watch video tutorials at - - Laracasts - - - - - -
  • - - -
    -
    - {{-- Laravel Logo --}} - - - - - - - - - + @else + @if (Route::has('register')) + + {{ __('Start exploring') }} + + @endif - {{-- 13 --}} - - - - - - - - - - - - + @if (Route::has('login')) + + {{ __('Sign in') }} + + @endif + @endauth +
    - - - - - - - - - - - +
    +
    +
    {{ __('Tools') }}
    +
    3
    +
    +
    +
    {{ __('Provider') }}
    +
    {{ __('Local') }}
    +
    +
    +
    {{ __('Storage') }}
    +
    {{ __('Vector') }}
    +
    +
    +
    - - - - - - - - - - - +
    +
    +
    + + + +
    +
    + {{ __('Ollama online') }} +
    +
    - - - - - - - - - - - +
    +
    + + +
    +
    + {{ __('Embed') }} + {{ __('Search') }} + {{ __('Compare') }} +
    + +
    +

    {{ __('Input phrases') }}

    +
    +
    {{ __('fast semantic search') }}
    +
    {{ __('local vector database') }}
    +
    {{ __('embedding comparison') }}
    +
    +
    + +
    +
    + {{ __('Phrase') }} + {{ __('Similarity') }} + {{ __('Distance') }} +
    +
    +
    + {{ __('fast semantic search') }} + 0.982 + 0.018 +
    +
    + {{ __('embedding comparison') }} + 0.741 + 0.259 +
    +
    + {{ __('local vector database') }} + 0.604 + 0.396 +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +

    {{ __('Generate') }}

    +

    {{ __('Inspect raw embeddings') }}

    +

    {{ __('Paste words or phrases, generate vectors, and inspect previews before storing them.') }}

    +
    + +
    +

    {{ __('Search') }}

    +

    {{ __('Find similar stored text') }}

    +

    {{ __('Query your saved embeddings and rank matching phrases by semantic similarity.') }}

    +
    + +
    +

    {{ __('Compare') }}

    +

    {{ __('Read pairwise proximity') }}

    +

    {{ __('Enter multiple phrases and review clear phrase-to-phrase scores in a matrix and sorted pair list.') }}

    +
    +
    +
    - - @if (Route::has('login')) - - @endif diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php index a576279..1a34a3b 100644 --- a/tests/Feature/ExampleTest.php +++ b/tests/Feature/ExampleTest.php @@ -3,5 +3,11 @@ test('returns a successful response', function () { $response = $this->get(route('home')); - $response->assertOk(); -}); \ No newline at end of file + $response + ->assertOk() + ->assertSee('Embedd HUB') + ->assertSee('Local embedding workbench') + ->assertSee('Ollama') + ->assertDontSee('favicon.svg') + ->assertDontSee('favicon.ico'); +});