bingocode 1.1.70 → 1.1.72
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 +1 -1
- package/src/cli/ProviderPanel.tsx +13 -8
- package/src/manager/CliMenuManager.tsx +78 -74
package/package.json
CHANGED
|
@@ -57,7 +57,8 @@ type Stage =
|
|
|
57
57
|
export const ProviderPanel: React.FC<{
|
|
58
58
|
apiUrl: string;
|
|
59
59
|
onBack?: () => void;
|
|
60
|
-
|
|
60
|
+
height?: number;
|
|
61
|
+
}> = ({ apiUrl, onBack, height = 10 }) => {
|
|
61
62
|
const { exit } = useApp();
|
|
62
63
|
const [loading, setLoading] = useState(false);
|
|
63
64
|
const [err, setErr] = useState<string | null>(null);
|
|
@@ -65,6 +66,10 @@ export const ProviderPanel: React.FC<{
|
|
|
65
66
|
// Scrolling
|
|
66
67
|
const [listOffset, setListOffset] = useState(0);
|
|
67
68
|
|
|
69
|
+
// Calculated visible counts based on height
|
|
70
|
+
const MAX_VISIBLE = Math.max(3, height - 7);
|
|
71
|
+
const MAX_VISIBLE_MODELS = Math.max(3, height - 6);
|
|
72
|
+
|
|
68
73
|
const [providers, setProviders] = useState<Provider[]>([]);
|
|
69
74
|
const [currentId, setCurrentId] = useState<string | null>(null);
|
|
70
75
|
|
|
@@ -141,13 +146,13 @@ export const ProviderPanel: React.FC<{
|
|
|
141
146
|
loadPresets();
|
|
142
147
|
}, [loadProviders, loadPresets]);
|
|
143
148
|
|
|
144
|
-
// Key processing for Page Up/Down in scrolling lists
|
|
149
|
+
// Key processing for Page Up/Down and Arrow keys in scrolling lists
|
|
145
150
|
useEffect(() => {
|
|
146
151
|
const handler = (buf: Buffer) => {
|
|
147
152
|
const s = buf.toString();
|
|
148
153
|
if (stage === 'add_select_preset' || stage === 'slot_select_model') {
|
|
149
|
-
if (s === 'j') setListOffset(prev => prev + 1);
|
|
150
|
-
if (s === 'k') setListOffset(prev => Math.max(0, prev - 1));
|
|
154
|
+
if (s === 'j' || s === '\u001b[B') setListOffset(prev => prev + 1); // j or Down
|
|
155
|
+
if (s === 'k' || s === '\u001b[A') setListOffset(prev => Math.max(0, prev - 1)); // k or Up
|
|
151
156
|
}
|
|
152
157
|
};
|
|
153
158
|
process.stdin.on('data', handler);
|
|
@@ -375,9 +380,9 @@ export const ProviderPanel: React.FC<{
|
|
|
375
380
|
value: pr.id
|
|
376
381
|
}));
|
|
377
382
|
|
|
378
|
-
// Add Custom option
|
|
383
|
+
// Add Custom option only if not already in presets
|
|
379
384
|
const finalItems = [
|
|
380
|
-
{ label: 'Custom (OpenAI Compatible)', value: 'custom' },
|
|
385
|
+
...(presets.some(p => p.id === 'custom') ? [] : [{ label: 'Custom (OpenAI Compatible)', value: 'custom' }]),
|
|
381
386
|
...items
|
|
382
387
|
];
|
|
383
388
|
|
|
@@ -410,7 +415,7 @@ export const ProviderPanel: React.FC<{
|
|
|
410
415
|
);
|
|
411
416
|
}
|
|
412
417
|
|
|
413
|
-
const MAX_VISIBLE = 8;
|
|
418
|
+
// const MAX_VISIBLE = 8;
|
|
414
419
|
const start = Math.min(listOffset, Math.max(0, finalItems.length - MAX_VISIBLE));
|
|
415
420
|
const sliced = finalItems.slice(start, start + MAX_VISIBLE);
|
|
416
421
|
|
|
@@ -756,7 +761,7 @@ export const ProviderPanel: React.FC<{
|
|
|
756
761
|
);
|
|
757
762
|
}
|
|
758
763
|
|
|
759
|
-
const MAX_VISIBLE_MODELS = 8;
|
|
764
|
+
// const MAX_VISIBLE_MODELS = 8;
|
|
760
765
|
const start = Math.min(listOffset, Math.max(0, items.length - MAX_VISIBLE_MODELS));
|
|
761
766
|
const sliced = items.slice(start, start + MAX_VISIBLE_MODELS);
|
|
762
767
|
|
|
@@ -607,12 +607,12 @@ export const CliMenuManager: React.FC = () => {
|
|
|
607
607
|
// History shortcuts
|
|
608
608
|
if (!showHelp && page === 'history') {
|
|
609
609
|
if (historyMenuStage === 'list') {
|
|
610
|
-
const HIST_VISIBLE =
|
|
611
|
-
if (key.downArrow || input === 'j') {
|
|
610
|
+
const HIST_VISIBLE = MID_H - 2;
|
|
611
|
+
if (key.downArrow || input === 'j' || input === '\u001b[B') {
|
|
612
612
|
// Internal SelectInput handles cursor, we just need to track offset for ScrollBar
|
|
613
613
|
setListOffset(o => Math.min(o + 1, Math.max(0, groupedHistoryItems.length - HIST_VISIBLE)));
|
|
614
614
|
}
|
|
615
|
-
if (key.upArrow || input === 'k') {
|
|
615
|
+
if (key.upArrow || input === 'k' || input === '\u001b[A') {
|
|
616
616
|
setListOffset(o => Math.max(0, o - 1));
|
|
617
617
|
}
|
|
618
618
|
if (input === 'q') {
|
|
@@ -681,18 +681,17 @@ export const CliMenuManager: React.FC = () => {
|
|
|
681
681
|
}
|
|
682
682
|
}, [menuItems, page, historyMenuStage, historyList, historyHasMore, navIndex, sessionMessages, settingData, MID_H, MSGS_PAGE_SIZE, showHelp, theme]);
|
|
683
683
|
|
|
684
|
+
function cleanText(text: string): string {
|
|
685
|
+
return String(text ?? '').replace(/[\n\r]+/g, ' ').replace(/\u001b\[[0-9;]*m/g, '').trim();
|
|
686
|
+
}
|
|
687
|
+
|
|
684
688
|
function clampTextLines(text: string, maxWidth: number, maxLines: number) {
|
|
685
|
-
const
|
|
689
|
+
const cleaned = cleanText(text);
|
|
686
690
|
const out: string[] = [];
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
if (raw.length <= maxWidth) {
|
|
692
|
-
out.push(raw);
|
|
693
|
-
} else {
|
|
694
|
-
out.push(ellipsis(raw, maxWidth));
|
|
695
|
-
}
|
|
691
|
+
if (cleaned.length <= maxWidth) {
|
|
692
|
+
out.push(cleaned);
|
|
693
|
+
} else {
|
|
694
|
+
out.push(cleaned.slice(0, maxWidth - 1) + '…');
|
|
696
695
|
}
|
|
697
696
|
return out.join('\n');
|
|
698
697
|
}
|
|
@@ -965,48 +964,43 @@ export const CliMenuManager: React.FC = () => {
|
|
|
965
964
|
if (historyMenuStage === 'deleteConfirm' && selectedHistory) {
|
|
966
965
|
const halfH = Math.floor(MID_H / 2);
|
|
967
966
|
const items = [
|
|
968
|
-
{ label: 'Yes,
|
|
969
|
-
{ label: 'No,
|
|
967
|
+
{ label: 'Yes, Delete', value: '__confirm_delete' },
|
|
968
|
+
{ label: 'No, Back', value: '__cancel_delete' },
|
|
970
969
|
];
|
|
971
970
|
return (
|
|
972
971
|
<Box width={VIEW_W} height={MID_H} flexDirection="column">
|
|
973
|
-
<Box height={halfH} flexDirection="column">
|
|
974
|
-
<Text color="red">
|
|
975
|
-
<Text>
|
|
976
|
-
<Text>
|
|
977
|
-
<Text>
|
|
972
|
+
<Box height={halfH} flexDirection="column" paddingX={1} paddingTop={1}>
|
|
973
|
+
<Text color="red" bold>Confirm Delete?</Text>
|
|
974
|
+
<Text>Title: {selectedHistory.title || 'Untitled'}</Text>
|
|
975
|
+
<Text dimColor>Time: {selectedHistory.createdAt?.replace('T',' ')}</Text>
|
|
976
|
+
<Text dimColor>ID: {selectedHistory.id}</Text>
|
|
978
977
|
</Box>
|
|
979
978
|
<Panel height={MID_H - halfH} borderStyle="round" borderColor="red" paddingX={1}>
|
|
980
|
-
<Text>Confirm Delete</Text>
|
|
981
979
|
<SelectInput
|
|
982
980
|
items={items}
|
|
983
981
|
onSelect={(item) => handleHistoryMenuAction(String(item.value))}
|
|
984
982
|
/>
|
|
985
|
-
<Hint
|
|
983
|
+
<Hint>Enter Confirm · q Cancel</Hint>
|
|
986
984
|
</Panel>
|
|
987
985
|
</Box>
|
|
988
986
|
);
|
|
989
987
|
}
|
|
990
988
|
if (!historyList.length && loadingHist) {
|
|
991
|
-
return <StateDisplay type="loading" message="
|
|
989
|
+
return <StateDisplay type="loading" message="Loading..." />;
|
|
992
990
|
}
|
|
993
991
|
if (!historyList.length) {
|
|
994
992
|
return <StateDisplay type="empty" message={i18nMap[lang].emptyHistory} />;
|
|
995
993
|
}
|
|
996
994
|
|
|
995
|
+
const LIST_H = Math.floor(MID_H * 0.7);
|
|
996
|
+
const ACTIONS_H = MID_H - LIST_H - 1; // -1 for separator/buffer
|
|
997
|
+
|
|
997
998
|
if (historyMenuStage === 'window' && selectedHistory) {
|
|
999
|
+
// Detailed View with Split
|
|
998
1000
|
const isMarked = markedSessionIds.has(selectedHistory.id);
|
|
999
|
-
|
|
1000
|
-
// INFO_H: Title + Metadata + Hint (3 lines)
|
|
1001
|
-
const INFO_H = 3;
|
|
1002
|
-
const MSGS_H = Math.max(3, MID_H - INFO_H);
|
|
1003
|
-
|
|
1004
|
-
// Filter messages
|
|
1005
1001
|
const displayMsgs = sessionMessages.filter(
|
|
1006
1002
|
m => m.type === 'user' || m.type === 'assistant' || m.type === 'system'
|
|
1007
1003
|
);
|
|
1008
|
-
|
|
1009
|
-
// Pagination
|
|
1010
1004
|
const totalPages = Math.max(1, Math.ceil(displayMsgs.length / MSGS_PAGE_SIZE));
|
|
1011
1005
|
const safePage = Math.min(msgsPage, totalPages - 1);
|
|
1012
1006
|
const pageStart = safePage * MSGS_PAGE_SIZE;
|
|
@@ -1014,62 +1008,70 @@ export const CliMenuManager: React.FC = () => {
|
|
|
1014
1008
|
|
|
1015
1009
|
return (
|
|
1016
1010
|
<Box width={VIEW_W} height={MID_H} flexDirection="column">
|
|
1017
|
-
{/*
|
|
1018
|
-
<Box height={
|
|
1019
|
-
<
|
|
1020
|
-
{isMarked ? '
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1011
|
+
{/* Upper Pane: Preview */}
|
|
1012
|
+
<Box height={LIST_H} flexDirection="column" paddingX={1}>
|
|
1013
|
+
<Box justifyContent="space-between" marginBottom={1}>
|
|
1014
|
+
<Text color={isMarked ? 'yellow' : 'cyan'} bold>
|
|
1015
|
+
{isMarked ? '★ ' : ''}{selectedHistory.title || 'Untitled'}
|
|
1016
|
+
</Text>
|
|
1017
|
+
<Text dimColor>{selectedHistory.createdAt?.slice(0,16).replace('T',' ')}</Text>
|
|
1018
|
+
</Box>
|
|
1019
|
+
|
|
1020
|
+
<Box flexDirection="column" flexGrow={1}>
|
|
1021
|
+
{loadingMsgs && <StateDisplay type="loading" message="Loading messages..." />}
|
|
1022
|
+
{msgsErr && <StateDisplay type="error" message={msgsErr} />}
|
|
1023
|
+
{!loadingMsgs && pageMsgs.length === 0 && <StateDisplay type="empty" message="No messages" />}
|
|
1024
|
+
{pageMsgs.map((msg) => {
|
|
1025
|
+
const text = extractTextFromContent(msg.content);
|
|
1026
|
+
const roleLabel = msg.type === 'user' ? 'You' : 'Bot';
|
|
1027
|
+
const roleColor = msg.type === 'user' ? 'green' : 'cyan';
|
|
1028
|
+
return (
|
|
1029
|
+
<Box key={msg.id} marginBottom={1} flexDirection="column">
|
|
1030
|
+
<Text color={roleColor} bold>{roleLabel}:</Text>
|
|
1031
|
+
<Box paddingLeft={2}>
|
|
1032
|
+
<Text>{clampTextLines(text, VIEW_W - 6, 3)}</Text>
|
|
1033
|
+
</Box>
|
|
1034
|
+
</Box>
|
|
1035
|
+
);
|
|
1036
|
+
})}
|
|
1037
|
+
</Box>
|
|
1038
|
+
{totalPages > 1 && (
|
|
1039
|
+
<Box justifyContent="center" marginTop={1}>
|
|
1040
|
+
<Hint>Page {safePage + 1}/{totalPages} (↑↓ to scroll)</Hint>
|
|
1041
|
+
</Box>
|
|
1042
|
+
)}
|
|
1027
1043
|
</Box>
|
|
1028
1044
|
|
|
1029
|
-
{
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
const roleLabel = isUser ? '👤 You' : isSystem ? '⚙ System' : '🤖 Assistant';
|
|
1042
|
-
const roleColor = isUser ? 'green' : isSystem ? 'gray' : 'cyan';
|
|
1043
|
-
return (
|
|
1044
|
-
<Box key={msg.id} flexDirection="column" marginBottom={1}>
|
|
1045
|
-
<Text color={roleColor} bold>{roleLabel}</Text>
|
|
1046
|
-
{isUser ? (
|
|
1047
|
-
<Text>{text}</Text>
|
|
1048
|
-
) : (
|
|
1049
|
-
<Ansi>{applyMarkdown(text, theme)}</Ansi>
|
|
1050
|
-
)}
|
|
1051
|
-
</Box>
|
|
1052
|
-
);
|
|
1053
|
-
})}
|
|
1045
|
+
<Box height={1}><Text dimColor>{'─'.repeat(VIEW_W - 2)}</Text></Box>
|
|
1046
|
+
|
|
1047
|
+
{/* Lower Pane: Actions */}
|
|
1048
|
+
<Box height={ACTIONS_H} paddingX={1} flexDirection="column">
|
|
1049
|
+
<Text color="magenta" bold>Actions</Text>
|
|
1050
|
+
<Box marginTop={0}>
|
|
1051
|
+
<SelectInput
|
|
1052
|
+
items={secondaryMenu?.items || []}
|
|
1053
|
+
onSelect={secondaryMenu?.onSelect}
|
|
1054
|
+
/>
|
|
1055
|
+
</Box>
|
|
1056
|
+
<Hint>ESC Back · ↑↓ Select Action · Q/M/C Shortcut</Hint>
|
|
1054
1057
|
</Box>
|
|
1055
1058
|
</Box>
|
|
1056
1059
|
);
|
|
1057
1060
|
}
|
|
1058
1061
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
const HIST_VISIBLE = Math.max(1, MID_H - 1);
|
|
1062
|
+
// History List View (Default)
|
|
1063
|
+
const HIST_VISIBLE = MID_H - 2;
|
|
1062
1064
|
const start = Math.min(listOffset, Math.max(0, groupedHistoryItems.length - HIST_VISIBLE));
|
|
1063
1065
|
const slicedItems = groupedHistoryItems.slice(start, start + HIST_VISIBLE);
|
|
1064
1066
|
|
|
1065
1067
|
return (
|
|
1066
1068
|
<Box width={VIEW_W} height={MID_H} flexDirection="row">
|
|
1067
|
-
<Box flexDirection="column" flexGrow={1}>
|
|
1069
|
+
<Box flexDirection="column" flexGrow={1} paddingX={1}>
|
|
1068
1070
|
<SelectInput
|
|
1069
1071
|
key={`${historyCursor ?? 'first'}:${slicedItems.length}:${start}`}
|
|
1070
1072
|
items={slicedItems}
|
|
1071
1073
|
onSelect={item => {
|
|
1072
|
-
if (String(item.value).startsWith('__group_')) return;
|
|
1074
|
+
if (String(item.value).startsWith('__group_')) return;
|
|
1073
1075
|
const session = historyList.find(h => h.id === item.value);
|
|
1074
1076
|
if (session) {
|
|
1075
1077
|
setSelectedHistory(session);
|
|
@@ -1082,12 +1084,14 @@ export const CliMenuManager: React.FC = () => {
|
|
|
1082
1084
|
const color = it?.color;
|
|
1083
1085
|
return (
|
|
1084
1086
|
<Text color={isGroup ? 'gray' : (color ? color : (isSelected ? 'cyan' : undefined))}>
|
|
1085
|
-
{label}
|
|
1087
|
+
{isSelected ? '> ' : ' '}{label}
|
|
1086
1088
|
</Text>
|
|
1087
1089
|
)
|
|
1088
1090
|
}}
|
|
1089
1091
|
/>
|
|
1090
|
-
<
|
|
1092
|
+
<Box marginTop="auto">
|
|
1093
|
+
<Hint>{i18nMap[lang].historyHint}</Hint>
|
|
1094
|
+
</Box>
|
|
1091
1095
|
</Box>
|
|
1092
1096
|
<ScrollBar total={groupedHistoryItems.length} offset={start} height={MID_H} />
|
|
1093
1097
|
</Box>
|
|
@@ -1110,7 +1114,7 @@ export const CliMenuManager: React.FC = () => {
|
|
|
1110
1114
|
}
|
|
1111
1115
|
return (
|
|
1112
1116
|
<Box width={VIEW_W} height={MID_H} flexDirection="column">
|
|
1113
|
-
<ProviderPanel apiUrl={apiUrl} onBack={() => setPage(null)} />
|
|
1117
|
+
<ProviderPanel apiUrl={apiUrl} height={MID_H} onBack={() => setPage(null)} />
|
|
1114
1118
|
</Box>
|
|
1115
1119
|
);
|
|
1116
1120
|
}
|