edutap.ai developers
SDK IntegrationGuides

React Integration

Easily integrate AI tutor in React apps with TapKit and useTapKit hook

TapKit provides a TapKit component and useTapKit hook for easy use in React apps.

Installation

npm install @coxwave/tap-kit

Basic Usage

'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 ready!'),
  });

  return (
    <div>
      <button onClick={tapkit.show} disabled={!tapkit.isReady}>
        Ask AI Tutor
      </button>
      <TapKit control={tapkit.control} style={{ height: '600px' }} />
    </div>
  );
}

When using Next.js App Router, you must add 'use client' at the top of the file.

useTapKit(options)

Options

OptionTypeRequiredDescription
apiKeystringYesAPI key
userIdstringUser ID
courseIdstringCourse ID
clipIdstringClip ID
clipPlayHeadnumberVideo playback position (seconds)
language'ko' | 'en'Language setting
mode'inline' | 'floating' | 'sidebar'Display mode
allowLayoutTogglebooleanAllow floating↔sidebar toggle (default: true)
videoTargetHTMLVideoElement | VideoPlayerAdapterVideo player binding
buttonIdstringCustom button element ID
debugbooleanDebug mode

mode default

If mode is not specified, it follows the <tap-kit> Web Component's default. Typically operates in inline mode.

root option not supported

useTapKit is a declarative API and does not support the root option. The <TapKit /> component renders at the declared location. To control position programmatically, use createTapKit().

Event Handlers

HandlerTypeDescription
onReady() => voidWhen ready
onError(error: Error) => voidWhen error occurs
onTimelineSeek(clipPlayHead: number, clipId: string) => voidWhen timeline moves
onAlarmFadeIn(messageInfo: AlarmMessageInstanceType) => voidWhen notification appears

Return Value

interface UseTapKitReturn {
  control: TapKitControl;           // For TapKit component
  isReady: boolean;                  // Whether ready
  error: Error | null;               // Error object
  show: () => void;                  // Open chat
  hide: () => void;                  // Close chat
  setCourse: (course: SetCourseParams) => void;  // Change course
  video: {                           // ⚠️ deprecated - use videoTarget option instead
    bind: (adapter: VideoPlayerAdapter, clipId?: string) => void;
    unbind: () => void;
  };
}

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

videoTarget recommended

Use the videoTarget option instead of video.bind(). It persists safely through lifecycle changes.

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

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

Changing Courses

Use setCourse() to dynamically change courses.

'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')}>Course 1</button>
        <button onClick={() => handleCourseChange('course-2', 'clip-2')}>Course 2</button>
      </div>
      <TapKit control={tapkit.control} style={{ height: '600px' }} />
    </div>
  );
}

Video Player Synchronization

Important: Video player synchronization is essential to fully utilize TapKit!

When you sync video with the AI tutor:

  • AI provides accurate answers based on current playback position
  • Auto-seek video when clicking specific segments in chat
  • Automatic clipId tracking when calling setCourse() (no rebinding needed!)

SSR Safety

video.bind() must be called only in the browser. Always call it inside 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 → Video: Seek to the clicked point in chat
      if (videoRef.current && seekClipId === clipId) {
        videoRef.current.currentTime = seekTime;
      }
    },
  });

  // Video player binding (automatic clipId tracking!)
  useEffect(() => {
    const videoElement = videoRef.current;
    if (!tapkit.isReady || !videoElement) return;

    tapkit.video.bind({
      getCurrentTime: () => videoElement.currentTime,
      // clipId is optional - use for cross-clip seeking support
      setCurrentTime: (time, clipId) => {
        // Move directly if same clip or no 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>
  );
}

Automatic clipId tracking

When calling video.bind(adapter) without clipId, the SDK's configured clipId is automatically used. When you call setCourse({ clipId: 'new-clip' }), it automatically syncs to the new 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>Course Page</h1>
      {/* AiTutor is a 'use client' component */}
      <AiTutor
        userId="user-123"
        courseId={params.courseId}
        clipId="clip-789"
      />
    </main>
  );
}

TypeScript Support

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);

Next Steps