Skip to content
oldmangan edited this page Oct 6, 2026 · 1 revision

A streaming AI chat surface: multiple conversations with local persistence, a model picker, an artifact preview panel, and push-to-talk or hands-free voice capture. wAwesomeChat is built on it.

from wapyt import ChatConfig, ChatMessageConfig

chat = self.add_chat("main", ChatConfig(storage_key="myapp", default_model="gpt-4o"))

def on_send(payload):
    prompt = payload["text"]           # also: payload["id"], payload["message"]
    reply_id = chat.start_stream(ChatMessageConfig(role="assistant", content=""))
    spawn(stream_reply(reply_id, prompt), "chat")

async def stream_reply(reply_id, prompt):
    stream = ...   # an (async) iterator of provider chunks, e.g. from a @bff_stream export
    chat.consume_stream(reply_id, stream)

chat.on_send(on_send)

Sending and streaming

  • on_send receives {id, text, message}. The prompt is text; message is the user message the widget appended (with auto_append_user_messages).
  • start_stream(message) → id; then append_stream(id, chunk) and finish_stream(id).
  • consume_stream(id, stream) drives a sync or async iterator of provider chunks for you. Streaming backends report failures in-band as {"error": {...}} chunks; consume_stream raises those as ChatStreamError instead of rendering an empty message. Any other consumer needs the same check (Chat.extract_stream_error).
  • build_history(system_prompt=..., exclude_ids=...) returns [{"role", "content"}] for the active chat.

Persistence and models

  • Conversations persist to localStorage under ChatConfig(storage_key=...). Without a key nothing survives a reload.
  • The selected model resolves as: the active chat's model, then the last model the user picked, then default_model, then the first available.
  • Load the catalogue from a BFF after construction with set_extra({"providerConfig": {"providers": {...}}}) or set_models([...]).

Voice

  • voice_input=True: a hold-to-talk mic. on_voice → {audio (base64), mimeType, bytes, durationMs}; transcribe server-side and hand the text back with set_composer_text(text, submit=...) or apply_transcript(...).
  • voice_continuous=True: a latching mic that segments speech on silence (vad_threshold, vad_silence_ms) and auto-submits.
  • voice_max_seconds (default 120) caps a take.
  • Needs a secure context (http://127.0.0.1 counts, a LAN IP does not). pyTincture sends Permissions-Policy: microphone=() by default; set PYTINCTURE_ALLOW_MICROPHONE=1 (pyTincture ≥ 1.0.0rc8) to allow the mic. on_voice_error reports refusals.

Other events and methods

Events: on_artifact_save, on_artifact_load, on_copy, on_voice_error. Methods: set_messages, add_message, update_message, remove_message, clear_messages, set_agent, set_theme, focus_composer, set_chat_badge, get_chats, get_messages, destroy.

Safety

Messages render through an escaping markdown renderer (marked is used only when DOMPurify is present too). Artifact previews run in <iframe sandbox="allow-scripts"> without allow-same-origin. See Security and Escaping.

Clone this wiki locally