Skip to content
/ sdk Public

AI-powered guided tours for React and Next.js apps. Wrap any UI element, and an AI assistant guides users step-by-step to the right features, no component rewrites required.

License

Notifications You must be signed in to change notification settings

eventop-s/sdk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@eventop/sdk

AI-powered guided tours for any React or Next.js app. Drop-in, themeable, works with any component library.


Install

npm install @eventop/sdk
# or
yarn add @eventop/sdk
# or
pnpm add @eventop/sdk

How it works

You wrap any element with <EventopTarget> at the call site. The SDK registers it as a feature the AI can guide users to. When a user types what they need in the chat bubble, the AI picks the right features and walks them through step by step.

The wrapped component stays completely generic — <Button>, <div>, anything from shadcn, MUI, Radix, whatever. You never modify the component itself.

// Same Button, two different features, two different places in the app
<EventopTarget id="export" name="Export Design" description="Download as PNG or SVG">
  <Button onClick={handleExport}>Export</Button>
</EventopTarget>

<EventopTarget id="share" name="Share Design" description="Share a link with teammates">
  <Button onClick={handleShare}>Share</Button>
</EventopTarget>

React app

1. Set up the server endpoint

Never put API keys in the browser. Create a server route that proxies the AI call.

// server.js (Express)
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

app.post('/api/guide', async (req, res) => {
  const { systemPrompt, messages } = req.body;
  const response = await client.messages.create({
    model:      'claude-sonnet-4-20250514',
    max_tokens: 1000,
    system:     systemPrompt,
    messages,
  });
  res.json(JSON.parse(response.content[0].text));
});

2. Add the provider at the root

// main.jsx
import { EventopAIProvider } from '@eventop/sdk/react';

const provider = async ({ systemPrompt, messages }) => {
  const res = await fetch('/api/guide', {
    method:  'POST',
    headers: { 'Content-Type': 'application/json' },
    body:    JSON.stringify({ systemPrompt, messages }),
  });
  return res.json();
};

export default function App() {
  return (
    <EventopAIProvider
      provider={provider}
      appName="My App"
      assistantName="AI Guide"
      suggestions={['How do I export?', 'Invite a teammate']}
      theme={{ mode: 'auto', tokens: { accent: '#6366f1' } }}
      position={{ corner: 'bottom-right' }}
    >
      <YourApp />
    </EventopAIProvider>
  );
}

3. Wrap features anywhere in the tree

// ExportPanel.jsx
import { EventopTarget } from '@eventop/sdk/react';

export function ExportPanel() {
  return (
    <EventopTarget
      id="export"
      name="Export Design"
      description="Download the design as PNG, SVG or PDF"
    >
      <div id="export-panel">
        <button>PNG</button>
        <button>SVG</button>
        <button>PDF</button>
      </div>
    </EventopTarget>
  );
}

That's it. The chat bubble appears automatically. Users type what they need and get a guided tour.


Next.js app

1. Create the API route

// app/api/guide/route.js  (App Router)
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

export async function POST(request) {
  const { systemPrompt, messages } = await request.json();
  const response = await client.messages.create({
    model:      'claude-sonnet-4-20250514',
    max_tokens: 1000,
    system:     systemPrompt,
    messages,
  });
  return Response.json(JSON.parse(response.content[0].text));
}
// pages/api/guide.js  (Pages Router)
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

export default async function handler(req, res) {
  const { systemPrompt, messages } = req.body;
  const response = await client.messages.create({
    model:      'claude-sonnet-4-20250514',
    max_tokens: 1000,
    system:     systemPrompt,
    messages,
  });
  res.json(JSON.parse(response.content[0].text));
}

2. Add the provider in a client component

The SDK touches the DOM so the provider must be a client component.

// components/EventopProvider.jsx
'use client';

import { EventopAIProvider } from '@eventop/sdk/react';

const provider = async ({ systemPrompt, messages }) => {
  const res = await fetch('/api/guide', {
    method:  'POST',
    headers: { 'Content-Type': 'application/json' },
    body:    JSON.stringify({ systemPrompt, messages }),
  });
  return res.json();
};

export function EventopProvider({ children }) {
  return (
    <EventopAIProvider
      provider={provider}
      appName="My App"
      assistantName="AI Guide"
      suggestions={['How do I export?', 'Invite a teammate']}
      theme={{ mode: 'auto', tokens: { accent: '#6366f1' } }}
      position={{ corner: 'bottom-right' }}
    >
      {children}
    </EventopAIProvider>
  );
}
// app/layout.jsx
import { EventopProvider } from '@/components/EventopProvider';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <EventopProvider>
          {children}
        </EventopProvider>
      </body>
    </html>
  );
}

3. Wrap features in client components

Any component that uses EventopTarget, EventopStep, or the hooks needs 'use client'.

// components/Toolbar.jsx
'use client';

import { EventopTarget } from '@eventop/sdk/react';

export function Toolbar() {
  return (
    <div className="toolbar">
      <EventopTarget
        id="export"
        name="Export Design"
        description="Download as PNG, SVG or PDF"
      >
        <button>Export</button>
      </EventopTarget>

      <EventopTarget
        id="share"
        name="Share Design"
        description="Share a link to this design"
      >
        <button>Share</button>
      </EventopTarget>
    </div>
  );
}

Multi-step flows

For features that require multiple actions in sequence (open a panel, toggle a switch, adjust sliders), use <EventopStep>. Steps can live in completely different components — they self-assemble by index.

// CanvasStage.jsx — step 0: click an element
'use client';
import { EventopStep } from '@eventop/sdk/react';

