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

@protoyard/ai-ui

v1.4.0

Published

Svelte 5 assistant interface components for simulated AI prototypes.

Readme

@protoyard/ai-ui

Svelte 5 components for prototypes that simulate AI assistants. The package renders conversations and interaction controls; it does not call a model provider, create database rows, or implement sharing itself. Mutations are callback props so the prototype can route them through its own services. Model labels and UI copy keep the simulated boundary visible.

Platform guide: docs/AI_KITS.md (workspace, virtual threads, widget launcher→sidebar→fullscreen, voice panel). Measured bundles and long threads: docs/PERFORMANCE.md → AI kits.

Install in a workspace prototype

Add "@protoyard/ai-ui": "workspace:*" to the prototype dependencies. Import the styles after Tailwind and the theme/UI styles:

@import 'tailwindcss';
@import '@protoyard/themes/base.css';
@import '@protoyard/ui/styles.css';
@import '@protoyard/ai-ui/styles.css';

Assistant workspace

AssistantWorkspace accepts conversation summaries and the active branch's messages. Send, stop, rename, pin, folder, feedback, branch, regenerate, edit and share operations are callbacks. The component does not persist changes. onShare must return a URL created by the consuming prototype; the UI then offers a copy action.

The workspace renders its conversation through MessageList, which is @texuli/chat's MessageList: it virtualizes long threads, keeps the visible message anchored as streamed content changes and offers "Jump to latest". ai-ui restores the first message in view and its offset from session storage when the same conversation is reopened in the tab (and reopens at the latest message when the reader left it at the bottom). A reopened position is held until it settles, even when late content (a chart answer, an image) resizes the thread. Editing a message calls onEditMessage only when its text changed: "Save and resend" with the same text closes the editor (@texuli/chat's inline edit), and Regenerate is the way to ask again for the last reply. The saved position is presentation state; conversation messages remain owned by the prototype.

<script lang="ts">
  import { AssistantWorkspace, type AssistantConversation, type AssistantMessage, type AssistantSendInput } from '@protoyard/ai-ui';

  let conversations: AssistantConversation[] = $state([]);
  let messages: AssistantMessage[] = $state([]);
  let streaming = $state(false);

  async function send(conversationId: string, input: AssistantSendInput) {
    // A real prototype calls its own service and persists its own attachment metadata.
    await assistantService.send(conversationId, input);
  }
</script>

<AssistantWorkspace
  {conversations}
  {messages}
  {streaming}
  onSend={send}
  onStop={() => assistantService.stop()}
  onRenameConversation={(id, title) => assistantService.rename(id, title)}
  onTogglePin={(id) => assistantService.togglePin(id)}
/>

The composer supports Enter to send, Shift+Enter for a newline, Escape to stop a stream, model selection, file selection, slash-command suggestions, and @mention suggestions. The parent owns streaming state and should update messages as deterministic chunks arrive. RichAnswer renders the plain-data AST from parseMarkdown using text nodes and Svelte elements; it never uses {@html}. CanvasPanel displays code or editable document text in a responsive side panel.

Scrollable code regions. Overflowing <pre> blocks in RichAnswer (fenced code), ChartAnswer (invalid chart data), and CanvasPanel (code kind) use tabindex="0" and an aria-label, matching the ui-kit CodeBlock pattern, so keyboard users can reach them (axe scrollable-region-focusable / WCAG 2.1.1). (Its unit test was removed in PLAN_009.)

