@byteoniclabs/intake 1.0.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 ADDED
@@ -0,0 +1,187 @@
1
+ # @byteoniclabs/intake
2
+
3
+ The official, lightweight client library to easily send form submissions and integrate real-time AI Chatbots securely into your Byteonic Intake projects. Works seamlessly with **Vanilla JS**, **React**, and **Node.js** backends.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @byteoniclabs/intake
9
+ # or
10
+ yarn add @byteoniclabs/intake
11
+ # or
12
+ pnpm add @byteoniclabs/intake
13
+ ```
14
+
15
+ ---
16
+
17
+ ## 1. Forms (React & Vanilla JS)
18
+
19
+ The easiest way to use Intake forms in React is via our native hooks.
20
+
21
+ ### React: Simple Form Hook
22
+
23
+ ```tsx
24
+ import { useByteonicIntake } from '@byteoniclabs/intake/react';
25
+
26
+ export default function ContactForm() {
27
+ // Pass your publishable API key and form slug
28
+ const { submit, isLoading, isSuccess, error } = useByteonicIntake({
29
+ formSlug: 'contact-form',
30
+ apiKey: 'YOUR_PUBLISHABLE_API_KEY', // You can also set VITE_BYTEONIC_API_KEY / NEXT_PUBLIC_BYTEONIC_API_KEY
31
+ });
32
+
33
+ if (isSuccess) {
34
+ return <div>Thanks for reaching out!</div>;
35
+ }
36
+
37
+ return (
38
+ <form onSubmit={submit}>
39
+ <input type="text" name="name" required placeholder="Name" />
40
+ <input type="email" name="email" required placeholder="Email" />
41
+ <button type="submit" disabled={isLoading}>
42
+ {isLoading ? 'Sending...' : 'Send'}
43
+ </button>
44
+ {error && <p style={{ color: 'red' }}>{error}</p>}
45
+ </form>
46
+ );
47
+ }
48
+ ```
49
+
50
+ ### React: Spam Protection (Honeypot)
51
+
52
+ Prevent bot spam effortlessly without annoying CAPTCHAs. Just drop `<ByteonicHoneypot />` anywhere inside your `<form>`! It renders an invisible input field that tricks bots into filling it out.
53
+
54
+ ```tsx
55
+ import { useByteonicIntake, ByteonicHoneypot } from '@byteoniclabs/intake/react';
56
+
57
+ export default function SafeForm() {
58
+ const { submit } = useByteonicIntake('contact-form');
59
+
60
+ return (
61
+ <form onSubmit={submit}>
62
+ <input type="text" name="name" placeholder="Name" />
63
+ {/* Invisible to humans, catches bots automatically */}
64
+ <ByteonicHoneypot />
65
+ <button type="submit">Submit</button>
66
+ </form>
67
+ );
68
+ }
69
+ ```
70
+
71
+ ### Vanilla JS Usage
72
+
73
+ If you're not using React, import the core client and use it anywhere. It automatically parses `FormData`.
74
+
75
+ ```javascript
76
+ import { ByteonicClient } from '@byteoniclabs/intake';
77
+
78
+ const client = new ByteonicClient({ apiKey: 'YOUR_PUBLISHABLE_API_KEY' });
79
+ const myForm = document.getElementById('my-form');
80
+
81
+ myForm.addEventListener('submit', async (e) => {
82
+ e.preventDefault();
83
+
84
+ // Pass the FormData object directly!
85
+ const response = await client.submit('contact-form', new FormData(myForm));
86
+
87
+ if (response.success) {
88
+ alert('Success!');
89
+ }
90
+ });
91
+ ```
92
+
93
+ ---
94
+
95
+ ## 2. AI Chatbot (React)
96
+
97
+ Deploy a fully customized, real-time AI Support Chatbot with built-in streaming directly into your UI.
98
+
99
+ ```tsx
100
+ import { useState } from 'react';
101
+ import { useByteonicChatbot } from '@byteoniclabs/intake/react';
102
+
103
+ export default function SupportChat() {
104
+ const {
105
+ messages,
106
+ sendMessage,
107
+ isLoading,
108
+ isStreaming,
109
+ streamedMessage,
110
+ config,
111
+ clearHistory
112
+ } = useByteonicChatbot({
113
+ botId: 'YOUR_BOT_ID',
114
+ apiKey: 'YOUR_PUBLISHABLE_API_KEY'
115
+ });
116
+
117
+ const [input, setInput] = useState('');
118
+
119
+ return (
120
+ <div className="chat-container">
121
+ {/* 1. Display chat history (automatically saved to localStorage!) */}
122
+ {messages.map((m, i) => (
123
+ <div key={i} className={m.role === 'user' ? 'user-bubble' : 'bot-bubble'}>
124
+ {m.content}
125
+ </div>
126
+ ))}
127
+
128
+ {/* 2. Display real-time streaming text as the AI types */}
129
+ {isStreaming && streamedMessage && (
130
+ <div className="bot-bubble streaming-effect">
131
+ {streamedMessage} <span className="cursor-blink">|</span>
132
+ </div>
133
+ )}
134
+
135
+ {/* 3. Send messages */}
136
+ <form onSubmit={(e) => { e.preventDefault(); sendMessage(input); setInput(''); }}>
137
+ <input value={input} onChange={e => setInput(e.target.value)} placeholder="Ask me anything..." />
138
+ <button type="submit" disabled={isLoading}>Send</button>
139
+ </form>
140
+ </div>
141
+ );
142
+ }
143
+ ```
144
+
145
+ ---
146
+
147
+ ## 3. Node.js Server SDK
148
+
149
+ If you are handling form submissions on your own backend (like Next.js API Routes, Express, or Fastify) and want to securely proxy the data to Byteonic Intake without exposing your API keys, use the Server SDK.
150
+
151
+ **Features:**
152
+ - Securely spoofs the Origin domain to safely bypass strict whitelisting rules.
153
+ - Forwards user IP addresses to Intake for accurate analytics.
154
+
155
+ ```typescript
156
+ import { ByteonicServerClient } from '@byteoniclabs/intake/server';
157
+
158
+ export async function POST(request) {
159
+ const body = await request.json();
160
+
161
+ // Create client with your private API key
162
+ const client = new ByteonicServerClient({
163
+ apiKey: process.env.BYTEONIC_PRIVATE_API_KEY,
164
+ origin: 'https://my-website.com' // Legally bypass domain whitelisting
165
+ });
166
+
167
+ const response = await client.submit('contact-form', body, {
168
+ userIp: request.headers.get('x-forwarded-for'),
169
+ userAgent: request.headers.get('user-agent'),
170
+ sourceUrl: 'Backend API Submission'
171
+ });
172
+
173
+ return Response.json(response);
174
+ }
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Features
180
+
181
+ - **Zero Configuration**: Connects directly to Byteonic Intake's API.
182
+ - **Real-Time Streaming**: Handles complex SSE JSON chunking securely.
183
+ - **LocalStorage Sync**: The React Chatbot automatically persists user conversations securely.
184
+ - **FormData Support**: Automatically digests native HTML `<form>` elements.
185
+ - **Full TypeScript** support out of the box.
186
+
187
+ *Built by [Byteonic Labs](https://byteoniclabs.com)*
@@ -0,0 +1,42 @@
1
+ import { B as ByteonicConfig, S as SubmissionResponse } from './types-1XzE88JK.mjs';
2
+
3
+ declare class ByteonicClient {
4
+ private apiKey;
5
+ private baseUrl;
6
+ constructor(config: ByteonicConfig);
7
+ /**
8
+ * Submit form data to Byteonic Intake
9
+ * @param formSlug The slug of the form to submit to
10
+ * @param data The form data (Record<string, any> or FormData)
11
+ */
12
+ submit(formSlug: string, data: Record<string, any> | FormData): Promise<SubmissionResponse>;
13
+ }
14
+
15
+ interface ChatMessage {
16
+ role: 'user' | 'assistant' | 'system';
17
+ content: string;
18
+ }
19
+ interface ChatbotConfig {
20
+ botId: string;
21
+ name?: string;
22
+ theme?: string;
23
+ welcomeMessage?: string;
24
+ [key: string]: any;
25
+ }
26
+ interface GenerateResponse {
27
+ success: boolean;
28
+ message?: string;
29
+ error?: string;
30
+ }
31
+ interface GenerateOptions {
32
+ onChunk?: (fullMessage: string) => void;
33
+ }
34
+ declare class ByteonicChatClient {
35
+ private apiKey;
36
+ private baseUrl;
37
+ constructor(config: ByteonicConfig);
38
+ getConfig(botId: string): Promise<ChatbotConfig | null>;
39
+ generate(botId: string, sessionId: string, message: string, history?: ChatMessage[], options?: GenerateOptions): Promise<GenerateResponse>;
40
+ }
41
+
42
+ export { ByteonicChatClient, ByteonicClient, ByteonicConfig, type ChatMessage, type ChatbotConfig, type GenerateOptions, type GenerateResponse, SubmissionResponse };
@@ -0,0 +1,42 @@
1
+ import { B as ByteonicConfig, S as SubmissionResponse } from './types-1XzE88JK.js';
2
+
3
+ declare class ByteonicClient {
4
+ private apiKey;
5
+ private baseUrl;
6
+ constructor(config: ByteonicConfig);
7
+ /**
8
+ * Submit form data to Byteonic Intake
9
+ * @param formSlug The slug of the form to submit to
10
+ * @param data The form data (Record<string, any> or FormData)
11
+ */
12
+ submit(formSlug: string, data: Record<string, any> | FormData): Promise<SubmissionResponse>;
13
+ }
14
+
15
+ interface ChatMessage {
16
+ role: 'user' | 'assistant' | 'system';
17
+ content: string;
18
+ }
19
+ interface ChatbotConfig {
20
+ botId: string;
21
+ name?: string;
22
+ theme?: string;
23
+ welcomeMessage?: string;
24
+ [key: string]: any;
25
+ }
26
+ interface GenerateResponse {
27
+ success: boolean;
28
+ message?: string;
29
+ error?: string;
30
+ }
31
+ interface GenerateOptions {
32
+ onChunk?: (fullMessage: string) => void;
33
+ }
34
+ declare class ByteonicChatClient {
35
+ private apiKey;
36
+ private baseUrl;
37
+ constructor(config: ByteonicConfig);
38
+ getConfig(botId: string): Promise<ChatbotConfig | null>;
39
+ generate(botId: string, sessionId: string, message: string, history?: ChatMessage[], options?: GenerateOptions): Promise<GenerateResponse>;
40
+ }
41
+
42
+ export { ByteonicChatClient, ByteonicClient, ByteonicConfig, type ChatMessage, type ChatbotConfig, type GenerateOptions, type GenerateResponse, SubmissionResponse };
package/dist/index.js ADDED
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ByteonicChatClient: () => ByteonicChatClient,
24
+ ByteonicClient: () => ByteonicClient
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/client.ts
29
+ var ByteonicClient = class {
30
+ constructor(config) {
31
+ if (!config.apiKey) {
32
+ throw new Error("[Byteonic Intake] API Key is required");
33
+ }
34
+ this.apiKey = config.apiKey;
35
+ this.baseUrl = config.baseUrl || "https://intake.byteoniclabs.com/api/external";
36
+ }
37
+ /**
38
+ * Submit form data to Byteonic Intake
39
+ * @param formSlug The slug of the form to submit to
40
+ * @param data The form data (Record<string, any> or FormData)
41
+ */
42
+ async submit(formSlug, data) {
43
+ const url = `${this.baseUrl}/forms/${formSlug}/submit`;
44
+ const meta = {
45
+ sdk_version: "1.0.0",
46
+ source_url: typeof window !== "undefined" ? window.location.href : "server",
47
+ submission_source: "byteonic_intake_sdk"
48
+ };
49
+ let fetchOptions = {
50
+ method: "POST",
51
+ headers: {
52
+ "Authorization": `Bearer ${this.apiKey}`
53
+ }
54
+ };
55
+ if (typeof FormData !== "undefined" && data instanceof FormData) {
56
+ data.append("_meta", JSON.stringify(meta));
57
+ fetchOptions.body = data;
58
+ } else {
59
+ const payload = data;
60
+ payload._meta = meta;
61
+ fetchOptions.headers = {
62
+ ...fetchOptions.headers,
63
+ "Content-Type": "application/json"
64
+ };
65
+ fetchOptions.body = JSON.stringify(payload);
66
+ }
67
+ try {
68
+ const response = await fetch(url, fetchOptions);
69
+ const responseData = await response.json().catch(() => ({}));
70
+ if (!response.ok) {
71
+ throw new Error(responseData.error || responseData.message || "Submission failed");
72
+ }
73
+ return responseData;
74
+ } catch (error) {
75
+ return {
76
+ success: false,
77
+ error: error.message || "An unexpected error occurred"
78
+ };
79
+ }
80
+ }
81
+ };
82
+
83
+ // src/chat.ts
84
+ var ByteonicChatClient = class {
85
+ constructor(config) {
86
+ if (!config.apiKey) {
87
+ throw new Error("[Byteonic Intake] API Key is required");
88
+ }
89
+ this.apiKey = config.apiKey;
90
+ this.baseUrl = config.baseUrl || "https://backend.intake.byteoniclabs.com/api/v1/chat";
91
+ }
92
+ async getConfig(botId) {
93
+ const url = `${this.baseUrl}/config?id=${botId}`;
94
+ try {
95
+ const response = await fetch(url, {
96
+ headers: {
97
+ "Authorization": `Bearer ${this.apiKey}`
98
+ }
99
+ });
100
+ if (!response.ok) return null;
101
+ return await response.json();
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
106
+ async generate(botId, sessionId, message, history = [], options) {
107
+ const url = `${this.baseUrl}/generate`;
108
+ try {
109
+ const response = await fetch(url, {
110
+ method: "POST",
111
+ headers: {
112
+ "Content-Type": "application/json",
113
+ "Authorization": `Bearer ${this.apiKey}`
114
+ },
115
+ body: JSON.stringify({
116
+ chatbot_id: botId,
117
+ session_id: sessionId,
118
+ message,
119
+ history
120
+ })
121
+ });
122
+ if (!response.ok) {
123
+ const errorData = await response.json().catch(() => ({}));
124
+ throw new Error(errorData.error || "Generation failed");
125
+ }
126
+ const reader = response.body?.getReader();
127
+ const decoder = new TextDecoder("utf-8");
128
+ let fullMessage = "";
129
+ if (reader) {
130
+ let done = false;
131
+ let buffer = "";
132
+ while (!done) {
133
+ const { value, done: readerDone } = await reader.read();
134
+ done = readerDone;
135
+ if (value) {
136
+ buffer += decoder.decode(value, { stream: true });
137
+ const lines = buffer.split("\n");
138
+ buffer = lines.pop() || "";
139
+ for (const line of lines) {
140
+ if (line.startsWith("data: ") && !line.includes("[DONE]")) {
141
+ try {
142
+ const data = JSON.parse(line.slice(6));
143
+ const content = data.choices?.[0]?.delta?.content;
144
+ if (content) {
145
+ fullMessage += content;
146
+ if (options?.onChunk) options.onChunk(fullMessage);
147
+ }
148
+ } catch (e) {
149
+ }
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+ return { success: true, message: fullMessage.trim() || "No response generated." };
156
+ } catch (err) {
157
+ return { success: false, error: err.message || "Network error" };
158
+ }
159
+ }
160
+ };
161
+ // Annotate the CommonJS export names for ESM import in node:
162
+ 0 && (module.exports = {
163
+ ByteonicChatClient,
164
+ ByteonicClient
165
+ });
166
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/chat.ts"],"sourcesContent":["export * from './client';\r\nexport * from './chat';\r\nexport * from './types';\n","import { ByteonicConfig, SubmissionResponse } from './types';\r\n\r\nexport class ByteonicClient {\r\n private apiKey: string;\r\n private baseUrl: string;\r\n\r\n constructor(config: ByteonicConfig) {\r\n if (!config.apiKey) {\r\n throw new Error('[Byteonic Intake] API Key is required');\r\n }\r\n this.apiKey = config.apiKey;\r\n this.baseUrl = config.baseUrl || 'https://intake.byteoniclabs.com/api/external';\r\n }\r\n\r\n /**\r\n * Submit form data to Byteonic Intake\r\n * @param formSlug The slug of the form to submit to\r\n * @param data The form data (Record<string, any> or FormData)\r\n */\r\n async submit(formSlug: string, data: Record<string, any> | FormData): Promise<SubmissionResponse> {\r\n const url = `${this.baseUrl}/forms/${formSlug}/submit`;\r\n \r\n // Auto-track metadata\r\n const meta = {\r\n sdk_version: '1.0.0',\r\n source_url: typeof window !== 'undefined' ? window.location.href : 'server',\r\n submission_source: 'byteonic_intake_sdk'\r\n };\r\n\r\n let fetchOptions: RequestInit = {\r\n method: 'POST',\r\n headers: {\r\n 'Authorization': `Bearer ${this.apiKey}`\r\n }\r\n };\r\n\r\n if (typeof FormData !== 'undefined' && data instanceof FormData) {\r\n // Native File uploads require FormData to remain untouched.\r\n // Do NOT set Content-Type header, the browser sets the multi-part boundary automatically.\r\n data.append('_meta', JSON.stringify(meta));\r\n fetchOptions.body = data;\r\n } else {\r\n // Standard JSON payload\r\n const payload = data as Record<string, any>;\r\n payload._meta = meta;\r\n \r\n fetchOptions.headers = {\r\n ...fetchOptions.headers,\r\n 'Content-Type': 'application/json'\r\n };\r\n fetchOptions.body = JSON.stringify(payload);\r\n }\r\n\r\n try {\r\n const response = await fetch(url, fetchOptions);\r\n\r\n const responseData = await response.json().catch(() => ({}));\r\n \r\n if (!response.ok) {\r\n throw new Error(responseData.error || responseData.message || 'Submission failed');\r\n }\r\n \r\n return responseData;\r\n } catch (error: any) {\r\n return {\r\n success: false,\r\n error: error.message || 'An unexpected error occurred'\r\n };\r\n }\r\n }\r\n}\r\n","import { ByteonicConfig } from './types';\n\nexport interface ChatMessage {\n role: 'user' | 'assistant' | 'system';\n content: string;\n}\n\nexport interface ChatbotConfig {\n botId: string;\n name?: string;\n theme?: string;\n welcomeMessage?: string;\n [key: string]: any;\n}\n\nexport interface GenerateResponse {\n success: boolean;\n message?: string;\n error?: string;\n}\n\nexport interface GenerateOptions {\n onChunk?: (fullMessage: string) => void;\n}\n\nexport class ByteonicChatClient {\n private apiKey: string;\n private baseUrl: string;\n\n constructor(config: ByteonicConfig) {\n if (!config.apiKey) {\n throw new Error('[Byteonic Intake] API Key is required');\n }\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl || 'https://backend.intake.byteoniclabs.com/api/v1/chat';\n }\n\n async getConfig(botId: string): Promise<ChatbotConfig | null> {\n const url = `${this.baseUrl}/config?id=${botId}`;\n try {\n const response = await fetch(url, {\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`\n }\n });\n if (!response.ok) return null;\n return await response.json();\n } catch {\n return null;\n }\n }\n\n async generate(botId: string, sessionId: string, message: string, history: ChatMessage[] = [], options?: GenerateOptions): Promise<GenerateResponse> {\n const url = `${this.baseUrl}/generate`;\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify({ \n chatbot_id: botId, \n session_id: sessionId, \n message, \n history \n })\n });\n \n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(errorData.error || 'Generation failed');\n }\n\n const reader = response.body?.getReader();\n const decoder = new TextDecoder('utf-8');\n let fullMessage = '';\n\n if (reader) {\n let done = false;\n let buffer = '';\n while (!done) {\n const { value, done: readerDone } = await reader.read();\n done = readerDone;\n if (value) {\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || ''; // Keep incomplete lines in buffer\n\n for (const line of lines) {\n if (line.startsWith('data: ') && !line.includes('[DONE]')) {\n try {\n const data = JSON.parse(line.slice(6));\n const content = data.choices?.[0]?.delta?.content;\n if (content) {\n fullMessage += content;\n if (options?.onChunk) options.onChunk(fullMessage);\n }\n } catch (e) {\n // Ignore partial chunk parse errors\n }\n }\n }\n }\n }\n }\n\n return { success: true, message: fullMessage.trim() || 'No response generated.' };\n } catch (err: any) {\n return { success: false, error: err.message || 'Network error' };\n }\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,138 @@
1
+ // src/client.ts
2
+ var ByteonicClient = class {
3
+ constructor(config) {
4
+ if (!config.apiKey) {
5
+ throw new Error("[Byteonic Intake] API Key is required");
6
+ }
7
+ this.apiKey = config.apiKey;
8
+ this.baseUrl = config.baseUrl || "https://intake.byteoniclabs.com/api/external";
9
+ }
10
+ /**
11
+ * Submit form data to Byteonic Intake
12
+ * @param formSlug The slug of the form to submit to
13
+ * @param data The form data (Record<string, any> or FormData)
14
+ */
15
+ async submit(formSlug, data) {
16
+ const url = `${this.baseUrl}/forms/${formSlug}/submit`;
17
+ const meta = {
18
+ sdk_version: "1.0.0",
19
+ source_url: typeof window !== "undefined" ? window.location.href : "server",
20
+ submission_source: "byteonic_intake_sdk"
21
+ };
22
+ let fetchOptions = {
23
+ method: "POST",
24
+ headers: {
25
+ "Authorization": `Bearer ${this.apiKey}`
26
+ }
27
+ };
28
+ if (typeof FormData !== "undefined" && data instanceof FormData) {
29
+ data.append("_meta", JSON.stringify(meta));
30
+ fetchOptions.body = data;
31
+ } else {
32
+ const payload = data;
33
+ payload._meta = meta;
34
+ fetchOptions.headers = {
35
+ ...fetchOptions.headers,
36
+ "Content-Type": "application/json"
37
+ };
38
+ fetchOptions.body = JSON.stringify(payload);
39
+ }
40
+ try {
41
+ const response = await fetch(url, fetchOptions);
42
+ const responseData = await response.json().catch(() => ({}));
43
+ if (!response.ok) {
44
+ throw new Error(responseData.error || responseData.message || "Submission failed");
45
+ }
46
+ return responseData;
47
+ } catch (error) {
48
+ return {
49
+ success: false,
50
+ error: error.message || "An unexpected error occurred"
51
+ };
52
+ }
53
+ }
54
+ };
55
+
56
+ // src/chat.ts
57
+ var ByteonicChatClient = class {
58
+ constructor(config) {
59
+ if (!config.apiKey) {
60
+ throw new Error("[Byteonic Intake] API Key is required");
61
+ }
62
+ this.apiKey = config.apiKey;
63
+ this.baseUrl = config.baseUrl || "https://backend.intake.byteoniclabs.com/api/v1/chat";
64
+ }
65
+ async getConfig(botId) {
66
+ const url = `${this.baseUrl}/config?id=${botId}`;
67
+ try {
68
+ const response = await fetch(url, {
69
+ headers: {
70
+ "Authorization": `Bearer ${this.apiKey}`
71
+ }
72
+ });
73
+ if (!response.ok) return null;
74
+ return await response.json();
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+ async generate(botId, sessionId, message, history = [], options) {
80
+ const url = `${this.baseUrl}/generate`;
81
+ try {
82
+ const response = await fetch(url, {
83
+ method: "POST",
84
+ headers: {
85
+ "Content-Type": "application/json",
86
+ "Authorization": `Bearer ${this.apiKey}`
87
+ },
88
+ body: JSON.stringify({
89
+ chatbot_id: botId,
90
+ session_id: sessionId,
91
+ message,
92
+ history
93
+ })
94
+ });
95
+ if (!response.ok) {
96
+ const errorData = await response.json().catch(() => ({}));
97
+ throw new Error(errorData.error || "Generation failed");
98
+ }
99
+ const reader = response.body?.getReader();
100
+ const decoder = new TextDecoder("utf-8");
101
+ let fullMessage = "";
102
+ if (reader) {
103
+ let done = false;
104
+ let buffer = "";
105
+ while (!done) {
106
+ const { value, done: readerDone } = await reader.read();
107
+ done = readerDone;
108
+ if (value) {
109
+ buffer += decoder.decode(value, { stream: true });
110
+ const lines = buffer.split("\n");
111
+ buffer = lines.pop() || "";
112
+ for (const line of lines) {
113
+ if (line.startsWith("data: ") && !line.includes("[DONE]")) {
114
+ try {
115
+ const data = JSON.parse(line.slice(6));
116
+ const content = data.choices?.[0]?.delta?.content;
117
+ if (content) {
118
+ fullMessage += content;
119
+ if (options?.onChunk) options.onChunk(fullMessage);
120
+ }
121
+ } catch (e) {
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }
128
+ return { success: true, message: fullMessage.trim() || "No response generated." };
129
+ } catch (err) {
130
+ return { success: false, error: err.message || "Network error" };
131
+ }
132
+ }
133
+ };
134
+ export {
135
+ ByteonicChatClient,
136
+ ByteonicClient
137
+ };
138
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts","../src/chat.ts"],"sourcesContent":["import { ByteonicConfig, SubmissionResponse } from './types';\r\n\r\nexport class ByteonicClient {\r\n private apiKey: string;\r\n private baseUrl: string;\r\n\r\n constructor(config: ByteonicConfig) {\r\n if (!config.apiKey) {\r\n throw new Error('[Byteonic Intake] API Key is required');\r\n }\r\n this.apiKey = config.apiKey;\r\n this.baseUrl = config.baseUrl || 'https://intake.byteoniclabs.com/api/external';\r\n }\r\n\r\n /**\r\n * Submit form data to Byteonic Intake\r\n * @param formSlug The slug of the form to submit to\r\n * @param data The form data (Record<string, any> or FormData)\r\n */\r\n async submit(formSlug: string, data: Record<string, any> | FormData): Promise<SubmissionResponse> {\r\n const url = `${this.baseUrl}/forms/${formSlug}/submit`;\r\n \r\n // Auto-track metadata\r\n const meta = {\r\n sdk_version: '1.0.0',\r\n source_url: typeof window !== 'undefined' ? window.location.href : 'server',\r\n submission_source: 'byteonic_intake_sdk'\r\n };\r\n\r\n let fetchOptions: RequestInit = {\r\n method: 'POST',\r\n headers: {\r\n 'Authorization': `Bearer ${this.apiKey}`\r\n }\r\n };\r\n\r\n if (typeof FormData !== 'undefined' && data instanceof FormData) {\r\n // Native File uploads require FormData to remain untouched.\r\n // Do NOT set Content-Type header, the browser sets the multi-part boundary automatically.\r\n data.append('_meta', JSON.stringify(meta));\r\n fetchOptions.body = data;\r\n } else {\r\n // Standard JSON payload\r\n const payload = data as Record<string, any>;\r\n payload._meta = meta;\r\n \r\n fetchOptions.headers = {\r\n ...fetchOptions.headers,\r\n 'Content-Type': 'application/json'\r\n };\r\n fetchOptions.body = JSON.stringify(payload);\r\n }\r\n\r\n try {\r\n const response = await fetch(url, fetchOptions);\r\n\r\n const responseData = await response.json().catch(() => ({}));\r\n \r\n if (!response.ok) {\r\n throw new Error(responseData.error || responseData.message || 'Submission failed');\r\n }\r\n \r\n return responseData;\r\n } catch (error: any) {\r\n return {\r\n success: false,\r\n error: error.message || 'An unexpected error occurred'\r\n };\r\n }\r\n }\r\n}\r\n","import { ByteonicConfig } from './types';\n\nexport interface ChatMessage {\n role: 'user' | 'assistant' | 'system';\n content: string;\n}\n\nexport interface ChatbotConfig {\n botId: string;\n name?: string;\n theme?: string;\n welcomeMessage?: string;\n [key: string]: any;\n}\n\nexport interface GenerateResponse {\n success: boolean;\n message?: string;\n error?: string;\n}\n\nexport interface GenerateOptions {\n onChunk?: (fullMessage: string) => void;\n}\n\nexport class ByteonicChatClient {\n private apiKey: string;\n private baseUrl: string;\n\n constructor(config: ByteonicConfig) {\n if (!config.apiKey) {\n throw new Error('[Byteonic Intake] API Key is required');\n }\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl || 'https://backend.intake.byteoniclabs.com/api/v1/chat';\n }\n\n async getConfig(botId: string): Promise<ChatbotConfig | null> {\n const url = `${this.baseUrl}/config?id=${botId}`;\n try {\n const response = await fetch(url, {\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`\n }\n });\n if (!response.ok) return null;\n return await response.json();\n } catch {\n return null;\n }\n }\n\n async generate(botId: string, sessionId: string, message: string, history: ChatMessage[] = [], options?: GenerateOptions): Promise<GenerateResponse> {\n const url = `${this.baseUrl}/generate`;\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify({ \n chatbot_id: botId, \n session_id: sessionId, \n message, \n history \n })\n });\n \n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(errorData.error || 'Generation failed');\n }\n\n const reader = response.body?.getReader();\n const decoder = new TextDecoder('utf-8');\n let fullMessage = '';\n\n if (reader) {\n let done = false;\n let buffer = '';\n while (!done) {\n const { value, done: readerDone } = await reader.read();\n done = readerDone;\n if (value) {\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || ''; // Keep incomplete lines in buffer\n\n for (const line of lines) {\n if (line.startsWith('data: ') && !line.includes('[DONE]')) {\n try {\n const data = JSON.parse(line.slice(6));\n const content = data.choices?.[0]?.delta?.content;\n if (content) {\n fullMessage += content;\n if (options?.onChunk) options.onChunk(fullMessage);\n }\n } catch (e) {\n // Ignore partial chunk parse errors\n }\n }\n }\n }\n }\n }\n\n return { success: true, message: fullMessage.trim() || 'No response generated.' };\n } catch (err: any) {\n return { success: false, error: err.message || 'Network error' };\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,51 @@
1
+ import { S as SubmissionResponse, B as ByteonicConfig } from '../types-1XzE88JK.mjs';
2
+ import React$1, { ReactNode } from 'react';
3
+ import { ByteonicClient, ChatMessage, ChatbotConfig } from '../index.mjs';
4
+
5
+ interface UseByteonicIntakeOptions {
6
+ formSlug: string;
7
+ apiKey?: string;
8
+ baseUrl?: string;
9
+ }
10
+ declare function useByteonicIntake<T = Record<string, any>>(options: UseByteonicIntakeOptions | string): {
11
+ submit: (data: T | FormData | React.FormEvent<HTMLFormElement>) => Promise<SubmissionResponse | {
12
+ success: boolean;
13
+ error: any;
14
+ }>;
15
+ isLoading: boolean;
16
+ isSuccess: boolean;
17
+ error: string | null;
18
+ response: SubmissionResponse | null;
19
+ };
20
+
21
+ interface ByteonicProviderProps extends ByteonicConfig {
22
+ children: ReactNode;
23
+ }
24
+ declare function ByteonicProvider({ apiKey, baseUrl, children }: ByteonicProviderProps): React$1.JSX.Element;
25
+ declare function useByteonicClient(): ByteonicClient;
26
+
27
+ /**
28
+ * A hidden input field used to trick bots into filling it out.
29
+ * If this field is submitted, Byteonic Intake will ignore the submission, protecting you from spam.
30
+ *
31
+ * Simply place `<ByteonicHoneypot />` anywhere inside your `<form>` element.
32
+ */
33
+ declare function ByteonicHoneypot(): React$1.JSX.Element;
34
+
35
+ interface UseByteonicChatbotOptions {
36
+ botId: string;
37
+ apiKey?: string;
38
+ baseUrl?: string;
39
+ stream?: boolean;
40
+ }
41
+ declare function useByteonicChatbot(options: UseByteonicChatbotOptions | string): {
42
+ messages: ChatMessage[];
43
+ sendMessage: (content: string) => Promise<void>;
44
+ isLoading: boolean;
45
+ isStreaming: boolean;
46
+ streamedMessage: string;
47
+ config: ChatbotConfig | null;
48
+ clearHistory: () => void;
49
+ };
50
+
51
+ export { ByteonicHoneypot, ByteonicProvider, useByteonicChatbot, useByteonicClient, useByteonicIntake };