Skip to content

cloudflare/ai-search-snippet

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

74 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

๐Ÿ” Search Snippet Library

A production-ready, self-contained TypeScript Web Component library providing both search and chat interfaces with streaming support. Zero dependencies, fully customizable, and framework-agnostic.

โœจ Features

  • ๐ŸŽจ Fully Customizable - 20+ CSS variables for complete theme control
  • โšก Zero Dependencies - Self-contained with everything bundled
  • ๐Ÿ“ฑ Responsive Design - Works seamlessly on all devices
  • ๐ŸŽญ Framework Agnostic - Native Web Components work everywhere
  • โ™ฟ Accessible - WCAG 2.1 AA compliant with ARIA support
  • ๐Ÿš€ Streaming Support - Real-time streaming responses with low latency
  • ๐ŸŒ“ Dark Mode - Automatic theme switching based on system preferences
  • ๐Ÿ“ฆ Tiny Bundle - Optimized for minimal size (< 50KB gzipped)
  • ๐Ÿ”’ Secure - XSS protection with HTML sanitization
  • โš™๏ธ TypeScript - Full type definitions included

๐Ÿš€ Quick Start

Note: This library requires an active AI Search endpoint in your Cloudflare Dashboard. Make sure to enable and configure your AI Search service before using these components.

Installation

npm install @cloudflare/ai-search-snippet
# or
yarn add @cloudflare/ai-search-snippet

Basic Usage

Note: Replace <hash> with your Cloudflare AI Search endpoint hash (you can find it in the Cloudflare Dashboard).

<!-- Import the library -->
<script
  type="module"
  src="https:/<hash>/search.ai.cloudflare.com/search-snippet.es.js"
></script>

<!-- Search bar with results -->
<search-bar-snippet
  api-url="https:/<hash>/search.ai.cloudflare.com/"
  placeholder="Search..."
  max-results="10"
  show-url="true"
  show-date="true"
>
</search-bar-snippet>

<!-- Modal search (opens with Cmd/Ctrl+K) -->
<search-modal-snippet
  api-url="https:/<hash>/search.ai.cloudflare.com/"
  placeholder="Search documentation..."
  max-results="10"
  show-url="true"
  show-date="true"
>
</search-modal-snippet>

<!-- Floating chat bubble -->
<chat-bubble-snippet
  api-url="https:/<hash>/search.ai.cloudflare.com/"
  placeholder="Type a message..."
>
</chat-bubble-snippet>

<!-- Full-page chat with history -->
<chat-page-snippet
  api-url="https:/<hash>/search.ai.cloudflare.com/"
  placeholder="Type a message..."
>
</chat-page-snippet>

๐Ÿ“– API Reference

Components

The library provides four Web Components:

Component Tag Description
SearchBarSnippet <search-bar-snippet> Search input with results dropdown
SearchModalSnippet <search-modal-snippet> Modal search with Cmd/Ctrl+K shortcut
ChatBubbleSnippet <chat-bubble-snippet> Floating chat bubble overlay
ChatPageSnippet <chat-page-snippet> Full-page chat with session history

Common Attributes

These attributes are available on all components:

Attribute Type Default Description
api-url string http://localhost:3000 API endpoint URL
placeholder string Component-specific Input placeholder text
theme 'light' | 'dark' | 'auto' 'auto' Color scheme
hide-branding boolean false Hide the "Powered by" branding

Search Components Attributes

Additional attributes for <search-bar-snippet> and <search-modal-snippet>:

Attribute Type Default Description
max-results number 10 Maximum search results to display
debounce-ms number 300 Input debounce delay in milliseconds
show-url boolean false Show URL in search results
show-date boolean false Show result dates when a timestamp is available

Modal-Specific Attributes

Additional attributes for <search-modal-snippet>:

Attribute Type Default Description
shortcut string 'k' Keyboard shortcut key (with Cmd/Ctrl)
use-meta-key boolean true Use meta key (Cmd on Mac, Ctrl on Windows)

