@quandev104/pi-style 0.2.3 → 0.2.4

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.
@@ -343,6 +343,7 @@ function rewriteTokens(text, rewritten, tmpRoot) {
343
343
  // extension-src/pi-style/features/messages/image-preview.ts
344
344
  import {
345
345
  getCapabilities,
346
+ getCellDimensions,
346
347
  getImageDimensions,
347
348
  Image
348
349
  } from "@earendil-works/pi-tui";
@@ -569,6 +570,8 @@ var IMAGE_PREVIEW_EXPANDED_MAX_WIDTH_CELLS = 60;
569
570
  var IMAGE_PREVIEW_MAX_WIDTH_CELLS = 60;
570
571
  var GRID_GAP = 2;
571
572
  var GRID_MIN_COLUMN = 14;
573
+ var GRID_MAX_HEIGHT_RATIO = 1.5;
574
+ var FALLBACK_IMAGE_DIMENSIONS = { widthPx: 800, heightPx: 600 };
572
575
  function isPreviewableImage(value) {
573
576
  if (!value || typeof value !== "object") return false;
574
577
  const data = value.data;
@@ -624,10 +627,10 @@ function renderImagePreviewEntry(entry, options, theme) {
624
627
  )
625
628
  );
626
629
  const labels2 = images.map((_image, index) => imageLabel(themed, index + 1, dims[index]));
627
- cached = { images, components: components2, labels: labels2 };
630
+ cached = { images, dimensions: dims, components: components2, labels: labels2 };
628
631
  previewCache.set(entry, cached);
629
632
  }
630
- const { components, labels } = cached;
633
+ const { components, dimensions, labels } = cached;
631
634
  let memo;
