kyd-shared-badge 0.3.35 → 0.3.37

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.
@@ -2,9 +2,9 @@ declare module 'ai' {
2
2
  export function streamText(args: any): any;
3
3
  }
4
4
 
5
- declare module '@ai-sdk/amazon-bedrock' {
5
+ declare module '@ai-sdk/openai' {
6
6
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
- export function bedrock(modelId: string, opts?: any): any;
7
+ export function createOpenAI(config?: any): (model: string, options?: any) => any;
8
8
  }
9
9
 
10
10
 
@@ -67,11 +67,21 @@ const CategoryBars: React.FC<CategoryBarsProps> = ({
67
67
  const hasTarget = typeof targetRaw === 'number' && isFinite(targetRaw);
68
68
  const targetClamp = hasTarget ? Math.max(0, Math.min(100, Math.round(targetRaw as number))) : 0;
69
69
  const targetWidth = targetClamp / 2; // same scale as fillWidth
70
+ const handleClick = (e: React.MouseEvent) => {
71
+ // Navigate to Appendix -> Skills category anchor
72
+ try {
73
+ if (typeof window !== 'undefined') {
74
+ const anchor = `#appendix-skills-cat-${encodeURIComponent(category.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, ''))}`;
75
+ const url = `#appendix${anchor}`;
76
+ window.location.hash = url;
77
+ }
78
+ } catch {}
79
+ };
70
80
  return (
71
81
  <div key={category} className="first:pt-0 group relative">
72
- <div className={'font-semibold mb-1'} style={{ color: 'var(--text-main)' }}>
82
+ <button type="button" onClick={handleClick} className={'font-semibold mb-1 underline-offset-2 hover:underline text-left'} style={{ color: 'var(--text-main)' }}>
73
83
  {category}
74
- </div>
84
+ </button>
75
85
  <div className="relative">
76
86
  <div
77
87
  className="w-full rounded-full overflow-hidden relative"
@@ -80,6 +90,9 @@ const CategoryBars: React.FC<CategoryBarsProps> = ({
80
90
  background: 'transparent',
81
91
  outline: '1px solid var(--icon-button-secondary)',
82
92
  }}
93
+ onClick={handleClick}
94
+ role={'button'}
95
+ aria-label={`Jump to ${category} in Appendix`}
83
96
  >
84
97
  {/* signed fill originating from center */}
85
98
  <div className="absolute top-0 h-full" style={{ left, width: `${fillWidth}%`, backgroundColor: isNegative ? `var(--status-negative, ${red})` : `var(--status-positive, ${green})` }} />
@@ -335,10 +335,19 @@ const Skills = ({ skillsCategoryRadar, headless }: { skillsMatrix?: SkillsMatrix
335
335
  <div ref={footprintLegendRef} className={'kyd-avoid-break'} style={{ position: 'relative', breakInside: 'avoid', pageBreakInside: 'avoid' as unknown as undefined }}>
336
336
  <div className="grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-1">
337
337
  {legendData.map((item, idx) => (
338
- <div
338
+ <button
339
339
  key={idx}
340
- className="flex items-center gap-2 text-xs"
341
- style={{ color: 'var(--text-secondary)' }}
340
+ className="flex items-center gap-2 text-xs text-left hover:underline underline-offset-2"
341
+ style={{ color: 'var(--text-secondary)', background: 'transparent' }}
342
+ onClick={() => {
343
+ try {
344
+ if (typeof window !== 'undefined') {
345
+ const anchor = `#appendix-skills-cat-${encodeURIComponent(item.label.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, ''))}`;
346
+ const url = `#appendix${anchor}`;
347
+ window.location.hash = url;
348
+ }
349
+ } catch {}
350
+ }}
342
351
  onMouseEnter={(e) => {
343
352
  const rect = footprintLegendRef.current?.getBoundingClientRect();
344
353
  if (!rect) return;
@@ -362,7 +371,7 @@ const Skills = ({ skillsCategoryRadar, headless }: { skillsMatrix?: SkillsMatrix
362
371
  <span className={'inline-block h-2 w-2 rounded-full'} style={{ backgroundColor: 'var(--text-secondary)', flexShrink: 0 }} />
363
372
  <span className="truncate">{item.label}</span>
364
373
  <span className="ml-auto opacity-80">{item.percent}%</span>
365
- </div>
374
+ </button>
366
375
  ))}
367
376
  </div>
368
377
  {!headless && <TooltipBox state={footprintLegendTooltip} />}
@@ -15,8 +15,9 @@ const SkillsAppendixTable = ({ skillsAll }: { skillsAll?: SkillsAll }) => {
15
15
  useEffect(() => {
16
16
  const flash = () => {
17
17
  const hash = typeof window !== 'undefined' ? window.location.hash : '';
18
- if (!hash || !hash.startsWith('#appendix-skills-')) return;
19
- const id = hash.slice(1);
18
+ if (!hash) return;
19
+ // Support skill rows and category headers
20
+ const id = hash.startsWith('#') ? hash.slice(1) : hash;
20
21
  const el = document.getElementById(id) as HTMLElement | null;
21
22
  if (!el) return;
22
23
  const originalBg = el.style.backgroundColor;
@@ -32,48 +33,70 @@ const SkillsAppendixTable = ({ skillsAll }: { skillsAll?: SkillsAll }) => {
32
33
  }, []);
33
34
 
34
35
  if (!rows.length) return null;
36
+ // Group rows by category; skills can be in multiple categories
37
+ const groupMap: Record<string, SkillRow[]> = {};
38
+ const UNCATEGORIZED = 'Uncategorized';
39
+ for (const r of rows) {
40
+ const cats = Array.isArray((r as any).categories) && (r as any).categories.length > 0 ? (r as any).categories as string[] : [UNCATEGORIZED];
41
+ for (const c of cats) {
42
+ const key = c || UNCATEGORIZED;
43
+ if (!groupMap[key]) groupMap[key] = [];
44
+ groupMap[key].push(r);
45
+ }
46
+ }
47
+ const orderedCategories = Object.keys(groupMap).sort((a, b) => {
48
+ if (a === UNCATEGORIZED) return 1;
49
+ if (b === UNCATEGORIZED) return -1;
50
+ return a.localeCompare(b);
51
+ });
52
+
35
53
  return (
36
- <div id="appendix-skills" className="mt-4">
37
- <div className={'overflow-auto rounded-lg border'} style={{ borderColor: 'var(--icon-button-secondary)' }}>
38
- <table className="min-w-full text-sm">
39
- <thead>
40
- <tr style={{ backgroundColor: 'var(--content-card-background)' }}>
41
- <th className="text-left p-3" style={{ color: 'var(--text-secondary)' }}>Skill</th>
42
- <th className="text-left p-3" style={{ color: 'var(--text-secondary)' }}>Observed</th>
43
- <th className="text-left p-3" style={{ color: 'var(--text-secondary)' }}>Self-reported</th>
44
- <th className="text-left p-3" style={{ color: 'var(--text-secondary)' }}>Certified</th>
45
- </tr>
46
- </thead>
47
- <tbody>
48
- {rows.map((row, idx: number) => (
49
- <tr id={`appendix-skills-${slugify(row.name)}`} key={idx} className="border-t" style={{ borderColor: 'var(--icon-button-secondary)' }}>
50
- <td className="p-3" style={{ color: 'var(--text-main)' }}>{row.name}</td>
51
- {(['observed','self_reported','certified'] as const).map((b) => {
52
- const bucket = row[b];
53
- const present = !!bucket.present;
54
- const dot = present ? green : 'var(--icon-button-secondary)';
55
- const ev = bucket.evidence;
56
- const sources = (bucket.sources || []).slice(0,4);
57
- return (
58
- <td key={b} className="p-3 align-top" style={{ color: 'var(--text-secondary)' }}>
59
- <div className="flex items-start gap-2">
60
- <div className={'mt-1 rounded-full'} style={{ backgroundColor: dot, minHeight: '8px', minWidth: '8px', height: '8px', width: '8px', flexShrink: 0 }} />
61
- <div>
62
- {(ev || (sources && sources.length > 0)) && (
63
- <div className={'text-xs mt-1'} style={{ color: 'var(--text-secondary)' }}>
64
- {ev ? <div>{ev}</div> : null}
54
+ <div id="appendix-skills" className="mt-4 space-y-8">
55
+ {orderedCategories.map((cat) => (
56
+ <div key={cat}>
57
+ <h5 id={`appendix-skills-cat-${slugify(cat)}`} className={'text-base font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>{cat}</h5>
58
+ <div className={'overflow-auto rounded-lg border'} style={{ borderColor: 'var(--icon-button-secondary)' }}>
59
+ <table className="min-w-full text-sm">
60
+ <thead>
61
+ <tr style={{ backgroundColor: 'var(--content-card-background)' }}>
62
+ <th className="text-left p-3" style={{ color: 'var(--text-secondary)' }}>Skill</th>
63
+ <th className="text-left p-3" style={{ color: 'var(--text-secondary)' }}>Observed</th>
64
+ <th className="text-left p-3" style={{ color: 'var(--text-secondary)' }}>Self-reported</th>
65
+ <th className="text-left p-3" style={{ color: 'var(--text-secondary)' }}>Certified</th>
66
+ </tr>
67
+ </thead>
68
+ <tbody>
69
+ {groupMap[cat].map((row, idx: number) => (
70
+ <tr id={`appendix-skills-${slugify(row.name)}`} key={`${cat}-${idx}`} className="border-t" style={{ borderColor: 'var(--icon-button-secondary)' }}>
71
+ <td className="p-3" style={{ color: 'var(--text-main)' }}>{row.name}</td>
72
+ {(['observed','self_reported','certified'] as const).map((b) => {
73
+ const bucket = row[b];
74
+ const present = !!bucket.present;
75
+ const dot = present ? green : 'var(--icon-button-secondary)';
76
+ const ev = bucket.evidence;
77
+ const sources = (bucket.sources || []).slice(0,4);
78
+ return (
79
+ <td key={b} className="p-3 align-top" style={{ color: 'var(--text-secondary)' }}>
80
+ <div className="flex items-start gap-2">
81
+ <div className={'mt-1 rounded-full'} style={{ backgroundColor: dot, minHeight: '8px', minWidth: '8px', height: '8px', width: '8px', flexShrink: 0 }} />
82
+ <div>
83
+ {(ev || (sources && sources.length > 0)) && (
84
+ <div className={'text-xs mt-1'} style={{ color: 'var(--text-secondary)' }}>
85
+ {ev ? <div>{ev}</div> : null}
86
+ </div>
87
+ )}
65
88
  </div>
66
- )}
67
- </div>
68
- </div>
69
- </td>
70
- );
71
- })}
72
- </tr>
73
- ))}
74
- </tbody>
75
- </table>
76
- </div>
89
+ </div>
90
+ </td>
91
+ );
92
+ })}
93
+ </tr>
94
+ ))}
95
+ </tbody>
96
+ </table>
97
+ </div>
98
+ </div>
99
+ ))}
77
100
  </div>
78
101
  );
79
102
  };
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './types';
2
2
  export { default as SharedBadgeDisplay } from './SharedBadgeDisplay';
3
+ export { default as PrintableBadgeDisplay } from './PrintableBadgeDisplay';
3
4
  export { default as ChatWindowStreaming } from './chat/ChatWindowStreaming';
4
5
  export { default as ChatWidget } from './chat/ChatWidget';
5
6
  export * from './utils/date';
package/src/lib/routes.ts CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  import { NextRequest } from 'next/server';
5
5
  import { streamText } from 'ai';
6
- import { bedrock } from '@ai-sdk/amazon-bedrock';
6
+ import { createOpenAI } from '@ai-sdk/openai';
7
7
 
8
8
  import { getHistory, putMessage } from './chat-store';
9
9
  import {
@@ -42,8 +42,13 @@ export async function chatStreamRoute(req: NextRequest, userId: string, companyI
42
42
  const system = buildAllContextPrompt(cleaned, graphData, { concise: true });
43
43
  const history = await getHistory(sessionId, 20);
44
44
 
45
+ const openrouter = createOpenAI({
46
+ apiKey: process.env.OPENROUTER_API_KEY,
47
+ baseURL: 'https://openrouter.ai/api/v1',
48
+ });
49
+
45
50
  const result = await streamText({
46
- model: bedrock('us.anthropic.claude-3-5-haiku-20241022-v1:0', { region: process.env.AWS_REGION }),
51
+ model: openrouter('openai/o4-mini'),
47
52
  system,
48
53
  messages: [...history, { role: 'user' as const, content }],
49
54
  maxTokens: 1024,
package/src/types.ts CHANGED
@@ -416,6 +416,8 @@ export interface SkillRow {
416
416
  observed: SkillBucket;
417
417
  self_reported: SkillBucket;
418
418
  certified: SkillBucket;
419
+ // optional list of skill categories from backend
420
+ categories?: string[];
419
421
  }
420
422
 
421
423
  export interface SkillsMatrix {