chatbotlite 0.0.1 → 0.3.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.
@@ -0,0 +1,594 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/react/ChatWidget.tsx
7
+
8
+ // src/core/prompts.ts
9
+ function buildSystemPrompt(knowledge) {
10
+ return [
11
+ "You are an AI assistant on a business website. Use ONLY the knowledge below to answer.",
12
+ "",
13
+ "## Business knowledge",
14
+ knowledge.trim(),
15
+ "",
16
+ "## Reply rules",
17
+ "- Reply in 1-2 short sentences, conversational tone.",
18
+ "- NEVER invent prices, availability, dispatch times, appointment confirmations, or facts not present in the business knowledge above.",
19
+ "- For anything not covered in the knowledge above, say the owner will follow up \u2014 do NOT guess.",
20
+ '- 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."',
21
+ `- If wrong number or asked to stop, say: "Sorry about that. We won't text again."`,
22
+ "- Match the caller's language automatically."
23
+ ].join("\n");
24
+ }
25
+
26
+ // src/core/guards.ts
27
+ var FORBIDDEN_PHRASES = [
28
+ "help is coming",
29
+ "someone is on the way",
30
+ "technician is on the way",
31
+ "provider is on the way",
32
+ "dispatching someone",
33
+ "i've booked",
34
+ "i have booked",
35
+ "reservation confirmed",
36
+ "your appointment is confirmed",
37
+ "i've scheduled",
38
+ "i have scheduled",
39
+ "we've dispatched",
40
+ "we have dispatched",
41
+ "i can confirm",
42
+ "i guarantee",
43
+ "guaranteed delivery",
44
+ "guaranteed arrival",
45
+ "will arrive at",
46
+ "arriving at",
47
+ "i'll send",
48
+ "i will send"
49
+ ];
50
+ function checkForbiddenPhrases(reply) {
51
+ const lower = reply.toLowerCase();
52
+ const violations = [];
53
+ for (const phrase of FORBIDDEN_PHRASES) {
54
+ if (lower.includes(phrase)) {
55
+ violations.push(`Forbidden phrase: "${phrase}"`);
56
+ }
57
+ }
58
+ return { ok: violations.length === 0, violations };
59
+ }
60
+ function stripForbidden(reply) {
61
+ const sentences = reply.split(/(?<=[.!?])\s+/);
62
+ const kept = sentences.filter((s) => {
63
+ const lower = s.toLowerCase();
64
+ return !FORBIDDEN_PHRASES.some((p) => lower.includes(p));
65
+ });
66
+ const trimmed = kept.join(" ").trim();
67
+ if (trimmed.length < 10) {
68
+ return "Thanks for reaching out \u2014 let me check with the owner and get back to you.";
69
+ }
70
+ return trimmed;
71
+ }
72
+
73
+ // src/client/types.ts
74
+ var PROVIDER_NAMES = /* @__PURE__ */ new Set([
75
+ "openai",
76
+ "deepseek",
77
+ "groq",
78
+ "gemini",
79
+ "anthropic",
80
+ "cerebras",
81
+ "sambanova",
82
+ "fireworks",
83
+ "mistral",
84
+ "openrouter",
85
+ "moonshot"
86
+ ]);
87
+ function isKnownProvider(name) {
88
+ return PROVIDER_NAMES.has(name);
89
+ }
90
+
91
+ // src/client/providers.ts
92
+ var PROVIDER_ENDPOINTS = {
93
+ openai: { baseUrl: "https://api.openai.com/v1", defaultModel: "gpt-4o-mini" },
94
+ deepseek: { baseUrl: "https://api.deepseek.com/v1", defaultModel: "deepseek-chat" },
95
+ groq: { baseUrl: "https://api.groq.com/openai/v1", defaultModel: "llama-3.3-70b-versatile" },
96
+ gemini: { baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai", defaultModel: "gemini-2.5-flash" },
97
+ anthropic: { baseUrl: "https://api.anthropic.com/v1", defaultModel: "claude-haiku-4-5" },
98
+ cerebras: { baseUrl: "https://api.cerebras.ai/v1", defaultModel: "qwen-3-235b-a22b-instruct-2507" },
99
+ sambanova: { baseUrl: "https://api.sambanova.ai/v1", defaultModel: "Meta-Llama-3.3-70B-Instruct" },
100
+ fireworks: { baseUrl: "https://api.fireworks.ai/inference/v1", defaultModel: "accounts/fireworks/models/llama-v3p3-70b-instruct" },
101
+ mistral: { baseUrl: "https://api.mistral.ai/v1", defaultModel: "mistral-small-latest" },
102
+ openrouter: { baseUrl: "https://openrouter.ai/api/v1", defaultModel: "deepseek/deepseek-chat" },
103
+ moonshot: { baseUrl: "https://api.moonshot.ai/v1", defaultModel: "moonshot-v1-32k" }
104
+ };
105
+ function isRetryableError(err) {
106
+ const msg = err instanceof Error ? err.message : String(err);
107
+ return /\b(429|rate.?limit|quota|exceed|5\d\d|timeout|ECONNRESET|fetch failed)\b/i.test(msg);
108
+ }
109
+
110
+ // src/client/chatbot.ts
111
+ var ChatBot = class {
112
+ steps;
113
+ keys;
114
+ fetcher;
115
+ timeoutMs;
116
+ cachedSystemPrompt;
117
+ constructor(init) {
118
+ if (!init.knowledge || typeof init.knowledge !== "string" || init.knowledge.trim().length === 0) {
119
+ throw new Error("chatbotlite: knowledge is required (a non-empty markdown string).");
120
+ }
121
+ this.keys = init.providers.keys ?? {};
122
+ this.steps = resolveChain(init.providers);
123
+ this.fetcher = init.options?.fetch ?? globalThis.fetch.bind(globalThis);
124
+ this.timeoutMs = init.options?.timeoutMs ?? 3e4;
125
+ this.cachedSystemPrompt = buildSystemPrompt(init.knowledge);
126
+ }
127
+ async reply(message, opts = {}) {
128
+ const systemPrompt = opts.systemPrompt ?? this.cachedSystemPrompt;
129
+ const messages = [
130
+ { role: "system", content: systemPrompt },
131
+ ...opts.history ?? [],
132
+ { role: "user", content: message }
133
+ ];
134
+ const attempts = [];
135
+ let lastError;
136
+ for (const step of this.steps) {
137
+ const t0 = Date.now();
138
+ try {
139
+ const result = await this.callProvider(step, messages);
140
+ attempts.push({ provider: step.provider, model: step.model, status: "ok", latencyMs: Date.now() - t0 });
141
+ const guard = checkForbiddenPhrases(result.reply);
142
+ const finalReply = guard.ok ? result.reply : stripForbidden(result.reply);
143
+ return {
144
+ reply: finalReply,
145
+ usedProvider: step.provider,
146
+ usedModel: step.model,
147
+ ...result.usage ? { usage: result.usage } : {},
148
+ guardWarnings: guard.violations,
149
+ attempts
150
+ };
151
+ } catch (err) {
152
+ lastError = err;
153
+ const errMsg = err instanceof Error ? err.message : String(err);
154
+ attempts.push({
155
+ provider: step.provider,
156
+ model: step.model,
157
+ status: "error",
158
+ error: errMsg,
159
+ latencyMs: Date.now() - t0
160
+ });
161
+ if (!isRetryableError(err)) {
162
+ throw new Error(`chatbotlite: ${step.label} failed (non-retryable). ${errMsg}`);
163
+ }
164
+ }
165
+ }
166
+ const summary = attempts.map((a) => `${a.provider}/${a.model}:${a.error ?? "ok"}`).join(" \u2192 ");
167
+ throw new Error(`chatbotlite: all chain steps failed. Trace: ${summary}. Last error: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
168
+ }
169
+ async callProvider(step, messages) {
170
+ const endpoint = PROVIDER_ENDPOINTS[step.provider];
171
+ const key = this.keys[step.provider];
172
+ if (!key) throw new Error(`Missing API key for provider: ${step.provider}`);
173
+ const controller = new AbortController();
174
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
175
+ try {
176
+ const res = await this.fetcher(`${endpoint.baseUrl}/chat/completions`, {
177
+ method: "POST",
178
+ headers: {
179
+ "Authorization": `Bearer ${key}`,
180
+ "Content-Type": "application/json"
181
+ },
182
+ body: JSON.stringify({
183
+ model: step.model,
184
+ messages,
185
+ temperature: 0.3,
186
+ max_tokens: 300
187
+ }),
188
+ signal: controller.signal
189
+ });
190
+ if (!res.ok) {
191
+ const body = await res.text();
192
+ throw new Error(`${res.status}: ${body.slice(0, 200)}`);
193
+ }
194
+ const data = await res.json();
195
+ const msg = data.choices?.[0]?.message;
196
+ const reply = (msg?.content?.trim() || msg?.reasoning_content?.trim()) ?? "";
197
+ if (!reply) throw new Error("empty reply from provider");
198
+ const result = { reply };
199
+ if (data.usage) result.usage = data.usage;
200
+ return result;
201
+ } finally {
202
+ clearTimeout(timer);
203
+ }
204
+ }
205
+ };
206
+ function resolveChain(providers) {
207
+ const keys = providers.keys ?? {};
208
+ const explicit = providers.chain;
209
+ if (explicit && explicit.length > 0) {
210
+ return explicit.map((entry) => normalizeChainEntry(entry, keys));
211
+ }
212
+ const orderedProviders = Object.keys(keys).filter((k) => isKnownProvider(k) && keys[k]);
213
+ if (orderedProviders.length === 0) {
214
+ throw new Error("chatbotlite: at least one provider key is required.");
215
+ }
216
+ return orderedProviders.map((provider) => ({
217
+ provider,
218
+ model: PROVIDER_ENDPOINTS[provider].defaultModel,
219
+ label: `${provider}/${PROVIDER_ENDPOINTS[provider].defaultModel}`
220
+ }));
221
+ }
222
+ function normalizeChainEntry(entry, keys) {
223
+ if (!isKnownProvider(entry.provider)) {
224
+ throw new Error(`chatbotlite: unknown provider "${entry.provider}" in chain entry.`);
225
+ }
226
+ const provider = entry.provider;
227
+ const model = entry.model ?? PROVIDER_ENDPOINTS[provider].defaultModel;
228
+ if (!keys[provider]) {
229
+ throw new Error(`chatbotlite: chain entry for "${provider}" needs a matching key in providers.keys.`);
230
+ }
231
+ return { provider, model, label: `${provider}/${model}` };
232
+ }
233
+ var BOLT = "\u26A1";
234
+ var DEFAULT_PRIMARY = "#0f172a";
235
+ var DEFAULT_ON_PRIMARY = "#ffffff";
236
+ var SURFACE = "#ffffff";
237
+ var SURFACE_MUTED = "#fafbfc";
238
+ var BORDER = "#e5e7eb";
239
+ var TEXT_BODY = "#0f172a";
240
+ var TEXT_MUTED = "#64748b";
241
+ var TEXT_FAINT = "#94a3b8";
242
+ var FONT_STACK = `'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif`;
243
+ var STYLE_TAG_ID = "chatbotlite-widget-styles";
244
+ var KEYFRAMES = `
245
+ @keyframes chatbotlite-pop { 0% { opacity: 0; transform: scale(0.6); } 100% { opacity: 1; transform: scale(1); } }
246
+ @keyframes chatbotlite-slide { 0% { opacity: 0; transform: translateY(16px) scale(0.98); } 100% { opacity: 1; transform: translateY(0) scale(1); } }
247
+ @keyframes chatbotlite-fade-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
248
+ @keyframes chatbotlite-dot { 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; } 30% { transform: translateY(-4px); opacity: 1; } }
249
+ .chatbotlite-launcher { transition: transform 180ms cubic-bezier(0.4, 0, 0.2, 1), box-shadow 180ms cubic-bezier(0.4, 0, 0.2, 1); }
250
+ .chatbotlite-launcher:hover { transform: translateY(-2px) scale(1.04); }
251
+ .chatbotlite-launcher:active { transform: translateY(0) scale(0.98); }
252
+ .chatbotlite-close { transition: background 120ms ease; }
253
+ .chatbotlite-close:hover { background: rgba(255,255,255,0.16); }
254
+ .chatbotlite-send { transition: transform 120ms ease, opacity 120ms ease, box-shadow 120ms ease; }
255
+ .chatbotlite-send:not(:disabled):hover { transform: translateY(-1px); }
256
+ .chatbotlite-send:not(:disabled):active { transform: translateY(0); }
257
+ .chatbotlite-input:focus { box-shadow: 0 0 0 3px rgba(15,23,42,0.06); }
258
+ .chatbotlite-msg { animation: chatbotlite-fade-in 220ms cubic-bezier(0.4, 0, 0.2, 1); }
259
+ .chatbotlite-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: ${TEXT_FAINT}; margin-right: 4px; animation: chatbotlite-dot 1.2s ease-in-out infinite; }
260
+ .chatbotlite-dot:nth-child(2) { animation-delay: 0.15s; }
261
+ .chatbotlite-dot:nth-child(3) { animation-delay: 0.3s; margin-right: 0; }
262
+ .chatbotlite-brand:hover { color: ${TEXT_MUTED} !important; }
263
+ `;
264
+ function ensureStyles() {
265
+ if (typeof document === "undefined") return;
266
+ if (document.getElementById(STYLE_TAG_ID)) return;
267
+ const style = document.createElement("style");
268
+ style.id = STYLE_TAG_ID;
269
+ style.textContent = KEYFRAMES;
270
+ document.head.appendChild(style);
271
+ }
272
+ function ChatWidget(props) {
273
+ const {
274
+ theme: themeOverrides,
275
+ title,
276
+ subtitle,
277
+ greeting,
278
+ showBranding = true,
279
+ position = "bottom-right"
280
+ } = props;
281
+ const isEndpointMode = "endpoint" in props && typeof props.endpoint === "string";
282
+ const resolvedTitle = title ?? "Chat";
283
+ const resolvedGreeting = greeting ?? "Hi! How can we help?";
284
+ const primary = themeOverrides?.primary ?? DEFAULT_PRIMARY;
285
+ const onPrimary = themeOverrides?.onPrimary ?? DEFAULT_ON_PRIMARY;
286
+ const [open, setOpen] = react.useState(false);
287
+ const [messages, setMessages] = react.useState([
288
+ { id: "g0", role: "assistant", content: resolvedGreeting, ts: Date.now() }
289
+ ]);
290
+ const [input, setInput] = react.useState("");
291
+ const [sending, setSending] = react.useState(false);
292
+ const scrollRef = react.useRef(null);
293
+ const inputRef = react.useRef(null);
294
+ react.useEffect(() => {
295
+ ensureStyles();
296
+ }, []);
297
+ const bot = react.useMemo(() => {
298
+ if (isEndpointMode) return null;
299
+ if (!props.knowledge || !props.providers) return null;
300
+ return new ChatBot({ knowledge: props.knowledge, providers: props.providers });
301
+ }, [isEndpointMode, props.knowledge, props.providers]);
302
+ react.useEffect(() => {
303
+ if (scrollRef.current) {
304
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
305
+ }
306
+ }, [messages, sending, open]);
307
+ react.useEffect(() => {
308
+ if (open && inputRef.current) {
309
+ const t = setTimeout(() => inputRef.current?.focus(), 240);
310
+ return () => clearTimeout(t);
311
+ }
312
+ return void 0;
313
+ }, [open]);
314
+ async function fetchReplyFromEndpoint(text, history) {
315
+ const res = await fetch(props.endpoint, {
316
+ method: "POST",
317
+ headers: { "Content-Type": "application/json" },
318
+ body: JSON.stringify({ message: text, transcript: history })
319
+ });
320
+ if (!res.ok) throw new Error(`Endpoint ${res.status}: ${await res.text().catch(() => "")}`);
321
+ const data = await res.json();
322
+ if (data.error) throw new Error(data.error);
323
+ if (!data.reply) throw new Error("Endpoint returned no reply.");
324
+ return data.reply;
325
+ }
326
+ async function send() {
327
+ const text = input.trim();
328
+ if (!text || sending) return;
329
+ setInput("");
330
+ const userMsg = { id: `u${Date.now()}`, role: "user", content: text, ts: Date.now() };
331
+ setMessages((prev) => [...prev, userMsg]);
332
+ setSending(true);
333
+ try {
334
+ const history = messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content }));
335
+ const reply = isEndpointMode ? await fetchReplyFromEndpoint(text, history) : (await bot.reply(text, { history })).reply;
336
+ setMessages((prev) => [...prev, { id: `a${Date.now()}`, role: "assistant", content: reply, ts: Date.now() }]);
337
+ } catch (err) {
338
+ const errMsg = err instanceof Error ? err.message : String(err);
339
+ setMessages((prev) => [...prev, { id: `e${Date.now()}`, role: "assistant", content: `Sorry \u2014 something went wrong. (${errMsg})`, ts: Date.now() }]);
340
+ } finally {
341
+ setSending(false);
342
+ }
343
+ }
344
+ const launcherPos = position === "bottom-left" ? { left: 20 } : { right: 20 };
345
+ const panelPos = position === "bottom-left" ? { left: 20 } : { right: 20 };
346
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
347
+ !open && /* @__PURE__ */ jsxRuntime.jsx(
348
+ "button",
349
+ {
350
+ className: "chatbotlite-launcher",
351
+ onClick: () => setOpen(true),
352
+ "aria-label": "Open chat",
353
+ style: {
354
+ position: "fixed",
355
+ bottom: 20,
356
+ ...launcherPos,
357
+ width: 60,
358
+ height: 60,
359
+ borderRadius: 18,
360
+ background: primary,
361
+ color: onPrimary,
362
+ border: "none",
363
+ fontSize: 28,
364
+ lineHeight: 1,
365
+ cursor: "pointer",
366
+ boxShadow: "0 12px 28px -8px rgba(15,23,42,0.32), 0 4px 8px -2px rgba(15,23,42,0.12)",
367
+ zIndex: 99999,
368
+ animation: "chatbotlite-pop 320ms cubic-bezier(0.34, 1.56, 0.64, 1)",
369
+ display: "flex",
370
+ alignItems: "center",
371
+ justifyContent: "center"
372
+ },
373
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.2))" }, children: BOLT })
374
+ }
375
+ ),
376
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
377
+ "div",
378
+ {
379
+ role: "dialog",
380
+ "aria-label": "Chat",
381
+ style: {
382
+ position: "fixed",
383
+ bottom: 20,
384
+ ...panelPos,
385
+ width: 380,
386
+ maxWidth: "calc(100vw - 40px)",
387
+ height: 580,
388
+ maxHeight: "calc(100vh - 40px)",
389
+ background: SURFACE,
390
+ color: TEXT_BODY,
391
+ borderRadius: 20,
392
+ boxShadow: "0 24px 60px -16px rgba(15,23,42,0.32), 0 8px 24px -8px rgba(15,23,42,0.12), 0 0 0 1px rgba(15,23,42,0.04)",
393
+ display: "flex",
394
+ flexDirection: "column",
395
+ overflow: "hidden",
396
+ fontFamily: FONT_STACK,
397
+ zIndex: 99999,
398
+ animation: "chatbotlite-slide 280ms cubic-bezier(0.16, 1, 0.3, 1)"
399
+ },
400
+ children: [
401
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { style: {
402
+ padding: "16px 18px",
403
+ background: primary,
404
+ color: onPrimary,
405
+ display: "flex",
406
+ justifyContent: "space-between",
407
+ alignItems: "center",
408
+ gap: 12
409
+ }, children: [
410
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", lineHeight: 1.2, minWidth: 0 }, children: [
411
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 600, fontSize: 15, letterSpacing: "-0.01em", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: resolvedTitle }),
412
+ subtitle && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, opacity: 0.7, marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: subtitle })
413
+ ] }),
414
+ /* @__PURE__ */ jsxRuntime.jsx(
415
+ "button",
416
+ {
417
+ className: "chatbotlite-close",
418
+ onClick: () => setOpen(false),
419
+ "aria-label": "Close chat",
420
+ style: {
421
+ background: "transparent",
422
+ border: "none",
423
+ color: onPrimary,
424
+ width: 32,
425
+ height: 32,
426
+ borderRadius: 10,
427
+ fontSize: 22,
428
+ lineHeight: 1,
429
+ cursor: "pointer",
430
+ display: "flex",
431
+ alignItems: "center",
432
+ justifyContent: "center",
433
+ flexShrink: 0
434
+ },
435
+ children: "\xD7"
436
+ }
437
+ )
438
+ ] }),
439
+ /* @__PURE__ */ jsxRuntime.jsxs(
440
+ "div",
441
+ {
442
+ ref: scrollRef,
443
+ style: {
444
+ flex: 1,
445
+ overflowY: "auto",
446
+ padding: "16px 14px",
447
+ display: "flex",
448
+ flexDirection: "column",
449
+ gap: 8,
450
+ background: SURFACE_MUTED
451
+ },
452
+ children: [
453
+ messages.map((m) => /* @__PURE__ */ jsxRuntime.jsx(
454
+ "div",
455
+ {
456
+ className: "chatbotlite-msg",
457
+ style: {
458
+ alignSelf: m.role === "user" ? "flex-end" : "flex-start",
459
+ maxWidth: "82%",
460
+ padding: "9px 13px",
461
+ borderRadius: m.role === "user" ? "18px 18px 4px 18px" : "18px 18px 18px 4px",
462
+ background: m.role === "user" ? primary : SURFACE,
463
+ color: m.role === "user" ? onPrimary : TEXT_BODY,
464
+ border: m.role === "user" ? "none" : `1px solid ${BORDER}`,
465
+ fontSize: 14,
466
+ lineHeight: 1.5,
467
+ letterSpacing: "-0.005em",
468
+ whiteSpace: "pre-wrap",
469
+ wordBreak: "break-word",
470
+ boxShadow: m.role === "user" ? "0 1px 2px rgba(15,23,42,0.12)" : "0 1px 2px rgba(15,23,42,0.04)"
471
+ },
472
+ children: m.content
473
+ },
474
+ m.id
475
+ )),
476
+ sending && /* @__PURE__ */ jsxRuntime.jsxs(
477
+ "div",
478
+ {
479
+ className: "chatbotlite-msg",
480
+ style: {
481
+ alignSelf: "flex-start",
482
+ padding: "12px 14px",
483
+ borderRadius: "18px 18px 18px 4px",
484
+ background: SURFACE,
485
+ border: `1px solid ${BORDER}`,
486
+ boxShadow: "0 1px 2px rgba(15,23,42,0.04)"
487
+ },
488
+ children: [
489
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chatbotlite-dot" }),
490
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chatbotlite-dot" }),
491
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chatbotlite-dot" })
492
+ ]
493
+ }
494
+ )
495
+ ]
496
+ }
497
+ ),
498
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
499
+ display: "flex",
500
+ padding: 12,
501
+ gap: 8,
502
+ background: SURFACE,
503
+ borderTop: `1px solid ${BORDER}`
504
+ }, children: [
505
+ /* @__PURE__ */ jsxRuntime.jsx(
506
+ "input",
507
+ {
508
+ ref: inputRef,
509
+ className: "chatbotlite-input",
510
+ type: "text",
511
+ value: input,
512
+ onChange: (e) => setInput(e.target.value),
513
+ onKeyDown: (e) => {
514
+ if (e.key === "Enter" && !e.shiftKey) {
515
+ e.preventDefault();
516
+ void send();
517
+ }
518
+ },
519
+ placeholder: "Type a message\u2026",
520
+ disabled: sending,
521
+ style: {
522
+ flex: 1,
523
+ padding: "10px 14px",
524
+ borderRadius: 12,
525
+ border: `1px solid ${BORDER}`,
526
+ background: SURFACE_MUTED,
527
+ fontSize: 14,
528
+ fontFamily: FONT_STACK,
529
+ color: TEXT_BODY,
530
+ outline: "none",
531
+ transition: "box-shadow 120ms ease, border-color 120ms ease"
532
+ }
533
+ }
534
+ ),
535
+ /* @__PURE__ */ jsxRuntime.jsx(
536
+ "button",
537
+ {
538
+ className: "chatbotlite-send",
539
+ onClick: () => void send(),
540
+ disabled: sending || !input.trim(),
541
+ "aria-label": "Send message",
542
+ style: {
543
+ padding: "0 16px",
544
+ height: 40,
545
+ minWidth: 64,
546
+ borderRadius: 12,
547
+ background: primary,
548
+ color: onPrimary,
549
+ border: "none",
550
+ fontSize: 14,
551
+ fontWeight: 600,
552
+ fontFamily: FONT_STACK,
553
+ cursor: sending || !input.trim() ? "default" : "pointer",
554
+ opacity: sending || !input.trim() ? 0.4 : 1,
555
+ boxShadow: "0 2px 6px -1px rgba(15,23,42,0.18)"
556
+ },
557
+ children: "Send"
558
+ }
559
+ )
560
+ ] }),
561
+ showBranding && /* @__PURE__ */ jsxRuntime.jsxs(
562
+ "a",
563
+ {
564
+ className: "chatbotlite-brand",
565
+ href: "https://github.com/agents-io/chatbotlite",
566
+ target: "_blank",
567
+ rel: "noreferrer",
568
+ style: {
569
+ padding: "8px 12px",
570
+ fontSize: 11,
571
+ fontWeight: 500,
572
+ color: TEXT_FAINT,
573
+ textAlign: "center",
574
+ textDecoration: "none",
575
+ background: SURFACE,
576
+ borderTop: `1px solid ${BORDER}`,
577
+ letterSpacing: "0.01em",
578
+ transition: "color 120ms ease"
579
+ },
580
+ children: [
581
+ BOLT,
582
+ " Powered by chatbotlite"
583
+ ]
584
+ }
585
+ )
586
+ ]
587
+ }
588
+ )
589
+ ] });
590
+ }
591
+
592
+ exports.ChatWidget = ChatWidget;
593
+ //# sourceMappingURL=index.cjs.map
594
+ //# sourceMappingURL=index.cjs.map