edutap.ai developers
SDK 연동가이드

React 통합

TapKit과 useTapKit 훅으로 React 앱에서 AI 튜터 쉽게 통합하기

TapKit은 React 앱에서 쉽게 사용할 수 있는 TapKit 컴포넌트와 useTapKit 훅을 제공합니다.

설치

npm install @coxwave/tap-kit

기본 사용법

'use client';

import { TapKit, useTapKit } from '@coxwave/tap-kit/react';

function VideoLearningPage() {
  const tapkit = useTapKit({
    apiKey: 'your-api-key',
    userId: 'user-123',
    courseId: 'course-456',
    clipId: 'clip-789',
    onReady: () => console.log('TapKit 준비 완료!'),
  });

  return (
    <div>
      <button onClick={tapkit.show} disabled={!tapkit.isReady}>
        AI 튜터에게 질문하기
      </button>
      <TapKit control={tapkit.control} style={{ height: '600px' }} />
    </div>
  );
}

Next.js App Router 사용 시 반드시 파일 상단에 'use client'를 추가하세요.

useTapKit(options)

옵션

옵션타입필수설명
apiKeystringOAPI 키
userIdstring사용자 ID
courseIdstring강의 ID
clipIdstring클립 ID
clipPlayHeadnumber비디오 재생 위치 (초)
language'ko' | 'en'언어 설정
mode'inline' | 'floating' | 'sidebar'표시 모드 (기본: 'inline')
allowLayoutTogglebooleanfloating↔sidebar 토글 허용 (기본: true)
videoTargetHTMLVideoElement | VideoAdapter비디오 플레이어 바인딩
buttonIdstring커스텀 버튼 요소 ID
debugboolean디버그 모드

이벤트 핸들러

핸들러타입설명
onReady() => void준비 완료 시
onError(error: Error) => void에러 발생 시
onTimelineSeek(clipPlayHead: number, clipId: string) => void타임라인 이동 시
onAlarmFadeIn(messageInfo: unknown) => void알림 표시 시

반환값

interface UseTapKitReturn {
  control: TapKitControl;           // TapKit 컴포넌트용
  isReady: boolean;                  // 준비 완료 여부
  error: Error | null;               // 에러 객체
  show: () => void;                  // 채팅 열기
  hide: () => void;                  // 채팅 닫기
  setCourse: (course: SetCourseParams) => void;  // 강의 변경
  video: {                           // ⚠️ deprecated - videoTarget 옵션 사용 권장
    bind: (adapter: VideoAdapter, clipId?: string) => void;
    unbind: () => void;
  };
}

interface VideoAdapter {
  getCurrentTime: () => number;
  setCurrentTime: (time: number, clipId?: string) => void;  // clipId for cross-clip seeking
}

videoTarget 권장

video.bind() 대신 videoTarget 옵션을 사용하세요. 라이프사이클 변경에도 안전합니다.

// ✅ 권장
const tapkit = useTapKit({ videoTarget: videoRef.current, ... });

// ❌ deprecated
tapkit.video.bind(videoRef.current);

강의 변경하기

setCourse()를 사용하여 동적으로 강의를 변경할 수 있습니다.

'use client';

import { TapKit, useTapKit } from '@coxwave/tap-kit/react';

export function CourseSwitcher({ userId }) {
  const tapkit = useTapKit({
    apiKey: process.env.NEXT_PUBLIC_TAP_API_KEY!,
    userId,
    courseId: 'initial-course',
    clipId: 'initial-clip',
  });

  const handleCourseChange = (courseId: string, clipId: string) => {
    tapkit.setCourse({ courseId, clipId, userId });
  };

  return (
    <div>
      <div className="flex gap-2 mb-4">
        <button onClick={() => handleCourseChange('course-1', 'clip-1')}>강의 1</button>
        <button onClick={() => handleCourseChange('course-2', 'clip-2')}>강의 2</button>
      </div>
      <TapKit control={tapkit.control} style={{ height: '600px' }} />
    </div>
  );
}

비디오 플레이어 동기화

중요: TapKit을 제대로 활용하려면 비디오 플레이어 동기화가 필수입니다!

비디오와 AI 튜터를 연동하면:

  • AI가 현재 재생 시점을 기반으로 정확한 답변 제공
  • 채팅에서 특정 구간 클릭 시 비디오 자동 이동
  • setCourse() 호출 시 자동으로 새 clipId 추적 (재바인딩 불필요!)

SSR 안전

video.bind()브라우저에서만 호출해야 합니다. 반드시 useEffect 내부에서 호출하세요.

'use client';

import { TapKit, useTapKit } from '@coxwave/tap-kit/react';
import { useEffect, useRef } from 'react';

export function SyncedVideoPlayer({ userId, courseId, clipId }) {
  const videoRef = useRef<HTMLVideoElement>(null);

  const tapkit = useTapKit({
    apiKey: process.env.NEXT_PUBLIC_TAP_API_KEY!,
    userId,
    courseId,
    clipId,
    onTimelineSeek: (seekTime, seekClipId) => {
      // TapKit → 비디오: 채팅에서 클릭한 시점으로 이동
      if (videoRef.current && seekClipId === clipId) {
        videoRef.current.currentTime = seekTime;
      }
    },
  });

  // 비디오 플레이어 바인딩 (clipId 자동 추적!)
  useEffect(() => {
    const videoElement = videoRef.current;
    if (!tapkit.isReady || !videoElement) return;

    tapkit.video.bind({
      getCurrentTime: () => videoElement.currentTime,
      // clipId는 optional - cross-clip seeking 지원 시 활용
      setCurrentTime: (time, clipId) => {
        // 같은 클립이거나 clipId가 없으면 바로 이동
        videoElement.currentTime = time;
      },
    });

    return () => {
      tapkit.video.unbind();
    };
  }, [tapkit.isReady, tapkit.video]);

  return (
    <div className="flex gap-4">
      <video ref={videoRef} controls className="flex-1">
        <source src="/lecture.mp4" type="video/mp4" />
      </video>
      <TapKit control={tapkit.control} className="w-[400px] h-[600px]" />
    </div>
  );
}

clipId 자동 추적

video.bind(adapter) 호출 시 clipId를 생략하면, SDK 설정의 clipId가 자동으로 사용됩니다. setCourse({ clipId: 'new-clip' })를 호출하면 자동으로 새 clipId로 동기화됩니다.

Next.js App Router

// app/course/[courseId]/page.tsx
import { AiTutor } from '@/components/AiTutor';

interface PageProps {
  params: { courseId: string };
}

export default function CoursePage({ params }: PageProps) {
  return (
    <main>
      <h1>강의 페이지</h1>
      {/* AiTutor는 'use client' 컴포넌트 */}
      <AiTutor
        userId="user-123"
        courseId={params.courseId}
        clipId="clip-789"
      />
    </main>
  );
}

TypeScript 지원

import type { UseTapKitOptions, UseTapKitReturn, TapKitProps } from '@coxwave/tap-kit/react';
import type { TapKitElement } from '@coxwave/tap-kit-types';

const options: UseTapKitOptions = {
  apiKey: 'key',
  userId: 'user-123',
  onReady: () => {},
};

const tapkit: UseTapKitReturn = useTapKit(options);

다음 단계