Skip to content

Electron ​

Experimental

이 패키지는 실험 단계입니다. API가 예고 없이 변경될 수 있으며, 프로덕션 환경에서의 안정성이 보장되지 않습니다.

Electron용 Klleon AI 아바타 SDK (@klleon/sdk-electron)는 핵심 대화 흐름(연결 → 발화 → 응답)이 Web SDK와 같지만, 일부 메서드는 시그니처가 다릅니다. 앱 코드에서 다르게 써야 하는 API는 아래 "Web SDK와의 차이"에 정리했습니다. 데스크톱 네이티브 기능(IPC, 클립보드, 트레이 등)과도 함께 사용할 수 있습니다.

SDK 요청 ​

Electron SDK는 별도 요청을 통해 제공됩니다. 담당자에게 문의하여 SDK 패키지를 받으세요.

요구사항 ​

항목버전
Electron>= 33
Node.js>= 20
agora-electron-sdk>= 4.3.2

Vite 5 필수 — Vite 6은 Electron Forge와 호환되지 않습니다.

설치 ​

1. SDK 패키지 설치 ​

제공받은 .tgz 파일을 프로젝트 루트에 두고 설치합니다.

bash
# SDK 설치
npm install ./klleon-sdk-electron-x.x.x.tgz

# Agora 네이티브 모듈 설치
npm install agora-electron-sdk

설치 후 package.json에 다음과 같이 추가됩니다:

json
{
  "dependencies": {
    "@klleon/sdk-electron": "file:klleon-sdk-electron-x.x.x.tgz",
    "agora-electron-sdk": "^4.3.2"
  }
}

2. SDK 업데이트 ​

새 버전의 .tgz를 받으면 기존 파일을 교체하고 다시 설치합니다.

bash
npm install ./klleon-sdk-electron-x.x.x.tgz

3. Vite 설정 ​

@klleon/sdk-electron은 순수 JavaScript 패키지(.node 바이너리 없음)이므로 Vite가 렌더러 번들에 포함시켜 tree shaking합니다. external에 넣으면 패키징된 앱에서 모듈을 찾지 못합니다.

C++ 바이너리가 포함된 네이티브 모듈(agora-electron-sdk)과 Electron 내장 모듈(electron)만 external로 설정합니다.

typescript
// vite.renderer.config.ts
import { defineConfig } from "vite";

export default defineConfig({
  optimizeDeps: {
    exclude: ["agora-electron-sdk", "electron"],
    // ❌ "@klleon/sdk-electron"은 여기 넣지 않기
  },
  build: {
    rollupOptions: {
      external: ["agora-electron-sdk", "electron"],
      // ❌ "@klleon/sdk-electron"은 여기 넣지 않기
    },
  },
});

ESM Tree Shaking

@klleon/sdk-electron은 ESM으로 빌드됩니다. Vite가 번들링하면서 사용하지 않는 코드를 자동으로 제거(tree shaking)합니다.

패키징 (DMG / EXE) ​

Electron Forge로 배포용 패키지를 만들 수 있습니다.

알려진 이슈 ​

Electron Forge Vite 플러그인 버그

@electron-forge/plugin-vite는 패키징 시 node_modules를 출력에 포함하지 않습니다. Vite가 모든 코드를 번들링했다고 판단하지만, external로 설정한 네이티브 모듈(agora-electron-sdk)과 그 의존성은 번들에 포함되지 않아 런타임에 로드 실패합니다.

이 문제는 Electron Forge에 공식 등록된 버그입니다:

해결: forge.config.ts 설정 ​

Forge 공식 훅 packageAfterCopy로 node_modules를 패키징 결과에 복사합니다. 복사 후 Forge가 자동으로 devDependencies를 제거(prune)합니다.

typescript
import type { ForgeConfig } from "@electron-forge/shared-types";
import { VitePlugin } from "@electron-forge/plugin-vite";
import { AutoUnpackNativesPlugin } from "@electron-forge/plugin-auto-unpack-natives";
import path from "path";
import fs from "fs";

const config: ForgeConfig = {
  packagerConfig: {
    asar: {
      // 네이티브 .node 파일은 asar 밖에서 로드해야 함
      unpack: "**/node_modules/agora-electron-sdk/**",
    },
    // assets 디렉토리를 Contents/Resources/에 복사
    extraResource: ["./assets"],
  },
  hooks: {
    packageAfterCopy: async (_config, buildPath) => {
      // Forge Vite 플러그인 버그 (#3738) 워크어라운드
      // 복사 후 Forge가 자동으로 devDependencies prune 처리
      fs.cpSync(
        path.join(__dirname, "node_modules"),
        path.join(buildPath, "node_modules"),
        { recursive: true },
      );
    },
  },
  plugins: [
    // 네이티브 .node 파일을 asar에서 자동 추출
    new AutoUnpackNativesPlugin({}),
    new VitePlugin({
      build: [
        { entry: "electron/main.ts", config: "vite.main.config.ts", target: "main" },
        { entry: "electron/preload.ts", config: "vite.preload.config.ts", target: "preload" },
      ],
      renderer: [
        { name: "main_window", config: "vite.renderer.config.ts" },
      ],
    }),
  ],
  makers: [
    { name: "@electron-forge/maker-dmg", config: {} },
    { name: "@electron-forge/maker-squirrel", config: {} },
    { name: "@electron-forge/maker-deb", config: {} },
  ],
};

