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

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

2325 lines
97 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ref, triggerRef, watch, computed, nextTick, onUnmounted, defineComponent, mergeProps, useSSRContext, createSlots, withCtx, openBlock, createBlock, createCommentVNode, toDisplayString, Fragment, renderList, createVNode, unref, createTextVNode, resolveDynamicComponent, withModifiers, onUpdated, toRef, onMounted, resolveComponent } from "vue";
import { ssrRenderAttrs, ssrRenderSlot, ssrRenderComponent, ssrRenderAttr, ssrInterpolate, ssrRenderList, ssrRenderVNode, ssrRenderClass, ssrIncludeBooleanAttr, ssrRenderStyle } from "vue/server-renderer";
import { useRouter, useRoute } from "vue-router";
import { u as useChatStore, w as ws, a as agents, g as getApiBase, b as auth, _ as _export_sfc, c as useTtsPlayer, d as useUI, e as agentLogo, s as scrollbarOptions } from "../main.mjs";
import { marked } from "marked";
import { OverlayScrollbarsComponent } from "overlayscrollbars-vue";
import { BoltIcon, ComputerDesktopIcon, CpuChipIcon, GlobeAltIcon, DocumentPlusIcon, WrenchIcon, PencilIcon, BookOpenIcon, ChatBubbleLeftIcon, LinkIcon, Cog6ToothIcon, SpeakerWaveIcon, ExclamationTriangleIcon, ChevronDownIcon, InformationCircleIcon, LockClosedIcon, UserGroupIcon, ChatBubbleBottomCenterTextIcon, PaperClipIcon, MicrophoneIcon, StopIcon, ArrowUpIcon } from "@heroicons/vue/20/solid";
import "@unhead/vue/server";
import "pinia";
import "@heroicons/vue/24/outline";
import "overlayscrollbars";
const VISIBLE_PAGE$1 = 50;
const MAX_HUD_NODES = 100;
function useSessionHistory(isAgentRunning, visibleCount, agentIdFn) {
const store = useChatStore();
const sessionHistoryComplete = ref(false);
const lastUsage = ref(null);
let loadStartTime = null;
let pendingMessages = [];
let pendingUsageTotals = null;
const lastSystemMsgRef = ref(null);
const hudTree = ref([]);
const hudVersion = ref(0);
const hudPending = /* @__PURE__ */ new Map();
const hudTurns = /* @__PURE__ */ new Map();
const toolCallMap = /* @__PURE__ */ new Map();
function lookupByToolCallId(toolCallId) {
if (!toolCallId) return null;
const ref2 = toolCallMap.get(toolCallId);
if (!ref2) return null;
const node = ref2.deref();
if (!node) {
toolCallMap.delete(toolCallId);
return null;
}
return node;
}
const activeTurnCorrId = ref(null);
function makeNode(partial) {
return {
id: partial.id || crypto.randomUUID(),
type: partial.type || "received",
state: partial.state || "running",
label: partial.label || "",
children: [],
replay: partial.replay ?? false,
startedAt: partial.startedAt || Date.now(),
...partial
};
}
function findNode(nodes, corrId) {
for (const n of nodes) {
if (n.correlationId === corrId) return n;
if (n.children) {
const found = findNode(n.children, corrId);
if (found) return found;
}
}
return null;
}
function addHudNode(node, parentCorrelationId) {
if (parentCorrelationId && parentCorrelationId !== "history") {
const parent = hudTurns.get(parentCorrelationId);
if (parent) {
parent.children.push(node);
triggerRef(hudTree);
hudVersion.value++;
return;
}
}
hudTree.value.unshift(node);
if (hudTree.value.length > MAX_HUD_NODES) hudTree.value.splice(MAX_HUD_NODES);
triggerRef(hudTree);
hudVersion.value++;
}
function pushHudEvent(event) {
const replay = !!event.replay;
const ts = event.ts || Date.now();
const corrId = event.correlationId;
const parentId = event.parentId;
switch (event.event) {
case "turn_start": {
if (corrId && (hudPending.has(corrId) || hudTurns.has(corrId))) break;
const node = makeNode({
type: "turn",
state: "running",
label: "🔄 Turn",
correlationId: corrId,
startedAt: ts,
replay
});
if (corrId) {
hudPending.set(corrId, node);
hudTurns.set(corrId, node);
}
if (!replay && corrId) activeTurnCorrId.value = corrId;
addHudNode(node);
break;
}
case "turn_end": {
const node = corrId ? hudPending.get(corrId) : null;
if (node) {
node.state = "done";
node.endedAt = ts;
node.durationMs = event.durationMs;
if (corrId) {
hudPending.delete(corrId);
hudTurns.delete(corrId);
}
triggerRef(hudTree);
hudVersion.value++;
}
if (!replay && corrId && activeTurnCorrId.value === corrId) activeTurnCorrId.value = null;
break;
}
case "think_start": {
const node = makeNode({
type: "think",
state: "running",
label: "Thinking",
correlationId: corrId,
startedAt: ts,
replay
});
if (corrId) hudPending.set(corrId, node);
addHudNode(node, parentId);
break;
}
case "think_end": {
const node = corrId ? hudPending.get(corrId) : null;
if (node) {
node.state = "done";
node.endedAt = ts;
node.durationMs = event.durationMs;
if (corrId) hudPending.delete(corrId);
triggerRef(hudTree);
hudVersion.value++;
} else {
addHudNode(makeNode({ type: "think", state: "done", label: "Thinking", correlationId: corrId, startedAt: ts, endedAt: ts, durationMs: event.durationMs, replay }), parentId);
}
break;
}
case "tool_start": {
const tool = event.tool || "unknown";
const args = event.args || {};
const label = buildToolLabel(tool, args);
const node = makeNode({
type: "tool",
state: "running",
label,
tool,
args,
correlationId: corrId,
startedAt: ts,
replay
});
if (corrId) hudPending.set(corrId, node);
if (event.toolCallId) toolCallMap.set(event.toolCallId, new WeakRef(node));
addHudNode(node, parentId);
lastSystemMsgRef.value = label;
break;
}
case "tool_end": {
const tool = event.tool || "unknown";
const result = event.result || {};
let node = lookupByToolCallId(event.toolCallId) ?? (corrId ? hudPending.get(corrId) : null);
if (!node && parentId) {
const turnNode = findNode(hudTree.value, parentId);
if (turnNode?.children) {
const match = turnNode.children.find(
(n) => n.type === "tool" && n.state === "running" && (n.tool === tool || n.tool === "unknown")
);
if (match) {
node = match;
if (match.correlationId) hudPending.delete(match.correlationId);
}
}
}
if (node) {
node.state = result.ok === false ? "error" : "done";
node.result = result;
node.endedAt = ts;
node.durationMs = event.durationMs;
node.label = buildToolLabel(tool, node.args || {}, result);
if (corrId) hudPending.delete(corrId);
if (event.toolCallId) toolCallMap.delete(event.toolCallId);
triggerRef(hudTree);
hudVersion.value++;
} else {
const label = buildToolLabel(tool, event.args || {}, result);
addHudNode(makeNode({ type: "tool", state: "done", label, tool, result, correlationId: corrId, startedAt: ts, endedAt: ts, durationMs: event.durationMs, replay }), parentId);
}
break;
}
case "received": {
const node = makeNode({
type: "received",
state: "done",
subtype: event.subtype,
label: event.label || event.subtype || "received",
startedAt: ts,
endedAt: ts,
replay
});
addHudNode(node);
break;
}
}
}
function buildToolLabel(tool, args, result) {
const fileTools = ["read", "write", "edit", "append"];
if (fileTools.includes(tool)) {
const vp = args.viewerPath || args.path || "";
const filename = vp.split("/").pop() || vp;
const area = result?.area || args.area;
const areaStr = area ? `:L${area.startLine}${area.endLine}` : "";
return `${filename}${areaStr}`;
}
if (tool === "exec") {
const cmd = args.command || "";
return `${cmd.slice(0, 60)}${cmd.length > 60 ? "…" : ""}`;
}
if (tool === "web_fetch") return (args.url || "").slice(0, 60);
if (tool === "web_search") return (args.query || "").slice(0, 60);
return tool;
}
function getToolsForTurn(corrId) {
if (!corrId) return [];
const turn = hudTree.value.find((n) => n.correlationId === corrId) ?? [...hudTurns.values()].find((n) => n.correlationId === corrId);
return turn ? turn.children.filter((c) => c.type === "tool") : [];
}
function hudSnapshot() {
const lines = [`HUD tree — ${hudTree.value.length} root node(s)
`];
for (const node of hudTree.value) {
const dur = node.durationMs != null ? ` [${node.durationMs}ms]` : "";
const repl = node.replay ? " (replay)" : "";
lines.push(` ${node.state === "running" ? "⏳" : node.state === "error" ? "❌" : "✅"} [${node.type}] ${node.label}${dur}${repl}`);
lines.push(` id=${node.id.slice(0, 8)} corrId=${(node.correlationId || "—").slice(0, 8)} children=${node.children.length}`);
for (const child of node.children) {
const cdur = child.durationMs != null ? ` [${child.durationMs}ms]` : "";
lines.push(` ${child.state === "running" ? "⏳" : child.state === "error" ? "❌" : "✅"} [${child.type}] ${child.label}${cdur}`);
if (child.args) lines.push(` args: ${JSON.stringify(child.args).slice(0, 80)}`);
if (child.result) lines.push(` result: ${JSON.stringify(child.result).slice(0, 80)}`);
}
}
return lines.join("\n");
}
function pushSystem(text) {
lastSystemMsgRef.value = text;
}
function flushPendingClear(pendingClearRef) {
if (!pendingClearRef.value) return;
pendingClearRef.value = false;
store.clearMessages();
visibleCount.value = VISIBLE_PAGE$1;
sessionHistoryComplete.value = false;
loadStartTime = performance.now();
pendingMessages = [];
pendingUsageTotals = null;
}
function revealMessages() {
loadStartTime = null;
const _pending = pendingMessages;
const _usage = pendingUsageTotals;
pendingMessages = [];
pendingUsageTotals = null;
const idx = store.messages.findIndex((m) => m.role === "system" && m.content.includes("Loading session history..."));
if (idx !== -1) {
store.messages.splice(idx, 1, ..._pending);
} else {
store.messages.unshift(..._pending);
}
const revealedCount = _pending.filter((m) => m.role !== "system").length;
store.sessionContextHint = revealedCount > 0 ? `${revealedCount} msgs in context` : "fresh context";
if (_usage) lastUsage.value = _usage;
}
function handleSessionHistory(entries) {
if (!entries?.length) return;
if (loadStartTime === null) loadStartTime = performance.now();
if (!store.messages.some((m) => m.content?.includes("Loading session history..."))) {
store.pushSystem("⏳ Loading session history...", agentIdFn());
}
const newMsgs = [];
const currentAgentId = agentIdFn();
const currentSessionId = store.localSessionId;
let pendingUsage = null;
for (const data of entries) {
if (data.type === "hud") {
pushHudEvent({ ...data, replay: true });
continue;
}
if (data.event === "tool_start" || data.event === "tool_end" || data.event === "think_start" || data.event === "think_end" || data.event === "turn_start" || data.event === "turn_end" || data.event === "received") {
pushHudEvent({ ...data, replay: true });
continue;
}
if (data.entry_type === "session_context") {
newMsgs.push({ role: "session_context", content: data.content || "", agentId: currentAgentId, sessionId: currentSessionId });
} else if (data.entry_type === "user_message") {
newMsgs.push({ role: "user", content: data.content || "", agentId: currentAgentId, sessionId: currentSessionId });
} else if (data.entry_type === "assistant_text") {
const content = (data.content || "").replace(/^\[\[reply_to[^\]]*\]\]\s*/i, "").trim();
if (!content) continue;
const msg = { role: "assistant", content, streaming: false, agentId: currentAgentId, sessionId: currentSessionId, timestamp: data.ts || null };
if (data.truncated) msg.truncated = true;
if (pendingUsage) {
msg.usage = pendingUsage;
pendingUsage = null;
}
newMsgs.push(msg);
} else if (data.entry_type === "usage") {
pendingUsage = {
input_tokens: data.input_tokens || 0,
output_tokens: data.output_tokens || 0,
total_tokens: data.total_tokens || 0,
cost: Number(data.cost || 0)
};
const last = newMsgs[newMsgs.length - 1];
if (last?.role === "assistant") {
last.usage = pendingUsage;
pendingUsage = null;
}
}
}
pendingMessages = newMsgs;
const totalUsage = entries.filter((e) => e.entry_type === "usage").reduce((acc, e) => ({
input_tokens: acc.input_tokens + (e.input_tokens || 0),
output_tokens: acc.output_tokens + (e.output_tokens || 0),
total_tokens: acc.total_tokens + (e.total_tokens || 0),
cost: acc.cost + Number(e.cost || 0)
}), { input_tokens: 0, output_tokens: 0, total_tokens: 0, cost: 0 });
pendingUsageTotals = totalUsage.total_tokens > 0 ? totalUsage : null;
}
function handleSessionEntry(data, sentMessages, pushSystemFn) {
if (data.type === "hud") {
pushHudEvent(data);
return;
}
const isReplay = !sessionHistoryComplete.value;
const currentAgentId = agentIdFn();
const currentSessionId = store.localSessionId;
switch (data.entry_type) {
case "user_message": {
const raw = data.content || "";
if (raw.startsWith("A new session was started")) break;
if (!isReplay && raw.includes("[voice transcript]:")) break;
const hasByMsgId = data.msgId && store.messages.some((m) => m.msgId === data.msgId);
if (hasByMsgId) break;
if (!isReplay && !sentMessages.has(raw.trim())) {
store.messages.push({ role: "user", content: raw, agentId: currentAgentId, sessionId: currentSessionId, msgId: data.msgId });
} else {
sentMessages.delete(raw.trim());
}
break;
}
}
}
function resetHudMaps() {
hudPending.clear();
hudTurns.clear();
toolCallMap.clear();
activeTurnCorrId.value = null;
hudTree.value = [];
hudVersion.value++;
}
function toolCallMapSnapshot() {
return [...toolCallMap.entries()].map(([k, ref2]) => {
const node = ref2.deref();
return { toolCallId: k, label: node?.label ?? null, state: node?.state ?? null, stale: !node };
});
}
return {
sessionHistoryComplete,
lastUsage,
lastSystemMsgRef,
hudTree,
hudVersion,
activeTurnCorrId,
getToolsForTurn,
pushHudEvent,
hudSnapshot,
toolCallMapSnapshot,
resetHudMaps,
flushPendingClear,
revealMessages,
handleSessionHistory,
handleSessionEntry,
pushSystem
};
}
function useAgentSocket(visibleCount, lastUsage, pendingClearRef, sentMessages, restoreLastSent) {
const chatStore = useChatStore();
const { send: wsSend, onMessage: onWsMessage, replayBuffer } = ws;
const { updateFromServer, selectedAgent } = agents;
const handoverInProgress = () => chatStore.smState === "HANDOVER_PENDING" || chatStore.smState === "HANDOVER_DONE";
const isAgentRunning = () => chatStore.smState === "AGENT_RUNNING";
const history = useSessionHistory(isAgentRunning, visibleCount, () => selectedAgent.value);
history.lastUsage = lastUsage;
watch(history.activeTurnCorrId, (id) => {
chatStore.activeTurnCorrId = id;
});
function pushSystem(text) {
history.pushSystem(text);
}
function mount() {
const handlers = {
auth_ok(data) {
updateFromServer(data);
},
ready(data) {
updateFromServer(data);
if (data.session_id) chatStore.sessionKey = data.session_id;
else if (data.sessionId) chatStore.sessionKey = data.sessionId;
chatStore.applyConnectionState("SYNCED");
chatStore.applyChannelState("READY");
},
// assay session info — store session ID for reconnect
session_info(data) {
if (data.session_id) chatStore.sessionKey = data.session_id;
},
// assay UI controls — store for later rendering
controls(_data) {
},
// assay artifacts — store for later rendering
artifacts(_data) {
},
// assay session cleared
cleared(_data) {
chatStore.messages.splice(0);
},
thinking(data) {
if (!handoverInProgress()) chatStore.appendThinking(data.content);
},
delta(data) {
if (!handoverInProgress()) {
history.flushPendingClear(pendingClearRef);
chatStore.collapseThinking();
chatStore.appendAssistantDelta(data.content, data.agentId);
}
},
message(data) {
if (handoverInProgress()) return;
history.flushPendingClear(pendingClearRef);
if (data.streaming === false) {
chatStore.createCompleteAssistantMessage(data.content, data.agentId, data.usage);
} else if (data.final) {
chatStore.finalizeAssistantMessage(null, data.usage);
}
},
truncated_warning(_data) {
chatStore.collapseThinking();
if (chatStore.hasActiveStreamingMessage()) chatStore.finalizeAssistantMessage(null, void 0, true);
chatStore.truncatedWarning = true;
},
done(data) {
if (handoverInProgress()) return;
chatStore.collapseThinking();
if (data.suppress) {
chatStore.suppressAssistantMessage();
} else {
const doneContent = data.content || null;
if (chatStore.hasActiveStreamingMessage()) {
const deltaLen = chatStore.streamingMessageLength();
const useContent = doneContent && deltaLen < doneContent.length ? doneContent : null;
chatStore.finalizeAssistantMessage(useContent, data.usage);
} else if (doneContent) {
chatStore.createCompleteAssistantMessage(doneContent, void 0, data.usage);
}
}
history.lastSystemMsgRef.value = null;
},
session_history(data) {
if (chatStore.hasActiveStreamingMessage()) chatStore.finalizeAssistantMessage(null);
chatStore.collapseThinking();
history.flushPendingClear(pendingClearRef);
history.handleSessionHistory(data.entries);
},
hud(data) {
history.pushHudEvent(data);
if (data.event === "turn_start" && !data.replay) {
chatStore.activeTurnCorrId = data.correlationId ?? null;
chatStore.startNewAssistantMessage(selectedAgent.value);
}
},
event(data) {
if (data.event === "agent") {
const stream = data.payload?.stream;
if (stream === "tool") {
console.log("[HUD agent/tool]", JSON.stringify(data.payload).slice(0, 400));
}
}
},
tool(data) {
if (data.action === "call") {
pushSystem(`${data.tool} ${data.args || ""}`);
} else if (data.action === "result") {
pushSystem(`${data.result || ""}`);
}
},
session_entry(data) {
history.handleSessionEntry(data, sentMessages, pushSystem);
},
message_update(data) {
if (!data.msgId || !data.patch) return;
const patch = { ...data.patch };
if (patch.voiceAudioUrl) {
const token = localStorage.getItem("nyx_session") || localStorage.getItem("titan_token") || "";
patch.voiceAudioUrl = `${patch.voiceAudioUrl}?token=${encodeURIComponent(token)}`;
}
if (patch.transcript) {
sentMessages.add(`[voice transcript]: ${patch.transcript}`.trim());
}
const patched = chatStore.patchMessage(data.msgId, patch);
if (!patched) {
console.warn("[message_update] no message found for msgId:", data.msgId);
}
},
handover_done(data) {
chatStore.messages.push({
role: "assistant",
content: data.content || "📋 Handover written.",
agentId: selectedAgent.value,
sessionId: chatStore.localSessionId
});
chatStore.messages.push({
role: "system",
content: "Start a new session with this handover context?",
confirmNew: true,
confirmed: false,
agentId: selectedAgent.value,
sessionId: chatStore.localSessionId
});
},
handover_context(_data) {
},
// New two-SM protocol: channel_state + connection_state
channel_state(data) {
if (!data.state) return;
if (data.clear_history) {
console.log("[clear] channel switch", { state: data.state, msgCount: chatStore.messages.length });
history.resetHudMaps();
chatStore.messages.length = 0;
pendingClearRef.value = false;
}
const prevChannel = chatStore.channelState;
chatStore.applyChannelState(data.state);
if (data.state === "READY" || data.state === "FRESH") {
history.lastSystemMsgRef.value = null;
if (chatStore.queuedThought !== null && prevChannel === "AGENT_RUNNING") {
const thought = chatStore.queuedThought;
chatStore.queuedThought = null;
wsSend({ type: "message", content: thought });
}
}
},
connection_state(data) {
if (!data.state) return;
chatStore.applyConnectionState(data.state);
if (data.state === "LOADING_HISTORY") {
history.resetHudMaps();
chatStore.messages.length = 0;
pendingClearRef.value = false;
}
if (data.state === "SYNCED") {
history.flushPendingClear(pendingClearRef);
history.revealMessages();
}
},
// Legacy: still handle session_state for backward compat
session_state(data) {
if (!data.state) return;
if (data.reconnected) ;
if (data.reconnected || data.clear_history) {
console.log("[clear] immediate flush", { reconnected: data.reconnected, clear_history: data.clear_history, state: data.state, msgCount: chatStore.messages.length });
chatStore.stashMessages();
history.resetHudMaps();
chatStore.messages.length = 0;
pendingClearRef.value = false;
}
chatStore.applySessionState(data.state);
if (data.state === "READY" || data.state === "FRESH" || data.state === "IDLE") {
history.lastSystemMsgRef.value = null;
if (chatStore.queuedThought !== null) {
const thought = chatStore.queuedThought;
chatStore.queuedThought = null;
wsSend({ type: "message", content: thought });
}
}
},
session_total_tokens(data) {
chatStore.sessionTotalTokens = data;
},
finance_update(data) {
chatStore.finance = data;
},
usage(data) {
if (!handoverInProgress()) {
chatStore.sessionTotalTokens = {
input_tokens: data.input_tokens || (chatStore.sessionTotalTokens?.input_tokens || 0),
cache_read_tokens: data.cache_read_tokens || (chatStore.sessionTotalTokens?.cache_read_tokens || 0),
output_tokens: data.output_tokens || (chatStore.sessionTotalTokens?.output_tokens || 0)
};
}
},
session_status(data) {
if (data.status === "no_session") {
pendingClearRef.value = true;
history.flushPendingClear(pendingClearRef);
chatStore.messages.push({
role: "system",
type: "no_session",
content: "-- NO SESSION --",
agentId: selectedAgent.value,
sessionId: chatStore.localSessionId
});
chatStore.sessionContextHint = "";
} else if (data.status === "watching") {
history.flushPendingClear(pendingClearRef);
history.sessionHistoryComplete.value = true;
history.revealMessages();
}
},
sent(_data) {
},
switch_ok(data) {
history.sessionHistoryComplete.value = false;
if (data.sessionKey) chatStore.sessionKey = data.sessionKey;
},
new_ok(_data) {
history.sessionHistoryComplete.value = false;
},
error(data) {
if (data.code === "SESSION_TERMINATED") {
chatStore.pushSystem("⚠️ Message not delivered — session was resetting. Please try again.", selectedAgent.value);
restoreLastSent?.();
} else if (data.code === "DISCARDED_NOT_IDLE" || data.code === "DISCARDED_NOT_READY") {
chatStore.pushSystem("⚠️ Message not delivered — agent was busy. Please try again.", selectedAgent.value);
restoreLastSent?.();
}
},
stopped(_data) {
chatStore.pushSystem("✅ Agent stopped", selectedAgent.value);
},
killed(_data) {
chatStore.pushSystem("☠️ Agent killed", selectedAgent.value);
}
};
const unsubscribe = onWsMessage((data) => {
const handler = handlers[data.type];
if (handler) handler(data);
});
replayBuffer((data) => {
const handler = handlers[data.type];
if (handler) handler(data);
});
return unsubscribe;
}
return {
mount,
lastSystemMsg: history.lastSystemMsgRef,
hudTree: history.hudTree,
hudVersion: history.hudVersion,
getToolsForTurn: history.getToolsForTurn,
hudSnapshot: history.hudSnapshot,
toolCallMapSnapshot: history.toolCallMapSnapshot,
sessionHistoryComplete: history.sessionHistoryComplete,
pushSystem,
hasActiveStreamingMessage: chatStore.hasActiveStreamingMessage
};
}
function generateMsgId() {
return crypto.randomUUID();
}
const renderer = new marked.Renderer();
renderer.link = ({ href, title, text }) => {
const titleAttr = title ? ` title="${title}"` : "";
return `<a href="${href}"${titleAttr} target="_blank" rel="noopener noreferrer">${text}</a>`;
};
function ansiToHtml(text) {
const colorMap = {
30: "#555",
31: "#e06c75",
32: "#98c379",
33: "#e5c07b",
34: "#61afef",
35: "#c678dd",
36: "#56b6c2",
37: "#abb2bf"
};
let open = false, bold = false, dim = false;
const result = text.replace(/\x1b\[([0-9;]*)m/g, (_match, codes) => {
const parts = codes.split(";").map(Number);
let out = "";
for (const code of parts) {
if (code === 0) {
if (open) {
out += "</span>";
open = false;
}
if (bold) {
out += "</strong>";
bold = false;
}
if (dim) {
out += "</span>";
dim = false;
}
} else if (code === 1) {
if (!bold) {
out += "<strong>";
bold = true;
}
} else if (code === 2) {
if (!dim) {
out += '<span style="opacity:0.45">';
dim = true;
}
} else if (colorMap[code]) {
if (open) {
out += "</span>";
}
out += `<span style="color:${colorMap[code]}">`;
open = true;
}
}
return out;
});
let tail = "";
if (open) tail += "</span>";
if (bold) tail += "</strong>";
if (dim) tail += "</span>";
return result + tail;
}
const WORKSPACE_PATH_RE = /(workspace\/[^\s"'<>)]+\.(?:pdf|png|jpg|jpeg|gif|csv|json|txt|md|html|zip|mp3|wav|ogg|webm|m4a))/g;
const WORKSPACE_PREFIX = "";
const AUDIO_EXTENSIONS = /* @__PURE__ */ new Set(["mp3", "wav", "ogg", "webm", "m4a"]);
function linkifyWorkspaceFiles(html) {
return html.replace(WORKSPACE_PATH_RE, (match) => {
const name = match.split("/").pop() || match;
const absPath = match.startsWith("/") ? match : WORKSPACE_PREFIX + match;
const ext = name.split(".").pop()?.toLowerCase() || "";
if (AUDIO_EXTENSIONS.has(ext)) {
return `<audio controls class="inline-audio" data-filepath="${absPath}" onplay="window.__hermesAudioSrc(this)"></audio>`;
}
return `<button class="file-download-link" data-filepath="${absPath}" data-filename="${name}" onclick="window.__hermesDownload(this)" title="Download ${name}">📎 ${name}</button>`;
});
}
if (typeof window !== "undefined" && !window.__hermesAudioSrc) {
window.__hermesAudioSrc = (el) => {
if (el.src) return;
const filepath = el.dataset.filepath;
if (!filepath) return;
const token = localStorage.getItem("nyx_session") || localStorage.getItem("titan_token") || "";
const apiBase = getApiBase();
el.src = `${apiBase}/api/files${filepath}?token=${encodeURIComponent(token)}`;
};
}
if (typeof window !== "undefined" && !window.__hermesDownload) {
window.__hermesDownload = async (el) => {
const filepath = el.dataset.filepath;
const filename = el.dataset.filename || "download";
if (!filepath) return;
el.textContent = "⏳ " + filename;
try {
const token = localStorage.getItem("nyx_session") || localStorage.getItem("titan_token") || "";
const apiBase2 = getApiBase();
const res = await fetch(`${apiBase2}/api/files${filepath}?token=${encodeURIComponent(token)}`);
if (!res.ok) throw new Error(`${res.status}`);
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
el.textContent = "✅ " + filename;
} catch (err) {
el.textContent = "❌ " + filename;
console.error("[download]", err);
}
};
}
function parseMd(content) {
const raw = content || "";
if (/\x1b\[/.test(raw)) {
const escaped = raw.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
return `<pre style="font-family:var(--font-mono);line-height:1.5;white-space:pre-wrap">${ansiToHtml(escaped)}</pre>`;
}
let html = marked.parse(raw, { renderer, async: false, gfm: true, breaks: true });
html = linkifyWorkspaceFiles(html);
return html;
}
const DRAFT_KEY = "chat_draft";
const HISTORY_KEY = "chat_input_history";
const HISTORY_MAX = 50;
function loadDraft() {
try {
return sessionStorage.getItem(DRAFT_KEY) || "";
} catch {
return "";
}
}
function saveDraft(v) {
try {
if (v) sessionStorage.setItem(DRAFT_KEY, v);
else sessionStorage.removeItem(DRAFT_KEY);
} catch {
}
}
function loadHistory() {
try {
return JSON.parse(sessionStorage.getItem(HISTORY_KEY) || "[]");
} catch {
return [];
}
}
function pushHistory(v) {
try {
const h = loadHistory().filter((x) => x !== v);
h.unshift(v);
sessionStorage.setItem(HISTORY_KEY, JSON.stringify(h.slice(0, HISTORY_MAX)));
} catch {
}
}
function useMessages(wsSendFn) {
const store = useChatStore();
const sending = ref(false);
const input = ref(loadDraft());
const messagesEl = ref(null);
let historyIdx = -1;
function getViewport() {
const el = messagesEl.value;
if (!el) return null;
const root = el.$el || el;
return root.querySelector?.("[data-overlayscrollbars-viewport]") || root;
}
function scrollToBottom() {
nextTick(() => {
nextTick(() => {
requestAnimationFrame(() => {
const vp = getViewport();
if (vp) vp.scrollTop = vp.scrollHeight;
});
});
});
}
function scrollIfAtBottom() {
const vp = getViewport();
if (!vp) return;
if (vp.scrollHeight - vp.scrollTop - vp.clientHeight < 80) scrollToBottom();
}
let draftTimer = null;
function onInputChange() {
if (draftTimer) clearTimeout(draftTimer);
draftTimer = setTimeout(() => saveDraft(input.value), 1e3);
}
function navigateHistory(dir) {
const history = loadHistory();
if (!history.length) return;
if (dir === "up") {
historyIdx = Math.min(historyIdx + 1, history.length - 1);
} else {
historyIdx = Math.max(historyIdx - 1, -1);
}
input.value = historyIdx === -1 ? "" : history[historyIdx];
}
let lastSentContent = "";
function restoreLastSent() {
input.value = lastSentContent;
}
async function send(attachmentPayload) {
const hasText = input.value.trim().length > 0;
const hasAttachments = attachmentPayload && attachmentPayload.length > 0;
if (!hasText && !hasAttachments || sending.value) return;
const content = input.value.trim();
if (hasText) {
lastSentContent = content;
pushHistory(content);
}
historyIdx = -1;
input.value = "";
saveDraft("");
sending.value = true;
const msgId = generateMsgId();
const localAttachments = hasAttachments ? attachmentPayload.map((a) => {
const mime = a.mimeType.split(";")[0];
let dataUrl;
if (a.mimeType.startsWith("audio/")) {
const bytes = Uint8Array.from(atob(a.content), (c) => c.charCodeAt(0));
dataUrl = URL.createObjectURL(new Blob([bytes], { type: mime }));
} else {
dataUrl = `data:${mime};base64,${a.content}`;
}
return { mimeType: a.mimeType, fileName: a.fileName, dataUrl };
}) : void 0;
const hasAudio = hasAttachments && attachmentPayload.some((a) => a.mimeType.startsWith("audio/"));
store.pushMessage({
role: "user",
content,
agentId: null,
msgId,
attachments: localAttachments,
pending: hasAudio
// audio messages are pending until transcript arrives
});
const payload = { type: "message", content, msgId };
if (hasAttachments) payload.attachments = attachmentPayload;
wsSendFn(payload);
sending.value = false;
}
return {
sending,
input,
messagesEl,
parseMd,
scrollToBottom,
scrollIfAtBottom,
send,
onInputChange,
navigateHistory,
restoreLastSent,
startNewAssistantMessage: store.startNewAssistantMessage,
appendAssistantMessage: store.appendAssistantDelta,
finalizeAssistantMessage: store.finalizeAssistantMessage,
resetAssistantMessageState: store.resetLocalSession,
hasActiveStreamingMessage: store.hasActiveStreamingMessage,
streamingMessageVisibleContent: computed(() => store.streamingMessageVisibleContent)
};
}
function useMessageGrouping(messages, visibleCount, selectedAgent, allAgents, sessionKey) {
const VISIBLE_PAGE2 = 50;
const visibleMsgs = computed(() => {
const all = messages.value;
const start = Math.max(0, all.length - visibleCount.value);
return all.slice(start).map((m, i) => ({ ...m, _sourceIndex: start + i }));
});
const hasMore = computed(() => messages.value.length > visibleCount.value);
function loadMore() {
visibleCount.value += VISIBLE_PAGE2;
}
function getFormattedAgentName(agentId) {
if (!agentId) return "Unknown";
const agent = allAgents.value.find((a) => a.id === agentId);
return agent ? agent.name : agentId;
}
function shouldShowHeadline(index, msgsArr) {
if (index === 0) return true;
const current = msgsArr[index];
const prev = msgsArr[index - 1];
if (!current.agentId || !prev.agentId) {
return current.sessionId !== prev.sessionId;
}
return current.agentId !== prev.agentId || current.sessionId !== prev.sessionId;
}
function formatHeadlineText(agentName) {
const key = sessionKey?.value;
return key ? `${agentName} · ${key}` : agentName;
}
function getHeadline(index, msgsArr) {
const current = msgsArr[index];
const targetAgentId = current.agentId || selectedAgent.value;
const agentName = getFormattedAgentName(targetAgentId);
if (index === 0) return { text: formatHeadlineText(agentName), kind: "agent" };
const prev = msgsArr[index - 1];
if (current.agentId !== prev.agentId) return { text: formatHeadlineText(agentName), kind: "agent" };
if (current.sessionId !== prev.sessionId) return { text: "New Session", kind: "new-session" };
return { text: formatHeadlineText(agentName), kind: "agent" };
}
const groupedVisibleMsgs = computed(() => {
const raw = visibleMsgs.value;
const result = [];
let currentGroup = null;
for (let i = 0; i < raw.length; i++) {
const msg = raw[i];
if (shouldShowHeadline(i, raw)) {
if (currentGroup) {
result.push(currentGroup);
currentGroup = null;
}
const { text, kind } = getHeadline(i, raw);
result.push({
role: "system",
type: "headline",
content: text,
headlineKind: kind,
agentId: msg.agentId,
sessionId: msg.sessionId,
position: "header"
// Header appears before agent block
});
}
if (msg.role === "system" && msg.type !== "no_session") {
if (!currentGroup) {
currentGroup = { role: "system_group", messages: [msg], agentId: msg.agentId, sessionId: msg.sessionId };
} else {
currentGroup.messages.push(msg);
}
} else {
if (currentGroup) {
result.push(currentGroup);
currentGroup = null;
}
result.push(msg);
const effectiveAgentId = msg.agentId || selectedAgent.value;
if (effectiveAgentId && i === raw.length - 1) {
const agentName = getFormattedAgentName(effectiveAgentId);
result.push({
role: "system",
type: "headline",
content: formatHeadlineText(agentName),
headlineKind: "agent",
agentId: effectiveAgentId,
sessionId: msg.sessionId,
position: "footer"
});
}
}
}
if (currentGroup) result.push(currentGroup);
return result;
});
return {
visibleMsgs,
groupedVisibleMsgs,
hasMore,
loadMore,
getFormattedAgentName
};
}
function useInputAutogrow(input) {
const inputEl = ref(null);
const isShaking = ref(false);
function autoGrow() {
const el = inputEl.value;
if (!el) return;
el.style.height = "auto";
el.style.height = el.scrollHeight + "px";
el.style.overflowY = el.scrollHeight > 160 ? "auto" : "hidden";
}
function triggerShake() {
isShaking.value = true;
setTimeout(() => {
isShaking.value = false;
}, 400);
}
watch(input, (val) => {
if (!val) nextTick(() => autoGrow());
});
return { inputEl, isShaking, autoGrow, triggerShake };
}
function useAgentDisplay(selectedAgent, defaultAgent, allAgents) {
const chatStore = useChatStore();
const defaultAgentName = computed(() => {
const agent = allAgents.value.find((a) => a.id === defaultAgent.value);
return agent ? agent.name : defaultAgent.value;
});
const agentDisplayName = computed(() => {
const agent = allAgents.value.find((a) => a.id === selectedAgent.value);
return (agent ? agent.name : selectedAgent.value).toUpperCase();
});
const isAgentRunning = computed(() => chatStore.smState === "AGENT_RUNNING");
const agentStatusDone = computed(() => chatStore.channelState === "READY" || chatStore.channelState === "FRESH");
const agentStatus = computed(() => {
switch (chatStore.smState) {
case "CONNECTING":
return "⚙️ Connecting…";
case "AGENT_RUNNING":
return "⚙️ Working…";
case "HANDOVER_PENDING":
return "📝 Writing handover…";
case "HANDOVER_DONE":
return "✅ Handover ready";
case "SWITCHING":
return "🔀 Switching…";
default:
return null;
}
});
return { defaultAgentName, agentDisplayName, isAgentRunning, agentStatusDone, agentStatus };
}
const ACCEPTED_TYPES = [
"image/jpeg",
"image/png",
"image/gif",
"image/webp",
"application/pdf",
"audio/webm",
"audio/mp4",
"audio/ogg",
"audio/mpeg",
"audio/wav",
"audio/x-m4a"
];
const MAX_BYTES = 10 * 1024 * 1024;
function readAsBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const result = reader.result;
const idx = result.indexOf(",");
resolve(idx >= 0 ? result.slice(idx + 1) : result);
};
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(file);
});
}
function useAttachments() {
const attachments = ref([]);
async function addFiles(files) {
for (const file of Array.from(files)) {
const baseType = file.type.split(";")[0];
if (!ACCEPTED_TYPES.includes(baseType) && !ACCEPTED_TYPES.includes(file.type)) {
console.warn(`[attachments] skipped ${file.name}: unsupported type ${file.type}`);
continue;
}
if (file.size > MAX_BYTES) {
console.warn(`[attachments] skipped ${file.name}: exceeds 5MB (${(file.size / 1024 / 1024).toFixed(1)}MB)`);
continue;
}
const base64 = await readAsBase64(file);
const preview = URL.createObjectURL(file);
attachments.value.push({ file, preview, base64, mimeType: file.type, fileName: file.name });
}
}
function removeAttachment(index) {
const att = attachments.value[index];
if (att) URL.revokeObjectURL(att.preview);
attachments.value.splice(index, 1);
}
function clearAttachments() {
for (const att of attachments.value) URL.revokeObjectURL(att.preview);
attachments.value = [];
}
function toPayload() {
return attachments.value.map((a) => ({
type: a.mimeType.startsWith("image/") ? "image" : a.mimeType.startsWith("audio/") ? "audio" : "document",
mimeType: a.mimeType,
content: a.base64,
fileName: a.fileName
}));
}
function hasAttachments() {
return attachments.value.length > 0;
}
return { attachments, addFiles, removeAttachment, clearAttachments, toPayload, hasAttachments };
}
function useAudioRecorder() {
const isRecording = ref(false);
const duration = ref(0);
const audioLevel = ref(0);
const micDenied = ref(false);
let mediaRecorder = null;
let stream = null;
let audioCtx = null;
let analyser = null;
let levelBuf = null;
let chunks = [];
let timer = null;
let startTime = 0;
function cleanup() {
if (timer) {
clearInterval(timer);
timer = null;
}
if (audioCtx) {
audioCtx.close().catch(() => {
});
audioCtx = null;
analyser = null;
levelBuf = null;
}
if (stream) {
stream.getTracks().forEach((t) => t.stop());
stream = null;
}
mediaRecorder = null;
chunks = [];
duration.value = 0;
audioLevel.value = 0;
isRecording.value = false;
}
function updateLevel() {
if (!analyser || !levelBuf) return;
analyser.getByteTimeDomainData(levelBuf);
let sum = 0;
for (let i = 0; i < levelBuf.length; i++) {
const v = (levelBuf[i] - 128) / 128;
sum += v * v;
}
audioLevel.value = Math.min(1, Math.sqrt(sum / levelBuf.length) * 3);
}
async function startRecording() {
if (isRecording.value) return;
try {
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
} catch (err) {
console.warn("[audio] mic access denied:", err);
micDenied.value = true;
setTimeout(() => {
micDenied.value = false;
}, 5e3);
return;
}
try {
audioCtx = new AudioContext();
const source = audioCtx.createMediaStreamSource(stream);
analyser = audioCtx.createAnalyser();
analyser.fftSize = 256;
source.connect(analyser);
levelBuf = new Uint8Array(analyser.fftSize);
} catch (err) {
console.warn("[audio] analyser setup failed:", err);
}
chunks = [];
const mimeType = MediaRecorder.isTypeSupported("audio/webm;codecs=opus") ? "audio/webm;codecs=opus" : MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "";
mediaRecorder = new MediaRecorder(stream, mimeType ? { mimeType } : void 0);
mediaRecorder.ondataavailable = (e) => {
if (e.data.size > 0) chunks.push(e.data);
};
mediaRecorder.start(250);
isRecording.value = true;
startTime = Date.now();
timer = setInterval(() => {
duration.value = Math.floor((Date.now() - startTime) / 1e3);
updateLevel();
}, 80);
}
function stopRecording() {
return new Promise((resolve) => {
if (!mediaRecorder || mediaRecorder.state === "inactive") {
cleanup();
resolve(null);
return;
}
mediaRecorder.onstop = () => {
const mimeType = mediaRecorder?.mimeType || "audio/webm";
const ext = mimeType.includes("mp4") ? "mp4" : mimeType.includes("ogg") ? "ogg" : "webm";
const blob = new Blob(chunks, { type: mimeType });
const file = new File([blob], `recording-${Date.now()}.${ext}`, { type: mimeType });
cleanup();
resolve(file);
};
mediaRecorder.stop();
});
}
function cancelRecording() {
if (mediaRecorder && mediaRecorder.state !== "inactive") {
mediaRecorder.onstop = () => {
};
mediaRecorder.stop();
}
cleanup();
}
function formatDuration(secs) {
const m = Math.floor(secs / 60);
const s = secs % 60;
return `${m}:${s.toString().padStart(2, "0")}`;
}
onUnmounted(cleanup);
return { isRecording, duration, audioLevel, micDenied, startRecording, stopRecording, cancelRecording, formatDuration };
}
const _sfc_main$6 = /* @__PURE__ */ defineComponent({
__name: "MessageFrame",
__ssrInlineRender: true,
props: {
role: {},
copyContent: {}
},
setup(__props) {
return (_ctx, _push, _parent, _attrs) => {
_push(`<div${ssrRenderAttrs(mergeProps({
class: ["message", __props.role]
}, _attrs))}>`);
if (__props.role !== "system") {
_push(`<button class="copy-btn" title="Copy">⎘</button>`);
} else {
_push(`<!---->`);
}
ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent);
if (_ctx.$slots.footer) {
_push(`<div class="bubble-footer">`);
ssrRenderSlot(_ctx.$slots, "footer", {}, null, _push, _parent);
_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/MessageFrame.vue");
return _sfc_setup$6 ? _sfc_setup$6(props, ctx) : void 0;
};
const _sfc_main$5 = /* @__PURE__ */ defineComponent({
__name: "UserMessage",
__ssrInlineRender: true,
props: {
msg: {}
},
setup(__props) {
const props = __props;
const username = auth.currentUser;
const expandedSrc = ref("");
function expandImage(src) {
expandedSrc.value = src;
}
const audioAttachment = computed(() => (props.msg.attachments || []).find((a) => a.mimeType?.startsWith("audio/")));
const nonAudioAttachments = computed(() => (props.msg.attachments || []).filter((a) => !a.mimeType?.startsWith("audio/")));
return (_ctx, _push, _parent, _attrs) => {
_push(`<!--[-->`);
_push(ssrRenderComponent(_sfc_main$6, {
role: "user",
copyContent: __props.msg.content
}, createSlots({
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
if (__props.msg.voiceAudioUrl) {
_push2(`<audio controls${ssrRenderAttr("src", __props.msg.voiceAudioUrl)} class="user-att-audio" data-v-90f9dace${_scopeId}></audio>`);
} else if (audioAttachment.value) {
_push2(`<audio controls${ssrRenderAttr("src", audioAttachment.value.dataUrl)} class="user-att-audio" data-v-90f9dace${_scopeId}></audio>`);
} else {
_push2(`<!---->`);
}
if (__props.msg.pending) {
_push2(`<div class="voice-pending" data-v-90f9dace${_scopeId}>transcribing...</div>`);
} else {
_push2(`<!---->`);
}
if (__props.msg.content) {
_push2(`<div data-v-90f9dace${_scopeId}>${ssrInterpolate(__props.msg.content)}</div>`);
} else {
_push2(`<!---->`);
}
if (nonAudioAttachments.value.length) {
_push2(`<div class="user-attachments" data-v-90f9dace${_scopeId}><!--[-->`);
ssrRenderList(nonAudioAttachments.value, (att, i) => {
_push2(`<!--[-->`);
if (att.mimeType === "application/pdf") {
_push2(`<div class="user-att-pdf"${ssrRenderAttr("title", att.fileName || "PDF")} data-v-90f9dace${_scopeId}><span class="pdf-icon" data-v-90f9dace${_scopeId}>📄</span><span class="pdf-name" data-v-90f9dace${_scopeId}>${ssrInterpolate(att.fileName || "document.pdf")}</span></div>`);
} else {
_push2(`<div class="user-att-thumb" data-v-90f9dace${_scopeId}><img${ssrRenderAttr("src", att.dataUrl)}${ssrRenderAttr("alt", att.fileName || "image")} loading="lazy" data-v-90f9dace${_scopeId}></div>`);
}
_push2(`<!--]-->`);
});
_push2(`<!--]--></div>`);
} else {
_push2(`<!---->`);
}
} else {
return [
__props.msg.voiceAudioUrl ? (openBlock(), createBlock("audio", {
key: 0,
controls: "",
src: __props.msg.voiceAudioUrl,
class: "user-att-audio"
}, null, 8, ["src"])) : audioAttachment.value ? (openBlock(), createBlock("audio", {
key: 1,
controls: "",
src: audioAttachment.value.dataUrl,
class: "user-att-audio"
}, null, 8, ["src"])) : createCommentVNode("", true),
__props.msg.pending ? (openBlock(), createBlock("div", {
key: 2,
class: "voice-pending"
}, "transcribing...")) : createCommentVNode("", true),
__props.msg.content ? (openBlock(), createBlock("div", { key: 3 }, toDisplayString(__props.msg.content), 1)) : createCommentVNode("", true),
nonAudioAttachments.value.length ? (openBlock(), createBlock("div", {
key: 4,
class: "user-attachments"
}, [
(openBlock(true), createBlock(Fragment, null, renderList(nonAudioAttachments.value, (att, i) => {
return openBlock(), createBlock(Fragment, { key: i }, [
att.mimeType === "application/pdf" ? (openBlock(), createBlock("div", {
key: 0,
class: "user-att-pdf",
title: att.fileName || "PDF"
}, [
createVNode("span", { class: "pdf-icon" }, "📄"),
createVNode("span", { class: "pdf-name" }, toDisplayString(att.fileName || "document.pdf"), 1)
], 8, ["title"])) : (openBlock(), createBlock("div", {
key: 1,
class: "user-att-thumb",
onClick: ($event) => expandImage(att.dataUrl)
}, [
createVNode("img", {
src: att.dataUrl,
alt: att.fileName || "image",
loading: "lazy"
}, null, 8, ["src", "alt"])
], 8, ["onClick"]))
], 64);
}), 128))
])) : createCommentVNode("", true)
];
}
}),
_: 2
}, [
unref(username) ? {
name: "footer",
fn: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(`${ssrInterpolate(unref(username))}`);
} else {
return [
createTextVNode(toDisplayString(unref(username)), 1)
];
}
}),
key: "0"
} : void 0
]), _parent));
if (expandedSrc.value) {
_push(`<div class="lightbox-overlay" data-v-90f9dace><img${ssrRenderAttr("src", expandedSrc.value)} class="lightbox-img" data-v-90f9dace></div>`);
} else {
_push(`<!---->`);
}
_push(`<!--]-->`);
};
}
});
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/UserMessage.vue");
return _sfc_setup$5 ? _sfc_setup$5(props, ctx) : void 0;
};
const UserMessage = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-90f9dace"]]);
const _sfc_main$4 = /* @__PURE__ */ defineComponent({
__name: "ToolIcon",
__ssrInlineRender: true,
props: {
tool: {}
},
setup(__props) {
const props = __props;
const iconMap = {
read: BookOpenIcon,
write: PencilIcon,
edit: WrenchIcon,
append: DocumentPlusIcon,
exec: BoltIcon,
web_search: GlobeAltIcon,
web_fetch: GlobeAltIcon,
memory_search: CpuChipIcon,
memory_get: CpuChipIcon,
browser: ComputerDesktopIcon
};
const icon = computed(() => {
if (!props.tool) return BoltIcon;
const t = props.tool.toLowerCase();
if (iconMap[t]) return iconMap[t];
if (t.includes("message")) return ChatBubbleLeftIcon;
if (t.includes("session")) return LinkIcon;
return Cog6ToothIcon;
});
return (_ctx, _push, _parent, _attrs) => {
ssrRenderVNode(_push, createVNode(resolveDynamicComponent(icon.value), mergeProps({ class: "tool-icon w-3.5 h-3.5 inline shrink-0" }, _attrs), null), _parent);
};
}
});
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/ToolIcon.vue");
return _sfc_setup$4 ? _sfc_setup$4(props, ctx) : void 0;
};
function relativeTime(isoOrDate) {
const then = typeof isoOrDate === "string" ? new Date(isoOrDate) : isoOrDate;
const now = Date.now();
const diffMs = now - then.getTime();
if (diffMs < 0) return "just now";
const sec = Math.floor(diffMs / 1e3);
if (sec < 60) return "just now";
const min = Math.floor(sec / 60);
if (min < 60) return `${min}m ago`;
const hr = Math.floor(min / 60);
if (hr < 24) return `${hr}h ago`;
const days = Math.floor(hr / 24);
if (days < 30) return `${days}d ago`;
return then.toLocaleDateString();
}
const _sfc_main$3 = /* @__PURE__ */ defineComponent({
__name: "AssistantMessage",
__ssrInlineRender: true,
props: {
msg: {},
agentDisplayName: {},
isAgentRunning: { type: Boolean },
allAgents: {},
getToolsForTurn: { type: Function },
hudVersion: {}
},
setup(__props) {
const chatStore = useChatStore();
const ttsPlayer = useTtsPlayer();
const props = __props;
const displayName = computed(() => {
const id = props.msg.agentId;
if (id) {
const agent = props.allAgents?.find((a) => a.id === id);
return (agent ? agent.name : id).toUpperCase();
}
return props.agentDisplayName;
});
const msgTimeLabel = computed(() => {
if (props.msg.timestamp) {
return relativeTime(props.msg.timestamp);
}
return "Done";
});
const content = computed(() => {
if (props.msg.streaming) {
return chatStore.streamingMessageVisibleContent + '<span class="typing-dots">...</span>';
}
return props.msg.content;
});
const tools = computed(() => {
void props.hudVersion;
return props.getToolsForTurn(props.msg.turnCorrId);
});
return (_ctx, _push, _parent, _attrs) => {
_push(ssrRenderComponent(_sfc_main$6, mergeProps({
role: "assistant",
copyContent: __props.msg.content
}, _attrs), {
footer: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(`<span class="footer-name" data-v-a59db10d${_scopeId}>${ssrInterpolate(displayName.value)}</span>`);
if (!__props.msg.streaming && __props.msg.content) {
_push2(`<button class="${ssrRenderClass([{ active: unref(ttsPlayer).isPlayingMsg(__props.msg) }, "tts-btn"])}" title="Listen" data-v-a59db10d${_scopeId}>`);
if (unref(ttsPlayer).isPlayingMsg(__props.msg) && unref(ttsPlayer).state.value === "loading") {
_push2(`<span class="tts-spinner" data-v-a59db10d${_scopeId}></span>`);
} else {
_push2(ssrRenderComponent(unref(SpeakerWaveIcon), { class: "w-3.5 h-3.5" }, null, _parent2, _scopeId));
}
_push2(`</button>`);
} else {
_push2(`<!---->`);
}
if (__props.msg.streaming && tools.value.length === 0) {
_push2(`<span class="footer-status" data-v-a59db10d${_scopeId}> ...</span>`);
} else if (!__props.msg.streaming && tools.value.length === 0) {
_push2(`<span class="footer-status" data-v-a59db10d${_scopeId}> | ${ssrInterpolate(msgTimeLabel.value)}</span>`);
} else {
_push2(`<!---->`);
}
if (tools.value.length > 0) {
_push2(`<span class="footer-tools" data-v-a59db10d${_scopeId}><!--[-->`);
ssrRenderList(tools.value, (tool) => {
_push2(`<span class="${ssrRenderClass([tool.state, "footer-tool-icon"])}"${ssrRenderAttr("title", `${tool.label} [${tool.state}]`)} data-v-a59db10d${_scopeId}>`);
_push2(ssrRenderComponent(_sfc_main$4, {
tool: tool.tool || ""
}, null, _parent2, _scopeId));
_push2(`</span>`);
});
_push2(`<!--]--></span>`);
} else {
_push2(`<!---->`);
}
if (__props.msg.truncated) {
_push2(`<span class="truncated-notice" data-v-a59db10d${_scopeId}>`);
_push2(ssrRenderComponent(unref(ExclamationTriangleIcon), { class: "w-4 h-4 inline" }, null, _parent2, _scopeId));
_push2(` Output limit reached — response was cut off</span>`);
} else {
_push2(`<!---->`);
}
} else {
return [
createVNode("span", { class: "footer-name" }, toDisplayString(displayName.value), 1),
!__props.msg.streaming && __props.msg.content ? (openBlock(), createBlock("button", {
key: 0,
class: ["tts-btn", { active: unref(ttsPlayer).isPlayingMsg(__props.msg) }],
onClick: withModifiers(($event) => unref(ttsPlayer).play(__props.msg, __props.msg._sourceIndex ?? 0), ["stop"]),
title: "Listen"
}, [
unref(ttsPlayer).isPlayingMsg(__props.msg) && unref(ttsPlayer).state.value === "loading" ? (openBlock(), createBlock("span", {
key: 0,
class: "tts-spinner"
})) : (openBlock(), createBlock(unref(SpeakerWaveIcon), {
key: 1,
class: "w-3.5 h-3.5"
}))
], 10, ["onClick"])) : createCommentVNode("", true),
__props.msg.streaming && tools.value.length === 0 ? (openBlock(), createBlock("span", {
key: 1,
class: "footer-status"
}, " ...")) : !__props.msg.streaming && tools.value.length === 0 ? (openBlock(), createBlock("span", {
key: 2,
class: "footer-status"
}, " | " + toDisplayString(msgTimeLabel.value), 1)) : createCommentVNode("", true),
tools.value.length > 0 ? (openBlock(), createBlock("span", {
key: 3,
class: "footer-tools"
}, [
(openBlock(true), createBlock(Fragment, null, renderList(tools.value, (tool) => {
return openBlock(), createBlock("span", {
key: tool.id,
class: ["footer-tool-icon", tool.state],
title: `${tool.label} [${tool.state}]`
}, [
createVNode(_sfc_main$4, {
tool: tool.tool || ""
}, null, 8, ["tool"])
], 10, ["title"]);
}), 128))
])) : createCommentVNode("", true),
__props.msg.truncated ? (openBlock(), createBlock("span", {
key: 4,
class: "truncated-notice"
}, [
createVNode(unref(ExclamationTriangleIcon), { class: "w-4 h-4 inline" }),
createTextVNode(" Output limit reached — response was cut off")
])) : createCommentVNode("", true)
];
}
}),
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(`<div data-v-a59db10d${_scopeId}>${unref(parseMd)(content.value) ?? ""}</div>`);
} else {
return [
createVNode("div", {
innerHTML: unref(parseMd)(content.value)
}, null, 8, ["innerHTML"])
];
}
}),
_: 1
}, _parent));
};
}
});
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/AssistantMessage.vue");
return _sfc_setup$3 ? _sfc_setup$3(props, ctx) : void 0;
};
const AssistantMessage = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__scopeId", "data-v-a59db10d"]]);
const _sfc_main$2 = /* @__PURE__ */ defineComponent({
__name: "SystemMessage",
__ssrInlineRender: true,
props: {
msg: {}
},
setup(__props) {
const props = __props;
const isCollapsed = ref(false);
const groupContentEl = ref(null);
onUpdated(() => {
nextTick(() => {
if (groupContentEl.value) {
groupContentEl.value.scrollTop = groupContentEl.value.scrollHeight;
}
});
});
function isSqlResult(content) {
if (!content) return false;
const raw = content.startsWith("→ ") ? content.slice(2) : content;
const lines = raw.split("\n").filter((l) => l.trim());
if (lines.length < 2) return false;
return lines.filter((l) => l.includes(" ")).length >= 2;
}
function parseSqlTable(content) {
const raw = content.startsWith("→ ") ? content.slice(2) : content;
const lines = raw.split("\n").filter((l) => l.trim());
const [headerLine, ...dataLines] = lines;
const headers = headerLine.split(" ");
const rows = dataLines.filter((l) => !l.startsWith("… [")).map((l) => l.split(" "));
return { headers, rows };
}
function getSummary() {
const msgs = props.msg.messages;
if (!msgs?.length) return "Event";
const toolNames = [];
for (const m of msgs) {
const raw = m.content || "";
const match = raw.match(/^[^\w]*(\w+)/u);
if (match) {
const name = match[1];
if (!["true", "false", "null", "done", "ok"].includes(name.toLowerCase())) {
if (!toolNames.includes(name)) toolNames.push(name);
}
}
}
const callCount = msgs.length;
const label = toolNames.length ? toolNames.join(" · ") : "Event";
return callCount === 1 ? label : `${label} · ${callCount}`;
}
return (_ctx, _push, _parent, _attrs) => {
_push(`<div${ssrRenderAttrs(mergeProps({ class: "message-hud" }, _attrs))} data-v-6cdcd5f1>`);
if (__props.msg.type === "headline" && __props.msg.headlineKind !== "new-session" && __props.msg.position !== "footer") {
_push(`<div class="headline-container headline-header" data-v-6cdcd5f1><div class="headline-line" data-v-6cdcd5f1></div><div class="headline-text" data-v-6cdcd5f1>${ssrInterpolate(__props.msg.content)}</div><div class="headline-line" data-v-6cdcd5f1></div></div>`);
} else if (__props.msg.type === "headline" && __props.msg.headlineKind !== "new-session" && __props.msg.position === "footer") {
_push(`<div class="headline-footer-wrapper" data-v-6cdcd5f1><div class="headline-container headline-footer" data-v-6cdcd5f1><div class="headline-line" data-v-6cdcd5f1></div><div class="headline-text" data-v-6cdcd5f1>${ssrInterpolate(__props.msg.content)}</div><div class="headline-line" data-v-6cdcd5f1></div></div></div>`);
} else if (__props.msg.type === "headline" && __props.msg.headlineKind === "new-session") {
_push(`<div class="headline-new-session" data-v-6cdcd5f1><span class="new-session-text" data-v-6cdcd5f1>${ssrInterpolate(__props.msg.content)}</span></div>`);
} else if (__props.msg.role === "system_group" || __props.msg.messages) {
_push(`<div class="system-group" data-v-6cdcd5f1><div class="system-group-header" data-v-6cdcd5f1>`);
_push(ssrRenderComponent(unref(Cog6ToothIcon), { class: "system-group-icon-svg" }, null, _parent));
_push(`<span class="system-group-summary" data-v-6cdcd5f1>${ssrInterpolate(getSummary())}</span>`);
_push(ssrRenderComponent(unref(ChevronDownIcon), {
class: ["chevron", { open: !isCollapsed.value }]
}, null, _parent));
_push(`</div>`);
if (!isCollapsed.value) {
_push(`<div class="system-group-content" data-v-6cdcd5f1><!--[-->`);
ssrRenderList(__props.msg.messages, (subMsg, idx) => {
_push(`<div class="system-item" data-v-6cdcd5f1>`);
if (isSqlResult(subMsg.content)) {
_push(`<div class="sql-table-wrap" data-v-6cdcd5f1><table class="sql-table" data-v-6cdcd5f1><thead data-v-6cdcd5f1><tr data-v-6cdcd5f1><!--[-->`);
ssrRenderList(parseSqlTable(subMsg.content).headers, (col, ci) => {
_push(`<th data-v-6cdcd5f1>${ssrInterpolate(col)}</th>`);
});
_push(`<!--]--></tr></thead><tbody data-v-6cdcd5f1><!--[-->`);
ssrRenderList(parseSqlTable(subMsg.content).rows, (row, ri) => {
_push(`<tr data-v-6cdcd5f1><!--[-->`);
ssrRenderList(row, (cell, ci) => {
_push(`<td data-v-6cdcd5f1>${ssrInterpolate(cell)}</td>`);
});
_push(`<!--]--></tr>`);
});
_push(`<!--]--></tbody></table></div>`);
} else {
_push(`<div class="system-content raw-text"${ssrRenderAttr("title", subMsg.content)} data-v-6cdcd5f1>${ssrInterpolate(subMsg.content)}</div>`);
}
_push(`</div>`);
});
_push(`<!--]--></div>`);
} else {
_push(`<!---->`);
}
_push(`</div>`);
} else {
_push(`<div class="system-group" data-v-6cdcd5f1><div class="system-group-header" data-v-6cdcd5f1>`);
_push(ssrRenderComponent(unref(InformationCircleIcon), { class: "system-group-icon-svg" }, null, _parent));
_push(`<span class="system-group-summary" data-v-6cdcd5f1>${ssrInterpolate(__props.msg.content)}</span></div></div>`);
}
_push(`</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/components/SystemMessage.vue");
return _sfc_setup$2 ? _sfc_setup$2(props, ctx) : void 0;
};
const SystemMessage = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-6cdcd5f1"]]);
const _sfc_main$1 = /* @__PURE__ */ defineComponent({
__name: "HudControls",
__ssrInlineRender: true,
props: {
smState: String,
connected: Boolean,
isAgentRunning: Boolean,
handoverPending: Boolean,
isPublic: Boolean
},
emits: ["new", "handover", "confirm-new", "stay"],
setup(__props) {
return (_ctx, _push, _parent, _attrs) => {
_push(`<div${ssrRenderAttrs(mergeProps({ class: "hud-controls" }, _attrs))} data-v-89c5a82c><div class="btn-group primary" data-v-89c5a82c><button class="control-btn"${ssrIncludeBooleanAttr(!__props.connected || __props.isAgentRunning || __props.handoverPending || __props.smState === "NO_SESSION" || __props.smState === "RESETTING") ? " disabled" : ""} data-v-89c5a82c>NEW</button>`);
if (!__props.isPublic) {
_push(`<button class="control-btn"${ssrIncludeBooleanAttr(!__props.connected || __props.isAgentRunning || __props.handoverPending || __props.smState === "NO_SESSION" || __props.smState === "RESETTING") ? " disabled" : ""} data-v-89c5a82c>HANDOVER</button>`);
} else {
_push(`<!---->`);
}
_push(`</div><div class="btn-group secondary" data-v-89c5a82c><button class="control-btn confirm-btn"${ssrIncludeBooleanAttr(!__props.handoverPending) ? " disabled" : ""} data-v-89c5a82c>YES, NEW</button><button class="control-btn stay-btn"${ssrIncludeBooleanAttr(!__props.handoverPending) ? " disabled" : ""} data-v-89c5a82c>STAY</button><button class="control-btn ns-btn"${ssrIncludeBooleanAttr(__props.smState !== "NO_SESSION") ? " disabled" : ""} data-v-89c5a82c>NEW SESSION</button></div></div>`);
};
}
});
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/HudControls.vue");
return _sfc_setup$1 ? _sfc_setup$1(props, ctx) : void 0;
};
const HudControls = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-89c5a82c"]]);
const VISIBLE_PAGE = 50;
const _sfc_main = /* @__PURE__ */ defineComponent({
...{ name: "AgentsView" },
__name: "AgentsView",
__ssrInlineRender: true,
setup(__props) {
useRouter();
const agentsRoute = useRoute();
const chatStore = useChatStore();
const showPicker = computed(() => !agentsRoute.query.agent);
watch(() => agentsRoute.query.agent, (val) => {
if (!val && agentsRoute.name === "agents") ;
}, { immediate: true });
const prevSessions = ref([]);
const prevHasMore = ref(false);
const prevLoading = ref(false);
const prevSkip = ref(0);
const prevMessages = computed(() => prevSessions.value.flatMap((s) => s.messages));
async function fetchPreviousSession(loadMore2 = false) {
if (!selectedAgent.value || prevLoading.value) return;
prevLoading.value = true;
try {
const token = localStorage.getItem("nyx_session") || localStorage.getItem("titan_token") || "";
const skip = loadMore2 ? prevSkip.value : 0;
const apiBase = getApiBase();
const res = await fetch(`${apiBase}/api/session-history?agent=${selectedAgent.value}&mode=${selectedMode.value}&skip=${skip}&count=1`, {
headers: { "Authorization": `Bearer ${token}` }
});
if (!res.ok) return;
const data = await res.json();
const newSessions = (data.sessions || []).map((s) => ({
messages: (s.entries || []).map((e) => ({
role: e.role,
content: e.content,
timestamp: e.timestamp,
agentId: selectedAgent.value,
streaming: false
})),
timestamp: s.resetTimestamp,
timeLabel: s.resetTimestamp ? relativeTime(s.resetTimestamp) : ""
}));
if (loadMore2) {
prevSessions.value = [...newSessions, ...prevSessions.value];
} else {
prevSessions.value = newSessions;
}
prevSkip.value = skip + newSessions.length;
prevHasMore.value = data.hasMore ?? false;
} catch {
} finally {
prevLoading.value = false;
}
}
const { connected, status, send: wsSend } = ws;
useUI(status);
ref("");
const { isLoggedIn } = auth;
const { selectedAgent, selectedMode, filteredAgents, defaultAgent, allAgents } = agents;
const SEGMENTS = ["personal", "common", "private", "public"];
const agentSegments = computed(
() => SEGMENTS.map((key) => ({
key,
agents: filteredAgents.value.filter((a) => (a.segment ?? "utility") === key).sort((a, b) => a.name.localeCompare(b.name))
})).filter((s) => s.agents.length > 0)
);
computed(() => agentLogo(selectedAgent.value));
const { sending, input, messagesEl, scrollToBottom, restoreLastSent } = useMessages(wsSend);
const controlsEl = ref(null);
ref(null);
const smState = toRef(chatStore, "smState");
const channelState = toRef(chatStore, "channelState");
const connectionState = toRef(chatStore, "connectionState");
const smLabel = toRef(chatStore, "smLabel");
const isHidden = computed(() => connectionState.value !== "SYNCED");
const footerHeadline = computed(() => {
const msgs = groupedVisibleMsgs.value;
const last = msgs[msgs.length - 1];
return last?.position === "footer" ? last : null;
});
const lastRealMsgIdx = computed(() => {
const msgs = groupedVisibleMsgs.value;
for (let i = msgs.length - 1; i >= 0; i--) {
if (msgs[i].type !== "headline") return i;
}
return -1;
});
const visibleCount = ref(VISIBLE_PAGE);
const { groupedVisibleMsgs, hasMore, loadMore, getFormattedAgentName } = useMessageGrouping(
computed(() => chatStore.messages),
visibleCount,
selectedAgent,
allAgents,
toRef(chatStore, "sessionKey")
);
const sentMessages = /* @__PURE__ */ new Set();
const lastUsage = ref(null);
const pendingClearRef = ref(false);
const { mount, hudVersion, getToolsForTurn, hudSnapshot, toolCallMapSnapshot } = useAgentSocket(visibleCount, lastUsage, pendingClearRef, sentMessages, restoreLastSent);
if (typeof window !== "undefined") {
window.__hudSnapshot = hudSnapshot;
window.__toolCallMap = toolCallMapSnapshot;
}
const viewActive = computed(() => agentsRoute.name === "agents");
let userSendInFlight = false;
const spacerHeight = ref(0);
function getViewport() {
const el = messagesEl.value;
if (!el) return null;
const root = el.$el || el;
return root.querySelector("[data-overlayscrollbars-viewport]");
}
function recalcSpacerSync() {
const viewport = getViewport();
if (!viewport) return 0;
const controls = controlsEl.value || viewport.querySelector(".msg-controls");
if (!controls) return 0;
const userMsgs = viewport.querySelectorAll(".message.user");
const last = userMsgs[userMsgs.length - 1];
if (!last) return 0;
const vpRect = viewport.getBoundingClientRect();
const st = viewport.scrollTop;
const msgTop = last.getBoundingClientRect().top - vpRect.top + st;
const ctrlBottom = controls.getBoundingClientRect().bottom - vpRect.top + st;
const vpH = viewport.clientHeight;
const needed = msgTop - 40 + vpH;
const h = Math.max(0, needed - ctrlBottom);
spacerHeight.value = h;
return h;
}
let _spacerInterval = null;
onMounted(() => {
_spacerInterval = setInterval(recalcSpacerSync, 200);
onUnmounted(() => {
if (_spacerInterval) clearInterval(_spacerInterval);
});
});
watch(connectionState, (val) => {
if (!viewActive.value) return;
if (val === "SYNCED") {
if (!prevMessages.value.length) fetchPreviousSession();
scrollToBottom();
}
});
watch(channelState, (val, prev) => {
if (!viewActive.value) return;
if (prev === "AGENT_RUNNING" && (val === "READY" || val === "FRESH")) {
userSendInFlight = false;
}
if (val === "AGENT_RUNNING" && prev !== "AGENT_RUNNING") {
if (!userSendInFlight) {
scrollToBottom();
}
}
});
function onNew() {
chatStore.newSession();
scrollToBottom();
}
function onConfirmNew() {
chatStore.confirmNew();
scrollToBottom();
}
const { isShaking } = useInputAutogrow(input);
const { isAgentRunning } = useAgentDisplay(selectedAgent, defaultAgent, allAgents);
const { attachments, hasAttachments } = useAttachments();
ref(null);
const isDragOver = ref(false);
const { isRecording, duration, audioLevel, micDenied, formatDuration } = useAudioRecorder();
watch([selectedAgent, selectedMode], ([agent, mode], [oldAgent, oldMode]) => {
if (agent !== oldAgent || mode !== oldMode) {
sessionStorage.setItem("agent", agent);
lastUsage.value = null;
sentMessages.clear();
prevSessions.value = [];
prevHasMore.value = false;
prevSkip.value = 0;
chatStore.resetLocalSession();
}
});
onMounted(() => {
chatStore.setWsSend(wsSend);
const unsubscribe = mount();
if (connectionState.value === "SYNCED") fetchPreviousSession();
const viewport = getViewport();
let ro = null;
if (viewport) {
ro = new ResizeObserver(() => recalcSpacerSync());
ro.observe(viewport);
}
recalcSpacerSync();
onUnmounted(() => {
unsubscribe();
ro?.disconnect();
});
});
return (_ctx, _push, _parent, _attrs) => {
const _component_RouterLink = resolveComponent("RouterLink");
if (unref(isLoggedIn)) {
_push(`<div${ssrRenderAttrs(mergeProps({ class: "agents-view flex flex-col h-full overflow-hidden bg-bg" }, _attrs))} data-v-8c19bd9d><div class="agent-picker" style="${ssrRenderStyle(showPicker.value ? null : { display: "none" })}" data-v-8c19bd9d><div class="agent-picker-content" data-v-8c19bd9d><p class="agent-picker-title" data-v-8c19bd9d>Select an agent</p><!--[-->`);
ssrRenderList(agentSegments.value, (seg) => {
_push(`<div class="agent-picker-segment" data-v-8c19bd9d><div class="agent-picker-segment-label" data-v-8c19bd9d>${ssrInterpolate(seg.key)}</div><!--[-->`);
ssrRenderList(seg.agents, (agent) => {
_push(`<div class="agent-picker-row" data-v-8c19bd9d><button class="agent-picker-btn"${ssrRenderAttr("title", agent.name + " (private)")} data-v-8c19bd9d><span class="${ssrRenderClass([`dot-${agent.role}`, "sidebar-room-dot"])}" data-v-8c19bd9d></span><span class="agent-picker-name" data-v-8c19bd9d>${ssrInterpolate(agent.name)}</span></button><div class="agent-picker-modes" data-v-8c19bd9d>`);
if (agent.modes?.includes("private")) {
_push(`<button class="agent-picker-mode" title="Private" data-v-8c19bd9d>`);
_push(ssrRenderComponent(unref(LockClosedIcon), { class: "w-3.5 h-3.5" }, null, _parent));
_push(`</button>`);
} else {
_push(`<!---->`);
}
if (agent.modes?.includes("public")) {
_push(`<button class="agent-picker-mode" title="Public" data-v-8c19bd9d>`);
_push(ssrRenderComponent(unref(UserGroupIcon), { class: "w-3.5 h-3.5" }, null, _parent));
_push(`</button>`);
} else {
_push(`<!---->`);
}
_push(`</div></div>`);
});
_push(`<!--]--></div>`);
});
_push(`<!--]--></div></div><div class="page flex flex-col flex-1 min-h-0" style="${ssrRenderStyle(!showPicker.value ? null : { display: "none" })}" data-v-8c19bd9d><div class="chat-frame flex-1 flex flex-col overflow-visible relative" data-v-8c19bd9d><div class="content flex-1 min-h-0" data-v-8c19bd9d>`);
_push(ssrRenderComponent(unref(OverlayScrollbarsComponent), {
class: ["messages h-full pb-4 flex flex-col gap-3 relative", { "is-switching": isHidden.value }],
options: unref(scrollbarOptions),
ref_key: "messagesEl",
ref: messagesEl,
element: "div"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
if (prevSessions.value.length) {
_push2(`<!--[-->`);
if (prevHasMore.value) {
_push2(`<button class="prev-load-more"${ssrIncludeBooleanAttr(prevLoading.value) ? " disabled" : ""} data-v-8c19bd9d${_scopeId}>${ssrInterpolate(prevLoading.value ? "Loading..." : "Load older session")}</button>`);
} else {
_push2(`<!---->`);
}
_push2(`<!--[-->`);
ssrRenderList(prevSessions.value, (session, si) => {
_push2(`<!--[--><div class="prev-session-header" data-v-8c19bd9d${_scopeId}> Session ${ssrInterpolate(prevSessions.value.length - si)}${ssrInterpolate(session.timeLabel ? " — " + session.timeLabel : "")}</div><div class="prev-session-wrapper" data-v-8c19bd9d${_scopeId}><!--[-->`);
ssrRenderList(session.messages, (msg, mi) => {
_push2(`<!--[-->`);
if (msg.role === "user") {
_push2(ssrRenderComponent(UserMessage, { msg }, null, _parent2, _scopeId));
} else if (msg.role === "assistant") {
_push2(ssrRenderComponent(AssistantMessage, {
msg,
agentDisplayName: unref(getFormattedAgentName)(msg.agentId || unref(selectedAgent)),
isAgentRunning: false,
allAgents: unref(allAgents),
getToolsForTurn: () => [],
hudVersion: 0
}, null, _parent2, _scopeId));
} else {
_push2(`<!---->`);
}
_push2(`<!--]-->`);
});
_push2(`<!--]--></div><!--]-->`);
});
_push2(`<!--]--><div class="session-divider" data-v-8c19bd9d${_scopeId}><span class="session-divider-text" data-v-8c19bd9d${_scopeId}>Current session</span></div><!--]-->`);
} else {
_push2(`<!---->`);
}
_push2(`<!--[-->`);
ssrRenderList(unref(groupedVisibleMsgs), (msg, i) => {
_push2(`<!--[-->`);
if (msg.type === "no_session") {
_push2(`<div class="no-session-center" data-v-8c19bd9d${_scopeId}>— NO SESSION —</div>`);
} else {
_push2(`<!---->`);
}
if (msg.type !== "no_session" && (msg.role === "system" || msg.role === "system_group" || msg.type === "headline") && msg.position !== "footer") {
_push2(ssrRenderComponent(SystemMessage, { msg }, null, _parent2, _scopeId));
} else {
_push2(`<!---->`);
}
if (unref(hasMore) && msg.type === "headline" && msg.position !== "footer" && i === 0) {
_push2(`<button class="load-more-btn" data-v-8c19bd9d${_scopeId}>↑ Load previous messages</button>`);
} else if (msg.role === "session_context") {
_push2(`<div class="session-context-badge" data-v-8c19bd9d${_scopeId}><span class="session-context-text" data-v-8c19bd9d${_scopeId}>${ssrInterpolate(msg.content)}</span></div>`);
} else if (msg.role === "user") {
_push2(ssrRenderComponent(UserMessage, { msg }, null, _parent2, _scopeId));
} else if (msg.role === "thinking") {
_push2(`<div class="message thinking" data-v-8c19bd9d${_scopeId}><details${ssrIncludeBooleanAttr(!msg.collapsed) ? " open" : ""} data-v-8c19bd9d${_scopeId}><summary data-v-8c19bd9d${_scopeId}>`);
_push2(ssrRenderComponent(unref(ChatBubbleBottomCenterTextIcon), { class: "w-4 h-4 inline" }, null, _parent2, _scopeId));
_push2(` thinking…</summary><pre class="thinking-content" data-v-8c19bd9d${_scopeId}>${ssrInterpolate(typeof msg.content === "object" ? msg.content.value : msg.content)}</pre></details></div>`);
} else if (msg.role === "assistant") {
_push2(ssrRenderComponent(AssistantMessage, {
msg,
agentDisplayName: unref(getFormattedAgentName)(msg.agentId),
isAgentRunning: unref(isAgentRunning),
allAgents: unref(allAgents),
getToolsForTurn: unref(getToolsForTurn),
hudVersion: unref(hudVersion)
}, null, _parent2, _scopeId));
} else {
_push2(`<!---->`);
}
if (i === lastRealMsgIdx.value) {
_push2(`<div class="sm-status-bar" data-v-8c19bd9d${_scopeId}><div class="${ssrRenderClass([smState.value, "sm-dot"])}" data-v-8c19bd9d${_scopeId}></div><span class="sm-status-label" data-v-8c19bd9d${_scopeId}>${ssrInterpolate(smLabel.value)}</span></div>`);
} else {
_push2(`<!---->`);
}
_push2(`<!--]-->`);
});
_push2(`<!--]-->`);
if (lastRealMsgIdx.value === -1) {
_push2(`<div class="sm-status-bar" data-v-8c19bd9d${_scopeId}><div class="${ssrRenderClass([smState.value, "sm-dot"])}" data-v-8c19bd9d${_scopeId}></div><span class="sm-status-label" data-v-8c19bd9d${_scopeId}>${ssrInterpolate(smLabel.value)}</span></div>`);
} else {
_push2(`<!---->`);
}
if (footerHeadline.value) {
_push2(ssrRenderComponent(SystemMessage, { msg: footerHeadline.value }, null, _parent2, _scopeId));
} else {
_push2(`<!---->`);
}
_push2(`<div class="msg-controls" data-v-8c19bd9d${_scopeId}>`);
_push2(ssrRenderComponent(HudControls, {
smState: smState.value,
connected: unref(connected),
isAgentRunning: unref(chatStore).isRunning,
handoverPending: unref(chatStore).handoverPending,
isPublic: unref(selectedMode) === "public",
onNew,
onHandover: ($event) => unref(chatStore).handover(),
onConfirmNew,
onStay: ($event) => unref(chatStore).stay()
}, null, _parent2, _scopeId));
_push2(`</div><div class="scroll-spacer" style="${ssrRenderStyle({ height: spacerHeight.value + "px", flexShrink: 0 })}" data-v-8c19bd9d${_scopeId}></div>`);
} else {
return [
prevSessions.value.length ? (openBlock(), createBlock(Fragment, { key: 0 }, [
prevHasMore.value ? (openBlock(), createBlock("button", {
key: 0,
class: "prev-load-more",
onClick: ($event) => fetchPreviousSession(true),
disabled: prevLoading.value
}, toDisplayString(prevLoading.value ? "Loading..." : "Load older session"), 9, ["onClick", "disabled"])) : createCommentVNode("", true),
(openBlock(true), createBlock(Fragment, null, renderList(prevSessions.value, (session, si) => {
return openBlock(), createBlock(Fragment, {
key: "ps-" + si
}, [
createVNode("div", { class: "prev-session-header" }, " Session " + toDisplayString(prevSessions.value.length - si) + toDisplayString(session.timeLabel ? " — " + session.timeLabel : ""), 1),
createVNode("div", { class: "prev-session-wrapper" }, [
(openBlock(true), createBlock(Fragment, null, renderList(session.messages, (msg, mi) => {
return openBlock(), createBlock(Fragment, {
key: "prev-" + si + "-" + mi
}, [
msg.role === "user" ? (openBlock(), createBlock(UserMessage, {
key: 0,
msg
}, null, 8, ["msg"])) : msg.role === "assistant" ? (openBlock(), createBlock(AssistantMessage, {
key: 1,
msg,
agentDisplayName: unref(getFormattedAgentName)(msg.agentId || unref(selectedAgent)),
isAgentRunning: false,
allAgents: unref(allAgents),
getToolsForTurn: () => [],
hudVersion: 0
}, null, 8, ["msg", "agentDisplayName", "allAgents"])) : createCommentVNode("", true)
], 64);
}), 128))
])
], 64);
}), 128)),
createVNode("div", { class: "session-divider" }, [
createVNode("span", { class: "session-divider-text" }, "Current session")
])
], 64)) : createCommentVNode("", true),
(openBlock(true), createBlock(Fragment, null, renderList(unref(groupedVisibleMsgs), (msg, i) => {
return openBlock(), createBlock(Fragment, { key: i }, [
msg.type === "no_session" ? (openBlock(), createBlock("div", {
key: 0,
class: "no-session-center"
}, "— NO SESSION —")) : createCommentVNode("", true),
msg.type !== "no_session" && (msg.role === "system" || msg.role === "system_group" || msg.type === "headline") && msg.position !== "footer" ? (openBlock(), createBlock(SystemMessage, {
key: 1,
msg
}, null, 8, ["msg"])) : createCommentVNode("", true),
unref(hasMore) && msg.type === "headline" && msg.position !== "footer" && i === 0 ? (openBlock(), createBlock("button", {
key: 2,
class: "load-more-btn",
onClick: unref(loadMore)
}, "↑ Load previous messages", 8, ["onClick"])) : msg.role === "session_context" ? (openBlock(), createBlock("div", {
key: 3,
class: "session-context-badge"
}, [
createVNode("span", { class: "session-context-text" }, toDisplayString(msg.content), 1)
])) : msg.role === "user" ? (openBlock(), createBlock(UserMessage, {
key: 4,
msg
}, null, 8, ["msg"])) : msg.role === "thinking" ? (openBlock(), createBlock("div", {
key: 5,
class: "message thinking"
}, [
createVNode("details", {
open: !msg.collapsed
}, [
createVNode("summary", null, [
createVNode(unref(ChatBubbleBottomCenterTextIcon), { class: "w-4 h-4 inline" }),
createTextVNode(" thinking…")
]),
createVNode("pre", { class: "thinking-content" }, toDisplayString(typeof msg.content === "object" ? msg.content.value : msg.content), 1)
], 8, ["open"])
])) : msg.role === "assistant" ? (openBlock(), createBlock(AssistantMessage, {
key: 6,
msg,
agentDisplayName: unref(getFormattedAgentName)(msg.agentId),
isAgentRunning: unref(isAgentRunning),
allAgents: unref(allAgents),
getToolsForTurn: unref(getToolsForTurn),
hudVersion: unref(hudVersion)
}, null, 8, ["msg", "agentDisplayName", "isAgentRunning", "allAgents", "getToolsForTurn", "hudVersion"])) : createCommentVNode("", true),
i === lastRealMsgIdx.value ? (openBlock(), createBlock("div", {
key: 7,
class: "sm-status-bar"
}, [
createVNode("div", {
class: ["sm-dot", smState.value]
}, null, 2),
createVNode("span", { class: "sm-status-label" }, toDisplayString(smLabel.value), 1)
])) : createCommentVNode("", true)
], 64);
}), 128)),
lastRealMsgIdx.value === -1 ? (openBlock(), createBlock("div", {
key: 1,
class: "sm-status-bar"
}, [
createVNode("div", {
class: ["sm-dot", smState.value]
}, null, 2),
createVNode("span", { class: "sm-status-label" }, toDisplayString(smLabel.value), 1)
])) : createCommentVNode("", true),
footerHeadline.value ? (openBlock(), createBlock(SystemMessage, {
key: 2,
msg: footerHeadline.value
}, null, 8, ["msg"])) : createCommentVNode("", true),
createVNode("div", {
class: "msg-controls",
ref_key: "controlsEl",
ref: controlsEl
}, [
createVNode(HudControls, {
smState: smState.value,
connected: unref(connected),
isAgentRunning: unref(chatStore).isRunning,
handoverPending: unref(chatStore).handoverPending,
isPublic: unref(selectedMode) === "public",
onNew,
onHandover: ($event) => unref(chatStore).handover(),
onConfirmNew,
onStay: ($event) => unref(chatStore).stay()
}, null, 8, ["smState", "connected", "isAgentRunning", "handoverPending", "isPublic", "onHandover", "onStay"])
], 512),
createVNode("div", {
class: "scroll-spacer",
style: { height: spacerHeight.value + "px", flexShrink: 0 }
}, null, 4)
];
}
}),
_: 1
}, _parent));
_push(`</div><div class="input-area" data-v-8c19bd9d>`);
if (unref(micDenied)) {
_push(`<div class="mic-denied-hint" data-v-8c19bd9d>Mic access denied — enable in browser settings</div>`);
} else {
_push(`<!---->`);
}
if (unref(isRecording)) {
_push(`<div class="recording-strip" data-v-8c19bd9d><span class="rec-dot" data-v-8c19bd9d></span><div class="rec-level-bar" data-v-8c19bd9d><div class="rec-level-fill" style="${ssrRenderStyle({ width: unref(audioLevel) * 100 + "%" })}" data-v-8c19bd9d></div></div><span class="rec-time" data-v-8c19bd9d>${ssrInterpolate(unref(formatDuration)(unref(duration)))}</span><button class="rec-cancel" title="Cancel" data-v-8c19bd9d>×</button></div>`);
} else {
_push(`<!---->`);
}
if (unref(attachments).length) {
_push(`<div class="attachment-strip" data-v-8c19bd9d><!--[-->`);
ssrRenderList(unref(attachments), (att, i) => {
_push(`<div class="${ssrRenderClass([{ "audio-thumb": att.mimeType.startsWith("audio/") }, "attachment-thumb"])}" data-v-8c19bd9d>`);
if (att.mimeType.startsWith("audio/")) {
_push(`<!--[--><span class="audio-icon" data-v-8c19bd9d>♫</span><span class="audio-name" data-v-8c19bd9d>${ssrInterpolate(att.fileName)}</span><!--]-->`);
} else {
_push(`<img${ssrRenderAttr("src", att.preview)}${ssrRenderAttr("alt", att.fileName)} data-v-8c19bd9d>`);
}
_push(`<button class="remove-att" data-v-8c19bd9d>×</button></div>`);
});
_push(`<!--]--></div>`);
} else {
_push(`<!---->`);
}
_push(`<div class="${ssrRenderClass([{ "drag-over": isDragOver.value }, "input-box"])}" data-v-8c19bd9d><button class="attach-btn" title="Attach file" data-v-8c19bd9d>`);
_push(ssrRenderComponent(unref(PaperClipIcon), { class: "w-4 h-4" }, null, _parent));
_push(`</button><button class="${ssrRenderClass([{ recording: unref(isRecording) }, "mic-btn"])}"${ssrRenderAttr("title", unref(isRecording) ? "Stop recording" : "Record audio")} data-v-8c19bd9d>`);
_push(ssrRenderComponent(unref(MicrophoneIcon), { class: "w-4 h-4" }, null, _parent));
_push(`</button><input type="file" class="hidden" accept="image/jpeg,image/png,image/gif,image/webp,application/pdf,audio/webm,audio/mp4,audio/ogg,audio/mpeg,audio/wav" multiple data-v-8c19bd9d><textarea placeholder="Message..." rows="1" class="${ssrRenderClass([{ shake: unref(isShaking) }, "chat-input"])}" data-v-8c19bd9d>${ssrInterpolate(unref(input))}</textarea>`);
if (smState.value === "AGENT_RUNNING" || smState.value === "STOP_PENDING") {
_push(`<button class="stop-btn"${ssrIncludeBooleanAttr(smState.value === "STOP_PENDING") ? " disabled" : ""}${ssrRenderAttr("title", smState.value === "STOP_PENDING" ? "Stopping..." : "Stop")} data-v-8c19bd9d>`);
_push(ssrRenderComponent(unref(StopIcon), { class: "w-3.5 h-3.5" }, null, _parent));
_push(`</button>`);
} else {
_push(`<!---->`);
}
_push(`<button class="send-btn"${ssrIncludeBooleanAttr(!unref(connected) || unref(sending) || !unref(input).trim() && !unref(hasAttachments)()) ? " disabled" : ""} data-v-8c19bd9d>`);
if (!unref(sending)) {
_push(ssrRenderComponent(unref(ArrowUpIcon), { class: "w-4 h-4" }, null, _parent));
} else {
_push(`<span style="${ssrRenderStyle({ "font-size": "11px" })}" data-v-8c19bd9d>…</span>`);
}
_push(`</button></div></div></div></div></div>`);
} else {
_push(`<div${ssrRenderAttrs(mergeProps({ class: "not-logged-in" }, _attrs))} data-v-8c19bd9d><p data-v-8c19bd9d>`);
_push(ssrRenderComponent(unref(LockClosedIcon), { class: "w-5 h-5 inline" }, null, _parent));
_push(` Not logged in</p>`);
_push(ssrRenderComponent(_component_RouterLink, { to: "/login" }, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(`Sign in →`);
} else {
return [
createTextVNode("Sign in →")
];
}
}),
_: 1
}, _parent));
_push(`</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/AgentsView.vue");
return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
};
const AgentsView = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-8c19bd9d"]]);
export {
AgentsView as default
};