@mindstudio-ai/local-model-tunnel 0.3.4 → 0.4.1
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 +27 -11
- package/dist/{chunk-PPP2FEIN.js → chunk-KLOTDVWL.js} +57 -3
- package/dist/chunk-KLOTDVWL.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/tui-EAU7OA4X.js +2839 -0
- package/dist/tui-EAU7OA4X.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-PPP2FEIN.js.map +0 -1
- package/dist/tui-4ZB4JAV4.js +0 -1616
- package/dist/tui-4ZB4JAV4.js.map +0 -1
package/dist/tui-4ZB4JAV4.js
DELETED
|
@@ -1,1616 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
TunnelRunner,
|
|
3
|
-
detectAllProviderStatuses,
|
|
4
|
-
discoverAllModelsWithParameters,
|
|
5
|
-
getApiKey,
|
|
6
|
-
getConfigPath,
|
|
7
|
-
getEnvironment,
|
|
8
|
-
getSyncedModels,
|
|
9
|
-
pollDeviceAuth,
|
|
10
|
-
requestDeviceAuth,
|
|
11
|
-
requestEvents,
|
|
12
|
-
setApiKey,
|
|
13
|
-
syncModels,
|
|
14
|
-
verifyApiKey
|
|
15
|
-
} from "./chunk-PPP2FEIN.js";
|
|
16
|
-
|
|
17
|
-
// src/tui/index.tsx
|
|
18
|
-
import { render } from "ink";
|
|
19
|
-
import { execFileSync, execSync } from "child_process";
|
|
20
|
-
|
|
21
|
-
// src/tui/App.tsx
|
|
22
|
-
import { useEffect as useEffect10, useCallback as useCallback8, useState as useState10, useRef as useRef5 } from "react";
|
|
23
|
-
import { Box as Box7, useApp, useStdout as useStdout6 } from "ink";
|
|
24
|
-
|
|
25
|
-
// src/tui/components/Header.tsx
|
|
26
|
-
import os from "os";
|
|
27
|
-
import { Box, Text } from "ink";
|
|
28
|
-
import { createRequire } from "module";
|
|
29
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
30
|
-
var require2 = createRequire(import.meta.url);
|
|
31
|
-
var pkg = require2("../package.json");
|
|
32
|
-
var LogoString = ` .=+-. :++.
|
|
33
|
-
*@@@@@+ :%@@@@%:
|
|
34
|
-
.%@@@@@@#..@@@@@@@=
|
|
35
|
-
.*@@@@@@@--@@@@@@@#.**.
|
|
36
|
-
*@@@@@@@.-@@@@@@@@.#@@*
|
|
37
|
-
.#@@@@@@@-.@@@@@@@* #@@@@%.
|
|
38
|
-
=@@@@@@@-.@@@@@@@#.-@@@@@@+
|
|
39
|
-
:@@@@@@: +@@@@@#. .@@@@@@:
|
|
40
|
-
.++: .-*-. .++:`;
|
|
41
|
-
var getConnectionDisplay = (status) => {
|
|
42
|
-
switch (status) {
|
|
43
|
-
case "connected":
|
|
44
|
-
return { color: "green", text: "Connected to Cloud" };
|
|
45
|
-
case "connecting":
|
|
46
|
-
return { color: "yellow", text: "Connecting..." };
|
|
47
|
-
case "not_authenticated":
|
|
48
|
-
return { color: "yellow", text: "Not Authenticated" };
|
|
49
|
-
case "disconnected":
|
|
50
|
-
return { color: "red", text: "Disconnected" };
|
|
51
|
-
default:
|
|
52
|
-
return { color: "red", text: "Error" };
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
function Header({
|
|
56
|
-
connection,
|
|
57
|
-
environment,
|
|
58
|
-
configPath,
|
|
59
|
-
connectionError,
|
|
60
|
-
compact
|
|
61
|
-
}) {
|
|
62
|
-
const { color: connectionColor, text: connectionText } = getConnectionDisplay(connection);
|
|
63
|
-
return /* @__PURE__ */ jsxs(
|
|
64
|
-
Box,
|
|
65
|
-
{
|
|
66
|
-
flexDirection: "row",
|
|
67
|
-
alignItems: "center",
|
|
68
|
-
borderStyle: "round",
|
|
69
|
-
borderColor: "cyan",
|
|
70
|
-
paddingX: 1,
|
|
71
|
-
paddingY: 1,
|
|
72
|
-
width: "100%",
|
|
73
|
-
children: [
|
|
74
|
-
!compact && /* @__PURE__ */ jsx(Box, { paddingLeft: 3, children: /* @__PURE__ */ jsx(Text, { color: "cyan", children: LogoString }) }),
|
|
75
|
-
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: compact ? 0 : 4, children: [
|
|
76
|
-
/* @__PURE__ */ jsxs(Box, { children: [
|
|
77
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: "MindStudio Local Tunnel" }),
|
|
78
|
-
compact && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
79
|
-
" v",
|
|
80
|
-
pkg.version
|
|
81
|
-
] }),
|
|
82
|
-
environment !== "prod" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
83
|
-
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
84
|
-
/* @__PURE__ */ jsx(Text, { color: "yellow", bold: true, children: "[LOCAL]" })
|
|
85
|
-
] })
|
|
86
|
-
] }),
|
|
87
|
-
!compact && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
88
|
-
"v",
|
|
89
|
-
pkg.version
|
|
90
|
-
] }),
|
|
91
|
-
/* @__PURE__ */ jsxs(Text, { color: connectionColor, children: [
|
|
92
|
-
"\u25CF ",
|
|
93
|
-
connectionText
|
|
94
|
-
] }),
|
|
95
|
-
connectionError && /* @__PURE__ */ jsx(Text, { color: "red", children: connectionError }),
|
|
96
|
-
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
97
|
-
"Config: ",
|
|
98
|
-
configPath.replace(os.homedir(), "~")
|
|
99
|
-
] })
|
|
100
|
-
] })
|
|
101
|
-
]
|
|
102
|
-
}
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// src/tui/components/NavigationMenu.tsx
|
|
107
|
-
import { useState, useEffect } from "react";
|
|
108
|
-
import { Box as Box2, Text as Text2, useInput, useStdout } from "ink";
|
|
109
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
110
|
-
function NavigationMenu({ items, onSelect, title }) {
|
|
111
|
-
const { stdout } = useStdout();
|
|
112
|
-
const compact = (stdout?.rows ?? 24) < 40;
|
|
113
|
-
const getDefaultIndex = () => {
|
|
114
|
-
const backIdx = items.findIndex((i) => i.id === "back");
|
|
115
|
-
if (backIdx >= 0) return backIdx;
|
|
116
|
-
const firstIdx = items.findIndex((i) => !i.disabled && !i.isSeparator);
|
|
117
|
-
return firstIdx >= 0 ? firstIdx : 0;
|
|
118
|
-
};
|
|
119
|
-
const [selectedIndex, setSelectedIndex] = useState(getDefaultIndex);
|
|
120
|
-
useEffect(() => {
|
|
121
|
-
setSelectedIndex(getDefaultIndex());
|
|
122
|
-
}, [items]);
|
|
123
|
-
const selectableItems = items.filter((i) => !i.isSeparator);
|
|
124
|
-
const findNextEnabled = (from, direction) => {
|
|
125
|
-
let idx = from;
|
|
126
|
-
for (let i = 0; i < items.length; i++) {
|
|
127
|
-
idx = (idx + direction + items.length) % items.length;
|
|
128
|
-
if (!items[idx].disabled && !items[idx].isSeparator) return idx;
|
|
129
|
-
}
|
|
130
|
-
return from;
|
|
131
|
-
};
|
|
132
|
-
useInput((input, key) => {
|
|
133
|
-
if (input === "q" || key.escape) {
|
|
134
|
-
const backItem = items.find((i) => i.id === "back");
|
|
135
|
-
if (backItem) {
|
|
136
|
-
onSelect("back");
|
|
137
|
-
} else if (input === "q") {
|
|
138
|
-
onSelect("quit");
|
|
139
|
-
}
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
if (key.upArrow || compact && key.leftArrow) {
|
|
143
|
-
setSelectedIndex((prev) => findNextEnabled(prev, -1));
|
|
144
|
-
} else if (key.downArrow || compact && key.rightArrow) {
|
|
145
|
-
setSelectedIndex((prev) => findNextEnabled(prev, 1));
|
|
146
|
-
} else if (key.return) {
|
|
147
|
-
const item = items[selectedIndex];
|
|
148
|
-
if (item && !item.disabled) {
|
|
149
|
-
onSelect(item.id);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
const hasBack = items.some((i) => i.id === "back");
|
|
154
|
-
if (compact) {
|
|
155
|
-
const selectedItem = items[selectedIndex];
|
|
156
|
-
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", paddingX: 1, borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: "gray", children: /* @__PURE__ */ jsx2(Box2, { height: 1, overflow: "hidden", gap: 1, children: items.map((item, index) => {
|
|
157
|
-
if (item.isSeparator) return null;
|
|
158
|
-
const isSelected = index === selectedIndex;
|
|
159
|
-
return /* @__PURE__ */ jsx2(
|
|
160
|
-
Text2,
|
|
161
|
-
{
|
|
162
|
-
color: item.disabled ? "gray" : isSelected ? "cyan" : "white",
|
|
163
|
-
bold: isSelected,
|
|
164
|
-
wrap: "truncate-end",
|
|
165
|
-
children: isSelected ? `\u276F ${item.label}` : ` ${item.label}`
|
|
166
|
-
},
|
|
167
|
-
item.id
|
|
168
|
-
);
|
|
169
|
-
}) }) });
|
|
170
|
-
}
|
|
171
|
-
const separatorExtraLines = items.filter((item, idx) => item.isSeparator && idx > 0).length;
|
|
172
|
-
const menuHeight = items.length + 4 + separatorExtraLines;
|
|
173
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, marginBottom: 1, borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: "gray", children: [
|
|
174
|
-
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "gray", children: title ?? "Actions" }) }),
|
|
175
|
-
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: items.map((item, index) => {
|
|
176
|
-
if (item.isSeparator) {
|
|
177
|
-
return /* @__PURE__ */ jsx2(Box2, { marginTop: index > 0 ? 1 : 0, children: item.label ? /* @__PURE__ */ jsx2(Text2, { bold: true, color: item.color ?? "gray", wrap: "truncate-end", children: item.label }) : null }, item.id);
|
|
178
|
-
}
|
|
179
|
-
const isSelected = index === selectedIndex;
|
|
180
|
-
const prefix = isSelected ? "\u276F" : " ";
|
|
181
|
-
if (item.disabled) {
|
|
182
|
-
return /* @__PURE__ */ jsx2(Box2, { height: 1, overflow: "hidden", children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", wrap: "truncate-end", children: [
|
|
183
|
-
prefix,
|
|
184
|
-
" ",
|
|
185
|
-
item.label,
|
|
186
|
-
item.disabledReason ? ` (${item.disabledReason})` : ""
|
|
187
|
-
] }) }, item.id);
|
|
188
|
-
}
|
|
189
|
-
return /* @__PURE__ */ jsxs2(Box2, { height: 1, overflow: "hidden", children: [
|
|
190
|
-
/* @__PURE__ */ jsxs2(Text2, { color: isSelected ? "cyan" : "white", bold: isSelected, wrap: "truncate-end", children: [
|
|
191
|
-
prefix,
|
|
192
|
-
" ",
|
|
193
|
-
item.label
|
|
194
|
-
] }),
|
|
195
|
-
isSelected && /* @__PURE__ */ jsxs2(Text2, { color: "gray", wrap: "truncate-end", children: [
|
|
196
|
-
" - ",
|
|
197
|
-
item.description
|
|
198
|
-
] })
|
|
199
|
-
] }, item.id);
|
|
200
|
-
}) }),
|
|
201
|
-
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, height: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "gray", wrap: "truncate-end", children: hasBack ? "Up/Down Navigate \u2022 Enter Select \u2022 q/Esc Back" : "Up/Down Navigate \u2022 Enter Select \u2022 q Quit" }) })
|
|
202
|
-
] });
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// src/tui/hooks/useConnection.ts
|
|
206
|
-
import { useState as useState2, useEffect as useEffect2, useCallback } from "react";
|
|
207
|
-
function useConnection() {
|
|
208
|
-
const [status, setStatus] = useState2("connecting");
|
|
209
|
-
const [error, setError] = useState2(null);
|
|
210
|
-
const environment = getEnvironment();
|
|
211
|
-
const connect = useCallback(async () => {
|
|
212
|
-
setStatus("connecting");
|
|
213
|
-
setError(null);
|
|
214
|
-
const apiKey = getApiKey();
|
|
215
|
-
if (!apiKey) {
|
|
216
|
-
setStatus("not_authenticated");
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
try {
|
|
220
|
-
const isValid = await verifyApiKey();
|
|
221
|
-
if (isValid) {
|
|
222
|
-
setStatus("connected");
|
|
223
|
-
} else {
|
|
224
|
-
setStatus("not_authenticated");
|
|
225
|
-
}
|
|
226
|
-
} catch (err) {
|
|
227
|
-
setStatus("error");
|
|
228
|
-
setError(err instanceof Error ? err.message : "Connection failed");
|
|
229
|
-
}
|
|
230
|
-
}, []);
|
|
231
|
-
useEffect2(() => {
|
|
232
|
-
connect();
|
|
233
|
-
}, [connect]);
|
|
234
|
-
return {
|
|
235
|
-
status,
|
|
236
|
-
environment,
|
|
237
|
-
error,
|
|
238
|
-
retry: connect
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// src/tui/hooks/useSetupProviders.ts
|
|
243
|
-
import { useState as useState3, useEffect as useEffect3, useCallback as useCallback2, useRef } from "react";
|
|
244
|
-
function useSetupProviders() {
|
|
245
|
-
const [providers, setProviders] = useState3([]);
|
|
246
|
-
const [loading, setLoading] = useState3(true);
|
|
247
|
-
const [refreshing, setRefreshing] = useState3(false);
|
|
248
|
-
const initialLoadDone = useRef(false);
|
|
249
|
-
const refresh = useCallback2(async () => {
|
|
250
|
-
if (!initialLoadDone.current) {
|
|
251
|
-
setLoading(true);
|
|
252
|
-
} else {
|
|
253
|
-
setRefreshing(true);
|
|
254
|
-
}
|
|
255
|
-
const statuses = await detectAllProviderStatuses();
|
|
256
|
-
setProviders(statuses);
|
|
257
|
-
initialLoadDone.current = true;
|
|
258
|
-
setLoading(false);
|
|
259
|
-
setRefreshing(false);
|
|
260
|
-
}, []);
|
|
261
|
-
useEffect3(() => {
|
|
262
|
-
refresh();
|
|
263
|
-
}, [refresh]);
|
|
264
|
-
return { providers, loading, refreshing, refresh };
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// src/tui/hooks/useModels.ts
|
|
268
|
-
import { useState as useState4, useEffect as useEffect4, useCallback as useCallback3, useRef as useRef2 } from "react";
|
|
269
|
-
function useModels() {
|
|
270
|
-
const [models, setModels] = useState4([]);
|
|
271
|
-
const [warnings, setWarnings] = useState4([]);
|
|
272
|
-
const [loading, setLoading] = useState4(true);
|
|
273
|
-
const [refreshing, setRefreshing] = useState4(false);
|
|
274
|
-
const initialLoadDone = useRef2(false);
|
|
275
|
-
const refresh = useCallback3(async () => {
|
|
276
|
-
if (!initialLoadDone.current) {
|
|
277
|
-
setLoading(true);
|
|
278
|
-
} else {
|
|
279
|
-
setRefreshing(true);
|
|
280
|
-
}
|
|
281
|
-
try {
|
|
282
|
-
const discoveredModels = await discoverAllModelsWithParameters();
|
|
283
|
-
setModels(discoveredModels.filter((m) => !m.statusHint));
|
|
284
|
-
setWarnings(discoveredModels.filter((m) => !!m.statusHint));
|
|
285
|
-
initialLoadDone.current = true;
|
|
286
|
-
return discoveredModels;
|
|
287
|
-
} catch {
|
|
288
|
-
return [];
|
|
289
|
-
} finally {
|
|
290
|
-
setLoading(false);
|
|
291
|
-
setRefreshing(false);
|
|
292
|
-
}
|
|
293
|
-
}, []);
|
|
294
|
-
useEffect4(() => {
|
|
295
|
-
refresh();
|
|
296
|
-
}, [refresh]);
|
|
297
|
-
return {
|
|
298
|
-
models,
|
|
299
|
-
warnings,
|
|
300
|
-
loading,
|
|
301
|
-
refreshing,
|
|
302
|
-
refresh
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// src/tui/hooks/useRequests.ts
|
|
307
|
-
import { useState as useState5, useEffect as useEffect5, useCallback as useCallback4, useRef as useRef3 } from "react";
|
|
308
|
-
function useRequests(maxHistory = 50) {
|
|
309
|
-
const [requests, setRequests] = useState5([]);
|
|
310
|
-
const requestsRef = useRef3(/* @__PURE__ */ new Map());
|
|
311
|
-
useEffect5(() => {
|
|
312
|
-
const interval = setInterval(() => {
|
|
313
|
-
setRequests((prev) => {
|
|
314
|
-
const hasActive = prev.some((r) => r.status === "processing");
|
|
315
|
-
return hasActive ? [...prev] : prev;
|
|
316
|
-
});
|
|
317
|
-
}, 1e3);
|
|
318
|
-
return () => clearInterval(interval);
|
|
319
|
-
}, []);
|
|
320
|
-
useEffect5(() => {
|
|
321
|
-
const unsubStart = requestEvents.onStart((event) => {
|
|
322
|
-
const entry = {
|
|
323
|
-
id: event.id,
|
|
324
|
-
modelId: event.modelId,
|
|
325
|
-
requestType: event.requestType,
|
|
326
|
-
status: "processing",
|
|
327
|
-
startTime: event.timestamp
|
|
328
|
-
};
|
|
329
|
-
requestsRef.current.set(event.id, entry);
|
|
330
|
-
setRequests((prev) => [...prev, entry].slice(-maxHistory));
|
|
331
|
-
});
|
|
332
|
-
const unsubProgress = requestEvents.onProgress((event) => {
|
|
333
|
-
const existing = requestsRef.current.get(event.id);
|
|
334
|
-
if (existing && existing.status === "processing") {
|
|
335
|
-
const updated = {
|
|
336
|
-
...existing,
|
|
337
|
-
...event.content !== void 0 && { content: event.content },
|
|
338
|
-
...event.step !== void 0 && { step: event.step },
|
|
339
|
-
...event.totalSteps !== void 0 && { totalSteps: event.totalSteps }
|
|
340
|
-
};
|
|
341
|
-
requestsRef.current.set(event.id, updated);
|
|
342
|
-
setRequests(
|
|
343
|
-
(prev) => prev.map((r) => r.id === event.id ? updated : r)
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
const unsubComplete = requestEvents.onComplete((event) => {
|
|
348
|
-
const existing = requestsRef.current.get(event.id);
|
|
349
|
-
if (existing) {
|
|
350
|
-
const updated = {
|
|
351
|
-
...existing,
|
|
352
|
-
status: event.success ? "completed" : "failed",
|
|
353
|
-
endTime: Date.now(),
|
|
354
|
-
duration: event.duration,
|
|
355
|
-
result: event.result,
|
|
356
|
-
error: event.error
|
|
357
|
-
};
|
|
358
|
-
requestsRef.current.set(event.id, updated);
|
|
359
|
-
setRequests(
|
|
360
|
-
(prev) => prev.map((r) => r.id === event.id ? updated : r)
|
|
361
|
-
);
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
return () => {
|
|
365
|
-
unsubStart();
|
|
366
|
-
unsubProgress();
|
|
367
|
-
unsubComplete();
|
|
368
|
-
};
|
|
369
|
-
}, [maxHistory]);
|
|
370
|
-
const activeCount = requests.filter((r) => r.status === "processing").length;
|
|
371
|
-
const clear = useCallback4(() => {
|
|
372
|
-
requestsRef.current.clear();
|
|
373
|
-
setRequests([]);
|
|
374
|
-
}, []);
|
|
375
|
-
return {
|
|
376
|
-
requests,
|
|
377
|
-
activeCount,
|
|
378
|
-
clear
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// src/tui/hooks/useRegisteredModels.ts
|
|
383
|
-
import { useState as useState6, useEffect as useEffect6, useCallback as useCallback5 } from "react";
|
|
384
|
-
function useSyncedModels(connectionStatus) {
|
|
385
|
-
const [syncedNames, setSyncedNames] = useState6(
|
|
386
|
-
/* @__PURE__ */ new Set()
|
|
387
|
-
);
|
|
388
|
-
const [syncedModels, setSyncedModels] = useState6([]);
|
|
389
|
-
const refresh = useCallback5(async () => {
|
|
390
|
-
if (connectionStatus !== "connected") {
|
|
391
|
-
setSyncedNames(/* @__PURE__ */ new Set());
|
|
392
|
-
setSyncedModels([]);
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
try {
|
|
396
|
-
const models = await getSyncedModels();
|
|
397
|
-
setSyncedNames(new Set(models.map((m) => m.name)));
|
|
398
|
-
setSyncedModels(models);
|
|
399
|
-
} catch {
|
|
400
|
-
}
|
|
401
|
-
}, [connectionStatus]);
|
|
402
|
-
useEffect6(() => {
|
|
403
|
-
refresh();
|
|
404
|
-
}, [refresh]);
|
|
405
|
-
return {
|
|
406
|
-
syncedNames,
|
|
407
|
-
syncedModels,
|
|
408
|
-
refresh
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// src/tui/pages/DashboardPage.tsx
|
|
413
|
-
import { useMemo } from "react";
|
|
414
|
-
import { Box as Box4, Text as Text4, useStdout as useStdout3 } from "ink";
|
|
415
|
-
import Spinner2 from "ink-spinner";
|
|
416
|
-
|
|
417
|
-
// src/tui/components/RequestLog.tsx
|
|
418
|
-
import { Box as Box3, Text as Text3, useStdout as useStdout2 } from "ink";
|
|
419
|
-
import Spinner from "ink-spinner";
|
|
420
|
-
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
421
|
-
function formatTime(timestamp) {
|
|
422
|
-
const date = new Date(timestamp);
|
|
423
|
-
return date.toLocaleTimeString("en-US", {
|
|
424
|
-
hour12: false,
|
|
425
|
-
hour: "2-digit",
|
|
426
|
-
minute: "2-digit",
|
|
427
|
-
second: "2-digit"
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
function formatDuration(ms) {
|
|
431
|
-
if (ms < 1e3) return `${ms}ms`;
|
|
432
|
-
return `${(ms / 1e3).toFixed(1)}s`;
|
|
433
|
-
}
|
|
434
|
-
function getRequestTypeLabel(type) {
|
|
435
|
-
switch (type) {
|
|
436
|
-
case "llm_chat":
|
|
437
|
-
return { label: "text", color: "gray" };
|
|
438
|
-
case "image_generation":
|
|
439
|
-
return { label: "image", color: "gray" };
|
|
440
|
-
case "video_generation":
|
|
441
|
-
return { label: "video", color: "gray" };
|
|
442
|
-
default:
|
|
443
|
-
return { label: type, color: "gray" };
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
function snippetLine(content, maxWidth) {
|
|
447
|
-
const flat = content.replace(/\s+/g, " ").trim();
|
|
448
|
-
if (flat.length <= maxWidth) return flat;
|
|
449
|
-
return "\u2026" + flat.slice(-(maxWidth - 1));
|
|
450
|
-
}
|
|
451
|
-
function RequestItem({ request, width }) {
|
|
452
|
-
const time = formatTime(request.startTime);
|
|
453
|
-
const typeLabel = getRequestTypeLabel(request.requestType);
|
|
454
|
-
const snippetIndent = " ";
|
|
455
|
-
const snippetWidth = width - snippetIndent.length - 2;
|
|
456
|
-
if (request.status === "processing") {
|
|
457
|
-
const elapsed = Date.now() - request.startTime;
|
|
458
|
-
const snippet = request.content && request.requestType === "llm_chat" ? snippetLine(request.content, snippetWidth) : null;
|
|
459
|
-
const stepProgress = request.step !== void 0 && request.totalSteps ? `Step ${request.step}/${request.totalSteps}` : null;
|
|
460
|
-
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
461
|
-
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
462
|
-
/* @__PURE__ */ jsx3(Text3, { color: "cyan", children: /* @__PURE__ */ jsx3(Spinner, { type: "dots" }) }),
|
|
463
|
-
/* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
|
|
464
|
-
" ",
|
|
465
|
-
time,
|
|
466
|
-
" "
|
|
467
|
-
] }),
|
|
468
|
-
/* @__PURE__ */ jsx3(Text3, { color: "white", children: request.modelId }),
|
|
469
|
-
/* @__PURE__ */ jsx3(Text3, { color: "gray", children: " " }),
|
|
470
|
-
/* @__PURE__ */ jsx3(Text3, { color: typeLabel.color, children: typeLabel.label }),
|
|
471
|
-
/* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
|
|
472
|
-
" ",
|
|
473
|
-
formatDuration(elapsed),
|
|
474
|
-
"..."
|
|
475
|
-
] })
|
|
476
|
-
] }),
|
|
477
|
-
snippet && /* @__PURE__ */ jsxs3(Text3, { color: "gray", wrap: "truncate-end", children: [
|
|
478
|
-
snippetIndent,
|
|
479
|
-
snippet
|
|
480
|
-
] }),
|
|
481
|
-
stepProgress && /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
|
|
482
|
-
snippetIndent,
|
|
483
|
-
stepProgress
|
|
484
|
-
] })
|
|
485
|
-
] });
|
|
486
|
-
}
|
|
487
|
-
if (request.status === "completed") {
|
|
488
|
-
const duration = request.duration ? formatDuration(request.duration) : "";
|
|
489
|
-
let resultInfo = "";
|
|
490
|
-
if (request.result?.chars) {
|
|
491
|
-
resultInfo = ` \xB7 ${request.result.chars} chars`;
|
|
492
|
-
} else if (request.result?.imageSize) {
|
|
493
|
-
resultInfo = ` \xB7 ${Math.round(request.result.imageSize / 1024)}KB`;
|
|
494
|
-
} else if (request.result?.videoSize) {
|
|
495
|
-
resultInfo = ` \xB7 ${Math.round(request.result.videoSize / 1024 / 1024)}MB`;
|
|
496
|
-
}
|
|
497
|
-
const snippet = request.content && request.requestType === "llm_chat" ? snippetLine(request.content, snippetWidth) : null;
|
|
498
|
-
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
499
|
-
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
500
|
-
/* @__PURE__ */ jsx3(Text3, { color: "green", children: "\u2713" }),
|
|
501
|
-
/* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
|
|
502
|
-
" ",
|
|
503
|
-
time,
|
|
504
|
-
" "
|
|
505
|
-
] }),
|
|
506
|
-
/* @__PURE__ */ jsx3(Text3, { color: "white", children: request.modelId }),
|
|
507
|
-
/* @__PURE__ */ jsx3(Text3, { color: "gray", children: " " }),
|
|
508
|
-
/* @__PURE__ */ jsx3(Text3, { color: typeLabel.color, children: typeLabel.label }),
|
|
509
|
-
/* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
|
|
510
|
-
" ",
|
|
511
|
-
duration,
|
|
512
|
-
resultInfo
|
|
513
|
-
] })
|
|
514
|
-
] }),
|
|
515
|
-
snippet && /* @__PURE__ */ jsxs3(Text3, { color: "gray", wrap: "truncate-end", children: [
|
|
516
|
-
snippetIndent,
|
|
517
|
-
snippet
|
|
518
|
-
] })
|
|
519
|
-
] });
|
|
520
|
-
}
|
|
521
|
-
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
522
|
-
/* @__PURE__ */ jsx3(Text3, { color: "red", children: "\u25CF" }),
|
|
523
|
-
/* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
|
|
524
|
-
" ",
|
|
525
|
-
time,
|
|
526
|
-
" "
|
|
527
|
-
] }),
|
|
528
|
-
/* @__PURE__ */ jsx3(Text3, { color: "white", children: request.modelId }),
|
|
529
|
-
/* @__PURE__ */ jsx3(Text3, { color: "gray", children: " " }),
|
|
530
|
-
/* @__PURE__ */ jsx3(Text3, { color: typeLabel.color, children: typeLabel.label }),
|
|
531
|
-
/* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
|
|
532
|
-
" ",
|
|
533
|
-
request.error || "Failed"
|
|
534
|
-
] })
|
|
535
|
-
] }) });
|
|
536
|
-
}
|
|
537
|
-
function RequestLog({ requests, maxVisible = 8, hasModels = true }) {
|
|
538
|
-
const { stdout } = useStdout2();
|
|
539
|
-
const width = stdout?.columns ?? 80;
|
|
540
|
-
const activeRequests = requests.filter((r) => r.status === "processing");
|
|
541
|
-
const completedRequests = requests.filter((r) => r.status !== "processing");
|
|
542
|
-
const itemLines = (r) => {
|
|
543
|
-
if (r.requestType === "llm_chat" && r.content) return 2;
|
|
544
|
-
if (r.status === "processing" && r.step !== void 0) return 2;
|
|
545
|
-
return 1;
|
|
546
|
-
};
|
|
547
|
-
let completedToShow = [];
|
|
548
|
-
let linesUsed = activeRequests.reduce((sum, r) => sum + itemLines(r), 0);
|
|
549
|
-
for (let i = completedRequests.length - 1; i >= 0 && linesUsed < maxVisible; i--) {
|
|
550
|
-
const r = completedRequests[i];
|
|
551
|
-
const lines = itemLines(r);
|
|
552
|
-
if (linesUsed + lines <= maxVisible) {
|
|
553
|
-
completedToShow.unshift(r);
|
|
554
|
-
linesUsed += lines;
|
|
555
|
-
} else {
|
|
556
|
-
break;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
const visibleRequests = [...completedToShow, ...activeRequests];
|
|
560
|
-
return /* @__PURE__ */ jsxs3(
|
|
561
|
-
Box3,
|
|
562
|
-
{
|
|
563
|
-
flexDirection: "column",
|
|
564
|
-
flexGrow: 1,
|
|
565
|
-
width: "100%",
|
|
566
|
-
paddingX: 1,
|
|
567
|
-
marginTop: 1,
|
|
568
|
-
children: [
|
|
569
|
-
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
570
|
-
/* @__PURE__ */ jsx3(Text3, { bold: true, underline: true, color: "white", children: "Generation Requests" }),
|
|
571
|
-
activeRequests.length > 0 && /* @__PURE__ */ jsxs3(Text3, { color: "cyan", children: [
|
|
572
|
-
" (",
|
|
573
|
-
activeRequests.length,
|
|
574
|
-
" active)"
|
|
575
|
-
] })
|
|
576
|
-
] }),
|
|
577
|
-
requests.length === 0 ? /* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx3(Text3, { color: "gray", children: hasModels ? "Tunnel is live \u2014 requests will appear here when models are used in MindStudio" : "Start a model to begin receiving generation requests." }) }) : /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", marginTop: 1, children: visibleRequests.map((request) => /* @__PURE__ */ jsx3(RequestItem, { request, width }, request.id)) })
|
|
578
|
-
]
|
|
579
|
-
}
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
// src/tui/pages/DashboardPage.tsx
|
|
584
|
-
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
585
|
-
function getWorkflowCount(model) {
|
|
586
|
-
const param = model.parameters?.find((p) => p.type === "comfyWorkflow");
|
|
587
|
-
if (!param) return null;
|
|
588
|
-
return param.comfyWorkflowOptions.availableWorkflows.length;
|
|
589
|
-
}
|
|
590
|
-
function getCapabilityLabel(capability) {
|
|
591
|
-
switch (capability) {
|
|
592
|
-
case "text":
|
|
593
|
-
return { label: "Text Generation", color: "gray" };
|
|
594
|
-
case "image":
|
|
595
|
-
return { label: "Image Generation", color: "gray" };
|
|
596
|
-
case "video":
|
|
597
|
-
return { label: "Video Generation", color: "gray" };
|
|
598
|
-
default:
|
|
599
|
-
return { label: capability, color: "gray" };
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
function DashboardPage({
|
|
603
|
-
requests,
|
|
604
|
-
models,
|
|
605
|
-
modelWarnings = [],
|
|
606
|
-
providers,
|
|
607
|
-
providersLoading,
|
|
608
|
-
syncedNames,
|
|
609
|
-
modelsLoading,
|
|
610
|
-
syncStatus = "idle",
|
|
611
|
-
onNavigate
|
|
612
|
-
}) {
|
|
613
|
-
const { stdout } = useStdout3();
|
|
614
|
-
const installedProviders = providers.filter(({ status }) => status.installed);
|
|
615
|
-
const provNameWidth = Math.max(
|
|
616
|
-
...installedProviders.map((p) => p.provider.displayName.length),
|
|
617
|
-
8
|
|
618
|
-
);
|
|
619
|
-
const provStatusWidth = "Local Server Running".length;
|
|
620
|
-
const allModelNames = new Set(models.map((m) => m.name));
|
|
621
|
-
const unavailableSynced = [...syncedNames].filter(
|
|
622
|
-
(name) => !allModelNames.has(name)
|
|
623
|
-
);
|
|
624
|
-
const syncDescription = syncStatus === "syncing" ? "Syncing..." : syncStatus === "synced" ? "\u2713 Synced" : "Re-detect providers and sync models to MindStudio";
|
|
625
|
-
const menuItems = useMemo(() => {
|
|
626
|
-
return [
|
|
627
|
-
{
|
|
628
|
-
id: "refresh",
|
|
629
|
-
label: "Sync Models",
|
|
630
|
-
description: syncDescription
|
|
631
|
-
},
|
|
632
|
-
{
|
|
633
|
-
id: "setup",
|
|
634
|
-
label: "Manage Providers",
|
|
635
|
-
description: "Manage local AI providers"
|
|
636
|
-
},
|
|
637
|
-
{
|
|
638
|
-
id: "auth",
|
|
639
|
-
label: "Re-authenticate",
|
|
640
|
-
description: "Re-authenticate with MindStudio"
|
|
641
|
-
},
|
|
642
|
-
{
|
|
643
|
-
id: "quit",
|
|
644
|
-
label: "Exit",
|
|
645
|
-
description: "Quit the application"
|
|
646
|
-
}
|
|
647
|
-
];
|
|
648
|
-
}, [syncDescription]);
|
|
649
|
-
const termHeight = (stdout?.rows ?? 24) - 4;
|
|
650
|
-
const compactHeader = (stdout?.rows ?? 24) <= 45 || (stdout?.columns ?? 80) <= 90;
|
|
651
|
-
const headerLines = compactHeader ? 7 : 14;
|
|
652
|
-
const providerContentLines = providersLoading ? 1 : installedProviders.length === 0 ? 2 : installedProviders.length;
|
|
653
|
-
const providersLines = 3 + providerContentLines;
|
|
654
|
-
const modelContentLines = modelsLoading ? 1 : models.length === 0 && unavailableSynced.length === 0 && modelWarnings.length === 0 ? 2 : models.length + modelWarnings.length + (unavailableSynced.length > 0 ? 1 + unavailableSynced.length : 0);
|
|
655
|
-
const modelsLines = 3 + modelContentLines;
|
|
656
|
-
const requestLogOverhead = 3;
|
|
657
|
-
const compactMenu = termHeight + 4 < 40;
|
|
658
|
-
const menuLines = compactMenu ? 2 : menuItems.length + 6;
|
|
659
|
-
const usedLines = headerLines + providersLines + modelsLines + requestLogOverhead + menuLines;
|
|
660
|
-
const maxVisible = Math.max(3, termHeight - usedLines);
|
|
661
|
-
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", flexGrow: 1, children: [
|
|
662
|
-
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
|
|
663
|
-
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "white", underline: true, children: "Providers" }),
|
|
664
|
-
providersLoading ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
|
|
665
|
-
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: /* @__PURE__ */ jsx4(Spinner2, { type: "dots" }) }),
|
|
666
|
-
/* @__PURE__ */ jsx4(Text4, { children: " Detecting providers..." })
|
|
667
|
-
] }) : installedProviders.length === 0 ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
668
|
-
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: "No providers installed." }),
|
|
669
|
-
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: 'Use "Manage Providers" below to install one.' })
|
|
670
|
-
] }) : /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", marginTop: 1, children: installedProviders.map(({ provider, status }) => {
|
|
671
|
-
const url = provider.baseUrl;
|
|
672
|
-
const statusColor = status.running ? "green" : "yellow";
|
|
673
|
-
const statusText = status.running ? "Local Server Running" : "Installed (not running)";
|
|
674
|
-
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
675
|
-
/* @__PURE__ */ jsx4(Text4, { color: "white", children: provider.displayName.padEnd(provNameWidth + 2) }),
|
|
676
|
-
/* @__PURE__ */ jsx4(Text4, { color: statusColor, children: statusText.padEnd(provStatusWidth + 2) }),
|
|
677
|
-
status.running && /* @__PURE__ */ jsx4(Text4, { color: "gray", children: url })
|
|
678
|
-
] }, provider.name);
|
|
679
|
-
}) })
|
|
680
|
-
] }),
|
|
681
|
-
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
|
|
682
|
-
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "white", underline: true, children: "Models" }),
|
|
683
|
-
modelsLoading ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
|
|
684
|
-
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: /* @__PURE__ */ jsx4(Spinner2, { type: "dots" }) }),
|
|
685
|
-
/* @__PURE__ */ jsx4(Text4, { children: " Discovering models..." })
|
|
686
|
-
] }) : models.length === 0 && unavailableSynced.length === 0 && modelWarnings.length === 0 ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
687
|
-
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: "No models found." }),
|
|
688
|
-
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: "Download models using your provider (e.g., ollama pull llama3.2)" })
|
|
689
|
-
] }) : /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, children: [
|
|
690
|
-
models.map((model) => {
|
|
691
|
-
const cap = getCapabilityLabel(model.capability);
|
|
692
|
-
const isSynced = syncedNames.has(model.name);
|
|
693
|
-
const displayProvider = providers.find((p) => p.provider.name === model.provider)?.provider.displayName ?? model.provider;
|
|
694
|
-
const workflowCount = getWorkflowCount(model);
|
|
695
|
-
const workflowSuffix = workflowCount !== null ? ` (${workflowCount} workflow${workflowCount !== 1 ? "s" : ""}, ${isSynced ? workflowCount : 0} synced)` : "";
|
|
696
|
-
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
697
|
-
/* @__PURE__ */ jsx4(Text4, { color: isSynced ? "green" : "gray", children: isSynced ? "\u25CF" : "\u25CB" }),
|
|
698
|
-
/* @__PURE__ */ jsx4(Text4, { color: "white", children: ` ${model.name}` }),
|
|
699
|
-
workflowSuffix && /* @__PURE__ */ jsx4(Text4, { color: "gray", children: workflowSuffix }),
|
|
700
|
-
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: " - " }),
|
|
701
|
-
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: displayProvider }),
|
|
702
|
-
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: " - " }),
|
|
703
|
-
/* @__PURE__ */ jsx4(Text4, { color: cap.color, children: cap.label })
|
|
704
|
-
] }, `${model.provider}:${model.name}`);
|
|
705
|
-
}),
|
|
706
|
-
modelWarnings.map((warning) => {
|
|
707
|
-
const displayProvider = providers.find((p) => p.provider.name === warning.provider)?.provider.displayName ?? warning.provider;
|
|
708
|
-
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
709
|
-
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: "\u25CB" }),
|
|
710
|
-
/* @__PURE__ */ jsx4(Text4, { color: "white", children: ` ${warning.name}` }),
|
|
711
|
-
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: " - " }),
|
|
712
|
-
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: displayProvider }),
|
|
713
|
-
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: " - " }),
|
|
714
|
-
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: warning.statusHint })
|
|
715
|
-
] }, `${warning.provider}:${warning.name}`);
|
|
716
|
-
}),
|
|
717
|
-
unavailableSynced.length > 0 && /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: models.length > 0 ? 1 : 0, children: [
|
|
718
|
-
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: "Synced but provider not running:" }),
|
|
719
|
-
unavailableSynced.map((name) => /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
720
|
-
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: "\u25CB" }),
|
|
721
|
-
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: ` ${name}` })
|
|
722
|
-
] }, name))
|
|
723
|
-
] })
|
|
724
|
-
] })
|
|
725
|
-
] }),
|
|
726
|
-
/* @__PURE__ */ jsx4(
|
|
727
|
-
RequestLog,
|
|
728
|
-
{
|
|
729
|
-
requests,
|
|
730
|
-
maxVisible,
|
|
731
|
-
hasModels: models.length > 0
|
|
732
|
-
}
|
|
733
|
-
),
|
|
734
|
-
/* @__PURE__ */ jsx4(NavigationMenu, { items: menuItems, onSelect: onNavigate })
|
|
735
|
-
] });
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
// src/tui/pages/SetupPage.tsx
|
|
739
|
-
import { useState as useState7, useMemo as useMemo3, useEffect as useEffect7 } from "react";
|
|
740
|
-
import { Box as Box5, Text as Text6, useInput as useInput2, useStdout as useStdout5 } from "ink";
|
|
741
|
-
import Spinner3 from "ink-spinner";
|
|
742
|
-
|
|
743
|
-
// src/tui/components/MarkdownText.tsx
|
|
744
|
-
import { useMemo as useMemo2 } from "react";
|
|
745
|
-
import { Text as Text5, useStdout as useStdout4 } from "ink";
|
|
746
|
-
import chalk from "chalk";
|
|
747
|
-
import { marked } from "marked";
|
|
748
|
-
import { markedTerminal } from "marked-terminal";
|
|
749
|
-
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
750
|
-
var codeStyle = chalk.cyan;
|
|
751
|
-
var identity = (s) => s;
|
|
752
|
-
function renderMarkdown(content, width) {
|
|
753
|
-
marked.use(
|
|
754
|
-
markedTerminal({
|
|
755
|
-
width,
|
|
756
|
-
codespan: codeStyle,
|
|
757
|
-
link: identity,
|
|
758
|
-
href: identity
|
|
759
|
-
})
|
|
760
|
-
);
|
|
761
|
-
marked.use({
|
|
762
|
-
renderer: {
|
|
763
|
-
code({ text }) {
|
|
764
|
-
const lines = text.trim().split("\n").map((l) => " " + codeStyle(l)).join("\n");
|
|
765
|
-
return lines + "\n\n";
|
|
766
|
-
},
|
|
767
|
-
link({ href, text }) {
|
|
768
|
-
if (text && text !== href) {
|
|
769
|
-
return `${text} (${href})`;
|
|
770
|
-
}
|
|
771
|
-
return href;
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
});
|
|
775
|
-
return marked.parse(content).trimEnd();
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// src/tui/pages/SetupPage.tsx
|
|
779
|
-
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
780
|
-
function ProviderDetailView({
|
|
781
|
-
provider,
|
|
782
|
-
onBack
|
|
783
|
-
}) {
|
|
784
|
-
const [scrollOffset, setScrollOffset] = useState7(0);
|
|
785
|
-
const { stdout } = useStdout5();
|
|
786
|
-
const termHeight = (stdout?.rows ?? 24) - 4;
|
|
787
|
-
const headerHeight = 14;
|
|
788
|
-
const footerLines = 6;
|
|
789
|
-
const contentPadding = 2;
|
|
790
|
-
const viewHeight = termHeight - headerHeight - footerLines - contentPadding;
|
|
791
|
-
const contentWidth = (stdout?.columns ?? 80) - 4;
|
|
792
|
-
const renderedLines = useMemo3(() => {
|
|
793
|
-
const rendered = renderMarkdown(provider.readme, contentWidth);
|
|
794
|
-
return rendered.split("\n");
|
|
795
|
-
}, [provider.readme, contentWidth]);
|
|
796
|
-
const maxScroll = Math.max(0, renderedLines.length - viewHeight);
|
|
797
|
-
useInput2((input, key) => {
|
|
798
|
-
if (input === "q" || key.escape || key.return) {
|
|
799
|
-
onBack();
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
if (key.upArrow) {
|
|
803
|
-
setScrollOffset((prev) => Math.max(0, prev - 1));
|
|
804
|
-
} else if (key.downArrow) {
|
|
805
|
-
setScrollOffset((prev) => Math.min(maxScroll, prev + 1));
|
|
806
|
-
}
|
|
807
|
-
});
|
|
808
|
-
const visibleContent = renderedLines.slice(scrollOffset, scrollOffset + viewHeight).join("\n");
|
|
809
|
-
const scrollbar = useMemo3(() => {
|
|
810
|
-
if (maxScroll === 0) return null;
|
|
811
|
-
const thumbSize = Math.max(
|
|
812
|
-
1,
|
|
813
|
-
Math.round(viewHeight / renderedLines.length * viewHeight)
|
|
814
|
-
);
|
|
815
|
-
const thumbPos = Math.round(
|
|
816
|
-
scrollOffset / maxScroll * (viewHeight - thumbSize)
|
|
817
|
-
);
|
|
818
|
-
return Array.from(
|
|
819
|
-
{ length: viewHeight },
|
|
820
|
-
(_, i) => i >= thumbPos && i < thumbPos + thumbSize
|
|
821
|
-
);
|
|
822
|
-
}, [scrollOffset, maxScroll, viewHeight, renderedLines.length]);
|
|
823
|
-
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
824
|
-
/* @__PURE__ */ jsxs5(Box5, { height: viewHeight, children: [
|
|
825
|
-
/* @__PURE__ */ jsx6(
|
|
826
|
-
Box5,
|
|
827
|
-
{
|
|
828
|
-
flexDirection: "column",
|
|
829
|
-
paddingX: 1,
|
|
830
|
-
paddingY: 1,
|
|
831
|
-
flexGrow: 1,
|
|
832
|
-
overflow: "hidden",
|
|
833
|
-
children: /* @__PURE__ */ jsx6(Text6, { children: visibleContent })
|
|
834
|
-
}
|
|
835
|
-
),
|
|
836
|
-
scrollbar && /* @__PURE__ */ jsx6(Box5, { flexDirection: "column", children: scrollbar.map((isThumb, i) => /* @__PURE__ */ jsx6(
|
|
837
|
-
Text6,
|
|
838
|
-
{
|
|
839
|
-
color: isThumb ? "cyan" : "gray",
|
|
840
|
-
dimColor: !isThumb,
|
|
841
|
-
children: isThumb ? "\u2503" : "\u2502"
|
|
842
|
-
},
|
|
843
|
-
i
|
|
844
|
-
)) })
|
|
845
|
-
] }),
|
|
846
|
-
/* @__PURE__ */ jsxs5(
|
|
847
|
-
Box5,
|
|
848
|
-
{
|
|
849
|
-
flexDirection: "column",
|
|
850
|
-
paddingX: 1,
|
|
851
|
-
borderStyle: "single",
|
|
852
|
-
borderTop: true,
|
|
853
|
-
borderBottom: false,
|
|
854
|
-
borderLeft: false,
|
|
855
|
-
borderRight: false,
|
|
856
|
-
borderColor: "gray",
|
|
857
|
-
children: [
|
|
858
|
-
/* @__PURE__ */ jsx6(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", children: "Actions" }) }),
|
|
859
|
-
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
860
|
-
/* @__PURE__ */ jsxs5(Text6, { color: "cyan", bold: true, children: [
|
|
861
|
-
"\u276F",
|
|
862
|
-
" Back"
|
|
863
|
-
] }),
|
|
864
|
-
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: " - Return to providers" })
|
|
865
|
-
] }),
|
|
866
|
-
/* @__PURE__ */ jsx6(Box5, { marginTop: 1, height: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "gray", wrap: "truncate-end", children: [
|
|
867
|
-
"Up/Down Scroll ",
|
|
868
|
-
"\u2022",
|
|
869
|
-
" Enter/q/Esc Back",
|
|
870
|
-
maxScroll > 0 && ` \u2022 ${Math.round(scrollOffset / maxScroll * 100)}%`
|
|
871
|
-
] }) })
|
|
872
|
-
]
|
|
873
|
-
}
|
|
874
|
-
)
|
|
875
|
-
] });
|
|
876
|
-
}
|
|
877
|
-
function SetupPage({ onBack }) {
|
|
878
|
-
const { providers, loading } = useSetupProviders();
|
|
879
|
-
const [selectedProvider, setSelectedProvider] = useState7(null);
|
|
880
|
-
const running = useMemo3(
|
|
881
|
-
() => providers.filter((p) => p.status.running),
|
|
882
|
-
[providers]
|
|
883
|
-
);
|
|
884
|
-
const installed = useMemo3(
|
|
885
|
-
() => providers.filter((p) => p.status.installed && !p.status.running),
|
|
886
|
-
[providers]
|
|
887
|
-
);
|
|
888
|
-
const notInstalled = useMemo3(
|
|
889
|
-
() => providers.filter((p) => !p.status.installed),
|
|
890
|
-
[providers]
|
|
891
|
-
);
|
|
892
|
-
const allProviders = useMemo3(
|
|
893
|
-
() => [...running, ...installed, ...notInstalled],
|
|
894
|
-
[running, installed, notInstalled]
|
|
895
|
-
);
|
|
896
|
-
const totalItems = allProviders.length + 1;
|
|
897
|
-
const backIndex = allProviders.length;
|
|
898
|
-
const [cursorIndex, setCursorIndex] = useState7(backIndex);
|
|
899
|
-
useEffect7(() => {
|
|
900
|
-
setCursorIndex(backIndex);
|
|
901
|
-
}, [backIndex]);
|
|
902
|
-
useInput2((input, key) => {
|
|
903
|
-
if (selectedProvider) return;
|
|
904
|
-
if (input === "q" || key.escape) {
|
|
905
|
-
onBack();
|
|
906
|
-
return;
|
|
907
|
-
}
|
|
908
|
-
if (key.upArrow) {
|
|
909
|
-
setCursorIndex((prev) => Math.max(0, prev - 1));
|
|
910
|
-
} else if (key.downArrow) {
|
|
911
|
-
setCursorIndex((prev) => Math.min(totalItems - 1, prev + 1));
|
|
912
|
-
} else if (key.return) {
|
|
913
|
-
if (cursorIndex === backIndex) {
|
|
914
|
-
onBack();
|
|
915
|
-
} else if (allProviders[cursorIndex]) {
|
|
916
|
-
setSelectedProvider(allProviders[cursorIndex].provider.name);
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
});
|
|
920
|
-
if (selectedProvider) {
|
|
921
|
-
const found = providers.find((p) => p.provider.name === selectedProvider);
|
|
922
|
-
if (found) {
|
|
923
|
-
return /* @__PURE__ */ jsx6(
|
|
924
|
-
ProviderDetailView,
|
|
925
|
-
{
|
|
926
|
-
provider: found.provider,
|
|
927
|
-
onBack: () => setSelectedProvider(null)
|
|
928
|
-
}
|
|
929
|
-
);
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
return /* @__PURE__ */ jsx6(Box5, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
|
|
933
|
-
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "white", underline: true, children: "Manage Providers" }),
|
|
934
|
-
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: "Select a provider to view its setup guide." }),
|
|
935
|
-
loading ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
|
|
936
|
-
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: /* @__PURE__ */ jsx6(Spinner3, { type: "dots" }) }),
|
|
937
|
-
/* @__PURE__ */ jsx6(Text6, { children: " Detecting providers..." })
|
|
938
|
-
] }) : /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
|
|
939
|
-
running.length > 0 && /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
940
|
-
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "green", children: "Running" }),
|
|
941
|
-
running.map(({ provider }, i) => {
|
|
942
|
-
const index = i;
|
|
943
|
-
const isSelected = index === cursorIndex;
|
|
944
|
-
return /* @__PURE__ */ jsxs5(
|
|
945
|
-
Box5,
|
|
946
|
-
{
|
|
947
|
-
flexDirection: "column",
|
|
948
|
-
marginTop: i > 0 ? 1 : 0,
|
|
949
|
-
children: [
|
|
950
|
-
/* @__PURE__ */ jsx6(Box5, { children: /* @__PURE__ */ jsxs5(
|
|
951
|
-
Text6,
|
|
952
|
-
{
|
|
953
|
-
color: isSelected ? "cyan" : "white",
|
|
954
|
-
bold: isSelected,
|
|
955
|
-
children: [
|
|
956
|
-
isSelected ? "\u276F" : " ",
|
|
957
|
-
" ",
|
|
958
|
-
"\u25CF",
|
|
959
|
-
" ",
|
|
960
|
-
provider.displayName
|
|
961
|
-
]
|
|
962
|
-
}
|
|
963
|
-
) }),
|
|
964
|
-
/* @__PURE__ */ jsxs5(Text6, { color: "gray", wrap: "wrap", children: [
|
|
965
|
-
" ",
|
|
966
|
-
provider.description
|
|
967
|
-
] })
|
|
968
|
-
]
|
|
969
|
-
},
|
|
970
|
-
provider.name
|
|
971
|
-
);
|
|
972
|
-
})
|
|
973
|
-
] }),
|
|
974
|
-
installed.length > 0 && /* @__PURE__ */ jsxs5(
|
|
975
|
-
Box5,
|
|
976
|
-
{
|
|
977
|
-
flexDirection: "column",
|
|
978
|
-
marginTop: running.length > 0 ? 1 : 0,
|
|
979
|
-
children: [
|
|
980
|
-
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: "Installed" }),
|
|
981
|
-
installed.map(({ provider, status }, i) => {
|
|
982
|
-
const index = running.length + i;
|
|
983
|
-
const isSelected = index === cursorIndex;
|
|
984
|
-
return /* @__PURE__ */ jsxs5(
|
|
985
|
-
Box5,
|
|
986
|
-
{
|
|
987
|
-
flexDirection: "column",
|
|
988
|
-
marginTop: i > 0 ? 1 : 0,
|
|
989
|
-
children: [
|
|
990
|
-
/* @__PURE__ */ jsx6(Box5, { children: /* @__PURE__ */ jsxs5(
|
|
991
|
-
Text6,
|
|
992
|
-
{
|
|
993
|
-
color: isSelected ? "cyan" : "white",
|
|
994
|
-
bold: isSelected,
|
|
995
|
-
children: [
|
|
996
|
-
isSelected ? "\u276F" : " ",
|
|
997
|
-
" ",
|
|
998
|
-
"\u25CB",
|
|
999
|
-
" ",
|
|
1000
|
-
provider.displayName
|
|
1001
|
-
]
|
|
1002
|
-
}
|
|
1003
|
-
) }),
|
|
1004
|
-
/* @__PURE__ */ jsxs5(Text6, { color: "gray", wrap: "wrap", children: [
|
|
1005
|
-
" ",
|
|
1006
|
-
provider.description
|
|
1007
|
-
] })
|
|
1008
|
-
]
|
|
1009
|
-
},
|
|
1010
|
-
provider.name
|
|
1011
|
-
);
|
|
1012
|
-
})
|
|
1013
|
-
]
|
|
1014
|
-
}
|
|
1015
|
-
),
|
|
1016
|
-
notInstalled.length > 0 && /* @__PURE__ */ jsxs5(
|
|
1017
|
-
Box5,
|
|
1018
|
-
{
|
|
1019
|
-
flexDirection: "column",
|
|
1020
|
-
marginTop: running.length > 0 || installed.length > 0 ? 1 : 0,
|
|
1021
|
-
children: [
|
|
1022
|
-
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "gray", children: "Not Installed" }),
|
|
1023
|
-
notInstalled.map(({ provider }, i) => {
|
|
1024
|
-
const index = running.length + installed.length + i;
|
|
1025
|
-
const isSelected = index === cursorIndex;
|
|
1026
|
-
return /* @__PURE__ */ jsxs5(
|
|
1027
|
-
Box5,
|
|
1028
|
-
{
|
|
1029
|
-
flexDirection: "column",
|
|
1030
|
-
marginTop: i > 0 ? 1 : 0,
|
|
1031
|
-
children: [
|
|
1032
|
-
/* @__PURE__ */ jsx6(Box5, { children: /* @__PURE__ */ jsxs5(
|
|
1033
|
-
Text6,
|
|
1034
|
-
{
|
|
1035
|
-
color: isSelected ? "cyan" : "white",
|
|
1036
|
-
bold: isSelected,
|
|
1037
|
-
children: [
|
|
1038
|
-
isSelected ? "\u276F" : " ",
|
|
1039
|
-
" ",
|
|
1040
|
-
provider.displayName
|
|
1041
|
-
]
|
|
1042
|
-
}
|
|
1043
|
-
) }),
|
|
1044
|
-
/* @__PURE__ */ jsxs5(Text6, { color: "gray", wrap: "wrap", children: [
|
|
1045
|
-
" ",
|
|
1046
|
-
provider.description
|
|
1047
|
-
] })
|
|
1048
|
-
]
|
|
1049
|
-
},
|
|
1050
|
-
provider.name
|
|
1051
|
-
);
|
|
1052
|
-
})
|
|
1053
|
-
]
|
|
1054
|
-
}
|
|
1055
|
-
),
|
|
1056
|
-
/* @__PURE__ */ jsx6(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(
|
|
1057
|
-
Text6,
|
|
1058
|
-
{
|
|
1059
|
-
color: cursorIndex === backIndex ? "cyan" : "white",
|
|
1060
|
-
bold: cursorIndex === backIndex,
|
|
1061
|
-
children: [
|
|
1062
|
-
cursorIndex === backIndex ? "\u276F" : " ",
|
|
1063
|
-
" Back"
|
|
1064
|
-
]
|
|
1065
|
-
}
|
|
1066
|
-
) })
|
|
1067
|
-
] }),
|
|
1068
|
-
/* @__PURE__ */ jsx6(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
|
|
1069
|
-
"Up/Down Navigate ",
|
|
1070
|
-
"\u2022",
|
|
1071
|
-
" Enter Select ",
|
|
1072
|
-
"\u2022",
|
|
1073
|
-
" q/Esc Back"
|
|
1074
|
-
] }) })
|
|
1075
|
-
] }) });
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
// src/tui/pages/OnboardingPage.tsx
|
|
1079
|
-
import { useEffect as useEffect9, useCallback as useCallback7, useState as useState9, useMemo as useMemo4 } from "react";
|
|
1080
|
-
import { Box as Box6, Text as Text7, useInput as useInput3 } from "ink";
|
|
1081
|
-
import Spinner4 from "ink-spinner";
|
|
1082
|
-
import chalk2 from "chalk";
|
|
1083
|
-
|
|
1084
|
-
// src/tui/hooks/useAuth.ts
|
|
1085
|
-
import { useState as useState8, useCallback as useCallback6, useRef as useRef4, useEffect as useEffect8 } from "react";
|
|
1086
|
-
import open from "open";
|
|
1087
|
-
var POLL_INTERVAL = 2e3;
|
|
1088
|
-
var MAX_ATTEMPTS = 30;
|
|
1089
|
-
function useAuth() {
|
|
1090
|
-
const [status, setStatus] = useState8("idle");
|
|
1091
|
-
const [authUrl, setAuthUrl] = useState8(null);
|
|
1092
|
-
const [timeRemaining, setTimeRemaining] = useState8(0);
|
|
1093
|
-
const cancelledRef = useRef4(false);
|
|
1094
|
-
const timerRef = useRef4(null);
|
|
1095
|
-
useEffect8(() => {
|
|
1096
|
-
return () => {
|
|
1097
|
-
cancelledRef.current = true;
|
|
1098
|
-
if (timerRef.current) clearInterval(timerRef.current);
|
|
1099
|
-
};
|
|
1100
|
-
}, []);
|
|
1101
|
-
const cancel = useCallback6(() => {
|
|
1102
|
-
cancelledRef.current = true;
|
|
1103
|
-
if (timerRef.current) {
|
|
1104
|
-
clearInterval(timerRef.current);
|
|
1105
|
-
timerRef.current = null;
|
|
1106
|
-
}
|
|
1107
|
-
setStatus("idle");
|
|
1108
|
-
setAuthUrl(null);
|
|
1109
|
-
setTimeRemaining(0);
|
|
1110
|
-
}, []);
|
|
1111
|
-
const startAuth = useCallback6(() => {
|
|
1112
|
-
cancelledRef.current = false;
|
|
1113
|
-
const run = async () => {
|
|
1114
|
-
try {
|
|
1115
|
-
const { url, token } = await requestDeviceAuth();
|
|
1116
|
-
if (cancelledRef.current) return;
|
|
1117
|
-
setAuthUrl(url);
|
|
1118
|
-
setStatus("waiting");
|
|
1119
|
-
const totalTime = MAX_ATTEMPTS * POLL_INTERVAL / 1e3;
|
|
1120
|
-
setTimeRemaining(totalTime);
|
|
1121
|
-
timerRef.current = setInterval(() => {
|
|
1122
|
-
setTimeRemaining((prev) => {
|
|
1123
|
-
const next = prev - 1;
|
|
1124
|
-
if (next <= 0 && timerRef.current) {
|
|
1125
|
-
clearInterval(timerRef.current);
|
|
1126
|
-
timerRef.current = null;
|
|
1127
|
-
}
|
|
1128
|
-
return Math.max(0, next);
|
|
1129
|
-
});
|
|
1130
|
-
}, 1e3);
|
|
1131
|
-
await open(url);
|
|
1132
|
-
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
|
1133
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
|
|
1134
|
-
if (cancelledRef.current) return;
|
|
1135
|
-
const result = await pollDeviceAuth(token);
|
|
1136
|
-
if (result.status === "completed" && result.apiKey) {
|
|
1137
|
-
if (timerRef.current) clearInterval(timerRef.current);
|
|
1138
|
-
setApiKey(result.apiKey);
|
|
1139
|
-
setStatus("success");
|
|
1140
|
-
return;
|
|
1141
|
-
}
|
|
1142
|
-
if (result.status === "expired") {
|
|
1143
|
-
if (timerRef.current) clearInterval(timerRef.current);
|
|
1144
|
-
setStatus("expired");
|
|
1145
|
-
return;
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
if (!cancelledRef.current) {
|
|
1149
|
-
setStatus("timeout");
|
|
1150
|
-
}
|
|
1151
|
-
} catch {
|
|
1152
|
-
if (!cancelledRef.current) {
|
|
1153
|
-
setStatus("expired");
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
};
|
|
1157
|
-
run();
|
|
1158
|
-
}, []);
|
|
1159
|
-
return {
|
|
1160
|
-
status,
|
|
1161
|
-
authUrl,
|
|
1162
|
-
timeRemaining,
|
|
1163
|
-
startAuth,
|
|
1164
|
-
cancel
|
|
1165
|
-
};
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
// src/tui/pages/OnboardingPage.tsx
|
|
1169
|
-
import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1170
|
-
var SHIMMER_SPEED = 35;
|
|
1171
|
-
function useShimmerLogo() {
|
|
1172
|
-
const [frame, setFrame] = useState9(0);
|
|
1173
|
-
const lines = useMemo4(() => LogoString.split("\n"), []);
|
|
1174
|
-
const totalChars = useMemo4(() => {
|
|
1175
|
-
let count = 0;
|
|
1176
|
-
for (const line of lines) {
|
|
1177
|
-
for (const ch of line) {
|
|
1178
|
-
if (ch !== " " && ch !== " ") count++;
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
return count;
|
|
1182
|
-
}, [lines]);
|
|
1183
|
-
const cycleLength = totalChars + 40;
|
|
1184
|
-
useEffect9(() => {
|
|
1185
|
-
const interval = setInterval(() => {
|
|
1186
|
-
setFrame((f) => (f + 1) % cycleLength);
|
|
1187
|
-
}, SHIMMER_SPEED);
|
|
1188
|
-
return () => clearInterval(interval);
|
|
1189
|
-
}, [cycleLength]);
|
|
1190
|
-
return useMemo4(() => {
|
|
1191
|
-
const sweepPos = frame;
|
|
1192
|
-
const holdEnd = totalChars + 20;
|
|
1193
|
-
let charIdx = 0;
|
|
1194
|
-
return lines.map((line) => {
|
|
1195
|
-
let result = "";
|
|
1196
|
-
for (let i = 0; i < line.length; i++) {
|
|
1197
|
-
const ch = line[i];
|
|
1198
|
-
if (ch === " " || ch === " ") {
|
|
1199
|
-
result += ch;
|
|
1200
|
-
continue;
|
|
1201
|
-
}
|
|
1202
|
-
let brightness;
|
|
1203
|
-
if (sweepPos <= totalChars) {
|
|
1204
|
-
const lag = charIdx;
|
|
1205
|
-
const t = sweepPos - lag;
|
|
1206
|
-
brightness = t <= 0 ? 0.1 : Math.min(1, t / 8);
|
|
1207
|
-
} else if (sweepPos <= holdEnd) {
|
|
1208
|
-
brightness = 1;
|
|
1209
|
-
} else {
|
|
1210
|
-
const fadeProgress = (sweepPos - holdEnd) / (cycleLength - holdEnd);
|
|
1211
|
-
brightness = Math.max(0.1, 1 - fadeProgress);
|
|
1212
|
-
}
|
|
1213
|
-
if (brightness >= 0.9) {
|
|
1214
|
-
result += chalk2.cyanBright.bold(ch);
|
|
1215
|
-
} else if (brightness >= 0.6) {
|
|
1216
|
-
result += chalk2.cyan(ch);
|
|
1217
|
-
} else if (brightness >= 0.3) {
|
|
1218
|
-
result += chalk2.rgb(0, 100, 120)(ch);
|
|
1219
|
-
} else {
|
|
1220
|
-
result += chalk2.rgb(0, 50, 60)(ch);
|
|
1221
|
-
}
|
|
1222
|
-
charIdx++;
|
|
1223
|
-
}
|
|
1224
|
-
return result;
|
|
1225
|
-
}).join("\n");
|
|
1226
|
-
}, [frame, lines, totalChars, cycleLength]);
|
|
1227
|
-
}
|
|
1228
|
-
function OnboardingPage({ onComplete }) {
|
|
1229
|
-
const {
|
|
1230
|
-
status: authStatus,
|
|
1231
|
-
authUrl,
|
|
1232
|
-
timeRemaining,
|
|
1233
|
-
startAuth,
|
|
1234
|
-
cancel: cancelAuth
|
|
1235
|
-
} = useAuth();
|
|
1236
|
-
const shimmerLogo = useShimmerLogo();
|
|
1237
|
-
useEffect9(() => {
|
|
1238
|
-
if (authStatus === "success") {
|
|
1239
|
-
const timer = setTimeout(() => onComplete(), 1500);
|
|
1240
|
-
return () => clearTimeout(timer);
|
|
1241
|
-
}
|
|
1242
|
-
}, [authStatus, onComplete]);
|
|
1243
|
-
useEffect9(() => {
|
|
1244
|
-
return () => cancelAuth();
|
|
1245
|
-
}, []);
|
|
1246
|
-
const handleAction = useCallback7(() => {
|
|
1247
|
-
cancelAuth();
|
|
1248
|
-
startAuth();
|
|
1249
|
-
}, [cancelAuth, startAuth]);
|
|
1250
|
-
const canAct = authStatus === "idle" || authStatus === "expired" || authStatus === "timeout";
|
|
1251
|
-
useInput3((_input, key) => {
|
|
1252
|
-
if (canAct && !key.ctrl) {
|
|
1253
|
-
handleAction();
|
|
1254
|
-
}
|
|
1255
|
-
});
|
|
1256
|
-
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", flexGrow: 1, children: [
|
|
1257
|
-
/* @__PURE__ */ jsx7(Box6, { flexGrow: 1 }),
|
|
1258
|
-
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", alignItems: "center", children: [
|
|
1259
|
-
/* @__PURE__ */ jsx7(Text7, { children: shimmerLogo }),
|
|
1260
|
-
/* @__PURE__ */ jsx7(Box6, { flexDirection: "column", alignItems: "center", marginTop: 2, children: /* @__PURE__ */ jsx7(Text7, { bold: true, color: "white", children: "MindStudio Local Tunnel" }) }),
|
|
1261
|
-
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", alignItems: "center", children: [
|
|
1262
|
-
authStatus === "idle" && /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
1263
|
-
/* @__PURE__ */ jsx7(Text7, { color: "gray", children: "Connect your MindStudio account to get started." }),
|
|
1264
|
-
/* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "cyan", bold: true, children: "Press any key to Connect Account" }) })
|
|
1265
|
-
] }),
|
|
1266
|
-
(authStatus === "expired" || authStatus === "timeout") && /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
1267
|
-
/* @__PURE__ */ jsx7(Text7, { color: "red", children: authStatus === "expired" ? "Authorization expired." : "Authorization timed out." }),
|
|
1268
|
-
/* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "cyan", bold: true, children: "Press any key to Try Again" }) })
|
|
1269
|
-
] }),
|
|
1270
|
-
authStatus === "waiting" && /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
1271
|
-
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
1272
|
-
/* @__PURE__ */ jsx7(Text7, { color: "cyan", children: /* @__PURE__ */ jsx7(Spinner4, { type: "dots" }) }),
|
|
1273
|
-
/* @__PURE__ */ jsxs6(Text7, { children: [
|
|
1274
|
-
" ",
|
|
1275
|
-
"Waiting for browser authorization... (",
|
|
1276
|
-
timeRemaining,
|
|
1277
|
-
"s remaining)"
|
|
1278
|
-
] })
|
|
1279
|
-
] }),
|
|
1280
|
-
authUrl && /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", alignItems: "center", marginTop: 1, children: [
|
|
1281
|
-
/* @__PURE__ */ jsx7(Text7, { color: "gray", children: "If browser didn't open, visit:" }),
|
|
1282
|
-
/* @__PURE__ */ jsx7(Text7, { color: "cyan", children: authUrl })
|
|
1283
|
-
] })
|
|
1284
|
-
] }),
|
|
1285
|
-
authStatus === "success" && /* @__PURE__ */ jsxs6(Text7, { color: "green", children: [
|
|
1286
|
-
"\u2713",
|
|
1287
|
-
" Authenticated!"
|
|
1288
|
-
] })
|
|
1289
|
-
] })
|
|
1290
|
-
] }),
|
|
1291
|
-
/* @__PURE__ */ jsx7(Box6, { flexGrow: 1 })
|
|
1292
|
-
] });
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
// src/tui/App.tsx
|
|
1296
|
-
import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1297
|
-
var MODEL_TYPE_MAP = {
|
|
1298
|
-
text: "llm_chat",
|
|
1299
|
-
image: "image_generation",
|
|
1300
|
-
video: "video_generation"
|
|
1301
|
-
};
|
|
1302
|
-
function App({ runner }) {
|
|
1303
|
-
const { exit } = useApp();
|
|
1304
|
-
const { stdout } = useStdout6();
|
|
1305
|
-
const {
|
|
1306
|
-
status: connectionStatus,
|
|
1307
|
-
environment,
|
|
1308
|
-
error: connectionError,
|
|
1309
|
-
retry: retryConnection
|
|
1310
|
-
} = useConnection();
|
|
1311
|
-
const {
|
|
1312
|
-
providers,
|
|
1313
|
-
loading: providersLoading,
|
|
1314
|
-
refresh: refreshProviders
|
|
1315
|
-
} = useSetupProviders();
|
|
1316
|
-
const {
|
|
1317
|
-
models,
|
|
1318
|
-
warnings: modelWarnings,
|
|
1319
|
-
loading: modelsLoading,
|
|
1320
|
-
refresh: refreshModels
|
|
1321
|
-
} = useModels();
|
|
1322
|
-
const { requests } = useRequests();
|
|
1323
|
-
const {
|
|
1324
|
-
syncedNames,
|
|
1325
|
-
syncedModels,
|
|
1326
|
-
refresh: refreshSynced
|
|
1327
|
-
} = useSyncedModels(connectionStatus);
|
|
1328
|
-
const shouldOnboard = getApiKey() === void 0;
|
|
1329
|
-
const [page, setPage] = useState10(
|
|
1330
|
-
shouldOnboard ? "onboarding" : "dashboard"
|
|
1331
|
-
);
|
|
1332
|
-
const [syncStatus, setSyncStatus] = useState10(
|
|
1333
|
-
"idle"
|
|
1334
|
-
);
|
|
1335
|
-
const syncTimerRef = useRef5();
|
|
1336
|
-
const lastSyncPayloadRef = useRef5("");
|
|
1337
|
-
useEffect10(() => {
|
|
1338
|
-
if (connectionStatus === "not_authenticated") {
|
|
1339
|
-
setPage("onboarding");
|
|
1340
|
-
}
|
|
1341
|
-
}, [connectionStatus]);
|
|
1342
|
-
useEffect10(() => {
|
|
1343
|
-
if (page === "dashboard") {
|
|
1344
|
-
refreshAll();
|
|
1345
|
-
}
|
|
1346
|
-
}, [page]);
|
|
1347
|
-
useEffect10(() => {
|
|
1348
|
-
if (connectionStatus === "connected" && syncedModels.length > 0) {
|
|
1349
|
-
runner.start(syncedModels);
|
|
1350
|
-
}
|
|
1351
|
-
}, [connectionStatus, syncedModels, runner]);
|
|
1352
|
-
useEffect10(() => () => runner.stop(), [runner]);
|
|
1353
|
-
const refreshAll = useCallback8(
|
|
1354
|
-
async (silent = false) => {
|
|
1355
|
-
if (!silent) setSyncStatus("syncing");
|
|
1356
|
-
const [discoveredModels] = await Promise.all([
|
|
1357
|
-
refreshModels(),
|
|
1358
|
-
refreshProviders()
|
|
1359
|
-
]);
|
|
1360
|
-
const modelsToSync = discoveredModels.filter((m) => !m.statusHint).map((m) => ({
|
|
1361
|
-
name: m.name,
|
|
1362
|
-
provider: m.provider,
|
|
1363
|
-
type: MODEL_TYPE_MAP[m.capability] || "llm_chat",
|
|
1364
|
-
parameters: m.parameters
|
|
1365
|
-
}));
|
|
1366
|
-
const payload = JSON.stringify(modelsToSync);
|
|
1367
|
-
if (payload !== lastSyncPayloadRef.current && modelsToSync.length > 0) {
|
|
1368
|
-
try {
|
|
1369
|
-
await syncModels(modelsToSync);
|
|
1370
|
-
lastSyncPayloadRef.current = payload;
|
|
1371
|
-
} catch {
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
await refreshSynced();
|
|
1375
|
-
if (!silent) {
|
|
1376
|
-
setSyncStatus("synced");
|
|
1377
|
-
clearTimeout(syncTimerRef.current);
|
|
1378
|
-
syncTimerRef.current = setTimeout(() => setSyncStatus("idle"), 1500);
|
|
1379
|
-
}
|
|
1380
|
-
},
|
|
1381
|
-
[refreshProviders, refreshModels, refreshSynced]
|
|
1382
|
-
);
|
|
1383
|
-
useEffect10(() => {
|
|
1384
|
-
if (connectionStatus !== "connected" || page !== "dashboard") return;
|
|
1385
|
-
const interval = setInterval(() => refreshAll(true), 1500);
|
|
1386
|
-
return () => clearInterval(interval);
|
|
1387
|
-
}, [connectionStatus, page, refreshAll]);
|
|
1388
|
-
const handleQuit = useCallback8(() => {
|
|
1389
|
-
runner.stop();
|
|
1390
|
-
exit();
|
|
1391
|
-
}, [runner, exit]);
|
|
1392
|
-
const handleOnboardingComplete = useCallback8(() => {
|
|
1393
|
-
retryConnection();
|
|
1394
|
-
refreshAll();
|
|
1395
|
-
setPage("dashboard");
|
|
1396
|
-
}, [retryConnection, refreshAll]);
|
|
1397
|
-
const handleNavigate = useCallback8(
|
|
1398
|
-
(id) => {
|
|
1399
|
-
switch (id) {
|
|
1400
|
-
case "auth":
|
|
1401
|
-
setPage("onboarding");
|
|
1402
|
-
break;
|
|
1403
|
-
case "setup":
|
|
1404
|
-
setPage("setup");
|
|
1405
|
-
break;
|
|
1406
|
-
case "refresh":
|
|
1407
|
-
refreshAll();
|
|
1408
|
-
break;
|
|
1409
|
-
case "quit":
|
|
1410
|
-
handleQuit();
|
|
1411
|
-
break;
|
|
1412
|
-
}
|
|
1413
|
-
},
|
|
1414
|
-
[refreshAll, handleQuit]
|
|
1415
|
-
);
|
|
1416
|
-
const subpageMenuItems = [
|
|
1417
|
-
{ id: "back", label: "Back", description: "Return to dashboard" }
|
|
1418
|
-
];
|
|
1419
|
-
const handleSubpageNavigate = useCallback8(
|
|
1420
|
-
(id) => {
|
|
1421
|
-
if (id === "back") {
|
|
1422
|
-
setPage("dashboard");
|
|
1423
|
-
} else {
|
|
1424
|
-
handleNavigate(id);
|
|
1425
|
-
}
|
|
1426
|
-
},
|
|
1427
|
-
[handleNavigate]
|
|
1428
|
-
);
|
|
1429
|
-
const [termSize, setTermSize] = useState10({
|
|
1430
|
-
rows: stdout?.rows ?? 24,
|
|
1431
|
-
columns: stdout?.columns ?? 80
|
|
1432
|
-
});
|
|
1433
|
-
useEffect10(() => {
|
|
1434
|
-
if (!stdout) return;
|
|
1435
|
-
const onResize = () => {
|
|
1436
|
-
stdout.write("\x1B[2J\x1B[H");
|
|
1437
|
-
setTermSize({ rows: stdout.rows, columns: stdout.columns });
|
|
1438
|
-
};
|
|
1439
|
-
stdout.on("resize", onResize);
|
|
1440
|
-
return () => {
|
|
1441
|
-
stdout.off("resize", onResize);
|
|
1442
|
-
};
|
|
1443
|
-
}, [stdout]);
|
|
1444
|
-
const termHeight = termSize.rows - 4;
|
|
1445
|
-
const compactHeader = termSize.rows <= 45 || termSize.columns <= 90;
|
|
1446
|
-
return /* @__PURE__ */ jsx8(Box7, { flexDirection: "column", height: termHeight, overflow: "hidden", children: page === "onboarding" ? /* @__PURE__ */ jsx8(OnboardingPage, { onComplete: handleOnboardingComplete }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
1447
|
-
/* @__PURE__ */ jsx8(
|
|
1448
|
-
Header,
|
|
1449
|
-
{
|
|
1450
|
-
connection: connectionStatus,
|
|
1451
|
-
environment,
|
|
1452
|
-
configPath: getConfigPath(),
|
|
1453
|
-
connectionError,
|
|
1454
|
-
compact: compactHeader
|
|
1455
|
-
}
|
|
1456
|
-
),
|
|
1457
|
-
page === "dashboard" && /* @__PURE__ */ jsx8(
|
|
1458
|
-
DashboardPage,
|
|
1459
|
-
{
|
|
1460
|
-
requests,
|
|
1461
|
-
models,
|
|
1462
|
-
modelWarnings,
|
|
1463
|
-
providers,
|
|
1464
|
-
providersLoading,
|
|
1465
|
-
syncedNames,
|
|
1466
|
-
modelsLoading,
|
|
1467
|
-
syncStatus,
|
|
1468
|
-
onNavigate: handleNavigate
|
|
1469
|
-
}
|
|
1470
|
-
),
|
|
1471
|
-
page === "setup" && /* @__PURE__ */ jsx8(SetupPage, { onBack: () => setPage("dashboard") }),
|
|
1472
|
-
page !== "dashboard" && page !== "setup" && /* @__PURE__ */ jsx8(Box7, { flexGrow: 1 }),
|
|
1473
|
-
page !== "dashboard" && page !== "setup" && /* @__PURE__ */ jsx8(
|
|
1474
|
-
NavigationMenu,
|
|
1475
|
-
{
|
|
1476
|
-
items: subpageMenuItems,
|
|
1477
|
-
onSelect: handleSubpageNavigate
|
|
1478
|
-
}
|
|
1479
|
-
)
|
|
1480
|
-
] }) });
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
// src/update.ts
|
|
1484
|
-
import { createRequire as createRequire2 } from "module";
|
|
1485
|
-
var require3 = createRequire2(import.meta.url);
|
|
1486
|
-
var pkg2 = require3("../package.json");
|
|
1487
|
-
function getCurrentVersion() {
|
|
1488
|
-
return pkg2.version;
|
|
1489
|
-
}
|
|
1490
|
-
async function fetchLatestVersion() {
|
|
1491
|
-
try {
|
|
1492
|
-
const res = await fetch(
|
|
1493
|
-
"https://registry.npmjs.org/@mindstudio-ai/local-model-tunnel/latest",
|
|
1494
|
-
{ signal: AbortSignal.timeout(5e3) }
|
|
1495
|
-
);
|
|
1496
|
-
if (!res.ok) return null;
|
|
1497
|
-
const data = await res.json();
|
|
1498
|
-
return data.version ?? null;
|
|
1499
|
-
} catch {
|
|
1500
|
-
return null;
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
function isNewerVersion(current, latest) {
|
|
1504
|
-
const currentParts = current.split(".").map(Number);
|
|
1505
|
-
const latestParts = latest.split(".").map(Number);
|
|
1506
|
-
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
|
|
1507
|
-
const c = currentParts[i] ?? 0;
|
|
1508
|
-
const l = latestParts[i] ?? 0;
|
|
1509
|
-
if (l > c) return true;
|
|
1510
|
-
if (l < c) return false;
|
|
1511
|
-
}
|
|
1512
|
-
return false;
|
|
1513
|
-
}
|
|
1514
|
-
async function checkForUpdate() {
|
|
1515
|
-
const currentVersion = getCurrentVersion();
|
|
1516
|
-
const latestVersion = await fetchLatestVersion();
|
|
1517
|
-
if (!latestVersion) return null;
|
|
1518
|
-
if (!isNewerVersion(currentVersion, latestVersion)) return null;
|
|
1519
|
-
return { currentVersion, latestVersion };
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
// src/tui/components/UpdatePrompt.tsx
|
|
1523
|
-
import { Box as Box8, Text as Text8, useInput as useInput4 } from "ink";
|
|
1524
|
-
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1525
|
-
function UpdatePrompt({
|
|
1526
|
-
currentVersion,
|
|
1527
|
-
latestVersion,
|
|
1528
|
-
onChoice
|
|
1529
|
-
}) {
|
|
1530
|
-
useInput4((input) => {
|
|
1531
|
-
if (input.toLowerCase() === "y") {
|
|
1532
|
-
onChoice(true);
|
|
1533
|
-
} else {
|
|
1534
|
-
onChoice(false);
|
|
1535
|
-
}
|
|
1536
|
-
});
|
|
1537
|
-
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
|
|
1538
|
-
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
1539
|
-
/* @__PURE__ */ jsx9(Text8, { color: "yellow", bold: true, children: "Update available:" }),
|
|
1540
|
-
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
1541
|
-
" ",
|
|
1542
|
-
"v",
|
|
1543
|
-
currentVersion,
|
|
1544
|
-
" ",
|
|
1545
|
-
"\u2192",
|
|
1546
|
-
" v",
|
|
1547
|
-
latestVersion
|
|
1548
|
-
] })
|
|
1549
|
-
] }),
|
|
1550
|
-
/* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
|
|
1551
|
-
"Press ",
|
|
1552
|
-
/* @__PURE__ */ jsx9(Text8, { bold: true, color: "cyan", children: "y" }),
|
|
1553
|
-
" to update, any other key to skip"
|
|
1554
|
-
] }) })
|
|
1555
|
-
] });
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
// src/tui/index.tsx
|
|
1559
|
-
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
1560
|
-
async function promptForUpdate(currentVersion, latestVersion) {
|
|
1561
|
-
return new Promise((resolve) => {
|
|
1562
|
-
const { unmount } = render(
|
|
1563
|
-
/* @__PURE__ */ jsx10(
|
|
1564
|
-
UpdatePrompt,
|
|
1565
|
-
{
|
|
1566
|
-
currentVersion,
|
|
1567
|
-
latestVersion,
|
|
1568
|
-
onChoice: (shouldUpdate) => {
|
|
1569
|
-
unmount();
|
|
1570
|
-
resolve(shouldUpdate);
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
),
|
|
1574
|
-
{ exitOnCtrlC: true }
|
|
1575
|
-
);
|
|
1576
|
-
});
|
|
1577
|
-
}
|
|
1578
|
-
async function startTUI() {
|
|
1579
|
-
console.clear();
|
|
1580
|
-
const update = await checkForUpdate();
|
|
1581
|
-
if (update) {
|
|
1582
|
-
const shouldUpdate = await promptForUpdate(
|
|
1583
|
-
update.currentVersion,
|
|
1584
|
-
update.latestVersion
|
|
1585
|
-
);
|
|
1586
|
-
if (shouldUpdate) {
|
|
1587
|
-
console.log("\nUpdating to v" + update.latestVersion + "...\n");
|
|
1588
|
-
try {
|
|
1589
|
-
execSync("npm install -g @mindstudio-ai/local-model-tunnel@latest", {
|
|
1590
|
-
stdio: "inherit"
|
|
1591
|
-
});
|
|
1592
|
-
console.log("\nRestarting...\n");
|
|
1593
|
-
execFileSync(process.execPath, process.argv.slice(1), {
|
|
1594
|
-
stdio: "inherit"
|
|
1595
|
-
});
|
|
1596
|
-
} catch {
|
|
1597
|
-
console.error("\nUpdate failed. Continuing with current version.\n");
|
|
1598
|
-
}
|
|
1599
|
-
return;
|
|
1600
|
-
}
|
|
1601
|
-
console.clear();
|
|
1602
|
-
}
|
|
1603
|
-
const runner = new TunnelRunner();
|
|
1604
|
-
const { waitUntilExit } = render(
|
|
1605
|
-
/* @__PURE__ */ jsx10(App, { runner }),
|
|
1606
|
-
{
|
|
1607
|
-
exitOnCtrlC: true
|
|
1608
|
-
}
|
|
1609
|
-
);
|
|
1610
|
-
await waitUntilExit();
|
|
1611
|
-
runner.stop();
|
|
1612
|
-
}
|
|
1613
|
-
export {
|
|
1614
|
-
startTUI
|
|
1615
|
-
};
|
|
1616
|
-
//# sourceMappingURL=tui-4ZB4JAV4.js.map
|