dsh-plugin-local-infer 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/docs/zh/README.md +4 -1
- package/dsh/client.js +627 -133
- package/package.json +11 -10
- package/packages/core/lib/adapter.d.ts +75 -4
- 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/status.d.ts +9 -1
- package/packages/core/lib/status.js +6 -1
- package/packages/providers/lib/acquire.d.ts +125 -0
- package/packages/providers/lib/acquire.js +325 -0
- package/packages/providers/lib/adapters.d.ts +42 -5
- package/packages/providers/lib/adapters.js +385 -57
- package/packages/providers/lib/deps.d.ts +4 -2
- package/packages/providers/lib/deps.js +16 -2
- package/packages/providers/lib/engines.d.ts +4 -3
- package/packages/providers/lib/engines.js +11 -3
- package/packages/providers/lib/guidelines.js +16 -0
- package/packages/providers/lib/index.d.ts +9 -3
- package/packages/providers/lib/index.js +8 -4
- package/packages/providers/lib/infernix.d.ts +170 -0
- package/packages/providers/lib/infernix.js +671 -0
- package/packages/providers/lib/model-defaults.d.ts +5 -3
- package/packages/providers/lib/model-defaults.js +27 -3
- package/packages/providers/lib/ninfer.d.ts +7 -0
- package/packages/providers/lib/ninfer.js +9 -2
- package/packages/providers/lib/platform.d.ts +31 -0
- package/packages/providers/lib/platform.js +58 -0
- package/packages/service/lib/index.d.ts +61 -18
- package/packages/service/lib/index.js +278 -42
- package/packages/service/lib/types.d.ts +44 -2
- package/packages/ui/lib/route.d.ts +83 -12
- package/packages/ui/lib/route.js +495 -55
- package/packages/ui/package.json +1 -1
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',
|
|
@@ -142,6 +152,10 @@ window.__ModuleLoader__.load({
|
|
|
142
152
|
entryLaunch: 'Launch',
|
|
143
153
|
entryStop: 'Stop',
|
|
144
154
|
entryLogs: 'Logs',
|
|
155
|
+
entryPrereqs: 'Prereqs...',
|
|
156
|
+
entryDownloading: 'Downloading...',
|
|
157
|
+
entryBuilding: 'Building...',
|
|
158
|
+
entryLaunching: 'Launching...',
|
|
145
159
|
chatTemplatePreset: 'Chat template preset',
|
|
146
160
|
customChatTemplate: 'Custom Chat template',
|
|
147
161
|
custom: 'Custom',
|
|
@@ -210,6 +224,8 @@ window.__ModuleLoader__.load({
|
|
|
210
224
|
run: '运行',
|
|
211
225
|
running: '运行中…',
|
|
212
226
|
get: '获取',
|
|
227
|
+
getting: '获取中…',
|
|
228
|
+
getFailed: '获取失败',
|
|
213
229
|
allowRun: '允许一键安装 (sudo)',
|
|
214
230
|
depsSatisfied: '所有依赖已满足。',
|
|
215
231
|
installRejected: '未允许一键安装;请复制安装命令。',
|
|
@@ -221,6 +237,7 @@ window.__ModuleLoader__.load({
|
|
|
221
237
|
unregistered: '已移除',
|
|
222
238
|
unregisterFailed: '移除失败',
|
|
223
239
|
launch: '启动',
|
|
240
|
+
autoRebuild: '自动重建',
|
|
224
241
|
launching: '启动中…',
|
|
225
242
|
launchFailed: '启动失败',
|
|
226
243
|
runningOn: '运行于端口',
|
|
@@ -233,6 +250,8 @@ window.__ModuleLoader__.load({
|
|
|
233
250
|
enginePlatform: '引擎平台',
|
|
234
251
|
engineDirectory: '引擎目录',
|
|
235
252
|
engineFork: '引擎 Fork (Git 仓库)',
|
|
253
|
+
buildCommands: '构建命令',
|
|
254
|
+
dockerTag: 'Docker 标签',
|
|
236
255
|
dirPlaceholder: '目录路径',
|
|
237
256
|
forkPlaceholder: 'git 仓库地址',
|
|
238
257
|
hfCliDetected: '检测到 HF CLI:',
|
|
@@ -242,13 +261,16 @@ window.__ModuleLoader__.load({
|
|
|
242
261
|
downloaded: '下载完成',
|
|
243
262
|
downloadFailed: '下载失败',
|
|
244
263
|
helpToggle: '显示或隐藏字段帮助',
|
|
264
|
+
refresh: '从主机重新加载卡片数据',
|
|
245
265
|
engineHelp: '推理引擎针对特定模型和硬件各有优劣。',
|
|
246
266
|
modelHelp: '引擎服务的模型。可选项包括引擎默认模型和模型目录中已有的模型文件。',
|
|
247
267
|
buildHelp:
|
|
248
268
|
'引擎的部署方式。local 在主机上从源码构建;docker 拉取预构建镜像。依赖计划和硬件准则按所选模式计价。',
|
|
249
269
|
modelDirHelp: '模型文件的下载和服务目录。',
|
|
250
|
-
engineDirHelp: '
|
|
270
|
+
engineDirHelp: '引擎源码的本地目录。“获取”会在此克隆或更新 git 仓库并构建引擎;未覆盖时,仓库落在模型目录下以引擎命名的子目录中。',
|
|
251
271
|
engineForkHelp: '用于克隆步骤、覆盖引擎默认上游的 git 仓库。',
|
|
272
|
+
buildCommandsHelp: '本地构建命令,覆盖引擎默认的检查构建。仅在本地构建模式下显示。',
|
|
273
|
+
dockerTagHelp: 'docker 部署拉取的镜像标签。仅在 docker 构建模式下显示。',
|
|
252
274
|
providers: '提供方',
|
|
253
275
|
newProvider: '新增提供方',
|
|
254
276
|
edit: '编辑',
|
|
@@ -265,6 +287,10 @@ window.__ModuleLoader__.load({
|
|
|
265
287
|
entryLaunch: '启动',
|
|
266
288
|
entryStop: '停止',
|
|
267
289
|
entryLogs: '日志',
|
|
290
|
+
entryPrereqs: '前置条件…',
|
|
291
|
+
entryDownloading: '下载中…',
|
|
292
|
+
entryBuilding: '构建中…',
|
|
293
|
+
entryLaunching: '启动中…',
|
|
268
294
|
chatTemplatePreset: '聊天模板预设',
|
|
269
295
|
customChatTemplate: '自定义聊天模板',
|
|
270
296
|
custom: '自定义',
|
|
@@ -432,6 +458,61 @@ window.__ModuleLoader__.load({
|
|
|
432
458
|
color: '#fff',
|
|
433
459
|
border: 'none',
|
|
434
460
|
});
|
|
461
|
+
|
|
462
|
+
// The per-entry launch state machine: the card's Launch control lifecycle.
|
|
463
|
+
// The states are the button's reads: idle (a plain Launch), the four
|
|
464
|
+
// rebuild phases (prereqs, download, build, launch), running (a Stop), and
|
|
465
|
+
// failed (a red Logs). launchNextState is the hardened transition: a step
|
|
466
|
+
// event only advances forward through the rebuild phases (a stale or
|
|
467
|
+
// out-of-order step is a no-op), a healthy result settles to running, a
|
|
468
|
+
// failed result settles to failed, and a stop or a break (the Logs control,
|
|
469
|
+
// a navigation away) resets to idle. An event that names no legal next
|
|
470
|
+
// state leaves the control where it is, so a late event never corrupts it.
|
|
471
|
+
var LAUNCH_STEP_ORDER = { prereqs: 0, download: 1, build: 2, launch: 3 };
|
|
472
|
+
var LAUNCH_PHASE_STATES = ['prereqs', 'download', 'build', 'launch'];
|
|
473
|
+
// The health-poll deadline: the route's poll runs up to two minutes, so a
|
|
474
|
+
// client-side deadline just past that cap forces a hung launch to the red
|
|
475
|
+
// Logs state. It arms only for the launch phase; the rebuild phases
|
|
476
|
+
// (prereqs, download, build) run as long as they need to, so a slow build
|
|
477
|
+
// never reads as a failure.
|
|
478
|
+
var LAUNCH_HEALTH_DEADLINE_MS = 150000;
|
|
479
|
+
|
|
480
|
+
function launchIsPhase(state) {
|
|
481
|
+
return LAUNCH_PHASE_STATES.indexOf(state) !== -1;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function launchNextState(current, event) {
|
|
485
|
+
var type = event.type;
|
|
486
|
+
if (type === 'start') {
|
|
487
|
+
// A launch begins from idle (a plain Launch) or failed (a re-launch
|
|
488
|
+
// after the Logs control cleared the failed state).
|
|
489
|
+
return current === 'idle' || current === 'failed' ? 'prereqs' : current;
|
|
490
|
+
}
|
|
491
|
+
if (type === 'step') {
|
|
492
|
+
var target = event.step;
|
|
493
|
+
if (LAUNCH_STEP_ORDER[target] === undefined) return current;
|
|
494
|
+
if (current === 'idle' || current === 'failed') return target;
|
|
495
|
+
if (current === 'running') return current;
|
|
496
|
+
var currentOrder = LAUNCH_STEP_ORDER[current];
|
|
497
|
+
if (currentOrder === undefined) return current;
|
|
498
|
+
return LAUNCH_STEP_ORDER[target] >= currentOrder ? target : current;
|
|
499
|
+
}
|
|
500
|
+
if (type === 'healthy') {
|
|
501
|
+
return launchIsPhase(current) ? 'running' : current;
|
|
502
|
+
}
|
|
503
|
+
if (type === 'failed') {
|
|
504
|
+
return launchIsPhase(current) ? 'failed' : current;
|
|
505
|
+
}
|
|
506
|
+
if (type === 'stop') {
|
|
507
|
+
// A stop settles a running engine (or a launch in flight) back to idle.
|
|
508
|
+
return current === 'running' || launchIsPhase(current) ? 'idle' : current;
|
|
509
|
+
}
|
|
510
|
+
if (type === 'reset') {
|
|
511
|
+
return 'idle';
|
|
512
|
+
}
|
|
513
|
+
return current;
|
|
514
|
+
}
|
|
515
|
+
|
|
435
516
|
// The deps panel. The install block is a copy/paste target, so it gets a
|
|
436
517
|
// monospace pre with its own copy button; the dep rows read their status
|
|
437
518
|
// from a state dot (the DSH StateDot, or its glyph fallback), not a word,
|
|
@@ -476,10 +557,10 @@ window.__ModuleLoader__.load({
|
|
|
476
557
|
gap: 4,
|
|
477
558
|
justifyContent: 'flex-end',
|
|
478
559
|
};
|
|
479
|
-
// The circular
|
|
480
|
-
//
|
|
481
|
-
//
|
|
482
|
-
//
|
|
560
|
+
// The circular header buttons: the "?" help toggle and the refresh
|
|
561
|
+
// action. The active state reads as the accent fill; the inactive state
|
|
562
|
+
// as the muted outline. The help preference persists through the save
|
|
563
|
+
// route, so it survives a reload.
|
|
483
564
|
var HELP_BUTTON_STYLE = (active) => ({
|
|
484
565
|
width: 20,
|
|
485
566
|
height: 20,
|
|
@@ -818,13 +899,24 @@ window.__ModuleLoader__.load({
|
|
|
818
899
|
// a collapsed card with a status dot (green running, red stopped, yellow
|
|
819
900
|
// The per-entry Launch control: a card entry's own lifecycle button, shown
|
|
820
901
|
// 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
|
-
//
|
|
902
|
+
// that starts the entry's engine; a rebuild in flight shows a disabled
|
|
903
|
+
// control with the spinner and the phase label (Prereqs, Downloading,
|
|
904
|
+
// Building, Launching); a healthy engine reads as an enabled Stop; a launch
|
|
905
|
+
// that timed out reads as a red Logs control that opens the entry for
|
|
906
|
+
// editing, the same action as Edit.
|
|
825
907
|
function renderEntryLaunchButton(react, primitives, provider, opts) {
|
|
826
908
|
var state = (opts.launchState || {})[provider.id];
|
|
827
|
-
|
|
909
|
+
var status = state !== undefined ? state.status : 'idle';
|
|
910
|
+
// A rebuild phase in flight: a disabled control with the spinner and the
|
|
911
|
+
// phase label, so the button reads the step that is actually running.
|
|
912
|
+
var phaseLabels = {
|
|
913
|
+
prereqs: opts.prereqsLabel,
|
|
914
|
+
download: opts.downloadingLabel,
|
|
915
|
+
build: opts.buildingLabel,
|
|
916
|
+
launch: opts.launchingLabel,
|
|
917
|
+
};
|
|
918
|
+
var phaseLabel = phaseLabels[status];
|
|
919
|
+
if (phaseLabel !== undefined) {
|
|
828
920
|
return react.createElement(
|
|
829
921
|
'button',
|
|
830
922
|
{
|
|
@@ -834,20 +926,17 @@ window.__ModuleLoader__.load({
|
|
|
834
926
|
{ display: 'inline-flex', alignItems: 'center', gap: 4, opacity: 0.7 },
|
|
835
927
|
GHOST_BUTTON,
|
|
836
928
|
),
|
|
837
|
-
'aria-label':
|
|
929
|
+
'aria-label': phaseLabel,
|
|
838
930
|
},
|
|
839
931
|
renderThrobber(react, primitives),
|
|
840
|
-
react.createElement('span', null,
|
|
932
|
+
react.createElement('span', null, phaseLabel),
|
|
841
933
|
);
|
|
842
934
|
}
|
|
843
935
|
// The Stop control reads from the provider status dot, not just the
|
|
844
936
|
// per-entry launch state: the launch state is lost when the user
|
|
845
937
|
// navigates away from the plugin page and back, so a running engine
|
|
846
938
|
// (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
|
-
) {
|
|
939
|
+
if (status === 'running' || provider.status === 'running') {
|
|
851
940
|
return renderButton(
|
|
852
941
|
react,
|
|
853
942
|
primitives,
|
|
@@ -856,10 +945,10 @@ window.__ModuleLoader__.load({
|
|
|
856
945
|
opts.stopLabel,
|
|
857
946
|
);
|
|
858
947
|
}
|
|
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.
|
|
948
|
+
if (status === 'failed') {
|
|
949
|
+
// A launch that timed out or failed: a red Logs control that opens the
|
|
950
|
+
// entry for editing and clears the failed state, so the control reads
|
|
951
|
+
// as Launch again and the user can fix the config and re-launch.
|
|
863
952
|
return react.createElement(
|
|
864
953
|
'button',
|
|
865
954
|
{
|
|
@@ -1568,6 +1657,11 @@ window.__ModuleLoader__.load({
|
|
|
1568
1657
|
);
|
|
1569
1658
|
}
|
|
1570
1659
|
|
|
1660
|
+
// The Get run's output-idle cap. Two minutes without a streamed line
|
|
1661
|
+
// reads as a stalled build: the card re-enables Launch while the run
|
|
1662
|
+
// keeps streaming in the background.
|
|
1663
|
+
var GET_IDLE_MS = 120000;
|
|
1664
|
+
|
|
1571
1665
|
// The "System dependencies" panel: the per-dep status list, the copy/paste
|
|
1572
1666
|
// install block, the Play/Run arrow (only while the trust gate is on),
|
|
1573
1667
|
// and the "Get" gate that stays disabled until every dep reports
|
|
@@ -1696,8 +1790,11 @@ window.__ModuleLoader__.load({
|
|
|
1696
1790
|
react,
|
|
1697
1791
|
primitives,
|
|
1698
1792
|
'primary',
|
|
1699
|
-
{
|
|
1700
|
-
|
|
1793
|
+
{
|
|
1794
|
+
onClick: actions.onGet,
|
|
1795
|
+
disabled: !deps.allSatisfied || actions.getting === true,
|
|
1796
|
+
},
|
|
1797
|
+
actions.getting === true ? t.getting : t.get,
|
|
1701
1798
|
),
|
|
1702
1799
|
),
|
|
1703
1800
|
actions.depsNote
|
|
@@ -1766,12 +1863,26 @@ window.__ModuleLoader__.load({
|
|
|
1766
1863
|
// does.
|
|
1767
1864
|
var launchingState = react.useState(false);
|
|
1768
1865
|
var stoppingState = react.useState(false);
|
|
1866
|
+
// The Get step's state. Get runs the git sync, the dependency plan,
|
|
1867
|
+
// and the engine build on the host through /local-infer/get, and the
|
|
1868
|
+
// streamed output lands in getLogs (the Logs section shows it while
|
|
1869
|
+
// the run is in flight). Launch stays disabled while a Get is in
|
|
1870
|
+
// flight; the idle timer re-enables it once GET_IDLE_MS passes with
|
|
1871
|
+
// no output, so a silent build never parks the button.
|
|
1872
|
+
var gettingState = react.useState(false);
|
|
1873
|
+
var getLogsState = react.useState(null);
|
|
1874
|
+
var getIdleTimerRef = react.useRef(null);
|
|
1769
1875
|
// Whether the draft's engine is confirmed running, so the single
|
|
1770
1876
|
// Launch/Stop control reads as Stop while the engine is up and Launch
|
|
1771
1877
|
// while it is down, the same toggle the per-entry control does. A
|
|
1772
1878
|
// healthy launch sets it, a stop clears it.
|
|
1773
1879
|
var draftRunningState = react.useState(false);
|
|
1774
1880
|
var lifecycleNoteState = react.useState('');
|
|
1881
|
+
// The Auto Rebuild checkbox next to the form's Launch button. Checked
|
|
1882
|
+
// by default: a launch runs the git sync (fetch, rebase, rebuild)
|
|
1883
|
+
// before spawning the engine. Unchecked, the launch skips the sync and
|
|
1884
|
+
// starts the checkout as-is.
|
|
1885
|
+
var autoRebuildState = react.useState(true);
|
|
1775
1886
|
// The model download state. The Download button runs the hf CLI
|
|
1776
1887
|
// download through the /local-infer/download route, and the note
|
|
1777
1888
|
// reports the answer the route returned.
|
|
@@ -1816,8 +1927,9 @@ window.__ModuleLoader__.load({
|
|
|
1816
1927
|
// from the summary's stored template on load; a Save persists it.
|
|
1817
1928
|
var logPathState = react.useState('');
|
|
1818
1929
|
// The per-entry launch state: a map from provider id to the entry's
|
|
1819
|
-
//
|
|
1820
|
-
// launch
|
|
1930
|
+
// launch-state-machine state (the rebuild phases 'prereqs', 'download',
|
|
1931
|
+
// 'build', 'launch', plus 'running' and 'failed') and the start time a
|
|
1932
|
+
// launch began, so a card entry can show its phase label, Stop, or red
|
|
1821
1933
|
// Logs control. Absent entries read as idle (a plain Launch button).
|
|
1822
1934
|
var entryLaunchState = react.useState({});
|
|
1823
1935
|
var data = dataState[0];
|
|
@@ -1834,8 +1946,11 @@ window.__ModuleLoader__.load({
|
|
|
1834
1946
|
var agentDefault = agentDefaultState[0];
|
|
1835
1947
|
var launching = launchingState[0];
|
|
1836
1948
|
var stopping = stoppingState[0];
|
|
1949
|
+
var getting = gettingState[0];
|
|
1950
|
+
var getLogs = getLogsState[0];
|
|
1837
1951
|
var draftRunning = draftRunningState[0];
|
|
1838
1952
|
var lifecycleNote = lifecycleNoteState[0];
|
|
1953
|
+
var autoRebuild = autoRebuildState[0];
|
|
1839
1954
|
var downloading = downloadingState[0];
|
|
1840
1955
|
var downloadNote = downloadNoteState[0];
|
|
1841
1956
|
var templateDownloading = templateDownloadingState[0];
|
|
@@ -1856,6 +1971,14 @@ window.__ModuleLoader__.load({
|
|
|
1856
1971
|
? 'new'
|
|
1857
1972
|
: providerFormMode;
|
|
1858
1973
|
var timerRef = react.useRef(null);
|
|
1974
|
+
// The latest committed draft, mirrored into a ref so the debounced
|
|
1975
|
+
// re-price reads the value the user last typed, not a stale closure.
|
|
1976
|
+
// The effect runs after each render, so the ref settles before the
|
|
1977
|
+
// 300ms timer fires.
|
|
1978
|
+
var draftRef = react.useRef(null);
|
|
1979
|
+
react.useEffect(() => {
|
|
1980
|
+
draftRef.current = draft;
|
|
1981
|
+
}, [draft]);
|
|
1859
1982
|
|
|
1860
1983
|
var fetchSummary = react.useCallback(
|
|
1861
1984
|
(engine, config) => {
|
|
@@ -1874,6 +1997,10 @@ window.__ModuleLoader__.load({
|
|
|
1874
1997
|
)
|
|
1875
1998
|
.then((next) => {
|
|
1876
1999
|
dataState[1](next);
|
|
2000
|
+
// The streamed Get buffer yields to the persisted last-run
|
|
2001
|
+
// logs the summary now carries, so the Logs section keeps
|
|
2002
|
+
// the run's tail without a second copy.
|
|
2003
|
+
getLogsState[1](null);
|
|
1877
2004
|
draftState[1]((current) => {
|
|
1878
2005
|
// The summary's platformConfigs is the host-persisted
|
|
1879
2006
|
// platform state, while the draft's platform entries are
|
|
@@ -1945,6 +2072,25 @@ window.__ModuleLoader__.load({
|
|
|
1945
2072
|
[],
|
|
1946
2073
|
);
|
|
1947
2074
|
|
|
2075
|
+
// scheduleReprice resets the re-price timeout on every field change
|
|
2076
|
+
// and fetches the summary (the VRAM Est. meter) and the deps panel
|
|
2077
|
+
// only after typing pauses, so a fast keystroke stream is one round
|
|
2078
|
+
// trip, not one per letter. The ref reads the latest committed draft,
|
|
2079
|
+
// so the fetch prices the value the user last typed.
|
|
2080
|
+
var scheduleReprice = react.useCallback(() => {
|
|
2081
|
+
if (timerRef.current) {
|
|
2082
|
+
clearTimeout(timerRef.current);
|
|
2083
|
+
}
|
|
2084
|
+
timerRef.current = setTimeout(() => {
|
|
2085
|
+
var latest = draftRef.current;
|
|
2086
|
+
if (latest === null || latest === undefined) {
|
|
2087
|
+
return;
|
|
2088
|
+
}
|
|
2089
|
+
fetchSummary(latest.engine, latest.config);
|
|
2090
|
+
fetchDeps(latest.engine, latest.config);
|
|
2091
|
+
}, 300);
|
|
2092
|
+
}, [fetchSummary, fetchDeps]);
|
|
2093
|
+
|
|
1948
2094
|
var load = react.useCallback(() => {
|
|
1949
2095
|
fetchSummary(null, null);
|
|
1950
2096
|
fetchDeps(null, null);
|
|
@@ -1956,9 +2102,11 @@ window.__ModuleLoader__.load({
|
|
|
1956
2102
|
}
|
|
1957
2103
|
}, [data, draft, load]);
|
|
1958
2104
|
|
|
1959
|
-
// A field edit updates the draft config and
|
|
1960
|
-
//
|
|
1961
|
-
//
|
|
2105
|
+
// A field edit updates the draft config and schedules a debounced
|
|
2106
|
+
// re-price of the meter and form gating. The draft update is pure;
|
|
2107
|
+
// the re-price (the VRAM Est. visualization) runs only after typing
|
|
2108
|
+
// pauses, so a fast keystroke stream is one round trip, not one per
|
|
2109
|
+
// letter.
|
|
1962
2110
|
var onValue = react.useCallback(
|
|
1963
2111
|
(key, value) => {
|
|
1964
2112
|
noteState[1]('');
|
|
@@ -1968,22 +2116,15 @@ window.__ModuleLoader__.load({
|
|
|
1968
2116
|
draftState[1]((current) => {
|
|
1969
2117
|
var config = Object.assign({}, current.config);
|
|
1970
2118
|
config[key] = value;
|
|
1971
|
-
|
|
2119
|
+
return {
|
|
1972
2120
|
engine: current.engine,
|
|
1973
2121
|
config: config,
|
|
1974
2122
|
platform: current.platform || {},
|
|
1975
2123
|
};
|
|
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
2124
|
});
|
|
2125
|
+
scheduleReprice();
|
|
1985
2126
|
},
|
|
1986
|
-
[
|
|
2127
|
+
[scheduleReprice],
|
|
1987
2128
|
);
|
|
1988
2129
|
|
|
1989
2130
|
// The Model Directory field edit. A valid directory (non-blank, no
|
|
@@ -2141,7 +2282,9 @@ window.__ModuleLoader__.load({
|
|
|
2141
2282
|
// The help toggle: flip the local state and persist the preference
|
|
2142
2283
|
// through the save route, so it survives a reload. The save response
|
|
2143
2284
|
// carries the fresh summary, so the data and the draft resync from
|
|
2144
|
-
// the same round trip, the way the allow-run toggle does.
|
|
2285
|
+
// the same round trip, the way the allow-run toggle does. The
|
|
2286
|
+
// settings-only flag keeps the route from upserting a provider entry,
|
|
2287
|
+
// so a toggle never mints a duplicate at the top of the list.
|
|
2145
2288
|
var onToggleHelp = react.useCallback(
|
|
2146
2289
|
(next) => {
|
|
2147
2290
|
if (draft === null) {
|
|
@@ -2157,6 +2300,7 @@ window.__ModuleLoader__.load({
|
|
|
2157
2300
|
allowRunDeps: deps !== null && deps.runAllowed === true,
|
|
2158
2301
|
platform: draft.platform || {},
|
|
2159
2302
|
showHelp: next,
|
|
2303
|
+
settingsOnly: true,
|
|
2160
2304
|
}),
|
|
2161
2305
|
})
|
|
2162
2306
|
.then((response) =>
|
|
@@ -2265,18 +2409,11 @@ window.__ModuleLoader__.load({
|
|
|
2265
2409
|
draftState[1]((_current) => {
|
|
2266
2410
|
// The platform config is per-engine, so an engine switch keeps
|
|
2267
2411
|
// 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;
|
|
2412
|
+
return { engine: engine, config: {}, platform: _current.platform || {} };
|
|
2277
2413
|
});
|
|
2414
|
+
scheduleReprice();
|
|
2278
2415
|
},
|
|
2279
|
-
[
|
|
2416
|
+
[scheduleReprice],
|
|
2280
2417
|
);
|
|
2281
2418
|
|
|
2282
2419
|
var save = react.useCallback(() => {
|
|
@@ -2492,6 +2629,9 @@ window.__ModuleLoader__.load({
|
|
|
2492
2629
|
}
|
|
2493
2630
|
}, [deps, t]);
|
|
2494
2631
|
|
|
2632
|
+
// The allow-run toggle persists the trust gate through the save route.
|
|
2633
|
+
// The settings-only flag keeps the route from upserting a provider
|
|
2634
|
+
// entry, so a toggle never mints a duplicate at the top of the list.
|
|
2495
2635
|
var onAllowRun = react.useCallback(
|
|
2496
2636
|
(checked) => {
|
|
2497
2637
|
if (draft === null) {
|
|
@@ -2506,6 +2646,7 @@ window.__ModuleLoader__.load({
|
|
|
2506
2646
|
allowRunDeps: checked,
|
|
2507
2647
|
platform: draft.platform || {},
|
|
2508
2648
|
showHelp: help,
|
|
2649
|
+
settingsOnly: true,
|
|
2509
2650
|
}),
|
|
2510
2651
|
})
|
|
2511
2652
|
.then((response) =>
|
|
@@ -2572,14 +2713,78 @@ window.__ModuleLoader__.load({
|
|
|
2572
2713
|
}, [deps, draft, installing, t]);
|
|
2573
2714
|
|
|
2574
2715
|
var onGet = react.useCallback(() => {
|
|
2575
|
-
if (deps === null || draft === null) {
|
|
2716
|
+
if (deps === null || draft === null || getting) {
|
|
2576
2717
|
return;
|
|
2577
2718
|
}
|
|
2578
|
-
// "Get"
|
|
2579
|
-
//
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2719
|
+
// "Get" runs the git sync, the dependency plan, and the build on
|
|
2720
|
+
// the host through /local-infer/get. The route streams its output;
|
|
2721
|
+
// each chunk appends to the Logs section and re-arms the idle
|
|
2722
|
+
// timer, and Launch stays disabled until the run finishes, fails,
|
|
2723
|
+
// or falls silent for GET_IDLE_MS.
|
|
2724
|
+
gettingState[1](true);
|
|
2725
|
+
depsNoteState[1]('');
|
|
2726
|
+
getLogsState[1]((data && data.lastLogs) || '');
|
|
2727
|
+
var append = (text) => {
|
|
2728
|
+
getLogsState[1]((current) => (current === null ? '' : current) + text);
|
|
2729
|
+
};
|
|
2730
|
+
var armIdle = () => {
|
|
2731
|
+
if (getIdleTimerRef.current) {
|
|
2732
|
+
clearTimeout(getIdleTimerRef.current);
|
|
2733
|
+
}
|
|
2734
|
+
// Two minutes without a line reads as a stalled build: release
|
|
2735
|
+
// Launch while the run keeps streaming in the background.
|
|
2736
|
+
getIdleTimerRef.current = setTimeout(() => {
|
|
2737
|
+
gettingState[1](false);
|
|
2738
|
+
}, GET_IDLE_MS);
|
|
2739
|
+
};
|
|
2740
|
+
armIdle();
|
|
2741
|
+
var platform = (draft.platform || {})[draft.engine];
|
|
2742
|
+
fetch('/local-infer/get', {
|
|
2743
|
+
method: 'POST',
|
|
2744
|
+
headers: { 'content-type': 'application/json' },
|
|
2745
|
+
body: JSON.stringify({
|
|
2746
|
+
engine: draft.engine,
|
|
2747
|
+
config: draft.config,
|
|
2748
|
+
platform: platform,
|
|
2749
|
+
}),
|
|
2750
|
+
})
|
|
2751
|
+
.then((response) => {
|
|
2752
|
+
if (!response.ok) {
|
|
2753
|
+
return response.text().then((text) => {
|
|
2754
|
+
throw new Error(text || 'HTTP ' + response.status);
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
if (!response.body || typeof response.body.getReader !== 'function') {
|
|
2758
|
+
// A host whose response cannot stream: read the whole body.
|
|
2759
|
+
return response.text().then((text) => append(text));
|
|
2760
|
+
}
|
|
2761
|
+
var reader = response.body.getReader();
|
|
2762
|
+
var decoder = new TextDecoder();
|
|
2763
|
+
var pump = () =>
|
|
2764
|
+
reader.read().then(({ done, value }) => {
|
|
2765
|
+
if (done) {
|
|
2766
|
+
return undefined;
|
|
2767
|
+
}
|
|
2768
|
+
append(decoder.decode(value, { stream: true }));
|
|
2769
|
+
armIdle();
|
|
2770
|
+
return pump();
|
|
2771
|
+
});
|
|
2772
|
+
return pump();
|
|
2773
|
+
})
|
|
2774
|
+
.catch((error) => {
|
|
2775
|
+
append('\n' + t.getFailed + ': ' + (error && error.message ? error.message : '') + '\n');
|
|
2776
|
+
})
|
|
2777
|
+
.then(() => {
|
|
2778
|
+
if (getIdleTimerRef.current) {
|
|
2779
|
+
clearTimeout(getIdleTimerRef.current);
|
|
2780
|
+
getIdleTimerRef.current = null;
|
|
2781
|
+
}
|
|
2782
|
+
gettingState[1](false);
|
|
2783
|
+
// The route persisted the run as the last logs; the summary
|
|
2784
|
+
// re-read hands the Logs section back to the stored value.
|
|
2785
|
+
fetchSummary(draft.engine, draft.config);
|
|
2786
|
+
});
|
|
2787
|
+
}, [deps, draft, getting, data, t, fetchSummary]);
|
|
2583
2788
|
|
|
2584
2789
|
var onRegister = react.useCallback(() => {
|
|
2585
2790
|
if (draft === null || registering) {
|
|
@@ -2678,6 +2883,10 @@ window.__ModuleLoader__.load({
|
|
|
2678
2883
|
engine: draft.engine,
|
|
2679
2884
|
config: draft.config,
|
|
2680
2885
|
platform: platform,
|
|
2886
|
+
// The Auto Rebuild checkbox: false skips the git sync (fetch,
|
|
2887
|
+
// rebase, rebuild) and launches the checkout as-is. The stop
|
|
2888
|
+
// route ignores the key.
|
|
2889
|
+
autoRebuild: autoRebuild,
|
|
2681
2890
|
};
|
|
2682
2891
|
// The provider entry the draft edits: the route persists the launch
|
|
2683
2892
|
// record (and the stop's clear) on that entry, so a restart re-links
|
|
@@ -2699,9 +2908,48 @@ window.__ModuleLoader__.load({
|
|
|
2699
2908
|
headers: { 'content-type': 'application/json' },
|
|
2700
2909
|
body: launchBody(),
|
|
2701
2910
|
})
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2911
|
+
// The launch route streams NDJSON (a step event per phase, then a
|
|
2912
|
+
// final result event). The main form's control does not show the
|
|
2913
|
+
// phase labels, so read the stream and keep only the result event.
|
|
2914
|
+
.then(function (response) {
|
|
2915
|
+
if (response.body === null || typeof response.body.getReader !== 'function') {
|
|
2916
|
+
return response.json().then(function (body) {
|
|
2917
|
+
return { ok: response.ok, result: body };
|
|
2918
|
+
});
|
|
2919
|
+
}
|
|
2920
|
+
var reader = response.body.getReader();
|
|
2921
|
+
var decoder = new TextDecoder();
|
|
2922
|
+
var buffer = '';
|
|
2923
|
+
var resultEvent = null;
|
|
2924
|
+
function pump() {
|
|
2925
|
+
return reader.read().then(function (chunk) {
|
|
2926
|
+
if (chunk.done) {
|
|
2927
|
+
return { ok: response.ok, result: resultEvent };
|
|
2928
|
+
}
|
|
2929
|
+
buffer += decoder.decode(chunk.value, { stream: true });
|
|
2930
|
+
var lines = buffer.split('\n');
|
|
2931
|
+
buffer = lines.pop();
|
|
2932
|
+
for (var i = 0; i < lines.length; i++) {
|
|
2933
|
+
if (lines[i] === '') {
|
|
2934
|
+
continue;
|
|
2935
|
+
}
|
|
2936
|
+
var event;
|
|
2937
|
+
try {
|
|
2938
|
+
event = JSON.parse(lines[i]);
|
|
2939
|
+
} catch {
|
|
2940
|
+
continue;
|
|
2941
|
+
}
|
|
2942
|
+
if (event.result !== undefined) {
|
|
2943
|
+
resultEvent = event.result;
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
return pump();
|
|
2947
|
+
});
|
|
2948
|
+
}
|
|
2949
|
+
return pump();
|
|
2950
|
+
})
|
|
2951
|
+
.then(({ ok, result: body }) => {
|
|
2952
|
+
if (ok && body !== null) {
|
|
2705
2953
|
if (body.healthy) {
|
|
2706
2954
|
lifecycleNoteState[1](
|
|
2707
2955
|
t.runningOn + ' ' + (body.plan && body.plan.port ? body.plan.port : ''),
|
|
@@ -2713,7 +2961,9 @@ window.__ModuleLoader__.load({
|
|
|
2713
2961
|
lifecycleNoteState[1](t.notHealthy + (body.detail ? ': ' + body.detail : ''));
|
|
2714
2962
|
}
|
|
2715
2963
|
} else {
|
|
2716
|
-
lifecycleNoteState[1](
|
|
2964
|
+
lifecycleNoteState[1](
|
|
2965
|
+
t.launchFailed + (body && body.detail ? ': ' + body.detail : ''),
|
|
2966
|
+
);
|
|
2717
2967
|
}
|
|
2718
2968
|
})
|
|
2719
2969
|
.catch((error) => {
|
|
@@ -2723,8 +2973,13 @@ window.__ModuleLoader__.load({
|
|
|
2723
2973
|
})
|
|
2724
2974
|
.then(() => {
|
|
2725
2975
|
launchingState[1](false);
|
|
2976
|
+
// The route persisted the run's launch command line and engine
|
|
2977
|
+
// output as the last logs; the summary re-read hands the Logs
|
|
2978
|
+
// section the new run, the same rule the per-entry launch and
|
|
2979
|
+
// the Get apply when their run ends.
|
|
2980
|
+
fetchSummary(draft.engine, draft.config);
|
|
2726
2981
|
});
|
|
2727
|
-
}, [draft, launching, editingProviderId, t]);
|
|
2982
|
+
}, [draft, launching, editingProviderId, autoRebuild, t, fetchSummary]);
|
|
2728
2983
|
|
|
2729
2984
|
var onStop = react.useCallback(() => {
|
|
2730
2985
|
if (draft === null || stopping) {
|
|
@@ -2759,39 +3014,66 @@ window.__ModuleLoader__.load({
|
|
|
2759
3014
|
}, [draft, stopping, editingProviderId, t]);
|
|
2760
3015
|
|
|
2761
3016
|
// 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
|
-
//
|
|
3017
|
+
// own engine, not the form's draft. It starts the launch state machine
|
|
3018
|
+
// at the prereqs phase, then reads the launch stream (an NDJSON step
|
|
3019
|
+
// event as each phase begins, then a final result event) and drives the
|
|
3020
|
+
// machine: each step event advances the phase label (Prereqs,
|
|
3021
|
+
// Downloading, Building, Launching), a healthy result settles to
|
|
3022
|
+
// running (the control becomes Stop), and a failed result or the
|
|
3023
|
+
// health-poll deadline settles to failed (the control becomes a red
|
|
3024
|
+
// Logs that opens the entry for editing).
|
|
2767
3025
|
var onLaunchEntry = react.useCallback(
|
|
2768
3026
|
(provider) => {
|
|
2769
|
-
var
|
|
2770
|
-
|
|
3027
|
+
var prior = entryLaunch[provider.id];
|
|
3028
|
+
var status = prior !== undefined ? prior.status : 'idle';
|
|
3029
|
+
// A launch in flight (a rebuild phase) is ignored; a running engine
|
|
3030
|
+
// is stopped by its own Stop control, not re-launched.
|
|
3031
|
+
if (status !== 'idle' && status !== 'failed') {
|
|
2771
3032
|
return;
|
|
2772
3033
|
}
|
|
3034
|
+
var startStatus = launchNextState(status, { type: 'start' });
|
|
2773
3035
|
entryLaunchState[1]((current) =>
|
|
2774
3036
|
Object.assign({}, current, {
|
|
2775
|
-
[provider.id]: { status:
|
|
3037
|
+
[provider.id]: { status: startStatus, startTime: Date.now() },
|
|
2776
3038
|
}),
|
|
2777
3039
|
);
|
|
2778
|
-
//
|
|
2779
|
-
//
|
|
2780
|
-
//
|
|
2781
|
-
|
|
2782
|
-
|
|
3040
|
+
// Move the entry's launch state through the machine, reading the
|
|
3041
|
+
// current status from the latest map so a late event prices the
|
|
3042
|
+
// state it lands on, not a stale one.
|
|
3043
|
+
var move = function (event) {
|
|
3044
|
+
entryLaunchState[1]((current) => {
|
|
3045
|
+
var entry = current[provider.id];
|
|
3046
|
+
var next = launchNextState(entry !== undefined ? entry.status : 'idle', event);
|
|
3047
|
+
return Object.assign({}, current, {
|
|
3048
|
+
[provider.id]: { status: next, startTime: Date.now() },
|
|
3049
|
+
});
|
|
3050
|
+
});
|
|
3051
|
+
};
|
|
3052
|
+
// The health-poll deadline: the route's poll runs up to two minutes,
|
|
3053
|
+
// so a client-side deadline just past that cap forces a hung launch
|
|
3054
|
+
// to the red Logs state. It arms only for the launch phase; the
|
|
3055
|
+
// rebuild phases (prereqs, download, build) run as long as they
|
|
3056
|
+
// need to, so a slow build never reads as a failure.
|
|
2783
3057
|
var settled = false;
|
|
2784
|
-
var deadline =
|
|
2785
|
-
|
|
3058
|
+
var deadline = null;
|
|
3059
|
+
var armDeadline = function () {
|
|
3060
|
+
if (deadline !== null) {
|
|
2786
3061
|
return;
|
|
2787
3062
|
}
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
3063
|
+
deadline = setTimeout(function () {
|
|
3064
|
+
if (settled) {
|
|
3065
|
+
return;
|
|
3066
|
+
}
|
|
3067
|
+
settled = true;
|
|
3068
|
+
move({ type: 'failed' });
|
|
3069
|
+
}, LAUNCH_HEALTH_DEADLINE_MS);
|
|
3070
|
+
};
|
|
3071
|
+
var clearDeadline = function () {
|
|
3072
|
+
if (deadline !== null) {
|
|
3073
|
+
clearTimeout(deadline);
|
|
3074
|
+
deadline = null;
|
|
3075
|
+
}
|
|
3076
|
+
};
|
|
2795
3077
|
fetch('/local-infer/launch', {
|
|
2796
3078
|
method: 'POST',
|
|
2797
3079
|
headers: { 'content-type': 'application/json' },
|
|
@@ -2804,34 +3086,86 @@ window.__ModuleLoader__.load({
|
|
|
2804
3086
|
providerId: provider.id,
|
|
2805
3087
|
}),
|
|
2806
3088
|
})
|
|
2807
|
-
.then((response)
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
})
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
3089
|
+
.then(function (response) {
|
|
3090
|
+
// The launch streams NDJSON: a step event as each phase begins,
|
|
3091
|
+
// then a final result event. Read the stream and drive the
|
|
3092
|
+
// state machine; a response with no body (a host without
|
|
3093
|
+
// streaming) settles straight to failed.
|
|
3094
|
+
if (response.body === null || typeof response.body.getReader !== 'function') {
|
|
3095
|
+
settled = true;
|
|
3096
|
+
clearDeadline();
|
|
3097
|
+
move({ type: 'failed' });
|
|
3098
|
+
return Promise.resolve();
|
|
3099
|
+
}
|
|
3100
|
+
var reader = response.body.getReader();
|
|
3101
|
+
var decoder = new TextDecoder();
|
|
3102
|
+
var buffer = '';
|
|
3103
|
+
function pump() {
|
|
3104
|
+
return reader.read().then(function (result) {
|
|
3105
|
+
if (result.done) {
|
|
3106
|
+
// The stream ended without a result event (a host that
|
|
3107
|
+
// cannot stream, a route that broke mid-stream): settle
|
|
3108
|
+
// to failed so the control does not sit on a phase label.
|
|
3109
|
+
if (!settled) {
|
|
3110
|
+
settled = true;
|
|
3111
|
+
clearDeadline();
|
|
3112
|
+
move({ type: 'failed' });
|
|
3113
|
+
}
|
|
3114
|
+
return;
|
|
3115
|
+
}
|
|
3116
|
+
buffer += decoder.decode(result.value, { stream: true });
|
|
3117
|
+
var lines = buffer.split('\n');
|
|
3118
|
+
buffer = lines.pop();
|
|
3119
|
+
for (var i = 0; i < lines.length; i++) {
|
|
3120
|
+
var line = lines[i];
|
|
3121
|
+
if (line === '') {
|
|
3122
|
+
continue;
|
|
3123
|
+
}
|
|
3124
|
+
var event;
|
|
3125
|
+
try {
|
|
3126
|
+
event = JSON.parse(line);
|
|
3127
|
+
} catch {
|
|
3128
|
+
// A malformed line is not a launch event; skip it.
|
|
3129
|
+
continue;
|
|
3130
|
+
}
|
|
3131
|
+
if (event.step !== undefined) {
|
|
3132
|
+
move({ type: 'step', step: event.step });
|
|
3133
|
+
// Arm the deadline only for the launch (health-poll)
|
|
3134
|
+
// step; the rebuild steps run without a timeout.
|
|
3135
|
+
if (event.step === 'launch') {
|
|
3136
|
+
armDeadline();
|
|
3137
|
+
}
|
|
3138
|
+
} else if (event.result !== undefined) {
|
|
3139
|
+
settled = true;
|
|
3140
|
+
clearDeadline();
|
|
3141
|
+
var healthy = event.result.healthy === true;
|
|
3142
|
+
move(healthy ? { type: 'healthy' } : { type: 'failed' });
|
|
3143
|
+
// The provider status dots price the process probe on a
|
|
3144
|
+
// summary fetch, so a launch that turned an engine
|
|
3145
|
+
// healthy re-fetches the summary to repaint the dot
|
|
3146
|
+
// from red to green.
|
|
3147
|
+
fetchSummary(provider.engine, provider.config || {});
|
|
3148
|
+
return;
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
return pump();
|
|
3152
|
+
});
|
|
3153
|
+
}
|
|
3154
|
+
return pump();
|
|
2821
3155
|
})
|
|
2822
|
-
.catch(()
|
|
3156
|
+
.catch(function () {
|
|
2823
3157
|
settled = true;
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
Object.assign({}, current, {
|
|
2827
|
-
[provider.id]: { status: 'failed', startTime: Date.now() },
|
|
2828
|
-
}),
|
|
2829
|
-
);
|
|
3158
|
+
clearDeadline();
|
|
3159
|
+
move({ type: 'failed' });
|
|
2830
3160
|
});
|
|
2831
3161
|
},
|
|
2832
3162
|
[entryLaunch, fetchSummary],
|
|
2833
3163
|
);
|
|
2834
3164
|
|
|
3165
|
+
// The per-entry stop: a running engine's Stop control settles the
|
|
3166
|
+
// launch state machine back to idle (the control reads as Launch
|
|
3167
|
+
// again). The stop clears the launch record on this entry, so a
|
|
3168
|
+
// recycled pid cannot re-link it.
|
|
2835
3169
|
var onStopEntry = react.useCallback(
|
|
2836
3170
|
(provider) => {
|
|
2837
3171
|
fetch('/local-infer/stop', {
|
|
@@ -2848,9 +3182,16 @@ window.__ModuleLoader__.load({
|
|
|
2848
3182
|
})
|
|
2849
3183
|
.then(() => {
|
|
2850
3184
|
entryLaunchState[1]((current) => {
|
|
2851
|
-
var
|
|
2852
|
-
|
|
2853
|
-
|
|
3185
|
+
var entry = current[provider.id];
|
|
3186
|
+
var status = entry !== undefined ? entry.status : 'idle';
|
|
3187
|
+
var next = launchNextState(status, { type: 'stop' });
|
|
3188
|
+
var map = Object.assign({}, current);
|
|
3189
|
+
if (next === 'idle') {
|
|
3190
|
+
delete map[provider.id];
|
|
3191
|
+
} else {
|
|
3192
|
+
map[provider.id] = { status: next, startTime: Date.now() };
|
|
3193
|
+
}
|
|
3194
|
+
return map;
|
|
2854
3195
|
});
|
|
2855
3196
|
})
|
|
2856
3197
|
.catch(() => {
|
|
@@ -2861,17 +3202,24 @@ window.__ModuleLoader__.load({
|
|
|
2861
3202
|
);
|
|
2862
3203
|
|
|
2863
3204
|
// 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
|
|
3205
|
+
// editing AND resets the launch state machine to idle, so the per-entry
|
|
3206
|
+
// control reads as Launch again. The user fixes the config in the form
|
|
3207
|
+
// and re-launches from the same entry, instead of being stuck on a
|
|
2867
3208
|
// non-actionable Logs control.
|
|
2868
3209
|
var onLogsEntry = react.useCallback(
|
|
2869
3210
|
(provider) => {
|
|
2870
3211
|
onEditProvider(provider);
|
|
2871
3212
|
entryLaunchState[1]((current) => {
|
|
2872
|
-
var
|
|
2873
|
-
|
|
2874
|
-
|
|
3213
|
+
var entry = current[provider.id];
|
|
3214
|
+
var status = entry !== undefined ? entry.status : 'idle';
|
|
3215
|
+
var next = launchNextState(status, { type: 'reset' });
|
|
3216
|
+
var map = Object.assign({}, current);
|
|
3217
|
+
if (next === 'idle') {
|
|
3218
|
+
delete map[provider.id];
|
|
3219
|
+
} else {
|
|
3220
|
+
map[provider.id] = { status: next, startTime: Date.now() };
|
|
3221
|
+
}
|
|
3222
|
+
return map;
|
|
2875
3223
|
});
|
|
2876
3224
|
},
|
|
2877
3225
|
[onEditProvider],
|
|
@@ -2956,9 +3304,14 @@ window.__ModuleLoader__.load({
|
|
|
2956
3304
|
: null,
|
|
2957
3305
|
)
|
|
2958
3306
|
: null,
|
|
2959
|
-
// The card header: the section title plus the circular
|
|
2960
|
-
//
|
|
2961
|
-
//
|
|
3307
|
+
// The card header: the section title plus the circular refresh and
|
|
3308
|
+
// "?" buttons. The refresh button re-fetches the summary and deps
|
|
3309
|
+
// from the host, so the card re-prices after the harness pauses
|
|
3310
|
+
// the plugin's routes and re-activates them. The refresh glyph is
|
|
3311
|
+
// the primitives seed's IconRefreshOutlineRegular; a host without
|
|
3312
|
+
// the seed keeps the native arrow character. The "?" button shows
|
|
3313
|
+
// or hides the per-field descriptions; the preference persists
|
|
3314
|
+
// through the save route, so it survives a reload.
|
|
2962
3315
|
react.createElement(
|
|
2963
3316
|
'div',
|
|
2964
3317
|
{
|
|
@@ -2971,16 +3324,39 @@ window.__ModuleLoader__.load({
|
|
|
2971
3324
|
},
|
|
2972
3325
|
react.createElement('div', { style: { fontWeight: 600, fontSize: 14 } }, t.section),
|
|
2973
3326
|
react.createElement(
|
|
2974
|
-
'
|
|
3327
|
+
'div',
|
|
2975
3328
|
{
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
onClick: () => onToggleHelp(!help),
|
|
3329
|
+
style: {
|
|
3330
|
+
display: 'flex',
|
|
3331
|
+
alignItems: 'center',
|
|
3332
|
+
gap: 8,
|
|
3333
|
+
},
|
|
2982
3334
|
},
|
|
2983
|
-
|
|
3335
|
+
react.createElement(
|
|
3336
|
+
'button',
|
|
3337
|
+
{
|
|
3338
|
+
type: 'button',
|
|
3339
|
+
style: HELP_BUTTON_STYLE(false),
|
|
3340
|
+
title: t.refresh,
|
|
3341
|
+
'aria-label': t.refresh,
|
|
3342
|
+
onClick: () => load(),
|
|
3343
|
+
},
|
|
3344
|
+
primitives && typeof primitives.IconRefreshOutlineRegular === 'function'
|
|
3345
|
+
? react.createElement(primitives.IconRefreshOutlineRegular, { size: 12 })
|
|
3346
|
+
: '↻',
|
|
3347
|
+
),
|
|
3348
|
+
react.createElement(
|
|
3349
|
+
'button',
|
|
3350
|
+
{
|
|
3351
|
+
type: 'button',
|
|
3352
|
+
style: HELP_BUTTON_STYLE(help),
|
|
3353
|
+
title: t.helpToggle,
|
|
3354
|
+
'aria-label': t.helpToggle,
|
|
3355
|
+
'aria-pressed': help,
|
|
3356
|
+
onClick: () => onToggleHelp(!help),
|
|
3357
|
+
},
|
|
3358
|
+
'?',
|
|
3359
|
+
),
|
|
2984
3360
|
),
|
|
2985
3361
|
),
|
|
2986
3362
|
// The provider list: the saved provider entries, each a collapsed
|
|
@@ -3011,7 +3387,10 @@ window.__ModuleLoader__.load({
|
|
|
3011
3387
|
launchLabel: t.entryLaunch,
|
|
3012
3388
|
stopLabel: t.entryStop,
|
|
3013
3389
|
logsLabel: t.entryLogs,
|
|
3014
|
-
launchingLabel: t.
|
|
3390
|
+
launchingLabel: t.entryLaunching,
|
|
3391
|
+
prereqsLabel: t.entryPrereqs,
|
|
3392
|
+
downloadingLabel: t.entryDownloading,
|
|
3393
|
+
buildingLabel: t.entryBuilding,
|
|
3015
3394
|
})
|
|
3016
3395
|
: null,
|
|
3017
3396
|
// The provider form: the Engine/Model config the card prices. It is
|
|
@@ -3314,6 +3693,94 @@ window.__ModuleLoader__.load({
|
|
|
3314
3693
|
),
|
|
3315
3694
|
),
|
|
3316
3695
|
help ? renderHelpRow(react, t.engineForkHelp) : null,
|
|
3696
|
+
(() => {
|
|
3697
|
+
// The build-method-dependent platform fields: local build mode
|
|
3698
|
+
// shows the Build commands override, docker build mode shows the
|
|
3699
|
+
// Docker tag override. Only one is visible at a time, keyed off
|
|
3700
|
+
// the same buildMode the Build row prices. The placeholder seeds
|
|
3701
|
+
// the engine's own default (the build step the deploy runs, or the
|
|
3702
|
+
// image tag the docker deploy pulls), so an empty field reads as
|
|
3703
|
+
// "use the default."
|
|
3704
|
+
var engineEntry = (data.engines || []).find(
|
|
3705
|
+
(entry) => entry.id === draft.engine,
|
|
3706
|
+
);
|
|
3707
|
+
var buildModes = [];
|
|
3708
|
+
if (engineEntry && engineEntry.supports.local) buildModes.push('local');
|
|
3709
|
+
if (engineEntry && engineEntry.supports.docker) buildModes.push('docker');
|
|
3710
|
+
var buildMode =
|
|
3711
|
+
typeof draft.config.buildMode === 'string'
|
|
3712
|
+
? draft.config.buildMode
|
|
3713
|
+
: buildModes[0] || 'local';
|
|
3714
|
+
if (buildMode === 'local') {
|
|
3715
|
+
return react.createElement(
|
|
3716
|
+
'div',
|
|
3717
|
+
null,
|
|
3718
|
+
react.createElement(
|
|
3719
|
+
'div',
|
|
3720
|
+
{ style: ROW_STYLE },
|
|
3721
|
+
react.createElement('label', { style: LABEL_STYLE }, t.buildCommands),
|
|
3722
|
+
react.createElement(
|
|
3723
|
+
'input',
|
|
3724
|
+
Object.assign(
|
|
3725
|
+
{ style: INPUT_STYLE },
|
|
3726
|
+
{
|
|
3727
|
+
type: 'text',
|
|
3728
|
+
value:
|
|
3729
|
+
typeof enginePlatform.buildCommands === 'string'
|
|
3730
|
+
? enginePlatform.buildCommands
|
|
3731
|
+
: '',
|
|
3732
|
+
placeholder:
|
|
3733
|
+
engineEntry && engineEntry.defaultBuildCommand
|
|
3734
|
+
? engineEntry.defaultBuildCommand
|
|
3735
|
+
: '',
|
|
3736
|
+
onChange: (event) =>
|
|
3737
|
+
onPlatform(
|
|
3738
|
+
'buildCommands',
|
|
3739
|
+
event.target.value === '' ? undefined : event.target.value,
|
|
3740
|
+
),
|
|
3741
|
+
},
|
|
3742
|
+
),
|
|
3743
|
+
),
|
|
3744
|
+
),
|
|
3745
|
+
help ? renderHelpRow(react, t.buildCommandsHelp) : null,
|
|
3746
|
+
);
|
|
3747
|
+
}
|
|
3748
|
+
if (buildMode === 'docker') {
|
|
3749
|
+
return react.createElement(
|
|
3750
|
+
'div',
|
|
3751
|
+
null,
|
|
3752
|
+
react.createElement(
|
|
3753
|
+
'div',
|
|
3754
|
+
{ style: ROW_STYLE },
|
|
3755
|
+
react.createElement('label', { style: LABEL_STYLE }, t.dockerTag),
|
|
3756
|
+
react.createElement(
|
|
3757
|
+
'input',
|
|
3758
|
+
Object.assign(
|
|
3759
|
+
{ style: INPUT_STYLE },
|
|
3760
|
+
{
|
|
3761
|
+
type: 'text',
|
|
3762
|
+
value:
|
|
3763
|
+
typeof enginePlatform.dockerTag === 'string'
|
|
3764
|
+
? enginePlatform.dockerTag
|
|
3765
|
+
: '',
|
|
3766
|
+
placeholder:
|
|
3767
|
+
engineEntry && engineEntry.defaultDockerTag
|
|
3768
|
+
? engineEntry.defaultDockerTag
|
|
3769
|
+
: '',
|
|
3770
|
+
onChange: (event) =>
|
|
3771
|
+
onPlatform(
|
|
3772
|
+
'dockerTag',
|
|
3773
|
+
event.target.value === '' ? undefined : event.target.value,
|
|
3774
|
+
),
|
|
3775
|
+
},
|
|
3776
|
+
),
|
|
3777
|
+
),
|
|
3778
|
+
),
|
|
3779
|
+
help ? renderHelpRow(react, t.dockerTagHelp) : null,
|
|
3780
|
+
);
|
|
3781
|
+
}
|
|
3782
|
+
return null;
|
|
3783
|
+
})(),
|
|
3317
3784
|
);
|
|
3318
3785
|
})(),
|
|
3319
3786
|
hasFields
|
|
@@ -3322,17 +3789,28 @@ window.__ModuleLoader__.load({
|
|
|
3322
3789
|
'div',
|
|
3323
3790
|
{ key: section.group },
|
|
3324
3791
|
react.createElement('div', { style: SECTION_STYLE }, section.group),
|
|
3325
|
-
section.fields.map((field) =>
|
|
3326
|
-
|
|
3792
|
+
section.fields.map((field) => {
|
|
3793
|
+
// The field value binds to the local draft, not the server
|
|
3794
|
+
// render model, so a typed keystroke shows in the input
|
|
3795
|
+
// immediately. The server model (field.value) resolves the
|
|
3796
|
+
// spec default and only surfaces after the debounced
|
|
3797
|
+
// re-price settles. A key the user has not edited is
|
|
3798
|
+
// absent from the draft, so it falls back to the model.
|
|
3799
|
+
var draftValue = draft.config[field.key];
|
|
3800
|
+
var effectiveField =
|
|
3801
|
+
draftValue !== undefined
|
|
3802
|
+
? Object.assign({}, field, { value: draftValue })
|
|
3803
|
+
: field;
|
|
3804
|
+
return renderField(
|
|
3327
3805
|
react,
|
|
3328
|
-
|
|
3806
|
+
effectiveField,
|
|
3329
3807
|
onValue,
|
|
3330
3808
|
inlineWarnings[field.key],
|
|
3331
3809
|
primitives,
|
|
3332
3810
|
help,
|
|
3333
3811
|
t,
|
|
3334
|
-
)
|
|
3335
|
-
),
|
|
3812
|
+
);
|
|
3813
|
+
}),
|
|
3336
3814
|
),
|
|
3337
3815
|
)
|
|
3338
3816
|
: react.createElement('div', { style: { fontSize: 12, opacity: 0.7 } }, t.noParameters),
|
|
@@ -3408,6 +3886,7 @@ window.__ModuleLoader__.load({
|
|
|
3408
3886
|
onAllowRun: onAllowRun,
|
|
3409
3887
|
onRun: onRun,
|
|
3410
3888
|
onGet: onGet,
|
|
3889
|
+
getting: getting,
|
|
3411
3890
|
installing: installing,
|
|
3412
3891
|
copyNote: copyNote,
|
|
3413
3892
|
depsNote: depsNote,
|
|
@@ -3418,6 +3897,8 @@ window.__ModuleLoader__.load({
|
|
|
3418
3897
|
// read-only text area (a launch fills it; the card never edits it),
|
|
3419
3898
|
// and the log-file location the launch writes to. The location field
|
|
3420
3899
|
// edits the template the route resolves and writes on the next run.
|
|
3900
|
+
// While a Get is in flight the streamed buffer wins: the git sync,
|
|
3901
|
+
// dependency, and build output appends there as it lands.
|
|
3421
3902
|
react.createElement(
|
|
3422
3903
|
'div',
|
|
3423
3904
|
{ style: { marginTop: 8 } },
|
|
@@ -3435,7 +3916,7 @@ window.__ModuleLoader__.load({
|
|
|
3435
3916
|
color: 'inherit',
|
|
3436
3917
|
background: 'transparent',
|
|
3437
3918
|
},
|
|
3438
|
-
value: data.lastLogs || '',
|
|
3919
|
+
value: getLogs !== null ? getLogs : data.lastLogs || '',
|
|
3439
3920
|
readOnly: true,
|
|
3440
3921
|
'aria-label': t.logs,
|
|
3441
3922
|
}),
|
|
@@ -3455,8 +3936,12 @@ window.__ModuleLoader__.load({
|
|
|
3455
3936
|
// draft's engine is down, Stop while a healthy launch set draftRunning,
|
|
3456
3937
|
// and an in-flight spinner (disabled) while a launch or stop is in
|
|
3457
3938
|
// flight. Launch stays disabled while the deps panel reports a
|
|
3458
|
-
// missing host dependency
|
|
3459
|
-
//
|
|
3939
|
+
// missing host dependency or a Get run is in flight, and re-enables
|
|
3940
|
+
// when the build finishes, errors, or falls silent for GET_IDLE_MS.
|
|
3941
|
+
// The Auto Rebuild checkbox sits after the control: checked, a
|
|
3942
|
+
// launch runs the git sync (fetch, rebase, rebuild) first;
|
|
3943
|
+
// unchecked, the launch starts the checkout as-is. The note reports
|
|
3944
|
+
// the answer the route returned.
|
|
3460
3945
|
react.createElement(
|
|
3461
3946
|
'div',
|
|
3462
3947
|
{ style: { display: 'flex', alignItems: 'center', gap: 8, marginTop: 8 } },
|
|
@@ -3491,10 +3976,19 @@ window.__ModuleLoader__.load({
|
|
|
3491
3976
|
'primary',
|
|
3492
3977
|
{
|
|
3493
3978
|
onClick: onLaunch,
|
|
3494
|
-
disabled:
|
|
3979
|
+
disabled:
|
|
3980
|
+
getting || (deps !== null && deps.allSatisfied !== true),
|
|
3495
3981
|
},
|
|
3496
3982
|
t.launch,
|
|
3497
3983
|
),
|
|
3984
|
+
// The Auto Rebuild checkbox: checked, a launch runs the git sync
|
|
3985
|
+
// (fetch, rebase, rebuild) before the spawn; unchecked, the
|
|
3986
|
+
// launch starts the checkout as-is.
|
|
3987
|
+
renderCheckbox(react, primitives, {
|
|
3988
|
+
checked: autoRebuild,
|
|
3989
|
+
onChange: (next) => autoRebuildState[1](next),
|
|
3990
|
+
label: t.autoRebuild,
|
|
3991
|
+
}),
|
|
3498
3992
|
),
|
|
3499
3993
|
lifecycleNote
|
|
3500
3994
|
? react.createElement('div', { style: { fontSize: 12 } }, lifecycleNote)
|