@syncagent/react 0.1.6 → 0.1.8

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.mjs CHANGED
@@ -34,6 +34,7 @@ function useSyncAgent(options = {}) {
34
34
  const [messages, setMessages] = useState([]);
35
35
  const [isLoading, setIsLoading] = useState(false);
36
36
  const [error, setError] = useState(null);
37
+ const [status, setStatus] = useState(null);
37
38
  const abortRef = useRef(null);
38
39
  const sendMessage = useCallback(
39
40
  async (content) => {
@@ -43,12 +44,14 @@ function useSyncAgent(options = {}) {
43
44
  setMessages(updated);
44
45
  setIsLoading(true);
45
46
  setError(null);
47
+ setStatus(null);
46
48
  const placeholder = { role: "assistant", content: "" };
47
49
  setMessages([...updated, placeholder]);
48
50
  abortRef.current = new AbortController();
49
51
  try {
50
52
  await client.chat(updated, {
51
53
  signal: abortRef.current.signal,
54
+ onStatus: (step, label) => setStatus({ step, label }),
52
55
  onToken: (token) => {
53
56
  placeholder.content += token;
54
57
  setMessages((prev) => {
@@ -58,6 +61,7 @@ function useSyncAgent(options = {}) {
58
61
  });
59
62
  },
60
63
  onComplete: (text) => {
64
+ setStatus(null);
61
65
  setMessages((prev) => {
62
66
  const next = [...prev];
63
67
  next[next.length - 1] = { role: "assistant", content: text };
@@ -76,6 +80,7 @@ function useSyncAgent(options = {}) {
76
80
  }
77
81
  } finally {
78
82
  setIsLoading(false);
83
+ setStatus(null);
79
84
  abortRef.current = null;
80
85
  }
81
86
  },
@@ -89,8 +94,9 @@ function useSyncAgent(options = {}) {
89
94
  setMessages([]);
90
95
  setError(null);
91
96
  setIsLoading(false);
97
+ setStatus(null);
92
98
  }, []);
93
- return { messages, isLoading, error, sendMessage, stop, reset };
99
+ return { messages, isLoading, error, status, sendMessage, stop, reset };
94
100
  }
95
101
 
96
102
  // src/chat.tsx
@@ -101,30 +107,63 @@ import {
101
107
  useCallback as useCallback2
102
108
  } from "react";
103
109
  import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
104
- var GLOBAL_CSS = (accent) => `
110
+ function loadHistory(key) {
111
+ try {
112
+ const raw = localStorage.getItem(`sa_chat_${key}`);
113
+ if (!raw) return null;
114
+ const parsed = JSON.parse(raw);
115
+ if (parsed.timestamps) {
116
+ parsed.timestamps = parsed.timestamps.map((t) => t ? new Date(t) : null);
117
+ }
118
+ return parsed;
119
+ } catch {
120
+ return null;
121
+ }
122
+ }
123
+ function saveHistory(key, messages, timestamps) {
124
+ try {
125
+ localStorage.setItem(`sa_chat_${key}`, JSON.stringify({
126
+ messages,
127
+ timestamps: timestamps.map((t) => t?.toISOString() ?? null),
128
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
129
+ }));
130
+ } catch {
131
+ }
132
+ }
133
+ function clearHistory(key) {
134
+ try {
135
+ localStorage.removeItem(`sa_chat_${key}`);
136
+ } catch {
137
+ }
138
+ }
139
+ var CSS = (accent) => `
105
140
  @keyframes sa-bounce { 0%,80%,100%{transform:translateY(0);opacity:.35} 40%{transform:translateY(-5px);opacity:1} }
106
141
  @keyframes sa-fadein { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:translateY(0)} }
107
142
  @keyframes sa-cursor { 0%,100%{opacity:1} 50%{opacity:0} }
108
143
  @keyframes sa-pulse { 0%,100%{opacity:.6} 50%{opacity:1} }
144
+ @keyframes sa-shake { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-4px)} 75%{transform:translateX(4px)} }
109
145
  .sa-msg { animation: sa-fadein .22s cubic-bezier(.16,1,.3,1) }
110
- .sa-fab { transition: transform .2s, box-shadow .2s }
111
146
  .sa-fab:hover { transform: scale(1.08) }
112
147
  .sa-fab:active { transform: scale(.96) }
113
148
  .sa-chip:hover { background: ${accent}22 !important; border-color: ${accent}88 !important }
114
149
  .sa-send:hover:not(:disabled) { filter: brightness(1.1); transform: scale(1.05) }
115
150
  .sa-send:active:not(:disabled) { transform: scale(.96) }
116
151
  .sa-send:disabled { opacity: .4; cursor: not-allowed }
117
- .sa-copy:hover { opacity: 1 !important; background: rgba(0,0,0,.06) !important }
152
+ .sa-act:hover { opacity: 1 !important; background: rgba(0,0,0,.06) !important }
153
+ .sa-react:hover { transform: scale(1.2) }
154
+ .sa-react.active { transform: scale(1.15) }
118
155
  .sa-scroll::-webkit-scrollbar { width: 4px }
119
156
  .sa-scroll::-webkit-scrollbar-track { background: transparent }
120
157
  .sa-scroll::-webkit-scrollbar-thumb { background: rgba(0,0,0,.12); border-radius: 4px }
121
158
  .sa-cursor { display:inline-block; width:2px; height:1em; background:currentColor; margin-left:1px; vertical-align:text-bottom; animation: sa-cursor .7s infinite }
159
+ .sa-resize { cursor: ns-resize; user-select: none }
160
+ .sa-resize:hover::after { opacity: 1 }
122
161
  .sa-md table { width:100%; border-collapse:collapse; font-size:12.5px; margin:10px 0 }
123
162
  .sa-md th { padding:7px 10px; text-align:left; font-weight:600; background:rgba(0,0,0,.04); border-bottom:2px solid rgba(0,0,0,.1); white-space:nowrap }
124
163
  .sa-md td { padding:6px 10px; border-bottom:1px solid rgba(0,0,0,.06); vertical-align:top }
125
164
  .sa-md tr:last-child td { border-bottom:none }
126
165
  .sa-md tr:hover td { background:rgba(0,0,0,.02) }
127
- .sa-md pre { background:#0f1117; color:#e2e8f0; padding:14px 16px; border-radius:10px; overflow-x:auto; font-size:12px; line-height:1.7; margin:10px 0; font-family:'Fira Code','Cascadia Code','JetBrains Mono',monospace; border:1px solid rgba(255,255,255,.06) }
166
+ .sa-md pre { background:#0f1117; color:#e2e8f0; padding:14px 16px; border-radius:10px; overflow-x:auto; font-size:12px; line-height:1.7; margin:10px 0; font-family:'Fira Code','Cascadia Code',monospace; border:1px solid rgba(255,255,255,.06) }
128
167
  .sa-md code { background:rgba(0,0,0,.07); padding:2px 6px; border-radius:4px; font-size:12px; font-family:monospace; color:#1e293b }
129
168
  .sa-md pre code { background:none; padding:0; color:inherit; font-size:inherit }
130
169
  .sa-md ul,.sa-md ol { margin:6px 0; padding-left:20px }
@@ -144,10 +183,10 @@ var GLOBAL_CSS = (accent) => `
144
183
  }
145
184
  `;
146
185
  function md(text) {
147
- let s = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/```(\w*)\n?([\s\S]*?)```/g, (_, lang, code) => `<pre><code class="lang-${lang}">${code.trimEnd()}</code></pre>`).replace(/`([^`\n]+)`/g, "<code>$1</code>").replace(/^### (.+)$/gm, "<h3>$1</h3>").replace(/^## (.+)$/gm, "<h2>$1</h2>").replace(/^# (.+)$/gm, "<h1>$1</h1>").replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>").replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/\*(.+?)\*/g, "<em>$1</em>").replace(/^> (.+)$/gm, "<blockquote>$1</blockquote>").replace(/^---$/gm, "<hr/>").replace(/\|(.+)\|\r?\n\|[-| :]+\|\r?\n((?:\|.+\|\r?\n?)+)/g, (_, hdr, body) => {
148
- const ths = hdr.split("|").filter((c) => c.trim()).map((c) => `<th>${c.trim()}</th>`).join("");
149
- const trs = body.trim().split("\n").map((row) => {
150
- const tds = row.split("|").filter((c) => c.trim()).map((c) => `<td>${c.trim()}</td>`).join("");
186
+ let s = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/```(\w*)\n?([\s\S]*?)```/g, (_, l, c) => `<pre><code class="lang-${l}">${c.trimEnd()}</code></pre>`).replace(/`([^`\n]+)`/g, "<code>$1</code>").replace(/^### (.+)$/gm, "<h3>$1</h3>").replace(/^## (.+)$/gm, "<h2>$1</h2>").replace(/^# (.+)$/gm, "<h1>$1</h1>").replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>").replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/\*(.+?)\*/g, "<em>$1</em>").replace(/^> (.+)$/gm, "<blockquote>$1</blockquote>").replace(/^---$/gm, "<hr/>").replace(/\|(.+)\|\r?\n\|[-| :]+\|\r?\n((?:\|.+\|\r?\n?)+)/g, (_, h, b) => {
187
+ const ths = h.split("|").filter((c) => c.trim()).map((c) => `<th>${c.trim()}</th>`).join("");
188
+ const trs = b.trim().split("\n").map((r) => {
189
+ const tds = r.split("|").filter((c) => c.trim()).map((c) => `<td>${c.trim()}</td>`).join("");
151
190
  return `<tr>${tds}</tr>`;
152
191
  }).join("");
153
192
  return `<div style="overflow-x:auto"><table><thead><tr>${ths}</tr></thead><tbody>${trs}</tbody></table></div>`;
@@ -155,109 +194,61 @@ function md(text) {
155
194
  return `<p>${s}</p>`;
156
195
  }
157
196
  function Dots({ color }) {
158
- return /* @__PURE__ */ jsx2("span", { style: { display: "inline-flex", gap: 4, alignItems: "center", padding: "4px 2px" }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx2("span", { style: {
159
- width: 7,
160
- height: 7,
161
- borderRadius: "50%",
162
- background: color,
163
- display: "inline-block",
164
- animation: "sa-bounce 1.3s infinite",
165
- animationDelay: `${i * 0.18}s`
166
- } }, i)) });
197
+ return /* @__PURE__ */ jsx2("span", { style: { display: "inline-flex", gap: 4, alignItems: "center", padding: "4px 2px" }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx2("span", { style: { width: 7, height: 7, borderRadius: "50%", background: color, display: "inline-block", animation: "sa-bounce 1.3s infinite", animationDelay: `${i * 0.18}s` } }, i)) });
167
198
  }
168
- function Copy({ text }) {
199
+ function CopyBtn({ text }) {
169
200
  const [ok, setOk] = useState2(false);
170
- return /* @__PURE__ */ jsx2("button", { className: "sa-copy", onClick: () => {
201
+ return /* @__PURE__ */ jsx2("button", { className: "sa-act", onClick: () => {
171
202
  navigator.clipboard?.writeText(text).then(() => {
172
203
  setOk(true);
173
204
  setTimeout(() => setOk(false), 1600);
174
205
  });
175
- }, title: "Copy response", style: {
176
- background: "none",
177
- border: "none",
178
- cursor: "pointer",
179
- padding: "3px 7px",
180
- borderRadius: 5,
181
- fontSize: 11,
182
- color: "#94a3b8",
183
- opacity: 0.65,
184
- transition: "all .15s",
185
- display: "flex",
186
- alignItems: "center",
187
- gap: 3
188
- }, children: ok ? /* @__PURE__ */ jsxs(Fragment, { children: [
206
+ }, style: { background: "none", border: "none", cursor: "pointer", padding: "3px 7px", borderRadius: 5, fontSize: 11, color: "#94a3b8", opacity: 0.65, transition: "all .15s", display: "flex", alignItems: "center", gap: 3 }, children: ok ? /* @__PURE__ */ jsxs(Fragment, { children: [
189
207
  /* @__PURE__ */ jsx2("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" }) }),
190
- " Copied"
208
+ "Copied"
191
209
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
192
210
  /* @__PURE__ */ jsx2("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" }) }),
193
- " Copy"
211
+ "Copy"
194
212
  ] }) });
195
213
  }
196
- function Bubble({ role, content, streaming, accent, time }) {
214
+ function ReactionBtns({ idx, reactions, onReact, accent }) {
215
+ const cur = reactions[idx];
216
+ return /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 2 }, children: ["up", "down"].map((r) => /* @__PURE__ */ jsx2("button", { className: `sa-react${cur === r ? " active" : ""}`, onClick: () => onReact(idx, r), title: r === "up" ? "Helpful" : "Not helpful", style: {
217
+ background: cur === r ? r === "up" ? `${accent}18` : "#fef2f2" : "none",
218
+ border: cur === r ? `1px solid ${r === "up" ? accent + "44" : "#fecaca"}` : "none",
219
+ cursor: "pointer",
220
+ padding: "3px 6px",
221
+ borderRadius: 5,
222
+ fontSize: 13,
223
+ transition: "all .15s",
224
+ opacity: cur && cur !== r ? 0.3 : 0.7
225
+ }, children: r === "up" ? "\u{1F44D}" : "\u{1F44E}" }, r)) });
226
+ }
227
+ function Bubble({ role, content, streaming, accent, time, idx, reactions, onReact, onRetry, hasError }) {
197
228
  const isUser = role === "user";
198
229
  const t = time.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
199
- return /* @__PURE__ */ jsxs("div", { className: "sa-msg", style: {
200
- display: "flex",
201
- flexDirection: "column",
202
- alignItems: isUser ? "flex-end" : "flex-start",
203
- gap: 4,
204
- maxWidth: "90%",
205
- alignSelf: isUser ? "flex-end" : "flex-start"
206
- }, children: [
207
- /* @__PURE__ */ jsxs("div", { style: {
208
- display: "flex",
209
- alignItems: "center",
210
- gap: 6,
211
- flexDirection: isUser ? "row-reverse" : "row"
212
- }, children: [
213
- /* @__PURE__ */ jsx2("div", { style: {
214
- width: 26,
215
- height: 26,
216
- borderRadius: "50%",
217
- flexShrink: 0,
218
- background: isUser ? `linear-gradient(135deg,${accent},${adj(accent, -25)})` : "linear-gradient(135deg,#6366f1,#8b5cf6)",
219
- display: "flex",
220
- alignItems: "center",
221
- justifyContent: "center",
222
- fontSize: 11,
223
- color: "white",
224
- fontWeight: 700,
225
- boxShadow: isUser ? `0 2px 8px ${accent}44` : "0 2px 8px #6366f144"
226
- }, children: isUser ? "U" : "\u2726" }),
230
+ return /* @__PURE__ */ jsxs("div", { className: "sa-msg", style: { display: "flex", flexDirection: "column", alignItems: isUser ? "flex-end" : "flex-start", gap: 4, maxWidth: "90%", alignSelf: isUser ? "flex-end" : "flex-start" }, children: [
231
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, flexDirection: isUser ? "row-reverse" : "row" }, children: [
232
+ /* @__PURE__ */ jsx2("div", { style: { width: 26, height: 26, borderRadius: "50%", flexShrink: 0, background: isUser ? `linear-gradient(135deg,${accent},${adj(accent, -25)})` : "linear-gradient(135deg,#6366f1,#8b5cf6)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 11, color: "white", fontWeight: 700, boxShadow: isUser ? `0 2px 8px ${accent}44` : "0 2px 8px #6366f144" }, children: isUser ? "U" : "\u2726" }),
227
233
  /* @__PURE__ */ jsx2("span", { style: { fontSize: 11.5, fontWeight: 600, color: isUser ? "#475569" : "#6366f1" }, children: isUser ? "You" : "SyncAgent" }),
228
234
  /* @__PURE__ */ jsx2("span", { style: { fontSize: 10, color: "#cbd5e1" }, children: t })
229
235
  ] }),
230
- /* @__PURE__ */ jsxs("div", { style: {
231
- padding: isUser ? "10px 14px" : "12px 16px",
232
- borderRadius: isUser ? "18px 4px 18px 18px" : "4px 18px 18px 18px",
233
- background: isUser ? `linear-gradient(135deg,${accent},${adj(accent, -20)})` : "#ffffff",
234
- color: isUser ? "white" : "#1e293b",
235
- fontSize: 13.5,
236
- lineHeight: 1.65,
237
- wordBreak: "break-word",
238
- border: isUser ? "none" : "1px solid #e8edf3",
239
- boxShadow: isUser ? `0 4px 16px ${accent}33` : "0 2px 12px rgba(0,0,0,0.07)",
240
- maxWidth: "100%"
241
- }, children: [
236
+ /* @__PURE__ */ jsxs("div", { style: { padding: isUser ? "10px 14px" : "12px 16px", borderRadius: isUser ? "18px 4px 18px 18px" : "4px 18px 18px 18px", background: isUser ? `linear-gradient(135deg,${accent},${adj(accent, -20)})` : "#ffffff", color: isUser ? "white" : "#1e293b", fontSize: 13.5, lineHeight: 1.65, wordBreak: "break-word", border: isUser ? "none" : "1px solid #e8edf3", boxShadow: isUser ? `0 4px 16px ${accent}33` : "0 2px 12px rgba(0,0,0,0.07)", maxWidth: "100%", animation: hasError ? "sa-shake .3s ease" : "none" }, children: [
242
237
  streaming && !content ? /* @__PURE__ */ jsx2(Dots, { color: accent }) : isUser ? /* @__PURE__ */ jsx2("span", { style: { whiteSpace: "pre-wrap" }, children: content }) : /* @__PURE__ */ jsx2("div", { className: "sa-md", dangerouslySetInnerHTML: { __html: md(content) } }),
243
238
  streaming && content && /* @__PURE__ */ jsx2("span", { className: "sa-cursor", style: { color: accent } })
244
239
  ] }),
245
- !isUser && content && !streaming && /* @__PURE__ */ jsx2("div", { style: { paddingLeft: 32, display: "flex", gap: 2 }, children: /* @__PURE__ */ jsx2(Copy, { text: content }) })
240
+ !isUser && content && !streaming && /* @__PURE__ */ jsxs("div", { style: { paddingLeft: 32, display: "flex", gap: 2, alignItems: "center" }, children: [
241
+ /* @__PURE__ */ jsx2(CopyBtn, { text: content }),
242
+ /* @__PURE__ */ jsx2(ReactionBtns, { idx, reactions, onReact, accent }),
243
+ onRetry && /* @__PURE__ */ jsxs("button", { className: "sa-act", onClick: onRetry, style: { background: "none", border: "none", cursor: "pointer", padding: "3px 7px", borderRadius: 5, fontSize: 11, color: "#94a3b8", opacity: 0.65, transition: "all .15s", display: "flex", alignItems: "center", gap: 3 }, children: [
244
+ /* @__PURE__ */ jsx2("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" }) }),
245
+ "Retry"
246
+ ] })
247
+ ] })
246
248
  ] });
247
249
  }
248
250
  function Chips({ items, onPick, accent }) {
249
- return /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, padding: "0 14px 10px" }, children: items.map((s) => /* @__PURE__ */ jsx2("button", { className: "sa-chip", onClick: () => onPick(s), style: {
250
- padding: "5px 13px",
251
- borderRadius: 20,
252
- fontSize: 12,
253
- cursor: "pointer",
254
- border: `1px solid ${accent}33`,
255
- background: `${accent}0a`,
256
- color: accent,
257
- fontWeight: 500,
258
- transition: "all .15s",
259
- whiteSpace: "nowrap"
260
- }, children: s }, s)) });
251
+ return /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, padding: "0 14px 10px" }, children: items.map((s) => /* @__PURE__ */ jsx2("button", { className: "sa-chip", onClick: () => onPick(s), style: { padding: "5px 13px", borderRadius: 20, fontSize: 12, cursor: "pointer", border: `1px solid ${accent}33`, background: `${accent}0a`, color: accent, fontWeight: 500, transition: "all .15s", whiteSpace: "nowrap" }, children: s }, s)) });
261
252
  }
262
253
  function ChatInner({
263
254
  mode = "floating",
@@ -270,29 +261,52 @@ function ChatInner({
270
261
  accentColor = "#10b981",
271
262
  className,
272
263
  style: customStyle,
273
- suggestions = ["Show all records", "Count total entries", "Show recent activity"]
264
+ suggestions = ["Show all records", "Count total entries", "Show recent activity"],
265
+ persistKey,
266
+ onReaction
274
267
  }) {
275
- const { messages, isLoading, error, sendMessage, stop, reset } = useSyncAgent();
268
+ const { messages, isLoading, error, status, sendMessage, stop, reset } = useSyncAgent();
276
269
  const [open, setOpen] = useState2(defaultOpen);
277
270
  const [input, setInput] = useState2("");
278
- const [ts] = useState2(() => /* @__PURE__ */ new Map());
271
+ const [ts, setTs] = useState2([]);
272
+ const [reactions, setReactions] = useState2({});
273
+ const [panelH, setPanelH] = useState2(600);
274
+ const [lastUserMsg, setLastUserMsg] = useState2("");
279
275
  const endRef = useRef2(null);
280
276
  const inputRef = useRef2(null);
281
- const prevLen = useRef2(0);
282
- if (messages.length > prevLen.current) {
283
- for (let i = prevLen.current; i < messages.length; i++)
284
- if (!ts.has(i)) ts.set(i, /* @__PURE__ */ new Date());
285
- prevLen.current = messages.length;
286
- }
277
+ const resizeRef = useRef2(null);
278
+ const STORAGE_KEY = persistKey || "default";
279
+ const loaded = useRef2(false);
280
+ useEffect(() => {
281
+ if (loaded.current || !persistKey) return;
282
+ loaded.current = true;
283
+ const saved = loadHistory(STORAGE_KEY);
284
+ if (saved?.messages?.length) {
285
+ }
286
+ }, []);
287
+ useEffect(() => {
288
+ if (!persistKey || messages.length === 0) return;
289
+ saveHistory(STORAGE_KEY, messages, ts);
290
+ }, [messages, ts, persistKey]);
291
+ useEffect(() => {
292
+ if (messages.length > ts.length) {
293
+ setTs((prev) => {
294
+ const next = [...prev];
295
+ while (next.length < messages.length) next.push(/* @__PURE__ */ new Date());
296
+ return next;
297
+ });
298
+ }
299
+ }, [messages.length]);
287
300
  useEffect(() => {
288
301
  endRef.current?.scrollIntoView({ behavior: "smooth" });
289
302
  }, [messages, isLoading]);
290
303
  useEffect(() => {
291
304
  if (open) setTimeout(() => inputRef.current?.focus(), 120);
292
305
  }, [open]);
293
- const send = useCallback2(() => {
294
- const t = input.trim();
306
+ const send = useCallback2((text) => {
307
+ const t = (text || input).trim();
295
308
  if (!t || isLoading) return;
309
+ setLastUserMsg(t);
296
310
  setInput("");
297
311
  sendMessage(t);
298
312
  }, [input, isLoading, sendMessage]);
@@ -302,13 +316,40 @@ function ChatInner({
302
316
  send();
303
317
  }
304
318
  };
319
+ const handleReact = useCallback2((idx, r) => {
320
+ setReactions((prev) => ({ ...prev, [idx]: prev[idx] === r ? void 0 : r }));
321
+ onReaction?.(idx, r, messages[idx]?.content || "");
322
+ }, [messages, onReaction]);
323
+ const newConversation = useCallback2(() => {
324
+ if (persistKey) clearHistory(STORAGE_KEY);
325
+ setTs([]);
326
+ setReactions({});
327
+ setLastUserMsg("");
328
+ reset();
329
+ }, [reset, persistKey]);
330
+ const onResizeStart = useCallback2((e) => {
331
+ e.preventDefault();
332
+ resizeRef.current = { startY: e.clientY, startH: panelH };
333
+ const onMove = (ev) => {
334
+ if (!resizeRef.current) return;
335
+ const delta = resizeRef.current.startY - ev.clientY;
336
+ setPanelH(Math.min(Math.max(resizeRef.current.startH + delta, 320), window.innerHeight - 120));
337
+ };
338
+ const onUp = () => {
339
+ resizeRef.current = null;
340
+ window.removeEventListener("mousemove", onMove);
341
+ window.removeEventListener("mouseup", onUp);
342
+ };
343
+ window.addEventListener("mousemove", onMove);
344
+ window.addEventListener("mouseup", onUp);
345
+ }, [panelH]);
305
346
  const noMsgs = messages.length === 0;
306
347
  const panel = /* @__PURE__ */ jsxs("div", { className: `sa-panel ${className || ""}`, style: {
307
- height: mode === "inline" ? "100%" : 600,
348
+ height: mode === "inline" ? "100%" : panelH,
308
349
  maxHeight: mode === "inline" ? "none" : "calc(100vh - 110px)",
309
350
  background: "#f8fafc",
310
351
  borderRadius: mode === "inline" ? 14 : 20,
311
- boxShadow: mode === "inline" ? "0 2px 20px rgba(0,0,0,.08)" : "0 24px 64px rgba(0,0,0,.18), 0 4px 24px rgba(0,0,0,.08)",
352
+ boxShadow: mode === "inline" ? "0 2px 20px rgba(0,0,0,.08)" : "0 24px 64px rgba(0,0,0,.18),0 4px 24px rgba(0,0,0,.08)",
312
353
  display: "flex",
313
354
  flexDirection: "column",
314
355
  overflow: "hidden",
@@ -316,116 +357,64 @@ function ChatInner({
316
357
  border: "1px solid rgba(0,0,0,.07)",
317
358
  ...customStyle
318
359
  }, children: [
319
- /* @__PURE__ */ jsx2("style", { children: GLOBAL_CSS(accentColor) }),
320
- /* @__PURE__ */ jsxs("div", { style: {
321
- padding: "13px 16px",
322
- background: `linear-gradient(135deg,${accentColor},${adj(accentColor, -28)})`,
360
+ /* @__PURE__ */ jsx2("style", { children: CSS(accentColor) }),
361
+ mode === "floating" && /* @__PURE__ */ jsx2("div", { className: "sa-resize", onMouseDown: onResizeStart, style: {
362
+ height: 6,
363
+ flexShrink: 0,
364
+ cursor: "ns-resize",
365
+ background: "transparent",
366
+ position: "relative",
323
367
  display: "flex",
324
368
  alignItems: "center",
325
- justifyContent: "space-between",
326
- flexShrink: 0,
327
- boxShadow: "0 2px 12px rgba(0,0,0,.12)"
328
- }, children: [
369
+ justifyContent: "center"
370
+ }, children: /* @__PURE__ */ jsx2("div", { style: { width: 32, height: 3, borderRadius: 2, background: "rgba(0,0,0,.12)" } }) }),
371
+ /* @__PURE__ */ jsxs("div", { style: { padding: "11px 16px", background: `linear-gradient(135deg,${accentColor},${adj(accentColor, -28)})`, display: "flex", alignItems: "center", justifyContent: "space-between", flexShrink: 0, boxShadow: "0 2px 12px rgba(0,0,0,.12)" }, children: [
329
372
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
330
- /* @__PURE__ */ jsx2("div", { style: {
331
- width: 36,
332
- height: 36,
333
- borderRadius: "50%",
334
- background: "rgba(255,255,255,.18)",
335
- backdropFilter: "blur(8px)",
336
- display: "flex",
337
- alignItems: "center",
338
- justifyContent: "center",
339
- fontSize: 17,
340
- boxShadow: "0 2px 8px rgba(0,0,0,.15)"
341
- }, children: "\u2726" }),
373
+ /* @__PURE__ */ jsx2("div", { style: { width: 36, height: 36, borderRadius: "50%", background: "rgba(255,255,255,.18)", backdropFilter: "blur(8px)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 17, boxShadow: "0 2px 8px rgba(0,0,0,.15)" }, children: "\u2726" }),
342
374
  /* @__PURE__ */ jsxs("div", { children: [
343
375
  /* @__PURE__ */ jsx2("div", { style: { color: "white", fontWeight: 700, fontSize: 14, lineHeight: 1.2, letterSpacing: "-0.01em" }, children: title }),
344
- /* @__PURE__ */ jsx2("div", { style: { color: "rgba(255,255,255,.72)", fontSize: 11, marginTop: 1 }, children: isLoading ? /* @__PURE__ */ jsx2("span", { style: { animation: "sa-pulse 1.2s infinite" }, children: "\u25CF Thinking..." }) : subtitle })
376
+ /* @__PURE__ */ jsx2("div", { style: { color: "rgba(255,255,255,.72)", fontSize: 11, marginTop: 1 }, children: isLoading ? /* @__PURE__ */ jsxs("span", { style: { animation: "sa-pulse 1.2s infinite" }, children: [
377
+ "\u25CF ",
378
+ status?.label || "Thinking..."
379
+ ] }) : subtitle })
345
380
  ] })
346
381
  ] }),
347
382
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 4 }, children: [
348
- messages.length > 0 && /* @__PURE__ */ jsx2("button", { onClick: reset, style: {
349
- background: "rgba(255,255,255,.15)",
350
- border: "none",
351
- color: "white",
352
- cursor: "pointer",
353
- borderRadius: 7,
354
- padding: "4px 9px",
355
- fontSize: 11,
356
- fontWeight: 500,
357
- backdropFilter: "blur(4px)"
358
- }, children: "Clear" }),
359
- mode === "floating" && /* @__PURE__ */ jsx2("button", { onClick: () => setOpen(false), style: {
360
- background: "rgba(255,255,255,.15)",
361
- border: "none",
362
- color: "white",
363
- cursor: "pointer",
364
- borderRadius: 7,
365
- padding: "4px 9px",
366
- fontSize: 18,
367
- lineHeight: 1,
368
- backdropFilter: "blur(4px)"
369
- }, children: "\xD7" })
383
+ messages.length > 0 && /* @__PURE__ */ jsxs("button", { onClick: newConversation, title: "New conversation", style: { background: "rgba(255,255,255,.15)", border: "none", color: "white", cursor: "pointer", borderRadius: 7, padding: "4px 9px", fontSize: 11, fontWeight: 500, backdropFilter: "blur(4px)", display: "flex", alignItems: "center", gap: 4 }, children: [
384
+ /* @__PURE__ */ jsx2("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" }) }),
385
+ "New"
386
+ ] }),
387
+ mode === "floating" && /* @__PURE__ */ jsx2("button", { onClick: () => setOpen(false), style: { background: "rgba(255,255,255,.15)", border: "none", color: "white", cursor: "pointer", borderRadius: 7, padding: "4px 9px", fontSize: 18, lineHeight: 1, backdropFilter: "blur(4px)" }, children: "\xD7" })
370
388
  ] })
371
389
  ] }),
372
- /* @__PURE__ */ jsxs("div", { className: "sa-scroll", style: {
373
- flex: 1,
374
- overflowY: "auto",
375
- padding: "16px 14px 8px",
376
- display: "flex",
377
- flexDirection: "column",
378
- gap: 16,
379
- background: "#f8fafc"
380
- }, children: [
381
- noMsgs && /* @__PURE__ */ jsxs("div", { style: {
382
- flex: 1,
383
- display: "flex",
384
- flexDirection: "column",
385
- alignItems: "center",
386
- justifyContent: "center",
387
- padding: "32px 20px",
388
- textAlign: "center"
389
- }, children: [
390
- /* @__PURE__ */ jsx2("div", { style: {
391
- width: 56,
392
- height: 56,
393
- borderRadius: "50%",
394
- marginBottom: 14,
395
- background: `linear-gradient(135deg,${accentColor}18,${accentColor}35)`,
396
- display: "flex",
397
- alignItems: "center",
398
- justifyContent: "center",
399
- fontSize: 24,
400
- boxShadow: `0 4px 20px ${accentColor}22`
401
- }, children: "\u2726" }),
390
+ /* @__PURE__ */ jsxs("div", { className: "sa-scroll", style: { flex: 1, overflowY: "auto", padding: "16px 14px 8px", display: "flex", flexDirection: "column", gap: 16, background: "#f8fafc" }, children: [
391
+ noMsgs && /* @__PURE__ */ jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", padding: "32px 20px", textAlign: "center" }, children: [
392
+ /* @__PURE__ */ jsx2("div", { style: { width: 56, height: 56, borderRadius: "50%", marginBottom: 14, background: `linear-gradient(135deg,${accentColor}18,${accentColor}35)`, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 24, boxShadow: `0 4px 20px ${accentColor}22` }, children: "\u2726" }),
402
393
  /* @__PURE__ */ jsx2("p", { style: { fontSize: 14, color: "#64748b", lineHeight: 1.65, maxWidth: 260, margin: 0 }, children: welcomeMessage })
403
394
  ] }),
404
395
  messages.map((msg, i) => /* @__PURE__ */ jsx2(
405
396
  Bubble,
406
397
  {
398
+ idx: i,
407
399
  role: msg.role,
408
400
  content: msg.content,
409
401
  streaming: isLoading && i === messages.length - 1 && msg.role === "assistant",
410
402
  accent: accentColor,
411
- time: ts.get(i) ?? /* @__PURE__ */ new Date()
403
+ time: ts[i] ?? /* @__PURE__ */ new Date(),
404
+ reactions,
405
+ onReact: handleReact,
406
+ onRetry: !isLoading && i === messages.length - 1 && msg.role === "assistant" && !!error ? () => send(lastUserMsg) : void 0,
407
+ hasError: !!error && i === messages.length - 1 && msg.role === "assistant"
412
408
  },
413
409
  i
414
410
  )),
415
- error && /* @__PURE__ */ jsxs("div", { style: {
416
- padding: "10px 14px",
417
- borderRadius: 10,
418
- fontSize: 13,
419
- background: "#fef2f2",
420
- color: "#dc2626",
421
- border: "1px solid #fecaca",
422
- alignSelf: "flex-start",
423
- display: "flex",
424
- alignItems: "center",
425
- gap: 6
426
- }, children: [
411
+ error && /* @__PURE__ */ jsxs("div", { style: { padding: "10px 14px", borderRadius: 10, fontSize: 13, background: "#fef2f2", color: "#dc2626", border: "1px solid #fecaca", alignSelf: "flex-start", display: "flex", alignItems: "center", gap: 8 }, children: [
427
412
  /* @__PURE__ */ jsx2("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" }) }),
428
- error.message
413
+ /* @__PURE__ */ jsx2("span", { children: error.message }),
414
+ lastUserMsg && /* @__PURE__ */ jsxs("button", { onClick: () => send(lastUserMsg), style: { marginLeft: 4, padding: "3px 10px", borderRadius: 6, border: "1px solid #fecaca", background: "white", color: "#dc2626", cursor: "pointer", fontSize: 11, fontWeight: 600, display: "flex", alignItems: "center", gap: 3 }, children: [
415
+ /* @__PURE__ */ jsx2("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" }) }),
416
+ "Retry"
417
+ ] })
429
418
  ] }),
430
419
  /* @__PURE__ */ jsx2("div", { ref: endRef })
431
420
  ] }),
@@ -433,22 +422,19 @@ function ChatInner({
433
422
  setInput(s);
434
423
  inputRef.current?.focus();
435
424
  }, accent: accentColor }),
436
- /* @__PURE__ */ jsxs("div", { style: {
437
- padding: "10px 12px 12px",
438
- borderTop: "1px solid #e8edf3",
439
- background: "#ffffff",
440
- flexShrink: 0
441
- }, children: [
442
- /* @__PURE__ */ jsxs("div", { style: {
443
- display: "flex",
444
- gap: 8,
445
- alignItems: "flex-end",
446
- background: "#f1f5f9",
447
- borderRadius: 14,
448
- border: "1.5px solid #e2e8f0",
449
- padding: "8px 8px 8px 14px",
450
- transition: "border-color .15s, box-shadow .15s"
451
- }, children: [
425
+ isLoading && status && status.step !== "done" && /* @__PURE__ */ jsxs("div", { style: { padding: "7px 14px", borderTop: "1px solid #f0f4f8", background: "#fafbfc", display: "flex", alignItems: "center", gap: 8, flexShrink: 0 }, children: [
426
+ /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 3 }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx2("div", { style: { width: 5, height: 5, borderRadius: "50%", background: accentColor, animation: "sa-bounce 1.2s infinite", animationDelay: `${i * 0.15}s` } }, i)) }),
427
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 11.5, color: "#64748b", fontWeight: 500 }, children: [
428
+ status.step === "connecting" && "\u{1F50C} ",
429
+ status.step === "schema" && "\u{1F4CB} ",
430
+ status.step === "thinking" && "\u{1F9E0} ",
431
+ status.step === "querying" && "\u{1F50D} ",
432
+ status.step === "writing" && "\u270F\uFE0F ",
433
+ status.label
434
+ ] })
435
+ ] }),
436
+ /* @__PURE__ */ jsxs("div", { style: { padding: "10px 12px 12px", borderTop: "1px solid #e8edf3", background: "#ffffff", flexShrink: 0 }, children: [
437
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "flex-end", background: "#f1f5f9", borderRadius: 14, border: "1.5px solid #e2e8f0", padding: "8px 8px 8px 14px", transition: "border-color .15s,box-shadow .15s" }, children: [
452
438
  /* @__PURE__ */ jsx2(
453
439
  "textarea",
454
440
  {
@@ -477,53 +463,15 @@ function ChatInner({
477
463
  placeholder,
478
464
  disabled: isLoading,
479
465
  rows: 1,
480
- style: {
481
- flex: 1,
482
- background: "none",
483
- border: "none",
484
- resize: "none",
485
- fontSize: 13.5,
486
- lineHeight: 1.55,
487
- color: "#1e293b",
488
- fontFamily: "inherit",
489
- padding: 0,
490
- maxHeight: 130,
491
- outline: "none"
492
- }
466
+ style: { flex: 1, background: "none", border: "none", resize: "none", fontSize: 13.5, lineHeight: 1.55, color: "#1e293b", fontFamily: "inherit", padding: 0, maxHeight: 130, outline: "none" }
493
467
  }
494
468
  ),
495
469
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 5, alignItems: "flex-end", flexShrink: 0 }, children: [
496
- isLoading && /* @__PURE__ */ jsxs("button", { onClick: stop, style: {
497
- background: "#fef2f2",
498
- border: "1px solid #fecaca",
499
- color: "#dc2626",
500
- borderRadius: 9,
501
- padding: "6px 11px",
502
- cursor: "pointer",
503
- fontSize: 11.5,
504
- fontWeight: 600,
505
- display: "flex",
506
- alignItems: "center",
507
- gap: 4
508
- }, children: [
470
+ isLoading && /* @__PURE__ */ jsxs("button", { onClick: stop, style: { background: "#fef2f2", border: "1px solid #fecaca", color: "#dc2626", borderRadius: 9, padding: "6px 11px", cursor: "pointer", fontSize: 11.5, fontWeight: 600, display: "flex", alignItems: "center", gap: 4 }, children: [
509
471
  /* @__PURE__ */ jsx2("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx2("rect", { x: "4", y: "4", width: "16", height: "16" }) }),
510
472
  "Stop"
511
473
  ] }),
512
- /* @__PURE__ */ jsx2("button", { className: "sa-send", onClick: send, disabled: isLoading || !input.trim(), style: {
513
- width: 36,
514
- height: 36,
515
- borderRadius: 10,
516
- border: "none",
517
- background: input.trim() && !isLoading ? `linear-gradient(135deg,${accentColor},${adj(accentColor, -20)})` : "#e2e8f0",
518
- color: input.trim() && !isLoading ? "white" : "#94a3b8",
519
- cursor: "pointer",
520
- display: "flex",
521
- alignItems: "center",
522
- justifyContent: "center",
523
- transition: "all .15s",
524
- flexShrink: 0,
525
- boxShadow: input.trim() && !isLoading ? `0 3px 12px ${accentColor}44` : "none"
526
- }, children: /* @__PURE__ */ jsx2("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) }) })
474
+ /* @__PURE__ */ jsx2("button", { className: "sa-send", onClick: () => send(), disabled: isLoading || !input.trim(), style: { width: 36, height: 36, borderRadius: 10, border: "none", background: input.trim() && !isLoading ? `linear-gradient(135deg,${accentColor},${adj(accentColor, -20)})` : "#e2e8f0", color: input.trim() && !isLoading ? "white" : "#94a3b8", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", transition: "all .15s", flexShrink: 0, boxShadow: input.trim() && !isLoading ? `0 3px 12px ${accentColor}44` : "none" }, children: /* @__PURE__ */ jsx2("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) }) })
527
475
  ] })
528
476
  ] }),
529
477
  /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", marginTop: 7, fontSize: 10, color: "#c4cdd8", letterSpacing: ".01em" }, children: [
@@ -537,51 +485,10 @@ function ChatInner({
537
485
  if (mode === "inline") return panel;
538
486
  const left = position === "bottom-left";
539
487
  return /* @__PURE__ */ jsxs(Fragment, { children: [
540
- /* @__PURE__ */ jsx2("div", { className: "sa-panel-float", style: {
541
- position: "fixed",
542
- bottom: 84,
543
- zIndex: 99998,
544
- ...left ? { left: 16 } : { right: 16 },
545
- width: 430,
546
- maxWidth: "calc(100vw - 32px)",
547
- transition: "opacity .28s cubic-bezier(.16,1,.3,1), transform .28s cubic-bezier(.16,1,.3,1)",
548
- opacity: open ? 1 : 0,
549
- transform: open ? "translateY(0) scale(1)" : "translateY(20px) scale(.95)",
550
- pointerEvents: open ? "auto" : "none"
551
- }, children: panel }),
552
- /* @__PURE__ */ jsxs("button", { className: "sa-fab", onClick: () => setOpen((o) => !o), style: {
553
- position: "fixed",
554
- bottom: 16,
555
- zIndex: 99999,
556
- ...left ? { left: 16 } : { right: 16 },
557
- width: 58,
558
- height: 58,
559
- borderRadius: "50%",
560
- border: "none",
561
- background: `linear-gradient(135deg,${accentColor},${adj(accentColor, -28)})`,
562
- cursor: "pointer",
563
- boxShadow: `0 6px 24px ${accentColor}55, 0 2px 8px rgba(0,0,0,.18)`,
564
- display: "flex",
565
- alignItems: "center",
566
- justifyContent: "center"
567
- }, children: [
568
- /* @__PURE__ */ jsx2("div", { style: {
569
- transition: "transform .3s cubic-bezier(.16,1,.3,1), opacity .2s",
570
- transform: open ? "rotate(90deg) scale(.9)" : "rotate(0) scale(1)",
571
- opacity: open ? 0.85 : 1,
572
- display: "flex"
573
- }, children: open ? /* @__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" }) }) }),
574
- !open && messages.length > 0 && /* @__PURE__ */ jsx2("div", { style: {
575
- position: "absolute",
576
- top: 3,
577
- right: 3,
578
- width: 13,
579
- height: 13,
580
- borderRadius: "50%",
581
- background: "#ef4444",
582
- border: "2.5px solid white",
583
- animation: "sa-pulse 2s infinite"
584
- } })
488
+ /* @__PURE__ */ jsx2("div", { className: "sa-panel-float", style: { position: "fixed", bottom: 84, zIndex: 99998, ...left ? { left: 16 } : { right: 16 }, width: 430, maxWidth: "calc(100vw - 32px)", transition: "opacity .28s cubic-bezier(.16,1,.3,1),transform .28s cubic-bezier(.16,1,.3,1)", opacity: open ? 1 : 0, transform: open ? "translateY(0) scale(1)" : "translateY(20px) scale(.95)", pointerEvents: open ? "auto" : "none" }, children: panel }),
489
+ /* @__PURE__ */ jsxs("button", { className: "sa-fab", onClick: () => setOpen((o) => !o), style: { position: "fixed", bottom: 16, zIndex: 99999, ...left ? { left: 16 } : { right: 16 }, width: 58, height: 58, borderRadius: "50%", border: "none", background: `linear-gradient(135deg,${accentColor},${adj(accentColor, -28)})`, cursor: "pointer", boxShadow: `0 6px 24px ${accentColor}55,0 2px 8px rgba(0,0,0,.18)`, display: "flex", alignItems: "center", justifyContent: "center", transition: "transform .2s,box-shadow .2s" }, children: [
490
+ /* @__PURE__ */ jsx2("div", { style: { transition: "transform .3s cubic-bezier(.16,1,.3,1),opacity .2s", transform: open ? "rotate(90deg) scale(.9)" : "rotate(0) scale(1)", opacity: open ? 0.85 : 1, display: "flex" }, children: open ? /* @__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" }) }) }),
491
+ !open && messages.length > 0 && /* @__PURE__ */ jsx2("div", { style: { position: "absolute", top: 3, right: 3, width: 13, height: 13, borderRadius: "50%", background: "#ef4444", border: "2.5px solid white", animation: "sa-pulse 2s infinite" } })
585
492
  ] })
586
493
  ] });
587
494
  }