chat-ui
89e9b33f - Artifacts: side panel with live preview, targeted edits, and version history (#2303)

Commit
137 days ago
Artifacts: side panel with live preview, targeted edits, and version history (#2303) * Add Artifacts: side panel with live preview, targeted edits, and version history Claude-style artifacts for chat-ui. The model emits <artifact> tags (taught via an injected system prompt, ENABLE_ARTIFACTS=false to opt out); the client renders them as cards in chat and a dedicated side panel next to the conversation. - Streaming-safe parser (src/lib/utils/artifacts.ts): partially received tags stay hidden, open artifacts grow token by token, and every operation folds into a versioned registry derived from the visible message path. - Targeted edits: type="update" blocks carry old_str/new_str find/replace pairs applied to the latest version, so small changes don't re-emit the whole artifact; bigger changes rewrite under the same identifier. Each create/update/rewrite becomes a navigable version (v1, v2, ...). - Panel UI: Preview/Code tabs, copy/download/fullscreen actions, version stepper, drag-to-resize on desktop, fullscreen overlay on mobile, auto-open while streaming (code view pinned to bottom, preview disabled until complete), runtime-error capture with one-click "ask to fix". - Live preview types: html and svg in a sandboxed iframe (reusing the hardened srcdoc builder extracted from HtmlPreviewModal), react via Babel standalone + React 18 + Tailwind Play CDN, mermaid via CDN ESM, markdown rendered natively; code gets syntax highlighting only. - ChatMessage strips artifact blocks from prose (and clipboard copies) and renders cards inline; ChatWindow hosts the panel beside the chat column with pointer-events handling that keeps the z-[-1] chat area clickable. Verified end-to-end against a mock OpenAI endpoint with Playwright: create + streaming auto-open, live preview interactivity, old_str/new_str update to v2, version navigation, Esc/close/reopen, download filename, and the mobile overlay. https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Harden artifacts against real-model output: doubled brackets, fuzzy edits, think rehearsals Live testing on HuggingChat (Kimi-K2.6 via the agentic route) surfaced four failure modes the mock-based tests missed: - Kimi consistently emits doubled opening brackets (<<artifact, <<old_str). The old_str/new_str pair regex stopped matching, so "make it green" became three silent no-op versions labeled "Edited" while the button stayed blue, with stray < characters leaking into the prose. All tag matchers now consume a run of brackets, and a closed update that parses zero pairs is surfaced as a failed edit instead of silently doing nothing. - Models mis-copy indentation in old_str (observed live: the model burned a whole thinking block counting spaces, failed anyway, then retried). applyArtifactUpdate now falls back to whitespace-tolerant matching, exact match first. - Models rehearse artifact tags inside <think> blocks; the registry parsed them as real operations, creating phantom versions. collectArtifacts now strips reasoning before parsing, mirroring ChatMessage. - One update block per find/replace pair cluttered the chat with a card per pair. Consecutive ops on the same artifact now collapse into the last card, and the system prompt asks for a single update block per reply. Also: only the card whose version the panel displays gets the active ring, and the version stepper label spacing is fixed. Verified end to end in Chrome against the live dev server: the previously broken conversation now renders one clean card and applies the green edit; create/rewrite/update cycles, mermaid and react artifacts, streaming auto-open, version navigation and failed-edit warnings all checked against real Kimi-K2.6 generations. https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Expose artifacts as a global user setting, enabled by default Adds an Artifacts toggle to Application Settings (global, not per-model). The setting gates the system prompt injection server-side, combined with the instance-wide ENABLE_ARTIFACTS env flag; existing artifacts in old conversations keep rendering even when the toggle is off. - Settings type + DEFAULT_SETTINGS: artifactsEnabled (default true) - Both settings endpoints (zod schemas) and the GET response expose it - TextGenerationContext carries the user value from the conversation endpoint into textGeneration, where it gates injectArtifactsPrompt - Application Settings page gets the Switch, styled like its neighbors Verified live in Chrome against real models: toggle off, "draw a blue button in html" returns a plain markdown code block (0 artifact cards, no panel) across two separate conversations; toggle back on, the same prompt produces an artifact card with the side panel streaming, and the model's reasoning explicitly references the artifact instructions again. Setting persistence confirmed through the API between page loads. https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Polish artifact panel and cards from live design feedback - Panel width: cap at 896px (was 1024) and never wider than calc(100dvw - 700px), so the chat column stays readable on large screens; double-clicking the resize handle resets to the default width. - Code tab now inherits the exact .prose pre styling chat code blocks use (white + Atom One Light in light mode, gray-900 + Atom One Dark in dark mode) instead of forcing a dark background with light-theme tokens. The pre's own 0.5px border is dropped (!border-0) since the panel provides its frame, fixing the doubled borders; the streaming fade gradient follows the light/dark background. - Artifact cards: drop the chevron icon, dim the dark-mode active ring to 30% opacity, and remove transition-colors so theme switches don't animate the card. - Panel header gets z-10 so the copy button's "Copied" tooltip isn't painted over by the body. https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Make artifact panel resize proportional: each pane keeps 20%, drag up to 80% Replace the fixed pixel caps (896px max, viewport-minus-700 guard) with proportional bounds on the chat/panel split: the panel can be dragged anywhere between 20% and 80% of the shared area, so the same floor protects both panes symmetrically. The drag clamp uses the live container width, so the handle tracks the pointer 1:1 with no dead zone at the bounds. Stored width keeps loose absolute sanity bounds only. https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Give artifact cards more vertical room when surrounded by text Cards keep the tight my-2 at the start/end of a message, but get 1rem of separation on the side that touches prose ([.prose+&] above, has-[+.prose] below), matching the spacing pattern used for tool blocks. https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Add word-wrap toggle to the code view; drop "Click to open" from cards The wrap button sits with the other header actions and only shows on the Code tab; the preference persists in localStorage. Card subtitles now read just "Web app ยท v2" since the whole card is clickable anyway. https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Default artifact panel to 65% of the split When the user hasn't resized, the panel takes 65% of the chat/panel area (stored width remains null until a drag persists one). Double-clicking the resize handle now returns to this proportional default instead of a fixed pixel width. https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Default artifact panel split to 70/30 https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Stop the white flash when opening artifact previews in dark mode The preview iframe was hard-coded bg-white, which flashed before the embedded document painted its own (often dark) background. The iframe backing now follows the panel theme. SVG previews get an explicit white document canvas since they're usually drawn for light backgrounds. https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Move the share button into the chat column so the artifact panel doesn't cover it It was absolutely positioned at the layout's top right, which the panel overlaps when open. It now anchors to the top right of the chat column, staying visible (and correctly placed) whether or not artifacts are open. https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Give artifact cards more right padding than left https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Match artifact panel borders to the nav sidebar color (gray-100) https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Make artifacts opt-in per model via supportsArtifacts in MODELS overrides Replaces the global artifactsEnabled user setting and the ENABLE_ARTIFACTS env kill switch: the artifacts system prompt is now injected only when the conversation's model carries supportsArtifacts: true, configured per model through the MODELS JSON5 overrides (same mechanism as supportsTools / supportsReasoning) and off by default. - modelConfig gains supportsArtifacts (default false); overridable per entry - textGeneration gates injectArtifactsPrompt on ctx.model.supportsArtifacts - Removed: the Application Settings toggle, artifactsEnabled across Settings types / both settings endpoints / store / layout types / generation ctx, and the ENABLE_ARTIFACTS config key (.env documents the new mechanism) - Artifact rendering (cards/panel) is untouched so old conversations keep displaying their artifacts; stale artifactsEnabled values in settings documents are simply ignored For Omni/router conversations the flag applies to the selected model entity (e.g. flag "omni" itself): the prompt is built before routing, so routed targets can't be gated individually. https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Expose artifacts on the model settings page with a per-model user override Adds a fourth capability row ("Artifacts") to /settings/<model> next to Tool calling / Multimodal / Reasoning effort, backed by a new artifactsOverrides record in user settings (same plumbing as toolsOverrides). The override wins over the model's supportsArtifacts config flag in both directions: users can enable artifacts on an unflagged model or disable them on a flagged one. Unlike the provider-determined capabilities, the switch stays editable on HuggingChat and the override is not stripped by the settings endpoints. supportsArtifacts is now serialized to the client (Model type, both models endpoints, GETModelsResponse) so the toggle reflects the config default. https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Add BETA badge to the Artifacts row on the model settings page https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Drop tracking-wide from the BETA badge https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Enable artifacts by default on selected models (omni + 7) in dev/prod configs Sets supportsArtifacts: true on omni, GLM-5.1, MiniMax-M2.7, Kimi-K2.6, DeepSeek-V4-Pro, Nemotron-3-Ultra, Qwen3.6-35B-A3B and Qwen3.6-27B in chart/env/{dev,prod}.yaml. The router alias (omni) is built after the MODELS overrides merge, so models.ts now applies a matching override entry to the alias too, letting it carry capability flags like any other model. https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Float the code wrap toggle over the code view so it doesn't shift the header tabs * Fix artifact edge cases found in live browser testing - Collapse doubled brackets on an artifact's leading content tag (Kimi-K2.6 emits "<<svg ..."), which rendered a stray "<" in the preview and polluted the code view / downloads. The fuzzy old_str matcher is bracket-run tolerant the same way, so updates quoting the raw "<<svg" still match the normalized content. - Clear the panel's highlighted code when the target version disappears, instead of holding the previous artifact's code while a new artifact's first tokens stream in. - Debounce the close-on-missing-artifact effect (300ms) so transient registry gaps during end-of-generation invalidation don't close the panel; real removals (branch switch, edits) still close it. - Give the React preview scaffold the same white canvas as SVG/Mermaid: unstyled components were invisible (default black text) on the dark panel background in dark mode. https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U * Simplify artifacts code: flatten fallbacks, drop redundant casts and IIFE Behavior-preserving cleanups across the artifacts feature: - ChatMessage: replace the nested-ternary each-key with the equivalent `${unit.kind}-${unitIndex}` template - ArtifactCard: flatten the title fallback chain (create ops always carry a title, so the kind branch was redundant) and rename the onclick helper to openInPanel so it no longer shadows the DOM attribute name - artifacts.ts: single-pass map in stripArtifacts (the filter only existed for type narrowing the map redid) and a named fileExtension helper instead of an IIFE in artifactFileName - models.ts: use the file's existing `void id` convention in the alias override merge instead of an eslint-disable comment - settings page: drop the double cast, BackendModel already types supportsArtifacts (matches the supportsReasoning getter style) - artifactsContext: explicit void return type on setArtifactsContext https://claude.ai/code/session_01DaEvzheeeFNXEd66t4135U --------- Co-authored-by: Claude <noreply@anthropic.com>
Author
Parents
Loading