postboy-tui 1.3.8 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +857 -326
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -67534,7 +67534,7 @@ var useFocusManager = () => {
67534
67534
  };
67535
67535
  var use_focus_manager_default = useFocusManager;
67536
67536
  // src/ui/app/ui.tsx
67537
- var import_react34 = __toESM(require_react(), 1);
67537
+ var import_react35 = __toESM(require_react(), 1);
67538
67538
 
67539
67539
  // src/utils/history.ts
67540
67540
  import { promises as fs2 } from "fs";
@@ -67626,7 +67626,7 @@ var historyManager = new HistoryManager;
67626
67626
  import http from "http";
67627
67627
  import https from "https";
67628
67628
  import { URL as URL2 } from "url";
67629
- function sendRequest({ method, url, headers = {}, body }) {
67629
+ function sendRequest({ method, url, headers = {}, body, onProgress }) {
67630
67630
  return new Promise((resolve, reject) => {
67631
67631
  const urlObj = new URL2(url);
67632
67632
  const isHttps = urlObj.protocol === "https:";
@@ -67649,12 +67649,26 @@ function sendRequest({ method, url, headers = {}, body }) {
67649
67649
  const req = reqModule.request(options, (res) => {
67650
67650
  timings.ttfb = performance.now();
67651
67651
  let data = "";
67652
+ let bytesReceived = 0;
67653
+ const totalBytes = parseInt(res.headers["content-length"] || "0", 10);
67654
+ const downloadStartTime = performance.now();
67652
67655
  res.on("data", (chunk) => {
67653
67656
  data += chunk;
67657
+ bytesReceived += Buffer.byteLength(chunk);
67658
+ if (onProgress) {
67659
+ const elapsed = (performance.now() - downloadStartTime) / 1000;
67660
+ const speed = elapsed > 0 ? bytesReceived / elapsed : 0;
67661
+ onProgress({
67662
+ bytesReceived,
67663
+ totalBytes,
67664
+ speed,
67665
+ elapsed
67666
+ });
67667
+ }
67654
67668
  });
67655
67669
  res.on("end", () => {
67656
67670
  timings.end = performance.now();
67657
- const contentLength = parseInt(res.headers["content-length"] || "0", 10) || Buffer.byteLength(data, "utf8");
67671
+ const contentLength = totalBytes || Buffer.byteLength(data, "utf8");
67658
67672
  const metrics = {
67659
67673
  dnsLookup: timings.dnsLookup - timings.start,
67660
67674
  tcpConnection: timings.tcpConnection - timings.dnsLookup,
@@ -69367,275 +69381,180 @@ var JsonSyntaxHighlight = import_react31.default.memo(({ jsonString, theme }) =>
69367
69381
 
69368
69382
  // src/ui/app/components/responsepanel.tsx
69369
69383
  var import_react32 = __toESM(require_react(), 1);
69370
-
69371
- // src/ui/app/components/metricspanel.tsx
69372
69384
  var jsx_dev_runtime10 = __toESM(require_jsx_dev_runtime(), 1);
69373
- var MetricBar = ({ label, value, maxValue, color, theme }) => {
69374
- const barWidth = 40;
69375
- const safeValue = Math.max(0, value);
69376
- const filledWidth = Math.max(0, Math.min(barWidth, maxValue > 0 ? Math.round(safeValue / maxValue * barWidth) : 0));
69377
- const emptyWidth = barWidth - filledWidth;
69378
- const bar = "█".repeat(filledWidth) + "░".repeat(emptyWidth);
69385
+ var StatusBadge = ({ status, statusText, theme }) => {
69386
+ const [glowFrame, setGlowFrame] = import_react32.useState(0);
69387
+ const statusNum = parseInt(status, 10);
69388
+ import_react32.useEffect(() => {
69389
+ const interval = setInterval(() => {
69390
+ setGlowFrame((prev) => (prev + 1) % 4);
69391
+ }, 300);
69392
+ return () => clearInterval(interval);
69393
+ }, []);
69394
+ const getStatusIcon = () => {
69395
+ if (statusNum >= 200 && statusNum < 300)
69396
+ return ["✓", "✔", "☑", "✓"][glowFrame];
69397
+ if (statusNum >= 300 && statusNum < 400)
69398
+ return ["↪", "→", "⇒", "↪"][glowFrame];
69399
+ if (statusNum >= 400 && statusNum < 500)
69400
+ return ["⚠", "⚡", "⚠", "⚡"][glowFrame];
69401
+ if (statusNum >= 500)
69402
+ return ["✗", "✘", "☒", "✗"][glowFrame];
69403
+ return "●";
69404
+ };
69405
+ const getBadgeStyle = () => {
69406
+ if (statusNum >= 200 && statusNum < 300)
69407
+ return { bg: theme.colors.success, icon: getStatusIcon(), label: "SUCCESS" };
69408
+ if (statusNum >= 300 && statusNum < 400)
69409
+ return { bg: theme.colors.accent, icon: getStatusIcon(), label: "REDIRECT" };
69410
+ if (statusNum >= 400 && statusNum < 500)
69411
+ return { bg: theme.colors.error, icon: getStatusIcon(), label: "CLIENT ERR" };
69412
+ if (statusNum >= 500)
69413
+ return { bg: theme.colors.error, icon: getStatusIcon(), label: "SERVER ERR" };
69414
+ return { bg: theme.colors.muted, icon: "●", label: "UNKNOWN" };
69415
+ };
69416
+ const style = getBadgeStyle();
69417
+ const glowChars = ["░", "▒", "▓", "▒"];
69418
+ if (!status || status === "Error") {
69419
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69420
+ children: [
69421
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69422
+ color: theme.colors.error,
69423
+ bold: true,
69424
+ children: "✗ "
69425
+ }, undefined, false, undefined, this),
69426
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69427
+ color: theme.colors.error,
69428
+ children: statusText || "Error"
69429
+ }, undefined, false, undefined, this)
69430
+ ]
69431
+ }, undefined, true, undefined, this);
69432
+ }
69379
69433
  return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69380
- marginY: 0,
69381
69434
  children: [
69382
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69383
- width: 18,
69384
- children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69385
- color: theme.colors.muted,
69386
- children: label
69387
- }, undefined, false, undefined, this)
69435
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69436
+ color: style.bg,
69437
+ dimColor: true,
69438
+ children: glowChars[glowFrame]
69388
69439
  }, undefined, false, undefined, this),
69389
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69390
- width: barWidth + 2,
69391
- children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69392
- color,
69393
- children: bar
69394
- }, undefined, false, undefined, this)
69440
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69441
+ backgroundColor: style.bg,
69442
+ color: theme.colors.white,
69443
+ bold: true,
69444
+ children: [
69445
+ " ",
69446
+ style.icon,
69447
+ " ",
69448
+ status,
69449
+ " "
69450
+ ]
69451
+ }, undefined, true, undefined, this),
69452
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69453
+ color: style.bg,
69454
+ dimColor: true,
69455
+ children: glowChars[glowFrame]
69395
69456
  }, undefined, false, undefined, this),
69396
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69397
- width: 12,
69398
- justifyContent: "flex-end",
69399
- children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69400
- color: theme.colors.white,
69401
- bold: true,
69402
- children: [
69403
- safeValue.toFixed(1),
69404
- "ms"
69405
- ]
69406
- }, undefined, true, undefined, this)
69457
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69458
+ color: theme.colors.muted,
69459
+ children: ""
69460
+ }, undefined, false, undefined, this),
69461
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69462
+ color: style.bg,
69463
+ bold: true,
69464
+ children: style.label
69465
+ }, undefined, false, undefined, this),
69466
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69467
+ color: theme.colors.muted,
69468
+ children: " │ "
69469
+ }, undefined, false, undefined, this),
69470
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69471
+ color: theme.colors.white,
69472
+ children: statusText
69407
69473
  }, undefined, false, undefined, this)
