Skip to main content

Overview

Add your Invent AI assistant to any Astro site. Perfect for content-focused websites with islands architecture and multi-framework support.

Installation

1

Create Assistant Component

Astro supports multiple frameworks. Choose your preferred approach:

Pure Astro Component

---
// src/components/InventAssistant.astro
export interface Props {
  assistantId: string;
  themeAppearance?: 'auto' | 'light' | 'dark';
  themeButtonBackgroundColor?: string;
  themeButtonColor?: string;
  userId?: string;
  userName?: string;
  userHash?: string;
  userAvatar?: string;
}

const {
  assistantId,
  themeAppearance = 'auto',
  themeButtonBackgroundColor,
  themeButtonColor,
  userId,
  userName,
  userHash,
  userAvatar,
} = Astro.props;
---

<invent-assistant
  assistant-id={assistantId}
  theme-appearance={themeAppearance}
  {themeButtonBackgroundColor && `theme-button-background-color="${themeButtonBackgroundColor}"`}
  {themeButtonColor && `theme-button-color="${themeButtonColor}"`}
  {userId && `user-id="${userId}"`}
  {userName && `user-name="${userName}"`}
  {userHash && `user-hash="${userHash}"`}
  {userAvatar && `user-avatar="${userAvatar}"`}
/>
<script type="text/javascript" src="https://www.useinvent.com/button.js" async defer></script>

React Component (with React integration)

// src/components/InventAssistant.tsx
interface Props {
  assistantId: string;
  themeAppearance?: 'auto' | 'light' | 'dark';
  themeButtonBackgroundColor?: string;
  themeButtonColor?: string;
  userId?: string;
  userName?: string;
  userHash?: string;
  userAvatar?: string;
}

export default function InventAssistant({
  assistantId,
  themeAppearance = 'auto',
  themeButtonBackgroundColor,
  themeButtonColor,
  userId,
  userName,
  userHash,
  userAvatar,
}: Props) {
  return (
    <>
      <invent-assistant
        assistant-id={assistantId}
        theme-appearance={themeAppearance}
        {...(themeButtonBackgroundColor && {
          'theme-button-background-color': themeButtonBackgroundColor,
        })}
        {...(themeButtonColor && {
          'theme-button-color': themeButtonColor,
        })}
        {...(userId && { 'user-id': userId })}
        {...(userName && { 'user-name': userName })}
        {...(userHash && { 'user-hash': userHash })}
        {...(userAvatar && { 'user-avatar': userAvatar })}
      />
      <script
        type="text/javascript"
        src="https://www.useinvent.com/button.js"
        async
        defer
      />
    </>
  );
}
2

Add to Layout

---
// src/layouts/Layout.astro
import InventAssistant from '../components/InventAssistant.astro';
// Or if using React:
// import InventAssistant from '../components/InventAssistant.tsx';

const assistantId = import.meta.env.PUBLIC_INVENT_ASSISTANT_ID;
---

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Your Site</title>
  </head>
  <body>
    <slot />
    <InventAssistant assistantId={assistantId} />

    <!-- If using React component, add client directive -->
    <!-- <InventAssistant client:load assistantId={assistantId} /> -->
  </body>
</html>
3

Configure Environment

Add to .env:
PUBLIC_INVENT_ASSISTANT_ID=ast_YOUR_ASSISTANT_ID
INVENT_SECRET_KEY=your_secret_key_here

User Authentication

Security Requirement: When using any user-* attributes (user-id, user-name, user-avatar), you must also provide user-hash. Both user-id and user-hash must be provided together, or neither should be provided. The user-hash must be generated on your backend using HMAC-SHA256 with your assistant’s secret key. Never expose the secret key to the client.

Server-Side Hash Generation

Use Astro API endpoints:
// src/pages/api/user-hash.ts
import type { APIRoute } from 'astro';
import crypto from 'crypto';

export const GET: APIRoute = async ({ locals }) => {
  // Get user from your auth system
  const user = locals.user;

  if (!user) {
    return new Response(
      JSON.stringify({ error: 'Unauthorized' }),
      { status: 401, headers: { 'Content-Type': 'application/json' } }
    );
  }

  const secretKey = import.meta.env.INVENT_SECRET_KEY;
  const userId = user.id;

  const userHash = crypto
    .createHmac('sha256', secretKey)
    .update(userId)
    .digest('hex');

  return new Response(
    JSON.stringify({
      userId,
      userName: user.name,
      userAvatar: user.avatar,
      userHash,
    }),
    { status: 200, headers: { 'Content-Type': 'application/json' } }
  );
};

