# Klleon SDK — Complete Reference for LLMs > AI 아바타와 실시간 대화를 구현하는 브라우저용 TypeScript SDK. > CDN UMD 번들로 배포. window.KlleonSDK 글로벌 변수로 접근. --- # Klleon SDK Klleon SDK는 웹 애플리케이션에 클레온의 실시간 인터랙티브 아바타를 통합하는 JavaScript SDK입니다. WebRTC 연결과 시그널링을 추상화하여 API 호출만으로 아바타 대화를 구현합니다. ## 주요 기능 - ` ``` `{VERSION}`을 실제 버전(예: `2.0.0`)으로 교체하세요. 로드 후 `window.KlleonSDK`로 SDK에 접근합니다. ## 기본 사용법 ### 1. HTML에 컴포넌트 추가 ```html ``` ### 2. 이벤트 구독 `onStatus`는 `init()` 중 상태 변경을 수신하려면 `init()` 전에 등록하세요. ```js const SDK = window.KlleonSDK; SDK.onStatus((status) => { console.log('상태 변경:', status); }); SDK.onSignal((data) => { console.log('시그널:', data.signal, data.payload); }); SDK.onError((error) => { console.error('에러:', error.code, error.message); }); ``` ```jsx const [status, setStatus] = useState('IDLE'); 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)); }, []); ``` ### 3. SDK 초기화 `init()`은 상태가 `CONNECTED_FINISH`로 전이된 직후 resolve됩니다. 초기화에 실패하면 상태는 `IDLE`로 돌아가며, 실패는 `onStatus`가 아니라 `init()`이 던지는 예외로만 알 수 있습니다. ```js SDK.init({ sdk_key: 'YOUR_SDK_KEY', avatar_id: 'YOUR_AVATAR_ID', }).catch((e) => console.error('초기화 실패:', e.message)); ``` ```jsx import { useEffect, useState } from 'react'; function App() { const [failed, setFailed] = useState(false); useEffect(() => { const SDK = window.KlleonSDK; SDK.init({ sdk_key: 'YOUR_SDK_KEY', avatar_id: 'YOUR_AVATAR_ID', }) .catch((e) => { setFailed(true); console.error('초기화 실패:', e.message); }); return () => { // 이 콜백에서는 비동기 종료 완료를 기다릴 수 없으므로 종료 요청만 시작합니다 SDK.destroy(); }; }, []); } ``` ### 4. 메시지 전송 전송은 상태가 `CONNECTED_FINISH`가 된 뒤에 하세요. 미연결 상태(`IDLE`)에서 호출하면 예외 없이 무시되고 경고 로그만 남습니다. 연결이 진행 중일 때의 호출 결과는 보장되지 않으므로, 상태를 확인한 뒤 호출하거나 전송 UI를 비활성화하세요. ```js if (SDK.getStatus() === 'CONNECTED_FINISH') { SDK.sendMessage('안녕하세요'); } ``` ```jsx // status는 2단계 onStatus로, failed는 3단계 init()의 catch에서 갱신한 값입니다 const isReady = status === 'CONNECTED_FINISH'; const label = failed ? '연결 실패' : isReady ? '대화 가능' : '연결 중'; return ( <>

