JuanCS-Dev's picture
feat: O2b observability live binding
e4931d5 verified
Raw History Blame Contribute Delete
11.4 kB
import {
getChannelContextSnapshot,
getObservabilityHistorySnapshot,
getPostStreamReportSnapshot,
getSemanticMemorySnapshot,
getObservabilitySnapshot,
getSentimentScoresSnapshot,
upsertSemanticMemoryEntry,
getRevenueConversionsSnapshot,
postRevenueConversion,
} from "./api.js";
import {
renderChannelContextSnapshot,
renderObservabilityHistorySnapshot,
renderPostStreamReportSnapshot,
renderSemanticMemorySnapshot,
renderObservabilitySnapshot,
setConnectionState,
renderRevenueConversionsSnapshot,
} from "./view.js";
import { applyChannelControlCapability } from "../channel-control/view.js";
import { renderControlPlaneCapabilities } from "../control-plane/view.js";
import { renderAutonomyRuntime } from "../autonomy/view.js";
const OBSERVABILITY_INTERVAL_MS = 10000;
const OBSERVABILITY_TIMEOUT_MS = 8000;
const OBSERVABILITY_HISTORY_LIMIT = 24;
const OBSERVABILITY_COMPARE_LIMIT = 6;
const SEMANTIC_MEMORY_LIMIT = 8;
const SEMANTIC_MEMORY_SEARCH_LIMIT = 60;
function normalizeSemanticMinSimilarity(value) {
const raw = String(value ?? "").trim();
if (!raw) return "";
const parsed = Number(raw);
if (!Number.isFinite(parsed)) return "";
const clamped = Math.max(-1, Math.min(1, parsed));
return String(Number(clamped.toFixed(3)));
}
export function createObservabilityController({
obsEls,
ctrlEls,
cpEls,
autEls,
}) {
let isPolling = false;
let timerId = 0;
let selectedChannel = "default";
let semanticMemoryQuery = "";
let semanticMemoryMinSimilarity = "";
let semanticMemoryForceFallback = false;
function syncSemanticMemorySearchTuning() {
if (!obsEls) return;
semanticMemoryMinSimilarity = normalizeSemanticMinSimilarity(
obsEls.intSemanticMemoryMinSimilarityInput?.value,
);
semanticMemoryForceFallback = Boolean(
obsEls.intSemanticMemoryForceFallbackToggle?.checked,
);
}
function applyRuntimeCapabilities(capabilities = {}, mode = "") {
if (ctrlEls) {
applyChannelControlCapability(
ctrlEls,
capabilities?.channel_control || {},
);
}
if (cpEls) {
renderControlPlaneCapabilities(cpEls, capabilities || {}, mode || "");
}
}
async function fetchAndRenderObservability() {
if (!obsEls || isPolling) return;
syncSemanticMemorySearchTuning();
isPolling = true;
setConnectionState("pending", obsEls);
try {
const sentimentPromise = getSentimentScoresSnapshot(
selectedChannel,
OBSERVABILITY_TIMEOUT_MS,
).catch((_error) => null);
const [
data,
channelData,
historyData,
sentimentData,
postStreamData,
semanticMemoryData,
revenueData,
] = await Promise.all([
getObservabilitySnapshot(selectedChannel, OBSERVABILITY_TIMEOUT_MS),
getChannelContextSnapshot(selectedChannel, OBSERVABILITY_TIMEOUT_MS),
getObservabilityHistorySnapshot(
selectedChannel,
OBSERVABILITY_TIMEOUT_MS,
OBSERVABILITY_HISTORY_LIMIT,
OBSERVABILITY_COMPARE_LIMIT,
),
sentimentPromise,
getPostStreamReportSnapshot(
selectedChannel,
OBSERVABILITY_TIMEOUT_MS,
).catch((_error) => null),
getSemanticMemorySnapshot(
selectedChannel,
OBSERVABILITY_TIMEOUT_MS,
semanticMemoryQuery,
SEMANTIC_MEMORY_LIMIT,
SEMANTIC_MEMORY_SEARCH_LIMIT,
semanticMemoryMinSimilarity,
semanticMemoryForceFallback,
).catch((_error) => null),
getRevenueConversionsSnapshot(
selectedChannel,
OBSERVABILITY_TIMEOUT_MS,
).catch((_error) => null),
]);
renderObservabilitySnapshot(data, obsEls, sentimentData);
renderChannelContextSnapshot(channelData, obsEls);
renderObservabilityHistorySnapshot(historyData, obsEls);
renderPostStreamReportSnapshot(postStreamData, obsEls);
renderSemanticMemorySnapshot(semanticMemoryData, obsEls);
renderRevenueConversionsSnapshot(revenueData, obsEls);
setConnectionState("ok", obsEls);
applyRuntimeCapabilities(data?.capabilities || {}, data?.bot?.mode || "");
renderAutonomyRuntime(data?.autonomy || {}, autEls);
} catch (error) {
console.error("Dashboard observability refresh error", error);
setConnectionState("error", obsEls);
} finally {
isPolling = false;
}
}
async function generatePostStreamReport() {
if (!obsEls) return null;
if (obsEls.intPostStreamGenerateBtn) {
obsEls.intPostStreamGenerateBtn.disabled = true;
}
try {
const payload = await getPostStreamReportSnapshot(
selectedChannel,
OBSERVABILITY_TIMEOUT_MS,
true,
);
renderPostStreamReportSnapshot(payload, obsEls);
return payload;
} catch (error) {
console.error("Dashboard post-stream report generation error", error);
return null;
} finally {
if (obsEls.intPostStreamGenerateBtn) {
obsEls.intPostStreamGenerateBtn.disabled = false;
}
}
}
async function refreshSemanticMemory(query = "") {
if (!obsEls) return null;
syncSemanticMemorySearchTuning();
semanticMemoryQuery = String(query || "").trim();
try {
const payload = await getSemanticMemorySnapshot(
selectedChannel,
OBSERVABILITY_TIMEOUT_MS,
semanticMemoryQuery,
SEMANTIC_MEMORY_LIMIT,
SEMANTIC_MEMORY_SEARCH_LIMIT,
semanticMemoryMinSimilarity,
semanticMemoryForceFallback,
);
renderSemanticMemorySnapshot(payload, obsEls);
return payload;
} catch (error) {
console.error("Dashboard semantic memory refresh error", error);
return null;
}
}
async function saveSemanticMemoryEntry() {
if (!obsEls) return null;
const content = String(
obsEls.intSemanticMemoryContentInput?.value || "",
).trim();
if (!content) return null;
if (obsEls.intSemanticMemorySaveBtn) {
obsEls.intSemanticMemorySaveBtn.disabled = true;
}
try {
await upsertSemanticMemoryEntry(
{
channel_id: selectedChannel,
content,
memory_type: String(
obsEls.intSemanticMemoryTypeInput?.value || "fact",
)
.trim()
.toLowerCase(),
tags: String(obsEls.intSemanticMemoryTagsInput?.value || ""),
},
OBSERVABILITY_TIMEOUT_MS,
);
if (obsEls.intSemanticMemoryContentInput) {
obsEls.intSemanticMemoryContentInput.value = "";
}
syncSemanticMemorySearchTuning();
const queryFromInput = String(
obsEls.intSemanticMemoryQueryInput?.value || "",
).trim();
await refreshSemanticMemory(queryFromInput);
return true;
} catch (error) {
console.error("Dashboard semantic memory save error", error);
return null;
} finally {
if (obsEls.intSemanticMemorySaveBtn) {
obsEls.intSemanticMemorySaveBtn.disabled = false;
}
}
}
async function simulateRevenueConversion() {
if (!obsEls || !obsEls.intRevenueSimulateBtn) return null;
const btn = obsEls.intRevenueSimulateBtn;
const eventType = obsEls.intRevenueEventType?.value || "sub";
const viewerLogin = String(
obsEls.intRevenueViewerLogin?.value || "simulated_viewer",
).trim();
const rawValue = obsEls.intRevenueValue?.value || "0";
const revenueValue = Number(rawValue) || 0.0;
btn.disabled = true;
try {
await postRevenueConversion(
{
channel_id: selectedChannel,
event_type: eventType,
viewer_login: viewerLogin,
revenue_value: revenueValue,
},
OBSERVABILITY_TIMEOUT_MS,
);
if (obsEls.intRevenueViewerLogin) obsEls.intRevenueViewerLogin.value = "";
if (obsEls.intRevenueValue) obsEls.intRevenueValue.value = "";
const payload = await getRevenueConversionsSnapshot(
selectedChannel,
OBSERVABILITY_TIMEOUT_MS,
);
renderRevenueConversionsSnapshot(payload, obsEls);
return true;
} catch (error) {
console.error("Dashboard revenue simulation error", error);
return null;
} finally {
btn.disabled = false;
}
}
function bindObservabilityEvents() {
if (!obsEls) return;
if (obsEls.intPostStreamGenerateBtn) {
obsEls.intPostStreamGenerateBtn.addEventListener(
"click",
async (event) => {
event.preventDefault();
await generatePostStreamReport();
},
);
}
if (obsEls.intSemanticMemorySearchBtn) {
obsEls.intSemanticMemorySearchBtn.addEventListener(
"click",
async (event) => {
event.preventDefault();
syncSemanticMemorySearchTuning();
const query = String(
obsEls.intSemanticMemoryQueryInput?.value || "",
).trim();
await refreshSemanticMemory(query);
},
);
}
if (obsEls.intSemanticMemorySaveBtn) {
obsEls.intSemanticMemorySaveBtn.addEventListener(
"click",
async (event) => {
event.preventDefault();
await saveSemanticMemoryEntry();
},
);
}
if (obsEls.intSemanticMemoryMinSimilarityInput) {
obsEls.intSemanticMemoryMinSimilarityInput.addEventListener(
"change",
async () => {
syncSemanticMemorySearchTuning();
const query = String(
obsEls.intSemanticMemoryQueryInput?.value || "",
).trim();
await refreshSemanticMemory(query);
},
);
}
if (obsEls.intSemanticMemoryForceFallbackToggle) {
obsEls.intSemanticMemoryForceFallbackToggle.addEventListener(
"change",
async () => {
syncSemanticMemorySearchTuning();
const query = String(
obsEls.intSemanticMemoryQueryInput?.value || "",
).trim();
await refreshSemanticMemory(query);
},
);
}
if (obsEls.intRevenueSimulateBtn) {
obsEls.intRevenueSimulateBtn.addEventListener("click", async (event) => {
event.preventDefault();
await simulateRevenueConversion();
});
}
}
function scheduleObservabilityPolling() {
if (timerId) {
window.clearTimeout(timerId);
}
timerId = window.setTimeout(async () => {
await fetchAndRenderObservability();
scheduleObservabilityPolling();
}, OBSERVABILITY_INTERVAL_MS);
}
return {
applyRuntimeCapabilities,
fetchAndRenderObservability,
// O2b: render the health metrics from the live ops snapshot.
// snap.observability === /api/observability and snap.sentiment ===
// /api/sentiment/scores (both verified) — the same args fetchAndRender uses.
renderObservabilityFromSnapshot(snap) {
if (obsEls && snap) {
renderObservabilitySnapshot(snap.observability || {}, obsEls, snap.sentiment || {});
setConnectionState("ok", obsEls);
}
},
generatePostStreamReport,
refreshSemanticMemory,
saveSemanticMemoryEntry,
bindObservabilityEvents,
setSelectedChannel(channelId) {
selectedChannel =
String(channelId || "")
.trim()
.toLowerCase() || "default";
},
scheduleObservabilityPolling,
};
}