HEX
Server: LiteSpeed
System: Linux houston.panomity.com 6.8.0-100-generic #100-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 13 16:40:06 UTC 2026 x86_64
User: nudepix (1011)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //proc/self/root/tmp/voice2image_check_frontend/app.js
const dom = {
  recordBtn: document.getElementById("recordButton"),
  recordLabel: document.getElementById("recordLabel"),
  recIndicator: document.getElementById("recIndicator"),
  statusChip: document.getElementById("statusChip"),
  stepsInput: document.getElementById("stepsInput"),
  stepsValue: document.getElementById("stepsValue"),
  cfgInput: document.getElementById("cfgInput"),
  cfgValue: document.getElementById("cfgValue"),
  seedInput: document.getElementById("seedInput"),
  transcriptBox: document.getElementById("transcriptBox"),
  promptBox: document.getElementById("promptBox"),
  latestImage: document.getElementById("latestImage"),
  imageMeta: document.getElementById("imageMeta"),
  galleryGrid: document.getElementById("galleryGrid"),
  historyList: document.getElementById("historyList"),
  historyCount: document.getElementById("historyCount"),
  refreshHistory: document.getElementById("refreshHistory"),
  settingsHelpButton: document.getElementById("settingsHelpButton"),
  settingsHelpModal: document.getElementById("settingsHelpModal"),
  settingsHelpClose: document.getElementById("settingsHelpClose"),
};

let mediaRecorder = null;
let chunks = [];
let stream = null;
let isRecording = false;
let historyItems = [];

const setStatus = (text, tone = "idle") => {
  dom.statusChip.textContent = text;
  dom.recIndicator.textContent = tone === "recording" ? "Recording" : tone === "busy" ? "Busy" : "Idle";
};

const updateSliderReadouts = () => {
  dom.stepsValue.textContent = dom.stepsInput.value;
  dom.cfgValue.textContent = dom.cfgInput.value;
};

dom.stepsInput.addEventListener("input", updateSliderReadouts);
dom.cfgInput.addEventListener("input", updateSliderReadouts);
updateSliderReadouts();

const supportsMedia = () => "MediaRecorder" in window && navigator.mediaDevices?.getUserMedia;

const startRecording = async () => {
  if (!supportsMedia()) {
    alert("Dein Browser unterstützt kein MediaRecorder API.");
    return;
  }
  try {
    stream = await navigator.mediaDevices.getUserMedia({ audio: true });
    chunks = [];
    mediaRecorder = new MediaRecorder(stream);
    mediaRecorder.ondataavailable = (e) => {
      if (e.data.size > 0) chunks.push(e.data);
    };
    mediaRecorder.onstop = () => {
      const blob = new Blob(chunks, { type: "audio/webm" });
      chunks = [];
      stream.getTracks().forEach((t) => t.stop());
      submitClip(blob);
    };
    mediaRecorder.start();
    isRecording = true;
    dom.recordLabel.textContent = "Aufnahme stoppen";
    dom.recordBtn.classList.add("active");
    setStatus("Aufnahme läuft", "recording");
  } catch (err) {
    console.error(err);
    alert("Konnte Mikrofon nicht starten: " + err.message);
  }
};

const stopRecording = () => {
  if (!mediaRecorder || !isRecording) return;
  isRecording = false;
  dom.recordLabel.textContent = "Aufnahme starten";
  dom.recordBtn.classList.remove("active");
  setStatus("Sende an Backend...", "busy");
  mediaRecorder.stop();
};

dom.recordBtn.addEventListener("click", () => {
  if (isRecording) {
    stopRecording();
  } else {
    startRecording();
  }
});

dom.refreshHistory.addEventListener("click", () => {
  loadHistory();
});

const openSettingsHelp = () => {
  dom.settingsHelpModal.hidden = false;
  dom.settingsHelpClose.focus();
};

const closeSettingsHelp = () => {
  dom.settingsHelpModal.hidden = true;
  dom.settingsHelpButton.focus();
};

dom.settingsHelpButton.addEventListener("click", openSettingsHelp);
dom.settingsHelpClose.addEventListener("click", closeSettingsHelp);
dom.settingsHelpModal.addEventListener("click", (event) => {
  if (event.target === dom.settingsHelpModal) closeSettingsHelp();
});

document.addEventListener("keydown", (event) => {
  if (event.key === "Escape" && !dom.settingsHelpModal.hidden) {
    closeSettingsHelp();
  }
});

