@syncagent/react 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -23,26 +23,18 @@ declare function useSyncAgent(options?: UseSyncAgentOptions): {
23
23
  };
24
24
 
25
25
  interface SyncAgentChatProps {
26
- /** Config — required if not wrapped in SyncAgentProvider */
27
26
  config?: SyncAgentConfig;
28
- /** Show as floating widget (FAB + panel) or inline */
29
27
  mode?: "floating" | "inline";
30
- /** FAB position */
31
28
  position?: "bottom-right" | "bottom-left";
32
- /** Start open */
33
29
  defaultOpen?: boolean;
34
- /** Widget title */
35
30
  title?: string;
36
- /** Placeholder text */
31
+ subtitle?: string;
37
32
  placeholder?: string;
38
- /** Welcome message */
39
33
  welcomeMessage?: string;
40
- /** Accent color */
41
34
  accentColor?: string;
42
- /** Custom class for the container */
43
35
  className?: string;
44
- /** Custom styles for the container */
45
36
  style?: CSSProperties;
37
+ suggestions?: string[];
46
38
  }
47
39
  declare function SyncAgentChat({ config, ...props }: SyncAgentChatProps): react_jsx_runtime.JSX.Element;
48
40
 
package/dist/index.d.ts CHANGED
@@ -23,26 +23,18 @@ declare function useSyncAgent(options?: UseSyncAgentOptions): {
23
23
  };
24
24
 
25
25
  interface SyncAgentChatProps {
26
- /** Config — required if not wrapped in SyncAgentProvider */
27
26
  config?: SyncAgentConfig;
28
- /** Show as floating widget (FAB + panel) or inline */
29
27
  mode?: "floating" | "inline";
30
- /** FAB position */
31
28
  position?: "bottom-right" | "bottom-left";
32
- /** Start open */
33
29
  defaultOpen?: boolean;
34
- /** Widget title */
35
30
  title?: string;
36
- /** Placeholder text */
31
+ subtitle?: string;
37
32
  placeholder?: string;
38
- /** Welcome message */
39
33
  welcomeMessage?: string;
40
- /** Accent color */
41
34
  accentColor?: string;
42
- /** Custom class for the container */
43
35
  className?: string;
44
- /** Custom styles for the container */
45
36
  style?: CSSProperties;
37
+ suggestions?: string[];
46
38
  }
47
39
  declare function SyncAgentChat({ config, ...props }: SyncAgentChatProps): react_jsx_runtime.JSX.Element;
48
40
 