69408
69474
  ]
69409
69475
  }, undefined, true, undefined, this);
69410
69476
  };
69411
- var formatBytes = (bytes) => {
69412
- if (bytes === 0)
69413
- return "0 B";
69414
- const k = 1024;
69415
- const sizes = ["B", "KB", "MB", "GB"];
69416
- const i = Math.floor(Math.log(bytes) / Math.log(k));
69417
- return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
69418
- };
69419
- var MetricsPanel = ({ metrics, theme }) => {
69420
- if (!metrics) {
69421
- return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69422
- flexDirection: "column",
69423
- padding: 1,
69424
- children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69425
- color: theme.colors.muted,
69426
- children: "No metrics available. Send a request first."
69427
- }, undefined, false, undefined, this)
69428
- }, undefined, false, undefined, this);
69429
- }
69430
- const maxTime = Math.max(Math.max(0, metrics.dnsLookup), Math.max(0, metrics.tcpConnection), Math.max(0, metrics.tlsHandshake), Math.max(0, metrics.ttfb), Math.max(0, metrics.contentDownload), 1);
69477
+ var TypewriterText = ({ text, theme, speed = 2 }) => {
69478
+ const [displayedLength, setDisplayedLength] = import_react32.useState(0);
69479
+ const [cursorVisible, setCursorVisible] = import_react32.useState(true);
69480
+ const previousTextRef = import_react32.useRef("");
69481
+ import_react32.useEffect(() => {
69482
+ if (text !== previousTextRef.current) {
69483
+ setDisplayedLength(0);
69484
+ previousTextRef.current = text;
69485
+ }
69486
+ }, [text]);
69487
+ import_react32.useEffect(() => {
69488
+ if (displayedLength < text.length) {
69489
+ const charsToAdd = Math.min(speed, text.length - displayedLength);
69490
+ const timeout = setTimeout(() => {
69491
+ setDisplayedLength((prev) => Math.min(prev + charsToAdd, text.length));
69492
+ }, 5);
69493
+ return () => clearTimeout(timeout);
69494
+ }
69495
+ }, [displayedLength, text, speed]);
69496
+ import_react32.useEffect(() => {
69497
+ const interval = setInterval(() => {
69498
+ setCursorVisible((prev) => !prev);
69499
+ }, 400);
69500
+ return () => clearInterval(interval);
69501
+ }, []);
69502
+ const displayedText = text.slice(0, displayedLength);
69503
+ const isComplete = displayedLength >= text.length;
69504
+ const cursor = !isComplete && cursorVisible ? "█" : !isComplete ? " " : "";
69431
69505
  return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69432
69506
  flexDirection: "column",
69433
- paddingX: 1,
69434
69507
  children: [
69435
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69436
- marginBottom: 1,
69437
- flexDirection: "column",
69438
- children: [
69439
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69440
- color: theme.colors.accent,
69441
- bold: true,
69442
- children: "⚡ Performance Breakdown"
69443
- }, undefined, false, undefined, this),
69444
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69445
- color: theme.colors.muted,
69446
- children: "────────────────────────────────────────────────────────────"
69447
- }, undefined, false, undefined, this)
69448
- ]
69449
- }, undefined, true, undefined, this),
69450
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69451
- flexDirection: "column",
69452
- gap: 0,
69453
- children: [
69454
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(MetricBar, {
69455
- label: "\uD83D\uDD0D DNS Lookup",
69456
- value: metrics.dnsLookup,
69457
- maxValue: maxTime,
69458
- color: theme.colors.cool,
69459
- theme
69460
- }, undefined, false, undefined, this),
69461
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(MetricBar, {
69462
- label: "\uD83D\uDD0C TCP Connect",
69463
- value: metrics.tcpConnection,
69464
- maxValue: maxTime,
69465
- color: theme.colors.success,
69466
- theme
69467
- }, undefined, false, undefined, this),
69468
- metrics.tlsHandshake > 0 && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(MetricBar, {
69469
- label: "\uD83D\uDD10 TLS Handshake",
69470
- value: metrics.tlsHandshake,
69471
- maxValue: maxTime,
69472
- color: theme.colors.secondary,
69473
- theme
69474
- }, undefined, false, undefined, this),
69475
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(MetricBar, {
69476
- label: "⏱️ TTFB",
69477
- value: metrics.ttfb,
69478
- maxValue: maxTime,
69479
- color: theme.colors.accent,
69480
- theme
69481
- }, undefined, false, undefined, this),
69482
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(MetricBar, {
69483
- label: "\uD83D\uDCE5 Download",
69484
- value: metrics.contentDownload,
69485
- maxValue: maxTime,
69486
- color: theme.colors.primary,
69487
- theme
69488
- }, undefined, false, undefined, this)
69489
- ]
69490
- }, undefined, true, undefined, this),
69491
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69492
- marginTop: 1,
69493
- flexDirection: "column",
69494
- children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69495
- color: theme.colors.muted,
69496
- children: "────────────────────────────────────────────────────────────"
69497
- }, undefined, false, undefined, this)
69508
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(JsonSyntaxHighlight, {
69509
+ jsonString: displayedText,
69510
+ theme
69498
69511
  }, undefined, false, undefined, this),
69499
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69500
- marginTop: 1,
69501
- flexDirection: "column",
69502
- gap: 0,
69503
- children: [
69504
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69505
- children: [
69506
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69507
- width: 18,
69508
- children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69509
- color: theme.colors.accent,
69510
- bold: true,
69511
- children: "\uD83D\uDCCA Total Time"
69512
- }, undefined, false, undefined, this)
69513
- }, undefined, false, undefined, this),
69514
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69515
- color: theme.colors.white,
69516
- bold: true,
69517
- children: [
69518
- metrics.total.toFixed(2),
69519
- " ms"
69520
- ]
69521
- }, undefined, true, undefined, this)
69522
- ]
69523
- }, undefined, true, undefined, this),
69524
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69525
- children: [
69526
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69527
- width: 18,
69528
- children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69529
- color: theme.colors.accent,
69530
- bold: true,
69531
- children: "\uD83D\uDCE6 Size"
69532
- }, undefined, false, undefined, this)
69533
- }, undefined, false, undefined, this),
69534
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69535
- color: theme.colors.white,
69536
- bold: true,
69537
- children: formatBytes(metrics.contentLength)
69538
- }, undefined, false, undefined, this)
69539
- ]
69540
- }, undefined, true, undefined, this),
69541
- metrics.contentLength > 0 && metrics.contentDownload > 0 && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69542
- children: [
69543
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69544
- width: 18,
69545
- children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69546
- color: theme.colors.accent,
69547
- bold: true,
69548
- children: "\uD83D\uDE80 Speed"
69549
- }, undefined, false, undefined, this)
69550
- }, undefined, false, undefined, this),
69551
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69552
- color: theme.colors.success,
69553
- bold: true,
69554
- children: [
69555
- formatBytes(metrics.contentLength / (metrics.contentDownload / 1000)),
69556
- "/s"
69557
- ]
69558
- }, undefined, true, undefined, this)
69559
- ]
69560
- }, undefined, true, undefined, this)
69561
- ]
69562
- }, undefined, true, undefined, this),
69563
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69564
- marginTop: 1,
69565
- flexDirection: "column",
69566
- children: [
69567
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69568
- color: theme.colors.muted,
69569
- children: "────────────────────────────────────────────────────────────"
69570
- }, undefined, false, undefined, this),
69571
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69572
- marginTop: 1,
69573
- children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69574
- color: theme.colors.muted,
69575
- dimColor: true,
69576
- children: "TTFB = Time To First Byte (DNS + TCP + TLS + Server Processing)"
69577
- }, undefined, false, undefined, this)
69578
- }, undefined, false, undefined, this)
69579
- ]
69580
- }, undefined, true, undefined, this)
69512
+ !isComplete && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69513
+ color: theme.colors.accent,
69514
+ children: cursor
69515
+ }, undefined, false, undefined, this)
69581
69516
  ]
