@djangocfg/ui-tools 2.1.335 → 2.1.337
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/README.md +68 -2
- package/dist/ChatRoot-PNNGQCYF.css +7 -0
- package/dist/ChatRoot-PNNGQCYF.css.map +1 -0
- package/dist/ChatRoot-XV2QXMV4.mjs +5 -0
- package/dist/ChatRoot-XV2QXMV4.mjs.map +1 -0
- package/dist/ChatRoot-YX4RLHQX.cjs +14 -0
- package/dist/ChatRoot-YX4RLHQX.cjs.map +1 -0
- package/dist/{CronScheduler.client-3O3VU4CI.mjs → CronScheduler.client-DLMXCPAJ.mjs} +4 -4
- package/dist/{CronScheduler.client-3O3VU4CI.mjs.map → CronScheduler.client-DLMXCPAJ.mjs.map} +1 -1
- package/dist/{CronScheduler.client-A4GO6YBY.cjs → CronScheduler.client-WEJF4PWQ.cjs} +14 -14
- package/dist/{CronScheduler.client-A4GO6YBY.cjs.map → CronScheduler.client-WEJF4PWQ.cjs.map} +1 -1
- package/dist/{DocsLayout-XLDB6CJ2.cjs → DocsLayout-N5ZJZPBY.cjs} +200 -199
- package/dist/DocsLayout-N5ZJZPBY.cjs.map +1 -0
- package/dist/{DocsLayout-CTJINVBM.mjs → DocsLayout-VFPPNKSQ.mjs} +7 -6
- package/dist/DocsLayout-VFPPNKSQ.mjs.map +1 -0
- package/dist/JsonSchemaForm-DD7CLRIG.cjs +13 -0
- package/dist/{JsonSchemaForm-6WMS4CIY.cjs.map → JsonSchemaForm-DD7CLRIG.cjs.map} +1 -1
- package/dist/JsonSchemaForm-XKUIVELK.mjs +4 -0
- package/dist/{JsonSchemaForm-KX4JT3M4.mjs.map → JsonSchemaForm-XKUIVELK.mjs.map} +1 -1
- package/dist/JsonTree-55625VVH.mjs +5 -0
- package/dist/{JsonTree-F27RMYSI.cjs.map → JsonTree-55625VVH.mjs.map} +1 -1
- package/dist/JsonTree-DCM5QGWF.cjs +11 -0
- package/dist/{JsonTree-QTJYSHCV.mjs.map → JsonTree-DCM5QGWF.cjs.map} +1 -1
- package/dist/{LottiePlayer.client-6WVWDO75.cjs → LottiePlayer.client-2S7ISJ2S.cjs} +6 -6
- package/dist/{LottiePlayer.client-6WVWDO75.cjs.map → LottiePlayer.client-2S7ISJ2S.cjs.map} +1 -1
- package/dist/{LottiePlayer.client-B4I6WNZM.mjs → LottiePlayer.client-5LDSSJWS.mjs} +4 -4
- package/dist/{LottiePlayer.client-B4I6WNZM.mjs.map → LottiePlayer.client-5LDSSJWS.mjs.map} +1 -1
- package/dist/{MapContainer-RYG4HPH4.cjs → MapContainer-76YL2JXL.cjs} +8 -8
- package/dist/{MapContainer-RYG4HPH4.cjs.map → MapContainer-76YL2JXL.cjs.map} +1 -1
- package/dist/{MapContainer-GXQLP5WY.mjs → MapContainer-7HXBI3OH.mjs} +3 -3
- package/dist/{MapContainer-GXQLP5WY.mjs.map → MapContainer-7HXBI3OH.mjs.map} +1 -1
- package/dist/{Mermaid.client-SXRRI2YW.mjs → Mermaid.client-NL4SVR7F.mjs} +4 -4
- package/dist/{Mermaid.client-SXRRI2YW.mjs.map → Mermaid.client-NL4SVR7F.mjs.map} +1 -1
- package/dist/{Mermaid.client-W76R5AKJ.cjs → Mermaid.client-NNTI6DFX.cjs} +26 -26
- package/dist/{Mermaid.client-W76R5AKJ.cjs.map → Mermaid.client-NNTI6DFX.cjs.map} +1 -1
- package/dist/Player-BRV7XTWR.mjs +4 -0
- package/dist/{Player-M3GC3VPE.mjs.map → Player-BRV7XTWR.mjs.map} +1 -1
- package/dist/Player-PM7F7DD7.cjs +13 -0
- package/dist/{Player-ZL2X5LGG.cjs.map → Player-PM7F7DD7.cjs.map} +1 -1
- package/dist/{PrettyCode.client-RPDIE5CH.cjs → PrettyCode.client-KOHDVPPN.cjs} +13 -13
- package/dist/{PrettyCode.client-RPDIE5CH.cjs.map → PrettyCode.client-KOHDVPPN.cjs.map} +1 -1
- package/dist/{PrettyCode.client-SPMTQEG4.mjs → PrettyCode.client-ZGYGKE7G.mjs} +4 -4
- package/dist/{PrettyCode.client-SPMTQEG4.mjs.map → PrettyCode.client-ZGYGKE7G.mjs.map} +1 -1
- package/dist/TreeRoot-N72OYKXU.cjs +19 -0
- package/dist/{TreeRoot-A3J65L6F.mjs.map → TreeRoot-N72OYKXU.cjs.map} +1 -1
- package/dist/TreeRoot-VGAIXCUA.mjs +4 -0
- package/dist/{TreeRoot-DSK5JILT.cjs.map → TreeRoot-VGAIXCUA.mjs.map} +1 -1
- package/dist/chunk-2ZLKZ5VR.mjs +631 -0
- package/dist/chunk-2ZLKZ5VR.mjs.map +1 -0
- package/dist/{chunk-LFWQ36LJ.mjs → chunk-5G5YBFS6.mjs} +4 -4
- package/dist/{chunk-LFWQ36LJ.mjs.map → chunk-5G5YBFS6.mjs.map} +1 -1
- package/dist/{chunk-IHAY6FO6.cjs → chunk-5I5QNGUG.cjs} +17 -17
- package/dist/{chunk-IHAY6FO6.cjs.map → chunk-5I5QNGUG.cjs.map} +1 -1
- package/dist/{chunk-F2CMIIOH.cjs → chunk-76NNDZH6.cjs} +42 -42
- package/dist/{chunk-F2CMIIOH.cjs.map → chunk-76NNDZH6.cjs.map} +1 -1
- package/dist/chunk-B5AWZOHJ.cjs +649 -0
- package/dist/chunk-B5AWZOHJ.cjs.map +1 -0
- package/dist/{chunk-KR6B3LVY.mjs → chunk-B6IR5KSC.mjs} +3 -3
- package/dist/{chunk-KR6B3LVY.mjs.map → chunk-B6IR5KSC.mjs.map} +1 -1
- package/dist/{chunk-5LBDYFWH.mjs → chunk-C6GXVH5J.mjs} +3 -3
- package/dist/{chunk-5LBDYFWH.mjs.map → chunk-C6GXVH5J.mjs.map} +1 -1
- package/dist/{chunk-NRKD4F5X.cjs → chunk-FEN5S772.cjs} +36 -36
- package/dist/{chunk-NRKD4F5X.cjs.map → chunk-FEN5S772.cjs.map} +1 -1
- package/dist/{chunk-2SMCH62O.cjs → chunk-FP2RLYQZ.cjs} +11 -11
- package/dist/{chunk-2SMCH62O.cjs.map → chunk-FP2RLYQZ.cjs.map} +1 -1
- package/dist/{chunk-MOME6KYD.mjs → chunk-G5IEC7SR.mjs} +3 -3
- package/dist/{chunk-MOME6KYD.mjs.map → chunk-G5IEC7SR.mjs.map} +1 -1
- package/dist/{chunk-SE5IERVH.mjs → chunk-GYIO7W7M.mjs} +3 -3
- package/dist/{chunk-SE5IERVH.mjs.map → chunk-GYIO7W7M.mjs.map} +1 -1
- package/dist/{chunk-3Z3A7FHA.cjs → chunk-IEEAENLX.cjs} +48 -48
- package/dist/{chunk-3Z3A7FHA.cjs.map → chunk-IEEAENLX.cjs.map} +1 -1
- package/dist/{chunk-DFTVB66S.cjs → chunk-KNDLV4PI.cjs} +85 -85
- package/dist/{chunk-DFTVB66S.cjs.map → chunk-KNDLV4PI.cjs.map} +1 -1
- package/dist/{chunk-SSUOENAZ.mjs → chunk-KNEQRUBA.mjs} +3 -3
- package/dist/{chunk-SSUOENAZ.mjs.map → chunk-KNEQRUBA.mjs.map} +1 -1
- package/dist/{chunk-CGILA3WO.mjs → chunk-N2XQF2OL.mjs} +5 -3
- package/dist/{chunk-CGILA3WO.mjs.map → chunk-N2XQF2OL.mjs.map} +1 -1
- package/dist/{chunk-EUADAUBQ.mjs → chunk-N4MZYNR4.mjs} +4 -4
- package/dist/{chunk-EUADAUBQ.mjs.map → chunk-N4MZYNR4.mjs.map} +1 -1
- package/dist/{chunk-GGKGH5PM.mjs → chunk-OBRSGM64.mjs} +4 -4
- package/dist/{chunk-GGKGH5PM.mjs.map → chunk-OBRSGM64.mjs.map} +1 -1
- package/dist/{chunk-6JTB2X72.mjs → chunk-ODO4GMW7.mjs} +3 -3
- package/dist/{chunk-6JTB2X72.mjs.map → chunk-ODO4GMW7.mjs.map} +1 -1
- package/dist/{chunk-WGEGR3DF.cjs → chunk-OLISEQHS.cjs} +5 -2
- package/dist/{chunk-WGEGR3DF.cjs.map → chunk-OLISEQHS.cjs.map} +1 -1
- package/dist/{chunk-PZKAH7WQ.mjs → chunk-PVAX67JG.mjs} +3 -3
- package/dist/{chunk-PZKAH7WQ.mjs.map → chunk-PVAX67JG.mjs.map} +1 -1
- package/dist/{chunk-PRPG2T2E.cjs → chunk-QJ6GTUCO.cjs} +6 -6
- package/dist/{chunk-PRPG2T2E.cjs.map → chunk-QJ6GTUCO.cjs.map} +1 -1
- package/dist/chunk-QW4RBGHN.cjs +961 -0
- package/dist/chunk-QW4RBGHN.cjs.map +1 -0
- package/dist/{chunk-33AMWFBZ.cjs → chunk-SGP7V2UW.cjs} +15 -15
- package/dist/{chunk-33AMWFBZ.cjs.map → chunk-SGP7V2UW.cjs.map} +1 -1
- package/dist/{chunk-FX2QFYWF.mjs → chunk-VWQ5WOIL.mjs} +3 -3
- package/dist/{chunk-FX2QFYWF.mjs.map → chunk-VWQ5WOIL.mjs.map} +1 -1
- package/dist/{chunk-ZLQHUZDU.cjs → chunk-YDPDTOSP.cjs} +139 -139
- package/dist/{chunk-ZLQHUZDU.cjs.map → chunk-YDPDTOSP.cjs.map} +1 -1
- package/dist/{chunk-77HQWEQ6.cjs → chunk-YW5IVWHQ.cjs} +33 -33
- package/dist/{chunk-77HQWEQ6.cjs.map → chunk-YW5IVWHQ.cjs.map} +1 -1
- package/dist/chunk-YWSQDBNU.mjs +2339 -0
- package/dist/chunk-YWSQDBNU.mjs.map +1 -0
- package/dist/{chunk-YXBOAGIM.cjs → chunk-YXZ6GU7H.cjs} +7 -7
- package/dist/{chunk-YXBOAGIM.cjs.map → chunk-YXZ6GU7H.cjs.map} +1 -1
- package/dist/{chunk-62Y65TGK.mjs → chunk-ZUFTH5IR.mjs} +8 -631
- package/dist/chunk-ZUFTH5IR.mjs.map +1 -0
- package/dist/chunk-ZWPBBAR2.cjs +2379 -0
- package/dist/chunk-ZWPBBAR2.cjs.map +1 -0
- package/dist/components-EHOGXATG.cjs +22 -0
- package/dist/{components-5UXYNAKR.cjs.map → components-EHOGXATG.cjs.map} +1 -1
- package/dist/components-MQ6DR7TX.cjs +26 -0
- package/dist/{components-CFXOEVPN.mjs.map → components-MQ6DR7TX.cjs.map} +1 -1
- package/dist/components-XRX7QGLB.mjs +5 -0
- package/dist/{components-WYEZL5TE.cjs.map → components-XRX7QGLB.mjs.map} +1 -1
- package/dist/components-YATKRWLH.mjs +5 -0
- package/dist/{components-ZAGG2PBO.mjs.map → components-YATKRWLH.mjs.map} +1 -1
- package/dist/file-icon/index.cjs +6 -6
- package/dist/file-icon/index.mjs +1 -1
- package/dist/index.cjs +739 -215
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1025 -39
- package/dist/index.d.ts +1025 -39
- package/dist/index.mjs +387 -31
- package/dist/index.mjs.map +1 -1
- package/dist/tree/index.cjs +38 -38
- package/dist/tree/index.d.cts +2 -2
- package/dist/tree/index.d.ts +2 -2
- package/dist/tree/index.mjs +3 -3
- package/package.json +6 -6
- package/src/index.ts +5 -0
- package/src/stories/index.ts +3 -1
- package/src/tools/Chat/Chat.story.tsx +1006 -0
- package/src/tools/Chat/README.md +528 -0
- package/src/tools/Chat/components/Attachments.tsx +192 -0
- package/src/tools/Chat/components/ChatRoot.tsx +208 -0
- package/src/tools/Chat/components/Composer.tsx +134 -0
- package/src/tools/Chat/components/EmptyState.tsx +47 -0
- package/src/tools/Chat/components/ErrorBanner.tsx +47 -0
- package/src/tools/Chat/components/JumpToLatest.tsx +30 -0
- package/src/tools/Chat/components/MessageActions.tsx +72 -0
- package/src/tools/Chat/components/MessageBubble.tsx +228 -0
- package/src/tools/Chat/components/MessageList.tsx +82 -0
- package/src/tools/Chat/components/Sources.tsx +55 -0
- package/src/tools/Chat/components/StreamingIndicator.tsx +29 -0
- package/src/tools/Chat/components/ToolCalls.tsx +172 -0
- package/src/tools/Chat/components/index.ts +24 -0
- package/src/tools/Chat/config.ts +55 -0
- package/src/tools/Chat/context/ChatProvider.tsx +126 -0
- package/src/tools/Chat/context/index.ts +9 -0
- package/src/tools/Chat/core/audio/audioBus.ts +172 -0
- package/src/tools/Chat/core/audio/index.ts +8 -0
- package/src/tools/Chat/core/audio/preferences.ts +68 -0
- package/src/tools/Chat/core/audio/types.ts +49 -0
- package/src/tools/Chat/core/ids.ts +16 -0
- package/src/tools/Chat/core/index.ts +5 -0
- package/src/tools/Chat/core/logger.ts +73 -0
- package/src/tools/Chat/core/markdown.ts +56 -0
- package/src/tools/Chat/core/payload-dispatch.ts +54 -0
- package/src/tools/Chat/core/persona.ts +35 -0
- package/src/tools/Chat/core/reducer.ts +335 -0
- package/src/tools/Chat/core/transport/http.ts +167 -0
- package/src/tools/Chat/core/transport/index.ts +13 -0
- package/src/tools/Chat/core/transport/mock.ts +134 -0
- package/src/tools/Chat/core/transport/sse.ts +116 -0
- package/src/tools/Chat/core/transport/types.ts +24 -0
- package/src/tools/Chat/hooks/index.ts +26 -0
- package/src/tools/Chat/hooks/useChat.ts +555 -0
- package/src/tools/Chat/hooks/useChatAudio.ts +191 -0
- package/src/tools/Chat/hooks/useChatComposer.ts +227 -0
- package/src/tools/Chat/hooks/useChatHistory.ts +59 -0
- package/src/tools/Chat/hooks/useChatLayout.ts +111 -0
- package/src/tools/Chat/hooks/useChatLightbox.ts +34 -0
- package/src/tools/Chat/hooks/useChatScroll.ts +132 -0
- package/src/tools/Chat/index.ts +161 -0
- package/src/tools/Chat/lazy.tsx +14 -0
- package/src/tools/Chat/types.ts +237 -0
- package/src/tools/Chat/utils/collectImageAttachments.ts +13 -0
- package/src/tools/Map/README.md +384 -0
- package/dist/DocsLayout-CTJINVBM.mjs.map +0 -1
- package/dist/DocsLayout-XLDB6CJ2.cjs.map +0 -1
- package/dist/JsonSchemaForm-6WMS4CIY.cjs +0 -13
- package/dist/JsonSchemaForm-KX4JT3M4.mjs +0 -4
- package/dist/JsonTree-F27RMYSI.cjs +0 -11
- package/dist/JsonTree-QTJYSHCV.mjs +0 -5
- package/dist/Player-M3GC3VPE.mjs +0 -4
- package/dist/Player-ZL2X5LGG.cjs +0 -13
- package/dist/TreeRoot-A3J65L6F.mjs +0 -4
- package/dist/TreeRoot-DSK5JILT.cjs +0 -19
- package/dist/chunk-62Y65TGK.mjs.map +0 -1
- package/dist/chunk-TKSFZHCG.cjs +0 -1597
- package/dist/chunk-TKSFZHCG.cjs.map +0 -1
- package/dist/components-5UXYNAKR.cjs +0 -22
- package/dist/components-CFXOEVPN.mjs +0 -5
- package/dist/components-WYEZL5TE.cjs +0 -26
- package/dist/components-ZAGG2PBO.mjs +0 -5
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory chat transport for stories and tests. Replays scripted replies.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
ChatMessage,
|
|
7
|
+
ChatStreamEvent,
|
|
8
|
+
ChatTransport,
|
|
9
|
+
CreateSessionOptions,
|
|
10
|
+
HistoryPage,
|
|
11
|
+
SendOptions,
|
|
12
|
+
SessionInfo,
|
|
13
|
+
StreamOptions,
|
|
14
|
+
} from '../../types';
|
|
15
|
+
import { createId } from '../ids';
|
|
16
|
+
|
|
17
|
+
export interface MockTransportOptions {
|
|
18
|
+
/** Each entry is the assistant's reply for one user turn. Strings are split
|
|
19
|
+
* into chunks; arrays are taken as the exact event sequence (after a
|
|
20
|
+
* prepended `message_start` and before a synthetic `message_end`). */
|
|
21
|
+
replies?: Array<string | ChatStreamEvent[]>;
|
|
22
|
+
latencyMs?: number;
|
|
23
|
+
/** Initial history returned by `createSession`. */
|
|
24
|
+
initialMessages?: ChatMessage[];
|
|
25
|
+
shouldFail?: (attempt: number) => boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DEFAULT_REPLY = 'Hi there!';
|
|
29
|
+
|
|
30
|
+
function splitForStream(text: string, parts = 4): string[] {
|
|
31
|
+
if (!text) return [];
|
|
32
|
+
const chunkSize = Math.max(1, Math.ceil(text.length / parts));
|
|
33
|
+
const out: string[] = [];
|
|
34
|
+
for (let i = 0; i < text.length; i += chunkSize) {
|
|
35
|
+
out.push(text.slice(i, i + chunkSize));
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
41
|
+
|
|
42
|
+
export function createMockTransport(opts: MockTransportOptions = {}): ChatTransport {
|
|
43
|
+
const replies = opts.replies?.length ? opts.replies : [DEFAULT_REPLY];
|
|
44
|
+
const latency = opts.latencyMs ?? 30;
|
|
45
|
+
const history: ChatMessage[] = [...(opts.initialMessages ?? [])];
|
|
46
|
+
let turn = 0;
|
|
47
|
+
let attempt = 0;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
async createSession(_opts?: CreateSessionOptions): Promise<SessionInfo> {
|
|
51
|
+
await sleep(latency);
|
|
52
|
+
return {
|
|
53
|
+
sessionId: createId('s'),
|
|
54
|
+
messages: history.length ? [...history] : undefined,
|
|
55
|
+
hasMore: false,
|
|
56
|
+
cursor: null,
|
|
57
|
+
resumed: history.length > 0,
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
async loadHistory(_sid, _cursor, _limit): Promise<HistoryPage> {
|
|
62
|
+
await sleep(latency);
|
|
63
|
+
return { messages: [], hasMore: false, nextCursor: null };
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async *stream(
|
|
67
|
+
_sid: string,
|
|
68
|
+
content: string,
|
|
69
|
+
options: StreamOptions,
|
|
70
|
+
): AsyncGenerator<ChatStreamEvent, void, void> {
|
|
71
|
+
attempt += 1;
|
|
72
|
+
if (opts.shouldFail?.(attempt)) {
|
|
73
|
+
throw new Error('mock transport scripted failure');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Record the user message for any subsequent loadHistory.
|
|
77
|
+
history.push({
|
|
78
|
+
id: createId('u'),
|
|
79
|
+
role: 'user',
|
|
80
|
+
content,
|
|
81
|
+
createdAt: Date.now(),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const messageId = createId('a');
|
|
85
|
+
yield { type: 'message_start', messageId, sessionId: _sid };
|
|
86
|
+
|
|
87
|
+
const reply = replies[turn % replies.length];
|
|
88
|
+
turn += 1;
|
|
89
|
+
|
|
90
|
+
if (typeof reply === 'string') {
|
|
91
|
+
for (const piece of splitForStream(reply)) {
|
|
92
|
+
if (options.signal.aborted) return;
|
|
93
|
+
await sleep(latency);
|
|
94
|
+
yield { type: 'chunk', delta: piece };
|
|
95
|
+
}
|
|
96
|
+
yield { type: 'message_end', tokensIn: content.length, tokensOut: reply.length };
|
|
97
|
+
} else {
|
|
98
|
+
for (const ev of reply) {
|
|
99
|
+
if (options.signal.aborted) return;
|
|
100
|
+
await sleep(latency);
|
|
101
|
+
yield ev;
|
|
102
|
+
}
|
|
103
|
+
// If the script didn't end the message itself, do it for them.
|
|
104
|
+
const lastType = reply[reply.length - 1]?.type;
|
|
105
|
+
if (lastType !== 'message_end' && lastType !== 'error') {
|
|
106
|
+
yield { type: 'message_end' };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
async send(_sid, content, _options?: SendOptions): Promise<ChatMessage> {
|
|
112
|
+
await sleep(latency);
|
|
113
|
+
const reply = replies[turn % replies.length];
|
|
114
|
+
turn += 1;
|
|
115
|
+
const text =
|
|
116
|
+
typeof reply === 'string'
|
|
117
|
+
? reply
|
|
118
|
+
: reply
|
|
119
|
+
.filter((e) => e.type === 'chunk')
|
|
120
|
+
.map((e) => (e as { delta: string }).delta)
|
|
121
|
+
.join('');
|
|
122
|
+
return {
|
|
123
|
+
id: createId('a'),
|
|
124
|
+
role: 'assistant',
|
|
125
|
+
content: text || DEFAULT_REPLY,
|
|
126
|
+
createdAt: Date.now(),
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
async closeSession(_sid: string): Promise<void> {
|
|
131
|
+
// no-op
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-Sent Events parser as an AsyncGenerator.
|
|
3
|
+
*
|
|
4
|
+
* Yields parsed events from a `Response` body. Handles the split-read case
|
|
5
|
+
* where `event:` and `data:` arrive in separate TCP packets. Skips malformed
|
|
6
|
+
* JSON gracefully. Honors AbortSignal (caller passes one to fetch).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ChatStreamEvent } from '../../types';
|
|
10
|
+
import { LIMITS } from '../../config';
|
|
11
|
+
|
|
12
|
+
interface RawEvent {
|
|
13
|
+
event?: string;
|
|
14
|
+
data?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ParseSSEOptions {
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
/** Map a raw SSE event to a ChatStreamEvent. Default: parse `data` as JSON
|
|
20
|
+
* and assume the JSON shape already matches `ChatStreamEvent`. */
|
|
21
|
+
map?: (raw: RawEvent) => ChatStreamEvent | null;
|
|
22
|
+
idleTimeoutMs?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const DEFAULT_MAP = (raw: RawEvent): ChatStreamEvent | null => {
|
|
26
|
+
if (!raw.data) return null;
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(raw.data) as ChatStreamEvent;
|
|
29
|
+
if (raw.event && !('type' in parsed)) {
|
|
30
|
+
return { ...(parsed as object), type: raw.event } as ChatStreamEvent;
|
|
31
|
+
}
|
|
32
|
+
return parsed;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export async function* parseSSE(
|
|
39
|
+
response: Response,
|
|
40
|
+
options: ParseSSEOptions = {},
|
|
41
|
+
): AsyncGenerator<ChatStreamEvent, void, void> {
|
|
42
|
+
if (!response.body) {
|
|
43
|
+
throw new Error('SSE response has no body');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const map = options.map ?? DEFAULT_MAP;
|
|
47
|
+
const idleMs = options.idleTimeoutMs ?? LIMITS.sseIdleMs;
|
|
48
|
+
const reader = response.body.getReader();
|
|
49
|
+
const decoder = new TextDecoder();
|
|
50
|
+
let buffer = '';
|
|
51
|
+
let lastChunkAt = Date.now();
|
|
52
|
+
|
|
53
|
+
const idleCheck = () => {
|
|
54
|
+
if (Date.now() - lastChunkAt > idleMs) {
|
|
55
|
+
throw new Error(`SSE idle timeout (${idleMs}ms)`);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
while (true) {
|
|
61
|
+
if (options.signal?.aborted) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const { value, done } = await reader.read();
|
|
65
|
+
if (done) break;
|
|
66
|
+
|
|
67
|
+
lastChunkAt = Date.now();
|
|
68
|
+
buffer += decoder.decode(value, { stream: true });
|
|
69
|
+
|
|
70
|
+
// Split on blank line which delimits SSE events.
|
|
71
|
+
let separator = buffer.indexOf('\n\n');
|
|
72
|
+
while (separator !== -1) {
|
|
73
|
+
const rawBlock = buffer.slice(0, separator);
|
|
74
|
+
buffer = buffer.slice(separator + 2);
|
|
75
|
+
|
|
76
|
+
const raw = parseEventBlock(rawBlock);
|
|
77
|
+
const evt = map(raw);
|
|
78
|
+
if (evt) yield evt;
|
|
79
|
+
|
|
80
|
+
separator = buffer.indexOf('\n\n');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
idleCheck();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Flush any trailing partial event.
|
|
87
|
+
if (buffer.trim()) {
|
|
88
|
+
const raw = parseEventBlock(buffer);
|
|
89
|
+
const evt = map(raw);
|
|
90
|
+
if (evt) yield evt;
|
|
91
|
+
}
|
|
92
|
+
} finally {
|
|
93
|
+
try {
|
|
94
|
+
reader.releaseLock();
|
|
95
|
+
} catch {
|
|
96
|
+
/* ignore */
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function parseEventBlock(block: string): RawEvent {
|
|
102
|
+
const out: RawEvent = {};
|
|
103
|
+
const lines = block.split(/\r?\n/);
|
|
104
|
+
const dataLines: string[] = [];
|
|
105
|
+
for (const line of lines) {
|
|
106
|
+
if (!line || line.startsWith(':')) continue;
|
|
107
|
+
const colon = line.indexOf(':');
|
|
108
|
+
if (colon === -1) continue;
|
|
109
|
+
const field = line.slice(0, colon).trim();
|
|
110
|
+
const value = line.slice(colon + 1).replace(/^ /, '');
|
|
111
|
+
if (field === 'event') out.event = value;
|
|
112
|
+
else if (field === 'data') dataLines.push(value);
|
|
113
|
+
}
|
|
114
|
+
if (dataLines.length) out.data = dataLines.join('\n');
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport surface re-export. Lives in core so transport implementations
|
|
3
|
+
* never need to reach into the public types module.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type {
|
|
7
|
+
ChatTransport,
|
|
8
|
+
ChatStreamEvent,
|
|
9
|
+
CreateSessionOptions,
|
|
10
|
+
SessionInfo,
|
|
11
|
+
HistoryPage,
|
|
12
|
+
StreamOptions,
|
|
13
|
+
SendOptions,
|
|
14
|
+
} from '../../types';
|
|
15
|
+
|
|
16
|
+
export class TransportError extends Error {
|
|
17
|
+
code: string;
|
|
18
|
+
|
|
19
|
+
constructor(message: string, code = 'transport_error') {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = 'TransportError';
|
|
22
|
+
this.code = code;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
export { useChat, type UseChatConfig, type UseChatReturn } from './useChat';
|
|
4
|
+
export {
|
|
5
|
+
useChatComposer,
|
|
6
|
+
type UseChatComposerOptions,
|
|
7
|
+
type UseChatComposerReturn,
|
|
8
|
+
} from './useChatComposer';
|
|
9
|
+
export {
|
|
10
|
+
useChatScroll,
|
|
11
|
+
type UseChatScrollOptions,
|
|
12
|
+
type UseChatScrollReturn,
|
|
13
|
+
} from './useChatScroll';
|
|
14
|
+
export { useChatHistory, type UseChatHistoryOptions } from './useChatHistory';
|
|
15
|
+
export {
|
|
16
|
+
useChatLayout,
|
|
17
|
+
type UseChatLayoutConfig,
|
|
18
|
+
type UseChatLayoutReturn,
|
|
19
|
+
} from './useChatLayout';
|
|
20
|
+
export { useChatAudio } from './useChatAudio';
|
|
21
|
+
export {
|
|
22
|
+
useChatLightbox,
|
|
23
|
+
type UseChatLightboxReturn,
|
|
24
|
+
type ChatLightboxState,
|
|
25
|
+
type ChatLightboxScope,
|
|
26
|
+
} from './useChatLightbox';
|