JavaScript API

Search Bar (<search-bar-snippet>)

const searchBar = document.querySelector("search-bar-snippet");

// Perform a search programmatically
searchBar.search("query");

Search Modal (<search-modal-snippet>)

const modal = document.querySelector("search-modal-snippet");

modal.open(); // Open the modal
modal.close(); // Close the modal
modal.toggle(); // Toggle open/closed
modal.search("query"); // Open and search
const results = modal.getResults(); // Get current results
const isOpen = modal.isModalOpen(); // Check if open

Chat Bubble (<chat-bubble-snippet>)

const chatBubble = document.querySelector("chat-bubble-snippet");

await chatBubble.sendMessage("Hello!"); // Send a message
const messages = chatBubble.getMessages(); // Get message history
chatBubble.clearChat(); // Clear chat history

Chat Page (<chat-page-snippet>)

const chatPage = document.querySelector("chat-page-snippet");

await chatPage.sendMessage("Hello!"); // Send a message
const messages = chatPage.getMessages(); // Get message history
chatPage.clearChat(); // Clear current chat
const sessions = chatPage.getSessions(); // Get all chat sessions
const current = chatPage.getCurrentSession(); // Get current session

Events

Common Events (all components)

const component = document.querySelector("search-bar-snippet");

component.addEventListener("ready", () => {
  console.log("Component ready");
});

component.addEventListener("error", (e) => {
  console.error("Error:", e.detail.error);
});

Modal-Specific Events

const modal = document.querySelector("search-modal-snippet");

modal.addEventListener("open", () => {
  console.log("Modal opened");
});

modal.addEventListener("close", () => {
  console.log("Modal closed");
});

modal.addEventListener("result-select", (e) => {
  console.log("Selected result:", e.detail.result);
});

Chat Events

const chat = document.querySelector("chat-bubble-snippet");

chat.addEventListener("message", (e) => {
  console.log("New message:", e.detail.message);
});

๐ŸŽจ Customization

CSS Variables

Customize the appearance using CSS variables:

