AI Search Plugin

AI-powered semantic search that understands natural language queries and finds relevant content based on meaning, not just keywords.


Overview

The AI Search plugin transforms how users find content in your SonicJS application. Instead of relying solely on keyword matching, it uses vector embeddings and semantic search to understand the intent behind queries and return the most relevant results.

Built on Cloudflare's edge infrastructure, it delivers fast, intelligent search experiences without sending data to external services.

🧠

Semantic Understanding

Understands natural language queries and finds content by meaning

Edge-Powered

Runs on Cloudflare Workers AI and Vectorize for low latency

🔍

Hybrid Search

Combines AI search with keyword fallback for best results

📊

Analytics

Track search patterns, popular queries, and usage metrics


Features

Semantic Search

  • Natural language query understanding
  • Context-aware results based on content meaning
  • Relevance scoring for better ranking
  • Works across all content types

Keyword Search

  • Traditional text matching as fallback
  • Full-text search across titles, slugs, and content
  • Supports filtering by collection, date, status, and author

Smart Autocomplete

  • AI-powered search suggestions
  • History-based suggestions as fallback
  • Configurable suggestion count

Content Indexing

  • Automatic collection discovery
  • Selective collection indexing
  • Real-time index updates
  • Index status monitoring

Setup

Prerequisites

The AI Search plugin requires Cloudflare Workers AI and Vectorize bindings:

wrangler.toml

# Add AI binding for embeddings
[ai]
binding = "AI"

# Add Vectorize index for vector search
[[vectorize]]
binding = "VECTORIZE_INDEX"
index_name = "sonicjs-content"

Create Vectorize Index

Create a Vectorize index in your Cloudflare account:

Create Index

# Create a new Vectorize index
npx wrangler vectorize create sonicjs-content --dimensions=768 --metric=cosine

Enable the Plugin

The AI Search plugin is included in SonicJS core. Navigate to the admin panel to configure it:

  1. Go to Admin > Plugins > AI Search
  2. Enable AI mode for semantic search
  3. Select collections to index
  4. Save settings

Configuration

Plugin Settings

interface AISearchSettings {
  enabled: boolean              // Enable/disable search functionality
  ai_mode_enabled: boolean      // Enable semantic AI search
  selected_collections: string[] // Collection IDs to index
  dismissed_collections: string[] // Collections user chose not to index
  autocomplete_enabled: boolean // Enable search suggestions
  cache_duration: number        // Cache duration in hours
  results_limit: number         // Max results per query (default: 20)
  index_media: boolean          // Index media file metadata
}

Default Settings

SettingDefaultDescription
enabledtrueSearch functionality active
ai_mode_enabledtrueUse AI for semantic search
autocomplete_enabledtrueShow search suggestions
cache_duration1Cache results for 1 hour
results_limit20Return up to 20 results
index_mediafalseDon't index media by default

API Reference

POST /api/search

Execute a search query with optional filters.

Search Request

curl -X POST http://localhost:8787/api/search \
  -H "Content-Type: application/json" \
  -d '{
    "query": "blog posts about security best practices",
    "mode": "ai",
    "filters": {
      "collections": ["1", "2"],
      "status": ["published"]
    },
    "limit": 10
  }'

Request Parameters

ParameterTypeRequiredDescription
querystringYesSearch query text
mode'ai' | 'keyword'NoSearch mode (default: 'keyword')
filtersobjectNoFilter options
limitnumberNoMax results (default: 20)
offsetnumberNoPagination offset

Filter Options

Filter Types

interface SearchFilters {
  collections?: string[]       // Filter by collection IDs
  dateRange?: {
    start: Date               // Start date
    end: Date                 // End date
    field?: 'created_at' | 'updated_at'
  }
  status?: string[]           // Filter by status (draft, published, etc.)
  tags?: string[]             // Filter by tags
  author?: string             // Filter by author ID
  custom?: Record<string, any> // Custom metadata filters
}

GET /api/search/suggest

Get autocomplete suggestions for partial queries.

Suggestions Request

curl "http://localhost:8787/api/search/suggest?q=sec"

GET /api/search/analytics

Get search usage analytics (requires authentication).

Analytics Request

curl http://localhost:8787/api/search/analytics \
  -H "Authorization: Bearer ${token}"

Search Modes

AI Mode (Semantic Search)

When AI mode is enabled and Vectorize is configured, queries are processed using vector embeddings:

  1. Query text is converted to a vector embedding using Workers AI
  2. Vectorize finds similar content vectors
  3. Results are ranked by semantic relevance
  4. Content snippets highlight relevant sections

Best for:

  • Natural language queries ("how do I set up authentication?")
  • Concept-based searches ("articles about performance")
  • Finding related content

Keyword Mode

Traditional text-based search using SQL LIKE queries:

  1. Query is matched against title, slug, and content fields
  2. Results are ordered by update date
  3. Exact and partial matches are found

Best for:

  • Specific term searches ("JWT token")
  • Known titles or slugs
  • When AI bindings aren't available

Automatic Fallback

If AI search fails or isn't available, the system automatically falls back to keyword search:

Fallback Logic

// Automatic fallback in the service
if (query.mode === 'ai' && settings.ai_mode_enabled && this.customRAG?.isAvailable()) {
  return this.searchAI(query, settings)
}
// Fallback to keyword search
return this.searchKeyword(query, settings)

Collection Indexing

Selecting Collections

Not all collections need to be searchable. The admin interface lets you:

  • Index - Include collection in search results
  • Dismiss - Hide collection from indexing prompts
  • New - Unreviewed collections awaiting decision

