JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 36
  • Score
    100M100P100Q85123F
  • License MIT

Dead simple RAG integration for React apps with OAuth authentication and custom scopes

Package Exports

  • @trainly/react
  • @trainly/react/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@trainly/react) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

@trainly/react

Dead simple RAG integration for React apps with V1 OAuth Authentication

Go from npm install to working AI in under 5 minutes. Now supports direct OAuth integration with permanent user subchats and complete privacy protection.

🆕 NEW: V1 Trusted Issuer Authentication

Use your existing OAuth provider (Clerk, Auth0, Cognito) directly with Trainly! Users get permanent private workspaces, and developers never see raw files or queries.

V1 Quick Start

1. Install

npm install @trainly/react

2. Register Your OAuth App (One-time)

curl -X POST "http://localhost:8000/v1/console/apps/register" \
  -H "X-Admin-Token: admin_dev_token_123" \
  -F "app_name=My App" \
  -F "issuer=https://clerk.myapp.com" \
  -F 'allowed_audiences=["my-clerk-frontend-api"]'

Save the app_id from the response!

3. Setup with V1 (Clerk Example)

// app/layout.tsx
import { ClerkProvider } from "@clerk/nextjs";
import { TrainlyProvider } from "@trainly/react";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <ClerkProvider>
          <TrainlyProvider appId="your_app_id_from_step_2">
            {children}
          </TrainlyProvider>
        </ClerkProvider>
      </body>
    </html>
  );
}

4. Use with OAuth Authentication

// Any component
import { useAuth } from "@clerk/nextjs";
import { useTrainly } from "@trainly/react";

function MyComponent() {
  const { getToken } = useAuth();
  const { ask, connectWithOAuthToken } = useTrainly();

  React.useEffect(() => {
    async function setupTrainly() {
      const idToken = await getToken();
      await connectWithOAuthToken(idToken);
    }
    setupTrainly();
  }, []);

  const handleClick = async () => {
    const answer = await ask("What files do I have?");
    console.log(answer); // AI response from user's permanent private subchat!
  };

  return <button onClick={handleClick}>Ask My AI</button>;
}

🔒 V1 Benefits

  • Permanent User Data: Same user = same private subchat forever
  • Complete Privacy: Developer never sees user files or queries
  • Any OAuth Provider: Clerk, Auth0, Cognito, Firebase, custom OIDC
  • Zero Migration: Works with your existing OAuth setup
  • Simple Integration: Just add appId and use connectWithOAuthToken()

🏷️ NEW: Custom Scopes (Zero Config)

Tag your documents with custom attributes for powerful filtering and organization:

import { useTrainly } from "@trainly/react";

function MyApp() {
  const { upload, ask } = useTrainly();

  // 1. Upload with scopes - use any keys you want!
  await upload(file, {
    playlist_id: "xyz123",
    workspace_id: "acme_corp",
    project: "alpha",
  });

  // 2. Query with scope filters - only get results from matching documents
  const answer = await ask("What are the key features?", {
    scope_filters: { playlist_id: "xyz123" },
  });
  // ☝️ Only searches documents with playlist_id="xyz123"

  // Query with multiple filters
  const answer2 = await ask("Show me updates", {
    scope_filters: {
      workspace_id: "acme_corp",
      project: "alpha",
    },
  });
  // ☝️ Only searches documents matching ALL specified scopes

  // Query everything (no filters)
  const answer3 = await ask("What do I have?");
  // ☝️ Searches ALL user's documents
}

No setup required! Just pass any key-value pairs - perfect for multi-tenant apps, playlist systems, workspace organization, and more.

Use Cases:

  • 🎵 Playlist Apps: Filter by playlist_id to query specific playlists
  • 🏢 Multi-Tenant SaaS: Filter by tenant_id or workspace_id
  • 📁 Project Management: Filter by project_id or team_id
  • 👥 User Segmentation: Filter by user_tier, department, etc.

📖 Full Scopes Guide

📁 File Management

Users can manage their uploaded files directly:

import { useTrainly, TrainlyFileManager } from "@trainly/react";

