unsloth/studio/frontend/package.json
Daniel Han 83e67231cd Studio: render HTML and SVG fences inline with sandboxed preview tabs
Adds a dedicated HtmlSvgRenderer that turns ```html / ```svg fences in
assistant messages into an inline preview with a Code/Preview tab
toggle. HTML runs inside an iframe with sandbox="allow-scripts" only
(no allow-same-origin, no allow-top-navigation) so JS games execute but
cannot reach parent.document. SVG is sanitized through DOMPurify with
the svg/svgFilters profile before being mounted via
dangerouslySetInnerHTML, stripping <script>, on* handlers, javascript:
URLs, and any tag that could escape the SVG sandbox.

The renderer defaults to the Preview tab for completed fences and
locks to the Code tab while the stream is still arriving so users see
partial tokens rather than a flashing preview. The Code tab keeps the
existing Streamdown syntax-highlighted view and the established copy /
download chrome from CodeBlockActions. HTML previews cap at 500px and
have a pop-out affordance that expands to 80vh; Esc exits the modal.

Wires the renderer into the markdown pipeline via the existing
StreamdownBlock fork in markdown-text.tsx, replacing the previous
stacked code+preview layout. Non-HTML / non-SVG fences fall through to
the original highlighted code block.

Adds Vitest + jsdom + React Testing Library to the frontend dev
dependencies and a vitest.config.ts. Covers: iframe sandbox attribute,
SVG script + onclick stripping, Code/Preview toggle, streaming lock,
DOMPurify behaviour on javascript: URLs and onload handlers, and that
non-HTML/SVG fences are not routed through the renderer.
2026-05-23 14:00:36 +00:00

111 lines
3.3 KiB
JSON

{
"name": "unsloth-theme",
"private": true,
"version": "0.0.0",
"type": "module",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
"typecheck": "tsc -b --pretty false",
"biome:check": "biome check .",
"biome:fix": "biome check . --write",
"test": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"@assistant-ui/core": "0.1.17",
"@assistant-ui/react": "0.12.28",
"@assistant-ui/tap": "0.5.10",
"@base-ui/react": "^1.2.0",
"@dagrejs/dagre": "^2.0.4",
"@dagrejs/graphlib": "^3.0.4",
"@fontsource-variable/figtree": "^5.2.10",
"@fontsource-variable/inter": "^5.2.8",
"@fontsource-variable/space-grotesk": "^5.2.10",
"@hugeicons/core-free-icons": "^4.1.1",
"@hugeicons/react": "^1.1.5",
"@huggingface/hub": "^2.9.0",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@streamdown/code": "1.1.1",
"@streamdown/math": "1.0.2",
"@streamdown/mermaid": "1.0.2",
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-router": "1.169.2",
"@tanstack/react-table": "^8.21.3",
"@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
"@tauri-apps/plugin-notification": "^2.3.3",
"@tauri-apps/plugin-opener": "^2.5.3",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-updater": "^2.10.1",
"@toolwind/corner-shape": "^0.0.8-3",
"@xyflow/react": "^12.10.0",
"assistant-stream": "0.3.12",
"canvas-confetti": "^1.9.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"dexie": "^4.3.0",
"dompurify": "^3.4.2",
"fflate": "0.8.3",
"js-yaml": "^4.1.1",
"katex": "^0.16.28",
"lucide-react": "^1.7.0",
"mammoth": "^1.11.0",
"motion": "^12.34.0",
"next-themes": "^0.4.6",
"node-forge": "^1.4.0",
"radix-ui": "^1.4.3",
"react": "^19.2.4",
"react-day-picker": "^9.13.2",
"react-dom": "^19.2.4",
"react-resizable-panels": "^4.6.4",
"recharts": "3.7.0",
"shadcn": "^4.2.0",
"sonner": "^2.0.7",
"streamdown": "2.5.0",
"tailwind-merge": "^3.4.0",
"tailwindcss": "^4.1.18",
"tw-animate-css": "^1.4.0",
"tw-shimmer": "^0.4.6",
"unpdf": "^1.4.0",
"zustand": "^5.0.11"
},
"overrides": {
"@tanstack/react-router": "1.169.2",
"@tanstack/router-core": "1.169.2",
"@tanstack/history": "1.161.6"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@eslint/js": "^9.39.1",
"@types/canvas-confetti": "^1.9.0",
"@types/js-yaml": "^4.0.9",
"@types/node-forge": "^1.3.14",
"@types/node": "^25.5.2",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@testing-library/dom": "^10.4.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@vitejs/plugin-react": "^6.0.1",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"jsdom": "^25.0.1",
"typescript": "~5.9.3",
"typescript-eslint": "^8.55.0",
"vite": "^8.0.1",
"vitest": "^2.1.9"
}
}