dsh-plugin-local-infer 0.1.1 → 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 +14 -3
- package/docs/zh/README.md +14 -3
- package/dsh/client.js +1214 -242
- package/package.json +16 -10
- package/packages/core/lib/adapter.d.ts +119 -6
- package/packages/core/lib/deps.d.ts +4 -3
- package/packages/core/lib/index.d.ts +2 -1
- package/packages/core/lib/index.js +1 -0
- 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/registrar.d.ts +5 -5
- package/packages/core/lib/registrar.js +3 -3
- 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 +127 -0
- package/packages/providers/lib/acquire.js +346 -0
- package/packages/providers/lib/adapters.d.ts +19 -172
- package/packages/providers/lib/adapters.js +114 -659
- package/packages/providers/lib/deps.d.ts +26 -2
- package/packages/providers/lib/deps.js +37 -4
- package/packages/providers/lib/engines/hyperqwenAdapter.d.ts +38 -0
- package/packages/providers/lib/engines/hyperqwenAdapter.js +105 -0
- package/packages/providers/lib/engines/infernix.d.ts +170 -0
- package/packages/providers/lib/engines/infernix.js +671 -0
- 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.d.ts → engines/ninfer.d.ts} +7 -0
- package/packages/providers/lib/{ninfer.js → engines/ninfer.js} +8 -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 -3
- package/packages/providers/lib/engines.js +16 -8
- package/packages/providers/lib/guidelines.js +16 -0
- package/packages/providers/lib/index.d.ts +21 -10
- package/packages/providers/lib/index.js +23 -9
- package/packages/providers/lib/model-defaults.d.ts +8 -5
- package/packages/providers/lib/model-defaults.js +47 -5
- package/packages/providers/lib/platform.d.ts +31 -0
- package/packages/providers/lib/platform.js +64 -0
- package/packages/providers/package.json +1 -1
- package/packages/service/lib/index.d.ts +74 -22
- package/packages/service/lib/index.js +354 -44
- package/packages/service/lib/types.d.ts +48 -5
- 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 +96 -357
- package/packages/ui/lib/route.js +123 -1415
- 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/{llamacpp.d.ts → engines/llamacpp.d.ts} +0 -0
- /package/packages/providers/lib/{llamacpp.js → engines/llamacpp.js} +0 -0
package/dsh/client.js
CHANGED
|
@@ -83,6 +83,8 @@ window.__ModuleLoader__.load({
|
|
|
83
83
|
run: 'Run',
|
|
84
84
|
running: 'running...',
|
|
85
85
|
get: 'Get',
|
|
86
|
+
getting: 'Getting...',
|
|
87
|
+
getFailed: 'get failed',
|
|
86
88
|
allowRun: 'Allow one-click install (sudo)',
|
|
87
89
|
depsSatisfied: 'All dependencies satisfied.',
|
|
88
90
|
installRejected: 'one-click install is not allowed; copy the install string instead',
|
|
@@ -94,6 +96,7 @@ window.__ModuleLoader__.load({
|
|
|
94
96
|
unregistered: 'removed',
|
|
95
97
|
unregisterFailed: 'remove failed',
|
|
96
98
|
launch: 'Launch',
|
|
99
|
+
autoRebuild: 'Auto Rebuild',
|
|
97
100
|
launching: 'Launching...',
|
|
98
101
|
launchFailed: 'launch failed',
|
|
99
102
|
runningOn: 'running on port',
|
|
@@ -106,6 +109,8 @@ window.__ModuleLoader__.load({
|
|
|
106
109
|
enginePlatform: 'Engine platform',
|
|
107
110
|
engineDirectory: 'Engine Directory',
|
|
108
111
|
engineFork: 'Engine Fork (Git Repo)',
|
|
112
|
+
buildCommands: 'Build commands',
|
|
113
|
+
dockerTag: 'Docker tag',
|
|
109
114
|
dirPlaceholder: 'directory path',
|
|
110
115
|
forkPlaceholder: 'git repo url',
|
|
111
116
|
hfCliDetected: 'HF CLI detected:',
|
|
@@ -115,6 +120,7 @@ window.__ModuleLoader__.load({
|
|
|
115
120
|
downloaded: 'download complete',
|
|
116
121
|
downloadFailed: 'download failed',
|
|
117
122
|
helpToggle: 'Show or hide field help',
|
|
123
|
+
refresh: 'Re-fetch the card data from the host',
|
|
118
124
|
engineHelp:
|
|
119
125
|
'Inference engines have different strengths and weaknesses for specific models and hardware.',
|
|
120
126
|
modelHelp:
|
|
@@ -123,8 +129,12 @@ window.__ModuleLoader__.load({
|
|
|
123
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.',
|
|
124
130
|
modelDirHelp: 'Where model files are downloaded and served from.',
|
|
125
131
|
engineDirHelp:
|
|
126
|
-
'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.',
|
|
127
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.',
|
|
128
138
|
providers: 'Providers',
|
|
129
139
|
newProvider: 'New provider',
|
|
130
140
|
edit: 'Edit',
|
|
@@ -139,9 +149,14 @@ window.__ModuleLoader__.load({
|
|
|
139
149
|
registerNeedsSave: 'Save the Provider configuration before registering with DSH.',
|
|
140
150
|
logs: 'Logs',
|
|
141
151
|
logLocation: 'Log location',
|
|
152
|
+
resize: 'resize',
|
|
142
153
|
entryLaunch: 'Launch',
|
|
143
154
|
entryStop: 'Stop',
|
|
144
155
|
entryLogs: 'Logs',
|
|
156
|
+
entryPrereqs: 'Prereqs...',
|
|
157
|
+
entryDownloading: 'Downloading...',
|
|
158
|
+
entryBuilding: 'Building...',
|
|
159
|
+
entryLaunching: 'Launching...',
|
|
145
160
|
chatTemplatePreset: 'Chat template preset',
|
|
146
161
|
customChatTemplate: 'Custom Chat template',
|
|
147
162
|
custom: 'Custom',
|
|
@@ -210,6 +225,8 @@ window.__ModuleLoader__.load({
|
|
|
210
225
|
run: '运行',
|
|
211
226
|
running: '运行中…',
|
|
212
227
|
get: '获取',
|
|
228
|
+
getting: '获取中…',
|
|
229
|
+
getFailed: '获取失败',
|
|
213
230
|
allowRun: '允许一键安装 (sudo)',
|
|
214
231
|
depsSatisfied: '所有依赖已满足。',
|
|
215
232
|
installRejected: '未允许一键安装;请复制安装命令。',
|
|
@@ -221,6 +238,7 @@ window.__ModuleLoader__.load({
|
|
|
221
238
|
unregistered: '已移除',
|
|
222
239
|
unregisterFailed: '移除失败',
|
|
223
240
|
launch: '启动',
|
|
241
|
+
autoRebuild: '自动重建',
|
|
224
242
|
launching: '启动中…',
|
|
225
243
|
launchFailed: '启动失败',
|
|
226
244
|
runningOn: '运行于端口',
|
|
@@ -233,6 +251,8 @@ window.__ModuleLoader__.load({
|
|
|
233
251
|
enginePlatform: '引擎平台',
|
|
234
252
|
engineDirectory: '引擎目录',
|
|
235
253
|
engineFork: '引擎 Fork (Git 仓库)',
|
|
254
|
+
buildCommands: '构建命令',
|
|
255
|
+
dockerTag: 'Docker 标签',
|
|
236
256
|
dirPlaceholder: '目录路径',
|
|
237
257
|
forkPlaceholder: 'git 仓库地址',
|
|
238
258
|
hfCliDetected: '检测到 HF CLI:',
|
|
@@ -242,13 +262,16 @@ window.__ModuleLoader__.load({
|
|
|
242
262
|
downloaded: '下载完成',
|
|
243
263
|
downloadFailed: '下载失败',
|
|
244
264
|
helpToggle: '显示或隐藏字段帮助',
|
|
265
|
+
refresh: '从主机重新加载卡片数据',
|
|
245
266
|
engineHelp: '推理引擎针对特定模型和硬件各有优劣。',
|
|
246
267
|
modelHelp: '引擎服务的模型。可选项包括引擎默认模型和模型目录中已有的模型文件。',
|
|
247
268
|
buildHelp:
|
|
248
269
|
'引擎的部署方式。local 在主机上从源码构建;docker 拉取预构建镜像。依赖计划和硬件准则按所选模式计价。',
|
|
249
270
|
modelDirHelp: '模型文件的下载和服务目录。',
|
|
250
|
-
engineDirHelp: '
|
|
271
|
+
engineDirHelp: '引擎源码的本地目录。“获取”会在此克隆或更新 git 仓库并构建引擎;未覆盖时,仓库落在模型目录下以引擎命名的子目录中。',
|
|
251
272
|
engineForkHelp: '用于克隆步骤、覆盖引擎默认上游的 git 仓库。',
|
|
273
|
+
buildCommandsHelp: '本地构建命令,覆盖引擎默认的检查构建。仅在本地构建模式下显示。',
|
|
274
|
+
dockerTagHelp: 'docker 部署拉取的镜像标签。仅在 docker 构建模式下显示。',
|
|
252
275
|
providers: '提供方',
|
|
253
276
|
newProvider: '新增提供方',
|
|
254
277
|
edit: '编辑',
|
|
@@ -262,9 +285,14 @@ window.__ModuleLoader__.load({
|
|
|
262
285
|
registerNeedsSave: '注册到 DSH 之前请先保存该提供方配置。',
|
|
263
286
|
logs: '日志',
|
|
264
287
|
logLocation: '日志位置',
|
|
288
|
+
resize: '调整大小',
|
|
265
289
|
entryLaunch: '启动',
|
|
266
290
|
entryStop: '停止',
|
|
267
291
|
entryLogs: '日志',
|
|
292
|
+
entryPrereqs: '前置条件…',
|
|
293
|
+
entryDownloading: '下载中…',
|
|
294
|
+
entryBuilding: '构建中…',
|
|
295
|
+
entryLaunching: '启动中…',
|
|
268
296
|
chatTemplatePreset: '聊天模板预设',
|
|
269
297
|
customChatTemplate: '自定义聊天模板',
|
|
270
298
|
custom: '自定义',
|
|
@@ -432,6 +460,127 @@ window.__ModuleLoader__.load({
|
|
|
432
460
|
color: '#fff',
|
|
433
461
|
border: 'none',
|
|
434
462
|
});
|
|
463
|
+
|
|
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
|
|
468
|
+
// failed (a red Logs). launchNextState is the hardened transition: a step
|
|
469
|
+
// event only advances forward through the rebuild phases (a stale or
|
|
470
|
+
// out-of-order step is a no-op), a healthy result settles to running, a
|
|
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.
|
|
481
|
+
var LAUNCH_STEP_ORDER = { prereqs: 0, download: 1, build: 2, launch: 3 };
|
|
482
|
+
var LAUNCH_PHASE_STATES = ['prereqs', 'download', 'build', 'launch'];
|
|
483
|
+
// The health-poll deadline: the route's poll runs up to two minutes, so a
|
|
484
|
+
// client-side deadline just past that cap forces a hung launch to the red
|
|
485
|
+
// Logs state. It arms only for the launch phase; the rebuild phases
|
|
486
|
+
// (prereqs, download, build) run as long as they need to, so a slow build
|
|
487
|
+
// never reads as a failure.
|
|
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;
|
|
498
|
+
|
|
499
|
+
function launchIsPhase(state) {
|
|
500
|
+
return LAUNCH_PHASE_STATES.indexOf(state) !== -1;
|
|
501
|
+
}
|
|
502
|
+
|
|
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;
|
|
506
|
+
var type = event.type;
|
|
507
|
+
var next;
|
|
508
|
+
var nextCause;
|
|
509
|
+
if (type === 'start') {
|
|
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') {
|
|
519
|
+
var target = event.step;
|
|
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;
|
|
580
|
+
}
|
|
581
|
+
return { status: next, cause: nextCause };
|
|
582
|
+
}
|
|
583
|
+
|
|
435
584
|
// The deps panel. The install block is a copy/paste target, so it gets a
|
|
436
585
|
// monospace pre with its own copy button; the dep rows read their status
|
|
437
586
|
// from a state dot (the DSH StateDot, or its glyph fallback), not a word,
|
|
@@ -476,10 +625,10 @@ window.__ModuleLoader__.load({
|
|
|
476
625
|
gap: 4,
|
|
477
626
|
justifyContent: 'flex-end',
|
|
478
627
|
};
|
|
479
|
-
// The circular
|
|
480
|
-
//
|
|
481
|
-
//
|
|
482
|
-
//
|
|
628
|
+
// The circular header buttons: the "?" help toggle and the refresh
|
|
629
|
+
// action. The active state reads as the accent fill; the inactive state
|
|
630
|
+
// as the muted outline. The help preference persists through the save
|
|
631
|
+
// route, so it survives a reload.
|
|
483
632
|
var HELP_BUTTON_STYLE = (active) => ({
|
|
484
633
|
width: 20,
|
|
485
634
|
height: 20,
|
|
@@ -818,36 +967,60 @@ window.__ModuleLoader__.load({
|
|
|
818
967
|
// a collapsed card with a status dot (green running, red stopped, yellow
|
|
819
968
|
// The per-entry Launch control: a card entry's own lifecycle button, shown
|
|
820
969
|
// alongside its Edit and Delete controls. Idle reads as a Launch button
|
|
821
|
-
// that starts the entry's engine; a
|
|
822
|
-
// control with the spinner
|
|
823
|
-
//
|
|
824
|
-
//
|
|
970
|
+
// that starts the entry's engine; a rebuild in flight shows a disabled
|
|
971
|
+
// control with the spinner and the phase label (Prereqs, Downloading,
|
|
972
|
+
// Building, Launching); a healthy engine reads as an enabled Stop; a launch
|
|
973
|
+
// that timed out reads as a red Logs control that opens the entry for
|
|
974
|
+
// editing, the same action as Edit.
|
|
825
975
|
function renderEntryLaunchButton(react, primitives, provider, opts) {
|
|
826
976
|
var state = (opts.launchState || {})[provider.id];
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
977
|
+
var status = state !== undefined ? state.status : 'idle';
|
|
978
|
+
// A rebuild phase in flight: a disabled control with the spinner and the
|
|
979
|
+
// phase label, so the button reads the step that is actually running.
|
|
980
|
+
var phaseLabels = {
|
|
981
|
+
prereqs: opts.prereqsLabel,
|
|
982
|
+
download: opts.downloadingLabel,
|
|
983
|
+
build: opts.buildingLabel,
|
|
984
|
+
launch: opts.launchingLabel,
|
|
985
|
+
};
|
|
986
|
+
var phaseLabel = phaseLabels[status];
|
|
987
|
+
if (phaseLabel !== undefined) {
|
|
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
|
+
});
|
|
842
1018
|
}
|
|
843
1019
|
// The Stop control reads from the provider status dot, not just the
|
|
844
1020
|
// per-entry launch state: the launch state is lost when the user
|
|
845
1021
|
// navigates away from the plugin page and back, so a running engine
|
|
846
1022
|
// (a green dot) must keep the control as Stop, not reset to Launch.
|
|
847
|
-
if (
|
|
848
|
-
(state !== undefined && state.status === 'running') ||
|
|
849
|
-
provider.status === 'running'
|
|
850
|
-
) {
|
|
1023
|
+
if (status === 'running' || provider.status === 'running') {
|
|
851
1024
|
return renderButton(
|
|
852
1025
|
react,
|
|
853
1026
|
primitives,
|
|
@@ -856,10 +1029,10 @@ window.__ModuleLoader__.load({
|
|
|
856
1029
|
opts.stopLabel,
|
|
857
1030
|
);
|
|
858
1031
|
}
|
|
859
|
-
if (
|
|
860
|
-
// A launch that timed out: a red Logs control that opens the
|
|
861
|
-
// editing and clears the failed state, so the control reads
|
|
862
|
-
// again and the user can fix the config and re-launch.
|
|
1032
|
+
if (status === 'failed') {
|
|
1033
|
+
// A launch that timed out or failed: a red Logs control that opens the
|
|
1034
|
+
// entry for editing and clears the failed state, so the control reads
|
|
1035
|
+
// as Launch again and the user can fix the config and re-launch.
|
|
863
1036
|
return react.createElement(
|
|
864
1037
|
'button',
|
|
865
1038
|
{
|
|
@@ -890,6 +1063,43 @@ window.__ModuleLoader__.load({
|
|
|
890
1063
|
);
|
|
891
1064
|
}
|
|
892
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
|
+
|
|
893
1103
|
// unsure), an "Engine: Model" title, and Edit/Delete controls. A New
|
|
894
1104
|
// provider button resets the form to a fresh entry. The list sits above
|
|
895
1105
|
// the editing form, so a provider's full config form stays the single
|
|
@@ -906,6 +1116,15 @@ window.__ModuleLoader__.load({
|
|
|
906
1116
|
((opts.engines || []).find(function (engine) {
|
|
907
1117
|
return engine.id === provider.engine;
|
|
908
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;
|
|
909
1128
|
var modelLabel =
|
|
910
1129
|
provider.model && provider.model !== '' ? provider.model : opts.modelAuto;
|
|
911
1130
|
return react.createElement(
|
|
@@ -934,21 +1153,21 @@ window.__ModuleLoader__.load({
|
|
|
934
1153
|
whiteSpace: 'nowrap',
|
|
935
1154
|
},
|
|
936
1155
|
title:
|
|
937
|
-
|
|
1156
|
+
titleLabel +
|
|
938
1157
|
': ' +
|
|
939
1158
|
modelLabel +
|
|
940
1159
|
' (' +
|
|
941
1160
|
(statusToLabel[provider.status] || provider.status) +
|
|
942
1161
|
')',
|
|
943
1162
|
'aria-label':
|
|
944
|
-
|
|
1163
|
+
titleLabel +
|
|
945
1164
|
': ' +
|
|
946
1165
|
modelLabel +
|
|
947
1166
|
' (' +
|
|
948
1167
|
(statusToLabel[provider.status] || provider.status) +
|
|
949
1168
|
')',
|
|
950
1169
|
},
|
|
951
|
-
|
|
1170
|
+
titleLabel +
|
|
952
1171
|
': ' +
|
|
953
1172
|
modelLabel +
|
|
954
1173
|
' (' +
|
|
@@ -1039,6 +1258,39 @@ window.__ModuleLoader__.load({
|
|
|
1039
1258
|
});
|
|
1040
1259
|
}
|
|
1041
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
|
+
|
|
1042
1294
|
// The chat-template control, split from the old finicky free-text combo
|
|
1043
1295
|
// into a preset dropdown (the shipped presets plus a "Custom" entry) and a
|
|
1044
1296
|
// "Custom Chat template" text field that shows while the preset is
|
|
@@ -1568,6 +1820,11 @@ window.__ModuleLoader__.load({
|
|
|
1568
1820
|
);
|
|
1569
1821
|
}
|
|
1570
1822
|
|
|
1823
|
+
// The Get run's output-idle cap. Two minutes without a streamed line
|
|
1824
|
+
// reads as a stalled build: the card re-enables Launch while the run
|
|
1825
|
+
// keeps streaming in the background.
|
|
1826
|
+
var GET_IDLE_MS = 120000;
|
|
1827
|
+
|
|
1571
1828
|
// The "System dependencies" panel: the per-dep status list, the copy/paste
|
|
1572
1829
|
// install block, the Play/Run arrow (only while the trust gate is on),
|
|
1573
1830
|
// and the "Get" gate that stays disabled until every dep reports
|
|
@@ -1684,21 +1941,22 @@ window.__ModuleLoader__.load({
|
|
|
1684
1941
|
label: t.allowRun,
|
|
1685
1942
|
}),
|
|
1686
1943
|
deps.runAllowed && !deps.allSatisfied
|
|
1687
|
-
?
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
)
|
|
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
|
+
})
|
|
1694
1951
|
: null,
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
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
|
+
}),
|
|
1702
1960
|
),
|
|
1703
1961
|
actions.depsNote
|
|
1704
1962
|
? react.createElement(
|
|
@@ -1766,12 +2024,26 @@ window.__ModuleLoader__.load({
|
|
|
1766
2024
|
// does.
|
|
1767
2025
|
var launchingState = react.useState(false);
|
|
1768
2026
|
var stoppingState = react.useState(false);
|
|
2027
|
+
// The Get step's state. Get runs the git sync, the dependency plan,
|
|
2028
|
+
// and the engine build on the host through /local-infer/get, and the
|
|
2029
|
+
// streamed output lands in getLogs (the Logs section shows it while
|
|
2030
|
+
// the run is in flight). Launch stays disabled while a Get is in
|
|
2031
|
+
// flight; the idle timer re-enables it once GET_IDLE_MS passes with
|
|
2032
|
+
// no output, so a silent build never parks the button.
|
|
2033
|
+
var gettingState = react.useState(false);
|
|
2034
|
+
var getLogsState = react.useState(null);
|
|
2035
|
+
var getIdleTimerRef = react.useRef(null);
|
|
1769
2036
|
// Whether the draft's engine is confirmed running, so the single
|
|
1770
2037
|
// Launch/Stop control reads as Stop while the engine is up and Launch
|
|
1771
2038
|
// while it is down, the same toggle the per-entry control does. A
|
|
1772
2039
|
// healthy launch sets it, a stop clears it.
|
|
1773
2040
|
var draftRunningState = react.useState(false);
|
|
1774
2041
|
var lifecycleNoteState = react.useState('');
|
|
2042
|
+
// The Auto Rebuild checkbox next to the form's Launch button. Checked
|
|
2043
|
+
// by default: a launch runs the git sync (fetch, rebase, rebuild)
|
|
2044
|
+
// before spawning the engine. Unchecked, the launch skips the sync and
|
|
2045
|
+
// starts the checkout as-is.
|
|
2046
|
+
var autoRebuildState = react.useState(true);
|
|
1775
2047
|
// The model download state. The Download button runs the hf CLI
|
|
1776
2048
|
// download through the /local-infer/download route, and the note
|
|
1777
2049
|
// reports the answer the route returned.
|
|
@@ -1815,11 +2087,39 @@ window.__ModuleLoader__.load({
|
|
|
1815
2087
|
// The log-file location the card's log-location field edits. Seeded
|
|
1816
2088
|
// from the summary's stored template on load; a Save persists it.
|
|
1817
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);
|
|
1818
2107
|
// The per-entry launch state: a map from provider id to the entry's
|
|
1819
|
-
//
|
|
1820
|
-
// launch
|
|
2108
|
+
// launch-state-machine state (the rebuild phases 'prereqs', 'download',
|
|
2109
|
+
// 'build', 'launch', plus 'running' and 'failed') and the start time a
|
|
2110
|
+
// launch began, so a card entry can show its phase label, Stop, or red
|
|
1821
2111
|
// Logs control. Absent entries read as idle (a plain Launch button).
|
|
1822
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 });
|
|
1823
2123
|
var data = dataState[0];
|
|
1824
2124
|
var draft = draftState[0];
|
|
1825
2125
|
var note = noteState[0];
|
|
@@ -1834,8 +2134,11 @@ window.__ModuleLoader__.load({
|
|
|
1834
2134
|
var agentDefault = agentDefaultState[0];
|
|
1835
2135
|
var launching = launchingState[0];
|
|
1836
2136
|
var stopping = stoppingState[0];
|
|
2137
|
+
var getting = gettingState[0];
|
|
2138
|
+
var getLogs = getLogsState[0];
|
|
1837
2139
|
var draftRunning = draftRunningState[0];
|
|
1838
2140
|
var lifecycleNote = lifecycleNoteState[0];
|
|
2141
|
+
var autoRebuild = autoRebuildState[0];
|
|
1839
2142
|
var downloading = downloadingState[0];
|
|
1840
2143
|
var downloadNote = downloadNoteState[0];
|
|
1841
2144
|
var templateDownloading = templateDownloadingState[0];
|
|
@@ -1847,7 +2150,36 @@ window.__ModuleLoader__.load({
|
|
|
1847
2150
|
var providerFormMode = providerFormModeState[0];
|
|
1848
2151
|
var cachedDraft = cachedDraftState[0];
|
|
1849
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 : '';
|
|
1850
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];
|
|
1851
2183
|
// The no-entries case forces the form into new-provider mode on
|
|
1852
2184
|
// render: with nothing stored, the form is where the first provider
|
|
1853
2185
|
// is entered, and the "New provider" control reads as plain text.
|
|
@@ -1856,6 +2188,77 @@ window.__ModuleLoader__.load({
|
|
|
1856
2188
|
? 'new'
|
|
1857
2189
|
: providerFormMode;
|
|
1858
2190
|
var timerRef = react.useRef(null);
|
|
2191
|
+
// The latest committed draft, mirrored into a ref so the debounced
|
|
2192
|
+
// re-price reads the value the user last typed, not a stale closure.
|
|
2193
|
+
// The effect runs after each render, so the ref settles before the
|
|
2194
|
+
// 300ms timer fires.
|
|
2195
|
+
var draftRef = react.useRef(null);
|
|
2196
|
+
react.useEffect(() => {
|
|
2197
|
+
draftRef.current = draft;
|
|
2198
|
+
}, [draft]);
|
|
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
|
+
);
|
|
1859
2262
|
|
|
1860
2263
|
var fetchSummary = react.useCallback(
|
|
1861
2264
|
(engine, config) => {
|
|
@@ -1874,6 +2277,17 @@ window.__ModuleLoader__.load({
|
|
|
1874
2277
|
)
|
|
1875
2278
|
.then((next) => {
|
|
1876
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;
|
|
2287
|
+
// The streamed Get buffer yields to the persisted last-run
|
|
2288
|
+
// logs the summary now carries, so the Logs section keeps
|
|
2289
|
+
// the run's tail without a second copy.
|
|
2290
|
+
getLogsState[1](null);
|
|
1877
2291
|
draftState[1]((current) => {
|
|
1878
2292
|
// The summary's platformConfigs is the host-persisted
|
|
1879
2293
|
// platform state, while the draft's platform entries are
|
|
@@ -1914,6 +2328,31 @@ window.__ModuleLoader__.load({
|
|
|
1914
2328
|
[t],
|
|
1915
2329
|
);
|
|
1916
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
|
+
|
|
1917
2356
|
// The deps panel reads its own route, because it re-probes the host
|
|
1918
2357
|
// on every fetch (the "Get" gate must read the live checkDeps
|
|
1919
2358
|
// answer), while the summary re-prices the meter. Both ride the same
|
|
@@ -1945,6 +2384,25 @@ window.__ModuleLoader__.load({
|
|
|
1945
2384
|
[],
|
|
1946
2385
|
);
|
|
1947
2386
|
|
|
2387
|
+
// scheduleReprice resets the re-price timeout on every field change
|
|
2388
|
+
// and fetches the summary (the VRAM Est. meter) and the deps panel
|
|
2389
|
+
// only after typing pauses, so a fast keystroke stream is one round
|
|
2390
|
+
// trip, not one per letter. The ref reads the latest committed draft,
|
|
2391
|
+
// so the fetch prices the value the user last typed.
|
|
2392
|
+
var scheduleReprice = react.useCallback(() => {
|
|
2393
|
+
if (timerRef.current) {
|
|
2394
|
+
clearTimeout(timerRef.current);
|
|
2395
|
+
}
|
|
2396
|
+
timerRef.current = setTimeout(() => {
|
|
2397
|
+
var latest = draftRef.current;
|
|
2398
|
+
if (latest === null || latest === undefined) {
|
|
2399
|
+
return;
|
|
2400
|
+
}
|
|
2401
|
+
fetchSummary(latest.engine, latest.config);
|
|
2402
|
+
fetchDeps(latest.engine, latest.config);
|
|
2403
|
+
}, 300);
|
|
2404
|
+
}, [fetchSummary, fetchDeps]);
|
|
2405
|
+
|
|
1948
2406
|
var load = react.useCallback(() => {
|
|
1949
2407
|
fetchSummary(null, null);
|
|
1950
2408
|
fetchDeps(null, null);
|
|
@@ -1956,9 +2414,11 @@ window.__ModuleLoader__.load({
|
|
|
1956
2414
|
}
|
|
1957
2415
|
}, [data, draft, load]);
|
|
1958
2416
|
|
|
1959
|
-
// A field edit updates the draft config and
|
|
1960
|
-
//
|
|
1961
|
-
//
|
|
2417
|
+
// A field edit updates the draft config and schedules a debounced
|
|
2418
|
+
// re-price of the meter and form gating. The draft update is pure;
|
|
2419
|
+
// the re-price (the VRAM Est. visualization) runs only after typing
|
|
2420
|
+
// pauses, so a fast keystroke stream is one round trip, not one per
|
|
2421
|
+
// letter.
|
|
1962
2422
|
var onValue = react.useCallback(
|
|
1963
2423
|
(key, value) => {
|
|
1964
2424
|
noteState[1]('');
|
|
@@ -1968,22 +2428,15 @@ window.__ModuleLoader__.load({
|
|
|
1968
2428
|
draftState[1]((current) => {
|
|
1969
2429
|
var config = Object.assign({}, current.config);
|
|
1970
2430
|
config[key] = value;
|
|
1971
|
-
|
|
2431
|
+
return {
|
|
1972
2432
|
engine: current.engine,
|
|
1973
2433
|
config: config,
|
|
1974
2434
|
platform: current.platform || {},
|
|
1975
2435
|
};
|
|
1976
|
-
if (timerRef.current) {
|
|
1977
|
-
clearTimeout(timerRef.current);
|
|
1978
|
-
}
|
|
1979
|
-
timerRef.current = setTimeout(() => {
|
|
1980
|
-
fetchSummary(next.engine, next.config);
|
|
1981
|
-
fetchDeps(next.engine, next.config);
|
|
1982
|
-
}, 300);
|
|
1983
|
-
return next;
|
|
1984
2436
|
});
|
|
2437
|
+
scheduleReprice();
|
|
1985
2438
|
},
|
|
1986
|
-
[
|
|
2439
|
+
[scheduleReprice],
|
|
1987
2440
|
);
|
|
1988
2441
|
|
|
1989
2442
|
// The Model Directory field edit. A valid directory (non-blank, no
|
|
@@ -2023,6 +2476,9 @@ window.__ModuleLoader__.load({
|
|
|
2023
2476
|
}
|
|
2024
2477
|
downloadingState[1](true);
|
|
2025
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);
|
|
2026
2482
|
fetch('/local-infer/download', {
|
|
2027
2483
|
method: 'POST',
|
|
2028
2484
|
headers: { 'content-type': 'application/json' },
|
|
@@ -2054,7 +2510,7 @@ window.__ModuleLoader__.load({
|
|
|
2054
2510
|
downloadingState[1](false);
|
|
2055
2511
|
});
|
|
2056
2512
|
},
|
|
2057
|
-
[draft, downloading, t, fetchSummary],
|
|
2513
|
+
[draft, downloading, t, fetchSummary, startProcessLog],
|
|
2058
2514
|
);
|
|
2059
2515
|
|
|
2060
2516
|
// The chat-template download: pulls the selected template (the
|
|
@@ -2141,7 +2597,9 @@ window.__ModuleLoader__.load({
|
|
|
2141
2597
|
// The help toggle: flip the local state and persist the preference
|
|
2142
2598
|
// through the save route, so it survives a reload. The save response
|
|
2143
2599
|
// carries the fresh summary, so the data and the draft resync from
|
|
2144
|
-
// the same round trip, the way the allow-run toggle does.
|
|
2600
|
+
// the same round trip, the way the allow-run toggle does. The
|
|
2601
|
+
// settings-only flag keeps the route from upserting a provider entry,
|
|
2602
|
+
// so a toggle never mints a duplicate at the top of the list.
|
|
2145
2603
|
var onToggleHelp = react.useCallback(
|
|
2146
2604
|
(next) => {
|
|
2147
2605
|
if (draft === null) {
|
|
@@ -2157,6 +2615,7 @@ window.__ModuleLoader__.load({
|
|
|
2157
2615
|
allowRunDeps: deps !== null && deps.runAllowed === true,
|
|
2158
2616
|
platform: draft.platform || {},
|
|
2159
2617
|
showHelp: next,
|
|
2618
|
+
settingsOnly: true,
|
|
2160
2619
|
}),
|
|
2161
2620
|
})
|
|
2162
2621
|
.then((response) =>
|
|
@@ -2265,18 +2724,11 @@ window.__ModuleLoader__.load({
|
|
|
2265
2724
|
draftState[1]((_current) => {
|
|
2266
2725
|
// The platform config is per-engine, so an engine switch keeps
|
|
2267
2726
|
// the map and only resets the per-engine parameter config.
|
|
2268
|
-
|
|
2269
|
-
if (timerRef.current) {
|
|
2270
|
-
clearTimeout(timerRef.current);
|
|
2271
|
-
}
|
|
2272
|
-
timerRef.current = setTimeout(() => {
|
|
2273
|
-
fetchSummary(next.engine, next.config);
|
|
2274
|
-
fetchDeps(next.engine, next.config);
|
|
2275
|
-
}, 300);
|
|
2276
|
-
return next;
|
|
2727
|
+
return { engine: engine, config: {}, platform: _current.platform || {} };
|
|
2277
2728
|
});
|
|
2729
|
+
scheduleReprice();
|
|
2278
2730
|
},
|
|
2279
|
-
[
|
|
2731
|
+
[scheduleReprice],
|
|
2280
2732
|
);
|
|
2281
2733
|
|
|
2282
2734
|
var save = react.useCallback(() => {
|
|
@@ -2492,6 +2944,9 @@ window.__ModuleLoader__.load({
|
|
|
2492
2944
|
}
|
|
2493
2945
|
}, [deps, t]);
|
|
2494
2946
|
|
|
2947
|
+
// The allow-run toggle persists the trust gate through the save route.
|
|
2948
|
+
// The settings-only flag keeps the route from upserting a provider
|
|
2949
|
+
// entry, so a toggle never mints a duplicate at the top of the list.
|
|
2495
2950
|
var onAllowRun = react.useCallback(
|
|
2496
2951
|
(checked) => {
|
|
2497
2952
|
if (draft === null) {
|
|
@@ -2506,6 +2961,7 @@ window.__ModuleLoader__.load({
|
|
|
2506
2961
|
allowRunDeps: checked,
|
|
2507
2962
|
platform: draft.platform || {},
|
|
2508
2963
|
showHelp: help,
|
|
2964
|
+
settingsOnly: true,
|
|
2509
2965
|
}),
|
|
2510
2966
|
})
|
|
2511
2967
|
.then((response) =>
|
|
@@ -2571,15 +3027,97 @@ window.__ModuleLoader__.load({
|
|
|
2571
3027
|
});
|
|
2572
3028
|
}, [deps, draft, installing, t]);
|
|
2573
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
|
+
|
|
2574
3047
|
var onGet = react.useCallback(() => {
|
|
2575
|
-
if (deps === null || draft === null) {
|
|
3048
|
+
if (deps === null || draft === null || getting) {
|
|
2576
3049
|
return;
|
|
2577
3050
|
}
|
|
2578
|
-
// "Get"
|
|
2579
|
-
//
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
3051
|
+
// "Get" runs the git sync, the dependency plan, and the build on
|
|
3052
|
+
// the host through /local-infer/get. The route streams its output;
|
|
3053
|
+
// each chunk appends to the Logs section and re-arms the idle
|
|
3054
|
+
// timer, and Launch stays disabled until the run finishes, fails,
|
|
3055
|
+
// or falls silent for GET_IDLE_MS. The click writes the "Getting..."
|
|
3056
|
+
// separator, so the run reads as a fresh Logs section.
|
|
3057
|
+
gettingState[1](true);
|
|
3058
|
+
depsNoteState[1]('');
|
|
3059
|
+
startProcessLog(t.getting);
|
|
3060
|
+
var append = (text) => {
|
|
3061
|
+
getLogsState[1]((current) => (current === null ? '' : current) + text);
|
|
3062
|
+
};
|
|
3063
|
+
var armIdle = () => {
|
|
3064
|
+
if (getIdleTimerRef.current) {
|
|
3065
|
+
clearTimeout(getIdleTimerRef.current);
|
|
3066
|
+
}
|
|
3067
|
+
// Two minutes without a line reads as a stalled build: release
|
|
3068
|
+
// Launch while the run keeps streaming in the background.
|
|
3069
|
+
getIdleTimerRef.current = setTimeout(() => {
|
|
3070
|
+
gettingState[1](false);
|
|
3071
|
+
}, GET_IDLE_MS);
|
|
3072
|
+
};
|
|
3073
|
+
armIdle();
|
|
3074
|
+
var platform = (draft.platform || {})[draft.engine];
|
|
3075
|
+
fetch('/local-infer/get', {
|
|
3076
|
+
method: 'POST',
|
|
3077
|
+
headers: { 'content-type': 'application/json' },
|
|
3078
|
+
body: JSON.stringify({
|
|
3079
|
+
engine: draft.engine,
|
|
3080
|
+
config: draft.config,
|
|
3081
|
+
platform: platform,
|
|
3082
|
+
}),
|
|
3083
|
+
})
|
|
3084
|
+
.then((response) => {
|
|
3085
|
+
if (!response.ok) {
|
|
3086
|
+
return response.text().then((text) => {
|
|
3087
|
+
throw new Error(text || 'HTTP ' + response.status);
|
|
3088
|
+
});
|
|
3089
|
+
}
|
|
3090
|
+
if (!response.body || typeof response.body.getReader !== 'function') {
|
|
3091
|
+
// A host whose response cannot stream: read the whole body.
|
|
3092
|
+
return response.text().then((text) => append(text));
|
|
3093
|
+
}
|
|
3094
|
+
var reader = response.body.getReader();
|
|
3095
|
+
var decoder = new TextDecoder();
|
|
3096
|
+
var pump = () =>
|
|
3097
|
+
reader.read().then(({ done, value }) => {
|
|
3098
|
+
if (done) {
|
|
3099
|
+
return undefined;
|
|
3100
|
+
}
|
|
3101
|
+
append(decoder.decode(value, { stream: true }));
|
|
3102
|
+
armIdle();
|
|
3103
|
+
return pump();
|
|
3104
|
+
});
|
|
3105
|
+
return pump();
|
|
3106
|
+
})
|
|
3107
|
+
.catch((error) => {
|
|
3108
|
+
append('\n' + t.getFailed + ': ' + (error && error.message ? error.message : '') + '\n');
|
|
3109
|
+
})
|
|
3110
|
+
.then(() => {
|
|
3111
|
+
if (getIdleTimerRef.current) {
|
|
3112
|
+
clearTimeout(getIdleTimerRef.current);
|
|
3113
|
+
getIdleTimerRef.current = null;
|
|
3114
|
+
}
|
|
3115
|
+
gettingState[1](false);
|
|
3116
|
+
// The route persisted the run as the last logs; the summary
|
|
3117
|
+
// re-read hands the Logs section back to the stored value.
|
|
3118
|
+
fetchSummary(draft.engine, draft.config);
|
|
3119
|
+
});
|
|
3120
|
+
}, [deps, draft, getting, data, t, fetchSummary, startProcessLog]);
|
|
2583
3121
|
|
|
2584
3122
|
var onRegister = react.useCallback(() => {
|
|
2585
3123
|
if (draft === null || registering) {
|
|
@@ -2678,6 +3216,10 @@ window.__ModuleLoader__.load({
|
|
|
2678
3216
|
engine: draft.engine,
|
|
2679
3217
|
config: draft.config,
|
|
2680
3218
|
platform: platform,
|
|
3219
|
+
// The Auto Rebuild checkbox: false skips the git sync (fetch,
|
|
3220
|
+
// rebase, rebuild) and launches the checkout as-is. The stop
|
|
3221
|
+
// route ignores the key.
|
|
3222
|
+
autoRebuild: autoRebuild,
|
|
2681
3223
|
};
|
|
2682
3224
|
// The provider entry the draft edits: the route persists the launch
|
|
2683
3225
|
// record (and the stop's clear) on that entry, so a restart re-links
|
|
@@ -2694,14 +3236,56 @@ window.__ModuleLoader__.load({
|
|
|
2694
3236
|
}
|
|
2695
3237
|
launchingState[1](true);
|
|
2696
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);
|
|
2697
3242
|
fetch('/local-infer/launch', {
|
|
2698
3243
|
method: 'POST',
|
|
2699
3244
|
headers: { 'content-type': 'application/json' },
|
|
2700
3245
|
body: launchBody(),
|
|
2701
3246
|
})
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
3247
|
+
// The launch route streams NDJSON (a step event per phase, then a
|
|
3248
|
+
// final result event). The main form's control does not show the
|
|
3249
|
+
// phase labels, so read the stream and keep only the result event.
|
|
3250
|
+
.then(function (response) {
|
|
3251
|
+
if (response.body === null || typeof response.body.getReader !== 'function') {
|
|
3252
|
+
return response.json().then(function (body) {
|
|
3253
|
+
return { ok: response.ok, result: body };
|
|
3254
|
+
});
|
|
3255
|
+
}
|
|
3256
|
+
var reader = response.body.getReader();
|
|
3257
|
+
var decoder = new TextDecoder();
|
|
3258
|
+
var buffer = '';
|
|
3259
|
+
var resultEvent = null;
|
|
3260
|
+
function pump() {
|
|
3261
|
+
return reader.read().then(function (chunk) {
|
|
3262
|
+
if (chunk.done) {
|
|
3263
|
+
return { ok: response.ok, result: resultEvent };
|
|
3264
|
+
}
|
|
3265
|
+
buffer += decoder.decode(chunk.value, { stream: true });
|
|
3266
|
+
var lines = buffer.split('\n');
|
|
3267
|
+
buffer = lines.pop();
|
|
3268
|
+
for (var i = 0; i < lines.length; i++) {
|
|
3269
|
+
if (lines[i] === '') {
|
|
3270
|
+
continue;
|
|
3271
|
+
}
|
|
3272
|
+
var event;
|
|
3273
|
+
try {
|
|
3274
|
+
event = JSON.parse(lines[i]);
|
|
3275
|
+
} catch {
|
|
3276
|
+
continue;
|
|
3277
|
+
}
|
|
3278
|
+
if (event.result !== undefined) {
|
|
3279
|
+
resultEvent = event.result;
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
return pump();
|
|
3283
|
+
});
|
|
3284
|
+
}
|
|
3285
|
+
return pump();
|
|
3286
|
+
})
|
|
3287
|
+
.then(({ ok, result: body }) => {
|
|
3288
|
+
if (ok && body !== null) {
|
|
2705
3289
|
if (body.healthy) {
|
|
2706
3290
|
lifecycleNoteState[1](
|
|
2707
3291
|
t.runningOn + ' ' + (body.plan && body.plan.port ? body.plan.port : ''),
|
|
@@ -2713,7 +3297,9 @@ window.__ModuleLoader__.load({
|
|
|
2713
3297
|
lifecycleNoteState[1](t.notHealthy + (body.detail ? ': ' + body.detail : ''));
|
|
2714
3298
|
}
|
|
2715
3299
|
} else {
|
|
2716
|
-
lifecycleNoteState[1](
|
|
3300
|
+
lifecycleNoteState[1](
|
|
3301
|
+
t.launchFailed + (body && body.detail ? ': ' + body.detail : ''),
|
|
3302
|
+
);
|
|
2717
3303
|
}
|
|
2718
3304
|
})
|
|
2719
3305
|
.catch((error) => {
|
|
@@ -2723,8 +3309,13 @@ window.__ModuleLoader__.load({
|
|
|
2723
3309
|
})
|
|
2724
3310
|
.then(() => {
|
|
2725
3311
|
launchingState[1](false);
|
|
3312
|
+
// The route persisted the run's launch command line and engine
|
|
3313
|
+
// output as the last logs; the summary re-read hands the Logs
|
|
3314
|
+
// section the new run, the same rule the per-entry launch and
|
|
3315
|
+
// the Get apply when their run ends.
|
|
3316
|
+
fetchSummary(draft.engine, draft.config);
|
|
2726
3317
|
});
|
|
2727
|
-
}, [draft, launching, editingProviderId, t]);
|
|
3318
|
+
}, [draft, launching, editingProviderId, autoRebuild, t, fetchSummary, startProcessLog]);
|
|
2728
3319
|
|
|
2729
3320
|
var onStop = react.useCallback(() => {
|
|
2730
3321
|
if (draft === null || stopping) {
|
|
@@ -2732,6 +3323,15 @@ window.__ModuleLoader__.load({
|
|
|
2732
3323
|
}
|
|
2733
3324
|
stoppingState[1](true);
|
|
2734
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
|
+
}
|
|
2735
3335
|
fetch('/local-infer/stop', {
|
|
2736
3336
|
method: 'POST',
|
|
2737
3337
|
headers: { 'content-type': 'application/json' },
|
|
@@ -2755,43 +3355,79 @@ window.__ModuleLoader__.load({
|
|
|
2755
3355
|
})
|
|
2756
3356
|
.then(() => {
|
|
2757
3357
|
stoppingState[1](false);
|
|
3358
|
+
if (stopTimersRef.current.draftPoll !== null) {
|
|
3359
|
+
clearInterval(stopTimersRef.current.draftPoll);
|
|
3360
|
+
stopTimersRef.current.draftPoll = null;
|
|
3361
|
+
}
|
|
2758
3362
|
});
|
|
2759
3363
|
}, [draft, stopping, editingProviderId, t]);
|
|
2760
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
|
+
|
|
2761
3380
|
// The per-entry launch: a card entry's own Launch control drives its
|
|
2762
|
-
// own engine, not the form's draft. It
|
|
2763
|
-
//
|
|
2764
|
-
//
|
|
2765
|
-
//
|
|
2766
|
-
//
|
|
3381
|
+
// own engine, not the form's draft. It starts the launch state machine
|
|
3382
|
+
// at the prereqs phase, then reads the launch stream (an NDJSON step
|
|
3383
|
+
// event as each phase begins, then a final result event) and drives the
|
|
3384
|
+
// machine: each step event advances the phase label (Prereqs,
|
|
3385
|
+
// Downloading, Building, Launching), a healthy result settles to
|
|
3386
|
+
// running (the control becomes Stop), and a failed result or the
|
|
3387
|
+
// health-poll deadline settles to failed (the control becomes a red
|
|
3388
|
+
// Logs that opens the entry for editing).
|
|
2767
3389
|
var onLaunchEntry = react.useCallback(
|
|
2768
3390
|
(provider) => {
|
|
2769
|
-
var
|
|
2770
|
-
|
|
3391
|
+
var prior = entryLaunch[provider.id];
|
|
3392
|
+
var status = prior !== undefined ? prior.status : 'idle';
|
|
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.
|
|
3396
|
+
if (status !== 'idle' && status !== 'failed') {
|
|
2771
3397
|
return;
|
|
2772
3398
|
}
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
)
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
//
|
|
2781
|
-
// deadline just past
|
|
2782
|
-
// Logs state
|
|
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);
|
|
3403
|
+
var move = function (event) {
|
|
3404
|
+
moveEntry(provider.id, event);
|
|
3405
|
+
};
|
|
3406
|
+
// The health-poll deadline: the route's poll runs up to two minutes,
|
|
3407
|
+
// so a client-side deadline just past that cap forces a hung launch
|
|
3408
|
+
// to the red Logs state. It arms only for the launch phase; the
|
|
3409
|
+
// rebuild phases (prereqs, download, build) run as long as they
|
|
3410
|
+
// need to, so a slow build never reads as a failure.
|
|
2783
3411
|
var settled = false;
|
|
2784
|
-
var deadline =
|
|
2785
|
-
|
|
3412
|
+
var deadline = null;
|
|
3413
|
+
var armDeadline = function () {
|
|
3414
|
+
if (deadline !== null) {
|
|
2786
3415
|
return;
|
|
2787
3416
|
}
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
3417
|
+
deadline = setTimeout(function () {
|
|
3418
|
+
if (settled) {
|
|
3419
|
+
return;
|
|
3420
|
+
}
|
|
3421
|
+
settled = true;
|
|
3422
|
+
move({ type: 'failed' });
|
|
3423
|
+
}, LAUNCH_HEALTH_DEADLINE_MS);
|
|
3424
|
+
};
|
|
3425
|
+
var clearDeadline = function () {
|
|
3426
|
+
if (deadline !== null) {
|
|
3427
|
+
clearTimeout(deadline);
|
|
3428
|
+
deadline = null;
|
|
3429
|
+
}
|
|
3430
|
+
};
|
|
2795
3431
|
fetch('/local-infer/launch', {
|
|
2796
3432
|
method: 'POST',
|
|
2797
3433
|
headers: { 'content-type': 'application/json' },
|
|
@@ -2804,36 +3440,157 @@ window.__ModuleLoader__.load({
|
|
|
2804
3440
|
providerId: provider.id,
|
|
2805
3441
|
}),
|
|
2806
3442
|
})
|
|
2807
|
-
.then((response)
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
})
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
3443
|
+
.then(function (response) {
|
|
3444
|
+
// The launch streams NDJSON: a step event as each phase begins,
|
|
3445
|
+
// then a final result event. Read the stream and drive the
|
|
3446
|
+
// state machine; a response with no body (a host without
|
|
3447
|
+
// streaming) settles straight to failed.
|
|
3448
|
+
if (response.body === null || typeof response.body.getReader !== 'function') {
|
|
3449
|
+
settled = true;
|
|
3450
|
+
clearDeadline();
|
|
3451
|
+
move({ type: 'failed' });
|
|
3452
|
+
return Promise.resolve();
|
|
3453
|
+
}
|
|
3454
|
+
var reader = response.body.getReader();
|
|
3455
|
+
var decoder = new TextDecoder();
|
|
3456
|
+
var buffer = '';
|
|
3457
|
+
function pump() {
|
|
3458
|
+
return reader.read().then(function (result) {
|
|
3459
|
+
if (result.done) {
|
|
3460
|
+
// The stream ended without a result event (a host that
|
|
3461
|
+
// cannot stream, a route that broke mid-stream): settle
|
|
3462
|
+
// to failed so the control does not sit on a phase label.
|
|
3463
|
+
if (!settled) {
|
|
3464
|
+
settled = true;
|
|
3465
|
+
clearDeadline();
|
|
3466
|
+
move({ type: 'failed' });
|
|
3467
|
+
}
|
|
3468
|
+
return;
|
|
3469
|
+
}
|
|
3470
|
+
buffer += decoder.decode(result.value, { stream: true });
|
|
3471
|
+
var lines = buffer.split('\n');
|
|
3472
|
+
buffer = lines.pop();
|
|
3473
|
+
for (var i = 0; i < lines.length; i++) {
|
|
3474
|
+
var line = lines[i];
|
|
3475
|
+
if (line === '') {
|
|
3476
|
+
continue;
|
|
3477
|
+
}
|
|
3478
|
+
var event;
|
|
3479
|
+
try {
|
|
3480
|
+
event = JSON.parse(line);
|
|
3481
|
+
} catch {
|
|
3482
|
+
// A malformed line is not a launch event; skip it.
|
|
3483
|
+
continue;
|
|
3484
|
+
}
|
|
3485
|
+
if (event.step !== undefined) {
|
|
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
|
+
}
|
|
3497
|
+
// Arm the deadline only for the launch (health-poll)
|
|
3498
|
+
// step; the rebuild steps run without a timeout.
|
|
3499
|
+
if (event.step === 'launch') {
|
|
3500
|
+
armDeadline();
|
|
3501
|
+
}
|
|
3502
|
+
} else if (event.result !== undefined) {
|
|
3503
|
+
settled = true;
|
|
3504
|
+
clearDeadline();
|
|
3505
|
+
var healthy = event.result.healthy === true;
|
|
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
|
+
}
|
|
3517
|
+
// The provider status dots price the process probe on a
|
|
3518
|
+
// summary fetch, so a launch that turned an engine
|
|
3519
|
+
// healthy re-fetches the summary to repaint the dot
|
|
3520
|
+
// from red to green.
|
|
3521
|
+
fetchSummary(provider.engine, provider.config || {});
|
|
3522
|
+
return;
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
return pump();
|
|
3526
|
+
});
|
|
3527
|
+
}
|
|
3528
|
+
return pump();
|
|
2821
3529
|
})
|
|
2822
|
-
.catch(()
|
|
3530
|
+
.catch(function () {
|
|
2823
3531
|
settled = true;
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
Object.assign({}, current, {
|
|
2827
|
-
[provider.id]: { status: 'failed', startTime: Date.now() },
|
|
2828
|
-
}),
|
|
2829
|
-
);
|
|
3532
|
+
clearDeadline();
|
|
3533
|
+
move({ type: 'failed' });
|
|
2830
3534
|
});
|
|
2831
3535
|
},
|
|
2832
|
-
[entryLaunch, fetchSummary],
|
|
3536
|
+
[entryLaunch, fetchSummary, startProcessLog, moveEntry, getLogsState, t],
|
|
2833
3537
|
);
|
|
2834
3538
|
|
|
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.
|
|
2835
3550
|
var onStopEntry = react.useCallback(
|
|
2836
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);
|
|
2837
3594
|
fetch('/local-infer/stop', {
|
|
2838
3595
|
method: 'POST',
|
|
2839
3596
|
headers: { 'content-type': 'application/json' },
|
|
@@ -2846,32 +3603,32 @@ window.__ModuleLoader__.load({
|
|
|
2846
3603
|
providerId: provider.id,
|
|
2847
3604
|
}),
|
|
2848
3605
|
})
|
|
2849
|
-
.then(() => {
|
|
2850
|
-
entryLaunchState[1]((current) => {
|
|
2851
|
-
var next = Object.assign({}, current);
|
|
2852
|
-
delete next[provider.id];
|
|
2853
|
-
return next;
|
|
2854
|
-
});
|
|
2855
|
-
})
|
|
2856
3606
|
.catch(() => {
|
|
2857
|
-
/* 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 */
|
|
2858
3609
|
});
|
|
2859
3610
|
},
|
|
2860
|
-
[],
|
|
3611
|
+
[entryLaunch, moveEntry],
|
|
2861
3612
|
);
|
|
2862
3613
|
|
|
2863
3614
|
// The per-entry Logs control: opening the logs loads the entry for
|
|
2864
|
-
// editing AND
|
|
2865
|
-
// reads as Launch again. The user fixes the config in the form
|
|
2866
|
-
// re-launches from the same entry, instead of being stuck on a
|
|
3615
|
+
// editing AND resets the launch state machine to idle, so the per-entry
|
|
3616
|
+
// control reads as Launch again. The user fixes the config in the form
|
|
3617
|
+
// and re-launches from the same entry, instead of being stuck on a
|
|
2867
3618
|
// non-actionable Logs control.
|
|
2868
3619
|
var onLogsEntry = react.useCallback(
|
|
2869
3620
|
(provider) => {
|
|
2870
3621
|
onEditProvider(provider);
|
|
2871
3622
|
entryLaunchState[1]((current) => {
|
|
2872
|
-
var
|
|
2873
|
-
|
|
2874
|
-
|
|
3623
|
+
var entry = current[provider.id] || { status: 'idle' };
|
|
3624
|
+
var next = launchNextState(entry, { type: 'reset' });
|
|
3625
|
+
var map = Object.assign({}, current);
|
|
3626
|
+
if (next.status === 'idle') {
|
|
3627
|
+
delete map[provider.id];
|
|
3628
|
+
} else {
|
|
3629
|
+
map[provider.id] = Object.assign({}, next, { startTime: Date.now() });
|
|
3630
|
+
}
|
|
3631
|
+
return map;
|
|
2875
3632
|
});
|
|
2876
3633
|
},
|
|
2877
3634
|
[onEditProvider],
|
|
@@ -2956,9 +3713,14 @@ window.__ModuleLoader__.load({
|
|
|
2956
3713
|
: null,
|
|
2957
3714
|
)
|
|
2958
3715
|
: null,
|
|
2959
|
-
// The card header: the section title plus the circular
|
|
2960
|
-
//
|
|
2961
|
-
//
|
|
3716
|
+
// The card header: the section title plus the circular refresh and
|
|
3717
|
+
// "?" buttons. The refresh button re-fetches the summary and deps
|
|
3718
|
+
// from the host, so the card re-prices after the harness pauses
|
|
3719
|
+
// the plugin's routes and re-activates them. The refresh glyph is
|
|
3720
|
+
// the primitives seed's IconRefreshOutlineRegular; a host without
|
|
3721
|
+
// the seed keeps the native arrow character. The "?" button shows
|
|
3722
|
+
// or hides the per-field descriptions; the preference persists
|
|
3723
|
+
// through the save route, so it survives a reload.
|
|
2962
3724
|
react.createElement(
|
|
2963
3725
|
'div',
|
|
2964
3726
|
{
|
|
@@ -2971,16 +3733,39 @@ window.__ModuleLoader__.load({
|
|
|
2971
3733
|
},
|
|
2972
3734
|
react.createElement('div', { style: { fontWeight: 600, fontSize: 14 } }, t.section),
|
|
2973
3735
|
react.createElement(
|
|
2974
|
-
'
|
|
3736
|
+
'div',
|
|
2975
3737
|
{
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
onClick: () => onToggleHelp(!help),
|
|
3738
|
+
style: {
|
|
3739
|
+
display: 'flex',
|
|
3740
|
+
alignItems: 'center',
|
|
3741
|
+
gap: 8,
|
|
3742
|
+
},
|
|
2982
3743
|
},
|
|
2983
|
-
|
|
3744
|
+
react.createElement(
|
|
3745
|
+
'button',
|
|
3746
|
+
{
|
|
3747
|
+
type: 'button',
|
|
3748
|
+
style: HELP_BUTTON_STYLE(false),
|
|
3749
|
+
title: t.refresh,
|
|
3750
|
+
'aria-label': t.refresh,
|
|
3751
|
+
onClick: () => load(),
|
|
3752
|
+
},
|
|
3753
|
+
primitives && typeof primitives.IconRefreshOutlineRegular === 'function'
|
|
3754
|
+
? react.createElement(primitives.IconRefreshOutlineRegular, { size: 12 })
|
|
3755
|
+
: '↻',
|
|
3756
|
+
),
|
|
3757
|
+
react.createElement(
|
|
3758
|
+
'button',
|
|
3759
|
+
{
|
|
3760
|
+
type: 'button',
|
|
3761
|
+
style: HELP_BUTTON_STYLE(help),
|
|
3762
|
+
title: t.helpToggle,
|
|
3763
|
+
'aria-label': t.helpToggle,
|
|
3764
|
+
'aria-pressed': help,
|
|
3765
|
+
onClick: () => onToggleHelp(!help),
|
|
3766
|
+
},
|
|
3767
|
+
'?',
|
|
3768
|
+
),
|
|
2984
3769
|
),
|
|
2985
3770
|
),
|
|
2986
3771
|
// The provider list: the saved provider entries, each a collapsed
|
|
@@ -3010,8 +3795,12 @@ window.__ModuleLoader__.load({
|
|
|
3010
3795
|
onLogs: onLogsEntry,
|
|
3011
3796
|
launchLabel: t.entryLaunch,
|
|
3012
3797
|
stopLabel: t.entryStop,
|
|
3798
|
+
stoppingLabel: t.stopping,
|
|
3013
3799
|
logsLabel: t.entryLogs,
|
|
3014
|
-
launchingLabel: t.
|
|
3800
|
+
launchingLabel: t.entryLaunching,
|
|
3801
|
+
prereqsLabel: t.entryPrereqs,
|
|
3802
|
+
downloadingLabel: t.entryDownloading,
|
|
3803
|
+
buildingLabel: t.entryBuilding,
|
|
3015
3804
|
})
|
|
3016
3805
|
: null,
|
|
3017
3806
|
// The provider form: the Engine/Model config the card prices. It is
|
|
@@ -3176,13 +3965,13 @@ window.__ModuleLoader__.load({
|
|
|
3176
3965
|
},
|
|
3177
3966
|
t.hfCliDetected + ' ' + status.downloadCommand,
|
|
3178
3967
|
),
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
),
|
|
3968
|
+
renderProcessButton(react, primitives, {
|
|
3969
|
+
busy: downloading === true,
|
|
3970
|
+
label: t.download,
|
|
3971
|
+
busyLabel: t.downloading,
|
|
3972
|
+
variant: 'ghost',
|
|
3973
|
+
onClick: onDownload,
|
|
3974
|
+
}),
|
|
3186
3975
|
);
|
|
3187
3976
|
}
|
|
3188
3977
|
return react.createElement(
|
|
@@ -3235,18 +4024,15 @@ window.__ModuleLoader__.load({
|
|
|
3235
4024
|
(status.hfCli ? t.hfCliDetected : t.noHfCli) + ' ' + status.downloadCommand,
|
|
3236
4025
|
),
|
|
3237
4026
|
status.hfCli
|
|
3238
|
-
?
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
},
|
|
3246
|
-
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);
|
|
3247
4034
|
},
|
|
3248
|
-
|
|
3249
|
-
)
|
|
4035
|
+
})
|
|
3250
4036
|
: null,
|
|
3251
4037
|
),
|
|
3252
4038
|
templateDownloadNote !== ''
|
|
@@ -3314,6 +4100,94 @@ window.__ModuleLoader__.load({
|
|
|
3314
4100
|
),
|
|
3315
4101
|
),
|
|
3316
4102
|
help ? renderHelpRow(react, t.engineForkHelp) : null,
|
|
4103
|
+
(() => {
|
|
4104
|
+
// The build-method-dependent platform fields: local build mode
|
|
4105
|
+
// shows the Build commands override, docker build mode shows the
|
|
4106
|
+
// Docker tag override. Only one is visible at a time, keyed off
|
|
4107
|
+
// the same buildMode the Build row prices. The placeholder seeds
|
|
4108
|
+
// the engine's own default (the build step the deploy runs, or the
|
|
4109
|
+
// image tag the docker deploy pulls), so an empty field reads as
|
|
4110
|
+
// "use the default."
|
|
4111
|
+
var engineEntry = (data.engines || []).find(
|
|
4112
|
+
(entry) => entry.id === draft.engine,
|
|
4113
|
+
);
|
|
4114
|
+
var buildModes = [];
|
|
4115
|
+
if (engineEntry && engineEntry.supports.local) buildModes.push('local');
|
|
4116
|
+
if (engineEntry && engineEntry.supports.docker) buildModes.push('docker');
|
|
4117
|
+
var buildMode =
|
|
4118
|
+
typeof draft.config.buildMode === 'string'
|
|
4119
|
+
? draft.config.buildMode
|
|
4120
|
+
: buildModes[0] || 'local';
|
|
4121
|
+
if (buildMode === 'local') {
|
|
4122
|
+
return react.createElement(
|
|
4123
|
+
'div',
|
|
4124
|
+
null,
|
|
4125
|
+
react.createElement(
|
|
4126
|
+
'div',
|
|
4127
|
+
{ style: ROW_STYLE },
|
|
4128
|
+
react.createElement('label', { style: LABEL_STYLE }, t.buildCommands),
|
|
4129
|
+
react.createElement(
|
|
4130
|
+
'input',
|
|
4131
|
+
Object.assign(
|
|
4132
|
+
{ style: INPUT_STYLE },
|
|
4133
|
+
{
|
|
4134
|
+
type: 'text',
|
|
4135
|
+
value:
|
|
4136
|
+
typeof enginePlatform.buildCommands === 'string'
|
|
4137
|
+
? enginePlatform.buildCommands
|
|
4138
|
+
: '',
|
|
4139
|
+
placeholder:
|
|
4140
|
+
engineEntry && engineEntry.defaultBuildCommand
|
|
4141
|
+
? engineEntry.defaultBuildCommand
|
|
4142
|
+
: '',
|
|
4143
|
+
onChange: (event) =>
|
|
4144
|
+
onPlatform(
|
|
4145
|
+
'buildCommands',
|
|
4146
|
+
event.target.value === '' ? undefined : event.target.value,
|
|
4147
|
+
),
|
|
4148
|
+
},
|
|
4149
|
+
),
|
|
4150
|
+
),
|
|
4151
|
+
),
|
|
4152
|
+
help ? renderHelpRow(react, t.buildCommandsHelp) : null,
|
|
4153
|
+
);
|
|
4154
|
+
}
|
|
4155
|
+
if (buildMode === 'docker') {
|
|
4156
|
+
return react.createElement(
|
|
4157
|
+
'div',
|
|
4158
|
+
null,
|
|
4159
|
+
react.createElement(
|
|
4160
|
+
'div',
|
|
4161
|
+
{ style: ROW_STYLE },
|
|
4162
|
+
react.createElement('label', { style: LABEL_STYLE }, t.dockerTag),
|
|
4163
|
+
react.createElement(
|
|
4164
|
+
'input',
|
|
4165
|
+
Object.assign(
|
|
4166
|
+
{ style: INPUT_STYLE },
|
|
4167
|
+
{
|
|
4168
|
+
type: 'text',
|
|
4169
|
+
value:
|
|
4170
|
+
typeof enginePlatform.dockerTag === 'string'
|
|
4171
|
+
? enginePlatform.dockerTag
|
|
4172
|
+
: '',
|
|
4173
|
+
placeholder:
|
|
4174
|
+
engineEntry && engineEntry.defaultDockerTag
|
|
4175
|
+
? engineEntry.defaultDockerTag
|
|
4176
|
+
: '',
|
|
4177
|
+
onChange: (event) =>
|
|
4178
|
+
onPlatform(
|
|
4179
|
+
'dockerTag',
|
|
4180
|
+
event.target.value === '' ? undefined : event.target.value,
|
|
4181
|
+
),
|
|
4182
|
+
},
|
|
4183
|
+
),
|
|
4184
|
+
),
|
|
4185
|
+
),
|
|
4186
|
+
help ? renderHelpRow(react, t.dockerTagHelp) : null,
|
|
4187
|
+
);
|
|
4188
|
+
}
|
|
4189
|
+
return null;
|
|
4190
|
+
})(),
|
|
3317
4191
|
);
|
|
3318
4192
|
})(),
|
|
3319
4193
|
hasFields
|
|
@@ -3322,17 +4196,28 @@ window.__ModuleLoader__.load({
|
|
|
3322
4196
|
'div',
|
|
3323
4197
|
{ key: section.group },
|
|
3324
4198
|
react.createElement('div', { style: SECTION_STYLE }, section.group),
|
|
3325
|
-
section.fields.map((field) =>
|
|
3326
|
-
|
|
4199
|
+
section.fields.map((field) => {
|
|
4200
|
+
// The field value binds to the local draft, not the server
|
|
4201
|
+
// render model, so a typed keystroke shows in the input
|
|
4202
|
+
// immediately. The server model (field.value) resolves the
|
|
4203
|
+
// spec default and only surfaces after the debounced
|
|
4204
|
+
// re-price settles. A key the user has not edited is
|
|
4205
|
+
// absent from the draft, so it falls back to the model.
|
|
4206
|
+
var draftValue = draft.config[field.key];
|
|
4207
|
+
var effectiveField =
|
|
4208
|
+
draftValue !== undefined
|
|
4209
|
+
? Object.assign({}, field, { value: draftValue })
|
|
4210
|
+
: field;
|
|
4211
|
+
return renderField(
|
|
3327
4212
|
react,
|
|
3328
|
-
|
|
4213
|
+
effectiveField,
|
|
3329
4214
|
onValue,
|
|
3330
4215
|
inlineWarnings[field.key],
|
|
3331
4216
|
primitives,
|
|
3332
4217
|
help,
|
|
3333
4218
|
t,
|
|
3334
|
-
)
|
|
3335
|
-
),
|
|
4219
|
+
);
|
|
4220
|
+
}),
|
|
3336
4221
|
),
|
|
3337
4222
|
)
|
|
3338
4223
|
: react.createElement('div', { style: { fontSize: 12, opacity: 0.7 } }, t.noParameters),
|
|
@@ -3408,37 +4293,92 @@ window.__ModuleLoader__.load({
|
|
|
3408
4293
|
onAllowRun: onAllowRun,
|
|
3409
4294
|
onRun: onRun,
|
|
3410
4295
|
onGet: onGet,
|
|
4296
|
+
getting: getting,
|
|
3411
4297
|
installing: installing,
|
|
3412
4298
|
copyNote: copyNote,
|
|
3413
4299
|
depsNote: depsNote,
|
|
3414
4300
|
},
|
|
3415
4301
|
primitives,
|
|
3416
4302
|
),
|
|
3417
|
-
// The Logs section: the last run's captured output in a
|
|
3418
|
-
//
|
|
3419
|
-
//
|
|
3420
|
-
//
|
|
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,
|
|
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.
|
|
3421
4316
|
react.createElement(
|
|
3422
4317
|
'div',
|
|
3423
4318
|
{ style: { marginTop: 8 } },
|
|
3424
4319
|
react.createElement('div', { style: LABEL_STYLE }, t.logs),
|
|
3425
|
-
react.createElement(
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
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
|
+
),
|
|
3442
4382
|
react.createElement(
|
|
3443
4383
|
'div',
|
|
3444
4384
|
{ style: ROW_STYLE },
|
|
@@ -3455,46 +4395,78 @@ window.__ModuleLoader__.load({
|
|
|
3455
4395
|
// draft's engine is down, Stop while a healthy launch set draftRunning,
|
|
3456
4396
|
// and an in-flight spinner (disabled) while a launch or stop is in
|
|
3457
4397
|
// flight. Launch stays disabled while the deps panel reports a
|
|
3458
|
-
// missing host dependency
|
|
3459
|
-
//
|
|
4398
|
+
// missing host dependency or a Get run is in flight, and re-enables
|
|
4399
|
+
// when the build finishes, errors, or falls silent for GET_IDLE_MS.
|
|
4400
|
+
// The Auto Rebuild checkbox sits after the control: checked, a
|
|
4401
|
+
// launch runs the git sync (fetch, rebase, rebuild) first;
|
|
4402
|
+
// unchecked, the launch starts the checkout as-is. The note reports
|
|
4403
|
+
// the answer the route returned.
|
|
3460
4404
|
react.createElement(
|
|
3461
4405
|
'div',
|
|
3462
4406
|
{ style: { display: 'flex', alignItems: 'center', gap: 8, marginTop: 8 } },
|
|
3463
|
-
|
|
3464
|
-
? react
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
react,
|
|
3481
|
-
primitives,
|
|
3482
|
-
'ghost',
|
|
3483
|
-
{ onClick: function () {}, disabled: true },
|
|
3484
|
-
t.stopping,
|
|
3485
|
-
)
|
|
3486
|
-
: draftRunning
|
|
3487
|
-
? renderButton(react, primitives, 'ghost', { onClick: onStop }, t.stop)
|
|
3488
|
-
: renderButton(
|
|
3489
|
-
react,
|
|
3490
|
-
primitives,
|
|
3491
|
-
'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',
|
|
3492
4424
|
{
|
|
3493
|
-
|
|
3494
|
-
|
|
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,
|
|
3495
4434
|
},
|
|
3496
|
-
t.
|
|
3497
|
-
)
|
|
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
|
+
}),
|
|
4462
|
+
// The Auto Rebuild checkbox: checked, a launch runs the git sync
|
|
4463
|
+
// (fetch, rebase, rebuild) before the spawn; unchecked, the
|
|
4464
|
+
// launch starts the checkout as-is.
|
|
4465
|
+
renderCheckbox(react, primitives, {
|
|
4466
|
+
checked: autoRebuild,
|
|
4467
|
+
onChange: (next) => autoRebuildState[1](next),
|
|
4468
|
+
label: t.autoRebuild,
|
|
4469
|
+
}),
|
|
3498
4470
|
),
|
|
3499
4471
|
lifecycleNote
|
|
3500
4472
|
? react.createElement('div', { style: { fontSize: 12 } }, lifecycleNote)
|