@pi-unipi/image 2.2.3 → 2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/image",
3
- "version": "2.2.3",
3
+ "version": "2.2.4",
4
4
  "description": "Image generation and image recognition tools for the Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { Component } from "@earendil-works/pi-tui";
9
- import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
9
+ import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
10
10
  import type { Theme } from "@earendil-works/pi-coding-agent";
11
11
  import { boxInnerWidth, safeRepeat } from "@pi-unipi/core";
12
12
 
@@ -61,7 +61,7 @@ export class ImageModelSelectorOverlay implements Component {
61
61
  handleInput(data: string): void {
62
62
  // Ctrl+C must always escape, even mid-filter. Without this the overlay traps
63
63
  // the user with no way out.
64
- if (data === "\x03") {
64
+ if (matchesKey(data, "ctrl+c")) {
65
65
  this.onClose?.();
66
66
  return;
67
67
  }
@@ -76,49 +76,61 @@ export class ImageModelSelectorOverlay implements Component {
76
76
  return;
77
77
  }
78
78
 
79
- switch (data) {
80
- case "\x1b[A":
81
- case "k":
82
- this.selectedIndex = Math.max(0, this.selectedIndex - 1);
83
- break;
84
- case "\x1b[B":
85
- case "j":
86
- this.selectedIndex = Math.min(this.filtered.length - 1, this.selectedIndex + 1);
87
- break;
88
- case "/":
89
- this.filterMode = true;
90
- this.filter = "";
91
- break;
92
- case "c":
93
- case "C":
94
- // Escape hatch: generator detection is heuristic, so a provider may
95
- // expose a model the catalog cannot recognise. Let the user name it.
96
- this.customMode = true;
97
- this.custom = "";
98
- this.error = null;
99
- break;
100
- case "\r":
101
- this.commit();
102
- break;
103
- case "\x1b":
104
- this.onClose?.();
105
- break;
79
+ // Escape is checked via matchesKey, not `data === "\x1b"`: under the kitty
80
+ // keyboard protocol it arrives as "\x1b[27u" (and as "\x1b[27;1;27~" with
81
+ // modifyOtherKeys), so a bare comparison silently fails to close.
82
+ if (matchesKey(data, "escape")) {
83
+ this.onClose?.();
84
+ return;
85
+ }
86
+ if (matchesKey(data, "up") || data === "k") {
87
+ this.selectedIndex = Math.max(0, this.selectedIndex - 1);
88
+ return;
89
+ }
90
+ if (matchesKey(data, "down") || data === "j") {
91
+ this.selectedIndex = Math.min(this.filtered.length - 1, this.selectedIndex + 1);
92
+ return;
93
+ }
94
+ if (matchesKey(data, "enter")) {
95
+ this.commit();
96
+ return;
97
+ }
98
+ if (data === "/") {
99
+ this.filterMode = true;
100
+ this.filter = "";
101
+ return;
102
+ }
103
+ if (data === "c" || data === "C") {
104
+ // Escape hatch: generator detection is heuristic, so a provider may
105
+ // expose a model the catalog cannot recognise. Let the user name it.
106
+ this.customMode = true;
107
+ this.custom = "";
108
+ this.error = null;
106
109
  }
107
110
  }
108
111
 
109
112
  private handleFilterInput(data: string): void {
110
- if (data === "\r") {
113
+ if (matchesKey(data, "enter")) {
111
114
  this.filterMode = false;
112
115
  return;
113
116
  }
114
- if (data === "\x1b") {
117
+ if (matchesKey(data, "escape")) {
115
118
  this.filter = "";
116
119
  this.filterMode = false;
117
120
  this.applyFilter();
118
121
  this.selectedIndex = 0;
119
122
  return;
120
123
  }
121
- if (data === "\x7f" || data === "\b") {
124
+ // Let the list be navigated without leaving the filter.
125
+ if (matchesKey(data, "up")) {
126
+ this.selectedIndex = Math.max(0, this.selectedIndex - 1);
127
+ return;
128
+ }
129
+ if (matchesKey(data, "down")) {
130
+ this.selectedIndex = Math.min(this.filtered.length - 1, this.selectedIndex + 1);
131
+ return;
132
+ }
133
+ if (matchesKey(data, "backspace") || data === "\x7f" || data === "\b") {
122
134
  this.filter = this.filter.slice(0, -1);
123
135
  this.applyFilter();
124
136
  this.clampSelection();
@@ -133,17 +145,17 @@ export class ImageModelSelectorOverlay implements Component {
133
145
 
134
146
  /** Free-text "provider/model-id" entry. */
135
147
  private handleCustomInput(data: string): void {
136
- if (data === "\r") {
148
+ if (matchesKey(data, "enter")) {
137
149
  this.commitCustom();
138
150
  return;
139
151
  }
140
- if (data === "\x1b") {
152
+ if (matchesKey(data, "escape")) {
141
153
  this.customMode = false;
142
154
  this.custom = "";
143
155
  this.error = null;
144
156
  return;
145
157
  }
146
- if (data === "\x7f" || data === "\b") {
158
+ if (matchesKey(data, "backspace") || data === "\x7f" || data === "\b") {
147
159
  this.custom = this.custom.slice(0, -1);
148
160
  this.error = null;
149
161
  return;