(() => { const IMAGE_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".ico", ".bmp"]); const MD_EXTS = /* @__PURE__ */ new Set([".md", ".markdown", ".mdown"]); const DOWNLOAD_EXTS = /* @__PURE__ */ new Set([ ".docx", ".doc", ".xlsx", ".xls", ".pptx", ".ppt", ".odt", ".ods", ".odp", ".pdf", ".zip", ".tar", ".gz", ".bz2", ".7z", ".rar", ".mp3", ".mp4", ".wav", ".m4a", ".ogg", ".flac", ".mov", ".avi", ".mkv", ".webm", ".exe", ".dmg", ".pkg", ".deb", ".rpm", ".woff", ".woff2", ".ttf", ".otf", ".eot", ".bin", ".dat", ".db", ".sqlite", ".pyc", ".class", ".so", ".dylib", ".dll" ]); window.IMAGE_EXTS = IMAGE_EXTS; window.MD_EXTS = MD_EXTS; window.DOWNLOAD_EXTS = DOWNLOAD_EXTS; function fileExt(p) { const i = p.lastIndexOf("."); return i >= 0 ? p.slice(i).toLowerCase() : ""; } window.fileExt = fileExt; async function api(path, opts = {}) { const rel = path.startsWith("/") ? path.slice(1) : path; const url = new URL(rel, location.href); const res = await fetch(url.href, { credentials: "include", headers: { "Content-Type": "application/json" }, ...opts }); if (!res.ok) { const text = await res.text(); try { const j = JSON.parse(text); throw new Error(j.error || j.message || text); } catch (e) { if (e instanceof SyntaxError) throw new Error(text); throw e; } } const ct = res.headers.get("content-type") || ""; return ct.includes("application/json") ? res.json() : res.text(); } window.api = api; function _wsExpandKey() { const ws = S.session && S.session.workspace; return ws ? "hermes-webui-expanded:" + ws : null; } function _saveExpandedDirs() { const key = _wsExpandKey(); if (!key) return; try { localStorage.setItem(key, JSON.stringify([...S._expandedDirs || /* @__PURE__ */ new Set()])); } catch (e) { } } function _restoreExpandedDirs() { const key = _wsExpandKey(); if (!key) { S._expandedDirs = /* @__PURE__ */ new Set(); return; } try { const raw = localStorage.getItem(key); S._expandedDirs = raw ? new Set(JSON.parse(raw)) : /* @__PURE__ */ new Set(); } catch (e) { S._expandedDirs = /* @__PURE__ */ new Set(); } } async function loadDir(path) { if (!S.session) return; try { if (!path || path === ".") { S._dirCache = {}; _restoreExpandedDirs(); } S.currentDir = path || "."; const data = await api(`/api/list?session_id=${encodeURIComponent(S.session.session_id)}&path=${encodeURIComponent(path)}`); S.entries = data.entries || []; renderBreadcrumb(); renderFileTree(); if (!path || path === ".") { for (const dirPath of S._expandedDirs || []) { if (!S._dirCache[dirPath]) { try { const dc = await api(`/api/list?session_id=${encodeURIComponent(S.session.session_id)}&path=${encodeURIComponent(dirPath)}`); S._dirCache[dirPath] = dc.entries || []; } catch (e2) { S._dirCache[dirPath] = []; } } } if (S._expandedDirs && S._expandedDirs.size > 0) renderFileTree(); } if (typeof clearPreview === "function") { if (typeof _previewDirty !== "undefined" && _previewDirty) { showConfirmDialog({ title: t("unsaved_confirm"), message: "", confirmLabel: "Discard", danger: true, focusCancel: true }).then((ok) => { if (ok) clearPreview(); }); } else { clearPreview(); } } if (!path || path === ".") _refreshGitBadge(); } catch (e) { console.warn("loadDir", e); } } async function _refreshGitBadge() { const badge = $("gitBadge"); if (!badge || !S.session) return; try { const data = await api(`/api/git-info?session_id=${encodeURIComponent(S.session.session_id)}`); if (data.git && data.git.is_git) { const g = data.git; let text = g.branch || "git"; if ((g.dirty ?? 0) > 0) text += ` \xB7 ${g.dirty}\u2206`; if ((g.behind ?? 0) > 0) text += ` \u2193${g.behind}`; if ((g.ahead ?? 0) > 0) text += ` \u2191${g.ahead}`; badge.textContent = text; badge.className = "git-badge" + ((g.dirty ?? 0) > 0 ? " dirty" : ""); badge.style.display = ""; } else { badge.style.display = "none"; badge.textContent = ""; } } catch (e) { if (badge) badge.style.display = "none"; } } function navigateUp() { if (!S.session || S.currentDir === ".") return; const parts = S.currentDir.split("/"); parts.pop(); loadDir(parts.length ? parts.join("/") : "."); } let _previewCurrentPath = ""; let _previewCurrentMode = ""; let _previewDirty = false; let _previewRawContent = ""; function showPreview(mode) { const codeEl = $("previewCode"); const imgWrap = $("previewImgWrap"); const mdEl = $("previewMd"); const editEl = $("previewEditArea"); const badge = $("previewBadge"); if (codeEl) codeEl.style.display = mode === "code" ? "" : "none"; if (imgWrap) imgWrap.style.display = mode === "image" ? "" : "none"; if (mdEl) mdEl.style.display = mode === "md" ? "" : "none"; if (editEl) editEl.style.display = "none"; if (badge) { badge.className = "preview-badge " + mode; const pathText = $("previewPathText"); badge.textContent = mode === "image" ? "image" : mode === "md" ? "md" : fileExt(pathText?.textContent || ""); } _previewCurrentMode = mode; _previewDirty = false; updateEditBtn(); } function updateEditBtn() { const btn = $("btnEditFile"); if (!btn) return; const editable = _previewCurrentMode === "code" || _previewCurrentMode === "md"; btn.style.display = editable ? "" : "none"; const editing = $("previewEditArea")?.style.display !== "none"; btn.innerHTML = editing ? `💾 ${t("save")}` : `✎ ${t("edit")}`; btn.title = editing ? t("save_title") : t("edit_title"); btn.style.color = editing ? "var(--blue)" : ""; if (_previewDirty) btn.innerHTML = "💾 Save*"; } async function toggleEditMode() { const editEl = $("previewEditArea"); const editing = editEl && editEl.style.display !== "none"; if (editing) { if (!S.session || !_previewCurrentPath) return; const content = editEl.value; try { await api("/api/file/save", { method: "POST", body: JSON.stringify({ session_id: S.session.session_id, path: _previewCurrentPath, content }) }); _previewDirty = false; const codeEl = $("previewCode"); const mdEl = $("previewMd"); if (_previewCurrentMode === "code" && codeEl) codeEl.textContent = content; else if (mdEl) { mdEl.innerHTML = renderMd(content); requestAnimationFrame(() => { if (typeof renderKatexBlocks === "function") renderKatexBlocks(); }); } if (editEl) editEl.style.display = "none"; if (codeEl && _previewCurrentMode === "code") codeEl.style.display = ""; else if (mdEl) mdEl.style.display = ""; showToast(t("saved")); } catch (e) { setStatus(t("save_failed") + (e instanceof Error ? e.message : String(e))); } } else { const currentText = _previewCurrentMode === "code" ? $("previewCode")?.textContent || "" : _previewRawContent || ""; if (editEl) { editEl.value = currentText; editEl.style.display = ""; } const codeEl = $("previewCode"); const mdEl = $("previewMd"); if (codeEl && _previewCurrentMode === "code") codeEl.style.display = "none"; else if (mdEl) mdEl.style.display = "none"; editEl.onkeydown = (e) => { if (e.key === "Escape") { e.preventDefault(); cancelEditMode(); } }; } updateEditBtn(); } function cancelEditMode() { const editEl = $("previewEditArea"); if (editEl) { editEl.style.display = "none"; editEl.onkeydown = null; } const codeEl = $("previewCode"); const mdEl = $("previewMd"); if (codeEl && _previewCurrentMode === "code") codeEl.style.display = ""; else if (mdEl) mdEl.style.display = ""; _previewDirty = false; updateEditBtn(); } async function openFile(path) { if (!S.session) return; const ext = fileExt(path); if (DOWNLOAD_EXTS.has(ext)) { downloadFile(path); return; } const previewPathText = $("previewPathText"); const previewArea = $("previewArea"); const fileTree = $("fileTree"); const wsSearch = $("wsSearchWrap"); if (previewPathText) previewPathText.textContent = path; previewArea?.classList.add("visible"); if (fileTree) fileTree.style.display = "none"; if (wsSearch) wsSearch.style.display = "none"; _previewCurrentPath = path; renderFileBreadcrumb(path); if (IMAGE_EXTS.has(ext)) { showPreview("image"); const url = `api/file/raw?session_id=${encodeURIComponent(S.session.session_id)}&path=${encodeURIComponent(path)}`; const previewImg = $("previewImg"); if (previewImg) { previewImg.alt = path; previewImg.src = url; previewImg.onerror = () => setStatus(t("image_load_failed")); } } else if (MD_EXTS.has(ext)) { try { const data = await api(`/api/file?session_id=${encodeURIComponent(S.session.session_id)}&path=${encodeURIComponent(path)}`); showPreview("md"); _previewRawContent = data.content || ""; const mdEl = $("previewMd"); if (mdEl) mdEl.innerHTML = renderMd(data.content || ""); requestAnimationFrame(() => { if (typeof renderKatexBlocks === "function") renderKatexBlocks(); }); } catch (e) { setStatus(t("file_open_failed")); } } else { try { const data = await api(`/api/file?session_id=${encodeURIComponent(S.session.session_id)}&path=${encodeURIComponent(path)}`); if (data.binary) { downloadFile(path); return; } showPreview("code"); const content = data.content || ""; const codeEl = $("previewCode"); if (["yml", "yaml"].includes(ext)) { if (codeEl) { codeEl.className = "preview-code hl-yaml"; codeEl.innerHTML = typeof highlightYAML === "function" ? highlightYAML(content) : _highlightWithLineNumbers(content); } } else if (ext === "json") { if (codeEl) { codeEl.className = "preview-code hl-json"; codeEl.innerHTML = _highlightJSON(content); } } else if (["py", "js", "ts", "sh", "bash", "zsh", "rb", "go", "rs", "java", "c", "cpp", "h", "css", "scss", "html", "xml", "sql", "r", "lua", "pl", "php", "swift", "kt", "dart"].includes(ext)) { if (codeEl) { codeEl.className = "preview-code"; codeEl.textContent = content; requestAnimationFrame(() => { if (typeof highlightCode === "function") highlightCode(); }); } } else { if (codeEl) { codeEl.className = "preview-code hl-text"; codeEl.innerHTML = _highlightWithLineNumbers(content); } } } catch (e) { downloadFile(path); } } } function downloadFile(path) { if (!S.session) return; const url = `api/file/raw?session_id=${encodeURIComponent(S.session.session_id)}&path=${encodeURIComponent(path)}&download=1`; const filename = path.split("/").pop() || path; const a = document.createElement("a"); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); setTimeout(() => document.body.removeChild(a), 100); showToast(t("downloading", filename), 2e3); } function renderFileBreadcrumb(filePath) { const bar = $("breadcrumbBar"); if (!bar) return; bar.style.display = "flex"; const upBtn = $("btnUpDir"); if (upBtn) upBtn.style.display = ""; bar.innerHTML = ""; const root = document.createElement("span"); root.className = "breadcrumb-seg breadcrumb-link"; root.textContent = "~"; root.onclick = () => { clearPreview(); loadDir("."); }; bar.appendChild(root); const parts = filePath.split("/"); let accumulated = ""; for (let i = 0; i < parts.length; i++) { const sep = document.createElement("span"); sep.className = "breadcrumb-sep"; sep.textContent = "/"; bar.appendChild(sep); accumulated += (accumulated ? "/" : "") + parts[i]; const seg = document.createElement("span"); seg.textContent = parts[i]; if (i < parts.length - 1) { seg.className = "breadcrumb-seg breadcrumb-link"; const target = accumulated; seg.onclick = () => { clearPreview(); loadDir(target); }; } else { seg.className = "breadcrumb-seg breadcrumb-current"; } bar.appendChild(seg); } } function _escHtml(s) { return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } function _highlightWithLineNumbers(text) { return text.split("\n").map((line) => '' + _escHtml(line) + "").join("\n"); } function _highlightJSON(text) { try { const pretty = JSON.stringify(JSON.parse(text), null, 2); return pretty.split("\n").map((raw) => { let line = _escHtml(raw); line = line.replace(/^(\s*)(")([\w\s.\/\-_@:]+)(")(\s*:)/, '$1$2$3$4$5'); line = line.replace(/(:\s*)("[^&]*?")/g, '$1$2'); line = line.replace(/:\s*(\d+\.?\d*)/g, ': $1'); line = line.replace(/:\s*(true|false|null)/g, ': $1'); return '' + line + ""; }).join("\n"); } catch { return _highlightWithLineNumbers(text); } } let _wsSearchTimer = null; function filterWsFiles() { clearTimeout(_wsSearchTimer ?? void 0); _wsSearchTimer = setTimeout(_doWsSearch, 300); } async function _doWsSearch() { const input = $("wsSearchInput"); const clearBtn = $("wsSearchClear"); const tree = $("fileTree"); if (!input || !tree) return; const query = input.value.trim().toLowerCase(); if (clearBtn) clearBtn.classList.toggle("visible", query.length > 0); const oldNoRes = tree.querySelector(".ws-no-results"); if (oldNoRes) oldNoRes.remove(); if (!query) { if (typeof renderFileTree === "function") renderFileTree(); return; } if (!S.session || !S.session.workspace) return; tree.innerHTML = '