bingocode 1.1.71 → 1.1.73

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.71",
3
+ "version": "1.1.73",
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
 
@@ -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
 
@@ -11,7 +11,7 @@ import path from 'path';
11
11
  import os from 'os';
12
12
  import { ensureSingletonLocalServer } from '../server/ensureSingletonLocalServer.ts';
13
13
  // New: Common UI elements and top toolbar
14
- import { TopBar, BottomBar, Panel, Hint, Kbd, SecondaryMenu, StateDisplay, ScrollBar } from '../manager/CliMenuUi.tsx';
14
+ import { TopBar, BottomBar, Panel, Hint, Kbd, SecondaryMenu, StateDisplay, ScrollBar, truncate, safePadEnd } from '../manager/CliMenuUi.tsx';
15
15
  import { WelcomeV2 } from '../components/LogoV2/WelcomeV2.tsx';
16
16
  import { TopToolbar } from '../manager/TopToolbar.tsx';
17
17
 
@@ -681,37 +681,30 @@ 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
  }
699
698
 
700
- // 新增:历史列表格式化(单行定宽 + 省略)
701
- function ellipsis(str: string, max: number) {
702
- const s = String(str ?? '');
703
- if (max <= 0) return '';
704
- if (s.length <= max) return s;
705
- return s.slice(0, Math.max(0, max - 1)) + '…';
706
- }
707
699
  function makeHistoryLabel(item: any, width: number, isMarked: boolean) {
708
700
  const star = isMarked ? '★ ' : '';
709
- const ts = String(item.createdAt || '').slice(0, 16).replace('T',' ');
701
+ const ts = String(item.createdAt || '').slice(0, 16).replace('T', ' ');
710
702
  const cnt = String(item.messageCount ?? 0).padStart(3, ' ');
711
- // 预留:前缀(星标+时间) + 双空格 + 末尾计数 + 单空格
712
- const reserved = star.length + ts.length + 2 + 1 + cnt.length;
703
+ // Reserved width for: prefix(star+time) + spacer(2) + suffix(1+cnt)
704
+ // Star is width 2, ts is width 16, spacer is 2, cnt is 3, padding is 1. Total = 24
705
+ const reserved = 24;
713
706
  const titleMax = Math.max(8, width - reserved);
714
- const title = ellipsis(String(item.title || ''), titleMax).padEnd(titleMax, ' ');
707
+ const title = safePadEnd(truncate(String(item.title || ''), titleMax), titleMax);
715
708
  return `${star}${ts} ${title} ${cnt}`;
716
709
  }
717
710
 