69582
69517
  }, undefined, true, undefined, this);
69583
69518
  };
69584
-
69585
- // src/ui/app/components/responsepanel.tsx
69586
- var jsx_dev_runtime11 = __toESM(require_jsx_dev_runtime(), 1);
69587
- var ResponsePanel = import_react32.default.memo(({ response, theme, metrics = null }) => {
69519
+ var ResponsePanel = import_react32.default.memo(({ response, theme }) => {
69588
69520
  const [activeTab, setActiveTab] = import_react32.useState("body");
69589
- const tabs = [{ name: "body", label: "Body" }, { name: "headers", label: "Headers" }, { name: "metrics", label: "⚡ Metrics" }];
69590
- return /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69521
+ const tabs = [{ name: "body", label: "Body" }, { name: "headers", label: "Headers" }];
69522
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69591
69523
  flexDirection: "column",
69592
69524
  flexGrow: 1,
69593
69525
  children: [
69594
- /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69526
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69595
69527
  marginBottom: 1,
69596
- children: [
69597
- /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69598
- width: 8,
69599
- children: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69600
- color: theme.colors.primary,
69601
- children: "STATUS:"
69602
- }, undefined, false, undefined, this)
69603
- }, undefined, false, undefined, this),
69604
- /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69605
- color: getStatusColor(response.status, theme),
69606
- bold: true,
69607
- children: [
69608
- response.status,
69609
- " ",
69610
- response.statustext
69611
- ]
69612
- }, undefined, true, undefined, this)
69613
- ]
69614
- }, undefined, true, undefined, this),
69615
- /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Tabs, {
69528
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(StatusBadge, {
69529
+ status: response.status,
69530
+ statusText: response.statustext,
69531
+ theme
69532
+ }, undefined, false, undefined, this)
69533
+ }, undefined, false, undefined, this),
69534
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Tabs, {
69616
69535
  tabs,
69617
69536
  activeTab,
69618
69537
  onChange: setActiveTab,
69619
69538
  theme
69620
69539
  }, undefined, false, undefined, this),
69621
- /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69540
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69622
69541
  marginTop: 1,
69623
69542
  flexGrow: 1,
69624
69543
  children: [
69625
- activeTab === "headers" && /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(ScrollableBox, {
69626
- children: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69544
+ activeTab === "headers" && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ScrollableBox, {
69545
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69627
69546
  flexDirection: "column",
69628
- children: Object.entries(JSON.parse(response.headers || "{}")).map(([key, value]) => /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69547
+ children: Object.entries(JSON.parse(response.headers || "{}")).map(([key, value]) => /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69629
69548
  children: [
69630
- /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69549
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69631
69550
  color: theme.colors.accent,
69632
69551
  children: key
69633
69552
  }, undefined, false, undefined, this),
69634
- /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69553
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69635
69554
  color: theme.colors.muted,
69636
69555
  children: ": "
69637
69556
  }, undefined, false, undefined, this),
69638
- /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69557
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69639
69558
  color: theme.colors.success,
69640
69559
  children: String(value)
69641
69560
  }, undefined, false, undefined, this)
@@ -69643,21 +69562,16 @@ var ResponsePanel = import_react32.default.memo(({ response, theme, metrics = nu
69643
69562
  }, key, true, undefined, this))
69644
69563
  }, undefined, false, undefined, this)
69645
69564
  }, undefined, false, undefined, this),
69646
- activeTab === "body" && /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(ScrollableBox, {
69647
- children: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69565
+ activeTab === "body" && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ScrollableBox, {
69566
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
69648
69567
  flexDirection: "column",
69649
69568
  flexGrow: 1,
69650
- children: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(JsonSyntaxHighlight, {
69651
- jsonString: response.body,
69652
- theme
69569
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(TypewriterText, {
69570
+ text: response.body,
69571
+ theme,
69572
+ speed: 50
69653
69573
  }, undefined, false, undefined, this)
69654
69574
  }, undefined, false, undefined, this)
69655
- }, undefined, false, undefined, this),
69656
- activeTab === "metrics" && /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(ScrollableBox, {
69657
- children: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(MetricsPanel, {
69658
- metrics,
69659
- theme
69660
- }, undefined, false, undefined, this)
69661
69575
  }, undefined, false, undefined, this)
69662
69576
  ]
69663
69577
  }, undefined, true, undefined, this)
@@ -69754,7 +69668,7 @@ var saveToFile = async (content, filePath) => {
69754
69668
  };
69755
69669
 
69756
69670
  // src/ui/app/components/exportdialog.tsx
69757
- var jsx_dev_runtime12 = __toESM(require_jsx_dev_runtime(), 1);
69671
+ var jsx_dev_runtime11 = __toESM(require_jsx_dev_runtime(), 1);
69758
69672
  var EXPORT_DIR = `${process.env.HOME}/.postboy/exports`;
69759
69673
  var ExportDialog = ({ request, onClose, theme }) => {
69760
69674
  const [format, setFormat] = import_react33.useState("curl");
@@ -69834,58 +69748,58 @@ ${finalPath}` : "Failed to save file");
69834
69748
  }
69835
69749
  });
69836
69750
  const preview = getExportContent();
69837
- return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
69751
+ return /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69838
69752
  flexDirection: "column",
69839
69753
  borderStyle: "double",
69840
69754
  borderColor: theme.accent,
69841
69755
  padding: 1,
69842
69756
  children: [
69843
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
69757
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69844
69758
  marginBottom: 1,
69845
69759
  children: [
69846
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
69760
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69847
69761
  color: theme.accent,
69848
69762
  bold: true,
69849
69763
  children: "Export Request"
69850
69764
  }, undefined, false, undefined, this),
69851
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
69765
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69852
69766
  color: theme.muted,
69853
69767
  children: " (Tab: switch, ←→: select, Enter: confirm, Esc: cancel)"
69854
69768
  }, undefined, false, undefined, this)
69855
69769
  ]
69856
69770
  }, undefined, true, undefined, this),
69857
- message ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
69771
+ message ? /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69858
69772
  padding: 1,
69859
69773
  flexDirection: "column",
69860
69774
  borderStyle: "round",
69861
69775
  borderColor: theme.success,
69862
69776
  children: message.split(`
69863
- `).map((line, i) => /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
69777
+ `).map((line, i) => /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69864
69778
  color: theme.success,
69865
69779
  bold: true,
69866
69780
  children: line
69867
69781
  }, i, false, undefined, this))
69868
- }, undefined, false, undefined, this) : showSavePrompt ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
69782
+ }, undefined, false, undefined, this) : showSavePrompt ? /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69869
69783
  flexDirection: "column",
69870
69784
  gap: 1,
69871
69785
  children: [
69872
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
69786
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69873
69787
  children: [
69874
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
69788
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69875
69789
  color: theme.primary,
69876
69790
  children: "File path: "
69877
69791
  }, undefined, false, undefined, this),
69878
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
69792
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69879
69793
  borderStyle: "round",
69880
69794
  borderColor: theme.accent,
69881
69795
  paddingX: 1,
69882
69796
  flexGrow: 1,
69883
69797
  children: [
69884
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
69798
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69885
69799
  color: theme.white,
69886
69800
  children: filePath
69887
69801
  }, undefined, false, undefined, this),
