import { useState, useRef, useEffect } from "react";
const API_URL = import.meta.env.VITE_API_URL || "https://happy4040-mock-technical-interviewer.hf.space";
function Markdown({ text }) {
const html = (text || "")
.replace(/\*\*(.+?)\*\*/g, "$1 ")
.replace(/\*(.+?)\*/g, "$1 ")
.replace(/`{3}[\w]*\n?([\s\S]*?)`{3}/g, "
${html}` }} />;
}
function Whiteboard({ onCapture }) {
const canvasRef = useRef(null);
const drawing = useRef(false);
const lastPos = useRef(null);
const [color, setColor] = useState("#f0f4ff");
const [lineWidth, setLineWidth] = useState(3);
const [eraser, setEraser] = useState(false);
const getPos = (e, canvas) => {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
if (e.touches) return { x: (e.touches[0].clientX - rect.left) * scaleX, y: (e.touches[0].clientY - rect.top) * scaleY };
return { x: (e.clientX - rect.left) * scaleX, y: (e.clientY - rect.top) * scaleY };
};
const startDraw = (e) => { e.preventDefault(); drawing.current = true; lastPos.current = getPos(e, canvasRef.current); };
const draw = (e) => {
e.preventDefault();
if (!drawing.current) return;
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
const pos = getPos(e, canvas);
ctx.beginPath(); ctx.moveTo(lastPos.current.x, lastPos.current.y); ctx.lineTo(pos.x, pos.y);
ctx.strokeStyle = eraser ? "#0e1117" : color; ctx.lineWidth = eraser ? 20 : lineWidth;
ctx.lineCap = "round"; ctx.lineJoin = "round"; ctx.stroke();
lastPos.current = pos;
};
const stopDraw = () => { drawing.current = false; };
const clear = () => {
const canvas = canvasRef.current; const ctx = canvas.getContext("2d");
ctx.fillStyle = "#0e1117"; ctx.fillRect(0, 0, canvas.width, canvas.height);
};
useEffect(() => { clear(); }, []);
const capture = () => { const b64 = canvasRef.current.toDataURL("image/png").split(",")[1]; onCapture(b64); };
return (
);
}
function Message({ role, text }) {
return (
{role === "ai" ? "AI" : "You"}
{role === "ai" ?
:
{text}
}
);
}
function CodeEditor({ value, onChange }) {
return (
);
}
export default function App() {
const [screen, setScreen] = useState("home");
const [apiKey, setApiKey] = useState("");
const [sessionId, setSessionId] = useState(null);
const [messages, setMessages] = useState([]);
const [problem, setProblem] = useState("");
const [code, setCode] = useState("# Your solution here\n");
const [codeChanged, setCodeChanged] = useState(false);
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
const [starting, setStarting] = useState(false);
const [finished, setFinished] = useState(false);
const [report, setReport] = useState("");
const [showWb, setShowWb] = useState(false);
const [error, setError] = useState("");
const chatEndRef = useRef(null);
useEffect(() => { chatEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]);
const apiFetch = (path, opts = {}) =>
fetch(`${API_URL}${path}`, {
...opts,
headers: { "Content-Type": "application/json", ...(opts.headers || {}) },
});
const startSession = async () => {
if (!apiKey.trim()) { setError("Please enter your Google Gemini API key."); return; }
setError(""); setStarting(true);
try {
const res = await apiFetch("/api/session/start", { method: "POST", body: JSON.stringify({ api_key: apiKey.trim() }) });
if (!res.ok) throw new Error(await res.text());
const data = await res.json();
setSessionId(data.session_id);
setMessages([{ role: "ai", text: data.message }]);
setScreen("interview");
} catch (e) {
setError("Could not connect to the server. Please try again.");
} finally { setStarting(false); }
};
const sendMessage = async (extraImageBase64 = null) => {
if (!input.trim() && !codeChanged && !extraImageBase64) return;
const userText = input.trim();
setInput("");
const userMsg = userText || (extraImageBase64 ? "[Whiteboard sent]" : "[Code updated]");
setMessages(m => [...m, { role: "user", text: userMsg }]);
setLoading(true);
try {
const body = { session_id: sessionId, message: userText, code, code_changed: codeChanged, image_base64: extraImageBase64 || null, api_key: apiKey.trim() };
setCodeChanged(false);
const res = await apiFetch("/api/chat", { method: "POST", body: JSON.stringify(body) });
if (!res.ok) throw new Error(await res.text());
const data = await res.json();
setMessages(m => [...m, { role: "ai", text: data.message }]);
if (data.problem && !data.problem.includes("not been selected")) setProblem(data.problem);
if (data.code && data.code !== "# Your code here") setCode(data.code);
if (data.finished) { setFinished(true); if (data.report) setReport(data.report); }
} catch (e) {
setMessages(m => [...m, { role: "ai", text: `Something went wrong. Please try again in a moment.` }]);
} finally { setLoading(false); }
};
const handleWbCapture = (b64) => { setShowWb(false); sendMessage(b64); };
const handleKeyDown = (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendMessage(); } };
const downloadReport = () => {
const blob = new Blob([report], { type: "text/markdown" });
const a = document.createElement("a"); a.href = URL.createObjectURL(blob);
a.download = "interview_report.md"; a.click();
};
if (screen === "home") return (
⬑
Mock TechnologieInterview Suite
Practice real technical interviews with an AI interviewer powered by Google Gemini. Get hints, use the whiteboard, write code, and receive a full evaluation report.
{error &&
{error}
}
{starting ? : "Start Interview β"}
{Array.from({length:24}).map((_,i)=>(
))}
);
return (
π Problem Statement
{!problem || problem === "No problem selected yet." || problem === "Problem has not been selected yet"
?
A question will appear here once selected.
:
}
{messages.map((m, i) =>
)}
{loading && (
)}
Code Editor
{codeChanged && β unsent changes }
{ setCode(v); setCodeChanged(true); }}/>
sendMessage()} disabled={loading || finished || !codeChanged}>
Submit Code Update
{showWb && (
Whiteboard β Draw your approach
setShowWb(false)}>β
)}
{screen === "report" && (
π Interview Evaluation Report
β¬ Download
setScreen("interview")}>β
)}
);
}