kyd-shared-badge 0.3.6 → 0.3.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.
package/package.json
CHANGED
package/src/chat/ChatWidget.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useEffect,
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { FiMessageSquare, FiX, FiChevronRight } from 'react-icons/fi';
|
|
5
5
|
import { useChatStreaming } from './useChatStreaming';
|
|
6
6
|
import '@chatscope/chat-ui-kit-styles/dist/default/styles.min.css';
|
|
@@ -47,44 +47,11 @@ export default function ChatWidget({ api = '/api/chat', title = 'KYD Bot', hintT
|
|
|
47
47
|
const [headerTop, setHeaderTop] = useState(0);
|
|
48
48
|
const [showHint, setShowHint] = useState(false);
|
|
49
49
|
const { messages, input, setInput, sending, sendMessage, cancel } = useChatStreaming({ api, badgeId });
|
|
50
|
-
const
|
|
51
|
-
const [autoScroll, setAutoScroll] = useState(true);
|
|
52
|
-
const prevScrollHeightRef = useRef<number>(0);
|
|
50
|
+
const listRef = useRef<HTMLDivElement>(null);
|
|
53
51
|
|
|
54
|
-
// Detect user scroll position to toggle auto-scroll
|
|
55
52
|
useEffect(() => {
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
if (!root) return;
|
|
59
|
-
const scrollEl = root.querySelector('.cs-message-list') as HTMLElement | null;
|
|
60
|
-
if (!scrollEl) return;
|
|
61
|
-
const onScroll = () => {
|
|
62
|
-
const nearBottom = scrollEl.scrollTop + scrollEl.clientHeight >= scrollEl.scrollHeight - 8;
|
|
63
|
-
setAutoScroll(nearBottom);
|
|
64
|
-
};
|
|
65
|
-
scrollEl.addEventListener('scroll', onScroll, { passive: true } as AddEventListenerOptions);
|
|
66
|
-
return () => scrollEl.removeEventListener('scroll', onScroll);
|
|
67
|
-
}, [open]);
|
|
68
|
-
|
|
69
|
-
// Preserve viewport when new content streams. If autoScroll is off, compensate for height growth.
|
|
70
|
-
useLayoutEffect(() => {
|
|
71
|
-
if (!open) return;
|
|
72
|
-
const root = containerRef.current;
|
|
73
|
-
if (!root) return;
|
|
74
|
-
const scrollEl = root.querySelector('.cs-message-list') as HTMLElement | null;
|
|
75
|
-
if (!scrollEl) return;
|
|
76
|
-
const newHeight = scrollEl.scrollHeight;
|
|
77
|
-
const prevHeight = prevScrollHeightRef.current || newHeight;
|
|
78
|
-
if (autoScroll) {
|
|
79
|
-
scrollEl.scrollTop = newHeight;
|
|
80
|
-
} else {
|
|
81
|
-
const delta = newHeight - prevHeight;
|
|
82
|
-
if (delta !== 0) {
|
|
83
|
-
scrollEl.scrollTop = scrollEl.scrollTop + delta;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
prevScrollHeightRef.current = newHeight;
|
|
87
|
-
}, [messages, open, autoScroll]);
|
|
53
|
+
if (listRef.current) listRef.current.scrollTop = listRef.current.scrollHeight;
|
|
54
|
+
}, [messages, open]);
|
|
88
55
|
|
|
89
56
|
// Optional hint (only shows if user hasn't dismissed before and when collapsed)
|
|
90
57
|
useEffect(() => {
|
|
@@ -241,7 +208,6 @@ export default function ChatWidget({ api = '/api/chat', title = 'KYD Bot', hintT
|
|
|
241
208
|
aria-label={'Chat sidebar'}
|
|
242
209
|
style={{ position: 'fixed', top: headerTop, right: 0, bottom: 0, width, maxWidth: '92vw' }}
|
|
243
210
|
className={'shadow-xl border flex flex-col overflow-hidden'}
|
|
244
|
-
ref={containerRef}
|
|
245
211
|
>
|
|
246
212
|
{/* Left-edge resizer */}
|
|
247
213
|
<div
|
|
@@ -256,7 +222,7 @@ export default function ChatWidget({ api = '/api/chat', title = 'KYD Bot', hintT
|
|
|
256
222
|
<div className={'flex-1'} style={{ minHeight: 0 }}>
|
|
257
223
|
<MainContainer style={{ height: '100%', position: 'relative', background: 'var(--content-card-background)', border: 'none'}}>
|
|
258
224
|
<ChatContainer style={{ height: '100%' }}>
|
|
259
|
-
<MessageList typingIndicator={undefined} autoScrollToBottom
|
|
225
|
+
<MessageList typingIndicator={undefined} autoScrollToBottom>
|
|
260
226
|
{messages.length === 0 && (
|
|
261
227
|
<Message model={{ message: 'Start a conversation. I can answer questions about this developer’s report.', sender: 'KYD', direction: 'incoming', position: 'single' }} />
|
|
262
228
|
)}
|
|
@@ -306,7 +272,7 @@ export default function ChatWidget({ api = '/api/chat', title = 'KYD Bot', hintT
|
|
|
306
272
|
</ChatContainer>
|
|
307
273
|
</MainContainer>
|
|
308
274
|
</div>
|
|
309
|
-
<form onSubmit={(e) => { e.preventDefault(); if (!sending && input.trim())
|
|
275
|
+
<form onSubmit={(e) => { e.preventDefault(); if (!sending && input.trim()) sendMessage(); }}>
|
|
310
276
|
<div className={'flex items-end gap-2 p-2 border-t'} style={{ borderColor: 'var(--icon-button-secondary)', background: 'var(--content-card-background)' }}>
|
|
311
277
|
<input
|
|
312
278
|
aria-label={'Type your message'}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useEffect, useRef
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
4
|
import './chat-overrides.css';
|
|
5
5
|
import { FiSend } from 'react-icons/fi';
|
|
6
6
|
import { useChatStreaming } from './useChatStreaming';
|
|
@@ -8,33 +8,14 @@ import { useChatStreaming } from './useChatStreaming';
|
|
|
8
8
|
export default function ChatWindowStreaming({ api = '/api/chat', badgeId }: { api?: string, badgeId: string }) {
|
|
9
9
|
const { messages, input, setInput, sending, sendMessage } = useChatStreaming({ api, badgeId });
|
|
10
10
|
const listRef = useRef<HTMLDivElement>(null);
|
|
11
|
-
const autoScrollRef = useRef<boolean>(true);
|
|
12
|
-
const prevScrollHeightRef = useRef<number>(0);
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (!el) return;
|
|
17
|
-
const nearBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 8;
|
|
18
|
-
autoScrollRef.current = nearBottom;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
useLayoutEffect(() => {
|
|
22
|
-
const el = listRef.current;
|
|
23
|
-
if (!el) return;
|
|
24
|
-
const newHeight = el.scrollHeight;
|
|
25
|
-
const prevHeight = prevScrollHeightRef.current || newHeight;
|
|
26
|
-
if (autoScrollRef.current) {
|
|
27
|
-
el.scrollTop = newHeight;
|
|
28
|
-
} else {
|
|
29
|
-
const delta = newHeight - prevHeight;
|
|
30
|
-
if (delta !== 0) el.scrollTop = el.scrollTop + delta;
|
|
31
|
-
}
|
|
32
|
-
prevScrollHeightRef.current = newHeight;
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (listRef.current) listRef.current.scrollTop = listRef.current.scrollHeight;
|
|
33
14
|
}, [messages]);
|
|
34
15
|
|
|
35
16
|
return (
|
|
36
17
|
<div className="flex flex-col border rounded-lg overflow-hidden" style={{ background: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)'}}>
|
|
37
|
-
<div ref={listRef} className="p-4 space-y-3 h-72 overflow-auto"
|
|
18
|
+
<div ref={listRef} className="p-4 space-y-3 h-72 overflow-auto">
|
|
38
19
|
{messages.map(m => (
|
|
39
20
|
<div key={m.id} className={m.role === 'user' ? 'text-right' : 'text-left'}>
|
|
40
21
|
<div
|
|
@@ -54,13 +35,13 @@ export default function ChatWindowStreaming({ api = '/api/chat', badgeId }: { ap
|
|
|
54
35
|
<input
|
|
55
36
|
value={input}
|
|
56
37
|
onChange={e=>setInput(e.target.value)}
|
|
57
|
-
onKeyDown={e=>{ if (e.key==='Enter')
|
|
38
|
+
onKeyDown={e=>{ if (e.key==='Enter') sendMessage(); }}
|
|
58
39
|
className="flex-1 px-3 py-2 rounded border kyd-chat-input"
|
|
59
40
|
style={{ background: 'var(--input-background)', color: 'var(--text-main)', borderColor: 'var(--icon-button-secondary)' }}
|
|
60
41
|
placeholder="Ask KYD…"
|
|
61
42
|
/>
|
|
62
43
|
<button
|
|
63
|
-
onClick={()=>
|
|
44
|
+
onClick={()=>sendMessage()}
|
|
64
45
|
disabled={sending}
|
|
65
46
|
className="px-3 py-2 rounded disabled:opacity-60 kyd-chat-button"
|
|
66
47
|
style={{ background: 'var(--gradient-start)', color: '#fff' }}
|