package/dist/index.js CHANGED
@@ -125,214 +125,469 @@ function useSyncAgent(options = {}) {
125
125
  // src/chat.tsx
126
126
  var import_react3 = require("react");
127
127
  var import_jsx_runtime2 = require("react/jsx-runtime");
128
+ function renderMarkdown(text) {
129
+ return text.replace(/```[\w]*\n?([\s\S]*?)```/g, `<pre style="background:#1e1e2e;color:#cdd6f4;padding:12px 14px;border-radius:8px;overflow-x:auto;font-size:12px;line-height:1.6;margin:8px 0;font-family:'Fira Code','Cascadia Code',monospace">$1</pre>`).replace(/`([^`]+)`/g, '<code style="background:rgba(0,0,0,0.08);padding:2px 6px;border-radius:4px;font-size:12px;font-family:monospace">$1</code>').replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/\*(.+?)\*/g, "<em>$1</em>").replace(/^### (.+)$/gm, '<div style="font-weight:700;font-size:13px;margin:10px 0 4px">$1</div>').replace(/^## (.+)$/gm, '<div style="font-weight:700;font-size:14px;margin:12px 0 4px">$1</div>').replace(/^# (.+)$/gm, '<div style="font-weight:700;font-size:15px;margin:12px 0 6px">$1</div>').replace(/\|(.+)\|\n\|[-| :]+\|\n((?:\|.+\|\n?)+)/g, (_, header, rows) => {
130
+ const ths = header.split("|").filter((c) => c.trim()).map((c) => `<th style="padding:6px 10px;text-align:left;font-weight:600;border-bottom:2px solid rgba(0,0,0,0.1)">${c.trim()}</th>`).join("");
131
+ const trs = rows.trim().split("\n").map((row) => {
132
+ const tds = row.split("|").filter((c) => c.trim()).map((c) => `<td style="padding:6px 10px;border-bottom:1px solid rgba(0,0,0,0.06)">${c.trim()}</td>`).join("");
133
+ return `<tr>${tds}</tr>`;
134
+ }).join("");
135
+ return `<div style="overflow-x:auto;margin:8px 0"><table style="width:100%;border-collapse:collapse;font-size:13px"><thead><tr>${ths}</tr></thead><tbody>${trs}</tbody></table></div>`;
136
+ }).replace(/^[-*] (.+)$/gm, '<li style="margin:2px 0;padding-left:4px">$1</li>').replace(/(<li[^>]*>.*<\/li>\n?)+/g, (m) => `<ul style="margin:6px 0;padding-left:18px;list-style:disc">${m}</ul>`).replace(/^\d+\. (.+)$/gm, '<li style="margin:2px 0;padding-left:4px">$1</li>').replace(/\n\n/g, "<br/><br/>").replace(/\n/g, "<br/>");
137
+ }
138
+ function TypingDots({ color }) {
139
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { display: "inline-flex", gap: 4, alignItems: "center", padding: "2px 0" }, children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: {
140
+ width: 6,
141
+ height: 6,
142
+ borderRadius: "50%",
143
+ background: color,
144
+ animation: "sa-bounce 1.2s infinite",
145
+ animationDelay: `${i * 0.2}s`,
146
+ display: "inline-block"
147
+ } }, i)) });
148
+ }
149
+ function CopyBtn({ text }) {
150
+ const [copied, setCopied] = (0, import_react3.useState)(false);
151
+ const copy = (0, import_react3.useCallback)(() => {
152
+ navigator.clipboard?.writeText(text).then(() => {
153
+ setCopied(true);
154
+ setTimeout(() => setCopied(false), 1500);
155
+ });
156
+ }, [text]);
157
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: copy, title: "Copy", style: {
158
+ background: "none",
159
+ border: "none",
160
+ cursor: "pointer",
161
+ padding: "2px 6px",
162
+ borderRadius: 4,
163
+ fontSize: 11,
164
+ color: "#9ca3af",
165
+ opacity: 0.7,
166
+ transition: "opacity 0.15s"
167
+ }, children: copied ? "\u2713" : "\u2398" });
168
+ }
169
+ function MessageBubble({
170
+ role,
171
+ content,
172
+ isStreaming,
173
+ accentColor,
174
+ timestamp
175
+ }) {
176
+ const isUser = role === "user";
177
+ const timeStr = timestamp.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
178
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
179
+ display: "flex",
180
+ flexDirection: "column",
181
+ alignItems: isUser ? "flex-end" : "flex-start",
182
+ gap: 3,
183
+ maxWidth: "88%",
184
+ alignSelf: isUser ? "flex-end" : "flex-start"
185
+ }, children: [
186
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
187
+ display: "flex",
188
+ alignItems: "center",
189
+ gap: 6,
190
+ flexDirection: isUser ? "row-reverse" : "row"
191
+ }, children: [
192
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
193
+ width: 24,
194
+ height: 24,
195
+ borderRadius: "50%",
196
+ background: isUser ? accentColor : "linear-gradient(135deg,#6366f1,#8b5cf6)",
197
+ display: "flex",
198
+ alignItems: "center",
199
+ justifyContent: "center",
200
+ fontSize: 11,
201
+ color: "white",
202
+ fontWeight: 700,
203
+ flexShrink: 0
204
+ }, children: isUser ? "U" : "\u2726" }),
205
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 11, color: "#9ca3af", fontWeight: 500 }, children: isUser ? "You" : "SyncAgent" }),
206
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 10, color: "#d1d5db" }, children: timeStr })
207
+ ] }),
208
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
209
+ padding: "10px 14px",
210
+ borderRadius: isUser ? "16px 4px 16px 16px" : "4px 16px 16px 16px",
211
+ background: isUser ? accentColor : "#f8fafc",
212
+ color: isUser ? "white" : "#1e293b",
213
+ fontSize: 13.5,
214
+ lineHeight: 1.6,
215
+ wordBreak: "break-word",
216
+ border: isUser ? "none" : "1px solid #e2e8f0",
217
+ boxShadow: isUser ? `0 2px 8px ${accentColor}33` : "0 1px 4px rgba(0,0,0,0.06)",
218
+ position: "relative"
219
+ }, children: isStreaming && !content ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TypingDots, { color: accentColor }) : isUser ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { whiteSpace: "pre-wrap" }, children: content }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { dangerouslySetInnerHTML: { __html: renderMarkdown(content) } }) }),
220
+ !isUser && content && !isStreaming && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { paddingLeft: 30 }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CopyBtn, { text: content }) })
221
+ ] });
222
+ }
223
+ function Suggestions({ items, onSelect, accentColor }) {
224
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, padding: "0 16px 12px" }, children: items.map((s2) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => onSelect(s2), style: {
225
+ padding: "5px 12px",
226
+ borderRadius: 20,
227
+ fontSize: 12,
228
+ cursor: "pointer",
229
+ border: `1px solid ${accentColor}44`,
230
+ background: `${accentColor}0d`,
231
+ color: accentColor,
232
+ fontWeight: 500,
233
+ transition: "all 0.15s"
234
+ }, children: s2 }, s2)) });
235
+ }
128
236
  function ChatInner({
129
237
  mode = "floating",
130
238
  position = "bottom-right",
131
239
  defaultOpen = false,
132
- title = "\u2728 AI Assistant",
133
- placeholder = "Ask about your data...",
134
- welcomeMessage = "Ask me anything about your data. I can query, create, update, and analyze your database.",
240
+ title = "SyncAgent",
241
+ subtitle = "AI Database Assistant",
242
+ placeholder = "Ask anything about your data...",
243
+ welcomeMessage = "Hi! I can query, analyze, and manage your database using natural language. What would you like to know?",
135
244
  accentColor = "#10b981",
136
245
  className,
137
- style: customStyle
246
+ style: customStyle,
247
+ suggestions = ["Show all records", "Count total entries", "Show recent activity"]
138
248
  }) {
139
- const { messages, isLoading, sendMessage } = useSyncAgent();
249
+ const { messages, isLoading, error, sendMessage, stop, reset } = useSyncAgent();
140
250
  const [isOpen, setIsOpen] = (0, import_react3.useState)(defaultOpen);
141
251
  const [input, setInput] = (0, import_react3.useState)("");
252
+ const [timestamps] = (0, import_react3.useState)(() => /* @__PURE__ */ new Map());
142
253
  const messagesEndRef = (0, import_react3.useRef)(null);
254
+ const inputRef = (0, import_react3.useRef)(null);
255
+ const msgCount = (0, import_react3.useRef)(0);
256
+ if (messages.length > msgCount.current) {
257
+ for (let i = msgCount.current; i < messages.length; i++) {
258
+ if (!timestamps.has(i)) timestamps.set(i, /* @__PURE__ */ new Date());
259
+ }
260
+ msgCount.current = messages.length;
261
+ }
143
262
  (0, import_react3.useEffect)(() => {
144
263
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
145
- }, [messages]);
146
- const handleSubmit = (e) => {
147
- e.preventDefault();
148
- if (!input.trim()) return;
149
- sendMessage(input);
264
+ }, [messages, isLoading]);
265
+ (0, import_react3.useEffect)(() => {
266
+ if (isOpen) setTimeout(() => inputRef.current?.focus(), 100);
267
+ }, [isOpen]);
268
+ const handleSend = (0, import_react3.useCallback)(() => {
269
+ const text = input.trim();
270
+ if (!text || isLoading) return;
150
271
  setInput("");
272
+ sendMessage(text);
273
+ }, [input, isLoading, sendMessage]);
274
+ const handleKeyDown = (e) => {
275
+ if (e.key === "Enter" && !e.shiftKey) {
276
+ e.preventDefault();
277
+ handleSend();
278
+ }
151
279
  };
152
- const panel = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { ...styles.panel, ...mode === "inline" ? styles.panelInline : {}, ...customStyle }, className, children: [
153
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { ...styles.header, background: `linear-gradient(135deg, ${accentColor}, ${adjustColor(accentColor, -20)})` }, children: [
154
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: title }),
155
- mode === "floating" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: styles.closeBtn, onClick: () => setIsOpen(false), children: "\xD7" })
280
+ const showSuggestions = messages.length === 0 && suggestions.length > 0;
281
+ const panel = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
282
+ ...s.panel,
283
+ ...mode === "inline" ? s.panelInline : {},
284
+ ...customStyle
285
+ }, className, children: [
286
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `
287
+ @keyframes sa-bounce {
288
+ 0%,80%,100% { transform: translateY(0); opacity:0.4 }
289
+ 40% { transform: translateY(-5px); opacity:1 }
290
+ }
291
+ @keyframes sa-fadein {
292
+ from { opacity:0; transform:translateY(6px) }
293
+ to { opacity:1; transform:translateY(0) }
294
+ }
295
+ .sa-msg-wrap { animation: sa-fadein 0.2s ease }
296
+ .sa-input:focus { outline:none; border-color:${accentColor} !important; box-shadow:0 0 0 3px ${accentColor}22 !important }
297
+ .sa-send:hover:not(:disabled) { opacity:0.88; transform:scale(1.04) }
298
+ .sa-send:disabled { opacity:0.4; cursor:not-allowed }
299
+ .sa-fab-btn:hover { transform:scale(1.08) }
300
+ ` }),
301
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
302
+ padding: "14px 16px",
303
+ background: `linear-gradient(135deg, ${accentColor}, ${adjustColor(accentColor, -30)})`,
304
+ display: "flex",
305
+ alignItems: "center",
306
+ justifyContent: "space-between",
307
+ flexShrink: 0
308
+ }, children: [
309
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
310
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
311
+ width: 34,
312
+ height: 34,
313
+ borderRadius: "50%",
314
+ background: "rgba(255,255,255,0.2)",
315
+ display: "flex",
316
+ alignItems: "center",
317
+ justifyContent: "center",
318
+ fontSize: 16
319
+ }, children: "\u2726" }),
320
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
321
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { color: "white", fontWeight: 700, fontSize: 14, lineHeight: 1.2 }, children: title }),
322
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { color: "rgba(255,255,255,0.75)", fontSize: 11 }, children: subtitle })
323
+ ] })
324
+ ] }),
325
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 4 }, children: [
326
+ messages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: reset, title: "Clear chat", style: {
327
+ background: "rgba(255,255,255,0.15)",
328
+ border: "none",
329
+ color: "white",
330
+ cursor: "pointer",
331
+ borderRadius: 6,
332
+ padding: "4px 8px",
333
+ fontSize: 11
334
+ }, children: "Clear" }),
335
+ mode === "floating" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => setIsOpen(false), style: {
336
+ background: "rgba(255,255,255,0.15)",
337
+ border: "none",
338
+ color: "white",
339
+ cursor: "pointer",
340
+ borderRadius: 6,
341
+ padding: "4px 8px",
342
+ fontSize: 16,
343
+ lineHeight: 1
344
+ }, children: "\xD7" })
345
+ ] })
156
346
  ] }),
157
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.messages, children: [
158
- messages.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: styles.welcome, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: 14, color: "#9ca3af" }, children: welcomeMessage }) }),
159
- messages.map((msg, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
160
- "div",
347
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: s.messages, children: [
348
+ messages.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: s.welcome, children: [
349
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
350
+ width: 48,
351
+ height: 48,
352
+ borderRadius: "50%",
353
+ margin: "0 auto 12px",
354
+ background: `linear-gradient(135deg, ${accentColor}22, ${accentColor}44)`,
355
+ display: "flex",
356
+ alignItems: "center",
357
+ justifyContent: "center",
358
+ fontSize: 22
359
+ }, children: "\u2726" }),
360
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: 14, color: "#64748b", lineHeight: 1.6, maxWidth: 260, margin: "0 auto" }, children: welcomeMessage })
361
+ ] }),
362
+ messages.map((msg, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sa-msg-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
363
+ MessageBubble,
161
364
  {
162
- style: {
163
- ...styles.msg,
164
- ...msg.role === "user" ? { ...styles.msgUser, background: accentColor } : styles.msgAi
165
- },
166
- children: msg.content || (isLoading && i === messages.length - 1 ? "..." : "")
167
- },
168
- i
169
- )),
365
+ role: msg.role,
366
+ content: msg.content,
367
+ isStreaming: isLoading && i === messages.length - 1 && msg.role === "assistant",
368
+ accentColor,
369
+ timestamp: timestamps.get(i) ?? /* @__PURE__ */ new Date()
370
+ }
371
+ ) }, i)),
372
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
373
+ padding: "10px 14px",
374
+ borderRadius: 10,
375
+ fontSize: 13,
376
+ background: "#fef2f2",
377
+ color: "#dc2626",
378
+ border: "1px solid #fecaca",
379
+ alignSelf: "flex-start"
380
+ }, children: [
381
+ "\u26A0\uFE0F ",
382
+ error.message
383
+ ] }),
170
384
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref: messagesEndRef })
171
385
  ] }),
172
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { style: styles.form, onSubmit: handleSubmit, children: [
173
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
174
- "input",
175
- {
176
- style: styles.input,
177
- value: input,
178
- onChange: (e) => setInput(e.target.value),
179
- placeholder,
180
- disabled: isLoading
181
- }
182
- ),
183
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
184
- "button",
185
- {
186
- type: "submit",
187
- disabled: isLoading || !input.trim(),
188
- style: { ...styles.sendBtn, background: accentColor },
189
- children: "Send"
190
- }
191
- )
386
+ showSuggestions && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
387
+ Suggestions,
388
+ {
389
+ items: suggestions,
390
+ onSelect: (s2) => {
391
+ setInput(s2);
392
+ inputRef.current?.focus();
393
+ },
394
+ accentColor
395
+ }
396
+ ),
397
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
398
+ padding: "10px 12px",
399
+ borderTop: "1px solid #f1f5f9",
400
+ background: "#fff",
401
+ flexShrink: 0
402
+ }, children: [
403
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
404
+ display: "flex",
405
+ gap: 8,
406
+ alignItems: "flex-end",
407
+ background: "#f8fafc",
408
+ borderRadius: 12,
409
+ border: "1px solid #e2e8f0",
410
+ padding: "6px 6px 6px 12px",
411
+ transition: "border-color 0.15s, box-shadow 0.15s"
412
+ }, children: [
413
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
414
+ "textarea",
415
+ {
416
+ ref: inputRef,
417
+ className: "sa-input",
418
+ value: input,
419
+ onChange: (e) => {
420
+ setInput(e.target.value);
421
+ e.target.style.height = "auto";
422
+ e.target.style.height = Math.min(e.target.scrollHeight, 120) + "px";
423
+ },
424
+ onKeyDown: handleKeyDown,
425
+ placeholder,
426
+ disabled: isLoading,
427
+ rows: 1,
428
+ style: {
429
+ flex: 1,
430
+ background: "none",
431
+ border: "none",
432
+ resize: "none",
433
+ fontSize: 13.5,
434
+ lineHeight: 1.5,
435
+ color: "#1e293b",
436
+ fontFamily: "inherit",
437
+ padding: 0,
438
+ maxHeight: 120,
439
+ outline: "none"
440
+ }
441
+ }
442
+ ),
443
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 4, alignItems: "flex-end", flexShrink: 0 }, children: [
444
+ isLoading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: stop, title: "Stop", style: {
445
+ background: "#fef2f2",
446
+ border: "1px solid #fecaca",
447
+ color: "#dc2626",
448
+ borderRadius: 8,
449
+ padding: "6px 10px",
450
+ cursor: "pointer",
451
+ fontSize: 12,
452
+ fontWeight: 600
453
+ }, children: "\u25A0 Stop" }),
454
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
455
+ "button",
456
+ {
457
+ className: "sa-send",
458
+ onClick: handleSend,
459
+ disabled: isLoading || !input.trim(),
460
+ style: {
461
+ width: 34,
462
+ height: 34,
463
+ borderRadius: 8,
464
+ border: "none",
465
+ background: input.trim() && !isLoading ? accentColor : "#e2e8f0",
466
+ color: input.trim() && !isLoading ? "white" : "#94a3b8",
467
+ cursor: "pointer",
468
+ display: "flex",
469
+ alignItems: "center",
470
+ justifyContent: "center",
471
+ transition: "all 0.15s",
472
+ flexShrink: 0
473
+ },
474
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) })
475
+ }
476
+ )
477
+ ] })
478
+ ] }),
479
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { textAlign: "center", marginTop: 6, fontSize: 10, color: "#cbd5e1" }, children: [
480
+ "Powered by ",
481
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: accentColor, fontWeight: 600 }, children: "SyncAgent" }),
482
+ " \xB7 Enter to send \xB7 Shift+Enter for new line"
483
+ ] })
192
484
  ] })
193
485
  ] });
194
486
  if (mode === "inline") return panel;
195
487
  const isLeft = position === "bottom-left";
196
488
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
197
- isOpen && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...styles.floatingPanel, ...isLeft ? { left: 24, right: "auto" } : {} }, children: panel }),
198
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
489
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
490
+ position: "fixed",
491
+ bottom: 88,
492
+ zIndex: 99998,
493
+ ...isLeft ? { left: 20 } : { right: 20 },
494
+ width: 420,
495
+ maxWidth: "calc(100vw - 40px)",
496
+ transition: "opacity 0.25s, transform 0.25s",
497
+ opacity: isOpen ? 1 : 0,
498
+ transform: isOpen ? "translateY(0) scale(1)" : "translateY(16px) scale(0.96)",
499
+ pointerEvents: isOpen ? "auto" : "none"
500
+ }, children: panel }),
501
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
199
502
  "button",
200
503
  {
504
+ className: "sa-fab-btn",
201
505
  onClick: () => setIsOpen(!isOpen),
202
506
  style: {
203
- ...styles.fab,
204
- background: `linear-gradient(135deg, ${accentColor}, ${adjustColor(accentColor, -20)})`,
205
- ...isLeft ? { left: 24, right: "auto" } : {}
507
+ position: "fixed",
508
+ bottom: 20,
509
+ zIndex: 99999,
510
+ ...isLeft ? { left: 20 } : { right: 20 },
511
+ width: 56,
512
+ height: 56,
513
+ borderRadius: "50%",
514
+ border: "none",
515
+ background: `linear-gradient(135deg, ${accentColor}, ${adjustColor(accentColor, -30)})`,
516
+ cursor: "pointer",
517
+ boxShadow: `0 4px 20px ${accentColor}55, 0 2px 8px rgba(0,0,0,0.15)`,
518
+ display: "flex",
519
+ alignItems: "center",
520
+ justifyContent: "center",
521
+ transition: "transform 0.2s, box-shadow 0.2s"
206
522
  },
207
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "28", height: "28", viewBox: "0 0 24 24", fill: "white", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z" }) })
523
+ children: [
524
+ isOpen ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "white", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }) }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "white", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z" }) }),
525
+ !isOpen && messages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
526
+ position: "absolute",
527
+ top: 2,
528
+ right: 2,
529
+ width: 12,
530
+ height: 12,
531
+ borderRadius: "50%",
532
+ background: "#ef4444",
533
+ border: "2px solid white"
534
+ } })
535
+ ]
208
536
  }
209
537
  )
210
538
  ] });
211
539
  }
212
- function SyncAgentChat({ config, ...props }) {
213
- if (config) {
214
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SyncAgentProvider, { config, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChatInner, { ...props }) });
215
- }
216
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChatInner, { ...props });
217
- }
218
- function adjustColor(hex, amount) {
219
- const num = parseInt(hex.replace("#", ""), 16);
220
- const r = Math.min(255, Math.max(0, (num >> 16 & 255) + amount));
221
- const g = Math.min(255, Math.max(0, (num >> 8 & 255) + amount));
222
- const b = Math.min(255, Math.max(0, (num & 255) + amount));
223
- return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
224
- }
225
- var styles = {
226
- fab: {
227
- position: "fixed",
228
- bottom: 24,
229
- right: 24,
230
- zIndex: 99999,
231
- width: 56,
232
- height: 56,
233
- borderRadius: "50%",
234
- border: "none",
235
- cursor: "pointer",
236
- boxShadow: "0 4px 20px rgba(0,0,0,0.2)",
237
- display: "flex",
238
- alignItems: "center",
239
- justifyContent: "center"
240
- },
241
- floatingPanel: {
242
- position: "fixed",
243
- bottom: 96,
244
- right: 24,
245
- zIndex: 99998,
246
- width: 400,
247
- maxWidth: "calc(100vw - 48px)"
248
- },
540
+ var s = {
249
541
  panel: {
250
- height: 560,
251
- maxHeight: "calc(100vh - 120px)",
252
- background: "#fff",
253
- borderRadius: 16,
254
- boxShadow: "0 8px 40px rgba(0,0,0,0.15)",
542
+ height: 580,
543
+ maxHeight: "calc(100vh - 110px)",
544
+ background: "#ffffff",
545
+ borderRadius: 18,
546
+ boxShadow: "0 20px 60px rgba(0,0,0,0.15), 0 4px 20px rgba(0,0,0,0.08)",
255
547
  display: "flex",
256
548
  flexDirection: "column",
257
549
  overflow: "hidden",
258
- fontFamily: "system-ui, -apple-system, sans-serif"
550
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
551
+ border: "1px solid rgba(0,0,0,0.06)"
259
552
  },
260
553
  panelInline: {
261
554
  height: "100%",
262
555
  maxHeight: "none",
263
- borderRadius: 12,
264
- boxShadow: "0 2px 12px rgba(0,0,0,0.1)"
265
- },
266
- header: {
267
- padding: "16px",
268
- color: "white",
269
- fontWeight: 600,
270
- fontSize: 15,
271
- display: "flex",
272
- alignItems: "center",
273
- justifyContent: "space-between"
274
- },
275
- closeBtn: {
276
- background: "none",
277
- border: "none",
278
- color: "white",
279
- cursor: "pointer",
280
- padding: 4,
281
- fontSize: 20
556
+ borderRadius: 14,
557
+ boxShadow: "0 2px 16px rgba(0,0,0,0.08)"
282
558
  },
283
559
  messages: {
284
560
  flex: 1,
285
561
  overflowY: "auto",
286
- padding: 16,
562
+ padding: "16px 16px 8px",
287
563
  display: "flex",
288
564
  flexDirection: "column",
289
- gap: 12
290
- },
291
- welcome: { textAlign: "center", padding: "40px 20px" },
292
- msg: {
293
- maxWidth: "85%",
294
- padding: "10px 14px",
295
- borderRadius: 12,
296
- fontSize: 14,
297
- lineHeight: 1.5,
298
- wordBreak: "break-word",
299
- whiteSpace: "pre-wrap"
300
- },
301
- msgUser: {
302
- alignSelf: "flex-end",
303
- color: "white",
304
- borderBottomRightRadius: 4
305
- },
306
- msgAi: {
307
- alignSelf: "flex-start",
308
- background: "#f3f4f6",
309
- color: "#1f2937",
310
- borderBottomLeftRadius: 4
311
- },
312
- form: {
313
- padding: "12px 16px",
314
- borderTop: "1px solid #e5e7eb",
315
- display: "flex",
316
- gap: 8
565
+ gap: 14,
566
+ scrollbarWidth: "thin"
317
567
  },
318
- input: {
568
+ welcome: {
569
+ textAlign: "center",
570
+ padding: "32px 20px",
319
571
  flex: 1,
320
- padding: "10px 14px",
321
- border: "1px solid #d1d5db",
322
- borderRadius: 10,
323
- fontSize: 14,
324
- outline: "none"
325
- },
326
- sendBtn: {
327
- padding: "10px 16px",
328
- color: "white",
329
- border: "none",
330
- borderRadius: 10,
331
- cursor: "pointer",
332
- fontWeight: 600,
333
- fontSize: 14
572
+ display: "flex",
573
+ flexDirection: "column",
574
+ alignItems: "center",
575
+ justifyContent: "center"
334
576
  }
335
577
  };
578
+ function adjustColor(hex, amount) {
579
+ const num = parseInt(hex.replace("#", ""), 16);
580
+ const r = Math.min(255, Math.max(0, (num >> 16 & 255) + amount));
581
+ const g = Math.min(255, Math.max(0, (num >> 8 & 255) + amount));
582
+ const b = Math.min(255, Math.max(0, (num & 255) + amount));
583
+ return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
584
+ }
585
+ function SyncAgentChat({ config, ...props }) {
586
+ if (config) {
587
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SyncAgentProvider, { config, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChatInner, { ...props }) });
588
+ }
589
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChatInner, { ...props });
590
+ }
336
591
 
337
592
  // src/index.ts
338
593
  var import_js2 = require("@syncagent/js");
package/dist/index.mjs CHANGED
@@ -94,216 +94,476 @@ function useSyncAgent(options = {}) {
94
94
  }
95
95
 
96
96
  // src/chat.tsx
97
- import { useState as useState2, useRef as useRef2, useEffect } from "react";
97
+ import {
98
+ useState as useState2,
99
+ useRef as useRef2,
100
+ useEffect,
101
+ useCallback as useCallback2
102
+ } from "react";
98
103
  import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
104
+ function renderMarkdown(text) {
105
+ return text.replace(/```[\w]*\n?([\s\S]*?)```/g, `<pre style="background:#1e1e2e;color:#cdd6f4;padding:12px 14px;border-radius:8px;overflow-x:auto;font-size:12px;line-height:1.6;margin:8px 0;font-family:'Fira Code','Cascadia Code',monospace">$1</pre>`).replace(/`([^`]+)`/g, '<code style="background:rgba(0,0,0,0.08);padding:2px 6px;border-radius:4px;font-size:12px;font-family:monospace">$1</code>').replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/\*(.+?)\*/g, "<em>$1</em>").replace(/^### (.+)$/gm, '<div style="font-weight:700;font-size:13px;margin:10px 0 4px">$1</div>').replace(/^## (.+)$/gm, '<div style="font-weight:700;font-size:14px;margin:12px 0 4px">$1</div>').replace(/^# (.+)$/gm, '<div style="font-weight:700;font-size:15px;margin:12px 0 6px">$1</div>').replace(/\|(.+)\|\n\|[-| :]+\|\n((?:\|.+\|\n?)+)/g, (_, header, rows) => {
106
+ const ths = header.split("|").filter((c) => c.trim()).map((c) => `<th style="padding:6px 10px;text-align:left;font-weight:600;border-bottom:2px solid rgba(0,0,0,0.1)">${c.trim()}</th>`).join("");
107
+ const trs = rows.trim().split("\n").map((row) => {
108
+ const tds = row.split("|").filter((c) => c.trim()).map((c) => `<td style="padding:6px 10px;border-bottom:1px solid rgba(0,0,0,0.06)">${c.trim()}</td>`).join("");
109
+ return `<tr>${tds}</tr>`;
110
+ }).join("");
111
+ return `<div style="overflow-x:auto;margin:8px 0"><table style="width:100%;border-collapse:collapse;font-size:13px"><thead><tr>${ths}</tr></thead><tbody>${trs}</tbody></table></div>`;
112
+ }).replace(/^[-*] (.+)$/gm, '<li style="margin:2px 0;padding-left:4px">$1</li>').replace(/(<li[^>]*>.*<\/li>\n?)+/g, (m) => `<ul style="margin:6px 0;padding-left:18px;list-style:disc">${m}</ul>`).replace(/^\d+\. (.+)$/gm, '<li style="margin:2px 0;padding-left:4px">$1</li>').replace(/\n\n/g, "<br/><br/>").replace(/\n/g, "<br/>");
113
+ }
114
+ function TypingDots({ color }) {
115
+ return /* @__PURE__ */ jsx2("span", { style: { display: "inline-flex", gap: 4, alignItems: "center", padding: "2px 0" }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx2("span", { style: {
116
+ width: 6,
117
+ height: 6,
118
+ borderRadius: "50%",
119
+ background: color,
120
+ animation: "sa-bounce 1.2s infinite",
121
+ animationDelay: `${i * 0.2}s`,
122
+ display: "inline-block"
123
+ } }, i)) });
124
+ }
125
+ function CopyBtn({ text }) {
126
+ const [copied, setCopied] = useState2(false);
127
+ const copy = useCallback2(() => {
128
+ navigator.clipboard?.writeText(text).then(() => {
129
+ setCopied(true);
130
+ setTimeout(() => setCopied(false), 1500);
131
+ });
132
+ }, [text]);
133
+ return /* @__PURE__ */ jsx2("button", { onClick: copy, title: "Copy", style: {
134
+ background: "none",
135
+ border: "none",
136
+ cursor: "pointer",
137
+ padding: "2px 6px",
138
+ borderRadius: 4,
139
+ fontSize: 11,
140
+ color: "#9ca3af",
141
+ opacity: 0.7,
142
+ transition: "opacity 0.15s"
143
+ }, children: copied ? "\u2713" : "\u2398" });
144
+ }
145
+ function MessageBubble({
146
+ role,
147
+ content,
148
+ isStreaming,
149
+ accentColor,
150
+ timestamp
151
+ }) {
152
+ const isUser = role === "user";
153
+ const timeStr = timestamp.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
154
+ return /* @__PURE__ */ jsxs("div", { style: {
155
+ display: "flex",
156
+ flexDirection: "column",
157
+ alignItems: isUser ? "flex-end" : "flex-start",
158
+ gap: 3,
159
+ maxWidth: "88%",
160
+ alignSelf: isUser ? "flex-end" : "flex-start"
161
+ }, children: [
162
+ /* @__PURE__ */ jsxs("div", { style: {
163
+ display: "flex",
164
+ alignItems: "center",
165
+ gap: 6,
166
+ flexDirection: isUser ? "row-reverse" : "row"
167
+ }, children: [
168
+ /* @__PURE__ */ jsx2("div", { style: {
169
+ width: 24,
170
+ height: 24,
171
+ borderRadius: "50%",
172
+ background: isUser ? accentColor : "linear-gradient(135deg,#6366f1,#8b5cf6)",
173
+ display: "flex",
174
+ alignItems: "center",
175
+ justifyContent: "center",
176
+ fontSize: 11,
177
+ color: "white",
178
+ fontWeight: 700,
179
+ flexShrink: 0
180
+ }, children: isUser ? "U" : "\u2726" }),
181
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: 11, color: "#9ca3af", fontWeight: 500 }, children: isUser ? "You" : "SyncAgent" }),
182
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: 10, color: "#d1d5db" }, children: timeStr })
183
+ ] }),
184
+ /* @__PURE__ */ jsx2("div", { style: {
185
+ padding: "10px 14px",
186
+ borderRadius: isUser ? "16px 4px 16px 16px" : "4px 16px 16px 16px",
187
+ background: isUser ? accentColor : "#f8fafc",
188
+ color: isUser ? "white" : "#1e293b",
189
+ fontSize: 13.5,
190
+ lineHeight: 1.6,
191
+ wordBreak: "break-word",
192
+ border: isUser ? "none" : "1px solid #e2e8f0",
193
+ boxShadow: isUser ? `0 2px 8px ${accentColor}33` : "0 1px 4px rgba(0,0,0,0.06)",
194
+ position: "relative"
195
+ }, children: isStreaming && !content ? /* @__PURE__ */ jsx2(TypingDots, { color: accentColor }) : isUser ? /* @__PURE__ */ jsx2("span", { style: { whiteSpace: "pre-wrap" }, children: content }) : /* @__PURE__ */ jsx2("div", { dangerouslySetInnerHTML: { __html: renderMarkdown(content) } }) }),
196
+ !isUser && content && !isStreaming && /* @__PURE__ */ jsx2("div", { style: { paddingLeft: 30 }, children: /* @__PURE__ */ jsx2(CopyBtn, { text: content }) })
197
+ ] });
198
+ }
199
+ function Suggestions({ items, onSelect, accentColor }) {
200
+ return /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, padding: "0 16px 12px" }, children: items.map((s2) => /* @__PURE__ */ jsx2("button", { onClick: () => onSelect(s2), style: {
201
+ padding: "5px 12px",
202
+ borderRadius: 20,
203
+ fontSize: 12,
204
+ cursor: "pointer",
205
+ border: `1px solid ${accentColor}44`,
206
+ background: `${accentColor}0d`,
207
+ color: accentColor,
208
+ fontWeight: 500,
209
+ transition: "all 0.15s"
210
+ }, children: s2 }, s2)) });
211
+ }
99
212
  function ChatInner({
100
213
  mode = "floating",
101
214
  position = "bottom-right",
102
215
  defaultOpen = false,
103
- title = "\u2728 AI Assistant",
104
- placeholder = "Ask about your data...",
105
- welcomeMessage = "Ask me anything about your data. I can query, create, update, and analyze your database.",
216
+ title = "SyncAgent",
217
+ subtitle = "AI Database Assistant",
218
+ placeholder = "Ask anything about your data...",
219
+ welcomeMessage = "Hi! I can query, analyze, and manage your database using natural language. What would you like to know?",
106
220
  accentColor = "#10b981",
107
221
  className,
108
- style: customStyle
222
+ style: customStyle,
223
+ suggestions = ["Show all records", "Count total entries", "Show recent activity"]
109
224
  }) {
110
- const { messages, isLoading, sendMessage } = useSyncAgent();
225
+ const { messages, isLoading, error, sendMessage, stop, reset } = useSyncAgent();
111
226
  const [isOpen, setIsOpen] = useState2(defaultOpen);
112
227
  const [input, setInput] = useState2("");
228
+ const [timestamps] = useState2(() => /* @__PURE__ */ new Map());
113
229
  const messagesEndRef = useRef2(null);
230
+ const inputRef = useRef2(null);
231
+ const msgCount = useRef2(0);
232
+ if (messages.length > msgCount.current) {
233
+ for (let i = msgCount.current; i < messages.length; i++) {
234
+ if (!timestamps.has(i)) timestamps.set(i, /* @__PURE__ */ new Date());
235
+ }
236
+ msgCount.current = messages.length;
237
+ }
114
238
  useEffect(() => {
115
239
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
116
- }, [messages]);
117
- const handleSubmit = (e) => {
118
- e.preventDefault();
119
- if (!input.trim()) return;
120
- sendMessage(input);
240
+ }, [messages, isLoading]);
241
+ useEffect(() => {
242
+ if (isOpen) setTimeout(() => inputRef.current?.focus(), 100);
243
+ }, [isOpen]);
244
+ const handleSend = useCallback2(() => {
245
+ const text = input.trim();
246
+ if (!text || isLoading) return;
121
247
  setInput("");
248
+ sendMessage(text);
249
+ }, [input, isLoading, sendMessage]);
250
+ const handleKeyDown = (e) => {
251
+ if (e.key === "Enter" && !e.shiftKey) {
252
+ e.preventDefault();
253
+ handleSend();
254
+ }
122
255
  };