function MyApp() {
  const { listFiles, deleteFile, upload } = useTrainly();

  // List all user's files
  const handleListFiles = async () => {
    const result = await listFiles();
    console.log(
      `${result.total_files} files, ${result.total_size_bytes} bytes total`,
    );
    result.files.forEach((file) => {
      console.log(
        `${file.filename}: ${file.size_bytes} bytes, ${file.chunk_count} chunks`,
      );
    });
  };

  // Delete a specific file
  const handleDeleteFile = async (fileId) => {
    const result = await deleteFile(fileId);
    console.log(
      `Deleted ${result.filename}, freed ${result.size_bytes_freed} bytes`,
    );
  };

  return (
    <div>
      <button onClick={handleListFiles}>List My Files</button>

      {/* Pre-built file manager component */}
      <TrainlyFileManager
        onFileDeleted={(fileId, filename) => {
          console.log(`File deleted: ${filename}`);
        }}
        onError={(error) => {
          console.error("File operation failed:", error);
        }}
        showUploadButton={true}
        maxFileSize={5} // MB
      />
    </div>
  );
}

File Management Features

  • 📋 List Files: View all uploaded documents with metadata
  • 🗑️ Delete Files: Remove files and free up storage space
  • 📊 Storage Analytics: Track file sizes and storage usage

🏷️ NEW in v1.4.0: Custom Scopes

Tag your documents with custom attributes for powerful data segmentation!

import { useTrainly } from "@trainly/react";

function PlaylistUploader({ playlistId }) {
  const { upload } = useTrainly();

  const handleUpload = async (file: File) => {
    // Upload with custom scope values
    await upload(file, {
      playlist_id: playlistId,
      user_id: currentUser.id,
      is_public: false,
    });
  };

  return (
    <input type="file" onChange={(e) => handleUpload(e.target.files[0])} />
  );
}

Scope Features

  • 🎯 Data Segmentation: Keep playlists, workspaces, or projects separate
  • Faster Queries: Filter at database level before vector search
  • 🔒 Complete Isolation: Multi-tenant apps with full data privacy
  • 🎨 Flexible: Define any custom attributes you need

With TrainlyUpload Component

<TrainlyUpload
  variant="drag-drop"
  scopeValues={{
    playlist_id: "playlist_123",
    workspace_id: "workspace_456",
  }}
  onUpload={(files) => console.log("Uploaded with scopes!")}
/>

Complete Documentation

See SCOPES_GUIDE.md for:

  • Complete API reference
  • Real-world examples
  • Advanced patterns
  • Testing & debugging
  • Migration guide

Quick Reference:

  • upload(file, scopeValues) - Upload with scopes
  • bulkUploadFiles(files, scopeValues) - Bulk upload with scopes
  • <TrainlyUpload scopeValues={{...}} /> - Component with scopes
  • 🔄 Auto-Refresh: File list updates after uploads/deletions
  • 🎨 Pre-built UI: TrainlyFileManager component with styling
  • 🔒 Privacy-First: Only works in V1 mode with OAuth authentication

📚 Detailed File Management Documentation

1. Listing Files

Get all files uploaded to the user's permanent subchat:

import { useTrainly } from "@trainly/react";

function FileList() {
  const { listFiles } = useTrainly();

  const handleListFiles = async () => {
    try {
      const result = await listFiles();

      console.log(`Total files: ${result.total_files}`);
      console.log(`Total storage: ${formatBytes(result.total_size_bytes)}`);

      result.files.forEach((file) => {
        console.log(`📄 ${file.filename}`);
        console.log(`   Size: ${formatBytes(file.size_bytes)}`);
        console.log(`   Chunks: ${file.chunk_count}`);
        console.log(
          `   Uploaded: ${new Date(parseInt(file.upload_date)).toLocaleDateString()}`,
        );
        console.log(`   ID: ${file.file_id}`);
      });
    } catch (error) {
      console.error("Failed to list files:", error);
    }
  };

  return <button onClick={handleListFiles}>List My Files</button>;
}

Response Structure:

interface FileListResult {
  success: boolean;
  files: FileInfo[];
  total_files: number;
  total_size_bytes: number;
}

interface FileInfo {
  file_id: string; // Unique identifier for deletion
  filename: string; // Original filename
  upload_date: string; // Unix timestamp (milliseconds)
  size_bytes: number; // File size in bytes
  chunk_count: number; // Number of text chunks created
}

2. Bulk Upload Files

Upload multiple files at once (up to 10 files per request):

import { useTrainly } from "@trainly/react";

