Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added fern/assets/images/sigmond-thumbnail.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions fern/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
// component's public surface (components + types) without enumerating them.
export * from "./voice-widget/index";
export * from "./skeleton/index";
export * from "./sigmond-card/index";
146 changes: 146 additions & 0 deletions fern/components/sigmond-card/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { useEffect } from "react";

const WIDGET_UMD_URL =
"https://cdn.signalwire.com/npm/@signalwire/address-widget@dev/dist/address-widget.umd.js";

const DEFAULT_TOKEN =
"eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwidHlwIjoiU0FUIiwiY2giOiJwdWMuc2lnbmFsd2lyZS5jb20ifQ..IKPXZDR3ZYlpnUuw.yU8NBgPFQN1LH0Ws-Rs2ca-vAbUEP7Gvr0upOf-hDaTUfp-Aa5TALLMkJdUli8UX0ina34hng0G2XPLh7NZ2VZ5cphdd8AUCDKWkJ5HCI8538aNxPWcUWG25B-eELha7yEIrHZlO5BIxuyGVPskf0BIPGgiFSlctotyGB-VyYwSp_Uf6kdcwTCiEgra9LcyUMpNZyz5xH3p93xDU9bvzunFj50qIbfmQ2S0tDgEdduQWlTIfqUjRT6ePuwLGDBZYCCz9JtCQM0S2Fs7ZwvdSOzLFLqThkWnH3mSnkgKtkSTTXpnKUplPBJQaNRn0P4rJiwkk61BGyCJQ8Y8XYNeUHa-m1pRERQNweohQNBA0Ph3ie3vZLFJFjOeWuuztK8JSnfu4m9fxlkiA6icaHYuUm7KTSQ6n7oV5a2dkbLs7s51GRi7di0mm6Xw3jr2Zw1EGS8NgIrAU2HTv7stMAjpvDneYDfPaysCrmhCwu_TJgpG7PARiA4glKv9IuPeF5xpnymM5p0V2KL4HSEz-MJwziOwPpQRED_Brd2DiE0cz.CEYQUqZhcs5wKCtJQqUAMQ";

const DEFAULT_DESTINATION = "/public/sigmond";
const DEFAULT_LABEL = "Talk with Sigmond";
const DEFAULT_POSTER = "https://mcdn.signalwire.com/images/sigmond_still.png";

function pageSlug(): string {
if (typeof window === "undefined") return "";
const segments = (window.location.pathname || "/").split("/").filter(Boolean);
if (segments.length === 0) return "home";
return segments
.map((s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, ""))
.filter(Boolean)
.join("__");
}

type WidgetInstance = { open: () => Promise<void>; close: () => Promise<void>; theme?: string };
type WidgetGlobal = {
mount: (target: Element | string, options: Record<string, unknown>) => WidgetInstance;
unmount?: (widget: WidgetInstance) => Promise<void>;
};

declare global {
interface Window {
SignalWireAddressWidget?: WidgetGlobal;
}
}

let loaderPromise: Promise<WidgetGlobal> | null = null;
function loadWidgetGlobal(): Promise<WidgetGlobal> {
if (typeof window === "undefined") return Promise.reject(new Error("no window"));
if (window.SignalWireAddressWidget) return Promise.resolve(window.SignalWireAddressWidget);
if (!loaderPromise) {
loaderPromise = new Promise<WidgetGlobal>((resolve, reject) => {
const s = document.createElement("script");
s.src = WIDGET_UMD_URL;
s.async = true;
s.onload = () =>
window.SignalWireAddressWidget
? resolve(window.SignalWireAddressWidget)
: reject(new Error("address-widget UMD loaded but global missing"));
s.onerror = () => {
loaderPromise = null;
reject(new Error("failed to load address-widget UMD"));
};
document.head.appendChild(s);
});
}
return loaderPromise;
}

export interface SigmondWidgetProps {
token?: string;
}