123
- const panel = /* @__PURE__ */ jsxs("div", { style: { ...styles.panel, ...mode === "inline" ? styles.panelInline : {}, ...customStyle }, className, children: [
124
- /* @__PURE__ */ jsxs("div", { style: { ...styles.header, background: `linear-gradient(135deg, ${accentColor}, ${adjustColor(accentColor, -20)})` }, children: [
125
- /* @__PURE__ */ jsx2("span", { children: title }),
126
- mode === "floating" && /* @__PURE__ */ jsx2("button", { style: styles.closeBtn, onClick: () => setIsOpen(false), children: "\xD7" })
256
+ const showSuggestions = messages.length === 0 && suggestions.length > 0;
257
+ const panel = /* @__PURE__ */ jsxs("div", { style: {
258
+ ...s.panel,
259
+ ...mode === "inline" ? s.panelInline : {},
260
+ ...customStyle
261
+ }, className, children: [
262
+ /* @__PURE__ */ jsx2("style", { children: `
263
+ @keyframes sa-bounce {
264
+ 0%,80%,100% { transform: translateY(0); opacity:0.4 }
265
+ 40% { transform: translateY(-5px); opacity:1 }
266
+ }
267
+ @keyframes sa-fadein {
268
+ from { opacity:0; transform:translateY(6px) }
269
+ to { opacity:1; transform:translateY(0) }
270
+ }
271
+ .sa-msg-wrap { animation: sa-fadein 0.2s ease }
272
+ .sa-input:focus { outline:none; border-color:${accentColor} !important; box-shadow:0 0 0 3px ${accentColor}22 !important }
273
+ .sa-send:hover:not(:disabled) { opacity:0.88; transform:scale(1.04) }
274
+ .sa-send:disabled { opacity:0.4; cursor:not-allowed }
275
+ .sa-fab-btn:hover { transform:scale(1.08) }
276
+ ` }),
277
+ /* @__PURE__ */ jsxs("div", { style: {
278
+ padding: "14px 16px",
279
+ background: `linear-gradient(135deg, ${accentColor}, ${adjustColor(accentColor, -30)})`,
280
+ display: "flex",
281
+ alignItems: "center",
282
+ justifyContent: "space-between",
283
+ flexShrink: 0
284
+ }, children: [
285
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
286
+ /* @__PURE__ */ jsx2("div", { style: {
287
+ width: 34,
288
+ height: 34,
289
+ borderRadius: "50%",
290
+ background: "rgba(255,255,255,0.2)",
291
+ display: "flex",
292
+ alignItems: "center",
293
+ justifyContent: "center",
294
+ fontSize: 16
295
+ }, children: "\u2726" }),
296
+ /* @__PURE__ */ jsxs("div", { children: [
297
+ /* @__PURE__ */ jsx2("div", { style: { color: "white", fontWeight: 700, fontSize: 14, lineHeight: 1.2 }, children: title }),
298
+ /* @__PURE__ */ jsx2("div", { style: { color: "rgba(255,255,255,0.75)", fontSize: 11 }, children: subtitle })
299
+ ] })
300
+ ] }),
301
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 4 }, children: [
302
+ messages.length > 0 && /* @__PURE__ */ jsx2("button", { onClick: reset, title: "Clear chat", style: {
303
+ background: "rgba(255,255,255,0.15)",
304
+ border: "none",
305
+ color: "white",
306
+ cursor: "pointer",
307
+ borderRadius: 6,
308
+ padding: "4px 8px",
309
+ fontSize: 11
310
+ }, children: "Clear" }),
311
+ mode === "floating" && /* @__PURE__ */ jsx2("button", { onClick: () => setIsOpen(false), style: {
312
+ background: "rgba(255,255,255,0.15)",
313
+ border: "none",
314
+ color: "white",
315
+ cursor: "pointer",
316
+ borderRadius: 6,
317
+ padding: "4px 8px",
318
+ fontSize: 16,
319
+ lineHeight: 1
320
+ }, children: "\xD7" })
321
+ ] })
127
322
  ] }),
128
- /* @__PURE__ */ jsxs("div", { style: styles.messages, children: [
129
- messages.length === 0 && /* @__PURE__ */ jsx2("div", { style: styles.welcome, children: /* @__PURE__ */ jsx2("p", { style: { fontSize: 14, color: "#9ca3af" }, children: welcomeMessage }) }),
130
- messages.map((msg, i) => /* @__PURE__ */ jsx2(
131
- "div",
323
+ /* @__PURE__ */ jsxs("div", { style: s.messages, children: [
324
+ messages.length === 0 && /* @__PURE__ */ jsxs("div", { style: s.welcome, children: [
325
+ /* @__PURE__ */ jsx2("div", { style: {
326
+ width: 48,
327
+ height: 48,
328
+ borderRadius: "50%",
329
+ margin: "0 auto 12px",
330
+ background: `linear-gradient(135deg, ${accentColor}22, ${accentColor}44)`,
331
+ display: "flex",
332
+ alignItems: "center",
333
+ justifyContent: "center",
334
+ fontSize: 22
335
+ }, children: "\u2726" }),
336
+ /* @__PURE__ */ jsx2("p", { style: { fontSize: 14, color: "#64748b", lineHeight: 1.6, maxWidth: 260, margin: "0 auto" }, children: welcomeMessage })
337
+ ] }),
338
+ messages.map((msg, i) => /* @__PURE__ */ jsx2("div", { className: "sa-msg-wrap", children: /* @__PURE__ */ jsx2(
339
+ MessageBubble,
132
340
  {
133
- style: {
134
- ...styles.msg,
135
- ...msg.role === "user" ? { ...styles.msgUser, background: accentColor } : styles.msgAi
136
- },
137
- children: msg.content || (isLoading && i === messages.length - 1 ? "..." : "")
138
- },
139
- i
140
- )),
341
+ role: msg.role,
342
+ content: msg.content,
343
+ isStreaming: isLoading && i === messages.length - 1 && msg.role === "assistant",
344
+ accentColor,
345
+ timestamp: timestamps.get(i) ?? /* @__PURE__ */ new Date()
346
+ }
347
+ ) }, i)),
348
+ error && /* @__PURE__ */ jsxs("div", { style: {
349
+ padding: "10px 14px",
350
+ borderRadius: 10,
351
+ fontSize: 13,
352
+ background: "#fef2f2",
353
+ color: "#dc2626",
354
+ border: "1px solid #fecaca",
355
+ alignSelf: "flex-start"
356
+ }, children: [
357
+ "\u26A0\uFE0F ",
358
+ error.message
359
+ ] }),
141
360
  /* @__PURE__ */ jsx2("div", { ref: messagesEndRef })
142
361
  ] }),
143
- /* @__PURE__ */ jsxs("form", { style: styles.form, onSubmit: handleSubmit, children: [
144
- /* @__PURE__ */ jsx2(
145
- "input",
146
- {
147
- style: styles.input,
148
- value: input,
149
- onChange: (e) => setInput(e.target.value),
150
- placeholder,
151
- disabled: isLoading
152
- }
153
- ),
154
- /* @__PURE__ */ jsx2(
155
- "button",
156
- {
157
- type: "submit",
158
- disabled: isLoading || !input.trim(),
159
- style: { ...styles.sendBtn, background: accentColor },
160
- children: "Send"
161
- }
162
- )
362
+ showSuggestions && /* @__PURE__ */ jsx2(
363
+ Suggestions,
364
+ {
365
+ items: suggestions,
366
+ onSelect: (s2) => {
367
+ setInput(s2);
368
+ inputRef.current?.focus();
369
+ },
370
+ accentColor
371
+ }
372
+ ),
373
+ /* @__PURE__ */ jsxs("div", { style: {
374
+ padding: "10px 12px",
375
+ borderTop: "1px solid #f1f5f9",
376
+ background: "#fff",
377
+ flexShrink: 0
378
+ }, children: [
379
+ /* @__PURE__ */ jsxs("div", { style: {
380
+ display: "flex",
381
+ gap: 8,
382
+ alignItems: "flex-end",
383
+ background: "#f8fafc",
384
+ borderRadius: 12,
385
+ border: "1px solid #e2e8f0",
386
+ padding: "6px 6px 6px 12px",
387
+ transition: "border-color 0.15s, box-shadow 0.15s"
388
+ }, children: [
389
+ /* @__PURE__ */ jsx2(
390
+ "textarea",
391
+ {
392
+ ref: inputRef,
393
+ className: "sa-input",
394
+ value: input,
395
+ onChange: (e) => {
396
+ setInput(e.target.value);
397
+ e.target.style.height = "auto";
398
+ e.target.style.height = Math.min(e.target.scrollHeight, 120) + "px";
399
+ },
400
+ onKeyDown: handleKeyDown,
401
+ placeholder,
402
+ disabled: isLoading,
403
+ rows: 1,
404
+ style: {
405
+ flex: 1,
406
+ background: "none",
407
+ border: "none",
408
+ resize: "none",
409
+ fontSize: 13.5,
410
+ lineHeight: 1.5,
411
+ color: "#1e293b",
412
+ fontFamily: "inherit",
413
+ padding: 0,
414
+ maxHeight: 120,
415
+ outline: "none"
416
+ }
417
+ }
418
+ ),
419
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 4, alignItems: "flex-end", flexShrink: 0 }, children: [
420
+ isLoading && /* @__PURE__ */ jsx2("button", { onClick: stop, title: "Stop", style: {
421
+ background: "#fef2f2",
422
+ border: "1px solid #fecaca",
423
+ color: "#dc2626",
424
+ borderRadius: 8,
425
+ padding: "6px 10px",
426
+ cursor: "pointer",
427
+ fontSize: 12,
428
+ fontWeight: 600
429
+ }, children: "\u25A0 Stop" }),
430
+ /* @__PURE__ */ jsx2(
431
+ "button",
432
+ {
433
+ className: "sa-send",
434
+ onClick: handleSend,
435
+ disabled: isLoading || !input.trim(),
436
+ style: {
437
+ width: 34,
438
+ height: 34,
439
+ borderRadius: 8,
440
+ border: "none",
441
+ background: input.trim() && !isLoading ? accentColor : "#e2e8f0",
442
+ color: input.trim() && !isLoading ? "white" : "#94a3b8",
443
+ cursor: "pointer",
444
+ display: "flex",
445
+ alignItems: "center",
446
+ justifyContent: "center",
447
+ transition: "all 0.15s",
448
+ flexShrink: 0
449
+ },
450
+ children: /* @__PURE__ */ jsx2("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) })
451
+ }
452
+ )
453
+ ] })
454
+ ] }),
455
+ /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", marginTop: 6, fontSize: 10, color: "#cbd5e1" }, children: [
456
+ "Powered by ",
457
+ /* @__PURE__ */ jsx2("span", { style: { color: accentColor, fontWeight: 600 }, children: "SyncAgent" }),
458
+ " \xB7 Enter to send \xB7 Shift+Enter for new line"
459
+ ] })
163
460
  ] })
164
461
  ] });
