Прошло некоторое время с тех пор, как я вел блог на среде. Этот блог посвящен CodeBoost.

CodeBoost — это программа для запуска кода, которая позволяет выполнять рефакторинг и запускать код в одном удобном месте. С CodeBoost вы можете улучшить качество и читабельность своего кода, а затем запустить его, чтобы сразу увидеть результаты.

Итак, две основные функции, которые предоставляет CodeBoost:

  • Рефакторинг кода: CodeBoost упрощает рефакторинг вашего кода, предлагая изменения, которые улучшат его качество, удобочитаемость и производительность. Вы можете провести рефакторинг всего кода или его части, выбрав фрагмент кода и рефакторинг этой части.
  • Выполнение кода: CodeBoost позволяет вам запускать код прямо из редактора, не переключаясь на другой инструмент или среду. Вы можете запускать свой код столько раз, сколько хотите, с разными входными значениями и видеть результат и любые возникающие ошибки или исключения. Вы можете выбирать между 9 различными языками, которые упоминаются как недостаточно поддерживаемые языки.

Поддерживаемые языки

  • Баш
  • C
  • C++
  • питон
  • Джава
  • Ржавчина
  • Go
  • Рубин
  • JavaScript

Теперь, когда мы знаем особенности CodeBoost, давайте посмотрим, как была построена каждая функция.

  1. КодРаннер

Как и в моем боте для разногласий, это приложение также может запускать код, но на этот раз оно использует API Judge0CE. Подробнее об этом можно узнать в хабе RapidAPI. Подпишитесь на любой из планов, используйте ключ API, и вы готовы к работе. Вы также можете выполнить указанные шаги, чтобы сделать вызов API. В этом приложении использовались два API от Judge0: один для создания заявки, а другой — для получения подробной информации о заявке. Вы также можете получить языки, поддерживаемые Judge0, используя конечную точку языков. В CodeBoost я сохранил информацию о языке в файле JSON, потому что вызов этого API казался бесполезным.

Еще одним важным моментом является функция заглушки кода. Некоторые языки, такие как python, имеют заглушку кода, которая помогает вам начать работу.

2. Рефакторинг кода

Рефакторинг кода использует возможности ChatGPT для рефакторинга кода. Чтобы узнать больше об API, предоставляемых ChatGPT, вы можете перейти к их Справочным документам по API. В этом приложении я использовал их API завершения чата, модель: gpt-3.5-turbo.

Создание CodeBoost

Приложение было создано с использованием фреймворка React. В этом разделе я расскажу вам, как создать CodeBoost,

  1. Создать реагирующее приложение

npx create-реагировать-приложение имя приложения

2. Я использовал tailwind для стилизации компонентов, чтобы вы могли либо установить tailwind из tailwind docs, в моем случае я просто вставил CDN в index.html под public.

3. Создайте такие компоненты, как CodeEditor, поле ввода, поле вывода, селектор языка и т. д.

Редактор кода

Я использовал пакет зеркала кода с https://github.com/uiwjs/react-codemirror. Вы можете обратиться к их документации для установки.

import CodeMirror from "@uiw/react-codemirror";
import { keymap } from "@codemirror/view";
import { sublime } from "@uiw/codemirror-theme-sublime";
import { defaultKeymap } from "@codemirror/commands";
import { EditorView } from "@codemirror/view";
import { mapLanguages } from "./utils";

const CodeEditor = ({
  editorRef,
  selectedLanguage,
  value,
  onChange,
  editable,
  runCode,
}) => {
  const handleIndentTab = (cm) => {
    const spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
    cm.replaceSelection(spaces);
  };
  const extensions = [
    keymap.of([
      {
        key: "Mod-Enter",
        run: () => {
          if (editable) runCode();
          return true;
        },
      },
      ...defaultKeymap,
    ]),
    keymap.of([handleIndentTab]),
    mapLanguages(selectedLanguage)(),
    EditorView.lineWrapping,
  ];
  const editorHeight = "calc(80vh - 165px)";
  return (
    <>
      <CodeMirror
        ref={editorRef}
        value={value}
        extensions={extensions}
        theme={sublime}
        onChange={(instance) => onChange(instance)}
        height={editorHeight}
        editable={editable}
        basicSetup={{ defaultKeymap: false }}
      />
    </>
  );
};
export default CodeEditor;

Здесь вам может быть интересно, что это за расширение и все такое, поэтому давайте углубимся в него,

Таким образом, в основном расширения похожи на упоминание в пакете зеркалирования кода о различных вещах, связанных с настройкой, например, о языке, необходимости переноса строк при уменьшении ширины редактора и т. д.

Существует множество расширений для зеркала кода, которые вы можете найти по вышеупомянутой ссылке на GitHub.

Здесь, как вы можете видеть, я отключил defaultKeymap для зеркала кода и использовал исправление для включения привязки клавиш ctrl + enter/cmd + enter (для Mac), чтобы ее можно было использовать для запуска кода вместо перехода на новый линия. Чтобы узнать больше об этом, вы можете обратиться к этой проблеме на GitHub.

