@bhooai/nexus-examples 2.0.5 → 2.0.7

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.
@@ -0,0 +1,101 @@
1
+ import React, { useRef, useState } from 'react';
2
+
3
+ interface Msg { role: 'user' | 'assistant'; content: string }
4
+
5
+ export function App() {
6
+ const [messages, setMessages] = useState<Msg[]>([]);
7
+ const [draft, setDraft] = useState('');
8
+ const [streaming, setStreaming] = useState(false);
9
+ const [error, setError] = useState('');
10
+ const abortRef = useRef<AbortController | null>(null);
11
+
12
+ const send = async () => {
13
+ const text = draft.trim();
14
+ if (!text || streaming) return;
15
+ setDraft('');
16
+ setError('');
17
+
18
+ const history = [...messages, { role: 'user', content: text } as Msg];
19
+ setMessages([...history, { role: 'assistant', content: '' }]);
20
+ setStreaming(true);
21
+
22
+ const controller = new AbortController();
23
+ abortRef.current = controller;
24
+
25
+ try {
26
+ const res = await fetch('/api/ai/chat', {
27
+ method: 'POST',
28
+ headers: { 'content-type': 'application/json' },
29
+ body: JSON.stringify({ messages: history }),
30
+ signal: controller.signal,
31
+ });
32
+ if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
33
+
34
+ // Consume the SSE stream manually with ReadableStream.
35
+ const reader = res.body.getReader();
36
+ const decoder = new TextDecoder();
37
+ let buffer = '';
38
+ for (;;) {
39
+ const { done, value } = await reader.read();
40
+ if (done) break;
41
+ buffer += decoder.decode(value, { stream: true });
42
+ const events = buffer.split('\n\n');
43
+ buffer = events.pop() ?? '';
44
+ for (const raw of events) {
45
+ const line = raw.replace(/^data: /, '').trim();
46
+ if (!line || line.startsWith(':')) continue;
47
+ if (line === '[DONE]') break;
48
+ const data = JSON.parse(line) as { delta?: string; error?: string };
49
+ if (data.error) setError(data.error);
50
+ if (data.delta) {
51
+ setMessages((m) => {
52
+ const copy = [...m];
53
+ const last = copy[copy.length - 1];
54
+ copy[copy.length - 1] = { ...last, content: last.content + data.delta };
55
+ return copy;
56
+ });
57
+ }
58
+ }
59
+ }
60
+ } catch (err) {
61
+ if ((err as Error).name !== 'AbortError') setError((err as Error).message);
62
+ } finally {
63
+ setStreaming(false);
64
+ abortRef.current = null;
65
+ }
66
+ };
67
+
68
+ const stop = () => abortRef.current?.abort();
69
+
70
+ return (
71
+ <main style={{ fontFamily: 'system-ui', maxWidth: 720, margin: '2rem auto', padding: '0 1rem' }}>
72
+ <h1>AI chat</h1>
73
+
74
+ <div style={{ border: '1px solid #ccc', borderRadius: 8, padding: '1rem', minHeight: 300, maxHeight: 440, overflow: 'auto', background: '#fafafa', marginBottom: '1rem' }}>
75
+ {messages.length === 0 && <p style={{ color: '#666' }}>Ask anything — responses stream token-by-token.</p>}
76
+ {messages.map((m, i) => (
77
+ <div key={i} style={{ marginBottom: '0.75rem' }}>
78
+ <strong style={{ color: m.role === 'user' ? '#06c' : '#060' }}>
79
+ {m.role === 'user' ? 'You' : 'AI'}
80
+ </strong>
81
+ <div style={{ whiteSpace: 'pre-wrap' }}>{m.content || (streaming && i === messages.length - 1 ? '...' : '')}</div>
82
+ </div>
83
+ ))}
84
+ </div>
85
+
86
+ {error && <p style={{ color: '#c00' }}>{error}</p>}
87
+
88
+ <div style={{ display: 'flex', gap: '0.5rem' }}>
89
+ <input
90
+ value={draft}
91
+ onChange={(e) => setDraft(e.target.value)}
92
+ onKeyDown={(e) => e.key === 'Enter' && send()}
93
+ placeholder="Type a message..."
94
+ style={{ flex: 1, padding: '0.5rem' }}
95
+ disabled={streaming}
96
+ />
97
+ {streaming ? <button onClick={stop}>Stop</button> : <button onClick={send}>Send</button>}
98
+ </div>
99
+ </main>
100
+ );
101
+ }
@@ -1,105 +1,8 @@
1
- import React, { useRef, useState } from 'react';
1
+ import React from 'react';
2
2
  import { createRoot } from 'react-dom/client';
