diff --git a/studio/backend/core/inference/llama_cpp.py b/studio/backend/core/inference/llama_cpp.py index 448502661f..7b552ca8b1 100644 --- a/studio/backend/core/inference/llama_cpp.py +++ b/studio/backend/core/inference/llama_cpp.py @@ -1277,12 +1277,13 @@ class LlamaCppBackend: the next token. Without this, iter_text() blocks until the next chunk arrives and cancellation can take many seconds on large models. """ + text_iter = response.iter_text() while True: if cancel_event is not None and cancel_event.is_set(): response.close() return try: - chunk = next(response.iter_text()) + chunk = next(text_iter) yield chunk except StopIteration: return diff --git a/studio/frontend/src/components/assistant-ui/markdown-text.tsx b/studio/frontend/src/components/assistant-ui/markdown-text.tsx index ce8829a37f..f61990ace2 100644 --- a/studio/frontend/src/components/assistant-ui/markdown-text.tsx +++ b/studio/frontend/src/components/assistant-ui/markdown-text.tsx @@ -64,6 +64,7 @@ function getCodeFilename(language: string | null) { ts: "ts", tsx: "tsx", typescript: "ts", + svg: "svg", yaml: "yml", yml: "yml", }; @@ -76,6 +77,33 @@ function getCodeFilename(language: string | null) { return `snippet.${ext}`; } +function isSvgFence(codeFence: CodeFence): boolean { + const lang = codeFence.language?.toLowerCase() ?? ""; + if (lang === "svg") return true; + if ((lang === "xml" || lang === "html") && codeFence.source.trimStart().startsWith("]|on\w+\s*=|javascript:|]|]|]|]/i; + +function sanitizeSvg(source: string): string | null { + if (UNSAFE_SVG_RE.test(source)) return null; + return source; +} + +function SvgPreview({ source }: { source: string }) { + const dataUri = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(source)}`; + return ( +
+ SVG preview +
+ ); +} + function downloadTextFile(filename: string, text: string): void { const blob = new Blob([text], { type: "text/plain;charset=utf-8" }); const url = URL.createObjectURL(blob); @@ -207,15 +235,19 @@ function StreamdownBlock(props: BlockProps) { } if (codeFence) { + const svgSource = !props.isIncomplete && isSvgFence(codeFence) ? sanitizeSvg(codeFence.source) : null; return ( -
- - -
+ <> +
+ + +
+ {svgSource && } + ); } diff --git a/studio/frontend/src/features/chat/api/chat-adapter.ts b/studio/frontend/src/features/chat/api/chat-adapter.ts index 3e689c11dc..29b5f60b64 100644 --- a/studio/frontend/src/features/chat/api/chat-adapter.ts +++ b/studio/frontend/src/features/chat/api/chat-adapter.ts @@ -232,8 +232,8 @@ async function autoLoadSmallestModel(): Promise { gguf_variant: variant.quant, trust_remote_code: false, }); + useChatRuntimeStore.getState().setCheckpoint(repo.repo_id, variant.quant); const store = useChatRuntimeStore.getState(); - store.setCheckpoint(repo.repo_id, variant.quant); store.setParams({ ...store.params, maxTokens: loadResp.context_length ?? 131072 }); // Add model to store so the selector shows the name const autoModel: ChatModelSummary = { @@ -282,8 +282,8 @@ async function autoLoadSmallestModel(): Promise { gguf_variant: null, trust_remote_code: false, }); + useChatRuntimeStore.getState().setCheckpoint(repo.repo_id); const store = useChatRuntimeStore.getState(); - store.setCheckpoint(repo.repo_id); store.setParams({ ...store.params, maxTokens: 4096 }); const sfModel: ChatModelSummary = { id: repo.repo_id, @@ -319,8 +319,8 @@ async function autoLoadSmallestModel(): Promise { gguf_variant: "UD-Q4_K_XL", trust_remote_code: false, }); + useChatRuntimeStore.getState().setCheckpoint("unsloth/Qwen3.5-4B-GGUF", "UD-Q4_K_XL"); const store = useChatRuntimeStore.getState(); - store.setCheckpoint("unsloth/Qwen3.5-4B-GGUF", "UD-Q4_K_XL"); store.setParams({ ...store.params, maxTokens: loadResp.context_length ?? 131072 }); const defaultModel: ChatModelSummary = { id: "unsloth/Qwen3.5-4B-GGUF", diff --git a/studio/frontend/src/features/chat/runtime-provider.tsx b/studio/frontend/src/features/chat/runtime-provider.tsx index 099889cc99..7985f4970e 100644 --- a/studio/frontend/src/features/chat/runtime-provider.tsx +++ b/studio/frontend/src/features/chat/runtime-provider.tsx @@ -31,7 +31,7 @@ import { useChatRuntimeStore } from "./stores/chat-runtime-store"; import type { MessageRecord, ModelType } from "./types"; const DEFAULT_SUGGESTIONS = [ - "Draw an ASCII art of a cute sloth", + "Draw an SVG of a cute sloth", "Solve the integral of x²·sin(x) step by step", "Write a Python function that finds the longest palindrome in a string", "Format a comparison of 3 databases as a markdown table with pros and cons",