@bootstrapware/answers 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -47,6 +47,8 @@ type AnswersProps = {
47
47
  defaultOpen?: boolean;
48
48
  theme?: AnswersTheme;
49
49
  pageContext?: AnswersPageContext;
50
+ /** Empty-state copy. Omit it and the widget shows "Ask a question this site already answers." */
51
+ greeting?: string;
50
52
  onFallback?: (result: AnswersResult) => void;
51
53
  /** Layout hook only. Skin the widget with the `--bsw-an-*` tokens. */
52
54
  className?: string;
package/dist/index.js CHANGED
@@ -502,7 +502,7 @@ function Answers(props) {
502
502
  mode === "local" ? /* @__PURE__ */ jsx3("p", { className: "bsw-an__note", children: "Sample answers for Northwind Outfitters." }) : null
503
503
  ] })
504
504
  ] }),
505
- /* @__PURE__ */ jsx3("div", { className: "bsw-an__log", ref: logRef, children: turns.length === 0 && !pendingQuestion ? /* @__PURE__ */ jsx3("p", { className: "bsw-an__empty", children: mode === "unwired" ? "This page is not ready to answer yet." : mode === "local" ? "Ask a question. The sample covers hours, returns, and shipping for Northwind Outfitters." : "Ask a question about this page." }) : /* @__PURE__ */ jsxs2("ol", { className: "bsw-an__turns", children: [
505
+ /* @__PURE__ */ jsx3("div", { className: "bsw-an__log", ref: logRef, children: turns.length === 0 && !pendingQuestion ? /* @__PURE__ */ jsx3("p", { className: "bsw-an__empty", children: mode === "unwired" ? "This page is not ready to answer yet." : mode === "local" ? "Ask a question. The sample covers hours, returns, and shipping for Northwind Outfitters." : props.greeting ?? "Ask a question this site already answers." }) : /* @__PURE__ */ jsxs2("ol", { className: "bsw-an__turns", children: [
506
506
  turns.map((turn, index) => {
507
507
  const citations = turn.result.outcome === "answered" ? turn.result.citations ?? [] : [];
508
508
  const allowed = new Set(citations.map((citation) => citation.url).filter((url) => Boolean(url)));
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Answers.tsx","../src/result.ts","../src/types.ts","../src/adapters.ts","../src/frame.tsx","../src/markdown.tsx","../src/session.ts","../src/mount.tsx"],"sourcesContent":["import { useEffect, useId, useRef, useState, type FormEvent } from \"react\";\nimport { createLocalAdapter } from \"./adapters\";\nimport { EmbedFrame } from \"./frame\";\nimport { renderAnswerMarkdown } from \"./markdown\";\nimport { codePointLength, sanitizeResult } from \"./result\";\nimport { readSessionId, sessionStorageKey } from \"./session\";\nimport {\n QUESTION_MAX,\n WIDGET_ROOT_CLASS,\n type AnswersAdapter,\n type AnswersCitation,\n type AnswersPageContext,\n type AnswersProps,\n type AnswersResult,\n} from \"./types\";\n\ntype Turn = {\n question: string;\n result: AnswersResult;\n feedback: \"idle\" | \"pending\" | \"done\" | \"error\";\n};\n\nconst LIMIT_MESSAGE = \"Questions are limited to 1,000 characters.\";\nconst ERROR_MESSAGE = \"Something went wrong. Your question is still here. Try again.\";\nconst FEEDBACK_ERROR = \"Feedback could not be saved. Try again.\";\n\nfunction useSessionId(appId?: string): string {\n const key = sessionStorageKey(appId);\n const [state, setState] = useState(() => ({\n key,\n id: readSessionId(typeof window === \"undefined\" ? null : window.sessionStorage, appId),\n }));\n if (state.key !== key) {\n setState({\n key,\n id: readSessionId(typeof window === \"undefined\" ? null : window.sessionStorage, appId),\n });\n }\n return state.id;\n}\n\nfunction sourcesLabel(count: number): string {\n return count === 1 ? \"1 source\" : `${count} sources`;\n}\n\nfunction CitationSources({ citations }: { citations: AnswersCitation[] }) {\n if (!citations.length) return null;\n return (\n <details className=\"bsw-an__sources-wrap\">\n <summary className=\"bsw-an__sources-toggle\">{sourcesLabel(citations.length)}</summary>\n <ul className=\"bsw-an__sources\">\n {citations.map((citation) => (\n <li key={citation.id} className=\"bsw-an__source\" data-citation-id={citation.id}>\n {citation.url ? (\n <a href={citation.url} target=\"_blank\" rel=\"noopener noreferrer\">\n {citation.title}\n </a>\n ) : (\n <span className=\"bsw-an__source-title\">{citation.title}</span>\n )}\n {citation.excerpt ? <p className=\"bsw-an__excerpt\">{citation.excerpt}</p> : null}\n </li>\n ))}\n </ul>\n </details>\n );\n}\n\nfunction pageContextForAsk(explicit: AnswersPageContext | undefined): AnswersPageContext {\n if (explicit) {\n const url = typeof explicit.url === \"string\" ? explicit.url : undefined;\n const title = typeof explicit.title === \"string\" ? explicit.title : undefined;\n return { url, title };\n }\n if (typeof window === \"undefined\") return {};\n return { url: window.location.href, title: document.title };\n}\n\nexport function Answers(props: AnswersProps) {\n const presentation = props.presentation ?? \"launcher\";\n const sessionId = useSessionId(props.appId);\n const localRef = useRef<AnswersAdapter | null>(null);\n const adapter = props.adapter ?? (props.publishableKey ? null : (localRef.current ??= createLocalAdapter()));\n const mode = props.adapter ? \"byo\" : props.publishableKey ? \"unwired\" : \"local\";\n const [draft, setDraft] = useState(\"\");\n const [pending, setPending] = useState(false);\n const [pendingQuestion, setPendingQuestion] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [turns, setTurns] = useState<Turn[]>([]);\n const pendingRef = useRef(false);\n const logRef = useRef<HTMLDivElement>(null);\n const feedbackLock = useRef(new Set<string>());\n const onFallbackRef = useRef(props.onFallback);\n onFallbackRef.current = props.onFallback;\n\n useEffect(() => {\n const log = logRef.current;\n if (!log) return;\n log.scrollTop = log.scrollHeight;\n }, [turns, pending, pendingQuestion, error]);\n\n const trimmed = draft.trim();\n const length = codePointLength(trimmed);\n const overLimit = length > QUESTION_MAX;\n const inputId = useId();\n\n async function onSubmit(event: FormEvent<HTMLFormElement>) {\n event.preventDefault();\n if (!adapter || pendingRef.current) return;\n if (!trimmed) {\n setError(\"Enter a question.\");\n return;\n }\n if (overLimit) {\n setError(LIMIT_MESSAGE);\n return;\n }\n const question = trimmed;\n const pageContext = pageContextForAsk(props.pageContext);\n pendingRef.current = true;\n setPending(true);\n setPendingQuestion(question);\n setDraft(\"\");\n setError(null);\n try {\n const raw = await adapter.ask({ sessionId, question, pageContext });\n const result = sanitizeResult(raw);\n if (!result) {\n setDraft(question);\n setError(ERROR_MESSAGE);\n return;\n }\n if (result.outcome === \"unanswered\") onFallbackRef.current?.(result);\n setTurns((current) => [...current, { question, result, feedback: \"idle\" }]);\n } catch {\n setDraft(question);\n setError(ERROR_MESSAGE);\n } finally {\n pendingRef.current = false;\n setPending(false);\n setPendingQuestion(null);\n }\n }\n\n async function onFeedback(answerId: string, helpful: boolean) {\n if (!adapter?.feedback || feedbackLock.current.has(answerId)) return;\n const current = turns.find((turn) => turn.result.answerId === answerId);\n if (!current || current.feedback === \"pending\" || current.feedback === \"done\") return;\n feedbackLock.current.add(answerId);\n setTurns((items) => items.map((turn) => (turn.result.answerId === answerId ? { ...turn, feedback: \"pending\" } : turn)));\n try {\n await adapter.feedback({ sessionId, answerId, helpful });\n setTurns((items) => items.map((turn) => (turn.result.answerId === answerId ? { ...turn, feedback: \"done\" } : turn)));\n } catch {\n feedbackLock.current.delete(answerId);\n setTurns((items) => items.map((turn) => (turn.result.answerId === answerId ? { ...turn, feedback: \"error\" } : turn)));\n }\n }\n\n const panel = (\n <div\n className={[WIDGET_ROOT_CLASS, presentation === \"section\" ? props.className : \"\"].filter(Boolean).join(\" \")}\n data-theme={props.theme}\n data-mode={mode}\n >\n <header className=\"bsw-an__header\">\n <span className=\"bsw-an__mark\" aria-hidden=\"true\" />\n <div className=\"bsw-an__heading\">\n <h2 className=\"bsw-an__title\">Answers</h2>\n {mode === \"local\" ? <p className=\"bsw-an__note\">Sample answers for Northwind Outfitters.</p> : null}\n </div>\n </header>\n <div className=\"bsw-an__log\" ref={logRef}>\n {turns.length === 0 && !pendingQuestion ? (\n <p className=\"bsw-an__empty\">\n {mode === \"unwired\"\n ? \"This page is not ready to answer yet.\"\n : mode === \"local\"\n ? \"Ask a question. The sample covers hours, returns, and shipping for Northwind Outfitters.\"\n : \"Ask a question about this page.\"}\n </p>\n ) : (\n <ol className=\"bsw-an__turns\">\n {turns.map((turn, index) => {\n const citations = turn.result.outcome === \"answered\" ? (turn.result.citations ?? []) : [];\n const allowed = new Set(citations.map((citation) => citation.url).filter((url): url is string => Boolean(url)));\n return (\n <li key={`${turn.result.answerId}:${index}`} className=\"bsw-an__turn\" data-outcome={turn.result.outcome}>\n <div className=\"bsw-an__row bsw-an__row--visitor\">\n <p className=\"bsw-an__question\">{turn.question}</p>\n </div>\n <div className=\"bsw-an__row bsw-an__row--reply\">\n {turn.result.outcome === \"answered\" && turn.result.answer ? (\n <div className=\"bsw-an__answer\">{renderAnswerMarkdown(turn.result.answer, allowed)}</div>\n ) : (\n <p className=\"bsw-an__fallback\">{turn.result.fallback}</p>\n )}\n <CitationSources citations={citations} />\n {adapter?.feedback ? (\n turn.feedback === \"done\" ? (\n <p className=\"bsw-an__thanks\">Thanks for the feedback.</p>\n ) : (\n <div className=\"bsw-an__feedback\">\n <button\n type=\"button\"\n className=\"bsw-an__button bsw-an__button--ghost\"\n disabled={turn.feedback === \"pending\"}\n onClick={() => void onFeedback(turn.result.answerId, true)}\n >\n Helpful\n </button>\n <button\n type=\"button\"\n className=\"bsw-an__button bsw-an__button--ghost\"\n disabled={turn.feedback === \"pending\"}\n onClick={() => void onFeedback(turn.result.answerId, false)}\n >\n Not helpful\n </button>\n {turn.feedback === \"error\" ? (\n <p className=\"bsw-an__error\" role=\"alert\">\n {FEEDBACK_ERROR}\n </p>\n ) : null}\n </div>\n )\n ) : null}\n </div>\n </li>\n );\n })}\n {pendingQuestion ? (\n <li className=\"bsw-an__turn\" data-pending=\"true\">\n <div className=\"bsw-an__row bsw-an__row--visitor\">\n <p className=\"bsw-an__question\">{pendingQuestion}</p>\n </div>\n <div className=\"bsw-an__row bsw-an__row--reply\">\n <p className=\"bsw-an__status\" role=\"status\">\n <span className=\"bsw-an__dots\" aria-hidden=\"true\">\n <i />\n <i />\n <i />\n </span>\n Looking that up.\n </p>\n </div>\n </li>\n ) : null}\n </ol>\n )}\n </div>\n <form className=\"bsw-an__composer\" autoComplete=\"off\" onSubmit={(event) => void onSubmit(event)}>\n {error ? (\n <p className=\"bsw-an__error\" role=\"alert\">\n {error}\n </p>\n ) : null}\n <label className=\"bsw-an__label\" htmlFor={inputId}>\n Question\n </label>\n <div className=\"bsw-an__composer-row\">\n <textarea\n id={inputId}\n className=\"bsw-an__textarea\"\n rows={2}\n value={draft}\n disabled={!adapter || pending}\n placeholder={mode === \"local\" ? \"Ask about hours, returns, or shipping\" : \"Ask a question\"}\n enterKeyHint=\"send\"\n onChange={(event) => {\n setDraft(event.target.value);\n if (error) setError(null);\n }}\n onKeyDown={(event) => {\n if (event.key !== \"Enter\" || event.shiftKey) return;\n event.preventDefault();\n event.currentTarget.form?.requestSubmit();\n }}\n />\n <button className=\"bsw-an__button\" type=\"submit\" disabled={!adapter || pending || !trimmed || overLimit}>\n Ask\n </button>\n </div>\n {overLimit ? (\n <p className=\"bsw-an__count\">\n {length} / 1,000\n <span className=\"bsw-an__error\"> {LIMIT_MESSAGE}</span>\n </p>\n ) : length > 800 ? (\n <p className=\"bsw-an__count\">{length} / 1,000</p>\n ) : null}\n </form>\n </div>\n );\n\n if (presentation === \"section\") return panel;\n return (\n <EmbedFrame presentation={presentation} defaultOpen={props.defaultOpen} label=\"Answers\" className={props.className} theme={props.theme}>\n {panel}\n </EmbedFrame>\n );\n}\n","import type { AnswersCitation, AnswersResult } from \"./types\";\n\nconst ANSWER_MAX = 8000;\nconst EXCERPT_MAX = 500;\nconst CITATION_MAX = 20;\nconst EMPTY_FALLBACK = \"No answer was found.\";\n\nexport function codePointLength(value: string): number {\n return Array.from(value).length;\n}\n\nexport function clipCodePoints(value: string, max: number): string {\n let count = 0;\n let out = \"\";\n for (const char of value) {\n if (count >= max) break;\n out += char;\n count += 1;\n }\n return out;\n}\n\nexport function safeHttpUrl(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n return undefined;\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") return undefined;\n return parsed.toString();\n}\n\nexport function sanitizeCitations(value: unknown): AnswersCitation[] {\n if (!Array.isArray(value)) return [];\n const out: AnswersCitation[] = [];\n const seen = new Set<string>();\n for (const item of value) {\n if (out.length >= CITATION_MAX) break;\n if (!item || typeof item !== \"object\") continue;\n const raw = item as Partial<AnswersCitation>;\n if (typeof raw.id !== \"string\" || !raw.id.trim()) continue;\n const id = raw.id.trim();\n if (seen.has(id)) continue;\n seen.add(id);\n const title = typeof raw.title === \"string\" && raw.title.trim() ? clipCodePoints(raw.title.trim(), 200) : id;\n const excerpt = typeof raw.excerpt === \"string\" && raw.excerpt.trim() ? clipCodePoints(raw.excerpt.trim(), EXCERPT_MAX) : undefined;\n const url = safeHttpUrl(raw.url);\n out.push({ id, title, excerpt, url });\n }\n return out;\n}\n\n/** Keeps answered text and citation ids. Unanswered drops any answer body and citations. */\nexport function sanitizeResult(input: unknown): AnswersResult | null {\n if (!input || typeof input !== \"object\") return null;\n const raw = input as Partial<AnswersResult>;\n if (typeof raw.answerId !== \"string\" || !raw.answerId.trim()) return null;\n if (typeof raw.sessionId !== \"string\") return null;\n const answerId = raw.answerId.trim();\n const sessionId = raw.sessionId;\n const fallback = typeof raw.fallback === \"string\" && raw.fallback.trim() ? raw.fallback.trim() : EMPTY_FALLBACK;\n\n if (raw.outcome === \"unanswered\") {\n return { sessionId, answerId, outcome: \"unanswered\", fallback };\n }\n if (raw.outcome !== \"answered\") return null;\n\n const answer = typeof raw.answer === \"string\" ? raw.answer.trim() : \"\";\n if (!answer) return { sessionId, answerId, outcome: \"unanswered\", fallback };\n return {\n sessionId,\n answerId,\n outcome: \"answered\",\n answer: clipCodePoints(answer, ANSWER_MAX),\n citations: sanitizeCitations(raw.citations),\n };\n}\n","export const QUESTION_MAX = 1000;\n\nexport const WIDGET_ROOT_CLASS = \"bsw-an\";\n\nexport type AnswersPageContext = {\n url?: string;\n title?: string;\n};\n\nexport type AnswersCitation = {\n id: string;\n title: string;\n url?: string;\n excerpt?: string;\n};\n\nexport type AnswersOutcome = \"answered\" | \"unanswered\";\n\nexport type AnswersAskInput = {\n sessionId: string;\n question: string;\n pageContext?: AnswersPageContext;\n};\n\nexport type AnswersResult = {\n sessionId: string;\n answerId: string;\n outcome: AnswersOutcome;\n answer?: string;\n citations?: AnswersCitation[];\n fallback?: string;\n};\n\nexport type AnswersFeedbackInput = {\n sessionId: string;\n answerId: string;\n helpful: boolean;\n /** Optional note. Webhook payloads omit this text. */\n reason?: string;\n};\n\nexport type AnswersAdapter = {\n ask: (input: AnswersAskInput) => Promise<AnswersResult>;\n feedback?: (input: AnswersFeedbackInput) => Promise<void> | void;\n};\n\nexport type AnswersPresentation = \"launcher\" | \"section\" | \"modal\" | \"window\";\n\nexport type AnswersTheme = \"light\" | \"dark\";\n\nexport type AnswersProps = {\n appId?: string;\n publishableKey?: string;\n adapter?: AnswersAdapter;\n presentation?: AnswersPresentation;\n defaultOpen?: boolean;\n theme?: AnswersTheme;\n pageContext?: AnswersPageContext;\n onFallback?: (result: AnswersResult) => void;\n /** Layout hook only. Skin the widget with the `--bsw-an-*` tokens. */\n className?: string;\n};\n","import { codePointLength } from \"./result\";\nimport { QUESTION_MAX, type AnswersAdapter, type AnswersCitation, type AnswersResult } from \"./types\";\n\nexport const LOCAL_FALLBACK =\n \"We could not find that in the Northwind Outfitters sample. Ask about hours, returns, or shipping.\";\n\nexport const LOCAL_PROMPTS = {\n returns: \"What is your return window?\",\n returnsFollowUp: \"How long do I have?\",\n hours: \"What are your hours?\",\n shipping: \"Is shipping free?\",\n unsupported: \"Do you price match?\",\n} as const;\n\ntype Topic = \"hours\" | \"returns\" | \"shipping\";\n\nconst RETURNS_ANSWER = \"Returns are accepted within 30 days of delivery.\";\nconst RETURNS_FOLLOW = \"You have 30 days from delivery. Exchanges use that same window.\";\nconst HOURS_ANSWER =\n \"Northwind Outfitters is open Monday through Friday, 9am to 5pm US Eastern, and closed Saturday and Sunday.\";\nconst HOURS_FOLLOW = \"Saturday and Sunday are closed. Weekday hours are 9am to 5pm US Eastern.\";\nconst SHIPPING_ANSWER = \"Standard shipping is free on orders over $50.\";\nconst SHIPPING_FOLLOW = \"Free standard shipping starts when the order is over $50.\";\n\nexport const LOCAL_CITATIONS: Record<Topic, AnswersCitation> = {\n hours: {\n id: \"ane_hours\",\n title: \"Store hours\",\n url: \"https://northwind.example/help/hours\",\n excerpt: \"Open Monday through Friday, 9am to 5pm US Eastern. Closed Saturday and Sunday.\",\n },\n returns: {\n id: \"ane_returns\",\n title: \"Returns\",\n url: \"https://northwind.example/help/returns\",\n excerpt: \"Returns are accepted within 30 days of delivery. Exchanges use the same 30 day window.\",\n },\n shipping: {\n id: \"ane_shipping\",\n title: \"Shipping\",\n url: \"https://northwind.example/help/shipping\",\n excerpt: \"Standard shipping is free on orders over $50.\",\n },\n};\n\nconst TOPICS: { id: Topic; words: string[] }[] = [\n { id: \"returns\", words: [\"return\", \"returns\", \"refund\", \"refunds\", \"exchange\", \"exchanges\"] },\n { id: \"shipping\", words: [\"ship\", \"shipping\", \"delivery\"] },\n { id: \"hours\", words: [\"hour\", \"hours\", \"open\", \"opening\", \"weekend\", \"saturday\", \"sunday\", \"weekday\", \"weekdays\"] },\n];\n\nconst ANSWERS: Record<Topic, string> = {\n hours: HOURS_ANSWER,\n returns: RETURNS_ANSWER,\n shipping: SHIPPING_ANSWER,\n};\n\nconst FOLLOW_ANSWERS: Record<Topic, string> = {\n hours: HOURS_FOLLOW,\n returns: RETURNS_FOLLOW,\n shipping: SHIPPING_FOLLOW,\n};\n\nconst FOLLOW: { phrase: string; topic?: Topic }[] = [\n { phrase: \"how long\" },\n { phrase: \"how long do i have\" },\n { phrase: \"are you open then\", topic: \"hours\" },\n { phrase: \"what about then\" },\n { phrase: \"is there a minimum\", topic: \"shipping\" },\n { phrase: \"how much is free\", topic: \"shipping\" },\n { phrase: \"tell me more\" },\n];\n\nfunction normalize(question: string): string {\n return question\n .trim()\n .toLowerCase()\n .replace(/[?!.,'\"]/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nfunction hasWord(question: string, word: string): boolean {\n return new RegExp(`\\\\b${word}\\\\b`).test(question);\n}\n\nfunction matchDirect(question: string): Topic | null {\n for (const topic of TOPICS) {\n if (topic.words.some((word) => hasWord(question, word))) return topic.id;\n }\n return null;\n}\n\nfunction matchFollow(question: string, last: Topic | undefined): Topic | null {\n const hit = FOLLOW.find((item) => item.phrase === question);\n if (!hit || !last) return null;\n if (hit.topic && hit.topic !== last) return null;\n return last;\n}\n\nfunction limitProblem(question: string): string | null {\n const trimmed = question.trim();\n if (!trimmed) return \"Enter a question.\";\n if (codePointLength(trimmed) > QUESTION_MAX) return \"Questions are limited to 1,000 characters.\";\n return null;\n}\n\n/**\n * Deterministic sample company. Supported facts, a follow-up that needs the\n * previous topic, and an explicit unanswered fallback. No network.\n */\nexport function createLocalAdapter(): AnswersAdapter {\n const lastTopic = new Map<string, Topic>();\n let sequence = 0;\n\n return {\n async ask(input): Promise<AnswersResult> {\n const problem = limitProblem(input.question);\n if (problem) throw new Error(problem);\n const question = normalize(input.question);\n const direct = matchDirect(question);\n const topic = direct ?? matchFollow(question, lastTopic.get(input.sessionId));\n sequence += 1;\n const answerId = `anq_${sequence.toString(16).padStart(24, \"0\")}`;\n if (!topic) {\n return {\n sessionId: input.sessionId,\n answerId,\n outcome: \"unanswered\",\n fallback: LOCAL_FALLBACK,\n };\n }\n lastTopic.set(input.sessionId, topic);\n return {\n sessionId: input.sessionId,\n answerId,\n outcome: \"answered\",\n answer: direct ? ANSWERS[topic] : FOLLOW_ANSWERS[topic],\n citations: [{ ...LOCAL_CITATIONS[topic] }],\n };\n },\n async feedback() {},\n };\n}\n\n/** Returns the caller adapter. Question text stays on the customer backend. */\nexport function createByoAdapter(impl: AnswersAdapter): AnswersAdapter {\n return impl;\n}\n","import { useEffect, useState, type ReactNode } from \"react\";\nimport type { AnswersPresentation, AnswersTheme } from \"./types\";\n\nexport function EmbedFrame({\n presentation = \"section\",\n defaultOpen = false,\n label,\n className,\n theme,\n children,\n}: {\n presentation?: AnswersPresentation;\n defaultOpen?: boolean;\n label: string;\n className?: string;\n theme?: AnswersTheme;\n children: ReactNode;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n const openKey = `${presentation}\\0${defaultOpen ? \"1\" : \"0\"}`;\n const [openKeySeen, setOpenKeySeen] = useState(openKey);\n if (openKey !== openKeySeen) {\n setOpenKeySeen(openKey);\n setOpen(defaultOpen);\n }\n const launcher = presentation === \"launcher\";\n const floating = launcher || presentation === \"window\";\n useEffect(() => {\n if (presentation === \"section\" || !open) return;\n function onKey(event: KeyboardEvent) {\n if (event.key === \"Escape\") setOpen(false);\n }\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [open, presentation]);\n if (presentation === \"section\") return children;\n const classes = [\n \"bsw-an-frame\",\n `bsw-an-frame--${presentation}`,\n open ? \"bsw-an-frame--open\" : \"\",\n floating ? \"bsw-an--launcher-host\" : \"\",\n floating && open ? \"bsw-an--launcher-open\" : \"\",\n floating && !open ? \"bsw-an--launcher-closed\" : \"\",\n className ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n return (\n <div className={classes} data-theme={theme} data-state={open ? \"open\" : \"closed\"}>\n {open && (presentation === \"modal\" || launcher) ? (\n <button type=\"button\" className=\"bsw-an-frame__backdrop\" aria-label={`Close ${label}`} onClick={() => setOpen(false)} />\n ) : null}\n <div\n className=\"bsw-an-frame__panel\"\n hidden={!open}\n role={open && presentation === \"modal\" ? \"dialog\" : open ? \"region\" : undefined}\n aria-modal={open && presentation === \"modal\" ? true : undefined}\n aria-label={open ? label : undefined}\n >\n <button type=\"button\" className=\"bsw-an-frame__close\" onClick={() => setOpen(false)}>\n <span className=\"bsw-an-frame__close-label\">Close</span>\n <span aria-hidden=\"true\">×</span>\n </button>\n {children}\n </div>\n {open ? null : (\n <button\n type=\"button\"\n className={launcher ? \"bsw-an-frame__fab\" : \"bsw-an-frame__trigger\"}\n aria-label={launcher ? \"Open answers\" : undefined}\n onClick={() => setOpen(true)}\n >\n {launcher ? \"Ask\" : label}\n </button>\n )}\n </div>\n );\n}\n","import type { ReactNode } from \"react\";\nimport { safeHttpUrl } from \"./result\";\n\nfunction inlineMarkup(text: string, allowed: ReadonlySet<string>, keyPrefix: string): ReactNode[] {\n const pattern = /(\\*\\*[^*\\n]+\\*\\*|(?<![A-Za-z0-9])_[^_\\n]+_(?![A-Za-z0-9])|`[^`\\n]+`|\\[[^\\]\\n]+\\]\\(https?:\\/\\/[^)\\s]+\\))/g;\n const parts: ReactNode[] = [];\n let last = 0;\n let index = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(text))) {\n if (match.index > last) parts.push(text.slice(last, match.index));\n const token = match[0];\n const key = `${keyPrefix}-${index++}`;\n if (token.startsWith(\"**\") && token.endsWith(\"**\") && token.length >= 4) {\n parts.push(<strong key={key}>{token.slice(2, -2)}</strong>);\n } else if (token.startsWith(\"_\") && token.endsWith(\"_\") && token.length >= 3) {\n parts.push(<em key={key}>{token.slice(1, -1)}</em>);\n } else if (token.startsWith(\"`\") && token.endsWith(\"`\") && token.length >= 3) {\n parts.push(<code key={key}>{token.slice(1, -1)}</code>);\n } else if (token.startsWith(\"[\")) {\n const link = /^\\[([^\\]\\n]+)\\]\\((https?:\\/\\/[^)\\s]+)\\)$/.exec(token);\n const href = link?.[2] ?? \"\";\n const safe = safeHttpUrl(href);\n if (link && safe && (allowed.has(href) || allowed.has(safe))) {\n parts.push(\n <a key={key} href={safe} target=\"_blank\" rel=\"noopener noreferrer\">\n {link[1]}\n </a>,\n );\n } else {\n parts.push(token);\n }\n } else {\n parts.push(token);\n }\n last = match.index + token.length;\n }\n if (last < text.length) parts.push(text.slice(last));\n return parts;\n}\n\n/** Bold, italic, code, and simple lists. Links render only for citation URLs. HTML stays text. */\nexport function renderAnswerMarkdown(text: string, allowedUrls: ReadonlySet<string>): ReactNode {\n const lines = text.split(\"\\n\");\n const nodes: ReactNode[] = [];\n let paragraph: string[] = [];\n let list: string[] = [];\n let key = 0;\n\n function flushParagraph() {\n if (!paragraph.length) return;\n const content = paragraph.join(\"\\n\");\n nodes.push(<p key={`p-${key++}`}>{inlineMarkup(content, allowedUrls, `p-${key}`)}</p>);\n paragraph = [];\n }\n\n function flushList() {\n if (!list.length) return;\n const items = list;\n nodes.push(\n <ul key={`ul-${key++}`}>\n {items.map((item, index) => (\n <li key={`li-${key}-${index}`}>{inlineMarkup(item, allowedUrls, `li-${key}-${index}`)}</li>\n ))}\n </ul>,\n );\n list = [];\n }\n\n for (const line of lines) {\n const item = /^\\s*[-*]\\s+(.*)$/.exec(line);\n if (item) {\n flushParagraph();\n list.push(item[1] ?? \"\");\n continue;\n }\n if (!line.trim()) {\n flushParagraph();\n flushList();\n continue;\n }\n flushList();\n paragraph.push(line);\n }\n flushParagraph();\n flushList();\n return nodes;\n}\n","const SESSION_ID = /^ans_[0-9a-f]{24}$/;\n\nexport function sessionStorageKey(appId?: string): string {\n const scope = appId?.trim() ? appId.trim() : \"local\";\n return `bsw_an_session:${scope}`;\n}\n\nexport function createOpaqueSessionId(): string {\n const bytes = new Uint8Array(12);\n globalThis.crypto.getRandomValues(bytes);\n let hex = \"\";\n for (const byte of bytes) hex += byte.toString(16).padStart(2, \"0\");\n return `ans_${hex}`;\n}\n\n/** Reads the visitor session from sessionStorage. Never writes a cookie. */\nexport function readSessionId(storage: Storage | null, appId?: string): string {\n if (!storage) return createOpaqueSessionId();\n const key = sessionStorageKey(appId);\n const existing = storage.getItem(key);\n if (existing && SESSION_ID.test(existing)) return existing;\n const id = createOpaqueSessionId();\n storage.setItem(key, id);\n return id;\n}\n","import { createRoot, type Root } from \"react-dom/client\";\nimport { Answers } from \"./Answers\";\nimport type { AnswersProps } from \"./types\";\n\nconst roots = new WeakMap<Element, Root>();\n\nexport function mount(el: Element, props: AnswersProps = {}): () => void {\n let root = roots.get(el);\n if (!root) {\n root = createRoot(el);\n roots.set(el, root);\n }\n root.render(<Answers {...props} />);\n return () => unmount(el);\n}\n\nexport function unmount(el: Element): void {\n const root = roots.get(el);\n if (!root) return;\n root.unmount();\n roots.delete(el);\n}\n"],"mappings":"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","names":["useEffect","useState","jsx","jsx","jsxs","useState","useEffect","jsx"]}
1
+ {"version":3,"sources":["../src/Answers.tsx","../src/result.ts","../src/types.ts","../src/adapters.ts","../src/frame.tsx","../src/markdown.tsx","../src/session.ts","../src/mount.tsx"],"sourcesContent":["import { useEffect, useId, useRef, useState, type FormEvent } from \"react\";\nimport { createLocalAdapter } from \"./adapters\";\nimport { EmbedFrame } from \"./frame\";\nimport { renderAnswerMarkdown } from \"./markdown\";\nimport { codePointLength, sanitizeResult } from \"./result\";\nimport { readSessionId, sessionStorageKey } from \"./session\";\nimport {\n QUESTION_MAX,\n WIDGET_ROOT_CLASS,\n type AnswersAdapter,\n type AnswersCitation,\n type AnswersPageContext,\n type AnswersProps,\n type AnswersResult,\n} from \"./types\";\n\ntype Turn = {\n question: string;\n result: AnswersResult;\n feedback: \"idle\" | \"pending\" | \"done\" | \"error\";\n};\n\nconst LIMIT_MESSAGE = \"Questions are limited to 1,000 characters.\";\nconst ERROR_MESSAGE = \"Something went wrong. Your question is still here. Try again.\";\nconst FEEDBACK_ERROR = \"Feedback could not be saved. Try again.\";\n\nfunction useSessionId(appId?: string): string {\n const key = sessionStorageKey(appId);\n const [state, setState] = useState(() => ({\n key,\n id: readSessionId(typeof window === \"undefined\" ? null : window.sessionStorage, appId),\n }));\n if (state.key !== key) {\n setState({\n key,\n id: readSessionId(typeof window === \"undefined\" ? null : window.sessionStorage, appId),\n });\n }\n return state.id;\n}\n\nfunction sourcesLabel(count: number): string {\n return count === 1 ? \"1 source\" : `${count} sources`;\n}\n\nfunction CitationSources({ citations }: { citations: AnswersCitation[] }) {\n if (!citations.length) return null;\n return (\n <details className=\"bsw-an__sources-wrap\">\n <summary className=\"bsw-an__sources-toggle\">{sourcesLabel(citations.length)}</summary>\n <ul className=\"bsw-an__sources\">\n {citations.map((citation) => (\n <li key={citation.id} className=\"bsw-an__source\" data-citation-id={citation.id}>\n {citation.url ? (\n <a href={citation.url} target=\"_blank\" rel=\"noopener noreferrer\">\n {citation.title}\n </a>\n ) : (\n <span className=\"bsw-an__source-title\">{citation.title}</span>\n )}\n {citation.excerpt ? <p className=\"bsw-an__excerpt\">{citation.excerpt}</p> : null}\n </li>\n ))}\n </ul>\n </details>\n );\n}\n\nfunction pageContextForAsk(explicit: AnswersPageContext | undefined): AnswersPageContext {\n if (explicit) {\n const url = typeof explicit.url === \"string\" ? explicit.url : undefined;\n const title = typeof explicit.title === \"string\" ? explicit.title : undefined;\n return { url, title };\n }\n if (typeof window === \"undefined\") return {};\n return { url: window.location.href, title: document.title };\n}\n\nexport function Answers(props: AnswersProps) {\n const presentation = props.presentation ?? \"launcher\";\n const sessionId = useSessionId(props.appId);\n const localRef = useRef<AnswersAdapter | null>(null);\n const adapter = props.adapter ?? (props.publishableKey ? null : (localRef.current ??= createLocalAdapter()));\n const mode = props.adapter ? \"byo\" : props.publishableKey ? \"unwired\" : \"local\";\n const [draft, setDraft] = useState(\"\");\n const [pending, setPending] = useState(false);\n const [pendingQuestion, setPendingQuestion] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [turns, setTurns] = useState<Turn[]>([]);\n const pendingRef = useRef(false);\n const logRef = useRef<HTMLDivElement>(null);\n const feedbackLock = useRef(new Set<string>());\n const onFallbackRef = useRef(props.onFallback);\n onFallbackRef.current = props.onFallback;\n\n useEffect(() => {\n const log = logRef.current;\n if (!log) return;\n log.scrollTop = log.scrollHeight;\n }, [turns, pending, pendingQuestion, error]);\n\n const trimmed = draft.trim();\n const length = codePointLength(trimmed);\n const overLimit = length > QUESTION_MAX;\n const inputId = useId();\n\n async function onSubmit(event: FormEvent<HTMLFormElement>) {\n event.preventDefault();\n if (!adapter || pendingRef.current) return;\n if (!trimmed) {\n setError(\"Enter a question.\");\n return;\n }\n if (overLimit) {\n setError(LIMIT_MESSAGE);\n return;\n }\n const question = trimmed;\n const pageContext = pageContextForAsk(props.pageContext);\n pendingRef.current = true;\n setPending(true);\n setPendingQuestion(question);\n setDraft(\"\");\n setError(null);\n try {\n const raw = await adapter.ask({ sessionId, question, pageContext });\n const result = sanitizeResult(raw);\n if (!result) {\n setDraft(question);\n setError(ERROR_MESSAGE);\n return;\n }\n if (result.outcome === \"unanswered\") onFallbackRef.current?.(result);\n setTurns((current) => [...current, { question, result, feedback: \"idle\" }]);\n } catch {\n setDraft(question);\n setError(ERROR_MESSAGE);\n } finally {\n pendingRef.current = false;\n setPending(false);\n setPendingQuestion(null);\n }\n }\n\n async function onFeedback(answerId: string, helpful: boolean) {\n if (!adapter?.feedback || feedbackLock.current.has(answerId)) return;\n const current = turns.find((turn) => turn.result.answerId === answerId);\n if (!current || current.feedback === \"pending\" || current.feedback === \"done\") return;\n feedbackLock.current.add(answerId);\n setTurns((items) => items.map((turn) => (turn.result.answerId === answerId ? { ...turn, feedback: \"pending\" } : turn)));\n try {\n await adapter.feedback({ sessionId, answerId, helpful });\n setTurns((items) => items.map((turn) => (turn.result.answerId === answerId ? { ...turn, feedback: \"done\" } : turn)));\n } catch {\n feedbackLock.current.delete(answerId);\n setTurns((items) => items.map((turn) => (turn.result.answerId === answerId ? { ...turn, feedback: \"error\" } : turn)));\n }\n }\n\n const panel = (\n <div\n className={[WIDGET_ROOT_CLASS, presentation === \"section\" ? props.className : \"\"].filter(Boolean).join(\" \")}\n data-theme={props.theme}\n data-mode={mode}\n >\n <header className=\"bsw-an__header\">\n <span className=\"bsw-an__mark\" aria-hidden=\"true\" />\n <div className=\"bsw-an__heading\">\n <h2 className=\"bsw-an__title\">Answers</h2>\n {mode === \"local\" ? <p className=\"bsw-an__note\">Sample answers for Northwind Outfitters.</p> : null}\n </div>\n </header>\n <div className=\"bsw-an__log\" ref={logRef}>\n {turns.length === 0 && !pendingQuestion ? (\n <p className=\"bsw-an__empty\">\n {mode === \"unwired\"\n ? \"This page is not ready to answer yet.\"\n : mode === \"local\"\n ? \"Ask a question. The sample covers hours, returns, and shipping for Northwind Outfitters.\"\n : props.greeting ?? \"Ask a question this site already answers.\"}\n </p>\n ) : (\n <ol className=\"bsw-an__turns\">\n {turns.map((turn, index) => {\n const citations = turn.result.outcome === \"answered\" ? (turn.result.citations ?? []) : [];\n const allowed = new Set(citations.map((citation) => citation.url).filter((url): url is string => Boolean(url)));\n return (\n <li key={`${turn.result.answerId}:${index}`} className=\"bsw-an__turn\" data-outcome={turn.result.outcome}>\n <div className=\"bsw-an__row bsw-an__row--visitor\">\n <p className=\"bsw-an__question\">{turn.question}</p>\n </div>\n <div className=\"bsw-an__row bsw-an__row--reply\">\n {turn.result.outcome === \"answered\" && turn.result.answer ? (\n <div className=\"bsw-an__answer\">{renderAnswerMarkdown(turn.result.answer, allowed)}</div>\n ) : (\n <p className=\"bsw-an__fallback\">{turn.result.fallback}</p>\n )}\n <CitationSources citations={citations} />\n {adapter?.feedback ? (\n turn.feedback === \"done\" ? (\n <p className=\"bsw-an__thanks\">Thanks for the feedback.</p>\n ) : (\n <div className=\"bsw-an__feedback\">\n <button\n type=\"button\"\n className=\"bsw-an__button bsw-an__button--ghost\"\n disabled={turn.feedback === \"pending\"}\n onClick={() => void onFeedback(turn.result.answerId, true)}\n >\n Helpful\n </button>\n <button\n type=\"button\"\n className=\"bsw-an__button bsw-an__button--ghost\"\n disabled={turn.feedback === \"pending\"}\n onClick={() => void onFeedback(turn.result.answerId, false)}\n >\n Not helpful\n </button>\n {turn.feedback === \"error\" ? (\n <p className=\"bsw-an__error\" role=\"alert\">\n {FEEDBACK_ERROR}\n </p>\n ) : null}\n </div>\n )\n ) : null}\n </div>\n </li>\n );\n })}\n {pendingQuestion ? (\n <li className=\"bsw-an__turn\" data-pending=\"true\">\n <div className=\"bsw-an__row bsw-an__row--visitor\">\n <p className=\"bsw-an__question\">{pendingQuestion}</p>\n </div>\n <div className=\"bsw-an__row bsw-an__row--reply\">\n <p className=\"bsw-an__status\" role=\"status\">\n <span className=\"bsw-an__dots\" aria-hidden=\"true\">\n <i />\n <i />\n <i />\n </span>\n Looking that up.\n </p>\n </div>\n </li>\n ) : null}\n </ol>\n )}\n </div>\n <form className=\"bsw-an__composer\" autoComplete=\"off\" onSubmit={(event) => void onSubmit(event)}>\n {error ? (\n <p className=\"bsw-an__error\" role=\"alert\">\n {error}\n </p>\n ) : null}\n <label className=\"bsw-an__label\" htmlFor={inputId}>\n Question\n </label>\n <div className=\"bsw-an__composer-row\">\n <textarea\n id={inputId}\n className=\"bsw-an__textarea\"\n rows={2}\n value={draft}\n disabled={!adapter || pending}\n placeholder={mode === \"local\" ? \"Ask about hours, returns, or shipping\" : \"Ask a question\"}\n enterKeyHint=\"send\"\n onChange={(event) => {\n setDraft(event.target.value);\n if (error) setError(null);\n }}\n onKeyDown={(event) => {\n if (event.key !== \"Enter\" || event.shiftKey) return;\n event.preventDefault();\n event.currentTarget.form?.requestSubmit();\n }}\n />\n <button className=\"bsw-an__button\" type=\"submit\" disabled={!adapter || pending || !trimmed || overLimit}>\n Ask\n </button>\n </div>\n {overLimit ? (\n <p className=\"bsw-an__count\">\n {length} / 1,000\n <span className=\"bsw-an__error\"> {LIMIT_MESSAGE}</span>\n </p>\n ) : length > 800 ? (\n <p className=\"bsw-an__count\">{length} / 1,000</p>\n ) : null}\n </form>\n </div>\n );\n\n if (presentation === \"section\") return panel;\n return (\n <EmbedFrame presentation={presentation} defaultOpen={props.defaultOpen} label=\"Answers\" className={props.className} theme={props.theme}>\n {panel}\n </EmbedFrame>\n );\n}\n","import type { AnswersCitation, AnswersResult } from \"./types\";\n\nconst ANSWER_MAX = 8000;\nconst EXCERPT_MAX = 500;\nconst CITATION_MAX = 20;\nconst EMPTY_FALLBACK = \"No answer was found.\";\n\nexport function codePointLength(value: string): number {\n return Array.from(value).length;\n}\n\nexport function clipCodePoints(value: string, max: number): string {\n let count = 0;\n let out = \"\";\n for (const char of value) {\n if (count >= max) break;\n out += char;\n count += 1;\n }\n return out;\n}\n\nexport function safeHttpUrl(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n return undefined;\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") return undefined;\n return parsed.toString();\n}\n\nexport function sanitizeCitations(value: unknown): AnswersCitation[] {\n if (!Array.isArray(value)) return [];\n const out: AnswersCitation[] = [];\n const seen = new Set<string>();\n for (const item of value) {\n if (out.length >= CITATION_MAX) break;\n if (!item || typeof item !== \"object\") continue;\n const raw = item as Partial<AnswersCitation>;\n if (typeof raw.id !== \"string\" || !raw.id.trim()) continue;\n const id = raw.id.trim();\n if (seen.has(id)) continue;\n seen.add(id);\n const title = typeof raw.title === \"string\" && raw.title.trim() ? clipCodePoints(raw.title.trim(), 200) : id;\n const excerpt = typeof raw.excerpt === \"string\" && raw.excerpt.trim() ? clipCodePoints(raw.excerpt.trim(), EXCERPT_MAX) : undefined;\n const url = safeHttpUrl(raw.url);\n out.push({ id, title, excerpt, url });\n }\n return out;\n}\n\n/** Keeps answered text and citation ids. Unanswered drops any answer body and citations. */\nexport function sanitizeResult(input: unknown): AnswersResult | null {\n if (!input || typeof input !== \"object\") return null;\n const raw = input as Partial<AnswersResult>;\n if (typeof raw.answerId !== \"string\" || !raw.answerId.trim()) return null;\n if (typeof raw.sessionId !== \"string\") return null;\n const answerId = raw.answerId.trim();\n const sessionId = raw.sessionId;\n const fallback = typeof raw.fallback === \"string\" && raw.fallback.trim() ? raw.fallback.trim() : EMPTY_FALLBACK;\n\n if (raw.outcome === \"unanswered\") {\n return { sessionId, answerId, outcome: \"unanswered\", fallback };\n }\n if (raw.outcome !== \"answered\") return null;\n\n const answer = typeof raw.answer === \"string\" ? raw.answer.trim() : \"\";\n if (!answer) return { sessionId, answerId, outcome: \"unanswered\", fallback };\n return {\n sessionId,\n answerId,\n outcome: \"answered\",\n answer: clipCodePoints(answer, ANSWER_MAX),\n citations: sanitizeCitations(raw.citations),\n };\n}\n","export const QUESTION_MAX = 1000;\n\nexport const WIDGET_ROOT_CLASS = \"bsw-an\";\n\nexport type AnswersPageContext = {\n url?: string;\n title?: string;\n};\n\nexport type AnswersCitation = {\n id: string;\n title: string;\n url?: string;\n excerpt?: string;\n};\n\nexport type AnswersOutcome = \"answered\" | \"unanswered\";\n\nexport type AnswersAskInput = {\n sessionId: string;\n question: string;\n pageContext?: AnswersPageContext;\n};\n\nexport type AnswersResult = {\n sessionId: string;\n answerId: string;\n outcome: AnswersOutcome;\n answer?: string;\n citations?: AnswersCitation[];\n fallback?: string;\n};\n\nexport type AnswersFeedbackInput = {\n sessionId: string;\n answerId: string;\n helpful: boolean;\n /** Optional note. Webhook payloads omit this text. */\n reason?: string;\n};\n\nexport type AnswersAdapter = {\n ask: (input: AnswersAskInput) => Promise<AnswersResult>;\n feedback?: (input: AnswersFeedbackInput) => Promise<void> | void;\n};\n\nexport type AnswersPresentation = \"launcher\" | \"section\" | \"modal\" | \"window\";\n\nexport type AnswersTheme = \"light\" | \"dark\";\n\nexport type AnswersProps = {\n appId?: string;\n publishableKey?: string;\n adapter?: AnswersAdapter;\n presentation?: AnswersPresentation;\n defaultOpen?: boolean;\n theme?: AnswersTheme;\n pageContext?: AnswersPageContext;\n /** Empty-state copy. Omit it and the widget shows \"Ask a question this site already answers.\" */\n greeting?: string;\n onFallback?: (result: AnswersResult) => void;\n /** Layout hook only. Skin the widget with the `--bsw-an-*` tokens. */\n className?: string;\n};\n","import { codePointLength } from \"./result\";\nimport { QUESTION_MAX, type AnswersAdapter, type AnswersCitation, type AnswersResult } from \"./types\";\n\nexport const LOCAL_FALLBACK =\n \"We could not find that in the Northwind Outfitters sample. Ask about hours, returns, or shipping.\";\n\nexport const LOCAL_PROMPTS = {\n returns: \"What is your return window?\",\n returnsFollowUp: \"How long do I have?\",\n hours: \"What are your hours?\",\n shipping: \"Is shipping free?\",\n unsupported: \"Do you price match?\",\n} as const;\n\ntype Topic = \"hours\" | \"returns\" | \"shipping\";\n\nconst RETURNS_ANSWER = \"Returns are accepted within 30 days of delivery.\";\nconst RETURNS_FOLLOW = \"You have 30 days from delivery. Exchanges use that same window.\";\nconst HOURS_ANSWER =\n \"Northwind Outfitters is open Monday through Friday, 9am to 5pm US Eastern, and closed Saturday and Sunday.\";\nconst HOURS_FOLLOW = \"Saturday and Sunday are closed. Weekday hours are 9am to 5pm US Eastern.\";\nconst SHIPPING_ANSWER = \"Standard shipping is free on orders over $50.\";\nconst SHIPPING_FOLLOW = \"Free standard shipping starts when the order is over $50.\";\n\nexport const LOCAL_CITATIONS: Record<Topic, AnswersCitation> = {\n hours: {\n id: \"ane_hours\",\n title: \"Store hours\",\n url: \"https://northwind.example/help/hours\",\n excerpt: \"Open Monday through Friday, 9am to 5pm US Eastern. Closed Saturday and Sunday.\",\n },\n returns: {\n id: \"ane_returns\",\n title: \"Returns\",\n url: \"https://northwind.example/help/returns\",\n excerpt: \"Returns are accepted within 30 days of delivery. Exchanges use the same 30 day window.\",\n },\n shipping: {\n id: \"ane_shipping\",\n title: \"Shipping\",\n url: \"https://northwind.example/help/shipping\",\n excerpt: \"Standard shipping is free on orders over $50.\",\n },\n};\n\nconst TOPICS: { id: Topic; words: string[] }[] = [\n { id: \"returns\", words: [\"return\", \"returns\", \"refund\", \"refunds\", \"exchange\", \"exchanges\"] },\n { id: \"shipping\", words: [\"ship\", \"shipping\", \"delivery\"] },\n { id: \"hours\", words: [\"hour\", \"hours\", \"open\", \"opening\", \"weekend\", \"saturday\", \"sunday\", \"weekday\", \"weekdays\"] },\n];\n\nconst ANSWERS: Record<Topic, string> = {\n hours: HOURS_ANSWER,\n returns: RETURNS_ANSWER,\n shipping: SHIPPING_ANSWER,\n};\n\nconst FOLLOW_ANSWERS: Record<Topic, string> = {\n hours: HOURS_FOLLOW,\n returns: RETURNS_FOLLOW,\n shipping: SHIPPING_FOLLOW,\n};\n\nconst FOLLOW: { phrase: string; topic?: Topic }[] = [\n { phrase: \"how long\" },\n { phrase: \"how long do i have\" },\n { phrase: \"are you open then\", topic: \"hours\" },\n { phrase: \"what about then\" },\n { phrase: \"is there a minimum\", topic: \"shipping\" },\n { phrase: \"how much is free\", topic: \"shipping\" },\n { phrase: \"tell me more\" },\n];\n\nfunction normalize(question: string): string {\n return question\n .trim()\n .toLowerCase()\n .replace(/[?!.,'\"]/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nfunction hasWord(question: string, word: string): boolean {\n return new RegExp(`\\\\b${word}\\\\b`).test(question);\n}\n\nfunction matchDirect(question: string): Topic | null {\n for (const topic of TOPICS) {\n if (topic.words.some((word) => hasWord(question, word))) return topic.id;\n }\n return null;\n}\n\nfunction matchFollow(question: string, last: Topic | undefined): Topic | null {\n const hit = FOLLOW.find((item) => item.phrase === question);\n if (!hit || !last) return null;\n if (hit.topic && hit.topic !== last) return null;\n return last;\n}\n\nfunction limitProblem(question: string): string | null {\n const trimmed = question.trim();\n if (!trimmed) return \"Enter a question.\";\n if (codePointLength(trimmed) > QUESTION_MAX) return \"Questions are limited to 1,000 characters.\";\n return null;\n}\n\n/**\n * Deterministic sample company. Supported facts, a follow-up that needs the\n * previous topic, and an explicit unanswered fallback. No network.\n */\nexport function createLocalAdapter(): AnswersAdapter {\n const lastTopic = new Map<string, Topic>();\n let sequence = 0;\n\n return {\n async ask(input): Promise<AnswersResult> {\n const problem = limitProblem(input.question);\n if (problem) throw new Error(problem);\n const question = normalize(input.question);\n const direct = matchDirect(question);\n const topic = direct ?? matchFollow(question, lastTopic.get(input.sessionId));\n sequence += 1;\n const answerId = `anq_${sequence.toString(16).padStart(24, \"0\")}`;\n if (!topic) {\n return {\n sessionId: input.sessionId,\n answerId,\n outcome: \"unanswered\",\n fallback: LOCAL_FALLBACK,\n };\n }\n lastTopic.set(input.sessionId, topic);\n return {\n sessionId: input.sessionId,\n answerId,\n outcome: \"answered\",\n answer: direct ? ANSWERS[topic] : FOLLOW_ANSWERS[topic],\n citations: [{ ...LOCAL_CITATIONS[topic] }],\n };\n },\n async feedback() {},\n };\n}\n\n/** Returns the caller adapter. Question text stays on the customer backend. */\nexport function createByoAdapter(impl: AnswersAdapter): AnswersAdapter {\n return impl;\n}\n","import { useEffect, useState, type ReactNode } from \"react\";\nimport type { AnswersPresentation, AnswersTheme } from \"./types\";\n\nexport function EmbedFrame({\n presentation = \"section\",\n defaultOpen = false,\n label,\n className,\n theme,\n children,\n}: {\n presentation?: AnswersPresentation;\n defaultOpen?: boolean;\n label: string;\n className?: string;\n theme?: AnswersTheme;\n children: ReactNode;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n const openKey = `${presentation}\\0${defaultOpen ? \"1\" : \"0\"}`;\n const [openKeySeen, setOpenKeySeen] = useState(openKey);\n if (openKey !== openKeySeen) {\n setOpenKeySeen(openKey);\n setOpen(defaultOpen);\n }\n const launcher = presentation === \"launcher\";\n const floating = launcher || presentation === \"window\";\n useEffect(() => {\n if (presentation === \"section\" || !open) return;\n function onKey(event: KeyboardEvent) {\n if (event.key === \"Escape\") setOpen(false);\n }\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [open, presentation]);\n if (presentation === \"section\") return children;\n const classes = [\n \"bsw-an-frame\",\n `bsw-an-frame--${presentation}`,\n open ? \"bsw-an-frame--open\" : \"\",\n floating ? \"bsw-an--launcher-host\" : \"\",\n floating && open ? \"bsw-an--launcher-open\" : \"\",\n floating && !open ? \"bsw-an--launcher-closed\" : \"\",\n className ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n return (\n <div className={classes} data-theme={theme} data-state={open ? \"open\" : \"closed\"}>\n {open && (presentation === \"modal\" || launcher) ? (\n <button type=\"button\" className=\"bsw-an-frame__backdrop\" aria-label={`Close ${label}`} onClick={() => setOpen(false)} />\n ) : null}\n <div\n className=\"bsw-an-frame__panel\"\n hidden={!open}\n role={open && presentation === \"modal\" ? \"dialog\" : open ? \"region\" : undefined}\n aria-modal={open && presentation === \"modal\" ? true : undefined}\n aria-label={open ? label : undefined}\n >\n <button type=\"button\" className=\"bsw-an-frame__close\" onClick={() => setOpen(false)}>\n <span className=\"bsw-an-frame__close-label\">Close</span>\n <span aria-hidden=\"true\">×</span>\n </button>\n {children}\n </div>\n {open ? null : (\n <button\n type=\"button\"\n className={launcher ? \"bsw-an-frame__fab\" : \"bsw-an-frame__trigger\"}\n aria-label={launcher ? \"Open answers\" : undefined}\n onClick={() => setOpen(true)}\n >\n {launcher ? \"Ask\" : label}\n </button>\n )}\n </div>\n );\n}\n","import type { ReactNode } from \"react\";\nimport { safeHttpUrl } from \"./result\";\n\nfunction inlineMarkup(text: string, allowed: ReadonlySet<string>, keyPrefix: string): ReactNode[] {\n const pattern = /(\\*\\*[^*\\n]+\\*\\*|(?<![A-Za-z0-9])_[^_\\n]+_(?![A-Za-z0-9])|`[^`\\n]+`|\\[[^\\]\\n]+\\]\\(https?:\\/\\/[^)\\s]+\\))/g;\n const parts: ReactNode[] = [];\n let last = 0;\n let index = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(text))) {\n if (match.index > last) parts.push(text.slice(last, match.index));\n const token = match[0];\n const key = `${keyPrefix}-${index++}`;\n if (token.startsWith(\"**\") && token.endsWith(\"**\") && token.length >= 4) {\n parts.push(<strong key={key}>{token.slice(2, -2)}</strong>);\n } else if (token.startsWith(\"_\") && token.endsWith(\"_\") && token.length >= 3) {\n parts.push(<em key={key}>{token.slice(1, -1)}</em>);\n } else if (token.startsWith(\"`\") && token.endsWith(\"`\") && token.length >= 3) {\n parts.push(<code key={key}>{token.slice(1, -1)}</code>);\n } else if (token.startsWith(\"[\")) {\n const link = /^\\[([^\\]\\n]+)\\]\\((https?:\\/\\/[^)\\s]+)\\)$/.exec(token);\n const href = link?.[2] ?? \"\";\n const safe = safeHttpUrl(href);\n if (link && safe && (allowed.has(href) || allowed.has(safe))) {\n parts.push(\n <a key={key} href={safe} target=\"_blank\" rel=\"noopener noreferrer\">\n {link[1]}\n </a>,\n );\n } else {\n parts.push(token);\n }\n } else {\n parts.push(token);\n }\n last = match.index + token.length;\n }\n if (last < text.length) parts.push(text.slice(last));\n return parts;\n}\n\n/** Bold, italic, code, and simple lists. Links render only for citation URLs. HTML stays text. */\nexport function renderAnswerMarkdown(text: string, allowedUrls: ReadonlySet<string>): ReactNode {\n const lines = text.split(\"\\n\");\n const nodes: ReactNode[] = [];\n let paragraph: string[] = [];\n let list: string[] = [];\n let key = 0;\n\n function flushParagraph() {\n if (!paragraph.length) return;\n const content = paragraph.join(\"\\n\");\n nodes.push(<p key={`p-${key++}`}>{inlineMarkup(content, allowedUrls, `p-${key}`)}</p>);\n paragraph = [];\n }\n\n function flushList() {\n if (!list.length) return;\n const items = list;\n nodes.push(\n <ul key={`ul-${key++}`}>\n {items.map((item, index) => (\n <li key={`li-${key}-${index}`}>{inlineMarkup(item, allowedUrls, `li-${key}-${index}`)}</li>\n ))}\n </ul>,\n );\n list = [];\n }\n\n for (const line of lines) {\n const item = /^\\s*[-*]\\s+(.*)$/.exec(line);\n if (item) {\n flushParagraph();\n list.push(item[1] ?? \"\");\n continue;\n }\n if (!line.trim()) {\n flushParagraph();\n flushList();\n continue;\n }\n flushList();\n paragraph.push(line);\n }\n flushParagraph();\n flushList();\n return nodes;\n}\n","const SESSION_ID = /^ans_[0-9a-f]{24}$/;\n\nexport function sessionStorageKey(appId?: string): string {\n const scope = appId?.trim() ? appId.trim() : \"local\";\n return `bsw_an_session:${scope}`;\n}\n\nexport function createOpaqueSessionId(): string {\n const bytes = new Uint8Array(12);\n globalThis.crypto.getRandomValues(bytes);\n let hex = \"\";\n for (const byte of bytes) hex += byte.toString(16).padStart(2, \"0\");\n return `ans_${hex}`;\n}\n\n/** Reads the visitor session from sessionStorage. Never writes a cookie. */\nexport function readSessionId(storage: Storage | null, appId?: string): string {\n if (!storage) return createOpaqueSessionId();\n const key = sessionStorageKey(appId);\n const existing = storage.getItem(key);\n if (existing && SESSION_ID.test(existing)) return existing;\n const id = createOpaqueSessionId();\n storage.setItem(key, id);\n return id;\n}\n","import { createRoot, type Root } from \"react-dom/client\";\nimport { Answers } from \"./Answers\";\nimport type { AnswersProps } from \"./types\";\n\nconst roots = new WeakMap<Element, Root>();\n\nexport function mount(el: Element, props: AnswersProps = {}): () => void {\n let root = roots.get(el);\n if (!root) {\n root = createRoot(el);\n roots.set(el, root);\n }\n root.render(<Answers {...props} />);\n return () => unmount(el);\n}\n\nexport function unmount(el: Element): void {\n const root = roots.get(el);\n if (!root) return;\n root.unmount();\n roots.delete(el);\n}\n"],"mappings":"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","names":["useEffect","useState","jsx","jsx","jsxs","useState","useEffect","jsx"]}
@@ -0,0 +1,313 @@
1
+ type AnswersCitation = {
2
+ id: string;
3
+ title: string;
4
+ url?: string;
5
+ excerpt?: string;
6
+ };
7
+ type AnswersPageContext = {
8
+ url?: string;
9
+ title?: string;
10
+ };
11
+ type AnswersAskInput = {
12
+ sessionId: string;
13
+ question: string;
14
+ pageContext?: AnswersPageContext;
15
+ };
16
+ type AnswersResult = {
17
+ sessionId: string;
18
+ answerId: string;
19
+ outcome: "answered" | "unanswered";
20
+ answer?: string;
21
+ citations?: AnswersCitation[];
22
+ fallback?: string;
23
+ };
24
+ type AnswersFeedbackInput = {
25
+ sessionId: string;
26
+ answerId: string;
27
+ helpful: boolean;
28
+ reason?: string;
29
+ };
30
+
31
+ /** Minimal OpenAI client over fetch. Chat Completions with JSON schema output, and embeddings. */
32
+ declare const DEFAULT_ANSWER_MODEL = "gpt-6-luna";
33
+ declare const FALLBACK_ANSWER_MODEL = "gpt-4.1-mini";
34
+ declare const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
35
+ declare const EMBEDDING_DIMENSIONS = 1536;
36
+ type ReasoningEffort = "none" | "low" | "medium" | "high";
37
+ type ChatMessage = {
38
+ role: "system" | "user" | "assistant";
39
+ content: string;
40
+ };
41
+ type LlmClient = {
42
+ readonly model: string;
43
+ /** JSON object that matches the schema, or throws. */
44
+ json<T>(messages: ChatMessage[], schema: {
45
+ name: string;
46
+ schema: Record<string, unknown>;
47
+ }, options?: {
48
+ maxTokens?: number;
49
+ }): Promise<T>;
50
+ embed(texts: string[]): Promise<number[][]>;
51
+ };
52
+ type OpenAiOptions = {
53
+ apiKey: string;
54
+ model?: string;
55
+ fallbackModel?: string | null;
56
+ embeddingModel?: string;
57
+ reasoningEffort?: ReasoningEffort;
58
+ baseUrl?: string;
59
+ fetch?: typeof fetch;
60
+ timeoutMs?: number;
61
+ };
62
+ declare class LlmError extends Error {
63
+ readonly status?: number | undefined;
64
+ readonly retryable: boolean;
65
+ constructor(message: string, status?: number | undefined, retryable?: boolean);
66
+ }
67
+ /** Reasoning-family models (gpt-5*, gpt-6*, o-series) take reasoning_effort and max_completion_tokens. */
68
+ declare function isReasoningModel(model: string): boolean;
69
+ declare function createOpenAiClient(options: OpenAiOptions): LlmClient;
70
+
71
+ /** One retrievable passage: a page chunk or a custom Q&A entry. */
72
+ type Passage = {
73
+ id: string;
74
+ title: string;
75
+ url?: string;
76
+ heading?: string;
77
+ text: string;
78
+ kind?: "page" | "qa";
79
+ };
80
+ type Turn = {
81
+ question: string;
82
+ answer?: string;
83
+ };
84
+ type RetrieveQuery = {
85
+ text: string;
86
+ original: string;
87
+ embedding: number[] | null;
88
+ limit: number;
89
+ };
90
+ type Retriever = (query: RetrieveQuery) => Promise<Passage[]>;
91
+ type AnswerCitation = {
92
+ id: string;
93
+ title: string;
94
+ url?: string;
95
+ excerpt?: string;
96
+ };
97
+ type AnswerReason = "answered" | "offtopic" | "injection" | "greeting" | "no_sources" | "not_in_sources" | "unverified" | "error";
98
+ type AnswerOutput = {
99
+ outcome: "answered" | "unanswered";
100
+ answer?: string;
101
+ citations: AnswerCitation[];
102
+ reason: AnswerReason;
103
+ /** Standalone search query the question was rewritten to. */
104
+ query: string;
105
+ language: string;
106
+ model: string;
107
+ /** Why the last draft was rejected, when reason is "unverified". */
108
+ problem?: string;
109
+ };
110
+ type AnswerInput = {
111
+ question: string;
112
+ history?: Turn[];
113
+ siteName?: string;
114
+ /** Language of the indexed content (BCP 47 primary tag). Default "en". */
115
+ siteLanguage?: string;
116
+ pageContext?: {
117
+ url?: string;
118
+ title?: string;
119
+ };
120
+ /** Allow quoting code exactly as written in the sources. New code is never written. */
121
+ allowCodeExcerpts?: boolean;
122
+ };
123
+ type AnswerDeps = {
124
+ llm: LlmClient;
125
+ retrieve: Retriever;
126
+ /** Passages handed to the model. Default 10. */
127
+ passages?: number;
128
+ /** Max citations returned. Default 3. */
129
+ maxCitations?: number;
130
+ };
131
+ type Prepared = {
132
+ intent: "site" | "offtopic" | "injection" | "greeting";
133
+ query: string;
134
+ language: string;
135
+ };
136
+ declare function prepareMessages(input: AnswerInput): ChatMessage[];
137
+ declare function sourceBlock(passages: Passage[]): string;
138
+ declare function answerMessages(input: AnswerInput, prepared: Prepared, passages: Passage[], correction?: string): ChatMessage[];
139
+ /** Figures (money, percents, decimals, 2+ digit numbers) the answer states. */
140
+ declare function figuresIn(text: string): string[];
141
+ type Verification = {
142
+ ok: true;
143
+ cited: number[];
144
+ } | {
145
+ ok: false;
146
+ problem: string;
147
+ };
148
+ /** Post-check: citations exist, every figure appears in a provided source, code is quoted not invented. */
149
+ declare function verifyAnswer(answer: string, cited: number[], passages: Passage[], question: string, options?: {
150
+ allowCode?: boolean;
151
+ }): Verification;
152
+ declare function citationsFor(cited: number[], passages: Passage[], answer: string, max: number): AnswerCitation[];
153
+ /**
154
+ * Grounded answer pipeline: route + rewrite, hybrid retrieval, cited synthesis, then a figure and
155
+ * citation check. Two model calls and one embedding per question.
156
+ */
157
+ /** A rejected API key is a configuration problem for the site owner, not a visitor-facing decline. */
158
+ declare function authFailure(error: unknown): boolean;
159
+ declare function answerQuestion(input: AnswerInput, deps: AnswerDeps): Promise<AnswerOutput>;
160
+
161
+ type IndexedEntry = Passage & {
162
+ content: string;
163
+ embedding?: number[];
164
+ };
165
+ type MemoryIndexSnapshot = {
166
+ version: 1;
167
+ entries: IndexedEntry[];
168
+ };
169
+ /** `id` (optional) becomes the citation id: the first chunk uses it as is, later chunks add "#2", "#3"... */
170
+ type DocumentInput = {
171
+ id?: string;
172
+ url: string;
173
+ html: string;
174
+ title?: string;
175
+ } | {
176
+ id?: string;
177
+ url?: string;
178
+ text: string;
179
+ title?: string;
180
+ };
181
+ type QaInput = {
182
+ id?: string;
183
+ question: string;
184
+ answer: string;
185
+ url?: string;
186
+ };
187
+ /**
188
+ * In-process index for BYO: section chunks, BM25, and embeddings fused with RRF.
189
+ * Snapshot with toJSON() to avoid re-embedding on restart.
190
+ */
191
+ declare class MemoryIndex {
192
+ private entries;
193
+ private postings;
194
+ private lengths;
195
+ static fromJSON(snapshot: MemoryIndexSnapshot): MemoryIndex;
196
+ toJSON(): MemoryIndexSnapshot;
197
+ get size(): number;
198
+ private push;
199
+ /** Swap in another index's entries (used after a background rebuild). */
200
+ replaceWith(other: MemoryIndex): void;
201
+ clear(): void;
202
+ /** Chunk documents and Q&A, embed them in batches, and add them. */
203
+ add(llm: Pick<LlmClient, "embed"> | null, docs: DocumentInput[], qa?: QaInput[]): Promise<number>;
204
+ lexical(text: string, limit: number): number[];
205
+ vector(embedding: number[], limit: number): number[];
206
+ search(query: RetrieveQuery): Promise<Passage[]>;
207
+ }
208
+
209
+ type AnswersBackendOptions = {
210
+ /** Server-side only. Never ship this to the browser. */
211
+ openaiApiKey?: string;
212
+ model?: string;
213
+ reasoningEffort?: ReasoningEffort;
214
+ /** Name used in the assistant's instructions, e.g. "Acme". */
215
+ siteName: string;
216
+ siteLanguage?: string;
217
+ /** Public pages to fetch and index on refresh(). */
218
+ urls?: string[];
219
+ /** A sitemap.xml whose <loc> pages are fetched on refresh(). Same-origin pages only. */
220
+ sitemap?: string;
221
+ /** Pages or Markdown you already have. */
222
+ documents?: DocumentInput[];
223
+ /** Custom Q&A. Preferred over page text when they conflict. */
224
+ qa?: QaInput[];
225
+ /** Restore a saved index instead of re-embedding. */
226
+ snapshot?: MemoryIndexSnapshot;
227
+ /** Replace the in-memory index with your own retrieval (e.g. pgvector). */
228
+ retrieve?: Retriever;
229
+ /** Unanswered fallback text shown by the widget. */
230
+ fallback?: string;
231
+ maxPages?: number;
232
+ llm?: LlmClient;
233
+ fetch?: typeof fetch;
234
+ onAnswer?: (event: {
235
+ input: AnswersAskInput;
236
+ result: AnswersResult;
237
+ detail: AnswerOutput;
238
+ }) => void | Promise<void>;
239
+ onFeedback?: (input: AnswersFeedbackInput) => void | Promise<void>;
240
+ };
241
+ type AnswersBackend = {
242
+ ask(input: AnswersAskInput): Promise<AnswersResult>;
243
+ feedback(input: AnswersFeedbackInput): Promise<void>;
244
+ /** Fetch urls/sitemap and (re)build the in-memory index. */
245
+ refresh(): Promise<{
246
+ pages: number;
247
+ chunks: number;
248
+ }>;
249
+ /** Route handler for POST JSON ask and feedback bodies. */
250
+ handler(request: Request): Promise<Response>;
251
+ index: MemoryIndex;
252
+ };
253
+ /**
254
+ * BYO Answers backend. Runs on your server with your OpenAI key; questions and knowledge stay on
255
+ * your infrastructure. Pair with createByoAdapter in the browser.
256
+ */
257
+ declare function createAnswersBackend(options: AnswersBackendOptions): AnswersBackend;
258
+
259
+ /** Server-side page extraction. Pure TS, no DOM. Tolerant of real-world HTML. */
260
+ type PageSection = {
261
+ /** Heading trail, outermost first. Empty for text before the first heading. */
262
+ headings: string[];
263
+ text: string;
264
+ };
265
+ type ExtractedPage = {
266
+ url: string;
267
+ title: string;
268
+ lang?: string;
269
+ description?: string;
270
+ sections: PageSection[];
271
+ };
272
+ declare function decodeEntities(text: string): string;
273
+ /** Cloudflare email obfuscation: hex string, first byte is the XOR key. */
274
+ declare function decodeCfEmail(hex: string): string | null;
275
+ /** Replace Cloudflare-protected email spans and links with the plain address. */
276
+ declare function decodeProtectedEmails(html: string): string;
277
+ /**
278
+ * Extract readable sections from HTML. Drops chrome (nav, header, footer, aside, forms, scripts),
279
+ * decodes protected emails, and splits on h1–h4 so each section keeps its heading trail.
280
+ */
281
+ declare function extractPage(html: string, url: string): ExtractedPage;
282
+ /** Plain text (Markdown or prose) as one page. Markdown headings become sections. */
283
+ declare function extractText(text: string, url: string, title?: string): ExtractedPage;
284
+
285
+ type Chunk = {
286
+ url: string;
287
+ title: string;
288
+ heading: string;
289
+ /** Body text without the context prefix. */
290
+ text: string;
291
+ /** Prefix + text. This is what gets embedded and searched. */
292
+ content: string;
293
+ };
294
+ declare const CHUNK_TARGET_CHARS = 1400;
295
+ declare const CHUNK_MAX_CHARS = 2200;
296
+ /**
297
+ * Section-aware chunks. Small neighbouring sections under the same top heading are merged up to
298
+ * the target size. Each chunk carries a "Title > Heading" prefix so retrieval sees context.
299
+ */
300
+ declare function chunkPage(page: ExtractedPage, options?: {
301
+ target?: number;
302
+ max?: number;
303
+ }): Chunk[];
304
+
305
+ /** Reciprocal rank fusion over ranked id lists. */
306
+ declare function reciprocalRankFusion<T extends string>(lists: T[][], k?: number, weights?: number[]): {
307
+ id: T;
308
+ score: number;
309
+ }[];
310
+ declare function cosine(a: ArrayLike<number>, b: ArrayLike<number>): number;
311
+ declare function tokenize(text: string): string[];
312
+
313
+ export { type AnswerCitation, type AnswerDeps, type AnswerInput, type AnswerOutput, type AnswerReason, type AnswersBackend, type AnswersBackendOptions, CHUNK_MAX_CHARS, CHUNK_TARGET_CHARS, type ChatMessage, type Chunk, DEFAULT_ANSWER_MODEL, DEFAULT_EMBEDDING_MODEL, type DocumentInput, EMBEDDING_DIMENSIONS, type ExtractedPage, FALLBACK_ANSWER_MODEL, type IndexedEntry, type LlmClient, LlmError, MemoryIndex, type MemoryIndexSnapshot, type OpenAiOptions, type PageSection, type Passage, type QaInput, type ReasoningEffort, type RetrieveQuery, type Retriever, type Turn, answerMessages, answerQuestion, authFailure, chunkPage, citationsFor, cosine, createAnswersBackend, createOpenAiClient, decodeCfEmail, decodeEntities, decodeProtectedEmails, extractPage, extractText, figuresIn, isReasoningModel, prepareMessages, reciprocalRankFusion, sourceBlock, tokenize, verifyAnswer };