@brian/claude-chat-ui (0.1.1)

Published 2026-07-20 20:34:53 +00:00 by brian in brian/claudechatwindow

Installation

@brian:registry=
npm install @brian/claude-chat-ui@0.1.1
"@brian/claude-chat-ui": "0.1.1"

About this package

@brian/claude-chat-ui

Shared React pieces for a Claude-powered chat window: a useSpeechToText hook (with the Android duplication fix applied), a ChatInput (textarea + send + mic button), and a bare-bones MessageList.

Deliberately does not ship a full chat drawer/panel component — every app's chat window has different chrome (bottom sheet vs. docked panel vs. floating launcher vs. sidebar-of-past-chats) and a different streaming wire format (NDJSON vs. SSE-like). Compose these pieces into your own layout.

Usage

import { useState } from "react";
import { ChatInput, MessageList, type ChatMessage } from "@brian/claude-chat-ui";

function ChatPanel() {
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [draft, setDraft] = useState("");

  return (
    <div className="chat-panel">
      <MessageList messages={messages} className="chat-panel__messages" />
      <ChatInput
        value={draft}
        onChange={setDraft}
        onSend={(text) => {
          setMessages((m) => [...m, { id: crypto.randomUUID(), role: "user", content: text }]);
          setDraft("");
          // POST to your backend, which uses @brian/claude-chat-client / claude-chat-client-py
        }}
      />
    </div>
  );
}

useSpeechToText can also be used standalone if you want your own mic button UI instead of ChatInput's:

const speech = useSpeechToText({ lang: "en-US" });
// speech.isSupported, speech.isListening, speech.transcript, speech.interimTranscript
// speech.error -> raw SpeechRecognition error code ("not-allowed", "no-speech", ...) or null;
//   map it to your own copy, e.g. "not-allowed"/"service-not-allowed" -> "Microphone access denied."
// speech.start() / speech.stop() / speech.reset()

Why this fixes the Android duplication bug

See the doc comment on useSpeechToText in src/useSpeechToText.ts. Short version: Android Chrome can put multiple entries in one event's results array where each later entry is a fuller cumulative re-transcription of everything said so far, not just the newly-finalized words — so summing entries (even only final ones, even rebuilding from index 0 every event) still double- and triple-counts words. Confirmed on a real device: v0.1.0 of this hook summed entries and reproduced exactly that failure. v0.1.1 fixes it by never summing at all — only the last entry is trusted, since it's always the most complete version available.

Separately, a fresh SpeechRecognition instance is started on every automatic restart (continuous mode still ends on its own periodically even mid-utterance) — except on a terminal error (not-allowed, service-not-allowed, audio-capture), which stops the session instead of retrying against a mic that won't work this session.

Dependencies

Development dependencies

ID Version
@types/react ^18.3.0
@types/react-dom ^18.3.0
typescript ^5.5.0

Peer dependencies

ID Version
react >=18
react-dom >=18
Details
npm
2026-07-20 20:34:53 +00:00
2
5.9 KiB
Assets (1)
Versions (4) View all
0.1.3 2026-07-20
0.1.2 2026-07-20
0.1.1 2026-07-20
0.1.0 2026-07-20