@bootstrapware/answers 0.2.13 → 0.2.14

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/AGENTS.md CHANGED
@@ -18,7 +18,7 @@ Stack discount is company-wide: 25% off the second paid product's list, 35% off
18
18
 
19
19
  Publishable env: `NEXT_PUBLIC_BSW_ANSWERS_PUBLISHABLE_KEY`.
20
20
 
21
- The React package does not call Hosted ask by itself. Passing `publishableKey` without `adapter` shows "This page is not ready to answer yet." and does not fetch.
21
+ Pass `appId` and `publishableKey` with no `adapter` and the React component and the script embed call Hosted ask. `createHostedAdapter({ appId, publishableKey })` is that client. A publishable key without an app id does not fetch.
22
22
 
23
23
  ## Install algorithm (do this in order)
24
24
 
@@ -53,7 +53,7 @@ Try these questions, in order, on one session:
53
53
  2. `list_answers_capabilities` → `create_answers_app` → `update_answers_draft` (toggles + `allowedOrigins` for localhost and production; `name` is optional) → `publish_answers_app`
54
54
  3. `ensure_answers_test_publishable`. Put `envLine` in `.env.local`. The full test publishable is returned every time. The env name is `NEXT_PUBLIC_BSW_ANSWERS_PUBLISHABLE_KEY`.
55
55
  4. `get_answers_install_snippet`. `appId` is real. Use `process.env.NEXT_PUBLIC_BSW_ANSWERS_PUBLISHABLE_KEY`. Never invent a key and never put a secret in the snippet.
56
- 5. Keep the adapter in the snippet. It posts to `POST /api/v1/ask?appId=`. Omitting `adapter` does not call that route.
56
+ 5. The snippet passes `appId` and `publishableKey`. That posts to `POST /api/v1/ask?appId=`.
57
57
  6. `add_answers_url_source` for each dedicated public page whose body already states the fact (`/pricing`, hours, returns). Do not stop at the homepage.
58
58
  7. After the index job succeeds, tell the human to ask through the widget with a test key: a product question, a follow-up that names no product, and a question the pages do not answer. The dashboard retrieval check shows which passages a question finds.
59
59
 
@@ -62,42 +62,31 @@ Try these questions, in order, on one session:
62
62
  1. Call `update_answers_draft` with `allowedOrigins` for localhost and production. Name is optional.
63
63
  2. Call `publish_answers_app`.
64
64
  3. Call `ensure_answers_test_publishable` to get or create a test publishable key.
65
- 4. Call `get_answers_install_snippet`. The React package does not call Hosted ask unless you pass an adapter.
65
+ 4. Call `get_answers_install_snippet`. `appId` and `publishableKey` call Hosted ask. No hand-written adapter.
66
66
  5. Call `add_answers_url_source` with a dedicated public page whose body already states the fact. A homepage of product blurbs is a weak first source.
67
67
  6. After the index job succeeds, ask through the widget with a test key: a product question, a follow-up that names no product, and a question the pages do not answer. The dashboard retrieval check shows which passages a question finds.
68
68
 
69
69
  `publish_answers_app` returns:
70
70
 
71
71
  1. Call `ensure_answers_test_publishable` to get or create a test publishable key.
72
- 2. Call `get_answers_install_snippet` and point the adapter at `POST /api/v1/ask`.
72
+ 2. Call `get_answers_install_snippet`. `appId` and `publishableKey` post to `POST /api/v1/ask`.
73
73
  3. Call `add_answers_url_source` with a dedicated public page whose body already states the fact. A homepage of product blurbs is a weak first source.
74
74
  4. After the index job succeeds, ask through the widget with a test key: a product question, a follow-up that names no product, and a question the pages do not answer. The dashboard retrieval check shows which passages a question finds.
75
75
 
76
76
  ```tsx
77
- import { Answers, createByoAdapter } from "@bootstrapware/answers";
77
+ import { Answers } from "@bootstrapware/answers";
78
78
  import "@bootstrapware/answers/styles.css";
79
79
 
80
80
  <Answers
81
81
  appId="ana_demo"
82
82
  publishableKey={process.env.NEXT_PUBLIC_BSW_ANSWERS_PUBLISHABLE_KEY}
83
- adapter={createByoAdapter({
84
- ask: async (input) => {
85
- const response = await fetch("https://answers.bootstrapware.co/api/v1/ask?appId=ana_demo", {
86
- method: "POST",
87
- headers: {
88
- Authorization: `Bearer ${process.env.NEXT_PUBLIC_BSW_ANSWERS_PUBLISHABLE_KEY}`,
89
- "Content-Type": "application/json",
90
- },
91
- body: JSON.stringify(input),
92
- });
93
- const json = await response.json();
94
- if (!response.ok) throw new Error(json?.error?.message ?? "Could not ask.");
95
- return json.data;
96
- },
97
- })}
83
+ suggestedQuestions={["What is your return window?"]}
84
+ shortcut="mod+k"
98
85
  />
99
86
  ```