Функция keymap.of([handleIndentTab]) предназначена для обработки отступов с помощью пробела вместо табуляции, что может быть проблемой при использовании таких языков, как python.

Функция map language сопоставляет языки, предоставленные Judge0, с форматом, понятным пакету зеркалирования кода.

import { javascript } from "@codemirror/lang-javascript";
import { python } from "@codemirror/lang-python";
import { java } from "@codemirror/lang-java";
import { rust } from "@codemirror/lang-rust";
import { cpp } from "@codemirror/lang-cpp";
import { StreamLanguage } from "@codemirror/language";
import { go } from "@codemirror/legacy-modes/mode/go";
import { ruby } from "@codemirror/legacy-modes/mode/ruby";
import { shell } from "@codemirror/legacy-modes/mode/shell";

export const mapLanguages = (value) => {
  const mappedLanguages = {
    javascript,
    python,
    java,
    rust,
    c: cpp,
    "c++": cpp,
    go: () => StreamLanguage.define(go),
    ruby: () => StreamLanguage.define(ruby),
    bash: () => StreamLanguage.define(shell)
  };
  return mappedLanguages[value];
};

Расширение EditorView.lineWrapping предназначено для переноса строк при уменьшении ширины редактора.

Поле ввода

const CustomInput = ({ input, setInput }) => {
  return (
    <textarea
      value={input}
      onChange={setInput}
      className="block px-1 w-full text-sm bg-gray-800 text-white border focus:ring-0 border-gray-400 mt-2"
      rows={5}
    />
  );
};
export default CustomInput;

Поле вывода

const OutputTerminal = ({ output, outputRef }) => {
  return (
    <div
      ref={outputRef}
      className="block px-1 w-full h-36 text-sm bg-gray-800 text-white border focus:ring-0 border-gray-400 mt-2"
    >
      {output}
    </div>
  );
};
export default OutputTerminal;

Кнопки

const CodeActions = ({ runEditorCode, refactorCode, isLoading }) => {
  return (
    <div className="flex md:space-x-2 w-full md:w-1/2 justify-between md:justify-end">
      <button
        className="disabled:opacity-75 disabled:cursor-not-allowed border border-gray-200 bg-gray-200 text-gray-700 rounded-md px-4 py-2 md:m-2 mt-2 transition duration-500 ease select-none hover:bg-gray-300 focus:outline-none focus:shadow-outline"
        onClick={runEditorCode}
        disabled={isLoading}
      >
        Run Code
      </button>
      <button
        className="disabled:opacity-75 disabled:cursor-not-allowed border border-gray-200 bg-gray-200 text-gray-700 rounded-md px-4 py-2 md:m-2 mt-2 transition duration-500 ease select-none hover:bg-gray-300 focus:outline-none focus:shadow-outline"
        onClick={refactorCode}
        disabled={isLoading}
      >
        Refactor Code
      </button>
    </div>
  );
};

export default CodeActions;

4. Настройка API

Настройка Axios

import axios from "axios";

