pi-ui-extend 0.1.15 → 0.1.18
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/apps/desktop-tauri/README.md +103 -0
- package/apps/desktop-tauri/bin/pix-desktop.mjs +89 -0
- package/dist/app/app.d.ts +2 -0
- package/dist/app/app.js +21 -6
- package/dist/app/commands/command-controller.js +1 -0
- package/dist/app/commands/command-host.d.ts +2 -0
- package/dist/app/commands/command-navigation-actions.d.ts +9 -0
- package/dist/app/commands/command-navigation-actions.js +62 -3
- package/dist/app/commands/command-registry.d.ts +1 -0
- package/dist/app/commands/command-registry.js +8 -0
- package/dist/app/constants.d.ts +0 -1
- package/dist/app/constants.js +0 -1
- package/dist/app/icons.d.ts +1 -0
- package/dist/app/icons.js +2 -0
- package/dist/app/input/input-action-controller.d.ts +1 -0
- package/dist/app/input/input-action-controller.js +5 -4
- package/dist/app/input/input-controller.d.ts +1 -0
- package/dist/app/input/input-controller.js +29 -0
- package/dist/app/input/input-paste-handler.d.ts +1 -1
- package/dist/app/input/input-paste-handler.js +6 -5
- package/dist/app/model/model-usage-status.js +4 -27
- package/dist/app/popup/menu-items-controller.d.ts +2 -0
- package/dist/app/popup/menu-items-controller.js +37 -14
- package/dist/app/popup/popup-menu-controller.js +30 -5
- package/dist/app/rendering/editor-panels.js +20 -9
- package/dist/app/rendering/popup-menu-renderer.d.ts +12 -0
- package/dist/app/rendering/popup-menu-renderer.js +151 -53
- package/dist/app/rendering/render-controller.js +29 -15
- package/dist/app/rendering/render-text.js +5 -2
- package/dist/app/rendering/status-line-renderer.d.ts +7 -0
- package/dist/app/rendering/status-line-renderer.js +191 -94
- package/dist/app/rendering/toast-controller.d.ts +1 -0
- package/dist/app/rendering/toast-controller.js +17 -0
- package/dist/app/screen/mouse-controller.d.ts +1 -0
- package/dist/app/screen/mouse-controller.js +17 -20
- package/dist/app/screen/scroll-controller.d.ts +1 -0
- package/dist/app/screen/scroll-controller.js +6 -0
- package/dist/app/screen/status-controller.js +2 -1
- package/dist/app/session/request-history.d.ts +4 -0
- package/dist/app/session/request-history.js +11 -0
- package/dist/app/session/session-search.js +10 -0
- package/dist/app/session/tabs-controller.d.ts +4 -4
- package/dist/app/session/tabs-controller.js +64 -6
- package/dist/app/todo/todo-model.d.ts +2 -2
- package/dist/app/todo/todo-model.js +15 -17
- package/dist/app/types.d.ts +12 -4
- package/dist/config.d.ts +1 -0
- package/dist/config.js +10 -1
- package/dist/default-pix-config.js +2 -0
- package/dist/fuzzy.d.ts +2 -0
- package/dist/fuzzy.js +27 -7
- package/dist/input-editor.d.ts +9 -0
- package/dist/input-editor.js +52 -0
- package/dist/schemas/pix-schema.d.ts +1 -0
- package/dist/schemas/pix-schema.js +1 -0
- package/dist/theme.js +6 -6
- package/dist/ui.d.ts +8 -0
- package/external/pi-tools-suite/README.md +2 -2
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +40 -5
- package/external/pi-tools-suite/src/antigravity-auth/commands.ts +1 -37
- package/external/pi-tools-suite/src/antigravity-auth/constants.ts +0 -2
- package/external/pi-tools-suite/src/antigravity-auth/index.ts +3 -16
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +33 -17
- package/external/pi-tools-suite/src/antigravity-auth/status.ts +1 -1
- package/external/pi-tools-suite/src/antigravity-auth/stream.ts +4 -12
- package/external/pi-tools-suite/src/antigravity-auth/types.ts +21 -0
- package/external/pi-tools-suite/src/config.ts +43 -0
- package/external/pi-tools-suite/src/dcp/commands.ts +1 -1
- package/external/pi-tools-suite/src/dcp/index.ts +21 -1
- package/external/pi-tools-suite/src/dcp/state.ts +225 -3
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +5 -0
- package/external/pi-tools-suite/src/index.ts +1 -0
- package/external/pi-tools-suite/src/telegram-mirror/README.md +168 -0
- package/external/pi-tools-suite/src/telegram-mirror/bot.ts +228 -0
- package/external/pi-tools-suite/src/telegram-mirror/events.ts +94 -0
- package/external/pi-tools-suite/src/telegram-mirror/format.ts +120 -0
- package/external/pi-tools-suite/src/telegram-mirror/index.ts +424 -0
- package/external/pi-tools-suite/src/telegram-mirror/ipc.ts +419 -0
- package/external/pi-tools-suite/src/telegram-mirror/multiplexer.ts +408 -0
- package/external/pi-tools-suite/src/telegram-mirror/renderer.ts +214 -0
- package/external/pi-tools-suite/src/todo/index.ts +3 -64
- package/external/pi-tools-suite/src/todo/state/persistence.ts +0 -1
- package/external/pi-tools-suite/src/todo/state/state-reducer.ts +7 -37
- package/external/pi-tools-suite/src/todo/todo.ts +2 -18
- package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +2 -11
- package/external/pi-tools-suite/src/todo/tool/types.ts +0 -29
- package/external/pi-tools-suite/src/todo/view/format.ts +1 -3
- package/external/pi-tools-suite/src/tool-descriptions.ts +5 -4
- package/external/pi-tools-suite/src/usage/lib/google.ts +50 -30
- package/external/pi-tools-suite/src/usage/lib/types.ts +12 -2
- package/package.json +14 -3
- package/schemas/pix.json +5 -0
|
@@ -815,9 +815,9 @@ function formatDurationShort(resetAt, now) {
|
|
|
815
815
|
const hours = Math.floor((totalMinutes % 1440) / 60);
|
|
816
816
|
const minutes = totalMinutes % 60;
|
|
817
817
|
if (days > 0)
|
|
818
|
-
return `${days}d
|
|
818
|
+
return `${days}d${hours}h`;
|
|
819
819
|
if (hours > 0)
|
|
820
|
-
return `${hours}h
|
|
820
|
+
return `${hours}h${minutes}m`;
|
|
821
821
|
return `${minutes}m`;
|
|
822
822
|
}
|
|
823
823
|
function maskCredential(value) {
|
|
@@ -826,29 +826,6 @@ function maskCredential(value) {
|
|
|
826
826
|
return visible ? "****" : "unknown";
|
|
827
827
|
return `${visible.slice(0, 4)}****${visible.slice(-4)}`;
|
|
828
828
|
}
|
|
829
|
-
function formatUsageWindow(
|
|
830
|
-
|
|
831
|
-
return `${window.remainingPercent}% ${formatCompactProgressBar(window.remainingPercent)} ${resetLabel}`;
|
|
832
|
-
}
|
|
833
|
-
function formatGlobalResetLabel(resetAt, now) {
|
|
834
|
-
if (resetAt <= now)
|
|
835
|
-
return "reset";
|
|
836
|
-
return resetAt - now <= DAY_SECONDS * 1000 ? formatResetTime(resetAt, now) : formatResetDate(resetAt, now);
|
|
837
|
-
}
|
|
838
|
-
function formatResetTime(resetAt, now) {
|
|
839
|
-
if (resetAt <= now)
|
|
840
|
-
return "reset";
|
|
841
|
-
return new Date(resetAt).toLocaleTimeString("ru-RU", {
|
|
842
|
-
hour: "2-digit",
|
|
843
|
-
minute: "2-digit",
|
|
844
|
-
hourCycle: "h23",
|
|
845
|
-
});
|
|
846
|
-
}
|
|
847
|
-
function formatResetDate(resetAt, now) {
|
|
848
|
-
if (resetAt <= now)
|
|
849
|
-
return "reset";
|
|
850
|
-
return new Date(resetAt).toLocaleDateString("ru-RU", {
|
|
851
|
-
day: "2-digit",
|
|
852
|
-
month: "2-digit",
|
|
853
|
-
});
|
|
829
|
+
function formatUsageWindow(_prefix, window, now) {
|
|
830
|
+
return `${window.remainingPercent}% ${formatCompactProgressBar(window.remainingPercent)} ${formatDurationShort(window.resetAt, now)}`;
|
|
854
831
|
}
|
|
@@ -11,6 +11,7 @@ export declare class AppMenuItemsController {
|
|
|
11
11
|
private readonly host;
|
|
12
12
|
private resumeMenuLoaderCache;
|
|
13
13
|
private userMessageJumpItems;
|
|
14
|
+
private userMessageJumpLoading;
|
|
14
15
|
constructor(host: AppMenuItemsControllerHost);
|
|
15
16
|
parseSlashInput(text: string): import("../types.js").ParsedSlashInput | undefined;
|
|
16
17
|
getResourceSlashCommands(): SlashCommand[];
|
|
@@ -22,6 +23,7 @@ export declare class AppMenuItemsController {
|
|
|
22
23
|
getThinkingMenuItems(query: string): PopupMenuItem<ThinkingMenuValue>[];
|
|
23
24
|
getUserMessageMenuItems(): PopupMenuItem<UserMessageMenuValue>[];
|
|
24
25
|
getUserMessageJumpMenuItems(query: string): PopupMenuItem<UserMessageJumpMenuValue>[];
|
|
26
|
+
isUserMessageJumpLoading(): boolean;
|
|
25
27
|
refreshUserMessageJumpMenuItems(): Promise<void>;
|
|
26
28
|
getQueueMessageMenuItems(): PopupMenuItem<QueueMessageMenuValue>[];
|
|
27
29
|
getResumeMenuItems(query: string, limit?: number): PopupMenuItem<ResumeMenuValue>[];
|
|
@@ -12,6 +12,7 @@ export class AppMenuItemsController {
|
|
|
12
12
|
host;
|
|
13
13
|
resumeMenuLoaderCache;
|
|
14
14
|
userMessageJumpItems;
|
|
15
|
+
userMessageJumpLoading = false;
|
|
15
16
|
constructor(host) {
|
|
16
17
|
this.host = host;
|
|
17
18
|
}
|
|
@@ -29,6 +30,9 @@ export class AppMenuItemsController {
|
|
|
29
30
|
value: match.value,
|
|
30
31
|
label: `/${match.value.name}`,
|
|
31
32
|
description: match.value.description,
|
|
33
|
+
labelHighlightRanges: match.matchedText === match.label
|
|
34
|
+
? match.matchedRanges.map((range) => ({ start: range.start + 1, end: range.end + 1 }))
|
|
35
|
+
: [],
|
|
32
36
|
}));
|
|
33
37
|
}
|
|
34
38
|
modelRef(model) {
|
|
@@ -64,6 +68,7 @@ export class AppMenuItemsController {
|
|
|
64
68
|
value: match.value,
|
|
65
69
|
label: `${match.value.ref}${match.value.current ? ` ${APP_ICONS.check}` : ""}`,
|
|
66
70
|
description: match.value.model.name,
|
|
71
|
+
labelHighlightRanges: labelHighlightRangesFromMatch(match.matchedText, match.matchedRanges, match.label),
|
|
67
72
|
}));
|
|
68
73
|
}
|
|
69
74
|
getThinkingMenuItems(query) {
|
|
@@ -96,26 +101,36 @@ export class AppMenuItemsController {
|
|
|
96
101
|
getUserMessageJumpMenuItems(query) {
|
|
97
102
|
return filterUserMessageJumpItems(this.userMessageJumpItems ?? buildUserMessageJumpItems(this.host.getEntries()), query);
|
|
98
103
|
}
|
|
104
|
+
isUserMessageJumpLoading() {
|
|
105
|
+
return this.userMessageJumpLoading;
|
|
106
|
+
}
|
|
99
107
|
async refreshUserMessageJumpMenuItems() {
|
|
100
108
|
const runtime = this.host.runtime();
|
|
101
109
|
if (!runtime) {
|
|
102
110
|
this.userMessageJumpItems = undefined;
|
|
111
|
+
this.userMessageJumpLoading = false;
|
|
103
112
|
return;
|
|
104
113
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
this.userMessageJumpLoading = true;
|
|
115
|
+
try {
|
|
116
|
+
const entries = await sessionHistoryFullBranchEntries(runtime.session);
|
|
117
|
+
const loadedBySessionEntryId = new Map(this.host.getEntries()
|
|
118
|
+
.filter((entry) => entry.kind === "user" && typeof entry.sessionEntryId === "string")
|
|
119
|
+
.map((entry) => [entry.sessionEntryId, entry]));
|
|
120
|
+
const sources = entries.flatMap((entry) => {
|
|
121
|
+
if (entry.type !== "message" || !isRecord(entry.message) || entry.message.role !== "user")
|
|
122
|
+
return [];
|
|
123
|
+
const text = renderUserMessageContent(entry.message.content);
|
|
124
|
+
if (!text)
|
|
125
|
+
return [];
|
|
126
|
+
const loaded = loadedBySessionEntryId.get(entry.id);
|
|
127
|
+
return [{ text, ...(loaded ? { entryId: loaded.id } : {}), sessionEntryId: entry.id }];
|
|
128
|
+
});
|
|
129
|
+
this.userMessageJumpItems = buildUserMessageJumpItems(sources);
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
this.userMessageJumpLoading = false;
|
|
133
|
+
}
|
|
119
134
|
}
|
|
120
135
|
getQueueMessageMenuItems() {
|
|
121
136
|
return [
|
|
@@ -205,6 +220,14 @@ export class AppMenuItemsController {
|
|
|
205
220
|
}
|
|
206
221
|
}
|
|
207
222
|
}
|
|
223
|
+
function labelHighlightRangesFromMatch(matchedText, matchedRanges, label) {
|
|
224
|
+
if (matchedText === label)
|
|
225
|
+
return matchedRanges;
|
|
226
|
+
const offset = label.toLocaleLowerCase().indexOf(matchedText.toLocaleLowerCase());
|
|
227
|
+
if (offset < 0)
|
|
228
|
+
return [];
|
|
229
|
+
return matchedRanges.map((range) => ({ start: offset + range.start, end: offset + range.end }));
|
|
230
|
+
}
|
|
208
231
|
function normalizeAvailableThinkingLevels(levels) {
|
|
209
232
|
const seen = new Set();
|
|
210
233
|
const normalized = [];
|
|
@@ -666,10 +666,18 @@ export class AppPopupMenuController {
|
|
|
666
666
|
value: item,
|
|
667
667
|
label: item.label,
|
|
668
668
|
...(item.keywords === undefined ? {} : { keywords: item.keywords }),
|
|
669
|
-
})), query
|
|
669
|
+
})), query, {
|
|
670
|
+
...(request.options.minScorePerCharacter === undefined ? {} : { minScorePerCharacter: request.options.minScorePerCharacter }),
|
|
671
|
+
preferKeyboardLayoutMatches: request.options.preferKeyboardLayoutMatches ?? false,
|
|
672
|
+
}).map((match) => ({
|
|
673
|
+
...match.value,
|
|
674
|
+
labelHighlightRanges: match.matchedText === match.label ? match.matchedRanges : [],
|
|
675
|
+
}));
|
|
670
676
|
return this.withoutCloseMenuItems(items.map((item) => ({
|
|
671
677
|
value: item,
|
|
672
678
|
label: item.label,
|
|
679
|
+
...(item.labelHighlightRanges === undefined ? {} : { labelHighlightRanges: item.labelHighlightRanges }),
|
|
680
|
+
...(item.descriptionHighlightRanges === undefined ? {} : { descriptionHighlightRanges: item.descriptionHighlightRanges }),
|
|
673
681
|
...(item.description === undefined ? {} : { description: item.description }),
|
|
674
682
|
})));
|
|
675
683
|
}
|
|
@@ -733,7 +741,8 @@ function formatSessionMenuDateTime(dateTime) {
|
|
|
733
741
|
time: dateTime.toLocaleTimeString("ru-RU", { hour: "2-digit", minute: "2-digit", hourCycle: "h23" }),
|
|
734
742
|
};
|
|
735
743
|
}
|
|
736
|
-
function formatSessionInfoMenuItem(
|
|
744
|
+
function formatSessionInfoMenuItem(source) {
|
|
745
|
+
const { session, labelPrefix } = source;
|
|
737
746
|
const { date, time } = formatSessionMenuDateTime(session.modified);
|
|
738
747
|
const messages = `${session.messageCount} msg${session.messageCount !== 1 ? "s" : ""}`;
|
|
739
748
|
const label = session.name ?? session.firstMessage.slice(0, 50);
|
|
@@ -741,6 +750,7 @@ function formatSessionInfoMenuItem(session, labelPrefix = "") {
|
|
|
741
750
|
value: session,
|
|
742
751
|
label: `${labelPrefix}${label}`,
|
|
743
752
|
description: `${date} ${time} · ${messages} · ${session.id.slice(0, 8)}`,
|
|
753
|
+
...(source.labelHighlightRanges === undefined ? {} : { labelHighlightRanges: source.labelHighlightRanges }),
|
|
744
754
|
};
|
|
745
755
|
}
|
|
746
756
|
function buildSessionInfoMenuSource(sessions, currentSessionFile, query) {
|
|
@@ -761,7 +771,11 @@ function buildSessionInfoMenuSource(sessions, currentSessionFile, query) {
|
|
|
761
771
|
session.id,
|
|
762
772
|
],
|
|
763
773
|
}));
|
|
764
|
-
return fuzzySearch(items, query).map((match) => ({
|
|
774
|
+
return fuzzySearch(items, query).map((match) => ({
|
|
775
|
+
session: match.value,
|
|
776
|
+
labelPrefix: "",
|
|
777
|
+
labelHighlightRanges: match.matchedText === match.label ? match.matchedRanges : [],
|
|
778
|
+
}));
|
|
765
779
|
}
|
|
766
780
|
export function createSessionInfoMenuItemsLoader(sessions, currentSessionFile, query) {
|
|
767
781
|
const source = buildSessionInfoMenuSource(sessions, currentSessionFile, query);
|
|
@@ -775,7 +789,7 @@ export function createSessionInfoMenuItemsLoader(sessions, currentSessionFile, q
|
|
|
775
789
|
const cached = cachedItems.get(effectiveLimit);
|
|
776
790
|
if (cached)
|
|
777
791
|
return cached;
|
|
778
|
-
const result = source.slice(0, effectiveLimit).map((item) => formatSessionInfoMenuItem(item
|
|
792
|
+
const result = source.slice(0, effectiveLimit).map((item) => formatSessionInfoMenuItem(item));
|
|
779
793
|
cachedItems.set(effectiveLimit, result);
|
|
780
794
|
return result;
|
|
781
795
|
},
|
|
@@ -812,5 +826,16 @@ export function filterUserMessageJumpItems(items, query) {
|
|
|
812
826
|
...(item.aliases === undefined ? {} : { aliases: item.aliases }),
|
|
813
827
|
...(item.keywords === undefined ? {} : { keywords: item.keywords }),
|
|
814
828
|
}));
|
|
815
|
-
return fuzzySearch(searchableItems, query).map((match) =>
|
|
829
|
+
return fuzzySearch(searchableItems, query).map((match) => ({
|
|
830
|
+
...match.value,
|
|
831
|
+
labelHighlightRanges: labelHighlightRangesFromMatch(match.matchedText, match.matchedRanges, match.label),
|
|
832
|
+
}));
|
|
833
|
+
}
|
|
834
|
+
function labelHighlightRangesFromMatch(matchedText, matchedRanges, label) {
|
|
835
|
+
if (matchedText === label)
|
|
836
|
+
return matchedRanges;
|
|
837
|
+
const offset = label.toLocaleLowerCase().indexOf(matchedText.toLocaleLowerCase());
|
|
838
|
+
if (offset < 0)
|
|
839
|
+
return [];
|
|
840
|
+
return matchedRanges.map((range) => ({ start: offset + range.start, end: offset + range.end }));
|
|
816
841
|
}
|
|
@@ -20,35 +20,46 @@ export function renderTodoPanel(details, expanded, width, colors) {
|
|
|
20
20
|
const todoPanelColor = colors.warning;
|
|
21
21
|
const todoMetaColor = colors.muted;
|
|
22
22
|
const todoThinkingColor = (level) => thinkingLevelThemeColor(level, colors);
|
|
23
|
+
const todoStatusThemeColor = (status) => {
|
|
24
|
+
switch (status) {
|
|
25
|
+
case "pending": return colors.muted;
|
|
26
|
+
case "in_progress": return colors.warning;
|
|
27
|
+
case "deferred": return colors.muted;
|
|
28
|
+
case "completed": return colors.success;
|
|
29
|
+
case "deleted": return colors.error;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
23
32
|
if (!expanded) {
|
|
24
33
|
const prefix = `${headerText} — current: `;
|
|
25
34
|
const current = activeTask ? formatTodoTaskLine(activeTask) : "no active todo";
|
|
26
35
|
const collapsedText = `${prefix}${current}`;
|
|
27
|
-
const segments =
|
|
28
|
-
|
|
36
|
+
const segments = [
|
|
37
|
+
{ start: 0, end: headerText.length, foreground: todoPanelColor },
|
|
38
|
+
{ start: headerText.length, end: prefix.length, foreground: todoMetaColor },
|
|
39
|
+
];
|
|
40
|
+
if (activeTask) {
|
|
41
|
+
const activeSegments = todoTaskLineSegments(activeTask, todoMetaColor, { thinkingColor: todoThinkingColor, statusColor: todoStatusThemeColor }).map((segment) => ({
|
|
29
42
|
...segment,
|
|
30
43
|
start: segment.start + prefix.length,
|
|
31
44
|
end: segment.end + prefix.length,
|
|
32
|
-
}))
|
|
33
|
-
|
|
45
|
+
}));
|
|
46
|
+
segments.push(...activeSegments);
|
|
47
|
+
}
|
|
34
48
|
const line = {
|
|
35
49
|
text: padOrTrimPlain(ellipsizeDisplay(collapsedText, contentWidth), width),
|
|
36
|
-
|
|
50
|
+
segments,
|
|
37
51
|
target,
|
|
38
52
|
};
|
|
39
|
-
if (segments)
|
|
40
|
-
line.segments = segments;
|
|
41
53
|
return [line];
|
|
42
54
|
}
|
|
43
55
|
const lines = [];
|
|
44
56
|
for (const { task, depth } of visibleTodoTaskRows(details)) {
|
|
45
57
|
const text = formatTodoTaskLine(task, { depth });
|
|
46
|
-
const segments = todoTaskLineSegments(task, todoMetaColor, { depth, thinkingColor: todoThinkingColor });
|
|
58
|
+
const segments = todoTaskLineSegments(task, todoMetaColor, { depth, thinkingColor: todoThinkingColor, statusColor: todoStatusThemeColor });
|
|
47
59
|
let start = 0;
|
|
48
60
|
for (const wrapped of wrapLine(text, contentWidth)) {
|
|
49
61
|
lines.push({
|
|
50
62
|
text: padOrTrimPlain(wrapped, width),
|
|
51
|
-
colorOverride: todoPanelColor,
|
|
52
63
|
segments: shiftSegmentsToSlice(segments, start, wrapped.length),
|
|
53
64
|
target,
|
|
54
65
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Theme } from "../../theme.js";
|
|
2
|
+
import { type ModelColorsConfig } from "../../config.js";
|
|
2
3
|
import type { PopupMenu } from "../../ui.js";
|
|
3
4
|
import type { ScreenStyler } from "../screen/screen-styler.js";
|
|
4
5
|
import type { Entry, ModelMenuValue, PixMenuItem, PixMenuOptions, QueueMessageMenuValue, RenderedLine, ResumeMenuValue, SlashCommand, ThinkingMenuValue, UserMessageJumpMenuValue, UserMessageMenuValue } from "../types.js";
|
|
@@ -8,8 +9,10 @@ export type PopupMenuRendererHost = {
|
|
|
8
9
|
readonly screenStyler: ScreenStyler;
|
|
9
10
|
readonly entries: readonly Entry[];
|
|
10
11
|
readonly session: AgentSession | undefined;
|
|
12
|
+
readonly modelColors?: ModelColorsConfig;
|
|
11
13
|
readonly resumeLoading: boolean;
|
|
12
14
|
readonly resumeSessionCount: number;
|
|
15
|
+
readonly userMessageJumpLoading: boolean;
|
|
13
16
|
};
|
|
14
17
|
export declare class PopupMenuRenderer {
|
|
15
18
|
private readonly host;
|
|
@@ -38,10 +41,19 @@ export declare class PopupMenuRenderer {
|
|
|
38
41
|
options: PixMenuOptions;
|
|
39
42
|
} | undefined, directQuery: string): RenderedLine[];
|
|
40
43
|
private hasPopupActionItems;
|
|
44
|
+
private labelDescriptionText;
|
|
41
45
|
private userMessageActionForeground;
|
|
42
46
|
private selectableItemVariant;
|
|
47
|
+
private thinkingMenuItemSegments;
|
|
48
|
+
private modelMenuItemSegments;
|
|
49
|
+
private modelMenuItemColor;
|
|
50
|
+
private availableThinkingLevels;
|
|
43
51
|
private queueMessageItemVariant;
|
|
44
52
|
private sdkItemVariant;
|
|
53
|
+
private sdkMenuItemSegments;
|
|
54
|
+
private itemHighlightSegments;
|
|
55
|
+
private highlightSegments;
|
|
56
|
+
private descriptionHighlightSegments;
|
|
45
57
|
private resumeMenuItemSegments;
|
|
46
58
|
private popupMenuHeader;
|
|
47
59
|
private popupLineForeground;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { colorLine } from "../../theme.js";
|
|
2
2
|
import { stringDisplayWidth } from "../../terminal-width.js";
|
|
3
|
+
import { resolveColor, resolveModelColor } from "../../config.js";
|
|
3
4
|
import { SLASH_COMMAND_DESCRIPTION_COLUMN, } from "../constants.js";
|
|
4
5
|
import { APP_ICONS } from "../icons.js";
|
|
5
6
|
import { ellipsizeDisplay, padOrTrimPlain, sanitizeText } from "./render-text.js";
|
|
7
|
+
import { modelProviderThemeColor, thinkingLevelThemeColor } from "./status-line-renderer.js";
|
|
6
8
|
const POPUP_MENU_ESCAPE_BUTTON = "Esc";
|
|
9
|
+
const POPUP_MENU_DESCRIPTION_GAP = " ";
|
|
10
|
+
const POPUP_MENU_HEADER_SIDE_PADDING = 2;
|
|
7
11
|
export class PopupMenuRenderer {
|
|
8
12
|
host;
|
|
9
13
|
constructor(host) {
|
|
@@ -25,7 +29,7 @@ export class PopupMenuRenderer {
|
|
|
25
29
|
const menuWidth = this.effectivePopupMenuWidth(width);
|
|
26
30
|
const rightMargin = Math.max(0, width - margin - menuWidth);
|
|
27
31
|
const selected = line.target?.kind === "popup-menu" && activeMenu.selectedIndex === line.target.index;
|
|
28
|
-
const foreground = this.popupLineForeground(line
|
|
32
|
+
const foreground = this.popupLineForeground(line);
|
|
29
33
|
const background = this.popupLineBackground(line, selected);
|
|
30
34
|
const plain = `${" ".repeat(margin)}${padOrTrimPlain(line.text, menuWidth)}${" ".repeat(rightMargin)}`;
|
|
31
35
|
if (this.host.screenStyler.selectionRangeForRow(row, width)) {
|
|
@@ -59,7 +63,7 @@ export class PopupMenuRenderer {
|
|
|
59
63
|
for (const item of menu.visibleItems()) {
|
|
60
64
|
const label = item.label.padEnd(18, " ");
|
|
61
65
|
const description = item.description ?? "";
|
|
62
|
-
const marker = item.selected ? "
|
|
66
|
+
const marker = item.selected ? "▶" : " ";
|
|
63
67
|
const rawText = `${marker} ${label}${description}`;
|
|
64
68
|
const text = ellipsizeDisplay(rawText, options.userContentWidth);
|
|
65
69
|
const line = options.userLine(text);
|
|
@@ -73,7 +77,7 @@ export class PopupMenuRenderer {
|
|
|
73
77
|
{
|
|
74
78
|
start: labelStart,
|
|
75
79
|
end: labelEnd,
|
|
76
|
-
foreground: this.userMessageActionForeground(item.
|
|
80
|
+
foreground: this.userMessageActionForeground(item.value),
|
|
77
81
|
bold: item.selected,
|
|
78
82
|
},
|
|
79
83
|
...(descriptionStart < contentStart + text.length
|
|
@@ -91,11 +95,12 @@ export class PopupMenuRenderer {
|
|
|
91
95
|
lines.push({ text: " No matching slash commands", variant: "muted" });
|
|
92
96
|
}
|
|
93
97
|
for (const item of visibleItems) {
|
|
94
|
-
const
|
|
95
|
-
const
|
|
98
|
+
const marker = item.selected ? "▶ " : " ";
|
|
99
|
+
const text = `${marker}${this.labelDescriptionText(item.label, item.description, width - 2)}`;
|
|
96
100
|
lines.push({
|
|
97
|
-
text
|
|
98
|
-
variant:
|
|
101
|
+
text,
|
|
102
|
+
variant: "normal",
|
|
103
|
+
segments: this.itemHighlightSegments(item, text),
|
|
99
104
|
target: { kind: "popup-menu", index: item.index },
|
|
100
105
|
});
|
|
101
106
|
}
|
|
@@ -111,11 +116,12 @@ export class PopupMenuRenderer {
|
|
|
111
116
|
});
|
|
112
117
|
}
|
|
113
118
|
for (const item of visibleItems) {
|
|
114
|
-
const
|
|
115
|
-
const
|
|
119
|
+
const marker = item.selected ? "▶ " : " ";
|
|
120
|
+
const text = `${marker}${this.labelDescriptionText(item.label, item.description, width - 2)}`;
|
|
116
121
|
lines.push({
|
|
117
|
-
text
|
|
118
|
-
variant: this.selectableItemVariant(item.
|
|
122
|
+
text,
|
|
123
|
+
variant: this.selectableItemVariant(item.value),
|
|
124
|
+
segments: [...this.modelMenuItemSegments(item.value), ...this.itemHighlightSegments(item, text)],
|
|
119
125
|
target: { kind: "popup-menu", index: item.index },
|
|
120
126
|
});
|
|
121
127
|
}
|
|
@@ -128,11 +134,12 @@ export class PopupMenuRenderer {
|
|
|
128
134
|
lines.push({ text: " No matching thinking levels", variant: "muted" });
|
|
129
135
|
}
|
|
130
136
|
for (const item of visibleItems) {
|
|
131
|
-
const
|
|
132
|
-
const
|
|
137
|
+
const marker = item.selected ? "▶ " : " ";
|
|
138
|
+
const text = `${marker}${this.labelDescriptionText(item.label, item.description, width - 2)}`;
|
|
133
139
|
lines.push({
|
|
134
|
-
text
|
|
135
|
-
variant: this.selectableItemVariant(item.
|
|
140
|
+
text,
|
|
141
|
+
variant: this.selectableItemVariant(item.value),
|
|
142
|
+
segments: this.thinkingMenuItemSegments(item.value),
|
|
136
143
|
target: { kind: "popup-menu", index: item.index },
|
|
137
144
|
});
|
|
138
145
|
}
|
|
@@ -151,12 +158,13 @@ export class PopupMenuRenderer {
|
|
|
151
158
|
for (const item of visibleItems) {
|
|
152
159
|
const label = item.label;
|
|
153
160
|
const description = item.description ?? "";
|
|
154
|
-
const
|
|
155
|
-
const
|
|
161
|
+
const marker = item.selected ? "▶ " : " ";
|
|
162
|
+
const text = `${marker}${label} ${description}`;
|
|
163
|
+
const segments = [...(this.resumeMenuItemSegments(item.value, label, description, text) ?? []), ...this.itemHighlightSegments(item, text)];
|
|
156
164
|
lines.push({
|
|
157
165
|
text,
|
|
158
|
-
variant:
|
|
159
|
-
...(segments ? {
|
|
166
|
+
variant: "normal",
|
|
167
|
+
...(segments.length === 0 ? {} : { segments }),
|
|
160
168
|
target: { kind: "popup-menu", index: item.index },
|
|
161
169
|
});
|
|
162
170
|
}
|
|
@@ -170,18 +178,24 @@ export class PopupMenuRenderer {
|
|
|
170
178
|
}
|
|
171
179
|
renderUserMessageJumpMenu(width, menu, directQuery) {
|
|
172
180
|
const lines = [this.popupMenuHeader("Jump to user message", width)];
|
|
173
|
-
if (
|
|
181
|
+
if (this.host.userMessageJumpLoading) {
|
|
182
|
+
lines.push({ text: ` ${APP_ICONS.timerSand} Loading user messages`, variant: "muted" });
|
|
183
|
+
}
|
|
184
|
+
else if (!this.hasPopupActionItems(menu.items)) {
|
|
174
185
|
lines.push({
|
|
175
186
|
text: this.host.entries.some((entry) => entry.kind === "user") ? " No matching user messages" : " No user messages yet",
|
|
176
187
|
variant: "muted",
|
|
177
188
|
});
|
|
178
189
|
}
|
|
179
|
-
const labelWidth = Math.max(1, width);
|
|
190
|
+
const labelWidth = Math.max(1, width - 2);
|
|
180
191
|
for (const item of menu.visibleItems()) {
|
|
181
192
|
const label = ellipsizeDisplay(item.label, labelWidth);
|
|
193
|
+
const marker = item.selected ? "▶ " : " ";
|
|
194
|
+
const text = `${marker}${label}`;
|
|
182
195
|
lines.push({
|
|
183
|
-
text
|
|
184
|
-
variant:
|
|
196
|
+
text,
|
|
197
|
+
variant: "normal",
|
|
198
|
+
segments: this.itemHighlightSegments(item, text),
|
|
185
199
|
target: { kind: "popup-menu", index: item.index },
|
|
186
200
|
});
|
|
187
201
|
}
|
|
@@ -193,11 +207,10 @@ export class PopupMenuRenderer {
|
|
|
193
207
|
renderQueueMessageMenu(width, menu) {
|
|
194
208
|
const lines = [this.popupMenuHeader("Queued message", width)];
|
|
195
209
|
for (const item of menu.visibleItems()) {
|
|
196
|
-
const
|
|
197
|
-
const description = item.description ?? "";
|
|
210
|
+
const marker = item.selected ? "▶ " : " ";
|
|
198
211
|
lines.push({
|
|
199
|
-
text: `${
|
|
200
|
-
variant: this.queueMessageItemVariant(item.
|
|
212
|
+
text: `${marker}${this.labelDescriptionText(item.label, item.description, width - 2, 16)}`,
|
|
213
|
+
variant: this.queueMessageItemVariant(item.value),
|
|
201
214
|
target: { kind: "popup-menu", index: item.index },
|
|
202
215
|
});
|
|
203
216
|
}
|
|
@@ -209,11 +222,13 @@ export class PopupMenuRenderer {
|
|
|
209
222
|
lines.push({ text: ` ${request?.options.emptyText ?? "No matching items"}`, variant: "muted" });
|
|
210
223
|
}
|
|
211
224
|
for (const item of menu.visibleItems()) {
|
|
212
|
-
const
|
|
213
|
-
const
|
|
225
|
+
const marker = item.selected ? "▶ " : " ";
|
|
226
|
+
const text = `${marker}${this.labelDescriptionText(item.label, item.description, width - 2)}`;
|
|
227
|
+
const segments = this.sdkMenuItemSegments(item, text);
|
|
214
228
|
lines.push({
|
|
215
|
-
text
|
|
216
|
-
variant: this.sdkItemVariant(item.
|
|
229
|
+
text,
|
|
230
|
+
variant: this.sdkItemVariant(item.value),
|
|
231
|
+
...(segments.length === 0 ? {} : { segments }),
|
|
217
232
|
target: { kind: "popup-menu", index: item.index },
|
|
218
233
|
});
|
|
219
234
|
}
|
|
@@ -225,39 +240,117 @@ export class PopupMenuRenderer {
|
|
|
225
240
|
hasPopupActionItems(items) {
|
|
226
241
|
return items.length > 0;
|
|
227
242
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
243
|
+
labelDescriptionText(label, description, width, labelColumn = SLASH_COMMAND_DESCRIPTION_COLUMN) {
|
|
244
|
+
const safeLabel = sanitizeText(label).replace(/\s+/gu, " ");
|
|
245
|
+
const safeDescription = description ? sanitizeText(description).replace(/\s+/gu, " ") : "";
|
|
246
|
+
if (!safeDescription)
|
|
247
|
+
return ellipsizeDisplay(safeLabel, width);
|
|
248
|
+
const gapWidth = stringDisplayWidth(POPUP_MENU_DESCRIPTION_GAP);
|
|
249
|
+
const labelDisplayWidth = stringDisplayWidth(safeLabel);
|
|
250
|
+
const descriptionDisplayWidth = stringDisplayWidth(safeDescription);
|
|
251
|
+
if (width <= gapWidth + 1)
|
|
252
|
+
return ellipsizeDisplay(safeLabel, width);
|
|
253
|
+
if (labelDisplayWidth <= labelColumn && labelColumn + gapWidth + descriptionDisplayWidth <= width) {
|
|
254
|
+
return `${safeLabel}${" ".repeat(labelColumn - labelDisplayWidth)}${POPUP_MENU_DESCRIPTION_GAP}${safeDescription}`;
|
|
255
|
+
}
|
|
256
|
+
if (labelDisplayWidth + gapWidth + descriptionDisplayWidth <= width) {
|
|
257
|
+
return `${safeLabel}${POPUP_MENU_DESCRIPTION_GAP}${safeDescription}`;
|
|
258
|
+
}
|
|
259
|
+
const labelWidth = descriptionDisplayWidth < width - gapWidth - 1
|
|
260
|
+
? Math.max(1, width - gapWidth - descriptionDisplayWidth)
|
|
261
|
+
: Math.max(1, Math.min(labelColumn, width - gapWidth - 1));
|
|
262
|
+
const visibleLabel = ellipsizeDisplay(safeLabel, labelWidth);
|
|
263
|
+
const padding = " ".repeat(Math.max(0, labelWidth - stringDisplayWidth(visibleLabel)));
|
|
264
|
+
return `${visibleLabel}${padding}${POPUP_MENU_DESCRIPTION_GAP}${safeDescription}`;
|
|
265
|
+
}
|
|
266
|
+
userMessageActionForeground(value) {
|
|
231
267
|
if (value === "undo")
|
|
232
268
|
return this.host.theme.colors.error;
|
|
233
269
|
return this.host.theme.colors.inputForeground;
|
|
234
270
|
}
|
|
235
|
-
selectableItemVariant(
|
|
236
|
-
if (selected)
|
|
237
|
-
return "accent";
|
|
271
|
+
selectableItemVariant(value) {
|
|
238
272
|
return value.current ? "muted" : "normal";
|
|
239
273
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
274
|
+
thinkingMenuItemSegments(value) {
|
|
275
|
+
const markerOffset = 2; // "▶ " or " "
|
|
276
|
+
return [{
|
|
277
|
+
start: markerOffset,
|
|
278
|
+
end: markerOffset + value.level.length,
|
|
279
|
+
foreground: thinkingLevelThemeColor(value.level, this.host.theme.colors, this.availableThinkingLevels()),
|
|
280
|
+
}];
|
|
281
|
+
}
|
|
282
|
+
modelMenuItemSegments(value) {
|
|
283
|
+
const markerOffset = 2; // "▶ " or " "
|
|
284
|
+
return [{
|
|
285
|
+
start: markerOffset,
|
|
286
|
+
end: markerOffset + value.ref.length,
|
|
287
|
+
foreground: this.modelMenuItemColor(value),
|
|
288
|
+
}];
|
|
289
|
+
}
|
|
290
|
+
modelMenuItemColor(value) {
|
|
291
|
+
const configuredColor = this.host.modelColors
|
|
292
|
+
? resolveModelColor(value.ref, this.host.modelColors)
|
|
293
|
+
: undefined;
|
|
294
|
+
return configuredColor
|
|
295
|
+
? resolveColor(configuredColor, this.host.theme.colors)
|
|
296
|
+
: modelProviderThemeColor(value.model.provider, this.host.theme.colors);
|
|
297
|
+
}
|
|
298
|
+
availableThinkingLevels() {
|
|
299
|
+
const levels = this.host.session?.getAvailableThinkingLevels();
|
|
300
|
+
return Array.isArray(levels) && levels.length > 0 ? levels.map(String) : ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
301
|
+
}
|
|
302
|
+
queueMessageItemVariant(value) {
|
|
243
303
|
return value === "cancel" ? "error" : "normal";
|
|
244
304
|
}
|
|
245
|
-
sdkItemVariant(
|
|
246
|
-
if (selected)
|
|
247
|
-
return "accent";
|
|
305
|
+
sdkItemVariant(value) {
|
|
248
306
|
return value.variant ?? "normal";
|
|
249
307
|
}
|
|
308
|
+
sdkMenuItemSegments(item, text) {
|
|
309
|
+
return [
|
|
310
|
+
...this.highlightSegments(item.labelHighlightRanges ?? item.value.labelHighlightRanges ?? [], text, 2),
|
|
311
|
+
...this.descriptionHighlightSegments(item.description, item.descriptionHighlightRanges ?? item.value.descriptionHighlightRanges ?? [], text),
|
|
312
|
+
];
|
|
313
|
+
}
|
|
314
|
+
itemHighlightSegments(item, text) {
|
|
315
|
+
return this.highlightSegments(item.labelHighlightRanges ?? [], text, 2);
|
|
316
|
+
}
|
|
317
|
+
highlightSegments(ranges, text, markerOffset) {
|
|
318
|
+
if (ranges.length === 0)
|
|
319
|
+
return [];
|
|
320
|
+
return ranges.flatMap((range) => {
|
|
321
|
+
const start = Math.max(markerOffset, markerOffset + range.start);
|
|
322
|
+
const end = Math.min(text.length, markerOffset + range.end);
|
|
323
|
+
if (end <= start)
|
|
324
|
+
return [];
|
|
325
|
+
return [{
|
|
326
|
+
start,
|
|
327
|
+
end,
|
|
328
|
+
foreground: this.host.theme.colors.accent,
|
|
329
|
+
bold: true,
|
|
330
|
+
}];
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
descriptionHighlightSegments(description, ranges, text) {
|
|
334
|
+
if (!description || ranges.length === 0)
|
|
335
|
+
return [];
|
|
336
|
+
const safeDescription = sanitizeText(description).replace(/\s+/gu, " ");
|
|
337
|
+
const descriptionStart = text.indexOf(safeDescription, 2);
|
|
338
|
+
if (descriptionStart < 0)
|
|
339
|
+
return [];
|
|
340
|
+
return this.highlightSegments(ranges, text, descriptionStart);
|
|
341
|
+
}
|
|
250
342
|
resumeMenuItemSegments(value, label, description, text) {
|
|
251
343
|
if (value.kind !== "session")
|
|
252
344
|
return undefined;
|
|
253
345
|
const sessionLabel = value.session.name ?? value.session.firstMessage.slice(0, 50);
|
|
254
|
-
const
|
|
346
|
+
const markerOffset = 2; // "▶ " or " "
|
|
347
|
+
const sessionLabelStart = Math.max(0, label.length - sessionLabel.length) + markerOffset;
|
|
255
348
|
const muted = this.host.theme.colors.popupMuted;
|
|
256
349
|
const segments = [];
|
|
257
|
-
if (sessionLabelStart >
|
|
258
|
-
segments.push({ start:
|
|
350
|
+
if (sessionLabelStart > markerOffset)
|
|
351
|
+
segments.push({ start: markerOffset, end: sessionLabelStart, foreground: muted });
|
|
259
352
|
if (description.length > 0)
|
|
260
|
-
segments.push({ start: label.length, end: text.length, foreground: muted });
|
|
353
|
+
segments.push({ start: markerOffset + label.length, end: text.length, foreground: muted });
|
|
261
354
|
return segments.length > 0 ? segments : undefined;
|
|
262
355
|
}
|
|
263
356
|
popupMenuHeader(title, width) {
|
|
@@ -268,10 +361,8 @@ export class PopupMenuRenderer {
|
|
|
268
361
|
target: { kind: "popup-menu-close" },
|
|
269
362
|
};
|
|
270
363
|
}
|
|
271
|
-
popupLineForeground(line
|
|
364
|
+
popupLineForeground(line) {
|
|
272
365
|
const colors = this.host.theme.colors;
|
|
273
|
-
if (selected)
|
|
274
|
-
return colors.popupSelectedForeground;
|
|
275
366
|
if (line.colorOverride)
|
|
276
367
|
return line.colorOverride;
|
|
277
368
|
switch (line.variant) {
|
|
@@ -300,8 +391,15 @@ export function formatPopupMenuHeader(title, width) {
|
|
|
300
391
|
const buttonWidth = stringDisplayWidth(POPUP_MENU_ESCAPE_BUTTON);
|
|
301
392
|
if (safeWidth <= buttonWidth + 1)
|
|
302
393
|
return padOrTrimPlain(POPUP_MENU_ESCAPE_BUTTON, safeWidth);
|
|
303
|
-
const
|
|
394
|
+
const sidePadding = safeWidth >= buttonWidth + POPUP_MENU_HEADER_SIDE_PADDING * 2 + 2
|
|
395
|
+
? POPUP_MENU_HEADER_SIDE_PADDING
|
|
396
|
+
: 1;
|
|
397
|
+
const contentWidth = Math.max(1, safeWidth - sidePadding * 2);
|
|
398
|
+
if (contentWidth <= buttonWidth + 1) {
|
|
399
|
+
return padOrTrimPlain(`${" ".repeat(sidePadding)}${POPUP_MENU_ESCAPE_BUTTON}`, safeWidth);
|
|
400
|
+
}
|
|
401
|
+
const titleWidth = contentWidth - buttonWidth - 1;
|
|
304
402
|
const titleText = ellipsizeDisplay(sanitizedTitle, titleWidth);
|
|
305
|
-
const gapWidth = Math.max(1,
|
|
306
|
-
return `${titleText}${" ".repeat(gapWidth)}${POPUP_MENU_ESCAPE_BUTTON}`;
|
|
403
|
+
const gapWidth = Math.max(1, contentWidth - stringDisplayWidth(titleText) - buttonWidth);
|
|
404
|
+
return `${" ".repeat(sidePadding)}${titleText}${" ".repeat(gapWidth)}${POPUP_MENU_ESCAPE_BUTTON}${" ".repeat(sidePadding)}`;
|
|
307
405
|
}
|