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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bingocode",
3
- "version": "1.1.70",
3
+ "version": "1.1.72",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "claude": "bin/claude-win.cjs",
@@ -57,7 +57,8 @@ type Stage =
57
57
  export const ProviderPanel: React.FC<{
58
58
  apiUrl: string;
59
59
  onBack?: () => void;
60
- }> = ({ apiUrl, onBack }) => {
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 at the beginning
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 = Math.max(1, MID_H - 1);
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 lines = String(text ?? '').split(/\r?\n/);
689
+ const cleaned = cleanText(text);
686
690
  const out: string[] = [];
687
- let used = 0;
688
- for (let i = 0; i < lines.length; i++) {
689
- if (out.length >= maxLines) break;
690
- const raw = lines[i];
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, delete', value: '__confirm_delete' },
969
- { label: 'No, back', value: '__cancel_delete' },
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">{i18nMap[lang].deleting}</Text>
975
- <Text>id: {selectedHistory.id}</Text>
976
- <Text>Title: {selectedHistory.title}</Text>
977
- <Text>Created At: {selectedHistory.createdAt}</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>↩ Enter · q Back</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="Fetching history..." />;
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
- {/* Top Info Bar */}
1018
- <Box height={INFO_H} flexDirection="column">
1019
- <Text color={isMarked ? 'yellow' : 'cyan'}>
1020
- {isMarked ? '' : ''}{selectedHistory.title || 'Untitled'}
1021
- <Text dimColor> {selectedHistory.createdAt?.slice(0, 16).replace('T', ' ') || ''} · {displayMsgs.length} msgs</Text>
1022
- </Text>
1023
- <Hint>
1024
- j/↓ Down · k/↑ Up · m Mark · c Continue · d Delete · q Back
1025
- {displayMsgs.length > MSGS_PAGE_SIZE ? ` [${safePage + 1}/${totalPages}]` : ''}
1026
- </Hint>
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
- {/* Message Area */}
1030
- <Box height={MSGS_H} flexDirection="column">
1031
- {loadingMsgs && <StateDisplay type="loading" message="Loading messages..." />}
1032
- {msgsErr && <StateDisplay type="error" message={`Error: ${msgsErr}`} />}
1033
- {!loadingMsgs && !msgsErr && displayMsgs.length === 0 && (
1034
- <StateDisplay type="empty" message="No message history" />
1035
- )}
1036
- {pageMsgs.map((msg) => {
1037
- const text = extractTextFromContent(msg.content);
1038
- if (!text.trim()) return null;
1039
- const isUser = msg.type === 'user';
1040
- const isSystem = msg.type === 'system';
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
- // History List
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; // Ignore group headers
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
- <Hint>{i18nMap[lang].historyHint}</Hint>
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
  }