function BulkFileUpload() {
  const { bulkUploadFiles } = useTrainly();
  const [selectedFiles, setSelectedFiles] = useState<File[]>([]);

  const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(event.target.files || []);
    if (files.length > 10) {
      alert("Maximum 10 files allowed per bulk upload");
      return;
    }
    setSelectedFiles(files);
  };

  const handleBulkUpload = async () => {
    if (selectedFiles.length === 0) return;

    try {
      const result = await bulkUploadFiles(selectedFiles);

      console.log(`Bulk upload completed: ${result.message}`);
      console.log(
        `Successful: ${result.successful_uploads}/${result.total_files}`,
      );
      console.log(`Total size: ${formatBytes(result.total_size_bytes)}`);

      // Review individual file results
      result.results.forEach((fileResult) => {
        if (fileResult.success) {
          console.log(`${fileResult.filename} - ${fileResult.message}`);
        } else {
          console.log(`${fileResult.filename} - ${fileResult.error}`);
        }
      });

      // Clear selection after successful upload
      setSelectedFiles([]);
    } catch (error) {
      console.error("Bulk upload failed:", error);
    }
  };

  return (
    <div>
      <input
        type="file"
        multiple
        accept=".pdf,.txt,.docx"
        onChange={handleFileSelect}
      />

      {selectedFiles.length > 0 && (
        <div>
          <p>Selected files: {selectedFiles.length}</p>
          <ul>
            {selectedFiles.map((file, index) => (
              <li key={index}>
                {file.name} ({formatBytes(file.size)})
              </li>
            ))}
          </ul>
          <button onClick={handleBulkUpload}>
            Upload {selectedFiles.length} Files
          </button>
        </div>
      )}
    </div>
  );
}

// Helper function for formatting file sizes
function formatBytes(bytes: number): string {
  if (bytes === 0) return "0 Bytes";
  const k = 1024;
  const sizes = ["Bytes", "KB", "MB", "GB"];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
}

Bulk Upload Features:

  • Efficient: Upload up to 10 files in a single API call
  • Detailed Results: Individual success/failure status for each file
  • Error Resilience: Partial failures don't stop other files
  • Progress Tracking: Total size and success metrics
  • Automatic Retry: Token refresh handling built-in

3. Deleting Files

Remove a specific file and free up storage space:

import { useTrainly } from "@trainly/react";

function FileDeleter() {
  const { deleteFile, listFiles } = useTrainly();

  const handleDeleteFile = async (fileId: string, filename: string) => {
    // Always confirm before deletion
    const confirmed = confirm(
      `Delete "${filename}"? This will permanently remove the file and cannot be undone.`,
    );

    if (!confirmed) return;

    try {
      const result = await deleteFile(fileId);

      console.log(`${result.message}`);
      console.log(`🗑️ Deleted: ${result.filename}`);
      console.log(`💾 Storage freed: ${formatBytes(result.size_bytes_freed)}`);
      console.log(`📊 Chunks removed: ${result.chunks_deleted}`);

      // Optionally refresh file list
      await listFiles();
    } catch (error) {
      console.error("Failed to delete file:", error);
      alert(`Failed to delete file: ${error.message}`);
    }
  };

  // Example: Delete first file
  const deleteFirstFile = async () => {
    const files = await listFiles();
    if (files.files.length > 0) {
      const firstFile = files.files[0];
      await handleDeleteFile(firstFile.file_id, firstFile.filename);
    }
  };

  return <button onClick={deleteFirstFile}>Delete First File</button>;
}

Response Structure:

interface FileDeleteResult {
  success: boolean;
  message: string; // Human-readable success message
  file_id: string; // ID of deleted file
  filename: string; // Name of deleted file
  chunks_deleted: number; // Number of chunks removed
  size_bytes_freed: number; // Storage space freed up
}

4. Pre-built File Manager Component

Use the ready-made component for complete file management:

import { TrainlyFileManager } from "@trainly/react";

function MyApp() {
  return (
    <TrainlyFileManager
      // Optional: Custom CSS class
      className="my-custom-styles"
      // Callback when file is deleted
      onFileDeleted={(fileId, filename) => {
        console.log(`File deleted: ${filename} (ID: ${fileId})`);
        // Update your app state, show notification, etc.
      }}
      // Error handling callback
      onError={(error) => {
        console.error("File operation failed:", error);
        // Show user-friendly error message
        alert(`Error: ${error.message}`);
      }}
      // Show upload button in component
      showUploadButton={true}
      // Maximum file size in MB
      maxFileSize={5}
    />
  );
}

