@syncagent/react 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +11 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +150 -99
- package/dist/index.mjs +150 -99
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode, CSSProperties } from 'react';
|
|
3
|
-
import { SyncAgentConfig, SyncAgentClient, Message } from '@syncagent/js';
|
|
3
|
+
import { SyncAgentConfig, SyncAgentClient, ToolData, Message } from '@syncagent/js';
|
|
4
4
|
export { ChatOptions, Message, SyncAgentClient, SyncAgentConfig, ToolDefinition, ToolParameter } from '@syncagent/js';
|
|
5
5
|
|
|
6
6
|
declare function SyncAgentProvider({ config, children, }: {
|
|
@@ -11,6 +11,10 @@ declare function useSyncAgentClient(): SyncAgentClient;
|
|
|
11
11
|
|
|
12
12
|
interface UseSyncAgentOptions {
|
|
13
13
|
client?: SyncAgentClient;
|
|
14
|
+
/** Extra context injected into every message */
|
|
15
|
+
context?: Record<string, any>;
|
|
16
|
+
/** Called when a DB tool returns structured data */
|
|
17
|
+
onData?: (data: ToolData) => void;
|
|
14
18
|
}
|
|
15
19
|
declare function useSyncAgent(options?: UseSyncAgentOptions): {
|
|
16
20
|
messages: Message[];
|
|
@@ -20,6 +24,7 @@ declare function useSyncAgent(options?: UseSyncAgentOptions): {
|
|
|
20
24
|
step: string;
|
|
21
25
|
label: string;
|
|
22
26
|
} | null;
|
|
27
|
+
lastData: ToolData | null;
|
|
23
28
|
sendMessage: (content: string) => Promise<void>;
|
|
24
29
|
stop: () => void;
|
|
25
30
|
reset: () => void;
|
|
@@ -38,10 +43,14 @@ interface SyncAgentChatProps {
|
|
|
38
43
|
className?: string;
|
|
39
44
|
style?: CSSProperties;
|
|
40
45
|
suggestions?: string[];
|
|
41
|
-
/** Unique key for localStorage persistence
|
|
46
|
+
/** Unique key for localStorage persistence */
|
|
42
47
|
persistKey?: string;
|
|
48
|
+
/** Extra context injected into every message */
|
|
49
|
+
context?: Record<string, any>;
|
|
43
50
|
/** Called when user reacts to a message */
|
|
44
51
|
onReaction?: (messageIndex: number, reaction: "up" | "down", content: string) => void;
|
|
52
|
+
/** Called when a DB tool returns structured data */
|
|
53
|
+
onData?: (data: ToolData) => void;
|
|
45
54
|
}
|
|
46
55
|
declare function SyncAgentChat({ config, ...props }: SyncAgentChatProps): react_jsx_runtime.JSX.Element;
|
|
47
56
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode, CSSProperties } from 'react';
|
|
3
|
-
import { SyncAgentConfig, SyncAgentClient, Message } from '@syncagent/js';
|
|
3
|
+
import { SyncAgentConfig, SyncAgentClient, ToolData, Message } from '@syncagent/js';
|
|
4
4
|
export { ChatOptions, Message, SyncAgentClient, SyncAgentConfig, ToolDefinition, ToolParameter } from '@syncagent/js';
|
|
5
5
|
|
|
6
6
|
declare function SyncAgentProvider({ config, children, }: {
|
|
@@ -11,6 +11,10 @@ declare function useSyncAgentClient(): SyncAgentClient;
|
|
|
11
11
|
|
|
12
12
|
interface UseSyncAgentOptions {
|
|
13
13
|
client?: SyncAgentClient;
|
|
14
|
+
/** Extra context injected into every message */
|
|
15
|
+
context?: Record<string, any>;
|
|
16
|
+
/** Called when a DB tool returns structured data */
|
|
17
|
+
onData?: (data: ToolData) => void;
|
|
14
18
|
}
|
|
15
19
|
declare function useSyncAgent(options?: UseSyncAgentOptions): {
|
|
16
20
|
messages: Message[];
|
|
@@ -20,6 +24,7 @@ declare function useSyncAgent(options?: UseSyncAgentOptions): {
|
|
|
20
24
|
step: string;
|
|
21
25
|
label: string;
|
|
22
26
|
} | null;
|
|
27
|
+
lastData: ToolData | null;
|
|
23
28
|
sendMessage: (content: string) => Promise<void>;
|
|
24
29
|
stop: () => void;
|
|
25
30
|
reset: () => void;
|
|
@@ -38,10 +43,14 @@ interface SyncAgentChatProps {
|
|
|
38
43
|
className?: string;
|
|
39
44
|
style?: CSSProperties;
|
|
40
45
|
suggestions?: string[];
|
|
41
|
-
/** Unique key for localStorage persistence
|
|
46
|
+
/** Unique key for localStorage persistence */
|
|
42
47
|
persistKey?: string;
|
|
48
|
+
/** Extra context injected into every message */
|
|
49
|
+
context?: Record<string, any>;
|
|
43
50
|
/** Called when user reacts to a message */
|
|
44
51
|
onReaction?: (messageIndex: number, reaction: "up" | "down", content: string) => void;
|
|
52
|
+
/** Called when a DB tool returns structured data */
|
|
53
|
+
onData?: (data: ToolData) => void;
|
|
45
54
|
}
|
|
46
55
|
declare function SyncAgentChat({ config, ...props }: SyncAgentChatProps): react_jsx_runtime.JSX.Element;
|
|
47
56
|
|
package/dist/index.js
CHANGED
|
@@ -64,6 +64,7 @@ function useSyncAgent(options = {}) {
|
|
|
64
64
|
const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
|
|
65
65
|
const [error, setError] = (0, import_react2.useState)(null);
|
|
66
66
|
const [status, setStatus] = (0, import_react2.useState)(null);
|
|
67
|
+
const [lastData, setLastData] = (0, import_react2.useState)(null);
|
|
67
68
|
const abortRef = (0, import_react2.useRef)(null);
|
|
68
69
|
const sendMessage = (0, import_react2.useCallback)(
|
|
69
70
|
async (content) => {
|
|
@@ -74,13 +75,19 @@ function useSyncAgent(options = {}) {
|
|
|
74
75
|
setIsLoading(true);
|
|
75
76
|
setError(null);
|
|
76
77
|
setStatus(null);
|
|
78
|
+
setLastData(null);
|
|
77
79
|
const placeholder = { role: "assistant", content: "" };
|
|
78
80
|
setMessages([...updated, placeholder]);
|
|
79
81
|
abortRef.current = new AbortController();
|
|
80
82
|
try {
|
|
81
83
|
await client.chat(updated, {
|
|
82
84
|
signal: abortRef.current.signal,
|
|
85
|
+
context: options.context,
|
|
83
86
|
onStatus: (step, label) => setStatus({ step, label }),
|
|
87
|
+
onData: (data) => {
|
|
88
|
+
setLastData(data);
|
|
89
|
+
options.onData?.(data);
|
|
90
|
+
},
|
|
84
91
|
onToken: (token) => {
|
|
85
92
|
placeholder.content += token;
|
|
86
93
|
setMessages((prev) => {
|
|
@@ -113,7 +120,7 @@ function useSyncAgent(options = {}) {
|
|
|
113
120
|
abortRef.current = null;
|
|
114
121
|
}
|
|
115
122
|
},
|
|
116
|
-
[client, messages, isLoading]
|
|
123
|
+
[client, messages, isLoading, options.context]
|
|
117
124
|
);
|
|
118
125
|
const stop = (0, import_react2.useCallback)(() => {
|
|
119
126
|
abortRef.current?.abort();
|
|
@@ -124,41 +131,70 @@ function useSyncAgent(options = {}) {
|
|
|
124
131
|
setError(null);
|
|
125
132
|
setIsLoading(false);
|
|
126
133
|
setStatus(null);
|
|
134
|
+
setLastData(null);
|
|
127
135
|
}, []);
|
|
128
|
-
return { messages, isLoading, error, status, sendMessage, stop, reset };
|
|
136
|
+
return { messages, isLoading, error, status, lastData, sendMessage, stop, reset };
|
|
129
137
|
}
|
|
130
138
|
|
|
131
139
|
// src/chat.tsx
|
|
132
140
|
var import_react3 = require("react");
|
|
133
141
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
142
|
+
var LS = {
|
|
143
|
+
load: (key) => {
|
|
144
|
+
try {
|
|
145
|
+
const r = localStorage.getItem(`sa_${key}`);
|
|
146
|
+
return r ? JSON.parse(r) : null;
|
|
147
|
+
} catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
save: (key, v) => {
|
|
152
|
+
try {
|
|
153
|
+
localStorage.setItem(`sa_${key}`, JSON.stringify(v));
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
del: (key) => {
|
|
158
|
+
try {
|
|
159
|
+
localStorage.removeItem(`sa_${key}`);
|
|
160
|
+
} catch {
|
|
141
161
|
}
|
|
142
|
-
return parsed;
|
|
143
|
-
} catch {
|
|
144
|
-
return null;
|
|
145
162
|
}
|
|
163
|
+
};
|
|
164
|
+
function mdTableToCSV(text) {
|
|
165
|
+
const match = text.match(/\|(.+)\|\r?\n\|[-| :]+\|\r?\n((?:\|.+\|\r?\n?)+)/);
|
|
166
|
+
if (!match) return null;
|
|
167
|
+
const headers = match[1].split("|").map((c) => c.trim()).filter(Boolean);
|
|
168
|
+
const rows = match[2].trim().split("\n").map(
|
|
169
|
+
(row) => row.split("|").map((c) => c.trim()).filter(Boolean)
|
|
170
|
+
);
|
|
171
|
+
const escape = (v) => `"${v.replace(/"/g, '""')}"`;
|
|
172
|
+
return [headers.map(escape).join(","), ...rows.map((r) => r.map(escape).join(","))].join("\n");
|
|
146
173
|
}
|
|
147
|
-
function
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
174
|
+
function downloadCSV(csv, filename = "syncagent-export.csv") {
|
|
175
|
+
const blob = new Blob([csv], { type: "text/csv" });
|
|
176
|
+
const url = URL.createObjectURL(blob);
|
|
177
|
+
const a = document.createElement("a");
|
|
178
|
+
a.href = url;
|
|
179
|
+
a.download = filename;
|
|
180
|
+
a.click();
|
|
181
|
+
URL.revokeObjectURL(url);
|
|
156
182
|
}
|
|
157
|
-
function
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
183
|
+
function BarChart({ data, accent }) {
|
|
184
|
+
const max = Math.max(...data.map((d) => d.value), 1);
|
|
185
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { margin: "10px 0", padding: "12px", background: "#f8fafc", borderRadius: 10, border: "1px solid #e2e8f0" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", alignItems: "flex-end", gap: 6, height: 80 }, children: data.slice(0, 12).map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 3, minWidth: 0 }, children: [
|
|
186
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { width: "100%", background: `${accent}cc`, borderRadius: "3px 3px 0 0", height: `${d.value / max * 68}px`, minHeight: 2, transition: "height .3s" }, title: `${d.label}: ${d.value}` }),
|
|
187
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 9, color: "#94a3b8", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", width: "100%", textAlign: "center" }, children: d.label })
|
|
188
|
+
] }, i)) }) });
|
|
189
|
+
}
|
|
190
|
+
function detectChartData(data) {
|
|
191
|
+
if (!data?.data?.length) return null;
|
|
192
|
+
const rows = data.data;
|
|
193
|
+
const keys = Object.keys(rows[0]);
|
|
194
|
+
const labelKey = keys.find((k) => typeof rows[0][k] === "string" || k === "_id");
|
|
195
|
+
const valueKey = keys.find((k) => typeof rows[0][k] === "number" && k !== labelKey);
|
|
196
|
+
if (!labelKey || !valueKey) return null;
|
|
197
|
+
return rows.map((r) => ({ label: String(r[labelKey] ?? ""), value: Number(r[valueKey] ?? 0) }));
|
|
162
198
|
}
|
|
163
199
|
var CSS = (accent) => `
|
|
164
200
|
@keyframes sa-bounce { 0%,80%,100%{transform:translateY(0);opacity:.35} 40%{transform:translateY(-5px);opacity:1} }
|
|
@@ -175,13 +211,10 @@ var CSS = (accent) => `
|
|
|
175
211
|
.sa-send:disabled { opacity: .4; cursor: not-allowed }
|
|
176
212
|
.sa-act:hover { opacity: 1 !important; background: rgba(0,0,0,.06) !important }
|
|
177
213
|
.sa-react:hover { transform: scale(1.2) }
|
|
178
|
-
.sa-react.active { transform: scale(1.15) }
|
|
179
214
|
.sa-scroll::-webkit-scrollbar { width: 4px }
|
|
180
215
|
.sa-scroll::-webkit-scrollbar-track { background: transparent }
|
|
181
216
|
.sa-scroll::-webkit-scrollbar-thumb { background: rgba(0,0,0,.12); border-radius: 4px }
|
|
182
217
|
.sa-cursor { display:inline-block; width:2px; height:1em; background:currentColor; margin-left:1px; vertical-align:text-bottom; animation: sa-cursor .7s infinite }
|
|
183
|
-
.sa-resize { cursor: ns-resize; user-select: none }
|
|
184
|
-
.sa-resize:hover::after { opacity: 1 }
|
|
185
218
|
.sa-md table { width:100%; border-collapse:collapse; font-size:12.5px; margin:10px 0 }
|
|
186
219
|
.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 }
|
|
187
220
|
.sa-md td { padding:6px 10px; border-bottom:1px solid rgba(0,0,0,.06); vertical-align:top }
|
|
@@ -235,35 +268,29 @@ function CopyBtn({ text }) {
|
|
|
235
268
|
"Copy"
|
|
236
269
|
] }) });
|
|
237
270
|
}
|
|
238
|
-
function
|
|
239
|
-
const cur = reactions[idx];
|
|
240
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", gap: 2 }, children: ["up", "down"].map((r) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: `sa-react${cur === r ? " active" : ""}`, onClick: () => onReact(idx, r), title: r === "up" ? "Helpful" : "Not helpful", style: {
|
|
241
|
-
background: cur === r ? r === "up" ? `${accent}18` : "#fef2f2" : "none",
|
|
242
|
-
border: cur === r ? `1px solid ${r === "up" ? accent + "44" : "#fecaca"}` : "none",
|
|
243
|
-
cursor: "pointer",
|
|
244
|
-
padding: "3px 6px",
|
|
245
|
-
borderRadius: 5,
|
|
246
|
-
fontSize: 13,
|
|
247
|
-
transition: "all .15s",
|
|
248
|
-
opacity: cur && cur !== r ? 0.3 : 0.7
|
|
249
|
-
}, children: r === "up" ? "\u{1F44D}" : "\u{1F44E}" }, r)) });
|
|
250
|
-
}
|
|
251
|
-
function Bubble({ role, content, streaming, accent, time, idx, reactions, onReact, onRetry, hasError }) {
|
|
271
|
+
function Bubble({ role, content, streaming, accent, time, idx, reactions, onReact, onRetry, chartData, hasTable }) {
|
|
252
272
|
const isUser = role === "user";
|
|
253
273
|
const t = time.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
274
|
+
const cur = reactions[idx];
|
|
275
|
+
const csv = hasTable ? mdTableToCSV(content) : null;
|
|
254
276
|
return /* @__PURE__ */ (0, import_jsx_runtime2.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: [
|
|
255
277
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6, flexDirection: isUser ? "row-reverse" : "row" }, children: [
|
|
256
278
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }),
|
|
257
279
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 11.5, fontWeight: 600, color: isUser ? "#475569" : "#6366f1" }, children: isUser ? "You" : "SyncAgent" }),
|
|
258
280
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 10, color: "#cbd5e1" }, children: t })
|
|
259
281
|
] }),
|
|
260
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.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%"
|
|
282
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.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%" }, children: [
|
|
261
283
|
streaming && !content ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dots, { color: accent }) : isUser ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { whiteSpace: "pre-wrap" }, children: content }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sa-md", dangerouslySetInnerHTML: { __html: md(content) } }),
|
|
262
284
|
streaming && content && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "sa-cursor", style: { color: accent } })
|
|
263
285
|
] }),
|
|
264
|
-
!isUser &&
|
|
286
|
+
!isUser && !streaming && chartData && chartData.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { paddingLeft: 32, width: "calc(100% - 32px)" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BarChart, { data: chartData, accent }) }),
|
|
287
|
+
!isUser && content && !streaming && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { paddingLeft: 32, display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap" }, children: [
|
|
265
288
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CopyBtn, { text: content }),
|
|
266
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
289
|
+
csv && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { className: "sa-act", onClick: () => downloadCSV(csv), 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: [
|
|
290
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" }) }),
|
|
291
|
+
"CSV"
|
|
292
|
+
] }),
|
|
293
|
+
["up", "down"].map((r) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "sa-react", onClick: () => onReact(idx, r), style: { background: cur === r ? r === "up" ? `${accent}18` : "#fef2f2" : "none", border: cur === r ? `1px solid ${r === "up" ? accent + "44" : "#fecaca"}` : "none", cursor: "pointer", padding: "3px 6px", borderRadius: 5, fontSize: 13, transition: "all .15s", opacity: cur && cur !== r ? 0.3 : 0.7 }, children: r === "up" ? "\u{1F44D}" : "\u{1F44E}" }, r)),
|
|
267
294
|
onRetry && /* @__PURE__ */ (0, import_jsx_runtime2.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: [
|
|
268
295
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }) }),
|
|
269
296
|
"Retry"
|
|
@@ -271,8 +298,11 @@ function Bubble({ role, content, streaming, accent, time, idx, reactions, onReac
|
|
|
271
298
|
] })
|
|
272
299
|
] });
|
|
273
300
|
}
|
|
274
|
-
function Chips({ items, onPick, accent }) {
|
|
275
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, padding: "0 14px 10px" }, children: items.map((s) => /* @__PURE__ */ (0, import_jsx_runtime2.
|
|
301
|
+
function Chips({ items, onPick, accent, onPin, pinned }) {
|
|
302
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, padding: "0 14px 10px" }, children: items.map((s) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 0 }, children: [
|
|
303
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "sa-chip", onClick: () => onPick(s), style: { padding: "5px 10px 5px 13px", borderRadius: onPin ? "20px 0 0 20px" : "20px", fontSize: 12, cursor: "pointer", border: `1px solid ${accent}33`, borderRight: onPin ? "none" : "", background: `${accent}0a`, color: accent, fontWeight: 500, transition: "all .15s", whiteSpace: "nowrap" }, children: s }),
|
|
304
|
+
onPin && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => onPin(s), title: pinned?.includes(s) ? "Unpin" : "Pin", style: { padding: "5px 7px", borderRadius: "0 20px 20px 0", fontSize: 10, cursor: "pointer", border: `1px solid ${accent}33`, borderLeft: "none", background: pinned?.includes(s) ? `${accent}18` : `${accent}0a`, color: accent, transition: "all .15s" }, children: pinned?.includes(s) ? "\u{1F4CC}" : "\u{1F4CD}" })
|
|
305
|
+
] }, s)) });
|
|
276
306
|
}
|
|
277
307
|
function ChatInner({
|
|
278
308
|
mode = "floating",
|
|
@@ -287,40 +317,48 @@ function ChatInner({
|
|
|
287
317
|
style: customStyle,
|
|
288
318
|
suggestions = ["Show all records", "Count total entries", "Show recent activity"],
|
|
289
319
|
persistKey,
|
|
290
|
-
|
|
320
|
+
context,
|
|
321
|
+
onReaction,
|
|
322
|
+
onData
|
|
291
323
|
}) {
|
|
292
|
-
const { messages, isLoading, error, status, sendMessage, stop, reset } = useSyncAgent();
|
|
324
|
+
const { messages, isLoading, error, status, lastData, sendMessage, stop, reset } = useSyncAgent({ context, onData });
|
|
293
325
|
const [open, setOpen] = (0, import_react3.useState)(defaultOpen);
|
|
294
326
|
const [input, setInput] = (0, import_react3.useState)("");
|
|
295
327
|
const [ts, setTs] = (0, import_react3.useState)([]);
|
|
296
328
|
const [reactions, setReactions] = (0, import_react3.useState)({});
|
|
297
329
|
const [panelH, setPanelH] = (0, import_react3.useState)(600);
|
|
298
330
|
const [lastUserMsg, setLastUserMsg] = (0, import_react3.useState)("");
|
|
331
|
+
const [pinnedQueries, setPinnedQueries] = (0, import_react3.useState)(() => persistKey ? LS.load(`pins_${persistKey}`) || [] : []);
|
|
332
|
+
const [historyIdx, setHistoryIdx] = (0, import_react3.useState)(-1);
|
|
333
|
+
const [chartDataMap, setChartDataMap] = (0, import_react3.useState)({});
|
|
299
334
|
const endRef = (0, import_react3.useRef)(null);
|
|
300
335
|
const inputRef = (0, import_react3.useRef)(null);
|
|
301
336
|
const resizeRef = (0, import_react3.useRef)(null);
|
|
302
|
-
const
|
|
303
|
-
const loaded = (0, import_react3.useRef)(false);
|
|
337
|
+
const PKEY = persistKey || "default";
|
|
304
338
|
(0, import_react3.useEffect)(() => {
|
|
305
|
-
if (
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
339
|
+
if (messages.length > ts.length) {
|
|
340
|
+
setTs((prev) => {
|
|
341
|
+
const n = [...prev];
|
|
342
|
+
while (n.length < messages.length) n.push(/* @__PURE__ */ new Date());
|
|
343
|
+
return n;
|
|
344
|
+
});
|
|
309
345
|
}
|
|
310
|
-
}, []);
|
|
346
|
+
}, [messages.length]);
|
|
311
347
|
(0, import_react3.useEffect)(() => {
|
|
312
348
|
if (!persistKey || messages.length === 0) return;
|
|
313
|
-
|
|
349
|
+
LS.save(`chat_${PKEY}`, { messages, ts: ts.map((t) => t?.toISOString() ?? null) });
|
|
314
350
|
}, [messages, ts, persistKey]);
|
|
315
351
|
(0, import_react3.useEffect)(() => {
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
352
|
+
if (!lastData) return;
|
|
353
|
+
const chart = detectChartData(lastData);
|
|
354
|
+
if (chart) {
|
|
355
|
+
const lastAiIdx = [...messages].reverse().findIndex((m) => m.role === "assistant");
|
|
356
|
+
if (lastAiIdx >= 0) {
|
|
357
|
+
const idx = messages.length - 1 - lastAiIdx;
|
|
358
|
+
setChartDataMap((prev) => ({ ...prev, [idx]: chart }));
|
|
359
|
+
}
|
|
322
360
|
}
|
|
323
|
-
}, [
|
|
361
|
+
}, [lastData]);
|
|
324
362
|
(0, import_react3.useEffect)(() => {
|
|
325
363
|
endRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
326
364
|
}, [messages, isLoading]);
|
|
@@ -331,24 +369,48 @@ function ChatInner({
|
|
|
331
369
|
const t = (text || input).trim();
|
|
332
370
|
if (!t || isLoading) return;
|
|
333
371
|
setLastUserMsg(t);
|
|
372
|
+
setHistoryIdx(-1);
|
|
334
373
|
setInput("");
|
|
335
374
|
sendMessage(t);
|
|
336
375
|
}, [input, isLoading, sendMessage]);
|
|
376
|
+
const userMessages = messages.filter((m) => m.role === "user").map((m) => m.content);
|
|
337
377
|
const onKey = (e) => {
|
|
338
378
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
339
379
|
e.preventDefault();
|
|
340
380
|
send();
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (e.key === "ArrowUp" && !input.trim()) {
|
|
384
|
+
e.preventDefault();
|
|
385
|
+
const next = Math.min(historyIdx + 1, userMessages.length - 1);
|
|
386
|
+
setHistoryIdx(next);
|
|
387
|
+
setInput(userMessages[userMessages.length - 1 - next] || "");
|
|
388
|
+
}
|
|
389
|
+
if (e.key === "ArrowDown" && historyIdx >= 0) {
|
|
390
|
+
e.preventDefault();
|
|
391
|
+
const next = historyIdx - 1;
|
|
392
|
+
setHistoryIdx(next);
|
|
393
|
+
setInput(next < 0 ? "" : userMessages[userMessages.length - 1 - next] || "");
|
|
341
394
|
}
|
|
342
395
|
};
|
|
343
396
|
const handleReact = (0, import_react3.useCallback)((idx, r) => {
|
|
344
397
|
setReactions((prev) => ({ ...prev, [idx]: prev[idx] === r ? void 0 : r }));
|
|
345
398
|
onReaction?.(idx, r, messages[idx]?.content || "");
|
|
346
399
|
}, [messages, onReaction]);
|
|
400
|
+
const togglePin = (0, import_react3.useCallback)((q) => {
|
|
401
|
+
setPinnedQueries((prev) => {
|
|
402
|
+
const next = prev.includes(q) ? prev.filter((p) => p !== q) : [...prev, q];
|
|
403
|
+
if (persistKey) LS.save(`pins_${PKEY}`, next);
|
|
404
|
+
return next;
|
|
405
|
+
});
|
|
406
|
+
}, [persistKey]);
|
|
347
407
|
const newConversation = (0, import_react3.useCallback)(() => {
|
|
348
|
-
if (persistKey)
|
|
408
|
+
if (persistKey) LS.del(`chat_${PKEY}`);
|
|
349
409
|
setTs([]);
|
|
350
410
|
setReactions({});
|
|
351
411
|
setLastUserMsg("");
|
|
412
|
+
setHistoryIdx(-1);
|
|
413
|
+
setChartDataMap({});
|
|
352
414
|
reset();
|
|
353
415
|
}, [reset, persistKey]);
|
|
354
416
|
const onResizeStart = (0, import_react3.useCallback)((e) => {
|
|
@@ -356,8 +418,7 @@ function ChatInner({
|
|
|
356
418
|
resizeRef.current = { startY: e.clientY, startH: panelH };
|
|
357
419
|
const onMove = (ev) => {
|
|
358
420
|
if (!resizeRef.current) return;
|
|
359
|
-
|
|
360
|
-
setPanelH(Math.min(Math.max(resizeRef.current.startH + delta, 320), window.innerHeight - 120));
|
|
421
|
+
setPanelH(Math.min(Math.max(resizeRef.current.startH + (resizeRef.current.startY - ev.clientY), 320), window.innerHeight - 120));
|
|
361
422
|
};
|
|
362
423
|
const onUp = () => {
|
|
363
424
|
resizeRef.current = null;
|
|
@@ -368,30 +429,10 @@ function ChatInner({
|
|
|
368
429
|
window.addEventListener("mouseup", onUp);
|
|
369
430
|
}, [panelH]);
|
|
370
431
|
const noMsgs = messages.length === 0;
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
maxHeight: mode === "inline" ? "none" : "calc(100vh - 110px)",
|
|
374
|
-
background: "#f8fafc",
|
|
375
|
-
borderRadius: mode === "inline" ? 14 : 20,
|
|
376
|
-
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)",
|
|
377
|
-
display: "flex",
|
|
378
|
-
flexDirection: "column",
|
|
379
|
-
overflow: "hidden",
|
|
380
|
-
fontFamily: "-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif",
|
|
381
|
-
border: "1px solid rgba(0,0,0,.07)",
|
|
382
|
-
...customStyle
|
|
383
|
-
}, children: [
|
|
432
|
+
const allChips = [.../* @__PURE__ */ new Set([...pinnedQueries, ...noMsgs ? suggestions : []])];
|
|
433
|
+
const panel = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `sa-panel ${className || ""}`, style: { height: mode === "inline" ? "100%" : panelH, maxHeight: mode === "inline" ? "none" : "calc(100vh - 110px)", background: "#f8fafc", borderRadius: mode === "inline" ? 14 : 20, 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)", display: "flex", flexDirection: "column", overflow: "hidden", fontFamily: "-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif", border: "1px solid rgba(0,0,0,.07)", ...customStyle }, children: [
|
|
384
434
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: CSS(accentColor) }),
|
|
385
|
-
mode === "floating" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", {
|
|
386
|
-
height: 6,
|
|
387
|
-
flexShrink: 0,
|
|
388
|
-
cursor: "ns-resize",
|
|
389
|
-
background: "transparent",
|
|
390
|
-
position: "relative",
|
|
391
|
-
display: "flex",
|
|
392
|
-
alignItems: "center",
|
|
393
|
-
justifyContent: "center"
|
|
394
|
-
}, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { width: 32, height: 3, borderRadius: 2, background: "rgba(0,0,0,.12)" } }) }),
|
|
435
|
+
mode === "floating" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { onMouseDown: onResizeStart, style: { height: 8, flexShrink: 0, cursor: "ns-resize", background: "transparent", display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { width: 32, height: 3, borderRadius: 2, background: "rgba(0,0,0,.1)" } }) }),
|
|
395
436
|
/* @__PURE__ */ (0, import_jsx_runtime2.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: [
|
|
396
437
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
|
|
397
438
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }),
|
|
@@ -404,7 +445,7 @@ function ChatInner({
|
|
|
404
445
|
] })
|
|
405
446
|
] }),
|
|
406
447
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 4 }, children: [
|
|
407
|
-
messages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { onClick: newConversation,
|
|
448
|
+
messages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { onClick: newConversation, 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: [
|
|
408
449
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" }) }),
|
|
409
450
|
"New"
|
|
410
451
|
] }),
|
|
@@ -427,8 +468,9 @@ function ChatInner({
|
|
|
427
468
|
time: ts[i] ?? /* @__PURE__ */ new Date(),
|
|
428
469
|
reactions,
|
|
429
470
|
onReact: handleReact,
|
|
430
|
-
|
|
431
|
-
|
|
471
|
+
chartData: chartDataMap[i],
|
|
472
|
+
hasTable: msg.content.includes("|"),
|
|
473
|
+
onRetry: !isLoading && i === messages.length - 1 && msg.role === "assistant" && !!error ? () => send(lastUserMsg) : void 0
|
|
432
474
|
},
|
|
433
475
|
i
|
|
434
476
|
)),
|
|
@@ -442,10 +484,19 @@ function ChatInner({
|
|
|
442
484
|
] }),
|
|
443
485
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref: endRef })
|
|
444
486
|
] }),
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
487
|
+
allChips.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
488
|
+
Chips,
|
|
489
|
+
{
|
|
490
|
+
items: allChips,
|
|
491
|
+
onPick: (s) => {
|
|
492
|
+
setInput(s);
|
|
493
|
+
inputRef.current?.focus();
|
|
494
|
+
},
|
|
495
|
+
accent: accentColor,
|
|
496
|
+
onPin: togglePin,
|
|
497
|
+
pinned: pinnedQueries
|
|
498
|
+
}
|
|
499
|
+
),
|
|
449
500
|
isLoading && status && status.step !== "done" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { padding: "7px 14px", borderTop: "1px solid #f0f4f8", background: "#fafbfc", display: "flex", alignItems: "center", gap: 8, flexShrink: 0 }, children: [
|
|
450
501
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", gap: 3 }, children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { width: 5, height: 5, borderRadius: "50%", background: accentColor, animation: "sa-bounce 1.2s infinite", animationDelay: `${i * 0.15}s` } }, i)) }),
|
|
451
502
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { fontSize: 11.5, color: "#64748b", fontWeight: 500 }, children: [
|
|
@@ -502,7 +553,7 @@ function ChatInner({
|
|
|
502
553
|
"Powered by ",
|
|
503
554
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: accentColor, fontWeight: 600 }, children: "SyncAgent" }),
|
|
504
555
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { margin: "0 5px", opacity: 0.5 }, children: "\xB7" }),
|
|
505
|
-
"Enter to send \xB7 Shift+Enter
|
|
556
|
+
"Enter to send \xB7 \u2191\u2193 history \xB7 Shift+Enter new line"
|
|
506
557
|
] })
|
|
507
558
|
] })
|
|
508
559
|
] });
|
package/dist/index.mjs
CHANGED
|
@@ -35,6 +35,7 @@ function useSyncAgent(options = {}) {
|
|
|
35
35
|
const [isLoading, setIsLoading] = useState(false);
|
|
36
36
|
const [error, setError] = useState(null);
|
|
37
37
|
const [status, setStatus] = useState(null);
|
|
38
|
+
const [lastData, setLastData] = useState(null);
|
|
38
39
|
const abortRef = useRef(null);
|
|
39
40
|
const sendMessage = useCallback(
|
|
40
41
|
async (content) => {
|
|
@@ -45,13 +46,19 @@ function useSyncAgent(options = {}) {
|
|
|
45
46
|
setIsLoading(true);
|
|
46
47
|
setError(null);
|
|
47
48
|
setStatus(null);
|
|
49
|
+
setLastData(null);
|
|
48
50
|
const placeholder = { role: "assistant", content: "" };
|
|
49
51
|
setMessages([...updated, placeholder]);
|
|
50
52
|
abortRef.current = new AbortController();
|
|
51
53
|
try {
|
|
52
54
|
await client.chat(updated, {
|
|
53
55
|
signal: abortRef.current.signal,
|
|
56
|
+
context: options.context,
|
|
54
57
|
onStatus: (step, label) => setStatus({ step, label }),
|
|
58
|
+
onData: (data) => {
|
|
59
|
+
setLastData(data);
|
|
60
|
+
options.onData?.(data);
|
|
61
|
+
},
|
|
55
62
|
onToken: (token) => {
|
|
56
63
|
placeholder.content += token;
|
|
57
64
|
setMessages((prev) => {
|
|
@@ -84,7 +91,7 @@ function useSyncAgent(options = {}) {
|
|
|
84
91
|
abortRef.current = null;
|
|
85
92
|
}
|
|
86
93
|
},
|
|
87
|
-
[client, messages, isLoading]
|
|
94
|
+
[client, messages, isLoading, options.context]
|
|
88
95
|
);
|
|
89
96
|
const stop = useCallback(() => {
|
|
90
97
|
abortRef.current?.abort();
|
|
@@ -95,8 +102,9 @@ function useSyncAgent(options = {}) {
|
|
|
95
102
|
setError(null);
|
|
96
103
|
setIsLoading(false);
|
|
97
104
|
setStatus(null);
|
|
105
|
+
setLastData(null);
|
|
98
106
|
}, []);
|
|
99
|
-
return { messages, isLoading, error, status, sendMessage, stop, reset };
|
|
107
|
+
return { messages, isLoading, error, status, lastData, sendMessage, stop, reset };
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
// src/chat.tsx
|
|
@@ -107,34 +115,62 @@ import {
|
|
|
107
115
|
useCallback as useCallback2
|
|
108
116
|
} from "react";
|
|
109
117
|
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
var LS = {
|
|
119
|
+
load: (key) => {
|
|
120
|
+
try {
|
|
121
|
+
const r = localStorage.getItem(`sa_${key}`);
|
|
122
|
+
return r ? JSON.parse(r) : null;
|
|
123
|
+
} catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
save: (key, v) => {
|
|
128
|
+
try {
|
|
129
|
+
localStorage.setItem(`sa_${key}`, JSON.stringify(v));
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
del: (key) => {
|
|
134
|
+
try {
|
|
135
|
+
localStorage.removeItem(`sa_${key}`);
|
|
136
|
+
} catch {
|
|
117
137
|
}
|
|
118
|
-
return parsed;
|
|
119
|
-
} catch {
|
|
120
|
-
return null;
|
|
121
138
|
}
|
|
139
|
+
};
|
|
140
|
+
function mdTableToCSV(text) {
|
|
141
|
+
const match = text.match(/\|(.+)\|\r?\n\|[-| :]+\|\r?\n((?:\|.+\|\r?\n?)+)/);
|
|
142
|
+
if (!match) return null;
|
|
143
|
+
const headers = match[1].split("|").map((c) => c.trim()).filter(Boolean);
|
|
144
|
+
const rows = match[2].trim().split("\n").map(
|
|
145
|
+
(row) => row.split("|").map((c) => c.trim()).filter(Boolean)
|
|
146
|
+
);
|
|
147
|
+
const escape = (v) => `"${v.replace(/"/g, '""')}"`;
|
|
148
|
+
return [headers.map(escape).join(","), ...rows.map((r) => r.map(escape).join(","))].join("\n");
|
|
122
149
|
}
|
|
123
|
-
function
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
150
|
+
function downloadCSV(csv, filename = "syncagent-export.csv") {
|
|
151
|
+
const blob = new Blob([csv], { type: "text/csv" });
|
|
152
|
+
const url = URL.createObjectURL(blob);
|
|
153
|
+
const a = document.createElement("a");
|
|
154
|
+
a.href = url;
|
|
155
|
+
a.download = filename;
|
|
156
|
+
a.click();
|
|
157
|
+
URL.revokeObjectURL(url);
|
|
132
158
|
}
|
|
133
|
-
function
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
159
|
+
function BarChart({ data, accent }) {
|
|
160
|
+
const max = Math.max(...data.map((d) => d.value), 1);
|
|
161
|
+
return /* @__PURE__ */ jsx2("div", { style: { margin: "10px 0", padding: "12px", background: "#f8fafc", borderRadius: 10, border: "1px solid #e2e8f0" }, children: /* @__PURE__ */ jsx2("div", { style: { display: "flex", alignItems: "flex-end", gap: 6, height: 80 }, children: data.slice(0, 12).map((d, i) => /* @__PURE__ */ jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 3, minWidth: 0 }, children: [
|
|
162
|
+
/* @__PURE__ */ jsx2("div", { style: { width: "100%", background: `${accent}cc`, borderRadius: "3px 3px 0 0", height: `${d.value / max * 68}px`, minHeight: 2, transition: "height .3s" }, title: `${d.label}: ${d.value}` }),
|
|
163
|
+
/* @__PURE__ */ jsx2("span", { style: { fontSize: 9, color: "#94a3b8", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", width: "100%", textAlign: "center" }, children: d.label })
|
|
164
|
+
] }, i)) }) });
|
|
165
|
+
}
|
|
166
|
+
function detectChartData(data) {
|
|
167
|
+
if (!data?.data?.length) return null;
|
|
168
|
+
const rows = data.data;
|
|
169
|
+
const keys = Object.keys(rows[0]);
|
|
170
|
+
const labelKey = keys.find((k) => typeof rows[0][k] === "string" || k === "_id");
|
|
171
|
+
const valueKey = keys.find((k) => typeof rows[0][k] === "number" && k !== labelKey);
|
|
172
|
+
if (!labelKey || !valueKey) return null;
|
|
173
|
+
return rows.map((r) => ({ label: String(r[labelKey] ?? ""), value: Number(r[valueKey] ?? 0) }));
|
|
138
174
|
}
|
|
139
175
|
var CSS = (accent) => `
|
|
140
176
|
@keyframes sa-bounce { 0%,80%,100%{transform:translateY(0);opacity:.35} 40%{transform:translateY(-5px);opacity:1} }
|
|
@@ -151,13 +187,10 @@ var CSS = (accent) => `
|
|
|
151
187
|
.sa-send:disabled { opacity: .4; cursor: not-allowed }
|
|
152
188
|
.sa-act:hover { opacity: 1 !important; background: rgba(0,0,0,.06) !important }
|
|
153
189
|
.sa-react:hover { transform: scale(1.2) }
|
|
154
|
-
.sa-react.active { transform: scale(1.15) }
|
|
155
190
|
.sa-scroll::-webkit-scrollbar { width: 4px }
|
|
156
191
|
.sa-scroll::-webkit-scrollbar-track { background: transparent }
|
|
157
192
|
.sa-scroll::-webkit-scrollbar-thumb { background: rgba(0,0,0,.12); border-radius: 4px }
|
|
158
193
|
.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 }
|
|
161
194
|
.sa-md table { width:100%; border-collapse:collapse; font-size:12.5px; margin:10px 0 }
|
|
162
195
|
.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 }
|
|
163
196
|
.sa-md td { padding:6px 10px; border-bottom:1px solid rgba(0,0,0,.06); vertical-align:top }
|
|
@@ -211,35 +244,29 @@ function CopyBtn({ text }) {
|
|
|
211
244
|
"Copy"
|
|
212
245
|
] }) });
|
|
213
246
|
}
|
|
214
|
-
function
|
|
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 }) {
|
|
247
|
+
function Bubble({ role, content, streaming, accent, time, idx, reactions, onReact, onRetry, chartData, hasTable }) {
|
|
228
248
|
const isUser = role === "user";
|
|
229
249
|
const t = time.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
250
|
+
const cur = reactions[idx];
|
|
251
|
+
const csv = hasTable ? mdTableToCSV(content) : null;
|
|
230
252
|
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
253
|
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, flexDirection: isUser ? "row-reverse" : "row" }, children: [
|
|
232
254
|
/* @__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" }),
|
|
233
255
|
/* @__PURE__ */ jsx2("span", { style: { fontSize: 11.5, fontWeight: 600, color: isUser ? "#475569" : "#6366f1" }, children: isUser ? "You" : "SyncAgent" }),
|
|
234
256
|
/* @__PURE__ */ jsx2("span", { style: { fontSize: 10, color: "#cbd5e1" }, children: t })
|
|
235
257
|
] }),
|
|
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%"
|
|
258
|
+
/* @__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%" }, children: [
|
|
237
259
|
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) } }),
|
|
238
260
|
streaming && content && /* @__PURE__ */ jsx2("span", { className: "sa-cursor", style: { color: accent } })
|
|
239
261
|
] }),
|
|
240
|
-
!isUser &&
|
|
262
|
+
!isUser && !streaming && chartData && chartData.length > 1 && /* @__PURE__ */ jsx2("div", { style: { paddingLeft: 32, width: "calc(100% - 32px)" }, children: /* @__PURE__ */ jsx2(BarChart, { data: chartData, accent }) }),
|
|
263
|
+
!isUser && content && !streaming && /* @__PURE__ */ jsxs("div", { style: { paddingLeft: 32, display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap" }, children: [
|
|
241
264
|
/* @__PURE__ */ jsx2(CopyBtn, { text: content }),
|
|
242
|
-
/* @__PURE__ */
|
|
265
|
+
csv && /* @__PURE__ */ jsxs("button", { className: "sa-act", onClick: () => downloadCSV(csv), 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: [
|
|
266
|
+
/* @__PURE__ */ jsx2("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" }) }),
|
|
267
|
+
"CSV"
|
|
268
|
+
] }),
|
|
269
|
+
["up", "down"].map((r) => /* @__PURE__ */ jsx2("button", { className: "sa-react", onClick: () => onReact(idx, r), style: { background: cur === r ? r === "up" ? `${accent}18` : "#fef2f2" : "none", border: cur === r ? `1px solid ${r === "up" ? accent + "44" : "#fecaca"}` : "none", cursor: "pointer", padding: "3px 6px", borderRadius: 5, fontSize: 13, transition: "all .15s", opacity: cur && cur !== r ? 0.3 : 0.7 }, children: r === "up" ? "\u{1F44D}" : "\u{1F44E}" }, r)),
|
|
243
270
|
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
271
|
/* @__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
272
|
"Retry"
|
|
@@ -247,8 +274,11 @@ function Bubble({ role, content, streaming, accent, time, idx, reactions, onReac
|
|
|
247
274
|
] })
|
|
248
275
|
] });
|
|
249
276
|
}
|
|
250
|
-
function Chips({ items, onPick, accent }) {
|
|
251
|
-
return /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, padding: "0 14px 10px" }, children: items.map((s) => /* @__PURE__ */
|
|
277
|
+
function Chips({ items, onPick, accent, onPin, pinned }) {
|
|
278
|
+
return /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, padding: "0 14px 10px" }, children: items.map((s) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 0 }, children: [
|
|
279
|
+
/* @__PURE__ */ jsx2("button", { className: "sa-chip", onClick: () => onPick(s), style: { padding: "5px 10px 5px 13px", borderRadius: onPin ? "20px 0 0 20px" : "20px", fontSize: 12, cursor: "pointer", border: `1px solid ${accent}33`, borderRight: onPin ? "none" : "", background: `${accent}0a`, color: accent, fontWeight: 500, transition: "all .15s", whiteSpace: "nowrap" }, children: s }),
|
|
280
|
+
onPin && /* @__PURE__ */ jsx2("button", { onClick: () => onPin(s), title: pinned?.includes(s) ? "Unpin" : "Pin", style: { padding: "5px 7px", borderRadius: "0 20px 20px 0", fontSize: 10, cursor: "pointer", border: `1px solid ${accent}33`, borderLeft: "none", background: pinned?.includes(s) ? `${accent}18` : `${accent}0a`, color: accent, transition: "all .15s" }, children: pinned?.includes(s) ? "\u{1F4CC}" : "\u{1F4CD}" })
|
|
281
|
+
] }, s)) });
|
|
252
282
|
}
|
|
253
283
|
function ChatInner({
|
|
254
284
|
mode = "floating",
|
|
@@ -263,40 +293,48 @@ function ChatInner({
|
|
|
263
293
|
style: customStyle,
|
|
264
294
|
suggestions = ["Show all records", "Count total entries", "Show recent activity"],
|
|
265
295
|
persistKey,
|
|
266
|
-
|
|
296
|
+
context,
|
|
297
|
+
onReaction,
|
|
298
|
+
onData
|
|
267
299
|
}) {
|
|
268
|
-
const { messages, isLoading, error, status, sendMessage, stop, reset } = useSyncAgent();
|
|
300
|
+
const { messages, isLoading, error, status, lastData, sendMessage, stop, reset } = useSyncAgent({ context, onData });
|
|
269
301
|
const [open, setOpen] = useState2(defaultOpen);
|
|
270
302
|
const [input, setInput] = useState2("");
|
|
271
303
|
const [ts, setTs] = useState2([]);
|
|
272
304
|
const [reactions, setReactions] = useState2({});
|
|
273
305
|
const [panelH, setPanelH] = useState2(600);
|
|
274
306
|
const [lastUserMsg, setLastUserMsg] = useState2("");
|
|
307
|
+
const [pinnedQueries, setPinnedQueries] = useState2(() => persistKey ? LS.load(`pins_${persistKey}`) || [] : []);
|
|
308
|
+
const [historyIdx, setHistoryIdx] = useState2(-1);
|
|
309
|
+
const [chartDataMap, setChartDataMap] = useState2({});
|
|
275
310
|
const endRef = useRef2(null);
|
|
276
311
|
const inputRef = useRef2(null);
|
|
277
312
|
const resizeRef = useRef2(null);
|
|
278
|
-
const
|
|
279
|
-
const loaded = useRef2(false);
|
|
313
|
+
const PKEY = persistKey || "default";
|
|
280
314
|
useEffect(() => {
|
|
281
|
-
if (
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
315
|
+
if (messages.length > ts.length) {
|
|
316
|
+
setTs((prev) => {
|
|
317
|
+
const n = [...prev];
|
|
318
|
+
while (n.length < messages.length) n.push(/* @__PURE__ */ new Date());
|
|
319
|
+
return n;
|
|
320
|
+
});
|
|
285
321
|
}
|
|
286
|
-
}, []);
|
|
322
|
+
}, [messages.length]);
|
|
287
323
|
useEffect(() => {
|
|
288
324
|
if (!persistKey || messages.length === 0) return;
|
|
289
|
-
|
|
325
|
+
LS.save(`chat_${PKEY}`, { messages, ts: ts.map((t) => t?.toISOString() ?? null) });
|
|
290
326
|
}, [messages, ts, persistKey]);
|
|
291
327
|
useEffect(() => {
|
|
292
|
-
if (
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
328
|
+
if (!lastData) return;
|
|
329
|
+
const chart = detectChartData(lastData);
|
|
330
|
+
if (chart) {
|
|
331
|
+
const lastAiIdx = [...messages].reverse().findIndex((m) => m.role === "assistant");
|
|
332
|
+
if (lastAiIdx >= 0) {
|
|
333
|
+
const idx = messages.length - 1 - lastAiIdx;
|
|
334
|
+
setChartDataMap((prev) => ({ ...prev, [idx]: chart }));
|
|
335
|
+
}
|
|
298
336
|
}
|
|
299
|
-
}, [
|
|
337
|
+
}, [lastData]);
|
|
300
338
|
useEffect(() => {
|
|
301
339
|
endRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
302
340
|
}, [messages, isLoading]);
|
|
@@ -307,24 +345,48 @@ function ChatInner({
|
|
|
307
345
|
const t = (text || input).trim();
|
|
308
346
|
if (!t || isLoading) return;
|
|
309
347
|
setLastUserMsg(t);
|
|
348
|
+
setHistoryIdx(-1);
|
|
310
349
|
setInput("");
|
|
311
350
|
sendMessage(t);
|
|
312
351
|
}, [input, isLoading, sendMessage]);
|
|
352
|
+
const userMessages = messages.filter((m) => m.role === "user").map((m) => m.content);
|
|
313
353
|
const onKey = (e) => {
|
|
314
354
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
315
355
|
e.preventDefault();
|
|
316
356
|
send();
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (e.key === "ArrowUp" && !input.trim()) {
|
|
360
|
+
e.preventDefault();
|
|
361
|
+
const next = Math.min(historyIdx + 1, userMessages.length - 1);
|
|
362
|
+
setHistoryIdx(next);
|
|
363
|
+
setInput(userMessages[userMessages.length - 1 - next] || "");
|
|
364
|
+
}
|
|
365
|
+
if (e.key === "ArrowDown" && historyIdx >= 0) {
|
|
366
|
+
e.preventDefault();
|
|
367
|
+
const next = historyIdx - 1;
|
|
368
|
+
setHistoryIdx(next);
|
|
369
|
+
setInput(next < 0 ? "" : userMessages[userMessages.length - 1 - next] || "");
|
|
317
370
|
}
|
|
318
371
|
};
|
|
319
372
|
const handleReact = useCallback2((idx, r) => {
|
|
320
373
|
setReactions((prev) => ({ ...prev, [idx]: prev[idx] === r ? void 0 : r }));
|
|
321
374
|
onReaction?.(idx, r, messages[idx]?.content || "");
|
|
322
375
|
}, [messages, onReaction]);
|
|
376
|
+
const togglePin = useCallback2((q) => {
|
|
377
|
+
setPinnedQueries((prev) => {
|
|
378
|
+
const next = prev.includes(q) ? prev.filter((p) => p !== q) : [...prev, q];
|
|
379
|
+
if (persistKey) LS.save(`pins_${PKEY}`, next);
|
|
380
|
+
return next;
|
|
381
|
+
});
|
|
382
|
+
}, [persistKey]);
|
|
323
383
|
const newConversation = useCallback2(() => {
|
|
324
|
-
if (persistKey)
|
|
384
|
+
if (persistKey) LS.del(`chat_${PKEY}`);
|
|
325
385
|
setTs([]);
|
|
326
386
|
setReactions({});
|
|
327
387
|
setLastUserMsg("");
|
|
388
|
+
setHistoryIdx(-1);
|
|
389
|
+
setChartDataMap({});
|
|
328
390
|
reset();
|
|
329
391
|
}, [reset, persistKey]);
|
|
330
392
|
const onResizeStart = useCallback2((e) => {
|
|
@@ -332,8 +394,7 @@ function ChatInner({
|
|
|
332
394
|
resizeRef.current = { startY: e.clientY, startH: panelH };
|
|
333
395
|
const onMove = (ev) => {
|
|
334
396
|
if (!resizeRef.current) return;
|
|
335
|
-
|
|
336
|
-
setPanelH(Math.min(Math.max(resizeRef.current.startH + delta, 320), window.innerHeight - 120));
|
|
397
|
+
setPanelH(Math.min(Math.max(resizeRef.current.startH + (resizeRef.current.startY - ev.clientY), 320), window.innerHeight - 120));
|
|
337
398
|
};
|
|
338
399
|
const onUp = () => {
|
|
339
400
|
resizeRef.current = null;
|
|
@@ -344,30 +405,10 @@ function ChatInner({
|
|
|
344
405
|
window.addEventListener("mouseup", onUp);
|
|
345
406
|
}, [panelH]);
|
|
346
407
|
const noMsgs = messages.length === 0;
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
maxHeight: mode === "inline" ? "none" : "calc(100vh - 110px)",
|
|
350
|
-
background: "#f8fafc",
|
|
351
|
-
borderRadius: mode === "inline" ? 14 : 20,
|
|
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)",
|
|
353
|
-
display: "flex",
|
|
354
|
-
flexDirection: "column",
|
|
355
|
-
overflow: "hidden",
|
|
356
|
-
fontFamily: "-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif",
|
|
357
|
-
border: "1px solid rgba(0,0,0,.07)",
|
|
358
|
-
...customStyle
|
|
359
|
-
}, children: [
|
|
408
|
+
const allChips = [.../* @__PURE__ */ new Set([...pinnedQueries, ...noMsgs ? suggestions : []])];
|
|
409
|
+
const panel = /* @__PURE__ */ jsxs("div", { className: `sa-panel ${className || ""}`, style: { height: mode === "inline" ? "100%" : panelH, maxHeight: mode === "inline" ? "none" : "calc(100vh - 110px)", background: "#f8fafc", borderRadius: mode === "inline" ? 14 : 20, 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)", display: "flex", flexDirection: "column", overflow: "hidden", fontFamily: "-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif", border: "1px solid rgba(0,0,0,.07)", ...customStyle }, children: [
|
|
360
410
|
/* @__PURE__ */ jsx2("style", { children: CSS(accentColor) }),
|
|
361
|
-
mode === "floating" && /* @__PURE__ */ jsx2("div", {
|
|
362
|
-
height: 6,
|
|
363
|
-
flexShrink: 0,
|
|
364
|
-
cursor: "ns-resize",
|
|
365
|
-
background: "transparent",
|
|
366
|
-
position: "relative",
|
|
367
|
-
display: "flex",
|
|
368
|
-
alignItems: "center",
|
|
369
|
-
justifyContent: "center"
|
|
370
|
-
}, children: /* @__PURE__ */ jsx2("div", { style: { width: 32, height: 3, borderRadius: 2, background: "rgba(0,0,0,.12)" } }) }),
|
|
411
|
+
mode === "floating" && /* @__PURE__ */ jsx2("div", { onMouseDown: onResizeStart, style: { height: 8, flexShrink: 0, cursor: "ns-resize", background: "transparent", display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx2("div", { style: { width: 32, height: 3, borderRadius: 2, background: "rgba(0,0,0,.1)" } }) }),
|
|
371
412
|
/* @__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: [
|
|
372
413
|
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
|
|
373
414
|
/* @__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" }),
|
|
@@ -380,7 +421,7 @@ function ChatInner({
|
|
|
380
421
|
] })
|
|
381
422
|
] }),
|
|
382
423
|
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 4 }, children: [
|
|
383
|
-
messages.length > 0 && /* @__PURE__ */ jsxs("button", { onClick: newConversation,
|
|
424
|
+
messages.length > 0 && /* @__PURE__ */ jsxs("button", { onClick: newConversation, 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
425
|
/* @__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
426
|
"New"
|
|
386
427
|
] }),
|
|
@@ -403,8 +444,9 @@ function ChatInner({
|
|
|
403
444
|
time: ts[i] ?? /* @__PURE__ */ new Date(),
|
|
404
445
|
reactions,
|
|
405
446
|
onReact: handleReact,
|
|
406
|
-
|
|
407
|
-
|
|
447
|
+
chartData: chartDataMap[i],
|
|
448
|
+
hasTable: msg.content.includes("|"),
|
|
449
|
+
onRetry: !isLoading && i === messages.length - 1 && msg.role === "assistant" && !!error ? () => send(lastUserMsg) : void 0
|
|
408
450
|
},
|
|
409
451
|
i
|
|
410
452
|
)),
|
|
@@ -418,10 +460,19 @@ function ChatInner({
|
|
|
418
460
|
] }),
|
|
419
461
|
/* @__PURE__ */ jsx2("div", { ref: endRef })
|
|
420
462
|
] }),
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
463
|
+
allChips.length > 0 && /* @__PURE__ */ jsx2(
|
|
464
|
+
Chips,
|
|
465
|
+
{
|
|
466
|
+
items: allChips,
|
|
467
|
+
onPick: (s) => {
|
|
468
|
+
setInput(s);
|
|
469
|
+
inputRef.current?.focus();
|
|
470
|
+
},
|
|
471
|
+
accent: accentColor,
|
|
472
|
+
onPin: togglePin,
|
|
473
|
+
pinned: pinnedQueries
|
|
474
|
+
}
|
|
475
|
+
),
|
|
425
476
|
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
477
|
/* @__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
478
|
/* @__PURE__ */ jsxs("span", { style: { fontSize: 11.5, color: "#64748b", fontWeight: 500 }, children: [
|
|
@@ -478,7 +529,7 @@ function ChatInner({
|
|
|
478
529
|
"Powered by ",
|
|
479
530
|
/* @__PURE__ */ jsx2("span", { style: { color: accentColor, fontWeight: 600 }, children: "SyncAgent" }),
|
|
480
531
|
/* @__PURE__ */ jsx2("span", { style: { margin: "0 5px", opacity: 0.5 }, children: "\xB7" }),
|
|
481
|
-
"Enter to send \xB7 Shift+Enter
|
|
532
|
+
"Enter to send \xB7 \u2191\u2193 history \xB7 Shift+Enter new line"
|
|
482
533
|
] })
|
|
483
534
|
] })
|
|
484
535
|
] });
|