Skip to main content
@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

roleDescription
userUser input
assistantAI response (text, Markdown)
notificationRich UI like search results and knowledge details
interactive_actionProposal UI requiring user action
toolTool execution information

Metadata Types

Messages with notification and interactive_action roles carry rich UI information in the metadata field.
metadata.typeDescriptionUI Example
search_resultSemantic search resultsQuery, hit count, result list
knowledge_viewKnowledge detail displayTitle, content, tags, related knowledge
knowledge_resultKnowledge operation completionAdd/update/delete success display
knowledge_proposalKnowledge change proposalInteractive approve/reject/edit UI
episode_query_resultEpisode search resultsQuery, time range, result list
episode_list_resultEpisode listingSummary, 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>
  );
}

Tool Execution Indicators

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
projectId
string
Project ID (for Librarian)
chatbotId
string
Chatbot ID (for Chatbot)
endpoint
string
required
API endpoint URL
getAuthHeaders
() => Promise<Record<string, string>>
Function that returns authentication headers
headers
Record<string, string>
Additional 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
onError
(error: Error) => void
Error callback

Return Value

State

PropertyTypeDescription
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

MethodDescription
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