gz-research-assistant 0.1.0

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,709 @@
1
+ import { controls as e, describeControl as t, excluded as n, locate as r, privateInput as i } from "gz-laya-pilot/browser";
2
+ //#region src/privacy.ts
3
+ var a = /password|passwd|pwd|token|authorization|cookie|secret|credential|session|api[-_]?key|身份证|手机号|银行卡|证件|phone|mobile|email|idcard|bankcard/i;
4
+ function o(e, t = 600) {
5
+ return e.slice(0, 12e3).replace(/Bearer\s+[^\s,;"']+/gi, "Bearer [已脱敏]").replace(/\beyJ[\w-]+\.[\w-]+\.[\w-]+\b/g, "[令牌已脱敏]").replace(/((?:password|passwd|pwd|token|secret|authorization|cookie|session(?:id)?|api[-_]?key)["']?\s*[=:]\s*["']?)[^"'\s,;&]+/gi, "$1[已脱敏]").replace(/\b1[3-9]\d{9}\b/g, "[手机号已脱敏]").replace(/\b\d{17}[\dXx]\b/g, "[证件号已脱敏]").replace(/[\w.+-]+@[\w.-]+\.[a-z]{2,}/gi, "[邮箱已脱敏]").slice(0, t);
6
+ }
7
+ function s(e) {
8
+ try {
9
+ let t = new URL(e, location.origin);
10
+ if (!["http:", "https:"].includes(t.protocol)) return "[" + t.protocol + "]";
11
+ let n = [...new Set(t.searchParams.keys())].slice(0, 20);
12
+ return o(t.origin + t.pathname, 300) + (n.length ? "?" + n.map((e) => encodeURIComponent(o(e, 40)) + "=[已脱敏]").join("&") : "") + (t.hash ? "#[已省略]" : "");
13
+ } catch {
14
+ return "[无效地址]";
15
+ }
16
+ }
17
+ function c(e, t = 0, n = /* @__PURE__ */ new WeakSet()) {
18
+ return t > 5 ? "[层级已截断]" : typeof e == "string" ? o(e) : e === null || typeof e == "number" || typeof e == "boolean" ? e : e instanceof Error ? {
19
+ name: o(e.name),
20
+ message: o(e.message),
21
+ stack: o(e.stack ?? "", 1800)
22
+ } : typeof e == "object" ? n.has(e) ? "[循环引用]" : (n.add(e), Array.isArray(e) ? e.slice(0, 30).map((e) => c(e, t + 1, n)) : Object.fromEntries(Object.entries(e).slice(0, 50).map(([e, r]) => [o(e, 80), a.test(e) ? "[已脱敏]" : c(r, t + 1, n)]))) : String(e).slice(0, 80);
23
+ }
24
+ function l(e) {
25
+ if (typeof e != "string") return e == null ? null : "[非文本正文未采集]";
26
+ if (e.length > 16e3) return "[正文超过 16 KB,未采集]";
27
+ try {
28
+ return c(JSON.parse(e));
29
+ } catch {
30
+ return "[非 JSON 正文未采集]";
31
+ }
32
+ }
33
+ function u(e) {
34
+ return /\/(?:login|logininfo|logout|auth|oauth|token|password|session)(?:\/|\?|$)/i.test(e);
35
+ }
36
+ //#endregion
37
+ //#region src/performance.ts
38
+ var d = () => ({
39
+ heapSamples: [],
40
+ longTasks: []
41
+ }), f = (e) => `${(e / 1048576).toFixed(1)} MB`;
42
+ function p(e, t, n) {
43
+ let r = [];
44
+ e.LCP > 2500 && r.push({
45
+ id: "lcp",
46
+ severity: e.LCP > 4e3 ? "error" : "warning",
47
+ title: "主要内容显示偏慢",
48
+ evidence: `本次页面加载 LCP 为 ${Math.round(e.LCP)} ms`,
49
+ rule: "LCP > 2.5 秒需改进,> 4 秒较差;此处为单次页面观测,不代表站点分位数。",
50
+ suggestions: ["在 DevTools Performance 中录制页面加载,定位 LCP 元素。", "检查关键图片大小、资源优先级与接口等待,减少阻塞渲染的脚本。"],
51
+ url: "https://web.dev/articles/lcp"
52
+ }), e.CLS > .1 && r.push({
53
+ id: "cls",
54
+ severity: e.CLS > .25 ? "error" : "warning",
55
+ title: "页面布局存在明显跳动",
56
+ evidence: `本次页面生命周期 CLS 为 ${e.CLS.toFixed(3)}`,
57
+ rule: "CLS > 0.1 需改进,> 0.25 较差;采用布局偏移会话窗口统计。",
58
+ suggestions: ["在 DevTools Performance 的 Layout shifts 轨道查看发生偏移的元素。", "为图片、表格和异步内容预留空间,检查字体替换和顶部动态插入内容。"],
59
+ url: "https://web.dev/articles/optimize-cls"
60
+ });
61
+ let i = t.longTasks.filter((e) => e.time <= n && n - e.time <= 3e4), a = i.reduce((e, t) => e + t.duration, 0), o = Math.max(0, ...i.map((e) => e.duration));
62
+ (o >= 200 || a >= 1e3) && r.push({
63
+ id: "long-task",
64
+ severity: o >= 1e3 ? "error" : "warning",
65
+ title: "主线程可能出现卡顿",
66
+ evidence: `最近 30 秒 ${i.length} 次长任务,最长 ${Math.round(o)} ms,累计 ${Math.round(a)} ms`,
67
+ rule: "本地告警规则:单次 ≥ 200 ms,或 30 秒累计 ≥ 1,000 ms;长任务采集门槛为 50 ms。",
68
+ suggestions: ["在 DevTools Performance 中录制卡顿操作,展开 Main 轨道中的长任务。", "检查大循环、重复渲染和同步 JSON 处理;拆分任务,长列表使用虚拟滚动。"],
69
+ url: "https://developer.chrome.com/docs/devtools/performance"
70
+ });
71
+ let s = [];
72
+ for (let e of t.heapSamples) e.time > n || n - e.time > 9e4 || (s.length && e.time - s[s.length - 1].time > 1e4 && (s.length = 0), s.push(e));
73
+ let c = s.at(-1);
74
+ if (!c || n - c.time > 1e4) return r;
75
+ let l = s.slice(-3);
76
+ if (l.length === 3 && l.every((e) => e.limit > 0 && e.used / e.limit >= .8) && r.push({
77
+ id: "heap-pressure",
78
+ severity: "error",
79
+ title: "JS 堆内存压力偏高",
80
+ evidence: `当前 ${f(c.used)} / 堆上限 ${f(c.limit)}(${Math.round(c.used / c.limit * 100)}%)`,
81
+ rule: "本地告警规则:连续 3 次采样 ≥ 浏览器 JS 堆上限的 80%。",
82
+ suggestions: ["在 Chrome 任务管理器核对内存占用,并在 Memory 中比较堆快照。", "检查大数组、缓存和未释放的对象;按实际设备验证内存预算。"],
83
+ url: "https://developer.chrome.com/docs/devtools/memory-problems"
84
+ }), s.length >= 13 && c.time - s[0].time >= 6e4) {
85
+ let e = Math.floor(s.length / 3), t = (e) => Math.min(...e.map((e) => e.used)), n = t(s.slice(0, e)), i = t(s.slice(e, e * 2)), a = t(s.slice(e * 2)), o = a - n;
86
+ n > 0 && o >= 20 * 1048576 && o / n >= .3 && i > n && a > i && r.push({
87
+ id: "heap-growth",
88
+ severity: "warning",
89
+ title: "内存持续增长,建议排查泄漏",
90
+ evidence: `${Math.round((c.time - s[0].time) / 1e3)} 秒内,分段内存低点增加 ${f(o)}(${Math.round(o / n * 100)}%)`,
91
+ rule: "本地风险规则:至少连续 60 秒、13 次采样,三段低点依次增长,增量 ≥ 20 MB 且 ≥ 30%。这不是泄漏结论。",
92
+ suggestions: [
93
+ "重复进入、退出同一业务页面,再静置观察内存是否回落。",
94
+ "在 DevTools Memory 手动回收垃圾后分别拍摄堆快照,用 Comparison 比较持续保留的对象。",
95
+ "检查 Detached DOM、事件监听器、定时器、订阅和缓存的清理;用 Allocation instrumentation 定位分配来源。"
96
+ ],
97
+ url: "https://developer.chrome.com/docs/devtools/memory-problems"
98
+ });
99
+ }
100
+ return r;
101
+ }
102
+ //#endregion
103
+ //#region src/runtime.ts
104
+ var m = 300, h = 60, g = 900 * 1e3, _ = {
105
+ events: [],
106
+ requests: [],
107
+ metrics: {},
108
+ performance: d(),
109
+ performanceUpdatedAt: Date.now(),
110
+ paused: !1,
111
+ frozen: null
112
+ }, v = /* @__PURE__ */ new Set(), y = 0, b, x = !1, S = 0, C, w = /* @__PURE__ */ new Set(), T;
113
+ function E() {
114
+ C ||= setTimeout(() => {
115
+ C = void 0, w.forEach((e) => e());
116
+ }, 150);
117
+ }
118
+ function D() {
119
+ let e = Date.now() - g;
120
+ _ = {
121
+ ..._,
122
+ events: _.events.filter((t) => t.time >= e).slice(-m),
123
+ requests: _.requests.filter((t) => t.time >= e).slice(-h),
124
+ performanceUpdatedAt: Date.now()
125
+ };
126
+ }
127
+ function O(e, t, n, r = Date.now(), i = b) {
128
+ if (!x || _.paused) return;
129
+ let a = {
130
+ id: ++y,
131
+ time: r,
132
+ kind: e,
133
+ title: o(t, 180),
134
+ page: s(location.href),
135
+ actionId: i,
136
+ detail: c(n)
137
+ };
138
+ (e === "click" || e === "change" || e === "route") && (b = a.id), _ = {
139
+ ..._,
140
+ events: [..._.events, a]
141
+ }, D(), E();
142
+ }
143
+ function k(e) {
144
+ return e ? i(e) ? "[敏感控件]" : o(t(e).label, 70) : "";
145
+ }
146
+ function A() {
147
+ return {
148
+ url: s(location.href),
149
+ title: o(document.title, 100),
150
+ viewport: {
151
+ width: innerWidth,
152
+ height: innerHeight,
153
+ pixelRatio: devicePixelRatio
154
+ },
155
+ scroll: {
156
+ x: scrollX,
157
+ y: scrollY
158
+ },
159
+ visibility: document.visibilityState,
160
+ focusedControl: k(document.activeElement),
161
+ headings: [...document.querySelectorAll("h1,h2,h3")].filter((e) => !e.closest("[data-research-assistant]")).slice(0, 12).map((e) => o(e.textContent ?? "", 80)),
162
+ domNodes: document.getElementsByTagName("*").length,
163
+ iframeCount: document.querySelectorAll("iframe").length
164
+ };
165
+ }
166
+ function j(e) {
167
+ try {
168
+ return Array.from({ length: Math.min(window[e].length, 60) }, (t, n) => o(window[e].key(n) ?? "", 100));
169
+ } catch {
170
+ return ["[浏览器禁止访问]"];
171
+ }
172
+ }
173
+ function M() {
174
+ let e = performance, t = navigator, n = [];
175
+ try {
176
+ n = document.cookie.split(";").filter((e) => e.trim()).slice(0, 60).map((e) => o(e.split("=")[0].trim(), 100));
177
+ } catch {
178
+ n = ["[浏览器禁止访问]"];
179
+ }
180
+ return {
181
+ userAgent: o(navigator.userAgent, 300),
182
+ language: navigator.language,
183
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
184
+ online: navigator.onLine,
185
+ cpuThreads: navigator.hardwareConcurrency,
186
+ deviceMemoryGB: t.deviceMemory ?? null,
187
+ heap: e.memory ? {
188
+ usedJSHeapSize: e.memory.usedJSHeapSize,
189
+ totalJSHeapSize: e.memory.totalJSHeapSize,
190
+ jsHeapSizeLimit: e.memory.jsHeapSizeLimit
191
+ } : null,
192
+ connection: t.connection ? {
193
+ effectiveType: t.connection.effectiveType,
194
+ downlinkMbps: t.connection.downlink,
195
+ rttMs: t.connection.rtt,
196
+ saveData: t.connection.saveData
197
+ } : null,
198
+ cookies: n,
199
+ localStorageKeys: j("localStorage"),
200
+ sessionStorageKeys: j("sessionStorage"),
201
+ coverage: "仅当前窗口;跨域 iframe 未接入。Cookie 不包含 HttpOnly,所有存储值均不采集。"
202
+ };
203
+ }
204
+ function N(e = "手动截取现场") {
205
+ return D(), structuredClone({
206
+ schema: "zhidao-research-assistant",
207
+ version: 1,
208
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
209
+ reason: o(e),
210
+ page: A(),
211
+ environment: M(),
212
+ metrics: _.metrics,
213
+ performance: _.performance,
214
+ events: _.events,
215
+ requests: _.requests,
216
+ limits: {
217
+ events: m,
218
+ requests: h,
219
+ retentionMinutes: g / 6e4
220
+ }
221
+ });
222
+ }
223
+ function P(e) {
224
+ _ = {
225
+ ..._,
226
+ frozen: N(e)
227
+ }, E();
228
+ }
229
+ function F(e, t) {
230
+ !x || _.paused || (O("error", e, t), P(e));
231
+ }
232
+ var I = {
233
+ subscribe(e) {
234
+ return w.add(e), () => {
235
+ w.delete(e);
236
+ };
237
+ },
238
+ getState: () => _,
239
+ pause(e) {
240
+ S++, _ = {
241
+ ..._,
242
+ paused: e,
243
+ performance: d()
244
+ }, v.clear(), E();
245
+ },
246
+ clear() {
247
+ S++, b = void 0, _ = {
248
+ ..._,
249
+ events: [],
250
+ requests: [],
251
+ metrics: {},
252
+ performance: d(),
253
+ frozen: null
254
+ }, v.clear(), E();
255
+ }
256
+ };
257
+ function L(e, t) {
258
+ !x || _.paused || S !== t || (_ = {
259
+ ..._,
260
+ requests: [..._.requests, e].slice(-h)
261
+ }, O("request", `${e.method} ${e.url} · ${e.status || "网络错误"} · ${Math.round(e.duration)} ms`, {
262
+ requestId: e.id,
263
+ failed: e.failed
264
+ }, e.time, e.actionId), e.failed && F("接口调用失败", {
265
+ requestId: e.id,
266
+ url: e.url,
267
+ status: e.status,
268
+ message: e.message
269
+ }));
270
+ }
271
+ async function R(e) {
272
+ if (!e.headers.get("content-type")?.includes("json")) return "[非 JSON 响应未采集]";
273
+ if (Number(e.headers.get("content-length")) > 16e3) return "[响应超过 16 KB,未采集]";
274
+ let t = e.clone().body?.getReader();
275
+ if (!t) return null;
276
+ let n = "", r = 0, i = new TextDecoder(), a = !1, o = setTimeout(() => {
277
+ a = !0, t.cancel().catch(() => {});
278
+ }, 2500);
279
+ try {
280
+ for (;;) {
281
+ let { done: e, value: a } = await t.read();
282
+ if (e) break;
283
+ if (r += a.byteLength, r > 16e3) return t.cancel().catch(() => {}), "[响应超过 16 KB,已截断]";
284
+ n += i.decode(a, { stream: !0 });
285
+ }
286
+ return a ? "[响应预览超时,未采集]" : l(n + i.decode());
287
+ } finally {
288
+ clearTimeout(o), t.releaseLock();
289
+ }
290
+ }
291
+ function z(r = {}) {
292
+ if (T) return T;
293
+ let a = (e) => {
294
+ try {
295
+ return r.isBusinessFailure?.(e) === !0;
296
+ } catch {
297
+ return !1;
298
+ }
299
+ };
300
+ x = !0;
301
+ let d = [], f = (e, t, n, r = !1) => {
302
+ e.addEventListener(t, n, r), d.push(() => e.removeEventListener(t, n, r));
303
+ };
304
+ f(document, "click", (r) => {
305
+ let a = r.target instanceof Element ? r.target : null;
306
+ if (a?.closest(n)) return;
307
+ let o = a?.closest(e);
308
+ o && !i(o) && O("click", "点击:" + k(o), { target: t(o) });
309
+ }, !0), f(document, "change", (e) => {
310
+ let r = e.target instanceof Element ? e.target : null;
311
+ !r || r.closest(n) || i(r) || O("change", "修改字段:" + k(r), {
312
+ target: t(r),
313
+ value: "[不采集输入值]"
314
+ });
315
+ }, !0);
316
+ let p = () => O("route", "进入页面", { url: s(location.href) });
317
+ for (let e of ["pushState", "replaceState"]) {
318
+ let t = history[e], n = function(...e) {
319
+ let n = t.apply(this, e);
320
+ return p(), n;
321
+ };
322
+ history[e] = n, d.push(() => {
323
+ history[e] === n && (history[e] = t);
324
+ });
325
+ }
326
+ f(window, "popstate", p), f(window, "hashchange", p), f(window, "error", (e) => {
327
+ e instanceof ErrorEvent ? F(e.message || "运行错误", e.error) : e.target instanceof Element && F("资源加载失败", {
328
+ tag: e.target.tagName,
329
+ url: s(e.target.getAttribute("src") || e.target.getAttribute("href") || "")
330
+ });
331
+ }, !0), f(window, "unhandledrejection", (e) => F("未处理的 Promise 错误", e.reason));
332
+ let m = window.fetch, h = async function(e, t) {
333
+ if (_.paused || !x) return m.call(this, e, t);
334
+ let n = S, i = performance.now(), c = Date.now(), d = typeof e == "string" ? e : e instanceof URL ? e.href : e.url;
335
+ if (new URL(d, location.href).pathname.startsWith("/__laya-pilot/") || r.ignoreRequest?.(new URL(d, location.href))) return m.call(this, e, t);
336
+ let f = u(d), p = {
337
+ id: ++y,
338
+ time: c,
339
+ actionId: b,
340
+ source: "fetch",
341
+ method: (t?.method ?? (e instanceof Request ? e.method : "GET")).toUpperCase(),
342
+ url: s(d),
343
+ status: 0,
344
+ duration: 0,
345
+ failed: !1
346
+ };
347
+ try {
348
+ p.request = f ? "[认证接口正文未采集]" : l(t?.body), p.resourceCode = o(new Headers(t?.headers ?? (e instanceof Request ? e.headers : void 0)).get("X-Resrc-Code") ?? "", 80);
349
+ } catch {}
350
+ try {
351
+ let r = await m.call(this, e, t);
352
+ return p.status = r.status, p.duration = performance.now() - i, p.failed = !r.ok, (async () => {
353
+ try {
354
+ p.response = f ? "[认证接口正文未采集]" : await R(r), p.failed ||= a(p.response);
355
+ } catch {
356
+ p.response = "[响应不可读取]";
357
+ }
358
+ L(p, n);
359
+ })(), r;
360
+ } catch (e) {
361
+ throw p.duration = performance.now() - i, p.failed = !0, p.message = o(e instanceof Error ? e.message : String(e)), L(p, n), e;
362
+ }
363
+ };
364
+ window.fetch = h, d.push(() => {
365
+ window.fetch === h && (window.fetch = m);
366
+ });
367
+ let v = XMLHttpRequest.prototype.open, w = XMLHttpRequest.prototype.send, A = XMLHttpRequest.prototype.setRequestHeader, j = /* @__PURE__ */ new WeakMap(), M = function(e, t, n = !0, r, i) {
368
+ return j.set(this, {
369
+ method: e,
370
+ url: String(t)
371
+ }), v.call(this, e, t, n, r, i);
372
+ }, N = function(e, t) {
373
+ let n = A.call(this, e, t), r = j.get(this);
374
+ return r && e.toLowerCase() === "x-resrc-code" && (r.resourceCode = o(t, 80)), n;
375
+ }, P = function(e) {
376
+ let t = j.get(this);
377
+ if (!t || !x || _.paused) return w.call(this, e);
378
+ let n = S, r = performance.now(), i = u(t.url), o = {
379
+ id: ++y,
380
+ time: Date.now(),
381
+ actionId: b,
382
+ method: t.method.toUpperCase(),
383
+ url: s(t.url),
384
+ source: "xhr",
385
+ resourceCode: t.resourceCode,
386
+ status: 0,
387
+ duration: 0,
388
+ failed: !1,
389
+ request: i ? "[认证接口正文未采集]" : l(e)
390
+ }, d = () => {
391
+ o.duration = performance.now() - r, o.status = this.status, o.failed = this.status === 0 || this.status >= 400;
392
+ try {
393
+ o.response = i ? "[认证接口正文未采集]" : this.responseType === "json" ? c(this.response) : this.responseType === "" || this.responseType === "text" ? l(this.responseText) : "[非文本响应未采集]", o.failed ||= a(o.response);
394
+ } catch {
395
+ o.response = "[响应不可读取]";
396
+ }
397
+ L(o, n);
398
+ };
399
+ this.addEventListener("loadend", d, { once: !0 });
400
+ try {
401
+ return w.call(this, e);
402
+ } catch (e) {
403
+ throw this.removeEventListener("loadend", d), e;
404
+ }
405
+ };
406
+ XMLHttpRequest.prototype.open = M, XMLHttpRequest.prototype.send = P, XMLHttpRequest.prototype.setRequestHeader = N, d.push(() => {
407
+ XMLHttpRequest.prototype.open === M && (XMLHttpRequest.prototype.open = v), XMLHttpRequest.prototype.send === P && (XMLHttpRequest.prototype.send = w), XMLHttpRequest.prototype.setRequestHeader === N && (XMLHttpRequest.prototype.setRequestHeader = A);
408
+ });
409
+ let I = (e, t) => {
410
+ try {
411
+ let n = new PerformanceObserver((e) => {
412
+ x && !_.paused && (e.getEntries().forEach(t), _ = {
413
+ ..._,
414
+ metrics: { ..._.metrics }
415
+ }, B(), E());
416
+ });
417
+ n.observe({
418
+ type: e,
419
+ buffered: !0
420
+ }), d.push(() => n.disconnect());
421
+ } catch {}
422
+ };
423
+ I("navigation", (e) => {
424
+ let t = e;
425
+ _.metrics.TTFB = t.responseStart, _.metrics.DOMContentLoaded = t.domContentLoadedEventEnd, _.metrics.load = t.loadEventEnd;
426
+ }), I("paint", (e) => {
427
+ e.name === "first-contentful-paint" && (_.metrics.FCP = e.startTime);
428
+ }), I("largest-contentful-paint", (e) => {
429
+ _.metrics.LCP = e.startTime;
430
+ }), I("longtask", (e) => {
431
+ _.metrics.longTasks = (_.metrics.longTasks ?? 0) + 1, _.metrics.longTaskDuration = (_.metrics.longTaskDuration ?? 0) + e.duration;
432
+ let t = Date.now();
433
+ _ = {
434
+ ..._,
435
+ performance: {
436
+ ..._.performance,
437
+ longTasks: [..._.performance.longTasks.filter((e) => t - e.time <= 3e4), {
438
+ time: performance.timeOrigin + e.startTime,
439
+ duration: e.duration
440
+ }].slice(-200)
441
+ }
442
+ }, e.duration > 200 && O("performance", "主线程长任务", { duration: Math.round(e.duration) });
443
+ });
444
+ let z = 0, V = 0, H = 0;
445
+ I("layout-shift", (e) => {
446
+ let t = e;
447
+ t.hadRecentInput || ((e.startTime - V > 1e3 || e.startTime - z > 5e3) && (z = e.startTime, H = 0), V = e.startTime, H += t.value, _.metrics.CLS = Math.max(_.metrics.CLS ?? 0, H));
448
+ });
449
+ let U = setInterval(() => {
450
+ D(), E();
451
+ }, 1e4);
452
+ d.push(() => clearInterval(U));
453
+ let W = () => {
454
+ if (!x || _.paused || document.visibilityState === "hidden") return;
455
+ let e = performance.memory, t = Date.now(), n = e && Number.isFinite(e.usedJSHeapSize) && e.usedJSHeapSize >= 0 && Number.isFinite(e.jsHeapSizeLimit) && e.jsHeapSizeLimit > 0 ? [{
456
+ time: t,
457
+ used: e.usedJSHeapSize,
458
+ limit: e.jsHeapSizeLimit
459
+ }] : [];
460
+ _ = {
461
+ ..._,
462
+ performance: {
463
+ heapSamples: [..._.performance.heapSamples, ...n].filter((e) => t - e.time <= g).slice(-180),
464
+ longTasks: _.performance.longTasks.filter((e) => t - e.time <= 3e4)
465
+ }
466
+ }, B(), E();
467
+ }, G = setInterval(W, 5e3);
468
+ return d.push(() => clearInterval(G)), W(), p(), T = () => {
469
+ x = !1, S++, d.reverse().forEach((e) => e()), C && clearTimeout(C), C = void 0, T = void 0;
470
+ }, T;
471
+ }
472
+ function B() {
473
+ _ = {
474
+ ..._,
475
+ performanceUpdatedAt: Date.now()
476
+ };
477
+ let e = p(_.metrics, _.performance, _.performanceUpdatedAt), t = new Set(e.map((e) => e.id));
478
+ for (let t of e) v.has(t.id) || O("performance", t.title, {
479
+ evidence: t.evidence,
480
+ rule: t.rule
481
+ });
482
+ v = t;
483
+ }
484
+ //#endregion
485
+ //#region src/snapshot.ts
486
+ function V(e) {
487
+ if (e.length > 2 * 1024 * 1024) throw Error("快照不能超过 2 MB");
488
+ let t = JSON.parse(e);
489
+ if (!t || t.schema !== "zhidao-research-assistant" || t.version !== 1 || typeof t.capturedAt != "string" || !Number.isFinite(Date.parse(t.capturedAt)) || typeof t.reason != "string" || typeof t.page?.url != "string" || !t.environment || typeof t.environment != "object" || !Array.isArray(t.events) || t.events.length > 300 || !Array.isArray(t.requests) || t.requests.length > 60 || !t.metrics || typeof t.metrics != "object") throw Error("不是受支持的研发小蜜快照");
490
+ let n = [
491
+ "route",
492
+ "click",
493
+ "change",
494
+ "request",
495
+ "error",
496
+ "performance"
497
+ ];
498
+ for (let e of t.events) if (!e || !n.includes(e.kind) || !Number.isFinite(e.id) || !Number.isFinite(e.time) || typeof e.title != "string" || typeof e.page != "string") throw Error("时间线数据格式错误");
499
+ for (let e of t.requests) if (!e || !Number.isFinite(e.id) || !Number.isFinite(e.time) || typeof e.url != "string" || typeof e.method != "string" || !Number.isFinite(e.duration) || !Number.isFinite(e.status)) throw Error("接口数据格式错误");
500
+ if (t.performance) {
501
+ let { heapSamples: e, longTasks: n } = t.performance;
502
+ if (!Array.isArray(e) || e.length > 180 || !Array.isArray(n) || n.length > 200 || e.some((t, n) => !t || !Number.isFinite(t.time) || !Number.isFinite(t.used) || t.used < 0 || !Number.isFinite(t.limit) || t.limit <= 0 || n > 0 && t.time <= e[n - 1].time) || n.some((e) => !e || !Number.isFinite(e.time) || !Number.isFinite(e.duration) || e.duration < 0)) throw Error("性能采样数据格式错误");
503
+ }
504
+ return {
505
+ schema: t.schema,
506
+ version: 1,
507
+ capturedAt: t.capturedAt,
508
+ reason: o(t.reason),
509
+ feedback: typeof t.feedback == "string" ? o(t.feedback, 1e3) : void 0,
510
+ page: {
511
+ ...c(t.page),
512
+ url: s(t.page.url)
513
+ },
514
+ environment: {
515
+ ...c(t.environment),
516
+ ...Object.fromEntries([
517
+ "cookies",
518
+ "localStorageKeys",
519
+ "sessionStorageKeys"
520
+ ].map((e) => {
521
+ let n = t.environment[e];
522
+ return [e, Array.isArray(n) ? n.slice(0, 60).filter((e) => typeof e == "string").map((e) => o(e, 100)) : []];
523
+ }))
524
+ },
525
+ metrics: Object.fromEntries(Object.entries(t.metrics).filter(([, e]) => typeof e == "number" && Number.isFinite(e)).slice(0, 30)),
526
+ ...t.performance ? { performance: {
527
+ heapSamples: t.performance.heapSamples.map(({ time: e, used: t, limit: n }) => ({
528
+ time: e,
529
+ used: t,
530
+ limit: n
531
+ })),
532
+ longTasks: t.performance.longTasks.map(({ time: e, duration: t }) => ({
533
+ time: e,
534
+ duration: t
535
+ }))
536
+ } } : {},
537
+ events: t.events.map((e) => ({
538
+ ...c(e),
539
+ page: s(e.page)
540
+ })),
541
+ requests: t.requests.map((e) => ({
542
+ ...c(e),
543
+ url: s(e.url)
544
+ })),
545
+ limits: {
546
+ events: 300,
547
+ requests: 60,
548
+ retentionMinutes: 15
549
+ }
550
+ };
551
+ }
552
+ function H(e, t) {
553
+ let n = JSON.stringify({
554
+ ...e,
555
+ feedback: o(t || e.feedback || "", 1e3)
556
+ }, null, 2), r = new Blob([n], { type: "application/json;charset=utf-8" }), i = URL.createObjectURL(r), a = document.createElement("a");
557
+ a.href = i, a.download = `研发小蜜快照_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`, document.body.append(a), a.click(), a.remove(), setTimeout(() => URL.revokeObjectURL(i), 3e4);
558
+ }
559
+ //#endregion
560
+ //#region src/automation/recorder.ts
561
+ var U = () => ({
562
+ schema: "zhidao-recorded-case",
563
+ version: 1,
564
+ name: "页面操作录制",
565
+ path: typeof location > "u" ? "/" : location.pathname,
566
+ steps: []
567
+ }), W = {
568
+ recording: !1,
569
+ draft: U(),
570
+ saved: null
571
+ }, G = /* @__PURE__ */ new Set(), K = () => {};
572
+ function q(e) {
573
+ W = {
574
+ ...W,
575
+ ...e
576
+ }, G.forEach((e) => e());
577
+ }
578
+ function J(e) {
579
+ if (W.draft.steps.length >= 100) {
580
+ Y.stop();
581
+ return;
582
+ }
583
+ let t = [...W.draft.steps], n = t.at(-1);
584
+ e.kind === "fill" && n?.kind === "fill" && n.target?.selector === e.target?.selector && t.pop(), t.push(e), q({ draft: {
585
+ ...W.draft,
586
+ steps: t
587
+ } });
588
+ }
589
+ var Y = {
590
+ subscribe(e) {
591
+ return G.add(e), () => {
592
+ G.delete(e);
593
+ };
594
+ },
595
+ getState: () => W,
596
+ start() {
597
+ K(), q({
598
+ recording: !0,
599
+ draft: U()
600
+ });
601
+ let t = -Infinity, a = (a) => {
602
+ if (!a.isTrusted || !(a.target instanceof Element) || a.target.closest(n) || a instanceof MouseEvent && a.type === "click" && a.detail === 0 && a.timeStamp - t < 500) return;
603
+ let o = a.target.closest(e);
604
+ if (!o) return;
605
+ if (i(o)) {
606
+ if (a.type === "input") {
607
+ let e = W.draft.steps.at(-1);
608
+ (e?.kind !== "unsupported" || e.value !== "敏感字段输入未录制") && J({
609
+ kind: "unsupported",
610
+ value: "敏感字段输入未录制"
611
+ });
612
+ }
613
+ return;
614
+ }
615
+ let s = r(o);
616
+ if (o instanceof HTMLInputElement && [
617
+ "file",
618
+ "date",
619
+ "datetime-local",
620
+ "range",
621
+ "color"
622
+ ].includes(o.type)) {
623
+ a.type === "change" && J({
624
+ kind: "unsupported",
625
+ value: `${s.label}:该控件需要手动适配`
626
+ });
627
+ return;
628
+ }
629
+ if (a.type === "input" && (o instanceof HTMLInputElement || o instanceof HTMLTextAreaElement)) ["checkbox", "radio"].includes(o.type) || J({
630
+ kind: "fill",
631
+ target: s,
632
+ value: o.value
633
+ });
634
+ else if (a.type === "change" && o instanceof HTMLSelectElement) J({
635
+ kind: "select",
636
+ target: s,
637
+ value: o.value
638
+ });
639
+ else if (a.type === "change" && o instanceof HTMLInputElement && ["checkbox", "radio"].includes(o.type)) J({
640
+ kind: "check",
641
+ target: s,
642
+ checked: o.checked
643
+ });
644
+ else if (a.type === "click" && !(o instanceof HTMLSelectElement) && !(o instanceof HTMLInputElement && ["checkbox", "radio"].includes(o.type))) J({
645
+ kind: "click",
646
+ target: s
647
+ });
648
+ else if (a instanceof KeyboardEvent && [
649
+ "Enter",
650
+ "Escape",
651
+ "Tab"
652
+ ].includes(a.key)) {
653
+ if (a.key === "Enter" && o.matches("button,[role=\"button\"]")) return;
654
+ a.key === "Enter" && (t = a.timeStamp), J({
655
+ kind: "press",
656
+ target: s,
657
+ value: a.key
658
+ });
659
+ }
660
+ };
661
+ for (let e of [
662
+ "click",
663
+ "input",
664
+ "change",
665
+ "keydown"
666
+ ]) document.addEventListener(e, a, !0);
667
+ K = () => {
668
+ for (let e of [
669
+ "click",
670
+ "input",
671
+ "change",
672
+ "keydown"
673
+ ]) document.removeEventListener(e, a, !0);
674
+ };
675
+ },
676
+ stop() {
677
+ K(), q({ recording: !1 });
678
+ },
679
+ edit(e) {
680
+ q({ draft: e });
681
+ },
682
+ loadDraft(e) {
683
+ if (W.recording) throw Error("请先结束主动录制,再生成时间线草稿");
684
+ q({ draft: structuredClone(e) });
685
+ },
686
+ checkpoint(e) {
687
+ J({
688
+ kind: "assert-text",
689
+ value: e
690
+ });
691
+ },
692
+ save(e) {
693
+ q({
694
+ saved: structuredClone(e),
695
+ draft: e
696
+ });
697
+ },
698
+ clear() {
699
+ K(), q({
700
+ recording: !1,
701
+ draft: U(),
702
+ saved: null
703
+ });
704
+ }
705
+ };
706
+ //#endregion
707
+ export { N as a, F as c, d, f, I as i, z as l, s as m, H as n, M as o, o as p, V as r, P as s, Y as t, p as u };
708
+
709
+ //# sourceMappingURL=recorder-Da1VSLid.js.map