@aixnn/aixautots 0.0.0-stage → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,665 @@
1
+ export { ControlBar, MirrorView } from '../chunk-CVV7FOMQ.js';
2
+ import { Button, Input } from '../chunk-EVHTAXQG.js';
3
+ import { useScript, useEnvRun, useClient } from '../chunk-UINEPEIE.js';
4
+ import '../chunk-OXIM6EYO.js';
5
+ import { envBlocker, summarizeEnv } from '../chunk-TATL3HTE.js';
6
+ import '../chunk-NWFYTFMI.js';
7
+ import React4, { useState, useId, useRef, useEffect, useMemo } from 'react';
8
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
+ import CodeMirror, { EditorView } from '@uiw/react-codemirror';
10
+ import { javascript } from '@codemirror/lang-javascript';
11
+
12
+ var box = {
13
+ background: "var(--aix-bg, #161616)",
14
+ color: "var(--aix-fg, #e6e6e6)",
15
+ border: "1px solid var(--aix-border, #2a2a2a)",
16
+ borderRadius: "var(--aix-radius, 6px)",
17
+ font: "var(--aix-font, 13px/1.5 system-ui, sans-serif)",
18
+ display: "flex",
19
+ flexDirection: "column"
20
+ };
21
+ var mono = {
22
+ fontFamily: "var(--aix-mono, ui-monospace, Menlo, monospace)",
23
+ whiteSpace: "pre-wrap",
24
+ wordBreak: "break-all"
25
+ };
26
+ function DebugConsole({ events, onCall, onClear, className }) {
27
+ const [method, setMethod] = useState("");
28
+ const [argsText, setArgsText] = useState("[]");
29
+ const [err, setErr] = useState(null);
30
+ const doCall = () => {
31
+ if (!onCall) return;
32
+ let args;
33
+ try {
34
+ args = argsText.trim() === "" ? [] : JSON.parse(argsText);
35
+ } catch {
36
+ setErr("Invalid JSON in args");
37
+ return;
38
+ }
39
+ setErr(null);
40
+ onCall(method, args);
41
+ };
42
+ return /* @__PURE__ */ jsxs("div", { style: box, className, children: [
43
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", padding: "6px 10px", borderBottom: "1px solid var(--aix-border, #2a2a2a)" }, children: [
44
+ /* @__PURE__ */ jsx("strong", { children: "Events" }),
45
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => onClear?.(), style: btn, children: "Clear" })
46
+ ] }),
47
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, overflow: "auto", padding: "8px 10px", minHeight: 80 }, children: events.length === 0 ? /* @__PURE__ */ jsx("div", { style: { color: "var(--aix-muted, #888)" }, children: "No events yet." }) : events.map((e, i) => /* @__PURE__ */ jsxs("div", { style: { marginBottom: 6 }, children: [
48
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--aix-accent, #4f9cff)" }, children: e.name }),
49
+ /* @__PURE__ */ jsx("span", { style: { ...mono, marginLeft: 8 }, children: JSON.stringify(e.data) })
50
+ ] }, i)) }),
51
+ onCall && /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 6, padding: "8px 10px", borderTop: "1px solid var(--aix-border, #2a2a2a)" }, children: [
52
+ /* @__PURE__ */ jsx("input", { placeholder: "method", value: method, onChange: (ev) => setMethod(ev.target.value), style: input }),
53
+ /* @__PURE__ */ jsx("input", { placeholder: "args (JSON array)", value: argsText, onChange: (ev) => setArgsText(ev.target.value), style: { ...input, ...mono, flex: 1 } }),
54
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: doCall, style: btn, children: "Call" })
55
+ ] }),
56
+ err && /* @__PURE__ */ jsx("div", { style: { color: "var(--aix-danger, #ff6b6b)", padding: "0 10px 8px" }, children: err })
57
+ ] });
58
+ }
59
+ var btn = {
60
+ background: "var(--aix-accent, #2d6cdf)",
61
+ color: "#fff",
62
+ border: "none",
63
+ borderRadius: "var(--aix-radius, 6px)",
64
+ padding: "4px 10px",
65
+ cursor: "pointer"
66
+ };
67
+ var input = {
68
+ background: "var(--aix-editor-bg, #1e1e1e)",
69
+ color: "var(--aix-editor-fg, #ddd)",
70
+ border: "1px solid var(--aix-border, #2a2a2a)",
71
+ borderRadius: "var(--aix-radius, 6px)",
72
+ padding: "4px 8px"
73
+ };
74
+ var AIX_CSS_VARS = [
75
+ "--aix-bg",
76
+ "--aix-fg",
77
+ "--aix-muted",
78
+ "--aix-accent",
79
+ "--aix-danger",
80
+ "--aix-border",
81
+ "--aix-radius",
82
+ "--aix-font",
83
+ "--aix-mono",
84
+ "--aix-editor-bg",
85
+ "--aix-editor-fg"
86
+ ];
87
+ function aixCodeMirrorTheme() {
88
+ return EditorView.theme({
89
+ "&": {
90
+ backgroundColor: "var(--aix-editor-bg, #1e1e1e)",
91
+ color: "var(--aix-editor-fg, #d4d4d4)",
92
+ fontFamily: "var(--aix-mono, ui-monospace, SFMono-Regular, Menlo, monospace)",
93
+ borderRadius: "var(--aix-radius, 6px)"
94
+ },
95
+ ".cm-gutters": {
96
+ backgroundColor: "var(--aix-editor-bg, #1e1e1e)",
97
+ color: "var(--aix-muted, #858585)",
98
+ border: "none"
99
+ },
100
+ ".cm-content": { caretColor: "var(--aix-accent, #4f9cff)" },
101
+ "&.cm-focused": { outline: "none" }
102
+ });
103
+ }
104
+ var wrap = {
105
+ background: "var(--aix-bg, #161616)",
106
+ border: "1px solid var(--aix-border, #2a2a2a)",
107
+ borderRadius: "var(--aix-radius, 6px)",
108
+ display: "flex",
109
+ flexDirection: "column",
110
+ overflow: "hidden"
111
+ };
112
+ var bar = {
113
+ display: "flex",
114
+ alignItems: "center",
115
+ gap: 8,
116
+ padding: "6px 10px",
117
+ borderBottom: "1px solid var(--aix-border, #2a2a2a)",
118
+ color: "var(--aix-fg, #e6e6e6)",
119
+ font: "var(--aix-font, 13px system-ui, sans-serif)"
120
+ };
121
+ var btn2 = {
122
+ background: "var(--aix-accent, #2d6cdf)",
123
+ color: "#fff",
124
+ border: "none",
125
+ borderRadius: "var(--aix-radius, 6px)",
126
+ padding: "4px 14px",
127
+ cursor: "pointer"
128
+ };
129
+ function ScriptEditor(props) {
130
+ const engineId = useId();
131
+ const engines = props.engines ?? ["frida", "scriptlet"];
132
+ return /* @__PURE__ */ jsxs("div", { style: wrap, className: props.className, children: [
133
+ /* @__PURE__ */ jsxs("div", { style: bar, children: [
134
+ /* @__PURE__ */ jsx("label", { htmlFor: engineId, children: "Engine" }),
135
+ /* @__PURE__ */ jsx(
136
+ "select",
137
+ {
138
+ id: engineId,
139
+ "aria-label": "engine",
140
+ value: props.engine,
141
+ onChange: (e) => props.onEngineChange(e.target.value),
142
+ style: { background: "var(--aix-editor-bg, #1e1e1e)", color: "var(--aix-editor-fg, #ddd)", border: "1px solid var(--aix-border, #2a2a2a)", borderRadius: "var(--aix-radius, 6px)", padding: "2px 6px" },
143
+ children: engines.map((e) => /* @__PURE__ */ jsx("option", { value: e, children: e }, e))
144
+ }
145
+ ),
146
+ /* @__PURE__ */ jsx("div", { style: { flex: 1 } }),
147
+ props.running ? /* @__PURE__ */ jsx("button", { type: "button", onClick: props.onCancel, style: { ...btn2, background: "#b3402f" }, children: "Cancel" }) : /* @__PURE__ */ jsx("button", { type: "button", onClick: props.onRun, style: btn2, children: "Run" })
148
+ ] }),
149
+ /* @__PURE__ */ jsx("div", { "data-testid": "aix-cm", children: /* @__PURE__ */ jsx(
150
+ CodeMirror,
151
+ {
152
+ value: props.value,
153
+ onChange: props.onChange,
154
+ extensions: [javascript({ typescript: true }), aixCodeMirrorTheme()],
155
+ basicSetup: { lineNumbers: true, foldGutter: false },
156
+ height: "240px"
157
+ }
158
+ ) })
159
+ ] });
160
+ }
161
+ function ScriptPanel({ deviceId, defaultEngine = "scriptlet", defaultScript = "", className }) {
162
+ const [engine, setEngine] = useState(defaultEngine);
163
+ const [script, setScript] = useState(defaultScript);
164
+ const { running, events, error, run, call, cancel, clearEvents } = useScript(deviceId, { engine });
165
+ return /* @__PURE__ */ jsxs("div", { className, style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
166
+ /* @__PURE__ */ jsx(
167
+ ScriptEditor,
168
+ {
169
+ value: script,
170
+ onChange: setScript,
171
+ engine,
172
+ onEngineChange: (e) => setEngine(e),
173
+ running,
174
+ onRun: () => {
175
+ void run(engine === "frida" ? { script, target: {} } : { script }).catch(() => {
176
+ });
177
+ },
178
+ onCancel: () => {
179
+ void cancel().catch(() => {
180
+ });
181
+ }
182
+ }
183
+ ),
184
+ error && /* @__PURE__ */ jsx("div", { style: { color: "var(--aix-danger, #ff6b6b)", font: "var(--aix-font, 13px system-ui)" }, children: error.message }),
185
+ /* @__PURE__ */ jsx(
186
+ DebugConsole,
187
+ {
188
+ events,
189
+ onClear: clearEvents,
190
+ onCall: (method, args) => {
191
+ void call(method, args).catch(() => {
192
+ });
193
+ }
194
+ }
195
+ )
196
+ ] });
197
+ }
198
+ var STATE_ICON = {
199
+ ok: "\u2713",
200
+ done: "\u2713",
201
+ skipped: "\u2298",
202
+ present: "\u2713",
203
+ running: "\u23F3",
204
+ missing: "\xB7",
205
+ pending: "\xB7",
206
+ failed: "\u2715",
207
+ error: "\u2715"
208
+ };
209
+ var stateColor = (s) => s === "failed" || s === "error" ? "var(--aix-danger, #e5484d)" : s === "ok" || s === "done" || s === "present" ? "var(--aix-accent, #2d6cdf)" : s === "running" ? "var(--aix-fg, #e6e6e6)" : "var(--aix-muted, #8a8a8a)";
210
+ var overlay = {
211
+ position: "fixed",
212
+ inset: 0,
213
+ background: "rgba(0,0,0,0.55)",
214
+ display: "flex",
215
+ alignItems: "center",
216
+ justifyContent: "center",
217
+ zIndex: 1e3
218
+ };
219
+ var card = {
220
+ width: "min(560px, 92vw)",
221
+ maxHeight: "86vh",
222
+ overflow: "auto",
223
+ background: "var(--aix-bg, #14161a)",
224
+ color: "var(--aix-fg, #e6e6e6)",
225
+ border: "1px solid var(--aix-border, #2a2e35)",
226
+ borderRadius: "var(--aix-radius, 10px)",
227
+ padding: "20px 22px",
228
+ font: "var(--aix-font, 14px/1.5 system-ui, sans-serif)",
229
+ boxShadow: "0 12px 40px rgba(0,0,0,0.5)"
230
+ };
231
+ var btn3 = (primary) => ({
232
+ background: primary ? "var(--aix-accent, #2d6cdf)" : "transparent",
233
+ color: primary ? "#fff" : "var(--aix-fg, #e6e6e6)",
234
+ border: primary ? "none" : "1px solid var(--aix-border, #2a2e35)",
235
+ borderRadius: "var(--aix-radius, 8px)",
236
+ padding: "7px 16px",
237
+ cursor: "pointer",
238
+ fontSize: 13
239
+ });
240
+ function EnvInitDialog(props) {
241
+ const { open, onClose, nodeId, os, route, mode = "prompt", seedEvents, onReady, onInstallWsl, title } = props;
242
+ const { events, finished, done, error, setup } = useEnvRun();
243
+ const [started, setStarted] = useState(false);
244
+ const [showLogs, setShowLogs] = useState(false);
245
+ const [installing, setInstalling] = useState(false);
246
+ const [wslFailed, setWslFailed] = useState(false);
247
+ const readyFired = useRef(false);
248
+ const wslTried = useRef(false);
249
+ useEffect(() => {
250
+ if (open) {
251
+ setStarted(false);
252
+ setShowLogs(false);
253
+ setInstalling(false);
254
+ setWslFailed(false);
255
+ readyFired.current = false;
256
+ wslTried.current = false;
257
+ }
258
+ }, [open, nodeId]);
259
+ const wslBlocked = done && envBlocker(finished) === "wsl-missing" && !!onInstallWsl;
260
+ const installWsl = React4.useCallback(async () => {
261
+ if (!onInstallWsl) return;
262
+ setInstalling(true);
263
+ setWslFailed(false);
264
+ try {
265
+ await onInstallWsl();
266
+ } catch {
267
+ setInstalling(false);
268
+ setWslFailed(true);
269
+ }
270
+ }, [onInstallWsl]);
271
+ useEffect(() => {
272
+ if (wslBlocked && !installing && !wslFailed && !wslTried.current) {
273
+ wslTried.current = true;
274
+ void installWsl();
275
+ }
276
+ }, [wslBlocked, installing, wslFailed, installWsl]);
277
+ const begin = React4.useCallback(() => {
278
+ setStarted(true);
279
+ void setup(nodeId, { os, route }).catch(() => {
280
+ });
281
+ }, [setup, nodeId, os, route]);
282
+ useEffect(() => {
283
+ if (open && mode === "auto" && !started) begin();
284
+ }, [open, mode, started, begin]);
285
+ useEffect(() => {
286
+ if (done && finished?.ok && !readyFired.current) {
287
+ readyFired.current = true;
288
+ onReady?.();
289
+ }
290
+ }, [done, finished, onReady]);
291
+ const summary = useMemo(
292
+ () => summarizeEnv(started ? events : seedEvents ?? []),
293
+ [started, events, seedEvents]
294
+ );
295
+ if (!open) return null;
296
+ const pct = summary.steps.length ? Math.round(summary.steps.reduce((a, s) => a + (s.state === "ok" || s.state === "done" || s.state === "skipped" || s.state === "present" ? 100 : s.pct), 0) / summary.steps.length) : 0;
297
+ const failed = done && finished && !finished.ok;
298
+ const success = done && finished?.ok;
299
+ return /* @__PURE__ */ jsx("div", { style: overlay, role: "dialog", "aria-modal": "true", onClick: (e) => {
300
+ if (e.target === e.currentTarget && !started) onClose();
301
+ }, children: /* @__PURE__ */ jsxs("div", { style: card, children: [
302
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 16, fontWeight: 600, marginBottom: 4 }, children: title ?? (os === "win" ? "Set up the Android host base (WSL2)" : "Set up the Android host base") }),
303
+ /* @__PURE__ */ jsx("div", { style: { color: "var(--aix-muted, #8a8a8a)", fontSize: 13, marginBottom: 16 }, children: success ? "Done \u2014 this machine can now run cloud phones." : wslBlocked && wslFailed ? "\u81EA\u52A8\u5B89\u88C5 WSL \u8FD0\u884C\u65F6\u5931\u8D25\uFF08\u53EF\u80FD\u5185\u7F51\u65E0\u6CD5\u4E0B\u8F7D\uFF09\u3002\u8BF7\u6309\u4E0B\u65B9\u6B65\u9AA4\u624B\u52A8\u4E0B\u8F7D\u5B89\u88C5\u540E\u91CD\u8BD5\u3002" : wslBlocked ? '\u9700\u8981\u5148\u5B89\u88C5 WSL \u8FD0\u884C\u65F6\uFF1A\u6B63\u5728\u81EA\u52A8\u4E0B\u8F7D\u5E76\u5B89\u88C5\uFF08\u9700\u5728 UAC \u5F39\u7A97\u70B9"\u662F"\uFF0C\u7EA6 250MB\uFF09\uFF0C\u5B8C\u6210\u540E\u4F1A\u81EA\u52A8\u91CD\u542F\u5E76\u7EE7\u7EED\u3002' : failed ? "Setup failed. Review the steps below and retry." : started ? "Installing the redroid base. This downloads ~1\u20132 GB and may take several minutes." : "This machine needs a one-time base (kernel + container runtime + image) to host cloud phones." }),
304
+ (started || summary.steps.length > 0) && /* @__PURE__ */ jsxs(Fragment, { children: [
305
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 14 }, children: [
306
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, height: 6, background: "var(--aix-border, #2a2e35)", borderRadius: 999, overflow: "hidden" }, children: /* @__PURE__ */ jsx("div", { style: { width: `${pct}%`, height: "100%", background: failed ? "var(--aix-danger, #e5484d)" : "var(--aix-accent, #2d6cdf)", transition: "width .3s" } }) }),
307
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: "var(--aix-muted, #8a8a8a)", minWidth: 36, textAlign: "right", fontVariantNumeric: "tabular-nums" }, children: [
308
+ pct,
309
+ "%"
310
+ ] })
311
+ ] }),
312
+ /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "grid", gap: 8 }, children: summary.steps.map((s) => /* @__PURE__ */ jsxs("li", { style: { display: "flex", alignItems: "baseline", gap: 10, fontSize: 13 }, children: [
313
+ /* @__PURE__ */ jsx("span", { style: { color: stateColor(s.state), width: 16, textAlign: "center" }, children: STATE_ICON[s.state] ?? "\xB7" }),
314
+ /* @__PURE__ */ jsxs("span", { style: { flex: 1 }, children: [
315
+ s.label,
316
+ s.detail ? /* @__PURE__ */ jsxs("span", { style: { color: "var(--aix-muted, #8a8a8a)" }, children: [
317
+ " \u2014 ",
318
+ s.detail
319
+ ] }) : null
320
+ ] }),
321
+ s.state === "running" && /* @__PURE__ */ jsxs("span", { style: { color: "var(--aix-muted, #8a8a8a)" }, children: [
322
+ s.pct,
323
+ "%"
324
+ ] })
325
+ ] }, s.index)) })
326
+ ] }),
327
+ failed && finished?.error && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12, padding: 10, background: "rgba(229,72,77,0.1)", border: "1px solid var(--aix-danger, #e5484d)", borderRadius: 8, fontSize: 13 }, children: [
328
+ finished.error,
329
+ finished.hint ? /* @__PURE__ */ jsxs("div", { style: { color: "var(--aix-muted, #8a8a8a)", marginTop: 4 }, children: [
330
+ "Hint: ",
331
+ finished.hint
332
+ ] }) : null
333
+ ] }),
334
+ error && !finished && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12, color: "var(--aix-danger, #e5484d)", fontSize: 13 }, children: [
335
+ "Could not start setup: ",
336
+ error.message
337
+ ] }),
338
+ wslBlocked && wslFailed && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12, padding: 12, background: "var(--aix-editor-bg, #0e0f12)", border: "1px solid var(--aix-border, #2a2e35)", borderRadius: 8, fontSize: 12.5, lineHeight: 1.6 }, children: [
339
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 600, marginBottom: 6 }, children: "\u624B\u52A8\u5B89\u88C5 WSL \u8FD0\u884C\u65F6\uFF08\u9700 Win10 21H2/19044+\uFF09" }),
340
+ /* @__PURE__ */ jsxs("ol", { style: { margin: 0, paddingLeft: 18 }, children: [
341
+ /* @__PURE__ */ jsxs("li", { children: [
342
+ "\u4E0B\u8F7D\uFF08\u7EA6 250MB\uFF09\uFF1A",
343
+ /* @__PURE__ */ jsx("br", {}),
344
+ /* @__PURE__ */ jsx("code", { style: { fontFamily: "var(--aix-mono, ui-monospace, Menlo, monospace)", wordBreak: "break-all" }, children: "https://github.com/microsoft/WSL/releases/latest" }),
345
+ /* @__PURE__ */ jsx("br", {}),
346
+ "\u9009 ",
347
+ /* @__PURE__ */ jsx("code", { children: "wsl.<\u7248\u672C>.x64.msi" }),
348
+ "\uFF08\u5185\u7F51\u673A\u53EF\u5728\u80FD\u8054\u7F51\u7684\u7535\u8111\u4E0B\u597D\u518D\u62F7\u8FC7\u6765\uFF09\u3002"
349
+ ] }),
350
+ /* @__PURE__ */ jsxs("li", { style: { marginTop: 4 }, children: [
351
+ "\u7BA1\u7406\u5458\u8FD0\u884C\uFF1A",
352
+ /* @__PURE__ */ jsx("br", {}),
353
+ /* @__PURE__ */ jsx("code", { style: { fontFamily: "var(--aix-mono, ui-monospace, Menlo, monospace)" }, children: "msiexec /i wsl.<\u7248\u672C>.x64.msi /quiet" }),
354
+ "\uFF08\u6216\u53CC\u51FB\u5B89\u88C5\uFF09\u3002"
355
+ ] }),
356
+ /* @__PURE__ */ jsxs("li", { style: { marginTop: 4 }, children: [
357
+ "\u91CD\u542F\u540E ",
358
+ /* @__PURE__ */ jsx("code", { children: "wsl --version" }),
359
+ " \u80FD\u663E\u793A\u7248\u672C\u5373\u6210\u529F\uFF0C\u518D\u70B9\u4E0B\u65B9\u300C\u91CD\u8BD5\u300D\u3002"
360
+ ] })
361
+ ] })
362
+ ] }),
363
+ started && summary.logs.length > 0 && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
364
+ /* @__PURE__ */ jsxs("button", { style: { ...btn3(), padding: "2px 0", border: "none", color: "var(--aix-muted, #8a8a8a)", fontSize: 12 }, onClick: () => setShowLogs((v) => !v), children: [
365
+ showLogs ? "\u25BE Hide logs" : "\u25B8 Show logs",
366
+ " (",
367
+ summary.logs.length,
368
+ ")"
369
+ ] }),
370
+ showLogs && /* @__PURE__ */ jsx("pre", { style: { marginTop: 6, maxHeight: 160, overflow: "auto", background: "var(--aix-editor-bg, #0e0f12)", color: "var(--aix-editor-fg, #cfcfcf)", padding: 10, borderRadius: 8, fontSize: 12, fontFamily: "var(--aix-mono, ui-monospace, Menlo, monospace)" }, children: summary.logs.slice(-200).join("\n") })
371
+ ] }),
372
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "flex-end", gap: 10, marginTop: 18 }, children: success ? /* @__PURE__ */ jsx("button", { style: btn3(true), onClick: onClose, children: "Done" }) : wslBlocked && wslFailed ? /* @__PURE__ */ jsxs(Fragment, { children: [
373
+ /* @__PURE__ */ jsx("button", { style: btn3(), onClick: onClose, children: "\u5173\u95ED" }),
374
+ /* @__PURE__ */ jsx("button", { style: btn3(true), onClick: begin, children: "\u91CD\u8BD5\uFF08\u5DF2\u624B\u52A8\u88C5\u597D\uFF09" })
375
+ ] }) : wslBlocked ? /* @__PURE__ */ jsx("button", { style: btn3(), disabled: true, onClick: () => {
376
+ }, children: installing ? "\u6B63\u5728\u4E0B\u8F7D\u5B89\u88C5 WSL \u8FD0\u884C\u65F6\u2026" : "\u51C6\u5907\u5B89\u88C5\u2026" }) : failed ? /* @__PURE__ */ jsxs(Fragment, { children: [
377
+ /* @__PURE__ */ jsx("button", { style: btn3(), onClick: onClose, children: "Close" }),
378
+ /* @__PURE__ */ jsx("button", { style: btn3(true), onClick: begin, children: "Retry" })
379
+ ] }) : started ? /* @__PURE__ */ jsx("button", { style: btn3(), disabled: true, onClick: () => {
380
+ }, children: "Installing\u2026" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
381
+ /* @__PURE__ */ jsx("button", { style: btn3(), onClick: onClose, children: "Not now" }),
382
+ /* @__PURE__ */ jsx("button", { style: btn3(true), onClick: begin, children: "Start setup" })
383
+ ] }) })
384
+ ] }) });
385
+ }
386
+ function installedTags(node) {
387
+ const v = node.reported_caps?.installed_images;
388
+ return Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
389
+ }
390
+ var str = (v) => typeof v === "string" && v ? v : "\u2014";
391
+ var sys = (node, k) => node.reported_caps?.system?.[k];
392
+ var capBool = (node, k) => node.reported_caps?.[k] === true;
393
+ function supportsRuntimeDataRoot(node) {
394
+ const capability = node.reported_caps?.runtime_data_root;
395
+ return capability?.supported === true;
396
+ }
397
+ function envOsOf(node) {
398
+ const os = str(sys(node, "os"));
399
+ return os === "macos" ? "mac" : os === "windows" ? "win" : null;
400
+ }
401
+ function vmPlan(node) {
402
+ const eff = node.effective_caps;
403
+ const os = str(sys(node, "os"));
404
+ const out = [];
405
+ if (eff?.android) {
406
+ const imageReady = node.reported_caps?.android_image_ready !== false;
407
+ if (imageReady) {
408
+ out.push({ key: "android", label: "Android \u4E91\u624B\u673A", state: "available" });
409
+ } else {
410
+ out.push({
411
+ key: "android",
412
+ label: "Android \u4E91\u624B\u673A",
413
+ state: "needs-image",
414
+ reason: "\u8FD0\u884C\u73AF\u5883\u5DF2\u5C31\u7EEA\uFF0CAndroid \u955C\u50CF\u5C1A\u672A\u4E0B\u8F7D\uFF08\u8282\u70B9\u542F\u52A8\u65F6\u81EA\u52A8\u62C9\u53D6\uFF0C\u4E5F\u53EF\u624B\u52A8\u89E6\u53D1\uFF09"
415
+ });
416
+ }
417
+ } else if (os === "windows" || os === "macos") {
418
+ out.push({
419
+ key: "android",
420
+ label: "Android \u4E91\u624B\u673A",
421
+ state: "needs-init",
422
+ reason: `\u9700\u5B89\u88C5 redroid \u73AF\u5883\u5E95\u5EA7\uFF08${os === "windows" ? "WSL2" : "lima"}\uFF09\u540E\u53EF\u7528`
423
+ });
424
+ } else if (os === "linux") {
425
+ const feature = node.reported_caps?.redroid_feature_enabled;
426
+ const setupSupported = feature !== false && !capBool(node, "redroid_capable") && capBool(node, "redroid_setup_supported");
427
+ out.push({
428
+ key: "android",
429
+ label: "Android \u4E91\u624B\u673A",
430
+ state: setupSupported ? "needs-init" : "unavailable",
431
+ reason: setupSupported ? "\u9700\u521D\u59CB\u5316\u8FD0\u884C\u73AF\u5883\u5E76\u5728\u7EBF\u4E0B\u8F7D Android \u955C\u50CF" : feature === false ? "\u8BE5 Linux \u8282\u70B9\u7248\u672C\u672A\u5305\u542B Redroid provider\uFF0C\u8BF7\u5347\u7EA7\u8282\u70B9" : capBool(node, "redroid_capable") ? "\u8282\u70B9\u672A\u542F\u7528 Android \u4E91\u624B\u673A\u6743\u9650" : feature === true ? "Linux \u8FD0\u884C\u73AF\u5883\u5C1A\u672A\u5C31\u7EEA\uFF0C\u6B64\u8282\u70B9\u4E0D\u652F\u6301\u81EA\u52A8\u521D\u59CB\u5316" : "\u8282\u70B9\u5C1A\u672A\u4E0A\u62A5 Linux \u521D\u59CB\u5316\u80FD\u529B\uFF0C\u8BF7\u68C0\u67E5\u8282\u70B9\u7248\u672C\u4E0E\u8FD0\u884C\u73AF\u5883"
432
+ });
433
+ } else {
434
+ out.push({ key: "android", label: "Android \u4E91\u624B\u673A", state: "unavailable", reason: "\u672C\u673A\u4E0D\u652F\u6301" });
435
+ }
436
+ if (capBool(node, "vphone_capable") && eff?.ios) {
437
+ out.push({ key: "ios", label: "iOS \u865A\u62DF\u673A", state: "available" });
438
+ } else {
439
+ const arch = str(sys(node, "arch"));
440
+ const reason = os === "macos" ? arch === "aarch64" ? "\u672A\u68C0\u6D4B\u5230 vphone \u80FD\u529B\uFF08\u9700\u5728\u6B64 Mac \u4E0A\u542F\u7528\uFF09" : "iOS \u865A\u62DF\u673A\u4EC5 Apple Silicon Mac \u652F\u6301\uFF08\u6B64 Mac \u4E3A Intel\uFF09" : "iOS \u865A\u62DF\u673A\u4EC5 Apple Silicon Mac \u652F\u6301\uFF0C\u672C\u673A\u4E0D\u53EF\u7528";
441
+ out.push({ key: "ios", label: "iOS \u865A\u62DF\u673A", state: "unavailable", reason });
442
+ }
443
+ if (eff?.real_devices && (os === "windows" || os === "macos")) {
444
+ out.push({ key: "ios-real", label: "iOS \u771F\u673A", state: "available" });
445
+ } else {
446
+ out.push({
447
+ key: "ios-real",
448
+ label: "iOS \u771F\u673A",
449
+ state: "unavailable",
450
+ reason: !eff?.real_devices ? "\u8282\u70B9\u672A\u542F\u7528\u771F\u673A\u63A5\u5165" : "iOS \u771F\u673A\u9700 Windows/macOS \u5BBF\u4E3B\uFF08\u7ECF go-ios \u9A71\u52A8\uFF09"
451
+ });
452
+ }
453
+ out.push(
454
+ eff?.real_devices ? { key: "android-real", label: "Android \u771F\u673A", state: "available" } : { key: "android-real", label: "Android \u771F\u673A", state: "unavailable", reason: "\u8282\u70B9\u672A\u542F\u7528\u771F\u673A\u63A5\u5165" }
455
+ );
456
+ return out;
457
+ }
458
+ function ImagePullProgress({
459
+ events,
460
+ done,
461
+ finished,
462
+ error,
463
+ onClose
464
+ }) {
465
+ const failed = !!error || done && finished !== null && !finished.ok;
466
+ const stepsMeta = [];
467
+ const stepState = /* @__PURE__ */ new Map();
468
+ const logs = [];
469
+ for (const e of events) {
470
+ if (e.kind === "started") stepsMeta.push(...e.steps);
471
+ if (e.kind === "step") stepState.set(e.id, { state: e.state, pct: e.pct, detail: e.detail });
472
+ if (e.kind === "log") logs.push(e.line);
473
+ }
474
+ const recentLogs = logs.slice(-6);
475
+ const activeStep = stepsMeta.find((s) => stepState.get(s.id)?.state === "running");
476
+ return /* @__PURE__ */ jsxs("div", { className: "rounded border border-border bg-card/60 p-3 text-[11px] space-y-2", children: [
477
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
478
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-xs text-foreground", children: done ? failed ? "\u4E0B\u8F7D\u5931\u8D25" : "\u4E0B\u8F7D\u5B8C\u6210" : activeStep ? `\u6B63\u5728\u6267\u884C\uFF1A${activeStep.label}` : "\u6B63\u5728\u8FDE\u63A5\u8282\u70B9\u2026" }),
479
+ done && /* @__PURE__ */ jsx("button", { onClick: onClose, className: "text-muted-foreground hover:text-foreground text-[11px] ml-2", children: "\u5173\u95ED" })
480
+ ] }),
481
+ stepsMeta.length > 0 && /* @__PURE__ */ jsx("div", { className: "space-y-1", children: stepsMeta.map((s) => {
482
+ const st = stepState.get(s.id);
483
+ const dotClass = st?.state === "ok" ? "bg-success" : st?.state === "running" ? "bg-warning animate-pulse" : st?.state === "failed" ? "bg-destructive" : "bg-muted-foreground/30";
484
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
485
+ /* @__PURE__ */ jsx("span", { className: `h-1.5 w-1.5 shrink-0 rounded-full ${dotClass}` }),
486
+ /* @__PURE__ */ jsx("span", { className: "flex-1 text-muted-foreground", children: s.label }),
487
+ st?.state === "running" && st.pct > 0 && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground whitespace-nowrap", children: [
488
+ st.pct,
489
+ "%",
490
+ st.detail ? ` \xB7 ${st.detail}` : ""
491
+ ] }),
492
+ st?.detail && st.state !== "running" && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground truncate max-w-[180px]", children: st.detail })
493
+ ] }, s.id);
494
+ }) }),
495
+ activeStep && (() => {
496
+ const pct = stepState.get(activeStep.id)?.pct ?? 0;
497
+ return pct > 0 ? /* @__PURE__ */ jsx("div", { className: "h-1 w-full rounded-full bg-muted overflow-hidden", children: /* @__PURE__ */ jsx(
498
+ "div",
499
+ {
500
+ className: "h-full bg-primary rounded-full transition-all duration-300",
501
+ style: { width: `${pct}%` }
502
+ }
503
+ ) }) : null;
504
+ })(),
505
+ recentLogs.length > 0 && /* @__PURE__ */ jsx("pre", { className: "text-[10px] font-mono text-muted-foreground bg-muted/20 rounded p-2 max-h-24 overflow-y-auto leading-relaxed whitespace-pre-wrap break-all", children: recentLogs.join("\n") }),
506
+ done && failed && /* @__PURE__ */ jsx("p", { className: "text-destructive", children: error?.message ?? finished?.error ?? "\u955C\u50CF\u62C9\u53D6\u5931\u8D25\uFF0C\u8BF7\u67E5\u770B\u6B65\u9AA4\u8BE6\u60C5\u540E\u91CD\u8BD5" })
507
+ ] });
508
+ }
509
+ function VmCapRow({
510
+ node,
511
+ vm,
512
+ onInit,
513
+ onDownload,
514
+ downloading
515
+ }) {
516
+ const canInit = vm.state === "needs-init" && node.is_owner && (envOsOf(node) !== null || sys(node, "os") === "linux" && capBool(node, "redroid_setup_supported"));
517
+ const canDownload = vm.state === "needs-image" && node.is_owner;
518
+ const dot = vm.state === "available" ? "bg-success" : vm.state === "needs-init" ? "bg-warning" : vm.state === "needs-image" ? "bg-warning" : "bg-muted-foreground/40";
519
+ const badge = vm.state === "available" ? "\u53EF\u7528" : vm.state === "needs-init" ? "\u9700\u521D\u59CB\u5316" : vm.state === "needs-image" ? "\u955C\u50CF\u672A\u5C31\u7EEA" : "\u672C\u673A\u4E0D\u53EF\u7528";
520
+ const badgeClass = vm.state === "available" ? "text-success" : vm.state === "needs-init" ? "text-warning" : vm.state === "needs-image" ? "text-warning" : "text-muted-foreground";
521
+ return /* @__PURE__ */ jsxs("div", { className: "rounded border border-border px-3 py-2", children: [
522
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
523
+ /* @__PURE__ */ jsx("span", { className: `h-1.5 w-1.5 shrink-0 rounded-full ${dot}` }),
524
+ /* @__PURE__ */ jsx("span", { className: "flex-1 text-xs text-foreground", children: vm.label }),
525
+ /* @__PURE__ */ jsx("span", { className: `text-[11px] ${badgeClass}`, children: badge }),
526
+ canInit && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "primary", onClick: onInit, disabled: downloading, className: "ml-2", children: downloading ? "\u521D\u59CB\u5316\u4E2D\u2026" : "\u521D\u59CB\u5316" }),
527
+ canDownload && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "primary", onClick: onDownload, disabled: downloading, className: "ml-2", children: downloading ? "\u4E0B\u8F7D\u4E2D\u2026" : "\u4E0B\u8F7D\u955C\u50CF" })
528
+ ] }),
529
+ vm.reason && /* @__PURE__ */ jsx("p", { className: "mt-1 pl-3.5 text-[11px] text-muted-foreground", children: vm.reason })
530
+ ] });
531
+ }
532
+ function DataRootConfig({ node }) {
533
+ const client = useClient();
534
+ const [dataRoot, setDataRoot] = useState("");
535
+ const [saving, setSaving] = useState(false);
536
+ const [message, setMessage] = useState(null);
537
+ if (!supportsRuntimeDataRoot(node)) {
538
+ return /* @__PURE__ */ jsx("p", { className: "mt-3 text-[11px] text-muted-foreground", children: "\u6570\u636E\u6839\u76EE\u5F55\u914D\u7F6E\u9700\u8981\u652F\u6301\u6B64\u529F\u80FD\u7684\u65B0\u7248 Linux \u8282\u70B9\u3002" });
539
+ }
540
+ const save = async () => {
541
+ if (!dataRoot.trim() || saving) return;
542
+ setSaving(true);
543
+ setMessage(null);
544
+ try {
545
+ const receipt = await client.nodes.setDataRoot(node.id, dataRoot);
546
+ setMessage(
547
+ receipt.restart_required ? `\u5DF2\u4FDD\u5B58 ${receipt.data_root}\u3002\u8BF7\u91CD\u542F\u8282\u70B9\u540E\uFF0C\u65B0\u5EFA Redroid \u5BB9\u5668\u624D\u4F1A\u4F7F\u7528\u6B64\u76EE\u5F55\u3002` : `\u5DF2\u4FDD\u5B58 ${receipt.data_root}\u3002`
548
+ );
549
+ } catch (error) {
550
+ setMessage(error instanceof Error ? error.message : String(error));
551
+ } finally {
552
+ setSaving(false);
553
+ }
554
+ };
555
+ return /* @__PURE__ */ jsxs("div", { className: "mt-3 rounded border border-border p-3 space-y-2", children: [
556
+ /* @__PURE__ */ jsxs("div", { children: [
557
+ /* @__PURE__ */ jsx("h4", { className: "text-xs font-medium text-foreground", children: "Redroid \u6570\u636E\u6839\u76EE\u5F55" }),
558
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-[11px] text-muted-foreground", children: "\u4EC5\u63A5\u53D7\u8282\u70B9\u539F\u751F\u6587\u4EF6\u7CFB\u7EDF\u7684\u7EDD\u5BF9\u8DEF\u5F84\u3002\u4E0D\u4F1A\u8FC1\u79FB\u6216\u5220\u9664\u73B0\u6709\u6570\u636E\uFF0C\u4FDD\u5B58\u540E\u9700\u624B\u52A8\u91CD\u542F\u8282\u70B9\u3002" })
559
+ ] }),
560
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
561
+ /* @__PURE__ */ jsx(
562
+ Input,
563
+ {
564
+ value: dataRoot,
565
+ onChange: (event) => setDataRoot(event.target.value),
566
+ placeholder: "/var/lib/aixautobot/instances",
567
+ "aria-label": "Redroid \u6570\u636E\u6839\u76EE\u5F55"
568
+ }
569
+ ),
570
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "primary", disabled: saving || !dataRoot.trim(), onClick: () => void save(), children: saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58" })
571
+ ] }),
572
+ message && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: message })
573
+ ] });
574
+ }
575
+ function AndroidImageRows({ images, installed }) {
576
+ return /* @__PURE__ */ jsx("div", { className: "mt-1.5 ml-3.5 space-y-1 border-l border-border/60 pl-3", children: images.map((img) => {
577
+ const has = installed.includes(img.image_tag);
578
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
579
+ /* @__PURE__ */ jsx("span", { className: `h-1 w-1 shrink-0 rounded-full ${has ? "bg-success" : "bg-muted-foreground/40"}` }),
580
+ /* @__PURE__ */ jsx("span", { className: "flex-1 text-[11px] text-muted-foreground", children: img.label }),
581
+ /* @__PURE__ */ jsx("span", { className: `text-[10px] ${has ? "text-success" : "text-muted-foreground"}`, children: has ? "\u5DF2\u5B89\u88C5" : "\u672A\u5B89\u88C5" })
582
+ ] }, img.id);
583
+ }) });
584
+ }
585
+ function NodeCapabilityPanel({ node, isTauri, onInstallWsl }) {
586
+ const client = useClient();
587
+ const [init, setInit] = useState(null);
588
+ const initOs = envOsOf(node);
589
+ const envRun = useEnvRun();
590
+ const imagePulling = envRun.reqId !== null && !envRun.done;
591
+ const showImagePanel = envRun.reqId !== null;
592
+ const handleDownload = async () => {
593
+ const setupOs = sys(node, "os") === "linux" ? "linux" : initOs;
594
+ if (!setupOs || imagePulling) return;
595
+ try {
596
+ await envRun.setup(node.id, { os: setupOs });
597
+ } catch {
598
+ }
599
+ };
600
+ const [androidImages, setAndroidImages] = useState([]);
601
+ useEffect(() => {
602
+ let cancelled = false;
603
+ client.images.options("android").then((imgs) => {
604
+ if (!cancelled) setAndroidImages(imgs);
605
+ }).catch(() => {
606
+ });
607
+ return () => {
608
+ cancelled = true;
609
+ };
610
+ }, [client]);
611
+ const installed = installedTags(node);
612
+ return /* @__PURE__ */ jsxs("section", { children: [
613
+ /* @__PURE__ */ jsx("h3", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-2", children: "\u865A\u62DF\u673A\u4E0E\u8BBE\u5907\u80FD\u529B" }),
614
+ !node.reported_caps ? /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: "\u80FD\u529B\u672A\u77E5\uFF1A\u8282\u70B9\u5C1A\u672A\u8FDE\u63A5\u6216\u4E3A\u65E7\u7248\u672C\uFF08\u9700\u5347\u7EA7\u4EE5\u4E0A\u62A5\u80FD\u529B\uFF09\u3002" }) : /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
615
+ vmPlan(node).map((vm) => /* @__PURE__ */ jsxs("div", { children: [
616
+ /* @__PURE__ */ jsx(
617
+ VmCapRow,
618
+ {
619
+ node,
620
+ vm,
621
+ onInit: () => {
622
+ if (sys(node, "os") === "linux") void handleDownload();
623
+ else setInit(vm);
624
+ },
625
+ onDownload: handleDownload,
626
+ downloading: imagePulling
627
+ }
628
+ ),
629
+ vm.key === "android" && vm.state !== "unavailable" && androidImages.length > 0 && /* @__PURE__ */ jsx(AndroidImageRows, { images: androidImages, installed })
630
+ ] }, vm.key)),
631
+ showImagePanel && /* @__PURE__ */ jsx(
632
+ ImagePullProgress,
633
+ {
634
+ events: envRun.events,
635
+ done: envRun.done,
636
+ finished: envRun.finished,
637
+ error: envRun.error,
638
+ onClose: () => envRun.close()
639
+ }
640
+ )
641
+ ] }),
642
+ node.is_owner && /* @__PURE__ */ jsx(DataRootConfig, { node }),
643
+ init && initOs && /* @__PURE__ */ jsx(
644
+ EnvInitDialog,
645
+ {
646
+ open: true,
647
+ nodeId: node.id,
648
+ os: initOs,
649
+ mode: "prompt",
650
+ title: `\u521D\u59CB\u5316 ${init.label} \u73AF\u5883\u5E95\u5EA7`,
651
+ onInstallWsl: isTauri ? onInstallWsl : void 0,
652
+ onClose: () => setInit(null),
653
+ onReady: async () => {
654
+ try {
655
+ await client.nodes.restart(node.id);
656
+ } catch {
657
+ }
658
+ setInit(null);
659
+ }
660
+ }
661
+ )
662
+ ] });
663
+ }
664
+
665
+ export { AIX_CSS_VARS, DebugConsole, EnvInitDialog, NodeCapabilityPanel, ScriptEditor, ScriptPanel, aixCodeMirrorTheme, vmPlan };