69888
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
69802
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69889
69803
  color: theme.accent,
69890
69804
  children: "▌"
69891
69805
  }, undefined, false, undefined, this)
@@ -69893,7 +69807,7 @@ ${finalPath}` : "Failed to save file");
69893
69807
  }, undefined, true, undefined, this)
69894
69808
  ]
69895
69809
  }, undefined, true, undefined, this),
69896
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
69810
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69897
69811
  color: theme.muted,
69898
69812
  children: [
69899
69813
  "Extension .",
@@ -69902,32 +69816,32 @@ ${finalPath}` : "Failed to save file");
69902
69816
  ]
69903
69817
  }, undefined, true, undefined, this)
69904
69818
  ]
69905
- }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
69819
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69906
69820
  flexDirection: "column",
69907
69821
  gap: 1,
69908
69822
  children: [
69909
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
69823
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69910
69824
  gap: 2,
69911
69825
  children: [
69912
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
69826
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69913
69827
  color: theme.primary,
69914
69828
  children: "Format: "
69915
69829
  }, undefined, false, undefined, this),
69916
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
69830
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69917
69831
  borderStyle: "round",
69918
69832
  borderColor: activeField === "format" && format === "curl" ? theme.accent : theme.muted,
69919
69833
  paddingX: 1,
69920
- children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
69834
+ children: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69921
69835
  color: format === "curl" ? theme.accent : theme.muted,
69922
69836
  bold: format === "curl",
69923
69837
  children: "cURL"
69924
69838
  }, undefined, false, undefined, this)
69925
69839
  }, undefined, false, undefined, this),
69926
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
69840
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69927
69841
  borderStyle: "round",
69928
69842
  borderColor: activeField === "format" && format === "fetch" ? theme.accent : theme.muted,
69929
69843
  paddingX: 1,
69930
- children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
69844
+ children: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69931
69845
  color: format === "fetch" ? theme.accent : theme.muted,
69932
69846
  bold: format === "fetch",
69933
69847
  children: "Fetch"
@@ -69935,28 +69849,28 @@ ${finalPath}` : "Failed to save file");
69935
69849
  }, undefined, false, undefined, this)
69936
69850
  ]
69937
69851
  }, undefined, true, undefined, this),
69938
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
69852
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69939
69853
  gap: 2,
69940
69854
  children: [
69941
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
69855
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69942
69856
  color: theme.primary,
69943
69857
  children: "Action: "
69944
69858
  }, undefined, false, undefined, this),
69945
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
69859
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69946
69860
  borderStyle: "round",
69947
69861
  borderColor: activeField === "action" && action === "copy" ? theme.accent : theme.muted,
69948
69862
  paddingX: 1,
69949
- children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
69863
+ children: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69950
69864
  color: action === "copy" ? theme.accent : theme.muted,
69951
69865
  bold: action === "copy",
69952
69866
  children: "Copy to Clipboard"
69953
69867
  }, undefined, false, undefined, this)
69954
69868
  }, undefined, false, undefined, this),
69955
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
69869
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69956
69870
  borderStyle: "round",
69957
69871
  borderColor: activeField === "action" && action === "save" ? theme.accent : theme.muted,
69958
69872
  paddingX: 1,
69959
- children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
69873
+ children: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69960
69874
  color: action === "save" ? theme.accent : theme.muted,
69961
69875
  bold: action === "save",
69962
69876
  children: "Save to File"
@@ -69964,21 +69878,21 @@ ${finalPath}` : "Failed to save file");
69964
69878
  }, undefined, false, undefined, this)
69965
69879
  ]
69966
69880
  }, undefined, true, undefined, this),
69967
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
69881
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69968
69882
  flexDirection: "column",
69969
69883
  marginTop: 1,
69970
69884
  children: [
69971
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
69885
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69972
69886
  color: theme.primary,
69973
69887
  children: "Preview:"
69974
69888
  }, undefined, false, undefined, this),
69975
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
69889
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
69976
69890
  borderStyle: "round",
69977
69891
  borderColor: theme.muted,
69978
69892
  padding: 1,
69979
69893
  flexDirection: "column",
