@tangle-network/sandbox-ui 0.94.2 → 0.95.0
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/dist/{WorkflowGraph-4TQFLY3E.js → WorkflowGraph-MPNVC5FE.js} +417 -143
- package/dist/{chunk-TIEOKIMX.js → chunk-3GX5BSD4.js} +251 -71
- package/dist/{chunk-7QKAVIZX.js → chunk-O7MHUR4U.js} +111 -9
- package/dist/globals.css +21 -0
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +5 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{sdk-hooks-VtiMwKa6.d.ts → sdk-hooks-Cjt_Wq7g.d.ts} +7 -1
- package/dist/sdk-hooks.d.ts +1 -1
- package/dist/sdk-hooks.js +1 -1
- package/dist/styles.css +21 -0
- package/dist/workflows.d.ts +35 -5
- package/dist/workflows.js +2 -2
- package/package.json +2 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/hooks/use-session-stream.ts
|
|
2
|
+
import { createParser } from "eventsource-parser";
|
|
2
3
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
4
|
|
|
4
5
|
// src/hooks/wire-encoding.ts
|
|
@@ -22,6 +23,17 @@ function encodeTextForWire(text) {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
// src/hooks/use-session-stream.ts
|
|
26
|
+
var SessionStreamError = class extends Error {
|
|
27
|
+
code;
|
|
28
|
+
status;
|
|
29
|
+
constructor(code, message, status) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = "SessionStreamError";
|
|
32
|
+
this.code = code;
|
|
33
|
+
this.status = status;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var SESSION_STREAM_RECONNECT_DELAY_MS = 1e3;
|
|
25
37
|
function mapApiMessage(msg, counterRef) {
|
|
26
38
|
const created = msg.info.timestamp ? new Date(msg.info.timestamp).getTime() : Date.now();
|
|
27
39
|
const message = {
|
|
@@ -57,11 +69,39 @@ function mapApiMessage(msg, counterRef) {
|
|
|
57
69
|
});
|
|
58
70
|
return { message, parts };
|
|
59
71
|
}
|
|
72
|
+
function readErrorDetails(value) {
|
|
73
|
+
if (typeof value === "string") return { message: value };
|
|
74
|
+
if (!value || typeof value !== "object") return {};
|
|
75
|
+
const record = value;
|
|
76
|
+
const nested = readErrorDetails(record.error);
|
|
77
|
+
return {
|
|
78
|
+
code: (typeof record.code === "string" ? record.code : void 0) ?? nested.code,
|
|
79
|
+
message: (typeof record.message === "string" ? record.message : void 0) ?? nested.message
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function formatError(details, fallback) {
|
|
83
|
+
const message = details.message ?? fallback;
|
|
84
|
+
return details.code && !message.includes(details.code) ? `${message} (${details.code})` : message;
|
|
85
|
+
}
|
|
86
|
+
async function responseError(res, prefix) {
|
|
87
|
+
let details = {};
|
|
88
|
+
try {
|
|
89
|
+
details = readErrorDetails(await res.json());
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
const code = details.code ?? `HTTP_${res.status}`;
|
|
93
|
+
const fallback = `${prefix}: HTTP ${res.status}${res.statusText ? ` ${res.statusText}` : ""}`;
|
|
94
|
+
return new SessionStreamError(
|
|
95
|
+
code,
|
|
96
|
+
formatError(details, fallback),
|
|
97
|
+
res.status
|
|
98
|
+
);
|
|
99
|
+
}
|
|
60
100
|
async function fetchJson(url, token, init) {
|
|
61
101
|
const headers = { Authorization: `Bearer ${token}` };
|
|
62
102
|
if (init?.body) headers["Content-Type"] = "application/json";
|
|
63
103
|
const res = await fetch(url, { ...init, headers: { ...headers, ...init?.headers }, credentials: "include" });
|
|
64
|
-
if (!res.ok) throw
|
|
104
|
+
if (!res.ok) throw await responseError(res, "Request failed");
|
|
65
105
|
return res.json();
|
|
66
106
|
}
|
|
67
107
|
function useSessionStream({
|
|
@@ -71,19 +111,28 @@ function useSessionStream({
|
|
|
71
111
|
enabled = true
|
|
72
112
|
}) {
|
|
73
113
|
const [messages, setMessages] = useState([]);
|
|
74
|
-
const [
|
|
114
|
+
const [partsState, setPartsState] = useState({
|
|
115
|
+
values: {},
|
|
116
|
+
sourceIds: {}
|
|
117
|
+
});
|
|
118
|
+
const partMap = partsState.values;
|
|
75
119
|
const [isStreaming, setIsStreaming] = useState(false);
|
|
76
120
|
const [error, setError] = useState(null);
|
|
77
121
|
const [connected, setConnected] = useState(false);
|
|
78
122
|
const abortRef = useRef(null);
|
|
79
123
|
const reconnectTimerRef = useRef(null);
|
|
80
|
-
const
|
|
124
|
+
const activeAssistantMessageIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
125
|
+
const completedExecutionIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
126
|
+
const lastEventIdRef = useRef("");
|
|
81
127
|
const insertionCounterRef = useRef(0);
|
|
82
128
|
const echoCounterRef = useRef(0);
|
|
83
129
|
const pendingEchoesRef = useRef(/* @__PURE__ */ new Map());
|
|
84
130
|
const handleSSEEventRef = useRef(null);
|
|
85
131
|
useEffect(() => {
|
|
86
132
|
pendingEchoesRef.current.clear();
|
|
133
|
+
activeAssistantMessageIdsRef.current.clear();
|
|
134
|
+
completedExecutionIdsRef.current.clear();
|
|
135
|
+
lastEventIdRef.current = "";
|
|
87
136
|
}, [sessionId]);
|
|
88
137
|
const refetch = useCallback(async () => {
|
|
89
138
|
if (!token || !sessionId || !apiUrl) return;
|
|
@@ -98,13 +147,23 @@ function useSessionStream({
|
|
|
98
147
|
newMessages.push(message);
|
|
99
148
|
newPartMap[message.id] = parts;
|
|
100
149
|
}
|
|
101
|
-
const
|
|
150
|
+
const canonicalIds = new Set(newMessages.map((message) => message.id));
|
|
151
|
+
const echoes = [...pendingEchoesRef.current.entries()].filter(
|
|
152
|
+
([, echo]) => !echo.canonicalMessageId || !canonicalIds.has(echo.canonicalMessageId)
|
|
153
|
+
).map(([, echo]) => echo);
|
|
154
|
+
for (const [echoId, echo] of pendingEchoesRef.current) {
|
|
155
|
+
if (echo.canonicalMessageId && canonicalIds.has(echo.canonicalMessageId)) {
|
|
156
|
+
pendingEchoesRef.current.delete(echoId);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
102
159
|
setMessages([...newMessages, ...echoes.map((echo) => echo.message)]);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
160
|
+
setPartsState({
|
|
161
|
+
values: {
|
|
162
|
+
...newPartMap,
|
|
163
|
+
...Object.fromEntries(echoes.map((echo) => [echo.message.id, echo.parts]))
|
|
164
|
+
},
|
|
165
|
+
sourceIds: {}
|
|
106
166
|
});
|
|
107
|
-
streamingMsgIdRef.current = null;
|
|
108
167
|
} catch (err) {
|
|
109
168
|
const msg = err instanceof Error ? err.message : "Failed to fetch messages";
|
|
110
169
|
setError(msg);
|
|
@@ -120,59 +179,109 @@ function useSessionStream({
|
|
|
120
179
|
const controller = new AbortController();
|
|
121
180
|
abortRef.current = controller;
|
|
122
181
|
try {
|
|
123
|
-
const
|
|
182
|
+
const eventQuery = new URLSearchParams({ sessionId });
|
|
183
|
+
const activeExecutionIds = [...activeAssistantMessageIdsRef.current];
|
|
184
|
+
if (activeExecutionIds.length === 1) {
|
|
185
|
+
eventQuery.set("executionId", activeExecutionIds[0]);
|
|
186
|
+
} else if (activeExecutionIds.length > 1) {
|
|
187
|
+
eventQuery.set("replayExecutionIds", activeExecutionIds.join(","));
|
|
188
|
+
}
|
|
189
|
+
if (lastEventIdRef.current) {
|
|
190
|
+
eventQuery.set("since", lastEventIdRef.current);
|
|
191
|
+
}
|
|
192
|
+
const url = `${apiUrl}/session/events?${eventQuery.toString()}`;
|
|
124
193
|
const res = await fetch(url, {
|
|
125
194
|
headers: { Authorization: `Bearer ${token}` },
|
|
126
195
|
signal: controller.signal,
|
|
127
196
|
credentials: "include"
|
|
128
197
|
});
|
|
129
|
-
if (!res.ok) throw
|
|
198
|
+
if (!res.ok) throw await responseError(res, "Live response connection failed");
|
|
130
199
|
setConnected(true);
|
|
131
200
|
setError(null);
|
|
132
201
|
const reader = res.body?.getReader();
|
|
133
202
|
if (!reader) throw new Error("No response body");
|
|
134
203
|
const decoder = new TextDecoder();
|
|
135
|
-
let
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const frames = buffer.split("\n\n");
|
|
141
|
-
buffer = frames.pop() ?? "";
|
|
142
|
-
for (const frame of frames) {
|
|
143
|
-
if (!frame.trim()) continue;
|
|
144
|
-
let eventType = "message";
|
|
145
|
-
const dataLines = [];
|
|
146
|
-
for (const line of frame.split("\n")) {
|
|
147
|
-
if (line.startsWith("event:")) {
|
|
148
|
-
eventType = line.slice(6).trim();
|
|
149
|
-
} else if (line.startsWith("data:")) {
|
|
150
|
-
dataLines.push(line.slice(5).trim());
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (dataLines.length === 0) continue;
|
|
154
|
-
let parsed;
|
|
204
|
+
let parserFailure = null;
|
|
205
|
+
const parser = createParser({
|
|
206
|
+
onEvent(event) {
|
|
207
|
+
if (event.id) lastEventIdRef.current = event.id;
|
|
208
|
+
if (!event.data) return;
|
|
155
209
|
try {
|
|
156
|
-
parsed = JSON.parse(
|
|
210
|
+
const parsed = JSON.parse(event.data);
|
|
211
|
+
handleSSEEventRef.current?.(event.event ?? "message", parsed);
|
|
157
212
|
} catch {
|
|
158
|
-
|
|
213
|
+
parserFailure = new SessionStreamError(
|
|
214
|
+
"INVALID_SESSION_EVENT",
|
|
215
|
+
"The live response stream sent malformed data."
|
|
216
|
+
);
|
|
159
217
|
}
|
|
160
|
-
|
|
218
|
+
},
|
|
219
|
+
onError(parseError) {
|
|
220
|
+
parserFailure = new SessionStreamError(
|
|
221
|
+
"INVALID_EVENT_STREAM",
|
|
222
|
+
`The live response stream could not be parsed: ${parseError.message}`
|
|
223
|
+
);
|
|
161
224
|
}
|
|
225
|
+
});
|
|
226
|
+
while (true) {
|
|
227
|
+
const { done, value } = await reader.read();
|
|
228
|
+
if (done) break;
|
|
229
|
+
parser.feed(decoder.decode(value, { stream: true }));
|
|
230
|
+
if (parserFailure) throw parserFailure;
|
|
162
231
|
}
|
|
232
|
+
const tail = decoder.decode();
|
|
233
|
+
if (tail) parser.feed(tail);
|
|
234
|
+
parser.reset({ consume: true });
|
|
235
|
+
if (parserFailure) throw parserFailure;
|
|
236
|
+
throw new SessionStreamError(
|
|
237
|
+
"SESSION_STREAM_CLOSED",
|
|
238
|
+
"The live response stream closed before the session ended. Reconnecting\u2026"
|
|
239
|
+
);
|
|
163
240
|
} catch (err) {
|
|
164
241
|
if (err.name === "AbortError") return;
|
|
165
242
|
const msg = err instanceof Error ? err.message : "SSE connection error";
|
|
166
243
|
setError(msg);
|
|
167
244
|
setConnected(false);
|
|
168
245
|
if (!controller.signal.aborted) {
|
|
169
|
-
reconnectTimerRef.current = setTimeout(
|
|
246
|
+
reconnectTimerRef.current = setTimeout(
|
|
247
|
+
() => connectSSE(),
|
|
248
|
+
SESSION_STREAM_RECONNECT_DELAY_MS
|
|
249
|
+
);
|
|
170
250
|
}
|
|
171
251
|
}
|
|
172
252
|
}, [apiUrl, token, sessionId, enabled]);
|
|
173
253
|
const handleSSEEvent = useCallback((type, raw) => {
|
|
174
254
|
const envelope = raw?.properties;
|
|
175
255
|
const props = envelope?.info ?? envelope?.part ?? envelope ?? raw;
|
|
256
|
+
const associatePendingEcho = (executionId) => {
|
|
257
|
+
for (const echo of pendingEchoesRef.current.values()) {
|
|
258
|
+
if (!echo.executionId) {
|
|
259
|
+
echo.executionId = executionId;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
const retireExecution = (executionId) => {
|
|
265
|
+
if (!executionId) {
|
|
266
|
+
const activeExecutionIds = [...activeAssistantMessageIdsRef.current];
|
|
267
|
+
if (activeExecutionIds.length === 1) {
|
|
268
|
+
return retireExecution(activeExecutionIds[0]);
|
|
269
|
+
}
|
|
270
|
+
if (activeExecutionIds.length > 1) {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
pendingEchoesRef.current.clear();
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
completedExecutionIdsRef.current.add(executionId);
|
|
277
|
+
activeAssistantMessageIdsRef.current.delete(executionId);
|
|
278
|
+
for (const [echoId, echo] of pendingEchoesRef.current) {
|
|
279
|
+
if (echo.executionId === executionId || !echo.executionId && pendingEchoesRef.current.size === 1) {
|
|
280
|
+
pendingEchoesRef.current.delete(echoId);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return activeAssistantMessageIdsRef.current.size > 0;
|
|
284
|
+
};
|
|
176
285
|
if (type === "message.updated") {
|
|
177
286
|
const id = props.id ?? props.messageId ?? "";
|
|
178
287
|
const role = props.role ?? "assistant";
|
|
@@ -191,28 +300,51 @@ function useSessionStream({
|
|
|
191
300
|
];
|
|
192
301
|
});
|
|
193
302
|
if (role === "assistant") {
|
|
194
|
-
|
|
195
|
-
|
|
303
|
+
associatePendingEcho(id);
|
|
304
|
+
if (!completedExecutionIdsRef.current.has(id)) {
|
|
305
|
+
activeAssistantMessageIdsRef.current.add(id);
|
|
306
|
+
setIsStreaming(true);
|
|
307
|
+
}
|
|
196
308
|
}
|
|
197
309
|
} else if (type === "message.part.updated") {
|
|
198
|
-
const msgId =
|
|
310
|
+
const msgId = props.messageID ?? props.messageId ?? "";
|
|
199
311
|
if (!msgId) return;
|
|
200
312
|
const partType = props.type ?? "text";
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
313
|
+
const sourcePartId = props.id ?? `${partType}-${msgId}`;
|
|
314
|
+
associatePendingEcho(msgId);
|
|
315
|
+
if (!completedExecutionIdsRef.current.has(msgId)) {
|
|
316
|
+
activeAssistantMessageIdsRef.current.add(msgId);
|
|
317
|
+
setIsStreaming(true);
|
|
318
|
+
}
|
|
319
|
+
setMessages((prev) => {
|
|
320
|
+
if (prev.some((message) => message.id === msgId)) return prev;
|
|
321
|
+
return [
|
|
322
|
+
...prev,
|
|
323
|
+
{
|
|
324
|
+
id: msgId,
|
|
325
|
+
role: "assistant",
|
|
326
|
+
time: { created: Date.now() },
|
|
327
|
+
_insertionIndex: insertionCounterRef.current++
|
|
328
|
+
}
|
|
329
|
+
];
|
|
330
|
+
});
|
|
331
|
+
setPartsState((prev) => {
|
|
332
|
+
const existing = prev.values[msgId] ?? [];
|
|
333
|
+
const sourceIds = prev.sourceIds[msgId] ?? [];
|
|
204
334
|
const updated = [...existing];
|
|
335
|
+
const updatedSourceIds = [...sourceIds];
|
|
336
|
+
const knownIndex = sourceIds.indexOf(sourcePartId);
|
|
205
337
|
if (partType === "text") {
|
|
206
338
|
const text = props.text ?? props.content ?? "";
|
|
207
|
-
const idx = updated.findIndex((p) => p.type === "text");
|
|
208
339
|
const textPart = { type: "text", text };
|
|
209
|
-
if (
|
|
210
|
-
updated[
|
|
340
|
+
if (knownIndex >= 0) {
|
|
341
|
+
updated[knownIndex] = textPart;
|
|
211
342
|
} else {
|
|
343
|
+
updatedSourceIds.push(sourcePartId);
|
|
212
344
|
updated.push(textPart);
|
|
213
345
|
}
|
|
214
346
|
} else if (partType === "tool") {
|
|
215
|
-
const toolId = props.id ?? props.toolId ??
|
|
347
|
+
const toolId = props.id ?? props.toolId ?? sourcePartId;
|
|
216
348
|
const toolName = props.tool ?? props.name ?? "unknown";
|
|
217
349
|
const state = props.state ?? { status: "running" };
|
|
218
350
|
const toolPart = {
|
|
@@ -228,41 +360,69 @@ function useSessionStream({
|
|
|
228
360
|
time: state.time
|
|
229
361
|
}
|
|
230
362
|
};
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
updated[idx] = toolPart;
|
|
363
|
+
if (knownIndex >= 0) {
|
|
364
|
+
updated[knownIndex] = toolPart;
|
|
234
365
|
} else {
|
|
366
|
+
updatedSourceIds.push(sourcePartId);
|
|
235
367
|
updated.push(toolPart);
|
|
236
368
|
}
|
|
237
369
|
} else if (partType === "reasoning") {
|
|
238
370
|
const text = props.text ?? "";
|
|
239
|
-
const idx = updated.findIndex((p) => p.type === "reasoning");
|
|
240
371
|
const reasoningPart = { type: "reasoning", text };
|
|
241
|
-
if (
|
|
242
|
-
updated[
|
|
372
|
+
if (knownIndex >= 0) {
|
|
373
|
+
updated[knownIndex] = reasoningPart;
|
|
243
374
|
} else {
|
|
375
|
+
updatedSourceIds.push(sourcePartId);
|
|
244
376
|
updated.push(reasoningPart);
|
|
245
377
|
}
|
|
378
|
+
} else {
|
|
379
|
+
return prev;
|
|
246
380
|
}
|
|
247
|
-
return {
|
|
381
|
+
return {
|
|
382
|
+
values: { ...prev.values, [msgId]: updated },
|
|
383
|
+
sourceIds: { ...prev.sourceIds, [msgId]: updatedSourceIds }
|
|
384
|
+
};
|
|
248
385
|
});
|
|
249
386
|
} else if (type === "session.idle") {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
refetch();
|
|
387
|
+
const executionId = props.executionId ?? props.executionID ?? "";
|
|
388
|
+
const stillStreaming = retireExecution(executionId);
|
|
389
|
+
setIsStreaming(stillStreaming);
|
|
390
|
+
if (!stillStreaming) refetch();
|
|
254
391
|
} else if (type === "session.error") {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
setError(
|
|
259
|
-
|
|
260
|
-
refetch();
|
|
392
|
+
const executionId = props.executionId ?? props.executionID ?? "";
|
|
393
|
+
const stillStreaming = retireExecution(executionId);
|
|
394
|
+
setIsStreaming(stillStreaming);
|
|
395
|
+
setError(formatError(readErrorDetails(props), "Agent response failed"));
|
|
396
|
+
if (!stillStreaming) refetch();
|
|
261
397
|
}
|
|
262
398
|
}, [refetch]);
|
|
263
399
|
handleSSEEventRef.current = handleSSEEvent;
|
|
264
400
|
const send = useCallback(async (text, options) => {
|
|
265
|
-
if (!token || !sessionId || !apiUrl)
|
|
401
|
+
if (!token || !sessionId || !apiUrl || !enabled) {
|
|
402
|
+
const admissionError = new SessionStreamError(
|
|
403
|
+
"CHAT_NOT_READY",
|
|
404
|
+
"Chat is not ready yet. Wait for the session connection and try again."
|
|
405
|
+
);
|
|
406
|
+
setError(admissionError.message);
|
|
407
|
+
throw admissionError;
|
|
408
|
+
}
|
|
409
|
+
if (!connected) {
|
|
410
|
+
const admissionError = new SessionStreamError(
|
|
411
|
+
"CHAT_DISCONNECTED",
|
|
412
|
+
"Chat is reconnecting. Your message was not sent."
|
|
413
|
+
);
|
|
414
|
+
setError(admissionError.message);
|
|
415
|
+
throw admissionError;
|
|
416
|
+
}
|
|
417
|
+
if (pendingEchoesRef.current.size > 0 || activeAssistantMessageIdsRef.current.size > 0) {
|
|
418
|
+
const admissionError = new SessionStreamError(
|
|
419
|
+
"CHAT_BUSY",
|
|
420
|
+
"The agent is still responding. Wait for this turn to finish before sending another message."
|
|
421
|
+
);
|
|
422
|
+
setError(admissionError.message);
|
|
423
|
+
throw admissionError;
|
|
424
|
+
}
|
|
425
|
+
setError(null);
|
|
266
426
|
const echoId = `local-echo-${echoCounterRef.current++}`;
|
|
267
427
|
const echo = {
|
|
268
428
|
message: {
|
|
@@ -275,11 +435,14 @@ function useSessionStream({
|
|
|
275
435
|
};
|
|
276
436
|
pendingEchoesRef.current.set(echoId, echo);
|
|
277
437
|
setMessages((prev) => [...prev, echo.message]);
|
|
278
|
-
|
|
438
|
+
setPartsState((prev) => ({
|
|
439
|
+
values: { ...prev.values, [echoId]: echo.parts },
|
|
440
|
+
sourceIds: prev.sourceIds
|
|
441
|
+
}));
|
|
279
442
|
setIsStreaming(true);
|
|
280
443
|
try {
|
|
281
444
|
const url = `${apiUrl}/session/sessions/${encodeURIComponent(sessionId)}/messages`;
|
|
282
|
-
await fetchJson(url, token, {
|
|
445
|
+
const response = await fetchJson(url, token, {
|
|
283
446
|
method: "POST",
|
|
284
447
|
body: JSON.stringify({
|
|
285
448
|
// The sidecar base64-decodes every text part at the route boundary;
|
|
@@ -291,22 +454,37 @@ function useSessionStream({
|
|
|
291
454
|
...options?.reasoningEffort ? { reasoningEffort: options.reasoningEffort } : {}
|
|
292
455
|
})
|
|
293
456
|
});
|
|
457
|
+
if (response.userMessageId) {
|
|
458
|
+
const pending = pendingEchoesRef.current.get(echoId);
|
|
459
|
+
if (pending) pending.canonicalMessageId = response.userMessageId;
|
|
460
|
+
}
|
|
461
|
+
const executionId = response.info?.id;
|
|
462
|
+
if (executionId) {
|
|
463
|
+
const pending = pendingEchoesRef.current.get(echoId);
|
|
464
|
+
if (pending) pending.executionId = executionId;
|
|
465
|
+
if (!completedExecutionIdsRef.current.has(executionId)) {
|
|
466
|
+
activeAssistantMessageIdsRef.current.add(executionId);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
294
469
|
} catch (err) {
|
|
295
470
|
pendingEchoesRef.current.delete(echoId);
|
|
296
471
|
setMessages((prev) => prev.filter((message) => message.id !== echoId));
|
|
297
|
-
|
|
298
|
-
if (!(echoId in prev)) return prev;
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
472
|
+
setPartsState((prev) => {
|
|
473
|
+
if (!(echoId in prev.values)) return prev;
|
|
474
|
+
const values = { ...prev.values };
|
|
475
|
+
const sourceIds = { ...prev.sourceIds };
|
|
476
|
+
delete values[echoId];
|
|
477
|
+
delete sourceIds[echoId];
|
|
478
|
+
return { values, sourceIds };
|
|
302
479
|
});
|
|
303
|
-
if (pendingEchoesRef.current.size === 0 &&
|
|
480
|
+
if (pendingEchoesRef.current.size === 0 && activeAssistantMessageIdsRef.current.size === 0) {
|
|
304
481
|
setIsStreaming(false);
|
|
305
482
|
}
|
|
306
483
|
const msg = err instanceof Error ? err.message : "Failed to send message";
|
|
307
484
|
setError(msg);
|
|
485
|
+
throw err;
|
|
308
486
|
}
|
|
309
|
-
}, [apiUrl, token, sessionId]);
|
|
487
|
+
}, [apiUrl, connected, enabled, token, sessionId]);
|
|
310
488
|
const abort = useCallback(async () => {
|
|
311
489
|
if (!token || !sessionId || !apiUrl) return;
|
|
312
490
|
try {
|
|
@@ -440,6 +618,8 @@ function useSidecarAuth({ resourceId, apiUrl, signMessage }) {
|
|
|
440
618
|
}
|
|
441
619
|
|
|
442
620
|
export {
|
|
621
|
+
SessionStreamError,
|
|
622
|
+
SESSION_STREAM_RECONNECT_DELAY_MS,
|
|
443
623
|
useSessionStream,
|
|
444
624
|
useSidecarAuth
|
|
445
625
|
};
|
|
@@ -237,9 +237,29 @@ var EDGE_LABEL_LANE = 180;
|
|
|
237
237
|
var CARD_CHROME = 20;
|
|
238
238
|
var HEADER_ROW = 34;
|
|
239
239
|
var DESCRIPTION_ROW = 40;
|
|
240
|
-
var
|
|
241
|
-
var
|
|
242
|
-
var
|
|
240
|
+
var AGENT_BODY_ROWS = 5;
|
|
241
|
+
var ACTION_OUTPUT_ROWS = 3;
|
|
242
|
+
var STRUCTURAL_OUTPUT_ROWS = 1;
|
|
243
|
+
var WELL_PROSE_LINE = 15.125;
|
|
244
|
+
var WELL_JSON_LINE = 14.4375;
|
|
245
|
+
var WELL_JSON_GAP = 2;
|
|
246
|
+
var WELL_CHROME = 39.5;
|
|
247
|
+
function wellRow(rows) {
|
|
248
|
+
const body = Math.max(
|
|
249
|
+
rows * WELL_PROSE_LINE,
|
|
250
|
+
rows * WELL_JSON_LINE + (rows - 1) * WELL_JSON_GAP
|
|
251
|
+
);
|
|
252
|
+
return Math.ceil(WELL_CHROME + body) + 3;
|
|
253
|
+
}
|
|
254
|
+
var SLIM_HEADER_ROW = 22;
|
|
255
|
+
var BODY_LINE = 15.125;
|
|
256
|
+
var AGENT_BODY_ROW = Math.ceil(8 + AGENT_BODY_ROWS * BODY_LINE);
|
|
257
|
+
var CHIP_SLOT_ROW = 22;
|
|
258
|
+
var DECISION_HEADER_ROW = 45;
|
|
259
|
+
var STRUCTURAL_KINDS = /* @__PURE__ */ new Set(["parallel", "foreach"]);
|
|
260
|
+
function isStructuralKind(kind) {
|
|
261
|
+
return kind !== void 0 && STRUCTURAL_KINDS.has(kind);
|
|
262
|
+
}
|
|
243
263
|
var OUTPUT_PREVIEW_CHARS = 240;
|
|
244
264
|
var FOOTER_ROW = 28;
|
|
245
265
|
var COMPACT_TILE = 76;
|
|
@@ -258,10 +278,19 @@ var COMPACT_NODE_SIZE_TB = {
|
|
|
258
278
|
height: COMPACT_TILE
|
|
259
279
|
};
|
|
260
280
|
function nodeHeight(data, withRunState) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
281
|
+
if (!withRunState || data.tone === "trigger") {
|
|
282
|
+
return CARD_CHROME + HEADER_ROW + (data.description ? DESCRIPTION_ROW : 0);
|
|
283
|
+
}
|
|
284
|
+
if (data.kind === "decision") {
|
|
285
|
+
return CARD_CHROME + DECISION_HEADER_ROW + DESCRIPTION_ROW + FOOTER_ROW;
|
|
286
|
+
}
|
|
287
|
+
if (isStructuralKind(data.kind)) {
|
|
288
|
+
return CARD_CHROME + HEADER_ROW + CHIP_SLOT_ROW + FOOTER_ROW;
|
|
289
|
+
}
|
|
290
|
+
if (data.kind === "agent.run") {
|
|
291
|
+
return CARD_CHROME + SLIM_HEADER_ROW + AGENT_BODY_ROW + FOOTER_ROW;
|
|
292
|
+
}
|
|
293
|
+
return CARD_CHROME + HEADER_ROW + wellRow(ACTION_OUTPUT_ROWS) + FOOTER_ROW;
|
|
265
294
|
}
|
|
266
295
|
function geometry(compact, runMode, direction) {
|
|
267
296
|
if (compact) {
|
|
@@ -552,6 +581,75 @@ function describeTrigger(on) {
|
|
|
552
581
|
asRecord(kind ? rec[kind] : void 0)
|
|
553
582
|
);
|
|
554
583
|
}
|
|
584
|
+
var WRAP_TARGET_ASPECT = 742 / 480;
|
|
585
|
+
var WRAP_ASPECT_TRIGGER = 4.2;
|
|
586
|
+
var WRAP_ROW_GAP = 28;
|
|
587
|
+
function foldColumns(count, cellW, cellH, colSep, rowSep) {
|
|
588
|
+
let best = count;
|
|
589
|
+
let bestError = Number.POSITIVE_INFINITY;
|
|
590
|
+
for (let cols = 1; cols <= count; cols++) {
|
|
591
|
+
const rows = Math.ceil(count / cols);
|
|
592
|
+
const width = cols * cellW + (cols - 1) * colSep;
|
|
593
|
+
const height = rows * cellH + (rows - 1) * rowSep;
|
|
594
|
+
const error = Math.abs(Math.log(width / height / WRAP_TARGET_ASPECT));
|
|
595
|
+
if (error < bestError) {
|
|
596
|
+
bestError = error;
|
|
597
|
+
best = cols;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return best;
|
|
601
|
+
}
|
|
602
|
+
function layoutSerpentine(nodes, edges, colSep, laneSep) {
|
|
603
|
+
const rowSep = Math.max(laneSep, WRAP_ROW_GAP);
|
|
604
|
+
const byRank = /* @__PURE__ */ new Map();
|
|
605
|
+
for (const n of nodes) {
|
|
606
|
+
const arr = byRank.get(n.rank);
|
|
607
|
+
if (arr) arr.push(n);
|
|
608
|
+
else byRank.set(n.rank, [n]);
|
|
609
|
+
}
|
|
610
|
+
const ranks = [...byRank.keys()].sort((a, b) => a - b);
|
|
611
|
+
if (ranks.some((r) => byRank.get(r).length !== 1)) return null;
|
|
612
|
+
const rankOf = new Map(nodes.map((n) => [n.id, n.rank]));
|
|
613
|
+
const joinsNextLayer = (e) => {
|
|
614
|
+
const from = rankOf.get(e.source);
|
|
615
|
+
const to = rankOf.get(e.target);
|
|
616
|
+
return from !== void 0 && to !== void 0 && to === from + 1;
|
|
617
|
+
};
|
|
618
|
+
if (!edges.every(joinsNextLayer)) return null;
|
|
619
|
+
const ordered = ranks.map((r) => byRank.get(r)[0]);
|
|
620
|
+
const count = ordered.length;
|
|
621
|
+
const cellW = Math.max(...ordered.map((n) => n.width));
|
|
622
|
+
const cellH = Math.max(...ordered.map((n) => n.height));
|
|
623
|
+
const lineAspect = (count * cellW + (count - 1) * colSep) / cellH;
|
|
624
|
+
if (lineAspect <= WRAP_TARGET_ASPECT * WRAP_ASPECT_TRIGGER) return null;
|
|
625
|
+
const cols = foldColumns(count, cellW, cellH, colSep, rowSep);
|
|
626
|
+
if (cols >= count) return null;
|
|
627
|
+
return ordered.map((node, i) => {
|
|
628
|
+
const row = Math.floor(i / cols);
|
|
629
|
+
const indexInRow = i % cols;
|
|
630
|
+
const leftToRight = row % 2 === 0;
|
|
631
|
+
const col = leftToRight ? indexInRow : cols - 1 - indexInRow;
|
|
632
|
+
return {
|
|
633
|
+
id: node.id,
|
|
634
|
+
position: {
|
|
635
|
+
x: col * (cellW + colSep) + (cellW - node.width) / 2,
|
|
636
|
+
y: row * (cellH + rowSep) + (cellH - node.height) / 2
|
|
637
|
+
},
|
|
638
|
+
width: node.width,
|
|
639
|
+
height: node.height,
|
|
640
|
+
data: node.data,
|
|
641
|
+
// Every node leaves the way its row travels and is entered from behind —
|
|
642
|
+
// including the one that turns the corner. Mirroring puts the last cell
|
|
643
|
+
// of a row directly above the first cell of the next, so a turn is
|
|
644
|
+
// source-right into target-right (or left into left): the renderer draws
|
|
645
|
+
// it as a bracket in the margin beside the column. Routing it through the
|
|
646
|
+
// BOTTOM instead would have to start below the name that sits under the
|
|
647
|
+
// tile, leaving the arrow floating clear of the node it comes from.
|
|
648
|
+
targetSide: leftToRight ? "left" : "right",
|
|
649
|
+
sourceSide: leftToRight ? "right" : "left"
|
|
650
|
+
};
|
|
651
|
+
});
|
|
652
|
+
}
|
|
555
653
|
function layoutLayers(nodes, direction, rankSep, crossSep) {
|
|
556
654
|
const isLR = direction === "LR";
|
|
557
655
|
const mainSize = (n) => isLR ? n.width : n.height;
|
|
@@ -778,8 +876,9 @@ function buildWorkflowGraph(yaml, options) {
|
|
|
778
876
|
(e) => e.whenLabel !== void 0 || e.backEdge === true
|
|
779
877
|
);
|
|
780
878
|
const rankSep = labelled ? Math.max(geo.rankSep, EDGE_LABEL_LANE) : geo.rankSep;
|
|
879
|
+
const folded = options?.wrap && direction === "LR" && !labelled ? layoutSerpentine(logical, edges, rankSep, geo.crossSep) : null;
|
|
781
880
|
return {
|
|
782
|
-
nodes: layoutLayers(logical, direction, rankSep, geo.crossSep),
|
|
881
|
+
nodes: folded ?? layoutLayers(logical, direction, rankSep, geo.crossSep),
|
|
783
882
|
edges,
|
|
784
883
|
error: null
|
|
785
884
|
};
|
|
@@ -797,7 +896,10 @@ export {
|
|
|
797
896
|
triggerNodeIndex,
|
|
798
897
|
actionNodeId,
|
|
799
898
|
branchNodeId,
|
|
800
|
-
|
|
899
|
+
AGENT_BODY_ROWS,
|
|
900
|
+
ACTION_OUTPUT_ROWS,
|
|
901
|
+
STRUCTURAL_OUTPUT_ROWS,
|
|
902
|
+
isStructuralKind,
|
|
801
903
|
OUTPUT_PREVIEW_CHARS,
|
|
802
904
|
COMPACT_TILE,
|
|
803
905
|
COMPACT_NODE_SIZE,
|
package/dist/globals.css
CHANGED
|
@@ -643,6 +643,24 @@
|
|
|
643
643
|
-webkit-box-orient: vertical;
|
|
644
644
|
-webkit-line-clamp: 3;
|
|
645
645
|
}
|
|
646
|
+
.line-clamp-4 {
|
|
647
|
+
overflow: hidden;
|
|
648
|
+
display: -webkit-box;
|
|
649
|
+
-webkit-box-orient: vertical;
|
|
650
|
+
-webkit-line-clamp: 4;
|
|
651
|
+
}
|
|
652
|
+
.line-clamp-5 {
|
|
653
|
+
overflow: hidden;
|
|
654
|
+
display: -webkit-box;
|
|
655
|
+
-webkit-box-orient: vertical;
|
|
656
|
+
-webkit-line-clamp: 5;
|
|
657
|
+
}
|
|
658
|
+
.line-clamp-6 {
|
|
659
|
+
overflow: hidden;
|
|
660
|
+
display: -webkit-box;
|
|
661
|
+
-webkit-box-orient: vertical;
|
|
662
|
+
-webkit-line-clamp: 6;
|
|
663
|
+
}
|
|
646
664
|
.block {
|
|
647
665
|
display: block;
|
|
648
666
|
}
|
|
@@ -805,6 +823,9 @@
|
|
|
805
823
|
.h-\[28rem\] {
|
|
806
824
|
height: 28rem;
|
|
807
825
|
}
|
|
826
|
+
.h-\[30rem\] {
|
|
827
|
+
height: 30rem;
|
|
828
|
+
}
|
|
808
829
|
.h-\[32rem\] {
|
|
809
830
|
height: 32rem;
|
|
810
831
|
}
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { AgentStreamEvent, AppendUserMessageOptions, ApplySdkEventOptions, AuthUser, AutomationStreamEvent, BeginAssistantMessageOptions, BotStreamEvent, CompleteAssistantMessageOptions, ConnectionState, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, TaskStreamEvent, TerminalStreamEvent, UseAuthOptions, UseAuthResult, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, createAuthFetcher, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useLiveTime, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useToolCallStream } from '@tangle-network/ui/hooks';
|
|
2
2
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
3
3
|
import { Session } from './types.js';
|
|
4
|
-
export { e as SendMessageOptions, S as SessionInfo, a as SidecarAuth, U as UseSessionStreamOptions, b as UseSessionStreamResult, c as UseSidecarAuthOptions, u as useSessionStream, d as useSidecarAuth } from './sdk-hooks-
|
|
4
|
+
export { e as SESSION_STREAM_RECONNECT_DELAY_MS, f as SendMessageOptions, S as SessionInfo, g as SessionStreamError, a as SidecarAuth, U as UseSessionStreamOptions, b as UseSessionStreamResult, c as UseSidecarAuthOptions, u as useSessionStream, d as useSidecarAuth } from './sdk-hooks-Cjt_Wq7g.js';
|
|
5
5
|
import '@tangle-network/ui/types';
|
|
6
6
|
import '@tangle-network/ui/stores';
|
|
7
7
|
import '@tangle-network/ui/sdk-hooks';
|