export default config;

빌드 명령어 ​

bash
# 개발
npm run dev          # electron-forge start (HMR)

# 패키징
npm run make         # DMG/EXE/DEB 생성

API ​

KlleonSDK (싱글톤) ​

typescript
import { KlleonSDK } from "@klleon/sdk-electron";
분류메서드설명
생명주기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)아바타 음량 조절
비디오setContainer(container)비디오 렌더링 컨테이너 설정 (<avatar-container> 사용 시 자동)
조회getVersion()SDK 버전
getStatus()현재 상태
clearMessages()메시지 초기화

비디오 렌더링 ​

<avatar-container> (권장) ​

HTML에 <avatar-container>를 넣으면 SDK 연결 시 자동으로 비디오가 렌더링됩니다. 내부적으로 setContainer()를 호출하고, 원격 유저 합류 시 <agora-view>를 생성·삽입합니다.

html
<avatar-container></avatar-container>

fit 속성은 현재 버전(0.1.0)에서는 적용되지 않습니다 — 비디오 렌더 모드가 내부적으로 고정돼 있습니다.

setContainer() (수동) ​

커스텀 엘리먼트 대신 직접 컨테이너를 지정할 수도 있습니다. init() 전에 호출해도 됩니다.

typescript
KlleonSDK.setContainer(document.getElementById("my-avatar")!);

agora-electron-sdk 참고

Agora 공식 문서에서는 macOS 패키징 시 심볼 충돌 방지를 위해 import 대신 require를 권장합니다. @klleon/sdk-electron은 내부적으로 new Function("module", "return require(module)")을 사용하여 Vite 번들링을 우회하고 런타임에 네이티브 모듈을 로드합니다.

sdkState (반응형 상태) ​

Valtio proxy로 상태를 구독할 수 있습니다.

이 예제는 valtio 패키지를 직접 설치해야 합니다: npm i valtio

typescript
import { sdkState } from "@klleon/sdk-electron";
import { subscribe } from "valtio/vanilla";

subscribe(sdkState, () => {
  console.log("status:", sdkState.status);
  console.log("messages:", sdkState.messageList);
});

초기화 옵션 ​

옵션타입기본값설명
sdk_keystring-SDK 인증 키 (필수)
avatar_idstring-아바타 ID (필수)
languageLanguageCode"ko_kr"TTS 음성·자막 및 STT 인식 언어 (ko_kr, en_us, ja_jp, id_id)
log_levelLogLevel"warn"로그 레벨
enable_microphonebooleantrue마이크 활성화
auto_sendbooleanfalsetrue: VAD 자동 응답, false: 수동 전송
stt_onlybooleanfalsetrue: STT 결과만, false: STT + 아바타 응답

상태 흐름 ​

IDLE → CONNECTING → SOCKET_CONNECTED → STREAMING_CONNECTED → CONNECTED_FINISH
상황흐름
정상 종료destroy() → IDLE
에러 발생자동으로 연결 리소스 정리 → IDLE
재연결onError + onStatus(IDLE) 수신 후 init() 재호출

에러 코드 ​

초기화 실패는 에러 코드 없이 init()의 예외(reject)로 전달됩니다. 아래 코드는 연결 이후 런타임에 onError로 전달됩니다.

코드설명
SOCKET_DISCONNECTED_UNEXPECTEDLY런타임 WebSocket 끊김
STREAMING_DISCONNECTED_UNEXPECTEDLY런타임 Agora 끊김
SERVER_ERROR서버 에러 시그널
WORKER_DISCONNECTED워커 종료 시그널

BrowserWindow 설정 ​

agora-electron-sdk는 렌더러 프로세스에서 Node.js API를 사용합니다.

보안 참고

nodeIntegration: true + contextIsolation: false는 Electron 보안 권장사항에 반하지만, agora-electron-sdk가 렌더러에서 require()를 직접 사용하기 때문에 현재로서는 불가피합니다. Agora SDK가 contextIsolation을 지원하면 즉시 마이그레이션하세요.

typescript
const mainWindow = new BrowserWindow({
  webPreferences: {
    nodeIntegration: true,     // 필수: agora-electron-sdk가 require() 사용
    contextIsolation: false,   // 필수: 렌더러에서 Node.js API 접근
  },
});

Web SDK와의 차이 ​

항목Web (sdk-klleon)Electron (sdk-electron)
Agoraagora-rtc-sdk-ngagora-electron-sdk
speak 시그니처speak(문자열 또는 옵션 객체)speak(문자열)만 지원
비디오 렌더링<video> track.play()<agora-view> (내부)
비디오 컨테이너<avatar-container> (Lit)<avatar-container> (Web Component)
모듈 로딩ESM importrequire() (Vite 우회)
디바이스 정보navigator.userAgentprocess.platform / process.arch
UILit 커스텀 엘리먼트Web과 같은 커스텀 엘리먼트 방식(<avatar-container>)
배포CDN (UMD)앱 패키징 (DMG/EXE/DEB)
패키징불필요Electron Forge
컨테이너 지정 API<avatar-container> 배치 시 자동 바인딩<avatar-container> 배치 시 자동 바인딩 + setContainer() 수동 지정
상태 구독 APIonStatus / onSignal 콜백onStatus / onSignal 콜백 + sdkState 직접 구독 (Valtio)