Skip to content

Commit

Permalink
Run python snippets
Browse files Browse the repository at this point in the history
  • Loading branch information
msveshnikov committed Mar 29, 2024
1 parent 290b598 commit 8a577f6
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 6 deletions.
9 changes: 7 additions & 2 deletions server/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Use the official Node.js image as the base image
FROM node:20-slim
FROM node:20.9.0-bullseye

ENV NODE_ENV production

RUN sed -i'.bak' 's/$/ contrib/' /etc/apt/sources.list
RUN apt-get update && apt-get install python python-dev build-essential -y


# Set the working directory in the container
WORKDIR /app
Expand Down
12 changes: 12 additions & 0 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { authenticateUser, registerUser, verifyToken } from "./auth.js";
import mongoose from "mongoose";
import { User, countCharacters, countTokens, storeUsageStats } from "./model/User.js";
import { fetchPageContent, fetchSearchResults } from "./search.js";
import { PythonShell } from "python-shell";

const MAX_CONTEXT_LENGTH = 16000;
const MAX_SEARCH_RESULT_LENGTH = 3000;
Expand Down Expand Up @@ -281,3 +282,14 @@ app.get("/stats", verifyToken, async (req, res) => {
res.status(500).json({ error: error.message });
}
});

app.post("/run", verifyToken, async (req, res) => {
try {
const { program } = req.body;
const output = await PythonShell.runString(program, null);
res.json({ output: output.join("\n") });
} catch (error) {
console.error(error);
res.status(500).json({ error: error.message });
}
});
9 changes: 9 additions & 0 deletions server/package-lock.json

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

1 change: 1 addition & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"mongoose": "^8.2.4",
"morgan": "^1.10.0",
"pdf-parse": "1.1.1",
"python-shell": "^5.0.0",
"xlsx": "^0.18.5"
}
}
3 changes: 2 additions & 1 deletion src/components/ChatHistory.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const linkStyle = {
wordBreak: "break-all", // Break words if they are too long to fit on a single line
};

const ChatHistory = memo(({ chatHistory, isModelResponding }) => {
const ChatHistory = memo(({ chatHistory, isModelResponding, onRun }) => {
return (
<Box id="chatid" flex={1} overflow="auto" padding={2} display="flex" flexDirection="column">
{chatHistory.map((chat, index) => (
Expand Down Expand Up @@ -78,6 +78,7 @@ const ChatHistory = memo(({ chatHistory, isModelResponding }) => {
<CodeBlock
language={language}
value={String(children).replace(/\n$/, "")}
onRun={onRun}
/>
) : (
<code className={className} {...props}>
Expand Down
22 changes: 20 additions & 2 deletions src/components/CodeBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Light as SyntaxHighlighter } from "react-syntax-highlighter";
import style from "react-syntax-highlighter/dist/esm/styles/hljs/monokai";
import { IconButton, Tooltip } from "@mui/material";
import FileCopyOutlinedIcon from "@mui/icons-material/FileCopyOutlined";
import PlayArrowIcon from "@mui/icons-material/PlayArrow"; // Import the PlayArrow icon
import js from "react-syntax-highlighter/dist/esm/languages/hljs/javascript";
import java from "react-syntax-highlighter/dist/esm/languages/hljs/java";
import python from "react-syntax-highlighter/dist/esm/languages/hljs/python";
Expand All @@ -11,7 +12,7 @@ SyntaxHighlighter.registerLanguage("js", js);
SyntaxHighlighter.registerLanguage("java", java);
SyntaxHighlighter.registerLanguage("python", python);

export const CodeBlock = ({ language, value }) => {
export const CodeBlock = ({ language, value, onRun }) => {
const [copied, setCopied] = React.useState(false);

const handleCopy = async () => {
Expand All @@ -36,14 +37,31 @@ export const CodeBlock = ({ language, value }) => {
style={{
position: "absolute",
top: -5,
right: 8,
right: 32, // Adjust the right position to make space for the new button
zIndex: 1,
}}
size="small"
>
<FileCopyOutlinedIcon fontSize="small" />
</IconButton>
</Tooltip>
{/* Add a new Tooltip and IconButton for the small green triangle button */}
<Tooltip title="Run" placement="top">
<IconButton
aria-label="Run code"
onClick={() => onRun(language, value)}
style={{
position: "absolute",
top: -5,
right: 8,
zIndex: 1,
color: "green",
}}
size="small"
>
<PlayArrowIcon fontSize="small" />
</IconButton>
</Tooltip>
<SyntaxHighlighter
language={language}
style={style}
Expand Down
41 changes: 40 additions & 1 deletion src/components/Main.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,45 @@ function Main() {
setOpenMyAccountModal(false);
};

const handleRun = async (language, program) => {
if (language === "python") {
const token = localStorage.getItem("token");
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
};

setChatHistory([...chatHistory, { user: "🏃", assistant: null }]);
setIsModelResponding(true);

const response = await fetch(API_URL + "/run", {
method: "POST",
headers,
body: JSON.stringify({ program }),
});

if (response.ok) {
const data = await response.json();
setChatHistory([
...chatHistory,
{
user: "🏃",
assistant: `Output:\n${data.output}`,
},
]);
} else {
const errorMessage = await response.text();
setChatHistory([
...chatHistory,
{
user: "🏃",
assistant: `Error: ${errorMessage}`,
},
]);
}
}
};

const fetchUserData = async () => {
const token = localStorage.getItem("token");
const headers = {
Expand Down Expand Up @@ -341,7 +380,7 @@ function Main() {
sx={isMobile ? { m: 0, p: 0 } : {}}
style={{ display: "flex", flexDirection: "column", height: "91vh" }}
>
<ChatHistory chatHistory={chatHistory} isModelResponding={isModelResponding} />
<ChatHistory onRun={handleRun} chatHistory={chatHistory} isModelResponding={isModelResponding} />
<ChatInput
input={input}
setInput={setInput}
Expand Down
18 changes: 18 additions & 0 deletions src/components/__tests__/CodeBlock.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from "react";
import { render, fireEvent, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import { CodeBlock } from "../CodeBlock";

describe("CodeBlock", () => {
const code = `console.log('Hello, World!')`;
const language = "js";

it("clicks the run button", () => {
const mockRunCode = jest.fn();
render(<CodeBlock language={language} value={code} onRun={mockRunCode} />);
const runButton = screen.getByRole("button", { name: "Run code" });
fireEvent.click(runButton);
expect(mockRunCode).toHaveBeenCalledTimes(1);
});

});

0 comments on commit 8a577f6

Please sign in to comment.