dsh-plugin-local-infer 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- package/docs/zh/README.md +10 -2
- package/dsh/client.js +669 -191
- package/package.json +6 -1
- package/packages/core/lib/adapter.d.ts +44 -2
- package/packages/core/lib/deps.d.ts +4 -3
- package/packages/core/lib/index.d.ts +1 -1
- package/packages/core/lib/registrar.d.ts +5 -5
- package/packages/core/lib/registrar.js +3 -3
- package/packages/core/package.json +1 -1
- package/packages/providers/lib/acquire.d.ts +5 -3
- package/packages/providers/lib/acquire.js +30 -9
- package/packages/providers/lib/adapters.d.ts +19 -209
- package/packages/providers/lib/adapters.js +13 -886
- package/packages/providers/lib/deps.d.ts +24 -2
- package/packages/providers/lib/deps.js +23 -4
- package/packages/providers/lib/engines/hyperqwenAdapter.d.ts +38 -0
- package/packages/providers/lib/engines/hyperqwenAdapter.js +105 -0
- package/packages/providers/lib/{infernix.js → engines/infernix.js} +1 -1
- package/packages/providers/lib/engines/infernixAdapter.d.ts +53 -0
- package/packages/providers/lib/engines/infernixAdapter.js +202 -0
- package/packages/providers/lib/engines/llamacppAdapter.d.ts +51 -0
- package/packages/providers/lib/engines/llamacppAdapter.js +232 -0
- package/packages/providers/lib/{ninfer.js → engines/ninfer.js} +1 -1
- package/packages/providers/lib/engines/ninferAdapter.d.ts +82 -0
- package/packages/providers/lib/engines/ninferAdapter.js +324 -0
- package/packages/providers/lib/engines/strata.d.ts +53 -0
- package/packages/providers/lib/engines/strata.js +197 -0
- package/packages/providers/lib/engines/strataAdapter.d.ts +98 -0
- package/packages/providers/lib/engines/strataAdapter.js +427 -0
- package/packages/providers/lib/engines.d.ts +5 -4
- package/packages/providers/lib/engines.js +10 -10
- package/packages/providers/lib/guidelines.js +1 -1
- package/packages/providers/lib/index.d.ts +14 -9
- package/packages/providers/lib/index.js +17 -7
- package/packages/providers/lib/model-defaults.d.ts +5 -4
- package/packages/providers/lib/model-defaults.js +22 -4
- package/packages/providers/lib/platform.js +7 -1
- package/packages/providers/package.json +1 -1
- package/packages/service/lib/index.d.ts +13 -4
- package/packages/service/lib/index.js +79 -5
- package/packages/service/lib/types.d.ts +5 -4
- package/packages/service/package.json +1 -1
- package/packages/tool/package.json +1 -1
- package/packages/ui/lib/config.d.ts +79 -0
- package/packages/ui/lib/config.js +235 -0
- package/packages/ui/lib/deps.d.ts +3 -3
- package/packages/ui/lib/ops/engine.d.ts +108 -0
- package/packages/ui/lib/ops/engine.js +493 -0
- package/packages/ui/lib/ops/hf.d.ts +27 -0
- package/packages/ui/lib/ops/hf.js +60 -0
- package/packages/ui/lib/ops/logs.d.ts +23 -0
- package/packages/ui/lib/ops/logs.js +58 -0
- package/packages/ui/lib/ops/model.d.ts +79 -0
- package/packages/ui/lib/ops/model.js +130 -0
- package/packages/ui/lib/request.d.ts +106 -0
- package/packages/ui/lib/request.js +80 -0
- package/packages/ui/lib/route.d.ts +88 -420
- package/packages/ui/lib/route.js +111 -1843
- package/packages/ui/lib/routes/chat-template.d.ts +14 -0
- package/packages/ui/lib/routes/chat-template.js +37 -0
- package/packages/ui/lib/routes/deps.d.ts +16 -0
- package/packages/ui/lib/routes/deps.js +27 -0
- package/packages/ui/lib/routes/download.d.ts +16 -0
- package/packages/ui/lib/routes/download.js +48 -0
- package/packages/ui/lib/routes/get.d.ts +18 -0
- package/packages/ui/lib/routes/get.js +57 -0
- package/packages/ui/lib/routes/install.d.ts +16 -0
- package/packages/ui/lib/routes/install.js +36 -0
- package/packages/ui/lib/routes/launch.d.ts +18 -0
- package/packages/ui/lib/routes/launch.js +50 -0
- package/packages/ui/lib/routes/provider-delete.d.ts +15 -0
- package/packages/ui/lib/routes/provider-delete.js +37 -0
- package/packages/ui/lib/routes/register.d.ts +16 -0
- package/packages/ui/lib/routes/register.js +28 -0
- package/packages/ui/lib/routes/save.d.ts +18 -0
- package/packages/ui/lib/routes/save.js +139 -0
- package/packages/ui/lib/routes/stop.d.ts +16 -0
- package/packages/ui/lib/routes/stop.js +29 -0
- package/packages/ui/lib/routes/summary.d.ts +15 -0
- package/packages/ui/lib/routes/summary.js +26 -0
- package/packages/ui/lib/routes/unregister.d.ts +14 -0
- package/packages/ui/lib/routes/unregister.js +26 -0
- package/packages/ui/lib/viewmodels/deps.d.ts +31 -0
- package/packages/ui/lib/viewmodels/deps.js +89 -0
- package/packages/ui/lib/viewmodels/depsHelpers.d.ts +28 -0
- package/packages/ui/lib/viewmodels/depsHelpers.js +33 -0
- package/packages/ui/lib/viewmodels/summary.d.ts +140 -0
- package/packages/ui/lib/viewmodels/summary.js +225 -0
- package/packages/ui/lib/viewmodels/summaryHelpers.d.ts +30 -0
- package/packages/ui/lib/viewmodels/summaryHelpers.js +42 -0
- package/packages/ui/package.json +1 -1
- package/packages/providers/lib/strata.d.ts +0 -15
- package/packages/providers/lib/strata.js +0 -14
- /package/packages/providers/lib/{hyperqwen.d.ts → engines/hyperqwen.d.ts} +0 -0
- /package/packages/providers/lib/{hyperqwen.js → engines/hyperqwen.js} +0 -0
- /package/packages/providers/lib/{infernix.d.ts → engines/infernix.d.ts} +0 -0
- /package/packages/providers/lib/{llamacpp.d.ts → engines/llamacpp.d.ts} +0 -0
- /package/packages/providers/lib/{llamacpp.js → engines/llamacpp.js} +0 -0
- /package/packages/providers/lib/{ninfer.d.ts → engines/ninfer.d.ts} +0 -0
package/dsh/client.js
CHANGED
|
@@ -83,7 +83,7 @@ window.__ModuleLoader__.load({
|
|
|
83
83
|
run: 'Run',
|
|
84
84
|
running: 'running...',
|
|
85
85
|
get: 'Get',
|
|
86
|
-
getting: '
|
|
86
|
+
getting: 'Getting...',
|
|
87
87
|
getFailed: 'get failed',
|
|
88
88
|
allowRun: 'Allow one-click install (sudo)',
|
|
89
89
|
depsSatisfied: 'All dependencies satisfied.',
|
|
@@ -149,6 +149,7 @@ window.__ModuleLoader__.load({
|
|
|
149
149
|
registerNeedsSave: 'Save the Provider configuration before registering with DSH.',
|
|
150
150
|
logs: 'Logs',
|
|
151
151
|
logLocation: 'Log location',
|
|
152
|
+
resize: 'resize',
|
|
152
153
|
entryLaunch: 'Launch',
|
|
153
154
|
entryStop: 'Stop',
|
|
154
155
|
entryLogs: 'Logs',
|
|
@@ -284,6 +285,7 @@ window.__ModuleLoader__.load({
|
|
|
284
285
|
registerNeedsSave: '注册到 DSH 之前请先保存该提供方配置。',
|
|
285
286
|
logs: '日志',
|
|
286
287
|
logLocation: '日志位置',
|
|
288
|
+
resize: '调整大小',
|
|
287
289
|
entryLaunch: '启动',
|
|
288
290
|
entryStop: '停止',
|
|
289
291
|
entryLogs: '日志',
|
|
@@ -459,15 +461,23 @@ window.__ModuleLoader__.load({
|
|
|
459
461
|
border: 'none',
|
|
460
462
|
});
|
|
461
463
|
|
|
462
|
-
// The per-entry
|
|
463
|
-
// The states are the button's reads: idle (a plain Launch), the
|
|
464
|
-
// rebuild phases (prereqs, download, build, launch), running (a
|
|
464
|
+
// The per-entry provider state machine: the card's Launch/Stop control
|
|
465
|
+
// lifecycle. The states are the button's reads: idle (a plain Launch), the
|
|
466
|
+
// four rebuild phases (prereqs, download, build, launch), running (a
|
|
467
|
+
// Stop), stopping (a disabled throbber while the stop is in flight), and
|
|
465
468
|
// failed (a red Logs). launchNextState is the hardened transition: a step
|
|
466
469
|
// event only advances forward through the rebuild phases (a stale or
|
|
467
470
|
// out-of-order step is a no-op), a healthy result settles to running, a
|
|
468
|
-
// failed result settles to failed,
|
|
469
|
-
//
|
|
470
|
-
//
|
|
471
|
+
// failed result settles to failed, a stop moves a running (or idle, a
|
|
472
|
+
// provider running per the summary with no machine entry) engine to
|
|
473
|
+
// stopping, a confirmed stop settles to idle, a stop that is still running
|
|
474
|
+
// at the deadline settles to failed with the cause 'stop', and a break
|
|
475
|
+
// (the Logs control, a navigation away) resets: a launch failure reads as
|
|
476
|
+
// Launch (idle), a stop timeout reads as Stop (running), because the
|
|
477
|
+
// engine is probably still up. The machine takes the entry ({status,
|
|
478
|
+
// cause}) and returns the next entry, so the cause-aware reset stays in
|
|
479
|
+
// one place. An event that names no legal next state leaves the control
|
|
480
|
+
// where it is, so a late event never corrupts it.
|
|
471
481
|
var LAUNCH_STEP_ORDER = { prereqs: 0, download: 1, build: 2, launch: 3 };
|
|
472
482
|
var LAUNCH_PHASE_STATES = ['prereqs', 'download', 'build', 'launch'];
|
|
473
483
|
// The health-poll deadline: the route's poll runs up to two minutes, so a
|
|
@@ -476,41 +486,99 @@ window.__ModuleLoader__.load({
|
|
|
476
486
|
// (prereqs, download, build) run as long as they need to, so a slow build
|
|
477
487
|
// never reads as a failure.
|
|
478
488
|
var LAUNCH_HEALTH_DEADLINE_MS = 150000;
|
|
489
|
+
// The stop-poll interval: while a stop is in flight, the card re-fetches
|
|
490
|
+
// the summary on this cadence so the provider's status dot repaints the
|
|
491
|
+
// moment the engine is down, instead of waiting out the host's 5-second
|
|
492
|
+
// process-watch and probe cycle.
|
|
493
|
+
var STOP_POLL_MS = 1000;
|
|
494
|
+
// The stop deadline: a provider still running 10 seconds after a Stop
|
|
495
|
+
// click settles to the red Logs state (the same path a failed launch
|
|
496
|
+
// takes), so the user can open the entry, fix the config, and retry.
|
|
497
|
+
var STOP_HEALTH_DEADLINE_MS = 10000;
|
|
479
498
|
|
|
480
499
|
function launchIsPhase(state) {
|
|
481
500
|
return LAUNCH_PHASE_STATES.indexOf(state) !== -1;
|
|
482
501
|
}
|
|
483
502
|
|
|
484
|
-
function launchNextState(
|
|
503
|
+
function launchNextState(entry, event) {
|
|
504
|
+
var current = entry && entry.status !== undefined ? entry.status : 'idle';
|
|
505
|
+
var cause = entry && entry.cause !== undefined ? entry.cause : undefined;
|
|
485
506
|
var type = event.type;
|
|
507
|
+
var next;
|
|
508
|
+
var nextCause;
|
|
486
509
|
if (type === 'start') {
|
|
487
|
-
// A launch begins from idle (a plain Launch)
|
|
488
|
-
// after the Logs control cleared the failed state)
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
510
|
+
// A launch begins from idle (a plain Launch), failed (a re-launch
|
|
511
|
+
// after the Logs control cleared the failed state), or downloading
|
|
512
|
+
// (a re-poll while the engine fetches its model files).
|
|
513
|
+
next =
|
|
514
|
+
current === 'idle' || current === 'failed' || current === 'downloading'
|
|
515
|
+
? 'prereqs'
|
|
516
|
+
: current;
|
|
517
|
+
nextCause = undefined;
|
|
518
|
+
} else if (type === 'step') {
|
|
492
519
|
var target = event.step;
|
|
493
|
-
if (LAUNCH_STEP_ORDER[target] === undefined)
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
520
|
+
if (LAUNCH_STEP_ORDER[target] === undefined) {
|
|
521
|
+
next = current;
|
|
522
|
+
nextCause = cause;
|
|
523
|
+
} else if (current === 'idle' || current === 'failed') {
|
|
524
|
+
next = target;
|
|
525
|
+
nextCause = undefined;
|
|
526
|
+
} else if (current === 'running' || current === 'stopping') {
|
|
527
|
+
// A stop in flight supersedes a late launch step.
|
|
528
|
+
next = current;
|
|
529
|
+
nextCause = cause;
|
|
530
|
+
} else {
|
|
531
|
+
var currentOrder = LAUNCH_STEP_ORDER[current];
|
|
532
|
+
if (currentOrder === undefined) {
|
|
533
|
+
next = current;
|
|
534
|
+
nextCause = cause;
|
|
535
|
+
} else {
|
|
536
|
+
next = LAUNCH_STEP_ORDER[target] >= currentOrder ? target : current;
|
|
537
|
+
nextCause = undefined;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
} else if (type === 'healthy') {
|
|
541
|
+
next = launchIsPhase(current) ? 'running' : current;
|
|
542
|
+
nextCause = undefined;
|
|
543
|
+
} else if (type === 'failed') {
|
|
544
|
+
next = launchIsPhase(current) ? 'failed' : current;
|
|
545
|
+
nextCause = undefined;
|
|
546
|
+
} else if (type === 'downloading') {
|
|
547
|
+
// The engine is fetching its model files: a busy throbber, not a
|
|
548
|
+
// failure. The card re-polls (a re-launch) until the engine reports
|
|
549
|
+
// ready, which settles the control to running.
|
|
550
|
+
next = launchIsPhase(current) ? 'downloading' : current;
|
|
551
|
+
nextCause = undefined;
|
|
552
|
+
} else if (type === 'stop') {
|
|
553
|
+
// A stop moves a running engine (or an idle one running per the
|
|
554
|
+
// summary, or a launch in flight) to the stopping state; a stop
|
|
555
|
+
// already in flight is a no-op.
|
|
556
|
+
next =
|
|
557
|
+
current === 'running' ||
|
|
558
|
+
current === 'stopping' ||
|
|
559
|
+
current === 'idle' ||
|
|
560
|
+
launchIsPhase(current)
|
|
561
|
+
? 'stopping'
|
|
562
|
+
: current;
|
|
563
|
+
nextCause = undefined;
|
|
564
|
+
} else if (type === 'stopped') {
|
|
565
|
+
// The fast poll confirmed the provider is no longer running.
|
|
566
|
+
next = current === 'stopping' ? 'idle' : current;
|
|
567
|
+
nextCause = undefined;
|
|
568
|
+
} else if (type === 'stopFailed') {
|
|
569
|
+
// The deadline fired while the provider was still running.
|
|
570
|
+
next = current === 'stopping' ? 'failed' : current;
|
|
571
|
+
nextCause = current === 'stopping' ? 'stop' : cause;
|
|
572
|
+
} else if (type === 'reset') {
|
|
573
|
+
// A launch failure reads as Launch (idle); a stop timeout reads as
|
|
574
|
+
// Stop (running), because the engine is probably still up.
|
|
575
|
+
next = current === 'failed' ? (cause === 'stop' ? 'running' : 'idle') : 'idle';
|
|
576
|
+
nextCause = undefined;
|
|
577
|
+
} else {
|
|
578
|
+
next = current;
|
|
579
|
+
nextCause = cause;
|
|
512
580
|
}
|
|
513
|
-
return
|
|
581
|
+
return { status: next, cause: nextCause };
|
|
514
582
|
}
|
|
515
583
|
|
|
516
584
|
// The deps panel. The install block is a copy/paste target, so it gets a
|
|
@@ -917,20 +985,36 @@ window.__ModuleLoader__.load({
|
|
|
917
985
|
};
|
|
918
986
|
var phaseLabel = phaseLabels[status];
|
|
919
987
|
if (phaseLabel !== undefined) {
|
|
920
|
-
return react
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
988
|
+
return renderProcessButton(react, primitives, {
|
|
989
|
+
busy: true,
|
|
990
|
+
label: phaseLabel,
|
|
991
|
+
busyLabel: phaseLabel,
|
|
992
|
+
variant: 'ghost',
|
|
993
|
+
onClick: function () {},
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
// A stop in flight: a disabled control with the spinner and the
|
|
997
|
+
// stopping label, the same throbber the launch phases show.
|
|
998
|
+
if (status === 'stopping') {
|
|
999
|
+
return renderProcessButton(react, primitives, {
|
|
1000
|
+
busy: true,
|
|
1001
|
+
label: opts.stoppingLabel,
|
|
1002
|
+
busyLabel: opts.stoppingLabel,
|
|
1003
|
+
variant: 'ghost',
|
|
1004
|
+
onClick: function () {},
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
// The engine is fetching its model files (the Strata download): a busy
|
|
1008
|
+
// throbber that reads as in progress, not a failure. The card re-polls
|
|
1009
|
+
// (a re-launch) until the engine reports ready.
|
|
1010
|
+
if (status === 'downloading') {
|
|
1011
|
+
return renderProcessButton(react, primitives, {
|
|
1012
|
+
busy: true,
|
|
1013
|
+
label: opts.downloadingLabel,
|
|
1014
|
+
busyLabel: opts.downloadingLabel,
|
|
1015
|
+
variant: 'ghost',
|
|
1016
|
+
onClick: function () {},
|
|
1017
|
+
});
|
|
934
1018
|
}
|
|
935
1019
|
// The Stop control reads from the provider status dot, not just the
|
|
936
1020
|
// per-entry launch state: the launch state is lost when the user
|
|
@@ -979,6 +1063,43 @@ window.__ModuleLoader__.load({
|
|
|
979
1063
|
);
|
|
980
1064
|
}
|
|
981
1065
|
|
|
1066
|
+
// The owner/repo short name the provider title bar renders when an entry
|
|
1067
|
+
// names a fork (the git repo that overrides the engine default upstream).
|
|
1068
|
+
// It accepts a protocol URL (https://github.com/owner/repo), an scp-style
|
|
1069
|
+
// git@host:owner/repo reference, and an already-short owner/repo form,
|
|
1070
|
+
// stripping a trailing .git. It returns undefined when no repo name is
|
|
1071
|
+
// present, so the title bar falls back to the engine label.
|
|
1072
|
+
function repoShortName(ref) {
|
|
1073
|
+
if (typeof ref !== 'string' || ref.trim() === '') {
|
|
1074
|
+
return undefined;
|
|
1075
|
+
}
|
|
1076
|
+
var value = ref.trim();
|
|
1077
|
+
var isUrl = /^[a-z][a-z0-9+.-]*:\/\//i.test(value);
|
|
1078
|
+
if (isUrl) {
|
|
1079
|
+
// Drop the scheme and the host segment, leaving owner/repo.
|
|
1080
|
+
value = value.replace(/^[a-z][a-z0-9+.-]*:\/\//i, '').replace(/^[^/]+\//, '');
|
|
1081
|
+
} else {
|
|
1082
|
+
var colon = value.indexOf(':');
|
|
1083
|
+
var slash = value.indexOf('/');
|
|
1084
|
+
// An scp-style git@host:owner/repo reference: drop the host prefix.
|
|
1085
|
+
if (colon !== -1 && (slash === -1 || colon < slash)) {
|
|
1086
|
+
value = value.slice(colon + 1);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
var segments = value.split('/').filter(function (part) {
|
|
1090
|
+
return part !== '';
|
|
1091
|
+
});
|
|
1092
|
+
if (segments.length === 0) {
|
|
1093
|
+
return undefined;
|
|
1094
|
+
}
|
|
1095
|
+
var repo = segments[segments.length - 1].replace(/\.git$/, '');
|
|
1096
|
+
if (repo === '') {
|
|
1097
|
+
return undefined;
|
|
1098
|
+
}
|
|
1099
|
+
var owner = segments.length > 1 ? segments[segments.length - 2] : undefined;
|
|
1100
|
+
return owner ? owner + '/' + repo : repo;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
982
1103
|
// unsure), an "Engine: Model" title, and Edit/Delete controls. A New
|
|
983
1104
|
// provider button resets the form to a fresh entry. The list sits above
|
|
984
1105
|
// the editing form, so a provider's full config form stays the single
|
|
@@ -995,6 +1116,15 @@ window.__ModuleLoader__.load({
|
|
|
995
1116
|
((opts.engines || []).find(function (engine) {
|
|
996
1117
|
return engine.id === provider.engine;
|
|
997
1118
|
}) || {}).label || provider.engine;
|
|
1119
|
+
// The title bar renders the fork's owner/repo when the entry names a
|
|
1120
|
+
// fork, so a forked engine reads as its repo (Ninfer/ninfer-all)
|
|
1121
|
+
// rather than the engine label (NInfer). No fork, or an unparseable
|
|
1122
|
+
// one, keeps the engine label.
|
|
1123
|
+
var forkName =
|
|
1124
|
+
provider.platform && typeof provider.platform === 'object'
|
|
1125
|
+
? repoShortName(provider.platform.engineFork)
|
|
1126
|
+
: undefined;
|
|
1127
|
+
var titleLabel = forkName !== undefined ? forkName : engineLabel;
|
|
998
1128
|
var modelLabel =
|
|
999
1129
|
provider.model && provider.model !== '' ? provider.model : opts.modelAuto;
|
|
1000
1130
|
return react.createElement(
|
|
@@ -1023,21 +1153,21 @@ window.__ModuleLoader__.load({
|
|
|
1023
1153
|
whiteSpace: 'nowrap',
|
|
1024
1154
|
},
|
|
1025
1155
|
title:
|
|
1026
|
-
|
|
1156
|
+
titleLabel +
|
|
1027
1157
|
': ' +
|
|
1028
1158
|
modelLabel +
|
|
1029
1159
|
' (' +
|
|
1030
1160
|
(statusToLabel[provider.status] || provider.status) +
|
|
1031
1161
|
')',
|
|
1032
1162
|
'aria-label':
|
|
1033
|
-
|
|
1163
|
+
titleLabel +
|
|
1034
1164
|
': ' +
|
|
1035
1165
|
modelLabel +
|
|
1036
1166
|
' (' +
|
|
1037
1167
|
(statusToLabel[provider.status] || provider.status) +
|
|
1038
1168
|
')',
|
|
1039
1169
|
},
|
|
1040
|
-
|
|
1170
|
+
titleLabel +
|
|
1041
1171
|
': ' +
|
|
1042
1172
|
modelLabel +
|
|
1043
1173
|
' (' +
|
|
@@ -1128,6 +1258,39 @@ window.__ModuleLoader__.load({
|
|
|
1128
1258
|
});
|
|
1129
1259
|
}
|
|
1130
1260
|
|
|
1261
|
+
// The reusable stateful process button: the Disable/Throbber/Reset
|
|
1262
|
+
// lifecycle every process control (Get, Launch, Stop, Run, Download)
|
|
1263
|
+
// shares. While the process is in flight (busy) it reads as a disabled
|
|
1264
|
+
// control with the spinner and the in-progress label; when it settles the
|
|
1265
|
+
// caller flips busy back to false and the control resets to the idle
|
|
1266
|
+
// action button (the label and the click handler). A host without the
|
|
1267
|
+
// primitives seed keeps the native fallback through renderButton.
|
|
1268
|
+
function renderProcessButton(react, primitives, opts) {
|
|
1269
|
+
if (opts.busy === true) {
|
|
1270
|
+
return react.createElement(
|
|
1271
|
+
'button',
|
|
1272
|
+
{
|
|
1273
|
+
type: 'button',
|
|
1274
|
+
disabled: true,
|
|
1275
|
+
style: Object.assign(
|
|
1276
|
+
{ display: 'inline-flex', alignItems: 'center', gap: 4, opacity: 0.7 },
|
|
1277
|
+
GHOST_BUTTON,
|
|
1278
|
+
),
|
|
1279
|
+
'aria-label': opts.busyLabel,
|
|
1280
|
+
},
|
|
1281
|
+
renderThrobber(react, primitives),
|
|
1282
|
+
react.createElement('span', null, opts.busyLabel),
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
return renderButton(
|
|
1286
|
+
react,
|
|
1287
|
+
primitives,
|
|
1288
|
+
opts.variant,
|
|
1289
|
+
{ onClick: opts.onClick, disabled: opts.disabled === true },
|
|
1290
|
+
opts.label,
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1131
1294
|
// The chat-template control, split from the old finicky free-text combo
|
|
1132
1295
|
// into a preset dropdown (the shipped presets plus a "Custom" entry) and a
|
|
1133
1296
|
// "Custom Chat template" text field that shows while the preset is
|
|
@@ -1778,24 +1941,22 @@ window.__ModuleLoader__.load({
|
|
|
1778
1941
|
label: t.allowRun,
|
|
1779
1942
|
}),
|
|
1780
1943
|
deps.runAllowed && !deps.allSatisfied
|
|
1781
|
-
?
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
)
|
|
1944
|
+
? renderProcessButton(react, primitives, {
|
|
1945
|
+
busy: actions.installing === true,
|
|
1946
|
+
label: '▶ ' + t.run,
|
|
1947
|
+
busyLabel: t.running,
|
|
1948
|
+
variant: 'ghost',
|
|
1949
|
+
onClick: actions.onRun,
|
|
1950
|
+
})
|
|
1788
1951
|
: null,
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
actions.getting === true ? t.getting : t.get,
|
|
1798
|
-
),
|
|
1952
|
+
renderProcessButton(react, primitives, {
|
|
1953
|
+
busy: actions.getting === true,
|
|
1954
|
+
label: t.get,
|
|
1955
|
+
busyLabel: t.getting,
|
|
1956
|
+
variant: 'primary',
|
|
1957
|
+
onClick: actions.onGet,
|
|
1958
|
+
disabled: !deps.allSatisfied,
|
|
1959
|
+
}),
|
|
1799
1960
|
),
|
|
1800
1961
|
actions.depsNote
|
|
1801
1962
|
? react.createElement(
|
|
@@ -1926,12 +2087,39 @@ window.__ModuleLoader__.load({
|
|
|
1926
2087
|
// The log-file location the card's log-location field edits. Seeded
|
|
1927
2088
|
// from the summary's stored template on load; a Save persists it.
|
|
1928
2089
|
var logPathState = react.useState('');
|
|
2090
|
+
// The Logs section's textarea height in px, resized by the diagonal
|
|
2091
|
+
// chevron drag handle in the section's bottom-right corner.
|
|
2092
|
+
var logsHeightState = react.useState(96);
|
|
2093
|
+
// The Logs section's auto-scroll mode. 'fresh' is the pane just
|
|
2094
|
+
// opened: the first content render scrolls to the bottom and arms
|
|
2095
|
+
// 'scrolling'. 'scrolling' pins the view to the bottom on content
|
|
2096
|
+
// updates. 'manual' means the user scrolled up, so content updates
|
|
2097
|
+
// leave the view where the user put it. A scroll to the very bottom
|
|
2098
|
+
// re-arms 'scrolling'.
|
|
2099
|
+
var logsScrollModeState = react.useState('fresh');
|
|
2100
|
+
// The Logs textarea element, so the auto-scroll effect and the scroll
|
|
2101
|
+
// handler read the live scroll metrics.
|
|
2102
|
+
var logsTextareaRef = react.useRef(null);
|
|
2103
|
+
// The in-flight chevron drag: the pointer's starting Y and the height
|
|
2104
|
+
// it started from, so a move computes the new height from a stable
|
|
2105
|
+
// base instead of a stale render closure.
|
|
2106
|
+
var logsDragRef = react.useRef(null);
|
|
1929
2107
|
// The per-entry launch state: a map from provider id to the entry's
|
|
1930
2108
|
// launch-state-machine state (the rebuild phases 'prereqs', 'download',
|
|
1931
2109
|
// 'build', 'launch', plus 'running' and 'failed') and the start time a
|
|
1932
2110
|
// launch began, so a card entry can show its phase label, Stop, or red
|
|
1933
2111
|
// Logs control. Absent entries read as idle (a plain Launch button).
|
|
1934
2112
|
var entryLaunchState = react.useState({});
|
|
2113
|
+
// The provider statuses the summary last painted, mirrored into a ref
|
|
2114
|
+
// so the stop fast-poll reads the value the last fetch settled, not a
|
|
2115
|
+
// stale closure.
|
|
2116
|
+
var providerStatusRef = react.useRef({});
|
|
2117
|
+
// The latest fetchSummary, mirrored into a ref so the stop fast-poll
|
|
2118
|
+
// re-fetches through the current callback, not a stale one.
|
|
2119
|
+
var fetchSummaryRef = react.useRef(null);
|
|
2120
|
+
// The stop fast-poll and deadline timers (per-entry), plus the
|
|
2121
|
+
// draft-based stop's poll timer, so an unmount clears them all.
|
|
2122
|
+
var stopTimersRef = react.useRef({ poll: null, deadline: null, draftPoll: null });
|
|
1935
2123
|
var data = dataState[0];
|
|
1936
2124
|
var draft = draftState[0];
|
|
1937
2125
|
var note = noteState[0];
|
|
@@ -1962,7 +2150,36 @@ window.__ModuleLoader__.load({
|
|
|
1962
2150
|
var providerFormMode = providerFormModeState[0];
|
|
1963
2151
|
var cachedDraft = cachedDraftState[0];
|
|
1964
2152
|
var logPath = logPathState[0];
|
|
2153
|
+
var logsHeight = logsHeightState[0];
|
|
2154
|
+
var logsScrollMode = logsScrollModeState[0];
|
|
2155
|
+
// The Logs section's displayed text: the streamed Get buffer while a
|
|
2156
|
+
// run is in flight, else the persisted last-run logs the summary
|
|
2157
|
+
// carries. One value the auto-scroll effect and the textarea share.
|
|
2158
|
+
var displayedLogs =
|
|
2159
|
+
getLogs !== null ? getLogs : data !== null && data.lastLogs ? data.lastLogs : '';
|
|
1965
2160
|
var entryLaunch = entryLaunchState[0];
|
|
2161
|
+
// The title-bar Launch/Stop control mirrors the edited provider's
|
|
2162
|
+
// per-entry launch state, so the entry's Launch button and the form's
|
|
2163
|
+
// control read the same lifecycle (a phase throbber, a Stop when
|
|
2164
|
+
// healthy, a red Logs on a failed launch).
|
|
2165
|
+
var editedProvider =
|
|
2166
|
+
editingProviderId !== null
|
|
2167
|
+
? (data.providers || []).find((provider) => provider.id === editingProviderId)
|
|
2168
|
+
: undefined;
|
|
2169
|
+
var mirroredStatus =
|
|
2170
|
+
editedProvider !== undefined ? (entryLaunch[editedProvider.id] || {}).status : undefined;
|
|
2171
|
+
var mirroredPhaseLabel =
|
|
2172
|
+
{
|
|
2173
|
+
prereqs: t.entryPrereqs,
|
|
2174
|
+
download: t.entryDownloading,
|
|
2175
|
+
build: t.entryBuilding,
|
|
2176
|
+
launch: t.entryLaunching,
|
|
2177
|
+
// The engine model fetch (the Strata download): the same throbber
|
|
2178
|
+
// label the download phase shows, so the title bar mirrors the
|
|
2179
|
+
// entry's Launch button while the engine fetches its files.
|
|
2180
|
+
downloading: t.entryDownloading,
|
|
2181
|
+
stopping: t.stopping,
|
|
2182
|
+
}[mirroredStatus];
|
|
1966
2183
|
// The no-entries case forces the form into new-provider mode on
|
|
1967
2184
|
// render: with nothing stored, the form is where the first provider
|
|
1968
2185
|
// is entered, and the "New provider" control reads as plain text.
|
|
@@ -1980,6 +2197,69 @@ window.__ModuleLoader__.load({
|
|
|
1980
2197
|
draftRef.current = draft;
|
|
1981
2198
|
}, [draft]);
|
|
1982
2199
|
|
|
2200
|
+
// The Logs auto-scroll: while the mode is 'fresh' (the pane just
|
|
2201
|
+
// opened) or 'scrolling', a content update pins the view to the
|
|
2202
|
+
// bottom. The first render scrolls and arms 'scrolling'; a manual
|
|
2203
|
+
// scroll up parks the mode at 'manual', so later updates leave the
|
|
2204
|
+
// view where the user put it.
|
|
2205
|
+
react.useEffect(() => {
|
|
2206
|
+
var el = logsTextareaRef.current;
|
|
2207
|
+
if (el === null) {
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
2210
|
+
if (logsScrollMode === 'fresh' || logsScrollMode === 'scrolling') {
|
|
2211
|
+
el.scrollTop = el.scrollHeight;
|
|
2212
|
+
if (logsScrollMode === 'fresh') {
|
|
2213
|
+
logsScrollModeState[1]('scrolling');
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
}, [displayedLogs, logsScrollMode]);
|
|
2217
|
+
|
|
2218
|
+
// The Logs scroll handler: a scroll to the very bottom re-arms
|
|
2219
|
+
// auto-scroll ('scrolling'); any other position parks the mode at
|
|
2220
|
+
// 'manual', so content updates stop pinning the view. The 4px
|
|
2221
|
+
// tolerance absorbs sub-pixel rounding at the end of the range.
|
|
2222
|
+
var onLogsScroll = react.useCallback(() => {
|
|
2223
|
+
var el = logsTextareaRef.current;
|
|
2224
|
+
if (el === null) {
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
var atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 4;
|
|
2228
|
+
logsScrollModeState[1](atBottom ? 'scrolling' : 'manual');
|
|
2229
|
+
}, []);
|
|
2230
|
+
|
|
2231
|
+
// The Logs resize handle: a pointerdown on the chevron starts a
|
|
2232
|
+
// vertical drag. A move computes the new height from the stable
|
|
2233
|
+
// starting base (drag up shrinks the section to the default height,
|
|
2234
|
+
// drag down expands it), clamped to a readable range. The window
|
|
2235
|
+
// listeners read the drag base from the ref, so a re-render mid-drag
|
|
2236
|
+
// never resets it.
|
|
2237
|
+
var onLogsResizeStart = react.useCallback(
|
|
2238
|
+
(event) => {
|
|
2239
|
+
event.preventDefault();
|
|
2240
|
+
logsDragRef.current = { startClientY: event.clientY, startHeight: logsHeight };
|
|
2241
|
+
var onMove = (moveEvent) => {
|
|
2242
|
+
var drag = logsDragRef.current;
|
|
2243
|
+
if (drag === null) {
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
moveEvent.preventDefault();
|
|
2247
|
+
// Drag down (clientY grows) expands, drag up shrinks to the
|
|
2248
|
+
// default height, so the delta is the pointer's downward travel.
|
|
2249
|
+
var delta = moveEvent.clientY - drag.startClientY;
|
|
2250
|
+
logsHeightState[1](Math.max(96, Math.min(480, drag.startHeight + delta)));
|
|
2251
|
+
};
|
|
2252
|
+
var onUp = () => {
|
|
2253
|
+
logsDragRef.current = null;
|
|
2254
|
+
window.removeEventListener('pointermove', onMove);
|
|
2255
|
+
window.removeEventListener('pointerup', onUp);
|
|
2256
|
+
};
|
|
2257
|
+
window.addEventListener('pointermove', onMove);
|
|
2258
|
+
window.addEventListener('pointerup', onUp);
|
|
2259
|
+
},
|
|
2260
|
+
[logsHeight],
|
|
2261
|
+
);
|
|
2262
|
+
|
|
1983
2263
|
var fetchSummary = react.useCallback(
|
|
1984
2264
|
(engine, config) => {
|
|
1985
2265
|
var params = new URLSearchParams();
|
|
@@ -1997,6 +2277,13 @@ window.__ModuleLoader__.load({
|
|
|
1997
2277
|
)
|
|
1998
2278
|
.then((next) => {
|
|
1999
2279
|
dataState[1](next);
|
|
2280
|
+
// Mirror the provider statuses the summary painted, so the
|
|
2281
|
+
// stop fast-poll reads the latest dot values.
|
|
2282
|
+
var statuses = {};
|
|
2283
|
+
(next.providers || []).forEach(function (provider) {
|
|
2284
|
+
statuses[provider.id] = provider.status;
|
|
2285
|
+
});
|
|
2286
|
+
providerStatusRef.current = statuses;
|
|
2000
2287
|
// The streamed Get buffer yields to the persisted last-run
|
|
2001
2288
|
// logs the summary now carries, so the Logs section keeps
|
|
2002
2289
|
// the run's tail without a second copy.
|
|
@@ -2041,6 +2328,31 @@ window.__ModuleLoader__.load({
|
|
|
2041
2328
|
[t],
|
|
2042
2329
|
);
|
|
2043
2330
|
|
|
2331
|
+
// Mirror the latest fetchSummary into a ref, so the stop fast-poll
|
|
2332
|
+
// re-fetches through the current callback (its deps change with the
|
|
2333
|
+
// locale), not a stale closure.
|
|
2334
|
+
react.useEffect(() => {
|
|
2335
|
+
fetchSummaryRef.current = fetchSummary;
|
|
2336
|
+
}, [fetchSummary]);
|
|
2337
|
+
|
|
2338
|
+
// Clear the stop fast-poll and deadline timers on unmount, so a
|
|
2339
|
+
// settled or abandoned stop never keeps re-fetching the summary.
|
|
2340
|
+
react.useEffect(
|
|
2341
|
+
() => () => {
|
|
2342
|
+
var timers = stopTimersRef.current;
|
|
2343
|
+
if (timers.poll !== null) {
|
|
2344
|
+
clearTimeout(timers.poll);
|
|
2345
|
+
}
|
|
2346
|
+
if (timers.deadline !== null) {
|
|
2347
|
+
clearTimeout(timers.deadline);
|
|
2348
|
+
}
|
|
2349
|
+
if (timers.draftPoll !== null) {
|
|
2350
|
+
clearInterval(timers.draftPoll);
|
|
2351
|
+
}
|
|
2352
|
+
},
|
|
2353
|
+
[],
|
|
2354
|
+
);
|
|
2355
|
+
|
|
2044
2356
|
// The deps panel reads its own route, because it re-probes the host
|
|
2045
2357
|
// on every fetch (the "Get" gate must read the live checkDeps
|
|
2046
2358
|
// answer), while the summary re-prices the meter. Both ride the same
|
|
@@ -2164,6 +2476,9 @@ window.__ModuleLoader__.load({
|
|
|
2164
2476
|
}
|
|
2165
2477
|
downloadingState[1](true);
|
|
2166
2478
|
downloadNoteState[1]('');
|
|
2479
|
+
// The click writes the "Downloading..." separator, so the run reads
|
|
2480
|
+
// as a fresh Logs section that the download appends to.
|
|
2481
|
+
startProcessLog(t.downloading);
|
|
2167
2482
|
fetch('/local-infer/download', {
|
|
2168
2483
|
method: 'POST',
|
|
2169
2484
|
headers: { 'content-type': 'application/json' },
|
|
@@ -2195,7 +2510,7 @@ window.__ModuleLoader__.load({
|
|
|
2195
2510
|
downloadingState[1](false);
|
|
2196
2511
|
});
|
|
2197
2512
|
},
|
|
2198
|
-
[draft, downloading, t, fetchSummary],
|
|
2513
|
+
[draft, downloading, t, fetchSummary, startProcessLog],
|
|
2199
2514
|
);
|
|
2200
2515
|
|
|
2201
2516
|
// The chat-template download: pulls the selected template (the
|
|
@@ -2712,6 +3027,23 @@ window.__ModuleLoader__.load({
|
|
|
2712
3027
|
});
|
|
2713
3028
|
}, [deps, draft, installing, t]);
|
|
2714
3029
|
|
|
3030
|
+
// The Logs-section separator a provider process button writes when
|
|
3031
|
+
// clicked: it appends a "--- {{label}}" line to the live Logs buffer
|
|
3032
|
+
// (seeding from the persisted last-run logs when no run is in flight),
|
|
3033
|
+
// so the new process reads as a fresh section that will append its
|
|
3034
|
+
// commands and output to the buffer and the log file. The host route
|
|
3035
|
+
// persists the same separator, so it survives a reload.
|
|
3036
|
+
var startProcessLog = react.useCallback(
|
|
3037
|
+
(label) => {
|
|
3038
|
+
getLogsState[1]((current) => {
|
|
3039
|
+
var base =
|
|
3040
|
+
current !== null ? current : (data !== null && data.lastLogs ? data.lastLogs : '');
|
|
3041
|
+
return base + (base === '' ? '' : '\n') + '--- ' + label + '\n';
|
|
3042
|
+
});
|
|
3043
|
+
},
|
|
3044
|
+
[data],
|
|
3045
|
+
);
|
|
3046
|
+
|
|
2715
3047
|
var onGet = react.useCallback(() => {
|
|
2716
3048
|
if (deps === null || draft === null || getting) {
|
|
2717
3049
|
return;
|
|
@@ -2720,10 +3052,11 @@ window.__ModuleLoader__.load({
|
|
|
2720
3052
|
// the host through /local-infer/get. The route streams its output;
|
|
2721
3053
|
// each chunk appends to the Logs section and re-arms the idle
|
|
2722
3054
|
// timer, and Launch stays disabled until the run finishes, fails,
|
|
2723
|
-
// or falls silent for GET_IDLE_MS.
|
|
3055
|
+
// or falls silent for GET_IDLE_MS. The click writes the "Getting..."
|
|
3056
|
+
// separator, so the run reads as a fresh Logs section.
|
|
2724
3057
|
gettingState[1](true);
|
|
2725
3058
|
depsNoteState[1]('');
|
|
2726
|
-
|
|
3059
|
+
startProcessLog(t.getting);
|
|
2727
3060
|
var append = (text) => {
|
|
2728
3061
|
getLogsState[1]((current) => (current === null ? '' : current) + text);
|
|
2729
3062
|
};
|
|
@@ -2784,7 +3117,7 @@ window.__ModuleLoader__.load({
|
|
|
2784
3117
|
// re-read hands the Logs section back to the stored value.
|
|
2785
3118
|
fetchSummary(draft.engine, draft.config);
|
|
2786
3119
|
});
|
|
2787
|
-
}, [deps, draft, getting, data, t, fetchSummary]);
|
|
3120
|
+
}, [deps, draft, getting, data, t, fetchSummary, startProcessLog]);
|
|
2788
3121
|
|
|
2789
3122
|
var onRegister = react.useCallback(() => {
|
|
2790
3123
|
if (draft === null || registering) {
|
|
@@ -2903,6 +3236,9 @@ window.__ModuleLoader__.load({
|
|
|
2903
3236
|
}
|
|
2904
3237
|
launchingState[1](true);
|
|
2905
3238
|
lifecycleNoteState[1]('');
|
|
3239
|
+
// The click writes the "Launching..." separator, so the run reads as
|
|
3240
|
+
// a fresh Logs section that the launch appends to.
|
|
3241
|
+
startProcessLog(t.launching);
|
|
2906
3242
|
fetch('/local-infer/launch', {
|
|
2907
3243
|
method: 'POST',
|
|
2908
3244
|
headers: { 'content-type': 'application/json' },
|
|
@@ -2979,7 +3315,7 @@ window.__ModuleLoader__.load({
|
|
|
2979
3315
|
// the Get apply when their run ends.
|
|
2980
3316
|
fetchSummary(draft.engine, draft.config);
|
|
2981
3317
|
});
|
|
2982
|
-
}, [draft, launching, editingProviderId, autoRebuild, t, fetchSummary]);
|
|
3318
|
+
}, [draft, launching, editingProviderId, autoRebuild, t, fetchSummary, startProcessLog]);
|
|
2983
3319
|
|
|
2984
3320
|
var onStop = react.useCallback(() => {
|
|
2985
3321
|
if (draft === null || stopping) {
|
|
@@ -2987,6 +3323,15 @@ window.__ModuleLoader__.load({
|
|
|
2987
3323
|
}
|
|
2988
3324
|
stoppingState[1](true);
|
|
2989
3325
|
lifecycleNoteState[1]('');
|
|
3326
|
+
// Re-fetch the summary on a fast cadence for the duration of the
|
|
3327
|
+
// stop action, so the provider's status dot repaints the moment the
|
|
3328
|
+
// engine is down, instead of waiting out the host's 5-second
|
|
3329
|
+
// process-watch cycle.
|
|
3330
|
+
if (stopTimersRef.current.draftPoll === null) {
|
|
3331
|
+
stopTimersRef.current.draftPoll = setInterval(function () {
|
|
3332
|
+
fetchSummaryRef.current(null, null);
|
|
3333
|
+
}, STOP_POLL_MS);
|
|
3334
|
+
}
|
|
2990
3335
|
fetch('/local-infer/stop', {
|
|
2991
3336
|
method: 'POST',
|
|
2992
3337
|
headers: { 'content-type': 'application/json' },
|
|
@@ -3010,9 +3355,28 @@ window.__ModuleLoader__.load({
|
|
|
3010
3355
|
})
|
|
3011
3356
|
.then(() => {
|
|
3012
3357
|
stoppingState[1](false);
|
|
3358
|
+
if (stopTimersRef.current.draftPoll !== null) {
|
|
3359
|
+
clearInterval(stopTimersRef.current.draftPoll);
|
|
3360
|
+
stopTimersRef.current.draftPoll = null;
|
|
3361
|
+
}
|
|
3013
3362
|
});
|
|
3014
3363
|
}, [draft, stopping, editingProviderId, t]);
|
|
3015
3364
|
|
|
3365
|
+
// Move one entry through the provider state machine, reading the
|
|
3366
|
+
// current entry from the latest map so a late event prices the state it
|
|
3367
|
+
// lands on, not a stale one. The per-entry launch and stop both drive
|
|
3368
|
+
// the same machine through this one helper, so a transition is never
|
|
3369
|
+
// priced twice.
|
|
3370
|
+
var moveEntry = react.useCallback((providerId, event) => {
|
|
3371
|
+
entryLaunchState[1]((current) => {
|
|
3372
|
+
var entry = current[providerId] || { status: 'idle' };
|
|
3373
|
+
var next = launchNextState(entry, event);
|
|
3374
|
+
return Object.assign({}, current, {
|
|
3375
|
+
[providerId]: Object.assign({}, next, { startTime: Date.now() }),
|
|
3376
|
+
});
|
|
3377
|
+
});
|
|
3378
|
+
}, []);
|
|
3379
|
+
|
|
3016
3380
|
// The per-entry launch: a card entry's own Launch control drives its
|
|
3017
3381
|
// own engine, not the form's draft. It starts the launch state machine
|
|
3018
3382
|
// at the prereqs phase, then reads the launch stream (an NDJSON step
|
|
@@ -3026,28 +3390,18 @@ window.__ModuleLoader__.load({
|
|
|
3026
3390
|
(provider) => {
|
|
3027
3391
|
var prior = entryLaunch[provider.id];
|
|
3028
3392
|
var status = prior !== undefined ? prior.status : 'idle';
|
|
3029
|
-
// A launch in flight (a rebuild phase)
|
|
3030
|
-
// is stopped by its own Stop control,
|
|
3393
|
+
// A launch in flight (a rebuild phase) or a stop in flight is
|
|
3394
|
+
// ignored; a running engine is stopped by its own Stop control,
|
|
3395
|
+
// not re-launched.
|
|
3031
3396
|
if (status !== 'idle' && status !== 'failed') {
|
|
3032
3397
|
return;
|
|
3033
3398
|
}
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
}),
|
|
3039
|
-
);
|
|
3040
|
-
// Move the entry's launch state through the machine, reading the
|
|
3041
|
-
// current status from the latest map so a late event prices the
|
|
3042
|
-
// state it lands on, not a stale one.
|
|
3399
|
+
moveEntry(provider.id, { type: 'start' });
|
|
3400
|
+
// The click writes the "Launching..." separator, so the entry's run
|
|
3401
|
+
// reads as a fresh Logs section that the launch appends to.
|
|
3402
|
+
startProcessLog(t.entryLaunching);
|
|
3043
3403
|
var move = function (event) {
|
|
3044
|
-
|
|
3045
|
-
var entry = current[provider.id];
|
|
3046
|
-
var next = launchNextState(entry !== undefined ? entry.status : 'idle', event);
|
|
3047
|
-
return Object.assign({}, current, {
|
|
3048
|
-
[provider.id]: { status: next, startTime: Date.now() },
|
|
3049
|
-
});
|
|
3050
|
-
});
|
|
3404
|
+
moveEntry(provider.id, event);
|
|
3051
3405
|
};
|
|
3052
3406
|
// The health-poll deadline: the route's poll runs up to two minutes,
|
|
3053
3407
|
// so a client-side deadline just past that cap forces a hung launch
|
|
@@ -3130,6 +3484,16 @@ window.__ModuleLoader__.load({
|
|
|
3130
3484
|
}
|
|
3131
3485
|
if (event.step !== undefined) {
|
|
3132
3486
|
move({ type: 'step', step: event.step });
|
|
3487
|
+
// The build step writes a "Building..." separator to
|
|
3488
|
+
// the live Logs buffer, so the build reads as a fresh
|
|
3489
|
+
// section the way the launch click writes
|
|
3490
|
+
// "Launching...".
|
|
3491
|
+
if (event.step === 'build') {
|
|
3492
|
+
getLogsState[1]((current) => {
|
|
3493
|
+
var base = current !== null ? current : '';
|
|
3494
|
+
return base + (base === '' ? '' : '\n') + '--- ' + t.entryBuilding + '\n';
|
|
3495
|
+
});
|
|
3496
|
+
}
|
|
3133
3497
|
// Arm the deadline only for the launch (health-poll)
|
|
3134
3498
|
// step; the rebuild steps run without a timeout.
|
|
3135
3499
|
if (event.step === 'launch') {
|
|
@@ -3139,7 +3503,17 @@ window.__ModuleLoader__.load({
|
|
|
3139
3503
|
settled = true;
|
|
3140
3504
|
clearDeadline();
|
|
3141
3505
|
var healthy = event.result.healthy === true;
|
|
3142
|
-
|
|
3506
|
+
// A launch that is still fetching its model files reads
|
|
3507
|
+
// as downloading (a busy throbber the card re-polls),
|
|
3508
|
+
// not as a failure; only a result that is neither
|
|
3509
|
+
// healthy nor downloading settles to the red Logs.
|
|
3510
|
+
if (healthy) {
|
|
3511
|
+
move({ type: 'healthy' });
|
|
3512
|
+
} else if (event.result.state === 'downloading') {
|
|
3513
|
+
move({ type: 'downloading' });
|
|
3514
|
+
} else {
|
|
3515
|
+
move({ type: 'failed' });
|
|
3516
|
+
}
|
|
3143
3517
|
// The provider status dots price the process probe on a
|
|
3144
3518
|
// summary fetch, so a launch that turned an engine
|
|
3145
3519
|
// healthy re-fetches the summary to repaint the dot
|
|
@@ -3159,15 +3533,64 @@ window.__ModuleLoader__.load({
|
|
|
3159
3533
|
move({ type: 'failed' });
|
|
3160
3534
|
});
|
|
3161
3535
|
},
|
|
3162
|
-
[entryLaunch, fetchSummary],
|
|
3536
|
+
[entryLaunch, fetchSummary, startProcessLog, moveEntry, getLogsState, t],
|
|
3163
3537
|
);
|
|
3164
3538
|
|
|
3165
|
-
// The per-entry stop: a running engine's Stop control
|
|
3166
|
-
//
|
|
3167
|
-
//
|
|
3168
|
-
//
|
|
3539
|
+
// The per-entry stop: a running engine's Stop control moves the
|
|
3540
|
+
// provider state machine to stopping (the control shows the throbber,
|
|
3541
|
+
// disabled, the same display the launch phases show). For the duration
|
|
3542
|
+
// of the stop action the card re-fetches the summary on a fast cadence
|
|
3543
|
+
// so the provider's status dot repaints the moment the engine is down,
|
|
3544
|
+
// instead of waiting out the host's 5-second process-watch cycle. A
|
|
3545
|
+
// provider still running when the 10-second deadline fires settles to
|
|
3546
|
+
// the red Logs state (the same path a failed launch takes); the Logs
|
|
3547
|
+
// control opens the entry for editing and resets the machine to
|
|
3548
|
+
// running, so the control reads as Stop again. The stop clears the
|
|
3549
|
+
// launch record on this entry, so a recycled pid cannot re-link it.
|
|
3169
3550
|
var onStopEntry = react.useCallback(
|
|
3170
3551
|
(provider) => {
|
|
3552
|
+
var prior = entryLaunch[provider.id];
|
|
3553
|
+
var priorStatus = prior !== undefined ? prior.status : 'idle';
|
|
3554
|
+
// A failed launch reads as Logs, not Stop, so a stop only starts
|
|
3555
|
+
// from the other states.
|
|
3556
|
+
if (priorStatus === 'failed') {
|
|
3557
|
+
return;
|
|
3558
|
+
}
|
|
3559
|
+
var move = function (event) {
|
|
3560
|
+
moveEntry(provider.id, event);
|
|
3561
|
+
};
|
|
3562
|
+
var clearStopTimers = function () {
|
|
3563
|
+
if (stopTimersRef.current.poll !== null) {
|
|
3564
|
+
clearTimeout(stopTimersRef.current.poll);
|
|
3565
|
+
stopTimersRef.current.poll = null;
|
|
3566
|
+
}
|
|
3567
|
+
if (stopTimersRef.current.deadline !== null) {
|
|
3568
|
+
clearTimeout(stopTimersRef.current.deadline);
|
|
3569
|
+
stopTimersRef.current.deadline = null;
|
|
3570
|
+
}
|
|
3571
|
+
};
|
|
3572
|
+
// A second Stop click re-arms the timers, so clear the in-flight
|
|
3573
|
+
// ones first, or they would stack and double-settle.
|
|
3574
|
+
clearStopTimers();
|
|
3575
|
+
move({ type: 'stop' });
|
|
3576
|
+
var poll = function () {
|
|
3577
|
+
var status = providerStatusRef.current[provider.id];
|
|
3578
|
+
if (status !== undefined && status !== 'running') {
|
|
3579
|
+
// The provider is no longer running: the stop succeeded.
|
|
3580
|
+
move({ type: 'stopped' });
|
|
3581
|
+
clearStopTimers();
|
|
3582
|
+
return;
|
|
3583
|
+
}
|
|
3584
|
+
fetchSummaryRef.current(null, null);
|
|
3585
|
+
stopTimersRef.current.poll = setTimeout(poll, STOP_POLL_MS);
|
|
3586
|
+
};
|
|
3587
|
+
stopTimersRef.current.poll = setTimeout(poll, STOP_POLL_MS);
|
|
3588
|
+
stopTimersRef.current.deadline = setTimeout(function () {
|
|
3589
|
+
// The provider is still running after the deadline: settle to
|
|
3590
|
+
// the red Logs state, the same path a failed launch takes.
|
|
3591
|
+
clearStopTimers();
|
|
3592
|
+
move({ type: 'stopFailed' });
|
|
3593
|
+
}, STOP_HEALTH_DEADLINE_MS);
|
|
3171
3594
|
fetch('/local-infer/stop', {
|
|
3172
3595
|
method: 'POST',
|
|
3173
3596
|
headers: { 'content-type': 'application/json' },
|
|
@@ -3180,25 +3603,12 @@ window.__ModuleLoader__.load({
|
|
|
3180
3603
|
providerId: provider.id,
|
|
3181
3604
|
}),
|
|
3182
3605
|
})
|
|
3183
|
-
.then(() => {
|
|
3184
|
-
entryLaunchState[1]((current) => {
|
|
3185
|
-
var entry = current[provider.id];
|
|
3186
|
-
var status = entry !== undefined ? entry.status : 'idle';
|
|
3187
|
-
var next = launchNextState(status, { type: 'stop' });
|
|
3188
|
-
var map = Object.assign({}, current);
|
|
3189
|
-
if (next === 'idle') {
|
|
3190
|
-
delete map[provider.id];
|
|
3191
|
-
} else {
|
|
3192
|
-
map[provider.id] = { status: next, startTime: Date.now() };
|
|
3193
|
-
}
|
|
3194
|
-
return map;
|
|
3195
|
-
});
|
|
3196
|
-
})
|
|
3197
3606
|
.catch(() => {
|
|
3198
|
-
/* a stop that fails leaves the entry in its last state
|
|
3607
|
+
/* a stop that fails leaves the entry in its last state; the
|
|
3608
|
+
fast-poll and the deadline settle it */
|
|
3199
3609
|
});
|
|
3200
3610
|
},
|
|
3201
|
-
[],
|
|
3611
|
+
[entryLaunch, moveEntry],
|
|
3202
3612
|
);
|
|
3203
3613
|
|
|
3204
3614
|
// The per-entry Logs control: opening the logs loads the entry for
|
|
@@ -3210,14 +3620,13 @@ window.__ModuleLoader__.load({
|
|
|
3210
3620
|
(provider) => {
|
|
3211
3621
|
onEditProvider(provider);
|
|
3212
3622
|
entryLaunchState[1]((current) => {
|
|
3213
|
-
var entry = current[provider.id];
|
|
3214
|
-
var
|
|
3215
|
-
var next = launchNextState(status, { type: 'reset' });
|
|
3623
|
+
var entry = current[provider.id] || { status: 'idle' };
|
|
3624
|
+
var next = launchNextState(entry, { type: 'reset' });
|
|
3216
3625
|
var map = Object.assign({}, current);
|
|
3217
|
-
if (next === 'idle') {
|
|
3626
|
+
if (next.status === 'idle') {
|
|
3218
3627
|
delete map[provider.id];
|
|
3219
3628
|
} else {
|
|
3220
|
-
map[provider.id] = {
|
|
3629
|
+
map[provider.id] = Object.assign({}, next, { startTime: Date.now() });
|
|
3221
3630
|
}
|
|
3222
3631
|
return map;
|
|
3223
3632
|
});
|
|
@@ -3386,6 +3795,7 @@ window.__ModuleLoader__.load({
|
|
|
3386
3795
|
onLogs: onLogsEntry,
|
|
3387
3796
|
launchLabel: t.entryLaunch,
|
|
3388
3797
|
stopLabel: t.entryStop,
|
|
3798
|
+
stoppingLabel: t.stopping,
|
|
3389
3799
|
logsLabel: t.entryLogs,
|
|
3390
3800
|
launchingLabel: t.entryLaunching,
|
|
3391
3801
|
prereqsLabel: t.entryPrereqs,
|
|
@@ -3555,13 +3965,13 @@ window.__ModuleLoader__.load({
|
|
|
3555
3965
|
},
|
|
3556
3966
|
t.hfCliDetected + ' ' + status.downloadCommand,
|
|
3557
3967
|
),
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
),
|
|
3968
|
+
renderProcessButton(react, primitives, {
|
|
3969
|
+
busy: downloading === true,
|
|
3970
|
+
label: t.download,
|
|
3971
|
+
busyLabel: t.downloading,
|
|
3972
|
+
variant: 'ghost',
|
|
3973
|
+
onClick: onDownload,
|
|
3974
|
+
}),
|
|
3565
3975
|
);
|
|
3566
3976
|
}
|
|
3567
3977
|
return react.createElement(
|
|
@@ -3614,18 +4024,15 @@ window.__ModuleLoader__.load({
|
|
|
3614
4024
|
(status.hfCli ? t.hfCliDetected : t.noHfCli) + ' ' + status.downloadCommand,
|
|
3615
4025
|
),
|
|
3616
4026
|
status.hfCli
|
|
3617
|
-
?
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
},
|
|
3625
|
-
disabled: templateDownloading,
|
|
4027
|
+
? renderProcessButton(react, primitives, {
|
|
4028
|
+
busy: templateDownloading === true,
|
|
4029
|
+
label: t.templateDownload,
|
|
4030
|
+
busyLabel: t.templateDownloading,
|
|
4031
|
+
variant: 'ghost',
|
|
4032
|
+
onClick: function () {
|
|
4033
|
+
onDownloadTemplate(status.repo);
|
|
3626
4034
|
},
|
|
3627
|
-
|
|
3628
|
-
)
|
|
4035
|
+
})
|
|
3629
4036
|
: null,
|
|
3630
4037
|
),
|
|
3631
4038
|
templateDownloadNote !== ''
|
|
@@ -3893,33 +4300,85 @@ window.__ModuleLoader__.load({
|
|
|
3893
4300
|
},
|
|
3894
4301
|
primitives,
|
|
3895
4302
|
),
|
|
3896
|
-
// The Logs section: the last run's captured output in a
|
|
3897
|
-
//
|
|
3898
|
-
//
|
|
3899
|
-
//
|
|
3900
|
-
//
|
|
4303
|
+
// The Logs section: the last run's captured output in a read-only
|
|
4304
|
+
// text area (a launch fills it; the card never edits it), and the
|
|
4305
|
+
// log-file location the launch writes to. The location field edits
|
|
4306
|
+
// the template the route resolves and writes on the next run. While
|
|
4307
|
+
// a Get is in flight the streamed buffer wins: the git sync,
|
|
3901
4308
|
// dependency, and build output appends there as it lands.
|
|
4309
|
+
//
|
|
4310
|
+
// The textarea auto-scrolls to the bottom while the scroll mode is
|
|
4311
|
+
// 'fresh' (the pane just opened) or 'scrolling'; a manual scroll up
|
|
4312
|
+
// parks the mode at 'manual' so updates stop moving the view, and a
|
|
4313
|
+
// scroll back to the bottom re-arms 'scrolling'. The diagonal
|
|
4314
|
+
// chevron in the section's bottom-right corner drags the height
|
|
4315
|
+
// vertically only.
|
|
3902
4316
|
react.createElement(
|
|
3903
4317
|
'div',
|
|
3904
4318
|
{ style: { marginTop: 8 } },
|
|
3905
4319
|
react.createElement('div', { style: LABEL_STYLE }, t.logs),
|
|
3906
|
-
react.createElement(
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
4320
|
+
react.createElement(
|
|
4321
|
+
'div',
|
|
4322
|
+
{ style: { position: 'relative' } },
|
|
4323
|
+
react.createElement('textarea', {
|
|
4324
|
+
ref: logsTextareaRef,
|
|
4325
|
+
style: {
|
|
4326
|
+
width: '100%',
|
|
4327
|
+
height: logsHeight,
|
|
4328
|
+
boxSizing: 'border-box',
|
|
4329
|
+
resize: 'none',
|
|
4330
|
+
fontFamily: 'monospace',
|
|
4331
|
+
fontSize: 11,
|
|
4332
|
+
padding: 6,
|
|
4333
|
+
border: '1px solid rgba(127,127,127,0.25)',
|
|
4334
|
+
borderRadius: 6,
|
|
4335
|
+
color: 'inherit',
|
|
4336
|
+
background: 'transparent',
|
|
4337
|
+
},
|
|
4338
|
+
value: displayedLogs,
|
|
4339
|
+
readOnly: true,
|
|
4340
|
+
onScroll: onLogsScroll,
|
|
4341
|
+
'aria-label': t.logs,
|
|
4342
|
+
}),
|
|
4343
|
+
// The diagonal resize chevron: a DSH primitives down chevron
|
|
4344
|
+
// rotated -45 degrees points into the bottom-right corner and
|
|
4345
|
+
// reads as a resize handle. Dragging it vertically resizes the
|
|
4346
|
+
// section; a host without the seed keeps the native corner glyph.
|
|
4347
|
+
react.createElement(
|
|
4348
|
+
'div',
|
|
4349
|
+
{
|
|
4350
|
+
style: {
|
|
4351
|
+
position: 'absolute',
|
|
4352
|
+
bottom: 0,
|
|
4353
|
+
right: 0,
|
|
4354
|
+
width: 16,
|
|
4355
|
+
height: 16,
|
|
4356
|
+
display: 'flex',
|
|
4357
|
+
alignItems: 'flex-end',
|
|
4358
|
+
justifyContent: 'flex-end',
|
|
4359
|
+
cursor: 'ns-resize',
|
|
4360
|
+
color: 'var(--dsw-alias-label-tertiary, rgba(127,127,127,0.8))',
|
|
4361
|
+
userSelect: 'none',
|
|
4362
|
+
touchAction: 'none',
|
|
4363
|
+
},
|
|
4364
|
+
onPointerDown: onLogsResizeStart,
|
|
4365
|
+
role: 'button',
|
|
4366
|
+
'aria-label': t.logs + ' ' + t.resize,
|
|
4367
|
+
},
|
|
4368
|
+
primitives && typeof primitives.IconChevronDownOutlineRegular === 'function'
|
|
4369
|
+
? react.createElement(
|
|
4370
|
+
'span',
|
|
4371
|
+
{
|
|
4372
|
+
style: {
|
|
4373
|
+
display: 'inline-flex',
|
|
4374
|
+
transform: 'rotate(-45deg)',
|
|
4375
|
+
},
|
|
4376
|
+
},
|
|
4377
|
+
react.createElement(primitives.IconChevronDownOutlineRegular, { size: 12 }),
|
|
4378
|
+
)
|
|
4379
|
+
: '\u231F',
|
|
4380
|
+
),
|
|
4381
|
+
),
|
|
3923
4382
|
react.createElement(
|
|
3924
4383
|
'div',
|
|
3925
4384
|
{ style: ROW_STYLE },
|
|
@@ -3945,42 +4404,61 @@ window.__ModuleLoader__.load({
|
|
|
3945
4404
|
react.createElement(
|
|
3946
4405
|
'div',
|
|
3947
4406
|
{ style: { display: 'flex', alignItems: 'center', gap: 8, marginTop: 8 } },
|
|
3948
|
-
|
|
3949
|
-
? react
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
react,
|
|
3966
|
-
primitives,
|
|
3967
|
-
'ghost',
|
|
3968
|
-
{ onClick: function () {}, disabled: true },
|
|
3969
|
-
t.stopping,
|
|
3970
|
-
)
|
|
3971
|
-
: draftRunning
|
|
3972
|
-
? renderButton(react, primitives, 'ghost', { onClick: onStop }, t.stop)
|
|
3973
|
-
: renderButton(
|
|
3974
|
-
react,
|
|
3975
|
-
primitives,
|
|
3976
|
-
'primary',
|
|
4407
|
+
mirroredPhaseLabel !== undefined
|
|
4408
|
+
? renderProcessButton(react, primitives, {
|
|
4409
|
+
busy: true,
|
|
4410
|
+
label: mirroredPhaseLabel,
|
|
4411
|
+
busyLabel: mirroredPhaseLabel,
|
|
4412
|
+
variant: 'ghost',
|
|
4413
|
+
onClick: function () {},
|
|
4414
|
+
})
|
|
4415
|
+
: mirroredStatus === 'running'
|
|
4416
|
+
? renderButton(react, primitives, 'ghost', {
|
|
4417
|
+
onClick: function () {
|
|
4418
|
+
onStopEntry(editedProvider);
|
|
4419
|
+
},
|
|
4420
|
+
}, t.stop)
|
|
4421
|
+
: mirroredStatus === 'failed'
|
|
4422
|
+
? react.createElement(
|
|
4423
|
+
'button',
|
|
3977
4424
|
{
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
4425
|
+
type: 'button',
|
|
4426
|
+
onClick: function () {
|
|
4427
|
+
onLogsEntry(editedProvider);
|
|
4428
|
+
},
|
|
4429
|
+
style: Object.assign(
|
|
4430
|
+
{ color: 'var(--dsw-alias-label-error, #ff3b30)' },
|
|
4431
|
+
GHOST_BUTTON,
|
|
4432
|
+
),
|
|
4433
|
+
'aria-label': t.entryLogs,
|
|
3981
4434
|
},
|
|
3982
|
-
t.
|
|
3983
|
-
)
|
|
4435
|
+
t.entryLogs,
|
|
4436
|
+
)
|
|
4437
|
+
: renderProcessButton(react, primitives, {
|
|
4438
|
+
busy: launching || stopping,
|
|
4439
|
+
label: draftRunning ? t.stop : t.launch,
|
|
4440
|
+
busyLabel: launching ? t.launching : t.stopping,
|
|
4441
|
+
variant: draftRunning ? 'ghost' : 'primary',
|
|
4442
|
+
// While a provider is being edited, the control drives the
|
|
4443
|
+
// provider state machine (the same entry the title bar
|
|
4444
|
+
// mirrors), so the throbber, the fast-poll, and the
|
|
4445
|
+
// 10-second Logs deadline apply. A fresh provider (no
|
|
4446
|
+
// entry) keeps the draft-based Launch/Stop.
|
|
4447
|
+
onClick: draftRunning
|
|
4448
|
+
? editedProvider !== undefined
|
|
4449
|
+
? function () {
|
|
4450
|
+
onStopEntry(editedProvider);
|
|
4451
|
+
}
|
|
4452
|
+
: onStop
|
|
4453
|
+
: editedProvider !== undefined
|
|
4454
|
+
? function () {
|
|
4455
|
+
onLaunchEntry(editedProvider);
|
|
4456
|
+
}
|
|
4457
|
+
: onLaunch,
|
|
4458
|
+
disabled:
|
|
4459
|
+
!draftRunning &&
|
|
4460
|
+
(getting || (deps !== null && deps.allSatisfied !== true)),
|
|
4461
|
+
}),
|
|
3984
4462
|
// The Auto Rebuild checkbox: checked, a launch runs the git sync
|
|
3985
4463
|
// (fetch, rebase, rebuild) before the spawn; unchecked, the
|
|
3986
4464
|
// launch starts the checkout as-is.
|