Claude Code Plugins

Community-maintained marketplace

Feedback

bodhi-sdk-react-integration

@BodhiSearch/bodhi-js
0
0

>

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name bodhi-sdk-react-integration
description Integrate React+Vite web apps with bodhi-js-sdk for local LLM integration. Use when user asks to: "integrate bodhi", "add bodhi sdk", "connect to bodhi", "setup bodhi provider", "bodhi react integration", "deploy bodhi to github pages", or troubleshoot bodhi-js-sdk connection/auth issues.
allowed-tools Read, Grep, Glob, Edit, Write, Bash(npm:*), Bash(npx:*)

Bodhi JS SDK React Integration

Guide for integrating React+Vite applications with bodhi-js-sdk to enable local LLM chat capabilities through the Bodhi Browser ecosystem.

When to Use This Skill

  • User wants to integrate a React app with bodhi-js-sdk
  • User needs to add chat/LLM capabilities to their React+Vite app
  • User is deploying a bodhi-integrated app to GitHub Pages
  • User is troubleshooting SDK connection, authentication, or streaming issues

Quick Integration Checklist

  1. Install: npm install @bodhiapp/bodhi-js-react
  2. Register: Create OAuth client at https://developer.getbodhi.app
  3. Wrap App: Add <BodhiProvider authClientId={...}> around your app
  4. Use Hook: Access useBodhi() for client, auth state, and actions
  5. Build UI: Create chat interface with streaming support

Core Concepts

Package Architecture

  • @bodhiapp/bodhi-js-react - Preset package for web apps (auto-creates WebUIClient)
  • @bodhiapp/bodhi-js-react-ext - Preset package for Chrome extensions (auto-creates ExtUIClient)
  • Both include React bindings + OpenAI-compatible API

Connection Modes

  • Extension mode: Via Bodhi Browser extension (preferred)
  • Direct mode: Direct HTTP to local server (fallback)
  • SDK auto-detects and switches modes automatically

Authentication

  • OAuth 2.0 + PKCE flow
  • Two auth servers:
    • Dev: https://main-id.getbodhi.app/realms/bodhi (allows localhost)
    • Prod: https://id.getbodhi.app/realms/bodhi (requires real domain)

Basic Integration Steps

Step 1: Install Package

npm install @bodhiapp/bodhi-js-react

Step 2: Wrap App with BodhiProvider

// App.tsx
import { BodhiProvider } from '@bodhiapp/bodhi-js-react';
import Chat from './Chat';

const CLIENT_ID = 'your-client-id-from-developer.getbodhi.app';

function App() {
  return (
    <BodhiProvider authClientId={CLIENT_ID}>
      <div className="app">
        <h1>My Bodhi Chat App</h1>
        <Chat />
      </div>
    </BodhiProvider>
  );
}

export default App;

Step 3: Create Chat Component

// Chat.tsx
import { useState, useEffect } from 'react';
import { useBodhi } from '@bodhiapp/bodhi-js-react';

function Chat() {
  const { client, isOverallReady, isAuthenticated, login, showSetup } = useBodhi();
  const [prompt, setPrompt] = useState('');
  const [response, setResponse] = useState('');
  const [loading, setLoading] = useState(false);
  const [models, setModels] = useState<string[]>([]);
  const [selectedModel, setSelectedModel] = useState('');

  // Load models on mount
  useEffect(() => {
    if (isOverallReady && isAuthenticated) {
      loadModels();
    }
  }, [isOverallReady, isAuthenticated]);

  const loadModels = async () => {
    const modelList: string[] = [];
    for await (const model of client.models.list()) {
      modelList.push(model.id);
    }
    setModels(modelList);
    if (modelList.length > 0) setSelectedModel(modelList[0]);
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!prompt.trim() || !selectedModel) return;

    setLoading(true);
    setResponse('');

    try {
      const stream = client.chat.completions.create({
        model: selectedModel,
        messages: [{ role: 'user', content: prompt }],
        stream: true,
      });

      for await (const chunk of stream) {
        const content = chunk.choices?.[0]?.delta?.content || '';
        setResponse(prev => prev + content);
      }
    } catch (err) {
      setResponse(`Error: ${err instanceof Error ? err.message : String(err)}`);
    } finally {
      setLoading(false);
    }
  };

  if (!isOverallReady) {
    return <button onClick={showSetup}>Open Setup</button>;
  }

  if (!isAuthenticated) {
    return <button onClick={login}>Login</button>;
  }

  return (
    <div>
      <select value={selectedModel} onChange={e => setSelectedModel(e.target.value)}>
        {models.map(model => (
          <option key={model} value={model}>
            {model}
          </option>
        ))}
      </select>
      <form onSubmit={handleSubmit}>
        <input value={prompt} onChange={e => setPrompt(e.target.value)} />
        <button type="submit" disabled={loading}>
          {loading ? 'Generating...' : 'Send'}
        </button>
      </form>
      {response && <div>{response}</div>}
    </div>
  );
}

export default Chat;

Key APIs and Hooks

useBodhi() Hook

const {
  client, // SDK client instance (OpenAI-compatible API)
  isOverallReady, // Both client AND server ready (most common check)
  isAuthenticated, // User has valid OAuth token
  login, // Initiate OAuth login flow
  logout, // Logout and clear tokens
  showSetup, // Open setup wizard modal

  // Additional properties
  isReady, // Client initialized (extension or direct URL)
  isServerReady, // Server status is 'ready'
  isInitializing, // client.init() in progress
  isExtension, // Using extension mode
  isDirect, // Using direct HTTP mode
  canLogin, // isReady && !isAuthLoading
  isAuthLoading, // Auth operation in progress
} = useBodhi();