const submitClip = async (blob) => {
  setStatus("Verarbeite Audio...", "busy");
  const form = new FormData();
  form.append("audio", blob, "voice.webm");
  const seedVal = dom.seedInput.value.trim();
  const params = new URLSearchParams({
    steps: dom.stepsInput.value,
    cfg: dom.cfgInput.value,
  });
  if (seedVal) params.set("seed", seedVal);

  try {
    const res = await fetch(`/api/generate?${params.toString()}`, {
      method: "POST",
      body: form,
    });

    if (!res.ok) {
      const error = await res.json().catch(() => ({}));
      throw new Error(error.detail || "Fehler beim Generieren.");
    }
    const data = await res.json();
    prependHistory(data);
    renderLatest(data);
    setStatus("Fertig", "idle");
  } catch (err) {
    console.error(err);
    setStatus("Fehler: " + err.message, "idle");
    alert(err.message);
  }
};

const prependHistory = (item) => {
  historyItems = [item, ...historyItems];
  renderHistory();
  renderGallery();
};

const renderLatest = (item) => {
  if (!item) {
    clearLatest();
    return;
  }
  dom.transcriptBox.textContent = item.transcript || "Keine Transkription.";
  dom.promptBox.textContent = item.prompt || "Kein Prompt.";
  dom.latestImage.src = item.image_url;
  dom.latestImage.dataset.imageId = item.id;
  dom.imageMeta.textContent = `Steps ${item.steps} • CFG ${item.cfg} • Seed ${item.seed ?? "random"} • ${item.created_at}`;
};

const clearLatest = () => {
  dom.transcriptBox.textContent = "Noch nichts aufgenommen.";
  dom.promptBox.textContent = "Warte auf Ergebnis.";
  dom.latestImage.removeAttribute("src");
  delete dom.latestImage.dataset.imageId;
  dom.imageMeta.textContent = "Noch kein Bild generiert.";
};

const renderGallery = () => {
  dom.galleryGrid.innerHTML = "";
  historyItems.forEach((item) => {
    const fig = document.createElement("figure");
    const img = document.createElement("img");
    img.src = item.image_url;
    img.alt = item.prompt.slice(0, 120);
    const caption = document.createElement("figcaption");
    caption.textContent = item.prompt.slice(0, 120);
    const deleteButton = document.createElement("button");
    deleteButton.className = "gallery-delete";
    deleteButton.type = "button";
    deleteButton.textContent = "×";
    deleteButton.title = "Bild löschen";
    deleteButton.setAttribute("aria-label", "Bild löschen");
    deleteButton.dataset.deleteImageId = item.id;
    fig.appendChild(img);
    fig.appendChild(deleteButton);
    fig.appendChild(caption);
    dom.galleryGrid.appendChild(fig);
  });
  dom.historyCount.textContent = `${historyItems.length} Items`;
};

const renderHistory = () => {
  dom.historyList.innerHTML = "";
  historyItems.forEach((item) => {
    const wrap = document.createElement("div");
    wrap.className = "item";
    const meta = document.createElement("div");
    meta.className = "meta";
    meta.textContent = `${item.created_at} — Steps ${item.steps} • CFG ${item.cfg} • Seed ${item.seed ?? "random"}`;
    const p = document.createElement("p");
    p.className = "prompt";
    p.textContent = item.prompt;
    wrap.appendChild(meta);
    wrap.appendChild(p);
    dom.historyList.appendChild(wrap);
  });
};

const loadHistory = async () => {
  try {
    const res = await fetch("/api/history");
    if (!res.ok) throw new Error("History konnte nicht geladen werden.");
    const data = await res.json();
    historyItems = data.items || [];
    renderHistory();
    renderGallery();
    if (historyItems.length) renderLatest(historyItems[0]);
    setStatus("History geladen", "idle");
  } catch (err) {
    console.error(err);
    setStatus("Konnte History nicht laden", "idle");
  }
};

window.voice2imageGallery = {
  findItem(imageId) {
    return historyItems.find((item) => item.id === imageId);
  },
  removeItem(imageId) {
    const wasLatest = dom.latestImage.dataset.imageId === imageId;
    historyItems = historyItems.filter((item) => item.id !== imageId);
    renderHistory();
    renderGallery();
    if (wasLatest) renderLatest(historyItems[0]);
  },
  setStatus,
};

loadHistory();