Skip to content
XLinkedIn
Sign Up →

React SDK

Package: @kelet-ai/feedback-ui  |  Version: 1.1.3  |  Requires: React ≥19

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.

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