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.
/plugin marketplace add BodhiSearch/bodhi-js/plugin install bodhisearch-bodhi-js-sdk-bodhi-js-sdk@BodhiSearch/bodhi-jsThis skill is limited to using the following tools:
code-examples.mdgithub-pages.mdoauth-setup.mdquick-start.mdtroubleshooting.mdGuide for integrating React+Vite applications with bodhi-js-sdk to enable local LLM chat capabilities through the Bodhi Browser ecosystem.
npm install @bodhiapp/bodhi-js-react<BodhiProvider authClientId={...}> around your appuseBodhi() for client, auth state, and actions@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)https://main-id.getbodhi.app/realms/bodhi (allows localhost)https://id.getbodhi.app/realms/bodhi (requires real domain)npm install @bodhiapp/bodhi-js-react
// 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;
// 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;
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();
// 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,
});
<BodhiProvider
authClientId={CLIENT_ID}
clientConfig={{
redirectUri: 'https://myapp.com/callback',
basePath: '/app',
logLevel: 'debug',
}}
>
<App />
</BodhiProvider>
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>
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 />;
}
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
}));
};
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);
}
}
For comprehensive information on specific topics, see the supporting documentation:
The bodhi-js-sdk repository contains comprehensive documentation:
bodhi-js-sdk/docs/quick-start.md - Official quick startbodhi-js-sdk/docs/react-integration.md - Deep dive into React integrationbodhi-js-sdk/docs/authentication.md - OAuth flow detailsbodhi-js-sdk/docs/streaming.md - Streaming patternsbodhi-js-sdk/docs/api-reference.md - Complete API documentationWhen user asks to integrate bodhi-js-sdk:
npm install @bodhiapp/bodhi-js-reactWhen troubleshooting:
isOverallReady, isReady, isServerReadyisAuthenticated, auth object[Bodhi/Web] prefixed logs in consoleAfter integration, verify:
[Bodhi/Web] Extension detected[Bodhi/Web] Server readyWhen user requests:
When implementing integration:
bodhi-js-sdk/docs/quick-start.md - Primary integration guidebodhi-js-sdk/docs/react-integration.md - React-specific patternsbodhi-js-sdk/docs/ - Comprehensive documentation and examples@bodhiapp/bodhi-js-react preset package (simplest)handleCallback={true})Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
This skill should be used when the user asks to "create a hookify rule", "write a hook rule", "configure hookify", "add a hookify rule", or needs guidance on hookify rule syntax and patterns.