Client Methods (OpenAI-Compatible)

// List models (AsyncGenerator)
for await (const model of client.models.list()) {
  console.log(model.id);
}

// Streaming chat
const stream = client.chat.completions.create({
  model: 'gemma-3n-e4b-it',
  messages: [{ role: 'user', content: 'Hello!' }],
  stream: true,
});

for await (const chunk of stream) {
  const content = chunk.choices?.[0]?.delta?.content || '';
  // Append to response
}

// Non-streaming chat
const response = await client.chat.completions.create({
  model: 'gemma-3n-e4b-it',
  messages: [{ role: 'user', content: 'Hello!' }],
  stream: false,
});

Advanced Configuration

Custom Client Config

<BodhiProvider
  authClientId={CLIENT_ID}
  clientConfig={{
    redirectUri: 'https://myapp.com/callback',
    basePath: '/app',
    logLevel: 'debug',
  }}
>
  <App />
</BodhiProvider>

basePath for Sub-paths or GitHub Pages

When your app runs on a sub-path (e.g., GitHub Pages at /repo-name/):

// Vite config
export default defineConfig({
  base: '/repo-name/',
});

// BodhiProvider
<BodhiProvider authClientId={CLIENT_ID} basePath="/repo-name" callbackPath="/repo-name/callback">
  <App />
</BodhiProvider>;

Common Patterns

Conditional Rendering

function App() {
  const { isOverallReady, isAuthenticated, showSetup, login } = useBodhi();

  if (!isOverallReady) {
    return <button onClick={showSetup}>Setup Required</button>;
  }

  if (!isAuthenticated) {
    return <button onClick={login}>Login Required</button>;
  }

  return <ChatInterface />;
}

Model Loading with Caching

const loadModels = async () => {
  const cached = localStorage.getItem('bodhi_models');
  if (cached) {
    const { models: cachedModels, expiry } = JSON.parse(cached);
    if (Date.now() < expiry) {
      setModels(cachedModels);
      return;
    }
  }

  const modelList: string[] = [];
  for await (const model of client.models.list()) {
    modelList.push(model.id);
  }
  setModels(modelList);

  localStorage.setItem(
    'bodhi_models',
    JSON.stringify({
      models: modelList,
      expiry: Date.now() + 3600000, // 1 hour
    })
  );
};

Error Handling

try {
  const stream = client.chat.completions.create({ ... });
  for await (const chunk of stream) {
    // Process chunk
  }
} catch (err) {
  if (err instanceof Error) {
    console.error('Chat error:', err.message);
    setError(err.message);
  }
}

Detailed Guides

For comprehensive information on specific topics, see the supporting documentation:

SDK Documentation Reference

The bodhi-js-sdk repository contains comprehensive documentation:

  • bodhi-js-sdk/docs/quick-start.md - Official quick start
  • bodhi-js-sdk/docs/react-integration.md - Deep dive into React integration
  • bodhi-js-sdk/docs/authentication.md - OAuth flow details
  • bodhi-js-sdk/docs/streaming.md - Streaming patterns
  • bodhi-js-sdk/docs/api-reference.md - Complete API documentation

Implementation Approach

When user asks to integrate bodhi-js-sdk:

  1. Check existing setup: Look for package.json, existing React components
  2. Install package: Run npm install @bodhiapp/bodhi-js-react
  3. Add BodhiProvider: Wrap root component with provider
  4. Create/update components: Add useBodhi() hook to components
  5. Test connection: Verify extension detection or direct mode
  6. Add OAuth: Guide user to register at developer.getbodhi.app
  7. Implement chat: Create streaming chat interface
  8. Handle errors: Add proper error boundaries and user feedback

When troubleshooting:

  1. Check connection status: Use isOverallReady, isReady, isServerReady
  2. Verify auth state: Check isAuthenticated, auth object
  3. Inspect logs: Look for [Bodhi/Web] prefixed logs in console
  4. Review config: Verify authClientId, redirectUri, basePath
  5. Test backend: Ensure local server at http://localhost:1135
  6. Check extension: Verify Bodhi Browser extension installed

Testing Integration

After integration, verify:

  1. Extension detection: Check console for [Bodhi/Web] Extension detected
  2. Server connection: Verify [Bodhi/Web] Server ready
  3. Setup flow: Click "Open Setup" to test modal
  4. Authentication: Click "Login" to test OAuth flow
  5. Model loading: Verify models populate in dropdown
  6. Streaming: Send message and verify real-time response streaming
  7. Error handling: Test with server offline to verify error states

Common Integration Tasks

When user requests:

  • "Add bodhi to my React app" → Follow basic integration steps
  • "Setup OAuth for bodhi" → Guide to oauth-setup.md, developer.getbodhi.app
  • "Deploy to GitHub Pages" → Reference github-pages.md for basePath and 404 hack
  • "Chat not working" → Check troubleshooting.md for connection/auth issues
  • "Models not loading" → Verify server, check models endpoint, review async iteration
  • "Streaming broken" → Verify stream:true, check AsyncGenerator pattern

Key Files to Reference

When implementing integration:

  • bodhi-js-sdk/docs/quick-start.md - Primary integration guide
  • bodhi-js-sdk/docs/react-integration.md - React-specific patterns
  • bodhi-js-sdk/docs/ - Comprehensive documentation and examples

Notes

  • Focus on React+Vite projects only
  • Always use @bodhiapp/bodhi-js-react preset package (simplest)
  • Auto-callback handling is enabled by default (handleCallback={true})
  • OAuth callback happens automatically without custom routes
  • Default backend: http://localhost:1135
  • Extension mode is preferred over direct mode
  • AsyncGenerator pattern for streaming (OpenAI-compatible)