chatbotlite 0.6.0 → 0.6.2

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.
@@ -89,12 +89,32 @@ interface ChatWidgetCommonProps {
89
89
  * - URL → rendered as image
90
90
  */
91
91
  launcherIcon?: string;
92
+ /**
93
+ * Open the chat panel on mount instead of showing only the launcher.
94
+ * Good for demo / landing pages where the visitor should see the bot
95
+ * immediately. Default false — production sites usually want the
96
+ * launcher-first behaviour so the chrome stays unobtrusive.
97
+ */
98
+ defaultOpen?: boolean;
92
99
  }
93
100
  interface ChatWidgetDirectProps extends ChatWidgetCommonProps {
94
101
  /** Markdown knowledge for the bot. Client-side mode — API keys WILL be exposed. */
95
102
  knowledge: Knowledge;
96
103
  /** Provider chain + API keys. */
97
104
  providers: ProviderConfig;
105
+ /**
106
+ * Append per-vertical behaviour tweaks to the default system prompt
107
+ * (tone, escalation rules, "don't quote price too early", etc.).
108
+ * Only used in direct (client-side) mode — in endpoint mode the server
109
+ * controls the prompt.
110
+ */
111
+ extraInstructions?: string;
112
+ /**
113
+ * Power-user hook to modify our default scaffolding inline.
114
+ * Receives the assembled default prompt, returns a transformed string.
115
+ * Direct mode only.
116
+ */
117
+ systemPromptTransform?: (defaultPrompt: string) => string;
98
118
  endpoint?: never;
99
119
  }