Component Features:

  • 📋 File List: Shows all files with metadata
  • 🔄 Auto-Refresh: Updates after uploads/deletions
  • ⚠️ Confirmation: Asks before deleting files
  • 📊 Storage Stats: Shows total files and storage used
  • 🎨 Styled: Clean, professional appearance
  • 📱 Responsive: Works on mobile and desktop

5. Complete Integration Example

Here's a full example showing all file operations together:

import React from "react";
import { useAuth } from "@clerk/nextjs"; // or your OAuth provider
import { useTrainly, TrainlyFileManager } from "@trainly/react";

export function CompleteFileExample() {
  const { getToken } = useAuth();
  const {
    ask,
    upload,
    listFiles,
    deleteFile,
    connectWithOAuthToken,
    isConnected,
  } = useTrainly();

  const [files, setFiles] = React.useState([]);
  const [storageUsed, setStorageUsed] = React.useState(0);

  // Connect to Trainly on mount
  React.useEffect(() => {
    async function connect() {
      const token = await getToken();
      if (token) {
        await connectWithOAuthToken(token);
      }
    }
    connect();
  }, []);

  // Load files when connected
  React.useEffect(() => {
    if (isConnected) {
      refreshFiles();
    }
  }, [isConnected]);

  const refreshFiles = async () => {
    try {
      const result = await listFiles();
      setFiles(result.files);
      setStorageUsed(result.total_size_bytes);
    } catch (error) {
      console.error("Failed to load files:", error);
    }
  };

  const handleBulkDelete = async () => {
    if (files.length === 0) {
      alert("No files to delete");
      return;
    }

    const confirmed = confirm(
      `Delete ALL ${files.length} files? This cannot be undone.`,
    );
    if (!confirmed) return;

    let deletedCount = 0;
    let totalFreed = 0;

    for (const file of files) {
      try {
        const result = await deleteFile(file.file_id);
        deletedCount++;
        totalFreed += result.size_bytes_freed;
        console.log(`Deleted: ${result.filename}`);
      } catch (error) {
        console.error(`Failed to delete ${file.filename}:`, error);
      }
    }

    alert(`Deleted ${deletedCount} files, freed ${formatBytes(totalFreed)}`);
    await refreshFiles();
  };

  const formatBytes = (bytes) => {
    if (bytes === 0) return "0 Bytes";
    const k = 1024;
    const sizes = ["Bytes", "KB", "MB", "GB"];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
  };

  if (!isConnected) {
    return <div>Connecting to Trainly...</div>;
  }

  return (
    <div style={{ maxWidth: "800px", margin: "0 auto", padding: "20px" }}>
      <h1>📁 My File Workspace</h1>

      {/* Storage Overview */}
      <div
        style={{
          background: "#f8fafc",
          padding: "20px",
          borderRadius: "8px",
          marginBottom: "20px",
        }}
      >
        <h3>Storage Overview</h3>
        <p>
          <strong>{files.length} files</strong> using{" "}
          <strong>{formatBytes(storageUsed)}</strong>
        </p>
        <div style={{ display: "flex", gap: "10px", marginTop: "10px" }}>
          <button onClick={refreshFiles}>🔄 Refresh</button>
          <button
            onClick={handleBulkDelete}
            disabled={files.length === 0}
            style={{ background: "#dc2626", color: "white" }}
          >
            🗑️ Delete All Files
          </button>
        </div>
      </div>

      {/* File Manager Component */}
      <TrainlyFileManager
        onFileDeleted={(fileId, filename) => {
          console.log(`File deleted: ${filename}`);
          // Update local state
          setFiles((prev) => prev.filter((f) => f.file_id !== fileId));
          refreshFiles(); // Refresh to get accurate totals
        }}
        onError={(error) => {
          alert(`Error: ${error.message}`);
        }}
        showUploadButton={true}
        maxFileSize={5}
      />

      {/* AI Integration */}
      <div
        style={{
          marginTop: "30px",
          padding: "20px",
          background: "#f0f9ff",
          borderRadius: "8px",
        }}
      >
        <h3>🤖 Ask AI About Your Files</h3>
        <button
          onClick={async () => {
            const answer = await ask(
              "What files do I have? Give me a summary of each.",
            );
            alert(`AI Response:\n\n${answer}`);
          }}
          style={{ background: "#059669", color: "white" }}
        >
          Get File Summary from AI
        </button>
      </div>
    </div>
  );
}

6. Error Handling Best Practices

import { useTrainly } from "@trainly/react";

