@schmitech/chatbot-api 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 ADDED
@@ -0,0 +1,262 @@
1
+ # Chatbot API Client
2
+
3
+ A JavaScript/TypeScript client library for interacting with the Chatbot server.
4
+
5
+ ## Installation
6
+
7
+ ### Option 1: Using npm link (for local development)
8
+
9
+ To use this library in other projects on your local machine during development:
10
+
11
+ 1. In this library directory, run:
12
+ ```bash
13
+ npm run build
14
+ npm link
15
+ ```
16
+
17
+ 2. In your project directory, run:
18
+ ```bash
19
+ npm link chatbot-api
20
+ ```
21
+
22
+ 3. Now you can import and use the library in your project:
23
+ ```javascript
24
+ import { streamChat } from 'chatbot-api';
25
+ ```
26
+
27
+ ### Option 2: Installing from a local directory
28
+
29
+ You can also install the package directly from the local directory:
30
+
31
+ 1. In this library directory, run:
32
+ ```bash
33
+ npm run build
34
+ ```
35
+
36
+ 2. In your project directory, run:
37
+ ```bash
38
+ npm install /path/to/qa-chatbot-server/api
39
+ ```
40
+
41
+ 3. Now you can import and use the library in your project:
42
+ ```javascript
43
+ import { streamChat } from 'chatbot-api';
44
+ ```
45
+
46
+ ### Option 3: Publishing to npm (for production)
47
+
48
+ To make this library available to anyone via npm:
49
+
50
+ 1. Create an account on npmjs.com if you don't have one
51
+ 2. Login to npm from the command line:
52
+ ```bash
53
+ npm login
54
+ ```
55
+ 3. Update the package name in package.json to ensure it's unique
56
+ 4. Publish the package:
57
+ ```bash
58
+ npm publish
59
+ ```
60
+
61
+ ## Usage
62
+
63
+ ### Configuration (Required)
64
+
65
+ Before using any API functions, you **must** configure the client with your server URL:
66
+
67
+ ```javascript
68
+ import { configureApi, streamChat } from 'chatbot-api';
69
+
70
+ // Configure the API with your server URL
71
+ configureApi('https://your-api-server.com');
72
+ ```
73
+
74
+ If you don't configure the API before calling other functions, an error will be thrown.
75
+
76
+ ### Basic Usage
77
+
78
+ ```javascript
79
+ import { configureApi, streamChat } from 'chatbot-api';
80
+
81
+ async function chat() {
82
+ // Configure the API with your server URL (required first step)
83
+ configureApi('https://your-api-server.com');
84
+
85
+ // Stream chat responses
86
+ for await (const response of streamChat('Hello, how can I help you?', false)) {
87
+ // Access the text response
88
+ console.log(response.text);
89
+
90
+ // Check if this is the final response
91
+ if (response.done) {
92
+ console.log('Chat complete!');
93
+ }
94
+ }
95
+ }
96
+
97
+ chat();
98
+ ```
99
+
100
+ ### With Voice Enabled
101
+
102
+ ```javascript
103
+ import { configureApi, streamChat } from 'chatbot-api';
104
+
105
+ async function chatWithVoice() {
106
+ // Configure the API with your server URL
107
+ configureApi('https://your-api-server.com');
108
+
109
+ // Stream chat responses with voice enabled (second parameter true)
110
+ for await (const response of streamChat('Tell me a joke', true)) {
111
+ // Process text responses
112
+ if (response.text) {
113
+ console.log(response.text);
114
+ }
115
+
116
+ // Handle audio content when available
117
+ if (response.type === 'audio' && response.content) {
118
+ // Process audio content (base64 encoded)
119
+ console.log('Received audio content');
120
+ // You can decode and play the audio here
121
+ }
122
+
123
+ if (response.done) {
124
+ console.log('Chat complete!');
125
+ }
126
+ }
127
+ }
128
+
129
+ chatWithVoice();
130
+ ```
131
+
132
+ ### React Example
133
+
134
+ ```jsx
135
+ import React, { useState, useEffect } from 'react';
136
+ import { configureApi, streamChat } from 'chatbot-api';
137
+
138
+ // Configure the API once at the application startup
139
+ configureApi('https://your-api-server.com');
140
+
141
+ function ChatComponent() {
142
+ const [messages, setMessages] = useState([]);
143
+ const [input, setInput] = useState('');
144
+ const [isLoading, setIsLoading] = useState(false);
145
+
146
+ const handleSubmit = async (e) => {
147
+ e.preventDefault();
148
+ if (!input.trim()) return;
149
+
150
+ const userMessage = input;
151
+ setInput('');
152
+ setMessages(prev => [...prev, { text: userMessage, isUser: true }]);
153
+ setIsLoading(true);
154
+
155
+ let fullResponse = '';
156
+
157
+ try {
158
+ for await (const response of streamChat(userMessage, false)) {
159
+ if (response.text) {
160
+ fullResponse += response.text;
161
+ // Update the UI with each chunk of text as it arrives
162
+ setMessages(prev => [
163
+ ...prev.slice(0, -1),
164
+ { text: userMessage, isUser: true },
165
+ { text: fullResponse, isUser: false, isComplete: response.done }
166
+ ]);
167
+ }
168
+
169
+ if (response.done) {
170
+ setIsLoading(false);
171
+ }
172
+ }
173
+ } catch (error) {
174
+ console.error('Chat error:', error);
175
+ setMessages(prev => [...prev, {
176
+ text: `Error: ${error.message}`,
177
+ isUser: false,
178
+ isError: true
179
+ }]);
180
+ setIsLoading(false);
181
+ }
182
+ };
183
+
184
+ return (
185
+ <div className="chat-container">
186
+ <div className="messages">
187
+ {messages.map((msg, i) => (
188
+ <div key={i} className={`message ${msg.isUser ? 'user' : 'bot'}`}>
189
+ {msg.text}
190
+ </div>
191
+ ))}
192
+ {isLoading && <div className="loading">...</div>}
193
+ </div>
194
+
195
+ <form onSubmit={handleSubmit}>
196
+ <input
197
+ value={input}
198
+ onChange={(e) => setInput(e.target.value)}
199
+ placeholder="Type your message..."
200
+ disabled={isLoading}
201
+ />
202
+ <button type="submit" disabled={isLoading}>Send</button>
203
+ </form>
204
+ </div>
205
+ );
206
+ }
207
+
208
+ export default ChatComponent;
209
+ ```
210
+
211
+ ## API Reference
212
+
213
+ ### configureApi(apiUrl)
214
+
215
+ Configures the API client with your server URL.
216
+
217
+ - **apiUrl** (string): The URL of your Chatbot server
218
+ - **Returns**: void
219
+
220
+ ### streamChat(message, voiceEnabled)
221
+
222
+ Streams chat responses from the server.
223
+
224
+ - **message** (string): The message to send to the chatbot
225
+ - **voiceEnabled** (boolean): Whether to enable voice responses
226
+ - **Returns**: AsyncGenerator that yields StreamResponse objects
227
+
228
+ ### StreamResponse Interface
229
+
230
+ ```typescript
231
+ interface StreamResponse {
232
+ text?: string; // The text response
233
+ content?: string; // Alternative property for text content
234
+ done?: boolean; // Whether this is the final response
235
+ type?: string; // Type of response (e.g., 'text', 'audio')
236
+ }
237
+ ```
238
+
239
+ ## Development
240
+
241
+ ### Building the Library
242
+
243
+ ```bash
244
+ npm run build
245
+ ```
246
+
247
+ ### Running Tests
248
+
249
+ ```bash
250
+ # Run tests once
251
+ npm test
252
+
253
+ # Run tests in watch mode
254
+ npm run test:watch
255
+
256
+ # Test a specific query
257
+ npm run test-query "how much is the fee?" "http://your-api-server.com"
258
+ ```
259
+
260
+ ## License
261
+
262
+ MIT
package/api.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export interface StreamResponse {
2
+ text?: string;
3
+ content?: string;
4
+ done?: boolean;
5
+ type?: string;
6
+ }
7
+ export declare const configureApi: (apiUrl: string) => void;
8
+ export declare function streamChat(message: string, voiceEnabled: boolean): AsyncGenerator<StreamResponse>;
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e={};exports.default=e;
2
+ //# sourceMappingURL=__vite-browser-external-BcPniuRQ.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"__vite-browser-external-BcPniuRQ.cjs","sources":["../__vite-browser-external"],"sourcesContent":["export default {}"],"names":["__viteBrowserExternal"],"mappings":"gFAAA,MAAeA,EAAA,CAAA"}
@@ -0,0 +1,5 @@
1
+ const e = {};
2
+ export {
3
+ e as default
4
+ };
5
+ //# sourceMappingURL=__vite-browser-external-DYxpcVy9.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"__vite-browser-external-DYxpcVy9.js","sources":["../__vite-browser-external"],"sourcesContent":["export default {}"],"names":["__viteBrowserExternal"],"mappings":"AAAA,MAAeA,IAAA,CAAA;"}
package/dist/api.cjs ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});let l=null,d=null,y=0,A=0;const m=async()=>{if(typeof window>"u")try{let e,r;try{e=await Promise.resolve().then(()=>require("./__vite-browser-external-BcPniuRQ.cjs")),r=await Promise.resolve().then(()=>require("./__vite-browser-external-BcPniuRQ.cjs"))}catch(t){console.warn("[Connection Pool] Failed to import Node.js modules:",t);return}l=new e.Agent({keepAlive:!0,keepAliveMsecs:3e4,maxSockets:5});const i=l.createConnection;l.createConnection=function(t,o){y++;const a=i.call(this,t,o);return a.on("reuse",()=>{A++}),a},d=new r.Agent({keepAlive:!0,keepAliveMsecs:3e4,maxSockets:5})}catch(e){console.warn("[Connection Pool] Failed to initialize HTTP agents:",e)}};(async()=>{try{await m()}catch(e){console.warn("Failed to initialize connection pool:",e)}})();let f=null;const P=e=>{if(!e||typeof e!="string")throw new Error("API URL must be a valid string");f=e},k=()=>{if(!f)throw new Error("API URL not configured. Please call configureApi() with your server URL before using any API functions.");return f},C=(e,r={})=>{const i=e.startsWith("https:");if(typeof window>"u"){if(i&&d)return{...r,agent:d};if(l)return{...r,agent:l}}const t=Date.now().toString(36)+Math.random().toString(36).substring(2);return{...r,headers:{...r.headers,Connection:"keep-alive","X-Request-ID":t}}};async function*v(e,r){var i;try{const t=k(),o=await fetch(`${t}/chat`,C(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:e,voiceEnabled:r})}));if(!o.ok){const n=await o.text();throw console.error(`API request failed: ${o.status} ${n}`),new Error(`Network response was not ok: ${o.status} ${n}`)}const a=(i=o.body)==null?void 0:i.getReader();if(!a)throw new Error("No reader available");const w=new TextDecoder;let s="";for(;;){const{done:n,value:g}=await a.read();if(n)break;const p=w.decode(g,{stream:!0});s+=p;const h=s.split(`
2
+ `);s=h.pop()||"";for(const u of h)if(u.trim())try{const c=JSON.parse(u);c.content&&!c.text&&(c.text=c.content),yield c}catch{console.warn("Error parsing JSON chunk:",u)}}if(s)try{const n=JSON.parse(s);n.content&&!n.text&&(n.text=n.content),yield n}catch{console.warn("Error parsing final JSON buffer:",s)}}catch(t){console.error("Chat API error:",t.message),yield{text:`Error connecting to chat server: ${t.message}`,done:!0}}}exports.configureApi=P;exports.streamChat=v;
3
+ //# sourceMappingURL=api.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.cjs","sources":["../api.ts"],"sourcesContent":["// For Node.js environments, we can use http.Agent for connection pooling\nlet httpAgent: any = null;\nlet httpsAgent: any = null;\n\n// Track connections for debugging\nlet connectionCounter = 0;\nlet connectionReuseCounter = 0;\n\n// Define the StreamResponse interface\nexport interface StreamResponse {\n text?: string;\n content?: string; // Adding alternative property name\n done?: boolean;\n type?: string; // Type of response (e.g., 'text', 'audio')\n}\n\n// Initialize the HTTP agents for connection pooling\nconst initConnectionPool = async () => {\n // Only run in Node.js environment\n if (typeof window === 'undefined') {\n try {\n // Use dynamic imports for Node.js modules in ESM context\n let http, https;\n \n try {\n http = await import('node:http');\n https = await import('node:https');\n } catch (e) {\n console.warn('[Connection Pool] Failed to import Node.js modules:', e);\n return;\n }\n \n // Create agents with keepAlive enabled and add tracking\n httpAgent = new http.Agent({ \n keepAlive: true,\n keepAliveMsecs: 30000, // 30 seconds\n maxSockets: 5 // Limit parallel connections\n });\n \n // Add tracking for connection reuse\n const originalCreateConnection = httpAgent.createConnection;\n httpAgent.createConnection = function(options: any, callback: any) {\n connectionCounter++;\n \n \n const socket = originalCreateConnection.call(this, options, callback);\n \n socket.on('reuse', () => {\n connectionReuseCounter++;\n });\n \n return socket;\n };\n \n httpsAgent = new https.Agent({ \n keepAlive: true,\n keepAliveMsecs: 30000,\n maxSockets: 5\n });\n \n } catch (error) {\n console.warn('[Connection Pool] Failed to initialize HTTP agents:', error);\n }\n }\n};\n\n// Try to initialize connection pool (as an async function)\n(async () => {\n try {\n await initConnectionPool();\n } catch (error) {\n console.warn('Failed to initialize connection pool:', error);\n }\n})();\n\n// Store the configured API URL\nlet configuredApiUrl: string | null = null;\n\n// Configure the API with a custom URL\nexport const configureApi = (apiUrl: string): void => {\n if (!apiUrl || typeof apiUrl !== 'string') {\n throw new Error('API URL must be a valid string');\n }\n configuredApiUrl = apiUrl;\n}\n\n// Get the configured API URL or throw an error if not configured\nconst getApiUrl = (): string => {\n if (!configuredApiUrl) {\n throw new Error('API URL not configured. Please call configureApi() with your server URL before using any API functions.');\n }\n return configuredApiUrl;\n};\n\n// Helper to get fetch options with connection pooling if available\nconst getFetchOptions = (apiUrl: string, options: RequestInit = {}): RequestInit | any => {\n const isHttps = apiUrl.startsWith('https:');\n \n // Only use agents in Node.js environment\n if (typeof window === 'undefined') {\n if (isHttps && httpsAgent) {\n // Using 'any' type to bypass TypeScript limitations with Node.js http.Agent\n return { ...options, agent: httpsAgent } as any;\n } else if (httpAgent) {\n return { ...options, agent: httpAgent } as any;\n }\n }\n \n // Browser environment\n const requestId = Date.now().toString(36) + Math.random().toString(36).substring(2);\n \n // Use keep-alive header in browser environments\n return {\n ...options,\n headers: {\n ...options.headers,\n 'Connection': 'keep-alive',\n 'X-Request-ID': requestId // Add unique ID to track requests\n }\n };\n};\n\nexport async function* streamChat(\n message: string,\n voiceEnabled: boolean\n): AsyncGenerator<StreamResponse> {\n try {\n // Get the API URL at the time of the request (allows for dynamic configuration)\n const API_URL = getApiUrl();\n \n // Skip the OPTIONS preflight check that was causing CORS issues\n const response = await fetch(`${API_URL}/chat`, getFetchOptions(API_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ message, voiceEnabled }),\n }));\n\n if (!response.ok) {\n const errorText = await response.text();\n console.error(`API request failed: ${response.status} ${errorText}`);\n throw new Error(`Network response was not ok: ${response.status} ${errorText}`);\n }\n \n const reader = response.body?.getReader();\n if (!reader) throw new Error('No reader available');\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n const chunk = decoder.decode(value, { stream: true });\n buffer += chunk;\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (line.trim()) {\n try {\n const data = JSON.parse(line);\n \n // Normalize the response to use 'text' property\n if (data.content && !data.text) {\n data.text = data.content;\n }\n \n yield data;\n } catch (error: any) {\n // Silent error handling for JSON parse errors\n console.warn('Error parsing JSON chunk:', line);\n }\n }\n }\n }\n\n if (buffer) {\n try {\n const data = JSON.parse(buffer);\n \n // Normalize the response to use 'text' property\n if (data.content && !data.text) {\n data.text = data.content;\n }\n \n yield data;\n } catch (error: any) {\n // Silent error handling for JSON parse errors\n console.warn('Error parsing final JSON buffer:', buffer);\n }\n }\n } catch (error: any) {\n console.error('Chat API error:', error.message);\n yield { \n text: `Error connecting to chat server: ${error.message}`, \n done: true \n };\n }\n}"],"names":["httpAgent","httpsAgent","connectionCounter","connectionReuseCounter","initConnectionPool","http","https","e","originalCreateConnection","options","callback","socket","error","configuredApiUrl","configureApi","apiUrl","getApiUrl","getFetchOptions","isHttps","requestId","streamChat","message","voiceEnabled","API_URL","response","errorText","reader","_a","decoder","buffer","done","value","chunk","lines","line","data"],"mappings":"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"}
package/dist/api.mjs ADDED
@@ -0,0 +1,124 @@
1
+ let l = null, d = null, y = 0, A = 0;
2
+ const k = async () => {
3
+ if (typeof window > "u")
4
+ try {
5
+ let e, o;
6
+ try {
7
+ e = await import("./__vite-browser-external-DYxpcVy9.js"), o = await import("./__vite-browser-external-DYxpcVy9.js");
8
+ } catch (t) {
9
+ console.warn("[Connection Pool] Failed to import Node.js modules:", t);
10
+ return;
11
+ }
12
+ l = new e.Agent({
13
+ keepAlive: !0,
14
+ keepAliveMsecs: 3e4,
15
+ // 30 seconds
16
+ maxSockets: 5
17
+ // Limit parallel connections
18
+ });
19
+ const i = l.createConnection;
20
+ l.createConnection = function(t, r) {
21
+ y++;
22
+ const a = i.call(this, t, r);
23
+ return a.on("reuse", () => {
24
+ A++;
25
+ }), a;
26
+ }, d = new o.Agent({
27
+ keepAlive: !0,
28
+ keepAliveMsecs: 3e4,
29
+ maxSockets: 5
30
+ });
31
+ } catch (e) {
32
+ console.warn("[Connection Pool] Failed to initialize HTTP agents:", e);
33
+ }
34
+ };
35
+ (async () => {
36
+ try {
37
+ await k();
38
+ } catch (e) {
39
+ console.warn("Failed to initialize connection pool:", e);
40
+ }
41
+ })();
42
+ let f = null;
43
+ const P = (e) => {
44
+ if (!e || typeof e != "string")
45
+ throw new Error("API URL must be a valid string");
46
+ f = e;
47
+ }, m = () => {
48
+ if (!f)
49
+ throw new Error("API URL not configured. Please call configureApi() with your server URL before using any API functions.");
50
+ return f;
51
+ }, C = (e, o = {}) => {
52
+ const i = e.startsWith("https:");
53
+ if (typeof window > "u") {
54
+ if (i && d)
55
+ return { ...o, agent: d };
56
+ if (l)
57
+ return { ...o, agent: l };
58
+ }
59
+ const t = Date.now().toString(36) + Math.random().toString(36).substring(2);
60
+ return {
61
+ ...o,
62
+ headers: {
63
+ ...o.headers,
64
+ Connection: "keep-alive",
65
+ "X-Request-ID": t
66
+ // Add unique ID to track requests
67
+ }
68
+ };
69
+ };
70
+ async function* x(e, o) {
71
+ var i;
72
+ try {
73
+ const t = m(), r = await fetch(`${t}/chat`, C(t, {
74
+ method: "POST",
75
+ headers: {
76
+ "Content-Type": "application/json"
77
+ },
78
+ body: JSON.stringify({ message: e, voiceEnabled: o })
79
+ }));
80
+ if (!r.ok) {
81
+ const n = await r.text();
82
+ throw console.error(`API request failed: ${r.status} ${n}`), new Error(`Network response was not ok: ${r.status} ${n}`);
83
+ }
84
+ const a = (i = r.body) == null ? void 0 : i.getReader();
85
+ if (!a) throw new Error("No reader available");
86
+ const p = new TextDecoder();
87
+ let s = "";
88
+ for (; ; ) {
89
+ const { done: n, value: w } = await a.read();
90
+ if (n)
91
+ break;
92
+ const g = p.decode(w, { stream: !0 });
93
+ s += g;
94
+ const h = s.split(`
95
+ `);
96
+ s = h.pop() || "";
97
+ for (const u of h)
98
+ if (u.trim())
99
+ try {
100
+ const c = JSON.parse(u);
101
+ c.content && !c.text && (c.text = c.content), yield c;
102
+ } catch {
103
+ console.warn("Error parsing JSON chunk:", u);
104
+ }
105
+ }
106
+ if (s)
107
+ try {
108
+ const n = JSON.parse(s);
109
+ n.content && !n.text && (n.text = n.content), yield n;
110
+ } catch {
111
+ console.warn("Error parsing final JSON buffer:", s);
112
+ }
113
+ } catch (t) {
114
+ console.error("Chat API error:", t.message), yield {
115
+ text: `Error connecting to chat server: ${t.message}`,
116
+ done: !0
117
+ };
118
+ }
119
+ }
120
+ export {
121
+ P as configureApi,
122
+ x as streamChat
123
+ };
124
+ //# sourceMappingURL=api.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.mjs","sources":["../api.ts"],"sourcesContent":["// For Node.js environments, we can use http.Agent for connection pooling\nlet httpAgent: any = null;\nlet httpsAgent: any = null;\n\n// Track connections for debugging\nlet connectionCounter = 0;\nlet connectionReuseCounter = 0;\n\n// Define the StreamResponse interface\nexport interface StreamResponse {\n text?: string;\n content?: string; // Adding alternative property name\n done?: boolean;\n type?: string; // Type of response (e.g., 'text', 'audio')\n}\n\n// Initialize the HTTP agents for connection pooling\nconst initConnectionPool = async () => {\n // Only run in Node.js environment\n if (typeof window === 'undefined') {\n try {\n // Use dynamic imports for Node.js modules in ESM context\n let http, https;\n \n try {\n http = await import('node:http');\n https = await import('node:https');\n } catch (e) {\n console.warn('[Connection Pool] Failed to import Node.js modules:', e);\n return;\n }\n \n // Create agents with keepAlive enabled and add tracking\n httpAgent = new http.Agent({ \n keepAlive: true,\n keepAliveMsecs: 30000, // 30 seconds\n maxSockets: 5 // Limit parallel connections\n });\n \n // Add tracking for connection reuse\n const originalCreateConnection = httpAgent.createConnection;\n httpAgent.createConnection = function(options: any, callback: any) {\n connectionCounter++;\n \n \n const socket = originalCreateConnection.call(this, options, callback);\n \n socket.on('reuse', () => {\n connectionReuseCounter++;\n });\n \n return socket;\n };\n \n httpsAgent = new https.Agent({ \n keepAlive: true,\n keepAliveMsecs: 30000,\n maxSockets: 5\n });\n \n } catch (error) {\n console.warn('[Connection Pool] Failed to initialize HTTP agents:', error);\n }\n }\n};\n\n// Try to initialize connection pool (as an async function)\n(async () => {\n try {\n await initConnectionPool();\n } catch (error) {\n console.warn('Failed to initialize connection pool:', error);\n }\n})();\n\n// Store the configured API URL\nlet configuredApiUrl: string | null = null;\n\n// Configure the API with a custom URL\nexport const configureApi = (apiUrl: string): void => {\n if (!apiUrl || typeof apiUrl !== 'string') {\n throw new Error('API URL must be a valid string');\n }\n configuredApiUrl = apiUrl;\n}\n\n// Get the configured API URL or throw an error if not configured\nconst getApiUrl = (): string => {\n if (!configuredApiUrl) {\n throw new Error('API URL not configured. Please call configureApi() with your server URL before using any API functions.');\n }\n return configuredApiUrl;\n};\n\n// Helper to get fetch options with connection pooling if available\nconst getFetchOptions = (apiUrl: string, options: RequestInit = {}): RequestInit | any => {\n const isHttps = apiUrl.startsWith('https:');\n \n // Only use agents in Node.js environment\n if (typeof window === 'undefined') {\n if (isHttps && httpsAgent) {\n // Using 'any' type to bypass TypeScript limitations with Node.js http.Agent\n return { ...options, agent: httpsAgent } as any;\n } else if (httpAgent) {\n return { ...options, agent: httpAgent } as any;\n }\n }\n \n // Browser environment\n const requestId = Date.now().toString(36) + Math.random().toString(36).substring(2);\n \n // Use keep-alive header in browser environments\n return {\n ...options,\n headers: {\n ...options.headers,\n 'Connection': 'keep-alive',\n 'X-Request-ID': requestId // Add unique ID to track requests\n }\n };\n};\n\nexport async function* streamChat(\n message: string,\n voiceEnabled: boolean\n): AsyncGenerator<StreamResponse> {\n try {\n // Get the API URL at the time of the request (allows for dynamic configuration)\n const API_URL = getApiUrl();\n \n // Skip the OPTIONS preflight check that was causing CORS issues\n const response = await fetch(`${API_URL}/chat`, getFetchOptions(API_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ message, voiceEnabled }),\n }));\n\n if (!response.ok) {\n const errorText = await response.text();\n console.error(`API request failed: ${response.status} ${errorText}`);\n throw new Error(`Network response was not ok: ${response.status} ${errorText}`);\n }\n \n const reader = response.body?.getReader();\n if (!reader) throw new Error('No reader available');\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n const chunk = decoder.decode(value, { stream: true });\n buffer += chunk;\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (line.trim()) {\n try {\n const data = JSON.parse(line);\n \n // Normalize the response to use 'text' property\n if (data.content && !data.text) {\n data.text = data.content;\n }\n \n yield data;\n } catch (error: any) {\n // Silent error handling for JSON parse errors\n console.warn('Error parsing JSON chunk:', line);\n }\n }\n }\n }\n\n if (buffer) {\n try {\n const data = JSON.parse(buffer);\n \n // Normalize the response to use 'text' property\n if (data.content && !data.text) {\n data.text = data.content;\n }\n \n yield data;\n } catch (error: any) {\n // Silent error handling for JSON parse errors\n console.warn('Error parsing final JSON buffer:', buffer);\n }\n }\n } catch (error: any) {\n console.error('Chat API error:', error.message);\n yield { \n text: `Error connecting to chat server: ${error.message}`, \n done: true \n };\n }\n}"],"names":["httpAgent","httpsAgent","connectionCounter","connectionReuseCounter","initConnectionPool","http","https","e","originalCreateConnection","options","callback","socket","error","configuredApiUrl","configureApi","apiUrl","getApiUrl","getFetchOptions","isHttps","requestId","streamChat","message","voiceEnabled","_a","API_URL","response","errorText","reader","decoder","buffer","done","value","chunk","lines","line","data"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@schmitech/chatbot-api",
3
+ "private": false,
4
+ "version": "0.1.0",
5
+ "description": "API client for the Chatbot server",
6
+ "type": "module",
7
+ "main": "./dist/api.cjs",
8
+ "module": "./dist/api.mjs",
9
+ "types": "./api.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/api.mjs",
13
+ "require": "./dist/api.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "api.d.ts"
19
+ ],
20
+ "scripts": {
21
+ "dev": "vite",
22
+ "build": "tsc --emitDeclarationOnly && vite build",
23
+ "lint": "eslint .",
24
+ "preview": "vite preview",
25
+ "test": "vitest",
26
+ "test:watch": "vitest",
27
+ "test-query": "node --import 'data:text/javascript,import { register } from \"node:module\"; import { pathToFileURL } from \"node:url\"; register(\"ts-node/esm\", pathToFileURL(\"./\"));' ./test/run-query.js"
28
+ },
29
+ "dependencies": {
30
+ "cors": "^2.8.5",
31
+ "express": "^4.18.3",
32
+ "node-fetch": "^3.3.2"
33
+ },
34
+ "devDependencies": {
35
+ "@eslint/js": "^9.9.1",
36
+ "@types/cors": "^2.8.17",
37
+ "@types/express": "^4.17.21",
38
+ "@types/node": "^20.11.24",
39
+ "eslint": "^9.9.1",
40
+ "globals": "^15.9.0",
41
+ "msw": "^2.0.11",
42
+ "ts-node": "^10.9.2",
43
+ "typescript": "^5.5.3",
44
+ "typescript-eslint": "^8.3.0",
45
+ "vite": "^5.4.2",
46
+ "vitest": "^0.34.6"
47
+ },
48
+ "peerDependencies": {
49
+ "react": ">=16.8.0",
50
+ "react-dom": ">=16.8.0"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "react": {
54
+ "optional": true
55
+ },
56
+ "react-dom": {
57
+ "optional": true
58
+ }
59
+ }
60
+ }