Skip to content

Commit

Permalink
Optimize reuse of Pyodide object again
Browse files Browse the repository at this point in the history
  • Loading branch information
bclswl0827 committed Feb 5, 2024
1 parent c91c3df commit e5c533d
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 57 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "chatgemini",
"version": "0.5.4",
"version": "0.5.5",
"homepage": ".",
"private": true,
"dependencies": {
Expand Down
86 changes: 61 additions & 25 deletions src/components/Markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,34 @@ import userDebounce from "../helpers/userDebounce";
import { Point } from "unist";
import { isObjectEqual } from "../helpers/isObjectEqual";
import { getPythonResult } from "../helpers/getPythonResult";
import { loadPyodide } from "pyodide";
import { PyodideInterface } from "pyodide";
import { getPythonRuntime } from "../helpers/getPythonRuntime";

interface MarkdownProps {
readonly className?: string;
readonly typingEffect: string;
readonly pythonRuntime: PyodideInterface | null;
readonly onPythonRuntimeCreated: (pyodide: PyodideInterface) => void;
readonly children: string;
}

const TraceLog = "😈 [TRACE]";
const DebugLog = "🚀 [DEBUG]";
const ErrorLog = "🤬 [ERROR]";
const PythonScriptDisplayName = "script.py";
const RunnerResultPlaceholder = `
😈 [Info] 结果需以 print 输出
🚀 [Info] 尝试执行 Python 脚本...
`;
${DebugLog} 结果需调用 print 打印
${DebugLog} 尝试执行 Python 脚本...`;

export const Markdown = (props: MarkdownProps) => {
const { className, typingEffect, children } = props;
const {
className,
typingEffect,
pythonRuntime,
onPythonRuntimeCreated,
children,
} = props;

const [pythonRuntime, setPythonRuntime] = useState<ReturnType<
typeof loadPyodide
> | null>(null);
const [pythonResult, setPythonResult] = useState<{
result: string;
startPos: Point | null;
Expand All @@ -54,10 +62,34 @@ export const Markdown = (props: MarkdownProps) => {
);

const handleRunnerResult = (x: string) =>
setPythonResult((prev) => ({
...prev,
result: `${prev.result.replace(RunnerResultPlaceholder, "")}\n${x}`,
}));
setPythonResult((prev) => {
let result = prev.result.replace(RunnerResultPlaceholder, "");
if (result.includes(TraceLog)) {
result = result
.split("\n")
.filter((x) => !x.includes(TraceLog))
.join("\n");
}
return { ...prev, result: `${result}\n${x}` };
});

const handleRunnerImporting = (x: string, err: boolean) =>
setPythonResult((prev) => {
let { result } = prev;
if (err) {
result += `\n${ErrorLog} ${x}`;
} else {
result += `\n${TraceLog} ${x}`;
}
return { ...prev, result };
});

const handleJobFinished = () =>
setPythonResult((prev) => {
let { result } = prev;
result += `\n$`;
return { ...prev, result };
});

const handleRunPython = userDebounce(
async (
Expand All @@ -66,24 +98,28 @@ export const Markdown = (props: MarkdownProps) => {
code: string,
currentTarget: EventTarget
) => {
let runtime: ReturnType<typeof loadPyodide> | null;
if (pythonRuntime) {
runtime = pythonRuntime;
} else {
runtime = getPythonRuntime(
`${window.location.pathname}pyodide/`,
handleRunnerResult,
handleRunnerResult
);
setPythonRuntime(runtime);
}
(currentTarget as HTMLButtonElement).disabled = true;
setPythonResult({
result: `$ python3 script.py${RunnerResultPlaceholder}`,
result: `$ python3 ${PythonScriptDisplayName}${RunnerResultPlaceholder}`,
startPos,
endPos,
});
await getPythonResult(runtime, code, handleRunnerResult);
let runtime = pythonRuntime;
if (!runtime) {
runtime = await getPythonRuntime(
`${window.location.pathname}pyodide/`
);
onPythonRuntimeCreated(runtime);
}
await getPythonResult(
runtime,
code,
handleRunnerResult,
handleRunnerResult,
handleRunnerImporting,
handleRunnerResult,
handleJobFinished
);
(currentTarget as HTMLButtonElement).disabled = false;
},
300
Expand Down
35 changes: 21 additions & 14 deletions src/helpers/getPythonResult.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { loadPyodide } from "pyodide";
import { PyodideInterface } from "pyodide";

export const getPythonResult = async (
pyodide: ReturnType<typeof loadPyodide>,
pyodide: PyodideInterface,
code: string,
onException: (x: string) => void
onStdout: (x: string) => void,
onStderr: (x: string) => void,
onImporting: (x: string, err: boolean) => void,
onException: (x: string) => void,
onJobFinished: () => void
) => {
const availablePackages = [
{ keyword: "numpy", package: "numpy" },
Expand All @@ -23,6 +27,8 @@ export const getPythonResult = async (
{ keyword: "hashlib", package: "hashlib" },
];
try {
pyodide.setStdout({ batched: onStdout });
pyodide.setStderr({ batched: onStderr });
const matchedPackages = availablePackages
.filter(
({ keyword }) =>
Expand All @@ -31,18 +37,19 @@ export const getPythonResult = async (
)
.map(({ package: pkg }) => pkg);
if (!!matchedPackages.length) {
await (await pyodide).loadPackage(matchedPackages);
await pyodide.loadPackage(matchedPackages, {
errorCallback: (x) => onImporting(x, true),
messageCallback: (x) => onImporting(x, false),
});
}
await (
await pyodide
).runPythonAsync(`
from js import prompt
def input(p):
return prompt(p)
__builtins__.input = input
`);
await (await pyodide).runPythonAsync(code);
await pyodide.runPythonAsync(code);
} catch (e) {
onException(`${e}`);
let err = String(e);
if (err.endsWith("\n")) {
err = err.slice(0, -1);
}
onException(err);
} finally {
onJobFinished();
}
};
18 changes: 10 additions & 8 deletions src/helpers/getPythonRuntime.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { loadPyodide } from "pyodide";

export const getPythonRuntime = (
repoURL: string,
onStdout: (x: string) => void,
onStderr: (x: string) => void
) =>
loadPyodide({
export const getPythonRuntime = async (repoURL: string) => {
const pyodide = await loadPyodide({
indexURL: repoURL,
stdout: onStdout,
stderr: onStderr,
homedir: "/home/user",
});
await pyodide.runPythonAsync(`
from js import prompt
def input(p):
return prompt(p)
__builtins__.input = input
`);
return pyodide;
};
26 changes: 19 additions & 7 deletions src/views/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ImageView } from "../components/ImageView";
import { sendUserConfirm } from "../helpers/sendUserConfirm";
import { sendUserAlert } from "../helpers/sendUserAlert";
import { RouterComponentProps } from "../config/router";
import { PyodideInterface } from "pyodide";

const RefreshPlaceholder = "重新生成中...";
const FallbackIfIdInvalid =
Expand All @@ -42,15 +43,20 @@ const Chat = (props: RouterComponentProps) => {
const [attachmentsURL, setAttachmentsURL] = useState<
Record<number, string>
>({});
const [pythonRuntime, setPythonRuntime] = useState<PyodideInterface | null>(
null
);

const handlePythonRuntimeCreated = (pyodide: PyodideInterface) =>
setPythonRuntime(pyodide);

const scrollToBottom = useCallback(
(force: boolean = false) => {
(force: boolean = false) =>
(ai.busy || force) &&
mainSectionRef?.scrollTo({
top: mainSectionRef.scrollHeight,
behavior: "smooth",
});
},
mainSectionRef?.scrollTo({
top: mainSectionRef.scrollHeight,
behavior: "smooth",
}),
[ai, mainSectionRef]
);

Expand Down Expand Up @@ -251,7 +257,13 @@ const Chat = (props: RouterComponentProps) => {
!!data.length ? attachmentPostscriptHtml : ""
}
>
<Markdown typingEffect={typingEffect}>{`${parts}${
<Markdown
typingEffect={typingEffect}
pythonRuntime={pythonRuntime}
onPythonRuntimeCreated={
handlePythonRuntimeCreated
}
>{`${parts}${
!!data.length ? attachmentPostscriptHtml : ""
}`}</Markdown>
</Session>
Expand Down

0 comments on commit e5c533d

Please sign in to comment.