function RobustFileManager() {
  const { deleteFile, listFiles } = useTrainly();

  const safeDeleteFile = async (fileId: string, filename: string) => {
    try {
      // 1. Confirm with user
      const confirmed = confirm(`Delete "${filename}"?`);
      if (!confirmed) return;

      // 2. Attempt deletion
      const result = await deleteFile(fileId);

      // 3. Success feedback
      console.log(`✅ Success: ${result.message}`);
      return result;
    } catch (error) {
      // 4. Handle specific error types
      if (error.message.includes("404")) {
        alert("File not found - it may have already been deleted");
      } else if (error.message.includes("401")) {
        alert("Authentication expired - please refresh the page");
      } else {
        alert(`Failed to delete file: ${error.message}`);
      }

      console.error("Delete error:", error);
      throw error;
    }
  };

  const safeListFiles = async () => {
    try {
      return await listFiles();
    } catch (error) {
      console.error("List files error:", error);

      if (error.message.includes("V1 mode")) {
        alert("File management requires V1 OAuth authentication");
      } else {
        alert(`Failed to load files: ${error.message}`);
      }

      return { success: false, files: [], total_files: 0, total_size_bytes: 0 };
    }
  };

  return (
    <div>
      <button onClick={() => safeListFiles()}>Safe List Files</button>
      <button onClick={() => safeDeleteFile("file_123", "example.pdf")}>
        Safe Delete Example
      </button>
    </div>
  );
}

7. TypeScript Support

Full TypeScript definitions included:

// Import types for better development experience
import type {
  FileInfo,
  FileListResult,
  FileDeleteResult,
  TrainlyFileManagerProps,
} from "@trainly/react";

// Type-safe file operations
const handleTypedFileOps = async () => {
  const fileList: FileListResult = await listFiles();
  const deleteResult: FileDeleteResult = await deleteFile("file_123");

  // Full IntelliSense support
  console.log(deleteResult.size_bytes_freed);
  console.log(fileList.total_size_bytes);
};

8. Security & Privacy Notes

  • 🔒 V1 Only: File management only works with V1 Trusted Issuer authentication
  • 👤 User Isolation: Users can only see and delete their own files
  • 🛡️ No Raw Access: Developers never see file content, only AI responses
  • 📊 Privacy-Safe Analytics: Storage tracking without exposing user data
  • ⚠️ Permanent Deletion: Deleted files cannot be recovered
  • 🔐 OAuth Required: Must be authenticated with valid OAuth token

9. Storage Management

File operations automatically update storage analytics:

// Storage is tracked automatically
const result = await deleteFile(fileId);
console.log(`Freed ${result.size_bytes_freed} bytes`);

// Check total storage
const files = await listFiles();
console.log(`Using ${files.total_size_bytes} bytes total`);

// Parent app analytics are updated automatically
// (visible in Trainly dashboard for developers)

🚀 Original Quick Start (Legacy)

1. Install

npm install @trainly/react

2. Setup (2 lines)

// app/layout.tsx
import { TrainlyProvider } from "@trainly/react";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <TrainlyProvider appSecret="as_your_app_secret">
          {children}
        </TrainlyProvider>
      </body>
    </html>
  );
}

3. Use anywhere (3 lines)

// Any component
import { useTrainly } from "@trainly/react";

function MyComponent() {
  const { ask } = useTrainly();

  const handleClick = async () => {
    const answer = await ask("What is photosynthesis?");
    console.log(answer); // Ready to use!
  };

  return <button onClick={handleClick}>Ask AI</button>;
}

That's it! No auth setup, no API routes, no session management.

📦 What's Included

Core Hook

const {
  ask, // (question: string) => Promise<string>
  upload, // (file: File) => Promise<void>
  isLoading, // boolean
  isConnected, // boolean
  error, // string | null
} = useTrainly();

Pre-built Components

import { TrainlyChat, TrainlyUpload, TrainlyStatus } from '@trainly/react';

// Drop-in chat interface
<TrainlyChat height="400px" showCitations={true} />

// Drop-in file upload
<TrainlyUpload accept=".pdf,.doc,.txt" />

// Connection status indicator
<TrainlyStatus />

🎯 Complete Example

import { TrainlyProvider, TrainlyChat, TrainlyUpload } from "@trainly/react";

