Skip to content

React Example

Prerequisites

Add the SDK script to the <head> of your index.html.

html
<script src="https://klleon.k1.klleon.io/{VERSION}/klleon-sdk.umd.js"></script>

Basic Example

A basic React example using SDK Web Components.

StrictMode Behavior

The SDK serializes init()/destroy() calls through an internal Mutex. In React StrictMode, mount → unmount → remount triggers init → destroy → init queued sequentially, resulting in 3 round-trips. Initial connection may be slower in development, but in production StrictMode is absent so only 1 round-trip occurs.

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('Signal:', data.signal, data.payload);
    });

    SDK.onError((error) => {
      console.error('Error:', error.code, error.message);
    });

    SDK.init({ sdk_key: SDK_KEY, avatar_id: AVATAR_ID })
      .catch((e) => console.error('Initialization failed:', e.message));

    return () => { SDK.destroy(); };
  }, []);

  const handleSpeak = () => {
    if (speakText.trim()) {
      window.KlleonSDK.speak(speakText);
      setSpeakText('');
    }
  };

  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: {status}</p>
        <chat-container style={{ flex: 1, borderRadius: 24 }} />
        <input
          value={speakText}
          onChange={(e) => setSpeakText(e.target.value)}
          onKeyDown={(e) => e.key === 'Enter' && handleSpeak()}
          placeholder="Enter speak text..."
        />
        <button onClick={handleSpeak}>Send speak</button>
      </div>
    </div>
  );
}

export default App;

TypeScript Project

If you get type errors when using Web Components in TypeScript, add the type definition file from the TypeScript Support page.

Custom Example (Without Web Components)

An advanced example that builds the UI directly without SDK Web Components. You can freely control the UI using SDK events and methods.

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([]);
  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: data.payload?.text
          }]);
          setIsSpeaking(true);
          break;
        case 'STT_RESULT':
          setMessages((prev) => [...prev, {
            type: 'request', text: 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 () => { 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 area */}
      <avatar-container style={{ flex: 1, borderRadius: 24, overflow: 'hidden' }} />

      {/* Chat area (custom implementation) */}
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 12 }}>
        <p>Status: {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>

        {/* Controls */}
        <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 ? 'Avatar is speaking...' : 'Enter message'}
            disabled={!isReady || isSpeaking}
            style={{ flex: 1, padding: 8 }}
          />
          <button onClick={send} disabled={!isReady || isSpeaking}>Send</button>
        </div>

        <div style={{ display: 'flex', gap: 8 }}>
          <button onClick={() => window.KlleonSDK.startListening()} disabled={!isReady}>Start STT</button>
          <button onClick={() => window.KlleonSDK.endListening()} disabled={!isReady}>End STT</button>
          <button onClick={() => window.KlleonSDK.stopSpeaking()} disabled={!isSpeaking}>Stop Speech</button>
        </div>
      </div>
    </div>
  );
}

export default CustomChat;