@neuradex/sdk/react provides a hook and rich message types for embedding Librarian / Chatbot chat functionality into React applications.
Beyond plain text, you can build rich UIs based on message types — search result cards, knowledge detail views, episode lists, and interactive knowledge proposal approval UIs .
import { useChat } from '@neuradex/sdk/react' ;
import type { ChatMessage , RichMessageMetadata } from '@neuradex/sdk/react' ;
Basic Usage
function ChatUI () {
const {
messages ,
isStreaming ,
toolUse ,
pendingProposal ,
sendMessage ,
loadSessions ,
startNewSession ,
approveProposal ,
rejectProposal ,
editProposal ,
} = useChat ({
feature: 'librarian' ,
projectId: 'your-project-id' ,
endpoint: 'https://api.neuradex.ai/projects/your-project-id/librarian' ,
getAuthHeaders : async () => ({
Authorization: `Bearer ${ await getToken () } ` ,
}),
onToolUse : ( tool ) => console . log ( `Executing: ${ tool . name } ` ),
onError : ( error ) => console . error ( error ),
});
return (
< div >
{ messages . map (( msg ) => (
< MessageRenderer key = { msg . id } message = { msg } />
)) }
{ toolUse && < ToolIndicator tool = { toolUse } /> }
</ div >
);
}
Rich Message System
Messages from useChat represent various UI elements through combinations of role and metadata.type.
Message Roles
role Description userUser input assistantAI response (text, Markdown) notificationRich UI like search results and knowledge details interactive_actionProposal UI requiring user action toolTool execution information
Messages with notification and interactive_action roles carry rich UI information in the metadata field.
metadata.type Description UI Example search_resultSemantic search results Query, hit count, result list knowledge_viewKnowledge detail display Title, content, tags, related knowledge knowledge_resultKnowledge operation completion Add/update/delete success display knowledge_proposalKnowledge change proposal Interactive approve/reject/edit UI episode_query_resultEpisode search results Query, time range, result list episode_list_resultEpisode listing Summary, question/answer counts, result list
Rendering Messages
Switch UI components based on message.role and message.metadata?.type.
function MessageRenderer ({ message } : { message : ChatMessage }) {
// Search results
if ( message . metadata ?. type === 'search_result' ) {
const meta = message . metadata ;
return (
< SearchResultCard
query = { meta . query }
totalCount = { meta . totalCount }
results = { meta . results }
/>
);
}
// Knowledge detail
if ( message . metadata ?. type === 'knowledge_view' ) {
const meta = message . metadata ;
return (
< KnowledgeViewCard
title = { meta . title }
content = { meta . content }
tags = { meta . tags }
connectedKnowledge = { meta . connectedKnowledge }
/>
);
}
// Knowledge operation result
if ( message . metadata ?. type === 'knowledge_result' ) {
const meta = message . metadata ;
return (
< KnowledgeResultCard
actionType = { meta . actionType }
title = { meta . title }
knowledgeId = { meta . knowledgeId }
/>
);
}
// Knowledge change proposal (interactive)
if ( message . role === 'interactive_action'
&& message . metadata ?. type === 'knowledge_proposal' ) {
return < ProposalCard message = { message } /> ;
}
// Episode results
if ( message . metadata ?. type === 'episode_query_result'
|| message . metadata ?. type === 'episode_list_result' ) {
return < EpisodeResultCard metadata = { message . metadata } /> ;
}
// Regular messages
return (
< div className = { message . role === 'user' ? 'user-bubble' : 'assistant-bubble' } >
{ message . content }
</ div >
);
}
Search Result Card (search_result)
When a knowledge search is performed, results are returned as structured data.
import type { SearchResultPayload , SearchResultItem } from '@neuradex/sdk/react' ;
function SearchResultCard ({ query , totalCount , results } : SearchResultPayload ) {
return (
< div className = "search-result-card" >
< div className = "header" >
< SearchIcon />
< span > Results for " { query } " ( { totalCount } items) </ span >
</ div >
< ul >
{ results . map (( item : SearchResultItem ) => (
< li key = { item . id } >
< a href = { `/knowledge/ ${ item . id } ` } > { item . title } </ a >
{ item . tags ?. map (( tag ) => < span className = "tag" > # { tag } </ span > ) }
</ li >
)) }
</ ul >
</ div >
);
}
Knowledge Detail Card (knowledge_view)
Displays a specific knowledge entry with its details and related knowledge.
import type { KnowledgeViewPayload , ConnectedKnowledgeItem } from '@neuradex/sdk/react' ;
function KnowledgeViewCard ({ title , content , tags , connectedKnowledge } : KnowledgeViewPayload ) {
return (
< div className = "knowledge-view-card" >
< div className = "header" >
< EyeIcon />
< a href = { `/knowledge/ ${ knowledgeId } ` } > { title } </ a >
{ connectedKnowledge . length > 0 && (
< span > + { connectedKnowledge . length } related </ span >
) }
</ div >
< p className = "content-preview" > { content } </ p >
< div className = "tags" >
{ tags . map (( tag ) => < span className = "tag" > # { tag } </ span > ) }
</ div >
{ connectedKnowledge . length > 0 && (
< div className = "connected" >
< LinkIcon /> Related ( { connectedKnowledge . length } )
{ connectedKnowledge . slice ( 0 , 3 ). map (( item : ConnectedKnowledgeItem ) => (
< a key = { item . edgeId } href = { `/knowledge/ ${ item . id } ` } >
{ item . title }
</ a >
)) }
</ div >
) }
</ div >
);
}
Knowledge Operation Result (knowledge_result)
Shows completion of add, update, or delete operations.
import type { KnowledgeResultPayload } from '@neuradex/sdk/react' ;
function KnowledgeResultCard ({ actionType , title , knowledgeId } : KnowledgeResultPayload ) {
const labels = { add: 'added' , update: 'updated' , delete: 'deleted' };
return (
< div className = { `result-card result- ${ actionType } ` } >
< span > Knowledge " { title } " { labels [ actionType ] } </ span >
{ actionType !== 'delete' && (
< a href = { `/knowledge/ ${ knowledgeId } ` } > View details </ a >
) }
</ div >
);
}
Knowledge Change Proposals (knowledge_proposal)
The Librarian may return knowledge additions, updates, or deletions as proposals . This creates an interactive UI where users can approve, reject, or edit the proposed change.
import type { KnowledgeProposalPayload } from '@neuradex/sdk/react' ;
function ProposalCard ({ message } : { message : ChatMessage }) {
const meta = message . metadata as KnowledgeProposalPayload ;
const isPending = meta . userResponse === null ;
const [ isEditing , setIsEditing ] = useState ( false );
const [ editedContent , setEditedContent ] = useState ( meta . content || '' );
const { approveProposal , rejectProposal , editProposal } = useChat ({ /* ... */ });
return (
< div className = "proposal-card" >
{ /* Action badge (add/update/delete) */ }
< div className = "badge" > { meta . actionType } </ div >
{ /* Status display */ }
< h3 >
{ isPending ? 'Execute this operation?'
: meta . userResponse === 'approve' ? 'Approved'
: meta . userResponse === 'reject' ? 'Rejected'
: 'Edited and executed' }
</ h3 >
{ /* Proposal reason */ }
{ meta . reason && < p > { meta . reason } </ p > }
{ /* Proposal content */ }
< div className = "proposal-content" >
< span className = "title" > { meta . title } </ span >
{ isEditing ? (
< textarea
value = { editedContent }
onChange = { ( e ) => setEditedContent ( e . target . value ) }
/>
) : (
< p > { meta . content } </ p >
) }
{ meta . tags ?. map (( tag ) => < span className = "tag" > # { tag } </ span > ) }
</ div >
{ /* Action buttons (only when pending) */ }
{ isPending && (
< div className = "actions" >
{ ! isEditing ? (
<>
< button onClick = { approveProposal } > Approve </ button >
< button onClick = { rejectProposal } > Reject </ button >
{ meta . actionType !== 'delete' && (
< button onClick = { () => setIsEditing ( true ) } > Edit </ button >
) }
</>
) : (
<>
< button onClick = { () => editProposal ( editedContent ) } >
Execute with this content
</ button >
< button onClick = { () => setIsEditing ( false ) } > Cancel </ button >
</>
) }
</ div >
) }
</ div >
);
}
The toolUse state lets you display which tool the AI is currently executing. ToolUseInfo provides a generic name and args — this works with both built-in Neuradex tools and any custom tools you define , so you can render tailored UIs for all of them.
import type { ToolUseInfo } from '@neuradex/sdk/react' ;
function ToolIndicator ({ tool } : { tool : ToolUseInfo }) {
switch ( tool . name ) {
// --- Built-in Neuradex tools ---
case 'search_knowledge' :
return (
< div className = "tool-indicator purple animate-pulse" >
< SearchIcon />
{ tool . args ?. query
? `Searching " ${ tool . args . query } "...`
: 'Searching...' }
</ div >
);
case 'query_episodes' :
return (
< div className = "tool-indicator amber animate-pulse" >
< MessageIcon />
Searching Q&A history...
</ div >
);
case 'list_recent_episodes' :
return (
< div className = "tool-indicator teal animate-pulse" >
< ClockIcon />
Loading recent episodes...
</ div >
);
// --- Your custom tools ---
case 'check_inventory' :
return (
< div className = "tool-indicator green animate-pulse" >
< BoxIcon />
Checking stock for " { tool . args ?. productName } "...
</ div >
);
case 'create_ticket' :
return (
< div className = "tool-indicator orange animate-pulse" >
< TicketIcon />
Creating support ticket...
</ div >
);
default :
return (
< div className = "tool-indicator blue animate-pulse" >
< SpinnerIcon />
Executing { tool . name } ...
</ div >
);
}
}
Since tool.name is a plain string and tool.args is a generic Record<string, unknown>, you can handle any tool your backend exposes — inventory checks, ticket creation, external API calls, and more — with the same pattern. Just add a case for each tool name.
Episode Results
Q&A history search results and listings can also be rendered as rich UI.
import type {
EpisodeQueryResultPayload ,
EpisodeListResultPayload ,
EpisodeResultItem ,
} from '@neuradex/sdk/react' ;
function EpisodeResultCard ({ metadata } : { metadata : RichMessageMetadata }) {
if ( metadata . type === 'episode_list_result' ) {
const meta = metadata as EpisodeListResultPayload ;
return (
< div className = "episode-card" >
< p > { meta . summary } </ p >
< div className = "counts" >
Questions: { meta . questionCount } / Answers: { meta . answerCount }
</ div >
{ meta . results . map (( ep : EpisodeResultItem ) => (
< div key = { ep . id } className = { `episode-item episode- ${ ep . type } ` } >
< span className = "actor" > { ep . actor } </ span >
< span className = "content" > { ep . content } </ span >
< span className = "time" > { ep . occurredAt } </ span >
</ div >
)) }
</ div >
);
}
if ( metadata . type === 'episode_query_result' ) {
const meta = metadata as EpisodeQueryResultPayload ;
return (
< div className = "episode-card" >
< p > Results for " { meta . query } " ( { meta . total } items) </ p >
{ meta . results . map (( ep : EpisodeResultItem ) => (
< div key = { ep . id } className = { `episode-item episode- ${ ep . type } ` } >
< span className = "actor" > { ep . actor } </ span >
< span className = "content" > { ep . content } </ span >
</ div >
)) }
</ div >
);
}
return null ;
}
UseChatOptions
feature
'librarian' | 'chatbot'
required
Feature type
Project ID (for Librarian)
Function that returns authentication headers
onToolUse
(tool: ToolUseInfo) => void
Callback during tool execution
onNotification
(data: NotificationData) => void
Callback when notifications (search results, knowledge views, etc.) are received
onProposal
(proposal: ProposalData) => void
Callback when a knowledge change is proposed
onSessionChange
(sessionId: string) => void
Callback when session changes
Return Value
State
Property Type Description messagesChatMessage[]Message array (includes rich metadata) sessionsChatSession[]Session list currentSessionIdstring | nullCurrent session ID isStreamingbooleanWhether streaming is in progress isLoadingSessionsbooleanWhether sessions are loading isLoadingMessagesbooleanWhether messages are loading toolUseToolUseInfo | nullCurrently executing tool info pendingProposalProposalData | nullPending knowledge change proposal
Actions
Method Description sendMessage(message)Send a message loadSessions()Load session list loadSession(id)Load a session startNewSession()Start a new session deleteSession(id)Delete a session approveProposal()Approve proposal rejectProposal()Reject proposal editProposal(content)Edit and approve proposal
Type Exports
All the following types are exported from @neuradex/sdk/react.
import type {
// Hook
UseChatOptions ,
UseChatReturn ,
ChatFeature ,
// Messages
ChatSession ,
ChatMessage ,
RichMessageMetadata ,
// Tools & Proposals
ToolUseInfo ,
ProposalData ,
NotificationData ,
KnowledgeActionType ,
UserResponseType ,
NotificationType ,
// Rich UI Payloads
KnowledgeProposalPayload ,
KnowledgeResultPayload ,
KnowledgeViewPayload ,
SearchResultPayload ,
EpisodeQueryResultPayload ,
EpisodeListResultPayload ,
// Item Types
SearchResultItem ,
ConnectedKnowledgeItem ,
EpisodeResultItem ,
} from '@neuradex/sdk/react' ;
Next Steps
Chat API Server-side Chat Completions
Knowledge API Knowledge management