AI Chat
A full-featured AI chatbot ported feature-by-feature from the official Nuxt AI Chatbot template (Nuxt UI + Vercel AI SDK) — probably the most demanding "real-world" validation of Vue Lynx so far: streaming responses with thinking/reasoning, markdown with highlighted code blocks and tables, tool-call cards (weather, charts, web-search sources), chat history with date grouping, votes, message editing, share/rename/delete flows, a model picker, persisted runtime theming with light/dark mode, attachments, fuzzy command-palette search, and a responsive mobile layout with a slide-over sidebar.
Try the quick prompts below — "What is the weather in Bordeaux?" streams a reasoning section and a weather card; "Show me a chart of sales data" renders an SVG line chart; "Help me create a Vue composable" shows markdown with highlighted code. Scan the QR code in the native tab to run the same bundle in LynxExplorer.
The playground above runs fully self-contained: the app probes for its API server and, when unreachable, falls back to an in-app demo backend with the same seeded history and deterministic mock AI streams. When you run the example locally with pnpm dev:server, the same UI talks to a standalone Node server that reimplements the Nuxt template's API routes — and streams real models through the Vercel AI Gateway if you set AI_GATEWAY_API_KEY.
Native chat techniques
A polished chat send is not one animation. It is a handoff between the composer, message list, keyboard, and streaming response. Vercel’s account of building the v0 iOS app inspired us to describe the work through those visible moments, then trace each moment back to the system that makes it possible.
The first send establishes the stage
Before an assistant message exists, the app captures the composer and viewport geometry. The outgoing text stays in a temporary animation layer while the app commits the real message, scroll position, and response space. The assistant reveal begins only after that update, so there is no target-position flash.
The launch distance depends on the bubble height, viewport, composer, and keyboard. We stage the bubble at the captured composer position, commit layout, then start its transform on a later frame. Layout and animation never compete for the same first frame.
The second send is the real test
Repeat sends expose mistakes that the empty state hides. Native aligns the new user turn at the top, but keeps the previous turn stable until the moving bubble reaches its target. Vue Lynx nextTick() waits for pending operations to reach the main thread before scrollIntoView performs the final alignment. Only then does the assistant stream appear.
Web uses a different policy because it does not have the same native positioning guarantees: observed scroll-top updates keep the message list pinned to the bottom during sends and streaming. The interaction stays familiar even when the mechanism changes.
The keyboard participates in layout
Lynx <input> elements do not avoid the keyboard automatically. The composer remains absolutely positioned at the bottom and listens for the keyboardstatuschanged global event. It applies the reported keyboard height with setNativeProps; the message list follows only if it was already following new output.
Gesture feedback stays on the main thread
Drawer gestures and press feedback use Vue Lynx Main Thread Script. Functions marked with 'main thread' update element styles from main-thread-bindtouch* events without a background-thread round trip. The regular tap handler still performs the application action on Vue’s background thread.
Porting notes
The port keeps the original's data model (AI SDK v5 UIMessage parts over the UI-message-stream protocol) and nearly all of its composable/page logic, while re-implementing the DOM-coupled layers with Lynx elements:
The example directory contains a complete PRD with per-feature parity status (65 features ported/adapted, 10 skipped with reasons), a PORTING.md documenting what was reused vs rewritten plus the platform quirks discovered along the way, and side-by-side screenshot comparisons against the live original captured on Lynx for Web.