100
120
  interface ChatWidgetEndpointProps extends ChatWidgetCommonProps {
@@ -89,12 +89,32 @@ interface ChatWidgetCommonProps {
89
89
  * - URL → rendered as image
90
90
  */
91
91
  launcherIcon?: string;
92
+ /**
93
+ * Open the chat panel on mount instead of showing only the launcher.
94
+ * Good for demo / landing pages where the visitor should see the bot
95
+ * immediately. Default false — production sites usually want the
96
+ * launcher-first behaviour so the chrome stays unobtrusive.
97
+ */
98
+ defaultOpen?: boolean;
92
99
  }
93
100
  interface ChatWidgetDirectProps extends ChatWidgetCommonProps {
94
101
  /** Markdown knowledge for the bot. Client-side mode — API keys WILL be exposed. */
95
102
  knowledge: Knowledge;
96
103
  /** Provider chain + API keys. */
97
104
  providers: ProviderConfig;
105
+ /**
106
+ * Append per-vertical behaviour tweaks to the default system prompt
107
+ * (tone, escalation rules, "don't quote price too early", etc.).
108
+ * Only used in direct (client-side) mode — in endpoint mode the server
109
+ * controls the prompt.
110
+ */
111
+ extraInstructions?: string;
112
+ /**
113
+ * Power-user hook to modify our default scaffolding inline.
114
+ * Receives the assembled default prompt, returns a transformed string.
115
+ * Direct mode only.
116
+ */
117
+ systemPromptTransform?: (defaultPrompt: string) => string;
98
118
  endpoint?: never;
99
119
  }
100
120
  interface ChatWidgetEndpointProps extends ChatWidgetCommonProps {
@@ -1,4 +1,4 @@
1
- import { useState, useRef, useEffect, useMemo } from 'react';
1
+ import { useState, useEffect, useRef, useMemo } from 'react';
2
2
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
3
 
4
4
  // src/react/ChatWidget.tsx
@@ -67,9 +67,11 @@ function buildToolsPromptAddendum(enabledTools) {
67
67
  }
68
68
 
69
69
  // src/core/prompts.ts
70
- function buildSystemPrompt(knowledge, enabledTools = []) {
71
- const toolsAddendum = buildToolsPromptAddendum(enabledTools);
72
- return [
70
+ function buildSystemPrompt(knowledge, optionsOrEnabledTools = []) {
71
+ const opts = Array.isArray(optionsOrEnabledTools) ? { enabledTools: optionsOrEnabledTools } : optionsOrEnabledTools;
72
+ const toolsAddendum = buildToolsPromptAddendum(opts.enabledTools ?? []);
73
+ const extras = opts.extraInstructions?.trim();
74
+ const parts = [
73
75
  "You are an AI assistant on a business website. Use ONLY the knowledge below to answer.",
74
76
  "",
75
77
  "## Business knowledge",
@@ -81,9 +83,16 @@ function buildSystemPrompt(knowledge, enabledTools = []) {
81
83
  "- For anything not covered in the knowledge above, say the owner will follow up \u2014 do NOT guess.",
82
84
  '- If the caller is clearly a vendor/sales pitch, say: "This does not look like a customer service request, so we will not continue this thread."',
83
85
  `- If wrong number or asked to stop, say: "Sorry about that. We won't text again."`,
84
- "- Match the caller's language automatically.",
85
- toolsAddendum
86
- ].filter(Boolean).join("\n");
86
+ "- Match the caller's language automatically."
87
+ ];
88
+ if (extras) {
89
+ parts.push("", "## Additional instructions", extras);
90
+ }
91
+ if (toolsAddendum) {
92
+ parts.push(toolsAddendum);
93
+ }
94
+ const defaultPrompt = parts.join("\n");
95
+ return opts.systemPromptTransform ? opts.systemPromptTransform(defaultPrompt) : defaultPrompt;
87
96
  }
88
97
 
89
98
  // src/core/guards.ts
@@ -207,6 +216,8 @@ var ChatBot = class {
207
216
  cachedSystemPrompt;
208
217
  guards;
209
218
  knowledge;
219
+ extraInstructions;
220
+ systemPromptTransform;
210
221
  constructor(init) {
211
222
  if (!init.knowledge || typeof init.knowledge !== "string" || init.knowledge.trim().length === 0) {
212
223
  throw new Error("chatbotlite: knowledge is required (a non-empty markdown string).");
@@ -216,14 +227,23 @@ var ChatBot = class {
216
227
  this.steps = resolveChain(init.providers);
217
228
  this.fetcher = init.options?.fetch ?? globalThis.fetch.bind(globalThis);
218
229
  this.timeoutMs = init.options?.timeoutMs ?? 3e4;
219
- this.cachedSystemPrompt = buildSystemPrompt(init.knowledge);
230
+ this.extraInstructions = init.extraInstructions;
231
+ this.systemPromptTransform = init.systemPromptTransform;
232
+ this.cachedSystemPrompt = buildSystemPrompt(init.knowledge, {
233
+ extraInstructions: this.extraInstructions,
234
+ systemPromptTransform: this.systemPromptTransform
235
+ });
220
236
  this.guards = init.guards ?? {};
221
237
  }
222
238
  /** Build system prompt for given opts — uses cached if no enabledTools, else rebuilds. */
223
239
  resolveSystemPrompt(opts) {
224
240
  if (opts.systemPrompt) return opts.systemPrompt;
225
241
  if (opts.enabledTools && opts.enabledTools.length > 0) {
226
- return buildSystemPrompt(this.knowledge, opts.enabledTools);
242
+ return buildSystemPrompt(this.knowledge, {
243
+ enabledTools: opts.enabledTools,
244
+ extraInstructions: this.extraInstructions,
245
+ systemPromptTransform: this.systemPromptTransform
246
+ });
227
247
  }
228
248
  return this.cachedSystemPrompt;
229
249
  }
@@ -893,7 +913,7 @@ function RequestPayment(props) {
893
913
  surface,
894
914
  textBody,
895
915
  textMuted,
896
- showInterac = true,
916
+ showInterac = false,
897
917
  stripeLink,
898
918
  onPick,
899
919
  submitting = false,
@@ -1009,8 +1029,12 @@ function RequestPayment(props) {
1009
1029
  display: "flex",
1010
1030
  alignItems: "center",
1011
1031
  justifyContent: "center",
1012
- flexShrink: 0
1013
- }, children: /* @__PURE__ */ jsx("span", { style: { color: "#635BFF", fontSize: 14, fontWeight: 700 }, children: "\u{1F4B3}" }) }),
1032
+ flexShrink: 0,
1033
+ color: "#635BFF"
1034
+ }, children: /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.75, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
1035
+ /* @__PURE__ */ jsx("rect", { x: "2", y: "5", width: "20", height: "14", rx: "2.5" }),
1036
+ /* @__PURE__ */ jsx("line", { x1: "2", y1: "10", x2: "22", y2: "10" })
1037
+ ] }) }),
1014
1038
  /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