const axiosOptions = {
  baseURL: "https://judge0-ce.p.rapidapi.com/",
  params: { base64_encoded: "true", fields: "*" },
  headers: {
    "content-type": "application/json",
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.REACT_APP_OPEN_AI_KEY}`,
    "X-RapidAPI-Key": process.env.REACT_APP_JUDGE0_KEY,
    "X-RapidAPI-Host": "judge0-ce.p.rapidapi.com"
  }
};
export default axios.create(axiosOptions);

Судья0CE

Получить API-ключ Judge0CE можно в хабе RapidAPI.

import axios from "./axios";
const createSubmission = (payload) => axios.post("submissions", payload);
const getSubmission = (token) => axios.get(`/submissions/${token}`);

export default { createSubmission, getSubmission };

ChatGPT

Вы можете получить API-ключ ChatGPT из панели инструментов openAI.

import axios from "./axios";
const createCompletion = (payload) =>
  axios.post("https://api.openai.com/v1/chat/completions", payload);
export default { createCompletion };

5. Я использовал реактивный запрос для оптимизации использования запроса, этот шаг не является обязательным.

import { useMutation } from "react-query";
import runCode from "../Apis/runCode";

const useCreateSubmissionsApi = () => {
  return useMutation((payload) => runCode.createSubmission(payload));
};
const useGetSubmissionsApi = () => {
  return useMutation((token) => runCode.getSubmission(token));
};
export { useCreateSubmissionsApi, useGetSubmissionsApi };
import { useMutation } from "react-query";
import refactorCode from "../Apis/refactorCode";

const useCreateCompletionApi = () => {
  return useMutation((payload) => refactorCode.createCompletion(payload));
};
export { useCreateCompletionApi };

Теперь у нас есть все, давайте соберем все в одном месте

import { useEffect, useRef, useState } from "react";

import { assoc } from "ramda";
import {
  useCreateSubmissionsApi,
  useGetSubmissionsApi,
} from "../Hooks/useSubmissionsApi";
import CodeEditor from "./CodeEditor";
import CustomInput from "./CustomInput";
import LanguageSelector from "./LanguageSelector";
import { LANGUAGE_OPTIONS } from "./LanguageSelector/constants";
import OutputTerminal from "./OutputTerminal";
import { decodeString, encodeString } from "./utils";
import { OUTPUT_STATUES, DEFAULT_OUTPUT_VALUE } from "./contants";
import CustomInputHeader from "./CustomInput/Header";
import OutputTerminalHeader from "./OutputTerminal/Header";
import { useCreateCompletionApi } from "../Hooks/useRefactorApi";
import Header from "./Header";
import CodeActions from "./CodeActions";
const Main = () => {
  const outputRef = useRef(null);
  const editorRef = useRef(null);
  const [selectedLanguage, setSelectedLanguage] = useState(LANGUAGE_OPTIONS[0]);
  const [value, setValue] = useState(selectedLanguage?.stub);
  const [input, setInput] = useState();
  const [output, setOutput] = useState(DEFAULT_OUTPUT_VALUE);
  const [isLoading, setIsLoading] = useState(false);
  const { mutateAsync: runCode } = useCreateSubmissionsApi();
  const { mutateAsync: getOutput } = useGetSubmissionsApi();
  const { mutateAsync: getRefactoredCode } = useCreateCompletionApi();
  useEffect(() => {
    setValue(selectedLanguage?.stub);
  }, [selectedLanguage]);
  useEffect(() => {
    if (output?.data) {
      outputRef.current.scrollIntoView({ behavior: "smooth" });
    }
  }, [output]);
  const clearInput = () => {
    setInput("");
  };
  const handleCustomInputChange = (e) => {
    const { value } = e.target;
    setInput(value);
  };
  const runEditorCode = async () => {
    try {
      setIsLoading(true);
      clearOutput();
      const { data: submissionData } = await runCode({
        source_code: encodeString(value),
        language_id: selectedLanguage.value,
        stdin: encodeString(input || ""),
      });
      const { data: outputData } = await getOutput(submissionData?.token);
      if (outputData.status_id === 3) {
        setOutput(
          assoc("data", decodeString(outputData.stdout) || "Empty Ouput")
        );
      } else {
        setOutput(assoc("data", decodeString(outputData.stderr)));
      }
      setOutput(assoc("status", OUTPUT_STATUES[outputData.status_id]));
    } catch (err) {
      console.log(err);
    } finally {
      setIsLoading(false);
    }
  };
  const refactorCode = async () => {
    try {
      setIsLoading(true);
      const cursorSelection = editorRef.current.view.state.selection.main;
      const startRange = cursorSelection.from;
      const endRange = cursorSelection.to;
      const selectedValue =
        startRange !== endRange ? value.substring(startRange, endRange) : value;
      const { data: chatGptOutput } = await getRefactoredCode({
        model: "gpt-3.5-turbo",
        messages: [{ role: "user", content: `Refactor code snippet ${selectedValue}` }],
      });
      const refactoredCode = chatGptOutput.choices[0].message.content;
      setValue((prevValue) => prevValue.replace(selectedValue, refactoredCode));
    } catch (err) {
      console.log(err);
    } finally {
      setIsLoading(false);
    }
  };
  const clearOutput = () => {
    setOutput(DEFAULT_OUTPUT_VALUE);
  };
  return (
    <div className="flex flex-col p-4">
      <Header />
      <div className="md:flex md:w-full mt-4 justify-between items-center">
        <LanguageSelector
          selectedLanguage={selectedLanguage}
          setSelectedLanguage={setSelectedLanguage}
        />
        <CodeActions
          refactorCode={refactorCode}
          runEditorCode={runEditorCode}
          isLoading={isLoading}
        />
      </div>
      <div className="editor-height mt-5">
        <CodeEditor
          editorRef={editorRef}
          selectedLanguage={selectedLanguage?.title.toLowerCase()}
          value={value}
          onChange={setValue}
          editable={!isLoading}
          runCode={runEditorCode}
        />
      </div>
      <div className="flex flex-col mt-5">
        <CustomInputHeader clearInput={clearInput} />
        <CustomInput input={input} setInput={handleCustomInputChange} />
      </div>
      {output.data && (
        <>
          <div className="flex flex-col py-4">
            <OutputTerminalHeader
              status={output?.status}
              clearOutput={clearOutput}
            />
            <OutputTerminal output={output?.data} outputRef={outputRef} />
          </div>
        </>
      )}
    </div>
  );
};
export default Main;

Здесь я использовал 2 ссылки

  1. outputRef -> Этот элемент предназначен для прокрутки к области вывода текста после успешного запуска кода.
  2. editorRef -> Это для отслеживания указателя курсора, на основе которого мы выбираем, следует ли рефакторить весь код или выбранный фрагмент кода.

Ссылка на сайт https://codeboost.vercel.app/.

Вы можете посмотреть демо этого приложения здесь на youtube.

Также вы можете обратиться к исходному коду здесь, на GitHub.

Если вы нашли этот проект интересным, поделитесь им и отметьте его звездой, а также подписывайтесь на меня на Medium :D.