@@ -999,8 +992,6 @@ export const CliMenuManager: React.FC = () => {
999
992
  if (historyMenuStage === 'window' && selectedHistory) {
1000
993
  // Detailed View with Split
1001
994
  const isMarked = markedSessionIds.has(selectedHistory.id);
1002
- const PREVIEW_H = Math.max(3, LIST_H - 2);
1003
-
1004
995
  const displayMsgs = sessionMessages.filter(
1005
996
  m => m.type === 'user' || m.type === 'assistant' || m.type === 'system'
1006
997
  );
@@ -1012,41 +1003,51 @@ export const CliMenuManager: React.FC = () => {
1012
1003
  return (
1013
1004
  <Box width={VIEW_W} height={MID_H} flexDirection="column">
1014
1005
  {/* Upper Pane: Preview */}
1015
- <Box height={LIST_H} borderStyle="single" borderColor="gray" flexDirection="column" paddingX={1}>
1016
- <Box justifyContent="space-between">
1006
+ <Box height={LIST_H} flexDirection="column" paddingX={1}>
1007
+ <Box justifyContent="space-between" marginBottom={1}>
1017
1008
  <Text color={isMarked ? 'yellow' : 'cyan'} bold>
1018
1009
  {isMarked ? '★ ' : ''}{selectedHistory.title || 'Untitled'}
1019
1010
  </Text>
1020
1011
  <Text dimColor>{selectedHistory.createdAt?.slice(0,16).replace('T',' ')}</Text>
1021
1012
  </Box>
1022
1013
 
1023
- <Box flexDirection="column" flexGrow={1} marginTop={1}>
1024
- {loadingMsgs && <Text dimColor>Loading messages...</Text>}
1014
+ <Box flexDirection="column" flexGrow={1}>
1015
+ {loadingMsgs && <StateDisplay type="loading" message="Loading messages..." />}
1016
+ {msgsErr && <StateDisplay type="error" message={msgsErr} />}
1017
+ {!loadingMsgs && pageMsgs.length === 0 && <StateDisplay type="empty" message="No messages" />}
1025
1018
  {pageMsgs.map((msg) => {
1026
1019
  const text = extractTextFromContent(msg.content);
1027
1020
  const roleLabel = msg.type === 'user' ? 'You' : 'Bot';
1028
1021
  const roleColor = msg.type === 'user' ? 'green' : 'cyan';
1029
1022
  return (
1030
- <Box key={msg.id} marginBottom={1}>
1031
- <Text color={roleColor} bold>{roleLabel}: </Text>
1032
- <Text>{clampTextLines(text, VIEW_W - 10, 2)}</Text>
1023
+ <Box key={msg.id} marginBottom={1} flexDirection="column">
1024
+ <Text color={roleColor} bold>{roleLabel}:</Text>
1025
+ <Box paddingLeft={2}>
1026
+ <Text>{clampTextLines(text, VIEW_W - 6, 3)}</Text>
1027
+ </Box>
1033
1028
  </Box>
1034
1029
  );
1035
1030
  })}
1036
1031
  </Box>
1037
- {totalPages > 1 && <Hint alignSelf="center">Page {safePage + 1}/{totalPages} (j/k to scroll)</Hint>}
1032
+ {totalPages > 1 && (
1033
+ <Box justifyContent="center" marginTop={1}>
1034
+ <Hint>Page {safePage + 1}/{totalPages} (↑↓ to scroll)</Hint>
1035
+ </Box>
1036
+ )}
1038
1037
  </Box>
1039
1038
 
1039
+ <Box height={1}><Text dimColor>{'─'.repeat(VIEW_W - 2)}</Text></Box>
1040
+
1040
1041
  {/* 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}>
1042
+ <Box height={ACTIONS_H} paddingX={1} flexDirection="column">
1043
+ <Text color="magenta" bold>Actions</Text>
1044
+ <Box marginTop={0}>
1044
1045
  <SelectInput
1045
1046
  items={secondaryMenu?.items || []}
1046
1047
  onSelect={secondaryMenu?.onSelect}
1047
1048
  />
1048
1049
  </Box>
1049
- <Hint>ESC Back · ↑↓ Select Action</Hint>
1050
+ <Hint>ESC Back · ↑↓ Select Action · Q/M/C Shortcut</Hint>
1050
1051
  </Box>
1051
1052
  </Box>
1052
1053
  );
@@ -1058,7 +1059,7 @@ export const CliMenuManager: React.FC = () => {
1058
1059
  const slicedItems = groupedHistoryItems.slice(start, start + HIST_VISIBLE);
1059
1060
 
1060
1061
  return (
1061
- <Box width={VIEW_W} height={MID_H} flexDirection="row">
1062
+ <Box width={VIEW_W} height={MID_H} flexDirection="row" position="relative">
1062
1063
  <Box flexDirection="column" flexGrow={1} paddingX={1}>
1063
1064
  <SelectInput
1064
1065
  key={`${historyCursor ?? 'first'}:${slicedItems.length}:${start}`}
@@ -1086,7 +1087,7 @@ export const CliMenuManager: React.FC = () => {
1086
1087
  <Hint>{i18nMap[lang].historyHint}</Hint>
1087
1088
  </Box>
1088
1089
  </Box>
1089
- <ScrollBar total={groupedHistoryItems.length} offset={start} height={MID_H} />
1090
+ <ScrollBar total={groupedHistoryItems.length} offset={start} height={MID_H - 2} />
1090
1091
  </Box>
1091
1092
  );
1092
1093
  }
@@ -1107,7 +1108,7 @@ export const CliMenuManager: React.FC = () => {
1107
1108
  }
1108
1109
  return (
1109
1110
  <Box width={VIEW_W} height={MID_H} flexDirection="column">
1110
- <ProviderPanel apiUrl={apiUrl} onBack={() => setPage(null)} />
1111
+ <ProviderPanel apiUrl={apiUrl} height={MID_H} onBack={() => setPage(null)} />
1111
1112
  </Box>
1112
1113
  );
1113
1114
  }