メインコンテンツへスキップ
@neuradex/sdk/reactは、Librarian / Chatbotのチャット機能をReactアプリケーションに組み込むためのフックとリッチメッセージ型を提供します。 テキストだけでなく、検索結果カード、ナレッジ詳細表示、エピソード一覧、ナレッジ変更提案の承認UIなど、メッセージの種類に応じたリッチUIを構築できます。
import { useChat } from '@neuradex/sdk/react';
import type { ChatMessage, RichMessageMetadata } from '@neuradex/sdk/react';

基本的な使用例

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(`実行中: ${tool.name}`),
    onError: (error) => console.error(error),
  });

  return (
    <div>
      {messages.map((msg) => (
        <MessageRenderer key={msg.id} message={msg} />
      ))}
      {toolUse && <ToolIndicator tool={toolUse} />}
    </div>
  );
}

リッチメッセージシステム

useChatのメッセージは通常のテキストだけでなく、rolemetadata.typeの組み合わせによって様々なUI要素を表現します。

メッセージロール

role説明
userユーザーの入力
assistantAIの応答(テキスト、Markdown)
notification検索結果やナレッジ詳細などのリッチUI
interactive_actionユーザーの操作が必要な提案UI
toolツール実行情報

メタデータタイプ

notificationinteractive_actionロールのメッセージは、metadataフィールドにリッチUIの情報を持ちます。
metadata.type説明UIの例
search_resultセマンティック検索の結果検索クエリ・ヒット件数・結果リスト
knowledge_viewナレッジの詳細表示タイトル・コンテンツ・タグ・関連ナレッジ
knowledge_resultナレッジ操作の完了通知追加/更新/削除の成功表示
knowledge_proposalナレッジ変更の提案承認・拒否・編集のインタラクティブUI
episode_query_resultエピソード検索の結果検索クエリ・時間範囲・結果リスト
episode_list_resultエピソード一覧要約・質問/回答件数・結果リスト

メッセージのレンダリング

message.rolemessage.metadata?.typeに基づいて、UIコンポーネントを切り替えます。
function MessageRenderer({ message }: { message: ChatMessage }) {
  // 検索結果
  if (message.metadata?.type === 'search_result') {
    const meta = message.metadata;
    return (
      <SearchResultCard
        query={meta.query}
        totalCount={meta.totalCount}
        results={meta.results}
      />
    );
  }

  // ナレッジ詳細
  if (message.metadata?.type === 'knowledge_view') {
    const meta = message.metadata;
    return (
      <KnowledgeViewCard
        title={meta.title}
        content={meta.content}
        tags={meta.tags}
        connectedKnowledge={meta.connectedKnowledge}
      />
    );
  }

  // ナレッジ操作結果
  if (message.metadata?.type === 'knowledge_result') {
    const meta = message.metadata;
    return (
      <KnowledgeResultCard
        actionType={meta.actionType}
        title={meta.title}
        knowledgeId={meta.knowledgeId}
      />
    );
  }

  // ナレッジ変更提案(インタラクティブ)
  if (message.role === 'interactive_action'
      && message.metadata?.type === 'knowledge_proposal') {
    return <ProposalCard message={message} />;
  }

  // エピソード関連
  if (message.metadata?.type === 'episode_query_result'
      || message.metadata?.type === 'episode_list_result') {
    return <EpisodeResultCard metadata={message.metadata} />;
  }

  // 通常メッセージ
  return (
    <div className={message.role === 'user' ? 'user-bubble' : 'assistant-bubble'}>
      {message.content}
    </div>
  );
}

検索結果カード (search_result)

ナレッジの検索が行われると、結果が構造化データとして返されます。
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>{query}」の検索結果({totalCount}件)</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_view)

特定のナレッジの詳細と関連ナレッジが表示されます。
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}</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 /> 関連({connectedKnowledge.length}件)
          {connectedKnowledge.slice(0, 3).map((item: ConnectedKnowledgeItem) => (
            <a key={item.edgeId} href={`/knowledge/${item.id}`}>
              {item.title}
            </a>
          ))}
        </div>
      )}
    </div>
  );
}

ナレッジ操作結果 (knowledge_result)

追加・更新・削除の操作完了を示します。
import type { KnowledgeResultPayload } from '@neuradex/sdk/react';

function KnowledgeResultCard({ actionType, title, knowledgeId }: KnowledgeResultPayload) {
  const labels = { add: '追加', update: '更新', delete: '削除' };

  return (
    <div className={`result-card result-${actionType}`}>
      <span>知識「{title}」を{labels[actionType]}しました</span>
      {actionType !== 'delete' && (
        <a href={`/knowledge/${knowledgeId}`}>詳細を見る</a>
      )}
    </div>
  );
}

ナレッジ変更提案 (knowledge_proposal)

