React
사전 준비
1. SDK 스크립트 추가
public/index.html
파일의 <head>
태그 내에 SDK 스크립트를 추가합니다.
public/index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
{/* ... 기타 메타 태그 및 링크 ... */}
<title>React + Klleon Chat SDK</title>
{/* TODO: {VERSION}을 실제 SDK 버전으로 변경하세요. (예: 1.2.0) */}
<script src="https://web.sdk.klleon.io/{VERSION}/klleon-chat.umd.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
2. TypeScript 타입 지원 (선택 사항)
Klleon Chat SDK는 TypeScript 타입 정의를 제공할 수 있습니다. 프로젝트에 해당 타입 정의(.d.ts) 파일이 포함되어 있다면, window.KlleonChat
전역 객체 및 SDK 메서드, 웹 컴포넌트 props 등에 대한 타입 추론과 자동 완성을 활용할 수 있습니다.
React 예제 (App.tsx
)
CSS 스타일
custom.css
.base-react-example-page {
width: 100%;
height: 100%;
.header {
margin-bottom: 30px;
}
.loading-text {
position: absolute;
font-size: 1.5em;
color: #555;
padding: 50px;
}
.start-chat-button {
width: 150px;
height: 50px;
background-color: #0c5ef0;
color: #fff;
border-radius: 10px;
font-size: 16px;
font-weight: 600;
border: none;
cursor: pointer;
}
.sdk-container {
display: flex;
width: 100%;
height: 720px;
gap: 0px 24px;
.avatar-container {
flex: 1;
}
.chat-control-container {
display: flex;
flex-direction: column;
flex: 1;
gap: 24px 0px;
.chat-container {
flex: 1;
border-radius: 24px;
}
.chat-echo-container {
display: flex;
flex-direction: column;
.echo-input {
width: 100%;
height: 40px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 0 10px;
font-size: 16px;
margin-bottom: 10px;
}
.echo-button {
width: 100%;
height: 40px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 0 10px;
background-color: #0c5ef0;
color: #fff;
font-size: 16px;
font-weight: 600;
cursor: pointer;
&:disabled {
background-color: #ccc;
color: #fff;
cursor: not-allowed;
}
}
}
}
}
}
src/App.tsx
import { ChatData, Status } from "@site/src/types/global";
import { useEffect, useRef, useState, CSSProperties } from "react";
// TODO: 실제 SDK 키와 아바타 ID로 변경하세요.
const SDK_KEY = "YOUR_SDK_KEY";
const AVATAR_ID = "YOUR_AVATAR_ID";
interface AvatarProps {
videoStyle?: CSSProperties;
volume?: number;
}
interface ChatProps {
delay?: number;
type?: "voice" | "text";
isShowCount?: boolean;
}
function App() {
const [echoText, setEchoText] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [isChatStarted, setIsChatStarted] = useState(false);
const avatarContainerRef = useRef<HTMLElement & AvatarProps>(null);
const chatContainerRef = useRef<HTMLElement & ChatProps>(null);
useEffect(() => {
if (avatarContainerRef.current) {
avatarContainerRef.current.videoStyle = {
borderRadius: "24px",
objectFit: "cover",
};
avatarContainerRef.current.volume = 100;
}
if (chatContainerRef.current) {
chatContainerRef.current.delay = 10;
chatContainerRef.current.type = "text";
chatContainerRef.current.isShowCount = true;
console.log("chat-container props set via ref.");
}
}, []);
const startChat = async () => {
setIsChatStarted(true);
const KlleonChat = window.KlleonChat;
console.log("SDK 로드 확인됨. 초기화를 시도합니다...");
KlleonChat.onStatusEvent((status: Status) => {
console.log(`SDK Status Event: ${status}`);
setIsLoading(status !== "VIDEO_CAN_PLAY");
});
KlleonChat.onChatEvent((chatData: ChatData) => {
console.log("SDK Chat Event:", chatData);
});
try {
await KlleonChat.init({
sdk_key: SDK_KEY,
avatar_id: AVATAR_ID,
log_level: "debug",
});
console.log("SDK 초기화 성공!");
} catch (error) {
console.error(`SDK 초기화 실패: ${(error as Error).message || error}`);
setIsLoading(false);
} finally {
setIsLoading(false);
}
};
const handleEcho = () => {
window.KlleonChat.echo(echoText);
setEchoText("");
};
return (
<div className="base-react-example-page">
{!isChatStarted && (
<button
onClick={startChat}
disabled={isLoading}
className="start-chat-button"
>
Start Chat
</button>
)}
<div className="sdk-container">
{isLoading && (
<span className="loading-text">Klleon Avatar Loading...</span>
)}
<avatar-container
ref={avatarContainerRef}
class="avatar-container" // className 속성 사용 불가 class 속성 사용
/>
<div className="chat-control-container">
<chat-container
ref={chatContainerRef}
class="chat-container" // className 속성 사용 불가 class 속성 사용
/>
<div className="chat-echo-container">
<input
type="text"
value={echoText}
onChange={(e) => setEchoText(e.target.value)}
placeholder="echo 내용 입력..."
disabled={isLoading}
className="echo-input"
/>
<button
onClick={handleEcho}
disabled={isLoading}
className="echo-button"
>
echo 전송
</button>
</div>
</div>
</div>
</div>
);
}
export default App;