CodeBlock.svelte 6.7 KB

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