Next.js 로 시작하기

Next.js 애플리케이션에 vox.ai 에이전트와 음성 챗 기능을 통합하여 AI 에이전트와 자연스러운 대화를 가능하게 하는 방법을 소개합니다.

필요한 것

  • vox.ai 대시보드에서 생성된 vox.ai 에이전트
  • vox.ai 대시보드에서 발급받은 에이전트 ID와 API 키
  • Next.js 프로젝트 (13.0.0 이상 권장)
  • React 훅에 대한 기본 지식

기본 사용법 (Next.js)

Next.js 프로젝트에서 vox.ai를 사용하는 방법을 단계별로 알아보겠습니다.

1. 프로젝트 설정

새 Next.js 프로젝트를 생성하거나 기존 프로젝트를 사용할 수 있습니다:

# 새 프로젝트 생성
npx create-next-app my-vox-ai-app
cd my-vox-ai-app

# vox.ai SDK 설치
npm install @vox-ai/react
# 또는
yarn add @vox-ai/react
# 또는
pnpm install @vox-ai/react

2. 대화 컴포넌트 생성

app/components 디렉토리를 생성하고 VoxConversation.tsx 파일을 만듭니다:

"use client";
import { useVoxAI } from "@vox-ai/react";
import { useCallback } from "react";

export function VoxConversation() {
  // vox.ai 훅 초기화
  const {
    connect,
    disconnect,
    state,
    messages,
    send,
    audioWaveform,
    toggleMic,
    setVolume,
  } = useVoxAI({
    onConnect: () => console.log("연결됨"),
    onDisconnect: () => console.log("연결 해제됨"),
    onMessage: (message) => console.log("메시지:", message),
    onError: (error) => console.error("오류:", error),
  });

  // 대화 시작 함수
  const startConversation = useCallback(async () => {
    try {
      // 마이크 권한 요청
      await navigator.mediaDevices.getUserMedia({ audio: true });

      // vox.ai 에이전트에 연결
      await connect({
        agentId: "YOUR_AGENT_ID", // 에이전트 ID로 교체하세요
        apiKey: "YOUR_API_KEY", // API 키로 교체하세요
        dynamicVariables: {
          // 대화 커스터마이징을 위한 동적 변수
          userName: "홍길동",
          context: "고객-지원",
        },
        metadata: {
          // 통화에 대한 메타데이터
          callerId: "customer-123",
          departmentId: "support",
        },
      });
    } catch (error) {
      console.error("대화 시작 실패:", error);
    }
  }, [connect]);

  // 대화 종료 함수
  const stopConversation = useCallback(async () => {
    await disconnect();
  }, [disconnect]);

  // UI 렌더링
  return (
    <div className="flex flex-col items-center gap-4">
      {/* 컨트롤 버튼 */}
      <div className="flex gap-2">
        <button
          onClick={startConversation}
          disabled={state !== "disconnected"}
          className="px-4 py-2 bg-blue-500 text-white rounded disabled:bg-gray-300"
        >
          대화 시작
        </button>
        <button
          onClick={stopConversation}
          disabled={state === "disconnected"}
          className="px-4 py-2 bg-red-500 text-white rounded disabled:bg-gray-300"
        >
          대화 종료
        </button>

      {/* 상태 표시 */}
      <div className="flex flex-col items-center">
        <p>상태: {state}</p>
        <p>에이전트가 {state === "speaking" ? "말하는 중" : "듣는 중"}</p>
      </div>

      {/* 대화 기록 */}
      <div className="w-full max-w-md mt-4">
        <h2 className="text-xl font-bold mb-2">대화 내용</h2>
        <ul className="rounded p-4 max-h-60 overflow-y-auto">
          {messages.map((msg, index) => (
            <li key={msg.id || index} className="mb-2 p-2 rounded">
              <strong>{msg.name === "agent" ? "에이전트" : "사용자"}:</strong>{" "}
              {msg.message}
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

3. 메인 페이지에 컴포넌트 추가

app/page.tsx 파일을 다음과 같이 수정합니다:

import { VoxConversation } from "./components/VoxConversation";

export default function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm">
        <h1 className="text-4xl font-bold mb-8 text-center">
          vox.ai 음성 대화
        </h1>
        <VoxConversation />
      </div>
    </main>
  );
}

4. 개발 서버 실행

다음 명령어로 개발 서버를 실행하고 브라우저에서 확인합니다:

npm run dev

고급 사용법

useVoxAI 훅

useVoxAI 훅은 다음과 같은 기능과 값을 제공합니다:

메서드

  • connect: Vox.ai 서비스에 연결을 설정합니다.
  • disconnect: 음성 AI 세션을 수동으로 종료합니다.
  • send: 텍스트 메시지나 DTMF 톤을 에이전트에 전송합니다.
  • audioWaveform: 에이전트나 사용자의 오디오 웨이브폼 데이터를 반환합니다.
  • toggleMic: 사용자의 마이크를 활성화/비활성화합니다.
  • setVolume: 에이전트의 볼륨을 설정합니다(0-1 범위).

상태 및 데이터

  • state: 현재 통화 상태를 나타냅니다(“disconnected”, “connecting”, “initializing”, “listening”, “thinking”, “speaking”).
  • messages: 대화 기록을 포함합니다.

메시지 구조

각 메시지는 다음 구조를 가집니다:

type VoxMessage = {
  id?: string;
  name: "agent" | "user" | "tool";
  message?: string;
  timestamp: number;
  isFinal?: boolean;
  tool?: FunctionToolsExecuted; // 에이전트가 실행한 함수 도구
};

동적 변수 및 메타데이터 추가하기

대화를 커스터마이징하거나 통화 데이터를 추적하기 위해 dynamicVariablesmetadata를 전달할 수 있습니다:

connect({
  agentId: "your-agent-id", // 에이전트 ID 입력
  apiKey: "your-api-key", // API 키 입력
  dynamicVariables: {
    userName: "홍길동",
    context: "고객-지원",
    // 기타 관련 정보 추가
  },
  metadata: {
    userId: "1234567890",
    // 기타 식별자 추가
  },
});
dynamicVariablesmetadata는 통화 기록에서도 확인할 수 있습니다.

메시지 전송하기

텍스트 메시지 또는 DTMF 톤을 에이전트에게 보낼 수 있습니다:

// 텍스트 메시지 전송
send({ message: "안녕하세요, 도움이 필요합니다." });

// DTMF 톤 전송 (0-9, *, #)
send({ digit: 1 });

대화 상태 모니터링

훅에서 제공하는 state 값은 대화 상태에 대한 실시간 정보를 제공합니다:

  • disconnected - vox.ai에 연결되지 않음
  • connecting - 연결 설정 중
  • initializing - 연결 설정됨, 음성 인터페이스 초기화 중
  • listening - 사용자 입력 대기 중
  • thinking - 사용자 입력 처리 중
  • speaking - AI 에이전트가 말하는 중

이 상태를 사용하여 사용자에게 시각적 피드백을 제공할 수 있습니다:

function getStatusIndicator(state) {
  switch (state) {
    case "listening":
      return <div className="status-indicator listening">듣는 중...</div>;
    case "thinking":
      return <div className="status-indicator thinking">생각하는 중...</div>;
    case "speaking":
      return <div className="status-indicator speaking">말하는 중...</div>;
    default:
      return null;
  }
}

// 렌더 함수 내에서
{
  getStatusIndicator(state);
}

오디오 웨이브폼 시각화

audioWaveform 메서드를 사용하여 오디오 활동을 시각화할 수 있습니다:

import React, { useState, useEffect } from "react";
import { useVoxAI } from "@vox-ai/react";

function WaveformVisualizer() {
  const { audioWaveform, state } = useVoxAI();
  const [waveformData, setWaveformData] = useState([]);

  // 웨이브폼 데이터를 정기적으로 업데이트
  useEffect(() => {
    if (state === "disconnected") return;

    const intervalId = setInterval(() => {
      // 에이전트 오디오 웨이브폼 데이터 가져오기
      const data = audioWaveform({
        speaker: "agent", // "agent" 또는 "user"
        barCount: 30, // 반환할 웨이브폼 바의 수
        updateInterval: 50, // 업데이트 간격 (ms)
      });
      setWaveformData(data);
    }, 50);

    return () => clearInterval(intervalId);
  }, [audioWaveform, state]);

  return (
    <div className="waveform-container">
      {waveformData.map((value, index) => (
        <div
          key={index}
          className="waveform-bar"
          style={{
            height: `${value * 100}%`,
            width: "10px",
            backgroundColor: "#3498db",
            margin: "0 2px",
          }}
        />
      ))}
    </div>
  );
}

마이크 및 볼륨 제어

toggleMicsetVolume 메서드를 사용하여 오디오 입출력을 제어할 수 있습니다:

// 마이크 켜기/끄기
toggleMic(true); // 마이크 활성화
toggleMic(false); // 마이크 비활성화

// 에이전트 볼륨 조절 (0: 음소거, 1: 최대 볼륨)
setVolume(0.5); // 볼륨 50%로 설정

메시지 처리하기

messages 배열은 대화 기록을 포함합니다. 각 메시지는 다음 구조를 가집니다. onMessage 콜백을 사용하여 새 메시지가 수신될 때 작업을 수행할 수 있습니다:

const { connect, disconnect, state, messages } = useVoxAI({
  onMessage: (message) => {
    // 에이전트의 최종 메시지만 처리
    if (message.name === "agent" && message.isFinal) {
      // 에이전트 응답에 따른 작업 수행
      console.log("에이전트 응답:", message.message);
    }
  },
});

문제 해결

마이크 접근 문제

사용자가 마이크 접근에 문제가 있는 경우:

  1. 사이트가 HTTPS로 제공되는지 확인 (마이크 접근에 필요)
  2. 브라우저 권한 설정 확인
  3. 권한 거부에 대한 적절한 오류 처리 구현:
try {
  await navigator.mediaDevices.getUserMedia({ audio: true });
} catch (error) {
  if (error.name === "NotAllowedError") {
    alert("음성 대화를 위해 마이크 접근 권한이 필요합니다");
  } else {
    console.error("마이크 오류:", error);
  }
}