From 940faeaa7366e9f9a1625a1fecbb823d3db2e4f5 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Tue, 31 Mar 2026 13:14:27 +0000 Subject: [PATCH] Remove problematic features: wake lock, artifacts panel, keyboard shortcuts, variable substitution - Remove screen wake lock from chat-adapter.ts (marginal value, doesn't survive tab switches) - Remove artifacts panel, artifact store, and ArtifactEmitter (session-scoped only, hash collision issues, duplicates existing code block actions) - Remove keyboard shortcuts except Cmd/Ctrl+K for command palette (other shortcuts conflict with browser-native bindings on Linux/Windows) - Remove keyboard-shortcut-help.tsx (no longer needed) - Simplify prompt library to copy raw template content instead of using window.prompt() for variable substitution (blocking dialog, broken in some embedded contexts) - Remove shortcut labels from command palette items - Clean up unused imports Kept: search, feedback, export, command palette (Cmd+K), memory, prompt library (without variable substitution), folders/pinning. --- .../components/assistant-ui/markdown-text.tsx | 28 --- .../src/features/chat/api/chat-adapter.ts | 15 -- .../frontend/src/features/chat/chat-page.tsx | 97 ++------- .../chat/components/artifact-panel.tsx | 187 ------------------ .../components/keyboard-shortcut-help.tsx | 52 ----- .../chat/components/prompt-library-sheet.tsx | 10 +- .../features/chat/stores/artifact-store.ts | 101 ---------- 7 files changed, 13 insertions(+), 477 deletions(-) delete mode 100644 studio/frontend/src/features/chat/components/artifact-panel.tsx delete mode 100644 studio/frontend/src/features/chat/components/keyboard-shortcut-help.tsx delete mode 100644 studio/frontend/src/features/chat/stores/artifact-store.ts diff --git a/studio/frontend/src/components/assistant-ui/markdown-text.tsx b/studio/frontend/src/components/assistant-ui/markdown-text.tsx index 828212055c..b7bfd76b22 100644 --- a/studio/frontend/src/components/assistant-ui/markdown-text.tsx +++ b/studio/frontend/src/components/assistant-ui/markdown-text.tsx @@ -16,7 +16,6 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { Block, type BlockProps, Streamdown } from "streamdown"; import "katex/dist/katex.min.css"; import { AudioPlayer } from "./audio-player"; -import { useArtifactStore } from "@/features/chat/stores/artifact-store"; const math = createMathPlugin({ singleDollarTextMath: true }); const { withSmoothContextProvider } = INTERNAL; @@ -337,23 +336,6 @@ function CodeBlockActions({ ); } -/** Emits an artifact to the store via useEffect (avoids side-effects in render). */ -function ArtifactEmitter({ language, source }: { language: string | null; source: string }) { - useEffect(() => { - const artifactId = `artifact-${hashCode(source)}`; - const store = useArtifactStore.getState(); - if (store.artifacts.some((a) => a.id === artifactId)) return; - store.addArtifact({ - id: artifactId, - title: language ? `${language} snippet` : "Code snippet", - language, - content: source, - createdAt: Date.now(), - }); - }, [language, source]); - return null; -} - function StreamdownBlock(props: BlockProps) { const hasMermaidFence = props.content.includes("```mermaid"); const mermaidSource = getMermaidSource(props.content); @@ -401,18 +383,8 @@ function StreamdownBlock(props: BlockProps) { const svgSource = !props.isIncomplete && isSvgFence(codeFence) ? sanitizeSvg(codeFence.source) : null; const htmlSource = !props.isIncomplete && isHtmlFence(codeFence) ? codeFence.source : null; - // Emit artifact for large code blocks or HTML/SVG/Mermaid - const lineCount = codeFence.source.split("\n").length; - const isArtifactWorthy = - !props.isIncomplete && - (lineCount >= 20 || svgSource !== null || htmlSource !== null); - return ( <> - {isArtifactWorthy && ( - - )} -
{}); - wakeLock = null; - } } }, }; diff --git a/studio/frontend/src/features/chat/chat-page.tsx b/studio/frontend/src/features/chat/chat-page.tsx index e844e97da8..25008a73e5 100644 --- a/studio/frontend/src/features/chat/chat-page.tsx +++ b/studio/frontend/src/features/chat/chat-page.tsx @@ -16,7 +16,6 @@ import { CommandInput, CommandItem, CommandList, - CommandShortcut, } from "@/components/ui/command"; import { SidebarProvider, SidebarTrigger, useSidebar } from "@/components/ui/sidebar"; import { @@ -39,8 +38,6 @@ import { BookOpenIcon, BrainIcon, ColumnsIcon, - KeyboardIcon, - PanelRightIcon, PencilIcon, SettingsIcon, } from "lucide-react"; @@ -56,9 +53,6 @@ import { useState, } from "react"; import { toast } from "sonner"; -import { KeyboardShortcutHelp } from "./components/keyboard-shortcut-help"; -import { ArtifactPanel } from "./components/artifact-panel"; -import { useArtifactStore } from "./stores/artifact-store"; import { PromptLibrarySheet } from "./components/prompt-library-sheet"; import { GuidedTour, useGuidedTourController } from "@/features/tour"; import { listLocalModels } from "./api/chat-api"; @@ -447,9 +441,7 @@ export function ChatPage(): ReactElement { null, ); const [commandPaletteOpen, setCommandPaletteOpen] = useState(false); - const [shortcutHelpOpen, setShortcutHelpOpen] = useState(false); const [promptLibraryOpen, setPromptLibraryOpen] = useState(false); - const artifactPanelOpen = useArtifactStore((s) => s.panelOpen); const inferenceParams = useChatRuntimeStore((state) => state.params); const setInferenceParams = useChatRuntimeStore((state) => state.setParams); const activeGgufVariant = useChatRuntimeStore((state) => state.activeGgufVariant); @@ -530,14 +522,12 @@ export function ChatPage(): ReactElement { const handleNewThread = useCallback( () => { useChatRuntimeStore.getState().setActiveThreadId(null); - useArtifactStore.getState().clearArtifacts(); setView({ mode: "single", newThreadNonce: crypto.randomUUID() }); }, [], ); const handleNewCompare = useCallback( () => { - useArtifactStore.getState().clearArtifacts(); setView({ mode: "compare", pairId: crypto.randomUUID() }); useChatRuntimeStore.getState().setContextUsage(null); }, @@ -566,7 +556,6 @@ export function ChatPage(): ReactElement { const openSidebar = useCallback(() => setSidebarOpen(true), []); const enterCompare = useCallback(() => { - useArtifactStore.getState().clearArtifacts(); setViewBeforeCompare((prev) => prev ?? view); setView({ mode: "compare", pairId: crypto.randomUUID() }); useChatRuntimeStore.getState().setContextUsage(null); @@ -574,7 +563,6 @@ export function ChatPage(): ReactElement { const exitCompare = useCallback(() => { if (!viewBeforeCompare) return; - useArtifactStore.getState().clearArtifacts(); setView(viewBeforeCompare); setViewBeforeCompare(null); // Restore context usage from the active thread's last assistant message @@ -596,14 +584,7 @@ export function ChatPage(): ReactElement { const handleThreadSelect = useCallback( (nextView: ChatView) => { - setView((prev) => { - const prevId = prev.mode === "single" ? prev.threadId : prev.pairId; - const nextId = nextView.mode === "single" ? nextView.threadId : nextView.pairId; - if (prevId !== nextId) { - useArtifactStore.getState().clearArtifacts(); - } - return nextView; - }); + setView(nextView); }, [], ); @@ -749,55 +730,22 @@ export function ChatPage(): ReactElement { return () => window.clearTimeout(timeoutId); }, [modelSelectorLocked, tour.open]); - // Global keyboard shortcuts + // Global keyboard shortcut: Cmd/Ctrl+K for command palette useEffect(() => { function handleKeyDown(e: KeyboardEvent) { const mod = e.metaKey || e.ctrlKey; - const target = e.target as HTMLElement; - const isInput = - target.tagName === "INPUT" || - target.tagName === "TEXTAREA" || - target.isContentEditable; - if (mod && e.key === "k") { e.preventDefault(); setCommandPaletteOpen((o) => !o); - return; - } - if (mod && e.shiftKey && e.key === "N") { - e.preventDefault(); - handleNewThread(); - return; - } - if (mod && e.shiftKey && e.key === "C") { - e.preventDefault(); - if (canCompare) handleNewCompare(); - return; - } - if (mod && e.shiftKey && e.key === "S") { - e.preventDefault(); - setSettingsOpen((o) => !o); - return; - } - if (e.key === "Escape") { - setCommandPaletteOpen(false); - setShortcutHelpOpen(false); - return; - } - if (e.key === "?" && !isInput) { - e.preventDefault(); - setShortcutHelpOpen((o) => !o); - return; } } document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); - }, [canCompare, handleNewThread, handleNewCompare]); + }, []); return (
- { handleNewThread(); setCommandPaletteOpen(false); }}> New Chat - Shift+N {canCompare && ( { handleNewCompare(); setCommandPaletteOpen(false); }}> Compare Mode - Shift+C )} { setSettingsOpen(true); setCommandPaletteOpen(false); }}> Settings - Shift+S @@ -835,22 +780,11 @@ export function ChatPage(): ReactElement { Prompt Library - { const s = useArtifactStore.getState(); s.setPanelOpen(!s.panelOpen); setCommandPaletteOpen(false); }}> - - Toggle Artifacts Panel - { handleEject(); setCommandPaletteOpen(false); }}> Eject Model - - { setShortcutHelpOpen(true); setCommandPaletteOpen(false); }}> - - Keyboard Shortcuts - ? - - @@ -943,22 +877,15 @@ export function ChatPage(): ReactElement {
-
-
- {view.mode === "single" ? ( - - ) : ( - - )} -
- {artifactPanelOpen && ( -
- -
+
+ {view.mode === "single" ? ( + + ) : ( + )}
diff --git a/studio/frontend/src/features/chat/components/artifact-panel.tsx b/studio/frontend/src/features/chat/components/artifact-panel.tsx deleted file mode 100644 index 3232613aba..0000000000 --- a/studio/frontend/src/features/chat/components/artifact-panel.tsx +++ /dev/null @@ -1,187 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0 - -import { Button } from "@/components/ui/button"; -import { copyToClipboard } from "@/lib/copy-to-clipboard"; -import { downloadTextFile } from "@/lib/download"; -import { - CheckIcon, - ChevronLeftIcon, - ChevronRightIcon, - CopyIcon, - DownloadIcon, - XIcon, -} from "lucide-react"; -import { type FC, useEffect, useRef, useState } from "react"; -import { - type Artifact, - useArtifactStore, -} from "../stores/artifact-store"; - -const COPY_RESET_MS = 2000; - -const ArtifactTab: FC<{ artifact: Artifact; isActive: boolean }> = ({ - artifact, - isActive, -}) => { - const setActive = useArtifactStore((s) => s.setActiveArtifact); - const remove = useArtifactStore((s) => s.removeArtifact); - - return ( -
setActive(artifact.id)} - onKeyDown={(e) => e.key === "Enter" && setActive(artifact.id)} - className={`group flex cursor-pointer items-center gap-1.5 rounded-t-md border-b-2 px-3 py-1.5 text-xs font-medium transition-colors ${ - isActive - ? "border-primary bg-background text-foreground" - : "border-transparent text-muted-foreground hover:text-foreground" - }`} - > - {artifact.title} - -
- ); -}; - -export const ArtifactPanel: FC = () => { - const artifacts = useArtifactStore((s) => s.artifacts); - const activeId = useArtifactStore((s) => s.activeArtifactId); - const panelOpen = useArtifactStore((s) => s.panelOpen); - const setPanelOpen = useArtifactStore((s) => s.setPanelOpen); - const setVersion = useArtifactStore((s) => s.setActiveVersion); - const updateContent = useArtifactStore((s) => s.updateArtifactContent); - - const [copied, setCopied] = useState(false); - const [localValue, setLocalValue] = useState(""); - const resetRef = useRef | null>(null); - const textareaRef = useRef(null); - - const active = artifacts.find((a) => a.id === activeId) ?? artifacts[0]; - const viewedContent = active - ? (active.history[active.activeVersion] ?? active.content) - : ""; - - // Sync local editor value when switching artifacts or versions - useEffect(() => { - if (!active) return; - setLocalValue(active.history[active.activeVersion] ?? active.content); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [active?.id, active?.activeVersion]); - - // Cleanup copy timer on unmount - useEffect(() => { - return () => { - if (resetRef.current) clearTimeout(resetRef.current); - }; - }, []); - - if (!panelOpen || artifacts.length === 0 || !active) return null; - - const handleCopy = () => { - if (copyToClipboard(localValue)) { - setCopied(true); - if (resetRef.current) clearTimeout(resetRef.current); - resetRef.current = setTimeout(() => setCopied(false), COPY_RESET_MS); - } - }; - - const handleDownload = () => { - const ext = active.language === "html" - ? ".html" - : active.language === "svg" - ? ".svg" - : active.language - ? `.${active.language}` - : ".txt"; - downloadTextFile(`${active.title}${ext}`, localValue); - }; - - const canPrev = active.activeVersion > 0; - const canNext = active.activeVersion < active.history.length - 1; - - return ( -
- {/* Tab bar */} -
-
- {artifacts.map((a) => ( - - ))} -
- -
- - {/* Toolbar */} -
- {active.title} - {active.history.length > 1 && ( -
- - - v{active.activeVersion + 1}/{active.history.length} - - -
- )} - - -
- - {/* Editor */} -
-