69980
69894
  children: preview.split(`
69981
- `).map((line, i) => /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
69895
+ `).map((line, i) => /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
69982
69896
  color: theme.white,
69983
69897
  children: line
69984
69898
  }, i, false, undefined, this))
@@ -70093,32 +70007,617 @@ class ThemeManager {
70093
70007
  }
70094
70008
  var themeManager = new ThemeManager;
70095
70009
 
70096
- // src/ui/app/ui.tsx
70097
- var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
70098
- var SendButton = ({ onPress, loading, theme }) => {
70099
- const { isFocused } = use_focus_default();
70100
- use_input_default((_, key) => {
70101
- if (isFocused && key.return)
70102
- onPress();
70103
- });
70104
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
70105
- borderStyle: "round",
70106
- paddingX: 2,
70107
- borderTopDimColor: true,
70108
- borderColor: isFocused ? theme.accent : theme.primary,
70109
- children: loading ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Spinner, {
70110
- theme
70111
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
70112
- bold: true,
70113
- color: isFocused ? theme.accent : theme.white,
70114
- children: "\uD83D\uDE80 Send"
70115
- }, undefined, false, undefined, this)
70116
- }, undefined, false, undefined, this);
70117
- };
70118
- var HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"];
70119
- var RequestPanel = import_react34.default.memo(({ request, onMethodChange, onUrlChange, onHeadersChange, onBodyChange, onSend, loading, theme, historyUrls = [], onInputFocus }) => /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
70120
- flexDirection: "column",
70121
- gap: 1,
70010
+ // src/ui/app/components/metricspanel.tsx
70011
+ var import_react34 = __toESM(require_react(), 1);
70012
+ var jsx_dev_runtime12 = __toESM(require_jsx_dev_runtime(), 1);
70013
+ var BAR_CHARS = ["▱", "▰"];
70014
+ var GLOW_FRAMES = ["◐", "◓", "◑", "◒"];
70015
+ var SPARK_CHARS = ["⚡", "✦", "✧", "⚡", "★", "☆"];
70016
+ var MetricBar = ({ label, value, maxValue, color, theme, delay }) => {
70017
+ const barWidth = 30;
70018
+ const safeValue = Math.max(0, value);
70019
+ const targetFilled = Math.max(0, Math.min(barWidth, maxValue > 0 ? Math.round(safeValue / maxValue * barWidth) : 0));
70020
+ const [animatedFilled, setAnimatedFilled] = import_react34.useState(0);
70021
+ const [glowFrame, setGlowFrame] = import_react34.useState(0);
70022
+ const [sparkle, setSparkle] = import_react34.useState(false);
70023
+ import_react34.useEffect(() => {
70024
+ const timeout = setTimeout(() => {
70025
+ if (animatedFilled < targetFilled) {
70026
+ const interval = setInterval(() => {
70027
+ setAnimatedFilled((prev) => {
70028
+ if (prev >= targetFilled) {
70029
+ clearInterval(interval);
70030
+ setSparkle(true);
70031
+ setTimeout(() => setSparkle(false), 300);
70032
+ return targetFilled;
70033
+ }
70034
+ return prev + 1;
70035
+ });
70036
+ }, 20);
70037
+ return () => clearInterval(interval);
70038
+ }
70039
+ }, delay);
70040
+ return () => clearTimeout(timeout);
70041
+ }, [targetFilled, delay]);
70042
+ import_react34.useEffect(() => {
70043
+ const interval = setInterval(() => {
70044
+ setGlowFrame((prev) => (prev + 1) % GLOW_FRAMES.length);
70045
+ }, 150);
70046
+ return () => clearInterval(interval);
70047
+ }, []);
70048
+ const filledBar = BAR_CHARS[1].repeat(animatedFilled);
70049
+ const emptyBar = BAR_CHARS[0].repeat(barWidth - animatedFilled);
70050
+ const indicator = animatedFilled > 0 && animatedFilled < targetFilled ? GLOW_FRAMES[glowFrame] : sparkle ? SPARK_CHARS[Math.floor(Math.random() * SPARK_CHARS.length)] : "";
70051
+ return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70052
+ marginY: 0,
70053
+ children: [
70054
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70055
+ width: 16,
70056
+ children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70057
+ color,
70058
+ bold: true,
70059
+ children: label
70060
+ }, undefined, false, undefined, this)
70061
+ }, undefined, false, undefined, this),
70062
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70063
+ color: theme.colors.muted,
70064
+ children: "│"
70065
+ }, undefined, false, undefined, this),
70066
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70067
+ width: barWidth + 3,
70068
+ children: [
70069
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70070
+ color,
70071
+ children: filledBar
70072
+ }, undefined, false, undefined, this),
70073
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70074
+ color,
70075
+ bold: true,
70076
+ children: indicator
70077
+ }, undefined, false, undefined, this),
70078
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70079
+ color: theme.colors.muted,
70080
+ dimColor: true,
70081
+ children: emptyBar
70082
+ }, undefined, false, undefined, this)
70083
+ ]
70084
+ }, undefined, true, undefined, this),
70085
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70086
+ color: theme.colors.muted,
70087
+ children: "│"
70088
+ }, undefined, false, undefined, this),
70089
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70090
+ width: 10,
70091
+ justifyContent: "flex-end",
70092
+ children: [
70093
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70094
+ color: theme.colors.white,
70095
+ bold: true,
70096
+ children: safeValue.toFixed(1)
70097
+ }, undefined, false, undefined, this),
70098
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70099
+ color: theme.colors.muted,
70100
+ dimColor: true,
70101
+ children: "ms"
70102
+ }, undefined, false, undefined, this)
70103
+ ]
70104
+ }, undefined, true, undefined, this)
70105
+ ]
70106
+ }, undefined, true, undefined, this);
70107
+ };
70108
+ var formatBytes = (bytes) => {
70109
+ if (bytes === 0)
70110
+ return "0 B";
70111
+ const k = 1024;
70112
+ const sizes = ["B", "KB", "MB", "GB"];
70113
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
70114
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
70115
+ };
70116
+ var AnimatedValue = ({ value, suffix, color }) => {
70117
+ const [displayValue, setDisplayValue] = import_react34.useState(0);
70118
+ import_react34.useEffect(() => {
70119
+ const duration = 500;
70120
+ const steps = 20;
70121
+ const increment = value / steps;
70122
+ let current = 0;
70123
+ const interval = setInterval(() => {
70124
+ current += increment;
70125
+ if (current >= value) {
70126
+ setDisplayValue(value);
70127
+ clearInterval(interval);
70128
+ } else {
70129
+ setDisplayValue(current);
70130
+ }
70131
+ }, duration / steps);
70132
+ return () => clearInterval(interval);
70133
+ }, [value]);
70134
+ return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70135
+ color,
70136
+ bold: true,
70137
+ children: [
70138
+ displayValue.toFixed(2),
70139
+ " ",
70140
+ suffix
70141
+ ]
70142
+ }, undefined, true, undefined, this);
70143
+ };
70144
+ var PulsingDot = ({ color }) => {
70145
+ const [frame, setFrame] = import_react34.useState(0);
70146
+ const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
70147
+ import_react34.useEffect(() => {
70148
+ const interval = setInterval(() => {
70149
+ setFrame((prev) => (prev + 1) % frames.length);
70150
+ }, 80);
70151
+ return () => clearInterval(interval);
70152
+ }, []);
70153
+ return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70154
+ color,
70155
+ children: frames[frame]
70156
+ }, undefined, false, undefined, this);
70157
+ };
70158
+ var TimelineWaterfall = ({ metrics, theme }) => {
70159
+ const [animationProgress, setAnimationProgress] = import_react34.useState(0);
70160
+ const [pulseFrame, setPulseFrame] = import_react34.useState(0);
70161
+ const totalWidth = 50;
70162
+ const segments = [
70163
+ { label: "DNS", value: Math.max(0, metrics.dnsLookup), color: theme.colors.cool, icon: "⚡" },
70164
+ { label: "TCP", value: Math.max(0, metrics.tcpConnection), color: theme.colors.success, icon: "\uD83D\uDD0C" },
70165
+ ...metrics.tlsHandshake > 0 ? [{ label: "TLS", value: Math.max(0, metrics.tlsHandshake), color: theme.colors.secondary, icon: "\uD83D\uDD10" }] : [],
70166
+ { label: "TTFB", value: Math.max(0, metrics.ttfb), color: theme.colors.accent, icon: "⏱️" },
70167
+ { label: "DL", value: Math.max(0, metrics.contentDownload), color: theme.colors.primary, icon: "\uD83D\uDCE5" }
70168
+ ];
70169
+ const totalTime = segments.reduce((sum, s) => sum + s.value, 0);
70170
+ import_react34.useEffect(() => {
70171
+ setAnimationProgress(0);
70172
+ const interval = setInterval(() => {
70173
+ setAnimationProgress((prev) => {
70174
+ if (prev >= 100) {
70175
+ clearInterval(interval);
70176
+ return 100;
70177
+ }
70178
+ return prev + 2;
70179
+ });
70180
+ }, 15);
70181
+ return () => clearInterval(interval);
70182
+ }, [metrics]);
70183
+ import_react34.useEffect(() => {
70184
+ const interval = setInterval(() => {
70185
+ setPulseFrame((prev) => (prev + 1) % 4);
70186
+ }, 200);
70187
+ return () => clearInterval(interval);
70188
+ }, []);
70189
+ const pulseChars = ["▁", "▂", "▃", "▄"];
70190
+ const endCaps = ["◀", "▶"];
70191
+ return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70192
+ flexDirection: "column",
70193
+ marginY: 1,
70194
+ children: [
70195
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70196
+ marginBottom: 1,
70197
+ children: [
70198
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70199
+ color: theme.colors.accent,
70200
+ bold: true,
70201
+ children: "⏳ Request Timeline"
70202
+ }, undefined, false, undefined, this),
70203
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70204
+ color: theme.colors.muted,
70205
+ children: [
70206
+ " (",
70207
+ totalTime.toFixed(0),
70208
+ "ms total)"
70209
+ ]
70210
+ }, undefined, true, undefined, this)
70211
+ ]
70212
+ }, undefined, true, undefined, this),
70213
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70214
+ children: [
70215
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70216
+ color: theme.colors.muted,
70217
+ children: endCaps[0]
70218
+ }, undefined, false, undefined, this),
70219
+ segments.map((segment, idx) => {
70220
+ const segmentWidth = Math.max(1, Math.round(segment.value / totalTime * totalWidth));
70221
+ const animatedWidth = Math.round(animationProgress / 100 * segmentWidth);
70222
+ const filledPart = "█".repeat(Math.max(0, animatedWidth));
70223
+ const emptyPart = "░".repeat(Math.max(0, segmentWidth - animatedWidth));
70224
+ return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70225
+ children: [
70226
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70227
+ color: segment.color,
70228
+ children: filledPart
70229
+ }, undefined, false, undefined, this),
70230
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70231
+ color: segment.color,
70232
+ dimColor: true,
70233
+ children: emptyPart
70234
+ }, undefined, false, undefined, this)
70235
+ ]
70236
+ }, idx, true, undefined, this);
70237
+ }),
70238
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70239
+ color: theme.colors.muted,
70240
+ children: endCaps[1]
70241
+ }, undefined, false, undefined, this),
70242
+ animationProgress < 100 && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70243
+ color: theme.colors.accent,
70244
+ children: [
70245
+ " ",
70246
+ pulseChars[pulseFrame]
70247
+ ]
70248
+ }, undefined, true, undefined, this),
70249
+ animationProgress >= 100 && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70250
+ color: theme.colors.success,
70251
+ children: " ✓"
70252
+ }, undefined, false, undefined, this)
70253
+ ]
70254
+ }, undefined, true, undefined, this),
70255
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70256
+ marginTop: 1,
70257
+ flexWrap: "wrap",
70258
+ children: segments.map((segment, idx) => /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70259
+ marginRight: 2,
70260
+ children: [
70261
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70262
+ color: segment.color,
70263
+ children: "■"
70264
+ }, undefined, false, undefined, this),
70265
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70266
+ color: theme.colors.muted,
70267
+ children: [
70268
+ " ",
70269
+ segment.label,
70270
+ " "
70271
+ ]
70272
+ }, undefined, true, undefined, this),
70273
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70274
+ color: theme.colors.white,
70275
+ bold: true,
70276
+ children: segment.value.toFixed(0)
70277
+ }, undefined, false, undefined, this),
70278
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70279
+ color: theme.colors.muted,
70280
+ dimColor: true,
70281
+ children: "ms"
70282
+ }, undefined, false, undefined, this)
70283
+ ]
70284
+ }, idx, true, undefined, this))
70285
+ }, undefined, false, undefined, this)
70286
+ ]
70287
+ }, undefined, true, undefined, this);
70288
+ };
70289
+ var MetricsPanel = ({ metrics, theme }) => {
70290
+ const [showContent, setShowContent] = import_react34.useState(false);
70291
+ import_react34.useEffect(() => {
70292
+ if (metrics) {
70293
+ setShowContent(false);
70294
+ const timeout = setTimeout(() => setShowContent(true), 100);
70295
+ return () => clearTimeout(timeout);
70296
+ }
70297
+ }, [metrics]);
70298
+ if (!metrics) {
70299
+ return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70300
+ flexDirection: "column",
70301
+ padding: 1,
70302
+ alignItems: "center",
70303
+ children: [
70304
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70305
+ marginBottom: 1,
70306
+ children: [
70307
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(PulsingDot, {
70308
+ color: theme.colors.muted
70309
+ }, undefined, false, undefined, this),
70310
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70311
+ color: theme.colors.muted,
70312
+ children: " Waiting for request..."
70313
+ }, undefined, false, undefined, this)
70314
+ ]
70315
+ }, undefined, true, undefined, this),
70316
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70317
+ color: theme.colors.muted,
70318
+ dimColor: true,
70319
+ children: "Send a request to see metrics"
70320
+ }, undefined, false, undefined, this)
70321
+ ]
70322
+ }, undefined, true, undefined, this);
70323
+ }
70324
+ const maxTime = Math.max(Math.max(0, metrics.dnsLookup), Math.max(0, metrics.tcpConnection), Math.max(0, metrics.tlsHandshake), Math.max(0, metrics.ttfb), Math.max(0, metrics.contentDownload), 1);
70325
+ if (!showContent) {
70326
+ return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70327
+ flexDirection: "column",
70328
+ padding: 1,
70329
+ alignItems: "center",
70330
+ children: [
70331
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(PulsingDot, {
70332
+ color: theme.colors.accent
70333
+ }, undefined, false, undefined, this),
70334
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70335
+ color: theme.colors.accent,
70336
+ children: " Analyzing..."
70337
+ }, undefined, false, undefined, this)
70338
+ ]
70339
+ }, undefined, true, undefined, this);
70340
+ }
70341
+ return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70342
+ flexDirection: "column",
70343
+ paddingX: 1,
70344
+ children: [
70345
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(TimelineWaterfall, {
70346
+ metrics,
70347
+ theme
70348
+ }, undefined, false, undefined, this),
70349
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70350
+ marginTop: 1,
70351
+ flexDirection: "column",
70352
+ children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70353
+ color: theme.colors.muted,
70354
+ children: "┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈"
70355
+ }, undefined, false, undefined, this)
70356
+ }, undefined, false, undefined, this),
70357
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70358
+ flexDirection: "column",
70359
+ gap: 0,
70360
+ children: [
70361
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(MetricBar, {
70362
+ label: "⚡ DNS",
70363
+ value: metrics.dnsLookup,
70364
+ maxValue: maxTime,
70365
+ color: theme.colors.cool,
70366
+ theme,
70367
+ delay: 0
70368
+ }, undefined, false, undefined, this),
70369
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(MetricBar, {
70370
+ label: "\uD83D\uDD0C TCP",
70371
+ value: metrics.tcpConnection,
70372
+ maxValue: maxTime,
70373
+ color: theme.colors.success,
70374
+ theme,
70375
+ delay: 100
70376
+ }, undefined, false, undefined, this),
70377
+ metrics.tlsHandshake > 0 && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(MetricBar, {
70378
+ label: "\uD83D\uDD10 TLS",
70379
+ value: metrics.tlsHandshake,
70380
+ maxValue: maxTime,
70381
+ color: theme.colors.secondary,
70382
+ theme,
70383
+ delay: 200
70384
+ }, undefined, false, undefined, this),
70385
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(MetricBar, {
70386
+ label: "⏱️ TTFB",
70387
+ value: metrics.ttfb,
70388
+ maxValue: maxTime,
70389
+ color: theme.colors.accent,
70390
+ theme,
70391
+ delay: 300
70392
+ }, undefined, false, undefined, this),
70393
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(MetricBar, {
70394
+ label: "\uD83D\uDCE5 Download",
70395
+ value: metrics.contentDownload,
70396
+ maxValue: maxTime,
70397
+ color: theme.colors.primary,
70398
+ theme,
70399
+ delay: 400
70400
+ }, undefined, false, undefined, this)
70401
+ ]
70402
+ }, undefined, true, undefined, this),
70403
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70404
+ marginTop: 1,
70405
+ flexDirection: "column",
70406
+ children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70407
+ color: theme.colors.muted,
70408
+ children: "┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈"
70409
+ }, undefined, false, undefined, this)
70410
+ }, undefined, false, undefined, this),
70411
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70412
+ marginTop: 1,
70413
+ flexDirection: "column",
70414
+ gap: 0,
70415
+ children: [
70416
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70417
+ children: [
70418
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70419
+ width: 12,
70420
+ children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70421
+ color: theme.colors.accent,
70422
+ children: "⚡ Total"
70423
+ }, undefined, false, undefined, this)
70424
+ }, undefined, false, undefined, this),
70425
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(AnimatedValue, {
70426
+ value: metrics.total,
70427
+ suffix: "ms",
70428
+ color: theme.colors.white
70429
+ }, undefined, false, undefined, this)
70430
+ ]
70431
+ }, undefined, true, undefined, this),
70432
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70433
+ children: [
70434
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70435
+ width: 12,
70436
+ children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70437
+ color: theme.colors.accent,
70438
+ children: "\uD83D\uDCE6 Size"
70439
+ }, undefined, false, undefined, this)
70440
+ }, undefined, false, undefined, this),
70441
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70442
+ color: theme.colors.white,
70443
+ bold: true,
70444
+ children: formatBytes(metrics.contentLength)
70445
+ }, undefined, false, undefined, this)
70446
+ ]
70447
+ }, undefined, true, undefined, this),
70448
+ metrics.contentLength > 0 && metrics.contentDownload > 0 && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70449
+ children: [
70450
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
70451
+ width: 12,
70452
+ children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70453
+ color: theme.colors.accent,
70454
+ children: "\uD83D\uDE80 Speed"
70455
+ }, undefined, false, undefined, this)
70456
+ }, undefined, false, undefined, this),
70457
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
70458
+ color: theme.colors.success,
70459
+ bold: true,
70460
+ children: [
70461
+ formatBytes(metrics.contentLength / (metrics.contentDownload / 1000)),
70462
+ "/s"
70463
+ ]
70464
+ }, undefined, true, undefined, this)
70465
+ ]
70466
+ }, undefined, true, undefined, this)
70467
+ ]
70468
+ }, undefined, true, undefined, this)
70469
+ ]
70470
+ }, undefined, true, undefined, this);
70471
+ };
70472
+
70473
+ // src/ui/app/ui.tsx
70474
+ var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
70475
+ var formatBytes2 = (bytes) => {
70476
+ if (bytes === 0)
70477
+ return "0 B";
70478
+ const k = 1024;
70479
+ const sizes = ["B", "KB", "MB", "GB"];
70480
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
70481
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
70482
+ };
70483
+ var LiveByteCounter = ({ progress, theme }) => {
70484
+ const [pulseFrame, setPulseFrame] = import_react35.useState(0);
70485
+ const pulseChars = ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"];
70486
+ const arrowFrames = ["↓", "⬇", "↓", "⇣"];
70487
+ import_react35.useEffect(() => {
70488
+ const interval = setInterval(() => {
70489
+ setPulseFrame((prev) => (prev + 1) % pulseChars.length);
70490
+ }, 100);
70491
+ return () => clearInterval(interval);
70492
+ }, []);
70493
+ if (!progress)
70494
+ return null;
70495
+ const { bytesReceived, totalBytes, speed } = progress;
70496
+ const percentage = totalBytes > 0 ? Math.round(bytesReceived / totalBytes * 100) : 0;
70497
+ const progressBarWidth = 20;
70498
+ const filledWidth = totalBytes > 0 ? Math.round(bytesReceived / totalBytes * progressBarWidth) : 0;
70499
+ const filledBar = "█".repeat(filledWidth);
70500
+ const emptyBar = "░".repeat(progressBarWidth - filledWidth);
70501
+ return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
70502
+ flexDirection: "column",
70503
+ paddingX: 1,
70504
+ paddingY: 1,
70505
+ borderStyle: "round",
70506
+ borderColor: theme.accent,
70507
+ children: [
70508
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
70509
+ marginBottom: 1,
70510
+ children: [
70511
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
70512
+ color: theme.accent,
70513
+ bold: true,
70514
+ children: [
70515
+ pulseChars[pulseFrame],
70516
+ " "
70517
+ ]
70518
+ }, undefined, true, undefined, this),
70519
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
70520
+ color: theme.primary,
70521
+ bold: true,
70522
+ children: "DOWNLOADING "
70523
+ }, undefined, false, undefined, this),
70524
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
70525
+ color: theme.accent,
70526
+ children: arrowFrames[pulseFrame % arrowFrames.length]
70527
+ }, undefined, false, undefined, this)
70528
+ ]
70529
+ }, undefined, true, undefined, this),
70530
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
70531
+ children: [
70532
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
70533
+ color: theme.success,
70534
+ children: filledBar
70535
+ }, undefined, false, undefined, this),
70536
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
70537
+ color: theme.muted,
70538
+ dimColor: true,
70539
+ children: emptyBar
70540
+ }, undefined, false, undefined, this),
70541
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
70542
+ color: theme.white,
70543
+ bold: true,
70544
+ children: [
70545
+ " ",
70546
+ percentage,
70547
+ "%"
70548
+ ]
70549
+ }, undefined, true, undefined, this)
70550
+ ]
70551
+ }, undefined, true, undefined, this),
70552
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
70553
+ marginTop: 1,
70554
+ children: [
70555
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
70556
+ marginRight: 2,
70557
+ children: [
70558
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
70559
+ color: theme.accent,
70560
+ children: "\uD83D\uDCE6 "
70561
+ }, undefined, false, undefined, this),
70562
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
70563
+ color: theme.white,
70564
+ bold: true,
70565
+ children: formatBytes2(bytesReceived)
70566
+ }, undefined, false, undefined, this),
70567
+ totalBytes > 0 && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
70568
+ color: theme.muted,
70569
+ children: [
70570
+ " / ",
70571
+ formatBytes2(totalBytes)
70572
+ ]
70573
+ }, undefined, true, undefined, this)
70574
+ ]
70575
+ }, undefined, true, undefined, this),
70576
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
70577
+ children: [
70578
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
70579
+ color: theme.success,
70580
+ children: "\uD83D\uDE80 "
70581
+ }, undefined, false, undefined, this),
70582
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
70583
+ color: theme.success,
70584
+ bold: true,
70585
+ children: [
70586
+ formatBytes2(speed),
70587
+ "/s"
70588
+ ]
70589
+ }, undefined, true, undefined, this)
70590
+ ]
70591
+ }, undefined, true, undefined, this)
70592
+ ]
70593
+ }, undefined, true, undefined, this)
70594
+ ]
70595
+ }, undefined, true, undefined, this);
70596
+ };
70597
+ var SendButton = ({ onPress, loading, theme }) => {
70598
+ const { isFocused } = use_focus_default();
70599
+ use_input_default((_, key) => {
70600
+ if (isFocused && key.return)
70601
+ onPress();
70602
+ });
70603
+ return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
70604
+ borderStyle: "round",
70605
+ paddingX: 2,
70606
+ borderTopDimColor: true,
70607
+ borderColor: isFocused ? theme.accent : theme.primary,
70608
+ children: loading ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Spinner, {
70609
+ theme
70610
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
70611
+ bold: true,
70612
+ color: isFocused ? theme.accent : theme.white,
70613
+ children: "\uD83D\uDE80 Send"
70614
+ }, undefined, false, undefined, this)
70615
+ }, undefined, false, undefined, this);
70616
+ };
70617
+ var HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"];
70618
+ var RequestPanel = import_react35.default.memo(({ request, onMethodChange, onUrlChange, onHeadersChange, onBodyChange, onSend, loading, theme, historyUrls = [], onInputFocus }) => /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
70619
+ flexDirection: "column",
70620
+ gap: 1,
70122
70621
  flexGrow: 1,
70123
70622
  children: [
70124
70623
  /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(FormField, {
@@ -70167,29 +70666,31 @@ var RequestPanel = import_react34.default.memo(({ request, onMethodChange, onUrl
70167
70666
  ]
70168
70667
  }, undefined, true, undefined, this));
70169
70668
  var UI = () => {
70170
- const [theme, setTheme] = import_react34.useState(themes.catppuccin);
70669
+ const [theme, setTheme] = import_react35.useState(themes.catppuccin);
70171
70670
  const { exit } = use_app_default();
70172
- const [activeTab, setActiveTab] = import_react34.useState("request");
70173
- const [request, setRequest] = import_react34.useState({ method: "GET", url: "", headers: "", body: "" });
70174
- const [response, setResponse] = import_react34.useState({ statustext: "", status: "", headers: "", body: "", error: "" });
70175
- const [metrics, setMetrics] = import_react34.useState(null);
70176
- const [history, setHistory] = import_react34.useState([]);
70177
- const [loading, setLoading] = import_react34.useState(false);
70178
- const requestRef = import_react34.useRef(request);
70671
+ const [activeTab, setActiveTab] = import_react35.useState("request");
70672
+ const [request, setRequest] = import_react35.useState({ method: "GET", url: "", headers: "", body: "" });
70673
+ const [response, setResponse] = import_react35.useState({ statustext: "", status: "", headers: "", body: "", error: "" });
70674
+ const [metrics, setMetrics] = import_react35.useState(null);
70675
+ const [history, setHistory] = import_react35.useState([]);
70676
+ const [loading, setLoading] = import_react35.useState(false);
70677
+ const [downloadProgress, setDownloadProgress] = import_react35.useState(null);
70678
+ const requestRef = import_react35.useRef(request);
70179
70679
  requestRef.current = request;
70180
- import_react34.useEffect(() => {
70680
+ import_react35.useEffect(() => {
70181
70681
  const loadHistory = async () => setHistory((await historyManager.loadHistory()).entries);
70182
70682
  loadHistory();
70183
70683
  }, []);
70184
- import_react34.useEffect(() => {
70684
+ import_react35.useEffect(() => {
70185
70685
  const loadTheme = async () => {
70186
70686
  const loadedTheme = await themeManager.loadCurrTheme();
70187
70687
  setTheme(loadedTheme);
70188
70688
  };
70189
70689
  loadTheme();
70190
70690
  }, []);
70191
- const handleSend = import_react34.useCallback(async () => {
70691
+ const handleSend = import_react35.useCallback(async () => {
70192
70692
  setLoading(true);
70693
+ setDownloadProgress(null);
70193
70694
  const startTime = Date.now();
70194
70695
  const currentRequest = requestRef.current;
70195
70696
  try {
@@ -70211,7 +70712,8 @@ var UI = () => {
70211
70712
  method: currentRequest.method,
70212
70713
  url: currentRequest.url,
70213
70714
  headers: parsedHeaders,
70214
- body: reqBody
70715
+ body: reqBody,
70716
+ onProgress: (progress) => setDownloadProgress(progress)
70215
70717
  });
70216
70718
  const responseTime = Date.now() - startTime;
70217
70719
  await historyManager.addEntry({ ...currentRequest }, res.status, responseTime);
@@ -70224,13 +70726,14 @@ var UI = () => {
70224
70726
  setActiveTab("response");
70225
70727
  } finally {
70226
70728
  setLoading(false);
70729
+ setDownloadProgress(null);
70227
70730
  }
70228
70731
  }, []);
70229
70732
  const handleThemeChange = (theme2) => {
70230
70733
  themeManager.ChangeTheme(theme2);
70231
70734
  setTheme(theme2);
70232
70735
  };
70233
- const handleHistoryClick = import_react34.useCallback((item) => {
70736
+ const handleHistoryClick = import_react35.useCallback((item) => {
70234
70737
  setRequest({
70235
70738
  method: item.method,
70236
70739
  url: item.url,
@@ -70241,9 +70744,9 @@ var UI = () => {
70241
70744
  }, []);
70242
70745
  const tabs = [{ name: "request", label: "Request" }, { name: "response", label: "Response" }];
70243
70746
  const activeIndex = tabs.findIndex((t) => t.name === activeTab);
70244
- const [showThemeSelector, setShowThemeSelector] = import_react34.useState(false);
70245
- const [showExportDialog, setShowExportDialog] = import_react34.useState(false);
70246
- const [inputFocused, setInputFocused] = import_react34.useState(false);
70747
+ const [showThemeSelector, setShowThemeSelector] = import_react35.useState(false);
70748
+ const [showExportDialog, setShowExportDialog] = import_react35.useState(false);
70749
+ const [inputFocused, setInputFocused] = import_react35.useState(false);
70247
70750
  use_input_default((input, key) => {
70248
70751
  if (input === "q" && !showExportDialog)
70249
70752
  exit();
@@ -70262,10 +70765,10 @@ var UI = () => {
70262
70765
  if ((input === "e" || input === "E") && !key.ctrl && !key.meta && !inputFocused && !showThemeSelector)
70263
70766
  setShowExportDialog((prev) => !prev);
70264
70767
  }, { isActive: !showExportDialog });
70265
- const onMethodChange = import_react34.useCallback((method) => setRequest((r) => ({ ...r, method })), []);
70266
- const onUrlChange = import_react34.useCallback((url) => setRequest((r) => ({ ...r, url })), []);
70267
- const onHeadersChange = import_react34.useCallback((headers) => setRequest((r) => ({ ...r, headers })), []);
70268
- const onBodyChange = import_react34.useCallback((body) => setRequest((r) => ({ ...r, body })), []);
70768
+ const onMethodChange = import_react35.useCallback((method) => setRequest((r) => ({ ...r, method })), []);
70769
+ const onUrlChange = import_react35.useCallback((url) => setRequest((r) => ({ ...r, url })), []);
70770
+ const onHeadersChange = import_react35.useCallback((headers) => setRequest((r) => ({ ...r, headers })), []);
70771
+ const onBodyChange = import_react35.useCallback((body) => setRequest((r) => ({ ...r, body })), []);
70269
70772
  const historyUrls = Array.from(new Set(history.map((h) => h.url))).filter(Boolean);
70270
70773
  return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
70271
70774
  padding: 1,
@@ -70370,6 +70873,27 @@ var UI = () => {
70370
70873
  theme
70371
70874
  }, undefined, false, undefined, this)
70372
70875
  }, undefined, false, undefined, this),
70876
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
70877
+ flexDirection: "column",
70878
+ borderStyle: "round",
70879
+ borderColor: theme.colors.muted,
70880
+ marginX: 1,
70881
+ children: [
70882
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
70883
+ alignSelf: "center",
70884
+ paddingX: 1,
70885
+ children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
70886
+ color: theme.colors.accent,
70887
+ bold: true,
70888
+ children: "⚡ Metrics"
70889
+ }, undefined, false, undefined, this)
70890
+ }, undefined, false, undefined, this),
70891
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(MetricsPanel, {
70892
+ metrics,
70893
+ theme
70894
+ }, undefined, false, undefined, this)
70895
+ ]
70896
+ }, undefined, true, undefined, this),
70373
70897
  /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
70374
70898
  alignSelf: "center",
70375
70899
  marginBottom: 1,
@@ -70402,6 +70926,14 @@ var UI = () => {
70402
70926
  onChange: setActiveTab,
70403
70927
  theme
70404
70928
  }, undefined, false, undefined, this),
70929
+ downloadProgress && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
70930
+ marginTop: 1,
70931
+ justifyContent: "center",
70932
+ children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(LiveByteCounter, {
70933
+ progress: downloadProgress,
70934
+ theme: theme.colors
70935
+ }, undefined, false, undefined, this)
70936
+ }, undefined, false, undefined, this),
70405
70937
  /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
70406
70938
  marginTop: 1,
70407
70939
  flexDirection: "column",
@@ -70428,8 +70960,7 @@ var UI = () => {
70428
70960
  flexGrow: 1,
70429
70961
  children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(ResponsePanel, {
70430
70962
  response,
70431
- theme,
70432
- metrics
70963
+ theme
70433
70964
  }, undefined, false, undefined, this)
70434
70965
  }, undefined, false, undefined, this)
70435
70966
  ]
@@ -70651,7 +71182,7 @@ async function mockApis() {
70651
71182
 
70652
71183
  // src/index.ts
70653
71184
  var program2 = new Command;
70654
- program2.version("1.3.8").description(import_chalk9.default.yellow("PostBoy CLI - Test your APIs with ease"));
71185
+ program2.version("1.4.0").description(import_chalk9.default.yellow("PostBoy CLI - Test your APIs with ease"));
70655
71186
  program2.command("run").description("Run a test API request").action(testCommand);
70656
71187
  program2.command("mock-list").description("List the mock API servers").action(mockApis);
70657
71188
  program2.command("ui").description("UI for PostBoy").action(uiCommand);