From bc0ccc6768efc20b5cbe6d5f35136249af088504 Mon Sep 17 00:00:00 2001 From: wasimysaid Date: Mon, 25 May 2026 21:40:23 +0200 Subject: [PATCH] Studio: address chat artifact review follow-ups --- studio/backend/routes/inference.py | 1 - .../assistant-ui/tool-ui-render-html.tsx | 1 - .../src/features/chat/artifacts/artifact-card.tsx | 12 +++++++----- .../src/features/chat/artifacts/html-frame.tsx | 7 ++++--- .../frontend/src/features/chat/artifacts/store.ts | 14 ++++++++++++++ studio/frontend/src/features/chat/chat-page.tsx | 2 ++ .../src/features/chat/stores/chat-runtime-store.ts | 8 +++++--- 7 files changed, 32 insertions(+), 13 deletions(-) diff --git a/studio/backend/routes/inference.py b/studio/backend/routes/inference.py index e1d2c8008d..f1345aa8cd 100644 --- a/studio/backend/routes/inference.py +++ b/studio/backend/routes/inference.py @@ -268,7 +268,6 @@ _ARTIFACT_PREVIEW_FRAME_HTML = """ if (!data || data.type !== "unsloth:artifact-html" || typeof data.html !== "string") return; render(data.html); }); - parent.postMessage({ chatArtifactReady: true }, "*"); })(); diff --git a/studio/frontend/src/components/assistant-ui/tool-ui-render-html.tsx b/studio/frontend/src/components/assistant-ui/tool-ui-render-html.tsx index 79b914d6de..f8b9887b09 100644 --- a/studio/frontend/src/components/assistant-ui/tool-ui-render-html.tsx +++ b/studio/frontend/src/components/assistant-ui/tool-ui-render-html.tsx @@ -50,7 +50,6 @@ const RenderHtmlToolUIImpl: ToolCallMessagePartComponent = ({ title={title} source="tool" sourceToolCallId={toolCallId} - preview={false} autoOpen={true} isStreaming={isRunning || codeIsStreaming} /> diff --git a/studio/frontend/src/features/chat/artifacts/artifact-card.tsx b/studio/frontend/src/features/chat/artifacts/artifact-card.tsx index cece2a43ed..080fd8f050 100644 --- a/studio/frontend/src/features/chat/artifacts/artifact-card.tsx +++ b/studio/frontend/src/features/chat/artifacts/artifact-card.tsx @@ -9,7 +9,11 @@ import { useAuiState } from "@assistant-ui/react"; import { CheckIcon, CopyIcon, DownloadIcon } from "lucide-react"; import { useEffect, useMemo, useRef, useState } from "react"; import { useChatRuntimeStore } from "../stores/chat-runtime-store"; -import { useChatArtifactsStore } from "./store"; +import { + hasAutoOpenedArtifact, + rememberAutoOpenedArtifact, + useChatArtifactsStore, +} from "./store"; import { type ChatArtifact, type ChatArtifactSource, @@ -18,7 +22,6 @@ import { } from "./types"; const COPY_RESET_MS = 2000; -const autoOpenedArtifactIds = new Set(); function downloadTextFile(filename: string, text: string): void { const blob = new Blob([text], { type: "text/html;charset=utf-8" }); @@ -74,7 +77,6 @@ export function ArtifactCard({ sourceToolCallId?: string | null; sourceMessageId?: string | null; className?: string; - preview?: boolean; autoOpen?: boolean; isStreaming?: boolean; }) { @@ -119,8 +121,8 @@ export function ArtifactCard({ useEffect(() => { if (!autoOpen) return; - if (!autoOpenedArtifactIds.has(artifact.id)) { - autoOpenedArtifactIds.add(artifact.id); + if (!hasAutoOpenedArtifact(artifact.id)) { + rememberAutoOpenedArtifact(artifact.id); openArtifact(artifact, { surface }); return; } diff --git a/studio/frontend/src/features/chat/artifacts/html-frame.tsx b/studio/frontend/src/features/chat/artifacts/html-frame.tsx index 1ac4dc3acd..f797062ad1 100644 --- a/studio/frontend/src/features/chat/artifacts/html-frame.tsx +++ b/studio/frontend/src/features/chat/artifacts/html-frame.tsx @@ -51,6 +51,9 @@ export function ArtifactHtmlFrame({ [code], ); const postArtifactHtml = useCallback(() => { + // The sandboxed frame intentionally has an opaque origin ("null"). + // A wildcard target is required here; + // the payload is sent only to this iframe's contentWindow. iframeRef.current?.contentWindow?.postMessage( { type: "unsloth:artifact-html", html: artifactHtml }, "*", @@ -60,9 +63,7 @@ export function ArtifactHtmlFrame({ useEffect(() => { const handler = (event: MessageEvent) => { if (event.source !== iframeRef.current?.contentWindow) return; - if (event.data?.chatArtifactReady === true) { - postArtifactHtml(); - } + if (event.origin !== "null") return; if (typeof event.data?.chatArtifactHeight !== "number") return; setHeight( Math.min( diff --git a/studio/frontend/src/features/chat/artifacts/store.ts b/studio/frontend/src/features/chat/artifacts/store.ts index e466126631..fe729fe484 100644 --- a/studio/frontend/src/features/chat/artifacts/store.ts +++ b/studio/frontend/src/features/chat/artifacts/store.ts @@ -4,6 +4,20 @@ import { create } from "zustand"; import type { ChatArtifact, ChatArtifactSurface } from "./types"; +const autoOpenedArtifactIds = new Set(); + +export function hasAutoOpenedArtifact(artifactId: string): boolean { + return autoOpenedArtifactIds.has(artifactId); +} + +export function rememberAutoOpenedArtifact(artifactId: string): void { + autoOpenedArtifactIds.add(artifactId); +} + +export function clearAutoOpenedArtifacts(): void { + autoOpenedArtifactIds.clear(); +} + type ChatArtifactsState = { artifactsById: Record; selectedArtifactId: string | null; diff --git a/studio/frontend/src/features/chat/chat-page.tsx b/studio/frontend/src/features/chat/chat-page.tsx index 6423d873d7..415aac450c 100644 --- a/studio/frontend/src/features/chat/chat-page.tsx +++ b/studio/frontend/src/features/chat/chat-page.tsx @@ -85,6 +85,7 @@ import { useExternalProvidersStore } from "./stores/external-providers-store"; import { buildChatTourSteps } from "./tour"; import { ArtifactSurface } from "./artifacts/artifact-surface"; import { + clearAutoOpenedArtifacts, useChatArtifactsStore, useSelectedChatArtifact, } from "./artifacts/store"; @@ -962,6 +963,7 @@ export function ChatPage(): ReactElement { : `compare:${view.pairId}`; useEffect(() => { + clearAutoOpenedArtifacts(); closeArtifactSurface(); }, [artifactViewKey, closeArtifactSurface]); diff --git a/studio/frontend/src/features/chat/stores/chat-runtime-store.ts b/studio/frontend/src/features/chat/stores/chat-runtime-store.ts index a1e34005a4..a5e322888a 100644 --- a/studio/frontend/src/features/chat/stores/chat-runtime-store.ts +++ b/studio/frontend/src/features/chat/stores/chat-runtime-store.ts @@ -326,7 +326,7 @@ type ChatRuntimeStore = { setToolsEnabled: (enabled: boolean, options?: { persist?: boolean }) => void; setCodeToolsEnabled: (enabled: boolean) => void; setImageToolsEnabled: (enabled: boolean) => void; - setArtifactsEnabled: (enabled: boolean) => void; + setArtifactsEnabled: (enabled: boolean, options?: { persist?: boolean }) => void; setToolStatus: (status: string | null) => void; setGeneratingStatus: (status: string | null) => void; setAutoHealToolCalls: (enabled: boolean) => void; @@ -811,9 +811,11 @@ export const useChatRuntimeStore = create((set, get) => ({ saveBool(CHAT_IMAGE_TOOLS_ENABLED_KEY, imageToolsEnabled); return { imageToolsEnabled }; }), - setArtifactsEnabled: (artifactsEnabled) => + setArtifactsEnabled: (artifactsEnabled, options) => set(() => { - saveBool(CHAT_ARTIFACTS_ENABLED_KEY, artifactsEnabled); + if (options?.persist !== false) { + saveBool(CHAT_ARTIFACTS_ENABLED_KEY, artifactsEnabled); + } return { artifactsEnabled }; }), setToolStatus: (toolStatus) => set({ toolStatus }),