@oh-my-pi/pi-tui 18.6.3 → 18.7.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.
- package/CHANGELOG.md +26 -58
- package/dist/types/app-keybindings.d.ts +1 -1
- package/dist/types/autocomplete.d.ts +5 -2
- package/dist/types/charts/chart-plan.d.ts +92 -0
- package/dist/types/charts/chart-svg.d.ts +30 -0
- package/dist/types/charts/table-data.d.ts +107 -0
- package/dist/types/chat/assistant-message.d.ts +4 -0
- package/dist/types/chat/chat-transcript-builder.d.ts +2 -0
- package/dist/types/chat/figure-markdown.d.ts +42 -0
- package/dist/types/chat/svg-figure.d.ts +33 -0
- package/dist/types/chat/svg-source.d.ts +49 -0
- package/dist/types/chat/table-chart.d.ts +88 -0
- package/dist/types/components/editor.d.ts +1 -1
- package/dist/types/components/image.d.ts +9 -0
- package/dist/types/components/markdown.d.ts +9 -0
- package/dist/types/fuzzy.d.ts +14 -0
- package/dist/types/keybindings.d.ts +1 -1
- package/dist/types/overlays/agents-hub.d.ts +9 -0
- package/dist/types/overlays/annotation-overlay.d.ts +5 -2
- package/dist/types/overlays/model-browser.d.ts +25 -3
- package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
- package/dist/types/prompt/interactive-context-helpers.d.ts +2 -0
- package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
- package/dist/types/render/composite.d.ts +2 -0
- package/dist/types/tools/glob.d.ts +4 -0
- package/dist/types/utils.d.ts +6 -0
- package/package.json +9 -9
- package/src/autocomplete.ts +8 -10
- package/src/charts/chart-plan.ts +367 -0
- package/src/charts/chart-svg.ts +693 -0
- package/src/charts/table-data.ts +436 -0
- package/src/chat/assistant-message.ts +139 -21
- package/src/chat/chat-transcript-builder.ts +4 -0
- package/src/chat/figure-markdown.ts +174 -0
- package/src/chat/svg-figure.ts +364 -0
- package/src/chat/svg-source.ts +188 -0
- package/src/chat/table-chart.ts +301 -0
- package/src/chrome/transcript-container.ts +7 -1
- package/src/components/editor.ts +52 -30
- package/src/components/image.ts +27 -1
- package/src/components/markdown.ts +6 -3
- package/src/fuzzy.ts +59 -19
- package/src/keybindings.ts +1 -1
- package/src/overlays/agent-transcript-viewer.ts +2 -0
- package/src/overlays/agents-hub.ts +14 -1
- package/src/overlays/annotation-overlay.ts +179 -80
- package/src/overlays/ask-dialog.ts +1 -1
- package/src/overlays/model-browser.ts +169 -76
- package/src/overlays/plan-review-overlay.ts +41 -4
- package/src/prompt/composer.ts +2 -14
- package/src/prompt/interactive-context-helpers.ts +3 -0
- package/src/prompt/model-mention-autocomplete.ts +14 -14
- package/src/render/composite.ts +62 -0
- package/src/status-line/segments.ts +3 -1
- package/src/tools/glob.ts +23 -6
- package/src/tui.ts +118 -105
- package/src/utils.ts +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.7.0] - 2026-10-06
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Assistant SVG code blocks now render as inline, theme-colored images on terminals with graphics support, updating as responses stream and adapting to terminal width; SVG that cannot be rendered remains available as code.
|
|
10
|
+
- Numeric tables in assistant responses can now include automatically selected, themed charts based on the table’s structure and units.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- The status line now recognizes projects located in the user’s `repos` directory.
|
|
15
|
+
- Model mentions, `/switch` completions, and model-picker search now update immediately while typing, including with large model catalogs.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Fixed terminal resizing issues that could cause flicker or briefly display an empty frame.
|
|
20
|
+
- Improved `/annotate` handling for long source lines and filenames, preserving indentation and typed note text.
|
|
21
|
+
- Fixed fullscreen inputs that could hide the cursor when hardware-cursor support was enabled.
|
|
22
|
+
- Fixed model picker and mention-list ordering for same-provider `-latest` models so results remain alphabetically stable.
|
|
23
|
+
- Model browser performance metrics now show the correct measurements for each service tier and identify the tier.
|
|
24
|
+
- Fixed plan review keyboard navigation so horizontal options use Left/Right and model-slider adjustments use Shift+Left/Right.
|
|
25
|
+
- Improved the Ask dialog footer so question-switching keyboard shortcuts are clearly labeled.
|
|
26
|
+
- Fixed creating a new agent when its generated system prompt contains a Markdown code fence.
|
|
27
|
+
- Timed-out `glob` scans are now labeled as timed out rather than truncated.
|
|
28
|
+
- Ctrl+Delete now deletes the word after the cursor, matching Ctrl+Backspace behavior.
|
|
29
|
+
|
|
5
30
|
## [18.6.3] - 2026-10-06
|
|
6
31
|
|
|
7
32
|
### Breaking Changes
|
|
@@ -2998,61 +3023,4 @@ Initial release under @oh-my-pi scope. See previous releases at [badlogic/pi-mon
|
|
|
2998
3023
|
|
|
2999
3024
|
- `Editor.getExpandedText()` method that returns text with paste markers expanded to their actual content ([#444](https://github.com/badlogic/pi-mono/pull/444) by [@aliou](https://github.com/aliou))
|
|
3000
3025
|
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
### Breaking Changes
|
|
3004
|
-
|
|
3005
|
-
- **Key detection functions removed**: All `isXxx()` key detection functions (`isEnter()`, `isEscape()`, `isCtrlC()`, etc.) have been removed. Use `matchesKey(data, keyId)` instead (e.g., `matchesKey(data, "enter")`, `matchesKey(data, "ctrl+c")`). This affects hooks and custom tools that use `ctx.ui.custom()` with keyboard input handling. ([#405](https://github.com/badlogic/pi-mono/pull/405))
|
|
3006
|
-
|
|
3007
|
-
### Added
|
|
3008
|
-
|
|
3009
|
-
- `Editor.insertTextAtCursor(text)` method for programmatic text insertion ([#419](https://github.com/badlogic/pi-mono/issues/419))
|
|
3010
|
-
- `EditorKeybindingsManager` for configurable editor keybindings. Components now use `matchesKey()` and keybindings manager instead of individual `isXxx()` functions. ([#405](https://github.com/badlogic/pi-mono/pull/405) by [@hjanuschka](https://github.com/hjanuschka))
|
|
3011
|
-
|
|
3012
|
-
### Changed
|
|
3013
|
-
|
|
3014
|
-
- Key detection refactored: consolidated `is*()` functions into generic `matchesKey(data, keyId)` function that accepts key identifiers like `"ctrl+c"`, `"shift+enter"`, `"alt+left"`, etc.
|
|
3015
|
-
|
|
3016
|
-
## [0.32.2] - 2026-01-03
|
|
3017
|
-
|
|
3018
|
-
### Fixed
|
|
3019
|
-
|
|
3020
|
-
- Slash command autocomplete now triggers for commands starting with `.`, `-`, or `_` (e.g., `/.land`, `/-foo`) ([#422](https://github.com/badlogic/pi-mono/issues/422))
|
|
3021
|
-
|
|
3022
|
-
## [0.32.0] - 2026-01-03
|
|
3023
|
-
|
|
3024
|
-
### Changed
|
|
3025
|
-
|
|
3026
|
-
- Editor component now uses word wrapping instead of character-level wrapping for better readability ([#382](https://github.com/badlogic/pi-mono/pull/382) by [@nickseelert](https://github.com/nickseelert))
|
|
3027
|
-
|
|
3028
|
-
### Fixed
|
|
3029
|
-
|
|
3030
|
-
- Shift+Space, Shift+Backspace, and Shift+Delete now work correctly in Kitty-protocol terminals (Kitty, WezTerm, etc.) instead of being silently ignored ([#411](https://github.com/badlogic/pi-mono/pull/411) by [@nathyong](https://github.com/nathyong))
|
|
3031
|
-
|
|
3032
|
-
## [0.31.1] - 2026-01-02
|
|
3033
|
-
|
|
3034
|
-
### Fixed
|
|
3035
|
-
|
|
3036
|
-
- `visibleWidth()` now strips OSC 8 hyperlink sequences, fixing text wrapping for clickable links ([#396](https://github.com/badlogic/pi-mono/pull/396) by [@Cursivez](https://github.com/Cursivez))
|
|
3037
|
-
|
|
3038
|
-
## [0.31.0] - 2026-01-02
|
|
3039
|
-
|
|
3040
|
-
### Added
|
|
3041
|
-
|
|
3042
|
-
- `isShiftCtrlO()` key detection function for Shift+Ctrl+O (Kitty protocol)
|
|
3043
|
-
- `isShiftCtrlD()` key detection function for Shift+Ctrl+D (Kitty protocol)
|
|
3044
|
-
- `TUI.onDebug` callback for global debug key handling (Shift+Ctrl+D)
|
|
3045
|
-
- `wrapTextWithAnsi()` utility now exported (wraps text to width, preserving ANSI codes)
|
|
3046
|
-
|
|
3047
|
-
### Changed
|
|
3048
|
-
|
|
3049
|
-
- README.md completely rewritten with accurate component documentation, theme interfaces, and examples
|
|
3050
|
-
- `visibleWidth()` reimplemented with grapheme-based width calculation, 10x faster on Bun and ~15% faster on Node ([#369](https://github.com/badlogic/pi-mono/pull/369) by [@nathyong](https://github.com/nathyong))
|
|
3051
|
-
|
|
3052
|
-
### Fixed
|
|
3053
|
-
|
|
3054
|
-
- Markdown component now renders HTML tags as plain text instead of silently dropping them ([#359](https://github.com/badlogic/pi-mono/issues/359))
|
|
3055
|
-
- Crash in `visibleWidth()` and grapheme iteration when encountering undefined code points ([#372](https://github.com/badlogic/pi-mono/pull/372) by [@HACKE-RC](https://github.com/HACKE-RC))
|
|
3056
|
-
- ZWJ emoji sequences (rainbow flag, family, etc.) now render with correct width instead of being split into multiple characters ([#369](https://github.com/badlogic/pi-mono/pull/369) by [@nathyong](https://github.com/nathyong))
|
|
3057
|
-
|
|
3058
|
-
Older entries are archived in [packages/tui/CHANGELOG.md@bac7e83b5b0e](https://github.com/can1357/oh-my-pi/blob/bac7e83b5b0eb86c909c17830a6666efc359578b/packages/tui/CHANGELOG.md).
|
|
3026
|
+
Older entries are archived in [packages/tui/CHANGELOG.md@58141d4e5fa8](https://github.com/can1357/oh-my-pi/blob/58141d4e5fa892166024e2168866c45e0baacde3/packages/tui/CHANGELOG.md).
|
|
@@ -119,7 +119,7 @@ export declare const KEYBINDINGS: {
|
|
|
119
119
|
readonly description: "Delete word backward";
|
|
120
120
|
};
|
|
121
121
|
readonly "tui.editor.deleteWordForward": {
|
|
122
|
-
readonly defaultKeys: ["alt+delete", "alt+d", "super+alt+delete", "super+alt+d"];
|
|
122
|
+
readonly defaultKeys: ["alt+delete", "alt+d", "ctrl+delete", "super+alt+delete", "super+alt+d"];
|
|
123
123
|
readonly description: "Delete word forward";
|
|
124
124
|
};
|
|
125
125
|
readonly "tui.editor.deleteToLineStart": {
|
|
@@ -77,8 +77,11 @@ export interface AutocompleteProvider {
|
|
|
77
77
|
};
|
|
78
78
|
/** Get inline hint text to show as dim ghost text after the cursor */
|
|
79
79
|
getInlineHint?(lines: string[], cursorLine: number, cursorCol: number): string | null;
|
|
80
|
-
/**
|
|
81
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Synchronously list slash command-name completions for a leading slash token (no async I/O).
|
|
82
|
+
* Mirrors the command-name branch of {@link getSuggestions}, including the bare `/` listing and
|
|
83
|
+
* the collapsed `/skill:` namespace row. Returns null outside a command-name token or with no match.
|
|
84
|
+
*/
|
|
82
85
|
trySyncSlashCompletion?(textBeforeCursor: string): {
|
|
83
86
|
items: AutocompleteItem[];
|
|
84
87
|
prefix: string;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which chart a table becomes and the data it plots. {@link planChart} is the
|
|
3
|
+
* local best guess over a {@link TableAnalysis}: the chart kind, the label
|
|
4
|
+
* column and the measure columns to draw. A host may substitute its own
|
|
5
|
+
* {@link ChartPlan} (a model's pick for a multi-series table); either way
|
|
6
|
+
* {@link buildChart} turns it into the {@link ChartSpec} that
|
|
7
|
+
* {@link renderChartSvg} draws, and {@link worthCharting} decides whether
|
|
8
|
+
* the picture says more than the table already does.
|
|
9
|
+
*
|
|
10
|
+
* The heuristics were fitted on ~7k tables from real assistant answers: the
|
|
11
|
+
* label is the first text column (index columns like `#`/`Rank` never are),
|
|
12
|
+
* table order is kept (most tables are deliberately unsorted), total rows stay
|
|
13
|
+
* out of the scale, and units never share an axis.
|
|
14
|
+
*/
|
|
15
|
+
import { type Dimension, type TableAnalysis } from "./table-data.js";
|
|
16
|
+
/**
|
|
17
|
+
* Chart kinds, by the table shapes they fit:
|
|
18
|
+
* - `bar`: one measure across categories.
|
|
19
|
+
* - `paired`: two same-unit measures per category (before/after, A vs B), or `a → b` cells.
|
|
20
|
+
* - `grouped`: three or four same-unit measures per category.
|
|
21
|
+
* - `line`: measures over an ordered axis (dates, runs, sizes).
|
|
22
|
+
* - `heatmap`: a matrix of same-unit values.
|
|
23
|
+
* - `multiples`: measures in different units, each scaled on its own; transposed when each row is its own metric.
|
|
24
|
+
* - `share`: parts of a whole (percentages summing to 100).
|
|
25
|
+
* - `diverging`: signed changes around zero.
|
|
26
|
+
* - `scatter`: two continuous measures per item.
|
|
27
|
+
*/
|
|
28
|
+
export type ChartKind = "bar" | "paired" | "grouped" | "line" | "heatmap" | "multiples" | "share" | "diverging" | "scatter";
|
|
29
|
+
/** What to draw from a table: the kind, the column naming each category, and the columns plotted. */
|
|
30
|
+
export interface ChartPlan {
|
|
31
|
+
readonly kind: ChartKind;
|
|
32
|
+
/** Column naming each category (or holding the x values of a line); rows are numbered without one. */
|
|
33
|
+
readonly label: number | undefined;
|
|
34
|
+
/** Plotted columns, in order. */
|
|
35
|
+
readonly series: readonly number[];
|
|
36
|
+
/** Each data row is its own series (a metric with its own unit) and the plotted columns are the categories. */
|
|
37
|
+
readonly transpose: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** One plotted value. */
|
|
40
|
+
export interface ChartPoint {
|
|
41
|
+
/** Value in base units (seconds, bytes, …). */
|
|
42
|
+
readonly value: number;
|
|
43
|
+
/** The number as the table wrote it (`~$250`, `12.5 ms`), shown as the value label. */
|
|
44
|
+
readonly text: string;
|
|
45
|
+
/** The table bolded the cell. */
|
|
46
|
+
readonly emphasis: boolean;
|
|
47
|
+
/** Upper end of a range cell (`24–72 h`). */
|
|
48
|
+
readonly upper?: number;
|
|
49
|
+
}
|
|
50
|
+
export interface ChartSeries {
|
|
51
|
+
readonly name: string;
|
|
52
|
+
readonly dim: Dimension;
|
|
53
|
+
/** Currency symbol or rate unit the axis prints; empty for other dimensions. */
|
|
54
|
+
readonly unit: string;
|
|
55
|
+
/** One per category; `null` where the cell holds no number of this series' dimension. */
|
|
56
|
+
readonly points: readonly (ChartPoint | null)[];
|
|
57
|
+
/** Status words standing in for a value (`TIMEOUT`, `OOM`), one per category. */
|
|
58
|
+
readonly notes: readonly (string | undefined)[];
|
|
59
|
+
}
|
|
60
|
+
/** The data a chart draws, independent of how it is drawn. */
|
|
61
|
+
export interface ChartSpec {
|
|
62
|
+
readonly kind: ChartKind;
|
|
63
|
+
readonly categories: readonly string[];
|
|
64
|
+
readonly series: readonly ChartSeries[];
|
|
65
|
+
/** Numeric x positions of a line or the x measure of a scatter, one per category. */
|
|
66
|
+
readonly x?: {
|
|
67
|
+
readonly name: string;
|
|
68
|
+
readonly dim: Dimension;
|
|
69
|
+
readonly unit: string;
|
|
70
|
+
readonly values: readonly number[];
|
|
71
|
+
};
|
|
72
|
+
/** Total/summary rows, printed under the chart instead of skewing its scale. */
|
|
73
|
+
readonly caption?: string;
|
|
74
|
+
/** Name of the category axis (the label column's header). */
|
|
75
|
+
readonly axis: string;
|
|
76
|
+
}
|
|
77
|
+
/** The local best guess at a chart for `table`, or `undefined` when no chart fits. */
|
|
78
|
+
export declare function planChart(table: TableAnalysis): ChartPlan | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* The data `plan` draws from `table`, or `undefined` when it leaves fewer than
|
|
81
|
+
* two categories or no series with two values. The kind is fitted to the
|
|
82
|
+
* series it ends up with ({@link fitKind}), so any plan — a model's pick
|
|
83
|
+
* included — draws as something the renderer supports.
|
|
84
|
+
*/
|
|
85
|
+
export declare function buildChart(table: TableAnalysis, plan: ChartPlan): ChartSpec | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* Whether a chart of `spec` reads faster than its table: at least four
|
|
88
|
+
* categories, and either nine plotted values or a 3× spread within a series.
|
|
89
|
+
* On a model-judged sample of assistant tables this gate kept 85% of the
|
|
90
|
+
* tables a chart clearly helped while rejecting most two- and three-row ones.
|
|
91
|
+
*/
|
|
92
|
+
export declare function worthCharting(spec: ChartSpec): boolean;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Draws a {@link ChartSpec} as a standalone SVG document colored only with
|
|
3
|
+
* figure tokens — `var(--fg)`, `var(--muted)`, `var(--border)`,
|
|
4
|
+
* `var(--accent)`, `var(--surface)`, `var(--c1)`…`var(--c6)` — on a
|
|
5
|
+
* transparent background — the token vocabulary of assistant ```svg figures —
|
|
6
|
+
* so the host resolves them against the live theme (`prepareSvg`) for both
|
|
7
|
+
* the rasterized terminal image and the native TSP blob.
|
|
8
|
+
*
|
|
9
|
+
* Geometry follows the terminal: 16 user units per row and a monospace face,
|
|
10
|
+
* so 13-unit labels land near the terminal's own glyph size. Marks are flat
|
|
11
|
+
* and square (no rounding, gradients or shadows); bolded table cells draw at
|
|
12
|
+
* full strength while their neighbours recede.
|
|
13
|
+
*/
|
|
14
|
+
import type { ChartSpec } from "./chart-plan.js";
|
|
15
|
+
import type { Dimension } from "./table-data.js";
|
|
16
|
+
/** A drawn chart: the SVG document and its size in user units. */
|
|
17
|
+
export interface ChartSvg {
|
|
18
|
+
readonly svg: string;
|
|
19
|
+
readonly width: number;
|
|
20
|
+
readonly height: number;
|
|
21
|
+
}
|
|
22
|
+
/** The SVG document for `spec`. */
|
|
23
|
+
export declare function renderChartSvg(spec: ChartSpec): ChartSvg;
|
|
24
|
+
/** One-line description of the chart, for screen readers and terminals that show the alt text. */
|
|
25
|
+
export declare function chartAlt(spec: ChartSpec): string;
|
|
26
|
+
/**
|
|
27
|
+
* A value in base units as an axis label: durations and sizes in the unit
|
|
28
|
+
* their magnitude reads best in, counts compacted (`12k`, `3.4M`).
|
|
29
|
+
*/
|
|
30
|
+
export declare function formatValue(value: number, dim: Dimension, unit: string): string;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Numeric reading of GFM table cells and columns, the input to
|
|
3
|
+
* {@link planChart}: what number a cell holds (in base units, with its
|
|
4
|
+
* dimension and annotations) and which role each column plays — a measure to
|
|
5
|
+
* plot, a category label, an ordered x axis, or a row index to ignore.
|
|
6
|
+
*
|
|
7
|
+
* Cells are model-written prose, not CSV: `~1,400`, `**4** ✅`, `17 (21.5%)`,
|
|
8
|
+
* `24–72 h`, `12 → 15`, `5/8`, `1m 56s`, `TIMEOUT`. The reader keeps the first
|
|
9
|
+
* number and its unit and classifies the rest, so a column of mostly clean
|
|
10
|
+
* numbers survives a few annotated or sentinel cells.
|
|
11
|
+
*/
|
|
12
|
+
/** What a number measures; values of one dimension share an axis after unit normalization. */
|
|
13
|
+
export type Dimension = "count" | "percent" | "duration" | "bytes" | "currency" | "ratio" | "fraction" | "rate";
|
|
14
|
+
/**
|
|
15
|
+
* How cleanly a cell reads as its number: bare (`12 ms`), with a short note
|
|
16
|
+
* (`17 (21.5%)`, `16 pass`), a range (`24–72 h`), a transition (`12 → 15`), a
|
|
17
|
+
* fraction (`5/8`), or a number leading prose (`~1,400w, nearly ready`).
|
|
18
|
+
*/
|
|
19
|
+
export type CellFit = "pure" | "note" | "range" | "arrow" | "fraction" | "prose";
|
|
20
|
+
/** A cell that reads as a number. */
|
|
21
|
+
export interface NumberCell {
|
|
22
|
+
readonly kind: "number";
|
|
23
|
+
/** Value in base units: seconds, bytes, a plain count, percent points. */
|
|
24
|
+
readonly value: number;
|
|
25
|
+
readonly dim: Dimension;
|
|
26
|
+
/** Unit or currency symbol as written (`ms`, `KiB`, `$`, `tok/s`); empty for bare numbers. */
|
|
27
|
+
readonly unit: string;
|
|
28
|
+
readonly fit: CellFit;
|
|
29
|
+
/** Written as an estimate or bound (`~`, `≈`, `<`, `≥`). */
|
|
30
|
+
readonly approx: boolean;
|
|
31
|
+
/** Written with an explicit sign (`+12%`, `-3`). */
|
|
32
|
+
readonly signed: boolean;
|
|
33
|
+
/** The cell was bolded: models bold the value they want noticed. */
|
|
34
|
+
readonly emphasis: boolean;
|
|
35
|
+
/** The cell text without Markdown decoration. */
|
|
36
|
+
readonly text: string;
|
|
37
|
+
/** The number as written, with approximation, sign, currency and unit (`~$250`, `12.5 ms`, `24–72 h`). */
|
|
38
|
+
readonly figure: string;
|
|
39
|
+
/** `a → b`: `b`, in the same base units. */
|
|
40
|
+
readonly to?: number;
|
|
41
|
+
/** `a → b`: `b` as written. */
|
|
42
|
+
readonly toFigure?: string;
|
|
43
|
+
/** `a–b`: `b`, in the same base units. */
|
|
44
|
+
readonly upper?: number;
|
|
45
|
+
/** `a/b`: `b`. */
|
|
46
|
+
readonly denominator?: number;
|
|
47
|
+
}
|
|
48
|
+
/** A cell that is not a quantity. */
|
|
49
|
+
export interface OtherCell {
|
|
50
|
+
readonly kind: "missing" | "date" | "time" | "version" | "id" | "text";
|
|
51
|
+
readonly text: string;
|
|
52
|
+
readonly emphasis: boolean;
|
|
53
|
+
}
|
|
54
|
+
export type Cell = NumberCell | OtherCell;
|
|
55
|
+
/**
|
|
56
|
+
* What a column contributes to a chart: `measure` values to plot, `label`
|
|
57
|
+
* category names, `temporal` dates or times, `sequence` an ordered step
|
|
58
|
+
* (`Run`, `Day`, `Workers`), `index` a row number or identifier never plotted.
|
|
59
|
+
*/
|
|
60
|
+
export type ColumnRole = "measure" | "label" | "temporal" | "sequence" | "index";
|
|
61
|
+
export interface TableColumn {
|
|
62
|
+
readonly index: number;
|
|
63
|
+
/** Header text without Markdown decoration. */
|
|
64
|
+
readonly header: string;
|
|
65
|
+
readonly role: ColumnRole;
|
|
66
|
+
readonly cells: readonly Cell[];
|
|
67
|
+
/** Dominant dimension of the numeric cells. */
|
|
68
|
+
readonly dim: Dimension | undefined;
|
|
69
|
+
/** Representative unit of the dominant dimension (`$`, `tok/s`); empty when bare. */
|
|
70
|
+
readonly unit: string;
|
|
71
|
+
/** Numeric cells disagree on dimension (a metric-per-row table). */
|
|
72
|
+
readonly mixed: boolean;
|
|
73
|
+
/** Every numeric data cell holds the same value: an echoed setting, not a measure. */
|
|
74
|
+
readonly constant: boolean;
|
|
75
|
+
/** Share of numeric cells written with an explicit sign. */
|
|
76
|
+
readonly signedShare: number;
|
|
77
|
+
/** Share of numeric cells written as `a → b`. */
|
|
78
|
+
readonly arrowShare: number;
|
|
79
|
+
}
|
|
80
|
+
/** A table read for charting. */
|
|
81
|
+
export interface TableAnalysis {
|
|
82
|
+
readonly columns: readonly TableColumn[];
|
|
83
|
+
/** Category labels: the first text or temporal column, else a sequence or index column. */
|
|
84
|
+
readonly label: TableColumn | undefined;
|
|
85
|
+
/** Plottable measure columns, constant ones excluded. */
|
|
86
|
+
readonly measures: readonly TableColumn[];
|
|
87
|
+
/** Data rows, in table order, without total/summary rows. */
|
|
88
|
+
readonly rows: readonly number[];
|
|
89
|
+
/** Total/summary rows (`Total`, `Average`, …), kept out of the scale. */
|
|
90
|
+
readonly totals: readonly number[];
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Strip inline Markdown from a cell: links keep their text; bold, italics,
|
|
94
|
+
* strikethrough, code spans and `<br>` keep their content.
|
|
95
|
+
*/
|
|
96
|
+
export declare function plainCell(markdown: string): {
|
|
97
|
+
text: string;
|
|
98
|
+
emphasis: boolean;
|
|
99
|
+
};
|
|
100
|
+
/** Parse the first number of a (Markdown) cell, or classify why it is not one. */
|
|
101
|
+
export declare function parseCell(markdown: string): Cell;
|
|
102
|
+
/** Read a table's columns and rows for charting. Cells are the raw Markdown of each cell. */
|
|
103
|
+
export declare function analyzeTable(header: readonly string[], rows: readonly (readonly string[])[]): TableAnalysis;
|
|
104
|
+
/** Strictly increasing or strictly decreasing. */
|
|
105
|
+
export declare function isMonotonic(values: readonly number[]): boolean;
|
|
106
|
+
/** Whether `cell` reads as a number. */
|
|
107
|
+
export declare function isNumber(cell: Cell | undefined): cell is NumberCell;
|
|
@@ -86,6 +86,8 @@ export declare class AssistantMessageComponent extends Container {
|
|
|
86
86
|
*/
|
|
87
87
|
setExpanded(expanded: boolean): void;
|
|
88
88
|
isTranscriptBlockFinalized(): boolean;
|
|
89
|
+
/** Whether a figure still waits for the raster of its final source; the transcript holds retirement meanwhile. */
|
|
90
|
+
isTranscriptBlockPending(): boolean;
|
|
89
91
|
render(width: number): readonly string[];
|
|
90
92
|
/**
|
|
91
93
|
* A `col` (role `omp.assistant`) of `md` nodes keyed by content index, so
|
|
@@ -123,6 +125,8 @@ export declare class AssistantMessageComponent extends Container {
|
|
|
123
125
|
messagePersistenceKey(): string | undefined;
|
|
124
126
|
/** Toggle rendering for assistant-native and tool-result images. */
|
|
125
127
|
setImagesVisible(visible: boolean): void;
|
|
128
|
+
/** Toggle charts under numeric tables (the main session's transcript only, never a subagent's). */
|
|
129
|
+
setTableChartsVisible(visible: boolean): void;
|
|
126
130
|
/** Toggle only images produced by tool results; assistant-native images remain governed by setImagesVisible. */
|
|
127
131
|
setToolResultImagesVisible(visible: boolean): void;
|
|
128
132
|
setToolResultImages(toolCallId: string, images: ImageContent[]): void;
|
|
@@ -28,6 +28,8 @@ export interface ChatTranscriptBuilderDeps {
|
|
|
28
28
|
expandThinkingBlocks?: () => boolean;
|
|
29
29
|
/** Session-scoped resolved destinations for model-authored Markdown links. */
|
|
30
30
|
linkTargets?: ReadonlyMap<string, string>;
|
|
31
|
+
/** Draw charts under numeric tables; false for transcripts that are not the main session's. Default: true. */
|
|
32
|
+
tableCharts?: boolean;
|
|
31
33
|
requestRender: () => void;
|
|
32
34
|
}
|
|
33
35
|
export declare class ChatTranscriptBuilder {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ImageBudget } from "../components/image.js";
|
|
2
|
+
import type { Markdown } from "../components/markdown.js";
|
|
3
|
+
import { Container } from "../tui.js";
|
|
4
|
+
export interface FigureMarkdownOptions {
|
|
5
|
+
/** Markdown for one prose run (and a failed figure's code fallback); FigureMarkdown drives its transience. */
|
|
6
|
+
markdown: (text: string) => Markdown;
|
|
7
|
+
/** Draw charts under numeric tables; off for transcripts that are not the main session's. */
|
|
8
|
+
charts: boolean;
|
|
9
|
+
/** Shared inline-image budget for the figures. */
|
|
10
|
+
budget?: ImageBudget;
|
|
11
|
+
/** A figure's rows changed outside a text update (raster landed, fell back to code). */
|
|
12
|
+
onChange: () => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Assistant Markdown whose top-level ```svg fences are lifted out of the prose
|
|
16
|
+
* and drawn as images ({@link SvgFigure}), and whose numeric tables get a chart
|
|
17
|
+
* drawn under them ({@link TableChartFigure}), with ordinary {@link Markdown}
|
|
18
|
+
* for the prose between them. Stands in for Markdown on an assistant text
|
|
19
|
+
* block that holds an svg fence or a chartable table: {@link setText} streams
|
|
20
|
+
* in place, reusing every part whose kind is unchanged, and
|
|
21
|
+
* {@link transientRenderCache} marks the block's tail as still streaming — the
|
|
22
|
+
* last prose run renders transient, the last figure throttles its rasters
|
|
23
|
+
* until its fence closes, and a table at the tail waits for its last row.
|
|
24
|
+
*/
|
|
25
|
+
export declare class FigureMarkdown extends Container {
|
|
26
|
+
#private;
|
|
27
|
+
constructor(text: string, options: FigureMarkdownOptions);
|
|
28
|
+
/** Replace the source; returns whether it changed. */
|
|
29
|
+
setText(text: string): boolean;
|
|
30
|
+
get transientRenderCache(): boolean;
|
|
31
|
+
set transientRenderCache(value: boolean);
|
|
32
|
+
/**
|
|
33
|
+
* The trimmed prose ahead of the first figure, or "" when the block opens
|
|
34
|
+
* with one. These are the only rows plain Markdown reproduces byte for
|
|
35
|
+
* byte, so they are all a transcript may publish ahead of the figure.
|
|
36
|
+
*/
|
|
37
|
+
get leadingProse(): string;
|
|
38
|
+
/** Whether a figure's final source still waits for its raster, or a chart for its pick or raster. */
|
|
39
|
+
get pending(): boolean;
|
|
40
|
+
/** Rebuild the prose runs through the Markdown factory after its theme changed; figures keep their rasters. */
|
|
41
|
+
restyle(): void;
|
|
42
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ImageBudget } from "../components/image.js";
|
|
2
|
+
import type { Markdown } from "../components/markdown.js";
|
|
3
|
+
import type { Component } from "../tui.js";
|
|
4
|
+
/** The active theme's color per figure token, for resolving a figure with `prepareSvg`. */
|
|
5
|
+
export declare function svgFigurePalette(): Record<string, string>;
|
|
6
|
+
/** Turn lifting ```svg fences into images on or off (the host's `tui.renderSvg`); the host rebuilds the transcript after. */
|
|
7
|
+
export declare function setSvgFigureRendering(enabled: boolean): void;
|
|
8
|
+
/** Whether assistant ```svg fences render as images where the terminal can show them. */
|
|
9
|
+
export declare function svgFigureRendering(): boolean;
|
|
10
|
+
export interface SvgFigureOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Markdown for the fenced-code fallback shown when the final source does not
|
|
13
|
+
* render. Without it the figure decorates content shown elsewhere (a chart
|
|
14
|
+
* under its table): it stays blank until drawn, and when it cannot be.
|
|
15
|
+
*/
|
|
16
|
+
markdown?: (text: string) => Markdown;
|
|
17
|
+
/** Shared inline-image budget; superseded rasters are released from it. */
|
|
18
|
+
budget?: ImageBudget;
|
|
19
|
+
/** The figure's rows changed outside a text update: a raster landed or rendering fell back to code. */
|
|
20
|
+
onChange: () => void;
|
|
21
|
+
}
|
|
22
|
+
export declare class SvgFigure implements Component {
|
|
23
|
+
#private;
|
|
24
|
+
constructor(options: SvgFigureOptions);
|
|
25
|
+
/** Feed the fence body; `final` once the fence closed or the message stopped streaming. */
|
|
26
|
+
update(source: string, final: boolean): void;
|
|
27
|
+
/** Whether the final source still waits for its raster (or for failing to render). */
|
|
28
|
+
get pending(): boolean;
|
|
29
|
+
render(width: number): readonly string[];
|
|
30
|
+
invalidate(): void;
|
|
31
|
+
dispose(): void;
|
|
32
|
+
debugState(): Record<string, unknown>;
|
|
33
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source-level handling of ```svg fences in assistant Markdown: splitting
|
|
3
|
+
* prose around them ({@link splitSvgFences}), turning a fence that is still
|
|
4
|
+
* streaming into a document an SVG parser accepts ({@link closePartialSvg}),
|
|
5
|
+
* and resolving the theme tokens figures color themselves with
|
|
6
|
+
* ({@link prepareSvg}). `FigureMarkdown` lifts the fences out as `SvgFigure`
|
|
7
|
+
* images.
|
|
8
|
+
*/
|
|
9
|
+
/** One run of an assistant text block: prose, or the body of a ```svg fence. */
|
|
10
|
+
export type FigureSegment = {
|
|
11
|
+
readonly kind: "markdown";
|
|
12
|
+
readonly text: string;
|
|
13
|
+
}
|
|
14
|
+
/** `closed` once the fence's closing line arrived. */
|
|
15
|
+
| {
|
|
16
|
+
readonly kind: "svg";
|
|
17
|
+
readonly source: string;
|
|
18
|
+
readonly closed: boolean;
|
|
19
|
+
};
|
|
20
|
+
/** Whether `markdown` holds a ```svg fence {@link splitSvgFences} would lift. */
|
|
21
|
+
export declare function hasSvgFence(markdown: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Split `markdown` into prose and ```svg fence bodies, in order. Only
|
|
24
|
+
* top-level fences lift; an svg fence nested in another fence, a list's
|
|
25
|
+
* indented block or a blockquote stays prose. Blank prose runs are dropped.
|
|
26
|
+
* An svg fence still open at the end is the last segment, `closed: false`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function splitSvgFences(markdown: string): FigureSegment[];
|
|
29
|
+
/**
|
|
30
|
+
* Turn a possibly truncated SVG source into a well-formed document: cut the
|
|
31
|
+
* trailing construct still being written (a tag without its `>`, an
|
|
32
|
+
* unterminated comment, CDATA section, processing instruction or declaration,
|
|
33
|
+
* a dangling `&entity`) and close every element left open, innermost first.
|
|
34
|
+
* A complete document passes through unchanged. Returns null until the root
|
|
35
|
+
* `<svg …>` start tag is complete — before that there is nothing to draw.
|
|
36
|
+
*/
|
|
37
|
+
export declare function closePartialSvg(source: string): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Make a figure's source what an SVG rasterizer draws as written, in the
|
|
40
|
+
* reader's theme. Rasterizers resolve neither CSS custom properties nor an
|
|
41
|
+
* inherited text color, and reject a root without the SVG namespace, so:
|
|
42
|
+
* - `var(--name)` / `var(--name, fallback)` become `palette[name]` (an
|
|
43
|
+
* unknown name takes its fallback, else `palette.fg`);
|
|
44
|
+
* - a root `<svg>` lacking them gets `color` = `palette.fg` (so
|
|
45
|
+
* `currentColor` follows the theme), a sans-serif `font-family` (instead
|
|
46
|
+
* of the rasterizer's Times), `xmlns`, and `xmlns:xlink` when the source
|
|
47
|
+
* uses `xlink:` attributes.
|
|
48
|
+
*/
|
|
49
|
+
export declare function prepareSvg(svg: string, palette: Readonly<Record<string, string>>): string;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Tokens } from "@oh-my-pi/pi-utils/marked";
|
|
2
|
+
import { type ChartPlan } from "../charts/chart-plan.js";
|
|
3
|
+
import { type TableAnalysis } from "../charts/table-data.js";
|
|
4
|
+
import type { ImageBudget } from "../components/image.js";
|
|
5
|
+
import type { NativeNode } from "../native/node.js";
|
|
6
|
+
import type { Component } from "../tui.js";
|
|
7
|
+
/**
|
|
8
|
+
* `smart`: a model picks the kind and columns of multi-series tables;
|
|
9
|
+
* `always`: the local best guess charts every table it reads as numeric;
|
|
10
|
+
* `off`: tables stay tables.
|
|
11
|
+
*/
|
|
12
|
+
export type TableChartMode = "smart" | "always" | "off";
|
|
13
|
+
/** A multi-series table the host's planner picks a chart for. */
|
|
14
|
+
export interface TableChartRequest {
|
|
15
|
+
/** The table's Markdown source. */
|
|
16
|
+
readonly markdown: string;
|
|
17
|
+
readonly table: TableAnalysis;
|
|
18
|
+
/** The local best guess; the planner may keep it. */
|
|
19
|
+
readonly guess: ChartPlan;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Picks the chart for a multi-series table under `smart`: a plan, or `null`
|
|
23
|
+
* for no chart. A rejection falls back to the local guess.
|
|
24
|
+
*/
|
|
25
|
+
export type TableChartPlanner = (request: TableChartRequest) => Promise<ChartPlan | null>;
|
|
26
|
+
/** A drawn chart, ready to resolve against the theme. */
|
|
27
|
+
export interface TableChart {
|
|
28
|
+
/** SVG colored with figure tokens (`var(--accent)`, …). */
|
|
29
|
+
readonly svg: string;
|
|
30
|
+
readonly alt: string;
|
|
31
|
+
/** Size in SVG user units. */
|
|
32
|
+
readonly width: number;
|
|
33
|
+
readonly height: number;
|
|
34
|
+
}
|
|
35
|
+
/** The chart under a table: drawn, none, or a smart pick on its way (the promise settles once the cache holds it). */
|
|
36
|
+
export type ChartLookup = TableChart | null | Promise<void>;
|
|
37
|
+
/** One run of a Markdown block: prose (ending at a charted table), or the chart drawn after that table. */
|
|
38
|
+
export type ChartSegment = {
|
|
39
|
+
readonly kind: "markdown";
|
|
40
|
+
readonly text: string;
|
|
41
|
+
} | {
|
|
42
|
+
readonly kind: "chart";
|
|
43
|
+
readonly table: Tokens.Table;
|
|
44
|
+
};
|
|
45
|
+
/** Set how tables become charts (the host's `tui.autoGraph`) and its `smart` planner; the host rebuilds the transcript after. */
|
|
46
|
+
export declare function setTableCharts(mode: TableChartMode, planner?: TableChartPlanner): void;
|
|
47
|
+
/** How assistant tables currently become charts. */
|
|
48
|
+
export declare function tableChartMode(): TableChartMode;
|
|
49
|
+
/** Whether `markdown` may hold a table {@link splitTableCharts} would chart. */
|
|
50
|
+
export declare function hasChartTable(markdown: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Split `markdown` after each top-level table that has (or may get) a chart.
|
|
53
|
+
* A table still streaming at the block's tail is left whole: rows may follow.
|
|
54
|
+
*/
|
|
55
|
+
export declare function splitTableCharts(markdown: string, streaming: boolean): ChartSegment[];
|
|
56
|
+
/**
|
|
57
|
+
* The chart for `table` under the current mode, read once per table source.
|
|
58
|
+
* Under `smart` the first lookup of a multi-series table asks the planner.
|
|
59
|
+
*/
|
|
60
|
+
export declare function lookupTableChart(table: Tokens.Table): ChartLookup;
|
|
61
|
+
/**
|
|
62
|
+
* A native `image` node drawing `chart` from its SVG — no raster — resolved
|
|
63
|
+
* against the active theme; rebuilt when the theme changes.
|
|
64
|
+
*/
|
|
65
|
+
export declare function describeTableChart(chart: TableChart, key: string): NativeNode;
|
|
66
|
+
export interface TableChartFigureOptions {
|
|
67
|
+
/** Shared inline-image budget. */
|
|
68
|
+
budget?: ImageBudget;
|
|
69
|
+
/** The chart's rows changed outside a text update: its pick or raster landed. */
|
|
70
|
+
onChange: () => void;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The chart under one table, as a terminal image a blank row below it. Looks
|
|
74
|
+
* the chart up on first render — a block rebuilt mid-stream never asks for a
|
|
75
|
+
* pick it drops — and renders nothing until it is drawn, or when the table
|
|
76
|
+
* gets no chart.
|
|
77
|
+
*/
|
|
78
|
+
export declare class TableChartFigure implements Component {
|
|
79
|
+
#private;
|
|
80
|
+
constructor(options: TableChartFigureOptions);
|
|
81
|
+
/** Point the figure at `table`. */
|
|
82
|
+
update(table: Tokens.Table): void;
|
|
83
|
+
/** Whether the chart is still being looked up, picked or drawn. */
|
|
84
|
+
get pending(): boolean;
|
|
85
|
+
render(width: number): readonly string[];
|
|
86
|
+
invalidate(): void;
|
|
87
|
+
dispose(): void;
|
|
88
|
+
}
|
|
@@ -285,7 +285,7 @@ export declare class Editor implements Component, Focusable {
|
|
|
285
285
|
* Mirrors the transient state the key dispatch tears down before this action so the two
|
|
286
286
|
* cannot diverge: a pending character jump is cancelled by any other key, and an open
|
|
287
287
|
* spelling-assist popup is dismissed by anything that is not one of its accept keys (its
|
|
288
|
-
*
|
|
288
|
+
* refresh skips assist mode, so a surviving list would hang around forever).
|
|
289
289
|
* While Vim owns the buffer (Normal or Visual) the operation is Vim's `x` — deleting the
|
|
290
290
|
* selection and returning to Normal in Visual mode, the grapheme under the cursor
|
|
291
291
|
* otherwise. Only Insert mode and Vim-off editors delete straight through. */
|
|
@@ -29,6 +29,8 @@ export interface ImageOptions {
|
|
|
29
29
|
* synchronously instead of committing its reserved blank rows.
|
|
30
30
|
*/
|
|
31
31
|
export declare function renderForScrollback<T>(render: () => T): T;
|
|
32
|
+
/** Widest an {@link Image} rendered at `width` columns draws; callers sizing a raster for it (`SvgFigure`) match it. */
|
|
33
|
+
export declare function imageMaxColumns(width: number): number;
|
|
32
34
|
/** Default count of inline images kept as live graphics before older ones fall back to text. */
|
|
33
35
|
export declare const DEFAULT_MAX_INLINE_IMAGES = 8;
|
|
34
36
|
/**
|
|
@@ -73,6 +75,13 @@ export declare class ImageBudget {
|
|
|
73
75
|
* gets a fresh id every call.
|
|
74
76
|
*/
|
|
75
77
|
acquireId(key?: string): number;
|
|
78
|
+
/**
|
|
79
|
+
* Retire the graphic held under `key` as soon as no frame shows it, instead
|
|
80
|
+
* of when the residency sweep reaches it. For an owner that replaced the
|
|
81
|
+
* image for good — a streaming SVG figure's earlier raster — so superseded
|
|
82
|
+
* revisions never push older images (scrollback included) out of the store.
|
|
83
|
+
*/
|
|
84
|
+
release(key: string): void;
|
|
76
85
|
/**
|
|
77
86
|
* Start an alternate-buffer lifecycle. Call once per `?1049h`, before the
|
|
78
87
|
* first pass of the fullscreen overlay or resize borrow that owns the buffer.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type TokensList } from "@oh-my-pi/pi-utils/marked";
|
|
1
2
|
import type { DescribeContext, NativeNode } from "../native/node.js";
|
|
2
3
|
import type { SymbolTheme } from "../symbols.js";
|
|
3
4
|
import type { Component } from "../tui.js";
|
|
@@ -5,6 +6,14 @@ import type { Component } from "../tui.js";
|
|
|
5
6
|
export declare function fastLineStartHazard(grownLine: string): boolean;
|
|
6
7
|
/** @internal exported for tests — the windowed lexer's first-probe window size. */
|
|
7
8
|
export declare const LEX_WINDOW_BYTES: number;
|
|
9
|
+
/**
|
|
10
|
+
* Lex a whole document with the renderer's rules (math, custom rules,
|
|
11
|
+
* strikethrough), windowing anything large enough for the quadratic scan to
|
|
12
|
+
* bite. `links` holds every reference definition, at any nesting depth. Callers
|
|
13
|
+
* that split a document along its blocks (table charts) lex through this so
|
|
14
|
+
* they see the blocks Markdown renders.
|
|
15
|
+
*/
|
|
16
|
+
export declare function lexDocument(text: string): TokensList;
|
|
8
17
|
/** A hyperlink as the renderer sees it: inline `[text](href)`, `<autolink>`, bare GFM URL, or reference link. */
|
|
9
18
|
export interface MarkdownLink {
|
|
10
19
|
/** Flattened visible label with whitespace collapsed to one row; falls back to `href` when empty. */
|