@mindstudio-ai/local-model-tunnel 0.3.4 → 0.4.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/{chunk-PPP2FEIN.js → chunk-44NXQXRB.js} +54 -2
- package/dist/{chunk-PPP2FEIN.js.map → chunk-44NXQXRB.js.map} +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{tui-4ZB4JAV4.js → tui-BW6XKMWK.js} +1082 -102
- package/dist/tui-BW6XKMWK.js.map +1 -0
- package/package.json +1 -1
- package/dist/tui-4ZB4JAV4.js.map +0 -1
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
import {
|
|
2
2
|
TunnelRunner,
|
|
3
|
+
deleteLocalInterfacePath,
|
|
3
4
|
detectAllProviderStatuses,
|
|
4
5
|
discoverAllModelsWithParameters,
|
|
6
|
+
getApiBaseUrl,
|
|
5
7
|
getApiKey,
|
|
6
8
|
getConfigPath,
|
|
9
|
+
getEditorSessions,
|
|
7
10
|
getEnvironment,
|
|
11
|
+
getLocalInterfacePath,
|
|
12
|
+
getLocalInterfacesDir,
|
|
8
13
|
getSyncedModels,
|
|
14
|
+
getUserId,
|
|
9
15
|
pollDeviceAuth,
|
|
10
16
|
requestDeviceAuth,
|
|
11
17
|
requestEvents,
|
|
12
18
|
setApiKey,
|
|
19
|
+
setLocalInterfacePath,
|
|
20
|
+
setUserId,
|
|
13
21
|
syncModels,
|
|
14
22
|
verifyApiKey
|
|
15
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-44NXQXRB.js";
|
|
16
24
|
|
|
17
25
|
// src/tui/index.tsx
|
|
18
26
|
import { render } from "ink";
|
|
19
|
-
import { execFileSync, execSync } from "child_process";
|
|
27
|
+
import { execFileSync, execSync as execSync2 } from "child_process";
|
|
20
28
|
|
|
21
29
|
// src/tui/App.tsx
|
|
22
|
-
import { useEffect as
|
|
23
|
-
import { Box as
|
|
30
|
+
import { useEffect as useEffect15, useCallback as useCallback10, useState as useState15, useRef as useRef9 } from "react";
|
|
31
|
+
import { Box as Box10, useApp, useStdout as useStdout7 } from "ink";
|
|
24
32
|
|
|
25
33
|
// src/tui/components/Header.tsx
|
|
26
34
|
import os from "os";
|
|
@@ -111,8 +119,6 @@ function NavigationMenu({ items, onSelect, title }) {
|
|
|
111
119
|
const { stdout } = useStdout();
|
|
112
120
|
const compact = (stdout?.rows ?? 24) < 40;
|
|
113
121
|
const getDefaultIndex = () => {
|
|
114
|
-
const backIdx = items.findIndex((i) => i.id === "back");
|
|
115
|
-
if (backIdx >= 0) return backIdx;
|
|
116
122
|
const firstIdx = items.findIndex((i) => !i.disabled && !i.isSeparator);
|
|
117
123
|
return firstIdx >= 0 ? firstIdx : 0;
|
|
118
124
|
};
|
|
@@ -212,7 +218,8 @@ function useConnection() {
|
|
|
212
218
|
setStatus("connecting");
|
|
213
219
|
setError(null);
|
|
214
220
|
const apiKey = getApiKey();
|
|
215
|
-
|
|
221
|
+
const userId = getUserId();
|
|
222
|
+
if (!apiKey || !userId) {
|
|
216
223
|
setStatus("not_authenticated");
|
|
217
224
|
return;
|
|
218
225
|
}
|
|
@@ -239,7 +246,7 @@ function useConnection() {
|
|
|
239
246
|
};
|
|
240
247
|
}
|
|
241
248
|
|
|
242
|
-
// src/tui/hooks/useSetupProviders.ts
|
|
249
|
+
// src/tui/models/hooks/useSetupProviders.ts
|
|
243
250
|
import { useState as useState3, useEffect as useEffect3, useCallback as useCallback2, useRef } from "react";
|
|
244
251
|
function useSetupProviders() {
|
|
245
252
|
const [providers, setProviders] = useState3([]);
|
|
@@ -264,7 +271,7 @@ function useSetupProviders() {
|
|
|
264
271
|
return { providers, loading, refreshing, refresh };
|
|
265
272
|
}
|
|
266
273
|
|
|
267
|
-
// src/tui/hooks/useModels.ts
|
|
274
|
+
// src/tui/models/hooks/useModels.ts
|
|
268
275
|
import { useState as useState4, useEffect as useEffect4, useCallback as useCallback3, useRef as useRef2 } from "react";
|
|
269
276
|
function useModels() {
|
|
270
277
|
const [models, setModels] = useState4([]);
|
|
@@ -303,7 +310,7 @@ function useModels() {
|
|
|
303
310
|
};
|
|
304
311
|
}
|
|
305
312
|
|
|
306
|
-
// src/tui/hooks/useRequests.ts
|
|
313
|
+
// src/tui/models/hooks/useRequests.ts
|
|
307
314
|
import { useState as useState5, useEffect as useEffect5, useCallback as useCallback4, useRef as useRef3 } from "react";
|
|
308
315
|
function useRequests(maxHistory = 50) {
|
|
309
316
|
const [requests, setRequests] = useState5([]);
|
|
@@ -379,7 +386,7 @@ function useRequests(maxHistory = 50) {
|
|
|
379
386
|
};
|
|
380
387
|
}
|
|
381
388
|
|
|
382
|
-
// src/tui/hooks/useRegisteredModels.ts
|
|
389
|
+
// src/tui/models/hooks/useRegisteredModels.ts
|
|
383
390
|
import { useState as useState6, useEffect as useEffect6, useCallback as useCallback5 } from "react";
|
|
384
391
|
function useSyncedModels(connectionStatus) {
|
|
385
392
|
const [syncedNames, setSyncedNames] = useState6(
|
|
@@ -409,12 +416,12 @@ function useSyncedModels(connectionStatus) {
|
|
|
409
416
|
};
|
|
410
417
|
}
|
|
411
418
|
|
|
412
|
-
// src/tui/pages/DashboardPage.tsx
|
|
419
|
+
// src/tui/models/pages/DashboardPage.tsx
|
|
413
420
|
import { useMemo } from "react";
|
|
414
421
|
import { Box as Box4, Text as Text4, useStdout as useStdout3 } from "ink";
|
|
415
422
|
import Spinner2 from "ink-spinner";
|
|
416
423
|
|
|
417
|
-
// src/tui/components/RequestLog.tsx
|
|
424
|
+
// src/tui/models/components/RequestLog.tsx
|
|
418
425
|
import { Box as Box3, Text as Text3, useStdout as useStdout2 } from "ink";
|
|
419
426
|
import Spinner from "ink-spinner";
|
|
420
427
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -580,7 +587,7 @@ function RequestLog({ requests, maxVisible = 8, hasModels = true }) {
|
|
|
580
587
|
);
|
|
581
588
|
}
|
|
582
589
|
|
|
583
|
-
// src/tui/pages/DashboardPage.tsx
|
|
590
|
+
// src/tui/models/pages/DashboardPage.tsx
|
|
584
591
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
585
592
|
function getWorkflowCount(model) {
|
|
586
593
|
const param = model.parameters?.find((p) => p.type === "comfyWorkflow");
|
|
@@ -624,6 +631,11 @@ function DashboardPage({
|
|
|
624
631
|
const syncDescription = syncStatus === "syncing" ? "Syncing..." : syncStatus === "synced" ? "\u2713 Synced" : "Re-detect providers and sync models to MindStudio";
|
|
625
632
|
const menuItems = useMemo(() => {
|
|
626
633
|
return [
|
|
634
|
+
{
|
|
635
|
+
id: "interfaces",
|
|
636
|
+
label: "Connect to IDE",
|
|
637
|
+
description: "Connect your local editor to a MindStudio interface or script"
|
|
638
|
+
},
|
|
627
639
|
{
|
|
628
640
|
id: "refresh",
|
|
629
641
|
label: "Sync Models",
|
|
@@ -735,7 +747,7 @@ function DashboardPage({
|
|
|
735
747
|
] });
|
|
736
748
|
}
|
|
737
749
|
|
|
738
|
-
// src/tui/pages/SetupPage.tsx
|
|
750
|
+
// src/tui/models/pages/SetupPage.tsx
|
|
739
751
|
import { useState as useState7, useMemo as useMemo3, useEffect as useEffect7 } from "react";
|
|
740
752
|
import { Box as Box5, Text as Text6, useInput as useInput2, useStdout as useStdout5 } from "ink";
|
|
741
753
|
import Spinner3 from "ink-spinner";
|
|
@@ -775,7 +787,7 @@ function renderMarkdown(content, width) {
|
|
|
775
787
|
return marked.parse(content).trimEnd();
|
|
776
788
|
}
|
|
777
789
|
|
|
778
|
-
// src/tui/pages/SetupPage.tsx
|
|
790
|
+
// src/tui/models/pages/SetupPage.tsx
|
|
779
791
|
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
780
792
|
function ProviderDetailView({
|
|
781
793
|
provider,
|
|
@@ -895,10 +907,10 @@ function SetupPage({ onBack }) {
|
|
|
895
907
|
);
|
|
896
908
|
const totalItems = allProviders.length + 1;
|
|
897
909
|
const backIndex = allProviders.length;
|
|
898
|
-
const [cursorIndex, setCursorIndex] = useState7(
|
|
910
|
+
const [cursorIndex, setCursorIndex] = useState7(0);
|
|
899
911
|
useEffect7(() => {
|
|
900
|
-
setCursorIndex(
|
|
901
|
-
}, [
|
|
912
|
+
setCursorIndex(0);
|
|
913
|
+
}, [allProviders.length]);
|
|
902
914
|
useInput2((input, key) => {
|
|
903
915
|
if (selectedProvider) return;
|
|
904
916
|
if (input === "q" || key.escape) {
|
|
@@ -1075,30 +1087,986 @@ function SetupPage({ onBack }) {
|
|
|
1075
1087
|
] }) });
|
|
1076
1088
|
}
|
|
1077
1089
|
|
|
1078
|
-
// src/tui/pages/
|
|
1079
|
-
import {
|
|
1090
|
+
// src/tui/interfaces/pages/InterfacesPage.tsx
|
|
1091
|
+
import { useState as useState12, useMemo as useMemo5, useEffect as useEffect12, useRef as useRef7 } from "react";
|
|
1092
|
+
import { Box as Box8, Text as Text9, useInput as useInput5 } from "ink";
|
|
1093
|
+
import Spinner5 from "ink-spinner";
|
|
1094
|
+
|
|
1095
|
+
// src/tui/interfaces/hooks/useEditorSessions.ts
|
|
1096
|
+
import { useState as useState8, useEffect as useEffect8, useCallback as useCallback6, useRef as useRef4 } from "react";
|
|
1097
|
+
function useEditorSessions() {
|
|
1098
|
+
const [sessions, setSessions] = useState8([]);
|
|
1099
|
+
const [loading, setLoading] = useState8(true);
|
|
1100
|
+
const [error, setError] = useState8(null);
|
|
1101
|
+
const [refreshStatus, setRefreshStatus] = useState8("idle");
|
|
1102
|
+
const initialLoadDone = useRef4(false);
|
|
1103
|
+
const timerRef = useRef4();
|
|
1104
|
+
const refresh = useCallback6(async () => {
|
|
1105
|
+
if (!initialLoadDone.current) {
|
|
1106
|
+
setLoading(true);
|
|
1107
|
+
} else {
|
|
1108
|
+
setRefreshStatus("refreshing");
|
|
1109
|
+
}
|
|
1110
|
+
setError(null);
|
|
1111
|
+
try {
|
|
1112
|
+
const data = await getEditorSessions();
|
|
1113
|
+
setSessions(data);
|
|
1114
|
+
initialLoadDone.current = true;
|
|
1115
|
+
setRefreshStatus("refreshed");
|
|
1116
|
+
clearTimeout(timerRef.current);
|
|
1117
|
+
timerRef.current = setTimeout(() => setRefreshStatus("idle"), 1500);
|
|
1118
|
+
} catch (err) {
|
|
1119
|
+
setError(err instanceof Error ? err.message : "Failed to fetch sessions");
|
|
1120
|
+
setRefreshStatus("idle");
|
|
1121
|
+
} finally {
|
|
1122
|
+
setLoading(false);
|
|
1123
|
+
}
|
|
1124
|
+
}, []);
|
|
1125
|
+
useEffect8(() => {
|
|
1126
|
+
refresh();
|
|
1127
|
+
}, [refresh]);
|
|
1128
|
+
useEffect8(() => {
|
|
1129
|
+
return () => clearTimeout(timerRef.current);
|
|
1130
|
+
}, []);
|
|
1131
|
+
return { sessions, loading, error, refreshStatus, refresh };
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// src/tui/interfaces/hooks/useLocalInterface.ts
|
|
1135
|
+
import { useState as useState9, useCallback as useCallback7, useRef as useRef5, useEffect as useEffect9 } from "react";
|
|
1136
|
+
import { spawn } from "child_process";
|
|
1137
|
+
import crypto from "crypto";
|
|
1138
|
+
import fs from "fs";
|
|
1139
|
+
import path from "path";
|
|
1140
|
+
var INTERFACE_SCAFFOLD_REPO = "https://github.com/mindstudio-ai/spa-bundle-scaffold";
|
|
1141
|
+
var SCRIPT_SCAFFOLD_REPO = "https://github.com/mindstudio-ai/script-scaffold";
|
|
1142
|
+
var MAX_OUTPUT_LINES = 500;
|
|
1143
|
+
function useLocalInterface({
|
|
1144
|
+
mode,
|
|
1145
|
+
appId,
|
|
1146
|
+
stepId,
|
|
1147
|
+
workflowId,
|
|
1148
|
+
sessionId
|
|
1149
|
+
}) {
|
|
1150
|
+
const key = `${appId}:${stepId}`;
|
|
1151
|
+
const [hasLocalCopy, setHasLocalCopy] = useState9(() => {
|
|
1152
|
+
const existing = getLocalInterfacePath(key);
|
|
1153
|
+
if (!existing) return false;
|
|
1154
|
+
try {
|
|
1155
|
+
return fs.existsSync(existing);
|
|
1156
|
+
} catch {
|
|
1157
|
+
return false;
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
const [phase, setPhase] = useState9("idle");
|
|
1161
|
+
const [outputLines, setOutputLines] = useState9([]);
|
|
1162
|
+
const [errorMessage, setErrorMessage] = useState9(null);
|
|
1163
|
+
const processRef = useRef5(null);
|
|
1164
|
+
const mountedRef = useRef5(true);
|
|
1165
|
+
const stoppedRef = useRef5(false);
|
|
1166
|
+
useEffect9(() => {
|
|
1167
|
+
mountedRef.current = true;
|
|
1168
|
+
return () => {
|
|
1169
|
+
mountedRef.current = false;
|
|
1170
|
+
if (processRef.current) {
|
|
1171
|
+
processRef.current.kill("SIGTERM");
|
|
1172
|
+
processRef.current = null;
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
}, []);
|
|
1176
|
+
const appendOutput = useCallback7((line) => {
|
|
1177
|
+
if (!mountedRef.current) return;
|
|
1178
|
+
setOutputLines((prev) => {
|
|
1179
|
+
const next = [...prev, line];
|
|
1180
|
+
return next.length > MAX_OUTPUT_LINES ? next.slice(next.length - MAX_OUTPUT_LINES) : next;
|
|
1181
|
+
});
|
|
1182
|
+
}, []);
|
|
1183
|
+
const runCommand = useCallback7(
|
|
1184
|
+
(command, args, options = {}) => {
|
|
1185
|
+
return new Promise((resolve, reject) => {
|
|
1186
|
+
const fullCommand = [command, ...args].join(" ");
|
|
1187
|
+
const proc = spawn(fullCommand, [], {
|
|
1188
|
+
cwd: options.cwd,
|
|
1189
|
+
shell: true,
|
|
1190
|
+
env: { ...process.env, FORCE_COLOR: "1", ...options.env }
|
|
1191
|
+
});
|
|
1192
|
+
processRef.current = proc;
|
|
1193
|
+
const handleData = (data) => {
|
|
1194
|
+
const lines = data.toString().split("\n");
|
|
1195
|
+
for (const line of lines) {
|
|
1196
|
+
if (line.length > 0) {
|
|
1197
|
+
appendOutput(line);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
proc.stdout?.on("data", handleData);
|
|
1202
|
+
proc.stderr?.on("data", handleData);
|
|
1203
|
+
proc.on("close", (code) => {
|
|
1204
|
+
processRef.current = null;
|
|
1205
|
+
resolve(code ?? 0);
|
|
1206
|
+
});
|
|
1207
|
+
proc.on("error", (err) => {
|
|
1208
|
+
processRef.current = null;
|
|
1209
|
+
reject(err);
|
|
1210
|
+
});
|
|
1211
|
+
});
|
|
1212
|
+
},
|
|
1213
|
+
[appendOutput]
|
|
1214
|
+
);
|
|
1215
|
+
const getScaffoldRepo = () => mode === "script" ? SCRIPT_SCAFFOLD_REPO : INTERFACE_SCAFFOLD_REPO;
|
|
1216
|
+
const getDirPrefix = () => mode === "script" ? "script" : "interface";
|
|
1217
|
+
const getDevLocalArgs = () => {
|
|
1218
|
+
const env = {};
|
|
1219
|
+
if (getEnvironment() === "local") {
|
|
1220
|
+
env.MINDSTUDIO_API_URL = getApiBaseUrl();
|
|
1221
|
+
}
|
|
1222
|
+
if (mode === "script") {
|
|
1223
|
+
env.MINDSTUDIO_API_KEY = getApiKey() ?? "";
|
|
1224
|
+
return {
|
|
1225
|
+
args: ["run", "dev:local", "--", "--app", appId, "--workflow", workflowId, "--step", stepId],
|
|
1226
|
+
env
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
return {
|
|
1230
|
+
args: ["run", "dev:local", "--", sessionId ?? ""],
|
|
1231
|
+
env
|
|
1232
|
+
};
|
|
1233
|
+
};
|
|
1234
|
+
const start = useCallback7(() => {
|
|
1235
|
+
setErrorMessage(null);
|
|
1236
|
+
setOutputLines([]);
|
|
1237
|
+
stoppedRef.current = false;
|
|
1238
|
+
const run = async () => {
|
|
1239
|
+
const localPath = getLocalInterfacePath(key);
|
|
1240
|
+
const dirExists = localPath && fs.existsSync(localPath);
|
|
1241
|
+
try {
|
|
1242
|
+
if (!dirExists) {
|
|
1243
|
+
setPhase("cloning");
|
|
1244
|
+
const interfacesDir = getLocalInterfacesDir();
|
|
1245
|
+
fs.mkdirSync(interfacesDir, { recursive: true });
|
|
1246
|
+
const shortId = crypto.randomBytes(4).toString("hex");
|
|
1247
|
+
const dirName = `${getDirPrefix()}-${shortId}`;
|
|
1248
|
+
const targetDir = path.join(interfacesDir, dirName);
|
|
1249
|
+
appendOutput(`Cloning scaffold into ${targetDir}...`);
|
|
1250
|
+
const cloneCode = await runCommand("git", [
|
|
1251
|
+
"clone",
|
|
1252
|
+
"--depth",
|
|
1253
|
+
"1",
|
|
1254
|
+
getScaffoldRepo(),
|
|
1255
|
+
targetDir
|
|
1256
|
+
]);
|
|
1257
|
+
if (!mountedRef.current) return;
|
|
1258
|
+
if (cloneCode !== 0) {
|
|
1259
|
+
throw new Error(`git clone failed with exit code ${cloneCode}`);
|
|
1260
|
+
}
|
|
1261
|
+
setPhase("installing");
|
|
1262
|
+
appendOutput("Installing dependencies...");
|
|
1263
|
+
const installCode = await runCommand("npm", ["install"], {
|
|
1264
|
+
cwd: targetDir
|
|
1265
|
+
});
|
|
1266
|
+
if (!mountedRef.current) return;
|
|
1267
|
+
if (installCode !== 0) {
|
|
1268
|
+
throw new Error(`npm install failed with exit code ${installCode}`);
|
|
1269
|
+
}
|
|
1270
|
+
setLocalInterfacePath(key, targetDir);
|
|
1271
|
+
setHasLocalCopy(true);
|
|
1272
|
+
setPhase("running");
|
|
1273
|
+
const { args, env } = getDevLocalArgs();
|
|
1274
|
+
appendOutput("Starting local dev server...");
|
|
1275
|
+
await runCommand("npm", args, { cwd: targetDir, env });
|
|
1276
|
+
if (mountedRef.current) {
|
|
1277
|
+
setPhase("idle");
|
|
1278
|
+
setOutputLines([]);
|
|
1279
|
+
}
|
|
1280
|
+
} else {
|
|
1281
|
+
setPhase("running");
|
|
1282
|
+
const { args, env } = getDevLocalArgs();
|
|
1283
|
+
appendOutput("Starting local dev server...");
|
|
1284
|
+
await runCommand("npm", args, { cwd: localPath, env });
|
|
1285
|
+
if (mountedRef.current) {
|
|
1286
|
+
setPhase("idle");
|
|
1287
|
+
setOutputLines([]);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
} catch (err) {
|
|
1291
|
+
if (mountedRef.current) {
|
|
1292
|
+
setPhase("error");
|
|
1293
|
+
setErrorMessage(
|
|
1294
|
+
err instanceof Error ? err.message : "Unknown error"
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
run();
|
|
1300
|
+
}, [key, mode, appId, stepId, workflowId, sessionId, appendOutput, runCommand]);
|
|
1301
|
+
const stop = useCallback7(() => {
|
|
1302
|
+
stoppedRef.current = true;
|
|
1303
|
+
if (processRef.current) {
|
|
1304
|
+
processRef.current.kill("SIGTERM");
|
|
1305
|
+
const proc = processRef.current;
|
|
1306
|
+
setTimeout(() => {
|
|
1307
|
+
if (proc && !proc.killed) {
|
|
1308
|
+
proc.kill("SIGKILL");
|
|
1309
|
+
}
|
|
1310
|
+
}, 2e3);
|
|
1311
|
+
processRef.current = null;
|
|
1312
|
+
}
|
|
1313
|
+
setPhase("idle");
|
|
1314
|
+
}, []);
|
|
1315
|
+
const deleteLocalCopy = useCallback7(() => {
|
|
1316
|
+
const localPath = getLocalInterfacePath(key);
|
|
1317
|
+
if (!localPath) return;
|
|
1318
|
+
setPhase("deleting");
|
|
1319
|
+
setOutputLines([]);
|
|
1320
|
+
appendOutput(`Deleting ${localPath}...`);
|
|
1321
|
+
try {
|
|
1322
|
+
fs.rmSync(localPath, { recursive: true, force: true });
|
|
1323
|
+
deleteLocalInterfacePath(key);
|
|
1324
|
+
setHasLocalCopy(false);
|
|
1325
|
+
appendOutput("Deleted successfully.");
|
|
1326
|
+
} catch (err) {
|
|
1327
|
+
setErrorMessage(
|
|
1328
|
+
err instanceof Error ? err.message : "Failed to delete"
|
|
1329
|
+
);
|
|
1330
|
+
}
|
|
1331
|
+
setPhase("idle");
|
|
1332
|
+
}, [key, appendOutput]);
|
|
1333
|
+
return {
|
|
1334
|
+
phase,
|
|
1335
|
+
hasLocalCopy,
|
|
1336
|
+
localPath: getLocalInterfacePath(key),
|
|
1337
|
+
outputLines,
|
|
1338
|
+
errorMessage,
|
|
1339
|
+
start,
|
|
1340
|
+
stop,
|
|
1341
|
+
deleteLocalCopy
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// src/tui/interfaces/pages/InterfaceSessionView.tsx
|
|
1346
|
+
import { useState as useState10, useEffect as useEffect10 } from "react";
|
|
1080
1347
|
import { Box as Box6, Text as Text7, useInput as useInput3 } from "ink";
|
|
1348
|
+
import os2 from "os";
|
|
1349
|
+
import open from "open";
|
|
1350
|
+
import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1351
|
+
function InterfaceSessionView({
|
|
1352
|
+
item,
|
|
1353
|
+
onStart,
|
|
1354
|
+
onDelete,
|
|
1355
|
+
onBack,
|
|
1356
|
+
hasLocalCopy,
|
|
1357
|
+
localPath
|
|
1358
|
+
}) {
|
|
1359
|
+
const menuItems = [
|
|
1360
|
+
{ id: "start", label: "Start Locally" }
|
|
1361
|
+
];
|
|
1362
|
+
if (hasLocalCopy) {
|
|
1363
|
+
menuItems.push({ id: "reveal", label: "Open Folder" });
|
|
1364
|
+
menuItems.push({ id: "delete", label: "Delete Local Copy" });
|
|
1365
|
+
}
|
|
1366
|
+
menuItems.push({ id: "back", label: "Back" });
|
|
1367
|
+
const [cursorIndex, setCursorIndex] = useState10(0);
|
|
1368
|
+
useEffect10(() => {
|
|
1369
|
+
setCursorIndex(0);
|
|
1370
|
+
}, [hasLocalCopy]);
|
|
1371
|
+
useInput3((input, key) => {
|
|
1372
|
+
if (input === "q" || key.escape) {
|
|
1373
|
+
onBack();
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
if (key.upArrow) {
|
|
1377
|
+
setCursorIndex((prev) => Math.max(0, prev - 1));
|
|
1378
|
+
} else if (key.downArrow) {
|
|
1379
|
+
setCursorIndex((prev) => Math.min(menuItems.length - 1, prev + 1));
|
|
1380
|
+
} else if (key.return) {
|
|
1381
|
+
const selected = menuItems[cursorIndex];
|
|
1382
|
+
if (!selected) return;
|
|
1383
|
+
switch (selected.id) {
|
|
1384
|
+
case "start":
|
|
1385
|
+
onStart();
|
|
1386
|
+
break;
|
|
1387
|
+
case "reveal":
|
|
1388
|
+
if (localPath) {
|
|
1389
|
+
open(localPath);
|
|
1390
|
+
}
|
|
1391
|
+
break;
|
|
1392
|
+
case "delete":
|
|
1393
|
+
onDelete();
|
|
1394
|
+
break;
|
|
1395
|
+
case "back":
|
|
1396
|
+
onBack();
|
|
1397
|
+
break;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
const name = `${item.step.workflowName} - ${item.step.displayName}`;
|
|
1402
|
+
const displayPath = localPath?.replace(os2.homedir(), "~");
|
|
1403
|
+
let sessionInfo = null;
|
|
1404
|
+
if (item.kind === "interface") {
|
|
1405
|
+
const hotUpdateDomain = item.step.spaEditorSession?.hotUpdateDomain ?? "";
|
|
1406
|
+
sessionInfo = hotUpdateDomain.replace(/^https?:\/\//, "").split(".")[0] || null;
|
|
1407
|
+
}
|
|
1408
|
+
return /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
|
|
1409
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, color: "white", underline: true, children: name }),
|
|
1410
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1411
|
+
sessionInfo && /* @__PURE__ */ jsxs6(Text7, { color: "gray", children: [
|
|
1412
|
+
"Session: ",
|
|
1413
|
+
sessionInfo
|
|
1414
|
+
] }),
|
|
1415
|
+
hasLocalCopy && localPath ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
1416
|
+
/* @__PURE__ */ jsx7(Text7, { color: "green", children: "Local copy exists" }),
|
|
1417
|
+
/* @__PURE__ */ jsxs6(Text7, { color: "gray", children: [
|
|
1418
|
+
"Path: ",
|
|
1419
|
+
displayPath
|
|
1420
|
+
] })
|
|
1421
|
+
] }) : /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: 'No local copy. "Start Locally" will clone the scaffold and install dependencies.' })
|
|
1422
|
+
] }),
|
|
1423
|
+
/* @__PURE__ */ jsx7(Box6, { marginTop: 1, flexDirection: "column", children: menuItems.map((menuItem, i) => {
|
|
1424
|
+
const isSelected = i === cursorIndex;
|
|
1425
|
+
return /* @__PURE__ */ jsxs6(
|
|
1426
|
+
Text7,
|
|
1427
|
+
{
|
|
1428
|
+
color: isSelected ? "cyan" : "white",
|
|
1429
|
+
bold: isSelected,
|
|
1430
|
+
children: [
|
|
1431
|
+
isSelected ? "\u276F" : " ",
|
|
1432
|
+
" ",
|
|
1433
|
+
menuItem.label
|
|
1434
|
+
]
|
|
1435
|
+
},
|
|
1436
|
+
menuItem.id
|
|
1437
|
+
);
|
|
1438
|
+
}) }),
|
|
1439
|
+
/* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text7, { color: "gray", children: [
|
|
1440
|
+
"Up/Down Navigate ",
|
|
1441
|
+
"\u2022",
|
|
1442
|
+
" Enter Select ",
|
|
1443
|
+
"\u2022",
|
|
1444
|
+
" q/Esc Back"
|
|
1445
|
+
] }) })
|
|
1446
|
+
] }) });
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// src/tui/interfaces/pages/InterfaceRunningView.tsx
|
|
1450
|
+
import { useState as useState11, useMemo as useMemo4, useRef as useRef6, useEffect as useEffect11 } from "react";
|
|
1451
|
+
import { Box as Box7, Text as Text8, useInput as useInput4, useStdout as useStdout6 } from "ink";
|
|
1452
|
+
import { execSync } from "child_process";
|
|
1453
|
+
import os3 from "os";
|
|
1454
|
+
import open2 from "open";
|
|
1081
1455
|
import Spinner4 from "ink-spinner";
|
|
1456
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1457
|
+
function copyToClipboard(text) {
|
|
1458
|
+
try {
|
|
1459
|
+
if (process.platform === "darwin") {
|
|
1460
|
+
execSync("pbcopy", { input: text });
|
|
1461
|
+
} else if (process.platform === "win32") {
|
|
1462
|
+
execSync("clip", { input: text });
|
|
1463
|
+
} else {
|
|
1464
|
+
execSync("xclip -selection clipboard", { input: text });
|
|
1465
|
+
}
|
|
1466
|
+
return true;
|
|
1467
|
+
} catch {
|
|
1468
|
+
return false;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
function getPhaseLabel(phase) {
|
|
1472
|
+
switch (phase) {
|
|
1473
|
+
case "cloning":
|
|
1474
|
+
return { text: "Cloning scaffold...", color: "cyan", showSpinner: true };
|
|
1475
|
+
case "installing":
|
|
1476
|
+
return { text: "Installing dependencies...", color: "cyan", showSpinner: true };
|
|
1477
|
+
case "running":
|
|
1478
|
+
return { text: "Dev server running", color: "green", showSpinner: true };
|
|
1479
|
+
case "error":
|
|
1480
|
+
return { text: "Error", color: "red", showSpinner: false };
|
|
1481
|
+
case "deleting":
|
|
1482
|
+
return { text: "Deleting local copy...", color: "yellow", showSpinner: true };
|
|
1483
|
+
default:
|
|
1484
|
+
return { text: "Idle", color: "gray", showSpinner: false };
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
function InterfaceRunningView({
|
|
1488
|
+
name,
|
|
1489
|
+
phase,
|
|
1490
|
+
outputLines,
|
|
1491
|
+
errorMessage,
|
|
1492
|
+
localPath,
|
|
1493
|
+
onStop,
|
|
1494
|
+
onBack
|
|
1495
|
+
}) {
|
|
1496
|
+
const { stdout } = useStdout6();
|
|
1497
|
+
const termHeight = (stdout?.rows ?? 24) - 4;
|
|
1498
|
+
const isActive = phase === "cloning" || phase === "installing" || phase === "running" || phase === "deleting";
|
|
1499
|
+
const displayPath = localPath?.replace(os3.homedir(), "~");
|
|
1500
|
+
const menuItems = useMemo4(() => {
|
|
1501
|
+
const items = [];
|
|
1502
|
+
if (isActive && displayPath) {
|
|
1503
|
+
items.push({ id: "claude", label: "Copy Claude Code Command", copyValue: `cd ${displayPath} && claude` });
|
|
1504
|
+
items.push({ id: "codex", label: "Copy Codex Command", copyValue: `cd ${displayPath} && codex` });
|
|
1505
|
+
items.push({ id: "reveal", label: "Open Folder" });
|
|
1506
|
+
}
|
|
1507
|
+
items.push({ id: "action", label: isActive ? "Stop" : "Back" });
|
|
1508
|
+
return items;
|
|
1509
|
+
}, [isActive, displayPath]);
|
|
1510
|
+
const [cursorIndex, setCursorIndex] = useState11(0);
|
|
1511
|
+
const [copiedId, setCopiedId] = useState11(null);
|
|
1512
|
+
const copiedTimerRef = useRef6();
|
|
1513
|
+
useEffect11(() => {
|
|
1514
|
+
return () => clearTimeout(copiedTimerRef.current);
|
|
1515
|
+
}, []);
|
|
1516
|
+
const headerHeight = 14;
|
|
1517
|
+
const menuHeight = menuItems.length;
|
|
1518
|
+
const chromeLines = 7 + menuHeight;
|
|
1519
|
+
const logHeight = Math.max(3, termHeight - headerHeight - chromeLines);
|
|
1520
|
+
const maxScroll = Math.max(0, outputLines.length - logHeight);
|
|
1521
|
+
const effectiveOffset = maxScroll;
|
|
1522
|
+
const copyItem = (item) => {
|
|
1523
|
+
if (!item.copyValue) return;
|
|
1524
|
+
const success = copyToClipboard(item.copyValue);
|
|
1525
|
+
if (success) {
|
|
1526
|
+
setCopiedId(item.id);
|
|
1527
|
+
clearTimeout(copiedTimerRef.current);
|
|
1528
|
+
copiedTimerRef.current = setTimeout(() => setCopiedId(null), 2e3);
|
|
1529
|
+
}
|
|
1530
|
+
};
|
|
1531
|
+
useInput4((input, key) => {
|
|
1532
|
+
if (key.upArrow) {
|
|
1533
|
+
setCursorIndex((prev) => Math.max(0, prev - 1));
|
|
1534
|
+
} else if (key.downArrow) {
|
|
1535
|
+
setCursorIndex((prev) => Math.min(menuItems.length - 1, prev + 1));
|
|
1536
|
+
} else if (key.return) {
|
|
1537
|
+
const item = menuItems[cursorIndex];
|
|
1538
|
+
if (!item) return;
|
|
1539
|
+
if (item.copyValue) {
|
|
1540
|
+
copyItem(item);
|
|
1541
|
+
} else if (item.id === "reveal" && localPath) {
|
|
1542
|
+
open2(localPath);
|
|
1543
|
+
} else if (isActive) {
|
|
1544
|
+
onStop();
|
|
1545
|
+
} else {
|
|
1546
|
+
onBack();
|
|
1547
|
+
}
|
|
1548
|
+
} else if (input === "q" || key.escape) {
|
|
1549
|
+
if (isActive) {
|
|
1550
|
+
onStop();
|
|
1551
|
+
} else {
|
|
1552
|
+
onBack();
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
});
|
|
1556
|
+
const visibleLines = outputLines.slice(
|
|
1557
|
+
effectiveOffset,
|
|
1558
|
+
effectiveOffset + logHeight
|
|
1559
|
+
);
|
|
1560
|
+
const phaseInfo = getPhaseLabel(phase);
|
|
1561
|
+
return /* @__PURE__ */ jsx8(Box7, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
|
|
1562
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: "white", underline: true, children: name }),
|
|
1563
|
+
/* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
|
|
1564
|
+
phaseInfo.showSpinner && /* @__PURE__ */ jsxs7(Text8, { color: phaseInfo.color, children: [
|
|
1565
|
+
/* @__PURE__ */ jsx8(Spinner4, { type: "dots" }),
|
|
1566
|
+
" "
|
|
1567
|
+
] }),
|
|
1568
|
+
/* @__PURE__ */ jsx8(Text8, { color: phaseInfo.color, children: phaseInfo.text })
|
|
1569
|
+
] }),
|
|
1570
|
+
errorMessage && /* @__PURE__ */ jsx8(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { color: "red", children: errorMessage }) }),
|
|
1571
|
+
/* @__PURE__ */ jsx8(Box7, { marginTop: 1, height: logHeight, children: /* @__PURE__ */ jsx8(Box7, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: visibleLines.map((line, i) => /* @__PURE__ */ jsx8(Text8, { wrap: "truncate-end", color: "gray", children: line }, effectiveOffset + i)) }) }),
|
|
1572
|
+
/* @__PURE__ */ jsx8(Box7, { marginTop: 1, flexDirection: "column", children: menuItems.map((item, i) => {
|
|
1573
|
+
const isSelected = i === cursorIndex;
|
|
1574
|
+
const isCopied = copiedId === item.id;
|
|
1575
|
+
return /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
1576
|
+
/* @__PURE__ */ jsxs7(Text8, { color: isSelected ? "cyan" : "white", bold: isSelected, children: [
|
|
1577
|
+
isSelected ? "\u276F" : " ",
|
|
1578
|
+
" ",
|
|
1579
|
+
item.label
|
|
1580
|
+
] }),
|
|
1581
|
+
isCopied && /* @__PURE__ */ jsxs7(Text8, { color: "green", children: [
|
|
1582
|
+
" ",
|
|
1583
|
+
"\u2713",
|
|
1584
|
+
" Copied!"
|
|
1585
|
+
] })
|
|
1586
|
+
] }, item.id);
|
|
1587
|
+
}) }),
|
|
1588
|
+
/* @__PURE__ */ jsx8(Box7, { marginTop: 1, height: 1, children: /* @__PURE__ */ jsxs7(Text8, { color: "gray", wrap: "truncate-end", children: [
|
|
1589
|
+
"Up/Down Navigate ",
|
|
1590
|
+
"\u2022",
|
|
1591
|
+
" Enter Select ",
|
|
1592
|
+
"\u2022",
|
|
1593
|
+
" q/Esc ",
|
|
1594
|
+
isActive ? "Stop" : "Back"
|
|
1595
|
+
] }) })
|
|
1596
|
+
] }) });
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
// src/tui/interfaces/pages/InterfacesPage.tsx
|
|
1600
|
+
import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1601
|
+
function getSessionStatus(item) {
|
|
1602
|
+
if (item.kind === "script") return "running";
|
|
1603
|
+
const status = item.step.spaEditorSession?.status;
|
|
1604
|
+
if (status === "running" || status === "compiling") return status;
|
|
1605
|
+
if (status === "starting") return "starting";
|
|
1606
|
+
return "stopped";
|
|
1607
|
+
}
|
|
1608
|
+
function isItemSelectable(item) {
|
|
1609
|
+
const status = getSessionStatus(item);
|
|
1610
|
+
return status === "running" || status === "compiling";
|
|
1611
|
+
}
|
|
1612
|
+
function getStatusLabel(status) {
|
|
1613
|
+
switch (status) {
|
|
1614
|
+
case "running":
|
|
1615
|
+
case "compiling":
|
|
1616
|
+
return { text: "Online", color: "green" };
|
|
1617
|
+
case "starting":
|
|
1618
|
+
return { text: "Starting", color: "yellow" };
|
|
1619
|
+
case "stopped":
|
|
1620
|
+
return { text: "Offline", color: "gray" };
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
function getRefreshSuffix(status) {
|
|
1624
|
+
switch (status) {
|
|
1625
|
+
case "refreshing":
|
|
1626
|
+
return "Refreshing...";
|
|
1627
|
+
case "refreshed":
|
|
1628
|
+
return "\u2713 Refreshed";
|
|
1629
|
+
case "idle":
|
|
1630
|
+
return null;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
function formatCount(interfaces, scripts) {
|
|
1634
|
+
const parts = [];
|
|
1635
|
+
if (interfaces > 0) {
|
|
1636
|
+
parts.push(`${interfaces} Interface${interfaces !== 1 ? "s" : ""}`);
|
|
1637
|
+
}
|
|
1638
|
+
if (scripts > 0) {
|
|
1639
|
+
parts.push(`${scripts} Script${scripts !== 1 ? "s" : ""}`);
|
|
1640
|
+
}
|
|
1641
|
+
return parts.join(", ");
|
|
1642
|
+
}
|
|
1643
|
+
function OfflineView({
|
|
1644
|
+
item,
|
|
1645
|
+
onBack
|
|
1646
|
+
}) {
|
|
1647
|
+
useInput5((input, key) => {
|
|
1648
|
+
if (input === "q" || key.escape || key.return) {
|
|
1649
|
+
onBack();
|
|
1650
|
+
}
|
|
1651
|
+
});
|
|
1652
|
+
const name = item.kind === "interface" ? `${item.step.workflowName} - ${item.step.displayName}` : `${item.step.workflowName} - ${item.step.displayName}`;
|
|
1653
|
+
return /* @__PURE__ */ jsx9(Box8, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
|
|
1654
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: "white", underline: true, children: name }),
|
|
1655
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
1656
|
+
/* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "This interface is offline." }),
|
|
1657
|
+
/* @__PURE__ */ jsx9(Text9, { color: "gray", children: "Start the interface designer in MindStudio to connect to it." })
|
|
1658
|
+
] }),
|
|
1659
|
+
/* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text9, { color: "cyan", bold: true, children: [
|
|
1660
|
+
"\u276F",
|
|
1661
|
+
" Back"
|
|
1662
|
+
] }) }),
|
|
1663
|
+
/* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "gray", children: "Enter/q/Esc Back" }) })
|
|
1664
|
+
] }) });
|
|
1665
|
+
}
|
|
1666
|
+
function AgentListView({
|
|
1667
|
+
sessions,
|
|
1668
|
+
onBack,
|
|
1669
|
+
onSelectAgent,
|
|
1670
|
+
onRefresh,
|
|
1671
|
+
refreshStatus
|
|
1672
|
+
}) {
|
|
1673
|
+
const refreshIndex = sessions.length;
|
|
1674
|
+
const backIndex = sessions.length + 1;
|
|
1675
|
+
const totalItems = sessions.length + 2;
|
|
1676
|
+
const [cursorIndex, setCursorIndex] = useState12(0);
|
|
1677
|
+
useEffect12(() => {
|
|
1678
|
+
setCursorIndex(sessions.length > 0 ? 0 : backIndex);
|
|
1679
|
+
}, [sessions.length, backIndex]);
|
|
1680
|
+
useInput5((input, key) => {
|
|
1681
|
+
if (input === "q" || key.escape) {
|
|
1682
|
+
onBack();
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
if (key.upArrow) {
|
|
1686
|
+
setCursorIndex((prev) => Math.max(0, prev - 1));
|
|
1687
|
+
} else if (key.downArrow) {
|
|
1688
|
+
setCursorIndex((prev) => Math.min(totalItems - 1, prev + 1));
|
|
1689
|
+
} else if (key.return) {
|
|
1690
|
+
if (cursorIndex === backIndex) {
|
|
1691
|
+
onBack();
|
|
1692
|
+
} else if (cursorIndex === refreshIndex) {
|
|
1693
|
+
onRefresh();
|
|
1694
|
+
} else if (sessions[cursorIndex]) {
|
|
1695
|
+
onSelectAgent(sessions[cursorIndex].appId);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
});
|
|
1699
|
+
return /* @__PURE__ */ jsx9(Box8, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
|
|
1700
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: "white", underline: true, children: "Choose an Agent" }),
|
|
1701
|
+
sessions.length === 0 ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
1702
|
+
/* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "No active editor sessions." }),
|
|
1703
|
+
/* @__PURE__ */ jsx9(Text9, { color: "gray", children: "Open an app in MindStudio's editor to see sessions here." })
|
|
1704
|
+
] }) : /* @__PURE__ */ jsx9(Box8, { flexDirection: "column", marginTop: 1, children: sessions.map((session, i) => {
|
|
1705
|
+
const isSelected = i === cursorIndex;
|
|
1706
|
+
const stats = formatCount(
|
|
1707
|
+
session.customInterfaceSteps.length,
|
|
1708
|
+
session.scriptSteps.length
|
|
1709
|
+
);
|
|
1710
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: i > 0 ? 1 : 0, children: [
|
|
1711
|
+
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
1712
|
+
/* @__PURE__ */ jsxs8(Text9, { color: isSelected ? "cyan" : "white", bold: isSelected, children: [
|
|
1713
|
+
isSelected ? "\u276F" : " ",
|
|
1714
|
+
" ",
|
|
1715
|
+
session.appName
|
|
1716
|
+
] }),
|
|
1717
|
+
isSelected && /* @__PURE__ */ jsx9(Text9, { color: "gray", children: " - Connect to this Agent" })
|
|
1718
|
+
] }),
|
|
1719
|
+
/* @__PURE__ */ jsxs8(Text9, { color: "gray", children: [
|
|
1720
|
+
" ",
|
|
1721
|
+
"https://app.mindstudio.ai/agents/",
|
|
1722
|
+
session.appId,
|
|
1723
|
+
"/edit"
|
|
1724
|
+
] }),
|
|
1725
|
+
/* @__PURE__ */ jsxs8(Text9, { color: "gray", children: [
|
|
1726
|
+
" ",
|
|
1727
|
+
stats
|
|
1728
|
+
] })
|
|
1729
|
+
] }, session.appId);
|
|
1730
|
+
}) }),
|
|
1731
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
1732
|
+
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
1733
|
+
/* @__PURE__ */ jsxs8(
|
|
1734
|
+
Text9,
|
|
1735
|
+
{
|
|
1736
|
+
color: cursorIndex === refreshIndex ? "cyan" : "white",
|
|
1737
|
+
bold: cursorIndex === refreshIndex,
|
|
1738
|
+
children: [
|
|
1739
|
+
cursorIndex === refreshIndex ? "\u276F" : " ",
|
|
1740
|
+
" Refresh"
|
|
1741
|
+
]
|
|
1742
|
+
}
|
|
1743
|
+
),
|
|
1744
|
+
getRefreshSuffix(refreshStatus) && /* @__PURE__ */ jsxs8(Text9, { color: "gray", children: [
|
|
1745
|
+
" ",
|
|
1746
|
+
getRefreshSuffix(refreshStatus)
|
|
1747
|
+
] })
|
|
1748
|
+
] }),
|
|
1749
|
+
/* @__PURE__ */ jsxs8(
|
|
1750
|
+
Text9,
|
|
1751
|
+
{
|
|
1752
|
+
color: cursorIndex === backIndex ? "cyan" : "white",
|
|
1753
|
+
bold: cursorIndex === backIndex,
|
|
1754
|
+
children: [
|
|
1755
|
+
cursorIndex === backIndex ? "\u276F" : " ",
|
|
1756
|
+
" Back"
|
|
1757
|
+
]
|
|
1758
|
+
}
|
|
1759
|
+
)
|
|
1760
|
+
] }),
|
|
1761
|
+
/* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text9, { color: "gray", children: [
|
|
1762
|
+
"Up/Down Navigate ",
|
|
1763
|
+
"\u2022",
|
|
1764
|
+
" Enter Select ",
|
|
1765
|
+
"\u2022",
|
|
1766
|
+
" q/Esc Back"
|
|
1767
|
+
] }) })
|
|
1768
|
+
] }) });
|
|
1769
|
+
}
|
|
1770
|
+
function AgentDetailView({
|
|
1771
|
+
session,
|
|
1772
|
+
onBack,
|
|
1773
|
+
onSelect,
|
|
1774
|
+
onRefresh,
|
|
1775
|
+
refreshStatus
|
|
1776
|
+
}) {
|
|
1777
|
+
const [offlineItem, setOfflineItem] = useState12(null);
|
|
1778
|
+
useEffect12(() => {
|
|
1779
|
+
onRefresh();
|
|
1780
|
+
}, []);
|
|
1781
|
+
const interfaces = useMemo5(
|
|
1782
|
+
() => session.customInterfaceSteps.map((step) => ({
|
|
1783
|
+
kind: "interface",
|
|
1784
|
+
appId: session.appId,
|
|
1785
|
+
appName: session.appName,
|
|
1786
|
+
step
|
|
1787
|
+
})),
|
|
1788
|
+
[session]
|
|
1789
|
+
);
|
|
1790
|
+
const scripts = useMemo5(
|
|
1791
|
+
() => session.scriptSteps.map((step) => ({
|
|
1792
|
+
kind: "script",
|
|
1793
|
+
appId: session.appId,
|
|
1794
|
+
appName: session.appName,
|
|
1795
|
+
step
|
|
1796
|
+
})),
|
|
1797
|
+
[session]
|
|
1798
|
+
);
|
|
1799
|
+
const allItems = useMemo5(
|
|
1800
|
+
() => [...interfaces, ...scripts],
|
|
1801
|
+
[interfaces, scripts]
|
|
1802
|
+
);
|
|
1803
|
+
const refreshIndex = allItems.length;
|
|
1804
|
+
const backIndex = allItems.length + 1;
|
|
1805
|
+
const totalItems = allItems.length + 2;
|
|
1806
|
+
const [cursorIndex, setCursorIndex] = useState12(0);
|
|
1807
|
+
useEffect12(() => {
|
|
1808
|
+
setCursorIndex(0);
|
|
1809
|
+
}, [allItems.length]);
|
|
1810
|
+
useInput5((input, key) => {
|
|
1811
|
+
if (offlineItem) return;
|
|
1812
|
+
if (input === "q" || key.escape) {
|
|
1813
|
+
onBack();
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
if (key.upArrow) {
|
|
1817
|
+
setCursorIndex((prev) => Math.max(0, prev - 1));
|
|
1818
|
+
} else if (key.downArrow) {
|
|
1819
|
+
setCursorIndex((prev) => Math.min(totalItems - 1, prev + 1));
|
|
1820
|
+
} else if (key.return) {
|
|
1821
|
+
if (cursorIndex === backIndex) {
|
|
1822
|
+
onBack();
|
|
1823
|
+
} else if (cursorIndex === refreshIndex) {
|
|
1824
|
+
onRefresh();
|
|
1825
|
+
} else {
|
|
1826
|
+
const item = allItems[cursorIndex];
|
|
1827
|
+
if (item) {
|
|
1828
|
+
if (isItemSelectable(item)) {
|
|
1829
|
+
onSelect(item);
|
|
1830
|
+
} else {
|
|
1831
|
+
setOfflineItem(item);
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
});
|
|
1837
|
+
if (offlineItem) {
|
|
1838
|
+
return /* @__PURE__ */ jsx9(OfflineView, { item: offlineItem, onBack: () => setOfflineItem(null) });
|
|
1839
|
+
}
|
|
1840
|
+
const scriptsOffset = interfaces.length;
|
|
1841
|
+
return /* @__PURE__ */ jsx9(Box8, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
|
|
1842
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: "white", underline: true, children: session.appName }),
|
|
1843
|
+
/* @__PURE__ */ jsx9(Text9, { color: "gray", children: "Select an interface or script to connect your local editor." }),
|
|
1844
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
1845
|
+
interfaces.length > 0 && /* @__PURE__ */ jsxs8(Fragment4, { children: [
|
|
1846
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: "white", underline: true, children: "Interfaces" }),
|
|
1847
|
+
/* @__PURE__ */ jsx9(Box8, { flexDirection: "column", marginTop: 1, children: interfaces.map((item, i) => {
|
|
1848
|
+
const isSelected = i === cursorIndex;
|
|
1849
|
+
const status = getSessionStatus(item);
|
|
1850
|
+
const statusLabel = getStatusLabel(status);
|
|
1851
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: i > 0 ? 1 : 0, children: [
|
|
1852
|
+
/* @__PURE__ */ jsxs8(Text9, { color: isSelected ? "cyan" : "white", bold: isSelected, children: [
|
|
1853
|
+
isSelected ? "\u276F" : " ",
|
|
1854
|
+
" ",
|
|
1855
|
+
item.step.workflowName,
|
|
1856
|
+
" - ",
|
|
1857
|
+
item.step.displayName
|
|
1858
|
+
] }),
|
|
1859
|
+
/* @__PURE__ */ jsx9(Box8, { children: /* @__PURE__ */ jsxs8(Text9, { color: statusLabel.color, children: [
|
|
1860
|
+
" ",
|
|
1861
|
+
statusLabel.text
|
|
1862
|
+
] }) })
|
|
1863
|
+
] }, `${item.step.workflowId}:${item.step.stepId}`);
|
|
1864
|
+
}) })
|
|
1865
|
+
] }),
|
|
1866
|
+
scripts.length > 0 && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: interfaces.length > 0 ? 1 : 0, children: [
|
|
1867
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: "white", underline: true, children: "Scripts" }),
|
|
1868
|
+
/* @__PURE__ */ jsx9(Box8, { flexDirection: "column", marginTop: 1, children: scripts.map((item, i) => {
|
|
1869
|
+
const index = scriptsOffset + i;
|
|
1870
|
+
const isSelected = index === cursorIndex;
|
|
1871
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: i > 0 ? 1 : 0, children: [
|
|
1872
|
+
/* @__PURE__ */ jsxs8(Text9, { color: isSelected ? "cyan" : "white", bold: isSelected, children: [
|
|
1873
|
+
isSelected ? "\u276F" : " ",
|
|
1874
|
+
" ",
|
|
1875
|
+
item.step.workflowName,
|
|
1876
|
+
" - ",
|
|
1877
|
+
item.step.displayName
|
|
1878
|
+
] }),
|
|
1879
|
+
/* @__PURE__ */ jsxs8(Text9, { color: "gray", children: [
|
|
1880
|
+
" ",
|
|
1881
|
+
item.step.entryFile
|
|
1882
|
+
] })
|
|
1883
|
+
] }, `${item.step.workflowId}:${item.step.stepId}`);
|
|
1884
|
+
}) })
|
|
1885
|
+
] }),
|
|
1886
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
1887
|
+
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
1888
|
+
/* @__PURE__ */ jsxs8(
|
|
1889
|
+
Text9,
|
|
1890
|
+
{
|
|
1891
|
+
color: cursorIndex === refreshIndex ? "cyan" : "white",
|
|
1892
|
+
bold: cursorIndex === refreshIndex,
|
|
1893
|
+
children: [
|
|
1894
|
+
cursorIndex === refreshIndex ? "\u276F" : " ",
|
|
1895
|
+
" Refresh"
|
|
1896
|
+
]
|
|
1897
|
+
}
|
|
1898
|
+
),
|
|
1899
|
+
getRefreshSuffix(refreshStatus) && /* @__PURE__ */ jsxs8(Text9, { color: "gray", children: [
|
|
1900
|
+
" ",
|
|
1901
|
+
getRefreshSuffix(refreshStatus)
|
|
1902
|
+
] })
|
|
1903
|
+
] }),
|
|
1904
|
+
/* @__PURE__ */ jsxs8(
|
|
1905
|
+
Text9,
|
|
1906
|
+
{
|
|
1907
|
+
color: cursorIndex === backIndex ? "cyan" : "white",
|
|
1908
|
+
bold: cursorIndex === backIndex,
|
|
1909
|
+
children: [
|
|
1910
|
+
cursorIndex === backIndex ? "\u276F" : " ",
|
|
1911
|
+
" Back"
|
|
1912
|
+
]
|
|
1913
|
+
}
|
|
1914
|
+
)
|
|
1915
|
+
] })
|
|
1916
|
+
] }),
|
|
1917
|
+
/* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text9, { color: "gray", children: [
|
|
1918
|
+
"Up/Down Navigate ",
|
|
1919
|
+
"\u2022",
|
|
1920
|
+
" Enter Select ",
|
|
1921
|
+
"\u2022",
|
|
1922
|
+
" q/Esc Back"
|
|
1923
|
+
] }) })
|
|
1924
|
+
] }) });
|
|
1925
|
+
}
|
|
1926
|
+
function LocalDevView({
|
|
1927
|
+
item,
|
|
1928
|
+
onBack
|
|
1929
|
+
}) {
|
|
1930
|
+
const mode = item.kind === "script" ? "script" : "interface";
|
|
1931
|
+
let sessionId = "";
|
|
1932
|
+
if (item.kind === "interface") {
|
|
1933
|
+
const hotUpdateDomain = item.step.spaEditorSession?.hotUpdateDomain ?? "";
|
|
1934
|
+
sessionId = hotUpdateDomain.replace(/^https?:\/\//, "").split(".")[0] || "";
|
|
1935
|
+
}
|
|
1936
|
+
const {
|
|
1937
|
+
phase,
|
|
1938
|
+
hasLocalCopy,
|
|
1939
|
+
localPath,
|
|
1940
|
+
outputLines,
|
|
1941
|
+
errorMessage,
|
|
1942
|
+
start,
|
|
1943
|
+
stop,
|
|
1944
|
+
deleteLocalCopy
|
|
1945
|
+
} = useLocalInterface({
|
|
1946
|
+
mode,
|
|
1947
|
+
appId: item.appId,
|
|
1948
|
+
stepId: item.step.stepId,
|
|
1949
|
+
workflowId: item.step.workflowId,
|
|
1950
|
+
sessionId
|
|
1951
|
+
});
|
|
1952
|
+
const name = `${item.step.workflowName} - ${item.step.displayName}`;
|
|
1953
|
+
const isActive = phase === "cloning" || phase === "installing" || phase === "running" || phase === "deleting";
|
|
1954
|
+
const wasActiveRef = useRef7(false);
|
|
1955
|
+
useEffect12(() => {
|
|
1956
|
+
if (isActive) {
|
|
1957
|
+
wasActiveRef.current = true;
|
|
1958
|
+
} else if (wasActiveRef.current && phase === "idle") {
|
|
1959
|
+
wasActiveRef.current = false;
|
|
1960
|
+
onBack();
|
|
1961
|
+
}
|
|
1962
|
+
}, [phase, isActive, onBack]);
|
|
1963
|
+
if (isActive || phase === "error") {
|
|
1964
|
+
return /* @__PURE__ */ jsx9(
|
|
1965
|
+
InterfaceRunningView,
|
|
1966
|
+
{
|
|
1967
|
+
name,
|
|
1968
|
+
phase,
|
|
1969
|
+
outputLines,
|
|
1970
|
+
errorMessage,
|
|
1971
|
+
localPath,
|
|
1972
|
+
onStop: stop,
|
|
1973
|
+
onBack
|
|
1974
|
+
}
|
|
1975
|
+
);
|
|
1976
|
+
}
|
|
1977
|
+
return /* @__PURE__ */ jsx9(
|
|
1978
|
+
InterfaceSessionView,
|
|
1979
|
+
{
|
|
1980
|
+
item,
|
|
1981
|
+
onStart: start,
|
|
1982
|
+
onDelete: deleteLocalCopy,
|
|
1983
|
+
onBack,
|
|
1984
|
+
hasLocalCopy,
|
|
1985
|
+
localPath
|
|
1986
|
+
}
|
|
1987
|
+
);
|
|
1988
|
+
}
|
|
1989
|
+
function InterfacesPage({ onBack }) {
|
|
1990
|
+
const { sessions, loading, error, refreshStatus, refresh } = useEditorSessions();
|
|
1991
|
+
const [selectedAppId, setSelectedAppId] = useState12(null);
|
|
1992
|
+
const [selectedItem, setSelectedItem] = useState12(null);
|
|
1993
|
+
if (loading) {
|
|
1994
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", flexGrow: 1, paddingX: 1, marginTop: 1, children: [
|
|
1995
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: "white", underline: true, children: "Choose an Agent" }),
|
|
1996
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
|
|
1997
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", children: /* @__PURE__ */ jsx9(Spinner5, { type: "dots" }) }),
|
|
1998
|
+
/* @__PURE__ */ jsx9(Text9, { children: " Loading editor sessions..." })
|
|
1999
|
+
] })
|
|
2000
|
+
] });
|
|
2001
|
+
}
|
|
2002
|
+
if (error) {
|
|
2003
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", flexGrow: 1, paddingX: 1, marginTop: 1, children: [
|
|
2004
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: "white", underline: true, children: "Choose an Agent" }),
|
|
2005
|
+
/* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "red", children: error }) })
|
|
2006
|
+
] });
|
|
2007
|
+
}
|
|
2008
|
+
if (selectedItem) {
|
|
2009
|
+
return /* @__PURE__ */ jsx9(
|
|
2010
|
+
LocalDevView,
|
|
2011
|
+
{
|
|
2012
|
+
item: selectedItem,
|
|
2013
|
+
onBack: () => setSelectedItem(null)
|
|
2014
|
+
}
|
|
2015
|
+
);
|
|
2016
|
+
}
|
|
2017
|
+
if (selectedAppId) {
|
|
2018
|
+
const session = sessions.find((s) => s.appId === selectedAppId);
|
|
2019
|
+
if (session) {
|
|
2020
|
+
return /* @__PURE__ */ jsx9(
|
|
2021
|
+
AgentDetailView,
|
|
2022
|
+
{
|
|
2023
|
+
session,
|
|
2024
|
+
onBack: () => setSelectedAppId(null),
|
|
2025
|
+
onSelect: (item) => {
|
|
2026
|
+
setSelectedItem(item);
|
|
2027
|
+
},
|
|
2028
|
+
onRefresh: refresh,
|
|
2029
|
+
refreshStatus
|
|
2030
|
+
}
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
return /* @__PURE__ */ jsx9(
|
|
2035
|
+
AgentListView,
|
|
2036
|
+
{
|
|
2037
|
+
sessions,
|
|
2038
|
+
onBack,
|
|
2039
|
+
onSelectAgent: setSelectedAppId,
|
|
2040
|
+
onRefresh: refresh,
|
|
2041
|
+
refreshStatus
|
|
2042
|
+
}
|
|
2043
|
+
);
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
// src/tui/pages/OnboardingPage.tsx
|
|
2047
|
+
import { useEffect as useEffect14, useCallback as useCallback9, useState as useState14, useMemo as useMemo6 } from "react";
|
|
2048
|
+
import { Box as Box9, Text as Text10, useInput as useInput6 } from "ink";
|
|
2049
|
+
import Spinner6 from "ink-spinner";
|
|
1082
2050
|
import chalk2 from "chalk";
|
|
1083
2051
|
|
|
1084
2052
|
// src/tui/hooks/useAuth.ts
|
|
1085
|
-
import { useState as
|
|
1086
|
-
import
|
|
2053
|
+
import { useState as useState13, useCallback as useCallback8, useRef as useRef8, useEffect as useEffect13 } from "react";
|
|
2054
|
+
import open3 from "open";
|
|
1087
2055
|
var POLL_INTERVAL = 2e3;
|
|
1088
2056
|
var MAX_ATTEMPTS = 30;
|
|
1089
2057
|
function useAuth() {
|
|
1090
|
-
const [status, setStatus] =
|
|
1091
|
-
const [authUrl, setAuthUrl] =
|
|
1092
|
-
const [timeRemaining, setTimeRemaining] =
|
|
1093
|
-
const cancelledRef =
|
|
1094
|
-
const timerRef =
|
|
1095
|
-
|
|
2058
|
+
const [status, setStatus] = useState13("idle");
|
|
2059
|
+
const [authUrl, setAuthUrl] = useState13(null);
|
|
2060
|
+
const [timeRemaining, setTimeRemaining] = useState13(0);
|
|
2061
|
+
const cancelledRef = useRef8(false);
|
|
2062
|
+
const timerRef = useRef8(null);
|
|
2063
|
+
useEffect13(() => {
|
|
1096
2064
|
return () => {
|
|
1097
2065
|
cancelledRef.current = true;
|
|
1098
2066
|
if (timerRef.current) clearInterval(timerRef.current);
|
|
1099
2067
|
};
|
|
1100
2068
|
}, []);
|
|
1101
|
-
const cancel =
|
|
2069
|
+
const cancel = useCallback8(() => {
|
|
1102
2070
|
cancelledRef.current = true;
|
|
1103
2071
|
if (timerRef.current) {
|
|
1104
2072
|
clearInterval(timerRef.current);
|
|
@@ -1108,7 +2076,7 @@ function useAuth() {
|
|
|
1108
2076
|
setAuthUrl(null);
|
|
1109
2077
|
setTimeRemaining(0);
|
|
1110
2078
|
}, []);
|
|
1111
|
-
const startAuth =
|
|
2079
|
+
const startAuth = useCallback8(() => {
|
|
1112
2080
|
cancelledRef.current = false;
|
|
1113
2081
|
const run = async () => {
|
|
1114
2082
|
try {
|
|
@@ -1128,7 +2096,7 @@ function useAuth() {
|
|
|
1128
2096
|
return Math.max(0, next);
|
|
1129
2097
|
});
|
|
1130
2098
|
}, 1e3);
|
|
1131
|
-
await
|
|
2099
|
+
await open3(url);
|
|
1132
2100
|
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
|
1133
2101
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
|
|
1134
2102
|
if (cancelledRef.current) return;
|
|
@@ -1136,6 +2104,9 @@ function useAuth() {
|
|
|
1136
2104
|
if (result.status === "completed" && result.apiKey) {
|
|
1137
2105
|
if (timerRef.current) clearInterval(timerRef.current);
|
|
1138
2106
|
setApiKey(result.apiKey);
|
|
2107
|
+
if (result.userId) {
|
|
2108
|
+
setUserId(result.userId);
|
|
2109
|
+
}
|
|
1139
2110
|
setStatus("success");
|
|
1140
2111
|
return;
|
|
1141
2112
|
}
|
|
@@ -1166,12 +2137,12 @@ function useAuth() {
|
|
|
1166
2137
|
}
|
|
1167
2138
|
|
|
1168
2139
|
// src/tui/pages/OnboardingPage.tsx
|
|
1169
|
-
import { Fragment as
|
|
2140
|
+
import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1170
2141
|
var SHIMMER_SPEED = 35;
|
|
1171
2142
|
function useShimmerLogo() {
|
|
1172
|
-
const [frame, setFrame] =
|
|
1173
|
-
const lines =
|
|
1174
|
-
const totalChars =
|
|
2143
|
+
const [frame, setFrame] = useState14(0);
|
|
2144
|
+
const lines = useMemo6(() => LogoString.split("\n"), []);
|
|
2145
|
+
const totalChars = useMemo6(() => {
|
|
1175
2146
|
let count = 0;
|
|
1176
2147
|
for (const line of lines) {
|
|
1177
2148
|
for (const ch of line) {
|
|
@@ -1181,13 +2152,13 @@ function useShimmerLogo() {
|
|
|
1181
2152
|
return count;
|
|
1182
2153
|
}, [lines]);
|
|
1183
2154
|
const cycleLength = totalChars + 40;
|
|
1184
|
-
|
|
2155
|
+
useEffect14(() => {
|
|
1185
2156
|
const interval = setInterval(() => {
|
|
1186
2157
|
setFrame((f) => (f + 1) % cycleLength);
|
|
1187
2158
|
}, SHIMMER_SPEED);
|
|
1188
2159
|
return () => clearInterval(interval);
|
|
1189
2160
|
}, [cycleLength]);
|
|
1190
|
-
return
|
|
2161
|
+
return useMemo6(() => {
|
|
1191
2162
|
const sweepPos = frame;
|
|
1192
2163
|
const holdEnd = totalChars + 20;
|
|
1193
2164
|
let charIdx = 0;
|
|
@@ -1234,66 +2205,66 @@ function OnboardingPage({ onComplete }) {
|
|
|
1234
2205
|
cancel: cancelAuth
|
|
1235
2206
|
} = useAuth();
|
|
1236
2207
|
const shimmerLogo = useShimmerLogo();
|
|
1237
|
-
|
|
2208
|
+
useEffect14(() => {
|
|
1238
2209
|
if (authStatus === "success") {
|
|
1239
2210
|
const timer = setTimeout(() => onComplete(), 1500);
|
|
1240
2211
|
return () => clearTimeout(timer);
|
|
1241
2212
|
}
|
|
1242
2213
|
}, [authStatus, onComplete]);
|
|
1243
|
-
|
|
2214
|
+
useEffect14(() => {
|
|
1244
2215
|
return () => cancelAuth();
|
|
1245
2216
|
}, []);
|
|
1246
|
-
const handleAction =
|
|
2217
|
+
const handleAction = useCallback9(() => {
|
|
1247
2218
|
cancelAuth();
|
|
1248
2219
|
startAuth();
|
|
1249
2220
|
}, [cancelAuth, startAuth]);
|
|
1250
2221
|
const canAct = authStatus === "idle" || authStatus === "expired" || authStatus === "timeout";
|
|
1251
|
-
|
|
2222
|
+
useInput6((_input, key) => {
|
|
1252
2223
|
if (canAct && !key.ctrl) {
|
|
1253
2224
|
handleAction();
|
|
1254
2225
|
}
|
|
1255
2226
|
});
|
|
1256
|
-
return /* @__PURE__ */
|
|
1257
|
-
/* @__PURE__ */
|
|
1258
|
-
/* @__PURE__ */
|
|
1259
|
-
/* @__PURE__ */
|
|
1260
|
-
/* @__PURE__ */
|
|
1261
|
-
/* @__PURE__ */
|
|
1262
|
-
authStatus === "idle" && /* @__PURE__ */
|
|
1263
|
-
/* @__PURE__ */
|
|
1264
|
-
/* @__PURE__ */
|
|
2227
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", flexGrow: 1, children: [
|
|
2228
|
+
/* @__PURE__ */ jsx10(Box9, { flexGrow: 1 }),
|
|
2229
|
+
/* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", alignItems: "center", children: [
|
|
2230
|
+
/* @__PURE__ */ jsx10(Text10, { children: shimmerLogo }),
|
|
2231
|
+
/* @__PURE__ */ jsx10(Box9, { flexDirection: "column", alignItems: "center", marginTop: 2, children: /* @__PURE__ */ jsx10(Text10, { bold: true, color: "white", children: "MindStudio Local Tunnel" }) }),
|
|
2232
|
+
/* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", alignItems: "center", children: [
|
|
2233
|
+
authStatus === "idle" && /* @__PURE__ */ jsxs9(Fragment5, { children: [
|
|
2234
|
+
/* @__PURE__ */ jsx10(Text10, { color: "gray", children: "Connect your MindStudio account to get started." }),
|
|
2235
|
+
/* @__PURE__ */ jsx10(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { color: "cyan", bold: true, children: "Press any key to Connect Account" }) })
|
|
1265
2236
|
] }),
|
|
1266
|
-
(authStatus === "expired" || authStatus === "timeout") && /* @__PURE__ */
|
|
1267
|
-
/* @__PURE__ */
|
|
1268
|
-
/* @__PURE__ */
|
|
2237
|
+
(authStatus === "expired" || authStatus === "timeout") && /* @__PURE__ */ jsxs9(Fragment5, { children: [
|
|
2238
|
+
/* @__PURE__ */ jsx10(Text10, { color: "red", children: authStatus === "expired" ? "Authorization expired." : "Authorization timed out." }),
|
|
2239
|
+
/* @__PURE__ */ jsx10(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { color: "cyan", bold: true, children: "Press any key to Try Again" }) })
|
|
1269
2240
|
] }),
|
|
1270
|
-
authStatus === "waiting" && /* @__PURE__ */
|
|
1271
|
-
/* @__PURE__ */
|
|
1272
|
-
/* @__PURE__ */
|
|
1273
|
-
/* @__PURE__ */
|
|
2241
|
+
authStatus === "waiting" && /* @__PURE__ */ jsxs9(Fragment5, { children: [
|
|
2242
|
+
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
2243
|
+
/* @__PURE__ */ jsx10(Text10, { color: "cyan", children: /* @__PURE__ */ jsx10(Spinner6, { type: "dots" }) }),
|
|
2244
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
1274
2245
|
" ",
|
|
1275
2246
|
"Waiting for browser authorization... (",
|
|
1276
2247
|
timeRemaining,
|
|
1277
2248
|
"s remaining)"
|
|
1278
2249
|
] })
|
|
1279
2250
|
] }),
|
|
1280
|
-
authUrl && /* @__PURE__ */
|
|
1281
|
-
/* @__PURE__ */
|
|
1282
|
-
/* @__PURE__ */
|
|
2251
|
+
authUrl && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", alignItems: "center", marginTop: 1, children: [
|
|
2252
|
+
/* @__PURE__ */ jsx10(Text10, { color: "gray", children: "If browser didn't open, visit:" }),
|
|
2253
|
+
/* @__PURE__ */ jsx10(Text10, { color: "cyan", children: authUrl })
|
|
1283
2254
|
] })
|
|
1284
2255
|
] }),
|
|
1285
|
-
authStatus === "success" && /* @__PURE__ */
|
|
2256
|
+
authStatus === "success" && /* @__PURE__ */ jsxs9(Text10, { color: "green", children: [
|
|
1286
2257
|
"\u2713",
|
|
1287
2258
|
" Authenticated!"
|
|
1288
2259
|
] })
|
|
1289
2260
|
] })
|
|
1290
2261
|
] }),
|
|
1291
|
-
/* @__PURE__ */
|
|
2262
|
+
/* @__PURE__ */ jsx10(Box9, { flexGrow: 1 })
|
|
1292
2263
|
] });
|
|
1293
2264
|
}
|
|
1294
2265
|
|
|
1295
2266
|
// src/tui/App.tsx
|
|
1296
|
-
import { Fragment as
|
|
2267
|
+
import { Fragment as Fragment6, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1297
2268
|
var MODEL_TYPE_MAP = {
|
|
1298
2269
|
text: "llm_chat",
|
|
1299
2270
|
image: "image_generation",
|
|
@@ -1301,7 +2272,7 @@ var MODEL_TYPE_MAP = {
|
|
|
1301
2272
|
};
|
|
1302
2273
|
function App({ runner }) {
|
|
1303
2274
|
const { exit } = useApp();
|
|
1304
|
-
const { stdout } =
|
|
2275
|
+
const { stdout } = useStdout7();
|
|
1305
2276
|
const {
|
|
1306
2277
|
status: connectionStatus,
|
|
1307
2278
|
environment,
|
|
@@ -1325,32 +2296,32 @@ function App({ runner }) {
|
|
|
1325
2296
|
syncedModels,
|
|
1326
2297
|
refresh: refreshSynced
|
|
1327
2298
|
} = useSyncedModels(connectionStatus);
|
|
1328
|
-
const shouldOnboard = getApiKey() === void 0;
|
|
1329
|
-
const [page, setPage] =
|
|
2299
|
+
const shouldOnboard = getApiKey() === void 0 || getUserId() === void 0;
|
|
2300
|
+
const [page, setPage] = useState15(
|
|
1330
2301
|
shouldOnboard ? "onboarding" : "dashboard"
|
|
1331
2302
|
);
|
|
1332
|
-
const [syncStatus, setSyncStatus] =
|
|
2303
|
+
const [syncStatus, setSyncStatus] = useState15(
|
|
1333
2304
|
"idle"
|
|
1334
2305
|
);
|
|
1335
|
-
const syncTimerRef =
|
|
1336
|
-
const lastSyncPayloadRef =
|
|
1337
|
-
|
|
2306
|
+
const syncTimerRef = useRef9();
|
|
2307
|
+
const lastSyncPayloadRef = useRef9("");
|
|
2308
|
+
useEffect15(() => {
|
|
1338
2309
|
if (connectionStatus === "not_authenticated") {
|
|
1339
2310
|
setPage("onboarding");
|
|
1340
2311
|
}
|
|
1341
2312
|
}, [connectionStatus]);
|
|
1342
|
-
|
|
2313
|
+
useEffect15(() => {
|
|
1343
2314
|
if (page === "dashboard") {
|
|
1344
2315
|
refreshAll();
|
|
1345
2316
|
}
|
|
1346
2317
|
}, [page]);
|
|
1347
|
-
|
|
2318
|
+
useEffect15(() => {
|
|
1348
2319
|
if (connectionStatus === "connected" && syncedModels.length > 0) {
|
|
1349
2320
|
runner.start(syncedModels);
|
|
1350
2321
|
}
|
|
1351
2322
|
}, [connectionStatus, syncedModels, runner]);
|
|
1352
|
-
|
|
1353
|
-
const refreshAll =
|
|
2323
|
+
useEffect15(() => () => runner.stop(), [runner]);
|
|
2324
|
+
const refreshAll = useCallback10(
|
|
1354
2325
|
async (silent = false) => {
|
|
1355
2326
|
if (!silent) setSyncStatus("syncing");
|
|
1356
2327
|
const [discoveredModels] = await Promise.all([
|
|
@@ -1380,23 +2351,26 @@ function App({ runner }) {
|
|
|
1380
2351
|
},
|
|
1381
2352
|
[refreshProviders, refreshModels, refreshSynced]
|
|
1382
2353
|
);
|
|
1383
|
-
|
|
2354
|
+
useEffect15(() => {
|
|
1384
2355
|
if (connectionStatus !== "connected" || page !== "dashboard") return;
|
|
1385
2356
|
const interval = setInterval(() => refreshAll(true), 1500);
|
|
1386
2357
|
return () => clearInterval(interval);
|
|
1387
2358
|
}, [connectionStatus, page, refreshAll]);
|
|
1388
|
-
const handleQuit =
|
|
2359
|
+
const handleQuit = useCallback10(() => {
|
|
1389
2360
|
runner.stop();
|
|
1390
2361
|
exit();
|
|
1391
2362
|
}, [runner, exit]);
|
|
1392
|
-
const handleOnboardingComplete =
|
|
2363
|
+
const handleOnboardingComplete = useCallback10(() => {
|
|
1393
2364
|
retryConnection();
|
|
1394
2365
|
refreshAll();
|
|
1395
2366
|
setPage("dashboard");
|
|
1396
2367
|
}, [retryConnection, refreshAll]);
|
|
1397
|
-
const handleNavigate =
|
|
2368
|
+
const handleNavigate = useCallback10(
|
|
1398
2369
|
(id) => {
|
|
1399
2370
|
switch (id) {
|
|
2371
|
+
case "interfaces":
|
|
2372
|
+
setPage("interfaces");
|
|
2373
|
+
break;
|
|
1400
2374
|
case "auth":
|
|
1401
2375
|
setPage("onboarding");
|
|
1402
2376
|
break;
|
|
@@ -1416,7 +2390,7 @@ function App({ runner }) {
|
|
|
1416
2390
|
const subpageMenuItems = [
|
|
1417
2391
|
{ id: "back", label: "Back", description: "Return to dashboard" }
|
|
1418
2392
|
];
|
|
1419
|
-
const handleSubpageNavigate =
|
|
2393
|
+
const handleSubpageNavigate = useCallback10(
|
|
1420
2394
|
(id) => {
|
|
1421
2395
|
if (id === "back") {
|
|
1422
2396
|
setPage("dashboard");
|
|
@@ -1426,11 +2400,11 @@ function App({ runner }) {
|
|
|
1426
2400
|
},
|
|
1427
2401
|
[handleNavigate]
|
|
1428
2402
|
);
|
|
1429
|
-
const [termSize, setTermSize] =
|
|
2403
|
+
const [termSize, setTermSize] = useState15({
|
|
1430
2404
|
rows: stdout?.rows ?? 24,
|
|
1431
2405
|
columns: stdout?.columns ?? 80
|
|
1432
2406
|
});
|
|
1433
|
-
|
|
2407
|
+
useEffect15(() => {
|
|
1434
2408
|
if (!stdout) return;
|
|
1435
2409
|
const onResize = () => {
|
|
1436
2410
|
stdout.write("\x1B[2J\x1B[H");
|
|
@@ -1443,8 +2417,8 @@ function App({ runner }) {
|
|
|
1443
2417
|
}, [stdout]);
|
|
1444
2418
|
const termHeight = termSize.rows - 4;
|
|
1445
2419
|
const compactHeader = termSize.rows <= 45 || termSize.columns <= 90;
|
|
1446
|
-
return /* @__PURE__ */
|
|
1447
|
-
/* @__PURE__ */
|
|
2420
|
+
return /* @__PURE__ */ jsx11(Box10, { flexDirection: "column", height: termHeight, overflow: "hidden", children: page === "onboarding" ? /* @__PURE__ */ jsx11(OnboardingPage, { onComplete: handleOnboardingComplete }) : /* @__PURE__ */ jsxs10(Fragment6, { children: [
|
|
2421
|
+
/* @__PURE__ */ jsx11(
|
|
1448
2422
|
Header,
|
|
1449
2423
|
{
|
|
1450
2424
|
connection: connectionStatus,
|
|
@@ -1454,7 +2428,7 @@ function App({ runner }) {
|
|
|
1454
2428
|
compact: compactHeader
|
|
1455
2429
|
}
|
|
1456
2430
|
),
|
|
1457
|
-
page === "dashboard" && /* @__PURE__ */
|
|
2431
|
+
page === "dashboard" && /* @__PURE__ */ jsx11(
|
|
1458
2432
|
DashboardPage,
|
|
1459
2433
|
{
|
|
1460
2434
|
requests,
|
|
@@ -1468,9 +2442,15 @@ function App({ runner }) {
|
|
|
1468
2442
|
onNavigate: handleNavigate
|
|
1469
2443
|
}
|
|
1470
2444
|
),
|
|
1471
|
-
page === "setup" && /* @__PURE__ */
|
|
1472
|
-
page
|
|
1473
|
-
|
|
2445
|
+
page === "setup" && /* @__PURE__ */ jsx11(SetupPage, { onBack: () => setPage("dashboard") }),
|
|
2446
|
+
page === "interfaces" && /* @__PURE__ */ jsx11(
|
|
2447
|
+
InterfacesPage,
|
|
2448
|
+
{
|
|
2449
|
+
onBack: () => setPage("dashboard")
|
|
2450
|
+
}
|
|
2451
|
+
),
|
|
2452
|
+
page !== "dashboard" && page !== "setup" && page !== "interfaces" && /* @__PURE__ */ jsx11(Box10, { flexGrow: 1 }),
|
|
2453
|
+
page !== "dashboard" && page !== "setup" && page !== "interfaces" && /* @__PURE__ */ jsx11(
|
|
1474
2454
|
NavigationMenu,
|
|
1475
2455
|
{
|
|
1476
2456
|
items: subpageMenuItems,
|
|
@@ -1520,24 +2500,24 @@ async function checkForUpdate() {
|
|
|
1520
2500
|
}
|
|
1521
2501
|
|
|
1522
2502
|
// src/tui/components/UpdatePrompt.tsx
|
|
1523
|
-
import { Box as
|
|
1524
|
-
import { jsx as
|
|
2503
|
+
import { Box as Box11, Text as Text11, useInput as useInput7 } from "ink";
|
|
2504
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1525
2505
|
function UpdatePrompt({
|
|
1526
2506
|
currentVersion,
|
|
1527
2507
|
latestVersion,
|
|
1528
2508
|
onChoice
|
|
1529
2509
|
}) {
|
|
1530
|
-
|
|
2510
|
+
useInput7((input) => {
|
|
1531
2511
|
if (input.toLowerCase() === "y") {
|
|
1532
2512
|
onChoice(true);
|
|
1533
2513
|
} else {
|
|
1534
2514
|
onChoice(false);
|
|
1535
2515
|
}
|
|
1536
2516
|
});
|
|
1537
|
-
return /* @__PURE__ */
|
|
1538
|
-
/* @__PURE__ */
|
|
1539
|
-
/* @__PURE__ */
|
|
1540
|
-
/* @__PURE__ */
|
|
2517
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
|
|
2518
|
+
/* @__PURE__ */ jsxs11(Text11, { children: [
|
|
2519
|
+
/* @__PURE__ */ jsx12(Text11, { color: "yellow", bold: true, children: "Update available:" }),
|
|
2520
|
+
/* @__PURE__ */ jsxs11(Text11, { children: [
|
|
1541
2521
|
" ",
|
|
1542
2522
|
"v",
|
|
1543
2523
|
currentVersion,
|
|
@@ -1547,20 +2527,20 @@ function UpdatePrompt({
|
|
|
1547
2527
|
latestVersion
|
|
1548
2528
|
] })
|
|
1549
2529
|
] }),
|
|
1550
|
-
/* @__PURE__ */
|
|
2530
|
+
/* @__PURE__ */ jsx12(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs11(Text11, { children: [
|
|
1551
2531
|
"Press ",
|
|
1552
|
-
/* @__PURE__ */
|
|
2532
|
+
/* @__PURE__ */ jsx12(Text11, { bold: true, color: "cyan", children: "y" }),
|
|
1553
2533
|
" to update, any other key to skip"
|
|
1554
2534
|
] }) })
|
|
1555
2535
|
] });
|
|
1556
2536
|
}
|
|
1557
2537
|
|
|
1558
2538
|
// src/tui/index.tsx
|
|
1559
|
-
import { jsx as
|
|
2539
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
1560
2540
|
async function promptForUpdate(currentVersion, latestVersion) {
|
|
1561
2541
|
return new Promise((resolve) => {
|
|
1562
2542
|
const { unmount } = render(
|
|
1563
|
-
/* @__PURE__ */
|
|
2543
|
+
/* @__PURE__ */ jsx13(
|
|
1564
2544
|
UpdatePrompt,
|
|
1565
2545
|
{
|
|
1566
2546
|
currentVersion,
|
|
@@ -1586,7 +2566,7 @@ async function startTUI() {
|
|
|
1586
2566
|
if (shouldUpdate) {
|
|
1587
2567
|
console.log("\nUpdating to v" + update.latestVersion + "...\n");
|
|
1588
2568
|
try {
|
|
1589
|
-
|
|
2569
|
+
execSync2("npm install -g @mindstudio-ai/local-model-tunnel@latest", {
|
|
1590
2570
|
stdio: "inherit"
|
|
1591
2571
|
});
|
|
1592
2572
|
console.log("\nRestarting...\n");
|
|
@@ -1602,7 +2582,7 @@ async function startTUI() {
|
|
|
1602
2582
|
}
|
|
1603
2583
|
const runner = new TunnelRunner();
|
|
1604
2584
|
const { waitUntilExit } = render(
|
|
1605
|
-
/* @__PURE__ */
|
|
2585
|
+
/* @__PURE__ */ jsx13(App, { runner }),
|
|
1606
2586
|
{
|
|
1607
2587
|
exitOnCtrlC: true
|
|
1608
2588
|
}
|
|
@@ -1613,4 +2593,4 @@ async function startTUI() {
|
|
|
1613
2593
|
export {
|
|
1614
2594
|
startTUI
|
|
1615
2595
|
};
|
|
1616
|
-
//# sourceMappingURL=tui-
|
|
2596
|
+
//# sourceMappingURL=tui-BW6XKMWK.js.map
|