@mindstudio-ai/local-model-tunnel 0.3.1 → 0.3.2
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-PTK4SJQK.js → chunk-V3RKCMCQ.js} +571 -706
- package/dist/chunk-V3RKCMCQ.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{tui-QOSKXZWU.js → tui-YFUZJIGF.js} +258 -101
- package/dist/tui-YFUZJIGF.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-PTK4SJQK.js.map +0 -1
- package/dist/tui-QOSKXZWU.js.map +0 -1
|
@@ -7,17 +7,19 @@ import {
|
|
|
7
7
|
getConfigPath,
|
|
8
8
|
getEnvironment,
|
|
9
9
|
getProviderStatuses,
|
|
10
|
-
|
|
10
|
+
getSyncedModels,
|
|
11
11
|
pollDeviceAuth,
|
|
12
|
-
registerLocalModel,
|
|
13
12
|
requestDeviceAuth,
|
|
14
13
|
requestEvents,
|
|
15
14
|
setApiKey,
|
|
15
|
+
syncLocalModel,
|
|
16
|
+
updateLocalModel,
|
|
16
17
|
verifyApiKey
|
|
17
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-V3RKCMCQ.js";
|
|
18
19
|
|
|
19
20
|
// src/tui/index.tsx
|
|
20
21
|
import { render } from "ink";
|
|
22
|
+
import { execFileSync, execSync } from "child_process";
|
|
21
23
|
|
|
22
24
|
// src/tui/App.tsx
|
|
23
25
|
import { useEffect as useEffect13, useCallback as useCallback10, useState as useState12 } from "react";
|
|
@@ -146,7 +148,7 @@ function NavigationMenu({ items, onSelect, title }) {
|
|
|
146
148
|
const separatorExtraLines = items.filter((item, idx) => item.isSeparator && idx > 0).length;
|
|
147
149
|
const menuHeight = items.length + 4 + separatorExtraLines;
|
|
148
150
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, marginBottom: 1, borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: "gray", children: [
|
|
149
|
-
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "gray",
|
|
151
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "gray", children: title ?? "Actions" }) }),
|
|
150
152
|
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: items.map((item, index) => {
|
|
151
153
|
if (item.isSeparator) {
|
|
152
154
|
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);
|
|
@@ -173,7 +175,7 @@ function NavigationMenu({ items, onSelect, title }) {
|
|
|
173
175
|
] })
|
|
174
176
|
] }, item.id);
|
|
175
177
|
}) }),
|
|
176
|
-
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, height: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "gray",
|
|
178
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, height: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "gray", wrap: "truncate-end", children: items.some((i) => i.id === "back") ? "Up/Down Navigate \u2022 Enter Select \u2022 q/Esc Back" : "Up/Down Navigate \u2022 Enter Select \u2022 q Quit" }) })
|
|
177
179
|
] });
|
|
178
180
|
}
|
|
179
181
|
|
|
@@ -244,12 +246,14 @@ function useProviders(pollInterval = 1e4) {
|
|
|
244
246
|
import { useState as useState4, useEffect as useEffect4, useCallback as useCallback3 } from "react";
|
|
245
247
|
function useModels() {
|
|
246
248
|
const [models, setModels] = useState4([]);
|
|
249
|
+
const [warnings, setWarnings] = useState4([]);
|
|
247
250
|
const [loading, setLoading] = useState4(true);
|
|
248
251
|
const refresh = useCallback3(async () => {
|
|
249
252
|
setLoading(true);
|
|
250
253
|
try {
|
|
251
254
|
const discoveredModels = await discoverAllModels();
|
|
252
|
-
setModels(discoveredModels);
|
|
255
|
+
setModels(discoveredModels.filter((m) => !m.statusHint));
|
|
256
|
+
setWarnings(discoveredModels.filter((m) => !!m.statusHint));
|
|
253
257
|
} catch {
|
|
254
258
|
} finally {
|
|
255
259
|
setLoading(false);
|
|
@@ -260,6 +264,7 @@ function useModels() {
|
|
|
260
264
|
}, [refresh]);
|
|
261
265
|
return {
|
|
262
266
|
models,
|
|
267
|
+
warnings,
|
|
263
268
|
loading,
|
|
264
269
|
refresh
|
|
265
270
|
};
|
|
@@ -293,10 +298,12 @@ function useRequests(maxHistory = 50) {
|
|
|
293
298
|
});
|
|
294
299
|
const unsubProgress = requestEvents.onProgress((event) => {
|
|
295
300
|
const existing = requestsRef.current.get(event.id);
|
|
296
|
-
if (existing && existing.status === "processing"
|
|
301
|
+
if (existing && existing.status === "processing") {
|
|
297
302
|
const updated = {
|
|
298
303
|
...existing,
|
|
299
|
-
content: event.content
|
|
304
|
+
...event.content !== void 0 && { content: event.content },
|
|
305
|
+
...event.step !== void 0 && { step: event.step },
|
|
306
|
+
...event.totalSteps !== void 0 && { totalSteps: event.totalSteps }
|
|
300
307
|
};
|
|
301
308
|
requestsRef.current.set(event.id, updated);
|
|
302
309
|
setRequests(
|
|
@@ -341,18 +348,18 @@ function useRequests(maxHistory = 50) {
|
|
|
341
348
|
|
|
342
349
|
// src/tui/hooks/useRegisteredModels.ts
|
|
343
350
|
import { useState as useState6, useEffect as useEffect6, useCallback as useCallback5 } from "react";
|
|
344
|
-
function
|
|
345
|
-
const [
|
|
351
|
+
function useSyncedModels(connectionStatus) {
|
|
352
|
+
const [syncedNames, setSyncedNames] = useState6(
|
|
346
353
|
/* @__PURE__ */ new Set()
|
|
347
354
|
);
|
|
348
355
|
const refresh = useCallback5(async () => {
|
|
349
356
|
if (connectionStatus !== "connected") {
|
|
350
|
-
|
|
357
|
+
setSyncedNames(/* @__PURE__ */ new Set());
|
|
351
358
|
return;
|
|
352
359
|
}
|
|
353
360
|
try {
|
|
354
|
-
const models = await
|
|
355
|
-
|
|
361
|
+
const models = await getSyncedModels();
|
|
362
|
+
setSyncedNames(new Set(models.map((m) => m.name)));
|
|
356
363
|
} catch {
|
|
357
364
|
}
|
|
358
365
|
}, [connectionStatus]);
|
|
@@ -360,7 +367,7 @@ function useRegisteredModels(connectionStatus) {
|
|
|
360
367
|
refresh();
|
|
361
368
|
}, [refresh]);
|
|
362
369
|
return {
|
|
363
|
-
|
|
370
|
+
syncedNames,
|
|
364
371
|
refresh
|
|
365
372
|
};
|
|
366
373
|
}
|
|
@@ -392,9 +399,9 @@ function getRequestTypeLabel(type) {
|
|
|
392
399
|
case "llm_chat":
|
|
393
400
|
return { label: "text", color: "gray" };
|
|
394
401
|
case "image_generation":
|
|
395
|
-
return { label: "image", color: "
|
|
402
|
+
return { label: "image", color: "gray" };
|
|
396
403
|
case "video_generation":
|
|
397
|
-
return { label: "video", color: "
|
|
404
|
+
return { label: "video", color: "gray" };
|
|
398
405
|
default:
|
|
399
406
|
return { label: type, color: "gray" };
|
|
400
407
|
}
|
|
@@ -412,6 +419,7 @@ function RequestItem({ request, width }) {
|
|
|
412
419
|
if (request.status === "processing") {
|
|
413
420
|
const elapsed = Date.now() - request.startTime;
|
|
414
421
|
const snippet = request.content && request.requestType === "llm_chat" ? snippetLine(request.content, snippetWidth) : null;
|
|
422
|
+
const stepProgress = request.step !== void 0 && request.totalSteps ? `Step ${request.step}/${request.totalSteps}` : null;
|
|
415
423
|
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
416
424
|
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
417
425
|
/* @__PURE__ */ jsx3(Text3, { color: "cyan", children: /* @__PURE__ */ jsx3(Spinner, { type: "dots" }) }),
|
|
@@ -429,9 +437,13 @@ function RequestItem({ request, width }) {
|
|
|
429
437
|
"..."
|
|
430
438
|
] })
|
|
431
439
|
] }),
|
|
432
|
-
snippet && /* @__PURE__ */ jsxs3(Text3, { color: "gray",
|
|
440
|
+
snippet && /* @__PURE__ */ jsxs3(Text3, { color: "gray", wrap: "truncate-end", children: [
|
|
433
441
|
snippetIndent,
|
|
434
442
|
snippet
|
|
443
|
+
] }),
|
|
444
|
+
stepProgress && /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
|
|
445
|
+
snippetIndent,
|
|
446
|
+
stepProgress
|
|
435
447
|
] })
|
|
436
448
|
] });
|
|
437
449
|
}
|
|
@@ -463,7 +475,7 @@ function RequestItem({ request, width }) {
|
|
|
463
475
|
resultInfo
|
|
464
476
|
] })
|
|
465
477
|
] }),
|
|
466
|
-
snippet && /* @__PURE__ */ jsxs3(Text3, { color: "gray",
|
|
478
|
+
snippet && /* @__PURE__ */ jsxs3(Text3, { color: "gray", wrap: "truncate-end", children: [
|
|
467
479
|
snippetIndent,
|
|
468
480
|
snippet
|
|
469
481
|
] })
|
|
@@ -490,7 +502,11 @@ function RequestLog({ requests, maxVisible = 8, hasModels = true }) {
|
|
|
490
502
|
const width = stdout?.columns ?? 80;
|
|
491
503
|
const activeRequests = requests.filter((r) => r.status === "processing");
|
|
492
504
|
const completedRequests = requests.filter((r) => r.status !== "processing");
|
|
493
|
-
const itemLines = (r) =>
|
|
505
|
+
const itemLines = (r) => {
|
|
506
|
+
if (r.requestType === "llm_chat" && r.content) return 2;
|
|
507
|
+
if (r.status === "processing" && r.step !== void 0) return 2;
|
|
508
|
+
return 1;
|
|
509
|
+
};
|
|
494
510
|
let completedToShow = [];
|
|
495
511
|
let linesUsed = activeRequests.reduce((sum, r) => sum + itemLines(r), 0);
|
|
496
512
|
for (let i = completedRequests.length - 1; i >= 0 && linesUsed < maxVisible; i--) {
|
|
@@ -521,7 +537,7 @@ function RequestLog({ requests, maxVisible = 8, hasModels = true }) {
|
|
|
521
537
|
" active)"
|
|
522
538
|
] })
|
|
523
539
|
] }),
|
|
524
|
-
requests.length === 0 ? /* @__PURE__ */ jsx3(Box3, {
|
|
540
|
+
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)) })
|
|
525
541
|
]
|
|
526
542
|
}
|
|
527
543
|
);
|
|
@@ -546,14 +562,19 @@ function useSetupProviders() {
|
|
|
546
562
|
|
|
547
563
|
// src/tui/pages/DashboardPage.tsx
|
|
548
564
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
565
|
+
function getWorkflowCount(model) {
|
|
566
|
+
const param = model.parameters?.find((p) => p.type === "comfyWorkflow");
|
|
567
|
+
if (!param) return null;
|
|
568
|
+
return param.comfyWorkflowOptions.availableWorkflows.length;
|
|
569
|
+
}
|
|
549
570
|
function getCapabilityLabel(capability) {
|
|
550
571
|
switch (capability) {
|
|
551
572
|
case "text":
|
|
552
573
|
return { label: "Text Generation", color: "gray" };
|
|
553
574
|
case "image":
|
|
554
|
-
return { label: "Image Generation", color: "
|
|
575
|
+
return { label: "Image Generation", color: "gray" };
|
|
555
576
|
case "video":
|
|
556
|
-
return { label: "Video Generation", color: "
|
|
577
|
+
return { label: "Video Generation", color: "gray" };
|
|
557
578
|
default:
|
|
558
579
|
return { label: capability, color: "gray" };
|
|
559
580
|
}
|
|
@@ -561,7 +582,8 @@ function getCapabilityLabel(capability) {
|
|
|
561
582
|
function DashboardPage({
|
|
562
583
|
requests,
|
|
563
584
|
models,
|
|
564
|
-
|
|
585
|
+
modelWarnings = [],
|
|
586
|
+
syncedNames,
|
|
565
587
|
modelsLoading,
|
|
566
588
|
onNavigate
|
|
567
589
|
}) {
|
|
@@ -574,7 +596,7 @@ function DashboardPage({
|
|
|
574
596
|
);
|
|
575
597
|
const provStatusWidth = "Local Server Running".length;
|
|
576
598
|
const allModelNames = new Set(models.map((m) => m.name));
|
|
577
|
-
const
|
|
599
|
+
const unavailableSynced = [...syncedNames].filter(
|
|
578
600
|
(name) => !allModelNames.has(name)
|
|
579
601
|
);
|
|
580
602
|
const menuItems = useMemo(() => {
|
|
@@ -610,7 +632,7 @@ function DashboardPage({
|
|
|
610
632
|
const headerLines = 14;
|
|
611
633
|
const providerContentLines = setupLoading ? 1 : installedProviders.length === 0 ? 2 : installedProviders.length;
|
|
612
634
|
const providersLines = 3 + providerContentLines;
|
|
613
|
-
const modelContentLines = modelsLoading ? 1 : models.length === 0 &&
|
|
635
|
+
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);
|
|
614
636
|
const modelsLines = 3 + modelContentLines;
|
|
615
637
|
const requestLogOverhead = 3;
|
|
616
638
|
const menuLines = menuItems.length + 6;
|
|
@@ -641,26 +663,40 @@ function DashboardPage({
|
|
|
641
663
|
modelsLoading ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
|
|
642
664
|
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: /* @__PURE__ */ jsx4(Spinner2, { type: "dots" }) }),
|
|
643
665
|
/* @__PURE__ */ jsx4(Text4, { children: " Discovering models..." })
|
|
644
|
-
] }) : models.length === 0 &&
|
|
666
|
+
] }) : models.length === 0 && unavailableSynced.length === 0 && modelWarnings.length === 0 ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
645
667
|
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: "No models found." }),
|
|
646
668
|
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: "Download models using your provider (e.g., ollama pull llama3.2)" })
|
|
647
669
|
] }) : /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, children: [
|
|
648
670
|
models.map((model) => {
|
|
649
671
|
const cap = getCapabilityLabel(model.capability);
|
|
650
|
-
const
|
|
672
|
+
const isSynced = syncedNames.has(model.name);
|
|
651
673
|
const displayProvider = providers.find((p) => p.provider.name === model.provider)?.provider.displayName ?? model.provider;
|
|
674
|
+
const workflowCount = getWorkflowCount(model);
|
|
675
|
+
const workflowSuffix = workflowCount !== null ? ` (${workflowCount} workflow${workflowCount !== 1 ? "s" : ""}, ${isSynced ? workflowCount : 0} synced)` : "";
|
|
652
676
|
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
653
|
-
/* @__PURE__ */ jsx4(Text4, { color:
|
|
677
|
+
/* @__PURE__ */ jsx4(Text4, { color: isSynced ? "green" : "gray", children: isSynced ? "\u25CF" : "\u25CB" }),
|
|
654
678
|
/* @__PURE__ */ jsx4(Text4, { color: "white", children: ` ${model.name}` }),
|
|
679
|
+
workflowSuffix && /* @__PURE__ */ jsx4(Text4, { color: "gray", children: workflowSuffix }),
|
|
655
680
|
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: " - " }),
|
|
656
681
|
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: displayProvider }),
|
|
657
682
|
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: " - " }),
|
|
658
683
|
/* @__PURE__ */ jsx4(Text4, { color: cap.color, children: cap.label })
|
|
659
684
|
] }, model.name);
|
|
660
685
|
}),
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
686
|
+
modelWarnings.map((warning) => {
|
|
687
|
+
const displayProvider = providers.find((p) => p.provider.name === warning.provider)?.provider.displayName ?? warning.provider;
|
|
688
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
689
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: "\u25CB" }),
|
|
690
|
+
/* @__PURE__ */ jsx4(Text4, { color: "white", children: ` ${warning.name}` }),
|
|
691
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: " - " }),
|
|
692
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: displayProvider }),
|
|
693
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: " - " }),
|
|
694
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: warning.statusHint })
|
|
695
|
+
] }, warning.name);
|
|
696
|
+
}),
|
|
697
|
+
unavailableSynced.length > 0 && /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: models.length > 0 ? 1 : 0, children: [
|
|
698
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: "Synced but not currently available:" }),
|
|
699
|
+
unavailableSynced.map((name) => /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
664
700
|
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: "\u25CB" }),
|
|
665
701
|
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: ` ${name}` })
|
|
666
702
|
] }, name))
|
|
@@ -691,13 +727,13 @@ var MODEL_TYPE_MAP = {
|
|
|
691
727
|
image: "image_generation",
|
|
692
728
|
video: "video_generation"
|
|
693
729
|
};
|
|
694
|
-
function
|
|
730
|
+
function useSync() {
|
|
695
731
|
const [status, setStatus] = useState8("idle");
|
|
696
732
|
const [progress, setProgress] = useState8({
|
|
697
733
|
current: 0,
|
|
698
734
|
total: 0
|
|
699
735
|
});
|
|
700
|
-
const [
|
|
736
|
+
const [syncedModels, setSyncedModels] = useState8(
|
|
701
737
|
[]
|
|
702
738
|
);
|
|
703
739
|
const [error, setError] = useState8(null);
|
|
@@ -711,10 +747,10 @@ function useRegister() {
|
|
|
711
747
|
cancelledRef.current = true;
|
|
712
748
|
setStatus("idle");
|
|
713
749
|
}, []);
|
|
714
|
-
const
|
|
750
|
+
const startSync = useCallback7(() => {
|
|
715
751
|
cancelledRef.current = false;
|
|
716
752
|
setError(null);
|
|
717
|
-
|
|
753
|
+
setSyncedModels([]);
|
|
718
754
|
const run = async () => {
|
|
719
755
|
try {
|
|
720
756
|
setStatus("discovering");
|
|
@@ -725,50 +761,48 @@ function useRegister() {
|
|
|
725
761
|
setStatus("error");
|
|
726
762
|
return;
|
|
727
763
|
}
|
|
728
|
-
const
|
|
764
|
+
const existingSynced = await getSyncedModels();
|
|
729
765
|
if (cancelledRef.current) return;
|
|
730
|
-
const
|
|
731
|
-
|
|
732
|
-
(m) => !registeredNames.has(m.name)
|
|
766
|
+
const remoteByName = new Map(
|
|
767
|
+
existingSynced.map((m) => [m.name, m.id])
|
|
733
768
|
);
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
capability: m.capability,
|
|
738
|
-
isNew: !registeredNames.has(m.name)
|
|
739
|
-
}));
|
|
740
|
-
if (unregisteredModels.length === 0) {
|
|
741
|
-
setRegisteredModels(allModels);
|
|
742
|
-
setProgress({ current: 0, total: 0 });
|
|
743
|
-
setStatus("done");
|
|
744
|
-
return;
|
|
745
|
-
}
|
|
746
|
-
setStatus("registering");
|
|
747
|
-
setProgress({ current: 0, total: unregisteredModels.length });
|
|
748
|
-
for (let i = 0; i < unregisteredModels.length; i++) {
|
|
769
|
+
setStatus("syncing");
|
|
770
|
+
setProgress({ current: 0, total: localModels.length });
|
|
771
|
+
for (let i = 0; i < localModels.length; i++) {
|
|
749
772
|
if (cancelledRef.current) return;
|
|
750
|
-
const model =
|
|
773
|
+
const model = localModels[i];
|
|
751
774
|
const modelType = MODEL_TYPE_MAP[model.capability];
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
775
|
+
const existingId = remoteByName.get(model.name);
|
|
776
|
+
if (existingId) {
|
|
777
|
+
await updateLocalModel({
|
|
778
|
+
modelId: existingId,
|
|
779
|
+
modelName: model.name,
|
|
780
|
+
provider: model.provider,
|
|
781
|
+
modelType,
|
|
782
|
+
parameters: model.parameters
|
|
783
|
+
});
|
|
784
|
+
} else {
|
|
785
|
+
await syncLocalModel({
|
|
786
|
+
modelName: model.name,
|
|
787
|
+
provider: model.provider,
|
|
788
|
+
modelType,
|
|
789
|
+
parameters: model.parameters
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
setProgress({ current: i + 1, total: localModels.length });
|
|
759
793
|
}
|
|
760
794
|
if (cancelledRef.current) return;
|
|
761
795
|
const finalModels = localModels.map((m) => ({
|
|
762
796
|
name: m.name,
|
|
763
797
|
provider: m.provider,
|
|
764
798
|
capability: m.capability,
|
|
765
|
-
isNew: !
|
|
799
|
+
isNew: !remoteByName.has(m.name)
|
|
766
800
|
}));
|
|
767
|
-
|
|
801
|
+
setSyncedModels(finalModels);
|
|
768
802
|
setStatus("done");
|
|
769
803
|
} catch (err) {
|
|
770
804
|
if (!cancelledRef.current) {
|
|
771
|
-
setError(err instanceof Error ? err.message : "
|
|
805
|
+
setError(err instanceof Error ? err.message : "Sync failed");
|
|
772
806
|
setStatus("error");
|
|
773
807
|
}
|
|
774
808
|
}
|
|
@@ -778,27 +812,27 @@ function useRegister() {
|
|
|
778
812
|
return {
|
|
779
813
|
status,
|
|
780
814
|
progress,
|
|
781
|
-
|
|
815
|
+
syncedModels,
|
|
782
816
|
error,
|
|
783
|
-
|
|
817
|
+
startSync,
|
|
784
818
|
cancel
|
|
785
819
|
};
|
|
786
820
|
}
|
|
787
821
|
|
|
788
822
|
// src/tui/pages/RegisterPage.tsx
|
|
789
823
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
790
|
-
function
|
|
791
|
-
const { status, progress,
|
|
824
|
+
function SyncPage() {
|
|
825
|
+
const { status, progress, syncedModels, error, startSync, cancel } = useSync();
|
|
792
826
|
useEffect9(() => {
|
|
793
|
-
|
|
827
|
+
startSync();
|
|
794
828
|
return () => cancel();
|
|
795
829
|
}, []);
|
|
796
830
|
if (status === "idle") {
|
|
797
|
-
return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginTop: 1, paddingX: 1, children: /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Starting model
|
|
831
|
+
return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginTop: 1, paddingX: 1, children: /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Starting model sync..." }) }) });
|
|
798
832
|
}
|
|
799
833
|
if (status === "error") {
|
|
800
834
|
return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginTop: 1, paddingX: 1, children: /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { color: "red", children: [
|
|
801
|
-
"
|
|
835
|
+
"Sync failed: ",
|
|
802
836
|
error
|
|
803
837
|
] }) }) });
|
|
804
838
|
}
|
|
@@ -808,12 +842,12 @@ function RegisterPage() {
|
|
|
808
842
|
/* @__PURE__ */ jsx5(Text5, { children: " Discovering local models..." })
|
|
809
843
|
] }) });
|
|
810
844
|
}
|
|
811
|
-
if (status === "
|
|
845
|
+
if (status === "syncing") {
|
|
812
846
|
return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginTop: 1, paddingX: 1, children: /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
|
|
813
847
|
/* @__PURE__ */ jsx5(Text5, { color: "cyan", children: /* @__PURE__ */ jsx5(Spinner3, { type: "dots" }) }),
|
|
814
848
|
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
815
849
|
" ",
|
|
816
|
-
"
|
|
850
|
+
"Syncing ",
|
|
817
851
|
progress.current,
|
|
818
852
|
"/",
|
|
819
853
|
progress.total,
|
|
@@ -821,12 +855,12 @@ function RegisterPage() {
|
|
|
821
855
|
] })
|
|
822
856
|
] }) });
|
|
823
857
|
}
|
|
824
|
-
const newModels =
|
|
825
|
-
const
|
|
858
|
+
const newModels = syncedModels.filter((m) => m.isNew);
|
|
859
|
+
const resyncedModels = syncedModels.filter((m) => !m.isNew);
|
|
826
860
|
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, paddingX: 1, children: [
|
|
827
|
-
newModels.length > 0
|
|
861
|
+
newModels.length > 0 && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
|
|
828
862
|
/* @__PURE__ */ jsxs5(Text5, { color: "green", children: [
|
|
829
|
-
"
|
|
863
|
+
"Synced ",
|
|
830
864
|
newModels.length,
|
|
831
865
|
" new model",
|
|
832
866
|
newModels.length !== 1 ? "s" : "",
|
|
@@ -844,18 +878,23 @@ function RegisterPage() {
|
|
|
844
878
|
"]"
|
|
845
879
|
] })
|
|
846
880
|
] }, m.name))
|
|
847
|
-
] })
|
|
848
|
-
|
|
849
|
-
/* @__PURE__ */ jsxs5(Text5, { color: "
|
|
850
|
-
"
|
|
851
|
-
|
|
852
|
-
"
|
|
881
|
+
] }),
|
|
882
|
+
resyncedModels.length > 0 && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
|
|
883
|
+
/* @__PURE__ */ jsxs5(Text5, { color: "green", children: [
|
|
884
|
+
"Resynced ",
|
|
885
|
+
resyncedModels.length,
|
|
886
|
+
" existing model",
|
|
887
|
+
resyncedModels.length !== 1 ? "s" : "",
|
|
888
|
+
":"
|
|
853
889
|
] }),
|
|
854
|
-
|
|
855
|
-
/* @__PURE__ */ jsx5(Text5, { color: "
|
|
856
|
-
/* @__PURE__ */ jsxs5(Text5, {
|
|
890
|
+
resyncedModels.map((m) => /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
891
|
+
/* @__PURE__ */ jsx5(Text5, { color: "green", children: " \u2713 " }),
|
|
892
|
+
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
857
893
|
m.name,
|
|
858
|
-
"
|
|
894
|
+
" "
|
|
895
|
+
] }),
|
|
896
|
+
/* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
|
|
897
|
+
"[",
|
|
859
898
|
m.provider,
|
|
860
899
|
"]"
|
|
861
900
|
] })
|
|
@@ -984,7 +1023,7 @@ function ProviderDetailView({
|
|
|
984
1023
|
borderRight: false,
|
|
985
1024
|
borderColor: "gray",
|
|
986
1025
|
children: [
|
|
987
|
-
/* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "gray",
|
|
1026
|
+
/* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "Actions" }) }),
|
|
988
1027
|
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
989
1028
|
/* @__PURE__ */ jsxs6(Text7, { color: "cyan", bold: true, children: [
|
|
990
1029
|
"\u276F",
|
|
@@ -992,7 +1031,7 @@ function ProviderDetailView({
|
|
|
992
1031
|
] }),
|
|
993
1032
|
/* @__PURE__ */ jsx7(Text7, { color: "gray", children: " - Return to providers" })
|
|
994
1033
|
] }),
|
|
995
|
-
/* @__PURE__ */ jsx7(Box6, { marginTop: 1, height: 1, children: /* @__PURE__ */ jsxs6(Text7, { color: "gray",
|
|
1034
|
+
/* @__PURE__ */ jsx7(Box6, { marginTop: 1, height: 1, children: /* @__PURE__ */ jsxs6(Text7, { color: "gray", wrap: "truncate-end", children: [
|
|
996
1035
|
"Up/Down Scroll ",
|
|
997
1036
|
"\u2022",
|
|
998
1037
|
" Enter/q/Esc Back",
|
|
@@ -1194,7 +1233,7 @@ function SetupPage({ onBack }) {
|
|
|
1194
1233
|
}
|
|
1195
1234
|
) })
|
|
1196
1235
|
] }),
|
|
1197
|
-
/* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text7, { color: "gray",
|
|
1236
|
+
/* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text7, { color: "gray", children: [
|
|
1198
1237
|
"Up/Down Navigate ",
|
|
1199
1238
|
"\u2022",
|
|
1200
1239
|
" Enter Select ",
|
|
@@ -1435,11 +1474,12 @@ function App({ runner }) {
|
|
|
1435
1474
|
const { refresh: refreshProviders } = useProviders();
|
|
1436
1475
|
const {
|
|
1437
1476
|
models,
|
|
1477
|
+
warnings: modelWarnings,
|
|
1438
1478
|
loading: modelsLoading,
|
|
1439
1479
|
refresh: refreshModels
|
|
1440
1480
|
} = useModels();
|
|
1441
1481
|
const { requests } = useRequests();
|
|
1442
|
-
const {
|
|
1482
|
+
const { syncedNames, refresh: refreshSynced } = useSyncedModels(connectionStatus);
|
|
1443
1483
|
const shouldOnboard = getApiKey() === void 0;
|
|
1444
1484
|
const [page, setPage] = useState12(
|
|
1445
1485
|
shouldOnboard ? "onboarding" : "dashboard"
|
|
@@ -1459,9 +1499,9 @@ function App({ runner }) {
|
|
|
1459
1499
|
await Promise.all([
|
|
1460
1500
|
refreshProviders(),
|
|
1461
1501
|
refreshModels(),
|
|
1462
|
-
|
|
1502
|
+
refreshSynced()
|
|
1463
1503
|
]);
|
|
1464
|
-
}, [refreshProviders, refreshModels,
|
|
1504
|
+
}, [refreshProviders, refreshModels, refreshSynced]);
|
|
1465
1505
|
const handleQuit = useCallback10(() => {
|
|
1466
1506
|
runner.stop();
|
|
1467
1507
|
exit();
|
|
@@ -1478,7 +1518,7 @@ function App({ runner }) {
|
|
|
1478
1518
|
setPage("onboarding");
|
|
1479
1519
|
break;
|
|
1480
1520
|
case "register":
|
|
1481
|
-
setPage("
|
|
1521
|
+
setPage("sync");
|
|
1482
1522
|
break;
|
|
1483
1523
|
case "setup":
|
|
1484
1524
|
setPage("setup");
|
|
@@ -1491,7 +1531,7 @@ function App({ runner }) {
|
|
|
1491
1531
|
break;
|
|
1492
1532
|
}
|
|
1493
1533
|
},
|
|
1494
|
-
[refreshModels,
|
|
1534
|
+
[refreshModels, refreshSynced, refreshAll, handleQuit]
|
|
1495
1535
|
);
|
|
1496
1536
|
const subpageMenuItems = [
|
|
1497
1537
|
{ id: "back", label: "Back", description: "Return to dashboard" }
|
|
@@ -1522,13 +1562,14 @@ function App({ runner }) {
|
|
|
1522
1562
|
{
|
|
1523
1563
|
requests,
|
|
1524
1564
|
models,
|
|
1525
|
-
|
|
1565
|
+
modelWarnings,
|
|
1566
|
+
syncedNames,
|
|
1526
1567
|
modelsLoading,
|
|
1527
1568
|
onNavigate: handleNavigate
|
|
1528
1569
|
}
|
|
1529
1570
|
),
|
|
1530
1571
|
page === "setup" && /* @__PURE__ */ jsx9(SetupPage, { onBack: () => setPage("dashboard") }),
|
|
1531
|
-
page === "
|
|
1572
|
+
page === "sync" && /* @__PURE__ */ jsx9(SyncPage, {}),
|
|
1532
1573
|
page !== "dashboard" && page !== "setup" && /* @__PURE__ */ jsx9(Box8, { flexGrow: 1 }),
|
|
1533
1574
|
page !== "dashboard" && page !== "setup" && /* @__PURE__ */ jsx9(
|
|
1534
1575
|
NavigationMenu,
|
|
@@ -1540,13 +1581,129 @@ function App({ runner }) {
|
|
|
1540
1581
|
] }) });
|
|
1541
1582
|
}
|
|
1542
1583
|
|
|
1584
|
+
// src/update.ts
|
|
1585
|
+
import { createRequire as createRequire2 } from "module";
|
|
1586
|
+
var require3 = createRequire2(import.meta.url);
|
|
1587
|
+
var pkg2 = require3("../package.json");
|
|
1588
|
+
function getCurrentVersion() {
|
|
1589
|
+
return pkg2.version;
|
|
1590
|
+
}
|
|
1591
|
+
async function fetchLatestVersion() {
|
|
1592
|
+
try {
|
|
1593
|
+
const res = await fetch(
|
|
1594
|
+
"https://registry.npmjs.org/@mindstudio-ai/local-model-tunnel/latest",
|
|
1595
|
+
{ signal: AbortSignal.timeout(5e3) }
|
|
1596
|
+
);
|
|
1597
|
+
if (!res.ok) return null;
|
|
1598
|
+
const data = await res.json();
|
|
1599
|
+
return data.version ?? null;
|
|
1600
|
+
} catch {
|
|
1601
|
+
return null;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
function isNewerVersion(current, latest) {
|
|
1605
|
+
const currentParts = current.split(".").map(Number);
|
|
1606
|
+
const latestParts = latest.split(".").map(Number);
|
|
1607
|
+
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
|
|
1608
|
+
const c = currentParts[i] ?? 0;
|
|
1609
|
+
const l = latestParts[i] ?? 0;
|
|
1610
|
+
if (l > c) return true;
|
|
1611
|
+
if (l < c) return false;
|
|
1612
|
+
}
|
|
1613
|
+
return false;
|
|
1614
|
+
}
|
|
1615
|
+
async function checkForUpdate() {
|
|
1616
|
+
const currentVersion = getCurrentVersion();
|
|
1617
|
+
const latestVersion = await fetchLatestVersion();
|
|
1618
|
+
if (!latestVersion) return null;
|
|
1619
|
+
if (!isNewerVersion(currentVersion, latestVersion)) return null;
|
|
1620
|
+
return { currentVersion, latestVersion };
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
// src/tui/components/UpdatePrompt.tsx
|
|
1624
|
+
import { Box as Box9, Text as Text9, useInput as useInput4 } from "ink";
|
|
1625
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1626
|
+
function UpdatePrompt({
|
|
1627
|
+
currentVersion,
|
|
1628
|
+
latestVersion,
|
|
1629
|
+
onChoice
|
|
1630
|
+
}) {
|
|
1631
|
+
useInput4((input) => {
|
|
1632
|
+
if (input.toLowerCase() === "y") {
|
|
1633
|
+
onChoice(true);
|
|
1634
|
+
} else {
|
|
1635
|
+
onChoice(false);
|
|
1636
|
+
}
|
|
1637
|
+
});
|
|
1638
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
|
|
1639
|
+
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
1640
|
+
/* @__PURE__ */ jsx10(Text9, { color: "yellow", bold: true, children: "Update available:" }),
|
|
1641
|
+
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
1642
|
+
" ",
|
|
1643
|
+
"v",
|
|
1644
|
+
currentVersion,
|
|
1645
|
+
" ",
|
|
1646
|
+
"\u2192",
|
|
1647
|
+
" v",
|
|
1648
|
+
latestVersion
|
|
1649
|
+
] })
|
|
1650
|
+
] }),
|
|
1651
|
+
/* @__PURE__ */ jsx10(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
1652
|
+
"Press ",
|
|
1653
|
+
/* @__PURE__ */ jsx10(Text9, { bold: true, color: "cyan", children: "y" }),
|
|
1654
|
+
" to update, any other key to skip"
|
|
1655
|
+
] }) })
|
|
1656
|
+
] });
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1543
1659
|
// src/tui/index.tsx
|
|
1544
|
-
import { jsx as
|
|
1660
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1661
|
+
async function promptForUpdate(currentVersion, latestVersion) {
|
|
1662
|
+
return new Promise((resolve) => {
|
|
1663
|
+
const { unmount } = render(
|
|
1664
|
+
/* @__PURE__ */ jsx11(
|
|
1665
|
+
UpdatePrompt,
|
|
1666
|
+
{
|
|
1667
|
+
currentVersion,
|
|
1668
|
+
latestVersion,
|
|
1669
|
+
onChoice: (shouldUpdate) => {
|
|
1670
|
+
unmount();
|
|
1671
|
+
resolve(shouldUpdate);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
),
|
|
1675
|
+
{ exitOnCtrlC: true }
|
|
1676
|
+
);
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1545
1679
|
async function startTUI() {
|
|
1546
1680
|
console.clear();
|
|
1681
|
+
const update = await checkForUpdate();
|
|
1682
|
+
if (update) {
|
|
1683
|
+
const shouldUpdate = await promptForUpdate(
|
|
1684
|
+
update.currentVersion,
|
|
1685
|
+
update.latestVersion
|
|
1686
|
+
);
|
|
1687
|
+
if (shouldUpdate) {
|
|
1688
|
+
console.log("\nUpdating to v" + update.latestVersion + "...\n");
|
|
1689
|
+
try {
|
|
1690
|
+
execSync("npm install -g @mindstudio-ai/local-model-tunnel@latest", {
|
|
1691
|
+
stdio: "inherit"
|
|
1692
|
+
});
|
|
1693
|
+
console.log("\nRestarting...\n");
|
|
1694
|
+
execFileSync(process.execPath, process.argv.slice(1), {
|
|
1695
|
+
stdio: "inherit"
|
|
1696
|
+
});
|
|
1697
|
+
} catch {
|
|
1698
|
+
console.error("\nUpdate failed. Continuing with current version.\n");
|
|
1699
|
+
}
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
console.clear();
|
|
1703
|
+
}
|
|
1547
1704
|
const runner = new TunnelRunner();
|
|
1548
1705
|
const { waitUntilExit } = render(
|
|
1549
|
-
/* @__PURE__ */
|
|
1706
|
+
/* @__PURE__ */ jsx11(App, { runner }),
|
|
1550
1707
|
{
|
|
1551
1708
|
exitOnCtrlC: true
|
|
1552
1709
|
}
|
|
@@ -1557,4 +1714,4 @@ async function startTUI() {
|
|
|
1557
1714
|
export {
|
|
1558
1715
|
startTUI
|
|
1559
1716
|
};
|
|
1560
|
-
//# sourceMappingURL=tui-
|
|
1717
|
+
//# sourceMappingURL=tui-YFUZJIGF.js.map
|