@tangle-network/sandbox-ui 0.3.12 → 0.3.13
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 +7 -1
- package/dist/auth.js +2 -3
- package/dist/{chunk-QMKWQF6F.js → chunk-DJEZKF5A.js} +3 -2
- package/dist/chunk-DLCFZDGX.js +182 -0
- package/dist/{chunk-FOQTE67I.js → chunk-FJLS7PNT.js} +9 -4
- package/dist/chunk-HXEA7L2T.js +1401 -0
- package/dist/{chunk-IAIJUFM6.js → chunk-HYLTXGOI.js} +1 -1
- package/dist/{chunk-MCGKDCOR.js → chunk-IW2JZCOC.js} +55 -14
- package/dist/{chunk-PCTEG6HR.js → chunk-OHMO7NUX.js} +2 -4
- package/dist/{chunk-DMYYQXPN.js → chunk-SMBF6HB5.js} +646 -465
- package/dist/dashboard.d.ts +1 -1
- package/dist/dashboard.js +40 -6
- package/dist/{document-editor-pane-AVKKXSLG.js → document-editor-pane-5TN2VWGZ.js} +1 -1
- package/dist/{document-editor-pane-Xnl8SmA7.d.ts → document-editor-pane-A70-EhdQ.d.ts} +1 -1
- package/dist/editor.d.ts +2 -2
- package/dist/editor.js +1 -1
- package/dist/files.d.ts +1 -1
- package/dist/files.js +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +2 -2
- package/dist/index-D7_ZDkwB.d.ts +375 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +74 -16
- package/dist/pages.d.ts +12 -2
- package/dist/pages.js +60 -5
- package/dist/primitives.js +4 -6
- package/dist/sdk-hooks.js +1 -1
- package/dist/terminal.d.ts +2 -2
- package/dist/terminal.js +9 -39
- package/dist/{use-pty-session-DeZSxOCN.d.ts → use-pty-session-COzVkhtc.d.ts} +1 -1
- package/dist/workspace.d.ts +3 -1
- package/dist/workspace.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-4HT5J6CE.js +0 -11001
- package/dist/chunk-B26TQ7SA.js +0 -47
- package/dist/chunk-GRYHFH5O.js +0 -110
- package/dist/index-BJIPTCKk.d.ts +0 -264
package/README.md
CHANGED
|
@@ -222,6 +222,12 @@ For that, compose directly from:
|
|
|
222
222
|
|
|
223
223
|
Retheming is absolutely supported, but the documentation was thinner than it should be. The token layer is strong; the higher-level surfaces are themeable, but more opinionated. For a radically different product look, prefer keeping the token contract and wrapping the higher-level workbench/chat surfaces rather than fighting every internal class.
|
|
224
224
|
|
|
225
|
+
## Docs
|
|
226
|
+
|
|
227
|
+
| Guide | Description |
|
|
228
|
+
|-------|-------------|
|
|
229
|
+
| [Sidebar](./docs/sidebar.md) | Composable Rail + Panel sidebar system (architecture, components, full API) |
|
|
230
|
+
|
|
225
231
|
## Subpath Exports
|
|
226
232
|
|
|
227
233
|
| Subpath | Description |
|
|
@@ -232,7 +238,7 @@ Retheming is absolutely supported, but the documentation was thinner than it sho
|
|
|
232
238
|
| `/workspace` | SandboxWorkbench, WorkspaceLayout, DirectoryPane, RuntimePane, StatusBar |
|
|
233
239
|
| `/openui` | OpenUIArtifactRenderer and schema types for structured artifact rendering |
|
|
234
240
|
| `/files` | FileTree, FilePreview, FileTabs, FileArtifactPane |
|
|
235
|
-
| `/dashboard` | DashboardLayout, BillingDashboard, UsageChart, ProfileSelector |
|
|
241
|
+
| `/dashboard` | [Sidebar](./docs/sidebar.md), DashboardLayout, BillingDashboard, UsageChart, ProfileSelector |
|
|
236
242
|
| `/editor` | TipTap collaborative editor (requires optional peers) |
|
|
237
243
|
| `/terminal` | xterm.js terminal view (requires optional peers) |
|
|
238
244
|
| `/markdown` | Markdown renderer with GFM, code blocks, copy button |
|
package/dist/auth.js
CHANGED
|
@@ -3,9 +3,8 @@ import {
|
|
|
3
3
|
GitHubLoginButton,
|
|
4
4
|
LoginLayout,
|
|
5
5
|
UserMenu
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-MCGKDCOR.js";
|
|
6
|
+
} from "./chunk-OHMO7NUX.js";
|
|
7
|
+
import "./chunk-IW2JZCOC.js";
|
|
9
8
|
import "./chunk-HWLX5NME.js";
|
|
10
9
|
import "./chunk-RQHJBTEU.js";
|
|
11
10
|
export {
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
FileArtifactPane,
|
|
19
19
|
FileTree,
|
|
20
20
|
filterFileTree
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-HYLTXGOI.js";
|
|
22
22
|
import {
|
|
23
23
|
ArtifactPane
|
|
24
24
|
} from "./chunk-W4LM3QYZ.js";
|
|
@@ -712,6 +712,7 @@ function RuntimePane({
|
|
|
712
712
|
subtitle = "Session state, execution output, and inspection surfaces",
|
|
713
713
|
statusBanner,
|
|
714
714
|
statusBar,
|
|
715
|
+
content,
|
|
715
716
|
terminal,
|
|
716
717
|
audit,
|
|
717
718
|
inspector,
|
|
@@ -733,7 +734,7 @@ function RuntimePane({
|
|
|
733
734
|
] }),
|
|
734
735
|
statusBanner && /* @__PURE__ */ jsx6(StatusBanner, { ...statusBanner }),
|
|
735
736
|
statusBar && /* @__PURE__ */ jsx6(StatusBar, { ...statusBar }),
|
|
736
|
-
/* @__PURE__ */ jsxs6("div", { className: "grid min-h-0 flex-1 gap-px bg-[var(--border-subtle)] lg:grid-cols-[minmax(0,1.35fr)_minmax(20rem,0.9fr)]", children: [
|
|
737
|
+
content ? /* @__PURE__ */ jsx6("div", { className: "min-h-0 flex-1 overflow-hidden", children: content }) : /* @__PURE__ */ jsxs6("div", { className: "grid min-h-0 flex-1 gap-px bg-[var(--border-subtle)] lg:grid-cols-[minmax(0,1.35fr)_minmax(20rem,0.9fr)]", children: [
|
|
737
738
|
/* @__PURE__ */ jsx6("div", { className: "min-h-0 overflow-auto bg-[var(--bg-card)]", children: terminal ? /* @__PURE__ */ jsx6(
|
|
738
739
|
TerminalPanel,
|
|
739
740
|
{
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// src/hooks/use-pty-session.ts
|
|
2
|
+
import { useState, useEffect, useRef, useCallback } from "react";
|
|
3
|
+
function usePtySession({ apiUrl, token, onData }) {
|
|
4
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
5
|
+
const [error, setError] = useState(null);
|
|
6
|
+
const sessionIdRef = useRef(null);
|
|
7
|
+
const abortRef = useRef(null);
|
|
8
|
+
const retryTimerRef = useRef(void 0);
|
|
9
|
+
const retryCountRef = useRef(0);
|
|
10
|
+
const mountedRef = useRef(true);
|
|
11
|
+
const onDataRef = useRef(onData);
|
|
12
|
+
onDataRef.current = onData;
|
|
13
|
+
const connectStreamRef = useRef(null);
|
|
14
|
+
const abortStream = useCallback(() => {
|
|
15
|
+
if (retryTimerRef.current) {
|
|
16
|
+
clearTimeout(retryTimerRef.current);
|
|
17
|
+
retryTimerRef.current = void 0;
|
|
18
|
+
}
|
|
19
|
+
if (abortRef.current) {
|
|
20
|
+
abortRef.current.abort();
|
|
21
|
+
abortRef.current = null;
|
|
22
|
+
}
|
|
23
|
+
}, []);
|
|
24
|
+
const cleanup = useCallback(() => {
|
|
25
|
+
abortStream();
|
|
26
|
+
if (sessionIdRef.current) {
|
|
27
|
+
const sid = sessionIdRef.current;
|
|
28
|
+
sessionIdRef.current = null;
|
|
29
|
+
fetch(`${apiUrl}/terminals/${sid}`, {
|
|
30
|
+
method: "DELETE",
|
|
31
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
32
|
+
credentials: "include"
|
|
33
|
+
}).catch(() => {
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
setIsConnected(false);
|
|
37
|
+
}, [apiUrl, token, abortStream]);
|
|
38
|
+
const connectStream = useCallback(async (sessionId) => {
|
|
39
|
+
abortStream();
|
|
40
|
+
setError(null);
|
|
41
|
+
try {
|
|
42
|
+
const controller = new AbortController();
|
|
43
|
+
abortRef.current = controller;
|
|
44
|
+
const streamRes = await fetch(`${apiUrl}/terminals/${sessionId}/stream`, {
|
|
45
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
46
|
+
credentials: "include",
|
|
47
|
+
signal: controller.signal
|
|
48
|
+
});
|
|
49
|
+
if (!streamRes.ok || !streamRes.body) {
|
|
50
|
+
const err = new Error(`SSE stream failed: ${streamRes.status}`);
|
|
51
|
+
err.httpStatus = streamRes.status;
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
if (mountedRef.current) {
|
|
55
|
+
setIsConnected(true);
|
|
56
|
+
setError(null);
|
|
57
|
+
retryCountRef.current = 0;
|
|
58
|
+
}
|
|
59
|
+
const reader = streamRes.body.getReader();
|
|
60
|
+
const decoder = new TextDecoder();
|
|
61
|
+
let buffer = "";
|
|
62
|
+
while (true) {
|
|
63
|
+
const { done, value } = await reader.read();
|
|
64
|
+
if (done) break;
|
|
65
|
+
buffer += decoder.decode(value, { stream: true });
|
|
66
|
+
const frames = buffer.split("\n\n");
|
|
67
|
+
buffer = frames.pop() ?? "";
|
|
68
|
+
for (const frame of frames) {
|
|
69
|
+
if (!frame.trim()) continue;
|
|
70
|
+
for (const line of frame.split("\n")) {
|
|
71
|
+
if (line.startsWith("data:")) {
|
|
72
|
+
const raw = line.slice(5).trim();
|
|
73
|
+
if (!raw) continue;
|
|
74
|
+
try {
|
|
75
|
+
const event = JSON.parse(raw);
|
|
76
|
+
if (event.type === "data.stdout" || event.type === "data.stderr") {
|
|
77
|
+
const text = event.properties?.text ?? "";
|
|
78
|
+
if (text && mountedRef.current) {
|
|
79
|
+
onDataRef.current(text);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
if (mountedRef.current) {
|
|
84
|
+
onDataRef.current(raw);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (mountedRef.current) {
|
|
92
|
+
setIsConnected(false);
|
|
93
|
+
retryTimerRef.current = setTimeout(() => {
|
|
94
|
+
if (mountedRef.current && sessionIdRef.current) {
|
|
95
|
+
connectStreamRef.current?.(sessionIdRef.current);
|
|
96
|
+
}
|
|
97
|
+
}, 1e3);
|
|
98
|
+
}
|
|
99
|
+
} catch (err) {
|
|
100
|
+
if (err.name === "AbortError") return;
|
|
101
|
+
if (mountedRef.current) {
|
|
102
|
+
const message = err instanceof Error ? err.message : "Stream connection failed";
|
|
103
|
+
setError(message);
|
|
104
|
+
setIsConnected(false);
|
|
105
|
+
const httpStatus = err.httpStatus;
|
|
106
|
+
const is4xx = httpStatus !== void 0 && httpStatus >= 400 && httpStatus < 500;
|
|
107
|
+
const MAX_RETRIES = 8;
|
|
108
|
+
if (!is4xx && retryCountRef.current < MAX_RETRIES) {
|
|
109
|
+
const delay = Math.min(3e3 * Math.pow(2, retryCountRef.current), 3e4);
|
|
110
|
+
retryCountRef.current++;
|
|
111
|
+
retryTimerRef.current = setTimeout(() => {
|
|
112
|
+
if (mountedRef.current && sessionIdRef.current) {
|
|
113
|
+
connectStreamRef.current?.(sessionIdRef.current);
|
|
114
|
+
}
|
|
115
|
+
}, delay);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}, [apiUrl, token, abortStream]);
|
|
120
|
+
connectStreamRef.current = connectStream;
|
|
121
|
+
const connect = useCallback(async () => {
|
|
122
|
+
cleanup();
|
|
123
|
+
retryCountRef.current = 0;
|
|
124
|
+
setError(null);
|
|
125
|
+
try {
|
|
126
|
+
const res = await fetch(`${apiUrl}/terminals`, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers: {
|
|
129
|
+
Authorization: `Bearer ${token}`,
|
|
130
|
+
"Content-Type": "application/json"
|
|
131
|
+
},
|
|
132
|
+
credentials: "include"
|
|
133
|
+
});
|
|
134
|
+
if (!res.ok) {
|
|
135
|
+
throw new Error(`Failed to create terminal: ${res.status}`);
|
|
136
|
+
}
|
|
137
|
+
const body = await res.json();
|
|
138
|
+
const sessionId = body.data?.sessionId ?? body.sessionId;
|
|
139
|
+
if (!sessionId) throw new Error("No sessionId in response");
|
|
140
|
+
if (!mountedRef.current) return;
|
|
141
|
+
sessionIdRef.current = sessionId;
|
|
142
|
+
await connectStream(sessionId);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
if (err.name === "AbortError") return;
|
|
145
|
+
if (mountedRef.current) {
|
|
146
|
+
const message = err instanceof Error ? err.message : "Terminal connection failed";
|
|
147
|
+
setError(message);
|
|
148
|
+
setIsConnected(false);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}, [apiUrl, token, cleanup, connectStream]);
|
|
152
|
+
const sendCommand = useCallback(async (command) => {
|
|
153
|
+
const sid = sessionIdRef.current;
|
|
154
|
+
if (!sid) return;
|
|
155
|
+
const res = await fetch(`${apiUrl}/terminals/${sid}/input`, {
|
|
156
|
+
method: "POST",
|
|
157
|
+
headers: {
|
|
158
|
+
Authorization: `Bearer ${token}`,
|
|
159
|
+
"Content-Type": "application/json"
|
|
160
|
+
},
|
|
161
|
+
credentials: "include",
|
|
162
|
+
body: JSON.stringify({ data: command })
|
|
163
|
+
});
|
|
164
|
+
if (!res.ok) {
|
|
165
|
+
const text = await res.text();
|
|
166
|
+
throw new Error(text || `Input failed: ${res.status}`);
|
|
167
|
+
}
|
|
168
|
+
}, [apiUrl, token]);
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
mountedRef.current = true;
|
|
171
|
+
connect();
|
|
172
|
+
return () => {
|
|
173
|
+
mountedRef.current = false;
|
|
174
|
+
cleanup();
|
|
175
|
+
};
|
|
176
|
+
}, [connect, cleanup]);
|
|
177
|
+
return { isConnected, error, sendCommand, reconnect: connect };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export {
|
|
181
|
+
usePtySession
|
|
182
|
+
};
|
|
@@ -1040,7 +1040,7 @@ function mapApiMessage(msg) {
|
|
|
1040
1040
|
async function fetchJson(url, token, init) {
|
|
1041
1041
|
const headers = { Authorization: `Bearer ${token}` };
|
|
1042
1042
|
if (init?.body) headers["Content-Type"] = "application/json";
|
|
1043
|
-
const res = await fetch(url, { ...init, headers: { ...headers, ...init?.headers } });
|
|
1043
|
+
const res = await fetch(url, { ...init, headers: { ...headers, ...init?.headers }, credentials: "include" });
|
|
1044
1044
|
if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
1045
1045
|
return res.json();
|
|
1046
1046
|
}
|
|
@@ -1057,6 +1057,7 @@ function useSessionStream({
|
|
|
1057
1057
|
const [connected, setConnected] = useState5(false);
|
|
1058
1058
|
const abortRef = useRef5(null);
|
|
1059
1059
|
const streamingMsgIdRef = useRef5(null);
|
|
1060
|
+
const handleSSEEventRef = useRef5(null);
|
|
1060
1061
|
const refetch = useCallback4(async () => {
|
|
1061
1062
|
if (!token || !sessionId || !apiUrl) return;
|
|
1062
1063
|
try {
|
|
@@ -1087,7 +1088,8 @@ function useSessionStream({
|
|
|
1087
1088
|
const url = `${apiUrl}/session/events?sessionId=${encodeURIComponent(sessionId)}`;
|
|
1088
1089
|
const res = await fetch(url, {
|
|
1089
1090
|
headers: { Authorization: `Bearer ${token}` },
|
|
1090
|
-
signal: controller.signal
|
|
1091
|
+
signal: controller.signal,
|
|
1092
|
+
credentials: "include"
|
|
1091
1093
|
});
|
|
1092
1094
|
if (!res.ok) throw new Error(`SSE connection failed: ${res.status}`);
|
|
1093
1095
|
setConnected(true);
|
|
@@ -1120,7 +1122,7 @@ function useSessionStream({
|
|
|
1120
1122
|
} catch {
|
|
1121
1123
|
continue;
|
|
1122
1124
|
}
|
|
1123
|
-
|
|
1125
|
+
handleSSEEventRef.current?.(eventType, parsed);
|
|
1124
1126
|
}
|
|
1125
1127
|
}
|
|
1126
1128
|
} catch (err) {
|
|
@@ -1133,7 +1135,9 @@ function useSessionStream({
|
|
|
1133
1135
|
}
|
|
1134
1136
|
}
|
|
1135
1137
|
}, [apiUrl, token, sessionId, enabled]);
|
|
1136
|
-
const handleSSEEvent = useCallback4((type,
|
|
1138
|
+
const handleSSEEvent = useCallback4((type, raw) => {
|
|
1139
|
+
const envelope = raw?.properties;
|
|
1140
|
+
const props = envelope?.info ?? envelope?.part ?? envelope ?? raw;
|
|
1137
1141
|
if (type === "message.updated") {
|
|
1138
1142
|
const id = props.id ?? props.messageId ?? "";
|
|
1139
1143
|
const role = props.role ?? "assistant";
|
|
@@ -1219,6 +1223,7 @@ function useSessionStream({
|
|
|
1219
1223
|
refetch();
|
|
1220
1224
|
}
|
|
1221
1225
|
}, [refetch]);
|
|
1226
|
+
handleSSEEventRef.current = handleSSEEvent;
|
|
1222
1227
|
const send = useCallback4(async (text) => {
|
|
1223
1228
|
if (!token || !sessionId || !apiUrl) return;
|
|
1224
1229
|
try {
|