@aixnn/aixautots 0.0.0-stage → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +239 -2
- package/dist/ScriptStudio-DaCqOF5H.d.ts +43 -0
- package/dist/agent-runner-V60B0is1.d.ts +57 -0
- package/dist/automation-queue-8HqRJV_K.d.ts +72 -0
- package/dist/chunk-536WVCTB.js +5744 -0
- package/dist/chunk-5BMFMDQR.js +18 -0
- package/dist/chunk-64Q7BBIQ.js +1347 -0
- package/dist/chunk-6YUAACW6.js +866 -0
- package/dist/chunk-AIUOLJZA.js +412 -0
- package/dist/chunk-CVV7FOMQ.js +175 -0
- package/dist/chunk-EHF2WQUR.js +2042 -0
- package/dist/chunk-EVHTAXQG.js +273 -0
- package/dist/chunk-FW23FLAF.js +56 -0
- package/dist/chunk-GKEPMDXO.js +410 -0
- package/dist/chunk-H7MHGYF3.js +341 -0
- package/dist/chunk-MRQ7VJFJ.js +1074 -0
- package/dist/chunk-NWFYTFMI.js +65 -0
- package/dist/chunk-OEZFYKL6.js +1 -0
- package/dist/chunk-PCYERBNZ.js +1564 -0
- package/dist/chunk-PUPVZRKZ.js +668 -0
- package/dist/chunk-SUKFJ73W.js +217 -0
- package/dist/config-Bm1hmXhD.d.ts +1380 -0
- package/dist/coords-LPR_MPyp.d.ts +22 -0
- package/dist/core/index.d.ts +135 -0
- package/dist/core/index.js +5 -0
- package/dist/device-normalize-BaThgnH7.d.ts +43 -0
- package/dist/device-session-KZVGLPOI.js +2 -0
- package/dist/i18n/index.d.ts +30 -0
- package/dist/i18n/index.js +1 -0
- package/dist/mirror-session-C_1vFkRV.d.ts +108 -0
- package/dist/nav/index.d.ts +19 -0
- package/dist/nav/index.js +1 -0
- package/dist/react/index.d.ts +487 -0
- package/dist/react/index.js +7 -0
- package/dist/script-debugger-Cw5jVRoz.d.ts +114 -0
- package/dist/task-agent-runner-eD9-SCJz.d.ts +160 -0
- package/dist/types-C2WXVcam.d.ts +1005 -0
- package/dist/ui/billing.d.ts +48 -0
- package/dist/ui/billing.js +425 -0
- package/dist/ui/device-create.d.ts +55 -0
- package/dist/ui/device-create.js +9 -0
- package/dist/ui/device-mirror.d.ts +42 -0
- package/dist/ui/device-mirror.js +10 -0
- package/dist/ui/devices.d.ts +223 -0
- package/dist/ui/devices.js +15 -0
- package/dist/ui/editor.d.ts +64 -0
- package/dist/ui/editor.js +344 -0
- package/dist/ui/index.d.ts +104 -0
- package/dist/ui/index.js +676 -0
- package/dist/ui/inspector.d.ts +5 -0
- package/dist/ui/inspector.js +1250 -0
- package/dist/ui/market.d.ts +10 -0
- package/dist/ui/market.js +60 -0
- package/dist/ui/mirror.d.ts +52 -0
- package/dist/ui/mirror.js +3 -0
- package/dist/ui/primitives/index.d.ts +101 -0
- package/dist/ui/primitives/index.js +1 -0
- package/dist/ui/script-studio.d.ts +85 -0
- package/dist/ui/script-studio.js +10 -0
- package/dist/ui/scripts.d.ts +18 -0
- package/dist/ui/scripts.js +210 -0
- package/dist/ui/tasks.d.ts +10 -0
- package/dist/ui/tasks.js +1039 -0
- package/dist/use-script-run-Diay7ibY.d.ts +50 -0
- package/package.json +76 -4
- package/styles.css +97 -0
- package/tailwind-preset.cjs +84 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { MirrorView, ControlBar } from './chunk-CVV7FOMQ.js';
|
|
2
|
+
import { useMirror, useDevices } from './chunk-EHF2WQUR.js';
|
|
3
|
+
import { useClient } from './chunk-SUKFJ73W.js';
|
|
4
|
+
import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
5
|
+
import { RotateCw, Minimize2, Maximize2, X } from 'lucide-react';
|
|
6
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
var PHASES = {
|
|
9
|
+
android: [
|
|
10
|
+
[25, "\u6B63\u5728\u8FDE\u63A5\u8BBE\u5907\u2026"],
|
|
11
|
+
[55, "\u5EFA\u7ACB\u52A0\u5BC6\u901A\u9053\u2026"],
|
|
12
|
+
[80, "\u62C9\u53D6\u5B9E\u65F6\u753B\u9762\u2026"],
|
|
13
|
+
[95, "\u5373\u5C06\u5448\u73B0\u2026"]
|
|
14
|
+
],
|
|
15
|
+
ios: [
|
|
16
|
+
[25, "\u6B63\u5728\u8FDE\u63A5\u8BBE\u5907\u2026"],
|
|
17
|
+
[55, "\u5EFA\u7ACB\u52A0\u5BC6\u901A\u9053\u2026"],
|
|
18
|
+
[80, "\u62C9\u53D6\u5B9E\u65F6\u753B\u9762\u2026"],
|
|
19
|
+
[95, "\u5373\u5C06\u5448\u73B0\u2026"]
|
|
20
|
+
]
|
|
21
|
+
};
|
|
22
|
+
function phaseLabel(phases, p) {
|
|
23
|
+
for (const [ceil, label] of phases) if (p <= ceil) return label;
|
|
24
|
+
return "\u5373\u5C06\u5448\u73B0\u2026";
|
|
25
|
+
}
|
|
26
|
+
function DeviceBootSplash({ error, expectedMs = 3e3, platform = "android", className }) {
|
|
27
|
+
const [pct, setPct] = useState(4);
|
|
28
|
+
const startRef = useRef(Date.now());
|
|
29
|
+
const phases = PHASES[platform];
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (error) return;
|
|
32
|
+
startRef.current = Date.now();
|
|
33
|
+
setPct(4);
|
|
34
|
+
const id = setInterval(() => {
|
|
35
|
+
const elapsed = Date.now() - startRef.current;
|
|
36
|
+
const target = 95 * (1 - Math.exp(-elapsed / (expectedMs * 0.55)));
|
|
37
|
+
setPct((prev) => Math.max(prev, Math.min(95, Math.round(target))));
|
|
38
|
+
}, 250);
|
|
39
|
+
return () => clearInterval(id);
|
|
40
|
+
}, [error, expectedMs]);
|
|
41
|
+
if (error) {
|
|
42
|
+
return /* @__PURE__ */ jsxs(
|
|
43
|
+
"div",
|
|
44
|
+
{
|
|
45
|
+
className: `flex h-full w-full flex-col items-center justify-center gap-3 bg-black/80 px-6 text-center ${className ?? ""}`,
|
|
46
|
+
children: [
|
|
47
|
+
/* @__PURE__ */ jsx("div", { className: "text-2xl", children: "\u26A0\uFE0F" }),
|
|
48
|
+
/* @__PURE__ */ jsxs("div", { className: "text-sm text-white/80", children: [
|
|
49
|
+
"\u8FDE\u63A5\u5931\u8D25\uFF1A",
|
|
50
|
+
error
|
|
51
|
+
] }),
|
|
52
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs text-white/40", children: "\u8BBE\u5907\u9700\u5728\u7EBF\uFF1B\u82E5\u521A\u521B\u5EFA\uFF0C\u8BF7\u7B49\u5F85\u542F\u52A8\u5B8C\u6210\u540E\u91CD\u8BD5" })
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return /* @__PURE__ */ jsxs(
|
|
58
|
+
"div",
|
|
59
|
+
{
|
|
60
|
+
className: `flex h-full w-full flex-col items-center justify-center gap-5 bg-gradient-to-b from-zinc-900 to-black px-8 ${className ?? ""}`,
|
|
61
|
+
children: [
|
|
62
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-center", children: [
|
|
63
|
+
/* @__PURE__ */ jsx(
|
|
64
|
+
"span",
|
|
65
|
+
{
|
|
66
|
+
className: `absolute h-20 w-20 animate-ping rounded-full ${platform === "ios" ? "bg-sky-300/20" : "bg-emerald-400/20"}`
|
|
67
|
+
}
|
|
68
|
+
),
|
|
69
|
+
/* @__PURE__ */ jsx(
|
|
70
|
+
"span",
|
|
71
|
+
{
|
|
72
|
+
className: `absolute h-16 w-16 animate-pulse rounded-full ${platform === "ios" ? "bg-sky-300/10" : "bg-emerald-400/10"}`
|
|
73
|
+
}
|
|
74
|
+
),
|
|
75
|
+
platform === "ios" ? /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-12 w-12 text-sky-200", fill: "currentColor", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M16.37 12.78c-.02-2.05 1.67-3.03 1.75-3.08-.95-1.39-2.43-1.58-2.96-1.6-1.26-.13-2.46.74-3.1.74-.64 0-1.62-.72-2.66-.7-1.37.02-2.63.8-3.33 2.02-1.42 2.47-.36 6.12 1.02 8.13.67.98 1.48 2.08 2.53 2.04 1.02-.04 1.4-.66 2.63-.66 1.22 0 1.57.66 2.65.64 1.09-.02 1.79-1 2.46-1.98.77-1.13 1.09-2.23 1.11-2.29-.02-.01-2.13-.82-2.15-3.25ZM14.4 6.45c.56-.68.94-1.62.84-2.56-.81.03-1.79.54-2.37 1.21-.52.6-.97 1.56-.85 2.48.9.07 1.82-.46 2.38-1.13Z" }) }) : /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-12 w-12 text-emerald-400", fill: "currentColor", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M6 9a6 6 0 0 1 12 0v6a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V9Zm2.5-3.2 -1-1.6a.4.4 0 0 1 .7-.4l1.05 1.7a6.7 6.7 0 0 1 3.5 0l1.05-1.7a.4.4 0 1 1 .7.4l-1 1.6A6 6 0 0 0 8.5 5.8ZM9.5 9.2a.9.9 0 1 0 0 1.8.9.9 0 0 0 0-1.8Zm5 0a.9.9 0 1 0 0 1.8.9.9 0 0 0 0-1.8ZM5 11a1 1 0 0 1 1 1v4a1 1 0 1 1-2 0v-4a1 1 0 0 1 1-1Zm14 0a1 1 0 0 1 1 1v4a1 1 0 1 1-2 0v-4a1 1 0 0 1 1-1ZM9 17h6a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1 1 1 0 1 1-2 0h-2a1 1 0 1 1-2 0 1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1Z" }) })
|
|
76
|
+
] }),
|
|
77
|
+
/* @__PURE__ */ jsxs("div", { className: "w-full max-w-[220px] space-y-2", children: [
|
|
78
|
+
/* @__PURE__ */ jsx("div", { className: "h-1.5 w-full overflow-hidden rounded-full bg-white/10", children: /* @__PURE__ */ jsx(
|
|
79
|
+
"div",
|
|
80
|
+
{
|
|
81
|
+
className: `h-full rounded-full transition-[width] duration-300 ease-out ${platform === "ios" ? "bg-sky-300" : "bg-emerald-400"}`,
|
|
82
|
+
style: { width: `${pct}%` }
|
|
83
|
+
}
|
|
84
|
+
) }),
|
|
85
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs", children: [
|
|
86
|
+
/* @__PURE__ */ jsx("span", { className: "text-white/70", children: phaseLabel(phases, pct) }),
|
|
87
|
+
/* @__PURE__ */ jsxs("span", { className: "tabular-nums text-white/40", children: [
|
|
88
|
+
pct,
|
|
89
|
+
"%"
|
|
90
|
+
] })
|
|
91
|
+
] })
|
|
92
|
+
] }),
|
|
93
|
+
/* @__PURE__ */ jsx("div", { className: "text-[11px] text-white/30", children: "\u6B63\u5728\u8FDE\u63A5\u5B9E\u65F6\u753B\u9762\uFF0C\u5C31\u7EEA\u540E\u81EA\u52A8\u663E\u793A" })
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/core/quality-auto.ts
|
|
100
|
+
var RTT_HIGH = 0.15;
|
|
101
|
+
var RTT_LOW = 0.04;
|
|
102
|
+
var HYSTERESIS = 0.03;
|
|
103
|
+
function pickAutoProfile(rtt, current) {
|
|
104
|
+
if (current === "low_latency") {
|
|
105
|
+
if (rtt >= RTT_HIGH - HYSTERESIS) return "low_latency";
|
|
106
|
+
return rtt < RTT_LOW ? "high_quality" : "balanced";
|
|
107
|
+
}
|
|
108
|
+
if (current === "high_quality") {
|
|
109
|
+
if (rtt <= RTT_LOW + HYSTERESIS) return "high_quality";
|
|
110
|
+
return rtt > RTT_HIGH ? "low_latency" : "balanced";
|
|
111
|
+
}
|
|
112
|
+
if (rtt > RTT_HIGH) return "low_latency";
|
|
113
|
+
if (rtt < RTT_LOW) return "high_quality";
|
|
114
|
+
return "balanced";
|
|
115
|
+
}
|
|
116
|
+
var PROFILE_LABELS = {
|
|
117
|
+
balanced: "\u5E73\u8861",
|
|
118
|
+
low_latency: "\u4F4E\u5EF6\u8FDF",
|
|
119
|
+
high_quality: "\u9AD8\u753B\u8D28"
|
|
120
|
+
};
|
|
121
|
+
var DEFAULT_SIZE = { width: 1080, height: 2340 };
|
|
122
|
+
var AUTO_QUALITY_FIRST_CHECK_MS = 2e3;
|
|
123
|
+
var AUTO_QUALITY_INTERVAL_MS = 8e3;
|
|
124
|
+
var AUTO_QUALITY_MIN_SWITCH_INTERVAL_MS = 15e3;
|
|
125
|
+
function MirrorSurface({ deviceId, platform = "android", onSize, videoAspect, shortcutBar, className }) {
|
|
126
|
+
const { stream, error, sendInput, setQuality, session } = useMirror(deviceId);
|
|
127
|
+
const [size, setSize] = useState(DEFAULT_SIZE);
|
|
128
|
+
const [hasFrame, setHasFrame] = useState(false);
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
setHasFrame(false);
|
|
131
|
+
}, [deviceId, stream]);
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
setSize(DEFAULT_SIZE);
|
|
134
|
+
onSize?.(DEFAULT_SIZE);
|
|
135
|
+
}, [deviceId, onSize]);
|
|
136
|
+
const [profile, setProfile] = useState("balanced");
|
|
137
|
+
const manualRef = useRef(false);
|
|
138
|
+
const changeProfile = useCallback(
|
|
139
|
+
(p, manual) => {
|
|
140
|
+
if (manual) manualRef.current = true;
|
|
141
|
+
setProfile(p);
|
|
142
|
+
setQuality(p);
|
|
143
|
+
},
|
|
144
|
+
[setQuality]
|
|
145
|
+
);
|
|
146
|
+
const [landscape, setLandscape] = useState(false);
|
|
147
|
+
const toggleOrientation = useCallback(() => {
|
|
148
|
+
setLandscape((prev) => {
|
|
149
|
+
const next = !prev;
|
|
150
|
+
sendInput({ t: "rotate", orientation: next ? "landscape" : "portrait" });
|
|
151
|
+
return next;
|
|
152
|
+
});
|
|
153
|
+
}, [sendInput]);
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
manualRef.current = false;
|
|
156
|
+
setProfile("balanced");
|
|
157
|
+
setLandscape(false);
|
|
158
|
+
}, [deviceId]);
|
|
159
|
+
const profileRef = useRef(profile);
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
profileRef.current = profile;
|
|
162
|
+
}, [profile]);
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (!stream || !session) return;
|
|
165
|
+
let cancelled = false;
|
|
166
|
+
let lastSwitchAt = 0;
|
|
167
|
+
const check = async () => {
|
|
168
|
+
if (cancelled || manualRef.current) return;
|
|
169
|
+
const rtt = await session.currentRtt();
|
|
170
|
+
if (cancelled || rtt === null || manualRef.current) return;
|
|
171
|
+
const next = pickAutoProfile(rtt, profileRef.current);
|
|
172
|
+
if (next === profileRef.current) return;
|
|
173
|
+
const now = Date.now();
|
|
174
|
+
if (now - lastSwitchAt < AUTO_QUALITY_MIN_SWITCH_INTERVAL_MS) return;
|
|
175
|
+
lastSwitchAt = now;
|
|
176
|
+
changeProfile(next, false);
|
|
177
|
+
};
|
|
178
|
+
const first = setTimeout(check, AUTO_QUALITY_FIRST_CHECK_MS);
|
|
179
|
+
const interval = setInterval(check, AUTO_QUALITY_INTERVAL_MS);
|
|
180
|
+
return () => {
|
|
181
|
+
cancelled = true;
|
|
182
|
+
clearTimeout(first);
|
|
183
|
+
clearInterval(interval);
|
|
184
|
+
};
|
|
185
|
+
}, [stream, session, changeProfile]);
|
|
186
|
+
const reportSize = useCallback(
|
|
187
|
+
(real) => {
|
|
188
|
+
if (real.width > 0 && real.height > 0) {
|
|
189
|
+
setSize(real);
|
|
190
|
+
onSize?.(real);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
[onSize]
|
|
194
|
+
);
|
|
195
|
+
const handleFirstFrame = useCallback(
|
|
196
|
+
(real) => {
|
|
197
|
+
setHasFrame(true);
|
|
198
|
+
reportSize(real);
|
|
199
|
+
},
|
|
200
|
+
[reportSize]
|
|
201
|
+
);
|
|
202
|
+
return /* @__PURE__ */ jsxs("div", { className: `flex min-h-0 flex-col items-center gap-2 ${className ?? ""}`, children: [
|
|
203
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-h-0 w-full flex-1 items-stretch justify-center gap-2", children: [
|
|
204
|
+
shortcutBar,
|
|
205
|
+
/* @__PURE__ */ jsxs(
|
|
206
|
+
"div",
|
|
207
|
+
{
|
|
208
|
+
className: `relative min-h-0 overflow-hidden rounded-lg ${videoAspect ? "h-full" : "h-full flex-1"}`,
|
|
209
|
+
style: videoAspect ? { aspectRatio: String(videoAspect) } : void 0,
|
|
210
|
+
children: [
|
|
211
|
+
stream && /* @__PURE__ */ jsx(
|
|
212
|
+
MirrorView,
|
|
213
|
+
{
|
|
214
|
+
stream,
|
|
215
|
+
deviceSize: size,
|
|
216
|
+
onInput: sendInput,
|
|
217
|
+
onFirstFrame: handleFirstFrame,
|
|
218
|
+
onResize: reportSize,
|
|
219
|
+
continuousTouch: platform === "android",
|
|
220
|
+
className: "h-full w-full"
|
|
221
|
+
}
|
|
222
|
+
),
|
|
223
|
+
(!stream || !hasFrame) && /* @__PURE__ */ jsx("div", { className: "absolute inset-0", children: /* @__PURE__ */ jsx(
|
|
224
|
+
DeviceBootSplash,
|
|
225
|
+
{
|
|
226
|
+
error: error ? error.message : null,
|
|
227
|
+
platform,
|
|
228
|
+
className: "rounded-lg"
|
|
229
|
+
}
|
|
230
|
+
) })
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
] }),
|
|
235
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
236
|
+
/* @__PURE__ */ jsx(
|
|
237
|
+
ControlBar,
|
|
238
|
+
{
|
|
239
|
+
platform,
|
|
240
|
+
onBack: () => sendInput({ t: "back" }),
|
|
241
|
+
onHome: () => sendInput({ t: "home" }),
|
|
242
|
+
onMenu: () => sendInput({ t: "key", code: "app_switch" })
|
|
243
|
+
}
|
|
244
|
+
),
|
|
245
|
+
/* @__PURE__ */ jsx(
|
|
246
|
+
"select",
|
|
247
|
+
{
|
|
248
|
+
"aria-label": "\u753B\u8D28\u6863\u4F4D",
|
|
249
|
+
value: profile,
|
|
250
|
+
onChange: (e) => changeProfile(e.target.value, true),
|
|
251
|
+
className: "rounded border border-border bg-secondary px-2 py-1 text-xs text-foreground outline-none hover:bg-card",
|
|
252
|
+
children: Object.keys(PROFILE_LABELS).map((p) => /* @__PURE__ */ jsx("option", { value: p, children: PROFILE_LABELS[p] }, p))
|
|
253
|
+
}
|
|
254
|
+
),
|
|
255
|
+
platform === "android" && /* @__PURE__ */ jsxs(
|
|
256
|
+
"button",
|
|
257
|
+
{
|
|
258
|
+
type: "button",
|
|
259
|
+
onClick: toggleOrientation,
|
|
260
|
+
"aria-label": landscape ? "\u5207\u56DE\u7AD6\u5C4F" : "\u5207\u6362\u6A2A\u5C4F",
|
|
261
|
+
title: landscape ? "\u5207\u56DE\u7AD6\u5C4F" : "\u5207\u6362\u6A2A\u5C4F",
|
|
262
|
+
"aria-pressed": landscape,
|
|
263
|
+
className: `flex items-center gap-1 rounded border border-border px-2 py-1 text-xs outline-none hover:bg-card ${landscape ? "bg-primary/15 text-primary" : "bg-secondary text-foreground"}`,
|
|
264
|
+
children: [
|
|
265
|
+
/* @__PURE__ */ jsx(RotateCw, { className: "h-3.5 w-3.5" }),
|
|
266
|
+
landscape ? "\u7AD6\u5C4F" : "\u6A2A\u5C4F"
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
)
|
|
270
|
+
] })
|
|
271
|
+
] });
|
|
272
|
+
}
|
|
273
|
+
function DeviceMirror({ deviceId, deviceName, platform = "android", shortcutBar, onClose }) {
|
|
274
|
+
const open = deviceId !== null;
|
|
275
|
+
const client = useClient();
|
|
276
|
+
const { data: devices } = useDevices();
|
|
277
|
+
const [size, setSize] = useState({ width: 1080, height: 2340 });
|
|
278
|
+
const overlayRef = useRef(null);
|
|
279
|
+
const [fullscreen, setFullscreen] = useState(false);
|
|
280
|
+
const sleepTimeoutMin = useMemo(() => {
|
|
281
|
+
if (!deviceId) return 30;
|
|
282
|
+
const d = devices?.find((dv) => dv.external_id === deviceId);
|
|
283
|
+
const v = d?.meta?.sleep_timeout_min;
|
|
284
|
+
return typeof v === "number" ? v : 30;
|
|
285
|
+
}, [devices, deviceId]);
|
|
286
|
+
const lastActivityRef = useRef(Date.now());
|
|
287
|
+
const resetActivity = useCallback(() => {
|
|
288
|
+
lastActivityRef.current = Date.now();
|
|
289
|
+
}, []);
|
|
290
|
+
useEffect(() => {
|
|
291
|
+
if (!open || !deviceId || sleepTimeoutMin === 0) return;
|
|
292
|
+
const ms = sleepTimeoutMin * 60 * 1e3;
|
|
293
|
+
const id = setInterval(() => {
|
|
294
|
+
if (Date.now() - lastActivityRef.current >= ms) {
|
|
295
|
+
void client.devices.sleep(deviceId).then(() => onClose()).catch(() => {
|
|
296
|
+
});
|
|
297
|
+
clearInterval(id);
|
|
298
|
+
}
|
|
299
|
+
}, 1e4);
|
|
300
|
+
return () => clearInterval(id);
|
|
301
|
+
}, [open, deviceId, sleepTimeoutMin, onClose, client]);
|
|
302
|
+
const enterFullscreen = useCallback(async () => {
|
|
303
|
+
const el = overlayRef.current;
|
|
304
|
+
if (!el) return;
|
|
305
|
+
try {
|
|
306
|
+
if (el.requestFullscreen) await el.requestFullscreen();
|
|
307
|
+
else if (el.webkitRequestFullscreen) await el.webkitRequestFullscreen();
|
|
308
|
+
} catch {
|
|
309
|
+
}
|
|
310
|
+
}, []);
|
|
311
|
+
const exitFullscreen = useCallback(async () => {
|
|
312
|
+
const doc = document;
|
|
313
|
+
try {
|
|
314
|
+
if (doc.fullscreenElement && doc.exitFullscreen) await doc.exitFullscreen();
|
|
315
|
+
else if (doc.webkitFullscreenElement && doc.webkitExitFullscreen) await doc.webkitExitFullscreen();
|
|
316
|
+
} catch {
|
|
317
|
+
}
|
|
318
|
+
}, []);
|
|
319
|
+
const toggleFullscreen = useCallback(() => {
|
|
320
|
+
if (fullscreen) void exitFullscreen();
|
|
321
|
+
else void enterFullscreen();
|
|
322
|
+
}, [fullscreen, enterFullscreen, exitFullscreen]);
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
const onFsChange = () => {
|
|
325
|
+
const doc = document;
|
|
326
|
+
const fsEl = doc.fullscreenElement || doc.webkitFullscreenElement || null;
|
|
327
|
+
setFullscreen(fsEl === overlayRef.current);
|
|
328
|
+
};
|
|
329
|
+
document.addEventListener("fullscreenchange", onFsChange);
|
|
330
|
+
document.addEventListener("webkitfullscreenchange", onFsChange);
|
|
331
|
+
return () => {
|
|
332
|
+
document.removeEventListener("fullscreenchange", onFsChange);
|
|
333
|
+
document.removeEventListener("webkitfullscreenchange", onFsChange);
|
|
334
|
+
};
|
|
335
|
+
}, []);
|
|
336
|
+
useEffect(() => {
|
|
337
|
+
if (!open) return;
|
|
338
|
+
const onKey = (e) => {
|
|
339
|
+
resetActivity();
|
|
340
|
+
if (e.key === "Escape" && !fullscreen) onClose();
|
|
341
|
+
};
|
|
342
|
+
window.addEventListener("keydown", onKey);
|
|
343
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
344
|
+
}, [open, onClose, resetActivity, fullscreen]);
|
|
345
|
+
useEffect(() => {
|
|
346
|
+
if (!open) void exitFullscreen();
|
|
347
|
+
}, [open, exitFullscreen]);
|
|
348
|
+
const stop = useCallback((e) => e.stopPropagation(), []);
|
|
349
|
+
if (!open) return null;
|
|
350
|
+
const aspect = size.width > 0 && size.height > 0 ? size.width / size.height : 9 / 16;
|
|
351
|
+
return /* @__PURE__ */ jsx(
|
|
352
|
+
"div",
|
|
353
|
+
{
|
|
354
|
+
ref: overlayRef,
|
|
355
|
+
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm",
|
|
356
|
+
onClick: onClose,
|
|
357
|
+
onMouseMove: resetActivity,
|
|
358
|
+
onPointerDown: resetActivity,
|
|
359
|
+
children: /* @__PURE__ */ jsxs(
|
|
360
|
+
"div",
|
|
361
|
+
{
|
|
362
|
+
className: `flex flex-col items-center ${fullscreen ? "h-full w-full gap-1 py-2" : "gap-3"}`,
|
|
363
|
+
onClick: stop,
|
|
364
|
+
children: [
|
|
365
|
+
/* @__PURE__ */ jsxs("div", { className: "flex w-full items-center justify-between gap-3 px-2 text-white/90", children: [
|
|
366
|
+
/* @__PURE__ */ jsxs("span", { className: "text-sm font-medium truncate max-w-[50vw]", children: [
|
|
367
|
+
deviceName || deviceId,
|
|
368
|
+
" \xB7 \u5B9E\u65F6\u955C\u50CF"
|
|
369
|
+
] }),
|
|
370
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
371
|
+
/* @__PURE__ */ jsx(
|
|
372
|
+
"button",
|
|
373
|
+
{
|
|
374
|
+
type: "button",
|
|
375
|
+
"aria-label": fullscreen ? "\u9000\u51FA\u5168\u5C4F" : "\u5168\u5C4F",
|
|
376
|
+
title: fullscreen ? "\u9000\u51FA\u5168\u5C4F" : "\u5168\u5C4F",
|
|
377
|
+
onClick: toggleFullscreen,
|
|
378
|
+
className: "rounded p-1 hover:bg-white/10",
|
|
379
|
+
children: fullscreen ? /* @__PURE__ */ jsx(Minimize2, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx(Maximize2, { className: "h-5 w-5" })
|
|
380
|
+
}
|
|
381
|
+
),
|
|
382
|
+
/* @__PURE__ */ jsx(
|
|
383
|
+
"button",
|
|
384
|
+
{
|
|
385
|
+
type: "button",
|
|
386
|
+
"aria-label": "\u5173\u95ED",
|
|
387
|
+
onClick: onClose,
|
|
388
|
+
className: "rounded p-1 hover:bg-white/10",
|
|
389
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
|
|
390
|
+
}
|
|
391
|
+
)
|
|
392
|
+
] })
|
|
393
|
+
] }),
|
|
394
|
+
/* @__PURE__ */ jsx("div", { className: `min-h-0 ${fullscreen ? "flex-1" : ""}`, style: fullscreen ? void 0 : { height: "80vh" }, children: /* @__PURE__ */ jsx(
|
|
395
|
+
MirrorSurface,
|
|
396
|
+
{
|
|
397
|
+
deviceId,
|
|
398
|
+
platform,
|
|
399
|
+
onSize: setSize,
|
|
400
|
+
videoAspect: aspect,
|
|
401
|
+
shortcutBar,
|
|
402
|
+
className: "h-full"
|
|
403
|
+
}
|
|
404
|
+
) })
|
|
405
|
+
]
|
|
406
|
+
}
|
|
407
|
+
)
|
|
408
|
+
}
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export { DeviceBootSplash, DeviceMirror, MirrorSurface };
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { toNormalizedPointClamped, classifyDrag, mapKey } from './chunk-NWFYTFMI.js';
|
|
2
|
+
import { useRef, useEffect, useCallback } from 'react';
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
var TOUCH_MOVE_THROTTLE_MS = 30;
|
|
6
|
+
function MirrorView({
|
|
7
|
+
stream,
|
|
8
|
+
deviceSize,
|
|
9
|
+
onInput,
|
|
10
|
+
keyboard = true,
|
|
11
|
+
autoFocus = true,
|
|
12
|
+
onFirstFrame,
|
|
13
|
+
onResize,
|
|
14
|
+
continuousTouch = false,
|
|
15
|
+
className
|
|
16
|
+
}) {
|
|
17
|
+
const videoRef = useRef(null);
|
|
18
|
+
const downRef = useRef(null);
|
|
19
|
+
const gestureActiveRef = useRef(false);
|
|
20
|
+
const lastMoveSentRef = useRef(0);
|
|
21
|
+
const firstFrameRef = useRef(false);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const v = videoRef.current;
|
|
24
|
+
if (v) v.srcObject = stream;
|
|
25
|
+
firstFrameRef.current = false;
|
|
26
|
+
}, [stream]);
|
|
27
|
+
const handleFrameReady = useCallback(() => {
|
|
28
|
+
if (firstFrameRef.current) return;
|
|
29
|
+
const v = videoRef.current;
|
|
30
|
+
if (!v || v.videoWidth === 0) return;
|
|
31
|
+
firstFrameRef.current = true;
|
|
32
|
+
onFirstFrame?.({ width: v.videoWidth, height: v.videoHeight });
|
|
33
|
+
}, [onFirstFrame]);
|
|
34
|
+
const handleResize = useCallback(() => {
|
|
35
|
+
const v = videoRef.current;
|
|
36
|
+
if (!v || v.videoWidth === 0) return;
|
|
37
|
+
onResize?.({ width: v.videoWidth, height: v.videoHeight });
|
|
38
|
+
}, [onResize]);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const v = videoRef.current;
|
|
41
|
+
if (!v) return;
|
|
42
|
+
v.addEventListener("resize", handleResize);
|
|
43
|
+
return () => v.removeEventListener("resize", handleResize);
|
|
44
|
+
}, [handleResize, stream]);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (autoFocus) videoRef.current?.focus();
|
|
47
|
+
}, [autoFocus, stream]);
|
|
48
|
+
const clampedPoint = useCallback(
|
|
49
|
+
(e) => {
|
|
50
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
51
|
+
const box = { left: r.left, top: r.top, width: r.width, height: r.height };
|
|
52
|
+
return toNormalizedPointClamped({ clientX: e.clientX, clientY: e.clientY }, box, deviceSize);
|
|
53
|
+
},
|
|
54
|
+
[deviceSize]
|
|
55
|
+
);
|
|
56
|
+
const handlePointerDown = useCallback((e) => {
|
|
57
|
+
if (!onInput) return;
|
|
58
|
+
e.currentTarget.focus();
|
|
59
|
+
try {
|
|
60
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
if (continuousTouch) {
|
|
64
|
+
gestureActiveRef.current = true;
|
|
65
|
+
lastMoveSentRef.current = 0;
|
|
66
|
+
const p = clampedPoint(e);
|
|
67
|
+
if (p) onInput({ t: "touch_down", x: p.x, y: p.y });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
downRef.current = { clientX: e.clientX, clientY: e.clientY, t: e.timeStamp };
|
|
71
|
+
}, [onInput, continuousTouch, clampedPoint]);
|
|
72
|
+
const handlePointerMove = useCallback((e) => {
|
|
73
|
+
if (!onInput || !continuousTouch || !gestureActiveRef.current) return;
|
|
74
|
+
const now = Date.now();
|
|
75
|
+
if (now - lastMoveSentRef.current < TOUCH_MOVE_THROTTLE_MS) return;
|
|
76
|
+
lastMoveSentRef.current = now;
|
|
77
|
+
const p = clampedPoint(e);
|
|
78
|
+
if (p) onInput({ t: "touch_move", x: p.x, y: p.y });
|
|
79
|
+
}, [onInput, continuousTouch, clampedPoint]);
|
|
80
|
+
const handlePointerUp = useCallback((e) => {
|
|
81
|
+
if (!onInput) return;
|
|
82
|
+
if (continuousTouch) {
|
|
83
|
+
if (!gestureActiveRef.current) return;
|
|
84
|
+
gestureActiveRef.current = false;
|
|
85
|
+
const p = clampedPoint(e);
|
|
86
|
+
if (p) onInput({ t: "touch_up", x: p.x, y: p.y });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const start = downRef.current;
|
|
90
|
+
downRef.current = null;
|
|
91
|
+
if (!start) return;
|
|
92
|
+
const box = e.currentTarget.getBoundingClientRect();
|
|
93
|
+
const msg = classifyDrag(
|
|
94
|
+
start,
|
|
95
|
+
{ clientX: e.clientX, clientY: e.clientY, t: e.timeStamp },
|
|
96
|
+
{ left: box.left, top: box.top, width: box.width, height: box.height },
|
|
97
|
+
deviceSize
|
|
98
|
+
);
|
|
99
|
+
if (msg) onInput(msg);
|
|
100
|
+
}, [onInput, continuousTouch, deviceSize, clampedPoint]);
|
|
101
|
+
const handleKeyDown = useCallback((e) => {
|
|
102
|
+
if (!onInput || !keyboard) return;
|
|
103
|
+
const msg = mapKey(e);
|
|
104
|
+
if (msg) {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
onInput(msg);
|
|
107
|
+
}
|
|
108
|
+
}, [onInput, keyboard]);
|
|
109
|
+
return /* @__PURE__ */ jsx(
|
|
110
|
+
"video",
|
|
111
|
+
{
|
|
112
|
+
ref: videoRef,
|
|
113
|
+
autoPlay: true,
|
|
114
|
+
playsInline: true,
|
|
115
|
+
muted: true,
|
|
116
|
+
tabIndex: keyboard ? 0 : -1,
|
|
117
|
+
className,
|
|
118
|
+
onPointerDown: handlePointerDown,
|
|
119
|
+
onPointerMove: handlePointerMove,
|
|
120
|
+
onPointerUp: handlePointerUp,
|
|
121
|
+
onPointerCancel: handlePointerUp,
|
|
122
|
+
onKeyDown: handleKeyDown,
|
|
123
|
+
onLoadedData: handleFrameReady,
|
|
124
|
+
onPlaying: handleFrameReady,
|
|
125
|
+
style: {
|
|
126
|
+
background: "var(--aix-editor-bg, #000)",
|
|
127
|
+
borderRadius: "var(--aix-radius, 6px)",
|
|
128
|
+
width: "100%",
|
|
129
|
+
objectFit: "contain",
|
|
130
|
+
touchAction: "none",
|
|
131
|
+
outline: "none"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
var btn = {
|
|
137
|
+
background: "var(--aix-accent, #2d6cdf)",
|
|
138
|
+
color: "#fff",
|
|
139
|
+
border: "none",
|
|
140
|
+
borderRadius: "var(--aix-radius, 6px)",
|
|
141
|
+
padding: "4px 12px",
|
|
142
|
+
cursor: "pointer",
|
|
143
|
+
display: "inline-flex",
|
|
144
|
+
alignItems: "center",
|
|
145
|
+
justifyContent: "center"
|
|
146
|
+
};
|
|
147
|
+
var iconProps = {
|
|
148
|
+
width: 18,
|
|
149
|
+
height: 18,
|
|
150
|
+
viewBox: "0 0 24 24",
|
|
151
|
+
fill: "none",
|
|
152
|
+
stroke: "currentColor",
|
|
153
|
+
strokeWidth: 1.8,
|
|
154
|
+
strokeLinecap: "round",
|
|
155
|
+
strokeLinejoin: "round"
|
|
156
|
+
};
|
|
157
|
+
var AndroidBack = () => /* @__PURE__ */ jsx("svg", { ...iconProps, "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M15.5 5 L7.5 12 L15.5 19 Z" }) });
|
|
158
|
+
var AndroidHome = () => /* @__PURE__ */ jsx("svg", { ...iconProps, "aria-hidden": true, children: /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "6.5" }) });
|
|
159
|
+
var AndroidMenu = () => /* @__PURE__ */ jsx("svg", { ...iconProps, "aria-hidden": true, children: /* @__PURE__ */ jsx("rect", { x: "7", y: "7", width: "10", height: "10", rx: "1.5" }) });
|
|
160
|
+
var IosBack = () => /* @__PURE__ */ jsx("svg", { ...iconProps, "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M14.5 5 L8.5 12 L14.5 19" }) });
|
|
161
|
+
var IosHome = () => /* @__PURE__ */ jsxs("svg", { ...iconProps, "aria-hidden": true, children: [
|
|
162
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "8.5" }),
|
|
163
|
+
/* @__PURE__ */ jsx("rect", { x: "8.5", y: "8.5", width: "7", height: "7", rx: "1.5" })
|
|
164
|
+
] });
|
|
165
|
+
function ControlBar({ platform = "android", onHome, onBack, onMenu, onScreenshot, className }) {
|
|
166
|
+
const android = platform === "android";
|
|
167
|
+
return /* @__PURE__ */ jsxs("div", { className, style: { display: "flex", gap: 8, padding: 8, background: "var(--aix-bg, #161616)", borderRadius: "var(--aix-radius, 6px)" }, children: [
|
|
168
|
+
onBack && /* @__PURE__ */ jsx("button", { type: "button", onClick: onBack, style: btn, "aria-label": "Back", title: "Back", children: android ? /* @__PURE__ */ jsx(AndroidBack, {}) : /* @__PURE__ */ jsx(IosBack, {}) }),
|
|
169
|
+
onHome && /* @__PURE__ */ jsx("button", { type: "button", onClick: onHome, style: btn, "aria-label": "Home", title: "Home", children: android ? /* @__PURE__ */ jsx(AndroidHome, {}) : /* @__PURE__ */ jsx(IosHome, {}) }),
|
|
170
|
+
android && onMenu && /* @__PURE__ */ jsx("button", { type: "button", onClick: onMenu, style: btn, "aria-label": "Menu", title: "Menu", children: /* @__PURE__ */ jsx(AndroidMenu, {}) }),
|
|
171
|
+
onScreenshot && /* @__PURE__ */ jsx("button", { type: "button", onClick: onScreenshot, style: btn, children: "Screenshot" })
|
|
172
|
+
] });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export { ControlBar, MirrorView };
|