1015
1039
  /* @__PURE__ */ jsx("p", { style: { margin: 0, fontSize: 13, fontWeight: 600, color: textBody }, children: "Pay by card" }),
1016
1040
  /* @__PURE__ */ jsx("p", { style: { margin: "2px 0 0", fontSize: 11, color: textMuted }, children: "Visa \xB7 Mastercard \xB7 Amex" })
@@ -1150,7 +1174,34 @@ function ChatWidget(props) {
1150
1174
  const voiceEnabled = voiceCfg?.enabled === true;
1151
1175
  const voiceLang = voiceCfg?.lang ?? "en-US";
1152
1176
  const speechSupported = typeof window !== "undefined" && (Boolean(window.SpeechRecognition) || Boolean(window.webkitSpeechRecognition));
1153
- const [open, setOpen] = useState(false);
1177
+ const [open, setOpen] = useState(Boolean(props.defaultOpen));
1178
+ const [expanded, setExpanded] = useState(() => {
1179
+ if (typeof window === "undefined") return false;
1180
+ try {
1181
+ return window.localStorage.getItem("cbl-panel-size") === "expanded";
1182
+ } catch {
1183
+ return false;
1184
+ }
1185
+ });
1186
+ const [isMobile, setIsMobile] = useState(
1187
+ () => typeof window === "undefined" ? false : window.innerWidth < 640
1188
+ );
1189
+ useEffect(() => {
1190
+ if (typeof window === "undefined") return;
1191
+ const onResize = () => setIsMobile(window.innerWidth < 640);
1192
+ window.addEventListener("resize", onResize);
1193
+ return () => window.removeEventListener("resize", onResize);
1194
+ }, []);
1195
+ function toggleExpanded() {
1196
+ setExpanded((prev) => {
1197
+ const next = !prev;
1198
+ try {
1199
+ window.localStorage.setItem("cbl-panel-size", next ? "expanded" : "compact");
1200
+ } catch {
1201
+ }
1202
+ return next;
1203
+ });
1204
+ }
1154
1205
  const [messages, setMessages] = useState([
1155
1206
  { id: "g0", role: "assistant", content: resolvedGreeting, ts: Date.now() }
1156
1207
  ]);
@@ -1243,11 +1294,17 @@ function ChatWidget(props) {
1243
1294
  useEffect(() => {
1244
1295
  ensureStyles();
1245
1296
  }, []);
1297
+ const directProps = isEndpointMode ? null : props;
1246
1298
  const bot = useMemo(() => {
1247
- if (isEndpointMode) return null;
1248
- if (!props.knowledge || !props.providers) return null;
1249
- return new ChatBot({ knowledge: props.knowledge, providers: props.providers });
1250
- }, [isEndpointMode, props.knowledge, props.providers]);
1299
+ if (!directProps) return null;
1300
+ if (!directProps.knowledge || !directProps.providers) return null;
1301
+ return new ChatBot({
1302
+ knowledge: directProps.knowledge,
1303
+ providers: directProps.providers,
1304
+ ...directProps.extraInstructions ? { extraInstructions: directProps.extraInstructions } : {},
1305
+ ...directProps.systemPromptTransform ? { systemPromptTransform: directProps.systemPromptTransform } : {}
1306
+ });
1307
+ }, [directProps]);
1251
1308
  useEffect(() => {
1252
1309
  if (scrollRef.current) {
1253
1310
  scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
@@ -1428,12 +1485,12 @@ function ChatWidget(props) {
1428
1485
  ["--cbl-primary"]: primary,
1429
1486
  ["--cbl-on-primary"]: onPrimary,
1430
1487
  position: "fixed",
1431
- bottom: 20,
1432
- ...panelPos,
1433
- width: 380,
1434
- maxWidth: "calc(100vw - 40px)",
1435
- height: 580,
1436
- maxHeight: "calc(100vh - 40px)",
1488
+ bottom: isMobile ? 0 : 20,
1489
+ ...isMobile ? { left: 0, right: 0 } : panelPos,
1490
+ width: isMobile ? "100vw" : expanded ? 720 : 380,
1491
+ maxWidth: isMobile ? "100vw" : "calc(100vw - 40px)",
1492
+ height: isMobile ? "100vh" : expanded ? 800 : 580,
1493
+ maxHeight: isMobile ? "100vh" : "calc(100vh - 40px)",
1437
1494
  background: SURFACE,
1438
1495
  color: TEXT_BODY,
1439
1496
  borderRadius: 20,
@@ -1491,30 +1548,63 @@ function ChatWidget(props) {
1491
1548
  (subtitle || sending) && /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: TEXT_MUTED, marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: subtitle ?? (sending ? "typing\u2026" : "") })
1492
1549
  ] })