165
462
  if (mode === "inline") return panel;
166
463
  const isLeft = position === "bottom-left";
167
464
  return /* @__PURE__ */ jsxs(Fragment, { children: [
168
- isOpen && /* @__PURE__ */ jsx2("div", { style: { ...styles.floatingPanel, ...isLeft ? { left: 24, right: "auto" } : {} }, children: panel }),
169
- /* @__PURE__ */ jsx2(
465
+ /* @__PURE__ */ jsx2("div", { style: {
466
+ position: "fixed",
467
+ bottom: 88,
468
+ zIndex: 99998,
469
+ ...isLeft ? { left: 20 } : { right: 20 },
470
+ width: 420,
471
+ maxWidth: "calc(100vw - 40px)",
472
+ transition: "opacity 0.25s, transform 0.25s",
473
+ opacity: isOpen ? 1 : 0,
474
+ transform: isOpen ? "translateY(0) scale(1)" : "translateY(16px) scale(0.96)",
475
+ pointerEvents: isOpen ? "auto" : "none"
476
+ }, children: panel }),
477
+ /* @__PURE__ */ jsxs(
170
478
  "button",
171
479
  {
480
+ className: "sa-fab-btn",
172
481
  onClick: () => setIsOpen(!isOpen),
173
482
  style: {
174
- ...styles.fab,
175
- background: `linear-gradient(135deg, ${accentColor}, ${adjustColor(accentColor, -20)})`,
176
- ...isLeft ? { left: 24, right: "auto" } : {}
483
+ position: "fixed",
484
+ bottom: 20,
485
+ zIndex: 99999,
486
+ ...isLeft ? { left: 20 } : { right: 20 },
487
+ width: 56,
488
+ height: 56,
489
+ borderRadius: "50%",
490
+ border: "none",
491
+ background: `linear-gradient(135deg, ${accentColor}, ${adjustColor(accentColor, -30)})`,
492
+ cursor: "pointer",
493
+ boxShadow: `0 4px 20px ${accentColor}55, 0 2px 8px rgba(0,0,0,0.15)`,
494
+ display: "flex",
495
+ alignItems: "center",
496
+ justifyContent: "center",
497
+ transition: "transform 0.2s, box-shadow 0.2s"
177
498
  },
178
- children: /* @__PURE__ */ jsx2("svg", { width: "28", height: "28", viewBox: "0 0 24 24", fill: "white", children: /* @__PURE__ */ jsx2("path", { d: "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z" }) })
499
+ children: [
500
+ isOpen ? /* @__PURE__ */ jsx2("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "white", children: /* @__PURE__ */ jsx2("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }) }) : /* @__PURE__ */ jsx2("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "white", children: /* @__PURE__ */ jsx2("path", { d: "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z" }) }),
501
+ !isOpen && messages.length > 0 && /* @__PURE__ */ jsx2("div", { style: {
502
+ position: "absolute",
503
+ top: 2,
504
+ right: 2,
505
+ width: 12,
506
+ height: 12,
507
+ borderRadius: "50%",
508
+ background: "#ef4444",
509
+ border: "2px solid white"
510
+ } })
511
+ ]
179
512
  }
180
513
  )
181
514
  ] });
