bingocode 1.1.69 → 1.1.71

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.69",
3
+ "version": "1.1.71",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "claude": "bin/claude-win.cjs",
@@ -141,13 +141,13 @@ export const ProviderPanel: React.FC<{
141
141
  loadPresets();
142
142
  }, [loadProviders, loadPresets]);
143
143
 
144
- // Key processing for Page Up/Down in scrolling lists
144
+ // Key processing for Page Up/Down and Arrow keys in scrolling lists
145
145
  useEffect(() => {
146
146
  const handler = (buf: Buffer) => {
147
147
  const s = buf.toString();
148
148
  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));
149
+ if (s === 'j' || s === '\u001b[B') setListOffset(prev => prev + 1); // j or Down
150
+ if (s === 'k' || s === '\u001b[A') setListOffset(prev => Math.max(0, prev - 1)); // k or Up
151
151
  }
152
152
  };
153
153
  process.stdin.on('data', handler);
@@ -679,7 +679,7 @@ export const ProviderPanel: React.FC<{
679
679
  : null;
680
680
  const modelDisplayName = entry?.label || entry?.modelId || 'Unconfigured';
681
681
  const status = entry ? `${providerName} / ${modelDisplayName}` : 'Unconfigured';
682
- const label = `[${s}] ${safePadEnd(status, 28)} — ${SLOT_DESCS[s]}`;
682
+ const label = `[${s}] ${safePadEnd(status, 30)} — ${SLOT_DESCS[s]}`;
683
683
  return { label, value: s };
684
684
  });
