@ghostystudio/widget
v0.2.1
Published
Ghosty Studio web widget for React: drop-in chat bubble (<GhostyWidget>) or headless hook (useGhostyChat) to draw your own chat.
Downloads
289
Maintainers
Readme
@ghostystudio/widget
The Ghosty Studio web widget for React. Put your agent on your site as the usual chat bubble, or draw your own chat with a headless hook. No dependencies besides React (≥ 18).
npm i @ghostystudio/widgetYou need a widget token (emb_…): agent → Channels → Web widget, or ghosty agents embed enable <agent-id> --domains yoursite.com. It only works on the domains you authorize there (https only; yoursite.com includes www., *.yoursite.com its subdomains).
The bubble: <GhostyWidget>
import { GhostyWidget } from "@ghostystudio/widget";
<GhostyWidget token="emb_…" />It mounts the same bubble + iframe as /widget.js and cleans up on unmount. It takes the look saved in Ghosty; props win over it:
| Prop | |
|---|---|
| position | "right" | "left" |
| offsetX, offsetY | px from the side and the bottom (0–200) |
| bubbleText | label next to the button (null hides it) |
| defaultOpen · open + onOpenChange | uncontrolled / controlled |
| style | the widget's style keys: fontFamily, radius, headerBg, headerText, titleUppercase, statusDot, headerAvatar, panelBg, userBubbleBg, userBubbleText, agentBubbleBg, agentBubbleText, inputBg, sendBg, sendIcon, sendArrow, launcherBg, launcherIcon, typing (bounce · flow · flashing · shimmer), typingColor (colors #rrggbb; validated by the server) |
| copy | the phrases while the agent works: waking, connecting, thinking (1–8 that rotate), stillWorking, cantStart, interrupted, retryLabel (plain text ≤ 80), showDuration, showCopyButton. Win over the saved ones; validated by the server |
| baseUrl | default https://www.ghosty.studio |
| zIndex | default 2147483000 |
A law firm that doesn't want Ghosty's playful waiting phrases:
<GhostyWidget token="emb_…" copy={{ waking: "Un momento, por favor", thinking: ["Revisando su consulta…"], showDuration: false }} />Headless: useGhostyChat
import { useState } from "react";
import { useGhostyChat } from "@ghostystudio/widget";
export function MyChat() {
const { messages, send, stop, status, error } = useGhostyChat({ token: "emb_…" });
const [text, setText] = useState("");
const busy = status === "thinking" || status === "streaming";
return (
<div>
{messages.map((m) => <p key={m.id} className={m.role}>{m.text}</p>)}
{status === "thinking" && <p>…</p>}
{error && <p role="alert">{error}</p>}
<form onSubmit={(e) => { e.preventDefault(); send(text); setText(""); }}>
<input value={text} onChange={(e) => setText(e.target.value)} />
{busy ? <button type="button" onClick={stop}>Stop</button> : <button>Send</button>}
</form>
</div>
);
}messages:{ id, role: "user" | "assistant", text, pending? }— the agent's text streams in.status:"connecting" | "idle" | "thinking" | "streaming".error: caps and notices as text ready to show.copy: the owner's phrases while the agent works (agent → Channels → Web widget → Texts, orghosty agents embed <id> --copy), complete with Ghosty's defaults — draw your own waiting state withcopy?.thinking[0].nulluntil it loads.- The visitor is a uuid in
localStorage(ghosty:widget:vid); their conversation comes back on return.
Requests go from your page to Ghosty with CORS: they only pass when the page's origin is one of the widget's domains. localhost is rejected — test on an authorized domain.
Changes in 0.2.0
<GhostyWidget copy>anduseGhostyChat().copy: the phrases while the agent works.
No package at all
ghosty agents embed <agent-id> --out src/components/GhostyChat.tsx writes a complete, editable component into your project with your token and theme baked in (shadcn style).
