@informedai/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/dist/index.mjs ADDED
@@ -0,0 +1,963 @@
1
+ // src/components/InformedAssistant.tsx
2
+ import { useState as useState2, useRef as useRef2, useEffect as useEffect2 } from "react";
3
+
4
+ // src/context/InformedAIContext.tsx
5
+ import { createContext, useContext, useEffect, useState, useCallback, useRef } from "react";
6
+
7
+ // src/utils/api-client.ts
8
+ var DEFAULT_API_URL = "https://api.informedassistant.ai/api/v1";
9
+ var InformedAIClient = class {
10
+ constructor(apiKey, apiUrl) {
11
+ this.apiKey = apiKey;
12
+ this.apiUrl = apiUrl || DEFAULT_API_URL;
13
+ }
14
+ getHeaders() {
15
+ return {
16
+ "Content-Type": "application/json",
17
+ "Authorization": `Bearer ${this.apiKey}`
18
+ };
19
+ }
20
+ async request(endpoint, options = {}) {
21
+ const response = await fetch(`${this.apiUrl}${endpoint}`, {
22
+ ...options,
23
+ headers: {
24
+ ...this.getHeaders(),
25
+ ...options.headers
26
+ }
27
+ });
28
+ if (!response.ok) {
29
+ const error = await response.json().catch(() => ({ error: "Request failed" }));
30
+ throw new Error(error.error || `HTTP ${response.status}`);
31
+ }
32
+ if (response.status === 204) {
33
+ return {};
34
+ }
35
+ return response.json();
36
+ }
37
+ // ========================================================================
38
+ // Document Operations
39
+ // ========================================================================
40
+ async getDocument(id) {
41
+ return this.request(`/documents/${id}`);
42
+ }
43
+ async getDocumentType(id) {
44
+ return this.request(`/document-types/${id}`);
45
+ }
46
+ async updateDocumentField(documentId, field, value) {
47
+ return this.request(`/documents/${documentId}/field`, {
48
+ method: "PATCH",
49
+ body: JSON.stringify({ field, value })
50
+ });
51
+ }
52
+ // ========================================================================
53
+ // Session Operations
54
+ // ========================================================================
55
+ async createSession(documentId) {
56
+ return this.request("/sessions", {
57
+ method: "POST",
58
+ body: JSON.stringify({ documentId })
59
+ });
60
+ }
61
+ async getSession(id) {
62
+ return this.request(`/sessions/${id}`);
63
+ }
64
+ async deleteSession(id) {
65
+ await this.request(`/sessions/${id}`, { method: "DELETE" });
66
+ }
67
+ /**
68
+ * Send a message to the session with SSE streaming
69
+ */
70
+ async sendMessage(sessionId, message, onEvent) {
71
+ const response = await fetch(`${this.apiUrl}/sessions/${sessionId}/message`, {
72
+ method: "POST",
73
+ headers: this.getHeaders(),
74
+ body: JSON.stringify({ message })
75
+ });
76
+ if (!response.ok) {
77
+ const error = await response.json().catch(() => ({ error: "Request failed" }));
78
+ throw new Error(error.error || `HTTP ${response.status}`);
79
+ }
80
+ await this.processSSEStream(response, onEvent);
81
+ }
82
+ /**
83
+ * Send a quick action to the session with SSE streaming
84
+ */
85
+ async sendQuickAction(sessionId, action, payload, onEvent) {
86
+ const response = await fetch(`${this.apiUrl}/sessions/${sessionId}/quick-action`, {
87
+ method: "POST",
88
+ headers: this.getHeaders(),
89
+ body: JSON.stringify({ action, payload })
90
+ });
91
+ if (!response.ok) {
92
+ const error = await response.json().catch(() => ({ error: "Request failed" }));
93
+ throw new Error(error.error || `HTTP ${response.status}`);
94
+ }
95
+ let finalSession = null;
96
+ await this.processSSEStream(response, (event) => {
97
+ onEvent?.(event);
98
+ if (event.session) {
99
+ finalSession = event.session;
100
+ }
101
+ });
102
+ if (!finalSession) {
103
+ throw new Error("No session returned from quick action");
104
+ }
105
+ return finalSession;
106
+ }
107
+ /**
108
+ * Apply the pending value for the active task
109
+ */
110
+ async applyPendingValue(sessionId) {
111
+ return this.request(`/sessions/${sessionId}/apply`, {
112
+ method: "POST"
113
+ });
114
+ }
115
+ /**
116
+ * Skip the active task
117
+ */
118
+ async skipTask(sessionId) {
119
+ return this.request(`/sessions/${sessionId}/skip`, {
120
+ method: "POST"
121
+ });
122
+ }
123
+ // ========================================================================
124
+ // SSE Stream Processing
125
+ // ========================================================================
126
+ async processSSEStream(response, onEvent) {
127
+ const reader = response.body?.getReader();
128
+ if (!reader) {
129
+ throw new Error("No response body");
130
+ }
131
+ const decoder = new TextDecoder();
132
+ let buffer = "";
133
+ try {
134
+ while (true) {
135
+ const { done, value } = await reader.read();
136
+ if (done) break;
137
+ buffer += decoder.decode(value, { stream: true });
138
+ const lines = buffer.split("\n");
139
+ buffer = lines.pop() || "";
140
+ for (const line of lines) {
141
+ if (line.startsWith("data: ")) {
142
+ try {
143
+ const data = JSON.parse(line.slice(6));
144
+ onEvent(data);
145
+ } catch {
146
+ }
147
+ }
148
+ }
149
+ }
150
+ if (buffer.startsWith("data: ")) {
151
+ try {
152
+ const data = JSON.parse(buffer.slice(6));
153
+ onEvent(data);
154
+ } catch {
155
+ }
156
+ }
157
+ } finally {
158
+ reader.releaseLock();
159
+ }
160
+ }
161
+ };
162
+
163
+ // src/context/InformedAIContext.tsx
164
+ import { jsx } from "react/jsx-runtime";
165
+ var InformedAIContext = createContext(null);
166
+ function useInformedAI() {
167
+ const context = useContext(InformedAIContext);
168
+ if (!context) {
169
+ throw new Error("useInformedAI must be used within an InformedAIProvider");
170
+ }
171
+ return context;
172
+ }
173
+ function InformedAIProvider({ config, children }) {
174
+ const [session, setSession] = useState(null);
175
+ const [document, setDocument] = useState(null);
176
+ const [documentType, setDocumentType] = useState(null);
177
+ const [isLoading, setIsLoading] = useState(true);
178
+ const [isStreaming, setIsStreaming] = useState(false);
179
+ const [error, setError] = useState(null);
180
+ const [streamingContent, setStreamingContent] = useState("");
181
+ const clientRef = useRef(null);
182
+ useEffect(() => {
183
+ clientRef.current = new InformedAIClient(config.apiKey, config.apiUrl);
184
+ }, [config.apiKey, config.apiUrl]);
185
+ useEffect(() => {
186
+ async function initialize() {
187
+ if (!clientRef.current) return;
188
+ try {
189
+ setIsLoading(true);
190
+ setError(null);
191
+ const doc = await clientRef.current.getDocument(config.documentId);
192
+ setDocument(doc);
193
+ const dt = await clientRef.current.getDocumentType(doc.documentTypeId);
194
+ setDocumentType(dt);
195
+ let sess;
196
+ if (config.sessionId) {
197
+ sess = await clientRef.current.getSession(config.sessionId);
198
+ } else {
199
+ sess = await clientRef.current.createSession(config.documentId);
200
+ }
201
+ setSession(sess);
202
+ config.onSessionChange?.(sess);
203
+ } catch (err) {
204
+ const error2 = err instanceof Error ? err : new Error("Initialization failed");
205
+ setError(error2);
206
+ config.onError?.(error2);
207
+ } finally {
208
+ setIsLoading(false);
209
+ }
210
+ }
211
+ initialize();
212
+ }, [config.documentId, config.sessionId]);
213
+ const handleSSEEvent = useCallback((event) => {
214
+ if (event.type === "content" && event.content) {
215
+ setStreamingContent((prev) => prev + event.content);
216
+ }
217
+ if (event.type === "done" || event.type === "session_update") {
218
+ if (event.session) {
219
+ setSession(event.session);
220
+ config.onSessionChange?.(event.session);
221
+ if (event.session.activeTask) {
222
+ const taskState = event.session.taskStates[event.session.activeTask];
223
+ if (taskState?.pendingValue !== void 0) {
224
+ }
225
+ }
226
+ }
227
+ setStreamingContent("");
228
+ setIsStreaming(false);
229
+ }
230
+ if (event.type === "error") {
231
+ const error2 = new Error(event.error || "Stream error");
232
+ setError(error2);
233
+ config.onError?.(error2);
234
+ setIsStreaming(false);
235
+ }
236
+ }, [config]);
237
+ const sendMessage = useCallback(async (message) => {
238
+ if (!clientRef.current || !session) return;
239
+ try {
240
+ setIsStreaming(true);
241
+ setStreamingContent("");
242
+ setError(null);
243
+ await clientRef.current.sendMessage(session.id, message, handleSSEEvent);
244
+ } catch (err) {
245
+ const error2 = err instanceof Error ? err : new Error("Failed to send message");
246
+ setError(error2);
247
+ config.onError?.(error2);
248
+ setIsStreaming(false);
249
+ }
250
+ }, [session, handleSSEEvent, config]);
251
+ const sendQuickAction = useCallback(async (action, payload) => {
252
+ if (!clientRef.current || !session) return;
253
+ try {
254
+ setIsStreaming(true);
255
+ setStreamingContent("");
256
+ setError(null);
257
+ const newSession = await clientRef.current.sendQuickAction(
258
+ session.id,
259
+ action,
260
+ payload,
261
+ handleSSEEvent
262
+ );
263
+ setSession(newSession);
264
+ config.onSessionChange?.(newSession);
265
+ } catch (err) {
266
+ const error2 = err instanceof Error ? err : new Error("Failed to send quick action");
267
+ setError(error2);
268
+ config.onError?.(error2);
269
+ } finally {
270
+ setIsStreaming(false);
271
+ }
272
+ }, [session, handleSSEEvent, config]);
273
+ const applyPendingValue = useCallback(async () => {
274
+ if (!clientRef.current || !session) return;
275
+ try {
276
+ setError(null);
277
+ const activeTask = session.activeTask;
278
+ const pendingValue = activeTask ? session.taskStates[activeTask]?.pendingValue : void 0;
279
+ const newSession = await clientRef.current.applyPendingValue(session.id);
280
+ setSession(newSession);
281
+ config.onSessionChange?.(newSession);
282
+ if (activeTask && pendingValue !== void 0) {
283
+ config.onFieldApply?.(activeTask, pendingValue);
284
+ }
285
+ } catch (err) {
286
+ const error2 = err instanceof Error ? err : new Error("Failed to apply value");
287
+ setError(error2);
288
+ config.onError?.(error2);
289
+ }
290
+ }, [session, config]);
291
+ const skipTask = useCallback(async () => {
292
+ if (!clientRef.current || !session) return;
293
+ try {
294
+ setError(null);
295
+ const newSession = await clientRef.current.skipTask(session.id);
296
+ setSession(newSession);
297
+ config.onSessionChange?.(newSession);
298
+ } catch (err) {
299
+ const error2 = err instanceof Error ? err : new Error("Failed to skip task");
300
+ setError(error2);
301
+ config.onError?.(error2);
302
+ }
303
+ }, [session, config]);
304
+ const clearError = useCallback(() => {
305
+ setError(null);
306
+ }, []);
307
+ const value = {
308
+ session,
309
+ document,
310
+ documentType,
311
+ isLoading,
312
+ isStreaming,
313
+ error,
314
+ streamingContent,
315
+ sendMessage,
316
+ sendQuickAction,
317
+ applyPendingValue,
318
+ skipTask,
319
+ clearError
320
+ };
321
+ return /* @__PURE__ */ jsx(InformedAIContext.Provider, { value, children });
322
+ }
323
+
324
+ // src/components/InformedAssistant.tsx
325
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
326
+ var defaultTheme = {
327
+ primaryColor: "#f59e0b",
328
+ // Amber
329
+ backgroundColor: "#ffffff",
330
+ textColor: "#1c1917",
331
+ borderRadius: "12px",
332
+ fontFamily: "system-ui, -apple-system, sans-serif"
333
+ };
334
+ function InformedAssistant({ className, ...config }) {
335
+ return /* @__PURE__ */ jsx2(InformedAIProvider, { config, children: /* @__PURE__ */ jsx2(
336
+ AssistantWidget,
337
+ {
338
+ className,
339
+ theme: { ...defaultTheme, ...config.theme },
340
+ position: config.position,
341
+ defaultCollapsed: config.defaultCollapsed
342
+ }
343
+ ) });
344
+ }
345
+ function AssistantWidget({ className, theme, position = "inline", defaultCollapsed = false }) {
346
+ const {
347
+ session,
348
+ documentType,
349
+ isLoading,
350
+ isStreaming,
351
+ error,
352
+ streamingContent,
353
+ sendMessage,
354
+ sendQuickAction,
355
+ applyPendingValue,
356
+ skipTask
357
+ } = useInformedAI();
358
+ const [isCollapsed, setIsCollapsed] = useState2(defaultCollapsed);
359
+ const [inputValue, setInputValue] = useState2("");
360
+ const messagesEndRef = useRef2(null);
361
+ useEffect2(() => {
362
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
363
+ }, [session?.widgetMessages, streamingContent]);
364
+ const handleSubmit = async (e) => {
365
+ e.preventDefault();
366
+ if (!inputValue.trim() || isStreaming) return;
367
+ const message = inputValue.trim();
368
+ setInputValue("");
369
+ await sendMessage(message);
370
+ };
371
+ const handleQuickAction = async (action) => {
372
+ await sendQuickAction(action.action, action.payload);
373
+ };
374
+ const activeTask = session?.activeTask;
375
+ const pendingValue = activeTask ? session?.taskStates[activeTask]?.pendingValue : void 0;
376
+ const hasPendingValue = pendingValue !== void 0 && pendingValue !== null;
377
+ const cssVars = {
378
+ "--ia-primary": theme.primaryColor,
379
+ "--ia-bg": theme.backgroundColor,
380
+ "--ia-text": theme.textColor,
381
+ "--ia-radius": theme.borderRadius,
382
+ "--ia-font": theme.fontFamily
383
+ };
384
+ const positionStyles = position === "inline" ? {} : {
385
+ position: "fixed",
386
+ [position === "bottom-right" ? "right" : "left"]: "20px",
387
+ bottom: "20px",
388
+ zIndex: 9999
389
+ };
390
+ if (isLoading) {
391
+ return /* @__PURE__ */ jsx2(
392
+ "div",
393
+ {
394
+ className,
395
+ style: {
396
+ ...cssVars,
397
+ ...positionStyles,
398
+ width: position === "inline" ? "100%" : "380px",
399
+ backgroundColor: "var(--ia-bg)",
400
+ borderRadius: "var(--ia-radius)",
401
+ border: "1px solid #e5e5e5",
402
+ fontFamily: "var(--ia-font)",
403
+ padding: "24px",
404
+ textAlign: "center",
405
+ color: "#737373"
406
+ },
407
+ children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: "8px" }, children: [
408
+ /* @__PURE__ */ jsx2(LoadingSpinner, {}),
409
+ /* @__PURE__ */ jsx2("span", { children: "Loading assistant..." })
410
+ ] })
411
+ }
412
+ );
413
+ }
414
+ if (error) {
415
+ return /* @__PURE__ */ jsx2(
416
+ "div",
417
+ {
418
+ className,
419
+ style: {
420
+ ...cssVars,
421
+ ...positionStyles,
422
+ width: position === "inline" ? "100%" : "380px",
423
+ backgroundColor: "var(--ia-bg)",
424
+ borderRadius: "var(--ia-radius)",
425
+ border: "1px solid #fecaca",
426
+ fontFamily: "var(--ia-font)",
427
+ padding: "24px",
428
+ textAlign: "center",
429
+ color: "#dc2626"
430
+ },
431
+ children: /* @__PURE__ */ jsx2("p", { style: { margin: 0 }, children: error.message })
432
+ }
433
+ );
434
+ }
435
+ if (isCollapsed && position !== "inline") {
436
+ return /* @__PURE__ */ jsx2(
437
+ "button",
438
+ {
439
+ onClick: () => setIsCollapsed(false),
440
+ style: {
441
+ ...positionStyles,
442
+ width: "56px",
443
+ height: "56px",
444
+ borderRadius: "50%",
445
+ backgroundColor: theme.primaryColor,
446
+ border: "none",
447
+ cursor: "pointer",
448
+ display: "flex",
449
+ alignItems: "center",
450
+ justifyContent: "center",
451
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)"
452
+ },
453
+ children: /* @__PURE__ */ jsx2(ChatIcon, { color: "#fff" })
454
+ }
455
+ );
456
+ }
457
+ return /* @__PURE__ */ jsxs(
458
+ "div",
459
+ {
460
+ className,
461
+ style: {
462
+ ...cssVars,
463
+ ...positionStyles,
464
+ width: position === "inline" ? "100%" : "380px",
465
+ height: position === "inline" ? "auto" : "500px",
466
+ maxHeight: position === "inline" ? "600px" : "500px",
467
+ backgroundColor: "var(--ia-bg)",
468
+ borderRadius: "var(--ia-radius)",
469
+ border: "1px solid #e5e5e5",
470
+ fontFamily: "var(--ia-font)",
471
+ display: "flex",
472
+ flexDirection: "column",
473
+ overflow: "hidden",
474
+ boxShadow: position !== "inline" ? "0 4px 24px rgba(0,0,0,0.12)" : void 0
475
+ },
476
+ children: [
477
+ /* @__PURE__ */ jsxs(
478
+ "div",
479
+ {
480
+ style: {
481
+ padding: "16px",
482
+ borderBottom: "1px solid #e5e5e5",
483
+ display: "flex",
484
+ alignItems: "center",
485
+ justifyContent: "space-between"
486
+ },
487
+ children: [
488
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
489
+ /* @__PURE__ */ jsx2(SparklesIcon, { color: theme.primaryColor }),
490
+ /* @__PURE__ */ jsx2("span", { style: { fontWeight: 600, color: "var(--ia-text)" }, children: "InformedAI Assistant" })
491
+ ] }),
492
+ position !== "inline" && /* @__PURE__ */ jsx2(
493
+ "button",
494
+ {
495
+ onClick: () => setIsCollapsed(true),
496
+ style: {
497
+ background: "none",
498
+ border: "none",
499
+ cursor: "pointer",
500
+ padding: "4px",
501
+ color: "#737373"
502
+ },
503
+ children: /* @__PURE__ */ jsx2(MinimizeIcon, {})
504
+ }
505
+ )
506
+ ]
507
+ }
508
+ ),
509
+ activeTask && documentType && /* @__PURE__ */ jsxs(
510
+ "div",
511
+ {
512
+ style: {
513
+ padding: "8px 16px",
514
+ backgroundColor: `${theme.primaryColor}15`,
515
+ borderBottom: "1px solid #e5e5e5",
516
+ fontSize: "13px"
517
+ },
518
+ children: [
519
+ /* @__PURE__ */ jsx2("span", { style: { color: "#737373" }, children: "Working on: " }),
520
+ /* @__PURE__ */ jsx2("span", { style: { fontWeight: 500, color: theme.primaryColor }, children: documentType.schema.fields[activeTask]?.label || activeTask })
521
+ ]
522
+ }
523
+ ),
524
+ /* @__PURE__ */ jsxs(
525
+ "div",
526
+ {
527
+ style: {
528
+ flex: 1,
529
+ overflowY: "auto",
530
+ padding: "16px",
531
+ display: "flex",
532
+ flexDirection: "column",
533
+ gap: "12px"
534
+ },
535
+ children: [
536
+ session?.widgetMessages.map((msg) => /* @__PURE__ */ jsx2(
537
+ MessageBubble,
538
+ {
539
+ message: msg,
540
+ theme,
541
+ onQuickAction: handleQuickAction
542
+ },
543
+ msg.id
544
+ )),
545
+ isStreaming && streamingContent && /* @__PURE__ */ jsxs(
546
+ "div",
547
+ {
548
+ style: {
549
+ padding: "12px 16px",
550
+ backgroundColor: "#f5f5f5",
551
+ borderRadius: "12px",
552
+ borderBottomLeftRadius: "4px",
553
+ color: "var(--ia-text)",
554
+ fontSize: "14px",
555
+ lineHeight: 1.5,
556
+ whiteSpace: "pre-wrap"
557
+ },
558
+ children: [
559
+ streamingContent,
560
+ /* @__PURE__ */ jsx2("span", { style: { opacity: 0.5 }, children: "\u258A" })
561
+ ]
562
+ }
563
+ ),
564
+ isStreaming && !streamingContent && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px", color: "#737373" }, children: [
565
+ /* @__PURE__ */ jsx2(LoadingSpinner, { size: 16 }),
566
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: "14px" }, children: "Thinking..." })
567
+ ] }),
568
+ /* @__PURE__ */ jsx2("div", { ref: messagesEndRef })
569
+ ]
570
+ }
571
+ ),
572
+ hasPendingValue && /* @__PURE__ */ jsxs(
573
+ "div",
574
+ {
575
+ style: {
576
+ padding: "12px 16px",
577
+ borderTop: "1px solid #e5e5e5",
578
+ backgroundColor: `${theme.primaryColor}10`,
579
+ display: "flex",
580
+ gap: "8px"
581
+ },
582
+ children: [
583
+ /* @__PURE__ */ jsx2(
584
+ "button",
585
+ {
586
+ onClick: applyPendingValue,
587
+ disabled: isStreaming,
588
+ style: {
589
+ flex: 1,
590
+ padding: "10px 16px",
591
+ backgroundColor: theme.primaryColor,
592
+ color: "#fff",
593
+ border: "none",
594
+ borderRadius: "8px",
595
+ fontWeight: 500,
596
+ cursor: "pointer",
597
+ fontSize: "14px",
598
+ opacity: isStreaming ? 0.5 : 1
599
+ },
600
+ children: "Apply"
601
+ }
602
+ ),
603
+ /* @__PURE__ */ jsx2(
604
+ "button",
605
+ {
606
+ onClick: skipTask,
607
+ disabled: isStreaming,
608
+ style: {
609
+ padding: "10px 16px",
610
+ backgroundColor: "transparent",
611
+ color: "#737373",
612
+ border: "1px solid #e5e5e5",
613
+ borderRadius: "8px",
614
+ fontWeight: 500,
615
+ cursor: "pointer",
616
+ fontSize: "14px",
617
+ opacity: isStreaming ? 0.5 : 1
618
+ },
619
+ children: "Skip"
620
+ }
621
+ )
622
+ ]
623
+ }
624
+ ),
625
+ /* @__PURE__ */ jsxs(
626
+ "form",
627
+ {
628
+ onSubmit: handleSubmit,
629
+ style: {
630
+ padding: "12px 16px",
631
+ borderTop: "1px solid #e5e5e5",
632
+ display: "flex",
633
+ gap: "8px"
634
+ },
635
+ children: [
636
+ /* @__PURE__ */ jsx2(
637
+ "input",
638
+ {
639
+ type: "text",
640
+ value: inputValue,
641
+ onChange: (e) => setInputValue(e.target.value),
642
+ placeholder: "Type a message...",
643
+ disabled: isStreaming,
644
+ style: {
645
+ flex: 1,
646
+ padding: "10px 14px",
647
+ border: "1px solid #e5e5e5",
648
+ borderRadius: "8px",
649
+ fontSize: "14px",
650
+ outline: "none",
651
+ fontFamily: "inherit"
652
+ }
653
+ }
654
+ ),
655
+ /* @__PURE__ */ jsx2(
656
+ "button",
657
+ {
658
+ type: "submit",
659
+ disabled: !inputValue.trim() || isStreaming,
660
+ style: {
661
+ padding: "10px 16px",
662
+ backgroundColor: theme.primaryColor,
663
+ color: "#fff",
664
+ border: "none",
665
+ borderRadius: "8px",
666
+ fontWeight: 500,
667
+ cursor: "pointer",
668
+ fontSize: "14px",
669
+ opacity: !inputValue.trim() || isStreaming ? 0.5 : 1
670
+ },
671
+ children: "Send"
672
+ }
673
+ )
674
+ ]
675
+ }
676
+ )
677
+ ]
678
+ }
679
+ );
680
+ }
681
+ function MessageBubble({ message, theme, onQuickAction }) {
682
+ const isUser = message.role === "user";
683
+ if (message.type === "quick_actions" && message.quickActions) {
684
+ return /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexWrap: "wrap", gap: "8px" }, children: message.quickActions.map((action) => /* @__PURE__ */ jsx2(
685
+ "button",
686
+ {
687
+ onClick: () => onQuickAction(action),
688
+ style: {
689
+ padding: "8px 14px",
690
+ backgroundColor: "#f5f5f5",
691
+ border: "1px solid #e5e5e5",
692
+ borderRadius: "20px",
693
+ fontSize: "13px",
694
+ cursor: "pointer",
695
+ color: theme.textColor,
696
+ transition: "all 0.15s"
697
+ },
698
+ onMouseOver: (e) => {
699
+ e.currentTarget.style.backgroundColor = `${theme.primaryColor}15`;
700
+ e.currentTarget.style.borderColor = theme.primaryColor;
701
+ },
702
+ onMouseOut: (e) => {
703
+ e.currentTarget.style.backgroundColor = "#f5f5f5";
704
+ e.currentTarget.style.borderColor = "#e5e5e5";
705
+ },
706
+ children: action.label
707
+ },
708
+ action.id
709
+ )) });
710
+ }
711
+ return /* @__PURE__ */ jsx2(
712
+ "div",
713
+ {
714
+ style: {
715
+ display: "flex",
716
+ justifyContent: isUser ? "flex-end" : "flex-start"
717
+ },
718
+ children: /* @__PURE__ */ jsx2(
719
+ "div",
720
+ {
721
+ style: {
722
+ maxWidth: "85%",
723
+ padding: "12px 16px",
724
+ backgroundColor: isUser ? theme.primaryColor : "#f5f5f5",
725
+ color: isUser ? "#fff" : theme.textColor,
726
+ borderRadius: "12px",
727
+ borderBottomRightRadius: isUser ? "4px" : "12px",
728
+ borderBottomLeftRadius: isUser ? "12px" : "4px",
729
+ fontSize: "14px",
730
+ lineHeight: 1.5,
731
+ whiteSpace: "pre-wrap"
732
+ },
733
+ children: message.content
734
+ }
735
+ )
736
+ }
737
+ );
738
+ }
739
+ function SparklesIcon({ color = "currentColor" }) {
740
+ return /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", children: [
741
+ /* @__PURE__ */ jsx2("path", { d: "M12 3l1.5 4.5L18 9l-4.5 1.5L12 15l-1.5-4.5L6 9l4.5-1.5L12 3z" }),
742
+ /* @__PURE__ */ jsx2("path", { d: "M19 13l1 3 3 1-3 1-1 3-1-3-3-1 3-1 1-3z" }),
743
+ /* @__PURE__ */ jsx2("path", { d: "M5 17l1 2 2 1-2 1-1 2-1-2-2-1 2-1 1-2z" })
744
+ ] });
745
+ }
746
+ function ChatIcon({ color = "currentColor" }) {
747
+ return /* @__PURE__ */ jsx2("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", children: /* @__PURE__ */ jsx2("path", { d: "M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z" }) });
748
+ }
749
+ function MinimizeIcon() {
750
+ return /* @__PURE__ */ jsx2("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx2("path", { d: "M19 9l-7 7-7-7" }) });
751
+ }
752
+ function LoadingSpinner({ size = 20 }) {
753
+ return /* @__PURE__ */ jsxs(
754
+ "svg",
755
+ {
756
+ width: size,
757
+ height: size,
758
+ viewBox: "0 0 24 24",
759
+ fill: "none",
760
+ style: { animation: "ia-spin 1s linear infinite" },
761
+ children: [
762
+ /* @__PURE__ */ jsx2("style", { children: `@keyframes ia-spin { to { transform: rotate(360deg); } }` }),
763
+ /* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "10", stroke: "#e5e5e5", strokeWidth: "3" }),
764
+ /* @__PURE__ */ jsx2(
765
+ "path",
766
+ {
767
+ d: "M12 2a10 10 0 019.17 6",
768
+ stroke: "currentColor",
769
+ strokeWidth: "3",
770
+ strokeLinecap: "round"
771
+ }
772
+ )
773
+ ]
774
+ }
775
+ );
776
+ }
777
+
778
+ // src/hooks/useSession.ts
779
+ import { useState as useState3, useEffect as useEffect3, useCallback as useCallback2, useRef as useRef3 } from "react";
780
+ function useSession(options) {
781
+ const { apiKey, apiUrl, documentId, sessionId, onSessionChange, onError } = options;
782
+ const [session, setSession] = useState3(null);
783
+ const [isLoading, setIsLoading] = useState3(true);
784
+ const [error, setError] = useState3(null);
785
+ const clientRef = useRef3(null);
786
+ const isStreamingRef = useRef3(false);
787
+ useEffect3(() => {
788
+ clientRef.current = new InformedAIClient(apiKey, apiUrl);
789
+ }, [apiKey, apiUrl]);
790
+ useEffect3(() => {
791
+ async function initialize() {
792
+ if (!clientRef.current) return;
793
+ try {
794
+ setIsLoading(true);
795
+ setError(null);
796
+ let sess;
797
+ if (sessionId) {
798
+ sess = await clientRef.current.getSession(sessionId);
799
+ } else {
800
+ sess = await clientRef.current.createSession(documentId);
801
+ }
802
+ setSession(sess);
803
+ onSessionChange?.(sess);
804
+ } catch (err) {
805
+ const error2 = err instanceof Error ? err : new Error("Failed to initialize session");
806
+ setError(error2);
807
+ onError?.(error2);
808
+ } finally {
809
+ setIsLoading(false);
810
+ }
811
+ }
812
+ initialize();
813
+ }, [documentId, sessionId, onSessionChange, onError]);
814
+ const handleSSEEvent = useCallback2((event) => {
815
+ if (event.type === "done" || event.type === "session_update") {
816
+ if (event.session) {
817
+ setSession(event.session);
818
+ onSessionChange?.(event.session);
819
+ }
820
+ isStreamingRef.current = false;
821
+ }
822
+ if (event.type === "error") {
823
+ const error2 = new Error(event.error || "Stream error");
824
+ setError(error2);
825
+ onError?.(error2);
826
+ isStreamingRef.current = false;
827
+ }
828
+ }, [onSessionChange, onError]);
829
+ const sendMessage = useCallback2(async (message) => {
830
+ if (!clientRef.current || !session || isStreamingRef.current) return;
831
+ try {
832
+ isStreamingRef.current = true;
833
+ setError(null);
834
+ await clientRef.current.sendMessage(session.id, message, handleSSEEvent);
835
+ } catch (err) {
836
+ const error2 = err instanceof Error ? err : new Error("Failed to send message");
837
+ setError(error2);
838
+ onError?.(error2);
839
+ isStreamingRef.current = false;
840
+ }
841
+ }, [session, handleSSEEvent, onError]);
842
+ const sendQuickAction = useCallback2(async (action, payload) => {
843
+ if (!clientRef.current || !session || isStreamingRef.current) return;
844
+ try {
845
+ isStreamingRef.current = true;
846
+ setError(null);
847
+ const newSession = await clientRef.current.sendQuickAction(
848
+ session.id,
849
+ action,
850
+ payload,
851
+ handleSSEEvent
852
+ );
853
+ setSession(newSession);
854
+ onSessionChange?.(newSession);
855
+ } catch (err) {
856
+ const error2 = err instanceof Error ? err : new Error("Failed to send quick action");
857
+ setError(error2);
858
+ onError?.(error2);
859
+ } finally {
860
+ isStreamingRef.current = false;
861
+ }
862
+ }, [session, handleSSEEvent, onSessionChange, onError]);
863
+ const applyPendingValue = useCallback2(async () => {
864
+ if (!clientRef.current || !session) return;
865
+ try {
866
+ setError(null);
867
+ const newSession = await clientRef.current.applyPendingValue(session.id);
868
+ setSession(newSession);
869
+ onSessionChange?.(newSession);
870
+ } catch (err) {
871
+ const error2 = err instanceof Error ? err : new Error("Failed to apply value");
872
+ setError(error2);
873
+ onError?.(error2);
874
+ }
875
+ }, [session, onSessionChange, onError]);
876
+ const skipTask = useCallback2(async () => {
877
+ if (!clientRef.current || !session) return;
878
+ try {
879
+ setError(null);
880
+ const newSession = await clientRef.current.skipTask(session.id);
881
+ setSession(newSession);
882
+ onSessionChange?.(newSession);
883
+ } catch (err) {
884
+ const error2 = err instanceof Error ? err : new Error("Failed to skip task");
885
+ setError(error2);
886
+ onError?.(error2);
887
+ }
888
+ }, [session, onSessionChange, onError]);
889
+ return {
890
+ session,
891
+ isLoading,
892
+ error,
893
+ sendMessage,
894
+ sendQuickAction,
895
+ applyPendingValue,
896
+ skipTask
897
+ };
898
+ }
899
+
900
+ // src/hooks/useDocument.ts
901
+ import { useState as useState4, useEffect as useEffect4, useCallback as useCallback3, useRef as useRef4 } from "react";
902
+ function useDocument(options) {
903
+ const { apiKey, apiUrl, documentId, onError } = options;
904
+ const [document, setDocument] = useState4(null);
905
+ const [documentType, setDocumentType] = useState4(null);
906
+ const [isLoading, setIsLoading] = useState4(true);
907
+ const [error, setError] = useState4(null);
908
+ const clientRef = useRef4(null);
909
+ useEffect4(() => {
910
+ clientRef.current = new InformedAIClient(apiKey, apiUrl);
911
+ }, [apiKey, apiUrl]);
912
+ const fetchDocument = useCallback3(async () => {
913
+ if (!clientRef.current) return;
914
+ try {
915
+ setIsLoading(true);
916
+ setError(null);
917
+ const doc = await clientRef.current.getDocument(documentId);
918
+ setDocument(doc);
919
+ const dt = await clientRef.current.getDocumentType(doc.documentTypeId);
920
+ setDocumentType(dt);
921
+ } catch (err) {
922
+ const error2 = err instanceof Error ? err : new Error("Failed to fetch document");
923
+ setError(error2);
924
+ onError?.(error2);
925
+ } finally {
926
+ setIsLoading(false);
927
+ }
928
+ }, [documentId, onError]);
929
+ useEffect4(() => {
930
+ fetchDocument();
931
+ }, [fetchDocument]);
932
+ const updateField = useCallback3(async (fieldName, value) => {
933
+ if (!clientRef.current || !document) return;
934
+ try {
935
+ setError(null);
936
+ const updatedDoc = await clientRef.current.updateDocumentField(document.id, fieldName, value);
937
+ setDocument(updatedDoc);
938
+ } catch (err) {
939
+ const error2 = err instanceof Error ? err : new Error("Failed to update field");
940
+ setError(error2);
941
+ onError?.(error2);
942
+ }
943
+ }, [document, onError]);
944
+ const refetch = useCallback3(async () => {
945
+ await fetchDocument();
946
+ }, [fetchDocument]);
947
+ return {
948
+ document,
949
+ documentType,
950
+ isLoading,
951
+ error,
952
+ updateField,
953
+ refetch
954
+ };
955
+ }
956
+ export {
957
+ InformedAIClient,
958
+ InformedAIProvider,
959
+ InformedAssistant,
960
+ useDocument,
961
+ useInformedAI,
962
+ useSession
963
+ };