100
87
 
88
+ `suggestedQuestions` renders clickable starter chips. `shortcut="mod+k"` opens the widget and focuses the question (Command on macOS, Control elsewhere). The same props work on `BootstrapwareAnswers.mount`. `createHostedAdapter({ appId, publishableKey, apiBaseUrl })` posts to `POST /api/v1/ask?appId=` when you want the client as an `adapter`. Optional `apiBaseUrl` defaults to `https://answers.bootstrapware.co`.
89
+
101
90
  `get_answers_install_snippet` substitutes the published `ana_` id. Optional API host in the snippet defaults to `https://answers.bootstrapware.co`.
102
91
 
103
92
  The ask body is `{ sessionId, question, pageContext }`. `pageContext` is `{ url, title }` only. The widget does not send the DOM. Success is `{ data }` with `outcome` `answered` or `unanswered`. An answered result includes `answer` and citations whose ids came from retrieval. An unanswered result includes `fallback` and no answer text. There is no public confidence percentage. The response is complete. It does not stream.
@@ -188,7 +177,7 @@ Draft defaults: `assistantLabel` "Ask", `greeting` "Ask a question this site alr
188
177
  ## Props
189
178
 
190
179
  - `appId`: published app id (`ana_`).
191
- - `publishableKey`: browser publishable key. Unused for fetch unless your adapter uses it.
180
+ - `publishableKey`: browser publishable key. With `appId` and no `adapter`, the widget calls Hosted ask.
192
181
  - `adapter`: `ask`, and optional `feedback`.
193
182
  - `presentation`: `launcher` (default), `section`, `modal`, or `window`.
194
183
  - `defaultOpen`: modal, window, and launcher start closed unless this is set.
@@ -196,6 +185,9 @@ Draft defaults: `assistantLabel` "Ask", `greeting` "Ask a question this site alr
196
185
  - `pageContext`: `{ url, title }`. Omit it and the widget sends `location.href` and `document.title`.
197
186
  - `greeting`: empty-state copy. Omit it and the widget shows "Ask a question this site already answers."
198
187
  - `onFallback`: called with the sanitized unanswered result.
188
+ - `suggestedQuestions`: starter questions shown as chips. A click asks that question.
189
+ - `shortcut`: for example `mod+k`. Opens the widget and focuses the question field. `mod` is Command on macOS and Control elsewhere.
190
+ - `apiBaseUrl`: Hosted origin when `appId` and `publishableKey` auto-wire the client. Defaults to `https://answers.bootstrapware.co`.
199
191
  - `className`: layout only.
200
192
 
201
193
  Escape closes modal, window, and launcher. The backdrop closes modal and launcher. Nudge the corner with `--bsw-an-frame-launcher-right` and `--bsw-an-frame-launcher-bottom`.
@@ -220,7 +212,7 @@ Questions are limited to 1,000 Unicode code points. A longer draft stays in the
220
212
  </script>
