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 +1 -1
- package/src/SharedBadgeDisplay.tsx +1 -1
- package/src/chat/useChatStreaming.ts +16 -6
- package/src/lib/routes.ts +26 -10
- package/src/types.ts +11 -1
package/package.json
CHANGED
|
@@ -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
|
|
71
|
-
done =
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
40
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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/
|
|
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
|
+
|