spres-react 1.0.1 → 1.0.2

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 CHANGED
@@ -83,7 +83,7 @@ import { ChatPanel } from 'spres-react';
83
83
  initialSessionId="session-123"
84
84
  design="cloud"
85
85
  />
86
- ````
86
+ ```
87
87
 
88
88
  ## Customization options
89
89
 
@@ -0,0 +1,2 @@
1
+ import { ChatPanelProps } from './types';
2
+ export declare function ChatPanel({ sdk, primaryColor, accentColor, backgroundColor, headerText, sidebarTitle, initialSessionId, design }: ChatPanelProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,89 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { useBrainboxChat } from './useBrainboxChat';
4
+ const sidebarStyle = {
5
+ width: '220px',
6
+ background: '#F8FAFC',
7
+ borderRight: '1px solid #E2E8F0',
8
+ padding: '16px',
9
+ boxSizing: 'border-box'
10
+ };
11
+ const panelStyle = {
12
+ display: 'flex',
13
+ height: '100%',
14
+ width: '100%',
15
+ borderRadius: '24px',
16
+ overflow: 'hidden',
17
+ boxShadow: '0 24px 80px rgba(15, 23, 42, 0.12)'
18
+ };
19
+ const bubbleStyle = {
20
+ borderRadius: '18px',
21
+ padding: '12px 14px',
22
+ marginBottom: '12px',
23
+ maxWidth: '100%'
24
+ };
25
+ function renderMessage(message, primaryColor, accentColor) {
26
+ const isUser = message.role === 'user';
27
+ return (_jsx("div", { style: { display: 'flex', justifyContent: isUser ? 'flex-end' : 'flex-start' }, children: _jsx("div", { style: {
28
+ ...bubbleStyle,
29
+ background: isUser ? primaryColor : accentColor,
30
+ color: '#fff',
31
+ borderRadius: isUser ? '18px 18px 6px 18px' : '18px 18px 18px 6px'
32
+ }, children: message.text }) }, message.id));
33
+ }
34
+ export function ChatPanel({ sdk, primaryColor = '#0F172A', accentColor = '#2563EB', backgroundColor = '#FFFFFF', headerText = 'Brainbox Chat', sidebarTitle = 'History', initialSessionId, design = 'cloud' }) {
35
+ const { messages, loading, error, sendMessage, sendVoiceNote, createSession, sessionId } = useBrainboxChat(sdk);
36
+ const [input, setInput] = useState('');
37
+ const [voiceNote, setVoiceNote] = useState('');
38
+ const endRef = useRef(null);
39
+ useEffect(() => {
40
+ if (initialSessionId) {
41
+ // session concept is preserved but not used for UI state yet
42
+ }
43
+ }, [initialSessionId]);
44
+ useEffect(() => {
45
+ var _a;
46
+ (_a = endRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: 'smooth' });
47
+ }, [messages]);
48
+ const handleSend = async () => {
49
+ if (!input.trim())
50
+ return;
51
+ await sendMessage(input.trim());
52
+ setInput('');
53
+ };
54
+ const handleVoiceNote = async () => {
55
+ if (!voiceNote.trim())
56
+ return;
57
+ await sendVoiceNote(voiceNote.trim());
58
+ setVoiceNote('');
59
+ };
60
+ const statusList = useMemo(() => [
61
+ { label: 'API status', value: 'Connected' },
62
+ { label: 'Session', value: sessionId || 'None' },
63
+ { label: 'Design', value: design },
64
+ { label: 'Real-time', value: 'Stream-ready' }
65
+ ], [sessionId, design]);
66
+ return (_jsxs("div", { style: { ...panelStyle, background: backgroundColor, minHeight: '560px' }, children: [_jsxs("aside", { style: sidebarStyle, children: [_jsxs("div", { style: { marginBottom: '20px' }, children: [_jsx("h3", { style: { margin: 0, fontSize: '18px', color: '#111827' }, children: sidebarTitle }), _jsx("p", { style: { marginTop: '8px', fontSize: '13px', color: '#475569' }, children: "Saved chat threads and endpoints." })] }), _jsx("div", { style: { marginBottom: '18px' }, children: statusList.map(item => (_jsxs("div", { style: { marginBottom: '12px' }, children: [_jsx("strong", { style: { display: 'block', color: '#334155', fontSize: '13px' }, children: item.label }), _jsx("span", { style: { color: '#475569', fontSize: '13px' }, children: item.value })] }, item.label))) }), _jsx("button", { onClick: () => createSession('Chat Panel Session'), style: {
67
+ width: '100%',
68
+ background: accentColor,
69
+ color: '#fff',
70
+ border: 'none',
71
+ borderRadius: '12px',
72
+ padding: '12px',
73
+ cursor: 'pointer'
74
+ }, children: "Create Session" })] }), _jsxs("main", { style: { flex: 1, display: 'flex', flexDirection: 'column', padding: '20px', boxSizing: 'border-box' }, children: [_jsxs("header", { style: { marginBottom: '16px' }, children: [_jsx("h2", { style: { margin: 0, color: primaryColor }, children: headerText }), _jsx("p", { style: { marginTop: '8px', color: '#475569' }, children: "Use the built-in chat UI with instant backend communication." })] }), _jsxs("section", { style: { flex: 1, overflowY: 'auto', paddingRight: '8px', marginBottom: '16px' }, children: [messages.length === 0 ? (_jsx("div", { style: { color: '#64748B', fontSize: '14px' }, children: "Your chat history appears here. Send a message to start." })) : (messages.map(message => renderMessage(message, accentColor, '#0F172A'))), _jsx("div", { ref: endRef })] }), _jsxs("div", { style: { borderTop: '1px solid #E2E8F0', paddingTop: '16px' }, children: [_jsxs("div", { style: { display: 'flex', gap: '8px', marginBottom: '10px', flexWrap: 'wrap' }, children: [_jsx("input", { value: input, onChange: event => setInput(event.target.value), onKeyDown: event => event.key === 'Enter' && handleSend(), placeholder: "Type a message...", style: { flex: 1, minWidth: '200px', padding: '12px 14px', border: '1px solid #CBD5E1', borderRadius: '14px' } }), _jsx("button", { onClick: handleSend, style: {
75
+ background: primaryColor,
76
+ color: '#fff',
77
+ border: 'none',
78
+ borderRadius: '14px',
79
+ padding: '12px 18px',
80
+ cursor: 'pointer'
81
+ }, children: loading ? 'Sending...' : 'Send' })] }), _jsxs("div", { style: { display: 'flex', gap: '8px', alignItems: 'center', flexWrap: 'wrap' }, children: [_jsx("input", { value: voiceNote, onChange: event => setVoiceNote(event.target.value), placeholder: "Voice note text (demo)", style: { flex: 1, minWidth: '200px', padding: '12px 14px', border: '1px solid #CBD5E1', borderRadius: '14px' } }), _jsx("button", { onClick: handleVoiceNote, style: {
82
+ background: accentColor,
83
+ color: '#fff',
84
+ border: 'none',
85
+ borderRadius: '14px',
86
+ padding: '12px 18px',
87
+ cursor: 'pointer'
88
+ }, children: "Add Voice Note" })] }), error && _jsx("div", { style: { marginTop: '12px', color: '#DC2626' }, children: error })] })] })] }));
89
+ }
@@ -0,0 +1,2 @@
1
+ import { ChatWidgetProps } from './types';
2
+ export declare function ChatWidget({ sdk, position, primaryColor, accentColor, backgroundColor, buttonText, placeholder, width, height, design }: ChatWidgetProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,99 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useRef, useState } from 'react';
3
+ import { useBrainboxChat } from './useBrainboxChat';
4
+ const defaultButtonStyle = {
5
+ border: 'none',
6
+ borderRadius: '999px',
7
+ color: '#fff',
8
+ cursor: 'pointer',
9
+ boxShadow: '0 8px 18px rgba(0,0,0,0.16)'
10
+ };
11
+ const bubbleStyle = {
12
+ borderRadius: '18px',
13
+ padding: '12px 14px',
14
+ marginBottom: '10px',
15
+ lineHeight: 1.5,
16
+ maxWidth: '100%'
17
+ };
18
+ function renderMessage(message, primaryColor, accentColor) {
19
+ const isUser = message.role === 'user';
20
+ const containerStyle = {
21
+ display: 'flex',
22
+ justifyContent: isUser ? 'flex-end' : 'flex-start'
23
+ };
24
+ return (_jsx("div", { style: containerStyle, children: _jsx("div", { style: {
25
+ ...bubbleStyle,
26
+ background: isUser ? primaryColor : accentColor,
27
+ color: '#fff',
28
+ borderRadius: isUser ? '18px 18px 6px 18px' : '18px 18px 18px 6px'
29
+ }, children: message.text }) }, message.id));
30
+ }
31
+ export function ChatWidget({ sdk, position = 'bottom-right', primaryColor = '#3B82F6', accentColor = '#111827', backgroundColor = '#F8FAFC', buttonText = 'Support', placeholder = 'Ask a question...', width = '340px', height = '480px', design = 'support' }) {
32
+ const [open, setOpen] = useState(false);
33
+ const [input, setInput] = useState('');
34
+ const { messages, loading, error, sendMessage } = useBrainboxChat(sdk);
35
+ const endRef = useRef(null);
36
+ const positionStyle = useMemo(() => {
37
+ const base = {
38
+ position: 'fixed',
39
+ zIndex: 9999,
40
+ maxWidth: width,
41
+ width
42
+ };
43
+ if (position.includes('bottom'))
44
+ base.bottom = '20px';
45
+ if (position.includes('top'))
46
+ base.top = '20px';
47
+ if (position.includes('right'))
48
+ base.right = '20px';
49
+ if (position.includes('left'))
50
+ base.left = '20px';
51
+ if (position === 'center') {
52
+ base.left = '50%';
53
+ base.transform = 'translateX(-50%)';
54
+ base.bottom = '20px';
55
+ }
56
+ return base;
57
+ }, [position, width]);
58
+ const handleSend = async () => {
59
+ if (!input.trim())
60
+ return;
61
+ await sendMessage(input.trim());
62
+ setInput('');
63
+ };
64
+ const headerText = design === 'support' ? 'Support Chat' : 'AI Assistant';
65
+ return (_jsxs("div", { style: positionStyle, children: [open && (_jsxs("div", { style: {
66
+ borderRadius: '24px',
67
+ background: '#fff',
68
+ boxShadow: '0 24px 80px rgba(15, 23, 42, 0.12)',
69
+ overflow: 'hidden',
70
+ height
71
+ }, children: [_jsx("div", { style: { background: primaryColor, color: '#fff', padding: '14px 18px' }, children: _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [_jsxs("div", { children: [_jsx("strong", { children: headerText }), _jsx("div", { style: { fontSize: '12px', opacity: 0.85 }, children: "Powered by Brainbox" })] }), _jsx("button", { onClick: () => setOpen(false), style: {
72
+ background: 'transparent',
73
+ border: 'none',
74
+ color: '#fff',
75
+ fontSize: '18px',
76
+ cursor: 'pointer'
77
+ }, children: "\u00D7" })] }) }), _jsxs("div", { style: { padding: '16px', background: backgroundColor, height: `calc(${height} - 132px)`, overflowY: 'auto' }, children: [messages.length === 0 ? (_jsx("div", { style: { color: '#475569', fontSize: '14px' }, children: design === 'support'
78
+ ? 'Ask a support question and get an answer in real time.'
79
+ : 'Start chatting with AI instantly.' })) : (messages.map(message => renderMessage(message, primaryColor, accentColor))), _jsx("div", { ref: endRef })] }), _jsxs("div", { style: { padding: '12px 16px', background: '#fff', borderTop: '1px solid #E2E8F0' }, children: [_jsxs("div", { style: { display: 'flex', gap: '8px' }, children: [_jsx("input", { value: input, onChange: event => setInput(event.target.value), onKeyDown: event => event.key === 'Enter' && handleSend(), placeholder: placeholder, style: {
80
+ flex: 1,
81
+ border: '1px solid #CBD5E1',
82
+ borderRadius: '999px',
83
+ padding: '12px 16px',
84
+ outline: 'none'
85
+ } }), _jsx("button", { onClick: handleSend, style: {
86
+ ...defaultButtonStyle,
87
+ background: primaryColor,
88
+ padding: '0 18px',
89
+ minWidth: '95px'
90
+ }, children: loading ? 'Sending...' : 'Send' })] }), error && _jsx("div", { style: { marginTop: '10px', color: '#EF4444' }, children: error })] })] })), _jsx("button", { onClick: () => setOpen(!open), style: {
91
+ ...defaultButtonStyle,
92
+ background: primaryColor,
93
+ padding: '14px 18px',
94
+ display: 'flex',
95
+ alignItems: 'center',
96
+ justifyContent: 'center',
97
+ minWidth: '120px'
98
+ }, children: buttonText })] }));
99
+ }
@@ -0,0 +1,13 @@
1
+ import { BrainboxChatResponse } from './types';
2
+ export declare class BrainboxReactSDK {
3
+ private apiUrl;
4
+ private apiKey;
5
+ private tenantId;
6
+ private client;
7
+ constructor(apiUrl: string, apiKey: string, tenantId: string);
8
+ ingest(sourceType: string, content: string, filePath?: string, metadata?: Record<string, any>): Promise<any>;
9
+ chat(question: string, sessionId?: string): Promise<BrainboxChatResponse>;
10
+ streamChat(question: string, sessionId: string | undefined, onChunk: (chunk: string) => void, onComplete?: (result: any) => void, onError?: (error: Error) => void): Promise<void>;
11
+ createChatSession(title?: string): Promise<any>;
12
+ healthCheck(): Promise<any>;
13
+ }
@@ -0,0 +1,100 @@
1
+ import axios from 'axios';
2
+ export class BrainboxReactSDK {
3
+ constructor(apiUrl, apiKey, tenantId) {
4
+ this.apiUrl = apiUrl.replace(/\/$/, '');
5
+ this.apiKey = apiKey;
6
+ this.tenantId = tenantId;
7
+ this.client = axios.create({
8
+ baseURL: this.apiUrl,
9
+ headers: {
10
+ 'Content-Type': 'application/json',
11
+ Authorization: `Bearer ${apiKey}`
12
+ }
13
+ });
14
+ }
15
+ async ingest(sourceType, content, filePath, metadata) {
16
+ const payload = {
17
+ tenant_id: this.tenantId,
18
+ source_type: sourceType,
19
+ content,
20
+ file_path: filePath,
21
+ metadata: metadata || {}
22
+ };
23
+ const response = await this.client.post('/api/ingest', payload);
24
+ return response.data;
25
+ }
26
+ async chat(question, sessionId) {
27
+ const payload = {
28
+ tenant_id: this.tenantId,
29
+ question,
30
+ session_id: sessionId
31
+ };
32
+ const response = await this.client.post('/api/chat', payload);
33
+ return response.data;
34
+ }
35
+ async streamChat(question, sessionId, onChunk, onComplete, onError) {
36
+ const payload = {
37
+ tenant_id: this.tenantId,
38
+ question,
39
+ session_id: sessionId
40
+ };
41
+ try {
42
+ if (typeof fetch !== 'undefined') {
43
+ const response = await fetch(`${this.apiUrl}/api/chat/stream`, {
44
+ method: 'POST',
45
+ headers: {
46
+ 'Content-Type': 'application/json',
47
+ Authorization: `Bearer ${this.apiKey}`
48
+ },
49
+ body: JSON.stringify(payload)
50
+ });
51
+ if (!response.ok) {
52
+ throw new Error(`Stream request failed: ${response.status}`);
53
+ }
54
+ if (!response.body || typeof response.body.getReader !== 'function') {
55
+ const text = await response.text();
56
+ let parsed;
57
+ try {
58
+ parsed = JSON.parse(text);
59
+ }
60
+ catch (_a) {
61
+ parsed = { response: text };
62
+ }
63
+ onChunk(parsed.response || text || '');
64
+ onComplete === null || onComplete === void 0 ? void 0 : onComplete(parsed);
65
+ return;
66
+ }
67
+ const reader = response.body.getReader();
68
+ const decoder = new TextDecoder('utf-8');
69
+ while (true) {
70
+ const { value, done } = await reader.read();
71
+ if (done)
72
+ break;
73
+ const chunk = decoder.decode(value, { stream: true });
74
+ onChunk(chunk);
75
+ }
76
+ onComplete === null || onComplete === void 0 ? void 0 : onComplete({ success: true });
77
+ return;
78
+ }
79
+ const result = await this.chat(question, sessionId);
80
+ onChunk(result.response || JSON.stringify(result));
81
+ onComplete === null || onComplete === void 0 ? void 0 : onComplete(result);
82
+ }
83
+ catch (error) {
84
+ const message = (error === null || error === void 0 ? void 0 : error.message) || 'Unknown stream error';
85
+ onError === null || onError === void 0 ? void 0 : onError(new Error(message));
86
+ }
87
+ }
88
+ async createChatSession(title) {
89
+ const payload = {
90
+ tenant_id: this.tenantId,
91
+ title: title || 'New Session'
92
+ };
93
+ const response = await this.client.post('/api/chat/session', payload);
94
+ return response.data;
95
+ }
96
+ async healthCheck() {
97
+ const response = await this.client.get('/api/health');
98
+ return response.data;
99
+ }
100
+ }
@@ -0,0 +1,5 @@
1
+ export { BrainboxReactSDK } from './brainbox-sdk';
2
+ export { useBrainboxChat } from './useBrainboxChat';
3
+ export { ChatWidget } from './ChatWidget';
4
+ export { ChatPanel } from './ChatPanel';
5
+ export type { ChatMessage, ChatWidgetProps, ChatPanelProps } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { BrainboxReactSDK } from './brainbox-sdk';
2
+ export { useBrainboxChat } from './useBrainboxChat';
3
+ export { ChatWidget } from './ChatWidget';
4
+ export { ChatPanel } from './ChatPanel';
@@ -0,0 +1,60 @@
1
+ import type { BrainboxReactSDK } from './brainbox-sdk';
2
+ export type ChatRole = 'user' | 'assistant' | 'system';
3
+ export interface ChatMessage {
4
+ id: string;
5
+ role: ChatRole;
6
+ text: string;
7
+ timestamp: string;
8
+ }
9
+ export interface ChatWidgetProps {
10
+ sdk: BrainboxReactSDK;
11
+ position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | 'center';
12
+ primaryColor?: string;
13
+ accentColor?: string;
14
+ backgroundColor?: string;
15
+ buttonText?: string;
16
+ placeholder?: string;
17
+ width?: string;
18
+ height?: string;
19
+ design?: 'support' | 'assistant';
20
+ }
21
+ export interface ChatPanelProps {
22
+ sdk: BrainboxReactSDK;
23
+ primaryColor?: string;
24
+ accentColor?: string;
25
+ backgroundColor?: string;
26
+ headerText?: string;
27
+ sidebarTitle?: string;
28
+ initialSessionId?: string;
29
+ design?: 'cloud' | 'classic';
30
+ }
31
+ export interface UseBrainboxChatHook {
32
+ messages: ChatMessage[];
33
+ loading: boolean;
34
+ error: string | null;
35
+ sessionId: string | null;
36
+ sendMessage: (text: string) => Promise<void>;
37
+ sendVoiceNote: (note: string) => Promise<void>;
38
+ createSession: (title?: string) => Promise<void>;
39
+ reset: () => void;
40
+ }
41
+ export interface BrainboxChatResponse {
42
+ response: string;
43
+ session_id?: string;
44
+ }
45
+ export interface ChatSessionPayload {
46
+ tenant_id: string;
47
+ title?: string;
48
+ }
49
+ export interface ChatPayload {
50
+ tenant_id: string;
51
+ question: string;
52
+ session_id?: string;
53
+ }
54
+ export interface IngestPayload {
55
+ tenant_id: string;
56
+ source_type: string;
57
+ content: string;
58
+ file_path?: string;
59
+ metadata?: Record<string, any>;
60
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import { BrainboxReactSDK } from './brainbox-sdk';
2
+ import { UseBrainboxChatHook } from './types';
3
+ export declare function useBrainboxChat(sdk: BrainboxReactSDK): UseBrainboxChatHook;
@@ -0,0 +1,85 @@
1
+ import { useCallback, useState } from 'react';
2
+ const createMessage = (role, text) => ({
3
+ id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
4
+ role,
5
+ text,
6
+ timestamp: new Date().toISOString()
7
+ });
8
+ export function useBrainboxChat(sdk) {
9
+ const [messages, setMessages] = useState([]);
10
+ const [loading, setLoading] = useState(false);
11
+ const [error, setError] = useState(null);
12
+ const [sessionId, setSessionId] = useState(null);
13
+ const appendMessage = useCallback((message) => {
14
+ setMessages(current => [...current, message]);
15
+ }, []);
16
+ const sendMessage = useCallback(async (text) => {
17
+ setError(null);
18
+ const userMessage = createMessage('user', text);
19
+ appendMessage(userMessage);
20
+ setLoading(true);
21
+ try {
22
+ await sdk.streamChat(text, sessionId || undefined, chunk => {
23
+ setMessages(current => {
24
+ const existing = current[current.length - 1];
25
+ if (existing && existing.role === 'assistant') {
26
+ return [
27
+ ...current.slice(0, -1),
28
+ {
29
+ ...existing,
30
+ text: existing.text + chunk
31
+ }
32
+ ];
33
+ }
34
+ return [...current, createMessage('assistant', chunk)];
35
+ });
36
+ }, result => {
37
+ if (result === null || result === void 0 ? void 0 : result.session_id) {
38
+ setSessionId(result.session_id);
39
+ }
40
+ setLoading(false);
41
+ }, err => {
42
+ setError(err.message);
43
+ setLoading(false);
44
+ });
45
+ }
46
+ catch (err) {
47
+ setError((err === null || err === void 0 ? void 0 : err.message) || 'Chat failed');
48
+ setLoading(false);
49
+ }
50
+ }, [appendMessage, messages, sdk, sessionId]);
51
+ const sendVoiceNote = useCallback(async (note) => {
52
+ setError(null);
53
+ const voiceMessage = createMessage('user', `Voice note: ${note}`);
54
+ appendMessage(voiceMessage);
55
+ await sendMessage(`Voice note: ${note}`);
56
+ }, [appendMessage, sendMessage]);
57
+ const createSession = useCallback(async (title) => {
58
+ setError(null);
59
+ try {
60
+ const response = await sdk.createChatSession(title);
61
+ if (response === null || response === void 0 ? void 0 : response.session_id) {
62
+ setSessionId(response.session_id);
63
+ }
64
+ }
65
+ catch (err) {
66
+ setError((err === null || err === void 0 ? void 0 : err.message) || 'Unable to create session');
67
+ }
68
+ }, [sdk]);
69
+ const reset = useCallback(() => {
70
+ setMessages([]);
71
+ setError(null);
72
+ setLoading(false);
73
+ setSessionId(null);
74
+ }, []);
75
+ return {
76
+ messages,
77
+ loading,
78
+ error,
79
+ sessionId,
80
+ sendMessage,
81
+ sendVoiceNote,
82
+ createSession,
83
+ reset
84
+ };
85
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spres-react",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "React SDK and customizable chat UI for Spres AI Backend",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",