@victor-software-house/pi-openai-proxy 4.2.1 → 4.2.3
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/extensions/proxy.ts +144 -33
- package/package.json +1 -1
package/extensions/proxy.ts
CHANGED
|
@@ -33,6 +33,9 @@ import {
|
|
|
33
33
|
import {
|
|
34
34
|
type Component,
|
|
35
35
|
Container,
|
|
36
|
+
fuzzyFilter,
|
|
37
|
+
getKeybindings,
|
|
38
|
+
Input,
|
|
36
39
|
type SettingItem,
|
|
37
40
|
SettingsList,
|
|
38
41
|
Text,
|
|
@@ -550,71 +553,179 @@ export default function proxyExtension(pi: ExtensionAPI): void {
|
|
|
550
553
|
_currentValue: string,
|
|
551
554
|
done: (selectedValue?: string) => void,
|
|
552
555
|
): Component {
|
|
553
|
-
|
|
556
|
+
interface ModelEntry {
|
|
557
|
+
canonical: string;
|
|
558
|
+
provider: string;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const models: ModelEntry[] = getAvailableModels().map((m) => ({
|
|
562
|
+
canonical: `${m.provider}/${m.id}`,
|
|
563
|
+
provider: m.provider,
|
|
564
|
+
}));
|
|
565
|
+
|
|
554
566
|
const selected = new Set(config.customModels);
|
|
555
567
|
let selectedIndex = 0;
|
|
568
|
+
const searchInput = new Input();
|
|
569
|
+
let filtered: ModelEntry[] = models;
|
|
570
|
+
const maxVisible = 20;
|
|
571
|
+
|
|
572
|
+
// Precompute provider boundary indices in the full list
|
|
573
|
+
function providerBoundaries(list: ModelEntry[]): {
|
|
574
|
+
firstOf: Map<string, number>;
|
|
575
|
+
lastOf: Map<string, number>;
|
|
576
|
+
} {
|
|
577
|
+
const firstOf = new Map<string, number>();
|
|
578
|
+
const lastOf = new Map<string, number>();
|
|
579
|
+
for (let i = 0; i < list.length; i++) {
|
|
580
|
+
const entry = list[i];
|
|
581
|
+
if (entry === undefined) continue;
|
|
582
|
+
if (!firstOf.has(entry.provider)) firstOf.set(entry.provider, i);
|
|
583
|
+
lastOf.set(entry.provider, i);
|
|
584
|
+
}
|
|
585
|
+
return { firstOf, lastOf };
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function jumpProvider(direction: "prev" | "next"): void {
|
|
589
|
+
if (filtered.length === 0) return;
|
|
590
|
+
const { firstOf, lastOf } = providerBoundaries(filtered);
|
|
591
|
+
const current = filtered[selectedIndex];
|
|
592
|
+
if (current === undefined) return;
|
|
593
|
+
|
|
594
|
+
const providers = [...firstOf.keys()]; // insertion-order = list order
|
|
595
|
+
const provIdx = providers.indexOf(current.provider);
|
|
596
|
+
|
|
597
|
+
if (direction === "prev") {
|
|
598
|
+
if (provIdx <= 0) {
|
|
599
|
+
selectedIndex = 0;
|
|
600
|
+
} else {
|
|
601
|
+
const prevProvider = providers[provIdx - 1];
|
|
602
|
+
if (prevProvider !== undefined) {
|
|
603
|
+
selectedIndex = lastOf.get(prevProvider) ?? 0;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
} else {
|
|
607
|
+
if (provIdx >= providers.length - 1) {
|
|
608
|
+
selectedIndex = filtered.length - 1;
|
|
609
|
+
} else {
|
|
610
|
+
const nextProvider = providers[provIdx + 1];
|
|
611
|
+
if (nextProvider !== undefined) {
|
|
612
|
+
selectedIndex = firstOf.get(nextProvider) ?? filtered.length - 1;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
556
617
|
|
|
557
|
-
function toggle(
|
|
558
|
-
|
|
559
|
-
|
|
618
|
+
function toggle(idx: number): void {
|
|
619
|
+
const entry = filtered[idx];
|
|
620
|
+
if (entry === undefined) return;
|
|
621
|
+
if (selected.has(entry.canonical)) {
|
|
622
|
+
selected.delete(entry.canonical);
|
|
560
623
|
} else {
|
|
561
|
-
selected.add(
|
|
624
|
+
selected.add(entry.canonical);
|
|
562
625
|
}
|
|
563
626
|
config = { ...config, customModels: [...selected] };
|
|
564
627
|
saveConfigToFile(config);
|
|
565
628
|
config = loadConfigFromFile();
|
|
566
629
|
}
|
|
567
630
|
|
|
631
|
+
function applyFilter(query: string): void {
|
|
632
|
+
if (query === "") {
|
|
633
|
+
filtered = models;
|
|
634
|
+
} else {
|
|
635
|
+
filtered = fuzzyFilter(models, query, (m) => m.canonical);
|
|
636
|
+
}
|
|
637
|
+
selectedIndex = 0;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const theme = getSettingsListTheme();
|
|
641
|
+
|
|
568
642
|
return {
|
|
569
643
|
render(width: number): string[] {
|
|
570
644
|
const lines: string[] = [];
|
|
571
|
-
lines.push(
|
|
645
|
+
lines.push(...searchInput.render(width));
|
|
572
646
|
lines.push("");
|
|
573
647
|
|
|
574
|
-
if (
|
|
575
|
-
lines.push(" No models
|
|
648
|
+
if (filtered.length === 0) {
|
|
649
|
+
lines.push(theme.hint(" No matching models"));
|
|
650
|
+
lines.push("");
|
|
651
|
+
lines.push(theme.hint(" Type to filter | Left/Right: jump provider | Esc: done"));
|
|
576
652
|
return lines;
|
|
577
653
|
}
|
|
578
654
|
|
|
579
|
-
const maxVisible = 15;
|
|
580
655
|
const start = Math.max(
|
|
581
656
|
0,
|
|
582
|
-
Math.min(selectedIndex - Math.floor(maxVisible / 2),
|
|
657
|
+
Math.min(selectedIndex - Math.floor(maxVisible / 2), filtered.length - maxVisible),
|
|
583
658
|
);
|
|
584
|
-
const end = Math.min(start + maxVisible,
|
|
659
|
+
const end = Math.min(start + maxVisible, filtered.length);
|
|
585
660
|
|
|
661
|
+
let lastProvider = "";
|
|
586
662
|
for (let i = start; i < end; i++) {
|
|
587
|
-
const
|
|
588
|
-
if (
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
663
|
+
const entry = filtered[i];
|
|
664
|
+
if (entry === undefined) continue;
|
|
665
|
+
|
|
666
|
+
// Provider separator
|
|
667
|
+
if (entry.provider !== lastProvider) {
|
|
668
|
+
if (lastProvider !== "") lines.push("");
|
|
669
|
+
lastProvider = entry.provider;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const check = selected.has(entry.canonical) ? "[x]" : "[ ]";
|
|
673
|
+
const cursor = i === selectedIndex ? theme.cursor : " ";
|
|
674
|
+
const label =
|
|
675
|
+
i === selectedIndex
|
|
676
|
+
? theme.label(`${check} ${entry.canonical}`, true)
|
|
677
|
+
: theme.label(`${check} ${entry.canonical}`, false);
|
|
678
|
+
lines.push(`${cursor}${label}`.slice(0, width));
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (start > 0 || end < filtered.length) {
|
|
682
|
+
lines.push(theme.hint(` (${String(selectedIndex + 1)}/${String(filtered.length)})`));
|
|
595
683
|
}
|
|
596
684
|
|
|
597
685
|
lines.push("");
|
|
598
|
-
lines.push(
|
|
686
|
+
lines.push(
|
|
687
|
+
theme.description(` ${String(selected.size)} of ${String(models.length)} selected`),
|
|
688
|
+
);
|
|
689
|
+
lines.push("");
|
|
690
|
+
lines.push(
|
|
691
|
+
theme.hint(" Type to filter | Space: toggle | Left/Right: jump provider | Esc: done"),
|
|
692
|
+
);
|
|
599
693
|
return lines;
|
|
600
694
|
},
|
|
601
|
-
invalidate(): void {
|
|
602
|
-
// no-op
|
|
603
|
-
},
|
|
695
|
+
invalidate(): void {},
|
|
604
696
|
handleInput(data: string): void {
|
|
605
|
-
|
|
697
|
+
const kb = getKeybindings();
|
|
698
|
+
|
|
699
|
+
if (kb.matches(data, "tui.select.cancel")) {
|
|
606
700
|
done(`${String(selected.size)} selected`);
|
|
607
701
|
return;
|
|
608
702
|
}
|
|
609
|
-
if (
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
703
|
+
if (filtered.length === 0) {
|
|
704
|
+
// Still allow typing to refine filter
|
|
705
|
+
const sanitized = data.replace(/ /g, "");
|
|
706
|
+
if (sanitized !== "") {
|
|
707
|
+
searchInput.handleInput(sanitized);
|
|
708
|
+
applyFilter(searchInput.getValue());
|
|
709
|
+
}
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (kb.matches(data, "tui.select.up")) {
|
|
713
|
+
selectedIndex = selectedIndex <= 0 ? filtered.length - 1 : selectedIndex - 1;
|
|
714
|
+
} else if (kb.matches(data, "tui.select.down")) {
|
|
715
|
+
selectedIndex = selectedIndex >= filtered.length - 1 ? 0 : selectedIndex + 1;
|
|
716
|
+
} else if (data === "\x1B[D") {
|
|
717
|
+
// Left arrow: previous provider
|
|
718
|
+
jumpProvider("prev");
|
|
719
|
+
} else if (data === "\x1B[C") {
|
|
720
|
+
// Right arrow: next provider
|
|
721
|
+
jumpProvider("next");
|
|
722
|
+
} else if (kb.matches(data, "tui.select.confirm") || data === " ") {
|
|
723
|
+
toggle(selectedIndex);
|
|
724
|
+
} else {
|
|
725
|
+
const sanitized = data.replace(/ /g, "");
|
|
726
|
+
if (sanitized !== "") {
|
|
727
|
+
searchInput.handleInput(sanitized);
|
|
728
|
+
applyFilter(searchInput.getValue());
|
|
618
729
|
}
|
|
619
730
|
}
|
|
620
731
|
},
|
package/package.json
CHANGED