@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.cjs CHANGED
@@ -350,18 +350,18 @@ var fallbackLog = {
350
350
  error: (...args) => console.error(...args),
351
351
  debug: (...args) => console.debug ? console.debug(...args) : console.log(...args)
352
352
  };
353
- var resolveLogMethod = (logger12, name) => {
354
- if (logger12 && typeof logger12[name] === "function") {
355
- return logger12[name].bind(logger12);
353
+ var resolveLogMethod = (logger13, name) => {
354
+ if (logger13 && typeof logger13[name] === "function") {
355
+ return logger13[name].bind(logger13);
356
356
  }
357
- if (name === "warning" && logger12 && typeof logger12.warn === "function") {
358
- return logger12.warn.bind(logger12);
357
+ if (name === "warning" && logger13 && typeof logger13.warn === "function") {
358
+ return logger13.warn.bind(logger13);
359
359
  }
360
360
  return fallbackLog[name];
361
361
  };
362
362
  var defaultLogger = null;
363
- var setDefaultLogger = (logger12) => {
364
- defaultLogger = logger12;
363
+ var setDefaultLogger = (logger13) => {
364
+ defaultLogger = logger13;
365
365
  };
366
366
  var resolveLogger = (explicitLogger) => {
367
367
  if (explicitLogger && typeof explicitLogger.info === "function") {
@@ -388,8 +388,8 @@ var colorize = (text, color) => {
388
388
  var createBaseLogger = (prefix = "", explicitLogger) => {
389
389
  const name = prefix ? String(prefix) : "";
390
390
  const dispatch = (methodName, icon, message, color) => {
391
- const logger12 = resolveLogger(explicitLogger);
392
- const logFn = resolveLogMethod(logger12, methodName);
391
+ const logger13 = resolveLogger(explicitLogger);
392
+ const logFn = resolveLogMethod(logger13, methodName);
393
393
  const timestamp = colorize(`[${formatTimestamp()}]`, ANSI.gray);
394
394
  const line = formatLine(name, icon, message);
395
395
  const coloredLine = colorize(line, color);
@@ -435,6 +435,33 @@ function createInternalLogger(moduleName, explicitLogger) {
435
435
  };
436
436
  }
437
437
 
438
+ // src/internals/viewport.js
439
+ var toPositiveInt = (value) => {
440
+ const number = Math.round(Number(value) || 0);
441
+ return number > 0 ? number : 0;
442
+ };
443
+ var resolveCurrentViewportSize = async (page, fallback = {}) => {
444
+ const directViewport = page?.viewportSize?.();
445
+ const directWidth = toPositiveInt(directViewport?.width);
446
+ const directHeight = toPositiveInt(directViewport?.height);
447
+ if (directWidth && directHeight) {
448
+ return {
449
+ width: directWidth,
450
+ height: directHeight
451
+ };
452
+ }
453
+ const measuredViewport = await page?.evaluate?.(() => ({
454
+ width: window.innerWidth || document.documentElement?.clientWidth || document.body?.clientWidth || 0,
455
+ height: window.innerHeight || document.documentElement?.clientHeight || document.body?.clientHeight || 0
456
+ })).catch(() => null);
457
+ const fallbackWidth = toPositiveInt(fallback?.width);
458
+ const fallbackHeight = toPositiveInt(fallback?.height);
459
+ return {
460
+ width: toPositiveInt(measuredViewport?.width) || fallbackWidth || 1,
461
+ height: toPositiveInt(measuredViewport?.height) || fallbackHeight || 1
462
+ };
463
+ };
464
+
438
465
  // src/internals/screenshot.js
439
466
  var logger = createInternalLogger("Screenshot");
440
467
  var DEFAULT_TIMEOUT_MS = 5e3;
@@ -461,7 +488,7 @@ var normalizeQuality = (value, type) => {
461
488
  };
462
489
  var buildFullPageClip = (metrics, viewport, maxClipHeight) => {
463
490
  const contentSize = metrics && typeof metrics === "object" ? metrics.contentSize || null : null;
464
- const width = Math.max(1, Math.ceil(contentSize?.width || viewport.width || 1));
491
+ const width = Math.max(1, Math.ceil(viewport.width || contentSize?.width || 1));
465
492
  let height = Math.max(1, Math.ceil(contentSize?.height || viewport.height || 1));
466
493
  if (maxClipHeight > 0) {
467
494
  height = Math.min(height, maxClipHeight);
@@ -498,7 +525,7 @@ var capturePageScreenshot = async (page, options = {}) => {
498
525
  const session = await context.newCDPSession(page);
499
526
  try {
500
527
  const metrics = await session.send("Page.getLayoutMetrics");
501
- const viewport = page.viewportSize() || { width: 1280, height: 720 };
528
+ const viewport = await resolveCurrentViewportSize(page);
502
529
  const captureParams = {
503
530
  format: type,
504
531
  fromSurface: true,
@@ -952,6 +979,9 @@ var ProxyMeterRuntime = {
952
979
  getProxyMeterSnapshot
953
980
  };
954
981
 
982
+ // src/internals/constants.js
983
+ var PageRuntimeStateKey = "__playwright_toolkit_runtime_state__";
984
+
955
985
  // src/runtime-env.js
956
986
  var BROWSER_PROFILE_SCHEMA_VERSION = 1;
957
987
  var rememberedRuntimeState = null;
@@ -1458,6 +1488,7 @@ var RuntimeEnv = {
1458
1488
  parseInput(input = {}, actor = "") {
1459
1489
  const runtime2 = tryParseJSON(input?.runtime) || {};
1460
1490
  const resolvedActor = String(actor || input?.actor || "").trim();
1491
+ const query = String(input?.query || "").trim();
1461
1492
  const cookies = normalizeCookies(runtime2?.cookies);
1462
1493
  const cookieMap = buildCookieMap(cookies);
1463
1494
  const localStorage = normalizeLocalStorage(runtime2?.local_storage);
@@ -1481,6 +1512,7 @@ var RuntimeEnv = {
1481
1512
  actor: resolvedActor,
1482
1513
  runtime: normalizedRuntime,
1483
1514
  envId,
1515
+ query,
1484
1516
  auth,
1485
1517
  cookies,
1486
1518
  cookieMap,
@@ -1548,6 +1580,12 @@ var RuntimeEnv = {
1548
1580
  async applyToPage(page, source = {}, options = {}) {
1549
1581
  if (!page) return;
1550
1582
  const state = normalizeRuntimeState(source, options?.actor || "");
1583
+ Object.defineProperty(page, PageRuntimeStateKey, {
1584
+ configurable: true,
1585
+ enumerable: false,
1586
+ writable: true,
1587
+ value: state
1588
+ });
1551
1589
  const localStorage = state.localStorage || {};
1552
1590
  const sessionStorage = state.sessionStorage || {};
1553
1591
  const cookies = (state.cookies || []).map((cookie) => {
@@ -4531,7 +4569,7 @@ var createTemplateLogger = (baseLogger = createBaseLogger()) => {
4531
4569
  };
4532
4570
  var getDefaultBaseLogger = () => createBaseLogger("");
4533
4571
  var Logger = {
4534
- setLogger: (logger12) => setDefaultLogger(logger12),
4572
+ setLogger: (logger13) => setDefaultLogger(logger13),
4535
4573
  info: (message) => getDefaultBaseLogger().info(message),
4536
4574
  success: (message) => getDefaultBaseLogger().success(message),
4537
4575
  warning: (message) => getDefaultBaseLogger().warning(message),
@@ -4539,8 +4577,8 @@ var Logger = {
4539
4577
  error: (message) => getDefaultBaseLogger().error(message),
4540
4578
  debug: (message) => getDefaultBaseLogger().debug(message),
4541
4579
  start: (message) => getDefaultBaseLogger().start(message),
4542
- useTemplate: (logger12) => {
4543
- if (logger12) return createTemplateLogger(createBaseLogger("", logger12));
4580
+ useTemplate: (logger13) => {
4581
+ if (logger13) return createTemplateLogger(createBaseLogger("", logger13));
4544
4582
  return createTemplateLogger();
4545
4583
  }
4546
4584
  };
@@ -4558,13 +4596,28 @@ var DEFAULT_WATERMARK_ROTATE_DEG = -16;
4558
4596
  var DEFAULT_WATERMARK_CELL_WIDTH = 860;
4559
4597
  var DEFAULT_WATERMARK_CELL_HEIGHT = 330;
4560
4598
  var DEFAULT_STRIP_LOGO_URL = "https://static.heartbitai.com/geo/icon/favicon.png";
4561
- var DEFAULT_IP_LOOKUP_URL = "http://myip.ipip.net";
4599
+ var DEFAULT_IP_LOOKUP_URL = "https://myip.ipip.net/json";
4562
4600
  var DEFAULT_LOGO_FETCH_TIMEOUT_MS = 2500;
4563
- var DEFAULT_STRIP_HEIGHT = 78;
4564
- var DEFAULT_STRIP_PADDING_LEFT = 20;
4565
- var DEFAULT_STRIP_PADDING_RIGHT = 22;
4566
- var DEFAULT_STRIP_GAP = 16;
4601
+ var DEFAULT_STRIP_ONE_LINE_HEIGHT = 78;
4602
+ var DEFAULT_STRIP_WRAPPED_MIN_HEIGHT = 108;
4603
+ var DEFAULT_STRIP_PADDING_LEFT = 22;
4604
+ var DEFAULT_STRIP_PADDING_RIGHT = 28;
4605
+ var DEFAULT_STRIP_GAP = 18;
4567
4606
  var DEFAULT_STRIP_LABEL_WIDTH = 56;
4607
+ var DEFAULT_STRIP_PROMPT_MAX_SAFE_CHARS = 180;
4608
+ var DEFAULT_STRIP_PROMPT_MIN_TOTAL_WIDTH = 320;
4609
+ var DEFAULT_STRIP_PROMPT_PREFERRED_WIDTH_RATIO = 0.58;
4610
+ var DEFAULT_STRIP_ZONE_GAP = 18;
4611
+ var DEFAULT_STRIP_PROMPT_TRAILING_PADDING = 16;
4612
+ var DEFAULT_STRIP_LABEL_FONT_SIZE = 14.5;
4613
+ var DEFAULT_STRIP_VALUE_FONT_SIZE = 15.5;
4614
+ var DEFAULT_STRIP_VALUE_PADDING = 10;
4615
+ var DEFAULT_STRIP_LABEL_PADDING = 14;
4616
+ var DEFAULT_STRIP_SECTION_GAP = DEFAULT_STRIP_GAP + 16;
4617
+ var DEFAULT_STRIP_LINE_HEIGHT_RATIO = 1.28;
4618
+ var DEFAULT_STRIP_PROMPT_MAX_LINES = 3;
4619
+ var DEFAULT_STRIP_CONTENT_SAFE_PADDING_X = 12;
4620
+ var DEFAULT_STRIP_TEXT_WIDTH_SAFETY = 10;
4568
4621
  var WEAK_LOCATION_VALUES = /* @__PURE__ */ new Set(["cn", "\u4E2D\u56FD"]);
4569
4622
  var LOCATION_NETWORK_SUFFIX_PATTERNS = [
4570
4623
  /(?:中国)?移动$/i,
@@ -4589,6 +4642,8 @@ var LOCATION_NETWORK_SUFFIX_PATTERNS = [
4589
4642
  /digitalocean$/i
4590
4643
  ];
4591
4644
  var cachedStripLogoSrcPromise = null;
4645
+ var cachedEnrichmentByContext = /* @__PURE__ */ new WeakMap();
4646
+ var logger11 = createInternalLogger("Watermarkify");
4592
4647
  var normalizeText = (value) => String(value || "").trim();
4593
4648
  var toInline = (value, maxLen = 200) => {
4594
4649
  const text = normalizeText(value);
@@ -4600,6 +4655,15 @@ var shortenTail = (value, maxLen = 80) => {
4600
4655
  if (!text || text.length <= maxLen) return text;
4601
4656
  return `${text.slice(0, Math.max(0, maxLen - 1)).trimEnd()}\u2026`;
4602
4657
  };
4658
+ var shortenByCharacters = (value, maxChars = 80) => {
4659
+ const text = normalizeWhitespace(value);
4660
+ if (!text) return "";
4661
+ const chars = Array.from(text);
4662
+ if (chars.length <= maxChars) {
4663
+ return text;
4664
+ }
4665
+ return `${chars.slice(0, Math.max(0, maxChars - 1)).join("").trimEnd()}\u2026`;
4666
+ };
4603
4667
  var padDatePart = (value) => String(value).padStart(2, "0");
4604
4668
  var formatUtcOffsetLabel = (offsetHours = DEFAULT_TIMEZONE_OFFSET) => {
4605
4669
  const sign = offsetHours >= 0 ? "+" : "-";
@@ -4647,18 +4711,26 @@ var pickHeaderValue = async (response, names = []) => {
4647
4711
  }
4648
4712
  return "";
4649
4713
  };
4650
- var parseIpIpResponse = (rawText) => {
4714
+ var parseIpIpJsonResponse = (rawText) => {
4651
4715
  const text = normalizeWhitespace(rawText);
4652
- if (!text) return null;
4653
- 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);
4654
- const locationMatch = text.match(/来自于[::]?\s*(.+)$/i);
4655
- const ip = toInline(ipMatch?.[1], 80);
4656
- const rawLocation = toInline(locationMatch?.[1], 120);
4657
- const location = toInline(stripLocationNetworkSuffix(rawLocation), 80) || rawLocation;
4658
- if (!ip && !location) {
4716
+ if (!text || !text.startsWith("{") && !text.startsWith("[")) {
4717
+ return null;
4718
+ }
4719
+ try {
4720
+ const payload = JSON.parse(text);
4721
+ const ip = toInline(payload?.data?.ip || payload?.ip, 80);
4722
+ const locationParts = Array.isArray(payload?.data?.location) ? payload.data.location.map((item) => normalizeWhitespace(item)).filter(Boolean) : [];
4723
+ const location = toInline(
4724
+ stripLocationNetworkSuffix(locationParts.join(" ")),
4725
+ 80
4726
+ );
4727
+ if (!ip && !location) {
4728
+ return null;
4729
+ }
4730
+ return { ip, location };
4731
+ } catch {
4659
4732
  return null;
4660
4733
  }
4661
- return { ip, location };
4662
4734
  };
4663
4735
  var fillEnrichment = (target, source) => {
4664
4736
  if (!target || !source || typeof source !== "object") {
@@ -4672,6 +4744,29 @@ var fillEnrichment = (target, source) => {
4672
4744
  }
4673
4745
  return target;
4674
4746
  };
4747
+ var readCachedEnrichment = (page) => {
4748
+ const context = page && typeof page.context === "function" ? page.context() : null;
4749
+ if (!context) {
4750
+ return null;
4751
+ }
4752
+ const cached = cachedEnrichmentByContext.get(context);
4753
+ return cached && typeof cached === "object" ? cached : null;
4754
+ };
4755
+ var writeCachedEnrichment = (page, source) => {
4756
+ const context = page && typeof page.context === "function" ? page.context() : null;
4757
+ if (!context || !source || typeof source !== "object") {
4758
+ return;
4759
+ }
4760
+ const current = cachedEnrichmentByContext.get(context) || {};
4761
+ const next = {
4762
+ ip: toInline(source.ip || current.ip, 80),
4763
+ location: toInline(source.location || current.location, 80)
4764
+ };
4765
+ if (!next.ip && !next.location) {
4766
+ return;
4767
+ }
4768
+ cachedEnrichmentByContext.set(context, next);
4769
+ };
4675
4770
  var getHostname = (url) => {
4676
4771
  try {
4677
4772
  const parsed = new URL(url);
@@ -4785,10 +4880,16 @@ var resolveWithCustomResolver = async (page, baseMeta, options = {}) => {
4785
4880
  if (!resolved || typeof resolved !== "object") {
4786
4881
  return null;
4787
4882
  }
4788
- return {
4883
+ const enrichment = {
4789
4884
  ip: toInline(resolved.ip, 80),
4790
4885
  location: toInline(resolved.location, 80)
4791
4886
  };
4887
+ if (enrichment.ip || enrichment.location) {
4888
+ logger11.info(`\u81EA\u5B9A\u4E49 resolver \u547D\u4E2D: ip=${enrichment.ip || "-"}, loc=${enrichment.location || "-"}`);
4889
+ } else {
4890
+ logger11.warning("\u81EA\u5B9A\u4E49 resolver \u5DF2\u6267\u884C\uFF0C\u4F46\u672A\u8FD4\u56DE IP/Loc");
4891
+ }
4892
+ return enrichment;
4792
4893
  } finally {
4793
4894
  controller?.abort();
4794
4895
  }
@@ -4801,44 +4902,43 @@ var openProbePage = async (page) => {
4801
4902
  if (!context) {
4802
4903
  return null;
4803
4904
  }
4905
+ const browser = typeof context.browser === "function" ? context.browser() : null;
4906
+ if (browser && typeof browser.newContext === "function") {
4907
+ const probeContext = await browser.newContext().catch(() => null);
4908
+ if (probeContext && typeof probeContext.newPage === "function") {
4909
+ const probePage = await probeContext.newPage().catch(async () => {
4910
+ await probeContext.close().catch(() => {
4911
+ });
4912
+ return null;
4913
+ });
4914
+ if (probePage) {
4915
+ return {
4916
+ page: probePage,
4917
+ close: async () => {
4918
+ await probeContext.close().catch(() => {
4919
+ });
4920
+ }
4921
+ };
4922
+ }
4923
+ } else {
4924
+ await probeContext?.close?.().catch(() => {
4925
+ });
4926
+ }
4927
+ }
4804
4928
  if (typeof context.newPage === "function") {
4805
4929
  try {
4806
- const probePage2 = await context.newPage();
4930
+ const probePage = await context.newPage();
4807
4931
  return {
4808
- page: probePage2,
4932
+ page: probePage,
4809
4933
  close: async () => {
4810
- await probePage2.close().catch(() => {
4934
+ await probePage.close().catch(() => {
4811
4935
  });
4812
4936
  }
4813
4937
  };
4814
4938
  } catch {
4815
4939
  }
4816
4940
  }
4817
- const browser = typeof context.browser === "function" ? context.browser() : null;
4818
- if (!browser || typeof browser.newContext !== "function") {
4819
- return null;
4820
- }
4821
- const probeContext = await browser.newContext().catch(() => null);
4822
- if (!probeContext || typeof probeContext.newPage !== "function") {
4823
- await probeContext?.close?.().catch(() => {
4824
- });
4825
- return null;
4826
- }
4827
- const probePage = await probeContext.newPage().catch(async () => {
4828
- await probeContext.close().catch(() => {
4829
- });
4830
- return null;
4831
- });
4832
- if (!probePage) {
4833
- return null;
4834
- }
4835
- return {
4836
- page: probePage,
4837
- close: async () => {
4838
- await probeContext.close().catch(() => {
4839
- });
4840
- }
4841
- };
4941
+ return null;
4842
4942
  };
4843
4943
  var resolveWithIpLookup = async (page, options = {}) => {
4844
4944
  if (!page || typeof page.context !== "function" || options.ipLookup === false) {
@@ -4846,6 +4946,7 @@ var resolveWithIpLookup = async (page, options = {}) => {
4846
4946
  }
4847
4947
  const probeScope = await openProbePage(page);
4848
4948
  if (!probeScope?.page) {
4949
+ logger11.warning("ipLookup \u8DF3\u8FC7: \u65E0\u6CD5\u521B\u5EFA probe page");
4849
4950
  return null;
4850
4951
  }
4851
4952
  const timeoutMs = Math.max(
@@ -4854,10 +4955,16 @@ var resolveWithIpLookup = async (page, options = {}) => {
4854
4955
  );
4855
4956
  try {
4856
4957
  const probePage = probeScope.page;
4958
+ logger11.info(`ipLookup \u5C1D\u8BD5: url=${DEFAULT_IP_LOOKUP_URL}, timeoutMs=${timeoutMs}`);
4857
4959
  const response = await probePage.goto(DEFAULT_IP_LOOKUP_URL, {
4858
4960
  waitUntil: "commit",
4859
4961
  timeout: timeoutMs
4860
- }).catch(() => null);
4962
+ }).catch((error) => {
4963
+ logger11.warning(`ipLookup \u8BF7\u6C42\u5931\u8D25: url=${DEFAULT_IP_LOOKUP_URL}, error=${error instanceof Error ? error.message : String(error)}`);
4964
+ return null;
4965
+ });
4966
+ const status = response && typeof response.status === "function" ? response.status() : 0;
4967
+ const contentType = normalizeText(await pickHeaderValue(response, ["content-type"]));
4861
4968
  let rawText = "";
4862
4969
  if (response && typeof response.text === "function") {
4863
4970
  rawText = String(await withTimeout(
@@ -4873,8 +4980,15 @@ var resolveWithIpLookup = async (page, options = {}) => {
4873
4980
  Math.min(timeoutMs, 500)
4874
4981
  ) || "");
4875
4982
  }
4876
- return parseIpIpResponse(rawText);
4877
- } catch {
4983
+ const parsed = parseIpIpJsonResponse(rawText);
4984
+ if (parsed?.ip || parsed?.location) {
4985
+ logger11.info(`ipLookup \u6210\u529F: url=${DEFAULT_IP_LOOKUP_URL}, status=${status || "-"}, contentType=${contentType || "-"}, ip=${parsed.ip || "-"}, loc=${parsed.location || "-"}`);
4986
+ return parsed;
4987
+ }
4988
+ logger11.warning(`ipLookup \u672A\u89E3\u6790\u51FA IP/Loc: url=${DEFAULT_IP_LOOKUP_URL}, status=${status || "-"}, contentType=${contentType || "-"}, preview=${shortenTail(rawText, 120) || "[empty]"}`);
4989
+ return null;
4990
+ } catch (error) {
4991
+ logger11.warning(`ipLookup \u6267\u884C\u5F02\u5E38\uFF0C\u672A\u83B7\u5F97 IP/Loc: ${error instanceof Error ? error.message : String(error)}`);
4878
4992
  return null;
4879
4993
  } finally {
4880
4994
  await probeScope.close().catch(() => {
@@ -4883,10 +4997,18 @@ var resolveWithIpLookup = async (page, options = {}) => {
4883
4997
  };
4884
4998
  var resolveEnrichment = async (page, baseMeta, options) => {
4885
4999
  const response = options.response && typeof options.response === "object" ? options.response : null;
5000
+ const cached = readCachedEnrichment(page);
4886
5001
  const merged = {
4887
5002
  ip: toInline(options.ip, 80),
4888
5003
  location: toInline(options.location, 80)
4889
5004
  };
5005
+ logger11.info(`enrichment \u5F00\u59CB: host=${baseMeta.hostname || "-"}, hasPresetIp=${Boolean(merged.ip)}, hasPresetLoc=${Boolean(merged.location)}, ipLookup=${options.ipLookup !== false}`);
5006
+ if (!merged.ip || !merged.location) {
5007
+ if (cached?.ip || cached?.location) {
5008
+ logger11.info(`enrichment \u547D\u4E2D\u4E0A\u4E0B\u6587\u7F13\u5B58: ip=${cached.ip || "-"}, loc=${cached.location || "-"}`);
5009
+ }
5010
+ fillEnrichment(merged, cached);
5011
+ }
4890
5012
  if (!merged.ip || !merged.location) {
4891
5013
  fillEnrichment(
4892
5014
  merged,
@@ -4907,9 +5029,16 @@ var resolveEnrichment = async (page, baseMeta, options) => {
4907
5029
  "x-geo-country"
4908
5030
  ]), 80);
4909
5031
  if (!merged.location || isWeakLocationValue(merged.location) && headerLocation) {
5032
+ logger11.info(`enrichment \u4F7F\u7528\u54CD\u5E94\u5934\u8865\u5145 Loc: ${headerLocation || "-"}`);
4910
5033
  merged.location = headerLocation || merged.location;
4911
5034
  }
4912
5035
  }
5036
+ writeCachedEnrichment(page, merged);
5037
+ if (merged.ip || merged.location) {
5038
+ logger11.info(`enrichment \u5B8C\u6210: ip=${merged.ip || "-"}, loc=${merged.location || "-"}`);
5039
+ } else {
5040
+ logger11.warning("enrichment \u5B8C\u6210: \u672A\u83B7\u5F97 IP/Loc");
5041
+ }
4913
5042
  return merged;
4914
5043
  };
4915
5044
  var buildWatermarkStamp = ({ prompt, captureTime, ip, location }) => {
@@ -4922,7 +5051,12 @@ var buildWatermarkStamp = ({ prompt, captureTime, ip, location }) => {
4922
5051
  return parts.join(" | ");
4923
5052
  };
4924
5053
  var buildStripSegments = ({ prompt, captureTime, ip, location }) => {
4925
- const promptValue = toInline(prompt, 240) || "-";
5054
+ const promptValue = shortenByCharacters(
5055
+ normalizeWhitespace(prompt) || "-",
5056
+ DEFAULT_STRIP_PROMPT_MAX_SAFE_CHARS
5057
+ ) || "-";
5058
+ const ipValue = normalizeWhitespace(ip) || "-";
5059
+ const locationValue = normalizeWhitespace(location) || "-";
4926
5060
  return [
4927
5061
  {
4928
5062
  kind: "prompt",
@@ -4939,14 +5073,14 @@ var buildStripSegments = ({ prompt, captureTime, ip, location }) => {
4939
5073
  {
4940
5074
  kind: "location",
4941
5075
  label: "Loc",
4942
- value: shortenTail(location, 24) || "-",
4943
- rawValue: toInline(location, 80) || "-"
5076
+ value: locationValue,
5077
+ rawValue: locationValue
4944
5078
  },
4945
5079
  {
4946
5080
  kind: "ip",
4947
5081
  label: "IP",
4948
- value: toInline(ip, 28) || "-",
4949
- rawValue: toInline(ip, 80) || "-"
5082
+ value: ipValue,
5083
+ rawValue: ipValue
4950
5084
  }
4951
5085
  ];
4952
5086
  };
@@ -4967,28 +5101,9 @@ var estimateTextWidth = (value, fontSize = 16) => {
4967
5101
  }
4968
5102
  return Math.ceil(width);
4969
5103
  };
4970
- var fitTextToWidth = (value, maxWidth, fontSize = 16) => {
4971
- const text = normalizeWhitespace(value);
4972
- if (!text) return "-";
4973
- const safeMaxWidth = Math.max(fontSize * 3, Number(maxWidth) || 0);
4974
- if (estimateTextWidth(text, fontSize) <= safeMaxWidth) {
4975
- return text;
4976
- }
4977
- const ellipsis = "\u2026";
4978
- const ellipsisWidth = estimateTextWidth(ellipsis, fontSize);
4979
- let current = "";
4980
- for (const char of text) {
4981
- const next = current + char;
4982
- if (estimateTextWidth(next, fontSize) + ellipsisWidth > safeMaxWidth) {
4983
- break;
4984
- }
4985
- current = next;
4986
- }
4987
- return `${current.trimEnd()}${ellipsis}`;
4988
- };
4989
5104
  var resolvePromptFields = (options = {}, fallbackTitle = "") => {
4990
5105
  const query = normalizeText(options.query);
4991
- const prompt = normalizeText(options.prompt) || query || normalizeText(fallbackTitle) || "\u672A\u63D0\u4F9B Prompt";
5106
+ const prompt = query || normalizeText(options.prompt) || normalizeText(fallbackTitle) || "\u672A\u63D0\u4F9B Prompt";
4992
5107
  return {
4993
5108
  prompt,
4994
5109
  query: query || prompt
@@ -5044,8 +5159,8 @@ var normalizeScreenshotWatermarkify = (value) => {
5044
5159
  response: source.response ?? null,
5045
5160
  ip: normalizeText(source.ip),
5046
5161
  location: normalizeText(source.location),
5047
- prompt: toInline(source.prompt, 220),
5048
- query: toInline(source.query, 220),
5162
+ prompt: normalizeWhitespace(source.prompt),
5163
+ query: normalizeWhitespace(source.query),
5049
5164
  ipLookup: source.ipLookup !== false,
5050
5165
  ipLookupTimeoutMs: Number.isFinite(ipLookupTimeoutMsRaw) ? ipLookupTimeoutMsRaw : DEFAULT_IP_LOOKUP_TIMEOUT_MS,
5051
5166
  resolver: typeof source.resolver === "function" ? source.resolver : null,
@@ -5098,53 +5213,332 @@ var resolveScreenshotWatermarkifyMeta = async (page, options = {}) => {
5098
5213
  };
5099
5214
  };
5100
5215
  var buildFontFamily = () => 'MiSans, "SF Pro Display", "PingFang SC", "Helvetica Neue", Arial, sans-serif';
5101
- var buildStripSegmentLayouts = (segments, contentWidth) => {
5216
+ var buildStripSegmentLayout = (segment, options = {}) => {
5217
+ const label = normalizeWhitespace(segment?.label || "");
5218
+ const rawValue = normalizeWhitespace(segment?.rawValue || segment?.value || "-") || "-";
5219
+ const labelFontSize = DEFAULT_STRIP_LABEL_FONT_SIZE;
5220
+ const valueFontSize = DEFAULT_STRIP_VALUE_FONT_SIZE;
5221
+ const labelWidth = Math.max(
5222
+ DEFAULT_STRIP_LABEL_WIDTH,
5223
+ estimateTextWidth(label, labelFontSize) + DEFAULT_STRIP_LABEL_PADDING
5224
+ );
5225
+ const rawValueWidth = Math.max(
5226
+ 24,
5227
+ estimateTextWidth(rawValue, valueFontSize) + DEFAULT_STRIP_VALUE_PADDING
5228
+ );
5229
+ const maxValueWidth = Math.max(0, Number(options.valueMaxWidth) || 0);
5230
+ const renderValue = maxValueWidth > 0 ? fitTextWithEllipsis(rawValue, Math.max(18, maxValueWidth - DEFAULT_STRIP_VALUE_PADDING), valueFontSize) : rawValue;
5231
+ const valueWidth = maxValueWidth > 0 ? Math.max(24, Math.min(maxValueWidth, estimateTextWidth(renderValue, valueFontSize) + DEFAULT_STRIP_VALUE_PADDING)) : rawValueWidth;
5232
+ return {
5233
+ ...segment,
5234
+ label,
5235
+ rawValue,
5236
+ renderValue,
5237
+ labelFontSize,
5238
+ valueFontSize,
5239
+ labelWidth,
5240
+ valueWidth,
5241
+ segmentWidth: labelWidth + valueWidth,
5242
+ rowHeight: Math.round(Math.max(labelFontSize, valueFontSize) * 1.42)
5243
+ };
5244
+ };
5245
+ var buildStripRowLayout = (segments, overrides = {}) => {
5102
5246
  const safeSegments = Array.isArray(segments) ? segments : [];
5103
- const sectionGap = DEFAULT_STRIP_GAP + 16;
5104
- const labelWidth = DEFAULT_STRIP_LABEL_WIDTH;
5105
- const promptFontSize = 15;
5106
- const otherFontSizes = {
5107
- time: 17,
5108
- location: 17,
5109
- ip: 17
5247
+ const sectionGap = DEFAULT_STRIP_SECTION_GAP;
5248
+ const layouts = safeSegments.map((segment, index) => buildStripSegmentLayout(segment, {
5249
+ valueMaxWidth: overrides?.valueMaxWidths?.[index]
5250
+ }));
5251
+ const totalWidth = layouts.reduce((sum, segment) => sum + segment.segmentWidth, 0) + Math.max(0, layouts.length - 1) * sectionGap;
5252
+ const rowHeight = layouts.reduce((max, segment) => Math.max(max, segment.rowHeight || 0), 0);
5253
+ return {
5254
+ sectionGap,
5255
+ layouts,
5256
+ totalWidth,
5257
+ rowHeight
5110
5258
  };
5111
- const maxValueWidths = {
5112
- time: 290,
5113
- location: 220,
5114
- ip: 160
5259
+ };
5260
+ var fitTextWithEllipsis = (chars, maxWidth, fontSize) => {
5261
+ const safeChars = Array.isArray(chars) ? chars.slice() : Array.from(String(chars || ""));
5262
+ if (!safeChars.length) {
5263
+ return "-";
5264
+ }
5265
+ if (estimateTextWidth(safeChars.join(""), fontSize) <= maxWidth) {
5266
+ return safeChars.join("").trim() || "-";
5267
+ }
5268
+ while (safeChars.length > 1 && estimateTextWidth(`${safeChars.join("").trimEnd()}\u2026`, fontSize) > maxWidth) {
5269
+ safeChars.pop();
5270
+ }
5271
+ return `${safeChars.join("").trimEnd()}\u2026` || "\u2026";
5272
+ };
5273
+ var splitTextToLines = (value, maxWidth, fontSize, maxLines = 2) => {
5274
+ const chars = Array.from(normalizeWhitespace(value) || "-");
5275
+ const safeMaxWidth = Math.max(18, maxWidth - DEFAULT_STRIP_TEXT_WIDTH_SAFETY);
5276
+ if (!chars.length) {
5277
+ return { lines: ["-"], truncated: false };
5278
+ }
5279
+ const lines = [];
5280
+ let cursor = 0;
5281
+ let truncated = false;
5282
+ while (cursor < chars.length && lines.length < maxLines) {
5283
+ while (cursor < chars.length && chars[cursor] === " ") {
5284
+ cursor += 1;
5285
+ }
5286
+ if (cursor >= chars.length) {
5287
+ break;
5288
+ }
5289
+ const isLastLine = lines.length === maxLines - 1;
5290
+ const lineChars = [];
5291
+ while (cursor < chars.length) {
5292
+ const nextChars = [...lineChars, chars[cursor]];
5293
+ const remaining = cursor < chars.length - 1;
5294
+ const previewText = nextChars.join("") + (isLastLine && remaining ? "\u2026" : "");
5295
+ if (estimateTextWidth(previewText, fontSize) <= safeMaxWidth || lineChars.length === 0) {
5296
+ lineChars.push(chars[cursor]);
5297
+ cursor += 1;
5298
+ continue;
5299
+ }
5300
+ break;
5301
+ }
5302
+ while (cursor < chars.length && chars[cursor] === " ") {
5303
+ cursor += 1;
5304
+ }
5305
+ if (isLastLine && cursor < chars.length) {
5306
+ lines.push(fitTextWithEllipsis([...lineChars, ...chars.slice(cursor)], safeMaxWidth, fontSize));
5307
+ truncated = true;
5308
+ cursor = chars.length;
5309
+ continue;
5310
+ }
5311
+ lines.push(lineChars.join("").trim() || "-");
5312
+ }
5313
+ return {
5314
+ lines: lines.length > 0 ? lines : ["-"],
5315
+ truncated
5115
5316
  };
5116
- const promptSegment = safeSegments.find((segment) => segment?.kind === "prompt") || null;
5117
- const fixedSegments = safeSegments.filter((segment) => segment?.kind !== "prompt");
5118
- const fixedLayouts = fixedSegments.map((segment) => {
5119
- const fontSize = otherFontSizes[segment.kind] || 17;
5120
- const maxValueWidth = maxValueWidths[segment.kind] || 220;
5121
- const renderValue = fitTextToWidth(segment.rawValue || segment.value || "-", maxValueWidth, fontSize);
5122
- const valueWidth = Math.min(maxValueWidth, estimateTextWidth(renderValue, fontSize) + 8);
5123
- return {
5124
- ...segment,
5125
- fontSize,
5126
- labelWidth,
5127
- renderValue,
5128
- segmentWidth: labelWidth + valueWidth
5129
- };
5317
+ };
5318
+ var resolveMetadataMinValueWidth = (segment) => {
5319
+ switch (segment?.kind) {
5320
+ case "time":
5321
+ return Math.max(148, estimateTextWidth("2026-04-23 21:27:16 UTC+8", DEFAULT_STRIP_VALUE_FONT_SIZE) + 8);
5322
+ case "ip":
5323
+ return Math.max(108, estimateTextWidth("255.255.255.255", DEFAULT_STRIP_VALUE_FONT_SIZE) + 8);
5324
+ case "location":
5325
+ return 76;
5326
+ default:
5327
+ return 56;
5328
+ }
5329
+ };
5330
+ var buildMetadataRowLayout = (segments, contentWidth) => {
5331
+ const safeSegments = Array.isArray(segments) ? segments.filter(Boolean) : [];
5332
+ const naturalRow = buildStripRowLayout(safeSegments);
5333
+ if (!safeSegments.length || naturalRow.totalWidth <= contentWidth) {
5334
+ return naturalRow;
5335
+ }
5336
+ const sectionGap = naturalRow.sectionGap;
5337
+ const totalLabelWidth = naturalRow.layouts.reduce((sum, segment) => sum + segment.labelWidth, 0);
5338
+ const availableValueWidth = Math.max(
5339
+ 72,
5340
+ Math.floor(contentWidth - totalLabelWidth - Math.max(0, safeSegments.length - 1) * sectionGap)
5341
+ );
5342
+ const minValueWidths = naturalRow.layouts.map((segment) => resolveMetadataMinValueWidth(segment));
5343
+ const naturalValueWidths = naturalRow.layouts.map((segment) => segment.valueWidth);
5344
+ const valueWidths = naturalValueWidths.slice();
5345
+ let overflow = naturalRow.totalWidth - contentWidth;
5346
+ const shrinkOrder = ["location", "ip", "time"];
5347
+ shrinkOrder.forEach((kind) => {
5348
+ if (overflow <= 0) return;
5349
+ naturalRow.layouts.forEach((segment, index) => {
5350
+ if (overflow <= 0 || segment.kind !== kind) return;
5351
+ const minWidth = minValueWidths[index];
5352
+ const reducible = Math.max(0, valueWidths[index] - minWidth);
5353
+ if (!reducible) return;
5354
+ const reduction = Math.min(reducible, overflow);
5355
+ valueWidths[index] -= reduction;
5356
+ overflow -= reduction;
5357
+ });
5130
5358
  });
5131
- const fixedWidth = fixedLayouts.reduce((sum, segment) => sum + segment.segmentWidth, 0);
5132
- const totalGapWidth = Math.max(0, safeSegments.length - 1) * sectionGap;
5133
- const promptValueWidth = Math.max(
5134
- 180,
5135
- Math.floor(contentWidth - fixedWidth - totalGapWidth - labelWidth)
5359
+ if (overflow > 0) {
5360
+ const minTotalWidth = minValueWidths.reduce((sum, width) => sum + width, 0);
5361
+ if (minTotalWidth > availableValueWidth) {
5362
+ const flexibleIndexes = naturalRow.layouts.map((segment, index) => ({ segment, index })).filter(({ segment }) => segment.kind !== "time");
5363
+ let extraOverflow = minTotalWidth - availableValueWidth;
5364
+ flexibleIndexes.forEach(({ index }) => {
5365
+ if (extraOverflow <= 0) return;
5366
+ const minFloor = 40;
5367
+ const reducible = Math.max(0, minValueWidths[index] - minFloor);
5368
+ if (!reducible) return;
5369
+ const reduction = Math.min(reducible, extraOverflow);
5370
+ valueWidths[index] -= reduction;
5371
+ extraOverflow -= reduction;
5372
+ });
5373
+ }
5374
+ }
5375
+ return buildStripRowLayout(safeSegments, {
5376
+ valueMaxWidths: valueWidths
5377
+ });
5378
+ };
5379
+ var buildPromptWrapLayout = (segment, contentWidth) => {
5380
+ const label = normalizeWhitespace(segment?.label || "Prompt") || "Prompt";
5381
+ const rawValue = normalizeWhitespace(segment?.rawValue || segment?.value || "-") || "-";
5382
+ const labelFontSize = DEFAULT_STRIP_LABEL_FONT_SIZE;
5383
+ const valueFontSize = DEFAULT_STRIP_VALUE_FONT_SIZE;
5384
+ const labelWidth = Math.max(
5385
+ DEFAULT_STRIP_LABEL_WIDTH,
5386
+ estimateTextWidth(label, labelFontSize) + DEFAULT_STRIP_LABEL_PADDING
5136
5387
  );
5137
- const promptLayout = promptSegment ? {
5138
- ...promptSegment,
5139
- fontSize: promptFontSize,
5388
+ const safeTotalWidth = Math.max(labelWidth + 56, Number(contentWidth) || 0);
5389
+ const valueWidth = Math.max(
5390
+ 56,
5391
+ safeTotalWidth - labelWidth - DEFAULT_STRIP_CONTENT_SAFE_PADDING_X
5392
+ );
5393
+ const result = splitTextToLines(rawValue, valueWidth, valueFontSize, DEFAULT_STRIP_PROMPT_MAX_LINES);
5394
+ const lineHeight = Math.max(18, Math.round(valueFontSize * DEFAULT_STRIP_LINE_HEIGHT_RATIO));
5395
+ const lineWidths = result.lines.map((line) => estimateTextWidth(line, valueFontSize) + DEFAULT_STRIP_VALUE_PADDING);
5396
+ const measuredValueWidth = Math.min(
5397
+ valueWidth,
5398
+ Math.max(56, ...lineWidths)
5399
+ );
5400
+ const rowHeight = Math.round(Math.max(labelFontSize, valueFontSize) * 1.42);
5401
+ return {
5402
+ mode: result.lines.length === 1 ? "single" : "wrapped",
5403
+ label,
5404
+ rawValue,
5405
+ labelFontSize,
5140
5406
  labelWidth,
5141
- renderValue: fitTextToWidth(promptSegment.rawValue || promptSegment.value || "-", promptValueWidth, promptFontSize),
5142
- segmentWidth: labelWidth + promptValueWidth
5143
- } : null;
5144
- return [
5145
- ...promptLayout ? [promptLayout] : [],
5146
- ...fixedLayouts
5147
- ];
5407
+ valueFontSize,
5408
+ valueWidth,
5409
+ lines: result.lines,
5410
+ truncated: result.truncated,
5411
+ lineHeight,
5412
+ blockHeight: result.lines.length === 1 ? rowHeight : Math.max(lineHeight, result.lines.length * lineHeight),
5413
+ totalWidth: labelWidth + measuredValueWidth
5414
+ };
5415
+ };
5416
+ var buildStripLayout = (segments, contentWidth) => {
5417
+ const safeSegments = Array.isArray(segments) ? segments.filter(Boolean) : [];
5418
+ const promptSegment = safeSegments.find((segment) => segment.kind === "prompt") || safeSegments[0] || null;
5419
+ const metadataSegments = safeSegments.filter((segment) => segment !== promptSegment);
5420
+ const zoneGap = DEFAULT_STRIP_ZONE_GAP;
5421
+ let metadataRow = buildMetadataRowLayout(
5422
+ metadataSegments,
5423
+ Math.max(220, contentWidth)
5424
+ );
5425
+ let metadataStartX = Math.max(0, contentWidth - (metadataRow?.totalWidth || 0));
5426
+ let promptAvailableWidth = Math.max(
5427
+ 220,
5428
+ metadataStartX - zoneGap - DEFAULT_STRIP_PROMPT_TRAILING_PADDING
5429
+ );
5430
+ if (promptAvailableWidth < DEFAULT_STRIP_PROMPT_MIN_TOTAL_WIDTH && metadataSegments.length > 0) {
5431
+ const tighterMetadataWidth = Math.max(180, contentWidth - DEFAULT_STRIP_PROMPT_MIN_TOTAL_WIDTH - zoneGap);
5432
+ metadataRow = buildMetadataRowLayout(metadataSegments, tighterMetadataWidth);
5433
+ metadataStartX = Math.max(0, contentWidth - (metadataRow?.totalWidth || 0));
5434
+ promptAvailableWidth = Math.max(
5435
+ 220,
5436
+ metadataStartX - zoneGap - DEFAULT_STRIP_PROMPT_TRAILING_PADDING
5437
+ );
5438
+ }
5439
+ const preferredPromptWidth = Math.min(
5440
+ promptAvailableWidth,
5441
+ Math.max(280, Math.round(contentWidth * DEFAULT_STRIP_PROMPT_PREFERRED_WIDTH_RATIO))
5442
+ );
5443
+ let promptRow = buildPromptWrapLayout(promptSegment, preferredPromptWidth);
5444
+ if (promptRow?.truncated && promptAvailableWidth > preferredPromptWidth + 24) {
5445
+ promptRow = buildPromptWrapLayout(promptSegment, promptAvailableWidth);
5446
+ }
5447
+ const contentHeight = Math.max(
5448
+ promptRow?.blockHeight || 0,
5449
+ metadataRow?.rowHeight || 0,
5450
+ 24
5451
+ );
5452
+ const verticalPadding = contentHeight > (metadataRow?.rowHeight || 0) ? 18 : 16;
5453
+ const stripHeight = Math.max(
5454
+ contentHeight > (metadataRow?.rowHeight || 0) ? DEFAULT_STRIP_WRAPPED_MIN_HEIGHT : DEFAULT_STRIP_ONE_LINE_HEIGHT,
5455
+ Math.ceil(contentHeight + verticalPadding * 2)
5456
+ );
5457
+ return {
5458
+ mode: "zoned",
5459
+ height: stripHeight,
5460
+ contentHeight,
5461
+ promptRow,
5462
+ promptStartX: 0,
5463
+ metadataRow,
5464
+ metadataStartX
5465
+ };
5466
+ };
5467
+ var renderStripRow = (row, options = {}) => {
5468
+ const startX = Number(options.startX) || 0;
5469
+ const centerY = Number(options.centerY) || 0;
5470
+ const fontFamily = String(options.fontFamily || "");
5471
+ const showDividers = options.showDividers !== false;
5472
+ const layouts = Array.isArray(row?.layouts) ? row.layouts : [];
5473
+ if (!layouts.length) {
5474
+ return "";
5475
+ }
5476
+ const rowHeight = Math.max(18, Number(row?.rowHeight) || 18);
5477
+ const dividerHalfHeight = Math.max(12, Math.round(rowHeight * 0.56));
5478
+ const sectionGap = Math.max(0, Number(row?.sectionGap) || 0);
5479
+ const svgSegments = [];
5480
+ let currentX = startX;
5481
+ layouts.forEach((segment, index) => {
5482
+ const label = escapeXml(segment.label || "");
5483
+ const value = escapeXml(segment.renderValue || "-");
5484
+ const labelX = currentX;
5485
+ const valueX = currentX + segment.labelWidth;
5486
+ svgSegments.push(`
5487
+ <text
5488
+ x="${labelX}"
5489
+ y="${centerY}"
5490
+ fill="#111111"
5491
+ fill-opacity="0.54"
5492
+ font-family="${fontFamily}"
5493
+ font-size="${segment.labelFontSize}"
5494
+ font-weight="650"
5495
+ dominant-baseline="middle"
5496
+ >${label}</text>
5497
+ <text
5498
+ x="${valueX}"
5499
+ y="${centerY}"
5500
+ fill="#111111"
5501
+ font-family="${fontFamily}"
5502
+ font-size="${segment.valueFontSize}"
5503
+ font-weight="${segment.kind === "prompt" ? 760 : 710}"
5504
+ dominant-baseline="middle"
5505
+ >${value}</text>
5506
+ `);
5507
+ currentX += segment.segmentWidth;
5508
+ if (showDividers && index < layouts.length - 1) {
5509
+ const lineX = currentX + Math.round(sectionGap / 2);
5510
+ svgSegments.push(`
5511
+ <line
5512
+ x1="${lineX}"
5513
+ y1="${centerY - dividerHalfHeight}"
5514
+ x2="${lineX}"
5515
+ y2="${centerY + dividerHalfHeight}"
5516
+ stroke="#94a3b8"
5517
+ stroke-opacity="0.34"
5518
+ stroke-width="1"
5519
+ />
5520
+ `);
5521
+ currentX += sectionGap;
5522
+ }
5523
+ });
5524
+ return svgSegments.join("");
5525
+ };
5526
+ var renderStripDivider = (x, centerY, height) => {
5527
+ const safeX = Number(x) || 0;
5528
+ const safeCenterY = Number(centerY) || 0;
5529
+ const safeHeight = Math.max(18, Number(height) || 18);
5530
+ const halfHeight = Math.max(12, Math.round(safeHeight * 0.56));
5531
+ return `
5532
+ <line
5533
+ x1="${safeX}"
5534
+ y1="${safeCenterY - halfHeight}"
5535
+ x2="${safeX}"
5536
+ y2="${safeCenterY + halfHeight}"
5537
+ stroke="#94a3b8"
5538
+ stroke-opacity="0.34"
5539
+ stroke-width="1"
5540
+ />
5541
+ `;
5148
5542
  };
5149
5543
  var buildWatermarkifySvg = (meta, imageWidth, imageHeight) => {
5150
5544
  const hasWatermark = meta?.watermark?.enabled !== false && normalizeText(meta?.watermarkText);
@@ -5194,67 +5588,97 @@ var buildWatermarkifySvg = (meta, imageWidth, imageHeight) => {
5194
5588
  parts.push(`<g id="watermarks">${stamps.join("")}</g>`);
5195
5589
  }
5196
5590
  if (hasStrip) {
5197
- const stripHeight = DEFAULT_STRIP_HEIGHT;
5198
- const stripY = height - stripHeight;
5199
5591
  const logoSize = 28;
5200
5592
  const logoX = DEFAULT_STRIP_PADDING_LEFT;
5201
- const logoY = stripY + Math.round((stripHeight - logoSize) / 2);
5202
5593
  const contentStartX = logoX + logoSize + DEFAULT_STRIP_GAP;
5203
5594
  const contentWidth = width - contentStartX - DEFAULT_STRIP_PADDING_RIGHT;
5595
+ const stripLayout = buildStripLayout(meta.stripSegments, contentWidth);
5596
+ const stripHeight = stripLayout.height;
5597
+ const stripY = height - stripHeight;
5598
+ const logoY = Number((stripY + (stripHeight - logoSize) / 2).toFixed(2));
5599
+ let stripContentSvg = "";
5204
5600
  const centerY = stripY + stripHeight / 2 + 1;
5205
- const layouts = buildStripSegmentLayouts(meta.stripSegments, contentWidth);
5206
- const svgSegments = [];
5207
- let currentX = contentStartX;
5208
- layouts.forEach((segment, index) => {
5209
- const label = escapeXml(segment.label || "");
5210
- const value = escapeXml(segment.renderValue || "-");
5211
- const labelX = currentX;
5212
- const valueX = currentX + segment.labelWidth;
5213
- svgSegments.push(`
5601
+ const promptRow = stripLayout.promptRow;
5602
+ const metadataRow = stripLayout.metadataRow;
5603
+ const promptParts = [];
5604
+ const hasPrompt = Boolean(promptRow?.label);
5605
+ const hasMetadata = Array.isArray(metadataRow?.layouts) && metadataRow.layouts.length > 0;
5606
+ const zoneDividerX = hasPrompt && hasMetadata ? contentStartX + stripLayout.metadataStartX - DEFAULT_STRIP_ZONE_GAP / 2 : null;
5607
+ if (promptRow?.mode === "single") {
5608
+ promptParts.push(renderStripRow({
5609
+ layouts: [
5610
+ {
5611
+ kind: "prompt",
5612
+ label: promptRow.label,
5613
+ renderValue: promptRow.lines[0] || "-",
5614
+ labelFontSize: promptRow.labelFontSize,
5615
+ valueFontSize: promptRow.valueFontSize,
5616
+ labelWidth: promptRow.labelWidth,
5617
+ segmentWidth: promptRow.labelWidth + promptRow.valueWidth
5618
+ }
5619
+ ],
5620
+ sectionGap: 0,
5621
+ rowHeight: promptRow.blockHeight
5622
+ }, {
5623
+ startX: contentStartX + stripLayout.promptStartX,
5624
+ centerY,
5625
+ fontFamily,
5626
+ showDividers: false
5627
+ }));
5628
+ } else if (promptRow) {
5629
+ const promptStartX = contentStartX + stripLayout.promptStartX;
5630
+ const labelX = promptStartX;
5631
+ const valueX = promptStartX + promptRow.labelWidth;
5632
+ const blockTop = centerY - promptRow.blockHeight / 2;
5633
+ const labelY = centerY;
5634
+ promptParts.push(`
5214
5635
  <text
5215
5636
  x="${labelX}"
5216
- y="${centerY}"
5217
- fill="#111111"
5218
- fill-opacity="0.42"
5219
- font-family="${fontFamily}"
5220
- font-size="12"
5221
- font-weight="600"
5222
- dominant-baseline="middle"
5223
- >${label}</text>
5224
- <text
5225
- x="${valueX}"
5226
- y="${centerY}"
5637
+ y="${labelY}"
5227
5638
  fill="#111111"
5639
+ fill-opacity="0.54"
5228
5640
  font-family="${fontFamily}"
5229
- font-size="${segment.fontSize}"
5230
- font-weight="${segment.kind === "prompt" ? 760 : 710}"
5641
+ font-size="${promptRow.labelFontSize}"
5642
+ font-weight="650"
5231
5643
  dominant-baseline="middle"
5232
- >${value}</text>
5644
+ >${escapeXml(promptRow.label)}</text>
5233
5645
  `);
5234
- currentX += segment.segmentWidth;
5235
- if (index < layouts.length - 1) {
5236
- const lineX = currentX + 8;
5237
- svgSegments.push(`
5238
- <line
5239
- x1="${lineX}"
5240
- y1="${stripY + 26}"
5241
- x2="${lineX}"
5242
- y2="${stripY + stripHeight - 26}"
5243
- stroke="#94a3b8"
5244
- stroke-opacity="0.34"
5245
- stroke-width="1"
5246
- />
5646
+ promptRow.lines.forEach((line, index) => {
5647
+ const lineY = blockTop + index * promptRow.lineHeight + promptRow.lineHeight / 2;
5648
+ promptParts.push(`
5649
+ <text
5650
+ x="${valueX}"
5651
+ y="${lineY}"
5652
+ fill="#111111"
5653
+ font-family="${fontFamily}"
5654
+ font-size="${promptRow.valueFontSize}"
5655
+ font-weight="760"
5656
+ dominant-baseline="middle"
5657
+ >${escapeXml(line)}</text>
5247
5658
  `);
5248
- currentX += DEFAULT_STRIP_GAP + 16;
5249
- }
5250
- });
5659
+ });
5660
+ }
5661
+ stripContentSvg = [
5662
+ promptParts.join(""),
5663
+ zoneDividerX == null ? "" : renderStripDivider(
5664
+ zoneDividerX,
5665
+ centerY,
5666
+ Math.max(promptRow?.blockHeight || 0, metadataRow?.rowHeight || 0)
5667
+ ),
5668
+ renderStripRow(metadataRow, {
5669
+ startX: contentStartX + stripLayout.metadataStartX,
5670
+ centerY,
5671
+ fontFamily,
5672
+ showDividers: true
5673
+ })
5674
+ ].join("");
5251
5675
  parts.push(`
5252
5676
  <g id="strip">
5253
5677
  <rect x="0" y="${stripY}" width="${width}" height="${stripHeight}" fill="#ffffff" fill-opacity="0.985" />
5254
5678
  <rect x="0" y="${stripY}" width="${width}" height="1" fill="#111111" fill-opacity="0.10" />
5255
5679
  <rect x="${DEFAULT_STRIP_PADDING_LEFT}" y="${stripY}" width="${Math.max(120, Math.round(width * 0.42))}" height="2" fill="url(#stripAccent)" />
5256
5680
  ${meta.stripLogoSrc ? `<image href="${escapeXml(meta.stripLogoSrc)}" x="${logoX}" y="${logoY}" width="${logoSize}" height="${logoSize}" />` : ""}
5257
- ${svgSegments.join("")}
5681
+ ${stripContentSvg}
5258
5682
  </g>
5259
5683
  `);
5260
5684
  }
@@ -5293,7 +5717,7 @@ var watermarkifyScreenshotBuffer = async (buffer, meta) => {
5293
5717
  };
5294
5718
 
5295
5719
  // src/share.js
5296
- var logger11 = createInternalLogger("Share");
5720
+ var logger12 = createInternalLogger("Share");
5297
5721
  var DEFAULT_TIMEOUT_MS2 = 50 * 1e3;
5298
5722
  var DEFAULT_PAYLOAD_SNAPSHOT_MAX_LEN = 500;
5299
5723
  var DEFAULT_POLL_INTERVAL_MS = 120;
@@ -5370,6 +5794,20 @@ var normalizeShare = (share) => {
5370
5794
  xurl: normalizeXurl(source.xurl)
5371
5795
  };
5372
5796
  };
5797
+ var resolveCaptureScreenWatermarkify = (page, optionValue) => {
5798
+ if (optionValue === false) {
5799
+ return normalizeScreenshotWatermarkify(false);
5800
+ }
5801
+ const runtimeQuery = String(page?.[PageRuntimeStateKey]?.query || "").trim();
5802
+ if (optionValue == null || optionValue === true) {
5803
+ return normalizeScreenshotWatermarkify(runtimeQuery ? { query: runtimeQuery } : true);
5804
+ }
5805
+ const optionSource = optionValue && typeof optionValue === "object" ? optionValue : {};
5806
+ return normalizeScreenshotWatermarkify({
5807
+ ...runtimeQuery ? { query: runtimeQuery } : {},
5808
+ ...optionSource
5809
+ });
5810
+ };
5373
5811
  var getByPathSegments = (source, pathSegments) => {
5374
5812
  if (!Array.isArray(pathSegments) || pathSegments.length === 0) return void 0;
5375
5813
  let current = source;
@@ -5416,7 +5854,7 @@ var createDomShareMonitor = async (page, options = {}) => {
5416
5854
  const onMatch = typeof options.onMatch === "function" ? options.onMatch : null;
5417
5855
  const onTelemetry = typeof options.onTelemetry === "function" ? options.onTelemetry : null;
5418
5856
  let matched = false;
5419
- logger11.info(`DOM \u76D1\u542C\u51C6\u5907\u6302\u8F7D: selectors=${toJsonInline(selectors, 120)}, mode=${mode}`);
5857
+ logger12.info(`DOM \u76D1\u542C\u51C6\u5907\u6302\u8F7D: selectors=${toJsonInline(selectors, 120)}, mode=${mode}`);
5420
5858
  const monitor = await Mutation.useMonitor(page, selectors, {
5421
5859
  mode,
5422
5860
  onMutation: (context = {}) => {
@@ -5434,12 +5872,12 @@ ${text}`;
5434
5872
  });
5435
5873
  }
5436
5874
  if (mutationCount <= 5 || mutationCount % 50 === 0) {
5437
- logger11.info(`DOM \u53D8\u5316\u5DF2\u6355\u83B7: mutationCount=${mutationCount}, mutationNodes=${mutationNodes.length}`);
5875
+ logger12.info(`DOM \u53D8\u5316\u5DF2\u6355\u83B7: mutationCount=${mutationCount}, mutationNodes=${mutationNodes.length}`);
5438
5876
  }
5439
5877
  const [candidate] = Utils.parseLinks(rawDom, { prefix }) || [];
5440
5878
  if (!candidate) return;
5441
5879
  matched = true;
5442
- logger11.success("captureLink.domHit", `DOM \u547D\u4E2D\u5206\u4EAB\u94FE\u63A5: mutationCount=${mutationCount}, link=${candidate}`);
5880
+ logger12.success("captureLink.domHit", `DOM \u547D\u4E2D\u5206\u4EAB\u94FE\u63A5: mutationCount=${mutationCount}, link=${candidate}`);
5443
5881
  if (onMatch) {
5444
5882
  onMatch({
5445
5883
  link: candidate,
@@ -5455,7 +5893,7 @@ ${text}`;
5455
5893
  return {
5456
5894
  stop: async () => {
5457
5895
  const result = await monitor.stop();
5458
- logger11.info(`DOM \u76D1\u542C\u5DF2\u505C\u6B62: totalMutations=${result?.totalMutations || 0}`);
5896
+ logger12.info(`DOM \u76D1\u542C\u5DF2\u505C\u6B62: totalMutations=${result?.totalMutations || 0}`);
5459
5897
  return result;
5460
5898
  }
5461
5899
  };
@@ -5495,8 +5933,8 @@ var Share = {
5495
5933
  if (share.mode === "response" && apiMatchers.length === 0) {
5496
5934
  throw new Error("Share.captureLink requires share.xurl[0] api matcher when mode=response");
5497
5935
  }
5498
- logger11.start("captureLink", `mode=${share.mode}, timeoutMs=${timeoutMs}, prefix=${share.prefix}`);
5499
- logger11.info(`captureLink \u914D\u7F6E: xurl=${toJsonInline(share.xurl)}, domMode=${domMode}, domSelectors=${toJsonInline(domSelectors, 120)}`);
5936
+ logger12.start("captureLink", `mode=${share.mode}, timeoutMs=${timeoutMs}, prefix=${share.prefix}`);
5937
+ logger12.info(`captureLink \u914D\u7F6E: xurl=${toJsonInline(share.xurl)}, domMode=${domMode}, domSelectors=${toJsonInline(domSelectors, 120)}`);
5500
5938
  const stats = {
5501
5939
  actionTimedOut: false,
5502
5940
  domMutationCount: 0,
@@ -5521,7 +5959,7 @@ var Share = {
5521
5959
  link: validated,
5522
5960
  payloadText: String(payloadText || "")
5523
5961
  };
5524
- logger11.info(`\u5019\u9009\u94FE\u63A5\u5DF2\u786E\u8BA4: source=${source}, link=${validated}`);
5962
+ logger12.info(`\u5019\u9009\u94FE\u63A5\u5DF2\u786E\u8BA4: source=${source}, link=${validated}`);
5525
5963
  return true;
5526
5964
  };
5527
5965
  const resolveResponseCandidate = (responseText) => {
@@ -5556,7 +5994,7 @@ var Share = {
5556
5994
  try {
5557
5995
  await monitor.stop();
5558
5996
  } catch (error) {
5559
- logger11.warning(`\u505C\u6B62 DOM \u76D1\u542C\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`);
5997
+ logger12.warning(`\u505C\u6B62 DOM \u76D1\u542C\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`);
5560
5998
  }
5561
5999
  };
5562
6000
  const onResponse = async (response) => {
@@ -5569,29 +6007,29 @@ var Share = {
5569
6007
  stats.responseSampleUrls.push(url);
5570
6008
  }
5571
6009
  if (stats.responseObserved <= 5) {
5572
- logger11.info(`\u63A5\u53E3\u54CD\u5E94\u91C7\u6837(${stats.responseObserved}): ${url}`);
6010
+ logger12.info(`\u63A5\u53E3\u54CD\u5E94\u91C7\u6837(${stats.responseObserved}): ${url}`);
5573
6011
  }
5574
6012
  if (!apiMatchers.some((matcher) => url.includes(matcher))) return;
5575
6013
  stats.responseMatched += 1;
5576
6014
  stats.lastMatchedUrl = url;
5577
- logger11.info(`\u63A5\u53E3\u547D\u4E2D\u5339\u914D(${stats.responseMatched}): ${url}`);
6015
+ logger12.info(`\u63A5\u53E3\u547D\u4E2D\u5339\u914D(${stats.responseMatched}): ${url}`);
5578
6016
  const text = await response.text();
5579
6017
  const hit = resolveResponseCandidate(text);
5580
6018
  if (!hit?.link) {
5581
6019
  if (stats.responseMatched <= 3) {
5582
- logger11.info(`\u63A5\u53E3\u89E3\u6790\u5B8C\u6210\u4F46\u672A\u63D0\u53D6\u5230\u5206\u4EAB\u94FE\u63A5: payloadSize=${text.length}`);
6020
+ logger12.info(`\u63A5\u53E3\u89E3\u6790\u5B8C\u6210\u4F46\u672A\u63D0\u53D6\u5230\u5206\u4EAB\u94FE\u63A5: payloadSize=${text.length}`);
5583
6021
  }
5584
6022
  return;
5585
6023
  }
5586
6024
  stats.responseResolved += 1;
5587
- logger11.success("captureLink.responseHit", `\u63A5\u53E3\u547D\u4E2D\u5206\u4EAB\u94FE\u63A5: url=${url}, link=${hit.link}`);
6025
+ logger12.success("captureLink.responseHit", `\u63A5\u53E3\u547D\u4E2D\u5206\u4EAB\u94FE\u63A5: url=${url}, link=${hit.link}`);
5588
6026
  setCandidate("response", hit.link, hit.payloadText);
5589
6027
  } catch (error) {
5590
- logger11.warning(`\u63A5\u53E3\u54CD\u5E94\u5904\u7406\u5F02\u5E38: ${error instanceof Error ? error.message : String(error)}`);
6028
+ logger12.warning(`\u63A5\u53E3\u54CD\u5E94\u5904\u7406\u5F02\u5E38: ${error instanceof Error ? error.message : String(error)}`);
5591
6029
  }
5592
6030
  };
5593
6031
  if (share.mode === "dom") {
5594
- logger11.info("\u5F53\u524D\u4E3A DOM \u6A21\u5F0F\uFF0C\u4EC5\u542F\u7528 DOM \u76D1\u542C");
6032
+ logger12.info("\u5F53\u524D\u4E3A DOM \u6A21\u5F0F\uFF0C\u4EC5\u542F\u7528 DOM \u76D1\u542C");
5595
6033
  domMonitor = await createDomShareMonitor(page, {
5596
6034
  prefix: share.prefix,
5597
6035
  selectors: domSelectors,
@@ -5606,14 +6044,14 @@ var Share = {
5606
6044
  });
5607
6045
  }
5608
6046
  if (share.mode === "response") {
5609
- logger11.info(`\u5F53\u524D\u4E3A\u63A5\u53E3\u6A21\u5F0F\uFF0C\u6302\u8F7D response \u76D1\u542C: apiMatchers=${toJsonInline(apiMatchers, 160)}`);
6047
+ logger12.info(`\u5F53\u524D\u4E3A\u63A5\u53E3\u6A21\u5F0F\uFF0C\u6302\u8F7D response \u76D1\u542C: apiMatchers=${toJsonInline(apiMatchers, 160)}`);
5610
6048
  page.on("response", onResponse);
5611
6049
  }
5612
6050
  const deadline = Date.now() + timeoutMs;
5613
6051
  const getRemainingMs = () => Math.max(0, deadline - Date.now());
5614
6052
  try {
5615
6053
  const actionTimeout = getRemainingMs();
5616
- logger11.start("captureLink.performActions", `\u6267\u884C\u52A8\u4F5C\u9884\u7B97=${actionTimeout}ms`);
6054
+ logger12.start("captureLink.performActions", `\u6267\u884C\u52A8\u4F5C\u9884\u7B97=${actionTimeout}ms`);
5617
6055
  if (actionTimeout > 0) {
5618
6056
  let timer = null;
5619
6057
  let actionError = null;
@@ -5627,21 +6065,21 @@ var Share = {
5627
6065
  const actionResult = await Promise.race([actionPromise, timeoutPromise]);
5628
6066
  if (timer) clearTimeout(timer);
5629
6067
  if (actionResult === "__ACTION_ERROR__") {
5630
- logger11.fail("captureLink.performActions", actionError);
6068
+ logger12.fail("captureLink.performActions", actionError);
5631
6069
  throw actionError;
5632
6070
  }
5633
6071
  if (actionResult === "__ACTION_TIMEOUT__") {
5634
6072
  stats.actionTimedOut = true;
5635
- logger11.warning(`performActions \u5DF2\u8D85\u65F6 (${actionTimeout}ms)\uFF0C\u52A8\u4F5C\u53EF\u80FD\u4ECD\u5728\u5F02\u6B65\u6267\u884C`);
6073
+ logger12.warning(`performActions \u5DF2\u8D85\u65F6 (${actionTimeout}ms)\uFF0C\u52A8\u4F5C\u53EF\u80FD\u4ECD\u5728\u5F02\u6B65\u6267\u884C`);
5636
6074
  } else {
5637
- logger11.success("captureLink.performActions", "\u6267\u884C\u52A8\u4F5C\u5B8C\u6210");
6075
+ logger12.success("captureLink.performActions", "\u6267\u884C\u52A8\u4F5C\u5B8C\u6210");
5638
6076
  }
5639
6077
  }
5640
6078
  let nextProgressLogTs = Date.now() + 3e3;
5641
6079
  while (true) {
5642
6080
  const selected = share.mode === "dom" ? candidates.dom : candidates.response;
5643
6081
  if (selected?.link) {
5644
- logger11.success("captureLink", `\u6355\u83B7\u6210\u529F: source=${share.mode}, link=${selected.link}`);
6082
+ logger12.success("captureLink", `\u6355\u83B7\u6210\u529F: source=${share.mode}, link=${selected.link}`);
5645
6083
  return {
5646
6084
  link: selected.link,
5647
6085
  payloadText: selected.payloadText,
@@ -5653,7 +6091,7 @@ var Share = {
5653
6091
  if (remaining <= 0) break;
5654
6092
  const now = Date.now();
5655
6093
  if (now >= nextProgressLogTs) {
5656
- logger11.info(
6094
+ logger12.info(
5657
6095
  `captureLink \u7B49\u5F85\u4E2D: remaining=${remaining}ms, domMutationCount=${stats.domMutationCount}, responseMatched=${stats.responseMatched}`
5658
6096
  );
5659
6097
  nextProgressLogTs = now + 5e3;
@@ -5661,11 +6099,11 @@ var Share = {
5661
6099
  await (0, import_delay2.default)(Math.max(0, Math.min(DEFAULT_POLL_INTERVAL_MS, remaining)));
5662
6100
  }
5663
6101
  if (share.mode === "response" && stats.responseMatched === 0) {
5664
- logger11.warning(
6102
+ logger12.warning(
5665
6103
  `\u63A5\u53E3\u76D1\u542C\u672A\u547D\u4E2D: apiMatchers=${toJsonInline(apiMatchers, 220)}, \u54CD\u5E94\u6837\u672CURLs=${toJsonInline(stats.responseSampleUrls, 420)}`
5666
6104
  );
5667
6105
  }
5668
- logger11.warning(
6106
+ logger12.warning(
5669
6107
  `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"}`
5670
6108
  );
5671
6109
  return {
@@ -5677,7 +6115,7 @@ var Share = {
5677
6115
  } finally {
5678
6116
  if (share.mode === "response") {
5679
6117
  page.off("response", onResponse);
5680
- logger11.info("response \u76D1\u542C\u5DF2\u5378\u8F7D");
6118
+ logger12.info("response \u76D1\u542C\u5DF2\u5378\u8F7D");
5681
6119
  }
5682
6120
  await stopDomMonitor();
5683
6121
  }
@@ -5693,12 +6131,12 @@ var Share = {
5693
6131
  * @returns {Promise<string>} base64 png
5694
6132
  */
5695
6133
  async captureScreen(page, options = {}) {
5696
- const originalViewport = page.viewportSize();
5697
- const defaultBuffer = Math.round((originalViewport?.height || 1080) / 2);
6134
+ const originalViewport = await resolveCurrentViewportSize(page);
6135
+ const defaultBuffer = Math.round((originalViewport.height || 1080) / 2);
5698
6136
  const buffer = options.buffer ?? defaultBuffer;
5699
6137
  const restore = options.restore ?? false;
5700
6138
  const maxHeight = options.maxHeight ?? 8e3;
5701
- const screenshotWatermarkify = normalizeScreenshotWatermarkify(options.watermarkify ?? true);
6139
+ const screenshotWatermarkify = resolveCaptureScreenWatermarkify(page, options.watermarkify);
5702
6140
  try {
5703
6141
  const maxScrollHeight = await page.evaluate(() => {
5704
6142
  let maxHeight2 = document.body.scrollHeight;
@@ -5722,7 +6160,7 @@ var Share = {
5722
6160
  });
5723
6161
  const targetHeight = Math.min(maxScrollHeight + buffer, maxHeight);
5724
6162
  await page.setViewportSize({
5725
- width: originalViewport?.width || 1280,
6163
+ width: originalViewport.width,
5726
6164
  height: targetHeight
5727
6165
  });
5728
6166
  await (0, import_delay2.default)(1e3);
@@ -5754,9 +6192,7 @@ var Share = {
5754
6192
  el.classList.remove("__pk_expanded__");
5755
6193
  });
5756
6194
  });
5757
- if (originalViewport) {
5758
- await page.setViewportSize(originalViewport);
5759
- }
6195
+ await page.setViewportSize(originalViewport);
5760
6196
  }
5761
6197
  }
5762
6198
  }