221
213
  ```
222
214
 
223
- `mount` returns an unmount function. Calling `mount` again on the same element updates props. `BootstrapwareAnswers.unmount(el)` removes it. The script is the same widget. Without an adapter, it does not call Hosted ask. Pass an adapter function from the page script when you want Path B or Path C.
215
+ `mount` returns an unmount function. Calling `mount` again on the same element updates props. `BootstrapwareAnswers.unmount(el)` removes it. The script is the same widget. Pass `appId` and `publishableKey` and it calls Hosted ask. `suggestedQuestions` and `shortcut` work the same as in React. Pass an adapter function from the page script for BYO.
224
216
 
225
217
  ## Publish pages that can be quoted
226
218
 
@@ -238,7 +230,7 @@ Hosted knowledge kinds are website root, sitemap, single public URL, PDF, DOCX,
238
230
 
239
231
  Every Hosted source is visitor-readable. Do not upload private runbooks or secrets. Crawl is public `http` and `https` only. Private hosts are rejected. `robots.txt` is honored. A failed crawl leaves the last good index in place. A page that is only navigation chrome is rejected and does not become a citation.
240
232
 
241
- Retrieval combines keyword and embedding search over the active index. When the question names a product whose pages live under a URL path, those pages are preferred, and passages on that path that mention the distinctive terms in the question are reserved for the model. Custom Q&A is always considered, and a matching entry is answered first. The model answers in 1 to 4 sentences in the visitor's language, citing passages it was given; citations are ids from the retrieved index, and the model does not invent URLs. A draft that declines, cites an unretrieved passage, states a figure that is not in a cited passage and does not follow from cited figures by the rules above, or writes code not quoted from a passage is retried once and then returns `unanswered` with the configured fallback. A derived total that depends on billing order is rejected unless the visitor set the order, the answer says order matters, or the answer itemizes list and discounted line items that sum to the total. Off-topic questions and prompt-injection attempts are declined before retrieval. How-to questions get the documented steps and a citation to the docs page; the model does not write new code. There is no model-memory fallback. Earlier turns are used only for clear follow-ups ("what about Chat?"); a standalone question is answered fresh.
233
+ Retrieval combines keyword and embedding search over the active index. When the question names a product whose pages live under a URL path, those pages are preferred, and passages on that path that mention the distinctive terms in the question are reserved for the model. Custom Q&A is always considered, and a matching entry is answered first. The model answers in 1 to 4 sentences in the visitor's language, citing passages it was given; citations are ids from the retrieved index, and the model does not invent URLs. A draft that declines, cites an unretrieved passage, states a figure that is not in a cited passage and does not follow from cited figures by the rules above, or writes code not quoted from a passage is retried once and then returns `unanswered` with the configured fallback. A derived total that depends on billing order is rejected unless the visitor set the order, the answer says order matters, or the answer itemizes list and discounted line items that sum to the total. That sum is checked even when the answer caveats order. A stated range must span every order total for the products the question names, and a total given for one of those products being first must be the total with that product first and the others in the order the sources list them. Equal list prices are not treated as order-dependent. A reply whose language does not match the question is retried once. A file the question names, such as `/llms.txt`, and a compare page for a competitor the question names stay in the retrieved context. Off-topic questions and prompt-injection attempts are declined before retrieval. How-to questions get the documented steps and a citation to the docs page; the model does not write new code. There is no model-memory fallback. Earlier turns are used only for clear follow-ups ("what about Chat?"); a standalone question is answered fresh.
242
234
 
243
235
  Unanswered observations store normalized question text even when conversation history is off, so the dashboard can group them. MCP does not return that text.
244
236
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.14
4
+
5
+ - Stack totals are checked for the products the question names. An itemized sum must add up even when the answer says order matters. A stated range must cover the lowest and highest order total. "If X is first, $T" must be the total with X first and the other named products in the order the sources list them. Two products with the same list price are not rejected as order-dependent.
6
+ - The reply language is checked against the question (script and stopwords). A mismatch is retried once.
7
+ - A root file the question names (`llms.txt`) and a compare page for a named competitor stay in the retrieved context on Hosted and BYO.
8
+ - Trailing `Sources:`, `Quellen:`, `Fuentes:`, `Fonti:`, `Fontes:`, and `[Q&A]` labels are removed, including a label left behind as `Sources:,.` after the citation markers are stripped. A citation marker used as the sentence subject does not leave a broken sentence. The prompt tells the model not to use source numbers as words.
9
+ - `createHostedAdapter({ appId, publishableKey })`. Passing `appId` and `publishableKey` without `adapter` calls Hosted ask from the React component and the script embed. `suggestedQuestions` renders starter chips. `shortcut` (for example `mod+k`) opens the widget and focuses the question.
10
+
3
11
  ## 0.2.13
4
12
 
5
13
  - Grounding treats a thousands group as one figure. `2,500`, `2.500`, `2 500` (including a narrow no-break space), and `2'500` are the same number, and `19,99` is the same decimal as `19.99`. A one-digit group no longer matches a different grouped number that happens to end in the same digits (`1,500` is not `2,500`).
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Visitors ask a question. The widget shows a cited answer, or an explicit fallback when nothing matches.
4
4
 
5
- **Local mode is free.** **BYO is $9.99 / month** (you store knowledge and answers). **Hosted is $19.99 / month.** Hosted stores approved sources, the derived index, visitor questions, optional conversations, unanswered items, and citations. Hosted includes 2,500 answers per workspace each month, 1 GiB of knowledge and index, 2,000 sources, 4,000 indexed chunks per app, 20 MiB per file, and 30 days of conversation history. Test keys are free. The React package does not call Hosted ask by itself. Pass `createLocalAdapter()` or your own adapter.
5
+ **Local mode is free.** **BYO is $9.99 / month** (you store knowledge and answers). **Hosted is $19.99 / month.** Hosted stores approved sources, the derived index, visitor questions, optional conversations, unanswered items, and citations. Hosted includes 2,500 answers per workspace each month, 1 GiB of knowledge and index, 2,000 sources, 4,000 indexed chunks per app, 20 MiB per file, and 30 days of conversation history. Test keys are free. Pass `appId` and `publishableKey` and the widget calls Hosted ask. Pass `createLocalAdapter()` for the sample, or your own adapter for BYO.
6
6
 
7
7
  ```bash
8
8
  pnpm add @bootstrapware/answers
@@ -37,7 +37,34 @@ The local adapter is a deterministic sample for Northwind Outfitters (hours, ret
37
37
  </script>
38
38
  ```