상태: {label}

); ``` ### 5. SDK 종료 `destroy()`는 비동기 함수입니다. 종료가 끝날 때까지 기다리려면 `await`하세요. ```js async function endSession() { await window.KlleonSDK.destroy(); } ``` React 컴포넌트의 cleanup 콜백처럼 `await`를 쓸 수 없는 곳에서는 종료 요청만 시작합니다. ## 음성 입력 ```js window.KlleonSDK.startListening(); // STT 시작 window.KlleonSDK.endListening(); // STT 종료 ``` ## 전체 예제 ```html ``` ```jsx import { useEffect } from 'react'; function App() { useEffect(() => { const SDK = window.KlleonSDK; SDK.onStatus((status) => console.log('상태:', status)); SDK.onSignal((data) => console.log(data.signal, data.payload)); SDK.onError((error) => console.error(error.code, error.message)); SDK.init({ sdk_key: 'YOUR_SDK_KEY', avatar_id: 'YOUR_AVATAR_ID', }) .catch((e) => console.error(e.message)); return () => { // 이 콜백에서는 비동기 종료 완료를 기다릴 수 없으므로 종료 요청만 시작합니다 SDK.destroy(); }; }, []); return (
); } export default App; ``` --- # init() SDK를 초기화하고 아바타 연결을 시작합니다. ## 사용법 ```js await SDK.init(options); ``` ## InitOption | 파라미터 | 타입 | 필수 | 기본값 | 설명 | |---------|------|------|--------|------| | `sdk_key` | `string` | O | - | SDK 인증 키 | | `avatar_id` | `string` | O | - | 아바타 ID | | `log_level` | `LogLevel` | X | `"warn"` | 로그 레벨 | | `language` | `LanguageCode` | X | `"ko_kr"` | TTS 음성, 자막 및 STT 인식에 사용할 언어 코드 | | `enable_microphone` | `boolean` | X | `true` | STT를 위한 마이크 입력 사용 여부. `true`이면 `init()` 진행 중 브라우저 마이크 권한을 요청하고, 연결 시점부터 마이크가 활성화되어 오디오가 전송됩니다. 음성 입력을 사용하지 않는다면 `false`로 초기화하세요. `false`이면 권한을 요청하지 않습니다. | | `auto_send` | `boolean` | X | `false` | `false`(수동 전송, Push-to-Send): `startListening`/`endListening`으로 음성 인식을 제어합니다. `true`(자동 전송, Auto Send): STT를 계속 활성화하고 서버 음성 활동 감지(VAD)가 발화 구간을 감지해 자동으로 응답을 요청합니다. | | `stt_only` | `boolean` | X | `false` | STT 전용 모드. `true`이면 아바타가 응답하지 않고 STT 결과만 반환합니다. `false`이면 STT 결과를 반환하고, 해당 결과를 기반으로 아바타가 응답합니다. | `enable_microphone`이 `false`인 상태에서 `startListening()`, `endListening()`, `cancelListening()`을 호출해도 오류가 발생하지 않습니다. 시그널은 그대로 서버로 전송되지만 음성은 전달되지 않습니다. 마이크를 가져오지 못한 경우에도 세션은 그대로 유지되며, 경고 로그만 남습니다. ### LogLevel 브라우저 콘솔에 출력되는 로그 수준을 제어합니다. 설정한 레벨 이상만 출력됩니다. | 값 | 출력 범위 | 용도 | |----|----------|------| | `"debug"` | debug, info, warn, error | 개발 중 상세 디버깅 | | `"info"` | info, warn, error | 주요 이벤트만 확인 (연결 완료, 초기화 등) | | `"warn"` | warn, error | 잠재적 문제만 확인 (기본값) | | `"error"` | error | 에러만 출력 | | `"silent"` | 없음 | 콘솔 출력 완전 비활성화 | ### LanguageCode 아바타의 TTS 음성·자막 언어와 사용자의 STT 인식 언어를 지정합니다. | 값 | 언어 | |----|------| | `"ko_kr"` | 한국어 (기본값) | | `"en_us"` | 영어 | | `"ja_jp"` | 일본어 | | `"id_id"` | 인도네시아어 | ## 연결 흐름 ```mermaid sequenceDiagram participant App participant SDK participant API participant WS as WebSocket participant Streaming App->>SDK: init(options) SDK->>API: checkStatus (SDK 키 검증) API-->>SDK: OK SDK->>WS: connect WS-->>SDK: MATCHED (channel, token, uid) SDK->>Streaming: join(channel, token, uid) Streaming-->>SDK: video + audio track 수신 SDK-->>App: status: CONNECTED_FINISH ``` ## 에러 처리 초기화에 실패하면 내부 연결 리소스를 정리하고 `IDLE` 상태로 돌아간 뒤 예외를 발생시킵니다. ```js try { await SDK.init(options); } catch (error) { // error.message에 실패 원인 포함 // "Failed to connect: ..." → SDK 키 검증 또는 WebSocket 연결 실패 // "Failed to connect streaming: ..." → 스트리밍 연결 실패 // "Init failed: ..." → 그 외 단계에서 발생한 초기화 실패 console.error(error.message); } ``` - init/destroy 진행 중 재호출 시 이전 작업 완료를 기다린 후 실행됩니다 (Mutex 직렬화). --- # 라이프사이클 ## destroy() SDK를 종료하고 모든 연결을 해제합니다. ```js await SDK.destroy(); ``` - WebSocket, 스트리밍 연결 종료 - 상태를 `IDLE`로 초기화 - 메시지 목록, 시그널 상태 초기화 - 원격 로거 분리 > **WARNING** `destroy()`는 비동기 함수입니다. 가능한 곳에서는 `await`를 권장합니다. React cleanup처럼 `await`할 수 없는 곳에서는 호출만으로 안전합니다(내부에서 직렬화됨). 비활성 상태(`IDLE`)에서 호출하면 무시됩니다. `destroy()` 후에도 이벤트 콜백은 유지되므로, 재초기화 시 다시 등록할 필요가 없습니다. ## 재연결 재연결이 필요한 경우 `destroy()` → `init()`을 사용하세요. ```js await SDK.destroy(); await SDK.init(options); ``` ## 라이프사이클 흐름 ```mermaid sequenceDiagram participant App participant SDK App->>SDK: 이벤트 콜백 등록 App->>SDK: init(options) SDK-->>App: CONNECTING SDK-->>App: SOCKET_CONNECTED SDK-->>App: STREAMING_CONNECTED SDK-->>App: CONNECTED_FINISH Note over App,SDK: 아바타 사용 중 App->>SDK: await destroy() SDK-->>App: IDLE Note over App,SDK: 콜백 유지됨 App->>SDK: init(options) SDK-->>App: CONNECTING ... ``` ## 상태 전이 ```mermaid flowchart TD IDLE -->|init| CONNECTING CONNECTING -->|WebSocket 연결| SOCKET_CONNECTED SOCKET_CONNECTED -->|비디오+오디오 수신| STREAMING_CONNECTED STREAMING_CONNECTED -->|완료| CONNECTED_FINISH CONNECTED_FINISH -->|destroy| IDLE CONNECTING -.->|연결 실패| IDLE SOCKET_CONNECTED -.->|스트리밍 실패| IDLE CONNECTED_FINISH -.->|런타임 에러| IDLE ``` ### SDKStatus | 값 | 설명 | |----|------| | `IDLE` | 초기/리셋 상태 | | `CONNECTING` | HTTP 검증 + WebSocket 연결 중 | | `SOCKET_CONNECTED` | WebSocket 연결 완료 | | `STREAMING_CONNECTED` | 비디오+오디오 트랙 수신 완료 | | `CONNECTED_FINISH` | 모든 연결 완료. 사용 가능 | 연결 실패 시 곧바로 `IDLE`로 복귀하며, 실패 원인은 `init()`의 예외(reject)로 전달됩니다. --- # 메시징 ## sendMessage(text) 아바타에게 텍스트 메시지를 전송합니다. 아바타가 LLM 응답을 생성하여 음성으로 답합니다. ```js SDK.sendMessage('안녕하세요'); ``` | 파라미터 | 타입 | 설명 | |---------|------|------| | `text` | `string` | 전송할 메시지 | ## speak(option) 아바타가 주어진 텍스트를 그대로 읽도록 합니다 (LLM 처리 없이). 문자열 또는 옵션 객체를 전달할 수 있습니다. ```js // 텍스트만 SDK.speak('이 문장을 그대로 읽어주세요'); // 옵션 객체로 SDK.speak({ text: '안녕하세요.' }); ``` | 파라미터 | 타입 | 설명 | |---------|------|------| | `option` | `string \| SpeakOption` | 텍스트 문자열 또는 옵션 객체 | ### SpeakOption | 필드 | 타입 | 필수 | 설명 | |------|------|------|------| | `text` | `string` | O | 아바타가 읽을 텍스트 | > **TIP** `stopSpeaking()` 직후 `speak()`나 `sendMessage()`를 바로 호출하면 서버에서 요청이 처리되지 않을 수 있습니다. 안전한 호출 패턴은 [아바타 제어 > stopSpeaking()](/ko/api/avatar#stopspeaking)을 참고하세요. ## sendSpeakAudio(audio, sample_rate) Base64 인코딩된 오디오 데이터를 전송하여 아바타가 해당 음성으로 립싱크(lip-sync) 합니다. ```js SDK.sendSpeakAudio(base64AudioData); // 샘플레이트를 함께 전달 SDK.sendSpeakAudio(base64AudioData, sampleRate); ``` | 파라미터 | 타입 | 필수 | 설명 | |---------|------|------|------| | `audio` | `string` | O | Base64 인코딩된 오디오 데이터 | | `sample_rate` | `number` | X | 전송하는 오디오의 샘플레이트(Hz). 원본 오디오의 실제 샘플레이트를 그대로 전달하세요 — 예: 브라우저 녹음이면 `AudioContext.sampleRate`(보통 48000). 생략하면 서버가 **16000Hz로 간주**해 처리하므로, 원본이 16000Hz가 아니면 재생 속도·음정이 왜곡될 수 있습니다. | - 크기 제한은 **호출 1회당** 적용됩니다. Base64를 디코딩한 원본 바이트가 0.1MB를 넘으면 그 호출은 전송되지 않습니다. 여러 번 나누어 호출하면 각 호출이 개별적으로 검사되며, 누적 총량에는 이 제한이 적용되지 않습니다. - `audio`에는 Base64 문자열만 전달하세요. `data:audio/...;base64,` 같은 data URL prefix가 붙어 있으면 전송되지 않습니다. - 크기를 초과하거나 Base64 형식이 올바르지 않아도 예외는 발생하지 않습니다. 해당 호출만 전송되지 않고 콘솔에 경고 로그가 남습니다(기본 `log_level` 기준 — `error`/`silent`로 낮추면 표시되지 않습니다). - `audio`는 항상 **조각 하나의 Base64 문자열**입니다(배열이 아님). 긴 오디오는 아래처럼 문자열을 잘라 호출을 반복하고, 전송이 끝나면 `endSpeakAudio()`를 호출하세요. ```js const CHUNK_SIZE = 96 * 1024; for (let i = 0; i < base64Audio.length; i += CHUNK_SIZE) { const chunk = base64Audio.slice(i, i + CHUNK_SIZE); // string SDK.sendSpeakAudio(chunk, sampleRate); } SDK.endSpeakAudio(); ``` ## endSpeakAudio() 오디오 립싱크 전송을 종료합니다. 마지막 청크를 전송한 뒤 1회 호출합니다. 서버가 이 신호를 받으면 수신한 오디오로 립싱크 처리를 시작합니다. ```js SDK.endSpeakAudio(); ``` ## clearMessages() `` 사용 시 대화 메시지 목록을 비웁니다. ```js SDK.clearMessages(); ``` --- # 음성 인식 (STT) 음성 입력은 `init()`의 `auto_send` 옵션에 따라 두 가지 방식으로 동작합니다. 먼저 어떤 입력 방식을 사용할지 선택하세요. ## 입력 방식 선택 | 방식 | InitOption | 호출 흐름 | 사용자 발화 종료 처리 | | --- | --- | --- | --- | | 수동 전송 (기본) | `auto_send: false` | `startListening()` → 사용자 발화 → `endListening()` | 앱이 `endListening()`으로 전송을 확정 | | 자동 전송 | `auto_send: true` | `startListening()`만 호출 | 서버가 사용자 발화 종료를 감지하고 자동으로 응답 요청 | ### 수동 전송 — `auto_send: false` (기본값) `startListening()`으로 듣기를 시작하고, 사용자의 발화가 끝나면 앱이 `endListening()`을 호출해 전송을 확정합니다. 마이크 버튼을 누르고 있는 동안의 음성만 입력으로 처리하는 Push-to-Talk형 UI에 적합합니다. ```js // 듣기 시작 — 음성 인식 구간 시작 SDK.startListening(); // 사용자 발화가 끝나면 호출 — 전송이 확정되고 아바타가 응답 SDK.endListening(); ``` ### 자동 전송 — `auto_send: true` 음성 입력을 기본 인터페이스로 사용하는 아바타에 적합합니다. `startListening()`만 호출하면 되고, 사용자 발화의 종료 감지와 응답 요청은 서버가 자동으로 처리하므로 `endListening()`을 호출할 필요가 없습니다. 연결 완료 시 자동으로 듣기를 시작하려면 다음과 같이 작성합니다. ```js SDK.onStatus((status) => { if (status === 'CONNECTED_FINISH') { SDK.startListening(); // 이후 사용자 발화의 종료 감지와 응답 요청은 서버가 자동 처리 } }); SDK.init({ sdk_key: 'YOUR_SDK_KEY', avatar_id: 'YOUR_AVATAR_ID', auto_send: true, }).catch((e) => console.error(e.message)); ``` ## startListening() 음성 인식 구간을 시작합니다. 마이크 자체는 `enable_microphone: true`(기본값)면 연결 시점부터 켜져 있습니다. `startListening()`은 이 구간의 음성을 입력으로 처리하도록 서버에 알리는 동작이며, 이전에 `endListening()`·`cancelListening()`으로 비활성화된 마이크 트랙이 있으면 다시 활성화합니다. 자동 전송(`auto_send: true`) 모드에서는 이 호출 하나로 음성 대화가 시작됩니다. ```js SDK.startListening(); ``` ## endListening() 수동 전송(`auto_send: false`) 모드에서 음성 인식 구간을 종료하고 마이크 트랙을 비활성화하며, 전송을 확정합니다. 인식 결과는 `STT_RESULT` 시그널로 전달되며, `stt_only=false`인 경우 해당 결과를 바탕으로 아바타가 응답합니다. 자동 전송(`auto_send: true`) 모드에서는 호출할 필요가 없습니다. ```js SDK.endListening(); ``` ## cancelListening() 음성 인식을 취소하고 마이크를 비활성화합니다. 인식된 텍스트를 폐기하고 아바타가 응답하지 않습니다. 수동·자동 전송 양쪽 모드 모두에서 호출할 수 있습니다. ```js SDK.cancelListening(); ``` ## stt_only 옵션 `stt_only: true`로 초기화하면 아바타가 응답하지 않고 STT 인식 결과(`STT_RESULT` 시그널)만 전달됩니다. 기본값은 `false`이며, 이 경우 STT 결과 전달 후 아바타 응답이 이어집니다. `auto_send`와 독립적인 옵션이므로 두 입력 방식 모두와 조합할 수 있습니다. 한편 `enable_microphone: false`로 초기화하면 마이크 권한을 요청하지 않으므로 음성 입력 자체를 사용할 수 없습니다. ## STT 관련 시그널 > **WARNING** `startListening()` / `endListening()`은 **음성 인식 구간**(듣기 구간)을 제어합니다. 마이크 자체는 `enable_microphone: true`면 연결 시점부터 켜져 있으며, `endListening()`·`cancelListening()` 호출 시 마이크 트랙이 비활성화되고 `startListening()` 호출 시 다시 활성화됩니다. `USER_SPEECH_STARTED` / `USER_SPEECH_STOPPED`는 서버가 감지한 **발화 구간**이며, 마이크가 켜져 있는 동안에는 듣기 구간 밖(예: 연결 직후 `startListening()` 호출 전)에서도 수신될 수 있습니다. `USER_SPEECH_STOPPED`을 수신해도 듣기 구간은 종료되지 않습니다. 수동 전송 모드에서는 `endListening()`을 호출해야 종료되며, 자동 전송 모드에서는 서버가 사용자 발화의 종료를 감지해 응답을 요청합니다. ```js SDK.onSignal((data) => { switch (data.signal) { case 'USER_SPEECH_STARTED': // 사용자가 말하기 시작 (마이크는 이미 켜져 있음) console.log('사용자 발화 시작'); break; case 'USER_SPEECH_STOPPED': // 사용자가 말을 멈춤 (마이크는 여전히 켜져 있음) console.log('사용자 발화 종료'); break; case 'STT_RESULT': console.log('인식 결과:', data.payload.text); break; } }); ``` ## 전체 STT 흐름 ### 수동 전송 (auto_send: false) `stt_only=false` 기준입니다. 마이크는 연결 시점부터 켜져 있으므로 `USER_SPEECH_*` 시그널은 `startListening()` 호출 전에도 수신될 수 있습니다. 아래는 대표적인 흐름입니다. ```mermaid sequenceDiagram participant App participant SDK participant Server App->>SDK: startListening() Server-->>SDK: USER_SPEECH_STARTED Server-->>SDK: USER_SPEECH_STOPPED App->>SDK: endListening() Server-->>SDK: STT_RESULT (인식된 텍스트) Server-->>SDK: RESPONSE_TEXT (아바타 응답) Server-->>SDK: RESPONSE_ENDED ``` ### 자동 전송 (auto_send: true) `endListening()` 호출 없이 서버가 사용자 발화의 종료를 감지해 응답까지 이어집니다. `stt_only=false` 기준입니다. ```mermaid sequenceDiagram participant App participant SDK participant Server App->>SDK: startListening() Server-->>SDK: USER_SPEECH_STARTED Server-->>SDK: USER_SPEECH_STOPPED Server-->>SDK: STT_RESULT (인식된 텍스트) Server-->>SDK: RESPONSE_TEXT (아바타 응답) Server-->>SDK: RESPONSE_ENDED ``` --- # 아바타 제어 ## stopSpeaking() 아바타의 현재 발화를 즉시 중단합니다. ```js SDK.stopSpeaking(); ``` > **WARNING** `stopSpeaking()` 직후 `speak()`나 `sendMessage()`를 바로 호출하면 서버에서 요청이 처리되지 않을 수 있습니다. `RESPONSE_ENDED` 시그널을 수신한 후에 호출하세요. ```js let pendingText = null; SDK.onSignal((data) => { if (data.signal === 'RESPONSE_ENDED' && pendingText) { SDK.speak(pendingText); pendingText = null; } }); function stopAndSpeak(text) { pendingText = text; SDK.stopSpeaking(); } ``` ## setVolume(volume) 아바타 음성 볼륨을 설정합니다. ```js SDK.setVolume(50); // 0 ~ 100 ``` | 파라미터 | 타입 | 설명 | |---------|------|------| | `volume` | `number` | 볼륨 (0 ~ 100) | ## getStatus() 현재 SDK 연결 상태를 반환합니다. ```js const status = SDK.getStatus(); // 'IDLE', 'CONNECTED_FINISH', ... ``` --- # 이벤트 ### 이벤트 등록 타이밍 콜백은 `init()` 전후 언제든 등록할 수 있습니다. 단, `onStatus`는 `init()` 중 상태 변경(`CONNECTING`, `SOCKET_CONNECTED` 등)을 수신하려면 `init()` 호출 전에 등록하세요. - `destroy()` 후에도 콜백은 유지되므로 재초기화 시 다시 등록할 필요 없습니다. > **WARNING** 각 타입당 콜백 1개만 등록 가능하며, 마지막 등록이 이전을 덮어씁니다. 여러 화면이나 컴포넌트에서 이벤트가 필요하면 SDK 콜백을 앱의 한 곳에 등록하고, 수신한 이벤트를 필요한 컴포넌트로 분배하세요. ## onSignal(callback) 서버로부터 수신한 시그널을 받습니다. ```js SDK.onSignal((data) => { console.log(data.signal, data.payload); }); ``` ```jsx useEffect(() => { window.KlleonSDK.onSignal((data) => { console.log(data.signal, data.payload); }); }, []); ``` ### 콜백 파라미터 | 필드 | 타입 | 필수 | 설명 | |------|------|------|------| | `signal` | `IncomingSignal` | O | 시그널 종류 (enum, 런타임에는 문자열) | | `payload` | `object` | X | 시그널별 추가 데이터 | | `id` | `string` | O | 시그널 UUID | | `type` | `string` | O | `"SESSION"` \| `"INPUT"` \| `"OUTPUT"` | ### 주요 시그널 #### 대화 흐름 | 시그널 | 설명 | payload | |--------|------|---------| | `RESPONSE_TEXT` | 아바타 응답 텍스트 | `{ text: string, language: string }` | | `STT_RESULT` | 음성 인식 결과 | `{ text: string }` | | `RESPONSE_PREPARING` | 응답 준비 중 (LLM 요청 직전) | - | | `RESPONSE_STARTED` | 응답 시작 | - | | `RESPONSE_ENDED` | 응답 완료 | - | | `USER_SPEECH_STARTED` | STT 세션 내 발화 구간 시작 (마이크는 이미 켜져 있음) | - | | `USER_SPEECH_STOPPED` | STT 세션 내 발화 구간 종료 (마이크는 여전히 켜져 있음) | - | | `ERROR` | 응답 처리 중 에러 (세션 유지) | `{ type: "STT_ERROR" \| "LLM_ERROR" \| ... }` | | `REJECTED` | 입력 거절 (세션 유지) | `{ type: "STT_EMPTY" \| "MODERATION" \| ... }` | #### 세션 관리 | 시그널 | 설명 | payload | |--------|------|---------| | `MATCH_WAITING` | 매칭 대기 중 (로딩 UI 표시용) | - | | `WAIT` | 서버 준비 중 (계속 대기) | - | | `SESSION_ENDED` | 세션 정상 종료 | - | | `IDLE_TIMED_OUT` | 유휴 시간 초과로 서버가 세션을 종료 | - | | `SESSION_TIMED_OUT` | 세션 타임아웃 | - | | `TIME_EXHAUSTED` | 사용 시간 소진 | - | | `QUOTA_EXCEEDED` | 동접 quota 초과 | - | | `SUSPEND_WARNED` | 오랜 대기 경고 | - | ## onStatus(callback) SDK 연결 상태 변경을 받습니다. ```js SDK.onStatus((status) => { console.log('상태:', status); if (status === 'CONNECTED_FINISH') { console.log('아바타 준비 완료'); } }); ``` ```jsx const [status, setStatus] = useState('IDLE'); useEffect(() => { window.KlleonSDK.onStatus((s) => setStatus(s)); }, []); ``` ## onError(callback) SDK 내부 에러 + 서버 에러를 받습니다. ```js SDK.onError((error) => { console.error(error.code, error.message); }); ``` ```jsx useEffect(() => { window.KlleonSDK.onError((error) => { console.error(error.code, error.message); }); }, []); ``` ### 콜백 파라미터 | 필드 | 타입 | 필수 | 설명 | |------|------|------|------| | `code` | `string` | O | 에러 코드 | | `message` | `string` | X | 상세 메시지 | ### 에러 코드 상세 #### 초기화 에러 (init 실패 시) 초기화 실패는 에러 코드 없이 `init()`의 예외(reject)로 전달됩니다. 메시지: `"Failed to connect: ..."` (SDK 키 검증 실패 또는 WebSocket 연결 실패) / `"Failed to connect streaming: ..."` (스트리밍 연결 실패) / `"Init failed: ..."` (그 외 초기화 실패) > 다만 연결 과정 중 스트리밍 쪽 문제로 `onError`가 함께 발생할 수 있으므로, `onError` 등록은 `init()` 호출 전에 해두는 것이 안전합니다. #### 런타임 에러 (연결 중 발생) | 코드 | 발생 시점 | message 예시 | |------|-----------|-------------| | `SOCKET_DISCONNECTED_UNEXPECTEDLY` | WebSocket 에러 발생 | `"WebSocket disconnected unexpectedly: {code}"` 또는 `"Handover failed after max retries and active socket is dead"` | | `STREAMING_FAILED` | 원격 트랙 구독 실패 — 연결은 유지되며 `onError`만 전달 | `"Failed to subscribe to remote track"` | | `STREAMING_DISCONNECTED_UNEXPECTEDLY` | 스트리밍 비정상 종료 | `"Streaming disconnected: {reason}"` 또는 `"Remote user left streaming"` | #### 서버 에러 | 코드 | 발생 시점 | message 예시 | |------|-----------|-------------| | `SERVER_ERROR` | 서버에서 `SERVER_ERROR` 시그널 수신 | 서버가 보낸 메시지 또는 `"SERVER_ERROR"` | | `WORKER_DISCONNECTED` | 서버 연결 종료 시그널 수신 | `"WORKER_DISCONNECTED"` | ## 이벤트 등록 예제 ```js const SDK = window.KlleonSDK; SDK.onStatus((status) => console.log('상태:', status)); SDK.onSignal((data) => { if (data.signal === 'RESPONSE_TEXT') { console.log('아바타:', data.payload.text); } }); SDK.onError((error) => { console.error(`[${error.code}] ${error.message}`); }); SDK.init({ sdk_key: 'YOUR_SDK_KEY', avatar_id: 'YOUR_AVATAR_ID', }).catch((e) => console.error(e.message)); ``` ```jsx import { useEffect } from 'react'; function App() { useEffect(() => { const SDK = window.KlleonSDK; SDK.onStatus((status) => console.log('상태:', status)); SDK.onSignal((data) => { if (data.signal === 'RESPONSE_TEXT') { console.log('아바타:', data.payload.text); } }); SDK.onError((error) => { console.error(`[${error.code}] ${error.message}`); }); SDK.init({ sdk_key: 'YOUR_SDK_KEY', avatar_id: 'YOUR_AVATAR_ID', }) .catch((e) => console.error(e.message)); return () => { // destroy()는 미초기화 상태에서도 안전하며, 진행 중인 init 뒤로 자동 직렬화됩니다 SDK.destroy(); }; }, []); return ; } ``` --- # avatar-container 아바타 비디오를 표시하는 Web Component입니다. 비디오 트랙이 자동으로 바인딩됩니다. ## 사용법 ```html ``` ## 속성 | 속성 | 타입 | 기본값 | 설명 | |------|------|--------|------| | `volume` | `number` | `100` | 오디오 볼륨 (0-100). 프로퍼티를 변경하면 `SDK.setVolume()`을 자동 호출합니다. HTML 속성으로 지정한 초기값은 적용되지 않습니다 — 아래 "볼륨 제어" 참고. | | `fit` | `string` | `"cover"` | 비디오 맞춤 모드. `"cover"` \| `"contain"` \| `"fill"` | ### fit 옵션 | 값 | 설명 | |----|------| | `"cover"` | 컨테이너를 꽉 채우며 비율 유지 (넘치는 부분 잘림) | | `"contain"` | 비율 유지하며 컨테이너 안에 전체 표시 (여백 발생 가능) | | `"fill"` | 비율 무시하고 컨테이너에 맞춤 | ## 이벤트 | 이벤트 | 설명 | |--------|------| | `avatar-connected` | 컴포넌트가 DOM에 마운트되고 첫 렌더가 완료된 후 발생합니다. SDK가 이 이벤트를 수신하여 비디오 트랙을 자동으로 바인딩합니다. | ## 비디오 자동 바인딩 비디오 트랙은 다음 순서로 자동 연결됩니다: 1. ``가 DOM에 추가되면 첫 렌더 완료 후 `avatar-connected` 이벤트를 발생시킵니다. 2. SDK Core가 이 이벤트를 수신하여 Agora 비디오 트랙을 컴포넌트 내부의 `.avatar` 요소에 재생합니다. 3. 비디오 트랙이 먼저 수신된 경우에도, ``가 나중에 DOM에 추가되면 자동으로 바인딩됩니다. > **WARNING** 웰컴 메시지가 설정된 아바타의 경우, ``를 `init()` 호출 **전에** DOM에 배치하세요. 웰컴 메시지는 연결 즉시 재생되므로, ``가 DOM에 없으면 영상 없이 소리만 먼저 나올 수 있습니다. ## 스타일링 > **WARNING** Shadow DOM으로 캡슐화되어 있어, `style` 속성은 컴포넌트의 크기, 위치, 테두리 등 **외부 레이아웃**에만 적용됩니다. 내부 비디오 요소의 스타일은 변경할 수 없습니다. ```html ``` ## 볼륨 제어 > **WARNING** ``처럼 HTML 속성으로 지정한 값은 **적용되지 않습니다.** 컴포넌트가 DOM에 붙는 시점은 `init()` 완료 전이라 이때의 `SDK.setVolume()` 호출은 무시되고, 연결이 끝난 뒤 다시 적용되지 않습니다. 초기 볼륨이 필요하면 연결 완료(`CONNECTED_FINISH`) 후에 지정하세요. ```js SDK.onStatus((status) => { if (status === 'CONNECTED_FINISH') { SDK.setVolume(80); } }); ``` ```js // 방법 1: 프로퍼티 변경 (내부적으로 SDK.setVolume()을 호출) document.querySelector('avatar-container').volume = 50; // 방법 2: SDK API 직접 호출 SDK.setVolume(50); ``` 두 방법 모두 동일한 결과를 냅니다. 프로퍼티를 변경하면 컴포넌트가 `SDK.setVolume()`을 자동 호출합니다. HTML 속성이 아니라 **JavaScript 프로퍼티**여야 합니다. --- # chat-container 채팅 인터페이스를 제공하는 Web Component입니다. 텍스트 입력과 음성 입력 모드를 지원합니다. ## 사용법 ```html ``` ## 속성 | 속성 | 타입 | 기본값 | 설명 | |------|------|--------|------| | `type` | `string` | `"text"` | 입력 모드. `"text"` \| `"voice"` | | `delay` | `number` | `30` | 응답 텍스트 타이핑 효과 딜레이 (글자당 ms). `0`으로 설정해도 렌더링 프레임당 1글자씩 표시됩니다(즉시 표시 아님). | | `isShowCount` | `boolean` | `true` | 텍스트 모드에서 글자 수 카운터 표시 여부 | | `scrollbar` | `boolean` | `false` | 메시지 영역 스크롤바 표시 여부. 스크롤바를 숨기려면 속성을 쓰지 않습니다. | ### boolean 속성 지정 방법 `isShowCount`와 `scrollbar`는 **HTML 속성으로 쓸 때의 해석 규칙이 서로 다릅니다.** | 속성 | 속성 생략 | `="false"` | 그 외 값 (`=""`, `="true"` 등) | |------|----------|-----------|------------------------------| | `isShowCount` | `true` (기본값) | `false` | `true` | | `scrollbar` | `false` (기본값) | **`true`** | `true` | - `isShowCount`는 `isShowCount="false"`로 끌 수 있습니다. 속성을 생략하면 기본값 `true`이며, `"false"` 외의 문자열은 모두 `true`로 해석됩니다. - `scrollbar`는 **속성이 존재하기만 하면 값과 상관없이 `true`** 입니다. 끄려면 속성 자체를 쓰지 않아야 합니다. - HTML 속성명은 대소문자를 구분하지 않으므로 `isShowCount`와 `isshowcount` 모두 동일하게 동작합니다. 브라우저 개발자 도구에는 소문자(`isshowcount`)로 표시됩니다. > **WARNING** `scrollbar`는 속성이 붙어 있다는 사실만으로 `true`가 되므로, `scrollbar="false"`라고 써도 스크롤바가 **표시됩니다.** 숨기려면 속성을 생략하거나 JavaScript 프로퍼티로 `false`를 지정하세요. ```html ``` ```html ``` ```js const el = document.querySelector('chat-container'); // 프로퍼티에는 false를 그대로 지정할 수 있습니다. el.isShowCount = false; // 카운터 숨김 el.scrollbar = false; // 스크롤바 숨김 el.scrollbar = true; // 스크롤바 표시 ``` ## 입력 모드 ### 텍스트 모드 (`type="text"`) 텍스트 입력창과 전송 버튼을 표시합니다. - **전송**: Enter 키 또는 전송 아이콘 클릭 - **줄바꿈**: Shift + Enter - **IME 처리**: 한국어/일본어 등 조합 중에는 Enter로 전송되지 않습니다. - **글자 제한**: 최대 350자 - **모바일**: 모바일 환경에서는 Enter 키가 줄바꿈으로 동작합니다 (전송 버튼 사용). - **자동 포커스**: 아바타 응답 완료 후 텍스트 입력창에 자동 포커스됩니다. 우측의 마이크 아이콘을 클릭하면 음성 모드로 전환됩니다. ### 음성 모드 (`type="voice"`) 마이크 버튼을 표시합니다. - **녹음 시작**: 마이크 버튼 클릭 - **녹음 종료**: 정지 버튼 클릭 또는 **10초** 경과 시 자동 종료 - **시각 피드백**: 녹음 중 파형 애니메이션과 카운트다운 타이머 표시 - 내부적으로 `SDK.startListening()`, `SDK.endListening()`을 호출합니다. 좌측의 키보드 아이콘을 클릭하면 텍스트 모드로 전환됩니다. ## 메시지 표시 - SDK의 메시지 목록(`sendMessage`, `speak`, STT 결과, 아바타 응답)을 자동으로 표시합니다. - 가상 스크롤링을 사용하여 대량의 메시지도 성능 저하 없이 렌더링합니다. - 새 메시지 수신 시 자동으로 하단으로 스크롤됩니다. ### 타이핑 효과 - 아바타 응답(response) 메시지는 `delay` 속성에 지정된 간격으로 한 글자씩 표시됩니다. - 사용자 메시지(request)는 타이핑 효과 없이 즉시 표시됩니다. - 새 메시지가 도착하면 진행 중인 타이핑이 즉시 완료되고 새 메시지의 타이핑이 시작됩니다. ### 로딩 표시 아바타가 응답을 준비 중일 때(`RESPONSE_PREPARING` 시그널) 마지막 사용자 메시지 아래에 로딩 애니메이션이 표시됩니다. ## 비활성 상태 SDK 상태가 `CONNECTED_FINISH`가 아니거나, 아바타가 응답 중일 때 입력이 비활성화됩니다. 다음 시그널이 수신되면 입력이 다시 활성화됩니다: - `RESPONSE_ENDED` — 아바타 응답 완료 - `ERROR` — 에러 수신 - `REJECTED` — 요청 거절 ## 스타일링 > **WARNING** Shadow DOM으로 캡슐화되어 있어, `style` 속성은 컴포넌트의 크기, 위치, 테두리 등 **외부 레이아웃**에만 적용됩니다. 내부 요소(배경색, 메시지 버블 등)의 스타일은 변경할 수 없습니다. ```html ``` --- # 에러 처리 SDK 에러는 3가지 경로로 전달됩니다: 1. **init 에러** — `init()`이 `Error` 예외를 발생시킴 2. **런타임 에러** — `onError` 콜백으로 전달 3. **처리 에러** — `onSignal`에서 `ERROR`/`REJECTED` 시그널로 전달 (세션 유지) ## 에러 코드 init 실패는 에러 코드 없이 `init()`의 예외로 전달됩니다. | 에러 코드 | 분류 | 설명 | |-----------|------|------| | `STREAMING_FAILED` | 런타임 | 원격 트랙 구독 실패 — 연결은 유지됨 | | `SOCKET_DISCONNECTED_UNEXPECTEDLY` | 런타임 | WebSocket 비정상 종료 | | `STREAMING_DISCONNECTED_UNEXPECTEDLY` | 런타임 | 네트워크 끊김 등으로 Agora 연결 끊김 | | `SERVER_ERROR` | 런타임 | 서버에서 SERVER_ERROR 시그널 수신 | | `WORKER_DISCONNECTED` | 런타임 | 서버 연결 종료 시그널 수신 | ## init 에러 `init()`은 실패하면 예외를 발생시킵니다. SDK는 내부 연결 리소스를 정리하고 `IDLE` 상태로 돌아갑니다. ```js try { await SDK.init(options); } catch (error) { console.error(error.message); } ``` ```jsx SDK.init({ sdk_key: 'YOUR_SDK_KEY', avatar_id: 'YOUR_AVATAR_ID' }) .catch((error) => console.error(error.message)); ``` 초기화 실패 자체는 `onError`가 아니라 `init()`의 예외(reject)로 전달됩니다. 다만 연결 과정 중 스트리밍 쪽 문제로 `onError`가 함께 발생할 수 있으므로, `onError` 등록은 `init()` 호출 전에 해두는 것이 안전합니다. | message | 원인 | |---------|------| | `"Failed to connect: ..."` | SDK 키 검증 또는 WebSocket 연결 실패 | | `"Failed to connect streaming: ..."` | 스트리밍 연결 실패 | | `"Init failed: ..."` | 그 외 초기화 실패 | ## 런타임 에러 init 이후 연결 중 발생하는 에러입니다. `onError` 콜백으로 전달됩니다. 런타임 에러 발생 시 SDK는 자동으로 연결을 정리하고 `IDLE` 상태로 복귀합니다. 클라이언트는 `onError` + `onStatus(IDLE)`를 수신한 후 `init()`으로 재연결할 수 있습니다. 단, `STREAMING_FAILED`(원격 트랙 구독 실패)는 예외입니다 — 연결 정리와 `IDLE` 복귀 없이 `onError`만 전달됩니다. ### 에러별 상태 흐름 | 에러 코드 | 발생 시점 | 상태 전이 | |-----------|-----------|-----------| | (init 예외) | init 중 | `CONNECTING` 또는 `SOCKET_CONNECTED` → `IDLE` | | `STREAMING_FAILED` | 런타임 | 상태 전이 없음 — `onError`만 전달, 연결 유지 | | `SOCKET_DISCONNECTED_UNEXPECTEDLY` | 런타임 | `CONNECTED_FINISH` → 연결 리소스 정리 → `IDLE` | | `STREAMING_DISCONNECTED_UNEXPECTEDLY` | 런타임 | `CONNECTED_FINISH` → 연결 리소스 정리 → `IDLE` | | `SERVER_ERROR` | 런타임 | `CONNECTED_FINISH` → 연결 리소스 정리 → `IDLE` | | `WORKER_DISCONNECTED` | 런타임 | `CONNECTED_FINISH` → 연결 리소스 정리 → `IDLE` | init 에러는 `init()`이 예외로 발생시키고, 런타임 에러는 `onError` 콜백 + `onStatus(IDLE)`로 전달됩니다. ### 에러 처리 예시 ```js SDK.onError((error) => { console.error('SDK Error:', error.code, error.message); }); ``` | 에러 코드 | message | 원인 | |-----------|---------|------| | `STREAMING_FAILED` | `"Failed to subscribe to remote track"` | 원격 트랙 구독 실패 — 연결 유지, `onError`만 전달 | | `SOCKET_DISCONNECTED_UNEXPECTEDLY` | `"WebSocket disconnected unexpectedly: {code}"` 또는 `"Handover failed after max retries and active socket is dead"` | WebSocket 비정상 종료 | | `STREAMING_DISCONNECTED_UNEXPECTEDLY` | `"Streaming disconnected: {reason}"` 또는 `"Remote user left streaming"` | 네트워크 끊김 등 Agora 연결 끊김 | | `SERVER_ERROR` | 서버가 보낸 메시지 또는 `"SERVER_ERROR"` | 서버에서 SERVER_ERROR 시그널 수신 | | `WORKER_DISCONNECTED` | 서버가 보낸 메시지 또는 `"WORKER_DISCONNECTED"` | 서버 연결 종료 시그널 수신 | ## 처리 에러 시그널 `ERROR`, `REJECTED` 시그널은 **세션을 종료하지 않습니다**. 개별 요청에 대한 실패를 나타냅니다. `onSignal`에서 수신합니다 (`onError`가 아님). ```js SDK.onSignal((data) => { if (data.signal === 'ERROR') { // 서버 처리 중 오류 console.warn('에러:', data.payload?.type); } if (data.signal === 'REJECTED') { // 입력이 비정상적 console.warn('거절:', data.payload?.type); } }); ``` | 시그널 | payload.type 예시 | 설명 | |--------|------------------|------| | `ERROR` | `STT_ERROR`, `LLM_ERROR`, `TTS_ERROR` | 서버 내부 처리 오류 | | `REJECTED` | `STT_EMPTY`, `MODERATION` | 입력 검증 실패 또는 정책 위반 | --- # TypeScript 지원 Klleon SDK는 UMD 번들로 제공되므로 TypeScript 프로젝트에서 사용하려면 타입 정의 파일을 추가해야 합니다. ## 타입 정의 파일 프로젝트 루트에 `KlleonSDK.d.ts` 파일을 생성합니다. > **WARNING** 아래 타입 정의는 **SDK 2.0.0** 기준입니다. 사용 중인 SDK 버전을 올린 뒤에는 각 API 문서와 대조하여 타입 정의를 다시 확인하세요. ```typescript /** Klleon SDK 2.0.0 기준 타입 정의 */ /** SDK 연결 상태 */ type SDKStatus = | "IDLE" | "CONNECTING" | "SOCKET_CONNECTED" | "STREAMING_CONNECTED" | "CONNECTED_FINISH"; type LogLevel = "debug" | "info" | "warn" | "error" | "silent"; type LanguageCode = "ko_kr" | "en_us" | "ja_jp" | "id_id"; /** init() 옵션 */ interface InitOption { sdk_key: string; avatar_id: string; log_level?: LogLevel; language?: LanguageCode; enable_microphone?: boolean; auto_send?: boolean; stt_only?: boolean; } /** speak() 옵션 */ interface SpeakOption { text: string; } /** onSignal 콜백 파라미터 */ interface IncomingSignalData { signal: string; payload?: Record; id: string; type: "SESSION" | "INPUT" | "OUTPUT"; } /** onError 콜백 파라미터 */ interface ErrorData { code: string; message?: string; } /** SDK 인터페이스 */ interface KlleonSDK { // 생명주기 init: (option: InitOption) => Promise; destroy: () => Promise; // 이벤트 onSignal: (cb: (data: IncomingSignalData) => void) => void; onStatus: (cb: (status: SDKStatus) => void) => void; onError: (cb: (error: ErrorData) => void) => void; // 텍스트 sendMessage: (text: string) => void; speak: (option: string | SpeakOption) => void; // 오디오 립싱크 전송 sendSpeakAudio: (audio: string, sample_rate?: number) => void; endSpeakAudio: () => void; // 음성 startListening: () => void; endListening: () => void; cancelListening: () => void; // 아바타 제어 stopSpeaking: () => void; // 미디어 setVolume: (volume: number) => void; // 상태 조회 getStatus: () => SDKStatus; clearMessages: () => void; } declare global { interface Window { KlleonSDK: KlleonSDK; } } // React에서 Web Component 사용 시 타입 에러 방지 (React 18/19 모두 동작) declare module "react" { namespace JSX { interface IntrinsicElements { "avatar-container": React.DetailedHTMLProps< React.HTMLAttributes & { volume?: number; fit?: "cover" | "contain" | "fill"; }, HTMLElement >; "chat-container": React.DetailedHTMLProps< React.HTMLAttributes & { type?: "text" | "voice"; delay?: number; // false를 지정하면 글자 수 카운터가 숨겨집니다. isShowCount?: boolean; // 스크롤바를 숨기려면 값을 넘기지 말고 prop 자체를 생략하세요. // HTML 속성으로 전달되면 "false"도 true로 해석됩니다. scrollbar?: boolean; }, HTMLElement >; } } } export type { SDKStatus, LogLevel, LanguageCode, InitOption, SpeakOption, IncomingSignalData, ErrorData, KlleonSDK, }; ``` `isShowCount`, `scrollbar`의 boolean 해석 규칙은 [chat-container](/ko/components/chat-container) 문서의 "boolean 속성 지정 방법"을 참고하세요. ## tsconfig.json 설정 타입 정의 파일을 TypeScript가 인식하도록 `include`에 추가합니다. ```json { "compilerOptions": { "jsx": "react-jsx" }, "include": ["src", "KlleonSDK.d.ts"] } ``` ## 사용 예시 타입 정의를 추가하면 `window.KlleonSDK`에 대한 자동 완성과 타입 검사가 동작합니다. ```tsx const SDK = window.KlleonSDK; SDK.onStatus((status) => console.log(status)); SDK.onSignal((data) => console.log(data.signal, data.payload)); SDK.init({ sdk_key: 'YOUR_SDK_KEY', avatar_id: 'YOUR_AVATAR_ID', }).catch((e) => console.error(e.message)); ``` --- # v1에서 v2 마이그레이션 ## CDN 경로 변경 ```diff - + ``` ## 글로벌 변수 변경 ```diff - window.KlleonChat + window.KlleonSDK ``` ## API 변경사항 ### 호환 API (그대로 사용 가능) - ``, `` — 동일 ### init(option) 변경사항 `init()` 호출 방식은 동일하지만, 옵션이 변경되었습니다. | v1 옵션 | v2 옵션 | 비고 | |---------|---------|------| | `sdk_key` | `sdk_key` | 동일 | | `avatar_id` | `avatar_id` | 동일 | | `enable_microphone` | `enable_microphone` | 동일 | | `log_level` | `log_level` | 동일 | | `voice_code` | — | 제거됨 | | `subtitle_code` | — | 제거됨. `language`로 통합 | | `mode` | — | 제거됨 | | `voice_tts_speech_speed` | — | 제거됨 | | `custom_id` | — | 제거됨 | | `user_key` | — | 제거됨 | | — | `language` | 신규. TTS 음성·자막 및 STT 인식 언어 통합 (`"ko_kr"`, `"en_us"`, `"ja_jp"`, `"id_id"`) | | — | `auto_send` | 신규. `true` 시 서버 VAD 자동 응답 | | — | `stt_only` | 신규. `true` 시 STT 결과만 반환 | ### 제거된 API | v1 | 대체 방법 | |----|-----------| | `reconnect()` | `await destroy()` → `await init(options)` | | `changeAvatar(option)` | 제거됨 | | `wakeUpAvatar()` | 제거됨 | ### 변경된 API (이름 변경) | v1 | v2 | 비고 | |----|----|------| | `onChatEvent(cb)` | `onSignal(cb)` | 서버 시그널 수신 (`SERVER_ERROR`·`WORKER_DISCONNECTED`는 `onError`로 분리) | | `onStatusEvent(cb)` | `onStatus(cb)` | | | `onErrorEvent(cb)` | `onError(cb)` | | | `destroy()` (sync) | `await destroy()` | async 변경 | | `sendTextMessage(text)` | `sendMessage(text)` | | | `echo(text)` | `speak(text)` | | | `startAudioEcho(audio)` | `sendSpeakAudio(audio)` | | | `endAudioEcho()` | `endSpeakAudio()` | | | `startStt()` | `startListening()` | | | `endStt()` | `endListening()` | | | `cancelStt()` | `cancelListening()` | | | `stopSpeech()` | `stopSpeaking()` | | | `clearMessageList()` | `clearMessages()` | | ### 새로운 API | API | 설명 | |-----|------| | `setVolume(volume)` | 볼륨 제어 (0-100) | | `getStatus()` | 현재 SDK 상태 조회 | ### 코드 변경 예시 ```diff // 글로벌 변수 변경 - const SDK = window.KlleonChat; + const SDK = window.KlleonSDK; // destroy가 async로 변경 - SDK.destroy(); + await SDK.destroy(); // 이벤트 이름 변경 + init 전에 등록 - SDK.onStatusEvent(cb); + SDK.onStatus(cb); - SDK.onChatEvent(cb); + SDK.onSignal(cb); - SDK.onErrorEvent(cb); + SDK.onError(cb); await SDK.init(option); // 메서드 이름 변경 - SDK.sendTextMessage('안녕하세요'); + SDK.sendMessage('안녕하세요'); - SDK.echo('반복할 텍스트'); + SDK.speak('반복할 텍스트'); - SDK.startStt(); + SDK.startListening(); - SDK.endStt(); + SDK.endListening(); - SDK.stopSpeech(); + SDK.stopSpeaking(); ``` ## 주의사항 - v2는 ES2018+ 환경이 필요합니다 - IE11은 미지원입니다 - `destroy()`는 가능한 곳에서는 `await`를 권장합니다. React cleanup처럼 `await`할 수 없는 곳에서는 호출만으로 안전합니다(내부에서 직렬화됨) --- # Vanilla JS 예제 `index.html` 파일 하나로 SDK를 연동하는 전체 예제입니다. `{VERSION}`, `YOUR_SDK_KEY`, `YOUR_AVATAR_ID`를 실제 값으로 변경 후 브라우저에서 실행합니다. ```html Klleon SDK - Vanilla JS

Klleon SDK - Vanilla JS

API 직접 호출 데모

sendMessage()는 LLM 응답을 생성하고, speak()는 입력한 텍스트를 그대로 읽습니다.

``` --- # React 예제 ## 사전 준비 `index.html`의 ``에 SDK 스크립트를 추가합니다. ```html ``` ## 기본 예제 SDK Web Component를 사용하는 기본 React 예제입니다. > **TIP** SDK는 내부 Mutex로 `init()`/`destroy()` 호출을 직렬화합니다. React StrictMode의 이중 mount(mount → unmount → remount)에서도 cleanup의 `destroy()`와 두 번째 `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 (

상태: {status}

{/* 대화 영역 — chat-container가 자체 입력 UI로 sendMessage()를 전송합니다 */} {/* speak() 직접 호출 데모 */}

speak() 직접 호출 데모

입력한 텍스트를 아바타가 그대로 읽습니다 (LLM 응답 없음). 대화 메시지는 위 chat-container 입력창을 사용하세요.

setSpeakText(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && !e.nativeEvent.isComposing && handleSpeak()} placeholder="speak 내용 입력..." disabled={!isReady} />
); } export default App; ``` > **TIP** TypeScript에서 Web Component 사용 시 타입 에러가 발생하면 [TypeScript 지원](/ko/guides/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 (
{/* 아바타 영역 */} {/* 채팅 영역 (직접 구현) */}

상태: {status}

{messages.map((m, i) => (
{m.text}
))}
{/* 컨트롤 */}
setText(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && !e.nativeEvent.isComposing && send()} placeholder={isSpeaking ? '아바타 발화 중...' : '메시지 입력'} disabled={!isReady || isSpeaking} style={{ flex: 1, padding: 8 }} />
); } export default CustomChat; ``` --- # React Native ::: danger Experimental 이 패키지는 실험 단계입니다. API가 예고 없이 변경될 수 있으며, 프로덕션 환경에서의 안정성이 보장되지 않습니다. > **WARNING** 네이티브 모듈(`react-native-agora`)을 사용하므로 **Expo Development Build** (EAS Build 또는 로컬 빌드) 또는 **bare workflow**에서만 동작합니다. Expo Go와 Snack에서는 사용할 수 없습니다. React Native용 Klleon AI 아바타 SDK (`@klleon/sdk-rn`)는 핵심 대화 흐름(연결 → 발화 → 응답)이 Web SDK와 같지만, 일부 메서드는 시그니처가 다릅니다. 네이티브 모듈 연동 같은 플랫폼 차이는 대부분 SDK 내부에서 처리하지만, 앱 코드에서 다르게 써야 하는 API는 아래 "Web SDK와의 차이"에 정리했습니다. ## SDK 요청 React Native SDK는 별도 요청을 통해 제공됩니다. 담당자에게 문의하여 SDK 패키지를 받으세요. ## 요구사항 | 항목 | 버전 | |------|------| | React Native | >= 0.73 (0.76+ 권장) | | React | >= 18 | | react-native-agora | >= 4.3 (4.5+ 권장) | | Expo | Development Build 필수 (Expo Go / Snack 불가) | ## 설치 ### 1. SDK 패키지 설치 제공받은 `.tgz` 파일을 프로젝트 루트에 두고 설치합니다. ```bash # SDK 설치 npm install ./klleon-sdk-rn-x.x.x.tgz # Agora 네이티브 모듈 설치 npm install react-native-agora ``` 설치 후 `package.json`에 다음과 같이 추가됩니다: ```json { "dependencies": { "@klleon/sdk-rn": "file:klleon-sdk-rn-x.x.x.tgz", "react-native-agora": "^4.3.0" } } ``` ### 2. 플랫폼별 설정 #### iOS ```bash cd ios && pod install ``` `Info.plist`에 마이크 권한 설명 추가: ```xml NSMicrophoneUsageDescription 음성 대화를 위해 마이크 접근이 필요합니다. ``` #### Android `android/app/src/main/AndroidManifest.xml`에 권한 추가: ```xml ``` #### Expo (Development Build) Expo Go에서는 네이티브 모듈을 사용할 수 없으므로 Development Build가 필요합니다. ```bash # 네이티브 모듈 설치 npx expo install react-native-agora # 네이티브 프로젝트 생성 npx expo prebuild # 로컬 빌드 실행 npx expo run:ios # 또는 npx expo run:android ``` EAS Build를 사용하는 경우: ```bash eas build --profile development --platform ios # 또는 android ``` ### 3. SDK 업데이트 새 버전의 `.tgz`를 받으면 기존 파일을 교체하고 다시 설치합니다. ```bash npm install ./klleon-sdk-rn-x.x.x.tgz ``` ## 빠른 시작 ```tsx import React, { useEffect } from "react"; import { View } from "react-native"; import { useKlleonSDK, KlleonAvatar } from "@klleon/sdk-rn"; export default function App() { const { status, init, destroy, sendMessage } = useKlleonSDK(); useEffect(() => { init({ sdk_key: "YOUR_SDK_KEY", avatar_id: "YOUR_AVATAR_ID" }).catch(console.error); return () => { destroy(); }; }, []); return ( ); } ``` ## API ### KlleonSDK (싱글톤) ```typescript import KlleonSDK from "@klleon/sdk-rn"; ``` | 분류 | 메서드 | 설명 | |------|--------|------| | **생명주기** | `init(options)` | SDK 초기화 및 연결 | | | `destroy()` | SDK 종료 및 리소스 해제 | | **콜백** | `onSignal(cb)` | 서버 시그널 수신 | | | `onStatus(cb)` | SDK 상태 변경 | | | `onError(cb)` | 에러 발생 | | **텍스트** | `sendMessage(text)` | 텍스트 전송 (LLM 응답 트리거) | | | `speak(text)` | 텍스트 에코 (아바타가 그대로 읽음) | | **오디오 립싱크 전송** | `sendSpeakAudio(base64)` | Base64 오디오 전송 | | | `endSpeakAudio()` | 오디오 립싱크 전송 종료 | | **음성** | `startListening()` | 음성 입력 시작 (수동 전송, Push-to-Send) | | | `endListening()` | 음성 입력 종료 | | | `cancelListening()` | 음성 입력 취소 | | **아바타** | `stopSpeaking()` | 아바타 발화 중지 | | | `setVolume(0~100)` | 아바타 음량 조절 | | **조회** | `getVersion()` | SDK 버전 | | | `getStatus()` | 현재 상태 | | | `clearMessages()` | 메시지 초기화 | 콜백은 `init()` 전에 등록 가능합니다. ### useKlleonSDK (React Hook) Valtio snapshot 기반으로 상태 변경 시 자동 리렌더링됩니다. ```typescript import { useKlleonSDK } from "@klleon/sdk-rn"; const { // 반응형 상태 status, remoteUid, messages, signal, // 생명주기 init, destroy, // 메서드 sendMessage, speak, startListening, endListening, stopSpeaking, setVolume, // 콜백 onSignal, onStatus, onError, } = useKlleonSDK(); ``` ### KlleonAvatar 아바타 비디오를 표시하는 컴포넌트입니다. SDK 연결 전에는 검정 배경을 표시합니다. ```tsx ``` | Prop | 타입 | 기본값 | 설명 | |------|------|--------|------| | `width` | `DimensionValue` | - | 너비 (`300` 또는 `"100%"`) | | `height` | `DimensionValue` | - | 높이 (`400` 또는 `"100%"`) | | `renderMode` | `"cover" \| "contain"` | `"cover"` | 비디오 fit 방식 | | `borderRadius` | `number` | `0` | 모서리 둥글기 | ## 초기화 옵션 | 옵션 | 타입 | 기본값 | 설명 | |------|------|--------|------| | `sdk_key` | `string` | - | SDK 인증 키 (필수) | | `avatar_id` | `string` | - | 아바타 ID (필수) | | `language` | `LanguageCode` | `"ko_kr"` | TTS 음성·자막 및 STT 인식 언어 (`ko_kr`, `en_us`, `ja_jp`, `id_id`) | | `log_level` | `LogLevel` | `"warn"` | 로그 레벨 | | `enable_microphone` | `boolean` | `true` | 마이크 활성화 | | `auto_send` | `boolean` | `false` | `true`: VAD 자동 응답, `false`: 수동 전송 | | `stt_only` | `boolean` | `false` | `true`: STT 결과만, `false`: STT + 아바타 응답 | ## 상태 흐름 ``` IDLE → CONNECTING → SOCKET_CONNECTED → STREAMING_CONNECTED → CONNECTED_FINISH ``` | 상황 | 흐름 | |------|------| | 정상 종료 | `destroy()` → `IDLE` | | 에러 발생 | 자동으로 연결 리소스 정리 → `IDLE` | | 재연결 | `onError` + `onStatus(IDLE)` 수신 후 `init()` 재호출 | ## 연동 예제 ### 권한 요청 (Android) ```typescript import { PermissionsAndroid } from "react-native"; await PermissionsAndroid.requestMultiple([ PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, ]); ``` ### 에러 복구 ```typescript KlleonSDK.onError((error) => { console.log("SDK error:", error.code); // onStatus(IDLE)이 따라옴 → init() 재호출로 재연결 가능 }); ``` ### 수동 전송 ```tsx KlleonSDK.startListening()} onPressOut={() => KlleonSDK.endListening()} > 길게 눌러서 말하기 ``` ## 에러 코드 초기화 실패는 에러 코드 없이 `init()`의 예외(reject)로 전달됩니다. 아래 코드는 연결 이후 런타임에 `onError`로 전달됩니다. | 코드 | 설명 | |------|------| | `SOCKET_DISCONNECTED_UNEXPECTEDLY` | 런타임 WebSocket 끊김 | | `STREAMING_DISCONNECTED_UNEXPECTEDLY` | 런타임 Agora 끊김 | | `SERVER_ERROR` | 서버 에러 시그널 | | `WORKER_DISCONNECTED` | 워커 종료 시그널 | ## 타입 ```typescript // Enum import { SDKStatus, ErrorCode, IncomingSignal, OutgoingSignal } from "@klleon/sdk-rn"; // Type import type { InitOption, IncomingSignalData, OutgoingSignalData, ErrorData, MessageItem, LogLevel, LanguageCode, } from "@klleon/sdk-rn"; ``` ## Web SDK와의 차이 | 항목 | Web (sdk-klleon) | RN (sdk-rn) | |------|-----------------|-------------| | 비디오 | agora-rtc-sdk-ng | react-native-agora | | `speak` 시그니처 | `speak(문자열 또는 옵션 객체)` | `speak(문자열)`만 지원 | | UI | Lit 커스텀 엘리먼트 | `KlleonAvatar` 컴포넌트 | | Hook | 없음 | `useKlleonSDK` | | 설치 | `