dsh-plugin-local-infer 0.1.0 → 0.1.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/LICENSE +7 -0
- package/README.md +8 -1
- package/cordis.patch.yml +6 -6
- package/docs/screenshot-20261014.png +0 -0
- package/docs/zh/README.md +100 -0
- package/dsh/client.js +644 -143
- package/dsh/host.js +1 -1
- package/package.json +14 -11
- package/packages/core/lib/adapter.d.ts +75 -4
- package/packages/core/lib/debug.d.ts +1 -1
- package/packages/core/lib/debug.js +1 -1
- package/packages/core/lib/index.d.ts +3 -2
- package/packages/core/lib/index.js +2 -1
- package/packages/core/lib/launch-command.d.ts +13 -0
- package/packages/core/lib/launch-command.js +64 -0
- package/packages/core/lib/models.d.ts +2 -1
- package/packages/core/lib/models.js +10 -1
- package/packages/core/lib/status.d.ts +9 -1
- package/packages/core/lib/status.js +6 -1
- package/packages/core/package.json +1 -1
- package/packages/providers/lib/acquire.d.ts +125 -0
- package/packages/providers/lib/acquire.js +325 -0
- package/packages/providers/lib/adapters.d.ts +43 -6
- package/packages/providers/lib/adapters.js +386 -58
- package/packages/providers/lib/debug.d.ts +1 -1
- package/packages/providers/lib/debug.js +1 -1
- package/packages/providers/lib/deps.d.ts +6 -4
- package/packages/providers/lib/deps.js +17 -3
- package/packages/providers/lib/engines.d.ts +6 -5
- package/packages/providers/lib/engines.js +12 -4
- package/packages/providers/lib/guidelines.d.ts +2 -2
- package/packages/providers/lib/guidelines.js +17 -1
- package/packages/providers/lib/hyperqwen.d.ts +2 -2
- package/packages/providers/lib/hyperqwen.js +1 -1
- package/packages/providers/lib/index.d.ts +10 -4
- package/packages/providers/lib/index.js +9 -5
- package/packages/providers/lib/infernix.d.ts +170 -0
- package/packages/providers/lib/infernix.js +671 -0
- package/packages/providers/lib/llamacpp.d.ts +2 -2
- package/packages/providers/lib/llamacpp.js +1 -1
- package/packages/providers/lib/model-defaults.d.ts +7 -5
- package/packages/providers/lib/model-defaults.js +28 -4
- package/packages/providers/lib/ninfer.d.ts +9 -2
- package/packages/providers/lib/ninfer.js +10 -3
- package/packages/providers/lib/platform.d.ts +31 -0
- package/packages/providers/lib/platform.js +58 -0
- package/packages/providers/lib/presets.d.ts +2 -2
- package/packages/providers/lib/presets.js +1 -1
- package/packages/providers/lib/strata.d.ts +2 -2
- package/packages/providers/lib/strata.js +1 -1
- package/packages/providers/lib/vram.d.ts +2 -2
- package/packages/providers/lib/vram.js +1 -1
- package/packages/providers/package.json +3 -3
- package/packages/service/lib/index.d.ts +63 -20
- package/packages/service/lib/index.js +279 -43
- package/packages/service/lib/types.d.ts +44 -2
- package/packages/service/package.json +2 -2
- package/packages/tool/lib/index.d.ts +1 -1
- package/packages/tool/lib/index.js +1 -1
- package/packages/tool/package.json +3 -3
- package/packages/ui/lib/deps.d.ts +2 -2
- package/packages/ui/lib/deps.js +1 -1
- package/packages/ui/lib/form.d.ts +2 -2
- package/packages/ui/lib/form.js +1 -1
- package/packages/ui/lib/index.d.ts +1 -1
- package/packages/ui/lib/index.js +1 -1
- package/packages/ui/lib/meter.d.ts +2 -2
- package/packages/ui/lib/meter.js +1 -1
- package/packages/ui/lib/route.d.ts +84 -13
- package/packages/ui/lib/route.js +496 -56
- package/packages/ui/lib/warnings.d.ts +2 -2
- package/packages/ui/lib/warnings.js +1 -1
- package/packages/ui/package.json +5 -5
package/dsh/client.js
CHANGED
|
@@ -20,13 +20,16 @@
|
|
|
20
20
|
// table predates the primitives seed keep the native controls through the
|
|
21
21
|
// require fallback in mountCard.
|
|
22
22
|
window.__ModuleLoader__.load({
|
|
23
|
-
id: '
|
|
23
|
+
id: 'dsh-plugin-local-infer',
|
|
24
24
|
factory: (require) => {
|
|
25
25
|
var module = { exports: {} };
|
|
26
26
|
var exports = module.exports;
|
|
27
27
|
|
|
28
|
-
// The Plugins page dispatches a bundle's configuration by
|
|
29
|
-
|
|
28
|
+
// The Plugins page dispatches a bundle's configuration by the bundle's
|
|
29
|
+
// manifest name (the root package.json "name" field, unscoped for git-URL
|
|
30
|
+
// installs), which is the key the package inventory uses for the
|
|
31
|
+
// plugins.bundle.config slot.
|
|
32
|
+
var PACKAGE_NAME = 'dsh-plugin-local-infer';
|
|
30
33
|
|
|
31
34
|
// The card's copy. Two short label sets rather than a locale bundle: the
|
|
32
35
|
// card has a couple of dozen strings, and a bundle would be more machinery
|
|
@@ -80,6 +83,8 @@ window.__ModuleLoader__.load({
|
|
|
80
83
|
run: 'Run',
|
|
81
84
|
running: 'running...',
|
|
82
85
|
get: 'Get',
|
|
86
|
+
getting: 'getting...',
|
|
87
|
+
getFailed: 'get failed',
|
|
83
88
|
allowRun: 'Allow one-click install (sudo)',
|
|
84
89
|
depsSatisfied: 'All dependencies satisfied.',
|
|
85
90
|
installRejected: 'one-click install is not allowed; copy the install string instead',
|
|
@@ -91,6 +96,7 @@ window.__ModuleLoader__.load({
|
|
|
91
96
|
unregistered: 'removed',
|
|
92
97
|
unregisterFailed: 'remove failed',
|
|
93
98
|
launch: 'Launch',
|
|
99
|
+
autoRebuild: 'Auto Rebuild',
|
|
94
100
|
launching: 'Launching...',
|
|
95
101
|
launchFailed: 'launch failed',
|
|
96
102
|
runningOn: 'running on port',
|
|
@@ -103,6 +109,8 @@ window.__ModuleLoader__.load({
|
|
|
103
109
|
enginePlatform: 'Engine platform',
|
|
104
110
|
engineDirectory: 'Engine Directory',
|
|
105
111
|
engineFork: 'Engine Fork (Git Repo)',
|
|
112
|
+
buildCommands: 'Build commands',
|
|
113
|
+
dockerTag: 'Docker tag',
|
|
106
114
|
dirPlaceholder: 'directory path',
|
|
107
115
|
forkPlaceholder: 'git repo url',
|
|
108
116
|
hfCliDetected: 'HF CLI detected:',
|
|
@@ -112,6 +120,7 @@ window.__ModuleLoader__.load({
|
|
|
112
120
|
downloaded: 'download complete',
|
|
113
121
|
downloadFailed: 'download failed',
|
|
114
122
|
helpToggle: 'Show or hide field help',
|
|
123
|
+
refresh: 'Re-fetch the card data from the host',
|
|
115
124
|
engineHelp:
|
|
116
125
|
'Inference engines have different strengths and weaknesses for specific models and hardware.',
|
|
117
126
|
modelHelp:
|
|
@@ -120,8 +129,12 @@ window.__ModuleLoader__.load({
|
|
|
120
129
|
'How the engine is deployed. local builds it from source on the host; docker pulls a prebuilt image. The dependency plan and hardware guidelines price the selected mode.',
|
|
121
130
|
modelDirHelp: 'Where model files are downloaded and served from.',
|
|
122
131
|
engineDirHelp:
|
|
123
|
-
'Local directory of the engine source.
|
|
132
|
+
'Local directory of the engine source. Get clones or updates the git repo and builds the engine here; with no override it lands in the model directory under the engine name.',
|
|
124
133
|
engineForkHelp: 'Git repo that overrides the engine default upstream for the clone step.',
|
|
134
|
+
buildCommandsHelp:
|
|
135
|
+
'Local build command that overrides the engine default for the checkout build. Shown only in local build mode.',
|
|
136
|
+
dockerTagHelp:
|
|
137
|
+
'Docker image tag the docker deploy pulls. Shown only in docker build mode.',
|
|
125
138
|
providers: 'Providers',
|
|
126
139
|
newProvider: 'New provider',
|
|
127
140
|
edit: 'Edit',
|
|
@@ -139,6 +152,10 @@ window.__ModuleLoader__.load({
|
|
|
139
152
|
entryLaunch: 'Launch',
|
|
140
153
|
entryStop: 'Stop',
|
|
141
154
|
entryLogs: 'Logs',
|
|
155
|
+
entryPrereqs: 'Prereqs...',
|
|
156
|
+
entryDownloading: 'Downloading...',
|
|
157
|
+
entryBuilding: 'Building...',
|
|
158
|
+
entryLaunching: 'Launching...',
|
|
142
159
|
chatTemplatePreset: 'Chat template preset',
|
|
143
160
|
customChatTemplate: 'Custom Chat template',
|
|
144
161
|
custom: 'Custom',
|
|
@@ -207,6 +224,8 @@ window.__ModuleLoader__.load({
|
|
|
207
224
|
run: '运行',
|
|
208
225
|
running: '运行中…',
|
|
209
226
|
get: '获取',
|
|
227
|
+
getting: '获取中…',
|
|
228
|
+
getFailed: '获取失败',
|
|
210
229
|
allowRun: '允许一键安装 (sudo)',
|
|
211
230
|
depsSatisfied: '所有依赖已满足。',
|
|
212
231
|
installRejected: '未允许一键安装;请复制安装命令。',
|
|
@@ -218,6 +237,7 @@ window.__ModuleLoader__.load({
|
|
|
218
237
|
unregistered: '已移除',
|
|
219
238
|
unregisterFailed: '移除失败',
|
|
220
239
|
launch: '启动',
|
|
240
|
+
autoRebuild: '自动重建',
|
|
221
241
|
launching: '启动中…',
|
|
222
242
|
launchFailed: '启动失败',
|
|
223
243
|
runningOn: '运行于端口',
|
|
@@ -230,6 +250,8 @@ window.__ModuleLoader__.load({
|
|
|
230
250
|
enginePlatform: '引擎平台',
|
|
231
251
|
engineDirectory: '引擎目录',
|
|
232
252
|
engineFork: '引擎 Fork (Git 仓库)',
|
|
253
|
+
buildCommands: '构建命令',
|
|
254
|
+
dockerTag: 'Docker 标签',
|
|
233
255
|
dirPlaceholder: '目录路径',
|
|
234
256
|
forkPlaceholder: 'git 仓库地址',
|
|
235
257
|
hfCliDetected: '检测到 HF CLI:',
|
|
@@ -239,13 +261,16 @@ window.__ModuleLoader__.load({
|
|
|
239
261
|
downloaded: '下载完成',
|
|
240
262
|
downloadFailed: '下载失败',
|
|
241
263
|
helpToggle: '显示或隐藏字段帮助',
|
|
264
|
+
refresh: '从主机重新加载卡片数据',
|
|
242
265
|
engineHelp: '推理引擎针对特定模型和硬件各有优劣。',
|
|
243
266
|
modelHelp: '引擎服务的模型。可选项包括引擎默认模型和模型目录中已有的模型文件。',
|
|
244
267
|
buildHelp:
|
|
245
268
|
'引擎的部署方式。local 在主机上从源码构建;docker 拉取预构建镜像。依赖计划和硬件准则按所选模式计价。',
|
|
246
269
|
modelDirHelp: '模型文件的下载和服务目录。',
|
|
247
|
-
engineDirHelp: '
|
|
270
|
+
engineDirHelp: '引擎源码的本地目录。“获取”会在此克隆或更新 git 仓库并构建引擎;未覆盖时,仓库落在模型目录下以引擎命名的子目录中。',
|
|
248
271
|
engineForkHelp: '用于克隆步骤、覆盖引擎默认上游的 git 仓库。',
|
|
272
|
+
buildCommandsHelp: '本地构建命令,覆盖引擎默认的检查构建。仅在本地构建模式下显示。',
|
|
273
|
+
dockerTagHelp: 'docker 部署拉取的镜像标签。仅在 docker 构建模式下显示。',
|
|
249
274
|
providers: '提供方',
|
|
250
275
|
newProvider: '新增提供方',
|
|
251
276
|
edit: '编辑',
|
|
@@ -262,6 +287,10 @@ window.__ModuleLoader__.load({
|
|
|
262
287
|
entryLaunch: '启动',
|
|
263
288
|
entryStop: '停止',
|
|
264
289
|
entryLogs: '日志',
|
|
290
|
+
entryPrereqs: '前置条件…',
|
|
291
|
+
entryDownloading: '下载中…',
|
|
292
|
+
entryBuilding: '构建中…',
|
|
293
|
+
entryLaunching: '启动中…',
|
|
265
294
|
chatTemplatePreset: '聊天模板预设',
|
|
266
295
|
customChatTemplate: '自定义聊天模板',
|
|
267
296
|
custom: '自定义',
|
|
@@ -429,6 +458,61 @@ window.__ModuleLoader__.load({
|
|
|
429
458
|
color: '#fff',
|
|
430
459
|
border: 'none',
|
|
431
460
|
});
|
|
461
|
+
|
|
462
|
+
// The per-entry launch state machine: the card's Launch control lifecycle.
|
|
463
|
+
// The states are the button's reads: idle (a plain Launch), the four
|
|
464
|
+
// rebuild phases (prereqs, download, build, launch), running (a Stop), and
|
|
465
|
+
// failed (a red Logs). launchNextState is the hardened transition: a step
|
|
466
|
+
// event only advances forward through the rebuild phases (a stale or
|
|
467
|
+
// out-of-order step is a no-op), a healthy result settles to running, a
|
|
468
|
+
// failed result settles to failed, and a stop or a break (the Logs control,
|
|
469
|
+
// a navigation away) resets to idle. An event that names no legal next
|
|
470
|
+
// state leaves the control where it is, so a late event never corrupts it.
|
|
471
|
+
var LAUNCH_STEP_ORDER = { prereqs: 0, download: 1, build: 2, launch: 3 };
|
|
472
|
+
var LAUNCH_PHASE_STATES = ['prereqs', 'download', 'build', 'launch'];
|
|
473
|
+
// The health-poll deadline: the route's poll runs up to two minutes, so a
|
|
474
|
+
// client-side deadline just past that cap forces a hung launch to the red
|
|
475
|
+
// Logs state. It arms only for the launch phase; the rebuild phases
|
|
476
|
+
// (prereqs, download, build) run as long as they need to, so a slow build
|
|
477
|
+
// never reads as a failure.
|
|
478
|
+
var LAUNCH_HEALTH_DEADLINE_MS = 150000;
|
|
479
|
+
|
|
480
|
+
function launchIsPhase(state) {
|
|
481
|
+
return LAUNCH_PHASE_STATES.indexOf(state) !== -1;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function launchNextState(current, event) {
|
|
485
|
+
var type = event.type;
|
|
486
|
+
if (type === 'start') {
|
|
487
|
+
// A launch begins from idle (a plain Launch) or failed (a re-launch
|
|
488
|
+
// after the Logs control cleared the failed state).
|
|
489
|
+
return current === 'idle' || current === 'failed' ? 'prereqs' : current;
|
|
490
|
+
}
|
|
491
|
+
if (type === 'step') {
|
|
492
|
+
var target = event.step;
|
|
493
|
+
if (LAUNCH_STEP_ORDER[target] === undefined) return current;
|
|
494
|
+
if (current === 'idle' || current === 'failed') return target;
|
|
495
|
+
if (current === 'running') return current;
|
|
496
|
+
var currentOrder = LAUNCH_STEP_ORDER[current];
|
|
497
|
+
if (currentOrder === undefined) return current;
|
|
498
|
+
return LAUNCH_STEP_ORDER[target] >= currentOrder ? target : current;
|
|
499
|
+
}
|
|
500
|
+
if (type === 'healthy') {
|
|
501
|
+
return launchIsPhase(current) ? 'running' : current;
|
|
502
|
+
}
|
|
503
|
+
if (type === 'failed') {
|
|
504
|
+
return launchIsPhase(current) ? 'failed' : current;
|
|
505
|
+
}
|
|
506
|
+
if (type === 'stop') {
|
|
507
|
+
// A stop settles a running engine (or a launch in flight) back to idle.
|
|
508
|
+
return current === 'running' || launchIsPhase(current) ? 'idle' : current;
|
|
509
|
+
}
|
|
510
|
+
if (type === 'reset') {
|
|
511
|
+
return 'idle';
|
|
512
|
+
}
|
|
513
|
+
return current;
|
|
514
|
+
}
|
|
515
|
+
|
|
432
516
|
// The deps panel. The install block is a copy/paste target, so it gets a
|
|
433
517
|
// monospace pre with its own copy button; the dep rows read their status
|
|
434
518
|
// from a state dot (the DSH StateDot, or its glyph fallback), not a word,
|
|
@@ -473,10 +557,10 @@ window.__ModuleLoader__.load({
|
|
|
473
557
|
gap: 4,
|
|
474
558
|
justifyContent: 'flex-end',
|
|
475
559
|
};
|
|
476
|
-
// The circular
|
|
477
|
-
//
|
|
478
|
-
//
|
|
479
|
-
//
|
|
560
|
+
// The circular header buttons: the "?" help toggle and the refresh
|
|
561
|
+
// action. The active state reads as the accent fill; the inactive state
|
|
562
|
+
// as the muted outline. The help preference persists through the save
|
|
563
|
+
// route, so it survives a reload.
|
|
480
564
|
var HELP_BUTTON_STYLE = (active) => ({
|
|
481
565
|
width: 20,
|
|
482
566
|
height: 20,
|
|
@@ -815,13 +899,24 @@ window.__ModuleLoader__.load({
|
|
|
815
899
|
// a collapsed card with a status dot (green running, red stopped, yellow
|
|
816
900
|
// The per-entry Launch control: a card entry's own lifecycle button, shown
|
|
817
901
|
// alongside its Edit and Delete controls. Idle reads as a Launch button
|
|
818
|
-
// that starts the entry's engine; a
|
|
819
|
-
// control with the
|
|
820
|
-
//
|
|
821
|
-
//
|
|
902
|
+
// that starts the entry's engine; a rebuild in flight shows a disabled
|
|
903
|
+
// control with the spinner and the phase label (Prereqs, Downloading,
|
|
904
|
+
// Building, Launching); a healthy engine reads as an enabled Stop; a launch
|
|
905
|
+
// that timed out reads as a red Logs control that opens the entry for
|
|
906
|
+
// editing, the same action as Edit.
|
|
822
907
|
function renderEntryLaunchButton(react, primitives, provider, opts) {
|
|
823
908
|
var state = (opts.launchState || {})[provider.id];
|
|
824
|
-
|
|
909
|
+
var status = state !== undefined ? state.status : 'idle';
|
|
910
|
+
// A rebuild phase in flight: a disabled control with the spinner and the
|
|
911
|
+
// phase label, so the button reads the step that is actually running.
|
|
912
|
+
var phaseLabels = {
|
|
913
|
+
prereqs: opts.prereqsLabel,
|
|
914
|
+
download: opts.downloadingLabel,
|
|
915
|
+
build: opts.buildingLabel,
|
|
916
|
+
launch: opts.launchingLabel,
|
|
917
|
+
};
|
|
918
|
+
var phaseLabel = phaseLabels[status];
|
|
919
|
+
if (phaseLabel !== undefined) {
|
|
825
920
|
return react.createElement(
|
|
826
921
|
'button',
|
|
827
922
|
{
|
|
@@ -831,20 +926,17 @@ window.__ModuleLoader__.load({
|
|
|
831
926
|
{ display: 'inline-flex', alignItems: 'center', gap: 4, opacity: 0.7 },
|
|
832
927
|
GHOST_BUTTON,
|
|
833
928
|
),
|
|
834
|
-
'aria-label':
|
|
929
|
+
'aria-label': phaseLabel,
|
|
835
930
|
},
|
|
836
931
|
renderThrobber(react, primitives),
|
|
837
|
-
react.createElement('span', null,
|
|
932
|
+
react.createElement('span', null, phaseLabel),
|
|
838
933
|
);
|
|
839
934
|
}
|
|
840
935
|
// The Stop control reads from the provider status dot, not just the
|
|
841
936
|
// per-entry launch state: the launch state is lost when the user
|
|
842
937
|
// navigates away from the plugin page and back, so a running engine
|
|
843
938
|
// (a green dot) must keep the control as Stop, not reset to Launch.
|
|
844
|
-
if (
|
|
845
|
-
(state !== undefined && state.status === 'running') ||
|
|
846
|
-
provider.status === 'running'
|
|
847
|
-
) {
|
|
939
|
+
if (status === 'running' || provider.status === 'running') {
|
|
848
940
|
return renderButton(
|
|
849
941
|
react,
|
|
850
942
|
primitives,
|
|
@@ -853,10 +945,10 @@ window.__ModuleLoader__.load({
|
|
|
853
945
|
opts.stopLabel,
|
|
854
946
|
);
|
|
855
947
|
}
|
|
856
|
-
if (
|
|
857
|
-
// A launch that timed out: a red Logs control that opens the
|
|
858
|
-
// editing and clears the failed state, so the control reads
|
|
859
|
-
// again and the user can fix the config and re-launch.
|
|
948
|
+
if (status === 'failed') {
|
|
949
|
+
// A launch that timed out or failed: a red Logs control that opens the
|
|
950
|
+
// entry for editing and clears the failed state, so the control reads
|
|
951
|
+
// as Launch again and the user can fix the config and re-launch.
|
|
860
952
|
return react.createElement(
|
|
861
953
|
'button',
|
|
862
954
|
{
|
|
@@ -1011,12 +1103,16 @@ window.__ModuleLoader__.load({
|
|
|
1011
1103
|
);
|
|
1012
1104
|
}
|
|
1013
1105
|
|
|
1014
|
-
// The DSH
|
|
1015
|
-
// launch is in flight
|
|
1016
|
-
//
|
|
1106
|
+
// The DSH loading indicator the per-entry Launch button shows while a
|
|
1107
|
+
// launch is in flight: the StateDot ongoing spinner (the primitives'
|
|
1108
|
+
// shared loader) with a CSS border-spin as the fallback a host without the
|
|
1109
|
+
// seed keeps.
|
|
1017
1110
|
function renderThrobber(react, primitives) {
|
|
1018
|
-
if (primitives && typeof primitives.
|
|
1019
|
-
return react.createElement(primitives.
|
|
1111
|
+
if (primitives && typeof primitives.StateDot === 'function') {
|
|
1112
|
+
return react.createElement(primitives.StateDot, {
|
|
1113
|
+
state: 'ongoing',
|
|
1114
|
+
size: 12,
|
|
1115
|
+
});
|
|
1020
1116
|
}
|
|
1021
1117
|
return react.createElement('span', {
|
|
1022
1118
|
style: {
|
|
@@ -1561,6 +1657,11 @@ window.__ModuleLoader__.load({
|
|
|
1561
1657
|
);
|
|
1562
1658
|
}
|
|
1563
1659
|
|
|
1660
|
+
// The Get run's output-idle cap. Two minutes without a streamed line
|
|
1661
|
+
// reads as a stalled build: the card re-enables Launch while the run
|
|
1662
|
+
// keeps streaming in the background.
|
|
1663
|
+
var GET_IDLE_MS = 120000;
|
|
1664
|
+
|
|
1564
1665
|
// The "System dependencies" panel: the per-dep status list, the copy/paste
|
|
1565
1666
|
// install block, the Play/Run arrow (only while the trust gate is on),
|
|
1566
1667
|
// and the "Get" gate that stays disabled until every dep reports
|
|
@@ -1689,8 +1790,11 @@ window.__ModuleLoader__.load({
|
|
|
1689
1790
|
react,
|
|
1690
1791
|
primitives,
|
|
1691
1792
|
'primary',
|
|
1692
|
-
{
|
|
1693
|
-
|
|
1793
|
+
{
|
|
1794
|
+
onClick: actions.onGet,
|
|
1795
|
+
disabled: !deps.allSatisfied || actions.getting === true,
|
|
1796
|
+
},
|
|
1797
|
+
actions.getting === true ? t.getting : t.get,
|
|
1694
1798
|
),
|
|
1695
1799
|
),
|
|
1696
1800
|
actions.depsNote
|
|
@@ -1759,12 +1863,26 @@ window.__ModuleLoader__.load({
|
|
|
1759
1863
|
// does.
|
|
1760
1864
|
var launchingState = react.useState(false);
|
|
1761
1865
|
var stoppingState = react.useState(false);
|
|
1866
|
+
// The Get step's state. Get runs the git sync, the dependency plan,
|
|
1867
|
+
// and the engine build on the host through /local-infer/get, and the
|
|
1868
|
+
// streamed output lands in getLogs (the Logs section shows it while
|
|
1869
|
+
// the run is in flight). Launch stays disabled while a Get is in
|
|
1870
|
+
// flight; the idle timer re-enables it once GET_IDLE_MS passes with
|
|
1871
|
+
// no output, so a silent build never parks the button.
|
|
1872
|
+
var gettingState = react.useState(false);
|
|
1873
|
+
var getLogsState = react.useState(null);
|
|
1874
|
+
var getIdleTimerRef = react.useRef(null);
|
|
1762
1875
|
// Whether the draft's engine is confirmed running, so the single
|
|
1763
1876
|
// Launch/Stop control reads as Stop while the engine is up and Launch
|
|
1764
1877
|
// while it is down, the same toggle the per-entry control does. A
|
|
1765
1878
|
// healthy launch sets it, a stop clears it.
|
|
1766
1879
|
var draftRunningState = react.useState(false);
|
|
1767
1880
|
var lifecycleNoteState = react.useState('');
|
|
1881
|
+
// The Auto Rebuild checkbox next to the form's Launch button. Checked
|
|
1882
|
+
// by default: a launch runs the git sync (fetch, rebase, rebuild)
|
|
1883
|
+
// before spawning the engine. Unchecked, the launch skips the sync and
|
|
1884
|
+
// starts the checkout as-is.
|
|
1885
|
+
var autoRebuildState = react.useState(true);
|
|
1768
1886
|
// The model download state. The Download button runs the hf CLI
|
|
1769
1887
|
// download through the /local-infer/download route, and the note
|
|
1770
1888
|
// reports the answer the route returned.
|
|
@@ -1809,8 +1927,9 @@ window.__ModuleLoader__.load({
|
|
|
1809
1927
|
// from the summary's stored template on load; a Save persists it.
|
|
1810
1928
|
var logPathState = react.useState('');
|
|
1811
1929
|
// The per-entry launch state: a map from provider id to the entry's
|
|
1812
|
-
//
|
|
1813
|
-
// launch
|
|
1930
|
+
// launch-state-machine state (the rebuild phases 'prereqs', 'download',
|
|
1931
|
+
// 'build', 'launch', plus 'running' and 'failed') and the start time a
|
|
1932
|
+
// launch began, so a card entry can show its phase label, Stop, or red
|
|
1814
1933
|
// Logs control. Absent entries read as idle (a plain Launch button).
|
|
1815
1934
|
var entryLaunchState = react.useState({});
|
|
1816
1935
|
var data = dataState[0];
|
|
@@ -1827,8 +1946,11 @@ window.__ModuleLoader__.load({
|
|
|
1827
1946
|
var agentDefault = agentDefaultState[0];
|
|
1828
1947
|
var launching = launchingState[0];
|
|
1829
1948
|
var stopping = stoppingState[0];
|
|
1949
|
+
var getting = gettingState[0];
|
|
1950
|
+
var getLogs = getLogsState[0];
|
|
1830
1951
|
var draftRunning = draftRunningState[0];
|
|
1831
1952
|
var lifecycleNote = lifecycleNoteState[0];
|
|
1953
|
+
var autoRebuild = autoRebuildState[0];
|
|
1832
1954
|
var downloading = downloadingState[0];
|
|
1833
1955
|
var downloadNote = downloadNoteState[0];
|
|
1834
1956
|
var templateDownloading = templateDownloadingState[0];
|
|
@@ -1849,6 +1971,14 @@ window.__ModuleLoader__.load({
|
|
|
1849
1971
|
? 'new'
|
|
1850
1972
|
: providerFormMode;
|
|
1851
1973
|
var timerRef = react.useRef(null);
|
|
1974
|
+
// The latest committed draft, mirrored into a ref so the debounced
|
|
1975
|
+
// re-price reads the value the user last typed, not a stale closure.
|
|
1976
|
+
// The effect runs after each render, so the ref settles before the
|
|
1977
|
+
// 300ms timer fires.
|
|
1978
|
+
var draftRef = react.useRef(null);
|
|
1979
|
+
react.useEffect(() => {
|
|
1980
|
+
draftRef.current = draft;
|
|
1981
|
+
}, [draft]);
|
|
1852
1982
|
|
|
1853
1983
|
var fetchSummary = react.useCallback(
|
|
1854
1984
|
(engine, config) => {
|
|
@@ -1867,6 +1997,10 @@ window.__ModuleLoader__.load({
|
|
|
1867
1997
|
)
|
|
1868
1998
|
.then((next) => {
|
|
1869
1999
|
dataState[1](next);
|
|
2000
|
+
// The streamed Get buffer yields to the persisted last-run
|
|
2001
|
+
// logs the summary now carries, so the Logs section keeps
|
|
2002
|
+
// the run's tail without a second copy.
|
|
2003
|
+
getLogsState[1](null);
|
|
1870
2004
|
draftState[1]((current) => {
|
|
1871
2005
|
// The summary's platformConfigs is the host-persisted
|
|
1872
2006
|
// platform state, while the draft's platform entries are
|
|
@@ -1938,6 +2072,25 @@ window.__ModuleLoader__.load({
|
|
|
1938
2072
|
[],
|
|
1939
2073
|
);
|
|
1940
2074
|
|
|
2075
|
+
// scheduleReprice resets the re-price timeout on every field change
|
|
2076
|
+
// and fetches the summary (the VRAM Est. meter) and the deps panel
|
|
2077
|
+
// only after typing pauses, so a fast keystroke stream is one round
|
|
2078
|
+
// trip, not one per letter. The ref reads the latest committed draft,
|
|
2079
|
+
// so the fetch prices the value the user last typed.
|
|
2080
|
+
var scheduleReprice = react.useCallback(() => {
|
|
2081
|
+
if (timerRef.current) {
|
|
2082
|
+
clearTimeout(timerRef.current);
|
|
2083
|
+
}
|
|
2084
|
+
timerRef.current = setTimeout(() => {
|
|
2085
|
+
var latest = draftRef.current;
|
|
2086
|
+
if (latest === null || latest === undefined) {
|
|
2087
|
+
return;
|
|
2088
|
+
}
|
|
2089
|
+
fetchSummary(latest.engine, latest.config);
|
|
2090
|
+
fetchDeps(latest.engine, latest.config);
|
|
2091
|
+
}, 300);
|
|
2092
|
+
}, [fetchSummary, fetchDeps]);
|
|
2093
|
+
|
|
1941
2094
|
var load = react.useCallback(() => {
|
|
1942
2095
|
fetchSummary(null, null);
|
|
1943
2096
|
fetchDeps(null, null);
|
|
@@ -1949,9 +2102,11 @@ window.__ModuleLoader__.load({
|
|
|
1949
2102
|
}
|
|
1950
2103
|
}, [data, draft, load]);
|
|
1951
2104
|
|
|
1952
|
-
// A field edit updates the draft config and
|
|
1953
|
-
//
|
|
1954
|
-
//
|
|
2105
|
+
// A field edit updates the draft config and schedules a debounced
|
|
2106
|
+
// re-price of the meter and form gating. The draft update is pure;
|
|
2107
|
+
// the re-price (the VRAM Est. visualization) runs only after typing
|
|
2108
|
+
// pauses, so a fast keystroke stream is one round trip, not one per
|
|
2109
|
+
// letter.
|
|
1955
2110
|
var onValue = react.useCallback(
|
|
1956
2111
|
(key, value) => {
|
|
1957
2112
|
noteState[1]('');
|
|
@@ -1961,22 +2116,15 @@ window.__ModuleLoader__.load({
|
|
|
1961
2116
|
draftState[1]((current) => {
|
|
1962
2117
|
var config = Object.assign({}, current.config);
|
|
1963
2118
|
config[key] = value;
|
|
1964
|
-
|
|
2119
|
+
return {
|
|
1965
2120
|
engine: current.engine,
|
|
1966
2121
|
config: config,
|
|
1967
2122
|
platform: current.platform || {},
|
|
1968
2123
|
};
|
|
1969
|
-
if (timerRef.current) {
|
|
1970
|
-
clearTimeout(timerRef.current);
|
|
1971
|
-
}
|
|
1972
|
-
timerRef.current = setTimeout(() => {
|
|
1973
|
-
fetchSummary(next.engine, next.config);
|
|
1974
|
-
fetchDeps(next.engine, next.config);
|
|
1975
|
-
}, 300);
|
|
1976
|
-
return next;
|
|
1977
2124
|
});
|
|
2125
|
+
scheduleReprice();
|
|
1978
2126
|
},
|
|
1979
|
-
[
|
|
2127
|
+
[scheduleReprice],
|
|
1980
2128
|
);
|
|
1981
2129
|
|
|
1982
2130
|
// The Model Directory field edit. A valid directory (non-blank, no
|
|
@@ -2134,7 +2282,9 @@ window.__ModuleLoader__.load({
|
|
|
2134
2282
|
// The help toggle: flip the local state and persist the preference
|
|
2135
2283
|
// through the save route, so it survives a reload. The save response
|
|
2136
2284
|
// carries the fresh summary, so the data and the draft resync from
|
|
2137
|
-
// the same round trip, the way the allow-run toggle does.
|
|
2285
|
+
// the same round trip, the way the allow-run toggle does. The
|
|
2286
|
+
// settings-only flag keeps the route from upserting a provider entry,
|
|
2287
|
+
// so a toggle never mints a duplicate at the top of the list.
|
|
2138
2288
|
var onToggleHelp = react.useCallback(
|
|
2139
2289
|
(next) => {
|
|
2140
2290
|
if (draft === null) {
|
|
@@ -2150,6 +2300,7 @@ window.__ModuleLoader__.load({
|
|
|
2150
2300
|
allowRunDeps: deps !== null && deps.runAllowed === true,
|
|
2151
2301
|
platform: draft.platform || {},
|
|
2152
2302
|
showHelp: next,
|
|
2303
|
+
settingsOnly: true,
|
|
2153
2304
|
}),
|
|
2154
2305
|
})
|
|
2155
2306
|
.then((response) =>
|
|
@@ -2258,18 +2409,11 @@ window.__ModuleLoader__.load({
|
|
|
2258
2409
|
draftState[1]((_current) => {
|
|
2259
2410
|
// The platform config is per-engine, so an engine switch keeps
|
|
2260
2411
|
// the map and only resets the per-engine parameter config.
|
|
2261
|
-
|
|
2262
|
-
if (timerRef.current) {
|
|
2263
|
-
clearTimeout(timerRef.current);
|
|
2264
|
-
}
|
|
2265
|
-
timerRef.current = setTimeout(() => {
|
|
2266
|
-
fetchSummary(next.engine, next.config);
|
|
2267
|
-
fetchDeps(next.engine, next.config);
|
|
2268
|
-
}, 300);
|
|
2269
|
-
return next;
|
|
2412
|
+
return { engine: engine, config: {}, platform: _current.platform || {} };
|
|
2270
2413
|
});
|
|
2414
|
+
scheduleReprice();
|
|
2271
2415
|
},
|
|
2272
|
-
[
|
|
2416
|
+
[scheduleReprice],
|
|
2273
2417
|
);
|
|
2274
2418
|
|
|
2275
2419
|
var save = react.useCallback(() => {
|
|
@@ -2485,6 +2629,9 @@ window.__ModuleLoader__.load({
|
|
|
2485
2629
|
}
|
|
2486
2630
|
}, [deps, t]);
|
|
2487
2631
|
|
|
2632
|
+
// The allow-run toggle persists the trust gate through the save route.
|
|
2633
|
+
// The settings-only flag keeps the route from upserting a provider
|
|
2634
|
+
// entry, so a toggle never mints a duplicate at the top of the list.
|
|
2488
2635
|
var onAllowRun = react.useCallback(
|
|
2489
2636
|
(checked) => {
|
|
2490
2637
|
if (draft === null) {
|
|
@@ -2499,6 +2646,7 @@ window.__ModuleLoader__.load({
|
|
|
2499
2646
|
allowRunDeps: checked,
|
|
2500
2647
|
platform: draft.platform || {},
|
|
2501
2648
|
showHelp: help,
|
|
2649
|
+
settingsOnly: true,
|
|
2502
2650
|
}),
|
|
2503
2651
|
})
|
|
2504
2652
|
.then((response) =>
|
|
@@ -2565,14 +2713,78 @@ window.__ModuleLoader__.load({
|
|
|
2565
2713
|
}, [deps, draft, installing, t]);
|
|
2566
2714
|
|
|
2567
2715
|
var onGet = react.useCallback(() => {
|
|
2568
|
-
if (deps === null || draft === null) {
|
|
2716
|
+
if (deps === null || draft === null || getting) {
|
|
2569
2717
|
return;
|
|
2570
2718
|
}
|
|
2571
|
-
// "Get"
|
|
2572
|
-
//
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2719
|
+
// "Get" runs the git sync, the dependency plan, and the build on
|
|
2720
|
+
// the host through /local-infer/get. The route streams its output;
|
|
2721
|
+
// each chunk appends to the Logs section and re-arms the idle
|
|
2722
|
+
// timer, and Launch stays disabled until the run finishes, fails,
|
|
2723
|
+
// or falls silent for GET_IDLE_MS.
|
|
2724
|
+
gettingState[1](true);
|
|
2725
|
+
depsNoteState[1]('');
|
|
2726
|
+
getLogsState[1]((data && data.lastLogs) || '');
|
|
2727
|
+
var append = (text) => {
|
|
2728
|
+
getLogsState[1]((current) => (current === null ? '' : current) + text);
|
|
2729
|
+
};
|
|
2730
|
+
var armIdle = () => {
|
|
2731
|
+
if (getIdleTimerRef.current) {
|
|
2732
|
+
clearTimeout(getIdleTimerRef.current);
|
|
2733
|
+
}
|
|
2734
|
+
// Two minutes without a line reads as a stalled build: release
|
|
2735
|
+
// Launch while the run keeps streaming in the background.
|
|
2736
|
+
getIdleTimerRef.current = setTimeout(() => {
|
|
2737
|
+
gettingState[1](false);
|
|
2738
|
+
}, GET_IDLE_MS);
|
|
2739
|
+
};
|
|
2740
|
+
armIdle();
|
|
2741
|
+
var platform = (draft.platform || {})[draft.engine];
|
|
2742
|
+
fetch('/local-infer/get', {
|
|
2743
|
+
method: 'POST',
|
|
2744
|
+
headers: { 'content-type': 'application/json' },
|
|
2745
|
+
body: JSON.stringify({
|
|
2746
|
+
engine: draft.engine,
|
|
2747
|
+
config: draft.config,
|
|
2748
|
+
platform: platform,
|
|
2749
|
+
}),
|
|
2750
|
+
})
|
|
2751
|
+
.then((response) => {
|
|
2752
|
+
if (!response.ok) {
|
|
2753
|
+
return response.text().then((text) => {
|
|
2754
|
+
throw new Error(text || 'HTTP ' + response.status);
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
if (!response.body || typeof response.body.getReader !== 'function') {
|
|
2758
|
+
// A host whose response cannot stream: read the whole body.
|
|
2759
|
+
return response.text().then((text) => append(text));
|
|
2760
|
+
}
|
|
2761
|
+
var reader = response.body.getReader();
|
|
2762
|
+
var decoder = new TextDecoder();
|
|
2763
|
+
var pump = () =>
|
|
2764
|
+
reader.read().then(({ done, value }) => {
|
|
2765
|
+
if (done) {
|
|
2766
|
+
return undefined;
|
|
2767
|
+
}
|
|
2768
|
+
append(decoder.decode(value, { stream: true }));
|
|
2769
|
+
armIdle();
|
|
2770
|
+
return pump();
|
|
2771
|
+
});
|
|
2772
|
+
return pump();
|
|
2773
|
+
})
|
|
2774
|
+
.catch((error) => {
|
|
2775
|
+
append('\n' + t.getFailed + ': ' + (error && error.message ? error.message : '') + '\n');
|
|
2776
|
+
})
|
|
2777
|
+
.then(() => {
|
|
2778
|
+
if (getIdleTimerRef.current) {
|
|
2779
|
+
clearTimeout(getIdleTimerRef.current);
|
|
2780
|
+
getIdleTimerRef.current = null;
|
|
2781
|
+
}
|
|
2782
|
+
gettingState[1](false);
|
|
2783
|
+
// The route persisted the run as the last logs; the summary
|
|
2784
|
+
// re-read hands the Logs section back to the stored value.
|
|
2785
|
+
fetchSummary(draft.engine, draft.config);
|
|
2786
|
+
});
|
|
2787
|
+
}, [deps, draft, getting, data, t, fetchSummary]);
|
|
2576
2788
|
|
|
2577
2789
|
var onRegister = react.useCallback(() => {
|
|
2578
2790
|
if (draft === null || registering) {
|
|
@@ -2671,6 +2883,10 @@ window.__ModuleLoader__.load({
|
|
|
2671
2883
|
engine: draft.engine,
|
|
2672
2884
|
config: draft.config,
|
|
2673
2885
|
platform: platform,
|
|
2886
|
+
// The Auto Rebuild checkbox: false skips the git sync (fetch,
|
|
2887
|
+
// rebase, rebuild) and launches the checkout as-is. The stop
|
|
2888
|
+
// route ignores the key.
|
|
2889
|
+
autoRebuild: autoRebuild,
|
|
2674
2890
|
};
|
|
2675
2891
|
// The provider entry the draft edits: the route persists the launch
|
|
2676
2892
|
// record (and the stop's clear) on that entry, so a restart re-links
|
|
@@ -2692,9 +2908,48 @@ window.__ModuleLoader__.load({
|
|
|
2692
2908
|
headers: { 'content-type': 'application/json' },
|
|
2693
2909
|
body: launchBody(),
|
|
2694
2910
|
})
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2911
|
+
// The launch route streams NDJSON (a step event per phase, then a
|
|
2912
|
+
// final result event). The main form's control does not show the
|
|
2913
|
+
// phase labels, so read the stream and keep only the result event.
|
|
2914
|
+
.then(function (response) {
|
|
2915
|
+
if (response.body === null || typeof response.body.getReader !== 'function') {
|
|
2916
|
+
return response.json().then(function (body) {
|
|
2917
|
+
return { ok: response.ok, result: body };
|
|
2918
|
+
});
|
|
2919
|
+
}
|
|
2920
|
+
var reader = response.body.getReader();
|
|
2921
|
+
var decoder = new TextDecoder();
|
|
2922
|
+
var buffer = '';
|
|
2923
|
+
var resultEvent = null;
|
|
2924
|
+
function pump() {
|
|
2925
|
+
return reader.read().then(function (chunk) {
|
|
2926
|
+
if (chunk.done) {
|
|
2927
|
+
return { ok: response.ok, result: resultEvent };
|
|
2928
|
+
}
|
|
2929
|
+
buffer += decoder.decode(chunk.value, { stream: true });
|
|
2930
|
+
var lines = buffer.split('\n');
|
|
2931
|
+
buffer = lines.pop();
|
|
2932
|
+
for (var i = 0; i < lines.length; i++) {
|
|
2933
|
+
if (lines[i] === '') {
|
|
2934
|
+
continue;
|
|
2935
|
+
}
|
|
2936
|
+
var event;
|
|
2937
|
+
try {
|
|
2938
|
+
event = JSON.parse(lines[i]);
|
|
2939
|
+
} catch {
|
|
2940
|
+
continue;
|
|
2941
|
+
}
|
|
2942
|
+
if (event.result !== undefined) {
|
|
2943
|
+
resultEvent = event.result;
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
return pump();
|
|
2947
|
+
});
|
|
2948
|
+
}
|
|
2949
|
+
return pump();
|
|
2950
|
+
})
|
|
2951
|
+
.then(({ ok, result: body }) => {
|
|
2952
|
+
if (ok && body !== null) {
|
|
2698
2953
|
if (body.healthy) {
|
|
2699
2954
|
lifecycleNoteState[1](
|
|
2700
2955
|
t.runningOn + ' ' + (body.plan && body.plan.port ? body.plan.port : ''),
|
|
@@ -2706,7 +2961,9 @@ window.__ModuleLoader__.load({
|
|
|
2706
2961
|
lifecycleNoteState[1](t.notHealthy + (body.detail ? ': ' + body.detail : ''));
|
|
2707
2962
|
}
|
|
2708
2963
|
} else {
|
|
2709
|
-
lifecycleNoteState[1](
|
|
2964
|
+
lifecycleNoteState[1](
|
|
2965
|
+
t.launchFailed + (body && body.detail ? ': ' + body.detail : ''),
|
|
2966
|
+
);
|
|
2710
2967
|
}
|
|
2711
2968
|
})
|
|
2712
2969
|
.catch((error) => {
|
|
@@ -2716,8 +2973,13 @@ window.__ModuleLoader__.load({
|
|
|
2716
2973
|
})
|
|
2717
2974
|
.then(() => {
|
|
2718
2975
|
launchingState[1](false);
|
|
2976
|
+
// The route persisted the run's launch command line and engine
|
|
2977
|
+
// output as the last logs; the summary re-read hands the Logs
|
|
2978
|
+
// section the new run, the same rule the per-entry launch and
|
|
2979
|
+
// the Get apply when their run ends.
|
|
2980
|
+
fetchSummary(draft.engine, draft.config);
|
|
2719
2981
|
});
|
|
2720
|
-
}, [draft, launching, editingProviderId, t]);
|
|
2982
|
+
}, [draft, launching, editingProviderId, autoRebuild, t, fetchSummary]);
|
|
2721
2983
|
|
|
2722
2984
|
var onStop = react.useCallback(() => {
|
|
2723
2985
|
if (draft === null || stopping) {
|
|
@@ -2752,39 +3014,66 @@ window.__ModuleLoader__.load({
|
|
|
2752
3014
|
}, [draft, stopping, editingProviderId, t]);
|
|
2753
3015
|
|
|
2754
3016
|
// The per-entry launch: a card entry's own Launch control drives its
|
|
2755
|
-
// own engine, not the form's draft. It
|
|
2756
|
-
//
|
|
2757
|
-
//
|
|
2758
|
-
//
|
|
2759
|
-
//
|
|
3017
|
+
// own engine, not the form's draft. It starts the launch state machine
|
|
3018
|
+
// at the prereqs phase, then reads the launch stream (an NDJSON step
|
|
3019
|
+
// event as each phase begins, then a final result event) and drives the
|
|
3020
|
+
// machine: each step event advances the phase label (Prereqs,
|
|
3021
|
+
// Downloading, Building, Launching), a healthy result settles to
|
|
3022
|
+
// running (the control becomes Stop), and a failed result or the
|
|
3023
|
+
// health-poll deadline settles to failed (the control becomes a red
|
|
3024
|
+
// Logs that opens the entry for editing).
|
|
2760
3025
|
var onLaunchEntry = react.useCallback(
|
|
2761
3026
|
(provider) => {
|
|
2762
|
-
var
|
|
2763
|
-
|
|
3027
|
+
var prior = entryLaunch[provider.id];
|
|
3028
|
+
var status = prior !== undefined ? prior.status : 'idle';
|
|
3029
|
+
// A launch in flight (a rebuild phase) is ignored; a running engine
|
|
3030
|
+
// is stopped by its own Stop control, not re-launched.
|
|
3031
|
+
if (status !== 'idle' && status !== 'failed') {
|
|
2764
3032
|
return;
|
|
2765
3033
|
}
|
|
3034
|
+
var startStatus = launchNextState(status, { type: 'start' });
|
|
2766
3035
|
entryLaunchState[1]((current) =>
|
|
2767
3036
|
Object.assign({}, current, {
|
|
2768
|
-
[provider.id]: { status:
|
|
3037
|
+
[provider.id]: { status: startStatus, startTime: Date.now() },
|
|
2769
3038
|
}),
|
|
2770
3039
|
);
|
|
2771
|
-
//
|
|
2772
|
-
//
|
|
2773
|
-
//
|
|
2774
|
-
|
|
2775
|
-
|
|
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.
|
|
3043
|
+
var move = function (event) {
|
|
3044
|
+
entryLaunchState[1]((current) => {
|
|
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
|
+
});
|
|
3051
|
+
};
|
|
3052
|
+
// The health-poll deadline: the route's poll runs up to two minutes,
|
|
3053
|
+
// so a client-side deadline just past that cap forces a hung launch
|
|
3054
|
+
// to the red Logs state. It arms only for the launch phase; the
|
|
3055
|
+
// rebuild phases (prereqs, download, build) run as long as they
|
|
3056
|
+
// need to, so a slow build never reads as a failure.
|
|
2776
3057
|
var settled = false;
|
|
2777
|
-
var deadline =
|
|
2778
|
-
|
|
3058
|
+
var deadline = null;
|
|
3059
|
+
var armDeadline = function () {
|
|
3060
|
+
if (deadline !== null) {
|
|
2779
3061
|
return;
|
|
2780
3062
|
}
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
3063
|
+
deadline = setTimeout(function () {
|
|
3064
|
+
if (settled) {
|
|
3065
|
+
return;
|
|
3066
|
+
}
|
|
3067
|
+
settled = true;
|
|
3068
|
+
move({ type: 'failed' });
|
|
3069
|
+
}, LAUNCH_HEALTH_DEADLINE_MS);
|
|
3070
|
+
};
|
|
3071
|
+
var clearDeadline = function () {
|
|
3072
|
+
if (deadline !== null) {
|
|
3073
|
+
clearTimeout(deadline);
|
|
3074
|
+
deadline = null;
|
|
3075
|
+
}
|
|
3076
|
+
};
|
|
2788
3077
|
fetch('/local-infer/launch', {
|
|
2789
3078
|
method: 'POST',
|
|
2790
3079
|
headers: { 'content-type': 'application/json' },
|
|
@@ -2797,34 +3086,86 @@ window.__ModuleLoader__.load({
|
|
|
2797
3086
|
providerId: provider.id,
|
|
2798
3087
|
}),
|
|
2799
3088
|
})
|
|
2800
|
-
.then((response)
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
})
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
3089
|
+
.then(function (response) {
|
|
3090
|
+
// The launch streams NDJSON: a step event as each phase begins,
|
|
3091
|
+
// then a final result event. Read the stream and drive the
|
|
3092
|
+
// state machine; a response with no body (a host without
|
|
3093
|
+
// streaming) settles straight to failed.
|
|
3094
|
+
if (response.body === null || typeof response.body.getReader !== 'function') {
|
|
3095
|
+
settled = true;
|
|
3096
|
+
clearDeadline();
|
|
3097
|
+
move({ type: 'failed' });
|
|
3098
|
+
return Promise.resolve();
|
|
3099
|
+
}
|
|
3100
|
+
var reader = response.body.getReader();
|
|
3101
|
+
var decoder = new TextDecoder();
|
|
3102
|
+
var buffer = '';
|
|
3103
|
+
function pump() {
|
|
3104
|
+
return reader.read().then(function (result) {
|
|
3105
|
+
if (result.done) {
|
|
3106
|
+
// The stream ended without a result event (a host that
|
|
3107
|
+
// cannot stream, a route that broke mid-stream): settle
|
|
3108
|
+
// to failed so the control does not sit on a phase label.
|
|
3109
|
+
if (!settled) {
|
|
3110
|
+
settled = true;
|
|
3111
|
+
clearDeadline();
|
|
3112
|
+
move({ type: 'failed' });
|
|
3113
|
+
}
|
|
3114
|
+
return;
|
|
3115
|
+
}
|
|
3116
|
+
buffer += decoder.decode(result.value, { stream: true });
|
|
3117
|
+
var lines = buffer.split('\n');
|
|
3118
|
+
buffer = lines.pop();
|
|
3119
|
+
for (var i = 0; i < lines.length; i++) {
|
|
3120
|
+
var line = lines[i];
|
|
3121
|
+
if (line === '') {
|
|
3122
|
+
continue;
|
|
3123
|
+
}
|
|
3124
|
+
var event;
|
|
3125
|
+
try {
|
|
3126
|
+
event = JSON.parse(line);
|
|
3127
|
+
} catch {
|
|
3128
|
+
// A malformed line is not a launch event; skip it.
|
|
3129
|
+
continue;
|
|
3130
|
+
}
|
|
3131
|
+
if (event.step !== undefined) {
|
|
3132
|
+
move({ type: 'step', step: event.step });
|
|
3133
|
+
// Arm the deadline only for the launch (health-poll)
|
|
3134
|
+
// step; the rebuild steps run without a timeout.
|
|
3135
|
+
if (event.step === 'launch') {
|
|
3136
|
+
armDeadline();
|
|
3137
|
+
}
|
|
3138
|
+
} else if (event.result !== undefined) {
|
|
3139
|
+
settled = true;
|
|
3140
|
+
clearDeadline();
|
|
3141
|
+
var healthy = event.result.healthy === true;
|
|
3142
|
+
move(healthy ? { type: 'healthy' } : { type: 'failed' });
|
|
3143
|
+
// The provider status dots price the process probe on a
|
|
3144
|
+
// summary fetch, so a launch that turned an engine
|
|
3145
|
+
// healthy re-fetches the summary to repaint the dot
|
|
3146
|
+
// from red to green.
|
|
3147
|
+
fetchSummary(provider.engine, provider.config || {});
|
|
3148
|
+
return;
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
return pump();
|
|
3152
|
+
});
|
|
3153
|
+
}
|
|
3154
|
+
return pump();
|
|
2814
3155
|
})
|
|
2815
|
-
.catch(()
|
|
3156
|
+
.catch(function () {
|
|
2816
3157
|
settled = true;
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
Object.assign({}, current, {
|
|
2820
|
-
[provider.id]: { status: 'failed', startTime: Date.now() },
|
|
2821
|
-
}),
|
|
2822
|
-
);
|
|
3158
|
+
clearDeadline();
|
|
3159
|
+
move({ type: 'failed' });
|
|
2823
3160
|
});
|
|
2824
3161
|
},
|
|
2825
3162
|
[entryLaunch, fetchSummary],
|
|
2826
3163
|
);
|
|
2827
3164
|
|
|
3165
|
+
// The per-entry stop: a running engine's Stop control settles the
|
|
3166
|
+
// launch state machine back to idle (the control reads as Launch
|
|
3167
|
+
// again). The stop clears the launch record on this entry, so a
|
|
3168
|
+
// recycled pid cannot re-link it.
|
|
2828
3169
|
var onStopEntry = react.useCallback(
|
|
2829
3170
|
(provider) => {
|
|
2830
3171
|
fetch('/local-infer/stop', {
|
|
@@ -2841,9 +3182,16 @@ window.__ModuleLoader__.load({
|
|
|
2841
3182
|
})
|
|
2842
3183
|
.then(() => {
|
|
2843
3184
|
entryLaunchState[1]((current) => {
|
|
2844
|
-
var
|
|
2845
|
-
|
|
2846
|
-
|
|
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;
|
|
2847
3195
|
});
|
|
2848
3196
|
})
|
|
2849
3197
|
.catch(() => {
|
|
@@ -2854,17 +3202,24 @@ window.__ModuleLoader__.load({
|
|
|
2854
3202
|
);
|
|
2855
3203
|
|
|
2856
3204
|
// The per-entry Logs control: opening the logs loads the entry for
|
|
2857
|
-
// editing AND
|
|
2858
|
-
// reads as Launch again. The user fixes the config in the form
|
|
2859
|
-
// re-launches from the same entry, instead of being stuck on a
|
|
3205
|
+
// editing AND resets the launch state machine to idle, so the per-entry
|
|
3206
|
+
// control reads as Launch again. The user fixes the config in the form
|
|
3207
|
+
// and re-launches from the same entry, instead of being stuck on a
|
|
2860
3208
|
// non-actionable Logs control.
|
|
2861
3209
|
var onLogsEntry = react.useCallback(
|
|
2862
3210
|
(provider) => {
|
|
2863
3211
|
onEditProvider(provider);
|
|
2864
3212
|
entryLaunchState[1]((current) => {
|
|
2865
|
-
var
|
|
2866
|
-
|
|
2867
|
-
|
|
3213
|
+
var entry = current[provider.id];
|
|
3214
|
+
var status = entry !== undefined ? entry.status : 'idle';
|
|
3215
|
+
var next = launchNextState(status, { type: 'reset' });
|
|
3216
|
+
var map = Object.assign({}, current);
|
|
3217
|
+
if (next === 'idle') {
|
|
3218
|
+
delete map[provider.id];
|
|
3219
|
+
} else {
|
|
3220
|
+
map[provider.id] = { status: next, startTime: Date.now() };
|
|
3221
|
+
}
|
|
3222
|
+
return map;
|
|
2868
3223
|
});
|
|
2869
3224
|
},
|
|
2870
3225
|
[onEditProvider],
|
|
@@ -2893,7 +3248,7 @@ window.__ModuleLoader__.load({
|
|
|
2893
3248
|
return react.createElement(
|
|
2894
3249
|
'div',
|
|
2895
3250
|
{ style: CARD_STYLE },
|
|
2896
|
-
// The keyframes the
|
|
3251
|
+
// The keyframes the fallback spinner animates with. Injected
|
|
2897
3252
|
// once per card so a host without the DSH seed still shows a spin.
|
|
2898
3253
|
react.createElement(
|
|
2899
3254
|
'style',
|
|
@@ -2949,9 +3304,14 @@ window.__ModuleLoader__.load({
|
|
|
2949
3304
|
: null,
|
|
2950
3305
|
)
|
|
2951
3306
|
: null,
|
|
2952
|
-
// The card header: the section title plus the circular
|
|
2953
|
-
//
|
|
2954
|
-
//
|
|
3307
|
+
// The card header: the section title plus the circular refresh and
|
|
3308
|
+
// "?" buttons. The refresh button re-fetches the summary and deps
|
|
3309
|
+
// from the host, so the card re-prices after the harness pauses
|
|
3310
|
+
// the plugin's routes and re-activates them. The refresh glyph is
|
|
3311
|
+
// the primitives seed's IconRefreshOutlineRegular; a host without
|
|
3312
|
+
// the seed keeps the native arrow character. The "?" button shows
|
|
3313
|
+
// or hides the per-field descriptions; the preference persists
|
|
3314
|
+
// through the save route, so it survives a reload.
|
|
2955
3315
|
react.createElement(
|
|
2956
3316
|
'div',
|
|
2957
3317
|
{
|
|
@@ -2964,16 +3324,39 @@ window.__ModuleLoader__.load({
|
|
|
2964
3324
|
},
|
|
2965
3325
|
react.createElement('div', { style: { fontWeight: 600, fontSize: 14 } }, t.section),
|
|
2966
3326
|
react.createElement(
|
|
2967
|
-
'
|
|
3327
|
+
'div',
|
|
2968
3328
|
{
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
onClick: () => onToggleHelp(!help),
|
|
3329
|
+
style: {
|
|
3330
|
+
display: 'flex',
|
|
3331
|
+
alignItems: 'center',
|
|
3332
|
+
gap: 8,
|
|
3333
|
+
},
|
|
2975
3334
|
},
|
|
2976
|
-
|
|
3335
|
+
react.createElement(
|
|
3336
|
+
'button',
|
|
3337
|
+
{
|
|
3338
|
+
type: 'button',
|
|
3339
|
+
style: HELP_BUTTON_STYLE(false),
|
|
3340
|
+
title: t.refresh,
|
|
3341
|
+
'aria-label': t.refresh,
|
|
3342
|
+
onClick: () => load(),
|
|
3343
|
+
},
|
|
3344
|
+
primitives && typeof primitives.IconRefreshOutlineRegular === 'function'
|
|
3345
|
+
? react.createElement(primitives.IconRefreshOutlineRegular, { size: 12 })
|
|
3346
|
+
: '↻',
|
|
3347
|
+
),
|
|
3348
|
+
react.createElement(
|
|
3349
|
+
'button',
|
|
3350
|
+
{
|
|
3351
|
+
type: 'button',
|
|
3352
|
+
style: HELP_BUTTON_STYLE(help),
|
|
3353
|
+
title: t.helpToggle,
|
|
3354
|
+
'aria-label': t.helpToggle,
|
|
3355
|
+
'aria-pressed': help,
|
|
3356
|
+
onClick: () => onToggleHelp(!help),
|
|
3357
|
+
},
|
|
3358
|
+
'?',
|
|
3359
|
+
),
|
|
2977
3360
|
),
|
|
2978
3361
|
),
|
|
2979
3362
|
// The provider list: the saved provider entries, each a collapsed
|
|
@@ -3004,7 +3387,10 @@ window.__ModuleLoader__.load({
|
|
|
3004
3387
|
launchLabel: t.entryLaunch,
|
|
3005
3388
|
stopLabel: t.entryStop,
|
|
3006
3389
|
logsLabel: t.entryLogs,
|
|
3007
|
-
launchingLabel: t.
|
|
3390
|
+
launchingLabel: t.entryLaunching,
|
|
3391
|
+
prereqsLabel: t.entryPrereqs,
|
|
3392
|
+
downloadingLabel: t.entryDownloading,
|
|
3393
|
+
buildingLabel: t.entryBuilding,
|
|
3008
3394
|
})
|
|
3009
3395
|
: null,
|
|
3010
3396
|
// The provider form: the Engine/Model config the card prices. It is
|
|
@@ -3307,6 +3693,94 @@ window.__ModuleLoader__.load({
|
|
|
3307
3693
|
),
|
|
3308
3694
|
),
|
|
3309
3695
|
help ? renderHelpRow(react, t.engineForkHelp) : null,
|
|
3696
|
+
(() => {
|
|
3697
|
+
// The build-method-dependent platform fields: local build mode
|
|
3698
|
+
// shows the Build commands override, docker build mode shows the
|
|
3699
|
+
// Docker tag override. Only one is visible at a time, keyed off
|
|
3700
|
+
// the same buildMode the Build row prices. The placeholder seeds
|
|
3701
|
+
// the engine's own default (the build step the deploy runs, or the
|
|
3702
|
+
// image tag the docker deploy pulls), so an empty field reads as
|
|
3703
|
+
// "use the default."
|
|
3704
|
+
var engineEntry = (data.engines || []).find(
|
|
3705
|
+
(entry) => entry.id === draft.engine,
|
|
3706
|
+
);
|
|
3707
|
+
var buildModes = [];
|
|
3708
|
+
if (engineEntry && engineEntry.supports.local) buildModes.push('local');
|
|
3709
|
+
if (engineEntry && engineEntry.supports.docker) buildModes.push('docker');
|
|
3710
|
+
var buildMode =
|
|
3711
|
+
typeof draft.config.buildMode === 'string'
|
|
3712
|
+
? draft.config.buildMode
|
|
3713
|
+
: buildModes[0] || 'local';
|
|
3714
|
+
if (buildMode === 'local') {
|
|
3715
|
+
return react.createElement(
|
|
3716
|
+
'div',
|
|
3717
|
+
null,
|
|
3718
|
+
react.createElement(
|
|
3719
|
+
'div',
|
|
3720
|
+
{ style: ROW_STYLE },
|
|
3721
|
+
react.createElement('label', { style: LABEL_STYLE }, t.buildCommands),
|
|
3722
|
+
react.createElement(
|
|
3723
|
+
'input',
|
|
3724
|
+
Object.assign(
|
|
3725
|
+
{ style: INPUT_STYLE },
|
|
3726
|
+
{
|
|
3727
|
+
type: 'text',
|
|
3728
|
+
value:
|
|
3729
|
+
typeof enginePlatform.buildCommands === 'string'
|
|
3730
|
+
? enginePlatform.buildCommands
|
|
3731
|
+
: '',
|
|
3732
|
+
placeholder:
|
|
3733
|
+
engineEntry && engineEntry.defaultBuildCommand
|
|
3734
|
+
? engineEntry.defaultBuildCommand
|
|
3735
|
+
: '',
|
|
3736
|
+
onChange: (event) =>
|
|
3737
|
+
onPlatform(
|
|
3738
|
+
'buildCommands',
|
|
3739
|
+
event.target.value === '' ? undefined : event.target.value,
|
|
3740
|
+
),
|
|
3741
|
+
},
|
|
3742
|
+
),
|
|
3743
|
+
),
|
|
3744
|
+
),
|
|
3745
|
+
help ? renderHelpRow(react, t.buildCommandsHelp) : null,
|
|
3746
|
+
);
|
|
3747
|
+
}
|
|
3748
|
+
if (buildMode === 'docker') {
|
|
3749
|
+
return react.createElement(
|
|
3750
|
+
'div',
|
|
3751
|
+
null,
|
|
3752
|
+
react.createElement(
|
|
3753
|
+
'div',
|
|
3754
|
+
{ style: ROW_STYLE },
|
|
3755
|
+
react.createElement('label', { style: LABEL_STYLE }, t.dockerTag),
|
|
3756
|
+
react.createElement(
|
|
3757
|
+
'input',
|
|
3758
|
+
Object.assign(
|
|
3759
|
+
{ style: INPUT_STYLE },
|
|
3760
|
+
{
|
|
3761
|
+
type: 'text',
|
|
3762
|
+
value:
|
|
3763
|
+
typeof enginePlatform.dockerTag === 'string'
|
|
3764
|
+
? enginePlatform.dockerTag
|
|
3765
|
+
: '',
|
|
3766
|
+
placeholder:
|
|
3767
|
+
engineEntry && engineEntry.defaultDockerTag
|
|
3768
|
+
? engineEntry.defaultDockerTag
|
|
3769
|
+
: '',
|
|
3770
|
+
onChange: (event) =>
|
|
3771
|
+
onPlatform(
|
|
3772
|
+
'dockerTag',
|
|
3773
|
+
event.target.value === '' ? undefined : event.target.value,
|
|
3774
|
+
),
|
|
3775
|
+
},
|
|
3776
|
+
),
|
|
3777
|
+
),
|
|
3778
|
+
),
|
|
3779
|
+
help ? renderHelpRow(react, t.dockerTagHelp) : null,
|
|
3780
|
+
);
|
|
3781
|
+
}
|
|
3782
|
+
return null;
|
|
3783
|
+
})(),
|
|
3310
3784
|
);
|
|
3311
3785
|
})(),
|
|
3312
3786
|
hasFields
|
|
@@ -3315,17 +3789,28 @@ window.__ModuleLoader__.load({
|
|
|
3315
3789
|
'div',
|
|
3316
3790
|
{ key: section.group },
|
|
3317
3791
|
react.createElement('div', { style: SECTION_STYLE }, section.group),
|
|
3318
|
-
section.fields.map((field) =>
|
|
3319
|
-
|
|
3792
|
+
section.fields.map((field) => {
|
|
3793
|
+
// The field value binds to the local draft, not the server
|
|
3794
|
+
// render model, so a typed keystroke shows in the input
|
|
3795
|
+
// immediately. The server model (field.value) resolves the
|
|
3796
|
+
// spec default and only surfaces after the debounced
|
|
3797
|
+
// re-price settles. A key the user has not edited is
|
|
3798
|
+
// absent from the draft, so it falls back to the model.
|
|
3799
|
+
var draftValue = draft.config[field.key];
|
|
3800
|
+
var effectiveField =
|
|
3801
|
+
draftValue !== undefined
|
|
3802
|
+
? Object.assign({}, field, { value: draftValue })
|
|
3803
|
+
: field;
|
|
3804
|
+
return renderField(
|
|
3320
3805
|
react,
|
|
3321
|
-
|
|
3806
|
+
effectiveField,
|
|
3322
3807
|
onValue,
|
|
3323
3808
|
inlineWarnings[field.key],
|
|
3324
3809
|
primitives,
|
|
3325
3810
|
help,
|
|
3326
3811
|
t,
|
|
3327
|
-
)
|
|
3328
|
-
),
|
|
3812
|
+
);
|
|
3813
|
+
}),
|
|
3329
3814
|
),
|
|
3330
3815
|
)
|
|
3331
3816
|
: react.createElement('div', { style: { fontSize: 12, opacity: 0.7 } }, t.noParameters),
|
|
@@ -3401,6 +3886,7 @@ window.__ModuleLoader__.load({
|
|
|
3401
3886
|
onAllowRun: onAllowRun,
|
|
3402
3887
|
onRun: onRun,
|
|
3403
3888
|
onGet: onGet,
|
|
3889
|
+
getting: getting,
|
|
3404
3890
|
installing: installing,
|
|
3405
3891
|
copyNote: copyNote,
|
|
3406
3892
|
depsNote: depsNote,
|
|
@@ -3411,6 +3897,8 @@ window.__ModuleLoader__.load({
|
|
|
3411
3897
|
// read-only text area (a launch fills it; the card never edits it),
|
|
3412
3898
|
// and the log-file location the launch writes to. The location field
|
|
3413
3899
|
// edits the template the route resolves and writes on the next run.
|
|
3900
|
+
// While a Get is in flight the streamed buffer wins: the git sync,
|
|
3901
|
+
// dependency, and build output appends there as it lands.
|
|
3414
3902
|
react.createElement(
|
|
3415
3903
|
'div',
|
|
3416
3904
|
{ style: { marginTop: 8 } },
|
|
@@ -3428,7 +3916,7 @@ window.__ModuleLoader__.load({
|
|
|
3428
3916
|
color: 'inherit',
|
|
3429
3917
|
background: 'transparent',
|
|
3430
3918
|
},
|
|
3431
|
-
value: data.lastLogs || '',
|
|
3919
|
+
value: getLogs !== null ? getLogs : data.lastLogs || '',
|
|
3432
3920
|
readOnly: true,
|
|
3433
3921
|
'aria-label': t.logs,
|
|
3434
3922
|
}),
|
|
@@ -3446,10 +3934,14 @@ window.__ModuleLoader__.load({
|
|
|
3446
3934
|
// The engine lifecycle row: a single Launch/Stop control that
|
|
3447
3935
|
// toggles like the per-entry control. It reads as Launch while the
|
|
3448
3936
|
// draft's engine is down, Stop while a healthy launch set draftRunning,
|
|
3449
|
-
// and an in-flight
|
|
3937
|
+
// and an in-flight spinner (disabled) while a launch or stop is in
|
|
3450
3938
|
// flight. Launch stays disabled while the deps panel reports a
|
|
3451
|
-
// missing host dependency
|
|
3452
|
-
//
|
|
3939
|
+
// missing host dependency or a Get run is in flight, and re-enables
|
|
3940
|
+
// when the build finishes, errors, or falls silent for GET_IDLE_MS.
|
|
3941
|
+
// The Auto Rebuild checkbox sits after the control: checked, a
|
|
3942
|
+
// launch runs the git sync (fetch, rebase, rebuild) first;
|
|
3943
|
+
// unchecked, the launch starts the checkout as-is. The note reports
|
|
3944
|
+
// the answer the route returned.
|
|
3453
3945
|
react.createElement(
|
|
3454
3946
|
'div',
|
|
3455
3947
|
{ style: { display: 'flex', alignItems: 'center', gap: 8, marginTop: 8 } },
|
|
@@ -3484,10 +3976,19 @@ window.__ModuleLoader__.load({
|
|
|
3484
3976
|
'primary',
|
|
3485
3977
|
{
|
|
3486
3978
|
onClick: onLaunch,
|
|
3487
|
-
disabled:
|
|
3979
|
+
disabled:
|
|
3980
|
+
getting || (deps !== null && deps.allSatisfied !== true),
|
|
3488
3981
|
},
|
|
3489
3982
|
t.launch,
|
|
3490
3983
|
),
|
|
3984
|
+
// The Auto Rebuild checkbox: checked, a launch runs the git sync
|
|
3985
|
+
// (fetch, rebase, rebuild) before the spawn; unchecked, the
|
|
3986
|
+
// launch starts the checkout as-is.
|
|
3987
|
+
renderCheckbox(react, primitives, {
|
|
3988
|
+
checked: autoRebuild,
|
|
3989
|
+
onChange: (next) => autoRebuildState[1](next),
|
|
3990
|
+
label: t.autoRebuild,
|
|
3991
|
+
}),
|
|
3491
3992
|
),
|
|
3492
3993
|
lifecycleNote
|
|
3493
3994
|
? react.createElement('div', { style: { fontSize: 12 } }, lifecycleNote)
|