@syncagent/react 0.1.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/README.md +69 -0
- package/dist/index.d.mts +49 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.js +346 -0
- package/dist/index.mjs +316 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# @syncagent/react
|
|
2
|
+
|
|
3
|
+
React SDK for SyncAgent — drop-in AI database chat widget & hooks.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @syncagent/react @syncagent/js
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { SyncAgentChat } from "@syncagent/react";
|
|
15
|
+
|
|
16
|
+
export default function App() {
|
|
17
|
+
return (
|
|
18
|
+
<SyncAgentChat
|
|
19
|
+
config={{
|
|
20
|
+
apiKey: "sa_your_api_key",
|
|
21
|
+
baseUrl: "https://your-syncagent-instance.com",
|
|
22
|
+
connectionString: process.env.DATABASE_URL,
|
|
23
|
+
}}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Widget Props
|
|
30
|
+
|
|
31
|
+
| Prop | Type | Default |
|
|
32
|
+
| ---------------- | --------------------------------- | ------------------ |
|
|
33
|
+
| `config` | `{ apiKey, baseUrl, connectionString }` | Required |
|
|
34
|
+
| `mode` | `"floating" \| "inline"` | `"floating"` |
|
|
35
|
+
| `position` | `"bottom-right" \| "bottom-left"` | `"bottom-right"` |
|
|
36
|
+
| `defaultOpen` | `boolean` | `false` |
|
|
37
|
+
| `title` | `string` | `"✨ AI Assistant"` |
|
|
38
|
+
| `placeholder` | `string` | `"Ask about..."` |
|
|
39
|
+
| `accentColor` | `string` | `"#10b981"` |
|
|
40
|
+
|
|
41
|
+
## Custom UI with Hooks
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { SyncAgentProvider, useSyncAgent } from "@syncagent/react";
|
|
45
|
+
|
|
46
|
+
function App() {
|
|
47
|
+
return (
|
|
48
|
+
<SyncAgentProvider config={{ apiKey: "...", baseUrl: "...", connectionString: "..." }}>
|
|
49
|
+
<MyChat />
|
|
50
|
+
</SyncAgentProvider>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function MyChat() {
|
|
55
|
+
const { messages, isLoading, sendMessage, stop, reset } = useSyncAgent();
|
|
56
|
+
// Build your own UI
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Hook Returns
|
|
61
|
+
|
|
62
|
+
| Return | Type | Description |
|
|
63
|
+
| ------------ | --------------------------- | -------------------- |
|
|
64
|
+
| `messages` | `Message[]` | Chat history |
|
|
65
|
+
| `isLoading` | `boolean` | Currently streaming |
|
|
66
|
+
| `error` | `Error \| null` | Last error |
|
|
67
|
+
| `sendMessage`| `(content: string) => void` | Send a message |
|
|
68
|
+
| `stop` | `() => void` | Abort current stream |
|
|
69
|
+
| `reset` | `() => void` | Clear messages |
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode, CSSProperties } from 'react';
|
|
3
|
+
import { SyncAgentConfig, SyncAgentClient, Message } from '@syncagent/js';
|
|
4
|
+
export { ChatOptions, Message, SyncAgentClient, SyncAgentConfig, ToolDefinition, ToolParameter } from '@syncagent/js';
|
|
5
|
+
|
|
6
|
+
declare function SyncAgentProvider({ config, children, }: {
|
|
7
|
+
config: SyncAgentConfig;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}): react_jsx_runtime.JSX.Element;
|
|
10
|
+
declare function useSyncAgentClient(): SyncAgentClient;
|
|
11
|
+
|
|
12
|
+
interface UseSyncAgentOptions {
|
|
13
|
+
/** Pass a client directly instead of using the provider */
|
|
14
|
+
client?: SyncAgentClient;
|
|
15
|
+
}
|
|
16
|
+
declare function useSyncAgent(options?: UseSyncAgentOptions): {
|
|
17
|
+
messages: Message[];
|
|
18
|
+
isLoading: boolean;
|
|
19
|
+
error: Error | null;
|
|
20
|
+
sendMessage: (content: string) => Promise<void>;
|
|
21
|
+
stop: () => void;
|
|
22
|
+
reset: () => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
interface SyncAgentChatProps {
|
|
26
|
+
/** Config — required if not wrapped in SyncAgentProvider */
|
|
27
|
+
config?: SyncAgentConfig;
|
|
28
|
+
/** Show as floating widget (FAB + panel) or inline */
|
|
29
|
+
mode?: "floating" | "inline";
|
|
30
|
+
/** FAB position */
|
|
31
|
+
position?: "bottom-right" | "bottom-left";
|
|
32
|
+
/** Start open */
|
|
33
|
+
defaultOpen?: boolean;
|
|
34
|
+
/** Widget title */
|
|
35
|
+
title?: string;
|
|
36
|
+
/** Placeholder text */
|
|
37
|
+
placeholder?: string;
|
|
38
|
+
/** Welcome message */
|
|
39
|
+
welcomeMessage?: string;
|
|
40
|
+
/** Accent color */
|
|
41
|
+
accentColor?: string;
|
|
42
|
+
/** Custom class for the container */
|
|
43
|
+
className?: string;
|
|
44
|
+
/** Custom styles for the container */
|
|
45
|
+
style?: CSSProperties;
|
|
46
|
+
}
|
|
47
|
+
declare function SyncAgentChat({ config, ...props }: SyncAgentChatProps): react_jsx_runtime.JSX.Element;
|
|
48
|
+
|
|
49
|
+
export { SyncAgentChat, type SyncAgentChatProps, SyncAgentProvider, useSyncAgent, useSyncAgentClient };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode, CSSProperties } from 'react';
|
|
3
|
+
import { SyncAgentConfig, SyncAgentClient, Message } from '@syncagent/js';
|
|
4
|
+
export { ChatOptions, Message, SyncAgentClient, SyncAgentConfig, ToolDefinition, ToolParameter } from '@syncagent/js';
|
|
5
|
+
|
|
6
|
+
declare function SyncAgentProvider({ config, children, }: {
|
|
7
|
+
config: SyncAgentConfig;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}): react_jsx_runtime.JSX.Element;
|
|
10
|
+
declare function useSyncAgentClient(): SyncAgentClient;
|
|
11
|
+
|
|
12
|
+
interface UseSyncAgentOptions {
|
|
13
|
+
/** Pass a client directly instead of using the provider */
|
|
14
|
+
client?: SyncAgentClient;
|
|
15
|
+
}
|
|
16
|
+
declare function useSyncAgent(options?: UseSyncAgentOptions): {
|
|
17
|
+
messages: Message[];
|
|
18
|
+
isLoading: boolean;
|
|
19
|
+
error: Error | null;
|
|
20
|
+
sendMessage: (content: string) => Promise<void>;
|
|
21
|
+
stop: () => void;
|
|
22
|
+
reset: () => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
interface SyncAgentChatProps {
|
|
26
|
+
/** Config — required if not wrapped in SyncAgentProvider */
|
|
27
|
+
config?: SyncAgentConfig;
|
|
28
|
+
/** Show as floating widget (FAB + panel) or inline */
|
|
29
|
+
mode?: "floating" | "inline";
|
|
30
|
+
/** FAB position */
|
|
31
|
+
position?: "bottom-right" | "bottom-left";
|
|
32
|
+
/** Start open */
|
|
33
|
+
defaultOpen?: boolean;
|
|
34
|
+
/** Widget title */
|
|
35
|
+
title?: string;
|
|
36
|
+
/** Placeholder text */
|
|
37
|
+
placeholder?: string;
|
|
38
|
+
/** Welcome message */
|
|
39
|
+
welcomeMessage?: string;
|
|
40
|
+
/** Accent color */
|
|
41
|
+
accentColor?: string;
|
|
42
|
+
/** Custom class for the container */
|
|
43
|
+
className?: string;
|
|
44
|
+
/** Custom styles for the container */
|
|
45
|
+
style?: CSSProperties;
|
|
46
|
+
}
|
|
47
|
+
declare function SyncAgentChat({ config, ...props }: SyncAgentChatProps): react_jsx_runtime.JSX.Element;
|
|
48
|
+
|
|
49
|
+
export { SyncAgentChat, type SyncAgentChatProps, SyncAgentProvider, useSyncAgent, useSyncAgentClient };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
SyncAgentChat: () => SyncAgentChat,
|
|
25
|
+
SyncAgentClient: () => import_js2.SyncAgentClient,
|
|
26
|
+
SyncAgentProvider: () => SyncAgentProvider,
|
|
27
|
+
useSyncAgent: () => useSyncAgent,
|
|
28
|
+
useSyncAgentClient: () => useSyncAgentClient
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/provider.tsx
|
|
33
|
+
var import_react = require("react");
|
|
34
|
+
var import_js = require("@syncagent/js");
|
|
35
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
36
|
+
var SyncAgentContext = (0, import_react.createContext)(null);
|
|
37
|
+
function SyncAgentProvider({
|
|
38
|
+
config,
|
|
39
|
+
children
|
|
40
|
+
}) {
|
|
41
|
+
const client = (0, import_react.useMemo)(
|
|
42
|
+
() => new import_js.SyncAgentClient(config),
|
|
43
|
+
[config.apiKey, config.connectionString]
|
|
44
|
+
);
|
|
45
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SyncAgentContext.Provider, { value: client, children });
|
|
46
|
+
}
|
|
47
|
+
function useSyncAgentClient() {
|
|
48
|
+
const client = (0, import_react.useContext)(SyncAgentContext);
|
|
49
|
+
if (!client) throw new Error("useSyncAgentClient must be used within <SyncAgentProvider>");
|
|
50
|
+
return client;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/use-sync-agent.ts
|
|
54
|
+
var import_react2 = require("react");
|
|
55
|
+
function useSyncAgent(options = {}) {
|
|
56
|
+
let client;
|
|
57
|
+
try {
|
|
58
|
+
client = options.client || useSyncAgentClient();
|
|
59
|
+
} catch {
|
|
60
|
+
if (!options.client) throw new Error("Provide a client via options or wrap in <SyncAgentProvider>");
|
|
61
|
+
client = options.client;
|
|
62
|
+
}
|
|
63
|
+
const [messages, setMessages] = (0, import_react2.useState)([]);
|
|
64
|
+
const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
|
|
65
|
+
const [error, setError] = (0, import_react2.useState)(null);
|
|
66
|
+
const abortRef = (0, import_react2.useRef)(null);
|
|
67
|
+
const sendMessage = (0, import_react2.useCallback)(
|
|
68
|
+
async (content) => {
|
|
69
|
+
if (!content.trim() || isLoading) return;
|
|
70
|
+
const userMsg = { role: "user", content };
|
|
71
|
+
const updated = [...messages, userMsg];
|
|
72
|
+
setMessages(updated);
|
|
73
|
+
setIsLoading(true);
|
|
74
|
+
setError(null);
|
|
75
|
+
const placeholder = { role: "assistant", content: "" };
|
|
76
|
+
setMessages([...updated, placeholder]);
|
|
77
|
+
abortRef.current = new AbortController();
|
|
78
|
+
try {
|
|
79
|
+
await client.chat(updated, {
|
|
80
|
+
signal: abortRef.current.signal,
|
|
81
|
+
onToken: (token) => {
|
|
82
|
+
placeholder.content += token;
|
|
83
|
+
setMessages((prev) => {
|
|
84
|
+
const next = [...prev];
|
|
85
|
+
next[next.length - 1] = { ...placeholder };
|
|
86
|
+
return next;
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
onComplete: (text) => {
|
|
90
|
+
setMessages((prev) => {
|
|
91
|
+
const next = [...prev];
|
|
92
|
+
next[next.length - 1] = { role: "assistant", content: text };
|
|
93
|
+
return next;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
} catch (e) {
|
|
98
|
+
if (e.name !== "AbortError") {
|
|
99
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
100
|
+
setError(err);
|
|
101
|
+
setMessages((prev) => {
|
|
102
|
+
const last = prev[prev.length - 1];
|
|
103
|
+
return last?.content === "" ? prev.slice(0, -1) : prev;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
} finally {
|
|
107
|
+
setIsLoading(false);
|
|
108
|
+
abortRef.current = null;
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
[client, messages, isLoading]
|
|
112
|
+
);
|
|
113
|
+
const stop = (0, import_react2.useCallback)(() => {
|
|
114
|
+
abortRef.current?.abort();
|
|
115
|
+
}, []);
|
|
116
|
+
const reset = (0, import_react2.useCallback)(() => {
|
|
117
|
+
abortRef.current?.abort();
|
|
118
|
+
setMessages([]);
|
|
119
|
+
setError(null);
|
|
120
|
+
setIsLoading(false);
|
|
121
|
+
}, []);
|
|
122
|
+
return { messages, isLoading, error, sendMessage, stop, reset };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/chat.tsx
|
|
126
|
+
var import_react3 = require("react");
|
|
127
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
128
|
+
function ChatInner({
|
|
129
|
+
mode = "floating",
|
|
130
|
+
position = "bottom-right",
|
|
131
|
+
defaultOpen = false,
|
|
132
|
+
title = "\u2728 AI Assistant",
|
|
133
|
+
placeholder = "Ask about your data...",
|
|
134
|
+
welcomeMessage = "Ask me anything about your data. I can query, create, update, and analyze your database.",
|
|
135
|
+
accentColor = "#10b981",
|
|
136
|
+
className,
|
|
137
|
+
style: customStyle
|
|
138
|
+
}) {
|
|
139
|
+
const { messages, isLoading, sendMessage } = useSyncAgent();
|
|
140
|
+
const [isOpen, setIsOpen] = (0, import_react3.useState)(defaultOpen);
|
|
141
|
+
const [input, setInput] = (0, import_react3.useState)("");
|
|
142
|
+
const messagesEndRef = (0, import_react3.useRef)(null);
|
|
143
|
+
(0, import_react3.useEffect)(() => {
|
|
144
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
145
|
+
}, [messages]);
|
|
146
|
+
const handleSubmit = (e) => {
|
|
147
|
+
e.preventDefault();
|
|
148
|
+
if (!input.trim()) return;
|
|
149
|
+
sendMessage(input);
|
|
150
|
+
setInput("");
|
|
151
|
+
};
|
|
152
|
+
const panel = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { ...styles.panel, ...mode === "inline" ? styles.panelInline : {}, ...customStyle }, className, children: [
|
|
153
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { ...styles.header, background: `linear-gradient(135deg, ${accentColor}, ${adjustColor(accentColor, -20)})` }, children: [
|
|
154
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: title }),
|
|
155
|
+
mode === "floating" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: styles.closeBtn, onClick: () => setIsOpen(false), children: "\xD7" })
|
|
156
|
+
] }),
|
|
157
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.messages, children: [
|
|
158
|
+
messages.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: styles.welcome, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: 14, color: "#9ca3af" }, children: welcomeMessage }) }),
|
|
159
|
+
messages.map((msg, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
160
|
+
"div",
|
|
161
|
+
{
|
|
162
|
+
style: {
|
|
163
|
+
...styles.msg,
|
|
164
|
+
...msg.role === "user" ? { ...styles.msgUser, background: accentColor } : styles.msgAi
|
|
165
|
+
},
|
|
166
|
+
children: msg.content || (isLoading && i === messages.length - 1 ? "..." : "")
|
|
167
|
+
},
|
|
168
|
+
i
|
|
169
|
+
)),
|
|
170
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref: messagesEndRef })
|
|
171
|
+
] }),
|
|
172
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { style: styles.form, onSubmit: handleSubmit, children: [
|
|
173
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
174
|
+
"input",
|
|
175
|
+
{
|
|
176
|
+
style: styles.input,
|
|
177
|
+
value: input,
|
|
178
|
+
onChange: (e) => setInput(e.target.value),
|
|
179
|
+
placeholder,
|
|
180
|
+
disabled: isLoading
|
|
181
|
+
}
|
|
182
|
+
),
|
|
183
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
184
|
+
"button",
|
|
185
|
+
{
|
|
186
|
+
type: "submit",
|
|
187
|
+
disabled: isLoading || !input.trim(),
|
|
188
|
+
style: { ...styles.sendBtn, background: accentColor },
|
|
189
|
+
children: "Send"
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
] })
|
|
193
|
+
] });
|
|
194
|
+
if (mode === "inline") return panel;
|
|
195
|
+
const isLeft = position === "bottom-left";
|
|
196
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
197
|
+
isOpen && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...styles.floatingPanel, ...isLeft ? { left: 24, right: "auto" } : {} }, children: panel }),
|
|
198
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
199
|
+
"button",
|
|
200
|
+
{
|
|
201
|
+
onClick: () => setIsOpen(!isOpen),
|
|
202
|
+
style: {
|
|
203
|
+
...styles.fab,
|
|
204
|
+
background: `linear-gradient(135deg, ${accentColor}, ${adjustColor(accentColor, -20)})`,
|
|
205
|
+
...isLeft ? { left: 24, right: "auto" } : {}
|
|
206
|
+
},
|
|
207
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "28", height: "28", viewBox: "0 0 24 24", fill: "white", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z" }) })
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
] });
|
|
211
|
+
}
|
|
212
|
+
function SyncAgentChat({ config, ...props }) {
|
|
213
|
+
if (config) {
|
|
214
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SyncAgentProvider, { config, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChatInner, { ...props }) });
|
|
215
|
+
}
|
|
216
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChatInner, { ...props });
|
|
217
|
+
}
|
|
218
|
+
function adjustColor(hex, amount) {
|
|
219
|
+
const num = parseInt(hex.replace("#", ""), 16);
|
|
220
|
+
const r = Math.min(255, Math.max(0, (num >> 16 & 255) + amount));
|
|
221
|
+
const g = Math.min(255, Math.max(0, (num >> 8 & 255) + amount));
|
|
222
|
+
const b = Math.min(255, Math.max(0, (num & 255) + amount));
|
|
223
|
+
return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
|
|
224
|
+
}
|
|
225
|
+
var styles = {
|
|
226
|
+
fab: {
|
|
227
|
+
position: "fixed",
|
|
228
|
+
bottom: 24,
|
|
229
|
+
right: 24,
|
|
230
|
+
zIndex: 99999,
|
|
231
|
+
width: 56,
|
|
232
|
+
height: 56,
|
|
233
|
+
borderRadius: "50%",
|
|
234
|
+
border: "none",
|
|
235
|
+
cursor: "pointer",
|
|
236
|
+
boxShadow: "0 4px 20px rgba(0,0,0,0.2)",
|
|
237
|
+
display: "flex",
|
|
238
|
+
alignItems: "center",
|
|
239
|
+
justifyContent: "center"
|
|
240
|
+
},
|
|
241
|
+
floatingPanel: {
|
|
242
|
+
position: "fixed",
|
|
243
|
+
bottom: 96,
|
|
244
|
+
right: 24,
|
|
245
|
+
zIndex: 99998,
|
|
246
|
+
width: 400,
|
|
247
|
+
maxWidth: "calc(100vw - 48px)"
|
|
248
|
+
},
|
|
249
|
+
panel: {
|
|
250
|
+
height: 560,
|
|
251
|
+
maxHeight: "calc(100vh - 120px)",
|
|
252
|
+
background: "#fff",
|
|
253
|
+
borderRadius: 16,
|
|
254
|
+
boxShadow: "0 8px 40px rgba(0,0,0,0.15)",
|
|
255
|
+
display: "flex",
|
|
256
|
+
flexDirection: "column",
|
|
257
|
+
overflow: "hidden",
|
|
258
|
+
fontFamily: "system-ui, -apple-system, sans-serif"
|
|
259
|
+
},
|
|
260
|
+
panelInline: {
|
|
261
|
+
height: "100%",
|
|
262
|
+
maxHeight: "none",
|
|
263
|
+
borderRadius: 12,
|
|
264
|
+
boxShadow: "0 2px 12px rgba(0,0,0,0.1)"
|
|
265
|
+
},
|
|
266
|
+
header: {
|
|
267
|
+
padding: "16px",
|
|
268
|
+
color: "white",
|
|
269
|
+
fontWeight: 600,
|
|
270
|
+
fontSize: 15,
|
|
271
|
+
display: "flex",
|
|
272
|
+
alignItems: "center",
|
|
273
|
+
justifyContent: "space-between"
|
|
274
|
+
},
|
|
275
|
+
closeBtn: {
|
|
276
|
+
background: "none",
|
|
277
|
+
border: "none",
|
|
278
|
+
color: "white",
|
|
279
|
+
cursor: "pointer",
|
|
280
|
+
padding: 4,
|
|
281
|
+
fontSize: 20
|
|
282
|
+
},
|
|
283
|
+
messages: {
|
|
284
|
+
flex: 1,
|
|
285
|
+
overflowY: "auto",
|
|
286
|
+
padding: 16,
|
|
287
|
+
display: "flex",
|
|
288
|
+
flexDirection: "column",
|
|
289
|
+
gap: 12
|
|
290
|
+
},
|
|
291
|
+
welcome: { textAlign: "center", padding: "40px 20px" },
|
|
292
|
+
msg: {
|
|
293
|
+
maxWidth: "85%",
|
|
294
|
+
padding: "10px 14px",
|
|
295
|
+
borderRadius: 12,
|
|
296
|
+
fontSize: 14,
|
|
297
|
+
lineHeight: 1.5,
|
|
298
|
+
wordBreak: "break-word",
|
|
299
|
+
whiteSpace: "pre-wrap"
|
|
300
|
+
},
|
|
301
|
+
msgUser: {
|
|
302
|
+
alignSelf: "flex-end",
|
|
303
|
+
color: "white",
|
|
304
|
+
borderBottomRightRadius: 4
|
|
305
|
+
},
|
|
306
|
+
msgAi: {
|
|
307
|
+
alignSelf: "flex-start",
|
|
308
|
+
background: "#f3f4f6",
|
|
309
|
+
color: "#1f2937",
|
|
310
|
+
borderBottomLeftRadius: 4
|
|
311
|
+
},
|
|
312
|
+
form: {
|
|
313
|
+
padding: "12px 16px",
|
|
314
|
+
borderTop: "1px solid #e5e7eb",
|
|
315
|
+
display: "flex",
|
|
316
|
+
gap: 8
|
|
317
|
+
},
|
|
318
|
+
input: {
|
|
319
|
+
flex: 1,
|
|
320
|
+
padding: "10px 14px",
|
|
321
|
+
border: "1px solid #d1d5db",
|
|
322
|
+
borderRadius: 10,
|
|
323
|
+
fontSize: 14,
|
|
324
|
+
outline: "none"
|
|
325
|
+
},
|
|
326
|
+
sendBtn: {
|
|
327
|
+
padding: "10px 16px",
|
|
328
|
+
color: "white",
|
|
329
|
+
border: "none",
|
|
330
|
+
borderRadius: 10,
|
|
331
|
+
cursor: "pointer",
|
|
332
|
+
fontWeight: 600,
|
|
333
|
+
fontSize: 14
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// src/index.ts
|
|
338
|
+
var import_js2 = require("@syncagent/js");
|
|
339
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
340
|
+
0 && (module.exports = {
|
|
341
|
+
SyncAgentChat,
|
|
342
|
+
SyncAgentClient,
|
|
343
|
+
SyncAgentProvider,
|
|
344
|
+
useSyncAgent,
|
|
345
|
+
useSyncAgentClient
|
|
346
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/provider.tsx
|
|
4
|
+
import { createContext, useContext, useMemo } from "react";
|
|
5
|
+
import { SyncAgentClient } from "@syncagent/js";
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
var SyncAgentContext = createContext(null);
|
|
8
|
+
function SyncAgentProvider({
|
|
9
|
+
config,
|
|
10
|
+
children
|
|
11
|
+
}) {
|
|
12
|
+
const client = useMemo(
|
|
13
|
+
() => new SyncAgentClient(config),
|
|
14
|
+
[config.apiKey, config.connectionString]
|
|
15
|
+
);
|
|
16
|
+
return /* @__PURE__ */ jsx(SyncAgentContext.Provider, { value: client, children });
|
|
17
|
+
}
|
|
18
|
+
function useSyncAgentClient() {
|
|
19
|
+
const client = useContext(SyncAgentContext);
|
|
20
|
+
if (!client) throw new Error("useSyncAgentClient must be used within <SyncAgentProvider>");
|
|
21
|
+
return client;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/use-sync-agent.ts
|
|
25
|
+
import { useState, useCallback, useRef } from "react";
|
|
26
|
+
function useSyncAgent(options = {}) {
|
|
27
|
+
let client;
|
|
28
|
+
try {
|
|
29
|
+
client = options.client || useSyncAgentClient();
|
|
30
|
+
} catch {
|
|
31
|
+
if (!options.client) throw new Error("Provide a client via options or wrap in <SyncAgentProvider>");
|
|
32
|
+
client = options.client;
|
|
33
|
+
}
|
|
34
|
+
const [messages, setMessages] = useState([]);
|
|
35
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
36
|
+
const [error, setError] = useState(null);
|
|
37
|
+
const abortRef = useRef(null);
|
|
38
|
+
const sendMessage = useCallback(
|
|
39
|
+
async (content) => {
|
|
40
|
+
if (!content.trim() || isLoading) return;
|
|
41
|
+
const userMsg = { role: "user", content };
|
|
42
|
+
const updated = [...messages, userMsg];
|
|
43
|
+
setMessages(updated);
|
|
44
|
+
setIsLoading(true);
|
|
45
|
+
setError(null);
|
|
46
|
+
const placeholder = { role: "assistant", content: "" };
|
|
47
|
+
setMessages([...updated, placeholder]);
|
|
48
|
+
abortRef.current = new AbortController();
|
|
49
|
+
try {
|
|
50
|
+
await client.chat(updated, {
|
|
51
|
+
signal: abortRef.current.signal,
|
|
52
|
+
onToken: (token) => {
|
|
53
|
+
placeholder.content += token;
|
|
54
|
+
setMessages((prev) => {
|
|
55
|
+
const next = [...prev];
|
|
56
|
+
next[next.length - 1] = { ...placeholder };
|
|
57
|
+
return next;
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
onComplete: (text) => {
|
|
61
|
+
setMessages((prev) => {
|
|
62
|
+
const next = [...prev];
|
|
63
|
+
next[next.length - 1] = { role: "assistant", content: text };
|
|
64
|
+
return next;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
} catch (e) {
|
|
69
|
+
if (e.name !== "AbortError") {
|
|
70
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
71
|
+
setError(err);
|
|
72
|
+
setMessages((prev) => {
|
|
73
|
+
const last = prev[prev.length - 1];
|
|
74
|
+
return last?.content === "" ? prev.slice(0, -1) : prev;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
} finally {
|
|
78
|
+
setIsLoading(false);
|
|
79
|
+
abortRef.current = null;
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
[client, messages, isLoading]
|
|
83
|
+
);
|
|
84
|
+
const stop = useCallback(() => {
|
|
85
|
+
abortRef.current?.abort();
|
|
86
|
+
}, []);
|
|
87
|
+
const reset = useCallback(() => {
|
|
88
|
+
abortRef.current?.abort();
|
|
89
|
+
setMessages([]);
|
|
90
|
+
setError(null);
|
|
91
|
+
setIsLoading(false);
|
|
92
|
+
}, []);
|
|
93
|
+
return { messages, isLoading, error, sendMessage, stop, reset };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/chat.tsx
|
|
97
|
+
import { useState as useState2, useRef as useRef2, useEffect } from "react";
|
|
98
|
+
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
99
|
+
function ChatInner({
|
|
100
|
+
mode = "floating",
|
|
101
|
+
position = "bottom-right",
|
|
102
|
+
defaultOpen = false,
|
|
103
|
+
title = "\u2728 AI Assistant",
|
|
104
|
+
placeholder = "Ask about your data...",
|
|
105
|
+
welcomeMessage = "Ask me anything about your data. I can query, create, update, and analyze your database.",
|
|
106
|
+
accentColor = "#10b981",
|
|
107
|
+
className,
|
|
108
|
+
style: customStyle
|
|
109
|
+
}) {
|
|
110
|
+
const { messages, isLoading, sendMessage } = useSyncAgent();
|
|
111
|
+
const [isOpen, setIsOpen] = useState2(defaultOpen);
|
|
112
|
+
const [input, setInput] = useState2("");
|
|
113
|
+
const messagesEndRef = useRef2(null);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
116
|
+
}, [messages]);
|
|
117
|
+
const handleSubmit = (e) => {
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
if (!input.trim()) return;
|
|
120
|
+
sendMessage(input);
|
|
121
|
+
setInput("");
|
|
122
|
+
};
|
|
123
|
+
const panel = /* @__PURE__ */ jsxs("div", { style: { ...styles.panel, ...mode === "inline" ? styles.panelInline : {}, ...customStyle }, className, children: [
|
|
124
|
+
/* @__PURE__ */ jsxs("div", { style: { ...styles.header, background: `linear-gradient(135deg, ${accentColor}, ${adjustColor(accentColor, -20)})` }, children: [
|
|
125
|
+
/* @__PURE__ */ jsx2("span", { children: title }),
|
|
126
|
+
mode === "floating" && /* @__PURE__ */ jsx2("button", { style: styles.closeBtn, onClick: () => setIsOpen(false), children: "\xD7" })
|
|
127
|
+
] }),
|
|
128
|
+
/* @__PURE__ */ jsxs("div", { style: styles.messages, children: [
|
|
129
|
+
messages.length === 0 && /* @__PURE__ */ jsx2("div", { style: styles.welcome, children: /* @__PURE__ */ jsx2("p", { style: { fontSize: 14, color: "#9ca3af" }, children: welcomeMessage }) }),
|
|
130
|
+
messages.map((msg, i) => /* @__PURE__ */ jsx2(
|
|
131
|
+
"div",
|
|
132
|
+
{
|
|
133
|
+
style: {
|
|
134
|
+
...styles.msg,
|
|
135
|
+
...msg.role === "user" ? { ...styles.msgUser, background: accentColor } : styles.msgAi
|
|
136
|
+
},
|
|
137
|
+
children: msg.content || (isLoading && i === messages.length - 1 ? "..." : "")
|
|
138
|
+
},
|
|
139
|
+
i
|
|
140
|
+
)),
|
|
141
|
+
/* @__PURE__ */ jsx2("div", { ref: messagesEndRef })
|
|
142
|
+
] }),
|
|
143
|
+
/* @__PURE__ */ jsxs("form", { style: styles.form, onSubmit: handleSubmit, children: [
|
|
144
|
+
/* @__PURE__ */ jsx2(
|
|
145
|
+
"input",
|
|
146
|
+
{
|
|
147
|
+
style: styles.input,
|
|
148
|
+
value: input,
|
|
149
|
+
onChange: (e) => setInput(e.target.value),
|
|
150
|
+
placeholder,
|
|
151
|
+
disabled: isLoading
|
|
152
|
+
}
|
|
153
|
+
),
|
|
154
|
+
/* @__PURE__ */ jsx2(
|
|
155
|
+
"button",
|
|
156
|
+
{
|
|
157
|
+
type: "submit",
|
|
158
|
+
disabled: isLoading || !input.trim(),
|
|
159
|
+
style: { ...styles.sendBtn, background: accentColor },
|
|
160
|
+
children: "Send"
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
] })
|
|
164
|
+
] });
|
|
165
|
+
if (mode === "inline") return panel;
|
|
166
|
+
const isLeft = position === "bottom-left";
|
|
167
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
168
|
+
isOpen && /* @__PURE__ */ jsx2("div", { style: { ...styles.floatingPanel, ...isLeft ? { left: 24, right: "auto" } : {} }, children: panel }),
|
|
169
|
+
/* @__PURE__ */ jsx2(
|
|
170
|
+
"button",
|
|
171
|
+
{
|
|
172
|
+
onClick: () => setIsOpen(!isOpen),
|
|
173
|
+
style: {
|
|
174
|
+
...styles.fab,
|
|
175
|
+
background: `linear-gradient(135deg, ${accentColor}, ${adjustColor(accentColor, -20)})`,
|
|
176
|
+
...isLeft ? { left: 24, right: "auto" } : {}
|
|
177
|
+
},
|
|
178
|
+
children: /* @__PURE__ */ jsx2("svg", { width: "28", height: "28", viewBox: "0 0 24 24", fill: "white", children: /* @__PURE__ */ jsx2("path", { d: "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z" }) })
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
] });
|
|
182
|
+
}
|
|
183
|
+
function SyncAgentChat({ config, ...props }) {
|
|
184
|
+
if (config) {
|
|
185
|
+
return /* @__PURE__ */ jsx2(SyncAgentProvider, { config, children: /* @__PURE__ */ jsx2(ChatInner, { ...props }) });
|
|
186
|
+
}
|
|
187
|
+
return /* @__PURE__ */ jsx2(ChatInner, { ...props });
|
|
188
|
+
}
|
|
189
|
+
function adjustColor(hex, amount) {
|
|
190
|
+
const num = parseInt(hex.replace("#", ""), 16);
|
|
191
|
+
const r = Math.min(255, Math.max(0, (num >> 16 & 255) + amount));
|
|
192
|
+
const g = Math.min(255, Math.max(0, (num >> 8 & 255) + amount));
|
|
193
|
+
const b = Math.min(255, Math.max(0, (num & 255) + amount));
|
|
194
|
+
return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
|
|
195
|
+
}
|
|
196
|
+
var styles = {
|
|
197
|
+
fab: {
|
|
198
|
+
position: "fixed",
|
|
199
|
+
bottom: 24,
|
|
200
|
+
right: 24,
|
|
201
|
+
zIndex: 99999,
|
|
202
|
+
width: 56,
|
|
203
|
+
height: 56,
|
|
204
|
+
borderRadius: "50%",
|
|
205
|
+
border: "none",
|
|
206
|
+
cursor: "pointer",
|
|
207
|
+
boxShadow: "0 4px 20px rgba(0,0,0,0.2)",
|
|
208
|
+
display: "flex",
|
|
209
|
+
alignItems: "center",
|
|
210
|
+
justifyContent: "center"
|
|
211
|
+
},
|
|
212
|
+
floatingPanel: {
|
|
213
|
+
position: "fixed",
|
|
214
|
+
bottom: 96,
|
|
215
|
+
right: 24,
|
|
216
|
+
zIndex: 99998,
|
|
217
|
+
width: 400,
|
|
218
|
+
maxWidth: "calc(100vw - 48px)"
|
|
219
|
+
},
|
|
220
|
+
panel: {
|
|
221
|
+
height: 560,
|
|
222
|
+
maxHeight: "calc(100vh - 120px)",
|
|
223
|
+
background: "#fff",
|
|
224
|
+
borderRadius: 16,
|
|
225
|
+
boxShadow: "0 8px 40px rgba(0,0,0,0.15)",
|
|
226
|
+
display: "flex",
|
|
227
|
+
flexDirection: "column",
|
|
228
|
+
overflow: "hidden",
|
|
229
|
+
fontFamily: "system-ui, -apple-system, sans-serif"
|
|
230
|
+
},
|
|
231
|
+
panelInline: {
|
|
232
|
+
height: "100%",
|
|
233
|
+
maxHeight: "none",
|
|
234
|
+
borderRadius: 12,
|
|
235
|
+
boxShadow: "0 2px 12px rgba(0,0,0,0.1)"
|
|
236
|
+
},
|
|
237
|
+
header: {
|
|
238
|
+
padding: "16px",
|
|
239
|
+
color: "white",
|
|
240
|
+
fontWeight: 600,
|
|
241
|
+
fontSize: 15,
|
|
242
|
+
display: "flex",
|
|
243
|
+
alignItems: "center",
|
|
244
|
+
justifyContent: "space-between"
|
|
245
|
+
},
|
|
246
|
+
closeBtn: {
|
|
247
|
+
background: "none",
|
|
248
|
+
border: "none",
|
|
249
|
+
color: "white",
|
|
250
|
+
cursor: "pointer",
|
|
251
|
+
padding: 4,
|
|
252
|
+
fontSize: 20
|
|
253
|
+
},
|
|
254
|
+
messages: {
|
|
255
|
+
flex: 1,
|
|
256
|
+
overflowY: "auto",
|
|
257
|
+
padding: 16,
|
|
258
|
+
display: "flex",
|
|
259
|
+
flexDirection: "column",
|
|
260
|
+
gap: 12
|
|
261
|
+
},
|
|
262
|
+
welcome: { textAlign: "center", padding: "40px 20px" },
|
|
263
|
+
msg: {
|
|
264
|
+
maxWidth: "85%",
|
|
265
|
+
padding: "10px 14px",
|
|
266
|
+
borderRadius: 12,
|
|
267
|
+
fontSize: 14,
|
|
268
|
+
lineHeight: 1.5,
|
|
269
|
+
wordBreak: "break-word",
|
|
270
|
+
whiteSpace: "pre-wrap"
|
|
271
|
+
},
|
|
272
|
+
msgUser: {
|
|
273
|
+
alignSelf: "flex-end",
|
|
274
|
+
color: "white",
|
|
275
|
+
borderBottomRightRadius: 4
|
|
276
|
+
},
|
|
277
|
+
msgAi: {
|
|
278
|
+
alignSelf: "flex-start",
|
|
279
|
+
background: "#f3f4f6",
|
|
280
|
+
color: "#1f2937",
|
|
281
|
+
borderBottomLeftRadius: 4
|
|
282
|
+
},
|
|
283
|
+
form: {
|
|
284
|
+
padding: "12px 16px",
|
|
285
|
+
borderTop: "1px solid #e5e7eb",
|
|
286
|
+
display: "flex",
|
|
287
|
+
gap: 8
|
|
288
|
+
},
|
|
289
|
+
input: {
|
|
290
|
+
flex: 1,
|
|
291
|
+
padding: "10px 14px",
|
|
292
|
+
border: "1px solid #d1d5db",
|
|
293
|
+
borderRadius: 10,
|
|
294
|
+
fontSize: 14,
|
|
295
|
+
outline: "none"
|
|
296
|
+
},
|
|
297
|
+
sendBtn: {
|
|
298
|
+
padding: "10px 16px",
|
|
299
|
+
color: "white",
|
|
300
|
+
border: "none",
|
|
301
|
+
borderRadius: 10,
|
|
302
|
+
cursor: "pointer",
|
|
303
|
+
fontWeight: 600,
|
|
304
|
+
fontSize: 14
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// src/index.ts
|
|
309
|
+
import { SyncAgentClient as SyncAgentClient2 } from "@syncagent/js";
|
|
310
|
+
export {
|
|
311
|
+
SyncAgentChat,
|
|
312
|
+
SyncAgentClient2 as SyncAgentClient,
|
|
313
|
+
SyncAgentProvider,
|
|
314
|
+
useSyncAgent,
|
|
315
|
+
useSyncAgentClient
|
|
316
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@syncagent/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SyncAgent React SDK — AI database chat widget & hooks",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": ["dist"],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup",
|
|
18
|
+
"dev": "tsup --watch"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"react": ">=18.0.0",
|
|
22
|
+
"react-dom": ">=18.0.0",
|
|
23
|
+
"@syncagent/js": ">=0.1.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@syncagent/js": "file:../js",
|
|
27
|
+
"@types/react": "^19.1.4",
|
|
28
|
+
"@types/react-dom": "^19.1.5",
|
|
29
|
+
"react": "^19.1.0",
|
|
30
|
+
"react-dom": "^19.1.0",
|
|
31
|
+
"tsup": "^8.4.0",
|
|
32
|
+
"typescript": "^5.8.3"
|
|
33
|
+
},
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"keywords": ["syncagent", "ai", "database", "agent", "react", "chat", "widget"]
|
|
36
|
+
}
|