diff --git a/public/runbook-embed.js b/public/runbook-embed.js index 16acc73..50b3e2c 100644 --- a/public/runbook-embed.js +++ b/public/runbook-embed.js @@ -252,7 +252,13 @@ description: (el.getAttribute("data-runbook-description") || "").trim() }); } - function handleOut() { + function handleOut(evt) { + var from = evt.target instanceof Element ? evt.target : null; + var to = evt.relatedTarget instanceof Element ? evt.relatedTarget : null; + if (!from) return; + var fromFeature = from.closest("[data-runbook-feature],[data-runbook-title],[data-runbook-description]"); + if (!fromFeature) return; + if (to && fromFeature.contains(to)) return; updateHoveredFeature(null); } document.addEventListener("pointerover", handleOver, true); @@ -406,8 +412,6 @@ } function pageContext() { - var t = document.title || ""; - var u = location.href || ""; var meta = ""; var m = document.querySelector('meta[name="description"]'); if (m) meta = m.getAttribute("content") || ""; @@ -445,7 +449,7 @@ bodyText = ""; } } - return [u, t, meta, headings, bodyText].filter(Boolean).join("\n"); + return [meta, headings, bodyText].filter(Boolean).join("\n"); } function addBot(html) { diff --git a/src/app/api/embed/chat/route.ts b/src/app/api/embed/chat/route.ts index 761ef05..13c01b7 100644 --- a/src/app/api/embed/chat/route.ts +++ b/src/app/api/embed/chat/route.ts @@ -111,7 +111,6 @@ function normalizePageContext(body: ChatBody): string { return [ `Page URL: ${body.pageUrl || "n/a"}`, `Page title: ${body.pageTitle || "n/a"}`, - hovered ? `Hovered feature: ${hovered}` : "", body.pageContext.trim() ] .filter(Boolean) @@ -124,9 +123,15 @@ function normalizePageContext(body: ChatBody): string { function sanitizeHoveredFeature(raw: unknown): string { if (!raw || typeof raw !== "object") return ""; const obj = raw as Record; - const feature = String(obj.feature || "").trim(); - const title = String(obj.title || "").trim(); - const description = String(obj.description || "").trim().slice(0, 400); + const asStr = (v: unknown, limit: number): string => + (typeof v === "string" ? v : "") + .replace(/[\u0000-\u001f]/g, " ") + .replace(/\s+/g, " ") + .trim() + .slice(0, limit); + const feature = asStr(obj.feature, 120); + const title = asStr(obj.title, 120); + const description = asStr(obj.description, 400); const joined = [title || feature, description].filter(Boolean).join(" — "); return joined.slice(0, 520); } diff --git a/src/components/EmbeddedRunbookAssistant.tsx b/src/components/EmbeddedRunbookAssistant.tsx index e34402d..2e71f52 100644 --- a/src/components/EmbeddedRunbookAssistant.tsx +++ b/src/components/EmbeddedRunbookAssistant.tsx @@ -213,11 +213,7 @@ export function EmbeddedRunbookAssistant({ typeof document !== "undefined" ? document.body.innerText.replace(/\s+/g, " ").trim().slice(0, 10_000) : ""; - const pageContext = - pageContextOverride || - (typeof window !== "undefined" - ? [window.location.href, document.title, pageBody].filter(Boolean).join("\n") - : pageBody); + const pageContext = pageContextOverride || pageBody; const res = await fetch(`${base}/api/embed/chat`, { method: "POST", headers: { "Content-Type": "application/json" }, diff --git a/src/lib/embedDemoKnowledge.ts b/src/lib/embedDemoKnowledge.ts index b0a8c58..3d4c750 100644 --- a/src/lib/embedDemoKnowledge.ts +++ b/src/lib/embedDemoKnowledge.ts @@ -86,6 +86,46 @@ function extractLocationTarget(text: string): string { return "the relevant action button"; } +function pageLooksLikeDashboard(ctx: string): boolean { + return /(workflow|integration|api key|deployment|settings|onboarding|template|dashboard)/i.test(ctx); +} + +function inferActionsFromContext(ctx: string, hovered: string): { bullets: string[]; steps: string[] } { + if (hovered) { + const hoverTitle = hovered.split("—")[0]?.trim() || hovered; + return { + bullets: [ + `Explore ${hoverTitle}`, + "Use the highlighted feature first", + "Follow page prompts for next action" + ], + steps: [ + "Start with the hovered feature on this page.", + "Complete one action in that section.", + "Ask What next? for step-by-step guidance." + ] + }; + } + if (pageLooksLikeDashboard(ctx)) { + return { + bullets: ["Create a workflow", "Connect integrations", "Set up API keys"], + steps: [ + "Start with the primary CTA on this page.", + "Complete one setup action.", + "Ask What next? for guided progression." + ] + }; + } + return { + bullets: ["Use the primary CTA", "Open navigation to key sections", "Follow visible setup hints"], + steps: [ + "Look for the most prominent action button.", + "Open the related section from page navigation.", + "Complete one visible setup task, then ask for next steps." + ] + }; +} + /** Deterministic demo responses for hackathon reliability. */ export function buildNorthstarDemoResponse(message: string, pageContext: string, hoveredFeature?: string): DemoChatResult { const m = message.toLowerCase().trim(); @@ -112,11 +152,12 @@ export function buildNorthstarDemoResponse(message: string, pageContext: string, } if (m.includes("what can i do here")) { + const inferred = inferActionsFromContext(ctx, hovered); return createResult({ answer: "Here are the key actions available on this page.", - bullets: ["Create a workflow", "Connect integrations", "Set up API keys"], + bullets: inferred.bullets, sources: product ? [{ title: product.title, excerpt: excerptFromContent(product.content), url: undefined }] : [], - steps: ["Start with the primary CTA on this page.", "Complete one setup action.", "Ask What next? for guided progression."] + steps: inferred.steps }); }