182
515
  }
183
- function SyncAgentChat({ config, ...props }) {
184
- if (config) {
185
- return /* @__PURE__ */ jsx2(SyncAgentProvider, { config, children: /* @__PURE__ */ jsx2(ChatInner, { ...props }) });
186
- }
187
- return /* @__PURE__ */ jsx2(ChatInner, { ...props });
188
- }
189
- function adjustColor(hex, amount) {
190
- const num = parseInt(hex.replace("#", ""), 16);
191
- const r = Math.min(255, Math.max(0, (num >> 16 & 255) + amount));
192
- const g = Math.min(255, Math.max(0, (num >> 8 & 255) + amount));
193
- const b = Math.min(255, Math.max(0, (num & 255) + amount));
194
- return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
195
- }
196
- var styles = {
197
- fab: {
198
- position: "fixed",
199
- bottom: 24,
200
- right: 24,
201
- zIndex: 99999,
202
- width: 56,
203
- height: 56,
204
- borderRadius: "50%",
205
- border: "none",
206
- cursor: "pointer",
207
- boxShadow: "0 4px 20px rgba(0,0,0,0.2)",
208
- display: "flex",
209
- alignItems: "center",
210
- justifyContent: "center"
211
- },
212
- floatingPanel: {
213
- position: "fixed",
214
- bottom: 96,
215
- right: 24,
216
- zIndex: 99998,
217
- width: 400,
218
- maxWidth: "calc(100vw - 48px)"
219
- },
516
+ var s = {
220
517
  panel: {
221
- height: 560,
222
- maxHeight: "calc(100vh - 120px)",
223
- background: "#fff",
224
- borderRadius: 16,
225
- boxShadow: "0 8px 40px rgba(0,0,0,0.15)",
518
+ height: 580,
519
+ maxHeight: "calc(100vh - 110px)",
520
+ background: "#ffffff",
521
+ borderRadius: 18,
522
+ boxShadow: "0 20px 60px rgba(0,0,0,0.15), 0 4px 20px rgba(0,0,0,0.08)",
226
523
  display: "flex",
227
524
  flexDirection: "column",
228
525
  overflow: "hidden",
229
- fontFamily: "system-ui, -apple-system, sans-serif"
526
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
527
+ border: "1px solid rgba(0,0,0,0.06)"
230
528
  },
231
529
  panelInline: {
232
530
  height: "100%",
233
531
  maxHeight: "none",
234
- borderRadius: 12,
235
- boxShadow: "0 2px 12px rgba(0,0,0,0.1)"
236
- },
237
- header: {
238
- padding: "16px",
239
- color: "white",
240
- fontWeight: 600,
241
- fontSize: 15,
242
- display: "flex",
243
- alignItems: "center",
244
- justifyContent: "space-between"
245
- },
246
- closeBtn: {
247
- background: "none",
248
- border: "none",
249
- color: "white",
250
- cursor: "pointer",
251
- padding: 4,
252
- fontSize: 20
532
+ borderRadius: 14,
533
+ boxShadow: "0 2px 16px rgba(0,0,0,0.08)"
253
534
  },
254
535
  messages: {
255
536
  flex: 1,
256
537
  overflowY: "auto",
257
- padding: 16,
538
+ padding: "16px 16px 8px",
258
539
  display: "flex",
259
540
  flexDirection: "column",
260
- gap: 12
261
- },
262
- welcome: { textAlign: "center", padding: "40px 20px" },
263
- msg: {
264
- maxWidth: "85%",
265
- padding: "10px 14px",
266
- borderRadius: 12,
267
- fontSize: 14,
268
- lineHeight: 1.5,
269
- wordBreak: "break-word",
270
- whiteSpace: "pre-wrap"
271
- },
272
- msgUser: {
273
- alignSelf: "flex-end",
274
- color: "white",
275
- borderBottomRightRadius: 4
276
- },
277
- msgAi: {
278
- alignSelf: "flex-start",
279
- background: "#f3f4f6",
280
- color: "#1f2937",
281
- borderBottomLeftRadius: 4
282
- },
283
- form: {
284
- padding: "12px 16px",
285
- borderTop: "1px solid #e5e7eb",
286
- display: "flex",
287
- gap: 8
541
+ gap: 14,
542
+ scrollbarWidth: "thin"
288
543
  },
289
- input: {
544
+ welcome: {
545
+ textAlign: "center",
546
+ padding: "32px 20px",
290
547
  flex: 1,
291
- padding: "10px 14px",
292
- border: "1px solid #d1d5db",
293
- borderRadius: 10,
294
- fontSize: 14,
295
- outline: "none"
296
- },
297
- sendBtn: {
298
- padding: "10px 16px",
299
- color: "white",
300
- border: "none",
301
- borderRadius: 10,
302
- cursor: "pointer",
303
- fontWeight: 600,
304
- fontSize: 14
548
+ display: "flex",
549
+ flexDirection: "column",
550
+ alignItems: "center",
551
+ justifyContent: "center"
305
552
  }
306
553
  };
554
+ function adjustColor(hex, amount) {
555
+ const num = parseInt(hex.replace("#", ""), 16);
556
+ const r = Math.min(255, Math.max(0, (num >> 16 & 255) + amount));
557
+ const g = Math.min(255, Math.max(0, (num >> 8 & 255) + amount));
558
+ const b = Math.min(255, Math.max(0, (num & 255) + amount));
559
+ return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
560
+ }
561
+ function SyncAgentChat({ config, ...props }) {
562
+ if (config) {
563
+ return /* @__PURE__ */ jsx2(SyncAgentProvider, { config, children: /* @__PURE__ */ jsx2(ChatInner, { ...props }) });
564
+ }
565
+ return /* @__PURE__ */ jsx2(ChatInner, { ...props });
566
+ }
307
567
 
308
568
  // src/index.ts
309
569
  import { SyncAgentClient as SyncAgentClient2 } from "@syncagent/js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncagent/react",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "SyncAgent React SDK — AI database chat widget & hooks",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",