Index Management

Collection Info

interface CollectionInfo {
  id: string              // Collection ID
  name: string            // Internal name
  display_name: string    // Human-readable name
  description?: string    // Collection description
  item_count?: number     // Number of items
  is_indexed: boolean     // Currently indexed
  is_dismissed: boolean   // User dismissed
  is_new?: boolean        // Awaiting decision
}

New Collection Detection

The plugin automatically detects new collections and prompts you to decide whether to index them:

Detection API

// Detect collections not yet indexed or dismissed
const notifications = await aiSearchService.detectNewCollections()

// Returns array of new collection notifications
// [{
//   collection: { id: '5', name: 'faqs', ... },
//   message: 'New collection "FAQs" with 25 items available for indexing'
// }]

Frontend Integration

Basic Search Form

Search Component

<form id="searchForm">
  <input
    type="text"
    name="query"
    placeholder="Search..."
    autocomplete="off"
  />
  <select name="mode">
    <option value="ai">AI Search</option>
    <option value="keyword">Keyword</option>
  </select>
  <button type="submit">Search</button>
</form>

<div id="results"></div>

<script>
document.getElementById('searchForm').addEventListener('submit', async (e) => {
  e.preventDefault()
  const formData = new FormData(e.target)

  const response = await fetch('/api/search', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      query: formData.get('query'),
      mode: formData.get('mode')
    })
  })

  const { data } = await response.json()

  document.getElementById('results').innerHTML = data.results
    .map(r => `
      <div class="result">
        <h3><a href="/content/${r.slug}">${r.title}</a></h3>
        <p>${r.snippet}</p>
        <small>${r.collection_name} - ${r.relevance_score?.toFixed(2) || 'N/A'}</small>
      </div>
    `)
    .join('')
})
</script>

With Autocomplete

Autocomplete Integration

const searchInput = document.getElementById('searchInput')
const suggestionsDiv = document.getElementById('suggestions')

let debounceTimer

searchInput.addEventListener('input', async (e) => {
  clearTimeout(debounceTimer)

  const query = e.target.value
  if (query.length < 2) {
    suggestionsDiv.innerHTML = ''
    return
  }

  debounceTimer = setTimeout(async () => {
    const response = await fetch(`/api/search/suggest?q=${encodeURIComponent(query)}`)
    const { data } = await response.json()

    suggestionsDiv.innerHTML = data
      .map(s => `<div class="suggestion" data-value="${s}">${s}</div>`)
      .join('')
  }, 300)
})

suggestionsDiv.addEventListener('click', (e) => {
  if (e.target.classList.contains('suggestion')) {
    searchInput.value = e.target.dataset.value
    suggestionsDiv.innerHTML = ''
    // Trigger search
  }
})

React Example

React Hook

import { useState, useCallback } from 'react'

interface SearchResult {
  id: string
  title: string
  slug: string
  snippet?: string
  relevance_score?: number
}

export function useAISearch() {
  const [results, setResults] = useState<SearchResult[]>([])
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)

  const search = useCallback(async (query: string, mode: 'ai' | 'keyword' = 'ai') => {
    setLoading(true)
    setError(null)

    try {
      const response = await fetch('/api/search', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ query, mode })
      })

      const { data, success, error } = await response.json()

      if (!success) throw new Error(error)

      setResults(data.results)
      return data
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Search failed')
      return null
    } finally {
      setLoading(false)
    }
  }, [])

  return { results, loading, error, search }
}

Analytics

The plugin tracks search patterns to help you understand user behavior:

Tracked Metrics

  • Total queries - Number of searches in the last 30 days
  • AI vs Keyword - Breakdown by search mode
  • Popular queries - Most common search terms
  • Query timing - Average search duration

Using Analytics Data

Analytics Integration

// Fetch analytics data
const analytics = await aiSearchService.getSearchAnalytics()

// Use for dashboard
console.log(`Total searches: ${analytics.total_queries}`)
console.log(`AI usage: ${(analytics.ai_queries / analytics.total_queries * 100).toFixed(1)}%`)

// Top searches
analytics.popular_queries.forEach(({ query, count }) => {
  console.log(`"${query}" - ${count} searches`)
})

Admin Interface

Settings Page

Navigate to Admin > Plugins > AI Search to access:

  • Enable/Disable - Toggle search functionality
  • AI Mode - Enable semantic search (requires Vectorize)
  • Collection Management - Select which collections to index
  • Autocomplete - Toggle search suggestions
  • Cache Settings - Configure result caching

Collection Manager

The collection manager shows:

  • All available collections
  • Indexing status for each
  • Item counts
  • Actions to index or dismiss

Search Testing

Test search directly from the admin interface:

  1. Enter a test query
  2. Select search mode (AI or Keyword)
  3. View results with relevance scores
  4. Check query timing

Troubleshooting

AI Search Not Working

Check Vectorize binding:

  • Ensure VECTORIZE_INDEX is configured in wrangler.toml
  • Verify the index was created with correct dimensions (768)

Check AI binding:

  • Ensure AI binding is configured
  • Verify Workers AI is available in your plan

No Results Returned

Check collection indexing:

  • Ensure collections are selected for indexing
  • Verify content exists in indexed collections

Check filters:

  • Ensure status filter includes your content status
  • Verify date range includes your content

Slow Search Performance

Enable caching:

  • Set cache_duration to cache frequent queries

Reduce index size:

  • Only index collections that need search
  • Disable media indexing if not needed

Next Steps

Was this page helpful?