39
39
 
40
- The hosted script is `GET https://answers.bootstrapware.co/embed/v1.js`.
40
+ The hosted script is `GET https://answers.bootstrapware.co/embed/v1.js`. Pass `appId` and `publishableKey` and it calls Hosted ask. `suggestedQuestions` renders starter chips. `shortcut="mod+k"` opens the widget and focuses the question (Command on macOS, Control elsewhere).
41
+
42
+ ```html
43
+ <script>
44
+ BootstrapwareAnswers.mount(document.getElementById("answers"), {
45
+ appId: "ana_…",
46
+ publishableKey: publishableKey,
47
+ suggestedQuestions: ["What is your return window?"],
48
+ shortcut: "mod+k",
49
+ });
50
+ </script>
51
+ ```
52
+
53
+ ## Hosted
54
+
55
+ ```tsx
56
+ import { Answers } from "@bootstrapware/answers";
57
+ import "@bootstrapware/answers/styles.css";
58
+
59
+ <Answers
60
+ appId="ana_…"
61
+ publishableKey={process.env.NEXT_PUBLIC_BSW_ANSWERS_PUBLISHABLE_KEY}
62
+ suggestedQuestions={["What is your return window?"]}
63
+ shortcut="mod+k"
64
+ />
65
+ ```
66
+
67
+ That posts to `POST https://answers.bootstrapware.co/api/v1/ask?appId=`. `createHostedAdapter({ appId, publishableKey, apiBaseUrl })` is the same client when you want to pass `adapter` yourself. Optional `apiBaseUrl` defaults to `https://answers.bootstrapware.co`.
41
68
 
42
69
  ## BYO
43
70
 
@@ -59,7 +86,7 @@ export const POST = (request: Request) => answers.handler(request);
59
86
 
60
87
  The index is built on the first question and kept in memory. Call `answers.refresh()` after your content changes (it returns `{ pages, chunks, skipped }`; `skipped` lists every page left out and why, including a URL that redirects to a different page, and `onRefresh` receives the same report after the automatic first build), or save `answers.index.toJSON()` and pass it back as `snapshot` to skip re-embedding on restart. Pass `retrieve` to use your own vector store instead.
61
88
 
62
- Answers come only from the indexed passages. `refresh()` also fetches `/llms.txt` and `/llms-full.txt` for each origin in `urls` or the sitemap when the host publishes them as text or Markdown, and ignores a missing file. A draft that cites something it did not retrieve, or states a price or number that is not in a cited passage and does not follow from cited figures (a single-step percent-off or percent-of of a cited amount, rounded to the nearest cent, or a sequential stack total with each line rounded to the nearest cent), is rejected and the visitor gets the fallback. The same number with a comma, dot, space, or apostrophe as the thousands separator counts as the figure in the source, and a comma decimal such as `19,99` counts as `$19.99`. A stack total that depends on billing order is rejected unless the visitor set the order, the answer says order matters, or the answer itemizes lines that sum to that total. Custom Q&A is always considered, and a matching entry is answered first. When the sources are silent on a yes/no question, the answer says the site does not state it (with the support contact if a source has one). How-to questions cite the docs page; no new code is written.
89
+ Answers come only from the indexed passages. `refresh()` also fetches `/llms.txt` and `/llms-full.txt` for each origin in `urls` or the sitemap when the host publishes them as text or Markdown, and ignores a missing file. A draft that cites something it did not retrieve, or states a price or number that is not in a cited passage and does not follow from cited figures (a single-step percent-off or percent-of of a cited amount, rounded to the nearest cent, or a sequential stack total with each line rounded to the nearest cent), is rejected and the visitor gets the fallback. The same number with a comma, dot, space, or apostrophe as the thousands separator counts as the figure in the source, and a comma decimal such as `19,99` counts as `$19.99`. A stack total that depends on billing order is rejected unless the visitor set the order, the answer says order matters, or the answer itemizes lines that sum to that total. The itemized sum is checked even when the answer caveats order. A stated range must span every order total for the products asked, and "if this product is first, $T" must be the total with that product first and the others in source order. Equal list prices are not treated as order-dependent. A reply in a different language than the question is retried once. Custom Q&A is always considered, and a matching entry is answered first. When the sources are silent on a yes/no question, the answer says the site does not state it (with the support contact if a source has one). How-to questions cite the docs page; no new code is written.
63
90
 
64
91
  Custom Q&A can change at runtime without a refresh: call `await answers.upsertQa({ id, question, answer })`, `answers.removeQa(id)`, or `answers.setQa([...])` from your save handler. Only the changed entry is embedded and it answers on the next question. Relevant custom Q&A are always offered to the model, even when many page passages outrank them, and that includes setups that pass their own `retrieve`.
65
92