import React, { useEffect, useRef, useState } from "react"; import { createRoot } from "react-dom/client"; import "./styles.css"; const SAMPLE_PROMPT = "Draft a short reply to the customer at billing@example.org. Mention that the $1,234.56 invoice is under review and ask them not to share card details in chat."; function makeId() { return globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`; } function makeConversationId() { return `conv_${makeId().replace(/[^A-Za-z0-9_-]/g, "")}`; } function initialTrace() { return { request_id: "READY", conversation_id: null, detected_entity_types: [], detected_entities_count: 0, masked_request: null, upstream_response: null, blocked_entities: [], preview_only: false, preview_reason: null, }; } function formatJson(value, emptyText) { return value ? JSON.stringify(value, null, 2) : emptyText; } function extractText(response) { return response?.choices?.[0]?.message?.content || response?.error?.message || "No response"; } async function loadPlaygroundConfig(signal) { const response = await fetch("/playground/config", { signal }); if (!response.ok) throw new Error("config unavailable"); return response.json(); } const playgroundConfigRequest = loadPlaygroundConfig(); function useObjectUrl(blob) { const [url, setUrl] = useState(null); useEffect(() => { if (!blob) { setUrl(null); return undefined; } const nextUrl = URL.createObjectURL(blob); setUrl(nextUrl); return () => URL.revokeObjectURL(nextUrl); }, [blob]); return url; } function FileAnonymizer() { const [file, setFile] = useState(null); const [status, setStatus] = useState("idle"); const [result, setResult] = useState(null); const requestInFlightRef = useRef(false); const downloadUrl = useObjectUrl(result?.blob || null); async function sanitize(event) { event.preventDefault(); if (!file || requestInFlightRef.current) return; requestInFlightRef.current = true; setStatus("running"); setResult(null); const formData = new FormData(); formData.append("file", file); try { const response = await fetch("/v1/privacy/files/anonymize", { method: "POST", body: formData, }); if (!response.ok) { const failure = await response.json().catch(() => ({})); throw new Error(failure?.error?.message || "MaskGate could not sanitize this file"); } const blob = await response.blob(); const disposition = response.headers.get("content-disposition") || ""; const filename = disposition.match(/filename="([^"]+)"/)?.[1] || "maskgate-output"; setResult({ blob, filename, redactions: response.headers.get("x-maskgate-redactions") || "0", types: response.headers.get("x-maskgate-entity-types") || "none", }); setStatus("complete"); } catch (fileError) { setResult({ error: fileError.message }); setStatus("error"); } finally { requestInFlightRef.current = false; } } return (
Sanitize a fileDOCX, PDF, or image local processing
{ setFile(event.target.files?.[0] || null); setStatus("idle"); setResult(null); }} />

Text, metadata, links, and detected faces are removed locally. Files are never sent to the LLM provider.

{result?.blob && downloadUrl && (
{result.redactions} redactions / {result.types} download {result.filename}
)} {result?.error &&
{result.error}
}
); } function App() { const [config, setConfig] = useState({ provider: "openai", model: "gpt-4.1-mini", masking_mode: "placeholder", conversation_ttl_seconds: 1800, provider_ready: false, }); const [draft, setDraft] = useState(""); const [mode, setMode] = useState("placeholder"); const [model, setModel] = useState("gpt-4.1-mini"); const [conversationId, setConversationId] = useState(makeConversationId); const [history, setHistory] = useState([]); const [trace, setTrace] = useState(initialTrace); const [finalText, setFinalText] = useState(""); const [runState, setRunState] = useState("idle"); const [connection, setConnection] = useState("connecting"); const [error, setError] = useState(""); const promptRef = useRef(null); const submitInFlightRef = useRef(false); const clearInFlightRef = useRef(false); useEffect(() => { let active = true; playgroundConfigRequest .then((data) => { if (!active) return; setConfig(data); setModel(data.model || "gpt-4.1-mini"); setMode(data.masking_mode || "placeholder"); setConnection("online"); }) .catch((loadError) => { if (active && loadError.name !== "AbortError") setConnection("offline"); }); return () => { active = false; }; }, []); function addHistory(role, content) { setHistory((current) => [...current.slice(-5), { id: makeId(), role, content }]); } function resetOutput() { setTrace(initialTrace()); setFinalText(""); setError(""); setRunState("idle"); } async function submit(event) { event.preventDefault(); const prompt = draft.trim(); if (!prompt || runState === "running" || submitInFlightRef.current) return; submitInFlightRef.current = true; addHistory("user", prompt); setDraft(""); resetOutput(); setRunState("running"); try { const response = await fetch("/playground/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: model.trim() || "gpt-4.1-mini", messages: [{ role: "user", content: prompt }], temperature: 0.7, masking_mode: mode, conversation_id: conversationId, }), }); if (!response.ok) { const failedEnvelope = await response.json().catch(() => ({})); const failedResult = failedEnvelope.response || failedEnvelope; setTrace({ ...initialTrace(), ...(failedEnvelope.trace || {}) }); const message = failedResult?.error?.message || "MaskGate stopped the request"; setRunState("blocked"); setError(message); addHistory("error", message); return; } const envelope = await response.json(); const result = envelope.response || envelope; const nextTrace = { ...initialTrace(), ...(envelope.trace || {}) }; setTrace(nextTrace); if ( response.ok && nextTrace.preview_only && nextTrace.preview_reason === "provider_not_configured" ) { const previewMessage = "Preview only — no provider call was made."; setFinalText(previewMessage); setRunState("preview"); addHistory("assistant", previewMessage); return; } if (result.error) { const message = result?.error?.message || "MaskGate stopped the request"; setRunState("blocked"); setError(message); addHistory("error", message); return; } const answer = extractText(result); setFinalText(answer); setRunState("complete"); addHistory("assistant", answer); } catch { const message = "Could not connect to the local MaskGate proxy."; setRunState("error"); setError(message); addHistory("error", message); } finally { submitInFlightRef.current = false; } } async function clearConversation() { if (clearInFlightRef.current) return; clearInFlightRef.current = true; const oldId = conversationId; try { await fetch(`/playground/api/conversations/${encodeURIComponent(oldId)}`, { method: "DELETE" }).catch(() => {}); setConversationId(makeConversationId()); setHistory([]); setDraft(""); resetOutput(); } finally { clearInFlightRef.current = false; } } function loadSample() { setDraft(SAMPLE_PROMPT); promptRef.current?.focus(); } const providerName = config.provider || "openai"; const readyLabel = config.provider_ready ? "key configured" : "preview only"; const statusLabel = runState === "running" ? "sending" : runState === "complete" ? "complete" : runState === "preview" ? "preview" : runState === "blocked" || runState === "error" ? "stopped" : "waiting"; return (
MASKGATE / PLAYGROUND

Inspect every request before it leaves.

Original input on the left. The exact masked payload and the returned result on the right.

{connection === "online" ? "local proxy online" : "local proxy"}
01 / INPUT

What I send

{history.length === 0 ? (
No messages yet.
) : ( history.map((item) => (
{item.role === "user" ? "you" : item.role === "assistant" ? "model" : "maskgate"}

{item.content}

)) )}