npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@italq/vloop

v0.0.9

Published

Embeddable voice-call widget powered by [Pipecat](https://github.com/pipecat-ai). Ships as both a Web Component (drop-in `<vloop-ai>` via a `<script>` tag) and a React component library (composable primitives + the preset all-in-one).

Readme

@italq/vloop

Embeddable voice-call widget powered by Pipecat. Ships as both a Web Component (drop-in <vloop-ai> via a <script> tag) and a React component library (composable primitives + the preset all-in-one).

  • Web Component: single bundle, zero install, works in any HTML page.
  • React API: VLoop.AI for the all-in-one case, or compose VLoop.Root + primitives (When, Trigger, Audio, Timer, Waveform, HangUp, …) for custom UI.
  • Themeable via CSS custom properties or a typed theme object — no JSON-string styling.
  • Pluggable transport. Default is Pipecat; implement VoiceTransport to swap.
  • Explicit state machine (idle | connecting | in-call | ended | error) exposed via hooks and render-time <When>.

Install

Script tag (CDN):

<script src="https://unpkg.com/@italq/[email protected]/dist/bundle.min.js" async></script>

npm / pnpm:

pnpm add @italq/vloop
# or: npm install @italq/vloop

Quick start

Web Component

<vloop-ai
  agent-id="4018131b-7068-4ef4-8663-4f850ab713e9"
  agent-name="Aria"
  client="vloop"
  btn-text="Call"
  size="230px"
  position="center" />

<script src="https://unpkg.com/@italq/[email protected]/dist/bundle.min.js" async></script>

React (preset)

import { VLoopAI } from '@italq/vloop';

export default function Page() {
  return (
    <VLoopAI
      agentId="4018131b-7068-4ef4-8663-4f850ab713e9"
      agentName="Aria"
      client="vloop"
      btnText="Call"
      size="230px"
      position="center"
    />
  );
}

React (composing primitives)

import { VLoop } from '@italq/vloop';

<VLoop.Provider call={{ client: 'vloop', cliId: 'c-001', appId: 'sales' }}>
  <VLoop.Root agent={{ id: 'agt_42', name: 'Aria' }}>
    <VLoop.When status="idle">
      <VLoop.Trigger asChild>
        <button className="my-btn">Talk to Aria</button>
      </VLoop.Trigger>
    </VLoop.When>

    <VLoop.When status="connecting">Connecting…</VLoop.When>

    <VLoop.When status="in-call">
      <VLoop.Audio />
      <header>
        <VLoop.StatusDot />
        <VLoop.AgentName as="h2" />
        <VLoop.Timer />
      </header>
      <VLoop.Waveform />
      <VLoop.HangUp asChild>
        <button>End call</button>
      </VLoop.HangUp>
    </VLoop.When>

    <VLoop.When status="error">
      <VLoop.ErrorLabel />
      <VLoop.Retry asChild>
        <button>Retry</button>
      </VLoop.Retry>
    </VLoop.When>
  </VLoop.Root>
</VLoop.Provider>

Web Component attributes

All attributes are kebab-case. Names that map to the legacy v0 camelCase form are still accepted and emit a deprecation warning in the console.

| Attribute | Type | Default | |------------------|-----------------------------------|-----------------------------------| | agent-id | string (required) | — | | agent-name | string | "AI Assistant" | | client | string | "unknown" | | cli-id | string | — | | app-id | string | — | | call-id | string | — | | caller-num | string | — | | callee-num | string | — | | endpoint | URL string | "https://api.italq.io/connect" | | btn-text | string | "Call" | | size | CSS length | "280px" | | position | inline | left | center | right | "center" | | variant | orb | pill | "orb" | | force-secure-ws (alias wss) | boolean (presence) | off — rewrites ws://wss:// on the backend's WebSocket URL (dev escape hatch) | | theme-accent | CSS color | "#1d5ba5" | | theme-radius | sm | md | lg | full | "full" | | theme-bg | CSS color | "rgba(10,30,65,0.7)" | | theme-font | CSS font-family | "Outfit, Inter, sans-serif" | | theme-metal | CSS color | "#1d5ba5" | | theme-aura | on | off | "on" | | theme-aura-intensity | subtle | normal | vivid | 01.5 | "normal" | | theme-aura-speed | number (1 = normal, 2 = 2× faster, 0.5 = half) | 1 | | theme-aura-color | CSS color | lightened theme-metal | | theme-agent-color | CSS color (agent-name text) | #ffffff | | theme-btn-color | CSS color (button label text) | theme text | | theme-btn-bg | CSS color (button fill, pill only) | theme-accent | | theme-shadow | raw CSS box-shadow (outer shadow) | layered dark drop shadow | | theme-pill-bg | CSS background (pill variant only) | theme-bg |

Variants

variant chooses the widget's visual form:

  • orb (default) — the full circular orb in every state.
  • pill — a compact horizontal pill at rest (mini aura avatar + agent name + call button) that morphs into the full orb once a call starts. Less intrusive; anchors per position. Its background is set independently via theme-pill-bg (any CSS background — color, gradient, rgba).
<vloop-ai agent-id="..." variant="pill" position="right" theme-pill-bg="#ffffff" />

The aura

The orb's surface is a living conic "fan" of rays that flows and breathes. It has two states, crossfaded smoothly:

| State | When | Feel | |-------|------|------| | Calm | no call (idle / ended) | slow drift, barely moving | | In-call | connecting / in-call | steadier, more present |

theme-metal recolors the whole effect from a single base color; light/mid/deep ray stops are derived from it automatically. The theme-aura-* attributes tune it further and compose on top of both states (e.g. theme-aura-speed="2" speeds up both proportionally rather than replacing them):

<vloop-ai
  agent-id="..."
  theme-metal="#6d28d9"
  theme-aura-intensity="vivid"
  theme-aura-color="#a78bfa" />

<!-- Sober, static fan (no animation, lower CPU): -->
<vloop-ai agent-id="..." theme-aura="off" />

Theming via inline CSS variables

theme-* attributes are a convenience for the common cases. For fine-grained control, set CSS variables directly:

<vloop-ai
  agent-id="..."
  style="--vloop-accent: #6366f1;
         --vloop-bg: rgba(20, 20, 30, 0.85);
         --vloop-radius-btn: 0.75em;" />

Available variables: --vloop-accent, --vloop-bg, --vloop-bg-deep, --vloop-font, --vloop-radius-btn, --vloop-text, --vloop-text-muted, --vloop-border, --vloop-shadow, --vloop-shadow-hover, --vloop-status-color, --vloop-metal, --vloop-aura-color, --vloop-aura-intensity, --vloop-aura-speed, --vloop-agent-color, --vloop-btn-color, --vloop-shadow-user, --vloop-pill-bg, --vloop-btn-bg.


React API

Components

| Export | Purpose | |-------------------------|--------------------------------------------------------------------------| | VLoop.AI | All-in-one preset. Equivalent to the Web Component. | | VLoop.Provider | Optional ancestor that supplies default call / theme / transport. | | VLoop.Root | Owns one call session and exposes state + actions via context. | | VLoop.When | Render its children only while state.status matches. | | VLoop.Trigger | Starts the call. asChild clones any child (Radix-style Slot). | | VLoop.HangUp | Ends the current call. | | VLoop.Retry | Re-runs start() after an error. | | VLoop.Audio | Mounts the Pipecat audio sink. Render only while in-call. | | VLoop.AgentName | Renders agent.name. as chooses the wrapping element. | | VLoop.StatusDot | Color-coded dot reflecting the current status. | | VLoop.Timer | Elapsed call time. Default format MM:SS, override with format. | | VLoop.Waveform | Animated bars; pulses while botSpeaking. | | VLoop.ErrorLabel | Displays state.error.message while in error. |

Theme object

VLoop.AI and VLoop.Provider accept a typed theme (the React equivalent of the theme-* attributes):

<VLoopAI
  agentId="..."
  theme={{
    accent: '#1d5ba5',      // primary accent (pill button default fill)
    radius: 'full',         // 'sm' | 'md' | 'lg' | 'full'
    bg: 'rgba(10,30,65,0.7)', // generic background (orb panel / pill default)
    font: 'Outfit, sans-serif',
    metal: '#6d28d9',       // base color the aura derives from
    auraColor: '#a78bfa',
    auraIntensity: 'vivid', // 'subtle' | 'normal' | 'vivid' | number (0–1.5)
    auraSpeed: 1.5,         // motion multiplier
    aura: true,             // false to freeze to a static fan
    agentColor: '#ffffff',  // agent-name text color (default)
    btnColor: '#ffffff',    // button label text color
    btnBg: '#1d5ba5',       // pill button fill color
    shadow: '0 20px 60px 0 rgba(109,40,217,0.5)', // raw CSS box-shadow (outer)
    pillBg: '#ffffff',      // pill-variant background (any CSS background)
  }}
/>

Hooks

| Hook | Returns | |----------------------|--------------------------------------------------------------------| | useVLoop() | The discriminated state plus start / hangUp / retry / reset. | | useCallStatus() | The current status literal. | | useBotSpeaking() | true while the bot is producing audio. | | useCallElapsed() | Elapsed seconds while in-call, 0 otherwise. |

All hooks throw if called outside <VLoop.Root> (or <VLoop.AI>, which renders a Root internally).

State shape

type VLoopState =
  | { status: 'idle' }
  | { status: 'connecting'; since: number }
  | { status: 'in-call'; botSpeaking: boolean; elapsedSeconds: number; startedAt: number }
  | { status: 'ended'; reason: 'user' | 'remote' | 'timeout'; durationSeconds: number }
  | { status: 'error'; error: Error; retryable: boolean };

Custom transport

import { VLoop, type VoiceTransport, type TransportEvent } from '@italq/vloop';

class MockTransport implements VoiceTransport {
  private listeners = new Set<(e: TransportEvent) => void>();
  subscribe(fn: (e: TransportEvent) => void) {
    this.listeners.add(fn);
    return () => this.listeners.delete(fn);
  }
  async start() { this.emit({ type: 'connecting' }); this.emit({ type: 'ready' }); }
  async stop()  { this.emit({ type: 'disconnected', reason: 'user' }); }
  private emit(e: TransportEvent) { this.listeners.forEach(fn => fn(e)); }
}

<VLoop.Root agent={{ id: 'agt_42' }} transport={new MockTransport()}>…</VLoop.Root>

Migration from v0.x

| v0 (deprecated) | v1 | |-----------------------------|-------------------------------------| | agentId="..." | agent-id="..." | | agentName="..." | agent-name="..." | | btnText="Call" | btn-text="Call" | | cliId="..." | cli-id="..." | | appId="..." | app-id="..." | | callId="..." | call-id="..." | | callerNum="..." | caller-num="..." | | calleeNum="..." | callee-num="..." | | styles='{"bottom":"40px"}'| style="--vloop-accent: ..." or theme-* attributes | | import VLoopWidget from '@italq/vloop' (default export) | import { VLoopAI } from '@italq/vloop' (named) |

The legacy camelCase attributes continue to work in v1 and emit a one-time console.warn with the canonical name. They will be removed in v2.


Development

pnpm install
pnpm dev          # vite dev server with hot reload
pnpm build        # tsc -b && vite build -> dist/bundle.min.js
pnpm lint         # eslint src

Manual smoke test of the bundled output:

pnpm build
# then open examples/agent.html and uncomment the local <script src="../dist/bundle.min.js">

Publishing

pnpm build
npm publish --access public