export function CanvasStage() {
  return (
    <EventopStep
      feature="drop-shadow"
      index={0}
      advanceOn={{ selector: '.canvas-el', event: 'click', delay: 300 }}
    >
      <div className="canvas-stage">...</div>
    </EventopStep>
  );
}

// Toolbar.jsx — step 1: click Effects (different component entirely)
export function EffectsButton() {
  return (
    <EventopStep
      feature="drop-shadow"
      index={1}
      waitFor=".canvas-el.selected"
      advanceOn={{ event: 'click', delay: 200 }}
    >
      <button id="btn-effects">✨ Effects</button>
    </EventopStep>
  );
}

// EffectsPanel.jsx — step 2: toggle shadow on
export function ShadowToggle({ onToggle }) {
  return (
    <EventopStep
      feature="drop-shadow"
      index={2}
      waitFor="#effects-panel.open"
      advanceOn={{ event: 'click', delay: 300 }}
    >
      <button id="shadow-toggle" onClick={onToggle}>Shadow</button>
    </EventopStep>
  );
}

// Step 3: sliders — only rendered when shadow is on
// SDK waits for them via waitFor before showing this step
export function ShadowSliders() {
  return (
    <EventopStep feature="drop-shadow" index={3} waitFor="#shadow-controls.visible">
      <div id="shadow-controls" className="visible">...</div>
    </EventopStep>
  );
}

The parent feature still needs a <EventopTarget> somewhere:

// In whichever component owns the canvas screen
<EventopTarget
  id="drop-shadow"
  name="Drop Shadow Effect"
  description="Apply a customisable drop shadow to a selected element"
  navigate={() => router.push('/canvas')}
>
  <div className="canvas-screen">
    <CanvasStage />
    <EffectsButton />
    <ShadowToggle />
    {shadowOn && <ShadowSliders />}
  </div>
</EventopTarget>

Async validation

Use useEventopAI when a tour step depends on the user completing a form action correctly before advancing.

'use client';
import { useEventopAI } from '@eventop/sdk/react';

export function CheckoutForm() {
  const { stepComplete, stepFail } = useEventopAI();
  const [email, setEmail] = useState('');

  async function handleContinue() {
    const ok = await validateEmail(email);
    if (ok) stepComplete();
    else stepFail('Please enter a valid email address.');
  }

  return (
    <EventopTarget id="email-field" name="Email Address" description="Enter your email">
      <div>
        <input
          type="email"
          value={email}
          onChange={e => setEmail(e.target.value)}
        />
        <button onClick={handleContinue}>Continue</button>
      </div>
    </EventopTarget>
  );
}

Tour status in your own UI

'use client';
import { useEventopTour } from '@eventop/sdk/react';

export function TourStatusBar() {
  const { isActive, isPaused, resume, cancel } = useEventopTour();

  if (!isActive && !isPaused) return null;

  return (
    <div className="tour-bar">
      {isPaused ? (
        <>
          <span>⏸ Tour paused</span>
          <button onClick={resume}>Resume</button>
        </>
      ) : (
        <span>▶ Guided tour running</span>
      )}
      <button onClick={cancel}>End tour</button>
    </div>
  );
}

API reference

<EventopAIProvider>

Prop Type Required Default Description
provider function Async function that calls your server route
appName string Shown in the chat header
assistantName string 'AI Guide' Name shown in the chat header
suggestions string[] [] Clickable chips on first open
theme object dark, default { mode, preset, tokens }
position object bottom-right { corner, offsetX, offsetY }

<EventopTarget>

Prop Type Required Description
id string Unique feature id
name string Human-readable name the AI reads
description string What it does — AI uses this to match user intent
navigate function Navigate here if component is not currently mounted
navigateWaitFor string CSS selector to wait for after navigating
advanceOn object { event, delay?, selector? } — auto-advance the tour
waitFor string CSS selector to wait for before showing this step

<EventopStep>

Prop Type Required Description
feature string * Feature id (*not needed if inside <EventopTarget>)
index number Position in the flow, 0-based
parentStep number Makes this a sub-step of another step
waitFor string CSS selector to wait for before showing
advanceOn object { event, delay?, selector? } — auto-advance

useEventopAI

Method Description
stepComplete() Advance the active tour step
stepFail(msg) Block advancement and show error in the tooltip
open() Open the chat panel
close() Close the chat panel
cancelTour() Hard cancel — no resume state saved
resumeTour() Resume a paused tour from where it left off
isActive() Returns true if a tour is currently running
isPaused() Returns true if a tour is paused
runTour(steps) Run a tour manually, bypassing the AI

useEventopTour

Property / Method Type Description
isActive boolean True if a tour is running
isPaused boolean True if a tour is paused
resume() function Resume a paused tour
cancel() function Hard cancel
open() function Open the chat panel
close() function Close the chat panel

Theme tokens

Token Default (dark) Default (light)
accent #e94560 #e94560
accentSecondary #a855f7 #7c3aed
bg #0f0f1a #ffffff
surface #1a1a2e #f8f8fc
border #2a2a4a #e4e4f0
text #e0e0f0 #1a1a2e
radius 16px 16px

Override any token:

theme={{
  mode: 'dark',
  tokens: {
    accent:     '#6366f1',
    radius:     '12px',
    fontFamily: "'Inter', sans-serif",
  }
}}

About

AI-powered guided tours for React and Next.js apps. Wrap any UI element, and an AI assistant guides users step-by-step to the right features, no component rewrites required.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published