@jamiexiongr/panda-hub 0.1.19 → 0.1.21
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 +24 -13
- package/dist/{chunk-N5CXOFMD.mjs → chunk-SPTBBMUL.mjs} +350 -27
- package/dist/chunk-YKNCRHVV.mjs +489 -0
- package/dist/cli.mjs +17 -8
- package/dist/index.mjs +4 -2
- package/dist/{src-EGC2EU26.mjs → src-M7W7LPHG.mjs} +1 -1
- package/dist/web/assets/{diagnostics-page-CNt0HQLr.js → diagnostics-page-BKp7IiZk.js} +1 -1
- package/dist/web/assets/index-DhjIbKkn.js +142 -0
- package/dist/web/assets/index-QBVYFqgo.css +1 -0
- package/dist/web/assets/{session-diff-preview-Dy910n6F.js → session-diff-preview-BU4XO0fT.js} +1 -1
- package/dist/web/assets/{web-C7Ef9J6I.js → web-BqX5x67V.js} +1 -1
- package/dist/web/assets/{web-BFlqriRI.js → web-C3EZUi0V.js} +1 -1
- package/dist/web/assets/{web-P7eDOfOq.js → web-CIHpZyzh.js} +1 -1
- package/dist/web/assets/{web-BGzSBKmu.js → web-Ck4th-0Z.js} +1 -1
- package/dist/web/assets/{web-DCi2I8TJ.js → web-HzS7kaoe.js} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/package.json +2 -1
- package/dist/chunk-SKHIQ4LT.mjs +0 -109
- package/dist/web/assets/index-5NBpaOlM.js +0 -142
- package/dist/web/assets/index-CtgLzUhf.css +0 -1
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import {
|
|
2
|
+
configureTailscaleServe,
|
|
3
|
+
ensurePandaHubApiKey,
|
|
4
|
+
printTerminalQr,
|
|
5
|
+
resolveTailscalePublicationMode,
|
|
6
|
+
resolveTailscaleServePort,
|
|
7
|
+
startPandaSessionService
|
|
8
|
+
} from "./chunk-SPTBBMUL.mjs";
|
|
9
|
+
|
|
10
|
+
// release/panda-hub/src/index.ts
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path2 from "path";
|
|
13
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
14
|
+
|
|
15
|
+
// release/panda-hub/package.json
|
|
16
|
+
var package_default = {
|
|
17
|
+
name: "@jamiexiongr/panda-hub",
|
|
18
|
+
version: "0.1.21",
|
|
19
|
+
type: "module",
|
|
20
|
+
private: false,
|
|
21
|
+
description: "Panda hub runtime",
|
|
22
|
+
dependencies: {
|
|
23
|
+
"@fastify/compress": "^8.3.1",
|
|
24
|
+
"@fastify/cors": "^10.0.2",
|
|
25
|
+
"@fastify/websocket": "^11.0.2",
|
|
26
|
+
fastify: "^5.2.1",
|
|
27
|
+
"node-windows": "^1.0.0-beta.8",
|
|
28
|
+
"web-push": "^3.6.7"
|
|
29
|
+
},
|
|
30
|
+
bin: {
|
|
31
|
+
"panda-hub": "./bin/panda-hub.cjs"
|
|
32
|
+
},
|
|
33
|
+
exports: {
|
|
34
|
+
".": "./dist/index.mjs"
|
|
35
|
+
},
|
|
36
|
+
files: [
|
|
37
|
+
"bin",
|
|
38
|
+
"dist"
|
|
39
|
+
],
|
|
40
|
+
publishConfig: {
|
|
41
|
+
access: "public",
|
|
42
|
+
registry: "https://registry.npmjs.org/"
|
|
43
|
+
},
|
|
44
|
+
engines: {
|
|
45
|
+
node: ">=20.19.0"
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// release/panda-hub/src/service.ts
|
|
50
|
+
import path from "path";
|
|
51
|
+
import { fileURLToPath } from "url";
|
|
52
|
+
|
|
53
|
+
// release/shared/src/windows-service.ts
|
|
54
|
+
import { spawnSync } from "child_process";
|
|
55
|
+
import { createRequire } from "module";
|
|
56
|
+
var require2 = createRequire(import.meta.url);
|
|
57
|
+
var cachedServiceConstructor = null;
|
|
58
|
+
var WINDOWS_SERVICE_TIMEOUT_MS = 2e4;
|
|
59
|
+
var ensureWindows = () => {
|
|
60
|
+
if (process.platform !== "win32") {
|
|
61
|
+
throw new Error("Windows \u670D\u52A1\u7BA1\u7406\u5F53\u524D\u53EA\u652F\u6301 Windows\u3002");
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var trimToNull = (value) => {
|
|
65
|
+
const normalized = value?.trim() ?? "";
|
|
66
|
+
return normalized || null;
|
|
67
|
+
};
|
|
68
|
+
var escapePowerShellString = (value) => value.replace(/'/g, "''");
|
|
69
|
+
var quoteForSc = (value) => value.trim();
|
|
70
|
+
var sanitizeNodeWindowsServiceId = (value) => `${value.replace(/[^\w]/g, "").toLowerCase()}.exe`;
|
|
71
|
+
var resolveWindowsServiceReference = (input) => {
|
|
72
|
+
const displayName = typeof input === "string" ? input : input.name;
|
|
73
|
+
const controllerName = typeof input === "string" ? sanitizeNodeWindowsServiceId(displayName) : trimToNull(input.id) ?? sanitizeNodeWindowsServiceId(displayName);
|
|
74
|
+
return {
|
|
75
|
+
displayName,
|
|
76
|
+
controllerName
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
var describeCommandFailure = (output, fallback) => {
|
|
80
|
+
const normalized = trimToNull(output);
|
|
81
|
+
return normalized ?? fallback;
|
|
82
|
+
};
|
|
83
|
+
var runPowerShell = (script) => spawnSync("powershell.exe", ["-NoProfile", "-Command", script], {
|
|
84
|
+
encoding: "utf8",
|
|
85
|
+
windowsHide: true
|
|
86
|
+
});
|
|
87
|
+
var runSc = (args) => {
|
|
88
|
+
ensureWindows();
|
|
89
|
+
const command = ["sc.exe", ...args.map((value) => /[\s"]/u.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value)].join(" ");
|
|
90
|
+
return spawnSync(process.env.ComSpec ?? "cmd.exe", ["/d", "/s", "/c", command], {
|
|
91
|
+
encoding: "utf8",
|
|
92
|
+
windowsHide: true
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
var parseWindowsServiceState = (output) => {
|
|
96
|
+
const normalized = output.trim().toLowerCase();
|
|
97
|
+
if (normalized === "__missing__") {
|
|
98
|
+
return "missing";
|
|
99
|
+
}
|
|
100
|
+
if (normalized === "running") {
|
|
101
|
+
return "running";
|
|
102
|
+
}
|
|
103
|
+
if (normalized === "stopped") {
|
|
104
|
+
return "stopped";
|
|
105
|
+
}
|
|
106
|
+
return "unknown";
|
|
107
|
+
};
|
|
108
|
+
var queryWindowsServiceStatus = (input) => {
|
|
109
|
+
ensureWindows();
|
|
110
|
+
const reference = resolveWindowsServiceReference(input);
|
|
111
|
+
const result = runPowerShell(
|
|
112
|
+
[
|
|
113
|
+
`$service = Get-Service -Name '${escapePowerShellString(reference.controllerName)}' -ErrorAction SilentlyContinue`,
|
|
114
|
+
`if ($null -eq $service) { $service = Get-Service -DisplayName '${escapePowerShellString(reference.displayName)}' -ErrorAction SilentlyContinue }`,
|
|
115
|
+
`if ($null -eq $service) { '__MISSING__' } else { @{ Name = $service.Name; DisplayName = $service.DisplayName; Status = $service.Status.ToString() } | ConvertTo-Json -Compress }`
|
|
116
|
+
].join("; ")
|
|
117
|
+
);
|
|
118
|
+
const rawOutput = `${result.stdout ?? ""}
|
|
119
|
+
${result.stderr ?? ""}`.trim();
|
|
120
|
+
let controllerName = reference.controllerName;
|
|
121
|
+
let displayName = reference.displayName;
|
|
122
|
+
let state = parseWindowsServiceState(rawOutput);
|
|
123
|
+
if (state !== "missing") {
|
|
124
|
+
try {
|
|
125
|
+
const parsed = JSON.parse(rawOutput);
|
|
126
|
+
controllerName = trimToNull(parsed.Name) ?? controllerName;
|
|
127
|
+
displayName = trimToNull(parsed.DisplayName) ?? displayName;
|
|
128
|
+
state = parseWindowsServiceState(trimToNull(parsed.Status) ?? rawOutput);
|
|
129
|
+
} catch {
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
name: displayName,
|
|
134
|
+
displayName,
|
|
135
|
+
controllerName,
|
|
136
|
+
exists: state !== "missing",
|
|
137
|
+
state,
|
|
138
|
+
rawOutput
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
var wait = (ms) => new Promise((resolve) => {
|
|
142
|
+
setTimeout(resolve, ms);
|
|
143
|
+
});
|
|
144
|
+
var waitForWindowsServiceState = async (input, desiredState, timeoutMs = WINDOWS_SERVICE_TIMEOUT_MS) => {
|
|
145
|
+
const startedAt = Date.now();
|
|
146
|
+
let latest = queryWindowsServiceStatus(input);
|
|
147
|
+
while (latest.state !== desiredState && Date.now() - startedAt < timeoutMs) {
|
|
148
|
+
await wait(750);
|
|
149
|
+
latest = queryWindowsServiceStatus(input);
|
|
150
|
+
}
|
|
151
|
+
return latest;
|
|
152
|
+
};
|
|
153
|
+
var startWindowsService = async (input) => {
|
|
154
|
+
ensureWindows();
|
|
155
|
+
const current = queryWindowsServiceStatus(input);
|
|
156
|
+
if (!current.exists) {
|
|
157
|
+
throw new Error(`Windows \u670D\u52A1 ${current.displayName} \u4E0D\u5B58\u5728\u3002`);
|
|
158
|
+
}
|
|
159
|
+
if (current.state === "running") {
|
|
160
|
+
return current;
|
|
161
|
+
}
|
|
162
|
+
const result = runPowerShell(
|
|
163
|
+
`Start-Service -Name '${escapePowerShellString(current.controllerName)}' -ErrorAction Stop`
|
|
164
|
+
);
|
|
165
|
+
const output = `${result.stdout ?? ""}
|
|
166
|
+
${result.stderr ?? ""}`.trim();
|
|
167
|
+
const latest = await waitForWindowsServiceState(input, "running");
|
|
168
|
+
if (latest.state !== "running") {
|
|
169
|
+
throw new Error(
|
|
170
|
+
describeCommandFailure(output || latest.rawOutput, `\u542F\u52A8\u670D\u52A1 ${current.displayName} \u5931\u8D25\u3002`)
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return latest;
|
|
174
|
+
};
|
|
175
|
+
var stopWindowsService = async (input) => {
|
|
176
|
+
ensureWindows();
|
|
177
|
+
const current = queryWindowsServiceStatus(input);
|
|
178
|
+
if (!current.exists || current.state === "stopped") {
|
|
179
|
+
return current;
|
|
180
|
+
}
|
|
181
|
+
const result = runPowerShell(
|
|
182
|
+
`Stop-Service -Name '${escapePowerShellString(current.controllerName)}' -Force -ErrorAction Stop`
|
|
183
|
+
);
|
|
184
|
+
const output = `${result.stdout ?? ""}
|
|
185
|
+
${result.stderr ?? ""}`.trim();
|
|
186
|
+
const latest = await waitForWindowsServiceState(input, "stopped");
|
|
187
|
+
if (latest.state !== "stopped") {
|
|
188
|
+
throw new Error(
|
|
189
|
+
describeCommandFailure(output || latest.rawOutput, `\u505C\u6B62\u670D\u52A1 ${current.displayName} \u5931\u8D25\u3002`)
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
return latest;
|
|
193
|
+
};
|
|
194
|
+
var restartWindowsService = async (input) => {
|
|
195
|
+
await stopWindowsService(input);
|
|
196
|
+
return await startWindowsService(input);
|
|
197
|
+
};
|
|
198
|
+
var collectServiceEnvironment = (env) => Object.entries(env ?? process.env).filter(([key, value]) => key.startsWith("PANDA_") && typeof value === "string" && value.trim()).map(([name, value]) => ({
|
|
199
|
+
name,
|
|
200
|
+
value: value.trim()
|
|
201
|
+
}));
|
|
202
|
+
var getNodeWindowsServiceConstructor = () => {
|
|
203
|
+
if (cachedServiceConstructor) {
|
|
204
|
+
return cachedServiceConstructor;
|
|
205
|
+
}
|
|
206
|
+
const mod = require2("node-windows");
|
|
207
|
+
if (!mod.Service) {
|
|
208
|
+
throw new Error("\u7F3A\u5C11 node-windows \u4F9D\u8D56\uFF0C\u65E0\u6CD5\u6CE8\u518C Windows \u670D\u52A1\u3002");
|
|
209
|
+
}
|
|
210
|
+
cachedServiceConstructor = mod.Service;
|
|
211
|
+
return cachedServiceConstructor;
|
|
212
|
+
};
|
|
213
|
+
var createNodeWindowsService = (definition) => {
|
|
214
|
+
ensureWindows();
|
|
215
|
+
const Service = getNodeWindowsServiceConstructor();
|
|
216
|
+
return new Service({
|
|
217
|
+
id: resolveWindowsServiceReference(definition).controllerName,
|
|
218
|
+
name: definition.name,
|
|
219
|
+
description: definition.description,
|
|
220
|
+
script: definition.scriptPath,
|
|
221
|
+
scriptOptions: trimToNull(definition.scriptOptions) ?? void 0,
|
|
222
|
+
workingDirectory: trimToNull(definition.workingDirectory) ?? void 0,
|
|
223
|
+
execPath: trimToNull(definition.execPath) ?? process.execPath,
|
|
224
|
+
env: collectServiceEnvironment(definition.env)
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
var waitForNodeWindowsAction = async (service, action) => await new Promise((resolve, reject) => {
|
|
228
|
+
let settled = false;
|
|
229
|
+
const finish = (error) => {
|
|
230
|
+
if (settled) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
settled = true;
|
|
234
|
+
if (error) {
|
|
235
|
+
reject(error);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
resolve();
|
|
239
|
+
};
|
|
240
|
+
const fail = (error) => {
|
|
241
|
+
finish(error instanceof Error ? error : new Error(String(error)));
|
|
242
|
+
};
|
|
243
|
+
service.once(action, () => finish());
|
|
244
|
+
service.once(action === "install" ? "alreadyinstalled" : "alreadyuninstalled", () => finish());
|
|
245
|
+
service.once(
|
|
246
|
+
"invalidinstallation",
|
|
247
|
+
() => fail(new Error(`Windows \u670D\u52A1 ${service.exists ? "\u5B89\u88C5" : "\u5378\u8F7D"}\u72B6\u6001\u65E0\u6548\u3002`))
|
|
248
|
+
);
|
|
249
|
+
service.once("error", fail);
|
|
250
|
+
try {
|
|
251
|
+
service[action]();
|
|
252
|
+
} catch (error) {
|
|
253
|
+
fail(error);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
var uninstallWindowsService = async (definition) => {
|
|
257
|
+
ensureWindows();
|
|
258
|
+
const current = queryWindowsServiceStatus(definition);
|
|
259
|
+
if (!current.exists) {
|
|
260
|
+
return current;
|
|
261
|
+
}
|
|
262
|
+
if (current.state === "running") {
|
|
263
|
+
await stopWindowsService(definition);
|
|
264
|
+
}
|
|
265
|
+
const service = createNodeWindowsService(definition);
|
|
266
|
+
await waitForNodeWindowsAction(service, "uninstall");
|
|
267
|
+
return queryWindowsServiceStatus(definition);
|
|
268
|
+
};
|
|
269
|
+
var installOrUpdateWindowsService = async (definition, options) => {
|
|
270
|
+
ensureWindows();
|
|
271
|
+
const reference = resolveWindowsServiceReference(definition);
|
|
272
|
+
const existing = queryWindowsServiceStatus(definition);
|
|
273
|
+
if (existing.exists) {
|
|
274
|
+
await uninstallWindowsService(definition);
|
|
275
|
+
}
|
|
276
|
+
const service = createNodeWindowsService(definition);
|
|
277
|
+
await waitForNodeWindowsAction(service, "install");
|
|
278
|
+
runSc(["config", quoteForSc(reference.controllerName), "start=", "auto"]);
|
|
279
|
+
let started = false;
|
|
280
|
+
let startError = null;
|
|
281
|
+
if (options?.start !== false) {
|
|
282
|
+
try {
|
|
283
|
+
const startedStatus = await startWindowsService(definition);
|
|
284
|
+
started = startedStatus.state === "running";
|
|
285
|
+
} catch (error) {
|
|
286
|
+
startError = error instanceof Error ? error.message : String(error);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
status: queryWindowsServiceStatus(definition),
|
|
291
|
+
started,
|
|
292
|
+
startError
|
|
293
|
+
};
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// release/panda-hub/src/service.ts
|
|
297
|
+
var currentDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
298
|
+
var packageRoot = path.resolve(currentDirectory, "..");
|
|
299
|
+
var defaultServiceName = "PandaHub";
|
|
300
|
+
var trimToNull2 = (value) => {
|
|
301
|
+
const normalized = value?.trim() ?? "";
|
|
302
|
+
return normalized || null;
|
|
303
|
+
};
|
|
304
|
+
var normalizeServiceName = (value) => trimToNull2(value) ?? defaultServiceName;
|
|
305
|
+
var joinScriptOptions = (argv) => argv.map((value) => value.trim()).filter(Boolean).join(" ");
|
|
306
|
+
var buildHubServiceDefinition = (options) => ({
|
|
307
|
+
name: normalizeServiceName(options?.name),
|
|
308
|
+
description: "Panda Hub Windows service",
|
|
309
|
+
scriptPath: path.join(packageRoot, "bin", "panda-hub.cjs"),
|
|
310
|
+
scriptOptions: trimToNull2(options?.scriptOptions),
|
|
311
|
+
workingDirectory: packageRoot,
|
|
312
|
+
env: options?.env ?? process.env
|
|
313
|
+
});
|
|
314
|
+
var parseHubServiceCommand = (argv) => {
|
|
315
|
+
let action = null;
|
|
316
|
+
let name = null;
|
|
317
|
+
let shouldStart = true;
|
|
318
|
+
const runtimeArgs = [];
|
|
319
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
320
|
+
const candidate = argv[index]?.trim() ?? "";
|
|
321
|
+
if (!candidate) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (!action) {
|
|
325
|
+
action = candidate.toLowerCase();
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
const normalized = candidate.toLowerCase();
|
|
329
|
+
if (normalized === "--name" || normalized === "name" || normalized === "--service-name" || normalized === "service-name") {
|
|
330
|
+
name = trimToNull2(argv[index + 1]);
|
|
331
|
+
index += 1;
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (normalized.startsWith("--name=") || normalized.startsWith("name=") || normalized.startsWith("--service-name=") || normalized.startsWith("service-name=")) {
|
|
335
|
+
name = trimToNull2(candidate.slice(candidate.indexOf("=") + 1));
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (normalized === "--no-start") {
|
|
339
|
+
shouldStart = false;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
runtimeArgs.push(candidate);
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
action: action ?? "status",
|
|
346
|
+
name,
|
|
347
|
+
shouldStart,
|
|
348
|
+
runtimeArgs
|
|
349
|
+
};
|
|
350
|
+
};
|
|
351
|
+
var manageJamiexiongrHubService = async (options) => {
|
|
352
|
+
const logger = options?.logger ?? console;
|
|
353
|
+
const parsed = parseHubServiceCommand(options?.argv ?? []);
|
|
354
|
+
const action = parsed.action;
|
|
355
|
+
const definition = buildHubServiceDefinition({
|
|
356
|
+
name: parsed.name,
|
|
357
|
+
scriptOptions: joinScriptOptions(parsed.runtimeArgs),
|
|
358
|
+
env: options?.env
|
|
359
|
+
});
|
|
360
|
+
if (action === "install" || action === "update" || action === "upsert" || action === "sync") {
|
|
361
|
+
const result = await installOrUpdateWindowsService(definition, {
|
|
362
|
+
start: parsed.shouldStart
|
|
363
|
+
});
|
|
364
|
+
if (result.startError) {
|
|
365
|
+
logger.warn(
|
|
366
|
+
`Windows \u670D\u52A1 ${definition.name} \u5DF2\u6CE8\u518C\uFF0C\u4F46\u542F\u52A8\u5931\u8D25\uFF1A${result.startError}`
|
|
367
|
+
);
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
logger.info(
|
|
371
|
+
result.started ? `Windows \u670D\u52A1 ${definition.name} \u5DF2\u6CE8\u518C\u5E76\u542F\u52A8\u3002` : `Windows \u670D\u52A1 ${definition.name} \u5DF2\u6CE8\u518C\u3002`
|
|
372
|
+
);
|
|
373
|
+
return result;
|
|
374
|
+
}
|
|
375
|
+
if (action === "uninstall" || action === "remove" || action === "delete") {
|
|
376
|
+
const status = await uninstallWindowsService(definition);
|
|
377
|
+
logger.info(
|
|
378
|
+
status.exists ? `Windows \u670D\u52A1 ${definition.name} \u5378\u8F7D\u540E\u4ECD\u5B58\u5728\uFF0C\u8BF7\u68C0\u67E5\u7CFB\u7EDF\u670D\u52A1\u7BA1\u7406\u5668\u3002` : `Windows \u670D\u52A1 ${definition.name} \u5DF2\u5378\u8F7D\u3002`
|
|
379
|
+
);
|
|
380
|
+
return {
|
|
381
|
+
status,
|
|
382
|
+
started: false,
|
|
383
|
+
startError: null
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
if (action === "start") {
|
|
387
|
+
const status = await startWindowsService(definition.name);
|
|
388
|
+
logger.info(`Windows \u670D\u52A1 ${definition.name} \u5F53\u524D\u72B6\u6001\uFF1A${status.state}`);
|
|
389
|
+
return {
|
|
390
|
+
status,
|
|
391
|
+
started: status.state === "running",
|
|
392
|
+
startError: null
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
if (action === "stop") {
|
|
396
|
+
const status = await stopWindowsService(definition.name);
|
|
397
|
+
logger.info(`Windows \u670D\u52A1 ${definition.name} \u5F53\u524D\u72B6\u6001\uFF1A${status.state}`);
|
|
398
|
+
return {
|
|
399
|
+
status,
|
|
400
|
+
started: false,
|
|
401
|
+
startError: null
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
if (action === "restart") {
|
|
405
|
+
const status = await restartWindowsService(definition.name);
|
|
406
|
+
logger.info(`Windows \u670D\u52A1 ${definition.name} \u5F53\u524D\u72B6\u6001\uFF1A${status.state}`);
|
|
407
|
+
return {
|
|
408
|
+
status,
|
|
409
|
+
started: status.state === "running",
|
|
410
|
+
startError: null
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
if (action === "status") {
|
|
414
|
+
const status = queryWindowsServiceStatus(definition.name);
|
|
415
|
+
logger.info(
|
|
416
|
+
status.exists ? `Windows \u670D\u52A1 ${definition.name} \u5F53\u524D\u72B6\u6001\uFF1A${status.state}` : `Windows \u670D\u52A1 ${definition.name} \u5C1A\u672A\u6CE8\u518C\u3002`
|
|
417
|
+
);
|
|
418
|
+
return {
|
|
419
|
+
status,
|
|
420
|
+
started: status.state === "running",
|
|
421
|
+
startError: null
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
throw new Error(`Unknown hub service action: ${action}`);
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
// release/panda-hub/src/index.ts
|
|
428
|
+
var currentDirectory2 = path2.dirname(fileURLToPath2(import.meta.url));
|
|
429
|
+
var resolveBundledWebUiDir = () => {
|
|
430
|
+
const candidates = [
|
|
431
|
+
path2.resolve(currentDirectory2, "web"),
|
|
432
|
+
path2.resolve(currentDirectory2, "../dist/web"),
|
|
433
|
+
path2.resolve(currentDirectory2, "../web")
|
|
434
|
+
];
|
|
435
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) ?? candidates[0];
|
|
436
|
+
};
|
|
437
|
+
var startJamiexiongrHub = async (options) => {
|
|
438
|
+
const publishMode = options?.tailscalePublicationMode ?? resolveTailscalePublicationMode({
|
|
439
|
+
envPrefix: "PANDA_HUB"
|
|
440
|
+
});
|
|
441
|
+
const port = Number(process.env.PANDA_HUB_PORT ?? 4343);
|
|
442
|
+
const tailscaleServe = configureTailscaleServe({
|
|
443
|
+
enabled: publishMode !== "disabled",
|
|
444
|
+
mode: publishMode === "disabled" ? void 0 : publishMode,
|
|
445
|
+
serviceName: "panda-hub",
|
|
446
|
+
localPort: port,
|
|
447
|
+
servePort: resolveTailscaleServePort({
|
|
448
|
+
envPrefix: "PANDA_HUB",
|
|
449
|
+
defaultPort: 443
|
|
450
|
+
}),
|
|
451
|
+
logger: console
|
|
452
|
+
});
|
|
453
|
+
const hubApiKey = await ensurePandaHubApiKey({
|
|
454
|
+
configuredApiKey: process.env.PANDA_HUB_API_KEY ?? null,
|
|
455
|
+
codexHome: process.env.PANDA_CODEX_HOME ?? null,
|
|
456
|
+
logger: console
|
|
457
|
+
});
|
|
458
|
+
if (hubApiKey.apiKey) {
|
|
459
|
+
process.env.PANDA_HUB_API_KEY = hubApiKey.apiKey;
|
|
460
|
+
}
|
|
461
|
+
const webUiDir = resolveBundledWebUiDir();
|
|
462
|
+
const app = await startPandaSessionService({
|
|
463
|
+
serviceName: "panda-hub",
|
|
464
|
+
mode: "hub",
|
|
465
|
+
port,
|
|
466
|
+
transport: "hub-routed",
|
|
467
|
+
version: package_default.version,
|
|
468
|
+
webUiDir
|
|
469
|
+
});
|
|
470
|
+
if (tailscaleServe.active && tailscaleServe.baseUrl) {
|
|
471
|
+
if (tailscaleServe.mode === "funnel") {
|
|
472
|
+
console.info(`Panda hub Public HTTPS URL: ${tailscaleServe.baseUrl}`);
|
|
473
|
+
console.info(`Public PWA install URL: ${tailscaleServe.baseUrl}`);
|
|
474
|
+
} else {
|
|
475
|
+
console.info(`Panda hub Tailscale HTTPS URL: ${tailscaleServe.baseUrl}`);
|
|
476
|
+
console.info(`Agent hub URL env: PANDA_HUB_URL=${tailscaleServe.baseUrl}`);
|
|
477
|
+
}
|
|
478
|
+
printTerminalQr(tailscaleServe.baseUrl, {
|
|
479
|
+
logger: console,
|
|
480
|
+
label: tailscaleServe.mode === "funnel" ? "Scan this QR code to open the public Panda hub on your phone:" : "Scan this QR code to open Panda hub on your phone:"
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
return app;
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
export {
|
|
487
|
+
manageJamiexiongrHubService,
|
|
488
|
+
startJamiexiongrHub
|
|
489
|
+
};
|
package/dist/cli.mjs
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
|
+
manageJamiexiongrHubService,
|
|
2
3
|
startJamiexiongrHub
|
|
3
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-YKNCRHVV.mjs";
|
|
4
5
|
import {
|
|
5
6
|
resolveTailscalePublicationMode
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-SPTBBMUL.mjs";
|
|
7
8
|
import "./chunk-AEQMWH7D.mjs";
|
|
8
9
|
|
|
9
10
|
// release/panda-hub/src/cli.ts
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
});
|
|
11
|
+
var argv = process.argv.slice(2);
|
|
12
|
+
var command = argv[0]?.trim().toLowerCase() ?? "";
|
|
13
|
+
if (command === "service") {
|
|
14
|
+
void manageJamiexiongrHubService({
|
|
15
|
+
argv: argv.slice(1)
|
|
16
|
+
});
|
|
17
|
+
} else {
|
|
18
|
+
void startJamiexiongrHub({
|
|
19
|
+
tailscalePublicationMode: resolveTailscalePublicationMode({
|
|
20
|
+
argv,
|
|
21
|
+
envPrefix: "PANDA_HUB"
|
|
22
|
+
})
|
|
23
|
+
});
|
|
24
|
+
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
+
manageJamiexiongrHubService,
|
|
2
3
|
startJamiexiongrHub
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-YKNCRHVV.mjs";
|
|
5
|
+
import "./chunk-SPTBBMUL.mjs";
|
|
5
6
|
import "./chunk-AEQMWH7D.mjs";
|
|
6
7
|
export {
|
|
8
|
+
manageJamiexiongrHubService,
|
|
7
9
|
startJamiexiongrHub
|
|
8
10
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{u as j,r as d,j as e}from"./index-5NBpaOlM.js";const v=[{key:"color-mix-oklab",property:"background",value:"color-mix(in oklab, white 50%, black)"},{key:"color-mix-srgb",property:"background",value:"color-mix(in srgb, white 50%, black)"},{key:"backdrop-filter",property:"backdrop-filter",value:"blur(12px)"},{key:"webkit-backdrop-filter",property:"-webkit-backdrop-filter",value:"blur(12px)"},{key:"viewport-dvh",property:"height",value:"100dvh"}],y=["--color-surface-base","--color-surface-panel","--color-surface-floating","--color-surface-border","--color-surface-border-soft","--color-text-primary","--color-text-secondary","--color-accent-primary","--color-accent-primary-soft"],w=[".conversation-topbar",".topbar-menu",".conversation-run-popover",".chat-composer",".chat-composer__input",".composer-utility-menu__popover",".session-run-panel__command-card",".session-run-panel__input",".session-git-panel__summary"],b=["background-color","background-image","backdrop-filter","-webkit-backdrop-filter","border-color","box-shadow","color","display","opacity","position"],x=(t,r=180)=>{const n=(t??"").replace(/\s+/g," ").trim();return n?n.length<=r?n:`${n.slice(0,r-1).trimEnd()}…`:null},h=t=>Number(t.toFixed(2)),k=(t,r)=>{var n;if(typeof window>"u"||typeof((n=window.CSS)==null?void 0:n.supports)!="function")return!1;try{return window.CSS.supports(t,r)}catch{return!1}},_=()=>Object.fromEntries(v.map(t=>[t.key,k(t.property,t.value)])),S=()=>typeof document>"u"?[]:Array.from(document.styleSheets).map(t=>{var n,i;let r=null;try{r=t.cssRules.length}catch{r=null}return{href:t.href??null,owner_node:t.ownerNode instanceof Element?t.ownerNode.tagName.toLowerCase():null,media:((i=(n=t.media)==null?void 0:n.mediaText)==null?void 0:i.trim())||null,disabled:t.disabled,css_rule_count:r}}),N=()=>{if(typeof document>"u"||typeof window>"u")return[];const t=new Map,r=(n,i)=>{const o=n==null?void 0:n.trim();if(o)try{const l=new URL(o,window.location.href);if(!/^https?:$/i.test(l.protocol))return;t.set(l.toString(),i)}catch{return}};for(const n of document.styleSheets)r(n.href,"stylesheet");for(const n of Array.from(document.scripts))r(n.src,"script");for(const n of Array.from(document.querySelectorAll('link[rel~="manifest"]')))r(n.href,"manifest");for(const n of Array.from(document.querySelectorAll('link[rel~="icon"], link[rel="apple-touch-icon"]')))r(n.href,"icon");return Array.from(t.entries()).slice(0,20).map(([n,i])=>({url:n,kind:i}))},E=async t=>{const r=async n=>fetch(t.url,{method:n,cache:"no-store",redirect:"follow"});try{let n=await r("HEAD");return(n.status===405||n.status===501)&&(n=await r("GET")),{url:t.url,kind:t.kind,ok:n.ok,status:n.status,content_type:n.headers.get("content-type"),cache_control:n.headers.get("cache-control"),error:null}}catch(n){return{url:t.url,kind:t.kind,ok:!1,status:null,content_type:null,cache_control:null,error:n instanceof Error?n.message:"Unknown fetch failure"}}},C=()=>typeof performance>"u"||typeof performance.getEntriesByType!="function"?[]:performance.getEntriesByType("resource").map(t=>t).filter(t=>{const r=t.name.toLowerCase();return r.includes(".css")||r.includes(".js")||r.includes("manifest")||t.initiatorType==="link"||t.initiatorType==="script"}).slice(-40).map(t=>({name:t.name,initiator_type:t.initiatorType||null,duration_ms:h(t.duration),transfer_size:typeof t.transferSize=="number"?t.transferSize:null,decoded_body_size:typeof t.decodedBodySize=="number"?t.decodedBodySize:null})),T=()=>{if(typeof document>"u"||typeof window>"u")return{};const t=window.getComputedStyle(document.documentElement);return Object.fromEntries(y.map(r=>[r,t.getPropertyValue(r).trim()||"(empty)"]))},R=t=>{if(typeof document>"u"||typeof window>"u")return{selector:t,found:!1,text_preview:null,rect:null,computed:{}};const r=document.querySelector(t);if(!r)return{selector:t,found:!1,text_preview:null,rect:null,computed:{}};const n=r.getBoundingClientRect(),i=window.getComputedStyle(r);return{selector:t,found:!0,text_preview:x(r.textContent),rect:{x:h(n.x),y:h(n.y),width:h(n.width),height:h(n.height)},computed:Object.fromEntries(b.map(o=>[o,i.getPropertyValue(o).trim()||"(empty)"]))}},A=async()=>{if(typeof navigator>"u"||!("serviceWorker"in navigator))return{supported:!1,controller:!1,registrations:[]};try{const t=await navigator.serviceWorker.getRegistrations();return{supported:!0,controller:!!navigator.serviceWorker.controller,registrations:t.map(r=>{var n,i,o;return{scope:r.scope,active_script_url:((n=r.active)==null?void 0:n.scriptURL)??null,waiting_script_url:((i=r.waiting)==null?void 0:i.scriptURL)??null,installing_script_url:((o=r.installing)==null?void 0:o.scriptURL)??null}})}}catch{return{supported:!0,controller:!!navigator.serviceWorker.controller,registrations:[]}}},O=async()=>{if(typeof window>"u"||!("caches"in window))return{supported:!1,keys:[]};try{return{supported:!0,keys:await window.caches.keys()}}catch{return{supported:!0,keys:[]}}},L=t=>{const r=[];return t.feature_support["color-mix-oklab"]||r.push("当前浏览器不支持 color-mix(in oklab, ...),混色背景会直接失效,常见现象就是输入区或面板背景透明。"),!t.feature_support["backdrop-filter"]&&!t.feature_support["webkit-backdrop-filter"]&&r.push("当前浏览器不支持 backdrop-filter,磨砂浮层会退化成纯色面板。"),t.service_worker.controller&&t.cache.keys.length>0&&r.push(`检测到 ${t.cache.keys.length} 个 Cache Storage 项,若真机页面和桌面不一致,可以先清理缓存再重试。`),t.stylesheets.length===0&&r.push("当前页面没有读取到任何样式表对象,需要重点检查 CSS 是否被正确加载。"),t.resource_probes.some(n=>!n.ok)&&r.push("至少有一个静态资源探测失败,需要继续检查网络、缓存或 Service Worker 拦截。"),r},P=async()=>{var u,p,m,s,a,g;const t=N(),[r,n,i]=await Promise.all([A(),O(),Promise.all(t.map(f=>E(f)))]),o=navigator,l={width:window.innerWidth,height:window.innerHeight,device_pixel_ratio:window.devicePixelRatio||1,visual_width:((u=window.visualViewport)==null?void 0:u.width)??null,visual_height:((p=window.visualViewport)==null?void 0:p.height)??null,screen_width:((m=window.screen)==null?void 0:m.width)??null,screen_height:((s=window.screen)==null?void 0:s.height)??null},c={captured_at:new Date().toISOString(),page:{href:window.location.href,pathname:window.location.pathname,referrer:x(document.referrer),visibility_state:document.visibilityState??null},environment:{user_agent:navigator.userAgent,language:navigator.language??null,languages:Array.isArray(navigator.languages)?navigator.languages:[],platform:navigator.platform??null,vendor:navigator.vendor??null,online:typeof navigator.onLine=="boolean"?navigator.onLine:null,cookie_enabled:typeof navigator.cookieEnabled=="boolean"?navigator.cookieEnabled:null,secure_context:window.isSecureContext,standalone_display_mode:window.matchMedia("(display-mode: standalone)").matches,hardware_concurrency:typeof navigator.hardwareConcurrency=="number"?navigator.hardwareConcurrency:null,device_memory_gb:typeof o.deviceMemory=="number"?o.deviceMemory:null,max_touch_points:typeof navigator.maxTouchPoints=="number"?navigator.maxTouchPoints:null},viewport:l,feature_support:_(),service_worker:r,cache:n,manifest:{href:((a=document.querySelector('link[rel~="manifest"]'))==null?void 0:a.href)??null,rel:((g=document.querySelector('link[rel~="manifest"]'))==null?void 0:g.rel)??null},stylesheets:S(),resource_probes:i,performance_entries:C(),theme_variables:T(),element_snapshots:w.map(f=>R(f)),notes:[]};return c.notes=L(c),c},W=t=>t?"支持":"不支持",$=t=>t?t.feature_support["color-mix-oklab"]?t.resource_probes.some(r=>!r.ok)?"至少有一个静态资源探测失败,更像是 CSS 或脚本资源没有正确加载,或者被缓存 / Service Worker 干扰。":t.service_worker.controller&&t.cache.keys.length>0?"样式能力本身看起来正常,但页面被 Service Worker 控制且存在缓存,下一步要重点排查缓存是否陈旧。":"浏览器能力和基础资源看起来正常,下一步需要对比具体元素快照与安卓真机截图。":"高概率是安卓浏览器不支持 color-mix(in oklab, ...),导致输入框和面板的混色背景规则整体失效。":"正在采集浏览器能力、样式表、缓存和关键元素快照。",U=t=>{if(!t.found)return"未找到对应元素";const r=t.computed["background-color"]??"(empty)",n=t.computed["background-image"]??"(empty)";return r==="rgba(0, 0, 0, 0)"&&n==="none"?"背景完全透明":`${r} / ${n}`},M=()=>{const t=j(),[r,n]=d.useState(null),[i,o]=d.useState(!0),[l,c]=d.useState(null),u=async()=>{o(!0),c(null);try{const s=await P();d.startTransition(()=>{n(s)})}catch(s){c(s instanceof Error?`采集失败:${s.message}`:"采集失败,请稍后重试。")}finally{o(!1)}};d.useEffect(()=>{u()},[]);const p=async()=>{var s;if(r){if(typeof navigator>"u"||typeof((s=navigator.clipboard)==null?void 0:s.writeText)!="function"){c("当前浏览器不支持直接复制,请手动截图或长按选择。");return}try{await navigator.clipboard.writeText(JSON.stringify(r,null,2)),c("诊断 JSON 已复制,可以直接发给我继续分析。")}catch(a){c(a instanceof Error?`复制失败:${a.message}`:"复制失败,请手动截图或长按选择。")}}},m=d.useMemo(()=>$(r),[r]);return e.jsx("main",{className:"diagnostics-page",children:e.jsxs("div",{className:"diagnostics-shell",children:[e.jsxs("header",{className:"diagnostics-header",children:[e.jsxs("div",{className:"diagnostics-header__copy",children:[e.jsx("button",{type:"button",className:"diagnostics-back",onClick:()=>void t({to:"/settings"}),children:"返回设置"}),e.jsx("h1",{children:"安卓样式诊断"}),e.jsx("p",{children:"在手机上打开本页,可以直接检查浏览器能力、样式加载、缓存和关键面板快照。"})]}),e.jsxs("div",{className:"diagnostics-toolbar",children:[e.jsx("button",{type:"button",className:"diagnostics-button",onClick:()=>void u(),disabled:i,children:i?"采集中…":"刷新诊断"}),e.jsx("button",{type:"button",className:"diagnostics-button diagnostics-button--ghost",onClick:()=>void p(),disabled:!r,children:"复制 JSON"})]})]}),l?e.jsx("p",{className:"diagnostics-status",children:l}):null,e.jsxs("section",{className:"diagnostics-card diagnostics-card--hero",children:[e.jsx("span",{className:"diagnostics-eyebrow",children:"初步判断"}),e.jsx("h2",{children:m}),e.jsx("p",{children:"真机异常而桌面浏览器和 F12 模拟正常,最常见就是浏览器 CSS 能力和缓存状态与桌面环境不同。"}),r!=null&&r.notes.length?e.jsx("ul",{className:"diagnostics-list",children:r.notes.map(s=>e.jsx("li",{children:s},s))}):null]}),r?e.jsxs(e.Fragment,{children:[e.jsxs("section",{className:"diagnostics-grid",children:[e.jsxs("article",{className:"diagnostics-card",children:[e.jsx("h2",{children:"环境"}),e.jsxs("dl",{className:"diagnostics-kv",children:[e.jsxs("div",{children:[e.jsx("dt",{children:"页面"}),e.jsx("dd",{children:r.page.pathname})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"安全上下文"}),e.jsx("dd",{children:r.environment.secure_context?"是":"否"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"独立窗口"}),e.jsx("dd",{children:r.environment.standalone_display_mode?"是":"否"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"在线状态"}),e.jsx("dd",{children:r.environment.online==null?"未知":r.environment.online?"在线":"离线"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"视口"}),e.jsxs("dd",{children:[r.viewport.width," x ",r.viewport.height," @ ",r.viewport.device_pixel_ratio]})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"UA"}),e.jsx("dd",{className:"diagnostics-break",children:r.environment.user_agent})]})]})]}),e.jsxs("article",{className:"diagnostics-card",children:[e.jsx("h2",{children:"浏览器能力"}),e.jsx("dl",{className:"diagnostics-kv",children:Object.entries(r.feature_support).map(([s,a])=>e.jsxs("div",{children:[e.jsx("dt",{children:s}),e.jsx("dd",{children:W(a)})]},s))})]}),e.jsxs("article",{className:"diagnostics-card",children:[e.jsx("h2",{children:"缓存与 SW"}),e.jsxs("dl",{className:"diagnostics-kv",children:[e.jsxs("div",{children:[e.jsx("dt",{children:"Service Worker"}),e.jsx("dd",{children:r.service_worker.supported?"支持":"不支持"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"当前受控"}),e.jsx("dd",{children:r.service_worker.controller?"是":"否"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"注册数量"}),e.jsx("dd",{children:r.service_worker.registrations.length})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"Cache Storage"}),e.jsx("dd",{children:r.cache.supported?r.cache.keys.join(", ")||"空":"不支持"})]})]})]}),e.jsxs("article",{className:"diagnostics-card",children:[e.jsx("h2",{children:"主题变量"}),e.jsx("dl",{className:"diagnostics-kv",children:Object.entries(r.theme_variables).map(([s,a])=>e.jsxs("div",{children:[e.jsx("dt",{children:s}),e.jsx("dd",{children:a})]},s))})]})]}),e.jsxs("section",{className:"diagnostics-card",children:[e.jsx("h2",{children:"资源探测"}),e.jsx("div",{className:"diagnostics-table-wrap",children:e.jsxs("table",{className:"diagnostics-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"类型"}),e.jsx("th",{children:"URL"}),e.jsx("th",{children:"状态"}),e.jsx("th",{children:"内容类型"}),e.jsx("th",{children:"Cache-Control"})]})}),e.jsx("tbody",{children:r.resource_probes.map(s=>e.jsxs("tr",{children:[e.jsx("td",{children:s.kind}),e.jsx("td",{className:"diagnostics-break",children:s.url}),e.jsx("td",{children:s.ok?`OK (${s.status??"-"})`:`失败 (${s.status??"-"})`}),e.jsx("td",{children:s.content_type??"-"}),e.jsx("td",{children:s.cache_control??s.error??"-"})]},`${s.kind}:${s.url}`))})]})})]}),e.jsxs("section",{className:"diagnostics-card",children:[e.jsx("h2",{children:"关键元素快照"}),e.jsx("div",{className:"diagnostics-element-grid",children:r.element_snapshots.map(s=>e.jsxs("article",{className:"diagnostics-element-card",children:[e.jsx("h3",{children:s.selector}),e.jsx("p",{children:U(s)}),e.jsxs("dl",{className:"diagnostics-kv diagnostics-kv--compact",children:[e.jsxs("div",{children:[e.jsx("dt",{children:"找到元素"}),e.jsx("dd",{children:s.found?"是":"否"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"尺寸"}),e.jsx("dd",{children:s.rect?`${s.rect.width} x ${s.rect.height}`:"-"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"背景色"}),e.jsx("dd",{children:s.computed["background-color"]??"-"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"背景图"}),e.jsx("dd",{className:"diagnostics-break",children:s.computed["background-image"]??"-"})]})]})]},s.selector))})]}),e.jsxs("section",{className:"diagnostics-card",children:[e.jsx("h2",{children:"原始 JSON"}),e.jsx("pre",{className:"diagnostics-json",children:JSON.stringify(r,null,2)})]})]}):e.jsx("section",{className:"diagnostics-card",children:e.jsx("p",{children:"正在等待诊断结果…"})})]})})};export{M as DiagnosticsPage};
|
|
1
|
+
import{u as j,r as d,j as e}from"./index-DhjIbKkn.js";const v=[{key:"color-mix-oklab",property:"background",value:"color-mix(in oklab, white 50%, black)"},{key:"color-mix-srgb",property:"background",value:"color-mix(in srgb, white 50%, black)"},{key:"backdrop-filter",property:"backdrop-filter",value:"blur(12px)"},{key:"webkit-backdrop-filter",property:"-webkit-backdrop-filter",value:"blur(12px)"},{key:"viewport-dvh",property:"height",value:"100dvh"}],y=["--color-surface-base","--color-surface-panel","--color-surface-floating","--color-surface-border","--color-surface-border-soft","--color-text-primary","--color-text-secondary","--color-accent-primary","--color-accent-primary-soft"],w=[".conversation-topbar",".topbar-menu",".conversation-run-popover",".chat-composer",".chat-composer__input",".composer-utility-menu__popover",".session-run-panel__command-card",".session-run-panel__input",".session-git-panel__summary"],b=["background-color","background-image","backdrop-filter","-webkit-backdrop-filter","border-color","box-shadow","color","display","opacity","position"],x=(t,r=180)=>{const n=(t??"").replace(/\s+/g," ").trim();return n?n.length<=r?n:`${n.slice(0,r-1).trimEnd()}…`:null},h=t=>Number(t.toFixed(2)),k=(t,r)=>{var n;if(typeof window>"u"||typeof((n=window.CSS)==null?void 0:n.supports)!="function")return!1;try{return window.CSS.supports(t,r)}catch{return!1}},_=()=>Object.fromEntries(v.map(t=>[t.key,k(t.property,t.value)])),S=()=>typeof document>"u"?[]:Array.from(document.styleSheets).map(t=>{var n,i;let r=null;try{r=t.cssRules.length}catch{r=null}return{href:t.href??null,owner_node:t.ownerNode instanceof Element?t.ownerNode.tagName.toLowerCase():null,media:((i=(n=t.media)==null?void 0:n.mediaText)==null?void 0:i.trim())||null,disabled:t.disabled,css_rule_count:r}}),N=()=>{if(typeof document>"u"||typeof window>"u")return[];const t=new Map,r=(n,i)=>{const o=n==null?void 0:n.trim();if(o)try{const l=new URL(o,window.location.href);if(!/^https?:$/i.test(l.protocol))return;t.set(l.toString(),i)}catch{return}};for(const n of document.styleSheets)r(n.href,"stylesheet");for(const n of Array.from(document.scripts))r(n.src,"script");for(const n of Array.from(document.querySelectorAll('link[rel~="manifest"]')))r(n.href,"manifest");for(const n of Array.from(document.querySelectorAll('link[rel~="icon"], link[rel="apple-touch-icon"]')))r(n.href,"icon");return Array.from(t.entries()).slice(0,20).map(([n,i])=>({url:n,kind:i}))},E=async t=>{const r=async n=>fetch(t.url,{method:n,cache:"no-store",redirect:"follow"});try{let n=await r("HEAD");return(n.status===405||n.status===501)&&(n=await r("GET")),{url:t.url,kind:t.kind,ok:n.ok,status:n.status,content_type:n.headers.get("content-type"),cache_control:n.headers.get("cache-control"),error:null}}catch(n){return{url:t.url,kind:t.kind,ok:!1,status:null,content_type:null,cache_control:null,error:n instanceof Error?n.message:"Unknown fetch failure"}}},C=()=>typeof performance>"u"||typeof performance.getEntriesByType!="function"?[]:performance.getEntriesByType("resource").map(t=>t).filter(t=>{const r=t.name.toLowerCase();return r.includes(".css")||r.includes(".js")||r.includes("manifest")||t.initiatorType==="link"||t.initiatorType==="script"}).slice(-40).map(t=>({name:t.name,initiator_type:t.initiatorType||null,duration_ms:h(t.duration),transfer_size:typeof t.transferSize=="number"?t.transferSize:null,decoded_body_size:typeof t.decodedBodySize=="number"?t.decodedBodySize:null})),T=()=>{if(typeof document>"u"||typeof window>"u")return{};const t=window.getComputedStyle(document.documentElement);return Object.fromEntries(y.map(r=>[r,t.getPropertyValue(r).trim()||"(empty)"]))},R=t=>{if(typeof document>"u"||typeof window>"u")return{selector:t,found:!1,text_preview:null,rect:null,computed:{}};const r=document.querySelector(t);if(!r)return{selector:t,found:!1,text_preview:null,rect:null,computed:{}};const n=r.getBoundingClientRect(),i=window.getComputedStyle(r);return{selector:t,found:!0,text_preview:x(r.textContent),rect:{x:h(n.x),y:h(n.y),width:h(n.width),height:h(n.height)},computed:Object.fromEntries(b.map(o=>[o,i.getPropertyValue(o).trim()||"(empty)"]))}},A=async()=>{if(typeof navigator>"u"||!("serviceWorker"in navigator))return{supported:!1,controller:!1,registrations:[]};try{const t=await navigator.serviceWorker.getRegistrations();return{supported:!0,controller:!!navigator.serviceWorker.controller,registrations:t.map(r=>{var n,i,o;return{scope:r.scope,active_script_url:((n=r.active)==null?void 0:n.scriptURL)??null,waiting_script_url:((i=r.waiting)==null?void 0:i.scriptURL)??null,installing_script_url:((o=r.installing)==null?void 0:o.scriptURL)??null}})}}catch{return{supported:!0,controller:!!navigator.serviceWorker.controller,registrations:[]}}},O=async()=>{if(typeof window>"u"||!("caches"in window))return{supported:!1,keys:[]};try{return{supported:!0,keys:await window.caches.keys()}}catch{return{supported:!0,keys:[]}}},L=t=>{const r=[];return t.feature_support["color-mix-oklab"]||r.push("当前浏览器不支持 color-mix(in oklab, ...),混色背景会直接失效,常见现象就是输入区或面板背景透明。"),!t.feature_support["backdrop-filter"]&&!t.feature_support["webkit-backdrop-filter"]&&r.push("当前浏览器不支持 backdrop-filter,磨砂浮层会退化成纯色面板。"),t.service_worker.controller&&t.cache.keys.length>0&&r.push(`检测到 ${t.cache.keys.length} 个 Cache Storage 项,若真机页面和桌面不一致,可以先清理缓存再重试。`),t.stylesheets.length===0&&r.push("当前页面没有读取到任何样式表对象,需要重点检查 CSS 是否被正确加载。"),t.resource_probes.some(n=>!n.ok)&&r.push("至少有一个静态资源探测失败,需要继续检查网络、缓存或 Service Worker 拦截。"),r},P=async()=>{var u,p,m,s,a,g;const t=N(),[r,n,i]=await Promise.all([A(),O(),Promise.all(t.map(f=>E(f)))]),o=navigator,l={width:window.innerWidth,height:window.innerHeight,device_pixel_ratio:window.devicePixelRatio||1,visual_width:((u=window.visualViewport)==null?void 0:u.width)??null,visual_height:((p=window.visualViewport)==null?void 0:p.height)??null,screen_width:((m=window.screen)==null?void 0:m.width)??null,screen_height:((s=window.screen)==null?void 0:s.height)??null},c={captured_at:new Date().toISOString(),page:{href:window.location.href,pathname:window.location.pathname,referrer:x(document.referrer),visibility_state:document.visibilityState??null},environment:{user_agent:navigator.userAgent,language:navigator.language??null,languages:Array.isArray(navigator.languages)?navigator.languages:[],platform:navigator.platform??null,vendor:navigator.vendor??null,online:typeof navigator.onLine=="boolean"?navigator.onLine:null,cookie_enabled:typeof navigator.cookieEnabled=="boolean"?navigator.cookieEnabled:null,secure_context:window.isSecureContext,standalone_display_mode:window.matchMedia("(display-mode: standalone)").matches,hardware_concurrency:typeof navigator.hardwareConcurrency=="number"?navigator.hardwareConcurrency:null,device_memory_gb:typeof o.deviceMemory=="number"?o.deviceMemory:null,max_touch_points:typeof navigator.maxTouchPoints=="number"?navigator.maxTouchPoints:null},viewport:l,feature_support:_(),service_worker:r,cache:n,manifest:{href:((a=document.querySelector('link[rel~="manifest"]'))==null?void 0:a.href)??null,rel:((g=document.querySelector('link[rel~="manifest"]'))==null?void 0:g.rel)??null},stylesheets:S(),resource_probes:i,performance_entries:C(),theme_variables:T(),element_snapshots:w.map(f=>R(f)),notes:[]};return c.notes=L(c),c},W=t=>t?"支持":"不支持",$=t=>t?t.feature_support["color-mix-oklab"]?t.resource_probes.some(r=>!r.ok)?"至少有一个静态资源探测失败,更像是 CSS 或脚本资源没有正确加载,或者被缓存 / Service Worker 干扰。":t.service_worker.controller&&t.cache.keys.length>0?"样式能力本身看起来正常,但页面被 Service Worker 控制且存在缓存,下一步要重点排查缓存是否陈旧。":"浏览器能力和基础资源看起来正常,下一步需要对比具体元素快照与安卓真机截图。":"高概率是安卓浏览器不支持 color-mix(in oklab, ...),导致输入框和面板的混色背景规则整体失效。":"正在采集浏览器能力、样式表、缓存和关键元素快照。",U=t=>{if(!t.found)return"未找到对应元素";const r=t.computed["background-color"]??"(empty)",n=t.computed["background-image"]??"(empty)";return r==="rgba(0, 0, 0, 0)"&&n==="none"?"背景完全透明":`${r} / ${n}`},M=()=>{const t=j(),[r,n]=d.useState(null),[i,o]=d.useState(!0),[l,c]=d.useState(null),u=async()=>{o(!0),c(null);try{const s=await P();d.startTransition(()=>{n(s)})}catch(s){c(s instanceof Error?`采集失败:${s.message}`:"采集失败,请稍后重试。")}finally{o(!1)}};d.useEffect(()=>{u()},[]);const p=async()=>{var s;if(r){if(typeof navigator>"u"||typeof((s=navigator.clipboard)==null?void 0:s.writeText)!="function"){c("当前浏览器不支持直接复制,请手动截图或长按选择。");return}try{await navigator.clipboard.writeText(JSON.stringify(r,null,2)),c("诊断 JSON 已复制,可以直接发给我继续分析。")}catch(a){c(a instanceof Error?`复制失败:${a.message}`:"复制失败,请手动截图或长按选择。")}}},m=d.useMemo(()=>$(r),[r]);return e.jsx("main",{className:"diagnostics-page",children:e.jsxs("div",{className:"diagnostics-shell",children:[e.jsxs("header",{className:"diagnostics-header",children:[e.jsxs("div",{className:"diagnostics-header__copy",children:[e.jsx("button",{type:"button",className:"diagnostics-back",onClick:()=>void t({to:"/settings"}),children:"返回设置"}),e.jsx("h1",{children:"安卓样式诊断"}),e.jsx("p",{children:"在手机上打开本页,可以直接检查浏览器能力、样式加载、缓存和关键面板快照。"})]}),e.jsxs("div",{className:"diagnostics-toolbar",children:[e.jsx("button",{type:"button",className:"diagnostics-button",onClick:()=>void u(),disabled:i,children:i?"采集中…":"刷新诊断"}),e.jsx("button",{type:"button",className:"diagnostics-button diagnostics-button--ghost",onClick:()=>void p(),disabled:!r,children:"复制 JSON"})]})]}),l?e.jsx("p",{className:"diagnostics-status",children:l}):null,e.jsxs("section",{className:"diagnostics-card diagnostics-card--hero",children:[e.jsx("span",{className:"diagnostics-eyebrow",children:"初步判断"}),e.jsx("h2",{children:m}),e.jsx("p",{children:"真机异常而桌面浏览器和 F12 模拟正常,最常见就是浏览器 CSS 能力和缓存状态与桌面环境不同。"}),r!=null&&r.notes.length?e.jsx("ul",{className:"diagnostics-list",children:r.notes.map(s=>e.jsx("li",{children:s},s))}):null]}),r?e.jsxs(e.Fragment,{children:[e.jsxs("section",{className:"diagnostics-grid",children:[e.jsxs("article",{className:"diagnostics-card",children:[e.jsx("h2",{children:"环境"}),e.jsxs("dl",{className:"diagnostics-kv",children:[e.jsxs("div",{children:[e.jsx("dt",{children:"页面"}),e.jsx("dd",{children:r.page.pathname})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"安全上下文"}),e.jsx("dd",{children:r.environment.secure_context?"是":"否"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"独立窗口"}),e.jsx("dd",{children:r.environment.standalone_display_mode?"是":"否"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"在线状态"}),e.jsx("dd",{children:r.environment.online==null?"未知":r.environment.online?"在线":"离线"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"视口"}),e.jsxs("dd",{children:[r.viewport.width," x ",r.viewport.height," @ ",r.viewport.device_pixel_ratio]})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"UA"}),e.jsx("dd",{className:"diagnostics-break",children:r.environment.user_agent})]})]})]}),e.jsxs("article",{className:"diagnostics-card",children:[e.jsx("h2",{children:"浏览器能力"}),e.jsx("dl",{className:"diagnostics-kv",children:Object.entries(r.feature_support).map(([s,a])=>e.jsxs("div",{children:[e.jsx("dt",{children:s}),e.jsx("dd",{children:W(a)})]},s))})]}),e.jsxs("article",{className:"diagnostics-card",children:[e.jsx("h2",{children:"缓存与 SW"}),e.jsxs("dl",{className:"diagnostics-kv",children:[e.jsxs("div",{children:[e.jsx("dt",{children:"Service Worker"}),e.jsx("dd",{children:r.service_worker.supported?"支持":"不支持"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"当前受控"}),e.jsx("dd",{children:r.service_worker.controller?"是":"否"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"注册数量"}),e.jsx("dd",{children:r.service_worker.registrations.length})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"Cache Storage"}),e.jsx("dd",{children:r.cache.supported?r.cache.keys.join(", ")||"空":"不支持"})]})]})]}),e.jsxs("article",{className:"diagnostics-card",children:[e.jsx("h2",{children:"主题变量"}),e.jsx("dl",{className:"diagnostics-kv",children:Object.entries(r.theme_variables).map(([s,a])=>e.jsxs("div",{children:[e.jsx("dt",{children:s}),e.jsx("dd",{children:a})]},s))})]})]}),e.jsxs("section",{className:"diagnostics-card",children:[e.jsx("h2",{children:"资源探测"}),e.jsx("div",{className:"diagnostics-table-wrap",children:e.jsxs("table",{className:"diagnostics-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"类型"}),e.jsx("th",{children:"URL"}),e.jsx("th",{children:"状态"}),e.jsx("th",{children:"内容类型"}),e.jsx("th",{children:"Cache-Control"})]})}),e.jsx("tbody",{children:r.resource_probes.map(s=>e.jsxs("tr",{children:[e.jsx("td",{children:s.kind}),e.jsx("td",{className:"diagnostics-break",children:s.url}),e.jsx("td",{children:s.ok?`OK (${s.status??"-"})`:`失败 (${s.status??"-"})`}),e.jsx("td",{children:s.content_type??"-"}),e.jsx("td",{children:s.cache_control??s.error??"-"})]},`${s.kind}:${s.url}`))})]})})]}),e.jsxs("section",{className:"diagnostics-card",children:[e.jsx("h2",{children:"关键元素快照"}),e.jsx("div",{className:"diagnostics-element-grid",children:r.element_snapshots.map(s=>e.jsxs("article",{className:"diagnostics-element-card",children:[e.jsx("h3",{children:s.selector}),e.jsx("p",{children:U(s)}),e.jsxs("dl",{className:"diagnostics-kv diagnostics-kv--compact",children:[e.jsxs("div",{children:[e.jsx("dt",{children:"找到元素"}),e.jsx("dd",{children:s.found?"是":"否"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"尺寸"}),e.jsx("dd",{children:s.rect?`${s.rect.width} x ${s.rect.height}`:"-"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"背景色"}),e.jsx("dd",{children:s.computed["background-color"]??"-"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"背景图"}),e.jsx("dd",{className:"diagnostics-break",children:s.computed["background-image"]??"-"})]})]})]},s.selector))})]}),e.jsxs("section",{className:"diagnostics-card",children:[e.jsx("h2",{children:"原始 JSON"}),e.jsx("pre",{className:"diagnostics-json",children:JSON.stringify(r,null,2)})]})]}):e.jsx("section",{className:"diagnostics-card",children:e.jsx("p",{children:"正在等待诊断结果…"})})]})})};export{M as DiagnosticsPage};
|