export function SigmondWidget({ token = DEFAULT_TOKEN }: SigmondWidgetProps) {
useEffect(() => {
const teardown: Array<() => void> = [];

if (!document.head.querySelector("link[data-signalwire-address-fonts]")) {
const marker = document.createElement("link");
marker.setAttribute("data-signalwire-address-fonts", "host");
document.head.appendChild(marker);
}

void loadWidgetGlobal().catch(() => {});

const launchers = Array.from(
document.querySelectorAll<HTMLElement>("[data-sigmond-launcher]"),
).filter((el) => !el.dataset.sigmondWired);

for (const el of launchers) {
el.dataset.sigmondWired = "true";
let widget: WidgetInstance | null = null;
let busy = false;

const open = async () => {
if (busy) return;
busy = true;
el.classList.add("sigmond-card--opening");
try {
const global = await loadWidgetGlobal();
const theme = document.documentElement.classList.contains("dark") ? "dark" : "light";
if (!widget) {
const host = document.createElement("div");
host.className = "sigmond-widget-host";
host.setAttribute("aria-hidden", "true");
document.body.appendChild(host);
widget = global.mount(host, {
token,
destination: el.dataset.destination || DEFAULT_DESTINATION,
label: el.dataset.label || DEFAULT_LABEL,
poster: el.dataset.poster || DEFAULT_POSTER,
theme,
layout: "stacked",
video: true,
audio: true,
autoGainControl: false,
inputVolume: 125,
userVariables: { page_slug: pageSlug() },
});
const mounted = widget;
teardown.push(() => {
host.remove();
if (window.SignalWireAddressWidget?.unmount) {
void window.SignalWireAddressWidget.unmount(mounted);
}
});
}
widget.theme = theme;
void widget.open();
} catch (e) {
console.error("SigmondWidget:", e);
} finally {
busy = false;
el.classList.remove("sigmond-card--opening");
}
};

const onClick = () => void open();
const onKey = (e: KeyboardEvent) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
void open();
}
};
el.addEventListener("click", onClick);
el.addEventListener("keydown", onKey);
teardown.push(() => {
el.removeEventListener("click", onClick);
el.removeEventListener("keydown", onKey);
delete el.dataset.sigmondWired;
});
}

return () => teardown.forEach((fn) => fn());
}, [token]);

return null;
}
127 changes: 127 additions & 0 deletions fern/components/sigmond-card/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
.sigmond-card-wrap {
margin: 1.5rem 0;
}

.sigmond-card-wrap.sigmond-card-wrap--inset {
margin: 2rem 2rem 3rem;
}

.sigmond-card {
position: relative;
display: flex;
align-items: stretch;
gap: 1.25rem;
width: 100%;
text-align: left;
padding: 1rem;
border: 1px solid var(--grayscale-5);
border-radius: 0.75rem;
background: var(--grayscale-a2);
cursor: pointer;
color: inherit;
font: inherit;
transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease,
box-shadow 0.15s ease;
}

.sigmond-card:hover {
border-color: var(--sw-fuchsia);
background: var(--grayscale-a3, var(--grayscale-a2));
transform: translateY(-1px);
box-shadow: 0 6px 20px -12px rgba(var(--sw-fuchsia-rgb), 0.6);
}

.sigmond-card:focus-visible {
outline: 2px solid var(--sw-fuchsia);
outline-offset: 2px;
}

.sigmond-card--opening {
cursor: wait;
}

.sigmond-card-media {
position: relative;
flex: 0 0 auto;
width: 160px;
height: 90px;
align-self: center;
border-radius: 0.6rem;
background-color: var(--grayscale-4);
overflow: hidden;
pointer-events: none;
}

.sigmond-card-media img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}

.sigmond-card-badge {
position: absolute;
right: 6px;
bottom: 6px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border-radius: 999px;
background: var(--status-success);
color: #fff;
box-shadow: 0 2px 8px -2px rgba(22, 163, 74, 0.7);
}

