CodeBlock.svelte 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. <script lang="ts">
  2. import Spinner from '$lib/components/common/Spinner.svelte';
  3. import { copyToClipboard } from '$lib/utils';
  4. import hljs from 'highlight.js';
  5. import 'highlight.js/styles/github-dark.min.css';
  6. import { loadPyodide } from 'pyodide';
  7. import { tick } from 'svelte';
  8. import PyodideWorker from '$lib/workers/pyodide.worker?worker';
  9. export let id = '';
  10. export let lang = '';
  11. export let code = '';
  12. let executing = false;
  13. let stdout = null;
  14. let stderr = null;
  15. let result = null;
  16. let copied = false;
  17. const copyCode = async () => {
  18. copied = true;
  19. await copyToClipboard(code);
  20. setTimeout(() => {
  21. copied = false;
  22. }, 1000);
  23. };
  24. const checkPythonCode = (str) => {
  25. // Check if the string contains typical Python syntax characters
  26. const pythonSyntax = [
  27. 'def ',
  28. 'else:',
  29. 'elif ',
  30. 'try:',
  31. 'except:',
  32. 'finally:',
  33. 'yield ',
  34. 'lambda ',
  35. 'assert ',
  36. 'nonlocal ',
  37. 'del ',
  38. 'True',
  39. 'False',
  40. 'None',
  41. ' and ',
  42. ' or ',
  43. ' not ',
  44. ' in ',
  45. ' is ',
  46. ' with '
  47. ];
  48. for (let syntax of pythonSyntax) {
  49. if (str.includes(syntax)) {
  50. return true;
  51. }
  52. }
  53. // If none of the above conditions met, it's probably not Python code
  54. return false;
  55. };
  56. const executePython = async (code) => {
  57. if (!code.includes('input') && !code.includes('matplotlib')) {
  58. executePythonAsWorker(code);
  59. } else {
  60. result = null;
  61. stdout = null;
  62. stderr = null;
  63. executing = true;
  64. document.pyodideMplTarget = document.getElementById(`plt-canvas-${id}`);
  65. let pyodide = await loadPyodide({
  66. indexURL: '/pyodide/',
  67. stdout: (text) => {
  68. console.log('Python output:', text);
  69. if (stdout) {
  70. stdout += `${text}\n`;
  71. } else {
  72. stdout = `${text}\n`;
  73. }
  74. },
  75. stderr: (text) => {
  76. console.log('An error occured:', text);
  77. if (stderr) {
  78. stderr += `${text}\n`;
  79. } else {
  80. stderr = `${text}\n`;
  81. }
  82. },
  83. packages: ['micropip']
  84. });
  85. try {
  86. const micropip = pyodide.pyimport('micropip');
  87. // await micropip.set_index_urls('https://pypi.org/pypi/{package_name}/json');
  88. let packages = [
  89. code.includes('requests') ? 'requests' : null,
  90. code.includes('bs4') ? 'beautifulsoup4' : null,
  91. code.includes('numpy') ? 'numpy' : null,
  92. code.includes('pandas') ? 'pandas' : null,
  93. code.includes('matplotlib') ? 'matplotlib' : null,
  94. code.includes('sklearn') ? 'scikit-learn' : null,
  95. code.includes('scipy') ? 'scipy' : null,
  96. code.includes('re') ? 'regex' : null,
  97. code.includes('seaborn') ? 'seaborn' : null
  98. ].filter(Boolean);
  99. console.log(packages);
  100. await micropip.install(packages);
  101. result = await pyodide.runPythonAsync(`from js import prompt
  102. def input(p):
  103. return prompt(p)
  104. __builtins__.input = input`);
  105. result = await pyodide.runPython(code);
  106. if (!result) {
  107. result = '[NO OUTPUT]';
  108. }
  109. console.log(result);
  110. console.log(stdout);
  111. console.log(stderr);
  112. const pltCanvasElement = document.getElementById(`plt-canvas-${id}`);
  113. if (pltCanvasElement?.innerHTML !== '') {
  114. pltCanvasElement.classList.add('pt-4');
  115. }
  116. } catch (error) {
  117. console.error('Error:', error);
  118. stderr = error;
  119. }
  120. executing = false;
  121. }
  122. };
  123. const executePythonAsWorker = async (code) => {
  124. result = null;
  125. stdout = null;
  126. stderr = null;
  127. executing = true;
  128. let packages = [
  129. code.includes('requests') ? 'requests' : null,
  130. code.includes('bs4') ? 'beautifulsoup4' : null,
  131. code.includes('numpy') ? 'numpy' : null,
  132. code.includes('pandas') ? 'pandas' : null,
  133. code.includes('sklearn') ? 'scikit-learn' : null,
  134. code.includes('scipy') ? 'scipy' : null,
  135. code.includes('re') ? 'regex' : null,
  136. code.includes('seaborn') ? 'seaborn' : null
  137. ].filter(Boolean);
  138. console.log(packages);
  139. const pyodideWorker = new PyodideWorker();
  140. pyodideWorker.postMessage({
  141. id: id,
  142. code: code,
  143. packages: packages
  144. });
  145. setTimeout(() => {
  146. if (executing) {
  147. executing = false;
  148. stderr = 'Execution Time Limit Exceeded';
  149. pyodideWorker.terminate();
  150. }
  151. }, 60000);
  152. pyodideWorker.onmessage = (event) => {
  153. console.log('pyodideWorker.onmessage', event);
  154. const { id, ...data } = event.data;
  155. console.log(id, data);
  156. data['stdout'] && (stdout = data['stdout']);
  157. data['stderr'] && (stderr = data['stderr']);
  158. data['result'] && (result = data['result']);
  159. executing = false;
  160. };
  161. pyodideWorker.onerror = (event) => {
  162. console.log('pyodideWorker.onerror', event);
  163. executing = false;
  164. };
  165. };
  166. $: highlightedCode = code ? hljs.highlightAuto(code, hljs.getLanguage(lang)?.aliases).value : '';
  167. </script>
  168. {#if code}
  169. <div class="mb-4" dir="ltr">
  170. <div
  171. class="flex justify-between bg-[#202123] text-white text-xs px-4 pt-1 pb-0.5 rounded-t-lg overflow-x-auto"
  172. >
  173. <div class="p-1">{@html lang}</div>
  174. <div class="flex items-center">
  175. {#if lang === 'python' || (lang === '' && checkPythonCode(code))}
  176. {#if executing}
  177. <div class="copy-code-button bg-none border-none p-1 cursor-not-allowed">Running</div>
  178. {:else}
  179. <button
  180. class="copy-code-button bg-none border-none p-1"
  181. on:click={() => {
  182. executePython(code);
  183. }}>Run</button
  184. >
  185. {/if}
  186. {/if}
  187. <button class="copy-code-button bg-none border-none p-1" on:click={copyCode}
  188. >{copied ? 'Copied' : 'Copy Code'}</button
  189. >
  190. </div>
  191. </div>
  192. <pre
  193. class=" hljs p-4 px-5 overflow-x-auto"
  194. style="border-top-left-radius: 0px; border-top-right-radius: 0px; {(executing ||
  195. stdout ||
  196. stderr ||
  197. result) &&
  198. 'border-bottom-left-radius: 0px; border-bottom-right-radius: 0px;'}"><code
  199. class="language-{lang} rounded-t-none whitespace-pre">{@html highlightedCode || code}</code
  200. ></pre>
  201. <div
  202. id="plt-canvas-{id}"
  203. class="bg-[#202123] text-white max-w-full overflow-x-auto scrollbar-hidden"
  204. />
  205. {#if executing}
  206. <div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
  207. <div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
  208. <div class="text-sm">Running...</div>
  209. </div>
  210. {:else if stdout || stderr || result}
  211. <div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
  212. <div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
  213. <div class="text-sm">{stdout || stderr || result}</div>
  214. </div>
  215. {/if}
  216. </div>
  217. {/if}