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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kyd-shared-badge",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "private": false,
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -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
  );
@@ -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
- // Measure sticky header height so the sidebar does not overlap it
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 loginUrl = `/auth/login?callbackUrl=${encodeURIComponent(currentPath)}&chatPrompt=${encodeURIComponent(promptText)}`;
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);