Skip to content
XLinkedIn
Sign Up →
Ask AI

React SDK

The React SDK (@kelet-ai/feedback-ui) lets you collect explicit and implicit user feedback directly from your React app. It provides ready-made components and hooks that connect to Kelet’s signal pipeline — no custom API wiring required. Requires React 19 or later.

Terminal window
npm install @kelet-ai/feedback-ui

Two API keys — never mix them:

  • Secret key (KELET_API_KEY): server-only. Used in SDK calls.
  • Publishable key: frontend-safe. Used in KeletProvider.
VITE_KELET_PUBLISHABLE_KEY=pk_... # Vite
NEXT_PUBLIC_KELET_PUBLISHABLE_KEY=pk_... # Next.js

Get both from Settings → API Keys in the console.

Wrap your app root.

import { KeletProvider } from '@kelet-ai/feedback-ui';
function App() {
return (
<KeletProvider
apiKey={import.meta.env.VITE_KELET_PUBLISHABLE_KEY}
project="my-agent"
>
<YourApp />
</KeletProvider>
);
}
PropTypeDescription
apiKeystringRequired. Publishable key (not secret key)
projectstringRequired. Kelet project name
baseUrlstringOptional. Override API URL

Multi-project apps: nest a second KeletProvider with only project — it inherits apiKey from the outer provider.

<KeletProvider apiKey="pk_..." project="agent-a">
<AgentAFeature />
<KeletProvider project="agent-b">
<AgentBFeature />
</KeletProvider>
</KeletProvider>

Headless compound component for thumbs up/down with optional text feedback.

import { VoteFeedback } from '@kelet-ai/feedback-ui';
function AgentResponse({ sessionId, content }) {
return (
<div>
<p>{content}</p>
<VoteFeedback.Root session_id={sessionId} trigger_name="user-vote">
<VoteFeedback.UpvoteButton>
{({ isSelected }) => <button>{isSelected ? 'Liked' : 'Good'}</button>}
</VoteFeedback.UpvoteButton>
<VoteFeedback.DownvoteButton>
{({ isSelected }) => (
<button>{isSelected ? 'Disliked' : 'Bad'}</button>
)}
</VoteFeedback.DownvoteButton>
<VoteFeedback.Popover>
<VoteFeedback.Textarea placeholder="What went wrong?" />
<VoteFeedback.SubmitButton>Submit</VoteFeedback.SubmitButton>
</VoteFeedback.Popover>
</VoteFeedback.Root>
</div>
);
}
PropTypeDescription
session_idstring | () => stringRequired. Must match the server agentic_session() session ID
trigger_namestringOptional. Signal category label, e.g., user-vote
trace_idstringOptional. Attach feedback to a specific trace
metadataobjectOptional. Extra context
onFeedback(data) => voidOptional. Override the default feedback handler

session_id must exactly match what the server used in agentic_session(). If they differ, feedback is captured but silently unlinked from the trace.

client generates UUID
→ sends in request body
→ server: agentic_session(session_id=uuid)
→ server returns X-Session-ID response header
→ client reads header
→ passes to VoteFeedback.Root session_id

Drop-in for useState. Tracks every state change as an edit signal — automatically distinguishes AI-generated content from user edits.

import { useFeedbackState } from '@kelet-ai/feedback-ui';
function EditableResponse({ sessionId, aiResponse }) {
const [text, setText] = useFeedbackState(aiResponse, sessionId);
return (
<textarea
value={text}
onChange={(e) => setText(e.target.value, 'user-edit')}
/>
);
}

Pass a trigger_name as the second argument to setState:

  • When setting AI-generated content: setText(aiOutput, 'ai-generation')
  • When the user edits: setText(userValue, 'user-edit')

Without trigger names, Kelet can’t distinguish “user accepted AI output” from “user corrected it.”

Drop-in for useReducer. Action type fields become trigger names automatically.

import { useFeedbackReducer } from '@kelet-ai/feedback-ui';
function reducer(state, action) {
switch (action.type) {
case 'accept':
return { ...state, accepted: true };
case 'edit':
return { ...state, content: action.payload };
default:
return state;
}
}
function Component({ sessionId }) {
const [state, dispatch] = useFeedbackReducer(
reducer,
initialState,
sessionId
);
return <button onClick={() => dispatch({ type: 'accept' })}>Accept</button>;
}

Each action.type becomes the trigger_name in the signal automatically.

Send signals manually for any event not covered by the other hooks — custom interactions, button clicks, page views, etc.

import { useKeletSignal } from '@kelet-ai/feedback-ui';
function CopyButton({ sessionId }) {
const sendSignal = useKeletSignal();
return (
<button
onClick={() => {
navigator.clipboard.writeText('...');
sendSignal({
session_id: sessionId,
kind: 'feedback',
source: 'human',
score: 1,
trigger_name: 'copy_button_clicked',
});
}}
>
Copy
</button>
);
}

Safe to call outside a KeletProvider — returns a no-op and logs a warning.

Use caseHook
Thumbs up/down ratingVoteFeedback
Editable AI-generated textuseFeedbackState with trigger names
Complex reducer-based stateuseFeedbackReducer
Custom events / manual signalsuseKeletSignal

Complete API reference, examples, and Storybook demos: feedback-ui.kelet.ai