685
685
  return (
@@ -82,13 +82,20 @@ const i18nMap = {
82
82
  zh: {
83
83
  menu: {
84
84
  newSession: 'New Session',
85
- history: 'Session History',
85
+ history: 'History',
86
86
  provider: 'API Config',
87
87
  settings: 'Settings',
88
88
  about: 'About',
89
89
  exit: 'Exit',
90
90
  },
91
- about: 'Bingo CLI Terminal - Version Info & About',
91
+ about: 'Bingo CLI - Version Info & About',
92
+ aboutContent: [
93
+ 'Bingo is an AI assistant terminal client.',
94
+ '1. API Config: Press "P" or select "API Config" to set up your keys.',
95
+ '2. Model Slots: Configure specific models in the Provider panel.',
96
+ '3. Background Service: Bingo runs a local server to manage sessions.',
97
+ '4. Start Chat: Run `bingocode` or `claude` in any terminal to start.',
98
+ ].join('\n'),
92
99
  mark: '→ Mark Session',
93
100
  unmark: '→ Unmark Session',
94
101
  tipsSimple: 'L Lang | ESC Back | ←→ Menu | ↩ Enter | ? Help',
@@ -108,6 +115,13 @@ const i18nMap = {
108
115
  exit: 'Exit',
109
116
  },
110
117
  about: 'Bingo CLI Terminal - Version Info & About',
118
+ aboutContent: [
119
+ 'Bingo is an AI assistant terminal client.',
120
+ '1. API Config: Press "P" or select "API Config" to set up your keys.',
121
+ '2. Model Slots: Configure specific models in the Provider panel.',
122
+ '3. Background Service: Bingo runs a local server to manage sessions.',
123
+ '4. Start Chat: Run `bingocode` or `claude` in any terminal to start.',
124
+ ].join('\n'),
111
125
  mark: '→ Mark Session',
112
126
  unmark: '→ Unmark Session',
113
127
  tipsSimple: 'L Lang | ESC Back | ←→ Menu | ↩ Enter | ? Help',
@@ -593,12 +607,12 @@ export const CliMenuManager: React.FC = () => {
593
607
  // History shortcuts
594
608
  if (!showHelp && page === 'history') {
595
609
  if (historyMenuStage === 'list') {
596
- const HIST_VISIBLE = Math.max(1, MID_H - 1);
597
- if (key.downArrow || input === 'j') {
610
+ const HIST_VISIBLE = MID_H - 2;
611
+ if (key.downArrow || input === 'j' || input === '\u001b[B') {
598
612
  // Internal SelectInput handles cursor, we just need to track offset for ScrollBar
599
613
  setListOffset(o => Math.min(o + 1, Math.max(0, groupedHistoryItems.length - HIST_VISIBLE)));
600
614
  }
601
- if (key.upArrow || input === 'k') {
615
+ if (key.upArrow || input === 'k' || input === '\u001b[A') {
602
616
  setListOffset(o => Math.max(0, o - 1));
603
617
  }
604
618
  if (input === 'q') {
@@ -951,48 +965,45 @@ export const CliMenuManager: React.FC = () => {
951
965
  if (historyMenuStage === 'deleteConfirm' && selectedHistory) {
952
966
  const halfH = Math.floor(MID_H / 2);
953
967
  const items = [
954
- { label: 'Yes, delete', value: '__confirm_delete' },
955
- { label: 'No, back', value: '__cancel_delete' },
968
+ { label: 'Yes, Delete', value: '__confirm_delete' },
969
+ { label: 'No, Back', value: '__cancel_delete' },
956
970
  ];
957
971
  return (
958
972
  <Box width={VIEW_W} height={MID_H} flexDirection="column">
959
- <Box height={halfH} flexDirection="column">
960
- <Text color="red">{i18nMap[lang].deleting}</Text>
961
- <Text>id: {selectedHistory.id}</Text>
962
- <Text>Title: {selectedHistory.title}</Text>
963
- <Text>Created At: {selectedHistory.createdAt}</Text>
973
+ <Box height={halfH} flexDirection="column" paddingX={1} paddingTop={1}>
974
+ <Text color="red" bold>Confirm Delete?</Text>
975
+ <Text>Title: {selectedHistory.title || 'Untitled'}</Text>
976
+ <Text dimColor>Time: {selectedHistory.createdAt?.replace('T',' ')}</Text>
977
+ <Text dimColor>ID: {selectedHistory.id}</Text>
964
978
  </Box>
965
979
  <Panel height={MID_H - halfH} borderStyle="round" borderColor="red" paddingX={1}>
966
- <Text>Confirm Delete</Text>
967
980
  <SelectInput
968
981
  items={items}
969
982
  onSelect={(item) => handleHistoryMenuAction(String(item.value))}
970
983
  />
971
- <Hint>↩ Enter · q Back</Hint>
984
+ <Hint>Enter Confirm · q Cancel</Hint>
972
985
  </Panel>
973
986
  </Box>
974
987
  );
975
988
  }
976
989
  if (!historyList.length && loadingHist) {
977
- return <StateDisplay type="loading" message="Fetching history..." />;
990
+ return <StateDisplay type="loading" message="Loading..." />;
978
991
  }
979
992
  if (!historyList.length) {
980
993
  return <StateDisplay type="empty" message={i18nMap[lang].emptyHistory} />;
981
994
  }
982
995
 
996
+ const LIST_H = Math.floor(MID_H * 0.7);
997
+ const ACTIONS_H = MID_H - LIST_H - 1; // -1 for separator/buffer
998
+
983
999
  if (historyMenuStage === 'window' && selectedHistory) {
1000
+ // Detailed View with Split
984
1001
  const isMarked = markedSessionIds.has(selectedHistory.id);
1002
+ const PREVIEW_H = Math.max(3, LIST_H - 2);
985
1003
 
986
- // INFO_H: Title + Metadata + Hint (3 lines)
987
- const INFO_H = 3;
988
- const MSGS_H = Math.max(3, MID_H - INFO_H);
989
-
990
- // Filter messages
991
1004
  const displayMsgs = sessionMessages.filter(
992
1005
  m => m.type === 'user' || m.type === 'assistant' || m.type === 'system'
993
1006
  );
994
-
995
- // Pagination
996
1007
  const totalPages = Math.max(1, Math.ceil(displayMsgs.length / MSGS_PAGE_SIZE));
997
1008
  const safePage = Math.min(msgsPage, totalPages - 1);
998
1009
  const pageStart = safePage * MSGS_PAGE_SIZE;
@@ -1000,62 +1011,60 @@ export const CliMenuManager: React.FC = () => {
1000
1011
 
1001
1012
  return (
1002
1013
  <Box width={VIEW_W} height={MID_H} flexDirection="column">
1003
- {/* Top Info Bar */}
1004
- <Box height={INFO_H} flexDirection="column">
1005
- <Text color={isMarked ? 'yellow' : 'cyan'}>
1006
- {isMarked ? '' : ''}{selectedHistory.title || 'Untitled'}
1007
- <Text dimColor> {selectedHistory.createdAt?.slice(0, 16).replace('T', ' ') || ''} · {displayMsgs.length} msgs</Text>
1008
- </Text>
1009
- <Hint>
1010
- j/↓ Down · k/↑ Up · m Mark · c Continue · d Delete · q Back
1011
- {displayMsgs.length > MSGS_PAGE_SIZE ? ` [${safePage + 1}/${totalPages}]` : ''}
1012
- </Hint>
1014
+ {/* Upper Pane: Preview */}
1015
+ <Box height={LIST_H} borderStyle="single" borderColor="gray" flexDirection="column" paddingX={1}>
1016
+ <Box justifyContent="space-between">
1017
+ <Text color={isMarked ? 'yellow' : 'cyan'} bold>
1018
+ {isMarked ? ' ' : ''}{selectedHistory.title || 'Untitled'}
1019
+ </Text>
1020
+ <Text dimColor>{selectedHistory.createdAt?.slice(0,16).replace('T',' ')}</Text>
1021
+ </Box>
1022
+
1023
+ <Box flexDirection="column" flexGrow={1} marginTop={1}>
1024
+ {loadingMsgs && <Text dimColor>Loading messages...</Text>}
1025
+ {pageMsgs.map((msg) => {
1026
+ const text = extractTextFromContent(msg.content);
1027
+ const roleLabel = msg.type === 'user' ? 'You' : 'Bot';
1028
+ const roleColor = msg.type === 'user' ? 'green' : 'cyan';
1029
+ return (
1030
+ <Box key={msg.id} marginBottom={1}>
1031
+ <Text color={roleColor} bold>{roleLabel}: </Text>
1032
+ <Text>{clampTextLines(text, VIEW_W - 10, 2)}</Text>
1033
+ </Box>
1034
+ );
1035
+ })}
1036
+ </Box>
1037
+ {totalPages > 1 && <Hint alignSelf="center">Page {safePage + 1}/{totalPages} (j/k to scroll)</Hint>}
1013
1038
  </Box>
1014
1039
 
1015
- {/* Message Area */}
1016
- <Box height={MSGS_H} flexDirection="column">
1017
- {loadingMsgs && <StateDisplay type="loading" message="Loading messages..." />}
1018
- {msgsErr && <StateDisplay type="error" message={`Error: ${msgsErr}`} />}
1019
- {!loadingMsgs && !msgsErr && displayMsgs.length === 0 && (
1020
- <StateDisplay type="empty" message="No message history" />
1021
- )}
1022
- {pageMsgs.map((msg) => {
1023
- const text = extractTextFromContent(msg.content);
1024
- if (!text.trim()) return null;
1025
- const isUser = msg.type === 'user';
1026
- const isSystem = msg.type === 'system';
1027
- const roleLabel = isUser ? '👤 You' : isSystem ? '⚙ System' : '🤖 Assistant';
1028
- const roleColor = isUser ? 'green' : isSystem ? 'gray' : 'cyan';
1029
- return (
1030
- <Box key={msg.id} flexDirection="column" marginBottom={1}>
1031
- <Text color={roleColor} bold>{roleLabel}</Text>
1032
- {isUser ? (
1033
- <Text>{text}</Text>
1034
- ) : (
1035
- <Ansi>{applyMarkdown(text, theme)}</Ansi>
1036
- )}
1037
- </Box>
1038
- );
1039
- })}
1040
+ {/* Lower Pane: Actions */}
1041
+ <Box height={ACTIONS_H} marginTop={1} paddingX={1} flexDirection="column">
1042
+ <Text color="cyan" bold>Session Actions</Text>
1043
+ <Box marginTop={1}>
1044
+ <SelectInput
1045
+ items={secondaryMenu?.items || []}
1046
+ onSelect={secondaryMenu?.onSelect}
1047
+ />
1048
+ </Box>
1049
+ <Hint>ESC Back · ↑↓ Select Action</Hint>
1040
1050
  </Box>
1041
1051
  </Box>
1042
1052
  );
1043
1053
  }
1044
1054
 
1045
-
1046
- // History List
1047
- const HIST_VISIBLE = Math.max(1, MID_H - 1);
1055
+ // History List View (Default)
1056
+ const HIST_VISIBLE = MID_H - 2;
1048
1057
  const start = Math.min(listOffset, Math.max(0, groupedHistoryItems.length - HIST_VISIBLE));
1049
1058
  const slicedItems = groupedHistoryItems.slice(start, start + HIST_VISIBLE);
1050
1059
 
1051
1060
  return (
1052
1061
  <Box width={VIEW_W} height={MID_H} flexDirection="row">
1053
- <Box flexDirection="column" flexGrow={1}>
1062
+ <Box flexDirection="column" flexGrow={1} paddingX={1}>
1054
1063
  <SelectInput
1055
1064
  key={`${historyCursor ?? 'first'}:${slicedItems.length}:${start}`}
1056
1065
  items={slicedItems}
1057
1066
  onSelect={item => {
1058
- if (String(item.value).startsWith('__group_')) return; // Ignore group headers
1067
+ if (String(item.value).startsWith('__group_')) return;
1059
1068
  const session = historyList.find(h => h.id === item.value);
1060
1069
  if (session) {
1061
1070
  setSelectedHistory(session);
@@ -1068,12 +1077,14 @@ export const CliMenuManager: React.FC = () => {
1068
1077
  const color = it?.color;
1069
1078
  return (
1070
1079
  <Text color={isGroup ? 'gray' : (color ? color : (isSelected ? 'cyan' : undefined))}>
1071
- {label}
1080
+ {isSelected ? '> ' : ' '}{label}
1072
1081
  </Text>
1073
1082
  )
1074
1083
  }}
1075
1084
  />
1076
- <Hint>{i18nMap[lang].historyHint}</Hint>
1085
+ <Box marginTop="auto">
1086
+ <Hint>{i18nMap[lang].historyHint}</Hint>
1087
+ </Box>
1077
1088
  </Box>
1078
1089
  <ScrollBar total={groupedHistoryItems.length} offset={start} height={MID_H} />
1079
1090
  </Box>
@@ -1125,10 +1136,15 @@ export const CliMenuManager: React.FC = () => {
1125
1136
  if (page === 'about') {
1126
1137
  return (
1127
1138
  <Box width={VIEW_W} height={MID_H} flexDirection="column">
1128
- <Text>{i18nMap[lang].about}</Text>
1129
- <Hint>
1130
- API Base: {apiUrl}
1131
- </Hint>
1139
+ <Text color="cyan" bold>{i18nMap[lang].about}</Text>
1140
+ <Box marginTop={1} flexDirection="column">
1141
+ <Text>{(i18nMap[lang] as any).aboutContent}</Text>
1142
+ </Box>
1143
+ <Box marginTop={1}>
1144
+ <Hint>
1145
+ API Base: {apiUrl}
1146
+ </Hint>
1147
+ </Box>
1132
1148
  </Box>
1133
1149
  );
1134
1150
  }