| | import { Artifact } from '@/components/create-artifact'; |
| | import { CodeEditor } from '@/components/code-editor'; |
| | import { |
| | CopyIcon, |
| | LogsIcon, |
| | MessageIcon, |
| | PlayIcon, |
| | RedoIcon, |
| | UndoIcon, |
| | } from '@/components/icons'; |
| | import { toast } from 'sonner'; |
| | import { generateUUID } from '@/lib/utils'; |
| | import { |
| | Console, |
| | type ConsoleOutput, |
| | type ConsoleOutputContent, |
| | } from '@/components/console'; |
| |
|
| | const OUTPUT_HANDLERS = { |
| | matplotlib: ` |
| | import io |
| | import base64 |
| | from matplotlib import pyplot as plt |
| | |
| | # Clear any existing plots |
| | plt.clf() |
| | plt.close('all') |
| | |
| | # Switch to agg backend |
| | plt.switch_backend('agg') |
| | |
| | def setup_matplotlib_output(): |
| | def custom_show(): |
| | if plt.gcf().get_size_inches().prod() * plt.gcf().dpi ** 2 > 25_000_000: |
| | print("Warning: Plot size too large, reducing quality") |
| | plt.gcf().set_dpi(100) |
| | |
| | png_buf = io.BytesIO() |
| | plt.savefig(png_buf, format='png') |
| | png_buf.seek(0) |
| | png_base64 = base64.b64encode(png_buf.read()).decode('utf-8') |
| | print(f'data:image/png;base64,{png_base64}') |
| | png_buf.close() |
| | |
| | plt.clf() |
| | plt.close('all') |
| | |
| | plt.show = custom_show |
| | `, |
| | basic: ` |
| | # Basic output capture setup |
| | `, |
| | }; |
| |
|
| | function detectRequiredHandlers(code: string): string[] { |
| | const handlers: string[] = ['basic']; |
| |
|
| | if (code.includes('matplotlib') || code.includes('plt.')) { |
| | handlers.push('matplotlib'); |
| | } |
| |
|
| | return handlers; |
| | } |
| |
|
| | interface Metadata { |
| | outputs: Array<ConsoleOutput>; |
| | } |
| |
|
| | export const codeArtifact = new Artifact<'code', Metadata>({ |
| | kind: 'code', |
| | description: |
| | 'Useful for code generation; Code execution is only available for python code.', |
| | initialize: async ({ setMetadata }) => { |
| | setMetadata({ |
| | outputs: [], |
| | }); |
| | }, |
| | onStreamPart: ({ streamPart, setArtifact }) => { |
| | if (streamPart.type === 'data-codeDelta') { |
| | setArtifact((draftArtifact) => ({ |
| | ...draftArtifact, |
| | content: streamPart.data, |
| | isVisible: |
| | draftArtifact.status === 'streaming' && |
| | draftArtifact.content.length > 300 && |
| | draftArtifact.content.length < 310 |
| | ? true |
| | : draftArtifact.isVisible, |
| | status: 'streaming', |
| | })); |
| | } |
| | }, |
| | content: ({ metadata, setMetadata, ...props }) => { |
| | return ( |
| | <> |
| | <div className="px-1"> |
| | <CodeEditor {...props} /> |
| | </div> |
| | |
| | {metadata?.outputs && ( |
| | <Console |
| | consoleOutputs={metadata.outputs} |
| | setConsoleOutputs={() => { |
| | setMetadata({ |
| | ...metadata, |
| | outputs: [], |
| | }); |
| | }} |
| | /> |
| | )} |
| | </> |
| | ); |
| | }, |
| | actions: [ |
| | { |
| | icon: <PlayIcon size={18} />, |
| | label: 'Run', |
| | description: 'Execute code', |
| | onClick: async ({ content, setMetadata }) => { |
| | const runId = generateUUID(); |
| | const outputContent: Array<ConsoleOutputContent> = []; |
| |
|
| | setMetadata((metadata) => ({ |
| | ...metadata, |
| | outputs: [ |
| | ...metadata.outputs, |
| | { |
| | id: runId, |
| | contents: [], |
| | status: 'in_progress', |
| | }, |
| | ], |
| | })); |
| |
|
| | try { |
| | |
| | const currentPyodideInstance = await globalThis.loadPyodide({ |
| | indexURL: 'https://cdn.jsdelivr.net/pyodide/v0.23.4/full/', |
| | }); |
| |
|
| | currentPyodideInstance.setStdout({ |
| | batched: (output: string) => { |
| | outputContent.push({ |
| | type: output.startsWith('data:image/png;base64') |
| | ? 'image' |
| | : 'text', |
| | value: output, |
| | }); |
| | }, |
| | }); |
| |
|
| | await currentPyodideInstance.loadPackagesFromImports(content, { |
| | messageCallback: (message: string) => { |
| | setMetadata((metadata) => ({ |
| | ...metadata, |
| | outputs: [ |
| | ...metadata.outputs.filter((output) => output.id !== runId), |
| | { |
| | id: runId, |
| | contents: [{ type: 'text', value: message }], |
| | status: 'loading_packages', |
| | }, |
| | ], |
| | })); |
| | }, |
| | }); |
| |
|
| | const requiredHandlers = detectRequiredHandlers(content); |
| | for (const handler of requiredHandlers) { |
| | if (OUTPUT_HANDLERS[handler as keyof typeof OUTPUT_HANDLERS]) { |
| | await currentPyodideInstance.runPythonAsync( |
| | OUTPUT_HANDLERS[handler as keyof typeof OUTPUT_HANDLERS], |
| | ); |
| |
|
| | if (handler === 'matplotlib') { |
| | await currentPyodideInstance.runPythonAsync( |
| | 'setup_matplotlib_output()', |
| | ); |
| | } |
| | } |
| | } |
| |
|
| | await currentPyodideInstance.runPythonAsync(content); |
| |
|
| | setMetadata((metadata) => ({ |
| | ...metadata, |
| | outputs: [ |
| | ...metadata.outputs.filter((output) => output.id !== runId), |
| | { |
| | id: runId, |
| | contents: outputContent, |
| | status: 'completed', |
| | }, |
| | ], |
| | })); |
| | } catch (error: any) { |
| | setMetadata((metadata) => ({ |
| | ...metadata, |
| | outputs: [ |
| | ...metadata.outputs.filter((output) => output.id !== runId), |
| | { |
| | id: runId, |
| | contents: [{ type: 'text', value: error.message }], |
| | status: 'failed', |
| | }, |
| | ], |
| | })); |
| | } |
| | }, |
| | }, |
| | { |
| | icon: <UndoIcon size={18} />, |
| | description: 'View Previous version', |
| | onClick: ({ handleVersionChange }) => { |
| | handleVersionChange('prev'); |
| | }, |
| | isDisabled: ({ currentVersionIndex }) => { |
| | if (currentVersionIndex === 0) { |
| | return true; |
| | } |
| |
|
| | return false; |
| | }, |
| | }, |
| | { |
| | icon: <RedoIcon size={18} />, |
| | description: 'View Next version', |
| | onClick: ({ handleVersionChange }) => { |
| | handleVersionChange('next'); |
| | }, |
| | isDisabled: ({ isCurrentVersion }) => { |
| | if (isCurrentVersion) { |
| | return true; |
| | } |
| |
|
| | return false; |
| | }, |
| | }, |
| | { |
| | icon: <CopyIcon size={18} />, |
| | description: 'Copy code to clipboard', |
| | onClick: ({ content }) => { |
| | navigator.clipboard.writeText(content); |
| | toast.success('Copied to clipboard!'); |
| | }, |
| | }, |
| | ], |
| | toolbar: [ |
| | { |
| | icon: <MessageIcon />, |
| | description: 'Add comments', |
| | onClick: ({ sendMessage }) => { |
| | sendMessage({ |
| | role: 'user', |
| | parts: [ |
| | { |
| | type: 'text', |
| | text: 'Add comments to the code snippet for understanding', |
| | }, |
| | ], |
| | }); |
| | }, |
| | }, |
| | { |
| | icon: <LogsIcon />, |
| | description: 'Add logs', |
| | onClick: ({ sendMessage }) => { |
| | sendMessage({ |
| | role: 'user', |
| | parts: [ |
| | { |
| | type: 'text', |
| | text: 'Add logs to the code snippet for debugging', |
| | }, |
| | ], |
| | }); |
| | }, |
| | }, |
| | ], |
| | }); |
| |
|