React例
事前準備
index.html の <head> にSDKスクリプトを追加します。
html
<script src="https://klleon.k1.klleon.io/{VERSION}/klleon-sdk.umd.js"></script>基本例
SDK Web Componentを使用する基本的なReact例です。
StrictMode動作
SDKは内部Mutexで init()/destroy() 呼び出しを直列化します。React StrictModeの二重mount(mount → unmount → remount)でも、cleanupの destroy() と2回目の init() が自動的に直列化され、安全に再接続されます。destroy() は未初期化状態で呼び出しても安全(no-op)なため、cleanupでは条件なしで呼び出してください。
tsx
import { useEffect, useState } from 'react';
const SDK_KEY = 'YOUR_SDK_KEY';
const AVATAR_ID = 'YOUR_AVATAR_ID';
function App() {
const [status, setStatus] = useState('IDLE');
const [speakText, setSpeakText] = useState('');
useEffect(() => {
const SDK = window.KlleonSDK;
SDK.onStatus((s) => setStatus(s));
SDK.onSignal((data) => {
console.log('シグナル:', data.signal, data.payload);
});
SDK.onError((error) => {
console.error('エラー:', error.code, error.message);
});
SDK.init({ sdk_key: SDK_KEY, avatar_id: AVATAR_ID })
.catch((e) => console.error('初期化失敗:', e.message));
return () => {
// destroy() は未初期化状態でも安全で、進行中の init の後に自動的に直列化されます
SDK.destroy();
};
}, []);
const handleSpeak = () => {
if (speakText.trim()) {
window.KlleonSDK.speak(speakText);
setSpeakText('');
}
};
const isReady = status === 'CONNECTED_FINISH';
return (
<div style={{ display: 'flex', width: 800, height: 720, gap: 24 }}>
<avatar-container
style={{ flex: 1, borderRadius: 24, overflow: 'hidden' }}
/>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 12 }}>
<p>状態: {status}</p>
{/* 会話領域 — chat-containerが独自の入力UIでsendMessage()を送信します */}
<chat-container style={{ flex: 1, borderRadius: 24 }} />
{/* speak() 直接呼び出しデモ */}
<p style={{ margin: 0, fontWeight: 'bold' }}>speak() 直接呼び出しデモ</p>
<p style={{ margin: 0, fontSize: 12, color: '#666' }}>
入力したテキストをアバターがそのまま読み上げます(LLM応答なし)。
会話メッセージは上のchat-container入力欄をご利用ください。
</p>
<input
value={speakText}
onChange={(e) => setSpeakText(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && !e.nativeEvent.isComposing && handleSpeak()}
placeholder="speak内容を入力..."
disabled={!isReady}
/>
<button onClick={handleSpeak} disabled={!isReady}>speak送信</button>
</div>
</div>
);
}
export default App;TypeScriptプロジェクト
TypeScriptでWeb Component使用時に型エラーが発生する場合は、TypeScriptサポートページの型定義ファイルを追加してください。
カスタム例(Web Componentなし)
SDK Web Componentを使用せず、独自にUIを構成する上級例です。SDKイベントとメソッドを活用して自由にUIを制御できます。
tsx
import { useEffect, useState } from 'react';
const SDK_KEY = 'YOUR_SDK_KEY';
const AVATAR_ID = 'YOUR_AVATAR_ID';
function CustomChat() {
const [status, setStatus] = useState('IDLE');
const [messages, setMessages] = useState<{ type: 'request' | 'response'; text: string }[]>([]);
const [text, setText] = useState('');
const [isSpeaking, setIsSpeaking] = useState(false);
useEffect(() => {
const SDK = window.KlleonSDK;
SDK.onStatus((s) => setStatus(s));
SDK.onSignal((data) => {
switch (data.signal) {
case 'RESPONSE_TEXT':
setMessages((prev) => [...prev, {
type: 'response', text: String(data.payload?.text ?? '')
}]);
setIsSpeaking(true);
break;
case 'STT_RESULT':
setMessages((prev) => [...prev, {
type: 'request', text: String(data.payload?.text ?? '')
}]);
break;
case 'RESPONSE_PREPARING':
setIsSpeaking(true);
break;
case 'RESPONSE_ENDED':
setIsSpeaking(false);
break;
}
});
SDK.onError((error) => {
console.error(error.code, error.message);
});
SDK.init({ sdk_key: SDK_KEY, avatar_id: AVATAR_ID })
.catch((e) => console.error(e.message));
return () => {
// destroy() は未初期化状態でも安全で、進行中の init の後に自動的に直列化されます
SDK.destroy();
};
}, []);
const send = () => {
if (!text.trim() || isSpeaking) return;
window.KlleonSDK.sendMessage(text);
setMessages((prev) => [...prev, { type: 'request', text }]);
setText('');
};
const isReady = status === 'CONNECTED_FINISH';
return (
<div style={{ display: 'flex', gap: 24, height: 720 }}>
{/* アバター領域 */}
<avatar-container style={{ flex: 1, borderRadius: 24, overflow: 'hidden' }} />
{/* チャット領域(独自実装) */}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 12 }}>
<p>状態: {status}</p>
<div style={{
flex: 1, overflowY: 'auto', border: '1px solid #ccc',
borderRadius: 12, padding: 12
}}>
{messages.map((m, i) => (
<div key={i} style={{
textAlign: m.type === 'request' ? 'right' : 'left',
margin: '4px 0'
}}>
<span style={{
display: 'inline-block', padding: '8px 12px', borderRadius: 8,
background: m.type === 'request' ? '#3579CC' : '#f0f0f0',
color: m.type === 'request' ? '#fff' : '#333',
}}>
{m.text}
</span>
</div>
))}
</div>
{/* コントロール */}
<div style={{ display: 'flex', gap: 8 }}>
<input
value={text}
onChange={(e) => setText(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && !e.nativeEvent.isComposing && send()}
placeholder={isSpeaking ? 'アバター発話中...' : 'メッセージ入力'}
disabled={!isReady || isSpeaking}
style={{ flex: 1, padding: 8 }}
/>
<button onClick={send} disabled={!isReady || isSpeaking}>送信</button>
</div>
<div style={{ display: 'flex', gap: 8 }}>
<button onClick={() => window.KlleonSDK.startListening()} disabled={!isReady}>STT開始</button>
<button onClick={() => window.KlleonSDK.endListening()} disabled={!isReady}>STT終了</button>
<button onClick={() => window.KlleonSDK.stopSpeaking()} disabled={!isSpeaking}>発話中断</button>
</div>
</div>
</div>
);
}
export default CustomChat;