632
635
  return {
633
636
  invalidate() {
@@ -643,7 +646,7 @@ function renderImagePreviewEntry(entry, options, theme) {
643
646
  let lines;
644
647
  if (width <= 0) lines = [];
645
648
  else if (images.length === 1 || !kitty) lines = renderStacked(width, maxWidth, labels, components);
646
- else lines = renderGrid(width, maxWidth, labels, components);
649
+ else lines = renderGrid(width, maxWidth, labels, components, dimensions);
647
650
  memo = { key, lines };
648
651
  return lines;
649
652
  }
@@ -667,7 +670,7 @@ function stripTrailingSpaces(line) {
667
670
  while (end > 0 && line.charCodeAt(end - 1) === 32) end--;
668
671
  return line.slice(0, end);
669
672
  }
670
- function renderGrid(width, maxWidth, labels, components) {
673
+ function renderGrid(width, maxWidth, labels, components, dimensions) {
671
674
  const usable = Math.max(1, width - 2);
672
675
  const maxColumns = Math.floor((usable + GRID_GAP) / (GRID_MIN_COLUMN + GRID_GAP));
673
676
  const columns = Math.max(1, Math.min(components.length, maxColumns, 3));
@@ -675,31 +678,56 @@ function renderGrid(width, maxWidth, labels, components) {
675
678
  const columnWidth = Math.min(maxWidth, Math.floor((usable - (columns - 1) * GRID_GAP) / columns));
676
679
  if (columnWidth < GRID_MIN_COLUMN) return renderStacked(width, maxWidth, labels, components);
677
680
  const lines = [];
681
+ const cellDimensions = getCellDimensions();
678
682
  for (let start = 0; start < components.length; start += columns) {
679
683
  const group = components.slice(start, start + columns);
680
684
  const groupLabels = labels.slice(start, start + columns);
685
+ const groupDimensions = dimensions.slice(start, start + columns);
681
686
  if (start > 0) lines.push("");
682
687
  const rendered = group.map((component) => component.render(columnWidth));
683
- const labelRow = groupLabels.map((label, _index) => {
684
- const pad = Math.max(0, columnWidth - visibleWidth(stripAnsi(label)));
688
+ const sizes = groupDimensions.map((imageDimensions) => imageCellSize(imageDimensions, columnWidth, cellDimensions));
689
+ const rowCounts = rendered.map((column) => column.length).filter((count) => count > 0);
690
+ const minRows = Math.min(...rowCounts);
691
+ const maxRows = Math.max(...rowCounts);
692
+ if (rowCounts.length > 1 && minRows > 0 && maxRows >= minRows * GRID_MAX_HEIGHT_RATIO) {
693
+ return renderStacked(width, maxWidth, labels, components);
694
+ }
695
+ const slotWidths = groupLabels.map(
696
+ (label, index) => Math.max(sizes[index]?.columns ?? 1, visibleWidth(stripAnsi(label)))
697
+ );
698
+ const labelRow = groupLabels.map((label, index) => {
699
+ const pad = Math.max(0, (slotWidths[index] ?? 0) - visibleWidth(stripAnsi(label)));
685
700
  return label + " ".repeat(pad);
686
701
  }).join(" ".repeat(GRID_GAP)).trimEnd();
687
702
  lines.push(labelRow);
688
703
  const rows = Math.max(...rendered.map((column) => column.length));
689
704
  for (let row = 0; row < rows; row++) {
690
705
  let line = "";
706
+ let startCol = 0;
691
707
  for (let column = 0; column < rendered.length; column++) {
692
- if (column > 0) {
693
- const startCol = column * (columnWidth + GRID_GAP);
694
- line += `\x1B[${startCol + 1}G`;
695
- }
708
+ if (column > 0) line += `\x1B[${startCol + 1}G`;
696
709
  line += rendered[column]?.[row] ?? "";
710
+ startCol += (slotWidths[column] ?? 0) + GRID_GAP;
697
711
  }
698
712
  lines.push(stripTrailingSpaces(line));
699
713
  }
700
714
  }
701
715
  return lines;
702
716
  }
717
+ function imageCellSize(dimensions, requestedWidth, cellDimensions) {
718
+ const imageDimensions = dimensions ?? FALLBACK_IMAGE_DIMENSIONS;
719
+ const maxWidth = Math.max(1, Math.min(requestedWidth - 2, 60));
720
+ const maxHeight = Math.max(1, Math.ceil(maxWidth * cellDimensions.widthPx / cellDimensions.heightPx));
721
+ const imageWidth = Math.max(1, imageDimensions.widthPx);
722
+ const imageHeight = Math.max(1, imageDimensions.heightPx);
723
+ const widthScale = maxWidth * cellDimensions.widthPx / imageWidth;
724
+ const heightScale = maxHeight * cellDimensions.heightPx / imageHeight;
725
+ const scale = Math.min(widthScale, heightScale);
726
+ return {
727
+ columns: Math.max(1, Math.min(maxWidth, Math.ceil(imageWidth * scale / cellDimensions.widthPx))),
728
+ rows: Math.max(1, Math.min(maxHeight, Math.ceil(imageHeight * scale / cellDimensions.heightPx)))
729
+ };
730
+ }
703
731
  function registerImagePreviewSurface(pi) {
704
732
  pi.registerEntryRenderer(IMAGE_PREVIEW_ENTRY_TYPE, renderImagePreviewEntry);
705
733
  }
@@ -13285,9 +13313,9 @@ function piStyleExtension(pi) {
13285
13313
  registerPiStyleCommand(pi, coordinator.app);
13286
13314
  registerImagePreviewSurface(pi);
13287
13315
  let stagedImagePreview;
13316
+ let previewFlushTimer;
13288
13317
  pi.on("before_agent_start", (event) => {
13289
- const staged = stageImagePreviewData(event.images ?? []);
13290
- if (staged) stagedImagePreview = staged;
13318
+ stagedImagePreview = stageImagePreviewData(event.images ?? []);
13291
13319
  });
13292
13320
  pi.on("session_start", async (event, ctx) => {
13293
13321
  resetUsageFromSessionCache(ctx.sessionManager);
@@ -13339,7 +13367,20 @@ function piStyleExtension(pi) {
13339
13367
  );
13340
13368
  pi.on("message_start", (event) => {
13341
13369
  closeActiveBatch();
13370
+ if (event.message?.role === "user" && stagedImagePreview && !previewFlushTimer) {
13371
+ const pending = stagedImagePreview;
13372
+ previewFlushTimer = setTimeout(() => {
13373
+ previewFlushTimer = void 0;
13374
+ if (stagedImagePreview !== pending) return;
13375
+ flushImagePreviewEntry(pi, pending);
13376
+ stagedImagePreview = void 0;
13377
+ }, 0);
13378
+ }
13342
13379
  if (stagedImagePreview && event.message?.role === "assistant") {
13380
+ if (previewFlushTimer) {
13381
+ clearTimeout(previewFlushTimer);
13382
+ previewFlushTimer = void 0;
13383
+ }
13343
13384
  flushImagePreviewEntry(pi, stagedImagePreview);
13344
13385
  stagedImagePreview = void 0;
13345
13386
  }