- 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>
2891 lines
108 KiB
JavaScript
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'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
|
|
};
|