> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tryvox.co/llms.txt
> Use this file to discover all available pages before exploring further.

# React

> useConversation 훅으로 React 앱에 음성 에이전트를 연결할 수 있어요.

`@vox-ai/react`는 vox.ai 에이전트 연결과 오디오 상태를 관리하는 React 훅 `useConversation`을 제공해요. 바닐라 JavaScript로 연결하려면 [JavaScript](/docs/sdk/javascript)를 보세요.

## 설치하기

프로젝트에 패키지를 설치하세요.

```shell theme={null}
npm install @vox-ai/react
# or
yarn add @vox-ai/react
# or
pnpm add @vox-ai/react
```

## 세션 시작하기

`useConversation`으로 Conversation 인스턴스를 만드세요.

```tsx theme={null}
import { useConversation } from "@vox-ai/react";

const conversation = useConversation();
```

음성 세션에는 마이크 권한이 필요해요. 세션을 시작하기 전에 화면에서 이유를 알리고 권한을 요청하세요.

```js theme={null}
// 마이크 권한이 필요한 이유를 UI에서 안내한 후 호출
await navigator.mediaDevices.getUserMedia({ audio: true });
```

### 훅 옵션과 콜백

훅을 초기화할 때 옵션과 콜백(Callback)을 넘길 수 있어요. 옵션 `textOnly`는 텍스트 전용 모드의 기본값이에요([텍스트로만 대화하기](#텍스트로만-대화하기)).

* `onConnect`: 세션이 연결되면 불려요.
* `onDisconnect`: 연결이 끊기면 불려요.
* `onMessage`: 메시지가 들어올 때 불려요. 사용자 음성을 옮긴 텍스트나 에이전트 응답이 여기로 와요.
* `onError`: 오류가 나면 불려요.
* `onStatusChange`: 연결 상태가 바뀔 때 불려요.
* `onAgentStateChange`: 에이전트 상태가 `"initializing"`, `"idle"`, `"listening"`, `"thinking"`, `"speaking"` 가운데 하나로 바뀔 때 불려요.

```tsx theme={null}
const conversation = useConversation({
  onConnect: () => console.log("Connected"),
  onDisconnect: () => console.log("Disconnected"),
  onMessage: (message) => console.log(`${message.source}: ${message.text}`),
  onError: (error) => console.error("Error:", error),
  onStatusChange: (status) => console.log("Status:", status),
  onAgentStateChange: (agentState) => console.log("Agent state:", agentState),
});
```

### startSession

세션을 시작하고, 세션 고유 ID를 resolve하는 Promise를 돌려줘요. Agent ID는 [vox.ai 대시보드](https://www.tryvox.co/dashboard/)에서 확인하세요.

```tsx theme={null}
const conversation = useConversation();

const conversationId = await conversation.startSession({
  agentId: "<your-agent-id>",
  apiKey: "<your-client-key>",
  visitorId: "visitor_1024",
});
```

| 옵션 | 타입 | 필수 | 설명 |
| - | - | - | - |
| `agentId` | `string` | O | Agent ID |
| `apiKey` | `string` | O | 클라이언트용 키예요. `pk_`로 시작하는 키를 넣어요 |
| `agentVersion` | `string` | - | 에이전트 버전. 기본값 `"current"` |
| `textOnly` | `boolean` | - | 세션 단위로 텍스트 전용 모드를 덮어써요 |
| `dynamicVariables` | `Record<string, string \| number \| boolean>` | - | 프롬프트에 넣을 [동적 변수](/docs/build/variables/dynamic-variables) |
| `metadata` | `Record<string, unknown>` | - | 통화 메타데이터. 웹훅과 통화 기록에 들어가요 |
| `visitorId` | `string` | - | 같은 방문자의 세션을 한 고객으로 이어 붙일 고정 ID. 생략하면 브라우저별로 자동으로 만들어요 |
| `audio` | `AudioOptions` | - | 브라우저 마이크 처리 옵션. `echoCancellation`·`noiseSuppression`·`autoGainControl`을 하나씩 끌 수 있고 기본값은 모두 `true` |

<Note>
  브라우저에는 클라이언트용 키(`pk_`)를 넣고, 서버용 키(`sk_`)는 공개 웹페이지에 넣지 마세요. 클라이언트용 키는 세션 시작만 할 수 있어요.
</Note>

키를 만들고 바꾸는 방법은 [API 키](/docs/workspace/api-keys#웹페이지와-모바일-앱-연결하기)에 있어요.

* **`visitorId`**: vox.ai 내부 고객 UUID가 아니에요. 에이전트의 [메모리](/docs/build/customer-memory/memory)가 켜져 있으면 같은 고객과 에이전트에 저장된 메모리를 첫 응답 전에 자동으로 불러와요.
* **`audio`**: 브라우저가 마이크 입력에 기본으로 적용하는 에코 제거, 잡음 억제, 자동 음량 조절을 항목별로 꺼요. 생략하면 셋 다 켜져 있고, Text Only 세션에서는 무시돼요.
* **Android Chrome의 통화 모드**: 에코 제거가 켜진 마이크를 열면 기기가 통화 모드로 바뀌어 에이전트 음성이 미디어가 아닌 통화 경로로 재생돼요. 사용자가 이어폰이나 헤드셋을 쓰거나 앱에서 마이크 입력을 따로 처리하면 `echoCancellation: false`로 피할 수 있어요.
* **스피커폰**: `echoCancellation`을 끄면 스피커로 나온 에이전트 음성이 마이크로 다시 들어가 사용자 발화로 처리돼요. 에이전트가 자기 목소리에 반응해 말을 끊을 수 있으니 스피커폰을 쓰는 화면에서는 끄지 마세요.

연결이 실패하면 `onError`가 불리고 `startSession`이 오류를 던져요. `status`는 `"disconnected"`로 돌아가요.

## 연결 확인

세션을 시작한 뒤 훅의 상태로 연결을 확인하세요.

* **연결 상태**: `status`가 `"connecting"`을 거쳐 `"connected"`가 되고, 연결되면 `onConnect`가 불려요.
* **마이크**: 음성 세션이면 `micMuted`가 `false`예요. `true`면 마이크를 켜지 못한 채 연결된 거예요.
* **에이전트 응답**: 에이전트가 말하면 `isSpeaking`이 `true`가 되고, `messages`에 `source`가 `"agent"`인 메시지가 쌓여요.

## 세션 제어하기

### endSession

세션을 끝내요. `Promise<void>`를 돌려줘요.

```tsx theme={null}
await conversation.endSession();
```

### setVolume

출력 볼륨을 조절해요. 0\~1 사이 값을 넣으세요.

```tsx theme={null}
conversation.setVolume({ volume: 0.5 });
```

### sendUserMessage

에이전트에게 텍스트 메시지를 보내요. 마이크 대신 텍스트로 입력할 때 쓰세요. `Promise<void>`를 돌려줘요.

```tsx theme={null}
const [value, setValue] = useState("");

return (
  <>
    <input value={value} onChange={(e) => setValue(e.target.value)} />
    <button
      onClick={async () => {
        await conversation.sendUserMessage(value);
        setValue("");
      }}
    >
      전송
    </button>
  </>
);
```

### setMicMuted

마이크를 음소거하거나 해제해요. `Promise<void>`를 돌려줘요.

```tsx theme={null}
// 음소거
await conversation.setMicMuted(true);

// 해제
await conversation.setMicMuted(false);
```

### getId

지금 세션 ID를 돌려줘요. 세션 ID가 없으면 `undefined`를 돌려줘요.

```tsx theme={null}
const conversationId = conversation.getId();
```

### getMessages

세션의 메시지 배열 snapshot을 돌려줘요.

```tsx theme={null}
const messages = conversation.getMessages();
```

### getAgentState

지금 에이전트 상태 snapshot을 돌려줘요.

```tsx theme={null}
const agentState = conversation.getAgentState();
// "initializing" | "idle" | "listening" | "thinking" | "speaking" | undefined
```

### getInputVolume / getOutputVolume

지금 입출력 볼륨을 0\~1 스케일로 돌려줘요.

```tsx theme={null}
const inputLevel = conversation.getInputVolume();
const outputLevel = conversation.getOutputVolume();
```

### getInputByteFrequencyData / getOutputByteFrequencyData

입출력 주파수 데이터를 `Uint8Array`로 돌려줘요. 세션이 없으면 `undefined`를 돌려줘요. 오디오 시각화에 쓸 수 있어요.

```tsx theme={null}
const inputFrequency = conversation.getInputByteFrequencyData();
const outputFrequency = conversation.getOutputByteFrequencyData();
```

오디오 모니터링 메서드는 음성 세션에서만 동작해요. 볼륨과 주파수 측정은 이 메서드를 처음 부를 때 시작돼서 첫 호출은 0이나 빈 배열을 돌려줄 수 있어요. 한 번도 부르지 않으면 SDK는 측정용 AudioContext를 만들지 않아요.

### changeInputDevice

세션 중에 오디오 입력 장치를 바꿔요. 전환 성공 여부를 담은 `Promise<boolean>`을 돌려줘요.

```tsx theme={null}
const changed = await conversation.changeInputDevice({
  inputDeviceId: "your-device-id",
});
```

### changeOutputDevice

세션 중에 오디오 출력 장치를 바꿔요. 전환 성공 여부를 담은 `Promise<boolean>`을 돌려줘요.

```tsx theme={null}
const changed = await conversation.changeOutputDevice({
  outputDeviceId: "your-device-id",
});
```

장치 전환은 음성 세션에서만 동작해요. `deviceId`를 지정하지 않으면 브라우저 기본 장치를 써요. 쓸 수 있는 장치 목록은 [MediaDevices.enumerateDevices()](https://developer.mozilla.org/docs/Web/API/MediaDevices/enumerateDevices) API로 조회하세요.

## React 상태 읽기

| State | 타입 | 설명 |
| - | - | - |
| `status` | `ConversationStatus` | `"disconnected"` \| `"connecting"` \| `"connected"` |
| `agentState` | `AgentState \| undefined` | `"initializing"` \| `"idle"` \| `"listening"` \| `"thinking"` \| `"speaking"` \| `undefined` |
| `isSpeaking` | `boolean` | `agentState === "speaking"`으로 계산한 편의 값 |
| `micMuted` | `boolean` | 마이크 음소거 상태 |
| `messages` | `ConversationMessage[]` | 세션에서 주고받은 메시지 배열 |

```tsx theme={null}
const { status, agentState, isSpeaking, micMuted, messages } = useConversation();
```

JavaScript SDK의 `getStatus()`, `getAgentState()`, `getMicMuted()`에 해당해요. React에서는 state로 제공돼서 값이 바뀌면 자동으로 다시 렌더링돼요.

## 텍스트로만 대화하기

텍스트 전용 모드(Text Only)를 쓰면 마이크 없이 텍스트만으로 에이전트와 대화할 수 있어요. 마이크 권한을 요청하지 않고 오디오 컨텍스트도 만들지 않아요.

```tsx theme={null}
const conversation = useConversation({
  textOnly: true,
});

await conversation.startSession({
  agentId: "<your-agent-id>",
  apiKey: "<your-client-key>",
});

await conversation.sendUserMessage("안녕하세요");
```

## 동적 변수와 메타데이터 넘기기

```tsx theme={null}
const conversationId = await conversation.startSession({
  agentId: "<your-agent-id>",
  apiKey: "<your-client-key>",
  dynamicVariables: {
    userName: "홍길동",
    userType: "premium",
    accountBalance: 50000,
  },
  metadata: {
    sessionId: "sess_abc123",
    source: "web-app",
  },
});
```

* `dynamicVariables`: 에이전트 프롬프트에서 `{{userName}}` 꼴로 참조해요. 자세한 쓰임은 [동적 변수](/docs/build/variables/dynamic-variables)에 있어요.
* `metadata`: 웹훅과 통화 기록에 들어가요.

## 전체 예제

```tsx theme={null}
import { useConversation } from "@vox-ai/react";
import { useState } from "react";

function VoiceWidget() {
  const conversation = useConversation({
    onConnect: () => console.log("Connected"),
    onDisconnect: () => console.log("Disconnected"),
    onAgentStateChange: (agentState) => console.log("Agent state:", agentState),
    onMessage: (msg) => console.log(`${msg.source}: ${msg.text}`),
    onError: (err) => console.error("Error:", err),
  });

  const start = async () => {
    await navigator.mediaDevices.getUserMedia({ audio: true });
    await conversation.startSession({
      agentId: "your-agent-id",
      apiKey: "your-client-key",
    });
  };

  return (
    <div>
      <p>Status: {conversation.status}</p>
      <p>Agent State: {conversation.agentState ?? "unknown"}</p>
      <p>Speaking: {conversation.isSpeaking ? "Yes" : "No"}</p>

      <button
        onClick={start}
        disabled={conversation.status !== "disconnected"}
      >
        시작
      </button>
      <button
        onClick={async () => {
          await conversation.endSession();
        }}
      >
        종료
      </button>
    </div>
  );
}
```

## 문제가 생겼을 때

<AccordionGroup>
  <Accordion title="startSession에 넘긴 콜백이 불리지 않아요">
    콜백은 `useConversation`의 초기화 인자로 옮기세요. `startSession`에 넣은 `onConnect`, `onMessage` 같은 콜백은 무시되고 `console.warn` 경고가 남아요.
  </Accordion>

  <Accordion title="startSession이 Session initialization failed 오류로 끝나요">
    `agentId`와 `apiKey` 값을 확인하세요. 세션을 만드는 요청이 거절되면 `Session initialization failed (상태 코드): 응답 본문` 꼴의 오류가 나고, 괄호 안 상태 코드와 응답 본문에 거절된 까닭이 있어요. 응답 본문에 `API_KEY_NOT_FOR_SESSIONS`가 있으면 클라이언트용 키 출시 이후 만든 서버용 키(`sk_`)를 브라우저에서 쓴 경우예요. 클라이언트용 키(`pk_`)로 바꾸세요.
  </Accordion>

  <Accordion title="연결됐는데 에이전트가 내 말을 듣지 못해요">
    브라우저의 마이크 권한과 입력 장치를 확인한 뒤 `setMicMuted(false)`로 마이크를 다시 켜세요. 세션을 시작할 때 마이크를 켜지 못하면 `onError`만 불리고 `micMuted`가 `true`인 채로 연결돼요.
  </Accordion>

  <Accordion title="연결이 끊긴 뒤 다시 이어지지 않아요">
    `startSession`을 다시 부르세요. 연결이 끊기면 `status`가 `"disconnected"`로 바뀌고 `onDisconnect`가 불려요. 세션이 남아 있을 때 `startSession`을 부르면 앞 세션을 끝내고 `messages`를 비운 뒤 새로 연결해요.
  </Accordion>
</AccordionGroup>

## 관련 문서

* [JavaScript](/docs/sdk/javascript): 바닐라 JavaScript로 웹에 연결하기
* [동적 변수](/docs/build/variables/dynamic-variables): 프롬프트에 세션별 값 넣기
* [고객](/docs/operate/monitor/customers): `visitorId`로 이어 붙인 고객 이력 확인
* [메모리](/docs/build/customer-memory/memory): 에이전트가 지난 대화를 기억하는 방식


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.