kyd-shared-badge 0.3.8 → 0.3.9
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
|
@@ -23,6 +23,13 @@ import { BusinessRulesProvider } from './components/BusinessRulesContext';
|
|
|
23
23
|
import Reveal from './components/Reveal';
|
|
24
24
|
import { formatLocalDateTime } from './utils/date';
|
|
25
25
|
import ChatWidget from './chat/ChatWidget';
|
|
26
|
+
type ChatWidgetProps = Partial<{
|
|
27
|
+
api: string;
|
|
28
|
+
title: string;
|
|
29
|
+
hintText: string;
|
|
30
|
+
loginPath: string;
|
|
31
|
+
headerOffset: 'auto' | 'none' | number;
|
|
32
|
+
}>;
|
|
26
33
|
|
|
27
34
|
// const hexToRgba = (hex: string, alpha: number) => {
|
|
28
35
|
// const clean = hex.replace('#', '');
|
|
@@ -32,7 +39,7 @@ import ChatWidget from './chat/ChatWidget';
|
|
|
32
39
|
// return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
33
40
|
// };
|
|
34
41
|
|
|
35
|
-
const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
42
|
+
const SharedBadgeDisplay = ({ badgeData, chatProps }: { badgeData: PublicBadgeData, chatProps?: ChatWidgetProps }) => {
|
|
36
43
|
const {
|
|
37
44
|
badgeId,
|
|
38
45
|
developerName,
|
|
@@ -534,7 +541,7 @@ const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
|
534
541
|
</footer>
|
|
535
542
|
</Reveal>
|
|
536
543
|
{/* Floating chat widget */}
|
|
537
|
-
<ChatWidget api={'/api/chat'} badgeId={badgeId} />
|
|
544
|
+
<ChatWidget api={chatProps?.api || '/api/chat'} badgeId={badgeId} title={chatProps?.title} hintText={chatProps?.hintText} loginPath={chatProps?.loginPath} headerOffset={chatProps?.headerOffset} />
|
|
538
545
|
</div>
|
|
539
546
|
</BusinessRulesProvider>
|
|
540
547
|
);
|
package/src/chat/ChatWidget.tsx
CHANGED
|
@@ -20,9 +20,12 @@ type Props = {
|
|
|
20
20
|
title?: string;
|
|
21
21
|
hintText?: string;
|
|
22
22
|
badgeId: string;
|
|
23
|
+
// Optional: customize login path and header offset behavior for different host apps
|
|
24
|
+
loginPath?: string; // e.g., '/login' in enterprise; defaults to '/auth/login'
|
|
25
|
+
headerOffset?: 'auto' | 'none' | number; // default 'auto' (measure <header>), use 'none' when no header
|
|
23
26
|
};
|
|
24
27
|
|
|
25
|
-
export default function ChatWidget({ api = '/api/chat', title = 'KYD Bot', hintText = 'Ask anything about the developer', badgeId }: Props) {
|
|
28
|
+
export default function ChatWidget({ api = '/api/chat', title = 'KYD Bot', hintText = 'Ask anything about the developer', badgeId, loginPath = '/auth/login', headerOffset = 'auto' }: Props) {
|
|
26
29
|
// Sidebar open state (default expanded)
|
|
27
30
|
const [open, setOpen] = useState<boolean>(() => {
|
|
28
31
|
try { const s = localStorage.getItem('kydChatSidebarOpen'); return s ? s === '1' : true; } catch { return true; }
|
|
@@ -46,7 +49,7 @@ export default function ChatWidget({ api = '/api/chat', title = 'KYD Bot', hintT
|
|
|
46
49
|
const tabDraggedRef = useRef(false);
|
|
47
50
|
const [headerTop, setHeaderTop] = useState(0);
|
|
48
51
|
const [showHint, setShowHint] = useState(false);
|
|
49
|
-
const { messages, input, setInput, sending, sendMessage, cancel } = useChatStreaming({ api, badgeId });
|
|
52
|
+
const { messages, input, setInput, sending, sendMessage, cancel } = useChatStreaming({ api, badgeId, loginPath });
|
|
50
53
|
const listRef = useRef<HTMLDivElement>(null);
|
|
51
54
|
|
|
52
55
|
useEffect(() => {
|
|
@@ -117,13 +120,23 @@ export default function ChatWidget({ api = '/api/chat', title = 'KYD Bot', hintT
|
|
|
117
120
|
return () => window.removeEventListener('keydown', onKey);
|
|
118
121
|
}, [open]);
|
|
119
122
|
|
|
120
|
-
//
|
|
123
|
+
// Header offset handling to keep chat below any sticky header in host app
|
|
121
124
|
useEffect(() => {
|
|
125
|
+
if (headerOffset === 'none') {
|
|
126
|
+
setHeaderTop(0);
|
|
127
|
+
setTabTop(t => Math.max(16, t));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (typeof headerOffset === 'number' && Number.isFinite(headerOffset)) {
|
|
131
|
+
const h = Math.max(0, Math.floor(headerOffset));
|
|
132
|
+
setHeaderTop(h);
|
|
133
|
+
setTabTop(t => Math.max(h + 16, t));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
122
136
|
const measure = () => {
|
|
123
137
|
const el = document.querySelector('header');
|
|
124
138
|
const h = el ? Math.floor(el.getBoundingClientRect().height) : 0;
|
|
125
139
|
setHeaderTop(h);
|
|
126
|
-
// Keep collapsed tab within bounds beneath header
|
|
127
140
|
setTabTop(t => Math.max(h + 16, t));
|
|
128
141
|
};
|
|
129
142
|
measure();
|
|
@@ -133,7 +146,7 @@ export default function ChatWidget({ api = '/api/chat', title = 'KYD Bot', hintT
|
|
|
133
146
|
window.removeEventListener('resize', measure);
|
|
134
147
|
window.removeEventListener('scroll', measure);
|
|
135
148
|
};
|
|
136
|
-
}, []);
|
|
149
|
+
}, [headerOffset]);
|
|
137
150
|
|
|
138
151
|
// Drag to resize (left edge of sidebar)
|
|
139
152
|
useEffect(() => {
|
|
@@ -8,10 +8,12 @@ export type ChatMessage = { id: string; role: Role; content: string };
|
|
|
8
8
|
export type UseChatStreamingConfig = {
|
|
9
9
|
api: string; // e.g. /api/chat
|
|
10
10
|
badgeId: string;
|
|
11
|
+
// Optional login path for redirect when 401 occurs. Defaults to '/auth/login' for portability across apps.
|
|
12
|
+
loginPath?: string;
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
export function useChatStreaming(cfg: UseChatStreamingConfig) {
|
|
14
|
-
const { api, badgeId } = cfg;
|
|
16
|
+
const { api, badgeId, loginPath = '/auth/login' } = cfg;
|
|
15
17
|
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
|
16
18
|
const [input, setInput] = useState('');
|
|
17
19
|
const [sending, setSending] = useState(false);
|
|
@@ -32,7 +34,8 @@ export function useChatStreaming(cfg: UseChatStreamingConfig) {
|
|
|
32
34
|
// Inform the user then redirect to login carrying callbackUrl and prompt
|
|
33
35
|
setMessages(m => m.map(msg => msg.id === assistantId ? { ...msg, content: 'You need to log in to use chat. Redirecting to login…' } : msg));
|
|
34
36
|
const currentPath = typeof window !== 'undefined' ? (window.location.pathname + window.location.search + window.location.hash) : '/';
|
|
35
|
-
const
|
|
37
|
+
const base = loginPath || '/auth/login';
|
|
38
|
+
const loginUrl = `${base}?callbackUrl=${encodeURIComponent(currentPath)}&chatPrompt=${encodeURIComponent(promptText)}`;
|
|
36
39
|
setTimeout(() => {
|
|
37
40
|
if (typeof window !== 'undefined') window.location.href = loginUrl;
|
|
38
41
|
}, 700);
|