CodeBlock.svelte 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  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 '../../../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 keywords, syntax, or functions
  26. const pythonKeywords = [
  27. 'def',
  28. 'class',
  29. 'import',
  30. 'from',
  31. 'if',
  32. 'else',
  33. 'elif',
  34. 'for',
  35. 'while',
  36. 'try',
  37. 'except',
  38. 'finally',
  39. 'return',
  40. 'yield',
  41. 'lambda',
  42. 'assert',
  43. 'pass',
  44. 'break',
  45. 'continue',
  46. 'global',
  47. 'nonlocal',
  48. 'del',
  49. 'True',
  50. 'False',
  51. 'None',
  52. 'and',
  53. 'or',
  54. 'not',
  55. 'in',
  56. 'is',
  57. 'as',
  58. 'with'
  59. ];
  60. for (let keyword of pythonKeywords) {
  61. if (str.includes(keyword)) {
  62. return true;
  63. }
  64. }
  65. // Check if the string contains typical Python syntax characters
  66. const pythonSyntax = [
  67. 'def ',
  68. 'class ',
  69. 'import ',
  70. 'from ',
  71. 'if ',
  72. 'else:',
  73. 'elif ',
  74. 'for ',
  75. 'while ',
  76. 'try:',
  77. 'except:',
  78. 'finally:',
  79. 'return ',
  80. 'yield ',
  81. 'lambda ',
  82. 'assert ',
  83. 'pass',
  84. 'break',
  85. 'continue',
  86. 'global ',
  87. 'nonlocal ',
  88. 'del ',
  89. 'True',
  90. 'False',
  91. 'None',
  92. ' and ',
  93. ' or ',
  94. ' not ',
  95. ' in ',
  96. ' is ',
  97. ' as ',
  98. ' with ',
  99. ':',
  100. '=',
  101. '==',
  102. '!=',
  103. '>',
  104. '<',
  105. '>=',
  106. '<=',
  107. '+',
  108. '-',
  109. '*',
  110. '/',
  111. '%',
  112. '**',
  113. '//',
  114. '(',
  115. ')',
  116. '[',
  117. ']',
  118. '{',
  119. '}'
  120. ];
  121. for (let syntax of pythonSyntax) {
  122. if (str.includes(syntax)) {
  123. return true;
  124. }
  125. }
  126. // If none of the above conditions met, it's probably not Python code
  127. return false;
  128. };
  129. const executePython = async (code) => {
  130. if (!code.includes('input') && !code.includes('matplotlib')) {
  131. executePythonAsWorker(code);
  132. } else {
  133. result = null;
  134. stdout = null;
  135. stderr = null;
  136. executing = true;
  137. document.pyodideMplTarget = document.getElementById(`plt-canvas-${id}`);
  138. let pyodide = await loadPyodide({
  139. indexURL: '/pyodide/',
  140. stdout: (text) => {
  141. console.log('Python output:', text);
  142. if (stdout) {
  143. stdout += `${text}\n`;
  144. } else {
  145. stdout = `${text}\n`;
  146. }
  147. },
  148. stderr: (text) => {
  149. console.log('An error occured:', text);
  150. if (stderr) {
  151. stderr += `${text}\n`;
  152. } else {
  153. stderr = `${text}\n`;
  154. }
  155. },
  156. packages: ['micropip']
  157. });
  158. try {
  159. const micropip = pyodide.pyimport('micropip');
  160. await micropip.set_index_urls('https://pypi.org/pypi/{package_name}/json');
  161. let packages = [
  162. code.includes('requests') ? 'requests' : null,
  163. code.includes('bs4') ? 'beautifulsoup4' : null,
  164. code.includes('numpy') ? 'numpy' : null,
  165. code.includes('pandas') ? 'pandas' : null,
  166. code.includes('matplotlib') ? 'matplotlib' : null,
  167. code.includes('sklearn') ? 'scikit-learn' : null,
  168. code.includes('scipy') ? 'scipy' : null,
  169. code.includes('re') ? 'regex' : null
  170. ].filter(Boolean);
  171. console.log(packages);
  172. await micropip.install(packages);
  173. result = await pyodide.runPythonAsync(`from js import prompt
  174. def input(p):
  175. return prompt(p)
  176. __builtins__.input = input`);
  177. result = await pyodide.runPython(code);
  178. if (!result) {
  179. result = '[NO OUTPUT]';
  180. }
  181. console.log(result);
  182. console.log(stdout);
  183. console.log(stderr);
  184. const pltCanvasElement = document.getElementById(`plt-canvas-${id}`);
  185. if (pltCanvasElement?.innerHTML !== '') {
  186. pltCanvasElement.classList.add('pt-4');
  187. }
  188. } catch (error) {
  189. console.error('Error:', error);
  190. stderr = error;
  191. }
  192. executing = false;
  193. }
  194. };
  195. const executePythonAsWorker = async (code) => {
  196. result = null;
  197. stdout = null;
  198. stderr = null;
  199. executing = true;
  200. let packages = [
  201. code.includes('requests') ? 'requests' : null,
  202. code.includes('bs4') ? 'beautifulsoup4' : null,
  203. code.includes('numpy') ? 'numpy' : null,
  204. code.includes('pandas') ? 'pandas' : null,
  205. code.includes('sklearn') ? 'scikit-learn' : null,
  206. code.includes('scipy') ? 'scipy' : null,
  207. code.includes('re') ? 'regex' : null
  208. ].filter(Boolean);
  209. console.log(packages);
  210. const pyodideWorker = new PyodideWorker();
  211. pyodideWorker.postMessage({
  212. id: id,
  213. code: code,
  214. packages: packages
  215. });
  216. setTimeout(() => {
  217. if (executing) {
  218. executing = false;
  219. stderr = 'Execution Time Limit Exceeded';
  220. pyodideWorker.terminate();
  221. }
  222. }, 60000);
  223. pyodideWorker.onmessage = (event) => {
  224. console.log('pyodideWorker.onmessage', event);
  225. const { id, ...data } = event.data;
  226. console.log(id, data);
  227. data['stdout'] && (stdout = data['stdout']);
  228. data['stderr'] && (stderr = data['stderr']);
  229. data['result'] && (result = data['result']);
  230. executing = false;
  231. };
  232. pyodideWorker.onerror = (event) => {
  233. console.log('pyodideWorker.onerror', event);
  234. executing = false;
  235. };
  236. };
  237. $: highlightedCode = code ? hljs.highlightAuto(code, hljs.getLanguage(lang)?.aliases).value : '';
  238. </script>
  239. {#if code}
  240. <div class="mb-4" dir="ltr">
  241. <div
  242. class="flex justify-between bg-[#202123] text-white text-xs px-4 pt-1 pb-0.5 rounded-t-lg overflow-x-auto"
  243. >
  244. <div class="p-1">{@html lang}</div>
  245. <div class="flex items-center">
  246. {#if ['', 'python'].includes(lang) && (lang === 'python' || checkPythonCode(code))}
  247. {#if executing}
  248. <div class="copy-code-button bg-none border-none p-1 cursor-not-allowed">Running</div>
  249. {:else}
  250. <button
  251. class="copy-code-button bg-none border-none p-1"
  252. on:click={() => {
  253. executePython(code);
  254. }}>Run</button
  255. >
  256. {/if}
  257. {/if}
  258. <button class="copy-code-button bg-none border-none p-1" on:click={copyCode}
  259. >{copied ? 'Copied' : 'Copy Code'}</button
  260. >
  261. </div>
  262. </div>
  263. <pre
  264. class=" hljs p-4 px-5 overflow-x-auto"
  265. style="border-top-left-radius: 0px; border-top-right-radius: 0px; {(executing ||
  266. stdout ||
  267. stderr ||
  268. result) &&
  269. 'border-bottom-left-radius: 0px; border-bottom-right-radius: 0px;'}"><code
  270. class="language-{lang} rounded-t-none whitespace-pre">{@html highlightedCode || code}</code
  271. ></pre>
  272. <div
  273. id="plt-canvas-{id}"
  274. class="bg-[#202123] text-white max-w-full overflow-x-auto scrollbar-hidden"
  275. />
  276. {#if executing}
  277. <div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
  278. <div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
  279. <div class="text-sm">Running...</div>
  280. </div>
  281. {:else if stdout || stderr || result}
  282. <div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
  283. <div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
  284. <div class="text-sm">{stdout || stderr || result}</div>
  285. </div>
  286. {/if}
  287. </div>
  288. {/if}