@termix-ssh/plugin-sdk 1.0.20 → 1.0.22
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/CHANGELOG.md +29 -0
- package/PLUGIN-GUIDE.md +66 -4
- package/cli/commands/build.mjs +55 -36
- package/cli/commands/validate.mjs +4 -0
- package/dist/backend.d.ts +32 -7
- package/dist/frontend.d.ts +7 -0
- package/dist/host/{PluginsTab-ZSMTJ5C5.js → PluginsTab-CM2B33BD.js} +26 -16
- package/dist/host/chunk-7J4QNGFM.js +345 -0
- package/dist/host/{chunk-J3ZQPBDK.js → chunk-HQ6NNFSX.js} +115 -1575
- package/dist/host/chunk-I5F2PSMR.js +1526 -0
- package/dist/host/{chunk-2MU6WNSX.js → chunk-JRV2HIDK.js} +1 -1
- package/dist/host/{chunk-7LEJ3HYF.js → chunk-P26QKKU5.js} +41 -10
- package/dist/host/{chunk-CITUENSU.js → chunk-ROB2SH5Q.js} +77 -49
- package/dist/host/chunk-TK3Y437H.js +53 -0
- package/dist/host/{chunk-EYONBBRJ.js → chunk-VKNGNEF3.js} +34650 -34968
- package/dist/host/core-classes.json +1 -1
- package/dist/host/frame-host.css +2 -0
- package/dist/host/frame-host.js +242 -0
- package/dist/host/{linked-server-OUF3POT7.js → linked-server-J5G457OA.js} +1 -1
- package/dist/host/{rbac-api-LZVENREG.js → rbac-api-PY22W5UY.js} +5 -1
- package/dist/host/testing-host.js +9 -6
- package/dist/host/types/src/types/host-sidebar-preferences.d.ts +2 -0
- package/dist/host/types/src/types/index.d.ts +8 -0
- package/dist/host/types/src/ui/api/look-api.d.ts +2 -0
- package/dist/host/types/src/ui/api/plugins-api.d.ts +3 -2
- package/dist/host/types/src/ui/api/rbac-api.d.ts +19 -1
- package/dist/host/types/src/ui/components/data-view.d.ts +1 -1
- package/dist/host/types/src/ui/lib/app-return.d.ts +13 -0
- package/dist/host/types/src/ui/main-axios.d.ts +1 -1
- package/dist/host/types/src/ui/plugin-host/loader.d.ts +6 -0
- package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +1 -1
- package/dist/host/types/src/ui/plugins/plugin-model.d.ts +1 -1
- package/dist/host/types/src/ui/plugins/use-plugins-manager.d.ts +1 -1
- package/dist/host/types/src/ui/shell/tab-registry.d.ts +2 -0
- package/dist/host/ui.js +418 -388
- package/dist/host/{use-permissions-BYCDS5PD.js → use-permissions-EWRI3WLT.js} +1 -1
- package/dist/host-commands.d.ts +9 -6
- package/dist/host-commands.js +38 -11
- package/dist/host-scripts/docker.d.ts +137 -0
- package/dist/host-scripts/docker.js +345 -0
- package/dist/host-scripts/index.d.ts +15 -0
- package/dist/host-scripts/index.js +15 -0
- package/dist/host-scripts/journal.d.ts +45 -0
- package/dist/host-scripts/journal.js +194 -0
- package/dist/host-scripts/metrics.d.ts +134 -0
- package/dist/host-scripts/metrics.js +320 -0
- package/dist/host-scripts/openrc.d.ts +6 -0
- package/dist/host-scripts/openrc.js +90 -0
- package/dist/host-scripts/shell.d.ts +43 -0
- package/dist/host-scripts/shell.js +95 -0
- package/dist/host-scripts/systemd.d.ts +73 -0
- package/dist/host-scripts/systemd.js +320 -0
- package/dist/host-scripts/text.d.ts +23 -0
- package/dist/host-scripts/text.js +111 -0
- package/dist/manifest.d.ts +28 -1
- package/dist/manifest.js +108 -1
- package/package.json +9 -1
- package/schema/manifest.schema.json +42 -0
|
@@ -0,0 +1,1526 @@
|
|
|
1
|
+
const __termixNoGlob = () => ({});
|
|
2
|
+
import {
|
|
3
|
+
arrangeCompanions,
|
|
4
|
+
companionApplies,
|
|
5
|
+
companionKey,
|
|
6
|
+
getPanel,
|
|
7
|
+
getSettingsComponent,
|
|
8
|
+
hostToSSHHost,
|
|
9
|
+
invokeAction,
|
|
10
|
+
patchCompanionState,
|
|
11
|
+
resolveCompanionLayout,
|
|
12
|
+
useActionSlot,
|
|
13
|
+
useCompanionDefs,
|
|
14
|
+
useCompanionLayoutDefs,
|
|
15
|
+
useCompanionState,
|
|
16
|
+
useIsMobile,
|
|
17
|
+
useSettingsComponents
|
|
18
|
+
} from "./chunk-HQ6NNFSX.js";
|
|
19
|
+
import {
|
|
20
|
+
usePermissions
|
|
21
|
+
} from "./chunk-JRV2HIDK.js";
|
|
22
|
+
import {
|
|
23
|
+
Button,
|
|
24
|
+
DefaultsOnly,
|
|
25
|
+
FakeSwitch,
|
|
26
|
+
HostDefaultBadge,
|
|
27
|
+
Input,
|
|
28
|
+
PluginIcon,
|
|
29
|
+
SectionCard,
|
|
30
|
+
SettingRow,
|
|
31
|
+
hasVisibleFields,
|
|
32
|
+
isFieldShown
|
|
33
|
+
} from "./chunk-7J4QNGFM.js";
|
|
34
|
+
import {
|
|
35
|
+
ArrowDownToLine,
|
|
36
|
+
ArrowLeftToLine,
|
|
37
|
+
ArrowRightToLine,
|
|
38
|
+
Check,
|
|
39
|
+
Checkbox,
|
|
40
|
+
CheckboxIndicator,
|
|
41
|
+
CircleSlash,
|
|
42
|
+
DocsLink,
|
|
43
|
+
Eye,
|
|
44
|
+
EyeOff,
|
|
45
|
+
LayoutPanelLeft,
|
|
46
|
+
OPEN_PLUGINS_EVENT,
|
|
47
|
+
PLUGINS_CHANGED_EVENT,
|
|
48
|
+
PanelRight,
|
|
49
|
+
Puzzle,
|
|
50
|
+
Select,
|
|
51
|
+
SelectContent,
|
|
52
|
+
SelectItem,
|
|
53
|
+
SelectTrigger,
|
|
54
|
+
SelectValue,
|
|
55
|
+
Server,
|
|
56
|
+
Settings,
|
|
57
|
+
X,
|
|
58
|
+
cn,
|
|
59
|
+
getPlugins,
|
|
60
|
+
isRedactedSecret,
|
|
61
|
+
pluginKey,
|
|
62
|
+
pluginName,
|
|
63
|
+
usePluginScope
|
|
64
|
+
} from "./chunk-VKNGNEF3.js";
|
|
65
|
+
import {
|
|
66
|
+
getTabType,
|
|
67
|
+
unregisteredViewStatus,
|
|
68
|
+
usePluginStore
|
|
69
|
+
} from "./chunk-P26QKKU5.js";
|
|
70
|
+
import {
|
|
71
|
+
init_define_import_meta_env
|
|
72
|
+
} from "./chunk-YGYBM7SQ.js";
|
|
73
|
+
|
|
74
|
+
// src/ui/components/checkbox.tsx
|
|
75
|
+
init_define_import_meta_env();
|
|
76
|
+
import { jsx } from "react/jsx-runtime";
|
|
77
|
+
function Checkbox2({
|
|
78
|
+
className,
|
|
79
|
+
...props
|
|
80
|
+
}) {
|
|
81
|
+
return /* @__PURE__ */ jsx(
|
|
82
|
+
Checkbox,
|
|
83
|
+
{
|
|
84
|
+
"data-slot": "checkbox",
|
|
85
|
+
className: cn(
|
|
86
|
+
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-none border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
|
87
|
+
className
|
|
88
|
+
),
|
|
89
|
+
...props,
|
|
90
|
+
children: /* @__PURE__ */ jsx(
|
|
91
|
+
CheckboxIndicator,
|
|
92
|
+
{
|
|
93
|
+
"data-slot": "checkbox-indicator",
|
|
94
|
+
className: "flex items-center justify-center text-current transition-none",
|
|
95
|
+
children: /* @__PURE__ */ jsx(Check, { className: "size-3.5" })
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/ui/components/password-input.tsx
|
|
103
|
+
init_define_import_meta_env();
|
|
104
|
+
import * as React from "react";
|
|
105
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
106
|
+
var PasswordInput = React.forwardRef(({ className, ...props }, ref) => {
|
|
107
|
+
const [showPassword, setShowPassword] = React.useState(false);
|
|
108
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [
|
|
109
|
+
/* @__PURE__ */ jsx2(
|
|
110
|
+
Input,
|
|
111
|
+
{
|
|
112
|
+
ref,
|
|
113
|
+
type: showPassword ? "text" : "password",
|
|
114
|
+
className: cn("h-11 text-base pr-12", className),
|
|
115
|
+
...props
|
|
116
|
+
}
|
|
117
|
+
),
|
|
118
|
+
/* @__PURE__ */ jsx2(
|
|
119
|
+
"button",
|
|
120
|
+
{
|
|
121
|
+
type: "button",
|
|
122
|
+
onClick: () => setShowPassword((prev) => !prev),
|
|
123
|
+
className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition",
|
|
124
|
+
"aria-label": showPassword ? "Hide password" : "Show password",
|
|
125
|
+
children: showPassword ? /* @__PURE__ */ jsx2(EyeOff, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx2(Eye, { className: "h-5 w-5" })
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
] });
|
|
129
|
+
});
|
|
130
|
+
PasswordInput.displayName = "PasswordInput";
|
|
131
|
+
|
|
132
|
+
// src/ui/components/textarea.tsx
|
|
133
|
+
init_define_import_meta_env();
|
|
134
|
+
import * as React2 from "react";
|
|
135
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
136
|
+
var Textarea = React2.forwardRef(
|
|
137
|
+
({ className, ...props }, ref) => {
|
|
138
|
+
return /* @__PURE__ */ jsx3(
|
|
139
|
+
"textarea",
|
|
140
|
+
{
|
|
141
|
+
className: cn(
|
|
142
|
+
"flex min-h-[80px] w-full rounded-none border border-input bg-transparent dark:bg-input/30 px-3 py-2 text-sm shadow-xs transition-[color,box-shadow] duration-200 outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 disabled:pointer-events-none",
|
|
143
|
+
className
|
|
144
|
+
),
|
|
145
|
+
ref,
|
|
146
|
+
...props
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
Textarea.displayName = "Textarea";
|
|
152
|
+
|
|
153
|
+
// src/ui/hooks/use-installed-plugins.ts
|
|
154
|
+
init_define_import_meta_env();
|
|
155
|
+
import { useEffect, useState as useState2 } from "react";
|
|
156
|
+
function useInstalledPlugins() {
|
|
157
|
+
const [plugins, setPlugins] = useState2([]);
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
let cancelled = false;
|
|
160
|
+
let request = 0;
|
|
161
|
+
const load = () => {
|
|
162
|
+
const current = ++request;
|
|
163
|
+
void getPlugins().then((loaded) => {
|
|
164
|
+
if (!cancelled && current === request) setPlugins(loaded);
|
|
165
|
+
}).catch(() => {
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
load();
|
|
169
|
+
window.addEventListener(PLUGINS_CHANGED_EVENT, load);
|
|
170
|
+
return () => {
|
|
171
|
+
cancelled = true;
|
|
172
|
+
window.removeEventListener(PLUGINS_CHANGED_EVENT, load);
|
|
173
|
+
};
|
|
174
|
+
}, []);
|
|
175
|
+
return plugins;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/ui/settings/SettingsFields.tsx
|
|
179
|
+
init_define_import_meta_env();
|
|
180
|
+
import { useTranslation } from "react-i18next";
|
|
181
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
182
|
+
function SettingsFieldRow({
|
|
183
|
+
pluginId,
|
|
184
|
+
field,
|
|
185
|
+
values,
|
|
186
|
+
setValue,
|
|
187
|
+
running,
|
|
188
|
+
error,
|
|
189
|
+
disabled = false,
|
|
190
|
+
defaultKey
|
|
191
|
+
}) {
|
|
192
|
+
const { t } = useTranslation();
|
|
193
|
+
useSettingsComponents();
|
|
194
|
+
const label = (key) => key ? t(pluginKey(pluginId, key)) : "";
|
|
195
|
+
if (field.type === "custom") {
|
|
196
|
+
const Component = getSettingsComponent(pluginId, field.component);
|
|
197
|
+
if (!Component) return null;
|
|
198
|
+
return /* @__PURE__ */ jsx4(
|
|
199
|
+
Component,
|
|
200
|
+
{
|
|
201
|
+
pluginId,
|
|
202
|
+
values,
|
|
203
|
+
setValue,
|
|
204
|
+
running
|
|
205
|
+
}
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
const value = values[field.key];
|
|
209
|
+
const readOnly = disabled || !running;
|
|
210
|
+
const description = field.descriptionKey ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
211
|
+
label(field.descriptionKey),
|
|
212
|
+
error && /* @__PURE__ */ jsx4("span", { className: "block text-destructive mt-0.5", children: error })
|
|
213
|
+
] }) : error ? /* @__PURE__ */ jsx4("span", { className: "text-destructive", children: error }) : void 0;
|
|
214
|
+
const control = /* @__PURE__ */ jsx4(
|
|
215
|
+
FieldControl,
|
|
216
|
+
{
|
|
217
|
+
pluginId,
|
|
218
|
+
field,
|
|
219
|
+
value,
|
|
220
|
+
setValue,
|
|
221
|
+
readOnly
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
if (field.type === "boolean") {
|
|
225
|
+
return /* @__PURE__ */ jsx4(
|
|
226
|
+
SettingRow,
|
|
227
|
+
{
|
|
228
|
+
label: label(field.labelKey),
|
|
229
|
+
description,
|
|
230
|
+
badge: error ? t("common.error") : void 0,
|
|
231
|
+
defaultKey,
|
|
232
|
+
children: control
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
return /* @__PURE__ */ jsx4(DefaultsOnly, { settingKey: defaultKey ?? "", children: /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-1.5 py-3 border-b border-border last:border-0 min-w-0", children: [
|
|
237
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1.5 flex-wrap", children: [
|
|
238
|
+
/* @__PURE__ */ jsx4("span", { className: "text-sm font-medium leading-snug", children: label(field.labelKey) }),
|
|
239
|
+
error && /* @__PURE__ */ jsx4("span", { className: "text-[10px] font-bold text-warning border border-warning/40 px-1 shrink-0", children: t("common.error") }),
|
|
240
|
+
defaultKey && /* @__PURE__ */ jsx4(HostDefaultBadge, { settingKey: defaultKey })
|
|
241
|
+
] }),
|
|
242
|
+
description && /* @__PURE__ */ jsx4("span", { className: "text-xs text-muted-foreground leading-snug", children: description }),
|
|
243
|
+
/* @__PURE__ */ jsx4("div", { className: "min-w-0", children: control })
|
|
244
|
+
] }) });
|
|
245
|
+
}
|
|
246
|
+
function FieldControl({
|
|
247
|
+
pluginId,
|
|
248
|
+
field,
|
|
249
|
+
value,
|
|
250
|
+
setValue,
|
|
251
|
+
readOnly
|
|
252
|
+
}) {
|
|
253
|
+
const { t } = useTranslation();
|
|
254
|
+
const label = (key) => key ? t(pluginKey(pluginId, key)) : "";
|
|
255
|
+
const placeholder = field.placeholderKey ? label(field.placeholderKey) : void 0;
|
|
256
|
+
switch (field.type) {
|
|
257
|
+
case "boolean":
|
|
258
|
+
return /* @__PURE__ */ jsx4(
|
|
259
|
+
FakeSwitch,
|
|
260
|
+
{
|
|
261
|
+
checked: value === true,
|
|
262
|
+
disabled: readOnly,
|
|
263
|
+
onChange: (next) => setValue(field.key, next)
|
|
264
|
+
}
|
|
265
|
+
);
|
|
266
|
+
case "number":
|
|
267
|
+
return /* @__PURE__ */ jsx4(
|
|
268
|
+
Input,
|
|
269
|
+
{
|
|
270
|
+
type: "number",
|
|
271
|
+
className: "h-8 w-full max-w-40 text-sm",
|
|
272
|
+
disabled: readOnly,
|
|
273
|
+
min: field.min,
|
|
274
|
+
max: field.max,
|
|
275
|
+
placeholder,
|
|
276
|
+
value: value === void 0 || value === null ? "" : String(value),
|
|
277
|
+
onChange: (event) => setValue(
|
|
278
|
+
field.key,
|
|
279
|
+
event.target.value === "" ? null : Number(event.target.value)
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
case "select":
|
|
284
|
+
return /* @__PURE__ */ jsxs2(
|
|
285
|
+
Select,
|
|
286
|
+
{
|
|
287
|
+
disabled: readOnly,
|
|
288
|
+
value: typeof value === "string" ? value : "",
|
|
289
|
+
onValueChange: (next) => setValue(field.key, next),
|
|
290
|
+
children: [
|
|
291
|
+
/* @__PURE__ */ jsx4(SelectTrigger, { className: "h-8 w-full min-w-0 text-sm", children: /* @__PURE__ */ jsx4(SelectValue, { placeholder }) }),
|
|
292
|
+
/* @__PURE__ */ jsx4(SelectContent, { children: (field.options ?? []).map((option) => /* @__PURE__ */ jsx4(SelectItem, { value: option.value, children: label(option.labelKey) }, option.value)) })
|
|
293
|
+
]
|
|
294
|
+
}
|
|
295
|
+
);
|
|
296
|
+
case "multiselect": {
|
|
297
|
+
const selected = Array.isArray(value) ? value : [];
|
|
298
|
+
return /* @__PURE__ */ jsx4("div", { className: "flex flex-col gap-1.5", children: (field.options ?? []).map((option) => /* @__PURE__ */ jsxs2(
|
|
299
|
+
"label",
|
|
300
|
+
{
|
|
301
|
+
className: "flex items-center gap-2 text-xs text-muted-foreground",
|
|
302
|
+
children: [
|
|
303
|
+
/* @__PURE__ */ jsx4(
|
|
304
|
+
Checkbox2,
|
|
305
|
+
{
|
|
306
|
+
disabled: readOnly,
|
|
307
|
+
checked: selected.includes(option.value),
|
|
308
|
+
onCheckedChange: (checked) => setValue(
|
|
309
|
+
field.key,
|
|
310
|
+
checked ? [...selected, option.value] : selected.filter((entry) => entry !== option.value)
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
),
|
|
314
|
+
/* @__PURE__ */ jsx4("span", { children: label(option.labelKey) })
|
|
315
|
+
]
|
|
316
|
+
},
|
|
317
|
+
option.value
|
|
318
|
+
)) });
|
|
319
|
+
}
|
|
320
|
+
case "secret":
|
|
321
|
+
return /* @__PURE__ */ jsx4(
|
|
322
|
+
SecretControl,
|
|
323
|
+
{
|
|
324
|
+
field,
|
|
325
|
+
value,
|
|
326
|
+
setValue,
|
|
327
|
+
readOnly,
|
|
328
|
+
placeholder
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
case "textarea":
|
|
332
|
+
case "json":
|
|
333
|
+
return /* @__PURE__ */ jsx4(
|
|
334
|
+
Textarea,
|
|
335
|
+
{
|
|
336
|
+
className: "w-full text-sm font-mono min-h-16",
|
|
337
|
+
disabled: readOnly,
|
|
338
|
+
placeholder,
|
|
339
|
+
value: typeof value === "string" ? value : value === void 0 || value === null ? "" : JSON.stringify(value, null, 2),
|
|
340
|
+
onChange: (event) => setValue(field.key, event.target.value)
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
default:
|
|
344
|
+
return /* @__PURE__ */ jsx4(
|
|
345
|
+
Input,
|
|
346
|
+
{
|
|
347
|
+
className: "h-8 w-full text-sm",
|
|
348
|
+
disabled: readOnly,
|
|
349
|
+
placeholder,
|
|
350
|
+
value: typeof value === "string" ? value : "",
|
|
351
|
+
onChange: (event) => setValue(field.key, event.target.value)
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function SecretControl({
|
|
357
|
+
field,
|
|
358
|
+
value,
|
|
359
|
+
setValue,
|
|
360
|
+
readOnly,
|
|
361
|
+
placeholder
|
|
362
|
+
}) {
|
|
363
|
+
const { t } = useTranslation();
|
|
364
|
+
const redacted = isRedactedSecret(value);
|
|
365
|
+
const stored = redacted && value.set;
|
|
366
|
+
if (redacted) {
|
|
367
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
368
|
+
/* @__PURE__ */ jsx4("span", { className: "text-xs text-muted-foreground", children: stored ? t("settings.secretSet") : t("settings.secretNotSet") }),
|
|
369
|
+
/* @__PURE__ */ jsx4(
|
|
370
|
+
Button,
|
|
371
|
+
{
|
|
372
|
+
variant: "outline",
|
|
373
|
+
size: "sm",
|
|
374
|
+
className: "h-7 text-xs",
|
|
375
|
+
disabled: readOnly,
|
|
376
|
+
onClick: () => setValue(field.key, ""),
|
|
377
|
+
children: stored ? t("settings.secretReplace") : t("common.edit")
|
|
378
|
+
}
|
|
379
|
+
)
|
|
380
|
+
] });
|
|
381
|
+
}
|
|
382
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
383
|
+
/* @__PURE__ */ jsx4("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx4(
|
|
384
|
+
PasswordInput,
|
|
385
|
+
{
|
|
386
|
+
className: "h-8 w-full text-sm",
|
|
387
|
+
disabled: readOnly,
|
|
388
|
+
placeholder,
|
|
389
|
+
value: typeof value === "string" ? value : "",
|
|
390
|
+
onChange: (event) => setValue(field.key, event.target.value)
|
|
391
|
+
}
|
|
392
|
+
) }),
|
|
393
|
+
/* @__PURE__ */ jsx4(
|
|
394
|
+
Button,
|
|
395
|
+
{
|
|
396
|
+
variant: "ghost",
|
|
397
|
+
size: "sm",
|
|
398
|
+
className: "h-7 text-xs",
|
|
399
|
+
disabled: readOnly,
|
|
400
|
+
onClick: () => setValue(field.key, { set: stored }),
|
|
401
|
+
children: t("common.cancel")
|
|
402
|
+
}
|
|
403
|
+
)
|
|
404
|
+
] });
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// src/ui/settings/HostPluginSections.tsx
|
|
408
|
+
init_define_import_meta_env();
|
|
409
|
+
import { useMemo } from "react";
|
|
410
|
+
import { useTranslation as useTranslation2 } from "react-i18next";
|
|
411
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
412
|
+
function usePluginHostSections() {
|
|
413
|
+
const plugins = useInstalledPlugins();
|
|
414
|
+
return useMemo(
|
|
415
|
+
() => plugins.filter((plugin) => {
|
|
416
|
+
if (!plugin.enabled) return false;
|
|
417
|
+
const host = plugin.contributes?.settings?.host;
|
|
418
|
+
return !!host && (hasVisibleFields(host.fields) || !!host.enableKey);
|
|
419
|
+
}),
|
|
420
|
+
[plugins]
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
function hostFieldDefaults(plugin) {
|
|
424
|
+
const host = plugin.contributes?.settings?.host;
|
|
425
|
+
if (!host) return {};
|
|
426
|
+
const defaults = {};
|
|
427
|
+
if (host.enableKey) defaults[host.enableKey] = host.enableDefault ?? false;
|
|
428
|
+
for (const field of host.fields) {
|
|
429
|
+
if (field.default !== void 0) defaults[field.key] = field.default;
|
|
430
|
+
}
|
|
431
|
+
return defaults;
|
|
432
|
+
}
|
|
433
|
+
function HostPluginSections({
|
|
434
|
+
plugins,
|
|
435
|
+
values,
|
|
436
|
+
setValue
|
|
437
|
+
}) {
|
|
438
|
+
if (plugins.length === 0) return null;
|
|
439
|
+
return /* @__PURE__ */ jsx5("div", { className: "flex flex-col gap-2", children: plugins.map((plugin) => /* @__PURE__ */ jsx5(
|
|
440
|
+
HostPluginSection,
|
|
441
|
+
{
|
|
442
|
+
plugin,
|
|
443
|
+
values: values[plugin.id] ?? {},
|
|
444
|
+
setValue: (key, value) => setValue(plugin.id, key, value)
|
|
445
|
+
},
|
|
446
|
+
plugin.id
|
|
447
|
+
)) });
|
|
448
|
+
}
|
|
449
|
+
function HostFeatureFields({
|
|
450
|
+
form,
|
|
451
|
+
updateForm
|
|
452
|
+
}) {
|
|
453
|
+
const pluginId = usePluginScope();
|
|
454
|
+
const plugin = usePluginHostSections().find((p) => p.id === pluginId);
|
|
455
|
+
if (!plugin) return null;
|
|
456
|
+
const values = form?.pluginSettings ?? {};
|
|
457
|
+
return /* @__PURE__ */ jsx5(
|
|
458
|
+
HostPluginSection,
|
|
459
|
+
{
|
|
460
|
+
plugin,
|
|
461
|
+
values: values[plugin.id] ?? {},
|
|
462
|
+
setValue: (key, value) => updateForm((current) => {
|
|
463
|
+
const all = current.pluginSettings ?? {};
|
|
464
|
+
return {
|
|
465
|
+
...current,
|
|
466
|
+
pluginSettings: {
|
|
467
|
+
...all,
|
|
468
|
+
[plugin.id]: { ...all[plugin.id] ?? {}, [key]: value }
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
})
|
|
472
|
+
}
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
function HostPluginSection({
|
|
476
|
+
plugin,
|
|
477
|
+
values,
|
|
478
|
+
setValue
|
|
479
|
+
}) {
|
|
480
|
+
const { t } = useTranslation2();
|
|
481
|
+
const host = plugin.contributes?.settings?.host;
|
|
482
|
+
if (!host) return null;
|
|
483
|
+
const shown = { ...hostFieldDefaults(plugin), ...values };
|
|
484
|
+
const running = plugin.enabled && plugin.state !== "failed";
|
|
485
|
+
const enableKey = host.enableKey;
|
|
486
|
+
const enabled = enableKey ? shown[enableKey] === true : true;
|
|
487
|
+
return /* @__PURE__ */ jsxs3(
|
|
488
|
+
SectionCard,
|
|
489
|
+
{
|
|
490
|
+
title: pluginName(t, plugin),
|
|
491
|
+
icon: /* @__PURE__ */ jsx5(PluginIcon, { name: plugin.icon, className: "size-3.5" }),
|
|
492
|
+
action: /* @__PURE__ */ jsx5(DocsLink, { href: plugin.docs }),
|
|
493
|
+
children: [
|
|
494
|
+
!running && /* @__PURE__ */ jsx5("div", { className: "my-3 border border-warning/40 bg-warning/10 px-3 py-2 text-xs text-warning", children: t("settings.featureUnavailable", {
|
|
495
|
+
name: pluginName(t, plugin)
|
|
496
|
+
}) }),
|
|
497
|
+
enableKey && /* @__PURE__ */ jsx5(
|
|
498
|
+
SettingsFieldRow,
|
|
499
|
+
{
|
|
500
|
+
pluginId: plugin.id,
|
|
501
|
+
field: {
|
|
502
|
+
key: enableKey,
|
|
503
|
+
type: "boolean",
|
|
504
|
+
labelKey: host.enableLabelKey,
|
|
505
|
+
descriptionKey: host.enableDescriptionKey
|
|
506
|
+
},
|
|
507
|
+
values: shown,
|
|
508
|
+
setValue,
|
|
509
|
+
running,
|
|
510
|
+
defaultKey: `${plugin.id}.${enableKey}`
|
|
511
|
+
}
|
|
512
|
+
),
|
|
513
|
+
enabled && host.fields.filter((field) => isFieldShown(field, shown)).map((field) => /* @__PURE__ */ jsx5(
|
|
514
|
+
SettingsFieldRow,
|
|
515
|
+
{
|
|
516
|
+
pluginId: plugin.id,
|
|
517
|
+
field,
|
|
518
|
+
values: shown,
|
|
519
|
+
setValue,
|
|
520
|
+
running,
|
|
521
|
+
defaultKey: `${plugin.id}.${field.key}`
|
|
522
|
+
},
|
|
523
|
+
field.key
|
|
524
|
+
))
|
|
525
|
+
]
|
|
526
|
+
}
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// src/ui/shell/ActionSlot.tsx
|
|
531
|
+
init_define_import_meta_env();
|
|
532
|
+
import React3 from "react";
|
|
533
|
+
import { useTranslation as useTranslation3 } from "react-i18next";
|
|
534
|
+
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
535
|
+
function ActionSlot({
|
|
536
|
+
slotId,
|
|
537
|
+
context,
|
|
538
|
+
className,
|
|
539
|
+
enabled = true,
|
|
540
|
+
renderItem,
|
|
541
|
+
hideLabels,
|
|
542
|
+
when
|
|
543
|
+
}) {
|
|
544
|
+
const { t } = useTranslation3();
|
|
545
|
+
const contributions = useActionSlot(slotId, when).filter(
|
|
546
|
+
(contribution) => contribution.kind !== "component"
|
|
547
|
+
);
|
|
548
|
+
if (!enabled || contributions.length === 0) return null;
|
|
549
|
+
return /* @__PURE__ */ jsx6(Fragment2, { children: contributions.map((contribution) => {
|
|
550
|
+
const invoke = () => {
|
|
551
|
+
void invokeAction(contribution.actionId, ...context?.() ?? []);
|
|
552
|
+
};
|
|
553
|
+
if (renderItem) {
|
|
554
|
+
return /* @__PURE__ */ jsx6(React3.Fragment, { children: renderItem(contribution, invoke) }, contribution.actionId);
|
|
555
|
+
}
|
|
556
|
+
const Icon = contribution.icon;
|
|
557
|
+
const label = t(contribution.titleKey);
|
|
558
|
+
return /* @__PURE__ */ jsxs4(
|
|
559
|
+
"button",
|
|
560
|
+
{
|
|
561
|
+
type: "button",
|
|
562
|
+
className,
|
|
563
|
+
"aria-label": label,
|
|
564
|
+
title: label,
|
|
565
|
+
onClick: invoke,
|
|
566
|
+
children: [
|
|
567
|
+
Icon && /* @__PURE__ */ jsx6(Icon, { className: "size-4 shrink-0" }),
|
|
568
|
+
!hideLabels && label
|
|
569
|
+
]
|
|
570
|
+
},
|
|
571
|
+
contribution.actionId
|
|
572
|
+
);
|
|
573
|
+
}) });
|
|
574
|
+
}
|
|
575
|
+
function ComponentSlot({
|
|
576
|
+
slotId,
|
|
577
|
+
props,
|
|
578
|
+
when
|
|
579
|
+
}) {
|
|
580
|
+
const contributions = useActionSlot(slotId, when).filter(
|
|
581
|
+
(contribution) => contribution.kind === "component" && contribution.component
|
|
582
|
+
);
|
|
583
|
+
if (contributions.length === 0) return null;
|
|
584
|
+
return /* @__PURE__ */ jsx6(Fragment2, { children: contributions.map((contribution) => {
|
|
585
|
+
const Contributed = contribution.component;
|
|
586
|
+
return /* @__PURE__ */ jsx6(Contributed, { ...props ?? {} }, contribution.actionId);
|
|
587
|
+
}) });
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// src/ui/shell/tab-handles.ts
|
|
591
|
+
init_define_import_meta_env();
|
|
592
|
+
import { useSyncExternalStore } from "react";
|
|
593
|
+
var version = 0;
|
|
594
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
595
|
+
var callbacks = /* @__PURE__ */ new WeakMap();
|
|
596
|
+
function subscribe(listener) {
|
|
597
|
+
listeners.add(listener);
|
|
598
|
+
return () => listeners.delete(listener);
|
|
599
|
+
}
|
|
600
|
+
function trackTabHandle(ref) {
|
|
601
|
+
if (!ref) return void 0;
|
|
602
|
+
let callback = callbacks.get(ref);
|
|
603
|
+
if (!callback) {
|
|
604
|
+
callback = (value) => {
|
|
605
|
+
if (ref.current === value) return;
|
|
606
|
+
ref.current = value;
|
|
607
|
+
version += 1;
|
|
608
|
+
for (const listener of listeners) listener();
|
|
609
|
+
};
|
|
610
|
+
callbacks.set(ref, callback);
|
|
611
|
+
}
|
|
612
|
+
return callback;
|
|
613
|
+
}
|
|
614
|
+
function useTabHandlesVersion() {
|
|
615
|
+
return useSyncExternalStore(
|
|
616
|
+
subscribe,
|
|
617
|
+
() => version,
|
|
618
|
+
() => version
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// src/ui/plugin-host/PluginViewPlaceholder.tsx
|
|
623
|
+
init_define_import_meta_env();
|
|
624
|
+
import { useTranslation as useTranslation4 } from "react-i18next";
|
|
625
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
626
|
+
function PluginViewPlaceholder({
|
|
627
|
+
kind,
|
|
628
|
+
viewId,
|
|
629
|
+
compact = false
|
|
630
|
+
}) {
|
|
631
|
+
const { t } = useTranslation4();
|
|
632
|
+
usePluginStore();
|
|
633
|
+
const { has } = usePermissions();
|
|
634
|
+
const { status, owner } = unregisteredViewStatus(kind, viewId);
|
|
635
|
+
const name = owner ? pluginName(t, owner.summary) : viewId;
|
|
636
|
+
if (status === "loading") {
|
|
637
|
+
return /* @__PURE__ */ jsx7(
|
|
638
|
+
"div",
|
|
639
|
+
{
|
|
640
|
+
className: "flex h-full w-full items-center justify-center",
|
|
641
|
+
"aria-label": t("plugins.runtime.loading", { name }),
|
|
642
|
+
children: /* @__PURE__ */ jsx7("div", { className: "size-5 rounded-full border-2 border-muted-foreground/30 border-t-muted-foreground/70 animate-spin" })
|
|
643
|
+
}
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
const title = status === "failed" ? t("plugins.runtime.failed", { name }) : owner ? t("plugins.runtime.needsPlugin", { name }) : t("plugins.runtime.needsUnknownPlugin");
|
|
647
|
+
const hint = status === "failed" ? t("plugins.runtime.failedHint") : status === "disabled" ? t("plugins.runtime.disabledHint") : t("plugins.runtime.missingHint");
|
|
648
|
+
return /* @__PURE__ */ jsxs5(
|
|
649
|
+
"div",
|
|
650
|
+
{
|
|
651
|
+
"data-testid": "plugin-view-placeholder",
|
|
652
|
+
"data-status": status,
|
|
653
|
+
className: `flex h-full w-full flex-col items-center justify-center gap-2 text-center ${compact ? "p-3" : "p-6"}`,
|
|
654
|
+
children: [
|
|
655
|
+
/* @__PURE__ */ jsx7(CircleSlash, { className: "size-6 text-muted-foreground/40" }),
|
|
656
|
+
/* @__PURE__ */ jsx7("span", { className: "text-sm font-semibold text-muted-foreground", children: title }),
|
|
657
|
+
/* @__PURE__ */ jsx7("span", { className: "text-xs text-muted-foreground/70", children: hint }),
|
|
658
|
+
!compact && status !== "failed" && has("admin.plugins.manage") && /* @__PURE__ */ jsx7(
|
|
659
|
+
Button,
|
|
660
|
+
{
|
|
661
|
+
variant: "outline",
|
|
662
|
+
size: "sm",
|
|
663
|
+
className: "mt-1",
|
|
664
|
+
onClick: () => window.dispatchEvent(new CustomEvent(OPEN_PLUGINS_EVENT)),
|
|
665
|
+
children: t("plugins.manager.browsePlugins")
|
|
666
|
+
}
|
|
667
|
+
)
|
|
668
|
+
]
|
|
669
|
+
}
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/ui/lib/adaptive-resource-budget.ts
|
|
674
|
+
init_define_import_meta_env();
|
|
675
|
+
|
|
676
|
+
// src/ui/lib/local-adaptive-engine.ts
|
|
677
|
+
init_define_import_meta_env();
|
|
678
|
+
var LOCAL_ADAPTIVE_ENGINE_KEY = "termix.local.adaptive-engine.v1";
|
|
679
|
+
var VERSION = 1;
|
|
680
|
+
var HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
681
|
+
var MAX_SCOPES = 128;
|
|
682
|
+
var MAX_ACTIONS_PER_SCOPE = 12;
|
|
683
|
+
var DEFAULT_MIN_EVIDENCE = 2.5;
|
|
684
|
+
var DEFAULT_MIN_SHARE = 0.6;
|
|
685
|
+
var DEFAULT_MIN_MARGIN = 1;
|
|
686
|
+
var EWMA_ALPHA = 0.25;
|
|
687
|
+
function emptyStore() {
|
|
688
|
+
return { version: VERSION, scopes: {} };
|
|
689
|
+
}
|
|
690
|
+
function finiteNonNegative(value) {
|
|
691
|
+
return Number.isFinite(value) && Number(value) >= 0 ? Number(value) : 0;
|
|
692
|
+
}
|
|
693
|
+
function sanitizeAction(value) {
|
|
694
|
+
if (!value || typeof value !== "object") return null;
|
|
695
|
+
const stat = value;
|
|
696
|
+
if (!Number.isFinite(stat.weight) || Number(stat.weight) < 0 || !Number.isFinite(stat.updatedAt)) {
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
const latencyEwmaMs = finiteNonNegative(stat.latencyEwmaMs);
|
|
700
|
+
return {
|
|
701
|
+
weight: Number(stat.weight),
|
|
702
|
+
updatedAt: Number(stat.updatedAt),
|
|
703
|
+
observations: finiteNonNegative(stat.observations),
|
|
704
|
+
successes: finiteNonNegative(stat.successes),
|
|
705
|
+
cancellations: finiteNonNegative(stat.cancellations),
|
|
706
|
+
fallbacks: finiteNonNegative(stat.fallbacks),
|
|
707
|
+
...latencyEwmaMs > 0 ? { latencyEwmaMs } : {}
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
function sanitizeStore(value) {
|
|
711
|
+
if (!value || typeof value !== "object") return emptyStore();
|
|
712
|
+
const candidate = value;
|
|
713
|
+
if (candidate.version !== VERSION || !candidate.scopes || typeof candidate.scopes !== "object" || Array.isArray(candidate.scopes)) {
|
|
714
|
+
return emptyStore();
|
|
715
|
+
}
|
|
716
|
+
const scopes2 = {};
|
|
717
|
+
for (const [scope, rawScope] of Object.entries(candidate.scopes)) {
|
|
718
|
+
if (!rawScope || typeof rawScope !== "object") continue;
|
|
719
|
+
const value2 = rawScope;
|
|
720
|
+
if (!Number.isFinite(value2.updatedAt) || !value2.actions || typeof value2.actions !== "object" || Array.isArray(value2.actions)) {
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
const actions = {};
|
|
724
|
+
for (const [action, rawStat] of Object.entries(value2.actions)) {
|
|
725
|
+
const stat = sanitizeAction(rawStat);
|
|
726
|
+
if (stat) actions[action] = stat;
|
|
727
|
+
}
|
|
728
|
+
scopes2[scope] = { actions, updatedAt: Number(value2.updatedAt) };
|
|
729
|
+
}
|
|
730
|
+
return { version: VERSION, scopes: scopes2 };
|
|
731
|
+
}
|
|
732
|
+
function readStore() {
|
|
733
|
+
if (typeof localStorage === "undefined") return emptyStore();
|
|
734
|
+
try {
|
|
735
|
+
return sanitizeStore(
|
|
736
|
+
JSON.parse(localStorage.getItem(LOCAL_ADAPTIVE_ENGINE_KEY) ?? "null")
|
|
737
|
+
);
|
|
738
|
+
} catch {
|
|
739
|
+
return emptyStore();
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
function writeStore(store) {
|
|
743
|
+
if (typeof localStorage === "undefined") return;
|
|
744
|
+
try {
|
|
745
|
+
localStorage.setItem(LOCAL_ADAPTIVE_ENGINE_KEY, JSON.stringify(store));
|
|
746
|
+
} catch {
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
function decayedWeight(stat, now) {
|
|
750
|
+
const age = Math.max(0, now - stat.updatedAt);
|
|
751
|
+
return stat.weight * 2 ** (-age / HALF_LIFE_MS);
|
|
752
|
+
}
|
|
753
|
+
function trimStore(store, now) {
|
|
754
|
+
for (const scope of Object.values(store.scopes)) {
|
|
755
|
+
scope.actions = Object.fromEntries(
|
|
756
|
+
Object.entries(scope.actions).sort(
|
|
757
|
+
([, a], [, b]) => decayedWeight(b, now) - decayedWeight(a, now) || b.updatedAt - a.updatedAt
|
|
758
|
+
).slice(0, MAX_ACTIONS_PER_SCOPE)
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
store.scopes = Object.fromEntries(
|
|
762
|
+
Object.entries(store.scopes).sort(([, a], [, b]) => b.updatedAt - a.updatedAt).slice(0, MAX_SCOPES)
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
function updateEwma(previous, value) {
|
|
766
|
+
return previous === void 0 ? value : previous + EWMA_ALPHA * (value - previous);
|
|
767
|
+
}
|
|
768
|
+
function getActionStats(store, scope, action, now) {
|
|
769
|
+
const scopeStats = store.scopes[scope] ?? { actions: {}, updatedAt: now };
|
|
770
|
+
return [
|
|
771
|
+
scopeStats,
|
|
772
|
+
scopeStats.actions[action] ?? {
|
|
773
|
+
weight: 0,
|
|
774
|
+
updatedAt: now,
|
|
775
|
+
observations: 0,
|
|
776
|
+
successes: 0,
|
|
777
|
+
cancellations: 0,
|
|
778
|
+
fallbacks: 0
|
|
779
|
+
}
|
|
780
|
+
];
|
|
781
|
+
}
|
|
782
|
+
function saveActionStats(store, scope, action, scopeStats, actionStats, now) {
|
|
783
|
+
scopeStats.actions[action] = actionStats;
|
|
784
|
+
scopeStats.updatedAt = now;
|
|
785
|
+
store.scopes[scope] = scopeStats;
|
|
786
|
+
trimStore(store, now);
|
|
787
|
+
writeStore(store);
|
|
788
|
+
}
|
|
789
|
+
function recordLocalAdaptiveChoice(scope, action, now = Date.now()) {
|
|
790
|
+
if (!scope || !action) return;
|
|
791
|
+
const store = readStore();
|
|
792
|
+
const [scopeStats, previous] = getActionStats(store, scope, action, now);
|
|
793
|
+
saveActionStats(
|
|
794
|
+
store,
|
|
795
|
+
scope,
|
|
796
|
+
action,
|
|
797
|
+
scopeStats,
|
|
798
|
+
{
|
|
799
|
+
...previous,
|
|
800
|
+
weight: decayedWeight(previous, now) + 1,
|
|
801
|
+
updatedAt: now
|
|
802
|
+
},
|
|
803
|
+
now
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
function recordLocalAdaptiveOutcome(scope, action, outcome, now = Date.now()) {
|
|
807
|
+
if (!scope || !action || !Object.values(outcome).some((v) => v !== void 0)) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
const store = readStore();
|
|
811
|
+
const [scopeStats, previous] = getActionStats(store, scope, action, now);
|
|
812
|
+
const latency = finiteNonNegative(outcome.latencyMs);
|
|
813
|
+
saveActionStats(
|
|
814
|
+
store,
|
|
815
|
+
scope,
|
|
816
|
+
action,
|
|
817
|
+
scopeStats,
|
|
818
|
+
{
|
|
819
|
+
...previous,
|
|
820
|
+
updatedAt: now,
|
|
821
|
+
observations: previous.observations + 1,
|
|
822
|
+
successes: previous.successes + (outcome.success === true ? 1 : 0),
|
|
823
|
+
cancellations: previous.cancellations + (outcome.cancelled === true ? 1 : 0),
|
|
824
|
+
fallbacks: previous.fallbacks + (outcome.fallback === true ? 1 : 0),
|
|
825
|
+
...latency > 0 ? { latencyEwmaMs: updateEwma(previous.latencyEwmaMs, latency) } : {}
|
|
826
|
+
},
|
|
827
|
+
now
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
function chooseLocalAdaptiveAction({
|
|
831
|
+
scope,
|
|
832
|
+
candidates,
|
|
833
|
+
fallback,
|
|
834
|
+
now = Date.now(),
|
|
835
|
+
ttlMs = 5 * 60 * 1e3,
|
|
836
|
+
minEvidence = DEFAULT_MIN_EVIDENCE,
|
|
837
|
+
minShare = DEFAULT_MIN_SHARE,
|
|
838
|
+
minMargin = DEFAULT_MIN_MARGIN
|
|
839
|
+
}) {
|
|
840
|
+
const expiresAt = now + Math.max(0, ttlMs);
|
|
841
|
+
const stats = readStore().scopes[scope]?.actions;
|
|
842
|
+
if (!stats || candidates.length === 0) {
|
|
843
|
+
return { action: fallback, confidence: 0, reason: "fallback", expiresAt };
|
|
844
|
+
}
|
|
845
|
+
const ranked = candidates.map((action) => ({
|
|
846
|
+
action,
|
|
847
|
+
weight: stats[action] ? decayedWeight(stats[action], now) : 0
|
|
848
|
+
})).sort((a, b) => b.weight - a.weight);
|
|
849
|
+
const total = ranked.reduce((sum, item) => sum + item.weight, 0);
|
|
850
|
+
const [best, second] = ranked;
|
|
851
|
+
if (!best || total <= 0) {
|
|
852
|
+
return { action: fallback, confidence: 0, reason: "fallback", expiresAt };
|
|
853
|
+
}
|
|
854
|
+
const share = best.weight / total;
|
|
855
|
+
const margin = best.weight - (second?.weight ?? 0);
|
|
856
|
+
const confidence = Math.min(
|
|
857
|
+
1,
|
|
858
|
+
best.weight / Math.max(minEvidence, Number.EPSILON),
|
|
859
|
+
share / Math.max(minShare, Number.EPSILON),
|
|
860
|
+
margin / Math.max(minMargin, Number.EPSILON)
|
|
861
|
+
);
|
|
862
|
+
const learned = best.weight >= minEvidence && share >= minShare && margin >= minMargin;
|
|
863
|
+
return {
|
|
864
|
+
action: learned ? best.action : fallback,
|
|
865
|
+
confidence,
|
|
866
|
+
reason: learned ? "learned" : "insufficient-evidence",
|
|
867
|
+
expiresAt
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
function getLocalAdaptiveStats(scope) {
|
|
871
|
+
return readStore().scopes[scope]?.actions ?? {};
|
|
872
|
+
}
|
|
873
|
+
function clearLocalAdaptiveEngine() {
|
|
874
|
+
if (typeof localStorage === "undefined") return;
|
|
875
|
+
try {
|
|
876
|
+
localStorage.removeItem(LOCAL_ADAPTIVE_ENGINE_KEY);
|
|
877
|
+
} catch {
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// src/ui/lib/adaptive-resource-budget.ts
|
|
882
|
+
var scopes = {
|
|
883
|
+
module: "resource-budget:module",
|
|
884
|
+
network: "resource-budget:network"
|
|
885
|
+
};
|
|
886
|
+
var usefulnessScopes = {
|
|
887
|
+
module: "resource-usefulness:module",
|
|
888
|
+
network: "resource-usefulness:network"
|
|
889
|
+
};
|
|
890
|
+
var PRELOAD_USE_WINDOW_MS = 1e4;
|
|
891
|
+
var pendingPreloads = /* @__PURE__ */ new Map();
|
|
892
|
+
var activeTasks = {
|
|
893
|
+
module: 0,
|
|
894
|
+
network: 0
|
|
895
|
+
};
|
|
896
|
+
var budgets = {
|
|
897
|
+
paused: {
|
|
898
|
+
tier: "paused",
|
|
899
|
+
allowModulePreload: false,
|
|
900
|
+
allowNetworkPrefetch: false,
|
|
901
|
+
maxPrefetchBytes: 0,
|
|
902
|
+
maxConcurrentModulePreloads: 0,
|
|
903
|
+
maxConcurrentNetworkPrefetches: 0,
|
|
904
|
+
reason: "hidden"
|
|
905
|
+
},
|
|
906
|
+
constrained: {
|
|
907
|
+
tier: "constrained",
|
|
908
|
+
allowModulePreload: false,
|
|
909
|
+
allowNetworkPrefetch: false,
|
|
910
|
+
maxPrefetchBytes: 0,
|
|
911
|
+
maxConcurrentModulePreloads: 0,
|
|
912
|
+
maxConcurrentNetworkPrefetches: 0,
|
|
913
|
+
reason: "environment"
|
|
914
|
+
},
|
|
915
|
+
balanced: {
|
|
916
|
+
tier: "balanced",
|
|
917
|
+
allowModulePreload: true,
|
|
918
|
+
allowNetworkPrefetch: true,
|
|
919
|
+
maxPrefetchBytes: 128 * 1024,
|
|
920
|
+
maxConcurrentModulePreloads: 1,
|
|
921
|
+
maxConcurrentNetworkPrefetches: 1,
|
|
922
|
+
reason: "environment"
|
|
923
|
+
},
|
|
924
|
+
eager: {
|
|
925
|
+
tier: "eager",
|
|
926
|
+
allowModulePreload: true,
|
|
927
|
+
allowNetworkPrefetch: true,
|
|
928
|
+
maxPrefetchBytes: 512 * 1024,
|
|
929
|
+
maxConcurrentModulePreloads: 2,
|
|
930
|
+
maxConcurrentNetworkPrefetches: 2,
|
|
931
|
+
reason: "default"
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
function withReason(tier, reason) {
|
|
935
|
+
return { ...budgets[tier], reason };
|
|
936
|
+
}
|
|
937
|
+
function feedbackIsPoor(feedback) {
|
|
938
|
+
if (!feedback || feedback.observations < 4) return false;
|
|
939
|
+
const failureRate = 1 - feedback.successes / feedback.observations;
|
|
940
|
+
const abandoned = feedback.cancellations + feedback.fallbacks;
|
|
941
|
+
return failureRate > 0.25 || abandoned / feedback.observations > 0.25 || (feedback.latencyEwmaMs ?? 0) > 2e3 || (feedback.usefulnessObservations ?? 0) >= 4 && (feedback.usefulPreloads ?? 0) / (feedback.usefulnessObservations ?? 1) < 0.25;
|
|
942
|
+
}
|
|
943
|
+
function preloadKey(kind, action) {
|
|
944
|
+
return `${kind}:${action}`;
|
|
945
|
+
}
|
|
946
|
+
function flushExpiredPreloads(now = Date.now()) {
|
|
947
|
+
for (const [key, preload] of pendingPreloads) {
|
|
948
|
+
if (preload.expiresAt > now) continue;
|
|
949
|
+
recordLocalAdaptiveOutcome(usefulnessScopes[preload.kind], preload.action, {
|
|
950
|
+
success: false
|
|
951
|
+
});
|
|
952
|
+
pendingPreloads.delete(key);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
function markAdaptiveResourceUsed(kind, action, now = Date.now()) {
|
|
956
|
+
flushExpiredPreloads(now);
|
|
957
|
+
const key = preloadKey(kind, action);
|
|
958
|
+
if (!pendingPreloads.has(key)) return;
|
|
959
|
+
pendingPreloads.delete(key);
|
|
960
|
+
recordLocalAdaptiveOutcome(
|
|
961
|
+
usefulnessScopes[kind],
|
|
962
|
+
action,
|
|
963
|
+
{ success: true },
|
|
964
|
+
now
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
function computeAdaptiveResourceBudget(environment, feedback) {
|
|
968
|
+
if (!environment.visible) return withReason("paused", "hidden");
|
|
969
|
+
if (environment.saveData || ["slow-2g", "2g", "3g"].includes(environment.effectiveType ?? "")) {
|
|
970
|
+
return withReason("constrained", "environment");
|
|
971
|
+
}
|
|
972
|
+
const limitedDevice = (environment.deviceMemoryGb ?? 8) <= 4 || (environment.hardwareConcurrency ?? 8) <= 4;
|
|
973
|
+
const baseline = limitedDevice ? "balanced" : "eager";
|
|
974
|
+
if (!feedbackIsPoor(feedback)) {
|
|
975
|
+
return withReason(baseline, limitedDevice ? "environment" : "default");
|
|
976
|
+
}
|
|
977
|
+
return withReason(
|
|
978
|
+
baseline === "eager" ? "balanced" : "constrained",
|
|
979
|
+
"feedback"
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
function readEnvironment() {
|
|
983
|
+
const navigatorLike = typeof navigator === "undefined" ? void 0 : navigator;
|
|
984
|
+
return {
|
|
985
|
+
visible: typeof document === "undefined" || document.visibilityState !== "hidden",
|
|
986
|
+
saveData: navigatorLike?.connection?.saveData,
|
|
987
|
+
effectiveType: navigatorLike?.connection?.effectiveType,
|
|
988
|
+
deviceMemoryGb: navigatorLike?.deviceMemory,
|
|
989
|
+
hardwareConcurrency: navigatorLike?.hardwareConcurrency
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
function readFeedback(kind) {
|
|
993
|
+
flushExpiredPreloads();
|
|
994
|
+
const stats = Object.values(getLocalAdaptiveStats(scopes[kind]));
|
|
995
|
+
const usefulness = Object.values(
|
|
996
|
+
getLocalAdaptiveStats(usefulnessScopes[kind])
|
|
997
|
+
);
|
|
998
|
+
const observations = stats.reduce((sum, stat) => sum + stat.observations, 0);
|
|
999
|
+
const latencySamples = stats.filter(
|
|
1000
|
+
(stat) => stat.latencyEwmaMs !== void 0 && stat.observations > 0
|
|
1001
|
+
);
|
|
1002
|
+
const latencyWeight = latencySamples.reduce(
|
|
1003
|
+
(sum, stat) => sum + stat.observations,
|
|
1004
|
+
0
|
|
1005
|
+
);
|
|
1006
|
+
return {
|
|
1007
|
+
observations,
|
|
1008
|
+
successes: stats.reduce((sum, stat) => sum + stat.successes, 0),
|
|
1009
|
+
cancellations: stats.reduce((sum, stat) => sum + stat.cancellations, 0),
|
|
1010
|
+
fallbacks: stats.reduce((sum, stat) => sum + stat.fallbacks, 0),
|
|
1011
|
+
usefulnessObservations: usefulness.reduce(
|
|
1012
|
+
(sum, stat) => sum + stat.observations,
|
|
1013
|
+
0
|
|
1014
|
+
),
|
|
1015
|
+
usefulPreloads: usefulness.reduce((sum, stat) => sum + stat.successes, 0),
|
|
1016
|
+
...latencyWeight > 0 ? {
|
|
1017
|
+
latencyEwmaMs: latencySamples.reduce(
|
|
1018
|
+
(sum, stat) => sum + (stat.latencyEwmaMs ?? 0) * stat.observations,
|
|
1019
|
+
0
|
|
1020
|
+
) / latencyWeight
|
|
1021
|
+
} : {}
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
function getAdaptiveResourceBudget(kind) {
|
|
1025
|
+
return computeAdaptiveResourceBudget(readEnvironment(), readFeedback(kind));
|
|
1026
|
+
}
|
|
1027
|
+
function runAdaptiveBackgroundTask(kind, action, task, options = {}) {
|
|
1028
|
+
const budget = getAdaptiveResourceBudget(kind);
|
|
1029
|
+
const allowed = kind === "module" ? budget.allowModulePreload : budget.allowNetworkPrefetch && (options.estimatedBytes === void 0 || options.estimatedBytes <= budget.maxPrefetchBytes);
|
|
1030
|
+
const concurrency = kind === "module" ? budget.maxConcurrentModulePreloads : budget.maxConcurrentNetworkPrefetches;
|
|
1031
|
+
if (!allowed || activeTasks[kind] >= concurrency) return false;
|
|
1032
|
+
activeTasks[kind] += 1;
|
|
1033
|
+
const key = preloadKey(kind, action);
|
|
1034
|
+
pendingPreloads.set(key, {
|
|
1035
|
+
kind,
|
|
1036
|
+
action,
|
|
1037
|
+
expiresAt: Date.now() + PRELOAD_USE_WINDOW_MS
|
|
1038
|
+
});
|
|
1039
|
+
const startedAt = performance.now();
|
|
1040
|
+
void Promise.resolve().then(task).then(
|
|
1041
|
+
() => {
|
|
1042
|
+
activeTasks[kind] -= 1;
|
|
1043
|
+
recordLocalAdaptiveOutcome(scopes[kind], action, {
|
|
1044
|
+
success: true,
|
|
1045
|
+
latencyMs: performance.now() - startedAt
|
|
1046
|
+
});
|
|
1047
|
+
},
|
|
1048
|
+
() => {
|
|
1049
|
+
activeTasks[kind] -= 1;
|
|
1050
|
+
pendingPreloads.delete(key);
|
|
1051
|
+
recordLocalAdaptiveOutcome(scopes[kind], action, {
|
|
1052
|
+
success: false,
|
|
1053
|
+
latencyMs: performance.now() - startedAt
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
);
|
|
1057
|
+
return true;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// src/ui/shell/tabUtils.tsx
|
|
1061
|
+
init_define_import_meta_env();
|
|
1062
|
+
import { lazy, memo as memo2, Suspense } from "react";
|
|
1063
|
+
import { useTranslation as useTranslation6 } from "react-i18next";
|
|
1064
|
+
|
|
1065
|
+
// src/ui/shell/CompanionFrame.tsx
|
|
1066
|
+
init_define_import_meta_env();
|
|
1067
|
+
import {
|
|
1068
|
+
memo,
|
|
1069
|
+
useCallback,
|
|
1070
|
+
useEffect as useEffect2,
|
|
1071
|
+
useMemo as useMemo2,
|
|
1072
|
+
useRef,
|
|
1073
|
+
useState as useState3
|
|
1074
|
+
} from "react";
|
|
1075
|
+
import { useTranslation as useTranslation5 } from "react-i18next";
|
|
1076
|
+
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1077
|
+
var DEFAULT_SIDE_SIZE = 360;
|
|
1078
|
+
var DEFAULT_BOTTOM_SIZE = 280;
|
|
1079
|
+
var MIN_SIZE = 200;
|
|
1080
|
+
function DefaultsReader({
|
|
1081
|
+
def,
|
|
1082
|
+
onValue
|
|
1083
|
+
}) {
|
|
1084
|
+
const Reader = def.Defaults;
|
|
1085
|
+
const report = useCallback(
|
|
1086
|
+
(value) => onValue(def.id, value),
|
|
1087
|
+
[def.id, onValue]
|
|
1088
|
+
);
|
|
1089
|
+
return Reader ? /* @__PURE__ */ jsx8(Reader, { onValue: report }) : null;
|
|
1090
|
+
}
|
|
1091
|
+
function sideIcon(side) {
|
|
1092
|
+
if (side === "left") return ArrowLeftToLine;
|
|
1093
|
+
if (side === "bottom") return ArrowDownToLine;
|
|
1094
|
+
return ArrowRightToLine;
|
|
1095
|
+
}
|
|
1096
|
+
var CompanionFrame = memo(function CompanionFrame2({
|
|
1097
|
+
tab,
|
|
1098
|
+
isVisible,
|
|
1099
|
+
children
|
|
1100
|
+
}) {
|
|
1101
|
+
const defs = useCompanionDefs();
|
|
1102
|
+
const layoutDefs = useCompanionLayoutDefs();
|
|
1103
|
+
const applicable = useMemo2(
|
|
1104
|
+
() => arrangeCompanions(
|
|
1105
|
+
defs.filter((def) => companionApplies(def, tab)),
|
|
1106
|
+
resolveCompanionLayout(tab.type, tab.host).order
|
|
1107
|
+
),
|
|
1108
|
+
// tab.host and tab.type decide it
|
|
1109
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1110
|
+
[defs, layoutDefs, tab.type, tab.host]
|
|
1111
|
+
);
|
|
1112
|
+
if (applicable.length === 0) return /* @__PURE__ */ jsx8(Fragment3, { children });
|
|
1113
|
+
return /* @__PURE__ */ jsx8(CompanionLayout, { tab, defs: applicable, isVisible, children });
|
|
1114
|
+
});
|
|
1115
|
+
function CompanionLayout({
|
|
1116
|
+
tab,
|
|
1117
|
+
defs,
|
|
1118
|
+
isVisible,
|
|
1119
|
+
children
|
|
1120
|
+
}) {
|
|
1121
|
+
const { t } = useTranslation5();
|
|
1122
|
+
const isMobile = useIsMobile();
|
|
1123
|
+
const key = companionKey(tab);
|
|
1124
|
+
const state = useCompanionState(key);
|
|
1125
|
+
const [defaults, setDefaults] = useState3(
|
|
1126
|
+
{}
|
|
1127
|
+
);
|
|
1128
|
+
const [dragging, setDragging] = useState3(false);
|
|
1129
|
+
const [mounted, setMounted] = useState3(/* @__PURE__ */ new Set());
|
|
1130
|
+
const rootRef = useRef(null);
|
|
1131
|
+
const onDefaults = useCallback((id, value) => {
|
|
1132
|
+
setDefaults((prev) => {
|
|
1133
|
+
const old = prev[id];
|
|
1134
|
+
if (old && old.open === value.open && old.side === value.side && old.size === value.size && old.edgeButton === value.edgeButton) {
|
|
1135
|
+
return prev;
|
|
1136
|
+
}
|
|
1137
|
+
return { ...prev, [id]: value };
|
|
1138
|
+
});
|
|
1139
|
+
}, []);
|
|
1140
|
+
useEffect2(() => {
|
|
1141
|
+
if (state.seen || !isVisible) return;
|
|
1142
|
+
const ready = defs.every(
|
|
1143
|
+
(def) => !def.Defaults || defaults[def.id] !== void 0
|
|
1144
|
+
);
|
|
1145
|
+
if (!ready) return;
|
|
1146
|
+
const auto = defs.find((def) => defaults[def.id]?.open);
|
|
1147
|
+
patchCompanionState(key, { seen: true, openId: auto?.id ?? null });
|
|
1148
|
+
}, [state.seen, isVisible, defs, defaults, key]);
|
|
1149
|
+
const active = defs.find((def) => def.id === state.openId) ?? null;
|
|
1150
|
+
useEffect2(() => {
|
|
1151
|
+
if (!active) return;
|
|
1152
|
+
setMounted(
|
|
1153
|
+
(prev) => prev.has(active.id) ? prev : new Set(prev).add(active.id)
|
|
1154
|
+
);
|
|
1155
|
+
}, [active]);
|
|
1156
|
+
const activeDefaults = active ? defaults[active.id] : void 0;
|
|
1157
|
+
const layout = useMemo2(
|
|
1158
|
+
() => resolveCompanionLayout(tab.type, tab.host),
|
|
1159
|
+
[tab.type, tab.host]
|
|
1160
|
+
);
|
|
1161
|
+
const side = isMobile ? "bottom" : state.side ?? layout.side ?? activeDefaults?.side ?? "right";
|
|
1162
|
+
const baseSize = state.size ?? activeDefaults?.size ?? (side === "bottom" ? DEFAULT_BOTTOM_SIZE : DEFAULT_SIDE_SIZE);
|
|
1163
|
+
const setSide = useCallback(
|
|
1164
|
+
(next) => patchCompanionState(key, { side: next }),
|
|
1165
|
+
[key]
|
|
1166
|
+
);
|
|
1167
|
+
const close = useCallback(
|
|
1168
|
+
() => patchCompanionState(key, { openId: null }),
|
|
1169
|
+
[key]
|
|
1170
|
+
);
|
|
1171
|
+
const startResize = (event) => {
|
|
1172
|
+
event.preventDefault();
|
|
1173
|
+
const root = rootRef.current;
|
|
1174
|
+
if (!root) return;
|
|
1175
|
+
const rect = root.getBoundingClientRect();
|
|
1176
|
+
const total = side === "bottom" ? rect.height : rect.width;
|
|
1177
|
+
const max = Math.max(MIN_SIZE, total - MIN_SIZE);
|
|
1178
|
+
setDragging(true);
|
|
1179
|
+
const move = (e) => {
|
|
1180
|
+
const raw = side === "right" ? rect.right - e.clientX : side === "left" ? e.clientX - rect.left : rect.bottom - e.clientY;
|
|
1181
|
+
patchCompanionState(key, {
|
|
1182
|
+
size: Math.round(Math.min(max, Math.max(MIN_SIZE, raw)))
|
|
1183
|
+
});
|
|
1184
|
+
};
|
|
1185
|
+
const up = () => {
|
|
1186
|
+
setDragging(false);
|
|
1187
|
+
window.removeEventListener("mousemove", move);
|
|
1188
|
+
window.removeEventListener("mouseup", up);
|
|
1189
|
+
};
|
|
1190
|
+
window.addEventListener("mousemove", move);
|
|
1191
|
+
window.addEventListener("mouseup", up);
|
|
1192
|
+
};
|
|
1193
|
+
const getHandle = useCallback(
|
|
1194
|
+
() => tab.terminalRef?.current ?? null,
|
|
1195
|
+
[tab.terminalRef]
|
|
1196
|
+
);
|
|
1197
|
+
const sshHost = useMemo2(
|
|
1198
|
+
() => tab.host ? hostToSSHHost(tab.host) : void 0,
|
|
1199
|
+
[tab.host]
|
|
1200
|
+
);
|
|
1201
|
+
const showEdge = defs.filter((def) => defaults[def.id]?.edgeButton !== false);
|
|
1202
|
+
const vertical = side === "bottom";
|
|
1203
|
+
const clamped = vertical ? Math.min(baseSize, 2e3) : baseSize;
|
|
1204
|
+
const paneStyle = isMobile ? { height: "55%" } : vertical ? { height: clamped } : { width: clamped };
|
|
1205
|
+
const SideIcon = sideIcon(side);
|
|
1206
|
+
return /* @__PURE__ */ jsxs6("div", { className: "flex h-full w-full min-h-0 min-w-0", children: [
|
|
1207
|
+
defs.map(
|
|
1208
|
+
(def) => def.Defaults ? /* @__PURE__ */ jsx8(DefaultsReader, { def, onValue: onDefaults }, def.id) : null
|
|
1209
|
+
),
|
|
1210
|
+
/* @__PURE__ */ jsxs6(
|
|
1211
|
+
"div",
|
|
1212
|
+
{
|
|
1213
|
+
ref: rootRef,
|
|
1214
|
+
className: cn(
|
|
1215
|
+
"relative flex flex-1 min-h-0 min-w-0",
|
|
1216
|
+
vertical ? "flex-col" : side === "left" ? "flex-row-reverse" : "flex-row"
|
|
1217
|
+
),
|
|
1218
|
+
children: [
|
|
1219
|
+
/* @__PURE__ */ jsx8("div", { className: "relative flex-1 min-h-0 min-w-0", children }),
|
|
1220
|
+
defs.map((def) => {
|
|
1221
|
+
if (!mounted.has(def.id)) return null;
|
|
1222
|
+
const open = active?.id === def.id;
|
|
1223
|
+
const Component = def.component;
|
|
1224
|
+
const Icon = def.icon;
|
|
1225
|
+
return /* @__PURE__ */ jsxs6(
|
|
1226
|
+
"div",
|
|
1227
|
+
{
|
|
1228
|
+
className: cn(
|
|
1229
|
+
"relative flex-shrink-0 flex-col min-h-0 min-w-0 bg-background border-border",
|
|
1230
|
+
open ? "flex" : "hidden",
|
|
1231
|
+
vertical ? "border-t" : side === "left" ? "border-r" : "border-l",
|
|
1232
|
+
isMobile && "absolute inset-x-0 bottom-0 z-20 shadow-lg"
|
|
1233
|
+
),
|
|
1234
|
+
style: paneStyle,
|
|
1235
|
+
children: [
|
|
1236
|
+
!isMobile && /* @__PURE__ */ jsx8(
|
|
1237
|
+
"div",
|
|
1238
|
+
{
|
|
1239
|
+
role: "separator",
|
|
1240
|
+
"aria-label": t("companion.resize"),
|
|
1241
|
+
onMouseDown: startResize,
|
|
1242
|
+
className: cn(
|
|
1243
|
+
"absolute z-10",
|
|
1244
|
+
vertical ? "inset-x-0 -top-1 h-2 cursor-row-resize" : side === "left" ? "inset-y-0 -right-1 w-2 cursor-col-resize" : "inset-y-0 -left-1 w-2 cursor-col-resize",
|
|
1245
|
+
dragging && "bg-primary/30"
|
|
1246
|
+
)
|
|
1247
|
+
}
|
|
1248
|
+
),
|
|
1249
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1.5 h-8 px-2 border-b border-border flex-shrink-0", children: [
|
|
1250
|
+
Icon && /* @__PURE__ */ jsx8(Icon, { className: "size-3.5 text-muted-foreground" }),
|
|
1251
|
+
/* @__PURE__ */ jsx8("span", { className: "text-xs font-medium truncate flex-1", children: def.titleKey ? t(def.titleKey) : def.id }),
|
|
1252
|
+
!isMobile && /* @__PURE__ */ jsx8(
|
|
1253
|
+
"button",
|
|
1254
|
+
{
|
|
1255
|
+
type: "button",
|
|
1256
|
+
className: "p-1 text-muted-foreground hover:text-foreground",
|
|
1257
|
+
title: t("companion.moveSide"),
|
|
1258
|
+
onClick: () => setSide(
|
|
1259
|
+
side === "right" ? "bottom" : side === "bottom" ? "left" : "right"
|
|
1260
|
+
),
|
|
1261
|
+
children: /* @__PURE__ */ jsx8(SideIcon, { className: "size-3.5" })
|
|
1262
|
+
}
|
|
1263
|
+
),
|
|
1264
|
+
/* @__PURE__ */ jsx8(
|
|
1265
|
+
"button",
|
|
1266
|
+
{
|
|
1267
|
+
type: "button",
|
|
1268
|
+
className: "p-1 text-muted-foreground hover:text-foreground",
|
|
1269
|
+
title: t("companion.close"),
|
|
1270
|
+
onClick: close,
|
|
1271
|
+
children: /* @__PURE__ */ jsx8(X, { className: "size-3.5" })
|
|
1272
|
+
}
|
|
1273
|
+
)
|
|
1274
|
+
] }),
|
|
1275
|
+
/* @__PURE__ */ jsx8("div", { className: "flex-1 min-h-0 min-w-0 overflow-hidden", children: /* @__PURE__ */ jsx8(
|
|
1276
|
+
Component,
|
|
1277
|
+
{
|
|
1278
|
+
tab,
|
|
1279
|
+
host: tab.host,
|
|
1280
|
+
sshHost,
|
|
1281
|
+
getHandle,
|
|
1282
|
+
side,
|
|
1283
|
+
isVisible: isVisible && open,
|
|
1284
|
+
close,
|
|
1285
|
+
setSide
|
|
1286
|
+
}
|
|
1287
|
+
) })
|
|
1288
|
+
]
|
|
1289
|
+
},
|
|
1290
|
+
def.id
|
|
1291
|
+
);
|
|
1292
|
+
})
|
|
1293
|
+
]
|
|
1294
|
+
}
|
|
1295
|
+
),
|
|
1296
|
+
showEdge.length > 0 && !isMobile && /* @__PURE__ */ jsx8("div", { className: "flex flex-col flex-shrink-0 w-6 items-center gap-1 pt-1 border-l border-border bg-background", children: showEdge.map((def) => {
|
|
1297
|
+
const Icon = def.icon ?? PanelRight;
|
|
1298
|
+
const open = active?.id === def.id;
|
|
1299
|
+
return /* @__PURE__ */ jsx8(
|
|
1300
|
+
"button",
|
|
1301
|
+
{
|
|
1302
|
+
type: "button",
|
|
1303
|
+
title: def.titleKey ? t(def.titleKey) : def.id,
|
|
1304
|
+
"aria-pressed": open,
|
|
1305
|
+
onClick: () => patchCompanionState(key, {
|
|
1306
|
+
seen: true,
|
|
1307
|
+
openId: open ? null : def.id
|
|
1308
|
+
}),
|
|
1309
|
+
className: cn(
|
|
1310
|
+
"p-1 text-muted-foreground hover:text-foreground",
|
|
1311
|
+
open && "text-foreground bg-accent"
|
|
1312
|
+
),
|
|
1313
|
+
children: /* @__PURE__ */ jsx8(Icon, { className: "size-3.5" })
|
|
1314
|
+
},
|
|
1315
|
+
def.id
|
|
1316
|
+
);
|
|
1317
|
+
}) })
|
|
1318
|
+
] });
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// src/ui/shell/tabUtils.tsx
|
|
1322
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1323
|
+
var PluginsTab = lazy(
|
|
1324
|
+
() => import("./PluginsTab-CM2B33BD.js").then((m) => ({ default: m.PluginsTab }))
|
|
1325
|
+
);
|
|
1326
|
+
function preloadTabSurface(type) {
|
|
1327
|
+
const loader = getTabType(type)?.preload;
|
|
1328
|
+
if (loader) runAdaptiveBackgroundTask("module", `tab:${type}`, loader);
|
|
1329
|
+
}
|
|
1330
|
+
function markTabSurfaceUsed(type) {
|
|
1331
|
+
markAdaptiveResourceUsed("module", `tab:${type}`);
|
|
1332
|
+
}
|
|
1333
|
+
function EmptyState({
|
|
1334
|
+
icon: Icon,
|
|
1335
|
+
messageKey
|
|
1336
|
+
}) {
|
|
1337
|
+
const { t } = useTranslation6();
|
|
1338
|
+
return /* @__PURE__ */ jsxs7("div", { className: "flex flex-col items-center justify-center flex-1 gap-3 p-6 text-center", children: [
|
|
1339
|
+
/* @__PURE__ */ jsx9("div", { className: "size-10 rounded-full bg-muted/40 flex items-center justify-center", children: /* @__PURE__ */ jsx9(Icon, { className: "size-5 text-muted-foreground/30" }) }),
|
|
1340
|
+
/* @__PURE__ */ jsx9("span", { className: "text-sm font-semibold text-muted-foreground/60", children: t(messageKey) })
|
|
1341
|
+
] });
|
|
1342
|
+
}
|
|
1343
|
+
function TabChunkFallback() {
|
|
1344
|
+
return /* @__PURE__ */ jsx9("div", { className: "flex h-full w-full items-center justify-center bg-background", children: /* @__PURE__ */ jsx9("div", { className: "size-5 rounded-full border-2 border-muted-foreground/30 border-t-muted-foreground/70 animate-spin" }) });
|
|
1345
|
+
}
|
|
1346
|
+
function withTabSuspense(node) {
|
|
1347
|
+
return /* @__PURE__ */ jsx9(Suspense, { fallback: /* @__PURE__ */ jsx9(TabChunkFallback, {}), children: node });
|
|
1348
|
+
}
|
|
1349
|
+
function PanelTabFrame({ children }) {
|
|
1350
|
+
return /* @__PURE__ */ jsx9("div", { className: "flex h-full w-full justify-center overflow-y-auto bg-background", children: /* @__PURE__ */ jsx9("div", { className: "flex flex-col flex-1 min-h-0 w-full max-w-5xl", children }) });
|
|
1351
|
+
}
|
|
1352
|
+
function tabIcon(type) {
|
|
1353
|
+
switch (type) {
|
|
1354
|
+
case "user-profile":
|
|
1355
|
+
case "admin-settings":
|
|
1356
|
+
return /* @__PURE__ */ jsx9(Settings, { className: "size-3.5" });
|
|
1357
|
+
case "split-screen":
|
|
1358
|
+
return /* @__PURE__ */ jsx9(LayoutPanelLeft, { className: "size-3.5" });
|
|
1359
|
+
case "plugins":
|
|
1360
|
+
return /* @__PURE__ */ jsx9(Puzzle, { className: "size-3.5" });
|
|
1361
|
+
default: {
|
|
1362
|
+
const Icon = getTabType(type)?.icon;
|
|
1363
|
+
return Icon ? /* @__PURE__ */ jsx9(Icon, { className: "size-3.5" }) : null;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
var RegisteredTab = memo2(function RegisteredTab2({
|
|
1368
|
+
def,
|
|
1369
|
+
tab,
|
|
1370
|
+
isVisible,
|
|
1371
|
+
isFocusedPane,
|
|
1372
|
+
inSplit,
|
|
1373
|
+
shell
|
|
1374
|
+
}) {
|
|
1375
|
+
const { host, label } = tab;
|
|
1376
|
+
if (def.requiresHost && !host) {
|
|
1377
|
+
return /* @__PURE__ */ jsx9(
|
|
1378
|
+
EmptyState,
|
|
1379
|
+
{
|
|
1380
|
+
icon: Server,
|
|
1381
|
+
messageKey: def.noHostMessageKey ?? "hosts.noHostSelected"
|
|
1382
|
+
}
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
const Component = def.component;
|
|
1386
|
+
const content = /* @__PURE__ */ jsx9(
|
|
1387
|
+
Component,
|
|
1388
|
+
{
|
|
1389
|
+
tab,
|
|
1390
|
+
host,
|
|
1391
|
+
sshHost: host ? hostToSSHHost(host) : void 0,
|
|
1392
|
+
label,
|
|
1393
|
+
isVisible,
|
|
1394
|
+
isFocusedPane,
|
|
1395
|
+
inSplit,
|
|
1396
|
+
handleRef: trackTabHandle(
|
|
1397
|
+
tab.terminalRef
|
|
1398
|
+
),
|
|
1399
|
+
shell
|
|
1400
|
+
}
|
|
1401
|
+
);
|
|
1402
|
+
return withTabSuspense(
|
|
1403
|
+
/* @__PURE__ */ jsx9(CompanionFrame, { tab, isVisible, children: def.panelFrame ? /* @__PURE__ */ jsx9(PanelTabFrame, { children: content }) : content })
|
|
1404
|
+
);
|
|
1405
|
+
});
|
|
1406
|
+
var PanelAsTab = memo2(function PanelAsTab2({
|
|
1407
|
+
panel,
|
|
1408
|
+
isVisible,
|
|
1409
|
+
targetTab,
|
|
1410
|
+
shell
|
|
1411
|
+
}) {
|
|
1412
|
+
const Panel = panel.component;
|
|
1413
|
+
return withTabSuspense(
|
|
1414
|
+
/* @__PURE__ */ jsx9(PanelTabFrame, { children: /* @__PURE__ */ jsx9(
|
|
1415
|
+
Panel,
|
|
1416
|
+
{
|
|
1417
|
+
targetTab,
|
|
1418
|
+
active: isVisible,
|
|
1419
|
+
shell,
|
|
1420
|
+
setEditing: noop,
|
|
1421
|
+
placement: "tab"
|
|
1422
|
+
}
|
|
1423
|
+
) })
|
|
1424
|
+
);
|
|
1425
|
+
});
|
|
1426
|
+
function noop() {
|
|
1427
|
+
}
|
|
1428
|
+
function renderTabContent(tab, context) {
|
|
1429
|
+
const {
|
|
1430
|
+
shell,
|
|
1431
|
+
isVisible = true,
|
|
1432
|
+
isFocusedPane = true,
|
|
1433
|
+
inSplit = false
|
|
1434
|
+
} = context;
|
|
1435
|
+
switch (tab.type) {
|
|
1436
|
+
case "split-screen":
|
|
1437
|
+
return null;
|
|
1438
|
+
case "plugins":
|
|
1439
|
+
return withTabSuspense(/* @__PURE__ */ jsx9(PluginsTab, {}));
|
|
1440
|
+
case "user-profile":
|
|
1441
|
+
case "admin-settings":
|
|
1442
|
+
return null;
|
|
1443
|
+
default: {
|
|
1444
|
+
const def = getTabType(tab.type);
|
|
1445
|
+
if (!def && tab.type === "dashboard") return null;
|
|
1446
|
+
if (!def) {
|
|
1447
|
+
const panel = getPanel(tab.type);
|
|
1448
|
+
if (panel) {
|
|
1449
|
+
return /* @__PURE__ */ jsx9(
|
|
1450
|
+
PanelAsTab,
|
|
1451
|
+
{
|
|
1452
|
+
panel,
|
|
1453
|
+
isVisible,
|
|
1454
|
+
targetTab: context.panelTargetTab,
|
|
1455
|
+
shell
|
|
1456
|
+
}
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
return /* @__PURE__ */ jsx9(PluginViewPlaceholder, { kind: "tab", viewId: tab.type });
|
|
1460
|
+
}
|
|
1461
|
+
return /* @__PURE__ */ jsx9(
|
|
1462
|
+
RegisteredTab,
|
|
1463
|
+
{
|
|
1464
|
+
def,
|
|
1465
|
+
tab,
|
|
1466
|
+
isVisible,
|
|
1467
|
+
isFocusedPane,
|
|
1468
|
+
inSplit,
|
|
1469
|
+
shell
|
|
1470
|
+
}
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
// src/ui/lib/local-adaptive-preferences.ts
|
|
1477
|
+
init_define_import_meta_env();
|
|
1478
|
+
var LOCAL_ADAPTIVE_PREFERENCES_KEY = LOCAL_ADAPTIVE_ENGINE_KEY;
|
|
1479
|
+
function recordLocalPreference(scope, action, now = Date.now()) {
|
|
1480
|
+
recordLocalAdaptiveChoice(scope, action, now);
|
|
1481
|
+
}
|
|
1482
|
+
function getLocalPreference(scope, candidates, fallback, now = Date.now()) {
|
|
1483
|
+
return chooseLocalAdaptiveAction({
|
|
1484
|
+
scope,
|
|
1485
|
+
candidates,
|
|
1486
|
+
fallback,
|
|
1487
|
+
now
|
|
1488
|
+
}).action;
|
|
1489
|
+
}
|
|
1490
|
+
function clearLocalAdaptivePreferences() {
|
|
1491
|
+
clearLocalAdaptiveEngine();
|
|
1492
|
+
}
|
|
1493
|
+
var hostActionScope = (hostId) => `host-action:${hostId}`;
|
|
1494
|
+
function recordHostActionPreference(hostId, action) {
|
|
1495
|
+
recordLocalPreference(hostActionScope(hostId), action);
|
|
1496
|
+
}
|
|
1497
|
+
function getPreferredHostAction(hostId, candidates, fallback) {
|
|
1498
|
+
return getLocalPreference(hostActionScope(hostId), candidates, fallback);
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
export {
|
|
1502
|
+
Checkbox2 as Checkbox,
|
|
1503
|
+
PasswordInput,
|
|
1504
|
+
Textarea,
|
|
1505
|
+
useInstalledPlugins,
|
|
1506
|
+
SettingsFieldRow,
|
|
1507
|
+
usePluginHostSections,
|
|
1508
|
+
HostPluginSections,
|
|
1509
|
+
HostFeatureFields,
|
|
1510
|
+
ActionSlot,
|
|
1511
|
+
ComponentSlot,
|
|
1512
|
+
trackTabHandle,
|
|
1513
|
+
useTabHandlesVersion,
|
|
1514
|
+
PluginViewPlaceholder,
|
|
1515
|
+
markAdaptiveResourceUsed,
|
|
1516
|
+
getAdaptiveResourceBudget,
|
|
1517
|
+
runAdaptiveBackgroundTask,
|
|
1518
|
+
preloadTabSurface,
|
|
1519
|
+
markTabSurfaceUsed,
|
|
1520
|
+
tabIcon,
|
|
1521
|
+
renderTabContent,
|
|
1522
|
+
LOCAL_ADAPTIVE_PREFERENCES_KEY,
|
|
1523
|
+
clearLocalAdaptivePreferences,
|
|
1524
|
+
recordHostActionPreference,
|
|
1525
|
+
getPreferredHostAction
|
|
1526
|
+
};
|