function App() {
  return (
    <TrainlyProvider appSecret="as_demo_secret_123">
      <div>
        <h1>My Document Assistant</h1>

        {/* File upload area */}
        <TrainlyUpload onUpload={(files) => console.log("Uploaded:", files)} />

        {/* Chat interface */}
        <TrainlyChat
          height="500px"
          placeholder="Ask about your documents..."
          showCitations={true}
        />
      </div>
    </TrainlyProvider>
  );
}

🔧 Configuration Options

Authentication Modes

// Mode 1: V1 Trusted Issuer (NEW - recommended for OAuth apps)
<TrainlyProvider appId="app_v1_12345" /> // Register via console API first

// Mode 2: App Secret (legacy - for multi-user apps)
<TrainlyProvider appSecret="as_secret_123" />

// Mode 3: With user context (legacy)
<TrainlyProvider
  appSecret="as_secret_123"
  userId="user_123"
  userEmail="user@example.com"
/>

// Mode 4: Direct API key (legacy - simple apps)
<TrainlyProvider apiKey="tk_chat_id_key" />

V1 OAuth Provider Examples

// With Clerk
<TrainlyProvider
  appId="app_v1_clerk_123"
  baseUrl="https://api.trainly.com"
/>

// With Auth0
<TrainlyProvider
  appId="app_v1_auth0_456"
  baseUrl="https://api.trainly.com"
/>

// With AWS Cognito
<TrainlyProvider
  appId="app_v1_cognito_789"
  baseUrl="https://api.trainly.com"
/>

Component Customization

<TrainlyChat
  height="600px"
  theme="dark"
  placeholder="Ask me anything..."
  showCitations={true}
  enableFileUpload={true}
  onMessage={(msg) => console.log(msg)}
  onError={(err) => console.error(err)}
/>

<TrainlyUpload
  variant="drag-drop" // or "button" or "minimal"
  accept=".pdf,.doc,.txt"
  maxSize="10MB"
  multiple={false}
  onUpload={(files) => console.log(files)}
/>

🎨 Styling

Components use Tailwind classes by default but can be fully customized:

<TrainlyChat
  className="my-custom-chat"
  height="400px"
/>

// Override with CSS
.my-custom-chat {
  border: 2px solid blue;
  border-radius: 12px;
}

📖 API Reference

useTrainly()

The main hook for interacting with Trainly.

const {
  // Core functions
  ask: (question: string) => Promise<string>,
  askWithCitations: (question: string) => Promise<{answer: string, citations: Citation[]}>,
  upload: (file: File) => Promise<UploadResult>,

  // NEW: V1 Authentication
  connectWithOAuthToken: (idToken: string) => Promise<void>,

  // State
  isLoading: boolean,
  isConnected: boolean,
  error: TrainlyError | null,

  // Advanced
  clearError: () => void,
  reconnect: () => Promise<void>,

  // For chat components
  messages: ChatMessage[],
  sendMessage: (content: string) => Promise<void>,
  clearMessages: () => void,
} = useTrainly();

TrainlyProvider Props

interface TrainlyProviderProps {
  children: React.ReactNode;
  appId?: string; // NEW: V1 app ID from console registration
  appSecret?: string; // Legacy: App secret from Trainly dashboard
  apiKey?: string; // Legacy: Direct API key (alternative to appSecret)
  baseUrl?: string; // Custom API URL (defaults to trainly.com)
  userId?: string; // Legacy: Your app's user ID
  userEmail?: string; // Legacy: Your app's user email
}

🔍 Examples

See complete implementation examples in the API Documentation.

🆚 V1 vs Legacy Comparison

Feature V1 Trusted Issuer Legacy App Secret
User Auth Your OAuth provider Trainly OAuth flow
User Data Permanent private subchat Temporary or shared
Privacy Complete (dev can't see files) Limited
Setup Register once, use OAuth tokens Generate app secrets
Migration Zero (uses existing OAuth) Requires auth integration
Permanence Same user = same subchat forever Depends on implementation

Recommendation: Use V1 for new apps and consider migrating existing apps for better privacy and user experience.

🛠️ Development

# Clone the repo
git clone https://github.com/trainly/react-sdk.git
cd react-sdk

# Install dependencies
npm install

# Build the package
npm run build

# Watch mode for development
npm run dev

📝 License

MIT - see LICENSE file for details.

🤝 Contributing

Contributions welcome! Please read CONTRIBUTING.md for guidelines.

🆘 Support


Made with ❤️ by the Trainly team

The simplest way to add AI to your React app