Librarianはナレッジの追加・更新・削除を提案として返すことがあります。ユーザーが承認・拒否・編集して応答するインタラクティブなUIです。
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">
      {/* アクションバッジ(追加/更新/削除) */}
      <div className="badge">{meta.actionType}</div>

      {/* 状態表示 */}
      <h3>
        {isPending ? 'この操作を実行しますか?'
          : meta.userResponse === 'approve' ? '承認済み'
          : meta.userResponse === 'reject' ? '拒否済み'
          : '修正して実行済み'}
      </h3>

      {/* 提案理由 */}
      {meta.reason && <p>{meta.reason}</p>}

      {/* 提案内容 */}
      <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>

      {/* 操作ボタン(未応答の場合のみ) */}
      {isPending && (
        <div className="actions">
          {!isEditing ? (
            <>
              <button onClick={approveProposal}>承認</button>
              <button onClick={rejectProposal}>拒否</button>
              {meta.actionType !== 'delete' && (
                <button onClick={() => setIsEditing(true)}>修正</button>
              )}
            </>
          ) : (
            <>
              <button onClick={() => editProposal(editedContent)}>
                この内容で実行
              </button>
              <button onClick={() => setIsEditing(false)}>キャンセル</button>
            </>
          )}
        </div>
      )}
    </div>
  );
}

ツール実行インジケーター

toolUseステートでAIが実行中のツールを表示できます。ToolUseInfoは汎用的なnameargsを提供するため、Neuradex組み込みツールだけでなく、ユーザーが定義したカスタムツールも同じパターンでハンドルできます。
import type { ToolUseInfo } from '@neuradex/sdk/react';

function ToolIndicator({ tool }: { tool: ToolUseInfo }) {
  switch (tool.name) {
    // --- Neuradex 組み込みツール ---
    case 'search_knowledge':
      return (
        <div className="tool-indicator purple animate-pulse">
          <SearchIcon />
          {tool.args?.query
            ? `「${tool.args.query}」で検索中...`
            : '検索中...'}
        </div>
      );
    case 'query_episodes':
      return (
        <div className="tool-indicator amber animate-pulse">
          <MessageIcon />
          Q&A履歴を検索中...
        </div>
      );
    case 'list_recent_episodes':
      return (
        <div className="tool-indicator teal animate-pulse">
          <ClockIcon />
          最近のエピソードを取得中...
        </div>
      );

    // --- カスタムツール ---
    case 'check_inventory':
      return (
        <div className="tool-indicator green animate-pulse">
          <BoxIcon />
{tool.args?.productName}」の在庫を確認中...
        </div>
      );
    case 'create_ticket':
      return (
        <div className="tool-indicator orange animate-pulse">
          <TicketIcon />
          サポートチケットを作成中...
        </div>
      );

    default:
      return (
        <div className="tool-indicator blue animate-pulse">
          <SpinnerIcon />
          {tool.name} を実行中...
        </div>
      );
  }
}
tool.nameは単なるstringtool.argsは汎用的なRecord<string, unknown>なので、バックエンドが公開する任意のツール(在庫確認、チケット作成、外部API呼び出しなど)を同じパターンでハンドルできます。ツール名ごとにcaseを追加するだけです。

エピソード結果

Q&A履歴の検索結果や一覧もリッチ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">
          質問: {meta.questionCount}件 / 回答: {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>{meta.query}」の検索結果({meta.total}件)</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'
必須
機能タイプ
projectId
string
プロジェクトID(Librarian用)
chatbotId
string
ChatbotID(Chatbot用)
endpoint
string
必須
APIエンドポイントURL
getAuthHeaders
() => Promise<Record<string, string>>
認証ヘッダーを返す関数
headers
Record<string, string>
追加ヘッダー
onToolUse
(tool: ToolUseInfo) => void
ツール実行時のコールバック
onNotification
(data: NotificationData) => void
通知(検索結果・ナレッジ表示等)受信時のコールバック
onProposal
(proposal: ProposalData) => void
ナレッジ変更提案時のコールバック
onSessionChange
(sessionId: string) => void
セッション変更時のコールバック
onError
(error: Error) => void
エラー時のコールバック

返り値

状態

プロパティ説明
messagesChatMessage[]メッセージ配列(リッチメタデータ含む)
sessionsChatSession[]セッション一覧
currentSessionIdstring | null現在のセッションID
isStreamingbooleanストリーミング中か
isLoadingSessionsbooleanセッション読み込み中か
isLoadingMessagesbooleanメッセージ読み込み中か
toolUseToolUseInfo | null実行中のツール情報
pendingProposalProposalData | null保留中のナレッジ変更提案

アクション

メソッド説明
sendMessage(message)メッセージ送信
loadSessions()セッション一覧読み込み
loadSession(id)セッション読み込み
startNewSession()新規セッション開始
deleteSession(id)セッション削除
approveProposal()提案を承認
rejectProposal()提案を拒否
editProposal(content)提案を編集して承認

型定義エクスポート

@neuradex/sdk/reactから以下の型がすべてエクスポートされます。
import type {
  // Hook
  UseChatOptions,
  UseChatReturn,
  ChatFeature,

  // メッセージ
  ChatSession,
  ChatMessage,
  RichMessageMetadata,

  // ツール・提案
  ToolUseInfo,
  ProposalData,
  NotificationData,
  KnowledgeActionType,
  UserResponseType,
  NotificationType,

  // リッチUIペイロード
  KnowledgeProposalPayload,
  KnowledgeResultPayload,
  KnowledgeViewPayload,
  SearchResultPayload,
  EpisodeQueryResultPayload,
  EpisodeListResultPayload,

  // アイテム型
  SearchResultItem,
  ConnectedKnowledgeItem,
  EpisodeResultItem,
} from '@neuradex/sdk/react';

次のステップ

Chat API

サーバーサイドのChat Completions

Knowledge API

ナレッジの管理