This repository has been archived on 2026-04-03. You can view files and clone it, but cannot push or open issues or pull requests.
Nico bf047d1292 Add company pages + HTML5 history mode
- CompanyView.vue: hero, plattform, produkte, nyx CTA, footer
- ImpressumView.vue + DatenschutzView.vue: legal pages
- Router: HTML5 history mode (no # URLs), company routes
- Reverted vite-ssg (SSR compat needs proper refactor, planned)
- Removed ssr-shim.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 00:17:37 +02:00

2891 lines
108 KiB
JavaScript

import { createHead } from "@unhead/vue/server";
import { defineComponent, ref, onMounted, createSSRApp, computed, watch, reactive, nextTick, unref, mergeProps, useSSRContext, onUnmounted, resolveComponent, withCtx, createVNode, resolveDynamicComponent, openBlock, createBlock, toDisplayString, createCommentVNode, defineAsyncComponent } from "vue";
import { createRouter, createMemoryHistory, useRouter, useRoute } from "vue-router";
import { defineStore, createPinia } from "pinia";
import { ssrRenderAttrs, ssrRenderList, ssrRenderClass, ssrRenderStyle, ssrRenderComponent, ssrInterpolate, ssrRenderAttr, ssrRenderVNode, ssrIncludeBooleanAttr } from "vue/server-renderer";
import { CubeTransparentIcon, SunIcon, CommandLineIcon } from "@heroicons/vue/24/outline";
import { ChevronDownIcon, ChevronRightIcon, DocumentIcon, ChevronLeftIcon, ChatBubbleLeftRightIcon, FolderIcon, FolderOpenIcon, WifiIcon, SignalIcon, CodeBracketIcon, UserCircleIcon, ArrowRightEndOnRectangleIcon } from "@heroicons/vue/20/solid";
import { OverlayScrollbarsComponent } from "overlayscrollbars-vue";
import { OverlayScrollbars, ClickScrollPlugin } from "overlayscrollbars";
if (typeof window === "undefined") {
const noop = () => {
};
const storage = { getItem: () => null, setItem: noop, removeItem: noop, length: 0, clear: noop, key: () => null };
globalThis.window = {
location: { protocol: "https:", hostname: "", port: "", hash: "", pathname: "/", origin: "", search: "", href: "" },
addEventListener: noop,
removeEventListener: noop,
__hermes: {},
matchMedia: () => ({ matches: false, addEventListener: noop, removeEventListener: noop }),
innerWidth: 1280,
innerHeight: 800,
open: noop,
getComputedStyle: () => ({})
};
globalThis.localStorage = storage;
globalThis.sessionStorage = storage;
globalThis.document = {
querySelector: () => null,
querySelectorAll: () => [],
createElement: () => ({ style: {}, getContext: () => null, addEventListener: noop }),
title: "",
head: { appendChild: noop },
body: { appendChild: noop },
documentElement: { setAttribute: noop, removeAttribute: noop, style: {} }
};
try {
globalThis.navigator = { mediaDevices: {}, clipboard: {}, userAgent: "", platform: "" };
} catch {
}
globalThis.HTMLElement = class {
};
globalThis.MediaStream = class {
};
globalThis.WebSocket = class {
send() {
}
close() {
}
};
globalThis.AudioContext = class {
};
globalThis.MediaRecorder = class {
};
globalThis.MutationObserver = class {
observe() {
}
disconnect() {
}
};
globalThis.ResizeObserver = class {
observe() {
}
disconnect() {
}
};
globalThis.IntersectionObserver = class {
observe() {
}
disconnect() {
}
};
}
const ClientOnly = defineComponent({
setup(props, { slots }) {
const mounted = ref(false);
onMounted(() => mounted.value = true);
return () => {
if (!mounted.value)
return slots.placeholder && slots.placeholder({});
return slots.default && slots.default({});
};
}
});
function ViteSSG(App, routerOptions, fn, options) {
const {
transformState,
registerComponents = true,
useHead = true,
rootContainer = "#app"
} = {};
async function createApp$1(routePath) {
const app = createSSRApp(App);
let head;
if (useHead) {
app.use(head = createHead());
}
const router = createRouter({
history: createMemoryHistory(routerOptions.base),
...routerOptions
});
const { routes: routes2 } = routerOptions;
if (registerComponents)
app.component("ClientOnly", ClientOnly);
const appRenderCallbacks = [];
const onSSRAppRendered = (cb) => appRenderCallbacks.push(cb);
const triggerOnSSRAppRendered = () => {
return Promise.all(appRenderCallbacks.map((cb) => cb()));
};
const context = {
app,
head,
isClient: false,
router,
routes: routes2,
onSSRAppRendered,
triggerOnSSRAppRendered,
initialState: {},
transformState,
routePath
};
await fn?.(context);
app.use(router);
let entryRoutePath;
let isFirstRoute = true;
router.beforeEach((to, from, next) => {
if (isFirstRoute || entryRoutePath && entryRoutePath === to.path) {
isFirstRoute = false;
entryRoutePath = to.path;
to.meta.state = context.initialState;
}
next();
});
{
const route = context.routePath ?? "/";
router.push(route);
await router.isReady();
context.initialState = router.currentRoute.value.meta.state || {};
}
const initialState = context.initialState;
return {
...context,
initialState
};
}
return createApp$1;
}
const version = "0.1.0";
const pkg = {
version
};
function useUI(status2) {
const version2 = `${pkg.version}-${"mnf5vvcg"}`;
const statusClass = computed(() => {
if (status2.value.includes("Connected")) return "connected";
if (status2.value.includes("Connecting")) return "connecting";
if (status2.value.includes("Error")) return "error";
return "";
});
return { version: version2, statusClass };
}
const _ssrFallback = {};
function useHermes() {
if (typeof window === "undefined") return _ssrFallback;
const w = window;
if (!w.__hermes) w.__hermes = {};
return w.__hermes;
}
const H$1 = useHermes();
function getStream() {
return H$1.captureStream || null;
}
function setStream(s) {
H$1.captureStream = s;
}
const isActive = ref(false);
syncState();
function syncState() {
const stream = getStream();
isActive.value = !!(stream && stream.active && stream.getVideoTracks().some((t) => t.readyState === "live"));
if (!isActive.value && stream) {
stream.getTracks().forEach((t) => t.stop());
setStream(null);
}
}
function useCapture() {
async function enable() {
const stream = getStream();
if (stream && stream.active) {
syncState();
if (isActive.value) return { enabled: true };
}
try {
const newStream = await navigator.mediaDevices.getDisplayMedia({
video: { displaySurface: "browser" },
preferCurrentTab: true
});
newStream.getVideoTracks()[0].onended = () => {
setStream(null);
syncState();
};
setStream(newStream);
syncState();
return { enabled: true };
} catch (e) {
setStream(null);
syncState();
return { enabled: false, error: `Capture denied: ${e.message}` };
}
}
function disable() {
const stream = getStream();
if (stream) {
stream.getTracks().forEach((t) => t.stop());
setStream(null);
}
syncState();
}
async function capture(quality = 0.7) {
syncState();
const stream = getStream();
if (!stream) return { error: "Capture not enabled -- send enableCapture first" };
const track = stream.getVideoTracks()[0];
if (!track || track.readyState !== "live") {
setStream(null);
syncState();
return { error: "Capture stream ended -- re-enable with enableCapture" };
}
const video = document.createElement("video");
video.srcObject = stream;
video.muted = true;
await video.play();
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext("2d").drawImage(video, 0, 0);
video.pause();
video.srcObject = null;
return new Promise((resolve) => {
canvas.toBlob((blob) => {
if (!blob) {
resolve({ error: "Canvas toBlob failed" });
return;
}
const reader = new FileReader();
reader.onloadend = () => {
const dataUrl = reader.result;
resolve({ dataUrl, length: dataUrl.length });
};
reader.readAsDataURL(blob);
}, "image/jpeg", quality);
});
}
function healthCheck() {
const stream = getStream();
if (!stream) return { active: false, tracks: 0, reason: "no stream" };
const tracks = stream.getVideoTracks();
const live = tracks.filter((t) => t.readyState === "live");
if (!stream.active || live.length === 0) {
setStream(null);
syncState();
return { active: false, tracks: 0, reason: "stream ended" };
}
return { active: true, tracks: live.length };
}
return { isActive, enable, disable, capture, healthCheck };
}
const windows = /* @__PURE__ */ new Map();
const PRESETS = {
mobile: [375, 812],
tablet: [768, 1024],
"tablet-landscape": [1024, 768],
desktop: [1280, 800]
};
function deriveToken(parentToken, name) {
return `${parentToken}-breakout-${name}`;
}
function useBreakout() {
const pendingRequest = ref(null);
async function open(args) {
const parentToken = sessionStorage.getItem("hermes_takeover_token");
if (!parentToken) return { error: "No takeover token active" };
const name = args.name || "mobile";
const preset = args.preset || "desktop";
const existing = windows.get(name);
if (existing && !existing.closed) existing.close();
const nonce = Math.random().toString(36).slice(2, 6);
const [pw, ph] = args.w && args.h ? [args.w, args.h] : PRESETS[preset] || PRESETS.desktop;
const presetLabel = `${pw}x${ph}`;
return new Promise((resolve) => {
pendingRequest.value = {
name,
preset: presetLabel,
nonce,
resolve: (confirmed) => {
pendingRequest.value = null;
if (!confirmed) {
resolve({ error: "rejected by user" });
return;
}
const token = deriveToken(parentToken, name);
const url = `${window.location.origin}${window.location.pathname}?breakout_token=${token}/agents`;
const popup = window.open(url, `hermes_breakout_${name}`, `width=${pw},height=${ph},resizable=yes,scrollbars=yes`);
if (!popup) {
resolve({ error: "popup blocked" });
return;
}
windows.set(name, popup);
resolve({ opened: name, token, size: presetLabel });
}
};
});
}
function openDirect(name, presetStr, parentToken) {
const [w, h] = presetStr.split("x").map(Number);
const token = deriveToken(parentToken, name);
const url = `${window.location.origin}${window.location.pathname}?breakout_token=${token}/agents`;
const popup = window.open(url, `hermes_breakout_${name}`, `width=${w},height=${h},resizable=yes,scrollbars=yes`);
if (!popup) {
alert("Popup blocked -- allow popups for this site");
return;
}
windows.set(name, popup);
}
function list() {
const result = {};
for (const [name, win] of windows) {
result[name] = { alive: !win.closed };
if (win.closed) windows.delete(name);
}
return result;
}
function close(args) {
const win = windows.get(args.name);
if (!win) return { error: `No breakout: ${args.name}` };
if (!win.closed) win.close();
windows.delete(args.name);
return { closed: args.name };
}
return { windows, pendingRequest, open, openDirect, list, close };
}
const TAKEOVER_KEY = "hermes_takeover_token";
let currentToken = typeof sessionStorage !== "undefined" ? sessionStorage.getItem(TAKEOVER_KEY) || "" : "";
function useTakeover(wsSend) {
const token = ref(currentToken);
const capture = useCapture();
const breakout = useBreakout();
function init() {
const t = crypto.randomUUID();
token.value = t;
currentToken = t;
sessionStorage.setItem(TAKEOVER_KEY, t);
wsSend({ type: "dev_takeover", token: t });
return t;
}
function revoke() {
token.value = "";
currentToken = "";
sessionStorage.removeItem(TAKEOVER_KEY);
}
function reregister() {
const urlParams = new URLSearchParams(window.location.search);
const breakoutToken = urlParams.get("breakout_token");
if (breakoutToken) {
sessionStorage.setItem(TAKEOVER_KEY, breakoutToken);
urlParams.delete("breakout_token");
const clean = urlParams.toString();
const newUrl = window.location.pathname + (clean ? "?" + clean : "") + window.location.hash;
window.history.replaceState(null, "", newUrl);
}
const t = sessionStorage.getItem(TAKEOVER_KEY);
if (t) {
token.value = t;
currentToken = t;
wsSend({ type: "dev_takeover", token: t });
}
}
function boxChain(args) {
const el = document.querySelector(args.selector);
if (!el) return { error: `No element: ${args.selector}` };
const chain = [];
let node = el;
while (node && node !== document.documentElement) {
const cs = getComputedStyle(node);
const rect = node.getBoundingClientRect();
chain.push({
tag: node.tagName.toLowerCase(),
cls: (node.className?.toString() || "").split(" ").filter(Boolean).slice(0, 5).join(" "),
w: Math.round(rect.width),
h: Math.round(rect.height),
l: Math.round(rect.left),
r: Math.round(rect.right),
pl: cs.paddingLeft,
pr: cs.paddingRight,
ml: cs.marginLeft,
mr: cs.marginRight
});
node = node.parentElement;
}
return chain;
}
function getStyles(args) {
const el = document.querySelector(args.selector);
if (!el) return { error: `No element: ${args.selector}` };
const cs = getComputedStyle(el);
const rect = el.getBoundingClientRect();
const props = args.props || ["padding", "margin", "width", "height", "display", "flexDirection", "overflow", "gap"];
const styles = {};
for (const p of props) styles[p] = cs.getPropertyValue(p.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()));
return { ...styles, boundingRect: { w: Math.round(rect.width), h: Math.round(rect.height), l: Math.round(rect.left), r: Math.round(rect.right), t: Math.round(rect.top), b: Math.round(rect.bottom) } };
}
function viewport() {
return { w: window.innerWidth, h: window.innerHeight, dpr: window.devicePixelRatio, hash: window.location.hash };
}
function navigate(args) {
window.location.hash = args.hash;
return { navigated: args.hash };
}
function reload() {
setTimeout(() => window.location.reload(), 100);
return { reloading: true };
}
function resize(args) {
window.resizeTo(args.w, args.h);
return { resized: `${args.w}x${args.h}` };
}
function querySelector(args) {
const els = document.querySelectorAll(args.selector);
const limit = args.limit || 10;
return Array.from(els).slice(0, limit).map((el, i) => {
const rect = el.getBoundingClientRect();
return {
i,
tag: el.tagName.toLowerCase(),
cls: (el.className?.toString() || "").split(" ").filter(Boolean).slice(0, 5).join(" "),
text: (el.textContent || "").slice(0, 60),
w: Math.round(rect.width),
h: Math.round(rect.height),
l: Math.round(rect.left),
t: Math.round(rect.top)
};
});
}
function click(args) {
const els = document.querySelectorAll(args.selector);
const idx = args.index || 0;
const el = els[idx];
if (!el) return { error: `No element: ${args.selector}[${idx}]` };
el.click();
return { clicked: args.selector, index: idx };
}
function getValue(args) {
const el = document.querySelector(args.selector);
if (!el) return { error: `No element: ${args.selector}` };
return { value: el.value, tag: el.tagName.toLowerCase() };
}
function setValue(args) {
const el = document.querySelector(args.selector);
if (!el) return { error: `No element: ${args.selector}` };
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
el.tagName === "SELECT" ? HTMLSelectElement.prototype : HTMLInputElement.prototype,
"value"
)?.set;
nativeInputValueSetter?.call(el, args.value);
el.dispatchEvent(new Event("input", { bubbles: true }));
el.dispatchEvent(new Event("change", { bubbles: true }));
return { set: args.value, selector: args.selector };
}
function typeText(args) {
const el = document.querySelector(args.selector);
if (!el) return { error: `No element: ${args.selector}` };
el.focus();
el.value = args.text;
el.dispatchEvent(new Event("input", { bubbles: true }));
return { typed: args.text, selector: args.selector };
}
function screenshot() {
return {
url: window.location.hash,
title: document.title,
viewport: { w: window.innerWidth, h: window.innerHeight },
body: document.body.innerText.slice(0, 2e3)
};
}
function scroll(args) {
const el = document.querySelector(args.selector);
if (!el) return { error: `No element: ${args.selector}` };
const before = el.scrollTop;
if (args.to !== void 0) {
if (args.to === "top") el.scrollTop = 0;
else if (args.to === "bottom") el.scrollTop = el.scrollHeight;
else if (args.to === "middle") el.scrollTop = (el.scrollHeight - el.clientHeight) / 2;
else el.scrollTop = args.to;
}
return {
scrollTop: el.scrollTop,
scrollHeight: el.scrollHeight,
clientHeight: el.clientHeight,
before
};
}
function getConsole(args) {
const h = useHermes();
if (!h.console?.length && !h._origConsole) return { error: "Console hook not initialized" };
let entries = h.console;
if (args.level) entries = entries.filter((e) => e.l === args.level);
if (args.pattern) {
const re = new RegExp(args.pattern, "i");
entries = entries.filter((e) => re.test(e.m));
}
const result = entries.slice(-(args.last || 50));
if (args.clear) h.console.length = 0;
return result;
}
const autoCommands = {
boxChain,
getStyles,
viewport,
navigate,
reload,
resize,
querySelector,
click,
screenshot,
getValue,
setValue,
typeText,
scroll,
getConsole,
listBreakouts: () => breakout.list(),
closeBreakout: (args) => breakout.close(args)
};
const asyncCommands = {
captureScreen: (args) => capture.capture(args.quality),
enableCapture: () => capture.enable(),
openBreakout: (args) => breakout.open(args)
};
function dispatch(cmdId, cmd, args, sendResult) {
const fn = autoCommands[cmd];
if (fn) {
try {
const result = fn(args);
sendResult({ type: "dev_cmd_result", cmdId, result: JSON.parse(JSON.stringify(result ?? null)) });
} catch (err) {
sendResult({ type: "dev_cmd_result", cmdId, error: err.message });
}
return;
}
const asyncFn = asyncCommands[cmd];
if (asyncFn) {
asyncFn(args).then((result) => {
sendResult({ type: "dev_cmd_result", cmdId, result: JSON.parse(JSON.stringify(result ?? null)) });
}).catch((err) => {
sendResult({ type: "dev_cmd_result", cmdId, error: err.message });
});
return;
}
if (cmd === "eval") {
const js = args.js;
if (!js) {
sendResult({ type: "dev_cmd_result", cmdId, error: "js required" });
return;
}
const ok = window.confirm(`Dev takeover eval request:
${js.slice(0, 500)}
Allow?`);
if (!ok) {
sendResult({ type: "dev_cmd_result", cmdId, error: "rejected by user" });
return;
}
try {
const result = new Function("return (" + js + ")")();
const serialized = result instanceof Element ? result.outerHTML.slice(0, 500) : JSON.parse(JSON.stringify(result ?? null));
sendResult({ type: "dev_cmd_result", cmdId, result: serialized });
} catch (err) {
sendResult({ type: "dev_cmd_result", cmdId, error: err.message });
}
return;
}
sendResult({ type: "dev_cmd_result", cmdId, error: `Unknown command: ${cmd}` });
}
return {
token,
capture,
breakout,
init,
revoke,
reregister,
dispatch
};
}
const H = useHermes();
let _ws = H.ws ?? null;
let _pingInterval = H.wsPing ?? null;
const _onMessageCallbacks = H.wsCbs ?? [];
const _messageBuffer = H.wsBuf ?? [];
let _reconnectTimer = null;
let _reconnectDelay = 1e3;
let _selectedAgentRef = null;
let _selectedModeRef = null;
let _isLoggedInRef = null;
let _loginErrorRef = null;
let _takeover = null;
const connected = H.wsConnected ?? ref(false);
const status = H.wsStatus ?? ref("Disconnected");
const currentUser = H.wsUser ?? ref("");
const sessionId = H.wsSid ?? ref(null);
const isInitialLoad = H.wsInit ?? ref(true);
H.wsConnected = connected;
H.wsStatus = status;
H.wsUser = currentUser;
H.wsSid = sessionId;
H.wsInit = isInitialLoad;
H.wsCbs = _onMessageCallbacks;
H.wsBuf = _messageBuffer;
function send(payload) {
if (_ws && _ws.readyState === WebSocket.OPEN) _ws.send(JSON.stringify(payload));
}
function getTakeover() {
if (!_takeover) _takeover = useTakeover(send);
return _takeover;
}
const DEV_TOKEN$1 = "7Oorb9S3OpwFyWgm4zi_Tq7GeamefbjjTgooPVPWAwPDOf6B4TvgvQlLbhmT4DjsqBS_D1g";
function getWsUrl() {
let base;
{
base = "wss://assay.loop42.de/ws";
}
const token = localStorage.getItem("nyx_session") || localStorage.getItem("titan_token") || DEV_TOKEN$1;
const session = sessionStorage.getItem("nyx_ws_session") || "";
const qs = new URLSearchParams();
qs.set("token", token);
if (session) qs.set("session", session);
return qs.toString() ? `${base}?${qs}` : base;
}
function scheduleReconnect() {
if (_reconnectTimer) return;
if (!_isLoggedInRef?.value) return;
status.value = `Reconnecting in ${Math.round(_reconnectDelay / 1e3)}s…`;
_reconnectTimer = setTimeout(() => {
_reconnectTimer = null;
if (_isLoggedInRef?.value) connect(_selectedAgentRef, _isLoggedInRef, _loginErrorRef);
}, _reconnectDelay);
_reconnectDelay = Math.min(_reconnectDelay * 2, 16e3);
}
function connect(selectedAgent, isLoggedInRef, loginErrorRef, selectedMode) {
if (_ws && _ws.readyState <= WebSocket.OPEN) return;
_selectedAgentRef = selectedAgent;
_selectedModeRef = selectedMode ?? null;
_isLoggedInRef = isLoggedInRef;
_loginErrorRef = loginErrorRef;
_reconnectDelay = 1e3;
console.log("WS CONNECT attempt, ws state:", _ws?.readyState, "url:", getWsUrl());
const wsUrl = getWsUrl();
if (isInitialLoad.value) {
status.value = "Connecting...";
isInitialLoad.value = false;
}
_ws = new WebSocket(wsUrl);
H.ws = _ws;
_ws.onopen = () => {
_reconnectDelay = 1e3;
if (_pingInterval) clearInterval(_pingInterval);
_pingInterval = setInterval(() => {
if (_ws?.readyState === WebSocket.OPEN) _ws.send(JSON.stringify({ type: "ping" }));
}, 3e4);
H.wsPing = _pingInterval;
};
_ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === "dev_cmd" && data.cmdId && data.cmd) {
getTakeover().dispatch(data.cmdId, data.cmd, data.args || {}, send);
return;
}
if (data.type === "session_info") {
sessionId.value = data.session_id;
sessionStorage.setItem("nyx_ws_session", data.session_id);
}
if (data.type === "ready") {
connected.value = true;
status.value = "Connected";
}
if (data.type === "error" && data.code === "SESSION_TERMINATED") {
console.warn("Message bounced: Session terminated.");
}
_messageBuffer.push(data);
_onMessageCallbacks.forEach((fn) => fn(data));
} catch (e) {
console.error("Parse error:", e);
}
};
_ws.onclose = (e) => {
if (_pingInterval) {
clearInterval(_pingInterval);
_pingInterval = null;
}
connected.value = false;
_messageBuffer.length = 0;
if (e.code === 4001) {
isLoggedInRef.value = false;
loginErrorRef.value = "Session expired. Please log in again.";
localStorage.removeItem("nyx_session");
localStorage.removeItem("titan_token");
sessionStorage.removeItem("agent");
status.value = "Logged out";
if (window.location.pathname !== "/login") {
window.location.pathname = "/login";
}
} else {
scheduleReconnect();
}
};
_ws.onerror = () => {
};
}
function disconnect() {
if (_pingInterval) {
clearInterval(_pingInterval);
_pingInterval = null;
H.wsPing = null;
}
if (_ws) {
_ws.onclose = null;
_ws.close();
_ws = null;
H.ws = null;
}
connected.value = false;
status.value = "Disconnected";
currentUser.value = "";
sessionId.value = null;
isInitialLoad.value = true;
}
function sendDeferredAuth() {
}
function switchAgent(agentId, mode) {
_messageBuffer.length = 0;
send({ type: "switch_agent", agent: agentId, mode: mode ?? _selectedModeRef?.value ?? "private" });
}
function clearBuffer() {
_messageBuffer.length = 0;
}
function onMessage(fn) {
_onMessageCallbacks.push(fn);
return () => {
const i = _onMessageCallbacks.indexOf(fn);
if (i !== -1) _onMessageCallbacks.splice(i, 1);
};
}
function replayBuffer(fn) {
_messageBuffer.forEach((data) => fn(data));
}
function useWebSocket() {
return {
connected,
status,
currentUser,
sessionId,
connect,
disconnect,
send,
switchAgent,
sendDeferredAuth,
clearBuffer,
onMessage,
replayBuffer,
getTakeover
};
}
let _cached = null;
function getApiBase() {
if (_cached !== null) return _cached;
const wsUrl = "wss://assay.loop42.de/ws";
try {
const url = new URL(wsUrl);
const proto = url.protocol === "wss:" ? "https:" : "http:";
_cached = `${proto}//${url.host}`;
} catch {
_cached = "";
}
return _cached;
}
const _allAgents = ref([]);
const _initAgent = (() => {
if (typeof window === "undefined") return "";
const url = new URLSearchParams(window.location.hash.split("?")[1] || "").get("agent");
if (url) return url;
const saved = sessionStorage.getItem("agent");
if (saved) return saved;
return "";
})();
const _selectedAgent = ref(_initAgent);
const _initMode = (() => {
if (typeof window === "undefined") return "private";
const url = new URLSearchParams(window.location.hash.split("?")[1] || "").get("mode");
if (url === "public" || url === "private") return url;
const saved = sessionStorage.getItem("agent_mode");
if (saved === "public" || saved === "private") return saved;
return "private";
})();
const _selectedMode = ref(_initMode);
const _defaultAgent = ref("titan");
const _allowedAgentIds = ref([]);
function useAgents(connected2) {
const allAgents = _allAgents;
const selectedAgent = _selectedAgent;
const selectedMode = _selectedMode;
const defaultAgent = _defaultAgent;
const allowedAgentIds = _allowedAgentIds;
const agentModels = ref([]);
const filteredAgents = computed(() => {
if (allowedAgentIds.value.length === 0) return allAgents.value;
return allAgents.value.filter((a) => allowedAgentIds.value.includes(a.id));
});
function updateFromServer(data) {
if (data.agents) {
allAgents.value = data.agents;
}
if (data.defaultAgent) {
_defaultAgent.value = data.defaultAgent;
}
if (data.allowedAgents) {
_allowedAgentIds.value = data.allowedAgents;
}
if (allAgents.value.length === 0) {
allAgents.value = [{ id: "assay", name: "assay", promptPrice: null, completionPrice: null }];
_allowedAgentIds.value = ["assay"];
_defaultAgent.value = "assay";
}
const urlParams = new URLSearchParams(window.location.hash.split("?")[1] || "");
const urlMode = urlParams.get("mode");
const savedMode = sessionStorage.getItem("agent_mode");
if (urlMode === "public" || urlMode === "private") {
_selectedMode.value = urlMode;
} else if (savedMode === "public" || savedMode === "private") {
_selectedMode.value = savedMode;
}
const urlAgent = urlParams.get("agent");
const savedAgent = sessionStorage.getItem("agent");
const isUrlAgentAllowed = urlAgent && _allowedAgentIds.value.includes(urlAgent);
const isSavedAgentAllowed = savedAgent && _allowedAgentIds.value.includes(savedAgent);
if (isUrlAgentAllowed) {
selectedAgent.value = urlAgent;
} else if (isSavedAgentAllowed) {
selectedAgent.value = savedAgent;
} else if (!selectedAgent.value && _defaultAgent.value) {
selectedAgent.value = _defaultAgent.value;
}
}
async function fetchAgentModels() {
try {
const protocol = window.location.protocol === "https:" ? "https:" : "http:";
const host = window.location.hostname;
const sessionToken = localStorage.getItem("nyx_session") ?? "";
const res = await fetch(`${protocol}//${host}/agents`, {
headers: { Authorization: `Bearer ${sessionToken}` }
});
agentModels.value = await res.json();
} catch (e) {
console.error("Failed to fetch agent models:", e);
}
}
watch([connected2, _allowedAgentIds], () => {
if (connected2.value && !_selectedAgent.value) {
updateFromServer({});
}
});
const channelStates = ref({});
let _pollTimer = null;
let _pollIdx = 0;
async function fetchOneAgent(agentId) {
const sessionToken = localStorage.getItem("nyx_session") ?? "";
if (!sessionToken) return;
try {
const apiBase = getApiBase();
const res = await fetch(`${apiBase}/api/channels/${agentId}`, {
headers: { Authorization: `Bearer ${sessionToken}` }
});
if (!res.ok) return;
const data = await res.json();
channelStates.value = {
...channelStates.value,
[agentId]: {
private: data.private ?? null,
public: data.public ?? null
}
};
} catch {
}
}
function pollNextAgent() {
const agents2 = allAgents.value;
if (!agents2.length) return;
_pollIdx = _pollIdx % agents2.length;
fetchOneAgent(agents2[_pollIdx].id);
_pollIdx++;
}
function startChannelPolling() {
if (_pollTimer) return;
_pollIdx = 0;
pollNextAgent();
_pollTimer = setInterval(pollNextAgent, 1e3);
}
function stopChannelPolling() {
if (_pollTimer) {
clearInterval(_pollTimer);
_pollTimer = null;
}
}
function getChannelState(agentId, mode) {
const entry = channelStates.value[agentId];
if (!entry) return null;
return mode === "private" ? entry.private : entry.public;
}
let currentUserRef = null;
function setCurrentUser(userRef) {
currentUserRef = userRef;
}
function syncStorage() {
if (_selectedAgent.value) sessionStorage.setItem("agent", _selectedAgent.value);
sessionStorage.setItem("agent_mode", _selectedMode.value);
}
watch([_selectedAgent, _selectedMode], syncStorage);
return {
allAgents,
selectedAgent,
selectedMode,
defaultAgent,
allowedAgentIds,
agentModels,
filteredAgents,
channelStates,
fetchAgentModels,
updateFromServer,
getChannelState,
setCurrentUser,
startChannelPolling,
stopChannelPolling
};
}
const SESSION_TOKEN_KEY = "nyx_session";
const DEV_TOKEN = "7Oorb9S3OpwFyWgm4zi_Tq7GeamefbjjTgooPVPWAwPDOf6B4TvgvQlLbhmT4DjsqBS_D1g";
function useAuth(connectFn) {
const router = useRouter();
if (typeof localStorage !== "undefined") {
if (!localStorage.getItem(SESSION_TOKEN_KEY) && !localStorage.getItem("titan_token")) {
localStorage.setItem(SESSION_TOKEN_KEY, DEV_TOKEN);
}
}
const isLoggedIn = ref(typeof localStorage !== "undefined" ? !!localStorage.getItem(SESSION_TOKEN_KEY) : false);
const loginToken = ref("");
const loginError = ref("");
const loggingIn = ref(false);
async function doLogin() {
const token = loginToken.value.trim();
if (!token) return;
loggingIn.value = true;
loginError.value = "";
try {
const nonceRes = await fetch(`${getApiBase()}/api/auth/nonce`);
if (!nonceRes.ok) {
loginError.value = "Auth unavailable";
loggingIn.value = false;
return;
}
const { nonce } = await nonceRes.json();
const res = await fetch(`${getApiBase()}/api/auth`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token, nonce })
});
if (!res.ok) {
const err = await res.json().catch(() => ({ error: "Login failed" }));
loginError.value = err.error || "Invalid token";
loggingIn.value = false;
return;
}
const { sessionToken } = await res.json();
localStorage.removeItem("titan_token");
localStorage.removeItem("nyx_token");
localStorage.setItem(SESSION_TOKEN_KEY, sessionToken);
sessionStorage.removeItem("agent");
isLoggedIn.value = true;
connectFn();
router.push("/chat");
setTimeout(() => {
loggingIn.value = false;
}, 500);
} catch {
loginError.value = "Network error";
loggingIn.value = false;
}
}
async function doLogout(disconnectFn) {
const sessionToken = localStorage.getItem(SESSION_TOKEN_KEY);
if (sessionToken) {
fetch(`${getApiBase()}/api/auth/logout`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ sessionToken })
}).catch(() => {
});
}
if (disconnectFn) disconnectFn();
localStorage.removeItem(SESSION_TOKEN_KEY);
localStorage.removeItem("titan_token");
localStorage.removeItem("nyx_token");
sessionStorage.removeItem("agent");
sessionStorage.removeItem("viewer_auth");
isLoggedIn.value = false;
loginToken.value = "";
loggingIn.value = false;
router.push("/");
}
return { isLoggedIn, loginToken, loginError, loggingIn, doLogin, doLogout };
}
const STORAGE_KEY$2 = "viewer_auth";
const REFRESH_BEFORE_EXPIRY_MS = 5 * 60 * 1e3;
function loadStored() {
try {
const raw = sessionStorage.getItem(STORAGE_KEY$2);
if (!raw) return null;
return JSON.parse(raw);
} catch {
return null;
}
}
function saveStored(auth2) {
try {
sessionStorage.setItem(STORAGE_KEY$2, JSON.stringify(auth2));
} catch {
}
}
function clearStored() {
sessionStorage.removeItem(STORAGE_KEY$2);
}
const useViewerStore = defineStore("viewer", () => {
const stored2 = loadStored();
const fstoken = ref(stored2?.fstoken ?? "");
const roots = ref(stored2?.roots ?? []);
const expiresAt = ref(stored2?.expiresAt ?? 0);
const ready = computed(() => !!fstoken.value && Date.now() < expiresAt.value);
let acquiring = null;
async function acquire(force = false) {
if (!force && fstoken.value && Date.now() < expiresAt.value - REFRESH_BEFORE_EXPIRY_MS) return;
if (acquiring) return acquiring;
acquiring = _acquire().finally(() => {
acquiring = null;
});
return acquiring;
}
async function _acquire() {
const sessionToken = localStorage.getItem("nyx_session") || localStorage.getItem("titan_token") || "";
if (!sessionToken) return;
try {
const res = await fetch(`${getApiBase()}/api/viewer/token`, {
method: "POST",
headers: { Authorization: `Bearer ${sessionToken}` }
});
if (!res.ok) {
fstoken.value = "";
roots.value = [];
expiresAt.value = 0;
clearStored();
return;
}
const data = await res.json();
const newToken = data.fstoken;
let newRoots = roots.value.length ? roots.value : ["shared", "workspace-titan"];
try {
const tr = await fetch(`${getApiBase()}/api/viewer/tree?root=&token=${encodeURIComponent(newToken)}`);
if (tr.ok) {
const td = await tr.json();
if (Array.isArray(td.dirs) && td.dirs.length) newRoots = td.dirs;
}
} catch {
}
const newExpiresAt = Date.now() + 60 * 60 * 1e3;
fstoken.value = newToken;
roots.value = newRoots;
expiresAt.value = newExpiresAt;
saveStored({ fstoken: newToken, roots: newRoots, expiresAt: newExpiresAt });
} catch {
}
}
function invalidate() {
fstoken.value = "";
roots.value = [];
expiresAt.value = 0;
clearStored();
}
return { fstoken, roots, ready, acquire, invalidate };
});
const lastViewerPath = ref(typeof localStorage !== "undefined" ? localStorage.getItem("viewer_last_path") || "" : "");
const viewerOpenDirs = reactive(/* @__PURE__ */ new Set());
const ws = useWebSocket();
const takeover = ws.getTakeover();
const agents = useAgents(ws.connected);
agents.setCurrentUser(ws.currentUser);
const auth = useAuth(() => {
ws.connect(agents.selectedAgent, auth.isLoggedIn, auth.loginError, agents.selectedMode);
useViewerStore().acquire();
});
const STORAGE_KEY$1 = "hermes_theme";
const THEME_ICONS = {
titan: CommandLineIcon,
eras: SunIcon,
loop42: CubeTransparentIcon
};
const THEME_NAMES = {
titan: "Titan",
eras: "ERAS",
loop42: "loop42"
};
const THEME_LOGOS = {
titan: null,
eras: null,
loop42: null
};
const AGENT_THEME_MAP = {
eras: "eras"
};
function agentLogo(agentId) {
const t = AGENT_THEME_MAP[agentId] ?? "titan";
return THEME_LOGOS[t];
}
const stored = localStorage.getItem(STORAGE_KEY$1);
const theme = ref(
stored === "workhorse" ? "loop42" : (
// migrate legacy name
stored || "loop42"
)
);
const THEME_FAVICONS = {
titan: "/favicon-titan.svg",
eras: "/favicon-eras.svg",
loop42: "/favicon-loop42.svg"
};
function applyTheme(t) {
if (t === "titan") {
document.documentElement.removeAttribute("data-theme");
} else {
document.documentElement.setAttribute("data-theme", t);
}
const link = document.querySelector('link[rel="icon"]');
if (link) link.href = THEME_FAVICONS[t] || "/favicon.svg";
}
applyTheme(theme.value);
watch(theme, (t) => {
applyTheme(t);
localStorage.setItem(STORAGE_KEY$1, t);
});
function useTheme() {
function setTheme(t) {
theme.value = t;
}
return { theme, setTheme };
}
const useChatStore = defineStore("chat", () => {
const messages = ref([]);
const channelState = ref("NO_SESSION");
const connectionState = ref("CONNECTING");
const smState = computed(() => {
if (connectionState.value !== "SYNCED") return connectionState.value;
return _stopPending.value ? "STOP_PENDING" : channelState.value;
});
const _stopPending = ref(false);
const truncatedWarning = ref(false);
const localSessionId = ref(Math.random().toString(36).slice(2, 10));
const sessionTotalTokens = ref(null);
const sessionUsage = ref(null);
const beVersion = ref("");
const finance = ref(null);
const sessionKey = ref("");
const sessionCost = computed(() => {
const p = finance.value?.pricing;
const u = sessionTotalTokens.value;
if (!p || !u) return 0;
return ((u.input_tokens || 0) * p.prompt + (u.output_tokens || 0) * p.completion) / 1e6;
});
const queuedThought = ref(null);
const activeTurnCorrId = ref(null);
const handoverPending = computed(
() => messages.value.some((m) => m.confirmNew && !m.confirmed)
);
const isRunning = computed(
() => channelState.value === "AGENT_RUNNING" || _stopPending.value || channelState.value === "HANDOVER_PENDING"
);
let _wsSend = null;
function setWsSend(fn) {
_wsSend = fn;
}
function newSession() {
stashMessages();
resetLocalSession();
_wsSend?.({ type: "new" });
}
function handover() {
_wsSend?.({ type: "handover_request" });
}
function stop() {
if (channelState.value !== "AGENT_RUNNING" && !_stopPending.value) return;
queuedThought.value = null;
_stopPending.value = true;
pushSystem("Stopping after current turn...");
_wsSend?.({ type: "stop" });
}
function confirmNew() {
const bubble = [...messages.value].reverse().find((m) => m.confirmNew && !m.confirmed);
if (bubble) bubble.confirmed = true;
stashMessages();
resetLocalSession();
_wsSend?.({ type: "new" });
}
function stay() {
const bubble = [...messages.value].reverse().find((m) => m.confirmNew && !m.confirmed);
if (bubble) {
bubble.confirmed = true;
setTimeout(() => {
const idx = messages.value.findIndex((m) => m === bubble);
if (idx !== -1) messages.value.splice(idx, 1);
}, 1e3);
}
_wsSend?.({ type: "cancel_handover" });
}
function applyChannelState(state) {
channelState.value = state;
if (state === "AGENT_RUNNING") sessionContextHint.value = "";
if (state === "READY" || state === "FRESH") _stopPending.value = false;
}
function applyConnectionState(state) {
connectionState.value = state;
}
function applySessionState(state) {
if (["FRESH", "READY", "AGENT_RUNNING", "HANDOVER_PENDING", "HANDOVER_DONE", "NO_SESSION"].includes(state)) {
applyChannelState(state);
} else if (["CONNECTING", "LOADING_HISTORY", "SYNCED", "SWITCHING"].includes(state)) {
applyConnectionState(state);
}
}
function setConnecting() {
connectionState.value = "CONNECTING";
}
let currentAssistantMessageIndex = -1;
const streamingMessageVisibleContent = ref("");
let streamingMessageFullContent = "";
let streamingInterval = null;
let charIndex = 0;
let pendingVisibleClear = false;
let thinkingMessageIndex = -1;
const thinkingContent = ref("");
const sessionContextHint = ref("");
const TYPING_TICK_MS = 10;
const smLabel = computed(() => {
if (connectionState.value !== "SYNCED") {
switch (connectionState.value) {
case "CONNECTING":
return "⏳ Connecting...";
case "LOADING_HISTORY":
return "⏳ Loading...";
case "SWITCHING":
return "🔀 Switching...";
}
}
if (_stopPending.value) return "⛔ Stopping...";
switch (channelState.value) {
case "AGENT_RUNNING":
return "⚙️ Working...";
case "HANDOVER_PENDING":
return "📝 Handover...";
case "HANDOVER_DONE":
return "✅ Handover ready";
case "READY":
return sessionContextHint.value ? `✓ Ready - ${sessionContextHint.value}` : "● ✓ Ready";
case "FRESH":
return "✨ New session";
case "RESETTING":
return "🔄 Resetting...";
case "NO_SESSION":
return "○ No session";
default:
return "● ✓ Ready";
}
});
const visibleMsgs = (visibleCount) => {
return messages.value.slice(-visibleCount);
};
function resetLocalSession() {
localSessionId.value = Math.random().toString(36).slice(2, 10);
console.log("[ChatStore] Local Session Reset:", localSessionId.value);
resetStreamingState();
}
function stashMessages() {
const saveable = messages.value.filter((m) => m.role === "user" || m.role === "assistant").map((m) => ({ role: m.role, content: m.content, agentId: m.agentId }));
if (saveable.length > 0) {
try {
sessionStorage.setItem("hermes_prev_session", JSON.stringify(saveable));
} catch (_) {
}
}
}
function getPreviousSession() {
try {
const raw = sessionStorage.getItem("hermes_prev_session");
return raw ? JSON.parse(raw) : [];
} catch {
return [];
}
}
function clearMessages() {
stashMessages();
messages.value = [];
sessionTotalTokens.value = null;
finance.value = null;
resetStreamingState();
}
function pushMessage(msg) {
hasActiveStreamingMessage() ? currentAssistantMessageIndex : messages.value.length;
const msgWithId = {
...msg,
sessionId: localSessionId.value
};
if (hasActiveStreamingMessage() && msg.role === "user") {
messages.value.splice(currentAssistantMessageIndex, 0, msgWithId);
currentAssistantMessageIndex++;
} else {
messages.value.push(msgWithId);
}
}
function patchMessage(msgId, patch) {
const msg = messages.value.find((m) => m.msgId === msgId);
if (!msg) return false;
Object.assign(msg, patch);
return true;
}
function pushSystem(text, agentId) {
const msg = {
role: "system",
content: text,
agentId: agentId || null,
sessionId: localSessionId.value
};
if (hasActiveStreamingMessage()) {
messages.value.splice(currentAssistantMessageIndex, 0, msg);
currentAssistantMessageIndex++;
} else {
messages.value.push(msg);
}
}
function resetStreamingState() {
if (streamingInterval !== null) clearInterval(streamingInterval);
streamingInterval = null;
currentAssistantMessageIndex = -1;
streamingMessageFullContent = "";
charIndex = 0;
pendingVisibleClear = true;
nextTick(() => {
if (pendingVisibleClear) {
streamingMessageVisibleContent.value = "";
pendingVisibleClear = false;
}
});
}
function startNewAssistantMessage(agentId) {
if (currentAssistantMessageIndex === -1 || !messages.value[currentAssistantMessageIndex]?.streaming) {
pendingVisibleClear = false;
resetStreamingState();
messages.value.push({
role: "assistant",
content: "",
fullContent: "",
usage: null,
streaming: true,
agentId: agentId ?? null,
sessionId: localSessionId.value,
turnCorrId: activeTurnCorrId.value ?? null
});
currentAssistantMessageIndex = messages.value.length - 1;
}
}
function appendAssistantDelta(delta, agentId) {
if (currentAssistantMessageIndex !== -1 && messages.value[currentAssistantMessageIndex]?.streaming) {
streamingMessageFullContent += delta;
messages.value[currentAssistantMessageIndex].fullContent = streamingMessageFullContent;
if (!streamingInterval) startTypewriter();
} else {
startNewAssistantMessage(agentId);
appendAssistantDelta(delta, agentId);
}
}
function startTypewriter() {
if (streamingInterval !== null) clearInterval(streamingInterval);
streamingInterval = setInterval(() => {
const backlog = streamingMessageFullContent.length - charIndex;
if (backlog <= 0) {
console.log(
"[hermes] typewriter done: charIndex=%d fullLen=%d visibleLen=%d streaming=%s",
charIndex,
streamingMessageFullContent.length,
streamingMessageVisibleContent.value.length,
messages.value[currentAssistantMessageIndex]?.streaming
);
if (streamingInterval !== null) clearInterval(streamingInterval);
streamingInterval = null;
return;
}
const charsThisTick = backlog >= 200 ? 10 : backlog >= 50 ? 4 : 1;
const end = Math.min(charIndex + charsThisTick, streamingMessageFullContent.length);
streamingMessageVisibleContent.value += streamingMessageFullContent.slice(charIndex, end);
charIndex = end;
}, TYPING_TICK_MS);
}
function finalizeAssistantMessage(content, usage, truncated = false) {
if (currentAssistantMessageIndex !== -1 && messages.value[currentAssistantMessageIndex]?.streaming) {
const msg = messages.value[currentAssistantMessageIndex];
const finalContent = (content !== null && content !== void 0 ? content : streamingMessageFullContent).replace(/\s*NO_REPLY\s*$/g, "").trim();
console.log(
"[hermes] finalizeAssistantMessage: charIndex=%d fullLen=%d visibleLen=%d finalLen=%d",
charIndex,
streamingMessageFullContent.length,
streamingMessageVisibleContent.value.length,
finalContent.length
);
if (streamingInterval !== null) {
clearInterval(streamingInterval);
streamingInterval = null;
}
streamingMessageVisibleContent.value = finalContent;
charIndex = finalContent.length;
msg.content = finalContent;
msg.fullContent = finalContent;
if (usage) msg.usage = usage;
if (truncated) msg.truncated = true;
msg.streaming = false;
console.log(
"[hermes] post-finalize: msg.content.length=%d msg.streaming=%s idx=%d",
msg.content.length,
msg.streaming,
currentAssistantMessageIndex
);
resetStreamingState();
}
}
function appendThinking(content) {
if (!content) return;
thinkingContent.value += content;
if (thinkingMessageIndex === -1) {
messages.value.push({ role: "thinking", content: thinkingContent, collapsed: false });
thinkingMessageIndex = messages.value.length - 1;
}
}
function collapseThinking() {
if (thinkingMessageIndex !== -1 && messages.value[thinkingMessageIndex]) {
messages.value[thinkingMessageIndex].collapsed = true;
}
thinkingMessageIndex = -1;
thinkingContent.value = "";
}
function createCompleteAssistantMessage(content, agentId, usage) {
if (hasActiveStreamingMessage()) {
finalizeAssistantMessage(content, usage);
return;
}
const msg = {
role: "assistant",
content: content.replace(/\s*NO_REPLY\s*$/g, "").trim(),
fullContent: content.replace(/\s*NO_REPLY\s*$/g, "").trim(),
usage: usage || null,
streaming: false,
agentId: agentId ?? null,
sessionId: localSessionId.value,
turnCorrId: activeTurnCorrId.value ?? null
};
messages.value.push(msg);
}
function hasActiveStreamingMessage() {
return currentAssistantMessageIndex !== -1 && messages.value[currentAssistantMessageIndex]?.streaming;
}
function streamingMessageLength() {
return streamingMessageFullContent.length;
}
function suppressAssistantMessage() {
if (currentAssistantMessageIndex !== -1 && messages.value[currentAssistantMessageIndex]?.streaming) {
if (streamingInterval !== null) {
clearInterval(streamingInterval);
streamingInterval = null;
}
messages.value.splice(currentAssistantMessageIndex, 1);
}
resetStreamingState();
}
return {
messages,
activeTurnCorrId,
sessionKey,
smState,
// legacy computed: connection priority, then channel
channelState,
connectionState,
applySessionState,
// legacy compat
applyChannelState,
applyConnectionState,
setConnecting,
truncatedWarning,
localSessionId,
sessionTotalTokens,
sessionUsage,
beVersion,
sessionCost,
finance,
smLabel,
sessionContextHint,
streamingMessageVisibleContent,
visibleMsgs,
resetLocalSession,
clearMessages,
stashMessages,
getPreviousSession,
pushMessage,
patchMessage,
pushSystem,
startNewAssistantMessage,
appendAssistantDelta,
finalizeAssistantMessage,
createCompleteAssistantMessage,
appendThinking,
collapseThinking,
hasActiveStreamingMessage,
streamingMessageLength,
suppressAssistantMessage,
queuedThought,
handoverPending,
isRunning,
setWsSend,
newSession,
handover,
stop,
confirmNew,
stay
};
});
const STORAGE_KEY = "hermes_dev_flags";
const defaults = {
showGrid: false,
showDebugInfo: false,
showHud: false
};
function load() {
try {
const raw = sessionStorage.getItem(STORAGE_KEY);
if (!raw) return { ...defaults };
return { ...defaults, ...JSON.parse(raw) };
} catch {
return { ...defaults };
}
}
const flags = reactive(load());
watch(flags, (v) => {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(v));
}, { deep: true });
function useDevFlags() {
return flags;
}
const columns = 12;
const _sfc_main$8 = /* @__PURE__ */ defineComponent({
__name: "GridOverlay",
__ssrInlineRender: true,
setup(__props) {
const devFlags = useDevFlags();
return (_ctx, _push, _parent, _attrs) => {
if (unref(devFlags).showGrid) {
_push(`<div${ssrRenderAttrs(mergeProps({
class: "grid-overlay",
"aria-hidden": "true"
}, _attrs))} data-v-967a780b><!--[-->`);
ssrRenderList(columns, (n) => {
_push(`<div class="grid-col" data-v-967a780b></div>`);
});
_push(`<!--]--></div>`);
} else {
_push(`<!---->`);
}
};
}
});
const _export_sfc = (sfc, props) => {
const target = sfc.__vccOpts || sfc;
for (const [key, val] of props) {
target[key] = val;
}
return target;
};
const _sfc_setup$8 = _sfc_main$8.setup;
_sfc_main$8.setup = (props, ctx) => {
const ssrContext = useSSRContext();
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/components/GridOverlay.vue");
return _sfc_setup$8 ? _sfc_setup$8(props, ctx) : void 0;
};
const GridOverlay = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["__scopeId", "data-v-967a780b"]]);
const _sfc_main$7 = /* @__PURE__ */ defineComponent({
__name: "BreakpointBadge",
__ssrInlineRender: true,
setup(__props) {
const devFlags = useDevFlags();
return (_ctx, _push, _parent, _attrs) => {
if (unref(devFlags).showDebugInfo) {
_push(`<div${ssrRenderAttrs(mergeProps({
class: "bp-badge",
"aria-hidden": "true"
}, _attrs))} data-v-321be928><span class="sm:hidden" data-v-321be928>xs</span><span class="hidden sm:inline md:hidden" data-v-321be928>sm</span><span class="hidden md:inline lg:hidden" data-v-321be928>md</span><span class="hidden lg:inline" data-v-321be928>lg</span></div>`);
} else {
_push(`<!---->`);
}
};
}
});
const _sfc_setup$7 = _sfc_main$7.setup;
_sfc_main$7.setup = (props, ctx) => {
const ssrContext = useSSRContext();
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/components/BreakpointBadge.vue");
return _sfc_setup$7 ? _sfc_setup$7(props, ctx) : void 0;
};
const BreakpointBadge = /* @__PURE__ */ _export_sfc(_sfc_main$7, [["__scopeId", "data-v-321be928"]]);
OverlayScrollbars.plugin(ClickScrollPlugin);
const scrollbarOptions = {
overflow: {
x: "hidden"
},
scrollbars: {
clickScroll: true,
autoHide: "never"
}
};
const _sfc_main$6 = /* @__PURE__ */ defineComponent({
__name: "TreeNode",
__ssrInlineRender: true,
props: {
label: {},
path: {},
token: {},
activePath: {},
expandTo: {},
depth: {},
hideSelf: { type: Boolean },
foldersOnly: { type: Boolean }
},
emits: ["select"],
setup(__props, { emit: __emit }) {
const props = __props;
const childDepth = computed(() => props.hideSelf ? props.depth : props.depth + 1);
const fetched = ref(false);
const hasSubdirs = computed(() => dirs.value.length > 0);
const isLeaf = computed(() => props.foldersOnly ? !hasSubdirs.value : false);
const viewerStore = useViewerStore();
const open = computed(() => viewerOpenDirs.has(props.path));
const loading = ref(false);
const showLoading = ref(false);
let loadingTimer = null;
const error = ref("");
const dirs = ref([]);
const files = ref([]);
function getBaseUrl() {
return getApiBase();
}
async function fetchTree(_retry = false) {
if (loading.value) return;
loading.value = true;
showLoading.value = false;
if (loadingTimer) clearTimeout(loadingTimer);
loadingTimer = setTimeout(() => {
showLoading.value = true;
}, 3e3);
error.value = "";
try {
const base = getBaseUrl();
const url = `${base}/api/viewer/tree?root=${encodeURIComponent(props.path)}&token=${encodeURIComponent(props.token)}`;
const res = await fetch(url);
if (res.status === 401 && !_retry) {
loading.value = false;
viewerStore.invalidate();
await viewerStore.acquire(true);
return fetchTree(true);
}
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
const data = await res.json();
dirs.value = data.dirs || [];
files.value = data.files || [];
} catch (e) {
error.value = e.message || "Failed to load";
} finally {
loading.value = false;
showLoading.value = false;
fetched.value = true;
if (loadingTimer) {
clearTimeout(loadingTimer);
loadingTimer = null;
}
}
}
function setOpen(val) {
viewerOpenDirs.add(props.path);
}
function maybeExpand(expandTo) {
if (expandTo && expandTo.startsWith(props.path + "/") && !open.value) {
setOpen();
if (!dirs.value.length && !files.value.length) fetchTree();
}
}
const { onMessage: onMessage2 } = ws;
let unsubDirWatch = null;
onMounted(() => {
if (props.hideSelf && !open.value) setOpen();
if ((open.value || props.hideSelf) && !dirs.value.length && !files.value.length) fetchTree();
else if (props.foldersOnly && !fetched.value && !loading.value) fetchTree();
maybeExpand(props.expandTo);
unsubDirWatch = onMessage2((data) => {
if (data.type === "viewer_tree_changed" && data.path === props.path && open.value) {
fetchTree();
}
});
});
onUnmounted(() => {
if (unsubDirWatch) unsubDirWatch();
});
watch(() => props.expandTo, maybeExpand);
return (_ctx, _push, _parent, _attrs) => {
const _component_TreeNode = resolveComponent("TreeNode", true);
_push(`<div${ssrRenderAttrs(mergeProps({ class: "tree-node" }, _attrs))} data-v-ae9a8cbc>`);
if (!__props.hideSelf) {
_push(`<div class="${ssrRenderClass([{ active: __props.path === __props.activePath, "is-leaf": isLeaf.value }, "tree-row dir-row"])}" style="${ssrRenderStyle({ paddingLeft: `${__props.depth * 12 + 14}px` })}" data-v-ae9a8cbc>`);
if (!isLeaf.value) {
_push(`<span class="chevron" data-v-ae9a8cbc>`);
if (open.value) {
_push(ssrRenderComponent(unref(ChevronDownIcon), { class: "icon w-3 h-3" }, null, _parent));
} else {
_push(ssrRenderComponent(unref(ChevronRightIcon), { class: "icon w-3 h-3" }, null, _parent));
}
_push(`</span>`);
} else {
_push(`<span class="chevron-spacer" data-v-ae9a8cbc></span>`);
}
_push(`<span class="label" data-v-ae9a8cbc>${ssrInterpolate(__props.label)}</span></div>`);
} else {
_push(`<!---->`);
}
if (open.value || __props.hideSelf) {
_push(`<div class="tree-children" data-v-ae9a8cbc>`);
if (showLoading.value) {
_push(`<div class="tree-loading" style="${ssrRenderStyle({ paddingLeft: `${childDepth.value * 12 + 14}px` })}" data-v-ae9a8cbc>…</div>`);
} else if (error.value) {
_push(`<div class="tree-error" style="${ssrRenderStyle({ paddingLeft: `${childDepth.value * 12 + 14}px` })}" data-v-ae9a8cbc>${ssrInterpolate(error.value)}</div>`);
} else {
_push(`<!--[--><!--[-->`);
ssrRenderList(dirs.value, (dir) => {
_push(ssrRenderComponent(_component_TreeNode, {
key: __props.path + "/" + dir,
label: dir,
path: __props.path + "/" + dir,
token: __props.token,
"active-path": __props.activePath,
"expand-to": __props.expandTo,
depth: childDepth.value,
"folders-only": __props.foldersOnly,
onSelect: ($event) => _ctx.$emit("select", $event)
}, null, _parent));
});
_push(`<!--]-->`);
if (!__props.foldersOnly) {
_push(`<!--[-->`);
ssrRenderList(files.value, (file) => {
_push(`<div class="${ssrRenderClass([{ active: file.path === __props.activePath }, "tree-row file-row"])}" style="${ssrRenderStyle({ paddingLeft: `${childDepth.value * 12 + 14}px` })}" data-v-ae9a8cbc>`);
_push(ssrRenderComponent(unref(DocumentIcon), { class: "icon file-icon w-3 h-3" }, null, _parent));
_push(`<span class="label" data-v-ae9a8cbc>${ssrInterpolate(file.name)}</span></div>`);
});
_push(`<!--]-->`);
} else {
_push(`<!---->`);
}
if (!loading.value && !dirs.value.length && !__props.foldersOnly && !files.value.length) {
_push(`<div class="tree-empty" style="${ssrRenderStyle({ paddingLeft: `${childDepth.value * 12 + 14}px` })}" data-v-ae9a8cbc>empty</div>`);
} else {
_push(`<!---->`);
}
_push(`<!--]-->`);
}
_push(`</div>`);
} else {
_push(`<!---->`);
}
_push(`</div>`);
};
}
});
const _sfc_setup$6 = _sfc_main$6.setup;
_sfc_main$6.setup = (props, ctx) => {
const ssrContext = useSSRContext();
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/components/TreeNode.vue");
return _sfc_setup$6 ? _sfc_setup$6(props, ctx) : void 0;
};
const TreeNode = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["__scopeId", "data-v-ae9a8cbc"]]);
const _sfc_main$5 = /* @__PURE__ */ defineComponent({
__name: "FileTree",
__ssrInlineRender: true,
props: {
token: {},
activePath: {},
expandTo: {},
roots: {},
hideRoot: { type: Boolean },
foldersOnly: { type: Boolean }
},
emits: ["select"],
setup(__props) {
const props = __props;
const roots = computed(
() => (props.roots && props.roots.length > 0 ? props.roots : ["shared", "workspace-titan"]).map((r) => ({ label: r, prefix: r }))
);
return (_ctx, _push, _parent, _attrs) => {
_push(`<div${ssrRenderAttrs(mergeProps({ class: "file-tree" }, _attrs))} data-v-e4eaa1f9><!--[-->`);
ssrRenderList(roots.value, (root) => {
_push(ssrRenderComponent(TreeNode, {
key: root.prefix,
label: root.label,
path: root.prefix,
token: __props.token,
"active-path": __props.activePath,
"expand-to": __props.expandTo,
depth: 0,
"hide-self": __props.hideRoot,
"folders-only": __props.foldersOnly,
onSelect: ($event) => _ctx.$emit("select", $event)
}, null, _parent));
});
_push(`<!--]--></div>`);
};
}
});
const _sfc_setup$5 = _sfc_main$5.setup;
_sfc_main$5.setup = (props, ctx) => {
const ssrContext = useSSRContext();
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/components/FileTree.vue");
return _sfc_setup$5 ? _sfc_setup$5(props, ctx) : void 0;
};
const FileTree = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-e4eaa1f9"]]);
const _sfc_main$4 = /* @__PURE__ */ defineComponent({
...{ name: "AppSidebar" },
__name: "AppSidebar",
__ssrInlineRender: true,
emits: ["logout"],
setup(__props, { emit: __emit }) {
const takeoverToken = takeover.token;
const captureActive = takeover.capture.isActive;
const takeoverPanelOpen = ref(false);
const tokenCopied = ref(false);
const systemOpen = ref(sessionStorage.getItem("sidebar_panel_system") === "true");
const connPanelOpen = ref(false);
const anyPanelOpen = computed(() => takeoverPanelOpen.value || userMenuOpen.value || versionPanelOpen.value || connPanelOpen.value);
const homeAgent = computed(() => allAgents.value.find((a) => a.id === defaultAgent.value));
computed(
() => filteredAgents.value.filter((a) => a.id !== defaultAgent.value).sort((a, b) => a.name.localeCompare(b.name))
);
const chatStore = useChatStore();
const viewerStore = useViewerStore();
const viewerToken = computed(() => viewerStore.fstoken);
const viewerRoots = computed(() => viewerStore.roots);
const sharedOpen = ref(lastViewerPath.value.startsWith("shared"));
const workspaceOpen = ref(lastViewerPath.value.startsWith("workspace"));
const viewerWorkspaceRoot = computed(() => {
const ws2 = viewerRoots.value.find((r) => r.startsWith("workspace"));
return ws2 || "workspace-titan";
});
function openInViewer(path) {
lastViewerPath.value = path;
localStorage.setItem("viewer_last_path", path);
router.push({ name: "viewer", query: { path } });
}
const { version: version2 } = useUI(ws.status);
const versionShort = version2.split("-")[0];
const envLabel = "prod";
const versionPanelOpen = ref(false);
const versionCopied = ref(false);
function stateToIndicator(state) {
switch (state) {
case "AGENT_RUNNING":
return { icon: "⚙️", cls: "ch-running" };
case "FRESH":
return { icon: "✨", cls: "ch-fresh" };
case "READY":
return { icon: "●", cls: "ch-ready" };
case "HANDOVER_PENDING":
return { icon: "📝", cls: "ch-handover" };
case "HANDOVER_DONE":
return { icon: "✅", cls: "ch-handover" };
case "RESETTING":
return { icon: "🔄", cls: "ch-resetting" };
case "NO_SESSION":
return { icon: "○", cls: "ch-nosession" };
default:
return { icon: "·", cls: "ch-none" };
}
}
computed(() => stateToIndicator(chatStore.channelState));
const connLabel = computed(() => {
switch (chatStore.connectionState) {
case "CONNECTING":
return "Connecting...";
case "LOADING_HISTORY":
return "Loading...";
case "SWITCHING":
return "Switching...";
case "SYNCED":
return "Connected";
default:
return "";
}
});
const connActive = computed(() => chatStore.connectionState === "SYNCED");
const route = useRoute();
const router = useRouter();
const { theme: theme2 } = useTheme();
const navLogo = computed(() => THEME_LOGOS[theme2.value]);
const { isLoggedIn } = auth;
const { currentUser: currentUser2 } = ws;
const { filteredAgents, selectedAgent, selectedMode, defaultAgent, allAgents } = agents;
const SEGMENTS = ["personal", "common", "private", "public"];
computed(
() => SEGMENTS.map((key) => ({
key,
agents: filteredAgents.value.filter((a) => (a.segment ?? "utility") === key && a.id !== defaultAgent.value).sort((a, b) => a.name.localeCompare(b.name))
})).filter((s) => s.agents.length > 0)
);
const isMobile = window.innerWidth <= 480;
const isLarge = window.innerWidth >= 1024;
const isOpen = ref(isMobile ? false : isLarge ? true : localStorage.getItem("sidebar_open") !== "false");
const userMenuOpen = ref(false);
const lgQuery = window.matchMedia("(min-width: 1024px)");
lgQuery.addEventListener("change", (e) => {
if (e.matches && !isOpen.value) {
isOpen.value = true;
localStorage.setItem("sidebar_open", "true");
} else if (!e.matches && isOpen.value) {
isOpen.value = false;
localStorage.setItem("sidebar_open", "false");
}
});
function collapse() {
if (window.innerWidth >= 1024) return;
isOpen.value = false;
localStorage.setItem("sidebar_open", "false");
}
return (_ctx, _push, _parent, _attrs) => {
const _component_RouterLink = resolveComponent("RouterLink");
_push(`<aside${ssrRenderAttrs(mergeProps({
class: ["app-sidebar", { "is-collapsed": !isOpen.value }]
}, _attrs))}><div class="sidebar-shadow"></div><div class="sidebar-close-target"></div><div class="sidebar-header"><button class="sidebar-toggle-btn"${ssrRenderAttr("title", isOpen.value ? "Collapse" : "Expand")}>`);
if (isOpen.value) {
_push(ssrRenderComponent(unref(ChevronLeftIcon), { class: "sidebar-chevron-anim w-4 h-4" }, null, _parent));
} else {
_push(ssrRenderComponent(unref(ChevronRightIcon), { class: "sidebar-chevron-anim w-4 h-4" }, null, _parent));
}
_push(`</button>`);
if (isOpen.value) {
_push(ssrRenderComponent(_component_RouterLink, {
to: "/",
class: "sidebar-brand",
title: "Home",
onClick: collapse
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
if (navLogo.value) {
_push2(`<img${ssrRenderAttr("src", navLogo.value)} class="sidebar-brand-logo" alt="Home"${_scopeId}>`);
} else {
ssrRenderVNode(_push2, createVNode(resolveDynamicComponent(unref(THEME_ICONS)[unref(theme2)]), { class: "sidebar-brand-icon" }, null), _parent2, _scopeId);
}
_push2(`<span class="sidebar-brand-name"${_scopeId}>${ssrInterpolate(unref(THEME_NAMES)[unref(theme2)])}</span>`);
} else {
return [
navLogo.value ? (openBlock(), createBlock("img", {
key: 0,
src: navLogo.value,
class: "sidebar-brand-logo",
alt: "Home"
}, null, 8, ["src"])) : (openBlock(), createBlock(resolveDynamicComponent(unref(THEME_ICONS)[unref(theme2)]), {
key: 1,
class: "sidebar-brand-icon"
})),
createVNode("span", { class: "sidebar-brand-name" }, toDisplayString(unref(THEME_NAMES)[unref(theme2)]), 1)
];
}
}),
_: 1
}, _parent));
} else {
_push(`<!---->`);
}
_push(`</div>`);
if (unref(isLoggedIn) && isOpen.value) {
_push(`<div class="${ssrRenderClass([{ "has-tree": sharedOpen.value || workspaceOpen.value }, "sidebar-top-section"])}"><div class="sidebar-home">`);
if (homeAgent.value) {
_push(`<div class="${ssrRenderClass([[`role-${homeAgent.value.role}`, { active: unref(selectedAgent) === homeAgent.value.id }], "sidebar-room"])}"${ssrRenderAttr("title", "Chat with " + homeAgent.value.name)}><span class="${ssrRenderClass([`dot-${homeAgent.value.role}`, "sidebar-room-dot"])}"></span><span class="sidebar-room-name">${ssrInterpolate(homeAgent.value.name)}</span></div>`);
} else {
_push(`<div class="sidebar-room sidebar-room-placeholder"><span class="sidebar-room-dot"></span></div>`);
}
_push(`</div><button class="${ssrRenderClass([{ active: unref(route).name === "agents" && !unref(route).query.agent }, "sidebar-link"])}">`);
_push(ssrRenderComponent(unref(ChatBubbleLeftRightIcon), { class: "w-4 h-4" }, null, _parent));
_push(`<span>Agents</span></button>`);
if (viewerToken.value) {
_push(`<div class="${ssrRenderClass([{ "is-open": sharedOpen.value }, "sidebar-file-section"])}"><button class="${ssrRenderClass([{ active: sharedOpen.value }, "sidebar-link sidebar-file-toggle"])}">`);
_push(ssrRenderComponent(unref(FolderIcon), { class: "w-4 h-4" }, null, _parent));
_push(`<span>Shared</span></button>`);
if (sharedOpen.value) {
_push(ssrRenderComponent(unref(OverlayScrollbarsComponent), {
class: "sidebar-file-scroll",
options: unref(scrollbarOptions),
element: "div"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(ssrRenderComponent(FileTree, {
token: viewerToken.value,
"active-path": unref(lastViewerPath),
"expand-to": unref(lastViewerPath),
roots: ["shared"],
"hide-root": true,
"folders-only": true,
onSelect: openInViewer
}, null, _parent2, _scopeId));
} else {
return [
createVNode(FileTree, {
token: viewerToken.value,
"active-path": unref(lastViewerPath),
"expand-to": unref(lastViewerPath),
roots: ["shared"],
"hide-root": true,
"folders-only": true,
onSelect: openInViewer
}, null, 8, ["token", "active-path", "expand-to"])
];
}
}),
_: 1
}, _parent));
} else {
_push(`<!---->`);
}
_push(`</div>`);
} else {
_push(`<!---->`);
}
if (viewerToken.value) {
_push(`<div class="${ssrRenderClass([{ "is-open": workspaceOpen.value }, "sidebar-file-section"])}"><button class="${ssrRenderClass([{ active: workspaceOpen.value }, "sidebar-link sidebar-file-toggle"])}">`);
_push(ssrRenderComponent(unref(FolderOpenIcon), { class: "w-4 h-4" }, null, _parent));
_push(`<span>Workspace</span></button>`);
if (workspaceOpen.value) {
_push(ssrRenderComponent(unref(OverlayScrollbarsComponent), {
class: "sidebar-file-scroll",
options: unref(scrollbarOptions),
element: "div"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(ssrRenderComponent(FileTree, {
token: viewerToken.value,
"active-path": unref(lastViewerPath),
"expand-to": unref(lastViewerPath),
roots: [viewerWorkspaceRoot.value],
"hide-root": true,
"folders-only": true,
onSelect: openInViewer
}, null, _parent2, _scopeId));
} else {
return [
createVNode(FileTree, {
token: viewerToken.value,
"active-path": unref(lastViewerPath),
"expand-to": unref(lastViewerPath),
roots: [viewerWorkspaceRoot.value],
"hide-root": true,
"folders-only": true,
onSelect: openInViewer
}, null, 8, ["token", "active-path", "expand-to", "roots"])
];
}
}),
_: 1
}, _parent));
} else {
_push(`<!---->`);
}
_push(`</div>`);
} else {
_push(`<!---->`);
}
_push(`</div>`);
} else {
_push(`<!---->`);
}
if (unref(isLoggedIn) && !isOpen.value) {
_push(`<div class="sidebar-collapsed-top">`);
if (homeAgent.value) {
_push(`<div class="${ssrRenderClass([[`role-${homeAgent.value.role}`, { active: unref(selectedAgent) === homeAgent.value.id }], "sidebar-room"])}"${ssrRenderAttr("title", homeAgent.value.name)}><span class="${ssrRenderClass([`dot-${homeAgent.value.role}`, "sidebar-room-dot"])}"></span></div>`);
} else {
_push(`<!---->`);
}
_push(`<button class="${ssrRenderClass([{ active: unref(route).name === "agents" && !unref(route).query.agent }, "sidebar-link"])}" title="Agents">`);
_push(ssrRenderComponent(unref(ChatBubbleLeftRightIcon), { class: "w-4 h-4" }, null, _parent));
_push(`</button>`);
_push(ssrRenderComponent(_component_RouterLink, {
to: { name: "viewer", query: { path: "shared" } },
class: ["sidebar-link", { active: unref(route).name === "viewer" && unref(lastViewerPath).startsWith("shared") }],
title: "Shared files"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(ssrRenderComponent(unref(FolderIcon), { class: "w-4 h-4" }, null, _parent2, _scopeId));
} else {
return [
createVNode(unref(FolderIcon), { class: "w-4 h-4" })
];
}
}),
_: 1
}, _parent));
_push(ssrRenderComponent(_component_RouterLink, {
to: { name: "viewer", query: { path: viewerWorkspaceRoot.value } },
class: ["sidebar-link", { active: unref(route).name === "viewer" && unref(lastViewerPath).startsWith("workspace") }],
title: "Workspace files"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(ssrRenderComponent(unref(FolderOpenIcon), { class: "w-4 h-4" }, null, _parent2, _scopeId));
} else {
return [
createVNode(unref(FolderOpenIcon), { class: "w-4 h-4" })
];
}
}),
_: 1
}, _parent));
_push(`</div>`);
} else {
_push(`<!---->`);
}
if (unref(isLoggedIn)) {
_push(`<div class="sidebar-flex-spacer"></div>`);
} else {
_push(`<!---->`);
}
if (anyPanelOpen.value) {
_push(`<div class="sidebar-panel-backdrop"></div>`);
} else {
_push(`<!---->`);
}
if (unref(isLoggedIn) && isOpen.value) {
_push(`<div class="${ssrRenderClass([{ collapsed: !systemOpen.value }, "sidebar-system-section"])}"><button class="sidebar-link sidebar-system-toggle">`);
if (systemOpen.value) {
_push(ssrRenderComponent(unref(ChevronDownIcon), { class: "w-4 h-4" }, null, _parent));
} else {
_push(ssrRenderComponent(unref(ChevronRightIcon), { class: "w-4 h-4" }, null, _parent));
}
_push(`<span>System</span></button>`);
if (systemOpen.value) {
_push(`<div class="sidebar-system-content"><div class="sidebar-conn-wrap"><button class="${ssrRenderClass([{ active: connActive.value }, "sidebar-link sidebar-conn-link"])}">`);
_push(ssrRenderComponent(unref(WifiIcon), { class: "w-4 h-4" }, null, _parent));
_push(`<span>${ssrInterpolate(connLabel.value)}</span></button>`);
if (connPanelOpen.value) {
_push(`<div class="sidebar-panel"><div class="sidebar-panel-header">Connection</div><div class="sidebar-panel-row"><span>WebSocket</span><span>${ssrInterpolate(unref(chatStore).connectionState)}</span></div><div class="sidebar-panel-row"><span>Channel</span><span>${ssrInterpolate(unref(chatStore).channelState)}</span></div><div class="sidebar-panel-row"><span>Agent</span><span>${ssrInterpolate(unref(selectedAgent) || "none")}</span></div><div class="sidebar-panel-row"><span>Mode</span><span>${ssrInterpolate(unref(selectedMode))}</span></div></div>`);
} else {
_push(`<!---->`);
}
_push(`</div><div class="${ssrRenderClass([{ active: !!unref(takeoverToken) }, "sidebar-takeover-wrap"])}"><button class="${ssrRenderClass([{ active: !!unref(takeoverToken) }, "sidebar-link"])}">`);
_push(ssrRenderComponent(unref(SignalIcon), { class: "w-4 h-4" }, null, _parent));
_push(`<span>Takeover</span></button>`);
if (takeoverPanelOpen.value && unref(takeoverToken)) {
_push(`<div class="sidebar-panel"><div class="sidebar-panel-header">Takeover Token</div><div class="sidebar-panel-token"${ssrRenderAttr("title", tokenCopied.value ? "Copied!" : "Click to copy")}><code>${ssrInterpolate(unref(takeoverToken))}</code>`);
if (tokenCopied.value) {
_push(`<span class="sidebar-panel-copied">Copied!</span>`);
} else {
_push(`<!---->`);
}
_push(`</div><div class="sidebar-panel-row"><span>Capture</span><span style="${ssrRenderStyle({ color: unref(captureActive) ? "var(--success, #22c55e)" : "var(--text-dim)" })}">${ssrInterpolate(unref(captureActive) ? "ON" : "OFF")}</span></div><button class="sidebar-panel-item">${ssrInterpolate(unref(captureActive) ? "Disable Capture" : "Enable Capture")}</button><button class="sidebar-panel-item">Revoke</button></div>`);
} else {
_push(`<!---->`);
}
_push(`</div>`);
_push(ssrRenderComponent(_component_RouterLink, {
to: "/dev",
class: ["sidebar-link", { active: unref(route).name === "dev" }],
onClick: collapse
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(ssrRenderComponent(unref(CodeBracketIcon), { class: "w-4 h-4" }, null, _parent2, _scopeId));
_push2(`<span${_scopeId}>dev</span>`);
} else {
return [
createVNode(unref(CodeBracketIcon), { class: "w-4 h-4" }),
createVNode("span", null, "dev")
];
}
}),
_: 1
}, _parent));
_push(`<div class="sidebar-version-wrap"><button class="sidebar-link sidebar-version-link"><span class="sidebar-version-text">${ssrInterpolate(unref(versionShort))}</span></button>`);
if (versionPanelOpen.value) {
_push(`<div class="sidebar-panel sidebar-version-panel"><div class="sidebar-panel-header">Version</div><div class="sidebar-panel-row"><span>Frontend</span><span>${ssrInterpolate(unref(version2))}</span></div><div class="sidebar-panel-row"><span>Backend</span><span>${ssrInterpolate(unref(chatStore).beVersion || "...")}</span></div><div class="sidebar-panel-row"><span>Env</span><span>${ssrInterpolate(unref(envLabel))}</span></div><button class="sidebar-panel-item">${ssrInterpolate(versionCopied.value ? "✓ Copied" : "Copy details")}</button></div>`);
} else {
_push(`<!---->`);
}
_push(`</div></div>`);
} else {
_push(`<!---->`);
}
_push(`</div>`);
} else {
_push(`<!---->`);
}
if (unref(isLoggedIn) && !isOpen.value) {
_push(`<div class="sidebar-bottom-section"><div class="sidebar-conn-wrap"><button class="${ssrRenderClass([{ active: connActive.value }, "sidebar-link sidebar-conn-link"])}"${ssrRenderAttr("title", connLabel.value)}>`);
_push(ssrRenderComponent(unref(WifiIcon), { class: "w-4 h-4" }, null, _parent));
_push(`</button>`);
if (connPanelOpen.value) {
_push(`<div class="sidebar-panel"><div class="sidebar-panel-header">Connection</div><div class="sidebar-panel-row"><span>WebSocket</span><span>${ssrInterpolate(unref(chatStore).connectionState)}</span></div><div class="sidebar-panel-row"><span>Channel</span><span>${ssrInterpolate(unref(chatStore).channelState)}</span></div><div class="sidebar-panel-row"><span>Agent</span><span>${ssrInterpolate(unref(selectedAgent) || "none")}</span></div><div class="sidebar-panel-row"><span>Mode</span><span>${ssrInterpolate(unref(selectedMode))}</span></div></div>`);
} else {
_push(`<!---->`);
}
_push(`</div><div class="${ssrRenderClass([{ active: !!unref(takeoverToken) }, "sidebar-takeover-wrap"])}"><button class="${ssrRenderClass([{ active: !!unref(takeoverToken) }, "sidebar-link"])}" title="Takeover">`);
_push(ssrRenderComponent(unref(SignalIcon), { class: "w-4 h-4" }, null, _parent));
_push(`</button>`);
if (takeoverPanelOpen.value && unref(takeoverToken)) {
_push(`<div class="sidebar-panel"><div class="sidebar-panel-header">Takeover Token</div><div class="sidebar-panel-token"${ssrRenderAttr("title", tokenCopied.value ? "Copied!" : "Click to copy")}><code>${ssrInterpolate(unref(takeoverToken))}</code>`);
if (tokenCopied.value) {
_push(`<span class="sidebar-panel-copied">Copied!</span>`);
} else {
_push(`<!---->`);
}
_push(`</div><div class="sidebar-panel-row"><span>Capture</span><span style="${ssrRenderStyle({ color: unref(captureActive) ? "var(--success, #22c55e)" : "var(--text-dim)" })}">${ssrInterpolate(unref(captureActive) ? "ON" : "OFF")}</span></div><button class="sidebar-panel-item">${ssrInterpolate(unref(captureActive) ? "Disable Capture" : "Enable Capture")}</button><button class="sidebar-panel-item">Revoke</button></div>`);
} else {
_push(`<!---->`);
}
_push(`</div>`);
_push(ssrRenderComponent(_component_RouterLink, {
to: "/dev",
class: ["sidebar-link", { active: unref(route).name === "dev" }],
title: "Dev",
onClick: collapse
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(ssrRenderComponent(unref(CodeBracketIcon), { class: "w-4 h-4" }, null, _parent2, _scopeId));
} else {
return [
createVNode(unref(CodeBracketIcon), { class: "w-4 h-4" })
];
}
}),
_: 1
}, _parent));
_push(`<div class="sidebar-version-wrap"><button class="sidebar-link sidebar-version-link"${ssrRenderAttr("title", unref(versionShort))}><span class="sidebar-version-text">${ssrInterpolate(unref(versionShort))}</span></button>`);
if (versionPanelOpen.value) {
_push(`<div class="sidebar-panel sidebar-version-panel"><div class="sidebar-panel-header">Version</div><div class="sidebar-panel-row"><span>Frontend</span><span>${ssrInterpolate(unref(version2))}</span></div><div class="sidebar-panel-row"><span>Backend</span><span>${ssrInterpolate(unref(chatStore).beVersion || "...")}</span></div><div class="sidebar-panel-row"><span>Env</span><span>${ssrInterpolate(unref(envLabel))}</span></div><button class="sidebar-panel-item">${ssrInterpolate(versionCopied.value ? "✓ Copied" : "Copy details")}</button></div>`);
} else {
_push(`<!---->`);
}
_push(`</div></div>`);
} else {
_push(`<!---->`);
}
_push(`<div class="sidebar-bottom">`);
if (unref(isLoggedIn)) {
_push(`<div class="${ssrRenderClass([{ open: userMenuOpen.value }, "sidebar-user-wrap"])}"><button class="sidebar-user-btn"${ssrRenderAttr("title", isOpen.value ? "" : unref(currentUser2))}>`);
_push(ssrRenderComponent(unref(UserCircleIcon), { class: "w-4 h-4" }, null, _parent));
if (isOpen.value) {
_push(`<span class="sidebar-user-name">${ssrInterpolate(unref(currentUser2))}</span>`);
} else {
_push(`<!---->`);
}
_push(`</button>`);
if (userMenuOpen.value) {
_push(`<div class="sidebar-user-menu"><div class="sidebar-user-menu-header">${ssrInterpolate(unref(currentUser2))}</div><button class="sidebar-user-menu-item">Logout</button></div>`);
} else {
_push(`<!---->`);
}
_push(`</div>`);
} else {
_push(ssrRenderComponent(_component_RouterLink, {
to: "/login",
class: "sidebar-link",
title: isOpen.value ? "" : "Sign in"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(ssrRenderComponent(unref(ArrowRightEndOnRectangleIcon), { class: "w-4 h-4" }, null, _parent2, _scopeId));
if (isOpen.value) {
_push2(`<span${_scopeId}>Sign in</span>`);
} else {
_push2(`<!---->`);
}
} else {
return [
createVNode(unref(ArrowRightEndOnRectangleIcon), { class: "w-4 h-4" }),
isOpen.value ? (openBlock(), createBlock("span", { key: 0 }, "Sign in")) : createCommentVNode("", true)
];
}
}),
_: 1
}, _parent));
}
_push(`</div></aside>`);
};
}
});
const _sfc_setup$4 = _sfc_main$4.setup;
_sfc_main$4.setup = (props, ctx) => {
const ssrContext = useSSRContext();
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/components/AppSidebar.vue");
return _sfc_setup$4 ? _sfc_setup$4(props, ctx) : void 0;
};
function stripMdForTts(text) {
return text.replace(/```[\s\S]*?```/g, "").replace(/`[^`]+`/g, "").replace(/!\[.*?\]\(.*?\)/g, "").replace(/\[([^\]]+)\]\(.*?\)/g, "$1").replace(/#{1,6}\s*/g, "").replace(/[*_~]+/g, "").replace(/\n{2,}/g, ". ").replace(/\n/g, " ").trim();
}
function createTtsPlayer() {
const store = useChatStore();
const state = ref("idle");
const currentTrack = ref(null);
const currentTime = ref(0);
const duration = ref(0);
const progress = computed(() => duration.value > 0 ? currentTime.value / duration.value : 0);
let audio = null;
const urlCache = /* @__PURE__ */ new Map();
function getAudio() {
if (!audio) {
audio = new Audio();
audio.addEventListener("timeupdate", () => {
currentTime.value = audio.currentTime;
});
audio.addEventListener("durationchange", () => {
duration.value = audio.duration || 0;
});
audio.addEventListener("ended", () => {
state.value = "idle";
currentTrack.value = null;
});
audio.addEventListener("error", () => {
console.error("[tts] playback error");
state.value = "idle";
currentTrack.value = null;
});
}
return audio;
}
const speakableMessages = computed(() => {
const result = [];
for (let i = 0; i < store.messages.length; i++) {
const m = store.messages[i];
if (m.role === "assistant" && !m.streaming && m.content) {
result.push({ msg: m, index: i });
}
}
return result;
});
function contentKey(text) {
let h = 0;
for (let i = 0; i < text.length; i++) {
h = (h << 5) - h + text.charCodeAt(i) | 0;
}
return String(h);
}
async function play(msg, sourceIndex) {
const el = getAudio();
el.pause();
const rawText = stripMdForTts(msg.content || "");
if (!rawText) return;
const text = rawText.slice(0, 4096);
const snippet = rawText.slice(0, 60) + (rawText.length > 60 ? "..." : "");
currentTrack.value = { msgRef: msg, sourceIndex, snippet, audioUrl: null };
currentTime.value = 0;
duration.value = 0;
state.value = "loading";
const key = contentKey(text);
let url = urlCache.get(key);
if (!url) {
try {
const token = localStorage.getItem("nyx_session") || "";
const apiBase = getApiBase();
const res = await fetch(`${apiBase}/api/tts`, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
body: JSON.stringify({ text })
});
if (!res.ok) throw new Error(`TTS ${res.status}`);
const data = await res.json();
url = `${apiBase}${data.url}?token=${encodeURIComponent(token)}`;
urlCache.set(key, url);
} catch (err) {
console.error("[tts]", err);
state.value = "idle";
currentTrack.value = null;
return;
}
}
if (currentTrack.value) currentTrack.value.audioUrl = url;
el.src = url;
try {
await el.play();
state.value = "playing";
} catch (err) {
console.error("[tts] play failed:", err);
state.value = "idle";
currentTrack.value = null;
}
}
function pause() {
if (audio && state.value === "playing") {
audio.pause();
state.value = "paused";
}
}
function resume() {
if (audio && state.value === "paused") {
audio.play();
state.value = "playing";
}
}
function stop() {
if (audio) {
audio.pause();
audio.removeAttribute("src");
audio.load();
}
state.value = "idle";
currentTrack.value = null;
currentTime.value = 0;
duration.value = 0;
}
function seek(fraction) {
if (audio && duration.value > 0) {
audio.currentTime = fraction * duration.value;
}
}
function nav(delta) {
if (!currentTrack.value) return;
const msgs = speakableMessages.value;
const idx = msgs.findIndex((m) => m.msg === currentTrack.value.msgRef);
if (idx < 0) return;
const next2 = msgs[idx + delta];
if (next2) play(next2.msg, next2.index);
}
function prev() {
nav(-1);
}
function next() {
nav(1);
}
function isPlayingMsg(msg) {
return currentTrack.value?.msgRef === msg;
}
watch(() => store.localSessionId, () => {
stop();
urlCache.clear();
});
return {
state,
currentTrack,
currentTime,
duration,
progress,
speakableMessages,
play,
pause,
resume,
stop,
seek,
prev,
next,
isPlayingMsg
};
}
let _instance = null;
function useTtsPlayer() {
if (!_instance) _instance = createTtsPlayer();
return _instance;
}
const _sfc_main$3 = /* @__PURE__ */ defineComponent({
__name: "TtsPlayerBar",
__ssrInlineRender: true,
setup(__props) {
const tts = useTtsPlayer();
function formatTime(secs) {
if (!secs || !isFinite(secs)) return "0:00";
const m = Math.floor(secs / 60);
const s = Math.floor(secs % 60);
return `${m}:${s.toString().padStart(2, "0")}`;
}
return (_ctx, _push, _parent, _attrs) => {
if (unref(tts).state.value !== "idle") {
_push(`<div${ssrRenderAttrs(mergeProps({ class: "tts-player-bar" }, _attrs))} data-v-7125b8a5><button class="tts-nav-btn" title="Previous" data-v-7125b8a5><svg class="w-4 h-4" viewBox="0 0 20 20" fill="currentColor" data-v-7125b8a5><path d="M15.707 15.707a1 1 0 01-1.414 0l-5-5a1 1 0 010-1.414l5-5a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 010 1.414zm-6 0a1 1 0 01-1.414 0l-5-5a1 1 0 010-1.414l5-5a1 1 0 011.414 1.414L5.414 10l4.293 4.293a1 1 0 010 1.414z" data-v-7125b8a5></path></svg></button><button class="tts-play-btn"${ssrRenderAttr("title", unref(tts).state.value === "playing" ? "Pause" : "Play")} data-v-7125b8a5>`);
if (unref(tts).state.value === "loading") {
_push(`<span class="tts-bar-spinner" data-v-7125b8a5></span>`);
} else if (unref(tts).state.value === "playing") {
_push(`<svg class="w-4 h-4" viewBox="0 0 20 20" fill="currentColor" data-v-7125b8a5><path fill-rule="evenodd" d="M5.75 3a.75.75 0 00-.75.75v12.5a.75.75 0 001.5 0V3.75A.75.75 0 005.75 3zm8.5 0a.75.75 0 00-.75.75v12.5a.75.75 0 001.5 0V3.75a.75.75 0 00-.75-.75z" clip-rule="evenodd" data-v-7125b8a5></path></svg>`);
} else {
_push(`<svg class="w-4 h-4" viewBox="0 0 20 20" fill="currentColor" data-v-7125b8a5><path d="M6.3 2.841A1.5 1.5 0 004 4.11V15.89a1.5 1.5 0 002.3 1.269l9.344-5.89a1.5 1.5 0 000-2.538L6.3 2.84z" data-v-7125b8a5></path></svg>`);
}
_push(`</button><button class="tts-nav-btn" title="Next" data-v-7125b8a5><svg class="w-4 h-4" viewBox="0 0 20 20" fill="currentColor" data-v-7125b8a5><path d="M4.293 15.707a1 1 0 010-1.414L8.586 10 4.293 5.707a1 1 0 011.414-1.414l5 5a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0zm6 0a1 1 0 010-1.414L14.586 10l-4.293-4.293a1 1 0 011.414-1.414l5 5a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0z" data-v-7125b8a5></path></svg></button><div class="tts-progress" data-v-7125b8a5><div class="tts-progress-fill" style="${ssrRenderStyle({ width: unref(tts).progress.value * 100 + "%" })}" data-v-7125b8a5></div></div><span class="tts-time" data-v-7125b8a5>${ssrInterpolate(formatTime(unref(tts).currentTime.value))} / ${ssrInterpolate(formatTime(unref(tts).duration.value))}</span><span class="tts-snippet" data-v-7125b8a5>${ssrInterpolate(unref(tts).currentTrack.value?.snippet || "")}</span><button class="tts-close-btn" title="Close" data-v-7125b8a5><svg class="w-4 h-4" viewBox="0 0 20 20" fill="currentColor" data-v-7125b8a5><path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" data-v-7125b8a5></path></svg></button></div>`);
} else {
_push(`<!---->`);
}
};
}
});
const _sfc_setup$3 = _sfc_main$3.setup;
_sfc_main$3.setup = (props, ctx) => {
const ssrContext = useSSRContext();
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/components/TtsPlayerBar.vue");
return _sfc_setup$3 ? _sfc_setup$3(props, ctx) : void 0;
};
const TtsPlayerBar = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__scopeId", "data-v-7125b8a5"]]);
const _sfc_main$2 = /* @__PURE__ */ defineComponent({
__name: "App",
__ssrInlineRender: true,
setup(__props) {
const router = useRouter();
const AgentsView = defineAsyncComponent(() => import("./assets/AgentsView-CFS236kk.js"));
const ViewerView = defineAsyncComponent(() => import("./assets/ViewerView-5go7ZmZ8.js"));
const DevView = defineAsyncComponent(() => import("./assets/DevView-CqaEvdwT.js"));
const chatStore = useChatStore();
const visited = reactive({ agents: false, viewer: false, dev: false });
const isSocketRoute = computed(() => ["agents", "viewer", "dev"].includes(route.name));
const routerReady = ref(false);
router.isReady().then(() => {
routerReady.value = true;
});
const route = useRoute();
const { version: version2 } = useUI(ws.status);
const { doLogout } = auth;
const { currentUser: currentUser2, connected: connected2, status: status2, sessionId: sessionId2, disconnect: disconnect2, onMessage: onWsMessage, replayBuffer: replayBuffer2 } = ws;
const { selectedAgent, updateFromServer } = agents;
const { theme: theme2 } = useTheme();
function handleLogout() {
doLogout(disconnect2);
visited.agents = visited.viewer = visited.dev = false;
}
function maybeConnect() {
if (auth.isLoggedIn.value && !ws.connected.value) {
ws.connect(agents.selectedAgent, auth.isLoggedIn, auth.loginError, agents.selectedMode);
}
}
router.beforeEach((to) => {
if (to.meta?.requiresSocket) {
if (!auth.isLoggedIn.value) return { name: "login" };
if (!selectedAgent.value) {
const urlAgent = to.query?.agent;
const saved = sessionStorage.getItem("agent");
if (urlAgent) selectedAgent.value = urlAgent;
else if (saved) selectedAgent.value = saved;
}
maybeConnect();
}
});
router.afterEach((to) => {
const name = to.name;
if (name in visited) visited[name] = true;
});
watch(connected2, (isConnected) => {
if (!isConnected) chatStore.setConnecting();
});
onWsMessage((data) => {
if (data.type === "connection_state" && data.state) {
chatStore.applyConnectionState(data.state);
}
if (data.type === "channel_state" && data.state) {
chatStore.applyChannelState(data.state);
}
});
replayBuffer2((data) => {
if (data.type === "connection_state" && data.state) chatStore.applyConnectionState(data.state);
if (data.type === "channel_state" && data.state) chatStore.applyChannelState(data.state);
});
watch(theme2, (t) => {
const logo = THEME_LOGOS[t];
const el = document.querySelector('link[rel~="icon"]');
if (el) el.href = logo ?? "/favicon.ico";
});
const beVersion = ref("");
const envLabel = "prod";
const versionState = ref("short");
const fullVersionText = computed(() => `[${envLabel}] fe: ${version2} | be: ${beVersion.value || "…"}`);
computed(() => {
if (versionState.value === "short") return version2.split("-")[0];
if (versionState.value === "copied") return "✓ copied";
return fullVersionText.value;
});
onMounted(() => {
onWsMessage((data) => {
if (data.type === "ready" || data.type === "auth_ok") {
connected2.value = true;
currentUser2.value = data.user;
sessionId2.value = data.sessionId;
status2.value = "Connected";
if (data.version) {
beVersion.value = data.version;
chatStore.beVersion = data.version;
}
updateFromServer(data);
if (route.path === "/login") router.push("/agents");
} else if (data.type === "cost_update") {
chatStore.sessionUsage = data.usage;
chatStore.sessionCost = data.cost;
}
});
});
return (_ctx, _push, _parent, _attrs) => {
const _component_RouterView = resolveComponent("RouterView");
_push(`<div${ssrRenderAttrs(mergeProps({
id: "app",
class: "app-container"
}, _attrs))}><div class="app-body">`);
_push(ssrRenderComponent(_sfc_main$4, { onLogout: handleLogout }, null, _parent));
_push(`<div class="sidebar-spacer"></div><div class="main-column"><div class="content-area">`);
_push(ssrRenderComponent(TtsPlayerBar, null, null, _parent));
if (visited.agents) {
_push(ssrRenderComponent(unref(AgentsView), {
class: { "view-hidden": unref(route).name !== "agents" }
}, null, _parent));
} else {
_push(`<!---->`);
}
if (visited.viewer) {
_push(ssrRenderComponent(unref(ViewerView), {
class: { "view-hidden": unref(route).name !== "viewer" }
}, null, _parent));
} else {
_push(`<!---->`);
}
if (visited.dev) {
_push(ssrRenderComponent(unref(DevView), {
class: { "view-hidden": unref(route).name !== "dev" }
}, null, _parent));
} else {
_push(`<!---->`);
}
if (routerReady.value && !isSocketRoute.value) {
_push(ssrRenderComponent(_component_RouterView, null, null, _parent));
} else {
_push(`<!---->`);
}
_push(`</div>`);
_push(ssrRenderComponent(GridOverlay, null, null, _parent));
_push(ssrRenderComponent(BreakpointBadge, null, null, _parent));
_push(`</div></div></div>`);
};
}
});
const _sfc_setup$2 = _sfc_main$2.setup;
_sfc_main$2.setup = (props, ctx) => {
const ssrContext = useSSRContext();
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/App.vue");
return _sfc_setup$2 ? _sfc_setup$2(props, ctx) : void 0;
};
const vertexShader = `
attribute vec2 a_position;
uniform float u_aspect;
void main() {
vec2 pos = a_position;
pos.x /= u_aspect;
gl_Position = vec4(pos, 0, 1);
gl_PointSize = 7.0;
}
`;
const fragmentShader = `
precision mediump float;
uniform vec3 u_color;
void main() {
float d = distance(gl_PointCoord, vec2(0.5));
if (d > 0.5) discard;
float alpha = (0.5 - d) * 2.0;
gl_FragColor = vec4(u_color, alpha * 0.9);
}
`;
const _sfc_main$1 = /* @__PURE__ */ defineComponent({
__name: "WebGLBackground",
__ssrInlineRender: true,
setup(__props) {
const canvas = ref();
const ready = ref(false);
let ctx = null;
let animationId;
let resizeHandler = null;
function createShader(gl, type, source) {
const shader = gl.createShader(type);
if (!shader) return null;
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
function createProgram(gl, vs, fs) {
const program = gl.createProgram();
if (!program) return null;
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program));
return null;
}
return program;
}
onMounted(() => {
const c = canvas.value;
if (!c) return;
resizeHandler = () => {
c.width = window.innerWidth;
c.height = window.innerHeight;
};
resizeHandler();
window.addEventListener("resize", resizeHandler);
ctx = c.getContext("webgl", { alpha: true, premultipliedAlpha: false });
if (!ctx) return;
const gl = ctx;
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
const vs = createShader(gl, gl.VERTEX_SHADER, vertexShader);
const fs = createShader(gl, gl.FRAGMENT_SHADER, fragmentShader);
if (!vs || !fs) return;
const program = createProgram(gl, vs, fs);
if (!program) return;
const positionLoc = gl.getAttribLocation(program, "a_position");
const colorLoc = gl.getUniformLocation(program, "u_color");
const aspectLoc = gl.getUniformLocation(program, "u_aspect");
const particleCount = 150;
const positions = new Float32Array(particleCount * 2);
const velocities = new Float32Array(particleCount * 2);
const initAspect = c.width / c.height;
for (let i = 0; i < particleCount; i++) {
positions[i * 2] = (Math.random() * 2 - 1) * initAspect;
positions[i * 2 + 1] = Math.random() * 2 - 1;
velocities[i * 2] = (Math.random() - 0.5) * 6e-4;
velocities[i * 2 + 1] = (Math.random() - 0.5) * 6e-4;
}
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
gl.useProgram(program);
const cssColor = getComputedStyle(document.documentElement).getPropertyValue("--secondary").trim();
let pr = 0.4, pg = 0.6, pb = 1;
if (cssColor.startsWith("#") && cssColor.length >= 7) {
pr = parseInt(cssColor.slice(1, 3), 16) / 255;
pg = parseInt(cssColor.slice(3, 5), 16) / 255;
pb = parseInt(cssColor.slice(5, 7), 16) / 255;
}
gl.uniform3f(colorLoc, pr, pg, pb);
let frameCount = 0;
function animate() {
if (!ready.value && ++frameCount > 30) ready.value = true;
gl.viewport(0, 0, c.width, c.height);
gl.uniform1f(aspectLoc, c.width / c.height);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
const aspect = c.width / c.height;
for (let i = 0; i < particleCount; i++) {
positions[i * 2] += velocities[i * 2];
positions[i * 2 + 1] += velocities[i * 2 + 1];
if (positions[i * 2] > aspect) positions[i * 2] -= 2 * aspect;
if (positions[i * 2] < -aspect) positions[i * 2] += 2 * aspect;
if (positions[i * 2 + 1] > 1) positions[i * 2 + 1] -= 2;
if (positions[i * 2 + 1] < -1) positions[i * 2 + 1] += 2;
}
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.DYNAMIC_DRAW);
gl.drawArrays(gl.POINTS, 0, particleCount);
animationId = requestAnimationFrame(animate);
}
animate();
});
onUnmounted(() => {
cancelAnimationFrame(animationId);
if (resizeHandler) window.removeEventListener("resize", resizeHandler);
});
return (_ctx, _push, _parent, _attrs) => {
_push(`<canvas${ssrRenderAttrs(mergeProps({
ref_key: "canvas",
ref: canvas,
class: ["webgl-bg", { ready: ready.value }]
}, _attrs))} data-v-3b55d999></canvas>`);
};
}
});
const _sfc_setup$1 = _sfc_main$1.setup;
_sfc_main$1.setup = (props, ctx) => {
const ssrContext = useSSRContext();
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/components/WebGLBackground.vue");
return _sfc_setup$1 ? _sfc_setup$1(props, ctx) : void 0;
};
const WebGLBackground = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-3b55d999"]]);
const _sfc_main = /* @__PURE__ */ defineComponent({
__name: "LoginView",
__ssrInlineRender: true,
setup(__props) {
useRouter();
const { isLoggedIn, loginToken, loginError, loggingIn } = auth;
return (_ctx, _push, _parent, _attrs) => {
_push(`<div${ssrRenderAttrs(mergeProps({ class: "login-view" }, _attrs))}>`);
_push(ssrRenderComponent(WebGLBackground, null, null, _parent));
_push(`<div class="login-card">`);
if (unref(isLoggedIn)) {
_push(`<!--[--><h2>✅ Already signed in</h2><p class="login-info">You&#39;re connected. Head back to chat or sign out below.</p><button>Go to Chat</button><button class="logout-btn">Sign out</button><!--]-->`);
} else {
_push(`<!--[--><h2>🔐 Sign in</h2><label class="login-label">Enter your Login Token</label><input${ssrRenderAttr("value", unref(loginToken))} type="password" placeholder="Login Token"${ssrIncludeBooleanAttr(unref(loggingIn)) ? " disabled" : ""} autofocus><button${ssrIncludeBooleanAttr(unref(loggingIn)) ? " disabled" : ""}>${ssrInterpolate(unref(loggingIn) ? "Connecting..." : "Connect")}</button>`);
if (unref(loginError)) {
_push(`<div class="login-error">${ssrInterpolate(unref(loginError))}</div>`);
} else {
_push(`<!---->`);
}
_push(`<!--]-->`);
}
_push(`</div></div>`);
};
}
});
const _sfc_setup = _sfc_main.setup;
_sfc_main.setup = (props, ctx) => {
const ssrContext = useSSRContext();
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/LoginView.vue");
return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
};
const routes = [
{ path: "/", name: "home", component: () => import("./assets/CompanyView-9MVdsTPz.js"), meta: { suffix: "" } },
{ path: "/impressum", name: "impressum", component: () => import("./assets/ImpressumView-B77dj09a.js"), meta: { suffix: "Impressum" } },
{ path: "/datenschutz", name: "datenschutz", component: () => import("./assets/DatenschutzView-CmUxA-7Z.js"), meta: { suffix: "Datenschutz" } },
{ path: "/login", name: "login", component: _sfc_main, meta: { suffix: "Login" } },
{ path: "/agents", name: "agents", component: () => import("./assets/AgentsView-CFS236kk.js"), meta: { suffix: "Home", requiresSocket: true } },
{ path: "/chat", redirect: "/agents" },
{ path: "/dev", name: "dev", component: () => import("./assets/DevView-CqaEvdwT.js"), meta: { suffix: "Dev", requiresSocket: true } },
{ path: "/viewer", name: "viewer", component: () => import("./assets/ViewerView-5go7ZmZ8.js"), meta: { suffix: "Viewer", requiresSocket: true } },
{ path: "/:pathMatch(.*)*", redirect: "/" }
];
const _h = typeof window !== "undefined" ? window.__hermes || (window.__hermes = {}) : {};
if (typeof window !== "undefined" && !_h._origConsole) {
const MAX = 200;
const buf = _h.console || [];
const orig = { log: console.log, warn: console.warn, error: console.error, info: console.info, debug: console.debug };
for (const [level, fn] of Object.entries(orig)) {
console[level] = (...args) => {
buf.push({ t: Date.now(), l: level, m: args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ") });
if (buf.length > MAX) buf.splice(0, buf.length - MAX);
fn.apply(console, args);
};
}
_h.console = buf;
_h._origConsole = orig;
}
const createApp = ViteSSG(
_sfc_main$2,
{ routes },
({ app }) => {
app.use(createPinia());
}
);
export {
THEME_ICONS as T,
_export_sfc as _,
agents as a,
auth as b,
useTtsPlayer as c,
createApp,
useUI as d,
agentLogo as e,
useTheme as f,
getApiBase as g,
useDevFlags as h,
useViewerStore as i,
lastViewerPath as l,
scrollbarOptions as s,
takeover as t,
useChatStore as u,
ws as w
};