@quandev104/pi-style 0.2.3 → 0.2.5
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/CHANGELOG.md +6 -0
- package/README.md +1 -2
- package/dist/extensions/pi-style.js +57 -41
- package/dist/extensions/pi-style.js.map +1 -1
- package/extension-src/pi-style/app/command-service.ts +0 -2
- package/extension-src/pi-style/domain/config-normalization.ts +0 -3
- package/extension-src/pi-style/domain/config-types.ts +0 -2
- package/extension-src/pi-style/features/messages/image-preview.ts +67 -20
- package/extension-src/pi-style/features/messages/index.ts +0 -44
- package/extension-src/pi-style/pi/compatibility-coordinator.ts +0 -1
- package/extension-src/pi-style/pi/compatibility-probe.ts +1 -2
- package/extension-src/pi-style/pi/index.ts +30 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
- **Editor** — compact/boxed/dock `CustomEditor` treatments with prompt glyph, metadata rows, and thinking-level border, preserving Pi keybindings and autocomplete.
|
|
18
18
|
- **Startup** — compact gradient logo header and optional overlay with System & Context / Available Tools panels, rendered from snapshot data collected before mount.
|
|
19
19
|
- **Messages** — assistant prefix and boxed compaction/skill/branch/MCP special blocks, and certified tool call/result selectors with pending/running/error markers.
|
|
20
|
-
- **Interim narration hiding** — the text of assistant messages that also carry tool calls is hidden (`messages.hideInterimText`): the feed shows only the tool blocks, the run summary, and the final answer.
|
|
21
20
|
- **Turn summaries** — when a turn completes, its finalized tool blocks collapse into one summary line (`➔ Read 2 files, ran 4 shell commands · 3.1s`); errors and interrupted turns stay visible, and Pi's global Ctrl+O toggle expands everything again (`tools.collapseAfterTurn`).
|
|
22
21
|
- **User-prompt image previews** — images attached to your prompt render inline **below your message** as display-only session entries (never sent to the LLM) with `#N · WxH` labels tying each image to its marker; multiple images lay out side-by-side on kitty-capable terminals (up to 3 columns, stacked fallback elsewhere), sized by `messages.previewMaxWidth` (default 30) with Pi's global Ctrl+O expanding to 60 (`messages.showImagePreviews`).
|
|
23
22
|
- **Clipboard image input** — the built-in `Ctrl+V` becomes a full image-paste flow with no other extension required: a sync native probe checks the clipboard at keystroke time, an `[Image #N] ` marker appears instantly in the editor (bytes attach asynchronously — no temp-file flash), backspacing right after a marker deletes it as one unit (discarding the image, image-paste semantics), and submit attaches real image attachments; raw pasted paths that bypass the editor still upgrade to `[image]` + attachment (`messages.clipboardImages`).
|
|
@@ -194,7 +193,7 @@ npm run check
|
|
|
194
193
|
|
|
195
194
|
`npm run check` runs all required automated gates in order.
|
|
196
195
|
|
|
197
|
-
Current test suite:
|
|
196
|
+
Current test suite: 819 tests across 41 files.
|
|
198
197
|
|
|
199
198
|
---
|
|
200
199
|
|
|
@@ -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
|
|
684
|
-
|
|
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
|
}
|
|
@@ -7602,7 +7630,6 @@ var DEFAULT_CONFIG = Object.freeze({
|
|
|
7602
7630
|
assistantPrefix: true,
|
|
7603
7631
|
specialBlocks: true,
|
|
7604
7632
|
hideThinkingLabel: true,
|
|
7605
|
-
hideInterimText: true,
|
|
7606
7633
|
showImagePreviews: true,
|
|
7607
7634
|
clipboardImages: true,
|
|
7608
7635
|
previewMaxWidth: 30
|
|
@@ -7722,7 +7749,6 @@ function normalizeConfig(input, defaults = DEFAULT_CONFIG) {
|
|
|
7722
7749
|
assistantPrefix: bool(messages.assistantPrefix, defaults.messages.assistantPrefix),
|
|
7723
7750
|
specialBlocks: bool(messages.specialBlocks, defaults.messages.specialBlocks),
|
|
7724
7751
|
hideThinkingLabel: bool(messages.hideThinkingLabel, defaults.messages.hideThinkingLabel),
|
|
7725
|
-
hideInterimText: bool(messages.hideInterimText, defaults.messages.hideInterimText),
|
|
7726
7752
|
showImagePreviews: bool(messages.showImagePreviews, defaults.messages.showImagePreviews),
|
|
7727
7753
|
clipboardImages: bool(messages.clipboardImages, defaults.messages.clipboardImages),
|
|
7728
7754
|
previewMaxWidth: boundedInt(messages.previewMaxWidth, defaults.messages.previewMaxWidth, 8, 60)
|
|
@@ -7778,7 +7804,6 @@ var BOOL_PATHS = /* @__PURE__ */ new Set([
|
|
|
7778
7804
|
"messages.assistantPrefix",
|
|
7779
7805
|
"messages.specialBlocks",
|
|
7780
7806
|
"messages.hideThinkingLabel",
|
|
7781
|
-
"messages.hideInterimText",
|
|
7782
7807
|
"messages.showImagePreviews",
|
|
7783
7808
|
"messages.clipboardImages",
|
|
7784
7809
|
"tools.enabled",
|
|
@@ -8250,7 +8275,6 @@ var allowedPaths = /* @__PURE__ */ new Set([
|
|
|
8250
8275
|
"messages.assistantPrefix",
|
|
8251
8276
|
"messages.specialBlocks",
|
|
8252
8277
|
"messages.hideThinkingLabel",
|
|
8253
|
-
"messages.hideInterimText",
|
|
8254
8278
|
"messages.showImagePreviews",
|
|
8255
8279
|
"messages.clipboardImages",
|
|
8256
8280
|
"messages.previewMaxWidth",
|
|
@@ -8285,7 +8309,6 @@ function validatePathValue(path, value) {
|
|
|
8285
8309
|
"messages.assistantPrefix",
|
|
8286
8310
|
"messages.specialBlocks",
|
|
8287
8311
|
"messages.hideThinkingLabel",
|
|
8288
|
-
"messages.hideInterimText",
|
|
8289
8312
|
"messages.showImagePreviews",
|
|
8290
8313
|
"messages.clipboardImages",
|
|
8291
8314
|
"messages.previewMaxWidth",
|
|
@@ -11839,8 +11862,7 @@ function prefixNative(lines, width, prefix) {
|
|
|
11839
11862
|
function decorateMessageRender(original, instance, args, snapshot = {
|
|
11840
11863
|
assistantPrefix: "\u2502 ",
|
|
11841
11864
|
assistantEnabled: true,
|
|
11842
|
-
collapseHiddenThinking: false
|
|
11843
|
-
hideInterimText: false
|
|
11865
|
+
collapseHiddenThinking: false
|
|
11844
11866
|
}) {
|
|
11845
11867
|
if (typeof original !== "function") return void 0;
|
|
11846
11868
|
const width = typeof args[0] === "number" ? args[0] : 0;
|
|
@@ -11864,17 +11886,6 @@ function decorateMessageRender(original, instance, args, snapshot = {
|
|
|
11864
11886
|
function isSpacerChild(child) {
|
|
11865
11887
|
return typeof child?.setLines === "function";
|
|
11866
11888
|
}
|
|
11867
|
-
function isInterimTextChild(child) {
|
|
11868
|
-
const candidate = child;
|
|
11869
|
-
return typeof candidate?.setText === "function" && candidate.options !== void 0 && typeof candidate.options === "object" && candidate.defaultTextStyle === void 0;
|
|
11870
|
-
}
|
|
11871
|
-
function hasToolCallItems(message) {
|
|
11872
|
-
if (!message || typeof message !== "object") return false;
|
|
11873
|
-
const content = message.content;
|
|
11874
|
-
return Array.isArray(content) && content.some(
|
|
11875
|
-
(item) => item !== null && typeof item === "object" && item.type === "toolCall"
|
|
11876
|
-
);
|
|
11877
|
-
}
|
|
11878
11889
|
function isBlankTextChild(child) {
|
|
11879
11890
|
const candidate = child;
|
|
11880
11891
|
if (typeof candidate?.setCustomBgFn !== "function" || typeof candidate.render !== "function") return false;
|
|
@@ -11891,8 +11902,7 @@ function markChildrenScanned(instance, children) {
|
|
|
11891
11902
|
function decorateMessageUpdate(original, instance, args, snapshot = {
|
|
11892
11903
|
assistantPrefix: "\u2502 ",
|
|
11893
11904
|
assistantEnabled: true,
|
|
11894
|
-
collapseHiddenThinking: false
|
|
11895
|
-
hideInterimText: false
|
|
11905
|
+
collapseHiddenThinking: false
|
|
11896
11906
|
}) {
|
|
11897
11907
|
if (typeof original !== "function") return void 0;
|
|
11898
11908
|
const result = Reflect.apply(original, instance, args);
|
|
@@ -11906,12 +11916,6 @@ function decorateMessageUpdate(original, instance, args, snapshot = {
|
|
|
11906
11916
|
if (isSpacerChild(children[index])) children.splice(index, 1);
|
|
11907
11917
|
}
|
|
11908
11918
|
}
|
|
11909
|
-
if (snapshot.hideInterimText && hasToolCallItems(args[0])) {
|
|
11910
|
-
for (let index = children.length - 1; index >= 0; index--) {
|
|
11911
|
-
if (isInterimTextChild(children[index])) children.splice(index, 1);
|
|
11912
|
-
}
|
|
11913
|
-
if (children.every((child) => isSpacerChild(child))) children.length = 0;
|
|
11914
|
-
}
|
|
11915
11919
|
markChildrenScanned(instance, children);
|
|
11916
11920
|
}
|
|
11917
11921
|
return result;
|
|
@@ -12375,7 +12379,7 @@ function surfaceDisabled(spec, config) {
|
|
|
12375
12379
|
if (!config.messages.enabled) return true;
|
|
12376
12380
|
if (spec.subtype === "native-assistant-message" && spec.method === "render") return !config.messages.assistantPrefix;
|
|
12377
12381
|
if (spec.subtype === "native-assistant-message" && spec.method === "updateContent")
|
|
12378
|
-
return !config.messages.hideThinkingLabel
|
|
12382
|
+
return !config.messages.hideThinkingLabel;
|
|
12379
12383
|
if (isSpecialBlock(spec)) return !config.messages.specialBlocks;
|
|
12380
12384
|
return true;
|
|
12381
12385
|
}
|
|
@@ -12749,8 +12753,7 @@ function createCompatibilityCoordinator(dispose = disposePiCompatibilityProbe) {
|
|
|
12749
12753
|
messageSnapshot: {
|
|
12750
12754
|
assistantPrefix: authorization.ascii ? "[assistant] " : "\u2502 ",
|
|
12751
12755
|
assistantEnabled,
|
|
12752
|
-
collapseHiddenThinking: thinkingCollapseEnabled
|
|
12753
|
-
hideInterimText: config.messages.hideInterimText && messagesEnabled
|
|
12756
|
+
collapseHiddenThinking: thinkingCollapseEnabled
|
|
12754
12757
|
},
|
|
12755
12758
|
toolSnapshot: {
|
|
12756
12759
|
callMarker: authorization.ascii ? "[tool] " : "[tool] ",
|
|
@@ -13285,9 +13288,9 @@ function piStyleExtension(pi) {
|
|
|
13285
13288
|
registerPiStyleCommand(pi, coordinator.app);
|
|
13286
13289
|
registerImagePreviewSurface(pi);
|
|
13287
13290
|
let stagedImagePreview;
|
|
13291
|
+
let previewFlushTimer;
|
|
13288
13292
|
pi.on("before_agent_start", (event) => {
|
|
13289
|
-
|
|
13290
|
-
if (staged) stagedImagePreview = staged;
|
|
13293
|
+
stagedImagePreview = stageImagePreviewData(event.images ?? []);
|
|
13291
13294
|
});
|
|
13292
13295
|
pi.on("session_start", async (event, ctx) => {
|
|
13293
13296
|
resetUsageFromSessionCache(ctx.sessionManager);
|
|
@@ -13339,7 +13342,20 @@ function piStyleExtension(pi) {
|
|
|
13339
13342
|
);
|
|
13340
13343
|
pi.on("message_start", (event) => {
|
|
13341
13344
|
closeActiveBatch();
|
|
13345
|
+
if (event.message?.role === "user" && stagedImagePreview && !previewFlushTimer) {
|
|
13346
|
+
const pending = stagedImagePreview;
|
|
13347
|
+
previewFlushTimer = setTimeout(() => {
|
|
13348
|
+
previewFlushTimer = void 0;
|
|
13349
|
+
if (stagedImagePreview !== pending) return;
|
|
13350
|
+
flushImagePreviewEntry(pi, pending);
|
|
13351
|
+
stagedImagePreview = void 0;
|
|
13352
|
+
}, 0);
|
|
13353
|
+
}
|
|
13342
13354
|
if (stagedImagePreview && event.message?.role === "assistant") {
|
|
13355
|
+
if (previewFlushTimer) {
|
|
13356
|
+
clearTimeout(previewFlushTimer);
|
|
13357
|
+
previewFlushTimer = void 0;
|
|
13358
|
+
}
|
|
13343
13359
|
flushImagePreviewEntry(pi, stagedImagePreview);
|
|
13344
13360
|
stagedImagePreview = void 0;
|
|
13345
13361
|
}
|