search-bar-snippet,
search-modal-snippet,
chat-bubble-snippet,
chat-page-snippet {
  /* ========== COLORS ========== */
  /* Primary */
  --search-snippet-primary-color: #2563eb;
  --search-snippet-primary-hover: #0f51dfff;

  /* Background & Surface */
  --search-snippet-background: #ffffff;
  --search-snippet-surface: #f8f9fa;
  --search-snippet-hover-background: #f1f3f5;

  /* Text */
  --search-snippet-text-color: #212529;
  --search-snippet-text-secondary: #6c757d;

  /* Border & Focus */
  --search-snippet-border-color: #dee2e6;
  --search-snippet-focus-ring: #0066cc40;

  /* Status Colors */
  --search-snippet-error-color: #dc3545;
  --search-snippet-error-background: #f8d7da;
  --search-snippet-success-color: #28a745;
  --search-snippet-success-background: #d4edda;
  --search-snippet-warning-color: #ffc107;
  --search-snippet-warning-background: #fff3cd;

  /* Message Colors */
  --search-snippet-user-message-bg: #0066cc;
  --search-snippet-user-message-text: #ffffff;
  --search-snippet-assistant-message-bg: #f1f3f5;
  --search-snippet-assistant-message-text: #212529;
  --search-snippet-system-message-bg: #fff3cd;
  --search-snippet-system-message-text: #856404;

  /* ========== TYPOGRAPHY ========== */
  --search-snippet-font-family:
    -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue",
    Arial, sans-serif;
  --search-snippet-font-family-mono:
    "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
  --search-snippet-font-size-base: 14px;
  --search-snippet-font-size-sm: 12px;
  --search-snippet-font-size-lg: 16px;
  --search-snippet-font-size-xl: 18px;
  --search-snippet-line-height: 1.5;
  --search-snippet-font-weight-normal: 400;
  --search-snippet-font-weight-medium: 500;
  --search-snippet-font-weight-bold: 600;

  /* ========== SPACING ========== */
  --search-snippet-spacing-xs: 4px;
  --search-snippet-spacing-sm: 8px;
  --search-snippet-spacing-md: 12px;
  --search-snippet-spacing-lg: 16px;
  --search-snippet-spacing-xl: 24px;
  --search-snippet-spacing-xxl: 32px;

  /* ========== SIZING ========== */
  --search-snippet-width: 100%;
  --search-snippet-max-width: 100%;
  --search-snippet-min-width: 320px;
  --search-snippet-max-height: 600px;
  --search-snippet-input-height: 44px;
  --search-snippet-button-height: 36px;
  --search-snippet-icon-size: 20px;
  --search-snippet-icon-margin-left: 6px;

  /* ========== BORDER ========== */
  --search-snippet-border-width: 1px;
  --search-snippet-border-radius: 18px;

  /* ========== SHADOWS ========== */
  --search-snippet-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  --search-snippet-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  --search-snippet-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.15);
  --search-snippet-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.2);
  --search-snippet-shadow-inner: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06);
  --search-snippet-result-item-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);

  /* ========== ANIMATION ========== */
  --search-snippet-transition-fast: 150ms ease;
  --search-snippet-transition: 200ms ease;
  --search-snippet-transition-slow: 300ms ease;
  --search-snippet-animation-duration: 0.2s;

  /* ========== Z-INDEX ========== */
  --search-snippet-z-dropdown: 1000;
  --search-snippet-z-modal: 1050;
  --search-snippet-z-popover: 1060;
  --search-snippet-z-tooltip: 1070;

  /* ========== CHAT BUBBLE SPECIFIC ========== */
  --chat-bubble-button-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  --chat-bubble-window-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  --chat-bubble-button-size: 60px;
  --chat-bubble-button-radius: 50%;
  --chat-bubble-button-icon-size: 28px;
  --chat-bubble-button-icon-color: white;
  --chat-bubble-button-bottom: 20px;
  --chat-bubble-button-right: 20px;
  --chat-bubble-button-z-index: 9999;
  --chat-bubble-position: fixed;
}

Theme Examples

Dark Theme:

search-bar-snippet {
  --search-snippet-primary-color: #4dabf7;
  --search-snippet-background: #1a1b1e;
  --search-snippet-text-color: #c1c2c5;
  --search-snippet-border-color: #373a40;
}

Custom Brand:

chat-bubble-snippet {
  --search-snippet-primary-color: #667eea;
  --search-snippet-primary-hover: #5568d3;
  --search-snippet-border-radius: 12px;
  --search-snippet-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
}

๐Ÿ”ง Advanced Usage

TypeScript

import {
  SearchBarSnippet,
  SearchModalSnippet,
  ChatBubbleSnippet,
  ChatPageSnippet,
  type SearchSnippetProps,
} from "@cloudflare/ai-search-snippet";

// Type-safe usage
const searchBar = document.createElement(
  "search-bar-snippet",
) as SearchBarSnippet;
searchBar.setAttribute("api-url", "https://api.example.com");
searchBar.setAttribute("max-results", "10");

const chatBubble = document.createElement(
  "chat-bubble-snippet",
) as ChatBubbleSnippet;
chatBubble.setAttribute("api-url", "https://api.example.com");
await chatBubble.sendMessage("Hello, world!");

React Integration

import { useEffect, useRef } from "react";
import "@cloudflare/ai-search-snippet";

function ChatWidget() {
  const ref = useRef<HTMLElement>(null);

  useEffect(() => {
    const chat = ref.current;

    const handleMessage = (e: CustomEvent) => {
      console.log("Message:", e.detail);
    };

    chat?.addEventListener("message", handleMessage as EventListener);

    return () => {
      chat?.removeEventListener("message", handleMessage as EventListener);
    };
  }, []);

  return (
    <chat-bubble-snippet
      ref={ref}
      api-url="https://api.example.com"
      placeholder="Ask a question..."
    />
  );
}

Vue Integration

