@maxaiagent/widget-sdk
v1.6.0
Published
MaxAI Widget SDK — embed AI chat with page context, client-side tools, and event system
Downloads
2,172
Readme
@maxaiagent/widget-sdk
SDK do osadzania widgetu czatu MaxAI na dowolnej stronie internetowej.
Instalacja
NPM (React, Vue, Next.js, itp.)
npm install @maxaiagent/widget-sdkimport { MaxAI } from '@maxaiagent/widget-sdk'
const widget = MaxAI.init({ key: 'TWOJ_KLUCZ' })CDN (zwykły HTML)
<script src="https://maxaiagent.app/sdk.js" data-key="TWOJ_KLUCZ"></script>Widget automatycznie się zainicjalizuje. Aby korzystać z API SDK:
<script src="https://maxaiagent.app/sdk.js" data-key="TWOJ_KLUCZ"></script>
<script>
// MaxAI jest dostępny globalnie
MaxAI.setPageContext({ pageType: 'product', visibleData: { name: 'Buty Nike', price: 549 } })
</script>API
MaxAI.init(options)
Inicjalizuje widget. Zwraca WidgetInstance.
const widget = MaxAI.init({
key: 'TWOJ_KLUCZ', // Wymagane — klucz z panelu MaxAI
position: 'bottom-right', // 'bottom-right' | 'bottom-left'
baseUrl: 'https://maxaiagent.app', // Opcjonalne — override URL serwera
nonce: 'abc123', // Opcjonalne — CSP nonce dla stylów
lazy: false, // Opcjonalne — lazy load iframe
styles: { // Opcjonalne — style widgetu (patrz sekcja Styles)
theme: 'dark',
density: 'compact',
},
})widget.setPageContext(context)
Wysyła kontekst strony do agenta AI. Agent automatycznie zbiera tytuł, URL, meta tagi i JSON-LD. Użyj tej metody, aby dodać dodatkowe dane.
widget.setPageContext({
pageType: 'product',
visibleData: {
name: 'Nike Air Max 90',
price: 549,
currency: 'PLN',
sizes: [40, 41, 42, 43, 44, 45, 46],
},
customContext: {
user_type: 'premium',
cart_count: '3',
},
})widget.registerTool(definition)
Rejestruje narzędzie po stronie klienta. Agent AI może je wywoływać w trakcie rozmowy. Zwraca funkcję do wyrejestrowania.
const unregister = widget.registerTool({
slug: 'add_to_cart',
name: 'Dodaj do koszyka',
description: 'Dodaje produkt do koszyka użytkownika',
schema: {
type: 'object',
properties: {
product_id: { type: 'string', description: 'ID produktu' },
quantity: { type: 'number', description: 'Ilość', default: 1 },
size: { type: 'string', description: 'Rozmiar (np. "42")' },
},
required: ['product_id'],
},
requiresConfirmation: true, // Pokaż dialog potwierdzenia
handler: async (args) => {
const result = await myAPI.addToCart(args.product_id, args.quantity, args.size)
return { success: true, cart_count: result.totalItems }
},
})
// Później:
unregister()widget.on(event, callback)
Nasłuchuje na zdarzenia widgetu. Zwraca funkcję do odsubskrybowania.
widget.on('message', (msg) => console.log(`${msg.role}: ${msg.content}`))
widget.on('tool:call', (call) => console.log(`Tool: ${call.name}`, call.args))
widget.on('open', () => console.log('Widget otwarty'))
widget.on('close', () => console.log('Widget zamknięty'))
widget.on('ready', () => console.log('Widget gotowy'))
widget.on('error', (err) => console.error('Błąd:', err.message))widget.open() / widget.close() / widget.destroy()
widget.open() // Otwórz panel czatu
widget.close() // Zamknij panel czatu
widget.destroy() // Usuń widget ze strony i wyczyść listeneryStyle — widget.updateStyles(styles)
Steruj wyglądem widgetu z poziomu kodu. Style można podać przy inicjalizacji (init({ styles })) lub zmienić w runtime (np. przy zmianie theme).
Podanie przy inicjalizacji
const widget = MaxAI.init({
key: 'KLUCZ',
styles: {
theme: 'dark',
density: 'compact',
radius: 14,
fontSize: 13,
hideHeader: true,
hideLauncher: true,
maxHeight: 500,
container: {
background: '#07111a',
border: '1px solid rgba(255,255,255,0.08)',
shadow: '0 8px 32px rgba(0,0,0,0.3)',
},
bubbles: {
assistantBg: '#0c1824',
userBg: '#13283a',
assistantText: '#e2e8f0',
userText: '#f1f5f9',
borderRadius: 12,
},
input: {
background: '#091521',
border: '1px solid rgba(255,255,255,0.06)',
textColor: '#f1f5f9',
placeholderColor: '#7f92a6',
},
header: {
background: '#0a1929',
textColor: '#e2e8f0',
border: '1px solid rgba(255,255,255,0.06)',
},
},
})Zmiana w runtime (np. theme toggle)
// Aktualizuje WSZYSTKIE widgety: bubble + wszystkie inline
widget.updateStyles({ theme: 'light', density: 'default' })Pełna lista WidgetStyles
| Pole | Typ | Opis |
|---|---|---|
| theme | "dark" \| "light" \| "auto" | Motyw kolorystyczny |
| density | "default" \| "compact" \| "dense" | Gęstość treści (padding, gap, font) |
| radius | number | Border radius kontenera w px |
| fontSize | number | Bazowy rozmiar fontu w px |
| maxHeight | string \| number | Max wysokość (inline mode) |
| minHeight | string \| number | Min wysokość |
| width | string \| number | Szerokość |
| height | string \| number | Wysokość |
| hideHeader | boolean | Ukryj nagłówek |
| hideInput | boolean | Ukryj pole wpisywania (read-only) |
| hideLauncher | boolean | Ukryj przycisk launchera |
| container | { background, border, shadow, borderRadius } | Style kontenera |
| bubbles | { assistantBg, userBg, assistantText, userText, borderRadius } | Style dymków |
| input | { background, border, textColor, placeholderColor } | Style pola wpisywania |
| header | { background, textColor, border } | Style nagłówka |
Eksportowane stałe
import { WIDGET_THEMES, WIDGET_DENSITIES } from '@maxaiagent/widget-sdk'
// WIDGET_THEMES = ["dark", "light", "auto"] as const
// WIDGET_DENSITIES = ["default", "compact", "dense"] as const
import type { WidgetTheme, WidgetDensity } from '@maxaiagent/widget-sdk'Inline Widget — widget.mountInline(el, options?)
Osadź widget bezpośrednio w kontenerze na stronie (zamiast pływającego bubble).
Zwraca InlineInstance — ref do sterowania tym konkretnym widgetem.
Sposób 1: Z kodu (SDK)
const widget = MaxAI.init({ key: 'KLUCZ' })
const chatRef = widget.mountInline('#news-chat-panel', {
sessionKey: 'article-123', // Opcjonalne — klucz sesji (patrz niżej)
sessionPolicy: 'per-key', // 'per-key' | 'sticky' | 'always-new'
styles: { maxHeight: 500 }, // Opcjonalne — style per-instancja
})Sposób 2: Z HTML (auto-mount)
Dodaj div z klasą maxai-inline — SDK automatycznie zamontuje widget (także dla elementów dodanych dynamicznie przez SPA):
<div
class="maxai-inline"
data-key="TWOJ_KLUCZ"
data-session-key="article-123"
style="height: 500px"
></div>| Atrybut | Opis |
|---|---|
| data-key | Klucz widgetu (opcjonalny jeśli podany w init()) |
| data-session-key | Klucz sesji — różne klucze = osobne rozmowy |
| data-context-* | Dodatkowy kontekst, np. data-context-article-id="123" |
Dostęp do instancji przez ref (Vue / React)
Jeśli widget jest zamontowany przez auto-mount (klasa maxai-inline), możesz uzyskać dostęp do jego API przez getInlineInstance():
Vue 3:
<template>
<div class="maxai-inline" data-key="KLUCZ" data-session-key="news-123" ref="chatDiv" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
const chatDiv = ref()
onMounted(() => {
const inst = MaxAI.getInlineInstance(chatDiv.value)
inst?.addMessage('Witaj! Oto podsumowanie tego artykułu...')
})
</script>React:
import { useRef, useEffect } from 'react'
function NewsChat({ newsId }: { newsId: string }) {
const chatRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const inst = MaxAI.getInlineInstance(chatRef.current!)
inst?.setSessionKey(newsId)
inst?.addMessage('Oto podsumowanie...')
}, [newsId])
return <div className="maxai-inline" data-key="KLUCZ" ref={chatRef} style={{ height: 500 }} />
}Sesje — InlineInstance API
Każdy inline widget może mieć osobną sesję powiązaną z kluczem (sessionKey).
To umożliwia np. osobną rozmowę per artykuł, per produkt, per ticket.
inline.setSessionKey(key)
Zmiana klucza sesji. Gdy klucz się zmienia — widget czyści rozmowę i startuje nową sesję. Gdy klucz jest taki sam — no-op (deduplikacja, bez resetu przy rerender).
const chat = widget.mountInline('#panel', { sessionKey: 'news-A' })
// Użytkownik przełącza się na inny news:
chat.setSessionKey('news-B') // → nowa sesja
// Powrót do poprzedniego newsa — sesja jest zachowana:
chat.setSessionKey('news-A') // → przywraca wcześniejszą rozmowęZachowanie sesji
| Akcja | Efekt |
|---|---|
| setSessionKey('X') gdy current = 'X' | no-op (deduplikacja) |
| setSessionKey('Y') gdy current = 'X' | clear + nowa sesja dla 'Y' |
| Rerender komponentu Vue/React | nic — nie resetuje sesji |
| resetSession() | Force clear nawet dla tego samego klucza |
Sesje per-key są trzymane w localStorage jako maxai_st_{widgetKey}_scope_{sessionKey}.
Powrót do wcześniejszego klucza przywraca zapisaną rozmowę.
sessionPolicy
| Wartość | Zachowanie |
|---|---|
| "per-key" (domyślne) | Nowa sesja gdy sessionKey się zmienia |
| "sticky" | Nigdy nie resetuj sesji (nawet przy zmianie klucza) |
| "always-new" | Nowa sesja przy każdym wywołaniu |
Wiadomości — inline.addMessage() / widget.addMessage()
Wstrzyknij wiadomość do czatu programowo (np. gotowy dymek z podsumowaniem).
// Na konkretnej instancji inline:
chat.addMessage('Oto podsumowanie tego artykułu...')
chat.addMessage('Mam pytanie o cenę', 'user')
// Na głównym bubble widgecie:
widget.addMessage('Witaj! Jak mogę Ci pomóc?')
widget.addMessage('Chcę zwrócić produkt', 'user')Parametry:
content: string— treść wiadomościrole?: "assistant" | "user"— domyślnie"assistant"
Pełne API InlineInstance
Zwracane przez widget.mountInline() lub MaxAI.getInlineInstance(el).
| Metoda | Opis |
|---|---|
| setSessionKey(key) | Zmień klucz sesji (nowy klucz = nowa rozmowa) |
| addMessage(content, role?) | Wstrzyknij wiadomość do czatu |
| updateStyles(styles) | Zmień style tej instancji |
| resetSession() | Wymuś nową sesję (nawet dla tego samego klucza) |
| destroy() | Usuń widget z DOM |
| sessionKey (readonly) | Aktualny klucz sesji |
| container | HTMLElement kontenera |
| iframe | HTMLIFrameElement |
Pełne API WidgetInstance
Zwracane przez MaxAI.init().
| Metoda | Opis |
|---|---|
| mountInline(el, options?) | Zamontuj inline widget, zwraca InlineInstance |
| getInlineInstance(el) | Pobierz instancję zamontowaną na danym elemencie |
| setPageContext(ctx) | Wyślij kontekst strony do agenta |
| setIdentity(payload) | Ustaw tożsamość użytkownika (HMAC podpis) |
| registerTool(def) | Zarejestruj narzędzie klienta |
| unregisterTool(slug) | Wyrejestruj narzędzie |
| on(event, callback) | Nasłuchuj na zdarzenia |
| off(event, callback) | Odsubskrybuj zdarzenie |
| updateStyles(styles) | Zmień style na bubble + wszystkich inline |
| addMessage(content, role?) | Wstrzyknij wiadomość do bubble widgetu |
| open() | Otwórz panel czatu |
| close() | Zamknij panel czatu |
| destroy() | Usuń widget i wyczyść listenery |
Atrybuty data-context-*
Dodatkowy kontekst można przekazać bezpośrednio w tagu <script> lub na kontenerze inline:
<script
src="https://maxaiagent.app/sdk.js"
data-key="TWOJ_KLUCZ"
data-context-page="product"
data-context-user-type="premium"
></script>Agent otrzyma te dane jako customContext: { page: 'product', user_type: 'premium' }.
Przykłady
Terminal / News — inline widget z sesjami per artykuł
import { MaxAI, type InlineInstance } from '@maxaiagent/widget-sdk'
const widget = MaxAI.init({
key: 'KLUCZ',
styles: {
theme: 'dark',
density: 'compact',
container: { background: '#07111a', border: '1px solid rgba(255,255,255,0.08)' },
bubbles: { assistantBg: '#0c1824', userBg: '#13283a' },
input: { background: '#091521', placeholderColor: '#7f92a6' },
},
})
let chatRef: InlineInstance | null = null
function showNews(newsId: string, title: string, summary: string) {
if (!chatRef) {
chatRef = widget.mountInline('#news-chat', {
sessionKey: newsId,
styles: { maxHeight: 500, hideHeader: true, hideLauncher: true },
})
} else {
chatRef.setSessionKey(newsId)
}
chatRef.addMessage(`**${title}**\n\n${summary}`)
}
// Użytkownik klika na news:
showNews('bybit-risk-2026', 'Bybit zmienia limity ryzyka', 'Bybit dostosowuje limity...')
// Przełącza na inny news:
showNews('shib-81b', 'SHIB nadpodaż', '81 bilionów tokenów...')
// Powrót do pierwszego — sesja zachowana:
showNews('bybit-risk-2026', 'Bybit zmienia limity ryzyka', 'Bybit dostosowuje limity...')E-commerce — strona produktu (CDN)
<script src="https://maxaiagent.app/sdk.js" data-key="KLUCZ"></script>
<script>
MaxAI.setPageContext({
pageType: 'product',
visibleData: {
name: document.querySelector('h1').textContent,
price: document.querySelector('.price').textContent,
},
})
MaxAI.registerTool({
slug: 'add_to_cart',
name: 'Dodaj do koszyka',
description: 'Dodaje produkt do koszyka',
schema: {
type: 'object',
properties: {
size: { type: 'string', description: 'Rozmiar' },
quantity: { type: 'number', default: 1 },
},
},
requiresConfirmation: true,
handler: async (args) => {
document.querySelector('.add-to-cart-btn').click()
return { success: true }
},
})
</script>SPA (Vue 3) — dynamiczny theme toggle
<template>
<button @click="toggleTheme">Toggle theme</button>
</template>
<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
import { MaxAI } from '@maxaiagent/widget-sdk'
const isDark = ref(true)
let widget = null
onMounted(() => {
widget = MaxAI.init({
key: 'KLUCZ',
styles: { theme: 'dark' },
})
})
onUnmounted(() => widget?.destroy())
function toggleTheme() {
isDark.value = !isDark.value
widget?.updateStyles({ theme: isDark.value ? 'dark' : 'light' })
}
</script>Typy TypeScript
import type {
MaxAIInitOptions,
PageContext,
ToolDefinition,
WidgetInstance,
WidgetEvent,
ChatMessage,
ToolCallEvent,
WidgetStyles,
WidgetTheme,
WidgetDensity,
SessionPolicy,
InlineMountOptions,
InlineInstance,
} from '@maxaiagent/widget-sdk'
import { WIDGET_THEMES, WIDGET_DENSITIES } from '@maxaiagent/widget-sdk'Funkcje automatyczne
Po włączeniu w panelu MaxAI (zakładka "Inteligencja"):
| Funkcja | Opis | Wymaga kodu? |
|---|---|---|
| Kontekst strony | Agent widzi tytuł, URL, meta, JSON-LD, treść <main> | Nie |
| Point-and-Ask | Użytkownik wskazuje element na stronie | Nie |
| Zgłaszanie problemów | Przycisk bug report z logami konsoli | Nie |
| GDPR | Baner zgody przed zbieraniem danych | Nie |
| Auto-adaptive theme | Widget dopasowuje kolory do strony | Nie |
| Glassmorphism | Półprzezroczysty efekt szkła | Nie |
| Narzędzia klienta | registerTool() — agent wywołuje funkcje | Tak |
| Explicit context | setPageContext() — dodatkowe dane | Tak |
| Inline widget | mountInline() — osadzony w kontenerze | Tak |
| Style SDK | updateStyles() — zmiana wyglądu z kodu | Tak |
| Sesje per-key | setSessionKey() — osobna rozmowa per kontekst | Tak |
| Inject messages | addMessage() — wstrzyknij dymek programowo | Tak |
