Studio: address chat artifact review follow-ups

This commit is contained in:
wasimysaid 2026-05-25 21:40:23 +02:00
commit bc0ccc6768
7 changed files with 32 additions and 13 deletions

View file

@ -268,7 +268,6 @@ _ARTIFACT_PREVIEW_FRAME_HTML = """<!doctype html>
if (!data || data.type !== "unsloth:artifact-html" || typeof data.html !== "string") return;
render(data.html);
});
parent.postMessage({ chatArtifactReady: true }, "*");
})();
</script>
</body>

View file

@ -50,7 +50,6 @@ const RenderHtmlToolUIImpl: ToolCallMessagePartComponent = ({
title={title}
source="tool"
sourceToolCallId={toolCallId}
preview={false}
autoOpen={true}
isStreaming={isRunning || codeIsStreaming}
/>

View file

@ -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<string>();
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;
}

View file

@ -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(

View file

@ -4,6 +4,20 @@
import { create } from "zustand";
import type { ChatArtifact, ChatArtifactSurface } from "./types";
const autoOpenedArtifactIds = new Set<string>();
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<string, ChatArtifact>;
selectedArtifactId: string | null;

View file

@ -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]);

View file

@ -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<ChatRuntimeStore>((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 }),