Chart answers. RichAnswer parses with { charts: true }.

  • A ```chart fence (see @protoyard/ai/chart) shows "Preparing chart…" while it streams.
  • When the fence closes, ChartAnswer loads on demand. It validates the spec, shows a table of the values at once, then the matching @protoyard/charts-ui chart (bar, line, area, donut, pie or funnel), which brings keyboard navigation, tooltips and its own table toggle.
  • An invalid spec shows a note with the reason and the raw data as text.
  • The charts kit and the zod schema are lazy chunks; only the small parser and renderer branch ship with RichAnswer. In nova-assistant-demo initial JS went from 169.40 KB to 169.93 KB gzip.
  • styles.css registers the charts-ui class sources, so prototypes need no extra import.

Conversation folder controls require folders, onCreateFolder, and onMoveToFolder callbacks. Features without a callback are disabled. The caller supplies onRegenerate, onEditMessage, onBranchChange, onFeedback, and onShare only when those operations are implemented.

All components use Svelte 5 runes and theme token utility classes. Import styles.css to register this package's class sources with Tailwind.

Built on Texuli

The list is @texuli/chat's MessageList (per-message actions snippet, inline edit, stream follow) and the composer is its Composer (stop and Escape while streaming, keydown hook, attachments and actions snippets), and every control is a texuli component: Button/IconButton, CopyButton, Textarea/Input (the attachment picker is a file Input), Select, Checkbox/RadioGroup, Field, Card, Badge/Tag, Table, Code, Kbd, DropdownMenu and EmptyState. Lucide glyphs render through LucideIcon from texuli/icons/lucide (or Icon for Texuli's built-in names). ai-ui keeps what is assistant-specific: slash and mention suggestions, the model choice, feedback, branches and regenerate, and the reading position per conversation; the public props are unchanged. Rich answers are still parsed by @protoyard/ai (parseMarkdown) and render as text, never HTML. Components accept data-only messages from @protoyard/ai; no credentials, provider SDKs, generated models or external network calls are included.

Simulated voice mode

Load the voice panel from its lazy subpath when the user opens voice mode:

const { VoiceMode } = await import('@protoyard/ai-ui/voice');

Pass the injected AiClock, scripted transcript options, the latest simulated assistant reply and an onTranscript handler. Captions are always visible. Browser speechSynthesis speaks replies locally when available; otherwise output stays muted and the reply caption remains visible. The panel does not request a microphone by default. A consumer may opt into showing the local mic level meter; enabling it asks for microphone permission and reads amplitude only with Web Audio, without recording, storing or uploading audio. Stop the meter to release its stream; component teardown also stops it.

Widget modes

const { AssistantWidget } = await import('@protoyard/ai-ui/widget');

AssistantWidget hosts the same workspace callbacks inside launcher → sidebar → fullscreen chrome. The panel module loads on first open. Mode is stored per prototypeId in localStorage when the browser allows it. Escape collapses one level. No model or network call is introduced by the widget shell.

Package output

The repository keeps source exports for workspace development. pnpm build:packages --filter svelte creates the compiled ESM, declarations and Svelte components under dist/ that the packed manifest exposes.

Suggestion approvals (lazy)

Import from @protoyard/ai-ui/approvals so the approval surface stays out of the assistant boot chunk. Pair with @protoyard/ai/approvals pure helpers.

| Export | Purpose | | --- | --- | | SuggestionCard | Waiting or decided suggestion with evidence and decision buttons | | ChatApprovalCard | Compact chat-row approval | | AutonomyLevelPicker | Per-area autonomy radios (setLevel via onChange) | | AuditTrail | Newest-first audit table |

Callbacks are required for every decision. Show pending state is built in. Label the UI as simulated — these controls are prototype UX, not security.

import {
  AuditTrail,
  AutonomyLevelPicker,
  ChatApprovalCard,
  SuggestionCard,
} from '@protoyard/ai-ui/approvals';

Bundle note: the approvals subpath is optional; assistant workspace entry graphs do not import it until a screen does.

Chat message blocks (lazy)

Import ChatMessageBlocks from @protoyard/ai-ui/messages to render protoyard.message@1 messages from @protoyard/ai/messages. It renders text (plain or safe markdown), buttons with inline confirmations, selects, forms, tables, link previews, attachments and the fallback for unknown blocks.

<script lang="ts">
import { ChatMessageBlocks, type MessageActionEvent } from '@protoyard/ai-ui/messages';

let { message, services } = $props();
const onAction = (event: MessageActionEvent) => services.chat.respond(event);
</script>

<ChatMessageBlocks {message} {onAction} onOpenAttachment={(file) => services.files.open(file.id)} />
  • Validation: input is always validated with parseChatMessage. An invalid message shows a short note and none of its content.
  • Actions: buttons, selects and forms report one typed MessageActionEvent (kind: "button" | "select" | "form") through onAction. The returned promise drives pending, error and done feedback. Without onAction the controls are disabled with a note.
  • No data writes: the component never writes data. Route every action through a service (session.assert → zod → environment.run).
  • Size: about 13.6 KB gzip over RichAnswer (zod external), and only for screens that import the subpath.