Files
windmill/frontend/src/lib/components/copilot/chat/script/CodeDisplay.svelte
T
Ruben FiszelandClaude Opus 4.8 cfb9f1dbc2 feat: render mermaid diagrams in chat code blocks (#9738)
* feat: render mermaid diagrams in chat code blocks

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: guard mermaid render against out-of-order async and transient streaming failures

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: only show mermaid diagram while it matches current source

Addresses Codex review: keeping the last good SVG through parse failures left a stale, mismatched diagram on screen when the source changed to something invalid. Tie the rendered SVG to the source that produced it and only display it while it still matches the current code, falling back to the raw source otherwise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 21:02:10 +00:00

127 lines
2.7 KiB
Svelte

<script lang="ts">
import { getAstNode } from 'svelte-exmarkdown'
import HighlightCode from '$lib/components/HighlightCode.svelte'
import {
csharp,
go,
graphql,
javascript,
php,
python,
rust,
shell,
sql,
typescript,
yaml
} from 'svelte-highlight/languages'
import { AIMode } from '../AIChatManager.svelte'
import { getAiChatManager } from '../aiChatManagerContext'
import { Check, Play } from 'lucide-svelte'
import MermaidDisplay from './MermaidDisplay.svelte'
const aiChatManager = getAiChatManager()
const astNode = getAstNode()
function getSmartLang(lang: string) {
switch (lang) {
case 'python':
case 'python3':
return 'python'
case 'deno':
case 'nativets':
case 'bun':
case 'bunnative':
case 'typescript':
return 'typescript'
case 'go':
return 'go'
case 'shell':
case 'bash':
return 'shell'
case 'frontend':
case 'javascript':
return 'javascript'
case 'graphql':
return 'graphql'
case 'mysql':
case 'snowflake':
case 'bigquery':
case 'oracledb':
case 'powershell':
case 'postgresql':
case 'sql':
return 'sql'
case 'php':
return 'php'
case 'rust':
return 'rust'
case 'csharp':
return 'csharp'
case 'ansible':
case 'yaml':
return 'yaml'
default:
return 'typescript'
}
}
const SMART_LANG_TO_HIGHLIGHT_LANG = {
python: python,
typescript: typescript,
go: go,
shell: shell,
javascript: javascript,
graphql: graphql,
sql: sql,
php: php,
rust: rust,
csharp: csharp,
yaml: yaml
}
let code = $derived(astNode.current.children?.[0]?.children?.[0]?.value)
let language = $derived(
(astNode.current.children?.[0]?.properties?.class as string | undefined)?.split('-')[1]
)
// Check if the apply button should be shown
let showApplyButton = $derived.by(() => {
if (
aiChatManager.mode !== AIMode.SCRIPT ||
!aiChatManager.scriptEditorApplyCode ||
code === aiChatManager.scriptEditorOptions?.getCode()
) {
return false
}
return true
})
function handleApplyCode() {
if (code && aiChatManager.scriptEditorApplyCode) {
void aiChatManager.applyScriptEditorCode(code, { mode: 'apply' })
}
}
</script>
<div class="flex flex-col gap-0.5 rounded-lg relative not-prose !text-xs">
<div
class="relative w-full border border-gray-300 dark:border-gray-600 rounded-lg overflow-hidden"
>
{#if language === 'mermaid'}
<MermaidDisplay code={code ?? ''} />
{:else}
<HighlightCode
className="p-1"
code={code ?? ''}
highlightLanguage={SMART_LANG_TO_HIGHLIGHT_LANG[getSmartLang(language as string)]}
language={undefined}
onApplyCode={handleApplyCode}
{showApplyButton}
applyButtonIcon={aiChatManager.pendingNewCode ? Check : Play}
/>
{/if}
</div>
</div>