.sigmond-card-body {
display: flex;
flex-direction: column;
justify-content: center;
gap: 0.25rem;
min-width: 0;
}

.sigmond-card-title {
font-size: 1rem;
font-weight: 700;
color: var(--grayscale-12);
}

.sigmond-card-desc {
font-size: 0.875rem;
line-height: 1.45;
color: var(--grayscale-a11);
}

.sigmond-card-cta {
display: inline-flex;
align-items: center;
gap: 0.35rem;
margin-top: 0.35rem;
font-size: 0.8125rem;
font-weight: 600;
color: var(--sw-fuchsia);
}

.sigmond-widget-host {
position: absolute;
width: 0;
height: 0;
}

signalwire-address::part(launcher) {
display: none !important;
}

@media (max-width: 640px) {
.sigmond-card-wrap.sigmond-card-wrap--inset {
margin: 1.5rem 1rem 2rem;
}

.sigmond-card-media {
width: 120px;
}
}
1 change: 1 addition & 0 deletions fern/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ css:
- styles.css
- components/skeleton/styles.css
- components/voice-widget/styles.css
- components/sigmond-card/styles.css

js:
- reo.js
Expand Down
22 changes: 22 additions & 0 deletions fern/products/home/pages/welcome.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ hide-toc: true
layout: custom
---

import { SigmondWidget } from "@/components/index";

<Markdown src="/snippets/llms-hint.mdx" />

<div class="lp-page-container">
Expand All @@ -32,6 +34,26 @@ layout: custom
<Button intent="primary" href="/docs/platform/getting-started" rightIcon="arrow-right">Get started</Button>
</div>

{/* Talk with Sigmond */}
<llms-ignore>

<div class="sigmond-card-wrap sigmond-card-wrap--inset">
<div class="sigmond-card" data-sigmond-launcher="true" role="button" tabindex="0" aria-label="Talk with Sigmond — start a video call">
<span class="sigmond-card-media">
<img class="sigmond-card-img" src="/assets/images/sigmond-thumbnail.jpg" alt="Sigmond" loading="eager" decoding="async" />
<span class="sigmond-card-badge"><Icon icon="phone" /></span>
</span>
<span class="sigmond-card-body">
<span class="sigmond-card-title">Talk with Sigmond</span>
<span class="sigmond-card-desc">Jump into a live video call with our AI agent.</span>
<span class="sigmond-card-cta">Start call <Icon icon="arrow-right" /></span>
</span>
</div>
</div>
<SigmondWidget />

</llms-ignore>

{/* What are you building? */}
<div class="products-section">
<div class="section-header">
Expand Down
10 changes: 10 additions & 0 deletions fern/products/platform/pages/ai/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,20 @@ description: Build programmable, realtime voice AI agents with SWML, the SignalW
max-toc-depth: 3
---

import { SigmondWidget } from "@/components/index";

SignalWire AI runs programmable voice agents on the same realtime platform that carries your calls.
Deploy a minimum viable product with no-code and low-code tools, then scale it with
[SWML](/docs/swml) or the [Server SDKs](/docs/server-sdks).

<llms-ignore>

<Markdown src="/snippets/common/talk-with-sigmond.mdx" />

<SigmondWidget />

</llms-ignore>

## Quickstart

Deploy a serverless AI agent and call it over the public switched telephone network (PSTN) in under 5 minutes.
Expand Down
9 changes: 9 additions & 0 deletions fern/snippets/common/talk-with-sigmond.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div class="sigmond-card-wrap">
<div class="sigmond-card" data-sigmond-launcher="true" role="button" tabindex="0" aria-label="Talk with Sigmond — start a video call">
<span class="sigmond-card-body">
<span class="sigmond-card-title">Talk with Sigmond</span>
<span class="sigmond-card-desc">Jump into a live video call with Sigmond, a helpful AI agent built with SignalWire.</span>
<span class="sigmond-card-cta">Start call <Icon icon="arrow-right" /></span>
</span>
</div>
</div>
Loading