@skrillex1224/playwright-toolkit 2.1.210 → 2.1.212

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/dist/index.js CHANGED
@@ -323,18 +323,18 @@ var fallbackLog = {
323
323
  error: (...args) => console.error(...args),
324
324
  debug: (...args) => console.debug ? console.debug(...args) : console.log(...args)
325
325
  };
326
- var resolveLogMethod = (logger12, name) => {
327
- if (logger12 && typeof logger12[name] === "function") {
328
- return logger12[name].bind(logger12);
326
+ var resolveLogMethod = (logger13, name) => {
327
+ if (logger13 && typeof logger13[name] === "function") {
328
+ return logger13[name].bind(logger13);
329
329
  }
330
- if (name === "warning" && logger12 && typeof logger12.warn === "function") {
331
- return logger12.warn.bind(logger12);
330
+ if (name === "warning" && logger13 && typeof logger13.warn === "function") {
331
+ return logger13.warn.bind(logger13);
332
332
  }
333
333
  return fallbackLog[name];
334
334
  };
335
335
  var defaultLogger = null;
336
- var setDefaultLogger = (logger12) => {
337
- defaultLogger = logger12;
336
+ var setDefaultLogger = (logger13) => {
337
+ defaultLogger = logger13;
338
338
  };
339
339
  var resolveLogger = (explicitLogger) => {
340
340
  if (explicitLogger && typeof explicitLogger.info === "function") {
@@ -361,8 +361,8 @@ var colorize = (text, color) => {
361
361
  var createBaseLogger = (prefix = "", explicitLogger) => {
362
362
  const name = prefix ? String(prefix) : "";
363
363
  const dispatch = (methodName, icon, message, color) => {
364
- const logger12 = resolveLogger(explicitLogger);
365
- const logFn = resolveLogMethod(logger12, methodName);
364
+ const logger13 = resolveLogger(explicitLogger);
365
+ const logFn = resolveLogMethod(logger13, methodName);
366
366
  const timestamp = colorize(`[${formatTimestamp()}]`, ANSI.gray);
367
367
  const line = formatLine(name, icon, message);
368
368
  const coloredLine = colorize(line, color);
@@ -408,6 +408,33 @@ function createInternalLogger(moduleName, explicitLogger) {
408
408
  };
409
409
  }
410
410
 
411
+ // src/internals/viewport.js
412
+ var toPositiveInt = (value) => {
413
+ const number = Math.round(Number(value) || 0);
414
+ return number > 0 ? number : 0;
415
+ };
416
+ var resolveCurrentViewportSize = async (page, fallback = {}) => {
417
+ const directViewport = page?.viewportSize?.();
418
+ const directWidth = toPositiveInt(directViewport?.width);
419
+ const directHeight = toPositiveInt(directViewport?.height);
420
+ if (directWidth && directHeight) {
421
+ return {
422
+ width: directWidth,
423
+ height: directHeight
424
+ };
425
+ }
426
+ const measuredViewport = await page?.evaluate?.(() => ({
427
+ width: window.innerWidth || document.documentElement?.clientWidth || document.body?.clientWidth || 0,
428
+ height: window.innerHeight || document.documentElement?.clientHeight || document.body?.clientHeight || 0
429
+ })).catch(() => null);
430
+ const fallbackWidth = toPositiveInt(fallback?.width);
431
+ const fallbackHeight = toPositiveInt(fallback?.height);
432
+ return {
433
+ width: toPositiveInt(measuredViewport?.width) || fallbackWidth || 1,
434
+ height: toPositiveInt(measuredViewport?.height) || fallbackHeight || 1
435
+ };
436
+ };
437
+
411
438
  // src/internals/screenshot.js
412
439
  var logger = createInternalLogger("Screenshot");
413
440
  var DEFAULT_TIMEOUT_MS = 5e3;
@@ -434,7 +461,7 @@ var normalizeQuality = (value, type) => {
434
461
  };
435
462
  var buildFullPageClip = (metrics, viewport, maxClipHeight) => {
436
463
  const contentSize = metrics && typeof metrics === "object" ? metrics.contentSize || null : null;
437
- const width = Math.max(1, Math.ceil(contentSize?.width || viewport.width || 1));
464
+ const width = Math.max(1, Math.ceil(viewport.width || contentSize?.width || 1));
438
465
  let height = Math.max(1, Math.ceil(contentSize?.height || viewport.height || 1));
439
466
  if (maxClipHeight > 0) {
440
467
  height = Math.min(height, maxClipHeight);
@@ -471,7 +498,7 @@ var capturePageScreenshot = async (page, options = {}) => {
471
498
  const session = await context.newCDPSession(page);
472
499
  try {
473
500
  const metrics = await session.send("Page.getLayoutMetrics");
474
- const viewport = page.viewportSize() || { width: 1280, height: 720 };
501
+ const viewport = await resolveCurrentViewportSize(page);
475
502
  const captureParams = {
476
503
  format: type,
477
504
  fromSurface: true,
@@ -924,6 +951,9 @@ var ProxyMeterRuntime = {
924
951
  getProxyMeterSnapshot
925
952
  };
926
953
 
954
+ // src/internals/constants.js
955
+ var PageRuntimeStateKey = "__playwright_toolkit_runtime_state__";
956
+
927
957
  // src/runtime-env.js
928
958
  var BROWSER_PROFILE_SCHEMA_VERSION = 1;
929
959
  var rememberedRuntimeState = null;
@@ -1430,6 +1460,7 @@ var RuntimeEnv = {
1430
1460
  parseInput(input = {}, actor = "") {
1431
1461
  const runtime2 = tryParseJSON(input?.runtime) || {};
1432
1462
  const resolvedActor = String(actor || input?.actor || "").trim();
1463
+ const query = String(input?.query || "").trim();
1433
1464
  const cookies = normalizeCookies(runtime2?.cookies);
1434
1465
  const cookieMap = buildCookieMap(cookies);
1435
1466
  const localStorage = normalizeLocalStorage(runtime2?.local_storage);
@@ -1453,6 +1484,7 @@ var RuntimeEnv = {
1453
1484
  actor: resolvedActor,
1454
1485
  runtime: normalizedRuntime,
1455
1486
  envId,
1487
+ query,
1456
1488
  auth,
1457
1489
  cookies,
1458
1490
  cookieMap,
@@ -1520,6 +1552,12 @@ var RuntimeEnv = {
1520
1552
  async applyToPage(page, source = {}, options = {}) {
1521
1553
  if (!page) return;
1522
1554
  const state = normalizeRuntimeState(source, options?.actor || "");
1555
+ Object.defineProperty(page, PageRuntimeStateKey, {
1556
+ configurable: true,
1557
+ enumerable: false,
1558
+ writable: true,
1559
+ value: state
1560
+ });
1523
1561
  const localStorage = state.localStorage || {};
1524
1562
  const sessionStorage = state.sessionStorage || {};
1525
1563
  const cookies = (state.cookies || []).map((cookie) => {
@@ -4503,7 +4541,7 @@ var createTemplateLogger = (baseLogger = createBaseLogger()) => {
4503
4541
  };
4504
4542
  var getDefaultBaseLogger = () => createBaseLogger("");
4505
4543
  var Logger = {
4506
- setLogger: (logger12) => setDefaultLogger(logger12),
4544
+ setLogger: (logger13) => setDefaultLogger(logger13),
4507
4545
  info: (message) => getDefaultBaseLogger().info(message),
4508
4546
  success: (message) => getDefaultBaseLogger().success(message),
4509
4547
  warning: (message) => getDefaultBaseLogger().warning(message),
@@ -4511,8 +4549,8 @@ var Logger = {
4511
4549
  error: (message) => getDefaultBaseLogger().error(message),
4512
4550
  debug: (message) => getDefaultBaseLogger().debug(message),
4513
4551
  start: (message) => getDefaultBaseLogger().start(message),
4514
- useTemplate: (logger12) => {
4515
- if (logger12) return createTemplateLogger(createBaseLogger("", logger12));
4552
+ useTemplate: (logger13) => {
4553
+ if (logger13) return createTemplateLogger(createBaseLogger("", logger13));
4516
4554
  return createTemplateLogger();
4517
4555
  }
4518
4556
  };
@@ -4530,13 +4568,28 @@ var DEFAULT_WATERMARK_ROTATE_DEG = -16;
4530
4568
  var DEFAULT_WATERMARK_CELL_WIDTH = 860;
4531
4569
  var DEFAULT_WATERMARK_CELL_HEIGHT = 330;
4532
4570
  var DEFAULT_STRIP_LOGO_URL = "https://static.heartbitai.com/geo/icon/favicon.png";
4533
- var DEFAULT_IP_LOOKUP_URL = "http://myip.ipip.net";
4571
+ var DEFAULT_IP_LOOKUP_URL = "https://myip.ipip.net/json";
4534
4572
  var DEFAULT_LOGO_FETCH_TIMEOUT_MS = 2500;
4535
- var DEFAULT_STRIP_HEIGHT = 78;
4536
- var DEFAULT_STRIP_PADDING_LEFT = 20;
4537
- var DEFAULT_STRIP_PADDING_RIGHT = 22;
4538
- var DEFAULT_STRIP_GAP = 16;
4573
+ var DEFAULT_STRIP_ONE_LINE_HEIGHT = 78;
4574
+ var DEFAULT_STRIP_WRAPPED_MIN_HEIGHT = 108;
4575
+ var DEFAULT_STRIP_PADDING_LEFT = 22;
4576
+ var DEFAULT_STRIP_PADDING_RIGHT = 28;
4577
+ var DEFAULT_STRIP_GAP = 18;
4539
4578
  var DEFAULT_STRIP_LABEL_WIDTH = 56;
4579
+ var DEFAULT_STRIP_PROMPT_MAX_SAFE_CHARS = 180;
4580
+ var DEFAULT_STRIP_PROMPT_MIN_TOTAL_WIDTH = 320;
4581
+ var DEFAULT_STRIP_PROMPT_PREFERRED_WIDTH_RATIO = 0.58;
4582
+ var DEFAULT_STRIP_ZONE_GAP = 18;
4583
+ var DEFAULT_STRIP_PROMPT_TRAILING_PADDING = 16;
4584
+ var DEFAULT_STRIP_LABEL_FONT_SIZE = 14.5;
4585
+ var DEFAULT_STRIP_VALUE_FONT_SIZE = 15.5;
4586
+ var DEFAULT_STRIP_VALUE_PADDING = 10;
4587
+ var DEFAULT_STRIP_LABEL_PADDING = 14;
4588
+ var DEFAULT_STRIP_SECTION_GAP = DEFAULT_STRIP_GAP + 16;
4589
+ var DEFAULT_STRIP_LINE_HEIGHT_RATIO = 1.28;
4590
+ var DEFAULT_STRIP_PROMPT_MAX_LINES = 3;
4591
+ var DEFAULT_STRIP_CONTENT_SAFE_PADDING_X = 12;
4592
+ var DEFAULT_STRIP_TEXT_WIDTH_SAFETY = 10;
4540
4593
  var WEAK_LOCATION_VALUES = /* @__PURE__ */ new Set(["cn", "\u4E2D\u56FD"]);
4541
4594
  var LOCATION_NETWORK_SUFFIX_PATTERNS = [
4542
4595
  /(?:中国)?移动$/i,
@@ -4561,6 +4614,8 @@ var LOCATION_NETWORK_SUFFIX_PATTERNS = [
4561
4614
  /digitalocean$/i
4562
4615
  ];
4563
4616
  var cachedStripLogoSrcPromise = null;
4617
+ var cachedEnrichmentByContext = /* @__PURE__ */ new WeakMap();
4618
+ var logger11 = createInternalLogger("Watermarkify");
4564
4619
  var normalizeText = (value) => String(value || "").trim();
4565
4620
  var toInline = (value, maxLen = 200) => {
4566
4621
  const text = normalizeText(value);
@@ -4572,6 +4627,15 @@ var shortenTail = (value, maxLen = 80) => {
4572
4627
  if (!text || text.length <= maxLen) return text;
4573
4628
  return `${text.slice(0, Math.max(0, maxLen - 1)).trimEnd()}\u2026`;
4574
4629
  };
4630
+ var shortenByCharacters = (value, maxChars = 80) => {
4631
+ const text = normalizeWhitespace(value);
4632
+ if (!text) return "";
4633
+ const chars = Array.from(text);
4634
+ if (chars.length <= maxChars) {
4635
+ return text;
4636
+ }
4637
+ return `${chars.slice(0, Math.max(0, maxChars - 1)).join("").trimEnd()}\u2026`;
4638
+ };
4575
4639
  var padDatePart = (value) => String(value).padStart(2, "0");
4576
4640
  var formatUtcOffsetLabel = (offsetHours = DEFAULT_TIMEZONE_OFFSET) => {
4577
4641
  const sign = offsetHours >= 0 ? "+" : "-";
@@ -4619,18 +4683,26 @@ var pickHeaderValue = async (response, names = []) => {
4619
4683
  }
4620
4684
  return "";
4621
4685
  };
4622
- var parseIpIpResponse = (rawText) => {
4686
+ var parseIpIpJsonResponse = (rawText) => {
4623
4687
  const text = normalizeWhitespace(rawText);
4624
- if (!text) return null;
4625
- const ipMatch = text.match(/当前\s*IP[::]\s*([^\s]+)/i) || text.match(/\b((?:\d{1,3}\.){3}\d{1,3}|(?:[0-9a-f]{0,4}:){2,}[0-9a-f]{0,4})\b/i);
4626
- const locationMatch = text.match(/来自于[::]?\s*(.+)$/i);
4627
- const ip = toInline(ipMatch?.[1], 80);
4628
- const rawLocation = toInline(locationMatch?.[1], 120);
4629
- const location = toInline(stripLocationNetworkSuffix(rawLocation), 80) || rawLocation;
4630
- if (!ip && !location) {
4688
+ if (!text || !text.startsWith("{") && !text.startsWith("[")) {
4689
+ return null;
4690
+ }
4691
+ try {
4692
+ const payload = JSON.parse(text);
4693
+ const ip = toInline(payload?.data?.ip || payload?.ip, 80);
4694
+ const locationParts = Array.isArray(payload?.data?.location) ? payload.data.location.map((item) => normalizeWhitespace(item)).filter(Boolean) : [];
4695
+ const location = toInline(
4696
+ stripLocationNetworkSuffix(locationParts.join(" ")),
4697
+ 80
4698
+ );
4699
+ if (!ip && !location) {
4700
+ return null;
4701
+ }
4702
+ return { ip, location };
4703
+ } catch {
4631
4704
  return null;
4632
4705
  }
4633
- return { ip, location };
4634
4706
  };
4635
4707
  var fillEnrichment = (target, source) => {
4636
4708
  if (!target || !source || typeof source !== "object") {
@@ -4644,6 +4716,29 @@ var fillEnrichment = (target, source) => {
4644
4716
  }
4645
4717
  return target;
4646
4718
  };
4719
+ var readCachedEnrichment = (page) => {
4720
+ const context = page && typeof page.context === "function" ? page.context() : null;
4721
+ if (!context) {
4722
+ return null;
4723
+ }
4724
+ const cached = cachedEnrichmentByContext.get(context);
4725
+ return cached && typeof cached === "object" ? cached : null;
4726
+ };
4727
+ var writeCachedEnrichment = (page, source) => {
4728
+ const context = page && typeof page.context === "function" ? page.context() : null;
4729
+ if (!context || !source || typeof source !== "object") {
4730
+ return;
4731
+ }
4732
+ const current = cachedEnrichmentByContext.get(context) || {};
4733
+ const next = {
4734
+ ip: toInline(source.ip || current.ip, 80),
4735
+ location: toInline(source.location || current.location, 80)
4736
+ };
4737
+ if (!next.ip && !next.location) {
4738
+ return;
4739
+ }
4740
+ cachedEnrichmentByContext.set(context, next);
4741
+ };
4647
4742
  var getHostname = (url) => {
4648
4743
  try {
4649
4744
  const parsed = new URL(url);
@@ -4757,10 +4852,16 @@ var resolveWithCustomResolver = async (page, baseMeta, options = {}) => {
4757
4852
  if (!resolved || typeof resolved !== "object") {
4758
4853
  return null;
4759
4854
  }
4760
- return {
4855
+ const enrichment = {
4761
4856
  ip: toInline(resolved.ip, 80),
4762
4857
  location: toInline(resolved.location, 80)
4763
4858
  };
4859
+ if (enrichment.ip || enrichment.location) {
4860
+ logger11.info(`\u81EA\u5B9A\u4E49 resolver \u547D\u4E2D: ip=${enrichment.ip || "-"}, loc=${enrichment.location || "-"}`);
4861
+ } else {
4862
+ logger11.warning("\u81EA\u5B9A\u4E49 resolver \u5DF2\u6267\u884C\uFF0C\u4F46\u672A\u8FD4\u56DE IP/Loc");
4863
+ }
4864
+ return enrichment;
4764
4865
  } finally {
4765
4866
  controller?.abort();
4766
4867
  }
@@ -4773,44 +4874,43 @@ var openProbePage = async (page) => {
4773
4874
  if (!context) {
4774
4875
  return null;
4775
4876
  }
4877
+ const browser = typeof context.browser === "function" ? context.browser() : null;
4878
+ if (browser && typeof browser.newContext === "function") {
4879
+ const probeContext = await browser.newContext().catch(() => null);
4880
+ if (probeContext && typeof probeContext.newPage === "function") {
4881
+ const probePage = await probeContext.newPage().catch(async () => {
4882
+ await probeContext.close().catch(() => {
4883
+ });
4884
+ return null;
4885
+ });
4886
+ if (probePage) {
4887
+ return {
4888
+ page: probePage,
4889
+ close: async () => {
4890
+ await probeContext.close().catch(() => {
4891
+ });
4892
+ }
4893
+ };
4894
+ }
4895
+ } else {
4896
+ await probeContext?.close?.().catch(() => {
4897
+ });
4898
+ }
4899
+ }
4776
4900
  if (typeof context.newPage === "function") {
4777
4901
  try {
4778
- const probePage2 = await context.newPage();
4902
+ const probePage = await context.newPage();
4779
4903
  return {
4780
- page: probePage2,
4904
+ page: probePage,
4781
4905
  close: async () => {
4782
- await probePage2.close().catch(() => {
4906
+ await probePage.close().catch(() => {
4783
4907
  });
4784
4908
  }
4785
4909
  };
4786
4910
  } catch {
4787
4911
  }
4788
4912
  }
4789
- const browser = typeof context.browser === "function" ? context.browser() : null;
4790
- if (!browser || typeof browser.newContext !== "function") {
4791
- return null;
4792
- }
4793
- const probeContext = await browser.newContext().catch(() => null);
4794
- if (!probeContext || typeof probeContext.newPage !== "function") {
4795
- await probeContext?.close?.().catch(() => {
4796
- });
4797
- return null;
4798
- }
4799
- const probePage = await probeContext.newPage().catch(async () => {
4800
- await probeContext.close().catch(() => {
4801
- });
4802
- return null;
4803
- });
4804
- if (!probePage) {
4805
- return null;
4806
- }
4807
- return {
4808
- page: probePage,
4809
- close: async () => {
4810
- await probeContext.close().catch(() => {
4811
- });
4812
- }
4813
- };
4913
+ return null;
4814
4914
  };
4815
4915
  var resolveWithIpLookup = async (page, options = {}) => {
4816
4916
  if (!page || typeof page.context !== "function" || options.ipLookup === false) {
@@ -4818,6 +4918,7 @@ var resolveWithIpLookup = async (page, options = {}) => {
4818
4918
  }
4819
4919
  const probeScope = await openProbePage(page);
4820
4920
  if (!probeScope?.page) {
4921
+ logger11.warning("ipLookup \u8DF3\u8FC7: \u65E0\u6CD5\u521B\u5EFA probe page");
4821
4922
  return null;
4822
4923
  }
4823
4924
  const timeoutMs = Math.max(
@@ -4826,10 +4927,16 @@ var resolveWithIpLookup = async (page, options = {}) => {
4826
4927
  );
4827
4928
  try {
4828
4929
  const probePage = probeScope.page;
4930
+ logger11.info(`ipLookup \u5C1D\u8BD5: url=${DEFAULT_IP_LOOKUP_URL}, timeoutMs=${timeoutMs}`);
4829
4931
  const response = await probePage.goto(DEFAULT_IP_LOOKUP_URL, {
4830
4932
  waitUntil: "commit",
4831
4933
  timeout: timeoutMs
4832
- }).catch(() => null);
4934
+ }).catch((error) => {
4935
+ logger11.warning(`ipLookup \u8BF7\u6C42\u5931\u8D25: url=${DEFAULT_IP_LOOKUP_URL}, error=${error instanceof Error ? error.message : String(error)}`);
4936
+ return null;
4937
+ });
4938
+ const status = response && typeof response.status === "function" ? response.status() : 0;
4939
+ const contentType = normalizeText(await pickHeaderValue(response, ["content-type"]));
4833
4940
  let rawText = "";
4834
4941
  if (response && typeof response.text === "function") {
4835
4942
  rawText = String(await withTimeout(
@@ -4845,8 +4952,15 @@ var resolveWithIpLookup = async (page, options = {}) => {
4845
4952
  Math.min(timeoutMs, 500)
4846
4953
  ) || "");
4847
4954
  }
4848
- return parseIpIpResponse(rawText);
4849
- } catch {
4955
+ const parsed = parseIpIpJsonResponse(rawText);
4956
+ if (parsed?.ip || parsed?.location) {
4957
+ logger11.info(`ipLookup \u6210\u529F: url=${DEFAULT_IP_LOOKUP_URL}, status=${status || "-"}, contentType=${contentType || "-"}, ip=${parsed.ip || "-"}, loc=${parsed.location || "-"}`);
4958
+ return parsed;
4959
+ }
4960
+ logger11.warning(`ipLookup \u672A\u89E3\u6790\u51FA IP/Loc: url=${DEFAULT_IP_LOOKUP_URL}, status=${status || "-"}, contentType=${contentType || "-"}, preview=${shortenTail(rawText, 120) || "[empty]"}`);
4961
+ return null;
4962
+ } catch (error) {
4963
+ logger11.warning(`ipLookup \u6267\u884C\u5F02\u5E38\uFF0C\u672A\u83B7\u5F97 IP/Loc: ${error instanceof Error ? error.message : String(error)}`);
4850
4964
  return null;
4851
4965
  } finally {
4852
4966
  await probeScope.close().catch(() => {
@@ -4855,10 +4969,18 @@ var resolveWithIpLookup = async (page, options = {}) => {
4855
4969
  };
4856
4970
  var resolveEnrichment = async (page, baseMeta, options) => {
4857
4971
  const response = options.response && typeof options.response === "object" ? options.response : null;
4972
+ const cached = readCachedEnrichment(page);
4858
4973
  const merged = {
4859
4974
  ip: toInline(options.ip, 80),
4860
4975
  location: toInline(options.location, 80)
4861
4976
  };
4977
+ logger11.info(`enrichment \u5F00\u59CB: host=${baseMeta.hostname || "-"}, hasPresetIp=${Boolean(merged.ip)}, hasPresetLoc=${Boolean(merged.location)}, ipLookup=${options.ipLookup !== false}`);
4978
+ if (!merged.ip || !merged.location) {
4979
+ if (cached?.ip || cached?.location) {
4980
+ logger11.info(`enrichment \u547D\u4E2D\u4E0A\u4E0B\u6587\u7F13\u5B58: ip=${cached.ip || "-"}, loc=${cached.location || "-"}`);
4981
+ }
4982
+ fillEnrichment(merged, cached);
4983
+ }
4862
4984
  if (!merged.ip || !merged.location) {
4863
4985
  fillEnrichment(
4864
4986
  merged,
@@ -4879,9 +5001,16 @@ var resolveEnrichment = async (page, baseMeta, options) => {
4879
5001
  "x-geo-country"
4880
5002
  ]), 80);
4881
5003
  if (!merged.location || isWeakLocationValue(merged.location) && headerLocation) {
5004
+ logger11.info(`enrichment \u4F7F\u7528\u54CD\u5E94\u5934\u8865\u5145 Loc: ${headerLocation || "-"}`);
4882
5005
  merged.location = headerLocation || merged.location;
4883
5006
  }
4884
5007
  }
5008
+ writeCachedEnrichment(page, merged);
5009
+ if (merged.ip || merged.location) {
5010
+ logger11.info(`enrichment \u5B8C\u6210: ip=${merged.ip || "-"}, loc=${merged.location || "-"}`);
5011
+ } else {
5012
+ logger11.warning("enrichment \u5B8C\u6210: \u672A\u83B7\u5F97 IP/Loc");
5013
+ }
4885
5014
  return merged;
4886
5015
  };
4887
5016
  var buildWatermarkStamp = ({ prompt, captureTime, ip, location }) => {
@@ -4894,7 +5023,12 @@ var buildWatermarkStamp = ({ prompt, captureTime, ip, location }) => {
4894
5023
  return parts.join(" | ");
4895
5024
  };
4896
5025
  var buildStripSegments = ({ prompt, captureTime, ip, location }) => {
4897
- const promptValue = toInline(prompt, 240) || "-";
5026
+ const promptValue = shortenByCharacters(
5027
+ normalizeWhitespace(prompt) || "-",
5028
+ DEFAULT_STRIP_PROMPT_MAX_SAFE_CHARS
5029
+ ) || "-";
5030
+ const ipValue = normalizeWhitespace(ip) || "-";
5031
+ const locationValue = normalizeWhitespace(location) || "-";
4898
5032
  return [
4899
5033
  {
4900
5034
  kind: "prompt",
@@ -4911,14 +5045,14 @@ var buildStripSegments = ({ prompt, captureTime, ip, location }) => {
4911
5045
  {
4912
5046
  kind: "location",
4913
5047
  label: "Loc",
4914
- value: shortenTail(location, 24) || "-",
4915
- rawValue: toInline(location, 80) || "-"
5048
+ value: locationValue,
5049
+ rawValue: locationValue
4916
5050
  },
4917
5051
  {
4918
5052
  kind: "ip",
4919
5053
  label: "IP",
4920
- value: toInline(ip, 28) || "-",
4921
- rawValue: toInline(ip, 80) || "-"
5054
+ value: ipValue,
5055
+ rawValue: ipValue
4922
5056
  }
4923
5057
  ];
4924
5058
  };
@@ -4939,28 +5073,9 @@ var estimateTextWidth = (value, fontSize = 16) => {
4939
5073
  }
4940
5074
  return Math.ceil(width);
4941
5075
  };
4942
- var fitTextToWidth = (value, maxWidth, fontSize = 16) => {
4943
- const text = normalizeWhitespace(value);
4944
- if (!text) return "-";
4945
- const safeMaxWidth = Math.max(fontSize * 3, Number(maxWidth) || 0);
4946
- if (estimateTextWidth(text, fontSize) <= safeMaxWidth) {
4947
- return text;
4948
- }
4949
- const ellipsis = "\u2026";
4950
- const ellipsisWidth = estimateTextWidth(ellipsis, fontSize);
4951
- let current = "";
4952
- for (const char of text) {
4953
- const next = current + char;
4954
- if (estimateTextWidth(next, fontSize) + ellipsisWidth > safeMaxWidth) {
4955
- break;
4956
- }
4957
- current = next;
4958
- }
4959
- return `${current.trimEnd()}${ellipsis}`;
4960
- };
4961
5076
  var resolvePromptFields = (options = {}, fallbackTitle = "") => {
4962
5077
  const query = normalizeText(options.query);
4963
- const prompt = normalizeText(options.prompt) || query || normalizeText(fallbackTitle) || "\u672A\u63D0\u4F9B Prompt";
5078
+ const prompt = query || normalizeText(options.prompt) || normalizeText(fallbackTitle) || "\u672A\u63D0\u4F9B Prompt";
4964
5079
  return {
4965
5080
  prompt,
4966
5081
  query: query || prompt
@@ -5016,8 +5131,8 @@ var normalizeScreenshotWatermarkify = (value) => {
5016
5131
  response: source.response ?? null,
5017
5132
  ip: normalizeText(source.ip),
5018
5133
  location: normalizeText(source.location),
5019
- prompt: toInline(source.prompt, 220),
5020
- query: toInline(source.query, 220),
5134
+ prompt: normalizeWhitespace(source.prompt),
5135
+ query: normalizeWhitespace(source.query),
5021
5136
  ipLookup: source.ipLookup !== false,
5022
5137
  ipLookupTimeoutMs: Number.isFinite(ipLookupTimeoutMsRaw) ? ipLookupTimeoutMsRaw : DEFAULT_IP_LOOKUP_TIMEOUT_MS,
5023
5138
  resolver: typeof source.resolver === "function" ? source.resolver : null,
@@ -5070,53 +5185,332 @@ var resolveScreenshotWatermarkifyMeta = async (page, options = {}) => {
5070
5185
  };
5071
5186
  };
5072
5187
  var buildFontFamily = () => 'MiSans, "SF Pro Display", "PingFang SC", "Helvetica Neue", Arial, sans-serif';
5073
- var buildStripSegmentLayouts = (segments, contentWidth) => {
5188
+ var buildStripSegmentLayout = (segment, options = {}) => {
5189
+ const label = normalizeWhitespace(segment?.label || "");
5190
+ const rawValue = normalizeWhitespace(segment?.rawValue || segment?.value || "-") || "-";
5191
+ const labelFontSize = DEFAULT_STRIP_LABEL_FONT_SIZE;
5192
+ const valueFontSize = DEFAULT_STRIP_VALUE_FONT_SIZE;
5193
+ const labelWidth = Math.max(
5194
+ DEFAULT_STRIP_LABEL_WIDTH,
5195
+ estimateTextWidth(label, labelFontSize) + DEFAULT_STRIP_LABEL_PADDING
5196
+ );
5197
+ const rawValueWidth = Math.max(
5198
+ 24,
5199
+ estimateTextWidth(rawValue, valueFontSize) + DEFAULT_STRIP_VALUE_PADDING
5200
+ );
5201
+ const maxValueWidth = Math.max(0, Number(options.valueMaxWidth) || 0);
5202
+ const renderValue = maxValueWidth > 0 ? fitTextWithEllipsis(rawValue, Math.max(18, maxValueWidth - DEFAULT_STRIP_VALUE_PADDING), valueFontSize) : rawValue;
5203
+ const valueWidth = maxValueWidth > 0 ? Math.max(24, Math.min(maxValueWidth, estimateTextWidth(renderValue, valueFontSize) + DEFAULT_STRIP_VALUE_PADDING)) : rawValueWidth;
5204
+ return {
5205
+ ...segment,
5206
+ label,
5207
+ rawValue,
5208
+ renderValue,
5209
+ labelFontSize,
5210
+ valueFontSize,
5211
+ labelWidth,
5212
+ valueWidth,
5213
+ segmentWidth: labelWidth + valueWidth,
5214
+ rowHeight: Math.round(Math.max(labelFontSize, valueFontSize) * 1.42)
5215
+ };
5216
+ };
5217
+ var buildStripRowLayout = (segments, overrides = {}) => {
5074
5218
  const safeSegments = Array.isArray(segments) ? segments : [];
5075
- const sectionGap = DEFAULT_STRIP_GAP + 16;
5076
- const labelWidth = DEFAULT_STRIP_LABEL_WIDTH;
5077
- const promptFontSize = 15;
5078
- const otherFontSizes = {
5079
- time: 17,
5080
- location: 17,
5081
- ip: 17
5219
+ const sectionGap = DEFAULT_STRIP_SECTION_GAP;
5220
+ const layouts = safeSegments.map((segment, index) => buildStripSegmentLayout(segment, {
5221
+ valueMaxWidth: overrides?.valueMaxWidths?.[index]
5222
+ }));
5223
+ const totalWidth = layouts.reduce((sum, segment) => sum + segment.segmentWidth, 0) + Math.max(0, layouts.length - 1) * sectionGap;
5224
+ const rowHeight = layouts.reduce((max, segment) => Math.max(max, segment.rowHeight || 0), 0);
5225
+ return {
5226
+ sectionGap,
5227
+ layouts,
5228
+ totalWidth,
5229
+ rowHeight
5082
5230
  };
5083
- const maxValueWidths = {
5084
- time: 290,
5085
- location: 220,
5086
- ip: 160
5231
+ };
5232
+ var fitTextWithEllipsis = (chars, maxWidth, fontSize) => {
5233
+ const safeChars = Array.isArray(chars) ? chars.slice() : Array.from(String(chars || ""));
5234
+ if (!safeChars.length) {
5235
+ return "-";
5236
+ }
5237
+ if (estimateTextWidth(safeChars.join(""), fontSize) <= maxWidth) {
5238
+ return safeChars.join("").trim() || "-";
5239
+ }
5240
+ while (safeChars.length > 1 && estimateTextWidth(`${safeChars.join("").trimEnd()}\u2026`, fontSize) > maxWidth) {
5241
+ safeChars.pop();
5242
+ }
5243
+ return `${safeChars.join("").trimEnd()}\u2026` || "\u2026";
5244
+ };
5245
+ var splitTextToLines = (value, maxWidth, fontSize, maxLines = 2) => {
5246
+ const chars = Array.from(normalizeWhitespace(value) || "-");
5247
+ const safeMaxWidth = Math.max(18, maxWidth - DEFAULT_STRIP_TEXT_WIDTH_SAFETY);
5248
+ if (!chars.length) {
5249
+ return { lines: ["-"], truncated: false };
5250
+ }
5251
+ const lines = [];
5252
+ let cursor = 0;
5253
+ let truncated = false;
5254
+ while (cursor < chars.length && lines.length < maxLines) {
5255
+ while (cursor < chars.length && chars[cursor] === " ") {
5256
+ cursor += 1;
5257
+ }
5258
+ if (cursor >= chars.length) {
5259
+ break;
5260
+ }
5261
+ const isLastLine = lines.length === maxLines - 1;
5262
+ const lineChars = [];
5263
+ while (cursor < chars.length) {
5264
+ const nextChars = [...lineChars, chars[cursor]];
5265
+ const remaining = cursor < chars.length - 1;
5266
+ const previewText = nextChars.join("") + (isLastLine && remaining ? "\u2026" : "");
5267
+ if (estimateTextWidth(previewText, fontSize) <= safeMaxWidth || lineChars.length === 0) {
5268
+ lineChars.push(chars[cursor]);
5269
+ cursor += 1;
5270
+ continue;
5271
+ }
5272
+ break;
5273
+ }
5274
+ while (cursor < chars.length && chars[cursor] === " ") {
5275
+ cursor += 1;
5276
+ }
5277
+ if (isLastLine && cursor < chars.length) {
5278
+ lines.push(fitTextWithEllipsis([...lineChars, ...chars.slice(cursor)], safeMaxWidth, fontSize));
5279
+ truncated = true;
5280
+ cursor = chars.length;
5281
+ continue;
5282
+ }
5283
+ lines.push(lineChars.join("").trim() || "-");
5284
+ }
5285
+ return {
5286
+ lines: lines.length > 0 ? lines : ["-"],
5287
+ truncated
5087
5288
  };
5088
- const promptSegment = safeSegments.find((segment) => segment?.kind === "prompt") || null;
5089
- const fixedSegments = safeSegments.filter((segment) => segment?.kind !== "prompt");
5090
- const fixedLayouts = fixedSegments.map((segment) => {
5091
- const fontSize = otherFontSizes[segment.kind] || 17;
5092
- const maxValueWidth = maxValueWidths[segment.kind] || 220;
5093
- const renderValue = fitTextToWidth(segment.rawValue || segment.value || "-", maxValueWidth, fontSize);
5094
- const valueWidth = Math.min(maxValueWidth, estimateTextWidth(renderValue, fontSize) + 8);
5095
- return {
5096
- ...segment,
5097
- fontSize,
5098
- labelWidth,
5099
- renderValue,
5100
- segmentWidth: labelWidth + valueWidth
5101
- };
5289
+ };
5290
+ var resolveMetadataMinValueWidth = (segment) => {
5291
+ switch (segment?.kind) {
5292
+ case "time":
5293
+ return Math.max(148, estimateTextWidth("2026-04-23 21:27:16 UTC+8", DEFAULT_STRIP_VALUE_FONT_SIZE) + 8);
5294
+ case "ip":
5295
+ return Math.max(108, estimateTextWidth("255.255.255.255", DEFAULT_STRIP_VALUE_FONT_SIZE) + 8);
5296
+ case "location":
5297
+ return 76;
5298
+ default:
5299
+ return 56;
5300
+ }
5301
+ };
5302
+ var buildMetadataRowLayout = (segments, contentWidth) => {
5303
+ const safeSegments = Array.isArray(segments) ? segments.filter(Boolean) : [];
5304
+ const naturalRow = buildStripRowLayout(safeSegments);
5305
+ if (!safeSegments.length || naturalRow.totalWidth <= contentWidth) {
5306
+ return naturalRow;
5307
+ }
5308
+ const sectionGap = naturalRow.sectionGap;
5309
+ const totalLabelWidth = naturalRow.layouts.reduce((sum, segment) => sum + segment.labelWidth, 0);
5310
+ const availableValueWidth = Math.max(
5311
+ 72,
5312
+ Math.floor(contentWidth - totalLabelWidth - Math.max(0, safeSegments.length - 1) * sectionGap)
5313
+ );
5314
+ const minValueWidths = naturalRow.layouts.map((segment) => resolveMetadataMinValueWidth(segment));
5315
+ const naturalValueWidths = naturalRow.layouts.map((segment) => segment.valueWidth);
5316
+ const valueWidths = naturalValueWidths.slice();
5317
+ let overflow = naturalRow.totalWidth - contentWidth;
5318
+ const shrinkOrder = ["location", "ip", "time"];
5319
+ shrinkOrder.forEach((kind) => {
5320
+ if (overflow <= 0) return;
5321
+ naturalRow.layouts.forEach((segment, index) => {
5322
+ if (overflow <= 0 || segment.kind !== kind) return;
5323
+ const minWidth = minValueWidths[index];
5324
+ const reducible = Math.max(0, valueWidths[index] - minWidth);
5325
+ if (!reducible) return;
5326
+ const reduction = Math.min(reducible, overflow);
5327
+ valueWidths[index] -= reduction;
5328
+ overflow -= reduction;
5329
+ });
5102
5330
  });
5103
- const fixedWidth = fixedLayouts.reduce((sum, segment) => sum + segment.segmentWidth, 0);
5104
- const totalGapWidth = Math.max(0, safeSegments.length - 1) * sectionGap;
5105
- const promptValueWidth = Math.max(
5106
- 180,
5107
- Math.floor(contentWidth - fixedWidth - totalGapWidth - labelWidth)
5331
+ if (overflow > 0) {
5332
+ const minTotalWidth = minValueWidths.reduce((sum, width) => sum + width, 0);
5333
+ if (minTotalWidth > availableValueWidth) {
5334
+ const flexibleIndexes = naturalRow.layouts.map((segment, index) => ({ segment, index })).filter(({ segment }) => segment.kind !== "time");
5335
+ let extraOverflow = minTotalWidth - availableValueWidth;
5336
+ flexibleIndexes.forEach(({ index }) => {
5337
+ if (extraOverflow <= 0) return;
5338
+ const minFloor = 40;
5339
+ const reducible = Math.max(0, minValueWidths[index] - minFloor);
5340
+ if (!reducible) return;
5341
+ const reduction = Math.min(reducible, extraOverflow);
5342
+ valueWidths[index] -= reduction;
5343
+ extraOverflow -= reduction;
5344
+ });
5345
+ }
5346
+ }
5347
+ return buildStripRowLayout(safeSegments, {
5348
+ valueMaxWidths: valueWidths
5349
+ });
5350
+ };
5351
+ var buildPromptWrapLayout = (segment, contentWidth) => {
5352
+ const label = normalizeWhitespace(segment?.label || "Prompt") || "Prompt";
5353
+ const rawValue = normalizeWhitespace(segment?.rawValue || segment?.value || "-") || "-";
5354
+ const labelFontSize = DEFAULT_STRIP_LABEL_FONT_SIZE;
5355
+ const valueFontSize = DEFAULT_STRIP_VALUE_FONT_SIZE;
5356
+ const labelWidth = Math.max(
5357
+ DEFAULT_STRIP_LABEL_WIDTH,
5358
+ estimateTextWidth(label, labelFontSize) + DEFAULT_STRIP_LABEL_PADDING
5108
5359
  );
5109
- const promptLayout = promptSegment ? {
5110
- ...promptSegment,
5111
- fontSize: promptFontSize,
5360
+ const safeTotalWidth = Math.max(labelWidth + 56, Number(contentWidth) || 0);
5361
+ const valueWidth = Math.max(
5362
+ 56,
5363
+ safeTotalWidth - labelWidth - DEFAULT_STRIP_CONTENT_SAFE_PADDING_X
5364
+ );
5365
+ const result = splitTextToLines(rawValue, valueWidth, valueFontSize, DEFAULT_STRIP_PROMPT_MAX_LINES);
5366
+ const lineHeight = Math.max(18, Math.round(valueFontSize * DEFAULT_STRIP_LINE_HEIGHT_RATIO));
5367
+ const lineWidths = result.lines.map((line) => estimateTextWidth(line, valueFontSize) + DEFAULT_STRIP_VALUE_PADDING);
5368
+ const measuredValueWidth = Math.min(
5369
+ valueWidth,
5370
+ Math.max(56, ...lineWidths)
5371
+ );
5372
+ const rowHeight = Math.round(Math.max(labelFontSize, valueFontSize) * 1.42);
5373
+ return {
5374
+ mode: result.lines.length === 1 ? "single" : "wrapped",
5375
+ label,
5376
+ rawValue,
5377
+ labelFontSize,
5112
5378
  labelWidth,
5113
- renderValue: fitTextToWidth(promptSegment.rawValue || promptSegment.value || "-", promptValueWidth, promptFontSize),
5114
- segmentWidth: labelWidth + promptValueWidth
5115
- } : null;
5116
- return [
5117
- ...promptLayout ? [promptLayout] : [],
5118
- ...fixedLayouts
5119
- ];
5379
+ valueFontSize,
5380
+ valueWidth,
5381
+ lines: result.lines,
5382
+ truncated: result.truncated,
5383
+ lineHeight,
5384
+ blockHeight: result.lines.length === 1 ? rowHeight : Math.max(lineHeight, result.lines.length * lineHeight),
5385
+ totalWidth: labelWidth + measuredValueWidth
5386
+ };
5387
+ };
5388
+ var buildStripLayout = (segments, contentWidth) => {
5389
+ const safeSegments = Array.isArray(segments) ? segments.filter(Boolean) : [];
5390
+ const promptSegment = safeSegments.find((segment) => segment.kind === "prompt") || safeSegments[0] || null;
5391
+ const metadataSegments = safeSegments.filter((segment) => segment !== promptSegment);
5392
+ const zoneGap = DEFAULT_STRIP_ZONE_GAP;
5393
+ let metadataRow = buildMetadataRowLayout(
5394
+ metadataSegments,
5395
+ Math.max(220, contentWidth)
5396
+ );
5397
+ let metadataStartX = Math.max(0, contentWidth - (metadataRow?.totalWidth || 0));
5398
+ let promptAvailableWidth = Math.max(
5399
+ 220,
5400
+ metadataStartX - zoneGap - DEFAULT_STRIP_PROMPT_TRAILING_PADDING
5401
+ );
5402
+ if (promptAvailableWidth < DEFAULT_STRIP_PROMPT_MIN_TOTAL_WIDTH && metadataSegments.length > 0) {
5403
+ const tighterMetadataWidth = Math.max(180, contentWidth - DEFAULT_STRIP_PROMPT_MIN_TOTAL_WIDTH - zoneGap);
5404
+ metadataRow = buildMetadataRowLayout(metadataSegments, tighterMetadataWidth);
5405
+ metadataStartX = Math.max(0, contentWidth - (metadataRow?.totalWidth || 0));
5406
+ promptAvailableWidth = Math.max(
5407
+ 220,
5408
+ metadataStartX - zoneGap - DEFAULT_STRIP_PROMPT_TRAILING_PADDING
5409
+ );
5410
+ }
5411
+ const preferredPromptWidth = Math.min(
5412
+ promptAvailableWidth,
5413
+ Math.max(280, Math.round(contentWidth * DEFAULT_STRIP_PROMPT_PREFERRED_WIDTH_RATIO))
5414
+ );
5415
+ let promptRow = buildPromptWrapLayout(promptSegment, preferredPromptWidth);
5416
+ if (promptRow?.truncated && promptAvailableWidth > preferredPromptWidth + 24) {
5417
+ promptRow = buildPromptWrapLayout(promptSegment, promptAvailableWidth);
5418
+ }
5419
+ const contentHeight = Math.max(
5420
+ promptRow?.blockHeight || 0,
5421
+ metadataRow?.rowHeight || 0,
5422
+ 24
5423
+ );
5424
+ const verticalPadding = contentHeight > (metadataRow?.rowHeight || 0) ? 18 : 16;
5425
+ const stripHeight = Math.max(
5426
+ contentHeight > (metadataRow?.rowHeight || 0) ? DEFAULT_STRIP_WRAPPED_MIN_HEIGHT : DEFAULT_STRIP_ONE_LINE_HEIGHT,
5427
+ Math.ceil(contentHeight + verticalPadding * 2)
5428
+ );
5429
+ return {
5430
+ mode: "zoned",
5431
+ height: stripHeight,
5432
+ contentHeight,
5433
+ promptRow,
5434
+ promptStartX: 0,
5435
+ metadataRow,
5436
+ metadataStartX
5437
+ };
5438
+ };
5439
+ var renderStripRow = (row, options = {}) => {
5440
+ const startX = Number(options.startX) || 0;
5441
+ const centerY = Number(options.centerY) || 0;
5442
+ const fontFamily = String(options.fontFamily || "");
5443
+ const showDividers = options.showDividers !== false;
5444
+ const layouts = Array.isArray(row?.layouts) ? row.layouts : [];
5445
+ if (!layouts.length) {
5446
+ return "";
5447
+ }
5448
+ const rowHeight = Math.max(18, Number(row?.rowHeight) || 18);
5449
+ const dividerHalfHeight = Math.max(12, Math.round(rowHeight * 0.56));
5450
+ const sectionGap = Math.max(0, Number(row?.sectionGap) || 0);
5451
+ const svgSegments = [];
5452
+ let currentX = startX;
5453
+ layouts.forEach((segment, index) => {
5454
+ const label = escapeXml(segment.label || "");
5455
+ const value = escapeXml(segment.renderValue || "-");
5456
+ const labelX = currentX;
5457
+ const valueX = currentX + segment.labelWidth;
5458
+ svgSegments.push(`
5459
+ <text
5460
+ x="${labelX}"
5461
+ y="${centerY}"
5462
+ fill="#111111"
5463
+ fill-opacity="0.54"
5464
+ font-family="${fontFamily}"
5465
+ font-size="${segment.labelFontSize}"
5466
+ font-weight="650"
5467
+ dominant-baseline="middle"
5468
+ >${label}</text>
5469
+ <text
5470
+ x="${valueX}"
5471
+ y="${centerY}"
5472
+ fill="#111111"
5473
+ font-family="${fontFamily}"
5474
+ font-size="${segment.valueFontSize}"
5475
+ font-weight="${segment.kind === "prompt" ? 760 : 710}"
5476
+ dominant-baseline="middle"
5477
+ >${value}</text>
5478
+ `);
5479
+ currentX += segment.segmentWidth;
5480
+ if (showDividers && index < layouts.length - 1) {
5481
+ const lineX = currentX + Math.round(sectionGap / 2);
5482
+ svgSegments.push(`
5483
+ <line
5484
+ x1="${lineX}"
5485
+ y1="${centerY - dividerHalfHeight}"
5486
+ x2="${lineX}"
5487
+ y2="${centerY + dividerHalfHeight}"
5488
+ stroke="#94a3b8"
5489
+ stroke-opacity="0.34"
5490
+ stroke-width="1"
5491
+ />
5492
+ `);
5493
+ currentX += sectionGap;
5494
+ }
5495
+ });
5496
+ return svgSegments.join("");
5497
+ };
5498
+ var renderStripDivider = (x, centerY, height) => {
5499
+ const safeX = Number(x) || 0;
5500
+ const safeCenterY = Number(centerY) || 0;
5501
+ const safeHeight = Math.max(18, Number(height) || 18);
5502
+ const halfHeight = Math.max(12, Math.round(safeHeight * 0.56));
5503
+ return `
5504
+ <line
5505
+ x1="${safeX}"
5506
+ y1="${safeCenterY - halfHeight}"
5507
+ x2="${safeX}"
5508
+ y2="${safeCenterY + halfHeight}"
5509
+ stroke="#94a3b8"
5510
+ stroke-opacity="0.34"
5511
+ stroke-width="1"
5512
+ />
5513
+ `;
5120
5514
  };
5121
5515
  var buildWatermarkifySvg = (meta, imageWidth, imageHeight) => {
5122
5516
  const hasWatermark = meta?.watermark?.enabled !== false && normalizeText(meta?.watermarkText);
@@ -5166,67 +5560,97 @@ var buildWatermarkifySvg = (meta, imageWidth, imageHeight) => {
5166
5560
  parts.push(`<g id="watermarks">${stamps.join("")}</g>`);
5167
5561
  }
5168
5562
  if (hasStrip) {
5169
- const stripHeight = DEFAULT_STRIP_HEIGHT;
5170
- const stripY = height - stripHeight;
5171
5563
  const logoSize = 28;
5172
5564
  const logoX = DEFAULT_STRIP_PADDING_LEFT;
5173
- const logoY = stripY + Math.round((stripHeight - logoSize) / 2);
5174
5565
  const contentStartX = logoX + logoSize + DEFAULT_STRIP_GAP;
5175
5566
  const contentWidth = width - contentStartX - DEFAULT_STRIP_PADDING_RIGHT;
5567
+ const stripLayout = buildStripLayout(meta.stripSegments, contentWidth);
5568
+ const stripHeight = stripLayout.height;
5569
+ const stripY = height - stripHeight;
5570
+ const logoY = Number((stripY + (stripHeight - logoSize) / 2).toFixed(2));
5571
+ let stripContentSvg = "";
5176
5572
  const centerY = stripY + stripHeight / 2 + 1;
5177
- const layouts = buildStripSegmentLayouts(meta.stripSegments, contentWidth);
5178
- const svgSegments = [];
5179
- let currentX = contentStartX;
5180
- layouts.forEach((segment, index) => {
5181
- const label = escapeXml(segment.label || "");
5182
- const value = escapeXml(segment.renderValue || "-");
5183
- const labelX = currentX;
5184
- const valueX = currentX + segment.labelWidth;
5185
- svgSegments.push(`
5573
+ const promptRow = stripLayout.promptRow;
5574
+ const metadataRow = stripLayout.metadataRow;
5575
+ const promptParts = [];
5576
+ const hasPrompt = Boolean(promptRow?.label);
5577
+ const hasMetadata = Array.isArray(metadataRow?.layouts) && metadataRow.layouts.length > 0;
5578
+ const zoneDividerX = hasPrompt && hasMetadata ? contentStartX + stripLayout.metadataStartX - DEFAULT_STRIP_ZONE_GAP / 2 : null;
5579
+ if (promptRow?.mode === "single") {
5580
+ promptParts.push(renderStripRow({
5581
+ layouts: [
5582
+ {
5583
+ kind: "prompt",
5584
+ label: promptRow.label,
5585
+ renderValue: promptRow.lines[0] || "-",
5586
+ labelFontSize: promptRow.labelFontSize,
5587
+ valueFontSize: promptRow.valueFontSize,
5588
+ labelWidth: promptRow.labelWidth,
5589
+ segmentWidth: promptRow.labelWidth + promptRow.valueWidth
5590
+ }
5591
+ ],
5592
+ sectionGap: 0,
5593
+ rowHeight: promptRow.blockHeight
5594
+ }, {
5595
+ startX: contentStartX + stripLayout.promptStartX,
5596
+ centerY,
5597
+ fontFamily,
5598
+ showDividers: false
5599
+ }));
5600
+ } else if (promptRow) {
5601
+ const promptStartX = contentStartX + stripLayout.promptStartX;
5602
+ const labelX = promptStartX;
5603
+ const valueX = promptStartX + promptRow.labelWidth;
5604
+ const blockTop = centerY - promptRow.blockHeight / 2;
5605
+ const labelY = centerY;
5606
+ promptParts.push(`
5186
5607
  <text
5187
5608
  x="${labelX}"
5188
- y="${centerY}"
5189
- fill="#111111"
5190
- fill-opacity="0.42"
5191
- font-family="${fontFamily}"
5192
- font-size="12"
5193
- font-weight="600"
5194
- dominant-baseline="middle"
5195
- >${label}</text>
5196
- <text
5197
- x="${valueX}"
5198
- y="${centerY}"
5609
+ y="${labelY}"
5199
5610
  fill="#111111"
5611
+ fill-opacity="0.54"
5200
5612
  font-family="${fontFamily}"
5201
- font-size="${segment.fontSize}"
5202
- font-weight="${segment.kind === "prompt" ? 760 : 710}"
5613
+ font-size="${promptRow.labelFontSize}"
5614
+ font-weight="650"
5203
5615
  dominant-baseline="middle"
5204
- >${value}</text>
5616
+ >${escapeXml(promptRow.label)}</text>
5205
5617
  `);
5206
- currentX += segment.segmentWidth;
5207
- if (index < layouts.length - 1) {
5208
- const lineX = currentX + 8;
5209
- svgSegments.push(`
5210
- <line
5211
- x1="${lineX}"
5212
- y1="${stripY + 26}"
5213
- x2="${lineX}"
5214
- y2="${stripY + stripHeight - 26}"
5215
- stroke="#94a3b8"
5216
- stroke-opacity="0.34"
5217
- stroke-width="1"
5218
- />
5618
+ promptRow.lines.forEach((line, index) => {
5619
+ const lineY = blockTop + index * promptRow.lineHeight + promptRow.lineHeight / 2;
5620
+ promptParts.push(`
5621
+ <text
5622
+ x="${valueX}"
5623
+ y="${lineY}"
5624
+ fill="#111111"
5625
+ font-family="${fontFamily}"
5626
+ font-size="${promptRow.valueFontSize}"
5627
+ font-weight="760"
5628
+ dominant-baseline="middle"
5629
+ >${escapeXml(line)}</text>
5219
5630
  `);
5220
- currentX += DEFAULT_STRIP_GAP + 16;
5221
- }
5222
- });
5631
+ });
5632
+ }
5633
+ stripContentSvg = [
5634
+ promptParts.join(""),
5635
+ zoneDividerX == null ? "" : renderStripDivider(
5636
+ zoneDividerX,
5637
+ centerY,
5638
+ Math.max(promptRow?.blockHeight || 0, metadataRow?.rowHeight || 0)
5639
+ ),
5640
+ renderStripRow(metadataRow, {
5641
+ startX: contentStartX + stripLayout.metadataStartX,
5642
+ centerY,
5643
+ fontFamily,
5644
+ showDividers: true
5645
+ })
5646
+ ].join("");
5223
5647
  parts.push(`
5224
5648
  <g id="strip">
5225
5649
  <rect x="0" y="${stripY}" width="${width}" height="${stripHeight}" fill="#ffffff" fill-opacity="0.985" />
5226
5650
  <rect x="0" y="${stripY}" width="${width}" height="1" fill="#111111" fill-opacity="0.10" />
5227
5651
  <rect x="${DEFAULT_STRIP_PADDING_LEFT}" y="${stripY}" width="${Math.max(120, Math.round(width * 0.42))}" height="2" fill="url(#stripAccent)" />
5228
5652
  ${meta.stripLogoSrc ? `<image href="${escapeXml(meta.stripLogoSrc)}" x="${logoX}" y="${logoY}" width="${logoSize}" height="${logoSize}" />` : ""}
5229
- ${svgSegments.join("")}
5653
+ ${stripContentSvg}
5230
5654
  </g>
5231
5655
  `);
5232
5656
  }
@@ -5265,7 +5689,7 @@ var watermarkifyScreenshotBuffer = async (buffer, meta) => {
5265
5689
  };
5266
5690
 
5267
5691
  // src/share.js
5268
- var logger11 = createInternalLogger("Share");
5692
+ var logger12 = createInternalLogger("Share");
5269
5693
  var DEFAULT_TIMEOUT_MS2 = 50 * 1e3;
5270
5694
  var DEFAULT_PAYLOAD_SNAPSHOT_MAX_LEN = 500;
5271
5695
  var DEFAULT_POLL_INTERVAL_MS = 120;
@@ -5342,6 +5766,20 @@ var normalizeShare = (share) => {
5342
5766
  xurl: normalizeXurl(source.xurl)
5343
5767
  };
5344
5768
  };
5769
+ var resolveCaptureScreenWatermarkify = (page, optionValue) => {
5770
+ if (optionValue === false) {
5771
+ return normalizeScreenshotWatermarkify(false);
5772
+ }
5773
+ const runtimeQuery = String(page?.[PageRuntimeStateKey]?.query || "").trim();
5774
+ if (optionValue == null || optionValue === true) {
5775
+ return normalizeScreenshotWatermarkify(runtimeQuery ? { query: runtimeQuery } : true);
5776
+ }
5777
+ const optionSource = optionValue && typeof optionValue === "object" ? optionValue : {};
5778
+ return normalizeScreenshotWatermarkify({
5779
+ ...runtimeQuery ? { query: runtimeQuery } : {},
5780
+ ...optionSource
5781
+ });
5782
+ };
5345
5783
  var getByPathSegments = (source, pathSegments) => {
5346
5784
  if (!Array.isArray(pathSegments) || pathSegments.length === 0) return void 0;
5347
5785
  let current = source;
@@ -5388,7 +5826,7 @@ var createDomShareMonitor = async (page, options = {}) => {
5388
5826
  const onMatch = typeof options.onMatch === "function" ? options.onMatch : null;
5389
5827
  const onTelemetry = typeof options.onTelemetry === "function" ? options.onTelemetry : null;
5390
5828
  let matched = false;
5391
- logger11.info(`DOM \u76D1\u542C\u51C6\u5907\u6302\u8F7D: selectors=${toJsonInline(selectors, 120)}, mode=${mode}`);
5829
+ logger12.info(`DOM \u76D1\u542C\u51C6\u5907\u6302\u8F7D: selectors=${toJsonInline(selectors, 120)}, mode=${mode}`);
5392
5830
  const monitor = await Mutation.useMonitor(page, selectors, {
5393
5831
  mode,
5394
5832
  onMutation: (context = {}) => {
@@ -5406,12 +5844,12 @@ ${text}`;
5406
5844
  });
5407
5845
  }
5408
5846
  if (mutationCount <= 5 || mutationCount % 50 === 0) {
5409
- logger11.info(`DOM \u53D8\u5316\u5DF2\u6355\u83B7: mutationCount=${mutationCount}, mutationNodes=${mutationNodes.length}`);
5847
+ logger12.info(`DOM \u53D8\u5316\u5DF2\u6355\u83B7: mutationCount=${mutationCount}, mutationNodes=${mutationNodes.length}`);
5410
5848
  }
5411
5849
  const [candidate] = Utils.parseLinks(rawDom, { prefix }) || [];
5412
5850
  if (!candidate) return;
5413
5851
  matched = true;
5414
- logger11.success("captureLink.domHit", `DOM \u547D\u4E2D\u5206\u4EAB\u94FE\u63A5: mutationCount=${mutationCount}, link=${candidate}`);
5852
+ logger12.success("captureLink.domHit", `DOM \u547D\u4E2D\u5206\u4EAB\u94FE\u63A5: mutationCount=${mutationCount}, link=${candidate}`);
5415
5853
  if (onMatch) {
5416
5854
  onMatch({
5417
5855
  link: candidate,
@@ -5427,7 +5865,7 @@ ${text}`;
5427
5865
  return {
5428
5866
  stop: async () => {
5429
5867
  const result = await monitor.stop();
5430
- logger11.info(`DOM \u76D1\u542C\u5DF2\u505C\u6B62: totalMutations=${result?.totalMutations || 0}`);
5868
+ logger12.info(`DOM \u76D1\u542C\u5DF2\u505C\u6B62: totalMutations=${result?.totalMutations || 0}`);
5431
5869
  return result;
5432
5870
  }
5433
5871
  };
@@ -5467,8 +5905,8 @@ var Share = {
5467
5905
  if (share.mode === "response" && apiMatchers.length === 0) {
5468
5906
  throw new Error("Share.captureLink requires share.xurl[0] api matcher when mode=response");
5469
5907
  }
5470
- logger11.start("captureLink", `mode=${share.mode}, timeoutMs=${timeoutMs}, prefix=${share.prefix}`);
5471
- logger11.info(`captureLink \u914D\u7F6E: xurl=${toJsonInline(share.xurl)}, domMode=${domMode}, domSelectors=${toJsonInline(domSelectors, 120)}`);
5908
+ logger12.start("captureLink", `mode=${share.mode}, timeoutMs=${timeoutMs}, prefix=${share.prefix}`);
5909
+ logger12.info(`captureLink \u914D\u7F6E: xurl=${toJsonInline(share.xurl)}, domMode=${domMode}, domSelectors=${toJsonInline(domSelectors, 120)}`);
5472
5910
  const stats = {
5473
5911
  actionTimedOut: false,
5474
5912
  domMutationCount: 0,
@@ -5493,7 +5931,7 @@ var Share = {
5493
5931
  link: validated,
5494
5932
  payloadText: String(payloadText || "")
5495
5933
  };
5496
- logger11.info(`\u5019\u9009\u94FE\u63A5\u5DF2\u786E\u8BA4: source=${source}, link=${validated}`);
5934
+ logger12.info(`\u5019\u9009\u94FE\u63A5\u5DF2\u786E\u8BA4: source=${source}, link=${validated}`);
5497
5935
  return true;
5498
5936
  };
5499
5937
  const resolveResponseCandidate = (responseText) => {
@@ -5528,7 +5966,7 @@ var Share = {
5528
5966
  try {
5529
5967
  await monitor.stop();
5530
5968
  } catch (error) {
5531
- logger11.warning(`\u505C\u6B62 DOM \u76D1\u542C\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`);
5969
+ logger12.warning(`\u505C\u6B62 DOM \u76D1\u542C\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`);
5532
5970
  }
5533
5971
  };
5534
5972
  const onResponse = async (response) => {
@@ -5541,29 +5979,29 @@ var Share = {
5541
5979
  stats.responseSampleUrls.push(url);
5542
5980
  }
5543
5981
  if (stats.responseObserved <= 5) {
5544
- logger11.info(`\u63A5\u53E3\u54CD\u5E94\u91C7\u6837(${stats.responseObserved}): ${url}`);
5982
+ logger12.info(`\u63A5\u53E3\u54CD\u5E94\u91C7\u6837(${stats.responseObserved}): ${url}`);
5545
5983
  }
5546
5984
  if (!apiMatchers.some((matcher) => url.includes(matcher))) return;
5547
5985
  stats.responseMatched += 1;
5548
5986
  stats.lastMatchedUrl = url;
5549
- logger11.info(`\u63A5\u53E3\u547D\u4E2D\u5339\u914D(${stats.responseMatched}): ${url}`);
5987
+ logger12.info(`\u63A5\u53E3\u547D\u4E2D\u5339\u914D(${stats.responseMatched}): ${url}`);
5550
5988
  const text = await response.text();
5551
5989
  const hit = resolveResponseCandidate(text);
5552
5990
  if (!hit?.link) {
5553
5991
  if (stats.responseMatched <= 3) {
5554
- logger11.info(`\u63A5\u53E3\u89E3\u6790\u5B8C\u6210\u4F46\u672A\u63D0\u53D6\u5230\u5206\u4EAB\u94FE\u63A5: payloadSize=${text.length}`);
5992
+ logger12.info(`\u63A5\u53E3\u89E3\u6790\u5B8C\u6210\u4F46\u672A\u63D0\u53D6\u5230\u5206\u4EAB\u94FE\u63A5: payloadSize=${text.length}`);
5555
5993
  }
5556
5994
  return;
5557
5995
  }
5558
5996
  stats.responseResolved += 1;
5559
- logger11.success("captureLink.responseHit", `\u63A5\u53E3\u547D\u4E2D\u5206\u4EAB\u94FE\u63A5: url=${url}, link=${hit.link}`);
5997
+ logger12.success("captureLink.responseHit", `\u63A5\u53E3\u547D\u4E2D\u5206\u4EAB\u94FE\u63A5: url=${url}, link=${hit.link}`);
5560
5998
  setCandidate("response", hit.link, hit.payloadText);
5561
5999
  } catch (error) {
5562
- logger11.warning(`\u63A5\u53E3\u54CD\u5E94\u5904\u7406\u5F02\u5E38: ${error instanceof Error ? error.message : String(error)}`);
6000
+ logger12.warning(`\u63A5\u53E3\u54CD\u5E94\u5904\u7406\u5F02\u5E38: ${error instanceof Error ? error.message : String(error)}`);
5563
6001
  }
5564
6002
  };
5565
6003
  if (share.mode === "dom") {
5566
- logger11.info("\u5F53\u524D\u4E3A DOM \u6A21\u5F0F\uFF0C\u4EC5\u542F\u7528 DOM \u76D1\u542C");
6004
+ logger12.info("\u5F53\u524D\u4E3A DOM \u6A21\u5F0F\uFF0C\u4EC5\u542F\u7528 DOM \u76D1\u542C");
5567
6005
  domMonitor = await createDomShareMonitor(page, {
5568
6006
  prefix: share.prefix,
5569
6007
  selectors: domSelectors,
@@ -5578,14 +6016,14 @@ var Share = {
5578
6016
  });
5579
6017
  }
5580
6018
  if (share.mode === "response") {
5581
- logger11.info(`\u5F53\u524D\u4E3A\u63A5\u53E3\u6A21\u5F0F\uFF0C\u6302\u8F7D response \u76D1\u542C: apiMatchers=${toJsonInline(apiMatchers, 160)}`);
6019
+ logger12.info(`\u5F53\u524D\u4E3A\u63A5\u53E3\u6A21\u5F0F\uFF0C\u6302\u8F7D response \u76D1\u542C: apiMatchers=${toJsonInline(apiMatchers, 160)}`);
5582
6020
  page.on("response", onResponse);
5583
6021
  }
5584
6022
  const deadline = Date.now() + timeoutMs;
5585
6023
  const getRemainingMs = () => Math.max(0, deadline - Date.now());
5586
6024
  try {
5587
6025
  const actionTimeout = getRemainingMs();
5588
- logger11.start("captureLink.performActions", `\u6267\u884C\u52A8\u4F5C\u9884\u7B97=${actionTimeout}ms`);
6026
+ logger12.start("captureLink.performActions", `\u6267\u884C\u52A8\u4F5C\u9884\u7B97=${actionTimeout}ms`);
5589
6027
  if (actionTimeout > 0) {
5590
6028
  let timer = null;
5591
6029
  let actionError = null;
@@ -5599,21 +6037,21 @@ var Share = {
5599
6037
  const actionResult = await Promise.race([actionPromise, timeoutPromise]);
5600
6038
  if (timer) clearTimeout(timer);
5601
6039
  if (actionResult === "__ACTION_ERROR__") {
5602
- logger11.fail("captureLink.performActions", actionError);
6040
+ logger12.fail("captureLink.performActions", actionError);
5603
6041
  throw actionError;
5604
6042
  }
5605
6043
  if (actionResult === "__ACTION_TIMEOUT__") {
5606
6044
  stats.actionTimedOut = true;
5607
- logger11.warning(`performActions \u5DF2\u8D85\u65F6 (${actionTimeout}ms)\uFF0C\u52A8\u4F5C\u53EF\u80FD\u4ECD\u5728\u5F02\u6B65\u6267\u884C`);
6045
+ logger12.warning(`performActions \u5DF2\u8D85\u65F6 (${actionTimeout}ms)\uFF0C\u52A8\u4F5C\u53EF\u80FD\u4ECD\u5728\u5F02\u6B65\u6267\u884C`);
5608
6046
  } else {
5609
- logger11.success("captureLink.performActions", "\u6267\u884C\u52A8\u4F5C\u5B8C\u6210");
6047
+ logger12.success("captureLink.performActions", "\u6267\u884C\u52A8\u4F5C\u5B8C\u6210");
5610
6048
  }
5611
6049
  }
5612
6050
  let nextProgressLogTs = Date.now() + 3e3;
5613
6051
  while (true) {
5614
6052
  const selected = share.mode === "dom" ? candidates.dom : candidates.response;
5615
6053
  if (selected?.link) {
5616
- logger11.success("captureLink", `\u6355\u83B7\u6210\u529F: source=${share.mode}, link=${selected.link}`);
6054
+ logger12.success("captureLink", `\u6355\u83B7\u6210\u529F: source=${share.mode}, link=${selected.link}`);
5617
6055
  return {
5618
6056
  link: selected.link,
5619
6057
  payloadText: selected.payloadText,
@@ -5625,7 +6063,7 @@ var Share = {
5625
6063
  if (remaining <= 0) break;
5626
6064
  const now = Date.now();
5627
6065
  if (now >= nextProgressLogTs) {
5628
- logger11.info(
6066
+ logger12.info(
5629
6067
  `captureLink \u7B49\u5F85\u4E2D: remaining=${remaining}ms, domMutationCount=${stats.domMutationCount}, responseMatched=${stats.responseMatched}`
5630
6068
  );
5631
6069
  nextProgressLogTs = now + 5e3;
@@ -5633,11 +6071,11 @@ var Share = {
5633
6071
  await delay2(Math.max(0, Math.min(DEFAULT_POLL_INTERVAL_MS, remaining)));
5634
6072
  }
5635
6073
  if (share.mode === "response" && stats.responseMatched === 0) {
5636
- logger11.warning(
6074
+ logger12.warning(
5637
6075
  `\u63A5\u53E3\u76D1\u542C\u672A\u547D\u4E2D: apiMatchers=${toJsonInline(apiMatchers, 220)}, \u54CD\u5E94\u6837\u672CURLs=${toJsonInline(stats.responseSampleUrls, 420)}`
5638
6076
  );
5639
6077
  }
5640
- logger11.warning(
6078
+ logger12.warning(
5641
6079
  `captureLink \u8D85\u65F6\u672A\u62FF\u5230\u94FE\u63A5: mode=${share.mode}, actionTimedOut=${stats.actionTimedOut}, domMutationCount=${stats.domMutationCount}, responseObserved=${stats.responseObserved}, responseMatched=${stats.responseMatched}, lastMatchedUrl=${stats.lastMatchedUrl || "none"}`
5642
6080
  );
5643
6081
  return {
@@ -5649,7 +6087,7 @@ var Share = {
5649
6087
  } finally {
5650
6088
  if (share.mode === "response") {
5651
6089
  page.off("response", onResponse);
5652
- logger11.info("response \u76D1\u542C\u5DF2\u5378\u8F7D");
6090
+ logger12.info("response \u76D1\u542C\u5DF2\u5378\u8F7D");
5653
6091
  }
5654
6092
  await stopDomMonitor();
5655
6093
  }
@@ -5665,12 +6103,12 @@ var Share = {
5665
6103
  * @returns {Promise<string>} base64 png
5666
6104
  */
5667
6105
  async captureScreen(page, options = {}) {
5668
- const originalViewport = page.viewportSize();
5669
- const defaultBuffer = Math.round((originalViewport?.height || 1080) / 2);
6106
+ const originalViewport = await resolveCurrentViewportSize(page);
6107
+ const defaultBuffer = Math.round((originalViewport.height || 1080) / 2);
5670
6108
  const buffer = options.buffer ?? defaultBuffer;
5671
6109
  const restore = options.restore ?? false;
5672
6110
  const maxHeight = options.maxHeight ?? 8e3;
5673
- const screenshotWatermarkify = normalizeScreenshotWatermarkify(options.watermarkify ?? true);
6111
+ const screenshotWatermarkify = resolveCaptureScreenWatermarkify(page, options.watermarkify);
5674
6112
  try {
5675
6113
  const maxScrollHeight = await page.evaluate(() => {
5676
6114
  let maxHeight2 = document.body.scrollHeight;
@@ -5694,7 +6132,7 @@ var Share = {
5694
6132
  });
5695
6133
  const targetHeight = Math.min(maxScrollHeight + buffer, maxHeight);
5696
6134
  await page.setViewportSize({
5697
- width: originalViewport?.width || 1280,
6135
+ width: originalViewport.width,
5698
6136
  height: targetHeight
5699
6137
  });
5700
6138
  await delay2(1e3);
@@ -5726,9 +6164,7 @@ var Share = {
5726
6164
  el.classList.remove("__pk_expanded__");
5727
6165
  });
5728
6166
  });
5729
- if (originalViewport) {
5730
- await page.setViewportSize(originalViewport);
5731
- }
6167
+ await page.setViewportSize(originalViewport);
5732
6168
  }
5733
6169
  }
5734
6170
  }