@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のメッセージは通常のテキストだけでなく、roleとmetadata.typeの組み合わせによって様々なUI要素を表現します。
メッセージロール
| role | 説明 |
|---|
user | ユーザーの入力 |
assistant | AIの応答(テキスト、Markdown) |
notification | 検索結果やナレッジ詳細などのリッチUI |
interactive_action | ユーザーの操作が必要な提案UI |
tool | ツール実行情報 |
メタデータタイプ
notificationとinteractive_actionロールのメッセージは、metadataフィールドにリッチUIの情報を持ちます。
| metadata.type | 説明 | UIの例 |
|---|
search_result | セマンティック検索の結果 | 検索クエリ・ヒット件数・結果リスト |
knowledge_view | ナレッジの詳細表示 | タイトル・コンテンツ・タグ・関連ナレッジ |
knowledge_result | ナレッジ操作の完了通知 | 追加/更新/削除の成功表示 |
knowledge_proposal | ナレッジ変更の提案 | 承認・拒否・編集のインタラクティブUI |
episode_query_result | エピソード検索の結果 | 検索クエリ・時間範囲・結果リスト |
episode_list_result | エピソード一覧 | 要約・質問/回答件数・結果リスト |
メッセージのレンダリング
message.roleとmessage.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は汎用的なnameとargsを提供するため、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は単なるstring、tool.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'
必須
機能タイプ
onToolUse
(tool: ToolUseInfo) => void
ツール実行時のコールバック
onNotification
(data: NotificationData) => void
通知(検索結果・ナレッジ表示等)受信時のコールバック
onProposal
(proposal: ProposalData) => void
ナレッジ変更提案時のコールバック
onSessionChange
(sessionId: string) => void
セッション変更時のコールバック
返り値
| プロパティ | 型 | 説明 |
|---|
messages | ChatMessage[] | メッセージ配列(リッチメタデータ含む) |
sessions | ChatSession[] | セッション一覧 |
currentSessionId | string | null | 現在のセッションID |
isStreaming | boolean | ストリーミング中か |
isLoadingSessions | boolean | セッション読み込み中か |
isLoadingMessages | boolean | メッセージ読み込み中か |
toolUse | ToolUseInfo | null | 実行中のツール情報 |
pendingProposal | ProposalData | 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