import React, { useEffect, useMemo, useRef, useState } from "react"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { ButtonPrimary, ButtonSoft } from "./ui"; import { apiFetch } from "../api/client"; import { parseResponseBody, resolveApiErrorInfo } from "../utils/responseShell"; import { useAppCommandListener } from "../utils/appCommands"; import { confirmDestructiveAction } from "../utils/confirmAction"; type HelperMessage = { id: string; role: "assistant" | "user"; text: string; tone?: "slate" | "green" | "amber" | "red" | "blue"; }; type UtilityTab = "assist" | "support"; const ROUTER_HELPER_CONTEXT_SESSION_KEY = "masters_toolkit_router_helper_context_v1"; const HELPER_INTRO = "I can answer router selection and FAQ questions from internal sources."; const HELPER_PROMPTS: string[] = [ "Recommend 2 routers for a small branch using primary 5G.", "Compare selected routers by device details.", "What is network slicing?", ]; const HELPER_DETAILS_THRESHOLD = 540; const TABLE_ROW_RE = /^\s*\|.*\|\s*$/; const TABLE_DELIM_RE = /^\s*\|?(?:\s*:?-{3,}:?\s*\|)+\s*:?-{3,}:?\s*\|?\s*$/; const SUPPORT_EMAIL = "support@masterstelecom.com"; const SUPPORT_PHONE_DISPLAY = "561-531-0462"; const SUPPORT_PHONE_TEL = "+15615310462"; const SUPPORT_PHONE_EXTENSION = "Option 6"; const SUPPORT_SLACK_URL = "https://verizon.enterprise.slack.com/archives/C035HSFT7MZ"; function safeUrlTransform(url: string) { try { const base = typeof window !== "undefined" ? window.location.origin : "http://localhost"; const parsed = new URL(url, base); if (parsed.protocol === "http:" || parsed.protocol === "https:") return parsed.toString(); return ""; } catch { return ""; } } function HelperMarkdownTable(props: any) { const [readerOpen, setReaderOpen] = useState(false); const inlineTableRef = useRef(null); const modalTableRef = useRef(null); useEffect(() => { if (!readerOpen) return; const onKey = (event: KeyboardEvent) => { if (event.key === "Escape") setReaderOpen(false); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [readerOpen]); return ( <>
Comparison ready
{readerOpen ? (
{ if (e.target === e.currentTarget) setReaderOpen(false); }} >
Comparison table reader
Full-table view with sticky headers for easier model comparison.
) : null} ); } function findMarkdownTableRange(lines: string[]): [number, number] | null { for (let i = 0; i < lines.length - 1; i++) { if (!TABLE_ROW_RE.test(lines[i]) || !TABLE_DELIM_RE.test(lines[i + 1])) continue; let end = i + 2; while (end < lines.length && TABLE_ROW_RE.test(lines[end])) end += 1; return [i, end]; } return null; } function hasMarkdownTable(markdown: string): boolean { const lines = String(markdown || "").split(/\r?\n/); return findMarkdownTableRange(lines) !== null; } function simplifyComparisonTableAnswer(markdown: string): string { const raw = String(markdown || "").trim(); if (!raw) return raw; const lines = raw.split(/\r?\n/); const tableRange = findMarkdownTableRange(lines); if (!tableRange) return raw; const [start, end] = tableRange; const tableOnly = lines.slice(start, end).join("\n").trim(); if (!tableOnly) return raw; return `Click here for comparison table.\n\n${tableOnly}`; } function buildMessageWithContext(message: string): string { const trimmed = String(message || "").trim(); if (!trimmed) return ""; try { const context = String(window.sessionStorage.getItem(ROUTER_HELPER_CONTEXT_SESSION_KEY) || "").trim(); if (!context) return trimmed; return `${trimmed}\n\nContext from Rapid Router form:\n${context}`; } catch { return trimmed; } } function helperPreviewText(markdown: string): string { const flattened = String(markdown || "") .replace(/```[\s\S]*?```/g, " ") .replace(/\|[^|\n]*(\|[^|\n]*)+\|?/g, " ") .replace(/[#>*_`~]/g, " ") .replace(/\s+/g, " ") .trim(); if (!flattened) return "Answer generated."; if (flattened.length <= 180) return flattened; return `${flattened.slice(0, 177).trimEnd()}...`; } function UtilityTabButton({ label, active, onClick, }: { label: string; active: boolean; onClick: () => void; }) { return ( ); } export default function FloatingRouterHelper() { const [open, setOpen] = useState(false); const [activeTab, setActiveTab] = useState("assist"); const [input, setInput] = useState(""); const [busy, setBusy] = useState(false); const [state, setState] = useState>({}); const [messages, setMessages] = useState([ { id: "global-router-helper-intro", role: "assistant", text: HELPER_INTRO, tone: "blue" }, ]); const helperMdComponents = useMemo( () => ({ h1: (p: any) =>

, h2: (p: any) =>

, h3: (p: any) =>

, p: (p: any) =>

, ul: (p: any) =>

    , ol: (p: any) =>
      , li: (p: any) =>
    1. , table: (p: any) => , th: (p: any) => (

), td: (p: any) => ( ), a: (p: any) => , code: (p: any) => , }), [] ); const resetChat = () => { if (!confirmDestructiveAction("Reset the assist conversation?")) return false; setBusy(false); setInput(""); setState({}); setMessages([{ id: `global-router-helper-intro-${Date.now()}`, role: "assistant", text: HELPER_INTRO, tone: "blue" }]); return true; }; const openLauncher = (tab: UtilityTab) => { setActiveTab(tab); setOpen(true); }; useAppCommandListener((command) => { if (command === "router_helper:open") { openLauncher("assist"); return; } if (command === "support:open") { openLauncher("support"); return; } if (command === "router_helper:close" || command === "support:close") { setOpen(false); } }); const sendMessage = async (overrideMessage?: string) => { if (busy) return; const raw = typeof overrideMessage === "string" ? overrideMessage : input; const userMessage = String(raw || "").trim(); if (!userMessage) return; setMessages((prev) => [...prev, { id: `global-router-helper-user-${Date.now()}`, role: "user", text: userMessage }]); if (typeof overrideMessage !== "string") setInput(""); setBusy(true); try { const enriched = buildMessageWithContext(userMessage); const res = await apiFetch("/api/knowledgebase/message", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: enriched, state, mode: "router_docs", audience: "auto", show_citations: false, }), }); const data = (await parseResponseBody(res)) as any; if (!res.ok) { const info = resolveApiErrorInfo(data, `Assist request failed (${res.status}).`); const errorText = info.shell?.result || info.text || "Assist request failed."; setMessages((prev) => [ ...prev, { id: `global-router-helper-assistant-error-${Date.now()}`, role: "assistant", text: errorText, tone: "red" }, ]); return; } const assistantRaw = String(data?.assistant || "").trim() || "I could not generate an answer for that request. Please rephrase."; const assistantText = simplifyComparisonTableAnswer(assistantRaw); const nextState = data?.state && typeof data.state === "object" ? (data.state as Record) : {}; setState(nextState); setMessages((prev) => [ ...prev, { id: `global-router-helper-assistant-${Date.now()}`, role: "assistant", text: assistantText, tone: "blue" }, ]); } catch (e: any) { setMessages((prev) => [ ...prev, { id: `global-router-helper-assistant-catch-${Date.now()}`, role: "assistant", text: e?.message || "Assist request failed.", tone: "red", }, ]); } finally { setBusy(false); } }; return ( <> {!open ? ( ) : null} {open ? (
Help + assist
{activeTab === "assist" ? busy ? "Assist is working on your question." : "Ask router questions here, or switch to Support for a person." : "Support is available by phone, email, or Slack. Switch back to Assist for router questions."}
setActiveTab("assist")} /> setActiveTab("support")} />
{activeTab === "assist" ? (
{messages.map((msg) => { const isAssistant = msg.role === "assistant"; const tone = msg.tone || "slate"; const isTableAnswer = isAssistant && hasMarkdownTable(msg.text); const hasLongAnswer = isAssistant && !isTableAnswer && msg.text.length > HELPER_DETAILS_THRESHOLD; const bubbleClass = tone === "red" ? "mt-panel-danger" : tone === "amber" ? "mt-panel-warning" : tone === "blue" ? "mt-panel-primary" : "border-slate-200 bg-slate-50 text-slate-900"; const bubbleWidthClass = isAssistant ? "w-full max-w-full" : "max-w-[95%]"; return (
{isAssistant ? ( isTableAnswer ? ( {msg.text} ) : hasLongAnswer ? (
Answer
{helperPreviewText(msg.text)}
View details
{msg.text}
) : ( {msg.text} ) ) : (
{msg.text}
)}
); })}
{HELPER_PROMPTS.map((prompt) => ( ))}