code-completion/src/App.jsx (268 lines of code) (raw):
import { useState, useRef, useEffect } from "react";
import Editor from "@monaco-editor/react";
import Progress from "./components/Progress";
const MODELS = [
"onnx-community/Qwen2.5-Coder-0.5B-ONNX",
"Xenova/tiny_starcoder_py",
"Xenova/codegen-350M-mono",
// 'Xenova/starcoderbase-1b',
];
function App() {
// Editor setup
const monaco = useRef(null);
const [monacoReady, setMonacoReady] = useState(false);
const [language, setLanguage] = useState("python"); // Only allow python for now
// Model loading
const [ready, setReady] = useState(null);
const [progressItems, setProgressItems] = useState([]);
// Inputs and outputs
const [model, setModel] = useState(MODELS[0]);
const [maxNewTokens, setMaxNewTokens] = useState(42);
const [code, setCode] = useState(
'\ndef fib(n):\n """Calculates the nth Fibonacci number"""\n',
);
// Generation parameters
const [temperature, setTemperature] = useState(0.5);
const [topK, setTopK] = useState(5);
const [doSample, setDoSample] = useState(false);
// Create a reference to the worker object.
const worker = useRef(null);
// We use the `useEffect` hook to setup the worker as soon as the `App` component is mounted.
useEffect(() => {
if (!worker.current) {
// Create the worker if it does not yet exist.
worker.current = new Worker(new URL("./worker.js", import.meta.url), {
type: "module",
});
}
// Create a callback function for messages from the worker thread.
const onMessageReceived = (e) => {
switch (e.data.status) {
case "initiate":
// Model file start load: add a new progress item to the list.
setReady(false);
setProgressItems((prev) => [...prev, e.data]);
break;
case "progress":
// Model file progress: update one of the progress items.
setProgressItems((prev) =>
prev.map((item) => {
if (item.file === e.data.file) {
return { ...item, ...e.data };
}
return item;
}),
);
break;
case "done":
// Model file loaded: remove the progress item from the list.
setProgressItems((prev) =>
prev.filter((item) => item.file !== e.data.file),
);
break;
case "ready":
// Pipeline ready: the worker is ready to accept messages.
setReady(true);
break;
case "update":
// Generation update: update the output text.
setCode((prev) => prev + e.data.output);
break;
}
};
// Attach the callback function as an event listener.
worker.current.addEventListener("message", onMessageReceived);
// Define a cleanup function for when the component is unmounted.
return () =>
worker.current.removeEventListener("message", onMessageReceived);
});
useEffect(() => {
const m = monaco.current;
if (!m) return;
let actionRegistration = m.addAction({
id: "generate",
label: "Generate",
contextMenuGroupId: "0_custom",
run: () => {
const val = m.getValue();
if (!val) return;
worker.current.postMessage({
model,
text: val,
max_new_tokens: maxNewTokens,
temperature,
top_k: topK,
do_sample: doSample,
});
},
});
// Define a cleanup function for when the component is unmounted.
return () => actionRegistration.dispose();
}, [monacoReady, model, maxNewTokens, temperature, topK, doSample]);
const showLoading = ready === false || progressItems.length > 0;
return (
<div className="flex h-screen w-screen">
<div
className={`gap-1 z-50 top-0 left-0 absolute w-full h-full transition-all px-8 flex flex-col justify-center text-center bg-black bg-opacity-50 backdrop-blur-sm ${
showLoading
? "opacity-100 pointer-events-auto"
: "opacity-0 pointer-events-none"
}`}
>
{ready === false && (
<label className="text-3xl p-3 text-white">Loading model...</label>
)}
{progressItems.map((data) => (
<div key={data.file}>
<Progress data={data} />
</div>
))}
</div>
<div>
<Editor
width="calc(100vw - 360px)"
language={language}
value={code}
onChange={(value) => setCode(value)}
theme="vs-dark"
onMount={(m) => {
monaco.current = m;
setMonacoReady(true);
}}
options={{
fontSize: 24,
}}
/>
</div>
<div className="flex-grow bg-gray-900 text-white p-4 flex flex-col overflow-y-auto">
<h2 className="text-2xl font-semibold text-center mb-2">
In-browser code completion
</h2>
<div className="text-center">
Made with
<a
className="text-blue-400 underline"
href="https://github.com/huggingface/transformers.js"
target="_blank"
rel="noreferrer"
>
🤗 Transformers.js
</a>
</div>
<label className="mt-3">Instructions:</label>
<ol className="list-decimal list-outside pl-4">
<li>
Choose a model from the dropdown and modify the generation settings
</li>
<li>Write some code in the editor</li>
<li>Right-click and select "Generate"</li>
</ol>
<label className="mt-3">Model:</label>
<select
value={model}
onChange={(e) => setModel(e.target.value)}
className="p-2.5 bg-gray-800 border border-gray-700 text-gray-100 rounded-lg"
>
{MODELS.map((value, i) => {
return (
<option key={i} value={value}>
{value}
</option>
);
})}
</select>
<div className="mt-3 flex justify-between">
<label>Max new tokens:</label>
<label>{maxNewTokens}</label>
</div>
<input
type="range"
min="1"
max="512"
value={maxNewTokens}
onChange={(event) => {
const newValue = parseInt(event.target.value);
setMaxNewTokens(newValue);
}}
className="w-full"
/>
<div className="mt-3 flex items-center">
<input
id="default-checkbox"
type="checkbox"
value={doSample}
onInput={(event) => {
setDoSample(event.target.checked);
}}
className="w-4 h-4 text-blue-600 rounded focus:ring focus:ring-blue-600 bg-gray-800 border-gray-700"
/>
<label htmlFor="default-checkbox" className="ml-2 font-medium">
Sample
</label>
</div>
<div
className={`mt-3 flex justify-between ${
doSample ? "opacity-100" : "opacity-50"
}`}
>
<label>Top K:</label>
<label>{topK}</label>
</div>
<input
type="range"
min="1"
max="50"
value={topK}
onChange={(e) => setTopK(parseInt(e.target.value))}
disabled={!doSample}
className="w-full"
/>
<div
className={`mt-3 flex justify-between ${
doSample ? "opacity-100" : "opacity-50"
}`}
>
<label>Temperature:</label>
<label>{temperature}</label>
</div>
<input
type="range"
min="0"
step="0.05"
max="1"
value={temperature}
onChange={(event) => {
const newValue = Number(event.target.value);
setTemperature(newValue);
}}
disabled={!doSample}
className="w-full"
/>
<div className="flex gap-2 justify-center mt-auto">
<svg
className="w-6 h-6 text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M10 .333A9.911 9.911 0 0 0 6.866 19.65c.5.092.678-.215.678-.477 0-.237-.01-1.017-.014-1.845-2.757.6-3.338-1.169-3.338-1.169a2.627 2.627 0 0 0-1.1-1.451c-.9-.615.07-.6.07-.6a2.084 2.084 0 0 1 1.518 1.021 2.11 2.11 0 0 0 2.884.823c.044-.503.268-.973.63-1.325-2.2-.25-4.516-1.1-4.516-4.9A3.832 3.832 0 0 1 4.7 7.068a3.56 3.56 0 0 1 .095-2.623s.832-.266 2.726 1.016a9.409 9.409 0 0 1 4.962 0c1.89-1.282 2.717-1.016 2.717-1.016.366.83.402 1.768.1 2.623a3.827 3.827 0 0 1 1.02 2.659c0 3.807-2.319 4.644-4.525 4.889a2.366 2.366 0 0 1 .673 1.834c0 1.326-.012 2.394-.012 2.72 0 .263.18.572.681.475A9.911 9.911 0 0 0 10 .333Z"
clipRule="evenodd"
/>
</svg>
<a
className="text-white font-normal underline underline-offset-1"
href="https://github.com/huggingface/transformers.js-examples/tree/main/code-completion"
target="_blank"
rel="noreferrer"
>
Source code
</a>
</div>
</div>
</div>
);
}
export default App;