<template>
  <chat-bubble-snippet
    :api-url="apiUrl"
    placeholder="Ask a question..."
    @message="handleMessage"
    @error="handleError"
  />
</template>

<script setup>
import { ref } from "vue";
import "@cloudflare/ai-search-snippet";

const apiUrl = ref("https://api.example.com");

const handleMessage = (event) => {
  console.log("Message:", event.detail.message);
};

const handleError = (event) => {
  console.error("Error:", event.detail.error);
};
</script>

๐Ÿ—๏ธ Development

Build from Source

# Install dependencies
npm install

# Development mode with hot reload
npm run dev

# Build for production
npm run build

# Preview production build
npm run preview

# Lint and format
npm run lint
npm run format

Project Structure

nlweb-cl-snippet/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ api/
โ”‚   โ”‚   โ”œโ”€โ”€ index.ts              # Base Client abstract class
โ”‚   โ”‚   โ””โ”€โ”€ ai-search.ts          # AISearchClient with streaming
โ”‚   โ”œโ”€โ”€ components/
โ”‚   โ”‚   โ”œโ”€โ”€ search-bar-snippet.ts   # Search input with results
โ”‚   โ”‚   โ”œโ”€โ”€ search-modal-snippet.ts # Modal search with Cmd/Ctrl+K
โ”‚   โ”‚   โ”œโ”€โ”€ chat-bubble-snippet.ts  # Floating chat bubble
โ”‚   โ”‚   โ”œโ”€โ”€ chat-page-snippet.ts    # Full-page chat with history
โ”‚   โ”‚   โ””โ”€โ”€ chat-view.ts            # Shared chat interface
โ”‚   โ”œโ”€โ”€ styles/
โ”‚   โ”‚   โ”œโ”€โ”€ theme.ts              # Base styles & CSS variables
โ”‚   โ”‚   โ”œโ”€โ”€ search.ts             # Search-specific styles
โ”‚   โ”‚   โ””โ”€โ”€ chat.ts               # Chat-specific styles
โ”‚   โ”œโ”€โ”€ types/
โ”‚   โ”‚   โ””โ”€โ”€ index.ts              # TypeScript definitions
โ”‚   โ”œโ”€โ”€ utils/
โ”‚   โ”‚   โ””โ”€โ”€ index.ts              # Utility functions
โ”‚   โ””โ”€โ”€ main.ts                   # Entry point
โ”œโ”€โ”€ dist/                          # Build output
โ”œโ”€โ”€ index.html                     # Demo page
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ tsconfig.json
โ”œโ”€โ”€ vite.config.ts
โ””โ”€โ”€ README.md

๐Ÿ“ API Server Requirements

The component expects the API server to implement the following endpoints:

Search Endpoint

POST /search

Request:

{
  "query": "search query",
  "max_results": 10,
  "filters": {}
}

Response:

{
  "results": [
    {
      "id": "result-1",
      "title": "Result Title",
      "snippet": "Result description...",
      "url": "https://example.com",
      "metadata": {}
    }
  ],
  "total": 42
}

Chat Endpoint (Streaming)

POST /ask

Request:

{
  "query": "user message",
  "generate_mode": "summarize",
  "prev": [
    {
      "role": "user",
      "content": "previous message",
      "timestamp": 1234567890
    }
  ]
}

Response: Streaming text chunks via ReadableStream

๐Ÿงช Browser Support

  • Chrome 90+
  • Firefox 88+
  • Safari 14+
  • Edge 90+

๐Ÿ“„ License

MIT License - see LICENSE file for details

๐Ÿค Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

๐Ÿ› Bug Reports

If you discover any bugs, please create an issue on GitHub with:

  • Description of the bug
  • Steps to reproduce
  • Expected behavior
  • Actual behavior
  • Browser and version

๐Ÿ“ฎ Support

For questions and support, please open a GitHub issue.


Built with TypeScript, Web Components, and โค๏ธ

About

AI Search embeddable snippet for Cloudflare AI Search

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Contributors