kyd-shared-badge 0.3.51 → 0.3.53

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.51",
3
+ "version": "0.3.53",
4
4
  "private": false,
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -179,7 +179,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
179
179
  setActiveTab(t.key);
180
180
  try { if (typeof window !== 'undefined') window.history.replaceState(null, '', `#${t.key}`); } catch {}
181
181
  }}
182
- className={`px-3 sm:px-4 py-2 text-sm rounded-t flex items-center gap-2 cursor-pointer ${activeTab === t.key ? 'font-semibold' : ''}`}
182
+ className={`px-3 text-xs sm:text-base sm:px-4 py-2 text-sm rounded-t flex items-center gap-2 cursor-pointer ${activeTab === t.key ? 'font-semibold' : ''}`}
183
183
  style={{
184
184
  color: 'var(--text-main)',
185
185
  background: activeTab === t.key ? 'var(--content-card-background)' : 'transparent',
@@ -56,7 +56,7 @@ export function useChatStreaming(cfg: UseChatStreamingConfig) {
56
56
  }
57
57
  const res = await fetch(api, {
58
58
  method: 'POST',
59
- headers: { 'Content-Type': 'application/json' },
59
+ headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream' },
60
60
  body: JSON.stringify({ content, sessionId: sid, badgeId }),
61
61
  signal: abortRef.current.signal,
62
62
  });
@@ -65,13 +65,23 @@ export function useChatStreaming(cfg: UseChatStreamingConfig) {
65
65
 
66
66
  const reader = res.body.getReader();
67
67
  const decoder = new TextDecoder();
68
+ let buffer = '';
68
69
  let done = false;
69
70
  while (!done) {
70
- const chunk = await reader.read();
71
- done = chunk.done || false;
72
- if (chunk.value) {
73
- const textPart = decoder.decode(chunk.value, { stream: true });
74
- setMessages(m => m.map(msg => msg.id === assistantId ? { ...msg, content: msg.content + textPart } : msg));
71
+ const { value, done: readerDone } = await reader.read();
72
+ done = readerDone || false;
73
+ if (value) {
74
+ buffer += decoder.decode(value, { stream: true });
75
+ const lines = buffer.split(/\r?\n/);
76
+ buffer = lines.pop() ?? '';
77
+ for (const raw of lines) {
78
+ const line = raw.trim();
79
+ if (!line.startsWith('data:')) continue;
80
+ const data = line.slice(5).trim();
81
+ if (!data) continue;
82
+ if (data === '[DONE]') { done = true; break; }
83
+ setMessages(m => m.map(msg => msg.id === assistantId ? { ...msg, content: msg.content + data } : msg));
84
+ }
75
85
  }
76
86
  }
77
87
  } catch (e) {
package/src/lib/routes.ts CHANGED
@@ -2,8 +2,6 @@
2
2
  // POST /api/chat { content: string, sessionId?: string }
3
3
 
4
4
  import { NextRequest } from 'next/server';
5
- import { streamText } from 'ai';
6
- import { createOpenAI } from '@ai-sdk/openai';
7
5
 
8
6
  import { getHistory, putMessage } from './chat-store';
9
7
  import {
@@ -34,19 +32,28 @@ export async function chatStreamRoute(req: NextRequest, userId: string, companyI
34
32
  return Response.json({ error: 'Rate limit exceeded' }, { status: 429 });
35
33
  }
36
34
 
35
+ // Persist the user message first so it is included in history
37
36
  await putMessage({ sessionId, role: 'user', content });
38
37
 
39
- const badgeUserId = await getBadgeUserId(badgeId);
40
- const aggregated = await aggregateUserData(badgeUserId, !!companyId, companyId);
38
+ // Build required context in parallel to reduce latency
39
+ const [badgeUserId, history] = await Promise.all([
40
+ getBadgeUserId(badgeId),
41
+ getHistory(sessionId, 20),
42
+ ]);
43
+ const [aggregated, graphData] = await Promise.all([
44
+ aggregateUserData(badgeUserId, !!companyId, companyId),
45
+ getReportGraphData(badgeId),
46
+ ]);
41
47
  const cleaned = cleanDeveloperProfile(aggregated);
42
- const graphData = await getReportGraphData(badgeId);
43
48
  const system = buildAllContextPrompt(cleaned, graphData, { concise: true });
44
- const history = await getHistory(sessionId, 20);
49
+
45
50
  const apiKey = process.env.OPENROUTER_API_KEY;
46
51
  if (!apiKey) {
47
52
  return Response.json({ error: 'Server misconfigured: missing OPENROUTER_API_KEY' }, { status: 500 });
48
53
  }
49
54
 
55
+ const model = process.env.OPENROUTER_MODEL || 'openai/o4-mini';
56
+
50
57
  const chatMessages = [
51
58
  { role: 'system', content: system },
52
59
  ...history.map((m: any) => ({ role: m.role, content: m.content })),
@@ -58,9 +65,10 @@ export async function chatStreamRoute(req: NextRequest, userId: string, companyI
58
65
  headers: {
59
66
  'Content-Type': 'application/json',
60
67
  'Authorization': `Bearer ${apiKey}`,
68
+ 'Accept': 'text/event-stream',
61
69
  },
62
70
  body: JSON.stringify({
63
- model: 'openai/o4-mini',
71
+ model,
64
72
  messages: chatMessages,
65
73
  stream: true,
66
74
  max_tokens: 1024,
@@ -84,6 +92,8 @@ export async function chatStreamRoute(req: NextRequest, userId: string, companyI
84
92
  const { value, done } = await reader.read();
85
93
  if (done) {
86
94
  try { if (assistantText) await putMessage({ sessionId, role: 'assistant', content: assistantText }); } catch {}
95
+ // Send final SSE terminator
96
+ controller.enqueue(encoder.encode('data: [DONE]\n\n'));
87
97
  controller.close();
88
98
  return;
89
99
  }
@@ -97,6 +107,7 @@ export async function chatStreamRoute(req: NextRequest, userId: string, companyI
97
107
  if (!data) continue;
98
108
  if (data === '[DONE]') {
99
109
  try { if (assistantText) await putMessage({ sessionId, role: 'assistant', content: assistantText }); } catch {}
110
+ controller.enqueue(encoder.encode('data: [DONE]\n\n'));
100
111
  controller.close();
101
112
  return;
102
113
  }
@@ -105,9 +116,13 @@ export async function chatStreamRoute(req: NextRequest, userId: string, companyI
105
116
  const delta = json?.choices?.[0]?.delta?.content ?? '';
106
117
  if (delta) {
107
118
  assistantText += delta;
108
- controller.enqueue(encoder.encode(delta));
119
+ // Emit proper SSE line to the client
120
+ controller.enqueue(encoder.encode(`data: ${delta}\n\n`));
109
121
  }
110
- } catch {}
122
+ } catch {
123
+ // If upstream sends a non-JSON data line, forward as-is
124
+ controller.enqueue(encoder.encode(`data: ${data}\n\n`));
125
+ }
111
126
  }
112
127
  pump();
113
128
  } catch (err) {
@@ -123,9 +138,10 @@ export async function chatStreamRoute(req: NextRequest, userId: string, companyI
123
138
 
124
139
  return new Response(stream, {
125
140
  headers: {
126
- 'Content-Type': 'text/plain; charset=utf-8',
141
+ 'Content-Type': 'text/event-stream; charset=utf-8',
127
142
  'Cache-Control': 'no-cache, no-transform',
128
143
  'X-Accel-Buffering': 'no',
144
+ 'Connection': 'keep-alive',
129
145
  },
130
146
  });
131
147
 
package/src/types.ts CHANGED
@@ -466,4 +466,14 @@ export interface TopBusinessRule {
466
466
  label: string;
467
467
  weight: number;
468
468
  uid: string;
469
- }
469
+ }
470
+
471
+ export type Role = {
472
+ roleId: string;
473
+ name: string;
474
+ description?: string;
475
+ organization?: string;
476
+ createdAt?: string;
477
+ userId?: string;
478
+ }
479
+