Using in Layout with Server-Side Data

---
// src/layouts/Layout.astro
import InventAssistant from '../components/InventAssistant.astro';
import crypto from 'crypto';

const assistantId = import.meta.env.PUBLIC_INVENT_ASSISTANT_ID;

// Get user from locals (set by middleware)
const user = Astro.locals.user;

let userData = null;
if (user) {
  const secretKey = import.meta.env.INVENT_SECRET_KEY;
  const userId = user.id;
  const userHash = crypto
    .createHmac('sha256', secretKey)
    .update(userId)
    .digest('hex');

  userData = {
    userId,
    userName: user.name,
    userAvatar: user.avatar,
    userHash,
  };
}
---

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Your Site</title>
  </head>
  <body>
    <slot />
    <InventAssistant
      assistantId={assistantId}
      {...userData}
    />
  </body>
</html>

Content Collections Integration

Pass content data for better context:
---
// src/pages/blog/[slug].astro
import { getCollection, getEntry } from 'astro:content';
import Layout from '../../layouts/Layout.astro';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map((post) => ({
    params: { slug: post.slug },
    props: { post },
  }));
}

const { post } = Astro.props;
const { Content } = await post.render();
---

<Layout title={post.data.title}>
  <article>
    <h1>{post.data.title}</h1>
    <Content />
  </article>

  <script define:vars={{ post: post.data }}>
    window.inventContext = {
      pageType: 'blog-post',
      title: post.title,
      author: post.author,
      category: post.category,
    };
  </script>
</Layout>

TypeScript Support

Add type declarations:
// src/env.d.ts
/// <reference types="astro/client" />

declare namespace App {
  interface Locals {
    user?: {
      id: string;
      name: string;
      avatar?: string;
    };
  }
}

interface ImportMetaEnv {
  readonly PUBLIC_INVENT_ASSISTANT_ID: string;
  readonly INVENT_SECRET_KEY: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

declare global {
  interface Window {
    inventContext?: Record<string, any>;
  }

  namespace JSX {
    interface IntrinsicElements {
      'invent-assistant': {
        'assistant-id': string;
        'theme-appearance'?: 'auto' | 'light' | 'dark';
        'theme-button-background-color'?: string;
        'theme-button-color'?: string;
        'user-id'?: string;
        'user-name'?: string;
        'user-hash'?: string;
        'user-avatar'?: string;
      };
    }
  }
}

Using with React/Vue/Svelte Islands

Astro’s island architecture allows mixing frameworks:
---
// src/pages/index.astro
import Layout from '../layouts/Layout.astro';
import ReactComponent from '../components/ReactComponent.tsx';
import VueComponent from '../components/VueComponent.vue';
---

<Layout>
  <h1>Welcome</h1>

  <!-- React island with client:load directive -->
  <ReactComponent client:load />

  <!-- Vue island with client:visible directive -->
  <VueComponent client:visible />
</Layout>

Tips for Astro

Islands Architecture

Perfect for partial hydration

Multi-Framework

Use React, Vue, Svelte, or pure Astro

Content Collections

Integrate with content data

SSG & SSR

Works with static and server modes

Troubleshooting

Solutions:
  • Ensure script tag is in component, not layout head
  • Check client directives if using framework components
  • Verify URL is correct
  • Check browser console for errors
Solutions:
  • Use appropriate client directive (client:load, client:visible)
  • Ensure script loads after component hydrates
  • Check for SSR/client mismatches
Solutions:
  • Restart dev server after adding env variables
  • Use PUBLIC_ prefix for client-accessible variables
  • Keep secret key without prefix (server-only)
  • Use import.meta.env not process.env

Best Practices

  • Use API routes or server-side code for hash generation
  • Keep secret key in .env (never commit)
  • Use PUBLIC_ prefix only for non-sensitive variables
  • Choose appropriate client directives for islands
  • Leverage Astro’s content collections for context