@nextclaw/ui 0.9.10 → 0.9.11
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/CHANGELOG.md +6 -0
- package/dist/assets/{ChannelsList-BgJbR6E9.js → ChannelsList-Brc1qLSU.js} +1 -1
- package/dist/assets/{ChatPage-Bv9UJPse.js → ChatPage-DmGI776q.js} +1 -1
- package/dist/assets/{DocBrowser-Dw9BGO1m.js → DocBrowser-xLVf1p4L.js} +1 -1
- package/dist/assets/{LogoBadge-CLc2B6st.js → LogoBadge-CcTyimdr.js} +1 -1
- package/dist/assets/{MarketplacePage-ChqCNL7k.js → MarketplacePage-Bk-qXxyh.js} +1 -1
- package/dist/assets/{McpMarketplacePage-B3PF-7ED.js → McpMarketplacePage-gFqAYekc.js} +1 -1
- package/dist/assets/{ModelConfig-Dqz_NOow.js → ModelConfig-DnKNTuw6.js} +1 -1
- package/dist/assets/{ProvidersList-D2WaZShJ.js → ProvidersList-Cjr8EFu_.js} +1 -1
- package/dist/assets/{RemoteAccessPage-D_l9irp4.js → RemoteAccessPage-Rzi5a6Gc.js} +1 -1
- package/dist/assets/{RuntimeConfig-TDxQLuGy.js → RuntimeConfig-CttN--Tv.js} +1 -1
- package/dist/assets/{SearchConfig-gba64nGn.js → SearchConfig-D-GzinsL.js} +1 -1
- package/dist/assets/{SecretsConfig-DpL8wgly.js → SecretsConfig-BvqQq4Ds.js} +1 -1
- package/dist/assets/{SessionsConfig-CAODVTNW.js → SessionsConfig-DbtnLmI6.js} +1 -1
- package/dist/assets/{chat-message-CSG50nNb.js → chat-message-DYQjL1tD.js} +1 -1
- package/dist/assets/{index-DaEflNCE.js → index-ClLy_7T2.js} +5 -5
- package/dist/assets/{label-3T28q3PJ.js → label-DBSKOMGE.js} +1 -1
- package/dist/assets/{page-layout-BrXOQeua.js → page-layout-B5th9UzR.js} +1 -1
- package/dist/assets/{popover-BrBJjElY.js → popover-BEIWRoeP.js} +1 -1
- package/dist/assets/{security-config-oGAhN4Zf.js → security-config-D72JskP5.js} +1 -1
- package/dist/assets/{skeleton-CIPQUKo2.js → skeleton-B_Pn9x0i.js} +1 -1
- package/dist/assets/{status-dot-QL3hmT1d.js → status-dot-CU5ZpOn1.js} +1 -1
- package/dist/assets/{switch-Dbt2kUg2.js → switch-BdaXEtXk.js} +1 -1
- package/dist/assets/{tabs-custom-y5hdkzXk.js → tabs-custom-BVhSoteN.js} +1 -1
- package/dist/assets/{useConfirmDialog-B4zwBVbl.js → useConfirmDialog-Dugi9V-Z.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +4 -4
- package/src/api/config.ts +1 -4
- package/src/hooks/use-realtime-query-bridge.ts +77 -71
- package/src/transport/local.transport.ts +5 -123
- package/src/transport/remote.transport.ts +39 -74
- package/src/transport/sse-stream.ts +114 -0
- package/src/transport/transport-websocket-url.ts +24 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { StreamEvent } from './transport.types';
|
|
2
|
+
|
|
3
|
+
type SseErrorPayload = { message?: string } | string | undefined;
|
|
4
|
+
|
|
5
|
+
function parseSseFrame(frame: string): StreamEvent | null {
|
|
6
|
+
const lines = frame.split('\n');
|
|
7
|
+
let name = '';
|
|
8
|
+
const dataLines: string[] = [];
|
|
9
|
+
for (const raw of lines) {
|
|
10
|
+
const line = raw.trimEnd();
|
|
11
|
+
if (!line || line.startsWith(':')) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (line.startsWith('event:')) {
|
|
15
|
+
name = line.slice(6).trim();
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (line.startsWith('data:')) {
|
|
19
|
+
dataLines.push(line.slice(5).trimStart());
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (!name) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let payload: unknown = undefined;
|
|
27
|
+
const data = dataLines.join('\n');
|
|
28
|
+
if (data) {
|
|
29
|
+
try {
|
|
30
|
+
payload = JSON.parse(data);
|
|
31
|
+
} catch {
|
|
32
|
+
payload = data;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { name, payload };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readSseErrorMessage(payload: SseErrorPayload, fallback: string): string {
|
|
40
|
+
return typeof payload === 'string'
|
|
41
|
+
? payload
|
|
42
|
+
: payload?.message ?? fallback;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function processSseFrame(
|
|
46
|
+
rawFrame: string,
|
|
47
|
+
onEvent: (event: StreamEvent) => void,
|
|
48
|
+
setFinalResult: (value: unknown) => void
|
|
49
|
+
): void {
|
|
50
|
+
const frame = parseSseFrame(rawFrame);
|
|
51
|
+
if (!frame) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (frame.name === 'final') {
|
|
55
|
+
setFinalResult(frame.payload);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (frame.name === 'error') {
|
|
59
|
+
throw new Error(readSseErrorMessage(frame.payload as SseErrorPayload, 'chat stream failed'));
|
|
60
|
+
}
|
|
61
|
+
onEvent(frame);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function flushBufferedFrames(
|
|
65
|
+
bufferState: { value: string },
|
|
66
|
+
onEvent: (event: StreamEvent) => void,
|
|
67
|
+
setFinalResult: (value: unknown) => void
|
|
68
|
+
): void {
|
|
69
|
+
let boundary = bufferState.value.indexOf('\n\n');
|
|
70
|
+
while (boundary !== -1) {
|
|
71
|
+
processSseFrame(bufferState.value.slice(0, boundary), onEvent, setFinalResult);
|
|
72
|
+
bufferState.value = bufferState.value.slice(boundary + 2);
|
|
73
|
+
boundary = bufferState.value.indexOf('\n\n');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function readSseStreamResult<TFinal>(
|
|
78
|
+
response: Response,
|
|
79
|
+
onEvent: (event: StreamEvent) => void
|
|
80
|
+
): Promise<TFinal> {
|
|
81
|
+
const reader = response.body?.getReader();
|
|
82
|
+
if (!reader) {
|
|
83
|
+
throw new Error('SSE response body unavailable');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const decoder = new TextDecoder();
|
|
87
|
+
const bufferState = { value: '' };
|
|
88
|
+
let finalResult: unknown = undefined;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
while (true) {
|
|
92
|
+
const { value, done } = await reader.read();
|
|
93
|
+
if (done) {
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
bufferState.value += decoder.decode(value, { stream: true });
|
|
97
|
+
flushBufferedFrames(bufferState, onEvent, (nextValue) => {
|
|
98
|
+
finalResult = nextValue;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (bufferState.value.trim()) {
|
|
102
|
+
processSseFrame(bufferState.value, onEvent, (nextValue) => {
|
|
103
|
+
finalResult = nextValue;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
} finally {
|
|
107
|
+
reader.releaseLock();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (finalResult === undefined) {
|
|
111
|
+
throw new Error('stream ended without final event');
|
|
112
|
+
}
|
|
113
|
+
return finalResult as TFinal;
|
|
114
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function resolveTransportWebSocketUrl(base: string, path: string): string {
|
|
2
|
+
const normalizedBase = base.replace(/\/$/, '');
|
|
3
|
+
try {
|
|
4
|
+
const resolved = new URL(normalizedBase, window.location.origin);
|
|
5
|
+
const protocol =
|
|
6
|
+
resolved.protocol === 'https:'
|
|
7
|
+
? 'wss:'
|
|
8
|
+
: resolved.protocol === 'http:'
|
|
9
|
+
? 'ws:'
|
|
10
|
+
: resolved.protocol;
|
|
11
|
+
return `${protocol}//${resolved.host}${path}`;
|
|
12
|
+
} catch {
|
|
13
|
+
if (normalizedBase.startsWith('wss://') || normalizedBase.startsWith('ws://')) {
|
|
14
|
+
return `${normalizedBase}${path}`;
|
|
15
|
+
}
|
|
16
|
+
if (normalizedBase.startsWith('https://')) {
|
|
17
|
+
return `${normalizedBase.replace(/^https:/, 'wss:')}${path}`;
|
|
18
|
+
}
|
|
19
|
+
if (normalizedBase.startsWith('http://')) {
|
|
20
|
+
return `${normalizedBase.replace(/^http:/, 'ws:')}${path}`;
|
|
21
|
+
}
|
|
22
|
+
return `${normalizedBase}${path}`;
|
|
23
|
+
}
|
|
24
|
+
}
|