123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- <script lang="ts">
- import hljs from 'highlight.js';
- import { loadPyodide } from 'pyodide';
- import mermaid from 'mermaid';
- import { v4 as uuidv4 } from 'uuid';
- import { getContext, getAllContexts, onMount, tick, createEventDispatcher } from 'svelte';
- import { copyToClipboard } from '$lib/utils';
- import 'highlight.js/styles/github-dark.min.css';
- import PyodideWorker from '$lib/workers/pyodide.worker?worker';
- import CodeEditor from '$lib/components/common/CodeEditor.svelte';
- const i18n = getContext('i18n');
- const dispatch = createEventDispatcher();
- export let id = '';
- export let token;
- export let lang = '';
- export let code = '';
- let _code = '';
- $: if (code) {
- updateCode();
- }
- const updateCode = () => {
- _code = code;
- };
- let _token = null;
- let mermaidHtml = null;
- let highlightedCode = null;
- let executing = false;
- let stdout = null;
- let stderr = null;
- let result = null;
- let copied = false;
- let saved = false;
- const saveCode = () => {
- saved = true;
- code = _code;
- dispatch('save', code);
- setTimeout(() => {
- saved = false;
- }, 1000);
- };
- const copyCode = async () => {
- copied = true;
- await copyToClipboard(code);
- setTimeout(() => {
- copied = false;
- }, 1000);
- };
- const checkPythonCode = (str) => {
- // Check if the string contains typical Python syntax characters
- const pythonSyntax = [
- 'def ',
- 'else:',
- 'elif ',
- 'try:',
- 'except:',
- 'finally:',
- 'yield ',
- 'lambda ',
- 'assert ',
- 'nonlocal ',
- 'del ',
- 'True',
- 'False',
- 'None',
- ' and ',
- ' or ',
- ' not ',
- ' in ',
- ' is ',
- ' with '
- ];
- for (let syntax of pythonSyntax) {
- if (str.includes(syntax)) {
- return true;
- }
- }
- // If none of the above conditions met, it's probably not Python code
- return false;
- };
- const executePython = async (code) => {
- if (!code.includes('input') && !code.includes('matplotlib')) {
- executePythonAsWorker(code);
- } else {
- result = null;
- stdout = null;
- stderr = null;
- executing = true;
- document.pyodideMplTarget = document.getElementById(`plt-canvas-${id}`);
- let pyodide = await loadPyodide({
- indexURL: '/pyodide/',
- stdout: (text) => {
- console.log('Python output:', text);
- if (stdout) {
- stdout += `${text}\n`;
- } else {
- stdout = `${text}\n`;
- }
- },
- stderr: (text) => {
- console.log('An error occured:', text);
- if (stderr) {
- stderr += `${text}\n`;
- } else {
- stderr = `${text}\n`;
- }
- },
- packages: ['micropip']
- });
- try {
- const micropip = pyodide.pyimport('micropip');
- // await micropip.set_index_urls('https://pypi.org/pypi/{package_name}/json');
- let packages = [
- code.includes('requests') ? 'requests' : null,
- code.includes('bs4') ? 'beautifulsoup4' : null,
- code.includes('numpy') ? 'numpy' : null,
- code.includes('pandas') ? 'pandas' : null,
- code.includes('matplotlib') ? 'matplotlib' : null,
- code.includes('sklearn') ? 'scikit-learn' : null,
- code.includes('scipy') ? 'scipy' : null,
- code.includes('re') ? 'regex' : null,
- code.includes('seaborn') ? 'seaborn' : null
- ].filter(Boolean);
- console.log(packages);
- await micropip.install(packages);
- result = await pyodide.runPythonAsync(`from js import prompt
- def input(p):
- return prompt(p)
- __builtins__.input = input`);
- result = await pyodide.runPython(code);
- if (!result) {
- result = '[NO OUTPUT]';
- }
- console.log(result);
- console.log(stdout);
- console.log(stderr);
- const pltCanvasElement = document.getElementById(`plt-canvas-${id}`);
- if (pltCanvasElement?.innerHTML !== '') {
- pltCanvasElement.classList.add('pt-4');
- }
- } catch (error) {
- console.error('Error:', error);
- stderr = error;
- }
- executing = false;
- }
- };
- const executePythonAsWorker = async (code) => {
- result = null;
- stdout = null;
- stderr = null;
- executing = true;
- let packages = [
- code.includes('requests') ? 'requests' : null,
- code.includes('bs4') ? 'beautifulsoup4' : null,
- code.includes('numpy') ? 'numpy' : null,
- code.includes('pandas') ? 'pandas' : null,
- code.includes('sklearn') ? 'scikit-learn' : null,
- code.includes('scipy') ? 'scipy' : null,
- code.includes('re') ? 'regex' : null,
- code.includes('seaborn') ? 'seaborn' : null
- ].filter(Boolean);
- console.log(packages);
- const pyodideWorker = new PyodideWorker();
- pyodideWorker.postMessage({
- id: id,
- code: code,
- packages: packages
- });
- setTimeout(() => {
- if (executing) {
- executing = false;
- stderr = 'Execution Time Limit Exceeded';
- pyodideWorker.terminate();
- }
- }, 60000);
- pyodideWorker.onmessage = (event) => {
- console.log('pyodideWorker.onmessage', event);
- const { id, ...data } = event.data;
- console.log(id, data);
- data['stdout'] && (stdout = data['stdout']);
- data['stderr'] && (stderr = data['stderr']);
- data['result'] && (result = data['result']);
- executing = false;
- };
- pyodideWorker.onerror = (event) => {
- console.log('pyodideWorker.onerror', event);
- executing = false;
- };
- };
- let debounceTimeout;
- const drawMermaidDiagram = async () => {
- try {
- if (await mermaid.parse(code)) {
- const { svg } = await mermaid.render(`mermaid-${uuidv4()}`, code);
- mermaidHtml = svg;
- }
- } catch (error) {
- console.log('Error:', error);
- }
- };
- const render = async () => {
- if (lang === 'mermaid' && (token?.raw ?? '').slice(-4).includes('```')) {
- (async () => {
- await drawMermaidDiagram();
- })();
- }
- };
- $: if (token) {
- if (JSON.stringify(token) !== JSON.stringify(_token)) {
- _token = token;
- }
- }
- $: if (_token) {
- render();
- }
- onMount(async () => {
- console.log('codeblock', lang, code);
- if (document.documentElement.classList.contains('dark')) {
- mermaid.initialize({
- startOnLoad: true,
- theme: 'dark',
- securityLevel: 'loose'
- });
- } else {
- mermaid.initialize({
- startOnLoad: true,
- theme: 'default',
- securityLevel: 'loose'
- });
- }
- });
- </script>
- <div class="my-2" dir="ltr">
- {#if lang === 'mermaid'}
- {#if mermaidHtml}
- {@html `${mermaidHtml}`}
- {:else}
- <pre class="mermaid">{code}</pre>
- {/if}
- {:else}
- <div
- class="flex justify-between bg-[#202123] text-white text-xs px-4 pt-1 pb-0.5 rounded-t-lg overflow-x-auto"
- >
- <div class="p-1">{lang}</div>
- <div class="flex items-center">
- {#if lang.toLowerCase() === 'python' || lang.toLowerCase() === 'py' || (lang === '' && checkPythonCode(code))}
- {#if executing}
- <div class="copy-code-button bg-none border-none p-1 cursor-not-allowed">Running</div>
- {:else}
- <button
- class="copy-code-button bg-none border-none p-1"
- on:click={async () => {
- code = _code;
- await tick();
- executePython(code);
- }}>{$i18n.t('Run')}</button
- >
- {/if}
- {/if}
- <button class="copy-code-button bg-none border-none p-1" on:click={saveCode}>
- {saved ? $i18n.t('Saved') : $i18n.t('Save')}
- </button>
- <button class="copy-code-button bg-none border-none p-1" on:click={copyCode}
- >{copied ? $i18n.t('Copied') : $i18n.t('Copy')}</button
- >
- </div>
- </div>
- <div
- class="language-{lang} rounded-t-none {executing || stdout || stderr || result
- ? ''
- : 'rounded-b-lg'} overflow-hidden"
- >
- <CodeEditor
- value={code}
- {id}
- {lang}
- on:save={() => {
- saveCode();
- }}
- on:change={(e) => {
- _code = e.detail.value;
- }}
- />
- </div>
- <!-- <pre
- class=" hljs p-4 px-5 overflow-x-auto"
- style="border-top-left-radius: 0px; border-top-right-radius: 0px; {(executing ||
- stdout ||
- stderr ||
- result) &&
- 'border-bottom-left-radius: 0px; border-bottom-right-radius: 0px;'}"><code></code></pre> -->
- <div
- id="plt-canvas-{id}"
- class="bg-[#202123] text-white max-w-full overflow-x-auto scrollbar-hidden"
- />
- {#if executing}
- <div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
- <div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
- <div class="text-sm">Running...</div>
- </div>
- {:else if stdout || stderr || result}
- <div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
- <div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
- <div class="text-sm">{stdout || stderr || result}</div>
- </div>
- {/if}
- {/if}
- </div>
|