3
+ import { App } from './App.js';
3
4
 
4
- interface Msg { role: 'user' | 'assistant'; content: string }
5
-
6
- function App() {
7
- const [messages, setMessages] = useState<Msg[]>([]);
8
- const [draft, setDraft] = useState('');
9
- const [streaming, setStreaming] = useState(false);
10
- const [error, setError] = useState('');
11
- const abortRef = useRef<AbortController | null>(null);
12
-
13
- const send = async () => {
14
- const text = draft.trim();
15
- if (!text || streaming) return;
16
- setDraft('');
17
- setError('');
18
-
19
- const history = [...messages, { role: 'user', content: text } as Msg];
20
- setMessages([...history, { role: 'assistant', content: '' }]);
21
- setStreaming(true);
22
-
23
- const controller = new AbortController();
24
- abortRef.current = controller;
25
-
26
- try {
27
- const res = await fetch('/api/ai/chat', {
28
- method: 'POST',
29
- headers: { 'content-type': 'application/json' },
30
- body: JSON.stringify({ messages: history }),
31
- signal: controller.signal,
32
- });
33
- if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
34
-
35
- // Consume the SSE stream manually with ReadableStream.
36
- const reader = res.body.getReader();
37
- const decoder = new TextDecoder();
38
- let buffer = '';
39
- for (;;) {
40
- const { done, value } = await reader.read();
41
- if (done) break;
42
- buffer += decoder.decode(value, { stream: true });
43
- const events = buffer.split('\n\n');
44
- buffer = events.pop() ?? '';
45
- for (const raw of events) {
46
- const line = raw.replace(/^data: /, '').trim();
47
- if (!line || line.startsWith(':')) continue;
48
- if (line === '[DONE]') break;
49
- const data = JSON.parse(line) as { delta?: string; error?: string };
50
- if (data.error) setError(data.error);
51
- if (data.delta) {
52
- setMessages((m) => {
53
- const copy = [...m];
54
- const last = copy[copy.length - 1];
55
- copy[copy.length - 1] = { ...last, content: last.content + data.delta };
56
- return copy;
57
- });
58
- }
59
- }
60
- }
61
- } catch (err) {
62
- if ((err as Error).name !== 'AbortError') setError((err as Error).message);
63
- } finally {
64
- setStreaming(false);
65
- abortRef.current = null;
66
- }
67
- };
68
-
69
- const stop = () => abortRef.current?.abort();
70
-
71
- return (
72
- <main style={{ fontFamily: 'system-ui', maxWidth: 720, margin: '2rem auto', padding: '0 1rem' }}>
73
- <h1>AI chat</h1>
74
-
75
- <div style={{ border: '1px solid #ccc', borderRadius: 8, padding: '1rem', minHeight: 300, maxHeight: 440, overflow: 'auto', background: '#fafafa', marginBottom: '1rem' }}>
76
- {messages.length === 0 && <p style={{ color: '#666' }}>Ask anything — responses stream token-by-token.</p>}
77
- {messages.map((m, i) => (
78
- <div key={i} style={{ marginBottom: '0.75rem' }}>
79
- <strong style={{ color: m.role === 'user' ? '#06c' : '#060' }}>
80
- {m.role === 'user' ? 'You' : 'AI'}
81
- </strong>
82
- <div style={{ whiteSpace: 'pre-wrap' }}>{m.content || (streaming && i === messages.length - 1 ? '...' : '')}</div>
83
- </div>
84
- ))}
85
- </div>
86
-
87
- {error && <p style={{ color: '#c00' }}>{error}</p>}
88
-
89
- <div style={{ display: 'flex', gap: '0.5rem' }}>
90
- <input
91
- value={draft}
92
- onChange={(e) => setDraft(e.target.value)}
93
- onKeyDown={(e) => e.key === 'Enter' && send()}
94
- placeholder="Type a message..."
95
- style={{ flex: 1, padding: '0.5rem' }}
96
- disabled={streaming}
97
- />
98
- {streaming ? <button onClick={stop}>Stop</button> : <button onClick={send}>Send</button>}
99
- </div>
100
- </main>
101
- );
5
+ const rootEl = document.getElementById('root');
6
+ if (rootEl) {
7
+ createRoot(rootEl).render(<App />);
102
8
  }
103
-
104
- const el = document.getElementById('root');
105
- if (el) createRoot(el).render(<App />);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bhooai/nexus-examples",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
4
4
  "description": "Working examples for BhooAI Nexus v2 — each demos a slice of the framework.",
5
5
  "license": "MIT",
6
6
  "type": "module",