1493
1550
  ] }),
1494
- /* @__PURE__ */ jsx(
1495
- "button",
1496
- {
1497
- className: "chatbotlite-close",
1498
- onClick: () => setOpen(false),
1499
- "aria-label": "Close chat",
1500
- style: {
1501
- background: "transparent",
1502
- border: "none",
1503
- color: TEXT_MUTED,
1504
- width: 32,
1505
- height: 32,
1506
- borderRadius: 10,
1507
- fontSize: 22,
1508
- lineHeight: 1,
1509
- cursor: "pointer",
1510
- display: "flex",
1511
- alignItems: "center",
1512
- justifyContent: "center",
1513
- flexShrink: 0
1514
- },
1515
- children: "\xD7"
1516
- }
1517
- )
1551
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 2, flexShrink: 0 }, children: [
1552
+ !isMobile && /* @__PURE__ */ jsx(
1553
+ "button",
1554
+ {
1555
+ className: "chatbotlite-resize",
1556
+ onClick: toggleExpanded,
1557
+ "aria-label": expanded ? "Compact view" : "Expand view",
1558
+ title: expanded ? "Compact view" : "Expand view",
1559
+ style: {
1560
+ background: "transparent",
1561
+ border: "none",
1562
+ color: TEXT_MUTED,
1563
+ width: 32,
1564
+ height: 32,
1565
+ borderRadius: 10,
1566
+ cursor: "pointer",
1567
+ display: "flex",
1568
+ alignItems: "center",
1569
+ justifyContent: "center"
1570
+ },
1571
+ children: expanded ? /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.75, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
1572
+ /* @__PURE__ */ jsx("polyline", { points: "9 4 4 4 4 9" }),
1573
+ /* @__PURE__ */ jsx("polyline", { points: "15 4 20 4 20 9" }),
1574
+ /* @__PURE__ */ jsx("polyline", { points: "4 15 4 20 9 20" }),
1575
+ /* @__PURE__ */ jsx("polyline", { points: "20 15 20 20 15 20" })
1576
+ ] }) : /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.75, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
1577
+ /* @__PURE__ */ jsx("polyline", { points: "3 9 3 3 9 3" }),
1578
+ /* @__PURE__ */ jsx("polyline", { points: "21 9 21 3 15 3" }),
1579
+ /* @__PURE__ */ jsx("polyline", { points: "3 15 3 21 9 21" }),
1580
+ /* @__PURE__ */ jsx("polyline", { points: "21 15 21 21 15 21" })
1581
+ ] })
1582
+ }
1583
+ ),
1584
+ /* @__PURE__ */ jsx(
1585
+ "button",
1586
+ {
1587
+ className: "chatbotlite-close",
1588
+ onClick: () => setOpen(false),
1589
+ "aria-label": "Close chat",
1590
+ style: {
1591
+ background: "transparent",
1592
+ border: "none",
1593
+ color: TEXT_MUTED,
1594
+ width: 32,
1595
+ height: 32,
1596
+ borderRadius: 10,
1597
+ fontSize: 22,
1598
+ lineHeight: 1,
1599
+ cursor: "pointer",
1600
+ display: "flex",
1601
+ alignItems: "center",
1602
+ justifyContent: "center"
1603
+ },
1604
+ children: "\xD7"
1605
+ }
1606
+ )
1607
+ ] })
1518
1608
  ] }),
1519
1609
  /* @__PURE__ */ jsxs(
1520
1610
  "div",