mirror of
https://github.com/open-webui/open-webui
synced 2024-11-26 14:02:29 +00:00
1 line
16 KiB
Plaintext
1 line
16 KiB
Plaintext
{"version":3,"file":"py-terminal-CgcHH2nx.js","sources":["../src/plugins/py-terminal.js"],"sourcesContent":["// PyScript py-terminal plugin\nimport { TYPES, hooks } from \"../core.js\";\nimport { notify } from \"./error.js\";\nimport { customObserver, defineProperties } from \"polyscript/exports\";\n\n// will contain all valid selectors\nconst SELECTORS = [];\n\n// show the error on main and\n// stops the module from keep executing\nconst notifyAndThrow = (message) => {\n notify(message);\n throw new Error(message);\n};\n\nconst onceOnMain = ({ attributes: { worker } }) => !worker;\n\nconst bootstrapped = new WeakSet();\n\nlet addStyle = true;\n\n// this callback will be serialized as string and it never needs\n// to be invoked multiple times. Each xworker here is bootstrapped\n// only once thanks to the `sync.is_pyterminal()` check.\nconst workerReady = ({ interpreter, io, run, type }, { sync }) => {\n if (!sync.is_pyterminal()) return;\n\n // in workers it's always safe to grab the polyscript currentScript\n // the ugly `_` dance is due MicroPython not able to import via:\n // `from polyscript.currentScript import terminal as __terminal__`\n run(\n \"from polyscript import currentScript as _; __terminal__ = _.terminal; del _\",\n );\n\n let data = \"\";\n const { pyterminal_read, pyterminal_write } = sync;\n const decoder = new TextDecoder();\n const generic = {\n isatty: false,\n write(buffer) {\n data = decoder.decode(buffer);\n pyterminal_write(data);\n return buffer.length;\n },\n };\n\n // This part works already in both Pyodide and MicroPython\n io.stderr = (error) => {\n pyterminal_write(String(error.message || error));\n };\n\n // MicroPython has no code or code.interact()\n // This part patches it in a way that simulates\n // the code.interact() module in Pyodide.\n if (type === \"mpy\") {\n // monkey patch global input otherwise broken in MicroPython\n interpreter.registerJsModule(\"_pyscript_input\", {\n input: pyterminal_read,\n });\n run(\"from _pyscript_input import input\");\n\n // this is needed to avoid truncated unicode in MicroPython\n // the reason is that `linebuffer` false just send one byte\n // per time and readline here doesn't like it much.\n // MicroPython also has issues with code-points and\n // replProcessChar(byte) but that function accepts only\n // one byte per time so ... we have an issue!\n // @see https://github.com/pyscript/pyscript/pull/2018\n // @see https://github.com/WebReflection/buffer-points\n const bufferPoints = (stdio) => {\n const bytes = [];\n let needed = 0;\n return (buffer) => {\n let written = 0;\n for (const byte of buffer) {\n bytes.push(byte);\n // @see https://encoding.spec.whatwg.org/#utf-8-bytes-needed\n if (needed) needed--;\n else if (0xc2 <= byte && byte <= 0xdf) needed = 1;\n else if (0xe0 <= byte && byte <= 0xef) needed = 2;\n else if (0xf0 <= byte && byte <= 0xf4) needed = 3;\n if (!needed) {\n written += bytes.length;\n stdio(new Uint8Array(bytes.splice(0)));\n }\n }\n return written;\n };\n };\n\n io.stdout = bufferPoints(generic.write);\n\n // tiny shim of the code module with only interact\n // to bootstrap a REPL like environment\n interpreter.registerJsModule(\"code\", {\n interact() {\n let input = \"\";\n let length = 1;\n\n const encoder = new TextEncoder();\n const acc = [];\n const handlePoints = bufferPoints((buffer) => {\n acc.push(...buffer);\n pyterminal_write(decoder.decode(buffer));\n });\n\n // avoid duplicating the output produced by the input\n io.stdout = (buffer) =>\n length++ > input.length ? handlePoints(buffer) : 0;\n\n interpreter.replInit();\n\n // loop forever waiting for user inputs\n (function repl() {\n const out = decoder.decode(new Uint8Array(acc.splice(0)));\n // print in current line only the last line produced by the REPL\n const data = `${pyterminal_read(out.split(\"\\n\").at(-1))}\\r`;\n length = 0;\n input = encoder.encode(data);\n for (const c of input) interpreter.replProcessChar(c);\n repl();\n })();\n },\n });\n } else {\n interpreter.setStdout(generic);\n interpreter.setStderr(generic);\n interpreter.setStdin({\n isatty: false,\n stdin: () => pyterminal_read(data),\n });\n }\n};\n\nconst pyTerminal = async (element) => {\n // lazy load these only when a valid terminal is found\n const [{ Terminal }, { Readline }, { FitAddon }, { WebLinksAddon }] =\n await Promise.all([\n import(/* webpackIgnore: true */ \"../3rd-party/xterm.js\"),\n import(/* webpackIgnore: true */ \"../3rd-party/xterm-readline.js\"),\n import(/* webpackIgnore: true */ \"../3rd-party/xterm_addon-fit.js\"),\n import(\n /* webpackIgnore: true */ \"../3rd-party/xterm_addon-web-links.js\"\n ),\n ]);\n\n const readline = new Readline();\n\n // common main thread initialization for both worker\n // or main case, bootstrapping the terminal on its target\n const init = (options) => {\n let target = element;\n const selector = element.getAttribute(\"target\");\n if (selector) {\n target =\n document.getElementById(selector) ||\n document.querySelector(selector);\n if (!target) throw new Error(`Unknown target ${selector}`);\n } else {\n target = document.createElement(\"py-terminal\");\n target.style.display = \"block\";\n element.after(target);\n }\n const terminal = new Terminal({\n theme: {\n background: \"#191A19\",\n foreground: \"#F5F2E7\",\n },\n ...options,\n });\n const fitAddon = new FitAddon();\n terminal.loadAddon(fitAddon);\n terminal.loadAddon(readline);\n terminal.loadAddon(new WebLinksAddon());\n terminal.open(target);\n fitAddon.fit();\n terminal.focus();\n defineProperties(element, {\n terminal: { value: terminal },\n process: {\n value: async (code) => {\n // this loop is the only way I could find to actually simulate\n // the user input char after char in a way that works in both\n // MicroPython and Pyodide\n for (const line of code.split(/(?:\\r|\\n|\\r\\n)/)) {\n terminal.paste(`${line}\\n`);\n do {\n await new Promise((resolve) =>\n setTimeout(resolve, 0),\n );\n } while (!readline.activeRead?.resolve);\n readline.activeRead.resolve(line);\n }\n },\n },\n });\n return terminal;\n };\n\n // branch logic for the worker\n if (element.hasAttribute(\"worker\")) {\n // add a hook on the main thread to setup all sync helpers\n // also bootstrapping the XTerm target on main *BUT* ...\n hooks.main.onWorker.add(function worker(_, xworker) {\n // ... as multiple workers will add multiple callbacks\n // be sure no xworker is ever initialized twice!\n if (bootstrapped.has(xworker)) return;\n bootstrapped.add(xworker);\n\n // still cleanup this callback for future scripts/workers\n hooks.main.onWorker.delete(worker);\n\n init({\n disableStdin: false,\n cursorBlink: true,\n cursorStyle: \"block\",\n });\n\n xworker.sync.is_pyterminal = () => true;\n xworker.sync.pyterminal_read = readline.read.bind(readline);\n xworker.sync.pyterminal_write = readline.write.bind(readline);\n });\n\n // setup remote thread JS/Python code for whenever the\n // worker is ready to become a terminal\n hooks.worker.onReady.add(workerReady);\n } else {\n // in the main case, just bootstrap XTerm without\n // allowing any input as that's not possible / awkward\n hooks.main.onReady.add(function main({ interpreter, io, run, type }) {\n console.warn(\"py-terminal is read only on main thread\");\n hooks.main.onReady.delete(main);\n\n // on main, it's easy to trash and clean the current terminal\n globalThis.__py_terminal__ = init({\n disableStdin: true,\n cursorBlink: false,\n cursorStyle: \"underline\",\n });\n run(\"from js import __py_terminal__ as __terminal__\");\n delete globalThis.__py_terminal__;\n\n io.stderr = (error) => {\n readline.write(String(error.message || error));\n };\n\n if (type === \"mpy\") {\n interpreter.setStdin = Object; // as no-op\n interpreter.setStderr = Object; // as no-op\n interpreter.setStdout = ({ write }) => {\n io.stdout = write;\n };\n }\n\n let data = \"\";\n const decoder = new TextDecoder();\n const generic = {\n isatty: false,\n write(buffer) {\n data = decoder.decode(buffer);\n readline.write(data);\n return buffer.length;\n },\n };\n interpreter.setStdout(generic);\n interpreter.setStderr(generic);\n interpreter.setStdin({\n isatty: false,\n stdin: () => readline.read(data),\n });\n });\n }\n};\n\nfor (const key of TYPES.keys()) {\n const selector = `script[type=\"${key}\"][terminal],${key}-script[terminal]`;\n SELECTORS.push(selector);\n customObserver.set(selector, async (element) => {\n // we currently support only one terminal on main as in \"classic\"\n const terminals = document.querySelectorAll(SELECTORS.join(\",\"));\n if ([].filter.call(terminals, onceOnMain).length > 1)\n notifyAndThrow(\"You can use at most 1 main terminal\");\n\n // import styles lazily\n if (addStyle) {\n addStyle = false;\n document.head.append(\n Object.assign(document.createElement(\"link\"), {\n rel: \"stylesheet\",\n href: new URL(\"./xterm.css\", import.meta.url),\n }),\n );\n }\n\n await pyTerminal(element);\n });\n}\n"],"names":["SELECTORS","notifyAndThrow","message","notify","Error","onceOnMain","attributes","worker","bootstrapped","WeakSet","addStyle","workerReady","interpreter","io","run","type","sync","is_pyterminal","data","pyterminal_read","pyterminal_write","decoder","TextDecoder","generic","isatty","write","buffer","decode","length","stderr","error","String","registerJsModule","input","bufferPoints","stdio","bytes","needed","written","byte","push","Uint8Array","splice","stdout","interact","encoder","TextEncoder","acc","handlePoints","replInit","repl","out","split","at","encode","c","replProcessChar","setStdout","setStderr","setStdin","stdin","pyTerminal","async","element","Terminal","Readline","FitAddon","WebLinksAddon","Promise","all","import","readline","init","options","target","selector","getAttribute","document","getElementById","querySelector","createElement","style","display","after","terminal","theme","background","foreground","fitAddon","loadAddon","open","fit","focus","defineProperties","value","process","code","line","paste","resolve","setTimeout","activeRead","hasAttribute","hooks","main","onWorker","add","_","xworker","has","delete","disableStdin","cursorBlink","cursorStyle","read","bind","onReady","console","warn","globalThis","__py_terminal__","Object","key","TYPES","keys","customObserver","set","terminals","querySelectorAll","join","filter","call","head","append","assign","rel","href","URL","url"],"mappings":"yGAMA,MAAMA,EAAY,GAIZC,EAAkBC,IAEpB,MADAC,EAAOD,GACD,IAAIE,MAAMF,EAAQ,EAGtBG,EAAa,EAAGC,YAAcC,cAAgBA,EAE9CC,EAAe,IAAIC,QAEzB,IAAIC,GAAW,EAKf,MAAMC,EAAc,EAAGC,cAAaC,KAAIC,MAAKC,SAAUC,WACnD,IAAKA,EAAKC,gBAAiB,OAK3BH,EACI,+EAGJ,IAAII,EAAO,GACX,MAAMC,gBAAEA,EAAeC,iBAAEA,GAAqBJ,EACxCK,EAAU,IAAIC,YACdC,EAAU,CACZC,QAAQ,EACRC,MAAMC,IACFR,EAAOG,EAAQM,OAAOD,GACtBN,EAAiBF,GACVQ,EAAOE,SAYtB,GAPAf,EAAGgB,OAAUC,IACTV,EAAiBW,OAAOD,EAAM5B,SAAW4B,GAAO,EAMvC,QAATf,EAAgB,CAEhBH,EAAYoB,iBAAiB,kBAAmB,CAC5CC,MAAOd,IAEXL,EAAI,qCAUJ,MAAMoB,EAAgBC,IAClB,MAAMC,EAAQ,GACd,IAAIC,EAAS,EACb,OAAQX,IACJ,IAAIY,EAAU,EACd,IAAK,MAAMC,KAAQb,EACfU,EAAMI,KAAKD,GAEPF,EAAQA,IACH,KAAQE,GAAQA,GAAQ,IAAMF,EAAS,EACvC,KAAQE,GAAQA,GAAQ,IAAMF,EAAS,EACvC,KAAQE,GAAQA,GAAQ,MAAMF,EAAS,GAC3CA,IACDC,GAAWF,EAAMR,OACjBO,EAAM,IAAIM,WAAWL,EAAMM,OAAO,MAG1C,OAAOJ,CAAO,CACjB,EAGLzB,EAAG8B,OAAST,EAAaX,EAAQE,OAIjCb,EAAYoB,iBAAiB,OAAQ,CACjC,QAAAY,GACI,IAAIX,EAAQ,GACRL,EAAS,EAEb,MAAMiB,EAAU,IAAIC,YACdC,EAAM,GACNC,EAAed,GAAcR,IAC/BqB,EAAIP,QAAQd,GACZN,EAAiBC,EAAQM,OAAOD,GAAQ,IAI5Cb,EAAG8B,OAAUjB,GACTE,IAAWK,EAAML,OAASoB,EAAatB,GAAU,EAErDd,EAAYqC,WAGZ,SAAUC,IACN,MAAMC,EAAM9B,EAAQM,OAAO,IAAIc,WAAWM,EAAIL,OAAO,KAE/CxB,EAAO,GAAGC,EAAgBgC,EAAIC,MAAM,MAAMC,IAAI,QACpDzB,EAAS,EACTK,EAAQY,EAAQS,OAAOpC,GACvB,IAAK,MAAMqC,KAAKtB,EAAOrB,EAAY4C,gBAAgBD,GACnDL,GACH,CARD,EASH,GAEb,MACQtC,EAAY6C,UAAUlC,GACtBX,EAAY8C,UAAUnC,GACtBX,EAAY+C,SAAS,CACjBnC,QAAQ,EACRoC,MAAO,IAAMzC,EAAgBD,IAEpC,EAGC2C,EAAaC,MAAOC,IAEtB,OAAOC,SAAEA,IAAYC,SAAEA,IAAYC,SAAEA,IAAYC,cAAEA,UACzCC,QAAQC,IAAI,CACdC,OAAiC,uBACjCA,OAAiC,gCACjCA,OAAiC,iCACjCA,OAC8B,yCAIhCC,EAAW,IAAIN,EAIfO,EAAQC,IACV,IAAIC,EAASX,EACb,MAAMY,EAAWZ,EAAQa,aAAa,UACtC,GAAID,GAIA,GAHAD,EACIG,SAASC,eAAeH,IACxBE,SAASE,cAAcJ,IACtBD,EAAQ,MAAM,IAAItE,MAAM,kBAAkBuE,UAE/CD,EAASG,SAASG,cAAc,eAChCN,EAAOO,MAAMC,QAAU,QACvBnB,EAAQoB,MAAMT,GAElB,MAAMU,EAAW,IAAIpB,EAAS,CAC1BqB,MAAO,CACHC,WAAY,UACZC,WAAY,cAEbd,IAEDe,EAAW,IAAItB,EA0BrB,OAzBAkB,EAASK,UAAUD,GACnBJ,EAASK,UAAUlB,GACnBa,EAASK,UAAU,IAAItB,GACvBiB,EAASM,KAAKhB,GACdc,EAASG,MACTP,EAASQ,QACTC,EAAiB9B,EAAS,CACtBqB,SAAU,CAAEU,MAAOV,GACnBW,QAAS,CACLD,MAAOhC,MAAOkC,IAIV,IAAK,MAAMC,KAAQD,EAAK5C,MAAM,kBAAmB,CAC7CgC,EAASc,MAAM,GAAGD,OAClB,SACU,IAAI7B,SAAS+B,GACfC,WAAWD,EAAS,YAElB5B,EAAS8B,YAAYF,SAC/B5B,EAAS8B,WAAWF,QAAQF,EAC/B,MAINb,CAAQ,EAIfrB,EAAQuC,aAAa,WAGrBC,EAAMC,KAAKC,SAASC,KAAI,SAASnG,EAAOoG,EAAGC,GAGnCpG,EAAaqG,IAAID,KACrBpG,EAAakG,IAAIE,GAGjBL,EAAMC,KAAKC,SAASK,OAAOvG,GAE3BiE,EAAK,CACDuC,cAAc,EACdC,aAAa,EACbC,YAAa,UAGjBL,EAAQ5F,KAAKC,cAAgB,KAAM,EACnC2F,EAAQ5F,KAAKG,gBAAkBoD,EAAS2C,KAAKC,KAAK5C,GAClDqC,EAAQ5F,KAAKI,iBAAmBmD,EAAS9C,MAAM0F,KAAK5C,GAChE,IAIQgC,EAAMhG,OAAO6G,QAAQV,IAAI/F,IAIzB4F,EAAMC,KAAKY,QAAQV,KAAI,SAASF,GAAK5F,YAAEA,EAAWC,GAAEA,EAAEC,IAAEA,EAAGC,KAAEA,IACzDsG,QAAQC,KAAK,2CACbf,EAAMC,KAAKY,QAAQN,OAAON,GAG1Be,WAAWC,gBAAkBhD,EAAK,CAC9BuC,cAAc,EACdC,aAAa,EACbC,YAAa,cAEjBnG,EAAI,yDACGyG,WAAWC,gBAElB3G,EAAGgB,OAAUC,IACTyC,EAAS9C,MAAMM,OAAOD,EAAM5B,SAAW4B,GAAO,EAGrC,QAATf,IACAH,EAAY+C,SAAW8D,OACvB7G,EAAY8C,UAAY+D,OACxB7G,EAAY6C,UAAY,EAAGhC,YACvBZ,EAAG8B,OAASlB,CAAK,GAIzB,IAAIP,EAAO,GACX,MAAMG,EAAU,IAAIC,YACdC,EAAU,CACZC,QAAQ,EACRC,MAAMC,IACFR,EAAOG,EAAQM,OAAOD,GACtB6C,EAAS9C,MAAMP,GACRQ,EAAOE,SAGtBhB,EAAY6C,UAAUlC,GACtBX,EAAY8C,UAAUnC,GACtBX,EAAY+C,SAAS,CACjBnC,QAAQ,EACRoC,MAAO,IAAMW,EAAS2C,KAAKhG,IAE3C,GACK,EAGL,IAAK,MAAMwG,KAAOC,EAAMC,OAAQ,CAC5B,MAAMjD,EAAW,gBAAgB+C,iBAAmBA,qBACpD1H,EAAUwC,KAAKmC,GACfkD,EAAeC,IAAInD,GAAUb,MAAOC,IAEhC,MAAMgE,EAAYlD,SAASmD,iBAAiBhI,EAAUiI,KAAK,MACvD,GAAGC,OAAOC,KAAKJ,EAAW1H,GAAYuB,OAAS,GAC/C3B,EAAe,uCAGfS,IACAA,GAAW,EACXmE,SAASuD,KAAKC,OACVZ,OAAOa,OAAOzD,SAASG,cAAc,QAAS,CAC1CuD,IAAK,aACLC,KAAM,IAAIC,IAAI,0BAA2BC,eAK/C7E,EAAWE,EAAQ,GAEjC"} |