import { Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Group, Panel, Separator } from 'react-resizable-panels' import { PrismLight as SyntaxHighlighter } from 'react-syntax-highlighter' import python from 'react-syntax-highlighter/dist/esm/languages/prism/python' import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism' import { readConversationArtifactText } from '../lib/api' import type { ArtifactFileInfo, LatestArtifactsResponse } from '../types' SyntaxHighlighter.registerLanguage('python', python) const ModelViewport = lazy(async () => { const module = await import('./ModelViewport') return { default: module.ModelViewport } }) interface WorkbenchPaneProps { artifacts: LatestArtifactsResponse | null loading: boolean } function dedupeArtifacts(items: ArtifactFileInfo[]): ArtifactFileInfo[] { const seen = new Set() return items.filter((item) => { if (!item.path || seen.has(item.path)) { return false } seen.add(item.path) return true }) } function formatArtifactLabel(path: string): string { const segments = path.replace(/\\/g, '/').split('/').filter(Boolean) if (!segments.length) { return path } return segments.slice(-3).join('/') } function deriveSiblingModelScriptPath(modelPath: string): string | null { const normalizedPath = modelPath.replace(/\\/g, '/').trim() if (!normalizedPath || normalizedPath.startsWith('Local /')) { return null } if (!normalizedPath.toLowerCase().endsWith('.stl')) { return null } const lastSlashIndex = normalizedPath.lastIndexOf('/') if (lastSlashIndex < 0) { return 'model.py' } return `${normalizedPath.slice(0, lastSlashIndex)}/model.py` } export function WorkbenchPane({ artifacts, loading }: WorkbenchPaneProps) { const modelFileInputId = 'local-model-file-input' const codeFileInputId = 'local-code-file-input' const baseCodeOptions = useMemo(() => { if (artifacts?.code_files?.length) { return dedupeArtifacts(artifacts.code_files) } return artifacts?.code_file ? [artifacts.code_file] : [] }, [artifacts]) const modelOptions = useMemo(() => { if (artifacts?.model_files?.length) { return dedupeArtifacts(artifacts.model_files) } return artifacts?.model_file ? [artifacts.model_file] : [] }, [artifacts]) const [selectedCodePath, setSelectedCodePath] = useState('') const [selectedModelPath, setSelectedModelPath] = useState('') const [localCodeFile, setLocalCodeFile] = useState<{ path: string; content: string } | null>( null, ) const [localModelFile, setLocalModelFile] = useState<{ path: string; url: string } | null>( null, ) const [autoCodeFile, setAutoCodeFile] = useState(null) const autoResolveRequestRef = useRef(0) const codeOptions = useMemo(() => { if (!autoCodeFile) { return baseCodeOptions } return dedupeArtifacts([autoCodeFile, ...baseCodeOptions]) }, [autoCodeFile, baseCodeOptions]) const latestCodePath = autoCodeFile?.path ?? artifacts?.code_file?.path ?? codeOptions[0]?.path ?? '' const latestModelPath = artifacts?.model_file?.path ?? modelOptions[0]?.path ?? '' const effectiveSelectedCodePath = codeOptions.some((item) => item.path === selectedCodePath) ? selectedCodePath : latestCodePath const effectiveSelectedModelPath = modelOptions.some((item) => item.path === selectedModelPath) ? selectedModelPath : latestModelPath const selectedCodeFile = useMemo( () => codeOptions.find((item) => item.path === effectiveSelectedCodePath) ?? codeOptions[0] ?? null, [codeOptions, effectiveSelectedCodePath], ) const selectedModelFile = useMemo( () => modelOptions.find((item) => item.path === effectiveSelectedModelPath) ?? modelOptions[0] ?? null, [effectiveSelectedModelPath, modelOptions], ) const codePath = localCodeFile?.path ?? selectedCodeFile?.path const codeValue = localCodeFile?.content ?? selectedCodeFile?.content ?? '' const codeReadFailed = !localCodeFile && Boolean(codePath && selectedCodeFile?.content == null) const modelPath = localModelFile?.path ?? selectedModelFile?.path const modelUrl = localModelFile?.url ?? selectedModelFile?.url const showModelArtifactPicker = !localModelFile && modelOptions.length > 1 const showCodeArtifactPicker = !localCodeFile && codeOptions.length > 1 useEffect(() => { return () => { if (localModelFile?.url) { URL.revokeObjectURL(localModelFile.url) } } }, [localModelFile]) useEffect(() => { autoResolveRequestRef.current += 1 setAutoCodeFile(null) }, [artifacts?.conversation_id]) const handleLocalModelSelection = (file: File | null) => { if (!file) { return } const nextUrl = URL.createObjectURL(file) setLocalModelFile((currentFile) => { if (currentFile?.url) { URL.revokeObjectURL(currentFile.url) } return { path: `Local / ${file.name}`, url: nextUrl, } }) setAutoCodeFile(null) setSelectedModelPath('') } const handleLocalCodeSelection = async (file: File | null) => { if (!file) { return } const nextContent = await file.text() setLocalCodeFile({ path: `Local / ${file.name}`, content: nextContent, }) setSelectedCodePath('') } const handleModelReady = useCallback( async (readyModelPath: string) => { if (localModelFile || !artifacts?.conversation_id) { setAutoCodeFile(null) return } const preferredCodePath = deriveSiblingModelScriptPath(readyModelPath) if (!preferredCodePath) { setAutoCodeFile(null) return } const existingCodeFile = baseCodeOptions.find((item) => item.path === preferredCodePath) if (existingCodeFile?.content != null) { setAutoCodeFile(existingCodeFile) setLocalCodeFile(null) setSelectedCodePath('') return } const requestId = autoResolveRequestRef.current + 1 autoResolveRequestRef.current = requestId const content = await readConversationArtifactText(artifacts.conversation_id, preferredCodePath) if (autoResolveRequestRef.current !== requestId) { return } if (content == null) { setAutoCodeFile(null) return } setAutoCodeFile({ path: preferredCodePath, content, content_type: 'text/plain; charset=utf-8', }) setLocalCodeFile(null) setSelectedCodePath('') }, [artifacts?.conversation_id, baseCodeOptions, localModelFile], ) const renderEmptyStateButton = ( className: string, heading: string, body: string, fileInputId: string, ) => ( ) useEffect(() => { console.info('[workbench] artifact selection state', { loading, latestCodePath: artifacts?.code_file?.path ?? null, latestModelPath: artifacts?.model_file?.path ?? null, codeOptions: codeOptions.map((item) => item.path), modelOptions: modelOptions.map((item) => item.path), outputFiles: artifacts?.output_files ?? [], selectedCodePath: codePath ?? null, selectedModelPath: modelPath ?? null, codeReadFailed, hasModelUrl: Boolean(modelUrl), autoCodePath: autoCodeFile?.path ?? null, }) }, [ autoCodeFile, artifacts, codeOptions, codePath, codeReadFailed, loading, modelOptions, modelPath, modelUrl, ]) return ( { handleLocalModelSelection(event.target.files?.[0] ?? null) event.currentTarget.value = '' }} /> { void handleLocalCodeSelection(event.target.files?.[0] ?? null) event.currentTarget.value = '' }} />

Preview

{modelPath ? formatArtifactLabel(modelPath) : 'No model yet'}

{showModelArtifactPicker ? ( ) : null} {modelUrl ? ( Download ) : null}
{loading ? ( renderEmptyStateButton( 'viewport-empty-state', 'Loading preview', 'Fetching the latest model files. Click to open a local STL instead.', modelFileInputId, ) ) : ( )}

Code

{codePath ? formatArtifactLabel(codePath) : 'No code yet'}

{showCodeArtifactPicker ? ( ) : null}
{codePath && !codeReadFailed ? (
{codeValue}
) : codeReadFailed ? ( renderEmptyStateButton( 'code-empty-state', 'Code preview unavailable', 'The selected file exists, but its text content could not be read. Click to open a local file instead.', codeFileInputId, ) ) : ( renderEmptyStateButton( 'code-empty-state', 'No code yet', 'The latest generated CAD script will show up here. Click to open a local code file now.', codeFileInputId, ) )}
) }