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-kitBasic 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
| Option | Type | Required | Description |
|---|---|---|---|
apiKey | string | Yes | API key |
userId | string | User ID | |
courseId | string | Course ID | |
clipId | string | Clip ID | |
clipPlayHead | number | Video playback position (seconds) | |
language | 'ko' | 'en' | Language setting | |
mode | 'inline' | 'floating' | 'sidebar' | Display mode | |
allowLayoutToggle | boolean | Allow floating↔sidebar toggle (default: true) | |
videoTarget | HTMLVideoElement | VideoPlayerAdapter | Video player binding | |
buttonId | string | Custom button element ID | |
debug | boolean | Debug 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
| Handler | Type | Description |
|---|---|---|
onReady | () => void | When ready |
onError | (error: Error) => void | When error occurs |
onTimelineSeek | (clipPlayHead: number, clipId: string) => void | When timeline moves |
onAlarmFadeIn | (messageInfo: AlarmMessageInstanceType) => void | When 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);