Rename chat artifacts copy to canvas (#6298)
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
This commit is contained in:
parent
08c3878919
commit
9d7740a82f
16 changed files with 66 additions and 66 deletions
|
|
@ -773,10 +773,10 @@ RENDER_HTML_TOOL = {
|
|||
"function": {
|
||||
"name": "render_html",
|
||||
"description": (
|
||||
"Render a self-contained HTML/CSS/JavaScript artifact for the user. "
|
||||
"Render a self-contained HTML/CSS/JavaScript canvas for the user. "
|
||||
"Call this at most once per assistant response unless the user "
|
||||
"explicitly asks for changes in that response. Future user requests "
|
||||
"for new artifacts may call render_html once. Put the entire document "
|
||||
"for new canvases may call render_html once. Put the entire document "
|
||||
"in code, including any CSS in <style> tags and JavaScript in <script> tags."
|
||||
),
|
||||
"parameters": {
|
||||
|
|
@ -788,7 +788,7 @@ RENDER_HTML_TOOL = {
|
|||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "Short display title for the artifact.",
|
||||
"description": "Short display title for the canvas.",
|
||||
},
|
||||
},
|
||||
"required": ["code"],
|
||||
|
|
@ -954,14 +954,14 @@ def _render_html_result(arguments: dict) -> str:
|
|||
if isinstance(title, str) and title.strip():
|
||||
safe_title = title.strip()[:120]
|
||||
return (
|
||||
f"Rendered HTML artifact: {safe_title}. Do not call render_html "
|
||||
f"Rendered HTML canvas: {safe_title}. Do not call render_html "
|
||||
"again in this response unless the user asks for changes. For a later "
|
||||
"user request for a new artifact, call render_html once."
|
||||
"user request for a new canvas, call render_html once."
|
||||
)
|
||||
return (
|
||||
"Rendered HTML artifact. Do not call render_html again in this response "
|
||||
"Rendered HTML canvas. Do not call render_html again in this response "
|
||||
"unless the user asks for changes. For a later user request for a new "
|
||||
"artifact, call render_html once."
|
||||
"canvas, call render_html once."
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -954,7 +954,7 @@ _ARTIFACT_PREVIEW_FRAME_HTML = """<!doctype html>
|
|||
configurable: true,
|
||||
});
|
||||
} catch {
|
||||
// Leave the sandbox failure contained in the artifact if the
|
||||
// Leave the sandbox failure contained in the canvas if the
|
||||
// browser refuses to shadow the Web Storage accessor.
|
||||
}
|
||||
};
|
||||
|
|
@ -986,7 +986,7 @@ async def artifact_preview_frame(
|
|||
allow_network: bool = False,
|
||||
token: Optional[str] = None,
|
||||
):
|
||||
"""Serve the opaque sandbox shell used for client-side HTML artifacts."""
|
||||
"""Serve the opaque sandbox shell used for client-side HTML canvases."""
|
||||
|
||||
if allow_network:
|
||||
auth_header = request.headers.get("authorization")
|
||||
|
|
@ -1212,12 +1212,12 @@ async def _await_disconnect_then_close(request, resp, cancel_event) -> None:
|
|||
|
||||
|
||||
# Centralized local/server tool nudge. Keep render_html guidance gated to turns
|
||||
# where the artifact tool is actually present in the tool schema; otherwise
|
||||
# where the canvas tool is actually present in the tool schema; otherwise
|
||||
# small local models can hallucinate a missing tool call instead of following
|
||||
# the fenced-HTML fallback prompt.
|
||||
_TOOL_BASE_NUDGE = (
|
||||
"Tools are available when they materially improve the answer. Use an enabled "
|
||||
"tool for current facts, calculations, code execution, or artifacts when it "
|
||||
"tool for current facts, calculations, code execution, or canvases when it "
|
||||
"materially helps; otherwise answer normally and follow the user's requested "
|
||||
"format."
|
||||
)
|
||||
|
|
@ -1233,11 +1233,11 @@ _TOOL_CODE_TIP = (
|
|||
"and analyze information from tool results."
|
||||
)
|
||||
_TOOL_ARTIFACT_TIP = (
|
||||
"For HTML, CSS, or JavaScript artifact requests, call render_html once when "
|
||||
"For HTML, CSS, or JavaScript canvas requests, call render_html once when "
|
||||
"it is available with one complete self-contained HTML document in the code "
|
||||
"argument. After render_html succeeds, do not call it again in the same "
|
||||
"response unless the user asks for changes. Future user requests for new "
|
||||
"artifacts may call render_html once."
|
||||
"canvases may call render_html once."
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class TestToolActionNudge:
|
|||
assert "Use code execution for math" in nudge
|
||||
assert "render_html" not in nudge
|
||||
|
||||
def test_balanced_nudge_preserves_compact_web_tip_and_artifact_gate(self):
|
||||
def test_balanced_nudge_preserves_compact_web_tip_and_canvas_gate(self):
|
||||
nudge = _build_tool_action_nudge(
|
||||
tools = [
|
||||
{"type": "function", "function": {"name": "web_search"}},
|
||||
|
|
@ -840,7 +840,7 @@ class TestAnthropicToolNonStreaming:
|
|||
"type": "tool_end",
|
||||
"tool_name": "render_html",
|
||||
"tool_call_id": "call_0",
|
||||
"result": "Rendered HTML artifact.",
|
||||
"result": "Rendered HTML canvas.",
|
||||
}
|
||||
|
||||
response = asyncio.run(_anthropic_tool_non_streaming(_run_gen, "msg_1", "m"))
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ def test_structured_tool_call_after_visible_preface_is_executed(monkeypatch):
|
|||
|
||||
tool_call_id = "call_render_late"
|
||||
first_stream = [
|
||||
_sse({"content": "Here is the artifact.\n\n"}),
|
||||
_sse({"content": "Here is the canvas.\n\n"}),
|
||||
_sse(
|
||||
{
|
||||
"tool_calls": [
|
||||
|
|
@ -140,7 +140,7 @@ def test_structured_tool_call_after_visible_preface_is_executed(monkeypatch):
|
|||
|
||||
def fake_execute_tool(name, arguments, **_kwargs):
|
||||
calls.append((name, arguments))
|
||||
return "Rendered HTML artifact: Simple Red Square."
|
||||
return "Rendered HTML canvas: Simple Red Square."
|
||||
|
||||
monkeypatch.setattr("core.inference.tools.execute_tool", fake_execute_tool)
|
||||
|
||||
|
|
@ -168,7 +168,7 @@ def test_structured_tool_call_after_visible_preface_is_executed(monkeypatch):
|
|||
)
|
||||
|
||||
content_events = [e for e in events if e.get("type") == "content"]
|
||||
assert content_events[0]["text"] == "Here is the artifact.\n\n"
|
||||
assert content_events[0]["text"] == "Here is the canvas.\n\n"
|
||||
|
||||
first_content_index = next(
|
||||
i for i, event in enumerate(events) if event.get("type") == "content"
|
||||
|
|
@ -195,7 +195,7 @@ def test_structured_tool_call_after_visible_preface_is_executed(monkeypatch):
|
|||
# plus the structured tool call, preserving OpenAI-compatible ordering.
|
||||
assert len(payloads) == 2
|
||||
assistant_messages = [m for m in payloads[1]["messages"] if m.get("role") == "assistant"]
|
||||
assert assistant_messages[-1]["content"] == "Here is the artifact.\n\n"
|
||||
assert assistant_messages[-1]["content"] == "Here is the canvas.\n\n"
|
||||
assert assistant_messages[-1]["tool_calls"][0]["id"] == tool_call_id
|
||||
assert assistant_messages[-1]["tool_calls"][0]["function"]["name"] == "render_html"
|
||||
|
||||
|
|
@ -257,7 +257,7 @@ def test_repeat_render_html_nudge_is_not_user_visible_error(monkeypatch):
|
|||
|
||||
def fake_execute_tool(name, arguments, **_kwargs):
|
||||
calls.append((name, arguments))
|
||||
return "Rendered HTML artifact: First."
|
||||
return "Rendered HTML canvas: First."
|
||||
|
||||
monkeypatch.setattr("core.inference.tools.execute_tool", fake_execute_tool)
|
||||
|
||||
|
|
@ -346,7 +346,7 @@ def test_render_html_success_drops_tool_schema_before_final_pass(monkeypatch):
|
|||
backend = _make_backend(monkeypatch, [first_stream, final_stream], payloads)
|
||||
|
||||
def fake_execute_tool(name, arguments, **_kwargs):
|
||||
return "Rendered HTML artifact: Done."
|
||||
return "Rendered HTML canvas: Done."
|
||||
|
||||
monkeypatch.setattr("core.inference.tools.execute_tool", fake_execute_tool)
|
||||
|
||||
|
|
@ -783,7 +783,7 @@ def test_same_turn_repeated_render_html_does_not_emit_second_provisional_start(m
|
|||
|
||||
def fake_execute_tool(name, arguments, **_kwargs):
|
||||
calls.append((name, arguments))
|
||||
return "Rendered HTML artifact: One."
|
||||
return "Rendered HTML canvas: One."
|
||||
|
||||
monkeypatch.setattr("core.inference.tools.execute_tool", fake_execute_tool)
|
||||
|
||||
|
|
@ -869,7 +869,7 @@ def test_render_html_success_does_not_reprompt_render_html_intent(monkeypatch):
|
|||
|
||||
The post-tool model pass can say it will use render_html again without
|
||||
emitting a tool call. That should be accepted as a final model mistake,
|
||||
not turned into repeated internal re-prompts after the artifact already
|
||||
not turned into repeated internal re-prompts after the canvas already
|
||||
exists.
|
||||
"""
|
||||
|
||||
|
|
@ -907,7 +907,7 @@ def test_render_html_success_does_not_reprompt_render_html_intent(monkeypatch):
|
|||
|
||||
def fake_execute_tool(name, arguments, **_kwargs):
|
||||
calls.append((name, arguments))
|
||||
return "Rendered HTML artifact: First."
|
||||
return "Rendered HTML canvas: First."
|
||||
|
||||
monkeypatch.setattr("core.inference.tools.execute_tool", fake_execute_tool)
|
||||
|
||||
|
|
@ -1146,7 +1146,7 @@ def test_reprompted_tool_call_still_streams_final_answer(monkeypatch):
|
|||
|
||||
def fake_execute_tool(name, arguments, **_kwargs):
|
||||
calls.append((name, arguments))
|
||||
return "Rendered HTML artifact: Forced."
|
||||
return "Rendered HTML canvas: Forced."
|
||||
|
||||
monkeypatch.setattr("core.inference.tools.execute_tool", fake_execute_tool)
|
||||
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ def _make_loop(
|
|||
|
||||
def test_active_tools_are_passed_to_single_turn_after_render_html_success():
|
||||
captured_tool_names: list[list[str]] = []
|
||||
exec_fn = FakeExecuteTool(["Rendered HTML artifact."])
|
||||
exec_fn = FakeExecuteTool(["Rendered HTML canvas."])
|
||||
|
||||
def fake_single_turn(_messages, *, active_tools = None):
|
||||
captured_tool_names.append(
|
||||
|
|
@ -357,7 +357,7 @@ class TestLoopBasic:
|
|||
assert "Result: 1" in contents[-1]["text"]
|
||||
|
||||
def test_render_html_emits_provisional_tool_start(self):
|
||||
exec_fn = FakeExecuteTool(["Rendered HTML artifact."])
|
||||
exec_fn = FakeExecuteTool(["Rendered HTML canvas."])
|
||||
turn_iter = iter(
|
||||
[
|
||||
[
|
||||
|
|
@ -412,8 +412,8 @@ class TestLoopBasic:
|
|||
assert tool_starts[0]["tool_name"] == "python"
|
||||
assert exec_fn.calls == [("python", {"code": "print('<function=render_html>')"})]
|
||||
|
||||
def test_render_html_success_blocks_second_artifact_call(self):
|
||||
exec_fn = FakeExecuteTool(["Rendered HTML artifact."])
|
||||
def test_render_html_success_blocks_second_canvas_call(self):
|
||||
exec_fn = FakeExecuteTool(["Rendered HTML canvas."])
|
||||
turn_iter = iter(
|
||||
[
|
||||
[
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ def test_render_html_success_filters_active_tools_and_repeat_is_internal():
|
|||
]
|
||||
|
||||
first = controller.prepare_call(_call("render_html", {"code": "<html></html>"}, "call_html_1"))
|
||||
controller.record_result(first, "Rendered HTML artifact: Demo")
|
||||
controller.record_result(first, "Rendered HTML canvas: Demo")
|
||||
|
||||
assert [t["function"]["name"] for t in controller.active_tools()] == ["web_search"]
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ function isRenderableRenderHtmlToolPart(part: unknown): boolean {
|
|||
}
|
||||
if (
|
||||
typeof toolPart.result === "string" &&
|
||||
toolPart.result.startsWith("Rendered HTML artifact")
|
||||
toolPart.result.startsWith("Rendered HTML canvas")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -286,7 +286,7 @@ function CodeBlockActions({
|
|||
}
|
||||
|
||||
// DiffusionGemma renders its denoising live in the bubble (see DiffusionCanvas in
|
||||
// thread.tsx) and has the artifacts canvas on by default, so a full-HTML answer
|
||||
// thread.tsx) and has the HTML canvas feature on by default, so a full-HTML answer
|
||||
// (e.g. a playable game) renders as an interactive card without the global toggle.
|
||||
function StreamdownBlock(props: BlockProps) {
|
||||
const shouldCollapseHtmlArtifacts = useChatRuntimeStore(
|
||||
|
|
@ -335,7 +335,7 @@ function StreamdownBlock(props: BlockProps) {
|
|||
) {
|
||||
return (
|
||||
<div className="my-4 flex h-48 items-center justify-center rounded-xl border border-border bg-muted/30 text-sm text-muted-foreground animate-pulse">
|
||||
Loading artifact preview...
|
||||
Loading canvas preview...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ const ToolGroupImpl: FC<
|
|||
const forceOpen =
|
||||
hasPendingConfirmation || (forcedOpenRef.current && messageRunning);
|
||||
|
||||
// Render single tool calls and artifacts directly so cards never hide in a
|
||||
// Render single tool calls and canvases directly so cards never hide in a
|
||||
// collapsed group.
|
||||
if (toolCount <= 1 || containsArtifactTool) {
|
||||
return <>{children}</>;
|
||||
|
|
|
|||
|
|
@ -37,12 +37,12 @@ const RenderHtmlToolUIImpl: ToolCallMessagePartComponent = ({
|
|||
const code = typeof parsedArgs.code === "string" ? parsedArgs.code : "";
|
||||
const hasCode = code.trim().length > 0;
|
||||
const title =
|
||||
typeof parsedArgs.title === "string" ? parsedArgs.title : "HTML artifact";
|
||||
typeof parsedArgs.title === "string" ? parsedArgs.title : "HTML canvas";
|
||||
const isRunning = status?.type === "running";
|
||||
const codeIsStreaming = propStatus.code === "streaming";
|
||||
|
||||
// Surface the backend error when the tool call completed with invalid args.
|
||||
// Success results start with "Rendered HTML artifact"; errors with "Error:".
|
||||
// Success results start with "Rendered HTML canvas"; errors with "Error:".
|
||||
const errorText =
|
||||
status?.type === "complete" &&
|
||||
typeof result === "string" &&
|
||||
|
|
@ -118,16 +118,16 @@ const RenderHtmlToolUIImpl: ToolCallMessagePartComponent = ({
|
|||
<span className="grid min-w-0 flex-1 gap-1">
|
||||
<span className="truncate text-sm font-medium leading-tight text-foreground">
|
||||
{errorText
|
||||
? "Artifact error"
|
||||
? "Canvas error"
|
||||
: isStaleGeneratingArtifact
|
||||
? "Artifact interrupted"
|
||||
: "Artifact unavailable"}
|
||||
? "Canvas interrupted"
|
||||
: "Canvas unavailable"}
|
||||
</span>
|
||||
<span className="truncate text-[11px] leading-none text-muted-foreground">
|
||||
{errorText ??
|
||||
(isStaleGeneratingArtifact
|
||||
? "Refresh stopped this preview"
|
||||
: "HTML artifact")}
|
||||
: "HTML canvas")}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1841,8 +1841,8 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter {
|
|||
const hasOutboundImage = Boolean(imageBase64);
|
||||
|
||||
// Keep render_html local-only and mirror the backend image-turn gate.
|
||||
// Artifacts are independent of Search/Code: a local tool-capable model
|
||||
// with Artifacts on exposes render_html even with no other pills active.
|
||||
// Canvas is independent of Search/Code: a local tool-capable model
|
||||
// with Canvas on exposes render_html even with no other pills active.
|
||||
const renderHtmlToolEnabledForThisTurn = Boolean(
|
||||
!isExternalRequest &&
|
||||
supportsTools &&
|
||||
|
|
@ -1851,12 +1851,12 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter {
|
|||
);
|
||||
const artifactInstruction = artifactsEnabled
|
||||
? renderHtmlToolEnabledForThisTurn
|
||||
? "When the user asks for an HTML, CSS, or JavaScript artifact, call render_html once with one complete self-contained HTML document in the code argument. Embed CSS and JavaScript inside the document. After render_html succeeds, do not call it again in the same response unless the user asks for changes. Future user requests for new artifacts may call render_html once."
|
||||
: "When the user asks for an HTML, CSS, or JavaScript artifact, return one complete self-contained fenced html code block. Embed CSS and JavaScript inside the document. Do not emit tool-call syntax."
|
||||
? "When the user asks for an HTML, CSS, or JavaScript canvas, call render_html once with one complete self-contained HTML document in the code argument. Embed CSS and JavaScript inside the document. After render_html succeeds, do not call it again in the same response unless the user asks for changes. Future user requests for new canvases may call render_html once."
|
||||
: "When the user asks for an HTML, CSS, or JavaScript canvas, return one complete self-contained fenced html code block. Embed CSS and JavaScript inside the document. Do not emit tool-call syntax."
|
||||
: null;
|
||||
const effectiveDisabledToolGuard =
|
||||
disabledToolGuard && artifactsEnabled
|
||||
? `${disabledToolGuard} HTML, CSS, or JavaScript artifact requests can still be answered by following the artifact fallback instruction.`
|
||||
? `${disabledToolGuard} HTML, CSS, or JavaScript canvas requests can still be answered by following the canvas fallback instruction.`
|
||||
: disabledToolGuard;
|
||||
addSystemInstruction(outboundMessages, effectiveDisabledToolGuard);
|
||||
addSystemInstruction(outboundMessages, artifactInstruction);
|
||||
|
|
@ -3091,7 +3091,7 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter {
|
|||
closeReasoningContent();
|
||||
cumulativeText += delta;
|
||||
}
|
||||
// Strip a trailing ${...} template-literal artifact from
|
||||
// Strip a trailing ${...} template-literal fragment from
|
||||
// external streams (mistral magistral occasionally emits one).
|
||||
if (isExternalRequest) {
|
||||
cumulativeText = cumulativeText.replace(
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ export function ArtifactCard({
|
|||
{artifact.title}
|
||||
</span>
|
||||
<span className="truncate text-[11px] leading-none text-muted-foreground">
|
||||
HTML artifact
|
||||
HTML canvas
|
||||
</span>
|
||||
</span>
|
||||
{isStreaming ? (
|
||||
|
|
|
|||
|
|
@ -47,9 +47,9 @@ function buildHtmlFence(source: string): string {
|
|||
const fence = "`".repeat(longestBacktickRun + 1);
|
||||
return `${fence}html\n${source}\n${fence}`;
|
||||
}
|
||||
// Sandboxed artifact iframes are deliberately outside the overlay focus trap:
|
||||
// Sandboxed canvas iframes are deliberately outside the overlay focus trap:
|
||||
// granting same-origin sandbox privileges would weaken isolation, so reaching
|
||||
// interactive artifact content via keyboard is a known sandbox limitation.
|
||||
// interactive canvas content via keyboard is a known sandbox limitation.
|
||||
const FOCUSABLE_SELECTOR =
|
||||
'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ function ArtifactGeneratingPanel() {
|
|||
className="mx-auto mb-3 size-20 object-contain"
|
||||
/>
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
Building artifact preview…
|
||||
Building canvas preview…
|
||||
</p>
|
||||
<p className="text-xs leading-relaxed text-muted-foreground">
|
||||
The preview will appear here when the HTML is ready.
|
||||
|
|
@ -206,7 +206,7 @@ export function ArtifactSurface({
|
|||
? "artifact-panel-shell mx-2 mt-[72px] mb-8 h-[calc(100%_-_104px)] overflow-visible rounded-[28px] border-t border-border/70 bg-card/95"
|
||||
: "h-[min(92vh,900px)] w-[min(96vw,1200px)] overflow-hidden rounded-2xl border border-border shadow-xl",
|
||||
)}
|
||||
aria-label={`${artifact.title} artifact`}
|
||||
aria-label={`${artifact.title} canvas`}
|
||||
>
|
||||
<header
|
||||
className={cn(
|
||||
|
|
@ -217,7 +217,7 @@ export function ArtifactSurface({
|
|||
<div
|
||||
className="flex items-center gap-1 rounded-full bg-muted/40 p-0.5"
|
||||
role="tablist"
|
||||
aria-label="Artifact view"
|
||||
aria-label="Canvas view"
|
||||
>
|
||||
{(["preview", "source"] as const).map((mode) => {
|
||||
const isPreview = mode === "preview";
|
||||
|
|
@ -239,7 +239,7 @@ export function ArtifactSurface({
|
|||
"cursor-not-allowed opacity-50",
|
||||
)}
|
||||
aria-label={
|
||||
isPreview ? "Preview artifact" : "View artifact source"
|
||||
isPreview ? "Preview canvas" : "View canvas source"
|
||||
}
|
||||
aria-selected={effectiveViewMode === mode}
|
||||
aria-pressed={effectiveViewMode === mode}
|
||||
|
|
@ -264,7 +264,7 @@ export function ArtifactSurface({
|
|||
className="size-8"
|
||||
disabled={isLoadingArtifact || !hasArtifactCode}
|
||||
onClick={() => downloadTextFile(filename, artifact.code)}
|
||||
aria-label="Download artifact HTML"
|
||||
aria-label="Download canvas HTML"
|
||||
>
|
||||
<HugeiconsIcon icon={Download01Icon} className="size-4" />
|
||||
</Button>
|
||||
|
|
@ -275,7 +275,7 @@ export function ArtifactSurface({
|
|||
className="size-8"
|
||||
disabled={isLoadingArtifact || !hasArtifactCode}
|
||||
onClick={handleCopy}
|
||||
aria-label="Copy artifact HTML"
|
||||
aria-label="Copy canvas HTML"
|
||||
>
|
||||
{copied ? (
|
||||
<CheckIcon className="size-4" />
|
||||
|
|
@ -290,7 +290,7 @@ export function ArtifactSurface({
|
|||
size="icon"
|
||||
className="size-8"
|
||||
onClick={onOpenFullscreen}
|
||||
aria-label="Open artifact fullscreen"
|
||||
aria-label="Open canvas fullscreen"
|
||||
>
|
||||
<Maximize2Icon className="size-4" />
|
||||
</Button>
|
||||
|
|
@ -301,7 +301,7 @@ export function ArtifactSurface({
|
|||
size="icon"
|
||||
className="size-8"
|
||||
onClick={onClose}
|
||||
aria-label="Close artifact"
|
||||
aria-label="Close canvas"
|
||||
>
|
||||
<XIcon className="size-4" />
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -28,12 +28,12 @@ export function buildArtifactSrcDoc(code: string): string {
|
|||
return `${code}\n${resizeScript}`;
|
||||
}
|
||||
|
||||
// Preview iframes intentionally omit allow-downloads: generated artifacts can
|
||||
// Preview iframes intentionally omit allow-downloads: generated canvases can
|
||||
// offer their own UI, but downloads must go through Studio's explicit
|
||||
// copy/download controls outside the no-same-origin sandbox.
|
||||
export function ArtifactHtmlFrame({
|
||||
code,
|
||||
title = "HTML artifact preview",
|
||||
title = "HTML canvas preview",
|
||||
className,
|
||||
fill = false,
|
||||
}: {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export interface ChatArtifactInput {
|
|||
isStreaming?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_ARTIFACT_TITLE = "HTML artifact";
|
||||
const DEFAULT_ARTIFACT_TITLE = "HTML canvas";
|
||||
|
||||
export function normalizeArtifactTitle(title?: string | null): string {
|
||||
const trimmed = title?.trim();
|
||||
|
|
@ -80,5 +80,5 @@ export function getArtifactFilename(
|
|||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "")
|
||||
.slice(0, 48);
|
||||
return `${slug || "artifact"}.html`;
|
||||
return `${slug || "canvas"}.html`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1493,7 +1493,7 @@ export function ChatPage(): ReactElement {
|
|||
if (view.mode !== "single") return;
|
||||
if (view.threadId || view.newThreadNonce || !selectedArtifact) return;
|
||||
// view excludes __LOCALID_ threads (they fall through to mode:"single"
|
||||
// with no threadId/nonce). Don't close an artifact whose thread is the
|
||||
// with no threadId/nonce). Don't close a canvas whose thread is the
|
||||
// active local thread.
|
||||
if (
|
||||
selectedArtifact.threadId &&
|
||||
|
|
|
|||
|
|
@ -197,13 +197,13 @@ export const en = {
|
|||
title: "Chat",
|
||||
description: "Manage chat history stored on this device.",
|
||||
artifacts: {
|
||||
title: "Artifacts",
|
||||
title: "Canvas",
|
||||
collapseHtmlBlocks: "Collapse HTML blocks",
|
||||
collapseHtmlBlocksDescription:
|
||||
"Artifacts mode collapses full HTML automatically. Turn on to also collapse fenced HTML documents when Artifacts is off.",
|
||||
allowNetworkAccess: "Allow artifact network access",
|
||||
"Canvas mode collapses full HTML automatically. Turn this on to also collapse fenced HTML documents when Canvas is off.",
|
||||
allowNetworkAccess: "Allow canvas network access",
|
||||
allowNetworkAccessDescription:
|
||||
"Let artifact previews load scripts, styles, fonts, media, and network resources from CDNs. Keep off for fully offline previews.",
|
||||
"Let canvas previews load scripts, styles, fonts, media, and network resources from CDNs. Keep off for fully offline previews.",
|
||||
},
|
||||
data: "Data",
|
||||
exportHistory: "Export chat history",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue