@boldare/askdare 0.0.0-stage → 0.1.0-rc.0
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/CHANGELOG.md +20 -0
- package/LICENSE +21 -0
- package/README.md +65 -2
- package/dist/cli.js +1461 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +193 -0
- package/dist/next.d.ts +86 -0
- package/dist/next.js +2864 -0
- package/dist/store.d.ts +413 -0
- package/dist/version.js +641 -0
- package/docs/attachments.md +41 -0
- package/docs/cli.md +227 -0
- package/docs/configuration.md +357 -0
- package/docs/costs.md +181 -0
- package/docs/custom-store.md +259 -0
- package/docs/deployment.md +76 -0
- package/docs/embedding.md +258 -0
- package/docs/getting-started.md +116 -0
- package/docs/going-public.md +81 -0
- package/docs/identity.md +109 -0
- package/docs/knowledge-base.md +65 -0
- package/docs/migrating-from-openai-assistant.md +43 -0
- package/docs/privacy.md +175 -0
- package/docs/protocol.md +174 -0
- package/docs/styling.md +166 -0
- package/docs/support.md +48 -0
- package/docs/texts.md +176 -0
- package/docs/themes.md +172 -0
- package/docs/tools.md +52 -0
- package/docs/troubleshooting.md +345 -0
- package/docs/voice-input.md +31 -0
- package/llms.txt +32 -0
- package/package.json +98 -3
- package/skills/askdare/SKILL.md +36 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
Each entry starts with what you see, then says why it happens and what to do. askdare's error lines in the server log start with `[askdare]` and end with a pointer to this page.
|
|
4
|
+
|
|
5
|
+
## Find the error first
|
|
6
|
+
|
|
7
|
+
- **Setup problems are explained in plain words** in the server log, in development and in production. In development the chat shows the same explanation when the problem shows up while it answers a message. In production the chat only shows a generic text.
|
|
8
|
+
- **The server log** gets one line for every server error (status 500 and up) and for every error during a streamed answer:
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
[askdare] bot_unavailable: OPENAI_API_KEY is not set. Add it to .env.local (or your host's environment) and restart the app. (see node_modules/@boldare/askdare/docs/troubleshooting.md)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Refusals such as `rate_limited` and `budget_exceeded` aren't logged. If you pass `onError` to `createChatHandler`, errors go to your function instead of the log.
|
|
15
|
+
- **The browser's network tab** shows the requests under `/api/chat/`. A failed request answers with JSON such as `{ "error": { "code": "rate_limited", "message": "…" } }`. A streamed answer reports its error as an `error` event.
|
|
16
|
+
- **The browser console of the page that embeds the chat** shows warnings from the loader, such as a site missing from `allowedOrigins`.
|
|
17
|
+
- **`npx @boldare/askdare doctor`** checks the project, the environment and the running app. For a deployed app, add `--url <your app> --env-file <file with its CHAT_SECRET>` ([Command line](cli.md#doctor)).
|
|
18
|
+
|
|
19
|
+
## Error codes
|
|
20
|
+
|
|
21
|
+
The codes are stable. The texts are the English defaults; the widget shows them in the visitor's language when a translation exists.
|
|
22
|
+
|
|
23
|
+
| Code | HTTP | The chat says | See |
|
|
24
|
+
| --- | --- | --- | --- |
|
|
25
|
+
| `invalid_input` | 400 | "The request was invalid.", or a specific text such as "Messages can be up to {max} characters." | [Attachments are rejected](#attachments-are-rejected) |
|
|
26
|
+
| `unauthorized` | 401 | "Authentication is required." | [Session expired](#session-expired) |
|
|
27
|
+
| `session_expired` | 401 | "Your session has expired. Please try again." | [Session expired](#session-expired) |
|
|
28
|
+
| `not_found` | 404 | "Not found.", or "This conversation is no longer available." | [The conversation is gone](#the-conversation-is-gone) |
|
|
29
|
+
| `content_blocked` | 422 | "This message cannot be processed." | askdare doesn't send this code in this version |
|
|
30
|
+
| `rate_limited` | 429 | "Too many messages. Please wait a moment.", or a specific text | [Too many messages](#too-many-messages) |
|
|
31
|
+
| `budget_exceeded` | 429 | "The assistant is unavailable for the rest of the day." | [Unavailable for the rest of the day](#unavailable-for-the-rest-of-the-day) |
|
|
32
|
+
| `internal_error` | 500 | "Something went wrong. Please try again." | The server log; also [In-memory rate limits](#in-memory-rate-limits) |
|
|
33
|
+
| `upstream_error` | 502 | "The assistant is temporarily unavailable. Please try again." | [OpenAI fails](#openai-fails) |
|
|
34
|
+
| `bot_unavailable` | 503 | "The assistant is currently unavailable." | [The assistant is unavailable](#the-assistant-is-unavailable) |
|
|
35
|
+
| `timeout` | 504 | "The assistant took too long to respond. Please try again." | [Answers are cut off or time out](#answers-are-cut-off-or-time-out) |
|
|
36
|
+
|
|
37
|
+
Some texts come from the widget itself, without a server code:
|
|
38
|
+
|
|
39
|
+
| The chat says | See |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| "Connection problem. Please try again." | [Connection problem](#connection-problem) |
|
|
42
|
+
| "The chat couldn’t load. Please try again later." | [The chat doesn't load](#the-chat-doesnt-load) |
|
|
43
|
+
| "The assistant was updated. Please reload the page." | [The assistant was updated](#the-assistant-was-updated) |
|
|
44
|
+
| "The connection stalled. Please try again." | [Answers are cut off or time out](#answers-are-cut-off-or-time-out) |
|
|
45
|
+
| "Could not load the previous conversation." | [The conversation is gone](#the-conversation-is-gone) |
|
|
46
|
+
|
|
47
|
+
## Model is required
|
|
48
|
+
|
|
49
|
+
**You see** this when the app starts, when `next build` runs, or on every request to `/api/chat`:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
[askdare] Invalid bot "support": model is required. Set the model in the bot definition, usually as model: process.env.ASKDARE_MODEL with ASKDARE_MODEL in .env.local and in every environment that builds or runs the app (on Vercel: Production and Preview).
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Why:** `defineBot` checks the bot as soon as your config is imported, and `next build` imports it too. The config that `init` writes reads the model from `ASKDARE_MODEL`. When the variable is missing or empty, the model is empty and the bot is refused. askdare never picks a model for you.
|
|
56
|
+
|
|
57
|
+
**Fix:**
|
|
58
|
+
1. Set `ASKDARE_MODEL` to a current OpenAI model your project may use, in `.env.local` for development.
|
|
59
|
+
2. Set it in every environment of your host that builds or runs the app. On Vercel that means Production **and** Preview; preview deployments fail to build without it.
|
|
60
|
+
3. Restart `next dev`, or redeploy.
|
|
61
|
+
|
|
62
|
+
`npx @boldare/askdare doctor` reports it as "ASKDARE_MODEL is not set." or "ASKDARE_MODEL is empty."
|
|
63
|
+
|
|
64
|
+
### Other `Invalid bot` messages
|
|
65
|
+
|
|
66
|
+
The same check reports every invalid setting as `Invalid bot "<id>": <setting> <problem>`. The most common ones:
|
|
67
|
+
|
|
68
|
+
| Message | Fix |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| `security.allowedOrigins.0 must be an https origin like https://www.example.com (no path or wildcard; http only for localhost)` | Use the scheme and host only, such as `https://www.example.com`, with no trailing slash |
|
|
71
|
+
| `id use 1-48 lowercase letters, digits or dashes` | Rename the bot, and update `?bot=` in the script tag |
|
|
72
|
+
| `knowledge.vectorStoreId must be a vector store ID like vs_…` | Set `KB_VECTOR_STORE_ID` to the ID that `kb sync` printed |
|
|
73
|
+
| `voice.model is required` | Set a transcription model in `voice` |
|
|
74
|
+
|
|
75
|
+
`createChatHandler` refuses `Duplicate bot id "<id>"`, an empty `bots` list (`Define at least one bot.`) and a `basePath` that doesn't look like `/api/chat`.
|
|
76
|
+
|
|
77
|
+
## The assistant is unavailable
|
|
78
|
+
|
|
79
|
+
**You see** "The assistant is currently unavailable." (`bot_unavailable`) after sending a message. In development, the chat shows the reason instead, and the server log has the same line.
|
|
80
|
+
|
|
81
|
+
**Why**, by the explanation in the log:
|
|
82
|
+
|
|
83
|
+
| Explanation | Cause |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `OPENAI_API_KEY is not set. Add it to .env.local (or your host's environment) and restart the app.` | The key is missing where the app runs |
|
|
86
|
+
| `OpenAI rejected the API key (401). Check OPENAI_API_KEY and the key's project permissions.` (or `403`) | The key is wrong, revoked, or a restricted key lacks a permission |
|
|
87
|
+
| `The bot's model doesn't exist or this OpenAI project can't use it: …` | A typo in the model, a retired model, or a model outside the project's allowlist |
|
|
88
|
+
| No explanation | `ASKDARE_DISABLED` is `true`; see [The kill switch](#the-kill-switch) |
|
|
89
|
+
|
|
90
|
+
**Fix:**
|
|
91
|
+
- Set `OPENAI_API_KEY` in `.env.local` or in your host's settings, then restart the app or redeploy.
|
|
92
|
+
- Run `npx @boldare/askdare doctor`. It tests the key, the model and each permission the key needs, and names the missing one.
|
|
93
|
+
- For a model problem, check the model ID and the project's model allowlist in the OpenAI dashboard.
|
|
94
|
+
|
|
95
|
+
Problems with `CHAT_SECRET` and `IDENTITY_SECRET` are also `bot_unavailable`: they stop the chat from opening a session. In development the chat shows the reason; the table in the next entry lists them.
|
|
96
|
+
|
|
97
|
+
## Connection problem
|
|
98
|
+
|
|
99
|
+
**You see** "Connection problem. Please try again." when sending a message, or "Could not load the previous conversation." when the chat opens.
|
|
100
|
+
|
|
101
|
+
**Why:** the network failed, or the server didn't answer. When the server refuses a session (`POST /api/chat/session`), the chat shows the server's own message instead: in development the reason in plain words, in production the generic text of its code (for example "The assistant is currently unavailable."). The server log always has the reason. Common causes of a refused session:
|
|
102
|
+
|
|
103
|
+
| Server log or session response | Cause |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `CHAT_SECRET is not set. Add it to .env.local (or your host's environment) and restart the app.` | `CHAT_SECRET` is missing where the app runs |
|
|
106
|
+
| `CHAT_SECRET must be at least 32 random characters. Generate one with: node -e "…"` | The secret is shorter than 32 characters, or has fewer than 12 different characters. The same applies to each value in `CHAT_SECRET_PREVIOUS` |
|
|
107
|
+
| `IDENTITY_SECRET must be at least 32 random characters …` or `IDENTITY_SECRET must differ from CHAT_SECRET.` | `IDENTITY_SECRET` is set but weak, or equal to `CHAT_SECRET` |
|
|
108
|
+
| `bot_unavailable` with no explanation | `ASKDARE_DISABLED` is `true` |
|
|
109
|
+
| `rate_limited` (429) | More than `sessionsPerIpPerHour` new sessions from one IP; see [Too many messages](#too-many-messages) |
|
|
110
|
+
| `unauthorized`, "Cross-origin requests are not allowed." | A proxy in front of the app changes the `Host` header, so it no longer matches the `Origin` of the chat's own requests |
|
|
111
|
+
|
|
112
|
+
**Fix:**
|
|
113
|
+
1. Read the chat's message in development, or the server log. The failed `session` request's JSON body has the same explanation.
|
|
114
|
+
2. Generate a secret with `node -e "console.log(require('node:crypto').randomBytes(32).toString('base64url'))"`, set it as `CHAT_SECRET`, and restart the app or redeploy.
|
|
115
|
+
3. Run `npx @boldare/askdare doctor`. It checks the strength of `CHAT_SECRET`, `CHAT_SECRET_PREVIOUS` and `IDENTITY_SECRET`.
|
|
116
|
+
|
|
117
|
+
## The chat doesn't load
|
|
118
|
+
|
|
119
|
+
**You see** an empty panel, then after 8 seconds "The chat couldn’t load. Please try again later." The browser console of the embedding page says:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
[askdare] The chat "support" didn't load on https://www.example.com. Is this origin in the bot's security.allowedOrigins? Check with: npx @boldare/askdare doctor --origin https://www.example.com
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Why:** the chat runs in an iframe served by your app. The app tells browsers which sites may show that iframe (`Content-Security-Policy: frame-ancestors`), from the bot's `security.allowedOrigins` plus the app's own origin. On any other site the browser refuses to show it. Less often:
|
|
126
|
+
- the bot ID in `widget.js?bot=` doesn't exist;
|
|
127
|
+
- something in front of `/api/chat` (`proxy.ts`, `headers()` in `next.config`, a CDN rule) adds `X-Frame-Options` or a second Content Security Policy;
|
|
128
|
+
- the embedding site's own Content Security Policy doesn't allow the app's origin in `frame-src` ([Embedding](embedding.md#your-sites-content-security-policy)).
|
|
129
|
+
|
|
130
|
+
**Fix:**
|
|
131
|
+
1. Add the exact origin to `security.allowedOrigins`: scheme and host, plus the port if it isn't the default. `https://example.com` and `https://www.example.com` are different origins.
|
|
132
|
+
2. Deploy the app.
|
|
133
|
+
3. Check it:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npx @boldare/askdare doctor --url https://your-app.example.com --env-file .env.production.local --origin https://www.example.com
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`doctor` reports "https://www.example.com may embed the chat." or says what to add. It also reports `X-Frame-Options` and a second Content Security Policy. The header checks run with the live checks, so the env file must hold the app's `CHAT_SECRET`.
|
|
140
|
+
|
|
141
|
+
If the console says "Load the widget as widget.js?bot=<bot id>.", the script tag has no bot ID.
|
|
142
|
+
|
|
143
|
+
## Too many messages
|
|
144
|
+
|
|
145
|
+
**You see** "Too many messages. Please wait a moment." (`rate_limited`), usually while testing.
|
|
146
|
+
|
|
147
|
+
**Why:** askdare limits requests per visitor, per client IP and per bot. In development every browser and tab on your machine counts as the same IP, so the per-IP limits come first.
|
|
148
|
+
|
|
149
|
+
| Limit | Default | Counted per |
|
|
150
|
+
| --- | --- | --- |
|
|
151
|
+
| `messagesPerVisitorPerMinute` | 10 | visitor |
|
|
152
|
+
| `messagesPerIpPerMinute` | 30 | IP |
|
|
153
|
+
| `conversationsPerIpPerHour` | 20 | IP |
|
|
154
|
+
| `sessionsPerIpPerHour` | 30 | IP (new visitors) |
|
|
155
|
+
| `uploadsPerVisitorPerHour` | 20 | visitor |
|
|
156
|
+
| `uploadsPerIpPerHour` | 60 | IP |
|
|
157
|
+
| `uploadsPerDay` | 500 | bot |
|
|
158
|
+
| `transcriptionsPerVisitorPerHour` | 30 | visitor |
|
|
159
|
+
| `maxTurns` | 50 | conversation |
|
|
160
|
+
|
|
161
|
+
Every limit is in [Configuration](configuration.md#limits). Some are fixed: 12 session renewals per returning visitor per hour, 20 history loads or deletions per visitor per minute, and 2 dictations per IP and 10 per bot being transcribed at once. Windows start with the first request, so a limit of one hour lasts one hour from then.
|
|
162
|
+
|
|
163
|
+
Two limits have their own text:
|
|
164
|
+
- "This conversation is too long. Please start a new one." means the conversation reached `maxTurns`.
|
|
165
|
+
- "Another dictation is still being transcribed. Please try again in a moment." means too many dictations are being transcribed at once.
|
|
166
|
+
|
|
167
|
+
**Fix:**
|
|
168
|
+
- Wait for the window to pass, or restart `next dev`: the default counters live in the server's memory.
|
|
169
|
+
- Raise the limits in development only:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
const development = process.env.NODE_ENV === 'development';
|
|
173
|
+
|
|
174
|
+
defineBot({
|
|
175
|
+
// …
|
|
176
|
+
limits: development
|
|
177
|
+
? { messagesPerIpPerMinute: 300, conversationsPerIpPerHour: 200, sessionsPerIpPerHour: 300 }
|
|
178
|
+
: undefined,
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
- With `@boldare/askdare-upstash` in development, give it its own database or key prefix, such as `upstashStore({ prefix: 'askdare-dev:' })`, so tests don't use up production counters.
|
|
183
|
+
|
|
184
|
+
### Every visitor is rate-limited
|
|
185
|
+
|
|
186
|
+
**You see** `rate_limited` in production for many visitors at once, while traffic is low.
|
|
187
|
+
|
|
188
|
+
**Why:** askdare reads the client IP from `X-Forwarded-For`, according to `trustProxy`. When it reads the wrong entry, every visitor counts as the same address: a CDN in front of Heroku, for example. Without a proxy that sets the header, the IP is unknown and every visitor shares one counter.
|
|
189
|
+
|
|
190
|
+
**Fix:** set `limits: { trustProxy: <number of proxies> }` in `createChatHandler` (Vercel and a plain Heroku app need nothing), and never expose `next start` directly. See [Deployment](deployment.md#behind-another-proxy-or-cdn).
|
|
191
|
+
|
|
192
|
+
## Unavailable for the rest of the day
|
|
193
|
+
|
|
194
|
+
**You see** "The assistant is unavailable for the rest of the day." (`budget_exceeded`).
|
|
195
|
+
|
|
196
|
+
**Why:** a daily budget is used up. Before each message, askdare reserves its expected tokens against every budget; when one would go over, the message is refused. The budgets reset at 00:00 UTC, and the response's `Retry-After` header counts down to then.
|
|
197
|
+
|
|
198
|
+
| Budget | Default | Where |
|
|
199
|
+
| --- | --- | --- |
|
|
200
|
+
| `dailyTokens` | 2,000,000 tokens per bot | `limits` in `defineBot` |
|
|
201
|
+
| `dailyTokensPerIp` | 200,000 tokens per IP and bot | `limits` in `defineBot` |
|
|
202
|
+
| `globalDailyTokens` | unlimited | `limits` in `createChatHandler` |
|
|
203
|
+
| `voiceSecondsPerDay` | 3,600 seconds per bot | `limits` in `defineBot` |
|
|
204
|
+
| `voiceSecondsPerIpPerDay` | 600 seconds per IP and bot | `limits` in `defineBot` |
|
|
205
|
+
|
|
206
|
+
A dictation that hits a speech budget says "Dictation is unavailable for the rest of the day. You can still type." Typing still works.
|
|
207
|
+
|
|
208
|
+
**It happens right after attaching a PDF:** each PDF page reserves about 2,000 tokens for every model call, so a 50-page PDF holds 100,000 tokens, half of the default `dailyTokensPerIp`. A visitor who sends several large PDFs uses up their day quickly. On a bot with tools, a call that the budget can't cover is skipped: the model answers from the files without calling tools. Lower `attachments.maxPdfPages` or raise `dailyTokensPerIp` if visitors need more.
|
|
209
|
+
|
|
210
|
+
When a whole bot's budget or the global one runs out, the server log says so once a day, for example `[askdare] budget_exceeded: The daily token budget of bot "support" (limits.dailyTokens) is used up until 00:00 UTC.`, and `onError` receives it. A single visitor's budget is never reported.
|
|
211
|
+
|
|
212
|
+
**Fix:** wait until 00:00 UTC, or raise the budget and restart. While testing, restarting `next dev` clears the in-memory counters. Before raising budgets in production, read [Costs](costs.md).
|
|
213
|
+
|
|
214
|
+
## The assistant was updated
|
|
215
|
+
|
|
216
|
+
**You see** "The assistant was updated. Please reload the page." on every message.
|
|
217
|
+
|
|
218
|
+
**Why:** the widget checks the protocol version that the server sends with each answer, in the `x-askdare-protocol` response header. It shows this text when the header is missing or differs from its own version. That happens when:
|
|
219
|
+
- a tab stayed open across an askdare upgrade that changed the protocol; or
|
|
220
|
+
- something between the browser and the app removes the header: a proxy, a CDN, or code in `proxy.ts` that rebuilds the response. Then it happens after every reload too.
|
|
221
|
+
|
|
222
|
+
**Fix:** reload the page. If the message comes back, check the `messages` request in the network tab: its response must have `x-askdare-protocol` and `Content-Type: text/event-stream`. Leave `/api/chat` out of anything that rewrites responses.
|
|
223
|
+
|
|
224
|
+
## Session expired
|
|
225
|
+
|
|
226
|
+
**You see** "Your session has expired. Please try again." (`session_expired`) or "Authentication is required." (`unauthorized`).
|
|
227
|
+
|
|
228
|
+
**Why:** sessions last 15 minutes. The widget renews them by itself and repeats a refused request once, so visitors rarely see these texts. When they do, the renewed session was refused too. The usual cause is instances that don't share the same `CHAT_SECRET`: a session from one instance is unreadable on another, for example during a deployment that changed the secret.
|
|
229
|
+
|
|
230
|
+
**Fix:** use the same `CHAT_SECRET` on every instance. When you change it, move the old value to `CHAT_SECRET_PREVIOUS` first ([Going public](going-public.md#2-secrets-and-environment)). Then reload the page.
|
|
231
|
+
|
|
232
|
+
## The conversation is gone
|
|
233
|
+
|
|
234
|
+
**You see** "This conversation is no longer available." (`not_found`), or "Could not load the previous conversation." when the chat opens.
|
|
235
|
+
|
|
236
|
+
**Why:**
|
|
237
|
+
- "This conversation is no longer available." means OpenAI no longer has the conversation, for example because it was deleted. The widget forgets it, and the next message starts a new one.
|
|
238
|
+
- "Could not load the previous conversation." means the history request failed: often a refused session ([Connection problem](#connection-problem)), or OpenAI didn't answer.
|
|
239
|
+
|
|
240
|
+
A conversation can also disappear without a message. The browser's handle to it expires 30 days after the last answer. After that, or after `CHAT_SECRET` changed without `CHAT_SECRET_PREVIOUS`, the chat quietly starts a new one.
|
|
241
|
+
|
|
242
|
+
**Fix:** for the second text, check the server log and the `session` and `history` requests in the network tab.
|
|
243
|
+
|
|
244
|
+
## The microphone doesn't work
|
|
245
|
+
|
|
246
|
+
**You see** no microphone button, or one of these texts:
|
|
247
|
+
|
|
248
|
+
| Text | Cause |
|
|
249
|
+
| --- | --- |
|
|
250
|
+
| "Microphone access is blocked. Allow it in your browser to dictate." | The visitor refused permission, or the embedding page doesn't let the iframe use the microphone (Safari shows this instead of hiding the button) |
|
|
251
|
+
| "No microphone was found." | No microphone is connected |
|
|
252
|
+
| "The microphone could not be started." | The browser can't record, or the microphone failed |
|
|
253
|
+
| "Nothing was recorded. Please try again." | The recording was almost empty, for example when **Done** was pressed right away |
|
|
254
|
+
| "This assistant does not accept voice input." | The bot has no `voice` |
|
|
255
|
+
| "This recording format is not supported." or "This recording is too long." | The server refused the file: not WebM, MP4, WAV or MP3, or bigger than `maxSeconds` allows |
|
|
256
|
+
| "The recording could not be transcribed." | The answer to the `transcriptions` request held no transcript, for example because something in front of the app answered instead; check the network tab |
|
|
257
|
+
|
|
258
|
+
**Why the button is missing:** the bot has no `voice`, the browser can't record, or the embedding page forbids the microphone. The loader's iframe asks for it (`allow="microphone"`), but a page that sends `Permissions-Policy: microphone=()`, or allows it only for itself, can't pass it on to the chat.
|
|
259
|
+
|
|
260
|
+
**Fix:**
|
|
261
|
+
- Add `voice: { model: '…' }` to the bot ([Voice input](voice-input.md)).
|
|
262
|
+
- If your site sends a `Permissions-Policy` header, allow the microphone for the app's origin too, for example `microphone=(self "https://your-app.example.com")` ([Voice input](voice-input.md#embedding)).
|
|
263
|
+
- Ask visitors who refused to allow the microphone in the browser's site settings.
|
|
264
|
+
|
|
265
|
+
## Attachments are rejected
|
|
266
|
+
|
|
267
|
+
**You see** one of these texts after choosing a file:
|
|
268
|
+
|
|
269
|
+
| Text | Cause and fix |
|
|
270
|
+
| --- | --- |
|
|
271
|
+
| "This assistant does not accept files." | The bot has no `attachments`. Add `attachments: {}` ([Attachments](attachments.md)) |
|
|
272
|
+
| "This file type is not supported." or "Only images (PNG, JPEG, WebP, GIF) and PDF files are supported." | The type is detected from the file's bytes. SVG, animated GIF and WebP, and anything else are refused. With `images: false` or `pdf: false` the text names the remaining type |
|
|
273
|
+
| "Too large (max {size})." or "Files can be up to {size}." | Larger than `maxBytes` (default 4 MB). On Vercel, requests can't exceed 4.5 MB, so don't raise it there |
|
|
274
|
+
| "PDFs can have up to {max} pages." | More pages than `maxPdfPages` (default 50) |
|
|
275
|
+
| "You can attach up to {count} files per message." or "Attach up to {count} files per message." | More than `maxPerMessage` (default 3) |
|
|
276
|
+
| "An attachment expired or is invalid. Please attach it again." | Uploaded files must be sent within 1 hour, by the same visitor. It also happens after `CHAT_SECRET` changed |
|
|
277
|
+
| "The file could not be uploaded." | The upload answered with something unexpected; check the network tab |
|
|
278
|
+
| "Too many messages. Please wait a moment." | An upload limit was reached; see [Too many messages](#too-many-messages) |
|
|
279
|
+
|
|
280
|
+
A PDF that passes these checks can still be refused with `budget_exceeded`; see [Unavailable for the rest of the day](#unavailable-for-the-rest-of-the-day).
|
|
281
|
+
|
|
282
|
+
## Answers are cut off or time out
|
|
283
|
+
|
|
284
|
+
**The answer stops mid-sentence, or is empty.** The model reached `limits.maxOutputTokens` (default 1,024 per model call). With a reasoning model, its reasoning counts toward the same limit, so it can use it up before writing anything visible. Raise `maxOutputTokens`, or ask for shorter answers in the instructions.
|
|
285
|
+
|
|
286
|
+
**"The assistant took too long to respond. Please try again."** (`timeout`). The whole answer, tool calls included, took longer than `limits.turnTimeoutMs` (default 120 seconds). Raise it, and check your tools' `timeoutMs`.
|
|
287
|
+
|
|
288
|
+
**Answers stop after the same number of seconds.** The host ended the request before askdare did. On Vercel, the route's `maxDuration` must be at least `turnTimeoutMs` plus 15 seconds, as a literal in the route file. `doctor` warns:
|
|
289
|
+
|
|
290
|
+
```text
|
|
291
|
+
! An answer may take up to 120 s, but the route's maxDuration is 60 s.
|
|
292
|
+
→ Raise maxDuration to at least 135, or lower limits.turnTimeoutMs.
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
On Heroku, use `--keepAliveTimeout 95000` in the `Procfile` ([Deployment](deployment.md#heroku)).
|
|
296
|
+
|
|
297
|
+
**"The connection stalled. Please try again."** The widget received nothing for 45 seconds, although the server sends a heartbeat every 15 seconds. Something between the browser and the app buffers the stream. Turn off buffering of `text/event-stream` for `/api/chat`; askdare already sends `X-Accel-Buffering: no` for nginx.
|
|
298
|
+
|
|
299
|
+
## OpenAI fails
|
|
300
|
+
|
|
301
|
+
**You see** "The assistant is temporarily unavailable. Please try again." (`upstream_error`). The server log shows OpenAI's status, error code and request ID.
|
|
302
|
+
|
|
303
|
+
**Why:** askdare couldn't reach OpenAI, or OpenAI refused or failed the request for a reason other than a rejected key or an unknown model: its own rate limits, an outage, or a project that ran out of credit or reached its hard spend limit.
|
|
304
|
+
|
|
305
|
+
**Fix:** check the OpenAI status page and the project's usage, limits and billing in the OpenAI dashboard. `doctor` reports a spent project as "The OpenAI project is out of credit or reached its spend limit."
|
|
306
|
+
|
|
307
|
+
## In-memory rate limits
|
|
308
|
+
|
|
309
|
+
**You see** this in the production log:
|
|
310
|
+
|
|
311
|
+
```text
|
|
312
|
+
[askdare] Rate limits are kept in memory, per instance. With more than one instance, use a shared store such as @boldare/askdare-upstash.
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
`doctor` reports it as "Rate limits and budgets are kept in memory, per instance."
|
|
316
|
+
|
|
317
|
+
**Why:** `createChatHandler` has no `limits.store`, so every instance keeps its own counters. That is fine for one server or one Heroku dyno. With several instances, each one allows the full limits and budgets, and a restart or deployment resets them.
|
|
318
|
+
|
|
319
|
+
**Fix:** with more than one instance, use the shared store ([Going public](going-public.md#4-rate-limits-across-instances)). With exactly one, you can ignore the warning.
|
|
320
|
+
|
|
321
|
+
Related errors:
|
|
322
|
+
- `[askdare] In-memory rate-limit store is full; use a shared store.` The memory store holds at most 50,000 counters and never forgets a budget, so it refuses requests instead. Visitors see "Something went wrong. Please try again."
|
|
323
|
+
- `[askdare-upstash] Set UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN, or pass a client.` The shared store has no credentials where the app runs.
|
|
324
|
+
|
|
325
|
+
## The kill switch
|
|
326
|
+
|
|
327
|
+
`ASKDARE_DISABLED=true` switches off every bot of the app. The value must be exactly `true`.
|
|
328
|
+
|
|
329
|
+
**What it does:** sessions, messages, history, uploads, dictations and `doctor` runs are refused with `bot_unavailable`. Visitors see "The assistant is currently unavailable." The launcher still shows. Answers that already started finish.
|
|
330
|
+
|
|
331
|
+
**Why it seems not to work:** askdare reads the variable on every request, but most hosts only pass a changed variable to the app after a restart. On Vercel, redeploy. On Heroku, changing a config var restarts the dynos. Locally, restart `next dev`.
|
|
332
|
+
|
|
333
|
+
**To act faster**, revoke the API key in the OpenAI dashboard. Every message then fails with `bot_unavailable`, and you need a new key afterwards.
|
|
334
|
+
|
|
335
|
+
**To switch back on**, remove the variable or set it to anything else, and restart or redeploy.
|
|
336
|
+
|
|
337
|
+
## Doctor can't check the app
|
|
338
|
+
|
|
339
|
+
| `doctor` says | Fix |
|
|
340
|
+
| --- | --- |
|
|
341
|
+
| "Skipped: the live checks need CHAT_SECRET." | Run it where `.env.local` has `CHAT_SECRET`, or pass `--env-file` |
|
|
342
|
+
| "The app rejected the doctor's token: its CHAT_SECRET differs from yours." | Pass the env file of that environment, such as `--env-file .env.production.local` |
|
|
343
|
+
| "The app is switched off (ASKDARE_DISABLED) or has no CHAT_SECRET where it runs." | See [The kill switch](#the-kill-switch) and [Connection problem](#connection-problem) |
|
|
344
|
+
| "Too many doctor runs at once or this minute." | Wait a minute; the app allows 3 runs a minute |
|
|
345
|
+
| "No askdare route at …" | Check `--url` and `--base-path`, and the route file |
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Voice input
|
|
2
|
+
|
|
3
|
+
Visitors can dictate a message instead of typing it. The recording is transcribed by OpenAI and the text lands in the message field, where the visitor can check and edit it before sending. Nothing is sent to the model until they press Send.
|
|
4
|
+
|
|
5
|
+
## Turn it on
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
defineBot({
|
|
9
|
+
// …
|
|
10
|
+
voice: { model: process.env.ASKDARE_TRANSCRIBE_MODEL ?? '' }, // dictations up to 60 seconds
|
|
11
|
+
});
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
| Option | Default | Meaning |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| `model` | required | OpenAI transcription model. The library never assumes one; `gpt-transcribe` is current (the older transcription models shut down on 2027-02-26) |
|
|
17
|
+
| `maxSeconds` | `60` | Longest dictation (5–300). Recording stops by itself at this point |
|
|
18
|
+
|
|
19
|
+
Limits (in `limits`): `transcriptionsPerVisitorPerHour` (default 30), and speech budgets in seconds, because transcription is billed by duration: `voiceSecondsPerDay` per bot (default 3,600) and `voiceSecondsPerIpPerDay` (default 600). Each dictation reserves `maxSeconds` and is then charged the duration OpenAI reports. If OpenAI reports no duration (token-billed models) or the call times out, it is charged the longest audio the file could hold, so prefer `gpt-transcribe`. A dictation that OpenAI refuses with an error is not charged. At most 2 dictations per IP and 10 per bot are transcribed at once.
|
|
20
|
+
|
|
21
|
+
## How it works
|
|
22
|
+
|
|
23
|
+
- The widget shows a microphone button when the browser can record and the page lets the iframe use the microphone. The browser asks the visitor for permission the first time.
|
|
24
|
+
- While recording, the widget shows "Listening…", a level meter and a timer. **Done** stops and transcribes; **×** or Escape cancels. Closing the chat or switching tabs cancels too, and recording stops by itself at `maxSeconds`.
|
|
25
|
+
- Chrome, Firefox and Edge record WebM/Opus, Safari records MP4. The server checks the format from the file's bytes before sending it to OpenAI.
|
|
26
|
+
- The visitor's browser languages and the widget locale are sent as hints, which helps with short phrases.
|
|
27
|
+
- askdare keeps no audio. The recording goes to OpenAI's transcription API and nowhere else.
|
|
28
|
+
|
|
29
|
+
## Embedding
|
|
30
|
+
|
|
31
|
+
The loader's iframe delegates the microphone (`allow="microphone"`), and the embed page enables it only for bots with `voice`. If the website itself forbids the microphone (`Permissions-Policy: microphone=()`), the button is hidden, or in Safari the visitor sees "Microphone access is blocked".
|
package/llms.txt
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# askdare
|
|
2
|
+
|
|
3
|
+
> `@boldare/askdare` adds an AI chatbot to any website. A Next.js 16 app (App Router, Node.js runtime) serves the chat from one catch-all route; other sites embed it with one `<script>` tag. Built on the official `openai` SDK (Responses and Conversations APIs); no database.
|
|
4
|
+
|
|
5
|
+
Set it up with `npx @boldare/askdare init` and verify it with `npx @boldare/askdare doctor`. The public API is `defineBot`, `defineTool` and `defineTheme` from `@boldare/askdare`, `createChatHandler` from `@boldare/askdare/next`, the `data-*` attributes of the script tag, `askdare(…)` on the page, the `--ask-*` CSS tokens, the `data-ask-part` names and the text keys. Everything else is internal.
|
|
6
|
+
|
|
7
|
+
## Docs
|
|
8
|
+
|
|
9
|
+
- [Getting started](docs/getting-started.md): install, `init`, `.env.local`, `doctor`, embedding on other sites
|
|
10
|
+
- [Configuration](docs/configuration.md): every `defineBot` and `createChatHandler` option, limits with defaults, environment variables
|
|
11
|
+
- [Embedding](docs/embedding.md): the script tag and its `data-*` attributes, several bots, the host page's CSP, staging and production
|
|
12
|
+
- [CLI](docs/cli.md): `init`, `doctor`, `kb sync` and `usage`, with every flag
|
|
13
|
+
- [Troubleshooting](docs/troubleshooting.md): symptoms, causes and fixes, by error code
|
|
14
|
+
- [Tools](docs/tools.md): `defineTool`, Zod schemas, what a tool may trust
|
|
15
|
+
- [Signed-in visitors](docs/identity.md): `askdare('identify')`, IDENTITY_SECRET, `context.identity`
|
|
16
|
+
- [Knowledge base](docs/knowledge-base.md): `npx @boldare/askdare kb sync`, vector stores, citations
|
|
17
|
+
- [Attachments](docs/attachments.md): images and PDFs from visitors
|
|
18
|
+
- [Voice input](docs/voice-input.md): dictation and its limits
|
|
19
|
+
- [Styling](docs/styling.md): texts, languages, AI disclosure, custom CSS, `askdare('open' | 'configure')`, inline mode
|
|
20
|
+
- [Texts](docs/texts.md): every text key with its English and Polish default, plurals, adding a language
|
|
21
|
+
- [Themes](docs/themes.md): Halo, Aura, Sage and Care, tokens and variants
|
|
22
|
+
- [Deployment](docs/deployment.md): Vercel, Heroku, proxies, shared limits
|
|
23
|
+
- [Going public](docs/going-public.md): OpenAI project, secrets, limits, privacy checklist
|
|
24
|
+
- [Costs](docs/costs.md): the worst case under the default limits, and how to keep it bounded
|
|
25
|
+
- [Privacy](docs/privacy.md): what is stored where, for your privacy policy
|
|
26
|
+
- [Support](docs/support.md): Node.js, Next.js, React, zod and TypeScript versions, browsers, sizes
|
|
27
|
+
- [Migrating from @boldare/openai-assistant](docs/migrating-from-openai-assistant.md): what maps to what
|
|
28
|
+
|
|
29
|
+
## Optional
|
|
30
|
+
|
|
31
|
+
- [Streaming protocol](docs/protocol.md): the SSE events between the server and the chat, endpoints and status codes
|
|
32
|
+
- [Custom rate-limit store](docs/custom-store.md): writing your own store for limits and budgets
|
package/package.json
CHANGED
|
@@ -1,6 +1,101 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boldare/askdare",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0-rc.0",
|
|
4
|
+
"description": "Embeddable AI chatbot for Next.js, built on the official OpenAI SDK.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Boldare",
|
|
8
|
+
"url": "https://www.boldare.com"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/boldare/askdare#readme",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/boldare/askdare.git",
|
|
14
|
+
"directory": "packages/askdare"
|
|
15
|
+
},
|
|
16
|
+
"bugs": "https://github.com/boldare/askdare/issues",
|
|
17
|
+
"keywords": [
|
|
18
|
+
"chatbot",
|
|
19
|
+
"chat-widget",
|
|
20
|
+
"ai",
|
|
21
|
+
"assistant",
|
|
22
|
+
"nextjs",
|
|
23
|
+
"next.js",
|
|
24
|
+
"widget",
|
|
25
|
+
"embed",
|
|
26
|
+
"openai",
|
|
27
|
+
"responses-api",
|
|
28
|
+
"rag",
|
|
29
|
+
"openai-assistant",
|
|
30
|
+
"assistants-api"
|
|
31
|
+
],
|
|
32
|
+
"type": "module",
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=22"
|
|
36
|
+
},
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"default": "./dist/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./next": {
|
|
43
|
+
"types": "./dist/next.d.ts",
|
|
44
|
+
"default": "./dist/next.js"
|
|
45
|
+
},
|
|
46
|
+
"./package.json": "./package.json"
|
|
47
|
+
},
|
|
48
|
+
"types": "./dist/index.d.ts",
|
|
49
|
+
"bin": {
|
|
50
|
+
"askdare": "./dist/cli.js"
|
|
51
|
+
},
|
|
52
|
+
"files": [
|
|
53
|
+
"dist",
|
|
54
|
+
"docs",
|
|
55
|
+
"llms.txt",
|
|
56
|
+
"skills",
|
|
57
|
+
"CHANGELOG.md"
|
|
58
|
+
],
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public",
|
|
61
|
+
"provenance": true
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
65
|
+
"@fontsource-variable/atkinson-hyperlegible-next": "5.3.0",
|
|
66
|
+
"@fontsource-variable/geist": "5.3.0",
|
|
67
|
+
"@fontsource-variable/plus-jakarta-sans": "5.3.0",
|
|
68
|
+
"@types/react": "^19.3.0",
|
|
69
|
+
"@types/react-dom": "^19.3.0",
|
|
70
|
+
"marked": "18.1.2",
|
|
71
|
+
"next": "^16.4.3",
|
|
72
|
+
"preact": "10.29.8",
|
|
73
|
+
"publint": "^0.3.25",
|
|
74
|
+
"react": "^19.3.1",
|
|
75
|
+
"react-dom": "^19.3.1",
|
|
76
|
+
"tsdown": "^0.23.3",
|
|
77
|
+
"vite": "^8.3.5",
|
|
78
|
+
"zod": "^4.6.7"
|
|
79
|
+
},
|
|
80
|
+
"peerDependencies": {
|
|
81
|
+
"next": "^16.0.0",
|
|
82
|
+
"zod": "^4.1.0"
|
|
83
|
+
},
|
|
84
|
+
"peerDependenciesMeta": {
|
|
85
|
+
"next": {
|
|
86
|
+
"optional": true
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"dependencies": {
|
|
90
|
+
"jose": "^6.2.13",
|
|
91
|
+
"openai": "^7.45.1"
|
|
92
|
+
},
|
|
93
|
+
"scripts": {
|
|
94
|
+
"build:assets": "vite build -c vite.widget.config.ts && vite build -c vite.loader.config.ts && node scripts/inline-assets.mjs",
|
|
95
|
+
"build": "pnpm run build:assets && tsdown && node scripts/copy-docs.mjs",
|
|
96
|
+
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.browser.json",
|
|
97
|
+
"check:package": "publint --strict && attw --pack . --profile esm-only && node ../../scripts/check-dts-no-leak.mjs dist && node scripts/api-snapshot.mjs && tsc -p types-test",
|
|
98
|
+
"api:update": "node scripts/api-snapshot.mjs --update",
|
|
99
|
+
"docs:texts": "node scripts/docs-texts.ts"
|
|
100
|
+
}
|
|
6
101
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: askdare
|
|
3
|
+
description: Add, configure or troubleshoot the @boldare/askdare AI chat in a Next.js app (defineBot, defineTool, createChatHandler, the widget script, themes, knowledge base, deployment). Use when the project depends on @boldare/askdare or the user asks for a website chatbot built on it.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# askdare
|
|
7
|
+
|
|
8
|
+
`@boldare/askdare` serves an embeddable AI chat from a Next.js 16 app (App Router, Node.js runtime). The full guides are in `node_modules/@boldare/askdare/docs/`; read the relevant one before changing anything. They match the installed version, unlike your memory of the API.
|
|
9
|
+
|
|
10
|
+
## Setup and checks
|
|
11
|
+
|
|
12
|
+
- New integration: `npx @boldare/askdare init`. It writes `askdare.config.ts`, `app/api/chat/[...path]/route.ts`, `.env.local` (with a generated `CHAT_SECRET`) and the script tag in the root layout, and never overwrites files.
|
|
13
|
+
- After any change to the config, the environment or the hosting: run the app, then `npx @boldare/askdare doctor` (add `--url` and `--env-file` for a deployed app). Fix what it reports before anything else.
|
|
14
|
+
- Knowledge base: `npx @boldare/askdare kb sync <folder>`, then set `KB_VECTOR_STORE_ID` to the ID it prints.
|
|
15
|
+
- Something fails: `docs/troubleshooting.md` lists symptoms, causes and fixes by error code. In development the server log says what is wrong with the setup; so does the chat when a reply fails.
|
|
16
|
+
- Every option, limit and default: `docs/configuration.md`; every CLI flag: `docs/cli.md`; script tag attributes and the host page's CSP: `docs/embedding.md`.
|
|
17
|
+
|
|
18
|
+
## Public API: use only this
|
|
19
|
+
|
|
20
|
+
- `defineBot`, `defineTool`, `defineTheme` from `@boldare/askdare`; `createChatHandler` from `@boldare/askdare/next`.
|
|
21
|
+
- On pages: `<script src="…/api/chat/widget.js?bot=<id>" async>` with `data-launcher="none"` or `data-mode="inline" data-target="…"`, and `askdare('open' | 'close' | 'toggle' | 'configure', …)`.
|
|
22
|
+
- Styling: `widget.texts`, `widget.theme`, `widget.css` with `[data-ask-part="…"]` selectors and `--ask-*` tokens (see `docs/styling.md`).
|
|
23
|
+
|
|
24
|
+
Never import from `@boldare/askdare/dist/…` or other internal paths.
|
|
25
|
+
|
|
26
|
+
## Rules
|
|
27
|
+
|
|
28
|
+
- `export const maxDuration = 300` must stay a literal in the route file. Don't set `runtime = 'edge'`.
|
|
29
|
+
- Keep `/api/chat` out of `proxy.ts` (or `middleware.ts`) matchers, of `headers()` in `next.config` and of anything that adds `X-Frame-Options`, a Content Security Policy or cookies.
|
|
30
|
+
- The model is the app owner's choice: never invent or swap a model ID; read it from config or ask. Model names change often, so check OpenAI's current model list.
|
|
31
|
+
- Secrets (`OPENAI_API_KEY`, `CHAT_SECRET`) belong in `.env.local` or the host's settings, never in code, instructions or commits. Never print them.
|
|
32
|
+
- Bot instructions must contain no secrets: visitors can make the model repeat them.
|
|
33
|
+
- Sites that embed the chat go in `security.allowedOrigins` as exact origins, without wildcards.
|
|
34
|
+
- Tools: every Zod field is required (use `.nullable()`, not `.optional()`). Never take user IDs, emails or permissions from tool arguments; use the verified `context` (`visitorId`, `botId`, `locale`, `identity`, `signal`). Keep tools read-only.
|
|
35
|
+
- Signed-in users: the site's backend signs an HS256 JWT with `IDENTITY_SECRET` (`sub`, `aud` = bot ID, `iat`, `exp`), and the page calls `askdare('identify', token)` (`null` on sign-out). See `docs/identity.md`.
|
|
36
|
+
- More than one server instance (Vercel, several dynos) needs shared limits: `@boldare/askdare-upstash`.
|