diff --git a/package-lock.json b/package-lock.json index cae0fc0..962ad6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "chatgemini", - "version": "0.5.1", + "version": "0.5.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "chatgemini", - "version": "0.5.1", + "version": "0.5.2", "dependencies": { "@fancyapps/ui": "^5.0.33", "@fingerprintjs/fingerprintjs": "^4.2.2", @@ -16,6 +16,7 @@ "crypto-js": "^4.2.0", "file-saver": "^2.0.5", "localforage": "^1.10.0", + "lodash.isequal": "^4.5.0", "node-fetch": "^2.7.0", "pyodide": "^0.23.4", "react": "^18.2.0", @@ -40,6 +41,7 @@ "@tailwindcss/typography": "^0.5.10", "@types/crypto-js": "^4.2.2", "@types/file-saver": "^2.0.7", + "@types/lodash.isequal": "^4.5.8", "@types/node": "^16.18.76", "@types/react": "^18.2.48", "@types/react-dom": "^18.2.18", @@ -3999,6 +4001,21 @@ "resolved": "https://registry.npmmirror.com/@types/katex/-/katex-0.16.7.tgz", "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==" }, + "node_modules/@types/lodash": { + "version": "4.14.202", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", + "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==", + "dev": true + }, + "node_modules/@types/lodash.isequal": { + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.8.tgz", + "integrity": "sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/mdast": { "version": "4.0.3", "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-4.0.3.tgz", @@ -12191,6 +12208,11 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", diff --git a/package.json b/package.json index e92d455..8badbaa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chatgemini", - "version": "0.5.1", + "version": "0.5.2", "homepage": ".", "private": true, "dependencies": { @@ -12,6 +12,7 @@ "crypto-js": "^4.2.0", "file-saver": "^2.0.5", "localforage": "^1.10.0", + "lodash.isequal": "^4.5.0", "node-fetch": "^2.7.0", "pyodide": "^0.23.4", "react": "^18.2.0", @@ -53,6 +54,7 @@ "@tailwindcss/typography": "^0.5.10", "@types/crypto-js": "^4.2.2", "@types/file-saver": "^2.0.7", + "@types/lodash.isequal": "^4.5.8", "@types/node": "^16.18.76", "@types/react": "^18.2.48", "@types/react-dom": "^18.2.18", diff --git a/src/components/Markdown.tsx b/src/components/Markdown.tsx index 02f60e1..2449537 100644 --- a/src/components/Markdown.tsx +++ b/src/components/Markdown.tsx @@ -11,6 +11,8 @@ import userThrottle from "../helpers/userThrottle"; import { useEffect, useState } from "react"; import { loadPyodide } from "pyodide"; import userDebounce from "../helpers/userDebounce"; +import { Position } from "unist"; +import isEqual from "lodash.isequal"; interface MarkdownProps { readonly className?: string; @@ -21,7 +23,10 @@ interface MarkdownProps { export const Markdown = (props: MarkdownProps) => { const { className, typingEffect, children } = props; - const [executeResult, setExecuteResult] = useState(""); + const [executeResult, setExecuteResult] = useState<{ + result: string; + position: Position | null; + }>({ result: "", position: null }); const handleCopyCode = userThrottle( async (code: string, currentTarget: EventTarget) => { @@ -38,16 +43,28 @@ export const Markdown = (props: MarkdownProps) => { ); const handleExecuteCode = userDebounce( - async (code: string, currentTarget: EventTarget) => { + async ( + position: Position | null, + code: string, + currentTarget: EventTarget + ) => { const innerText = (currentTarget as HTMLButtonElement).innerText; try { (currentTarget as HTMLButtonElement).innerText = "正在执行"; (currentTarget as HTMLButtonElement).disabled = true; - setExecuteResult("# 正在加载资源"); + setExecuteResult({ result: "# 正在加载资源", position }); const pyodide = await loadPyodide({ indexURL: `${window.location.pathname}pyodide/`, - stdout: (x: string) => setExecuteResult(`# stdout\n${x}`), - stderr: (x: string) => setExecuteResult(`# stderr\n${x}`), + stdout: (x: string) => + setExecuteResult({ + result: `# stdout\n${x}`, + position, + }), + stderr: (x: string) => + setExecuteResult({ + result: `# stderr\n${x}`, + position, + }), }); await pyodide.runPythonAsync(` from js import prompt @@ -55,10 +72,10 @@ def input(p): return prompt(p) __builtins__.input = input `); - setExecuteResult("# 正在执行代码"); + setExecuteResult({ result: "# 正在执行代码", position }); await pyodide.runPythonAsync(code); } catch (e) { - setExecuteResult(`# 运行失败\n${e}`); + setExecuteResult({ result: `# 执行失败\n${e}`, position }); } (currentTarget as HTMLButtonElement).innerText = innerText; (currentTarget as HTMLButtonElement).disabled = false; @@ -67,7 +84,7 @@ __builtins__.input = input ); useEffect(() => { - setExecuteResult(""); + setExecuteResult({ result: "", position: null }); }, [children]); return ( @@ -90,13 +107,14 @@ __builtins__.input = input pre: ({ node, ...props }) => (
), - code: ({ className, children }) => { + code: ({ className, children, node }) => { const typeEffectPlaceholder = "❚"; const match = /language-(\w+)/.exec(className ?? ""); const lang = match !== null ? match[1] : ""; const code = ( !!children ? String(children) : typeEffectPlaceholder ).replace(typingEffect, typeEffectPlaceholder); + const position = node?.position ?? null; return match ? ( <>