bingocode 1.1.74 → 1.1.76
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/manager/CliMenuManager.tsx +105 -95
package/package.json
CHANGED
|
@@ -91,6 +91,8 @@ const i18nMap = {
|
|
|
91
91
|
about: 'Bingo CLI - Version Info & About',
|
|
92
92
|
aboutContent: [
|
|
93
93
|
'Bingo is an AI assistant terminal client.',
|
|
94
|
+
'Author: leanchy (Email: leanchy07@outlook.com)',
|
|
95
|
+
'Github: github.com/leanchy/bingo-claude-code-offline-installer',
|
|
94
96
|
'1. API Config: Press "P" or select "API Config" to set up your keys.',
|
|
95
97
|
'2. Model Slots: Configure specific models in the Provider panel.',
|
|
96
98
|
'3. Background Service: Bingo runs a local server to manage sessions.',
|
|
@@ -117,6 +119,8 @@ const i18nMap = {
|
|
|
117
119
|
about: 'Bingo CLI Terminal - Version Info & About',
|
|
118
120
|
aboutContent: [
|
|
119
121
|
'Bingo is an AI assistant terminal client.',
|
|
122
|
+
'Author: leanchy (Email: leanchy07@outlook.com)',
|
|
123
|
+
'Github: github.com/leanchy/bingo-claude-code-offline-installer',
|
|
120
124
|
'1. API Config: Press "P" or select "API Config" to set up your keys.',
|
|
121
125
|
'2. Model Slots: Configure specific models in the Provider panel.',
|
|
122
126
|
'3. Background Service: Bingo runs a local server to manage sessions.',
|
|
@@ -739,47 +743,54 @@ export const CliMenuManager: React.FC = () => {
|
|
|
739
743
|
}
|
|
740
744
|
|
|
741
745
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
746
|
+
// 历史分组展示
|
|
747
|
+
const groupedHistoryItems = useMemo(() => {
|
|
748
|
+
if (!historyList || !Array.isArray(historyList)) return [];
|
|
749
|
+
const now = new Date();
|
|
750
|
+
const today: any[] = [];
|
|
751
|
+
const week: any[] = [];
|
|
752
|
+
const earlier: any[] = [];
|
|
753
|
+
const marked: any[] = [];
|
|
754
|
+
|
|
755
|
+
for (const item of historyList) {
|
|
756
|
+
if (markedSessionIds.has(item.id)) {
|
|
757
|
+
marked.push(item);
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
const dt = new Date(item.createdAt);
|
|
761
|
+
const isToday =
|
|
762
|
+
dt.getFullYear() === now.getFullYear() &&
|
|
763
|
+
dt.getMonth() === now.getMonth() &&
|
|
764
|
+
dt.getDate() === now.getDate();
|
|
765
|
+
const weekStart = new Date(now);
|
|
766
|
+
weekStart.setDate(now.getDate() - ((now.getDay() + 6) % 7));
|
|
767
|
+
weekStart.setHours(0, 0, 0, 0);
|
|
768
|
+
if (isToday) today.push(item);
|
|
769
|
+
else if (dt >= weekStart) week.push(item);
|
|
770
|
+
else earlier.push(item);
|
|
771
|
+
}
|
|
772
|
+
function groupToItems(group: any[], groupTitle: string) {
|
|
773
|
+
if (group.length === 0) return [];
|
|
774
|
+
return [
|
|
775
|
+
{ label: groupTitle, value: `__group_${groupTitle}`, isGroup: true },
|
|
776
|
+
...group.map(item => {
|
|
777
|
+
const isMarked = markedSessionIds.has(item.id);
|
|
778
|
+
return {
|
|
779
|
+
label: makeHistoryLabel(item, Math.max(20, VIEW_W - 8), isMarked),
|
|
780
|
+
value: item.id,
|
|
781
|
+
color: isMarked ? 'yellow' : undefined,
|
|
782
|
+
};
|
|
783
|
+
})
|
|
784
|
+
];
|
|
785
|
+
}
|
|
786
|
+
const items = [
|
|
787
|
+
...groupToItems(marked, '—— Marked ——'),
|
|
788
|
+
...groupToItems(today, '—— Today ——'),
|
|
789
|
+
...groupToItems(week, '—— This Week ——'),
|
|
790
|
+
...groupToItems(earlier, '—— Earlier ——'),
|
|
791
|
+
];
|
|
792
|
+
return items;
|
|
793
|
+
}, [historyList, markedSessionIds]);
|
|
783
794
|
|
|
784
795
|
// Toggle Mark
|
|
785
796
|
const toggleMarkSession = (sessionId: string) => {
|
|
@@ -986,8 +997,8 @@ export const CliMenuManager: React.FC = () => {
|
|
|
986
997
|
return <StateDisplay type="empty" message={i18nMap[lang].emptyHistory} />;
|
|
987
998
|
}
|
|
988
999
|
|
|
989
|
-
const
|
|
990
|
-
const
|
|
1000
|
+
const ACTIONS_H = 6;
|
|
1001
|
+
const LIST_H = Math.max(2, MID_H - ACTIONS_H - 1);
|
|
991
1002
|
|
|
992
1003
|
if (historyMenuStage === 'window' && selectedHistory) {
|
|
993
1004
|
// Detailed View with Split
|
|
@@ -1003,15 +1014,15 @@ export const CliMenuManager: React.FC = () => {
|
|
|
1003
1014
|
return (
|
|
1004
1015
|
<Box width={VIEW_W} height={MID_H} flexDirection="column">
|
|
1005
1016
|
{/* Upper Pane: Preview */}
|
|
1006
|
-
<Box height={LIST_H} flexDirection="column" paddingX={1}>
|
|
1007
|
-
<Box justifyContent="space-between" marginBottom={
|
|
1017
|
+
<Box height={LIST_H} flexDirection="column" paddingX={1} overflow="hidden">
|
|
1018
|
+
<Box justifyContent="space-between" marginBottom={0}>
|
|
1008
1019
|
<Text color={isMarked ? 'yellow' : 'cyan'} bold>
|
|
1009
|
-
{isMarked ? '★ ' : ''}{selectedHistory.title || 'Untitled'}
|
|
1020
|
+
{isMarked ? '★ ' : ''}{truncate(selectedHistory.title || 'Untitled', VIEW_W - 24)}
|
|
1010
1021
|
</Text>
|
|
1011
1022
|
<Text dimColor>{selectedHistory.createdAt?.slice(0,16).replace('T',' ')}</Text>
|
|
1012
1023
|
</Box>
|
|
1013
1024
|
|
|
1014
|
-
<Box flexDirection="column" flexGrow={1}>
|
|
1025
|
+
<Box flexDirection="column" flexGrow={1} overflow="hidden">
|
|
1015
1026
|
{loadingMsgs && <StateDisplay type="loading" message="Loading messages..." />}
|
|
1016
1027
|
{msgsErr && <StateDisplay type="error" message={msgsErr} />}
|
|
1017
1028
|
{!loadingMsgs && pageMsgs.length === 0 && <StateDisplay type="empty" message="No messages" />}
|
|
@@ -1020,28 +1031,25 @@ export const CliMenuManager: React.FC = () => {
|
|
|
1020
1031
|
const roleLabel = msg.type === 'user' ? 'You' : 'Bot';
|
|
1021
1032
|
const roleColor = msg.type === 'user' ? 'green' : 'cyan';
|
|
1022
1033
|
return (
|
|
1023
|
-
<Box key={msg.id} marginBottom={
|
|
1024
|
-
<Text color={roleColor} bold>{roleLabel}
|
|
1025
|
-
<Box paddingLeft={2}>
|
|
1026
|
-
<Text>{clampTextLines(text, VIEW_W - 6, 3)}</Text>
|
|
1027
|
-
</Box>
|
|
1034
|
+
<Box key={msg.id} marginBottom={0} flexDirection="column" height={1} overflow="hidden">
|
|
1035
|
+
<Text color={roleColor} bold>{roleLabel}: <Text color="white" bold={false}>{clampTextLines(text, VIEW_W - 10, 1)}</Text></Text>
|
|
1028
1036
|
</Box>
|
|
1029
1037
|
);
|
|
1030
1038
|
})}
|
|
1031
1039
|
</Box>
|
|
1032
1040
|
{totalPages > 1 && (
|
|
1033
|
-
<Box justifyContent="center"
|
|
1041
|
+
<Box justifyContent="center" height={1}>
|
|
1034
1042
|
<Hint>Page {safePage + 1}/{totalPages} (↑↓ to scroll)</Hint>
|
|
1035
1043
|
</Box>
|
|
1036
1044
|
)}
|
|
1037
1045
|
</Box>
|
|
1038
1046
|
|
|
1039
|
-
<Box height={1}><Text dimColor>{'─'.repeat(VIEW_W - 2)}</Text></Box>
|
|
1047
|
+
<Box height={1} marginBottom={0}><Text dimColor>{'─'.repeat(VIEW_W - 2)}</Text></Box>
|
|
1040
1048
|
|
|
1041
1049
|
{/* Lower Pane: Actions */}
|
|
1042
|
-
<Box height={ACTIONS_H} paddingX={1} flexDirection="column">
|
|
1050
|
+
<Box height={ACTIONS_H} paddingX={1} flexDirection="column" overflow="hidden">
|
|
1043
1051
|
<Text color="magenta" bold>Actions</Text>
|
|
1044
|
-
<Box marginTop={0}>
|
|
1052
|
+
<Box marginTop={0} height={ACTIONS_H - 2} overflow="hidden">
|
|
1045
1053
|
<SelectInput
|
|
1046
1054
|
items={secondaryMenu?.items || []}
|
|
1047
1055
|
onSelect={secondaryMenu?.onSelect}
|
|
@@ -1053,44 +1061,46 @@ export const CliMenuManager: React.FC = () => {
|
|
|
1053
1061
|
);
|
|
1054
1062
|
}
|
|
1055
1063
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1064
|
+
// History List View (Default)
|
|
1065
|
+
const HIST_VISIBLE = MID_H - 2;
|
|
1066
|
+
const start = Math.min(listOffset, Math.max(0, groupedHistoryItems.length - HIST_VISIBLE));
|
|
1067
|
+
const slicedItems = groupedHistoryItems.slice(start, start + HIST_VISIBLE);
|
|
1068
|
+
|
|
1069
|
+
return (
|
|
1070
|
+
<Box width={VIEW_W} height={MID_H} flexDirection="row" position="relative">
|
|
1071
|
+
<Box flexDirection="column" flexGrow={1} paddingX={1}>
|
|
1072
|
+
<SelectInput
|
|
1073
|
+
key={`${historyCursor ?? 'first'}:${slicedItems.length}:${start}`}
|
|
1074
|
+
items={slicedItems}
|
|
1075
|
+
onSelect={item => {
|
|
1076
|
+
if (String(item.value).startsWith('__group_')) return;
|
|
1077
|
+
const session = historyList.find(h => h.id === item.value);
|
|
1078
|
+
if (session) {
|
|
1079
|
+
setSelectedHistory(session);
|
|
1080
|
+
setHistoryMenuStage('window');
|
|
1081
|
+
}
|
|
1082
|
+
}}
|
|
1083
|
+
itemComponent={({ isSelected, label }) => {
|
|
1084
|
+
const it = groupedHistoryItems.find(i => i.label === label);
|
|
1085
|
+
const isGroup = it?.isGroup;
|
|
1086
|
+
const color = it?.color;
|
|
1087
|
+
return (
|
|
1088
|
+
<Box height={1} overflow="hidden">
|
|
1089
|
+
<Text wrap="truncate" color={isGroup ? 'gray' : (color ? color : (isSelected ? 'cyan' : undefined))}>
|
|
1090
|
+
{isSelected ? '> ' : ' '}{label}
|
|
1091
|
+
</Text>
|
|
1092
|
+
</Box>
|
|
1093
|
+
)
|
|
1094
|
+
}}
|
|
1095
|
+
/>
|
|
1096
|
+
</Box>
|
|
1097
|
+
<ScrollBar total={groupedHistoryItems.length} offset={start} height={MID_H - 2} />
|
|
1098
|
+
<Box position="absolute" bottom={0} left={1} width={VIEW_W - 4}>
|
|
1099
|
+
<Hint>{i18nMap[lang].historyHint}</Hint>
|
|
1100
|
+
</Box>
|
|
1101
|
+
</Box>
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1094
1104
|
|
|
1095
1105
|
// Provider
|
|
1096
1106
|
if (page === 'provider') {
|
|
@@ -1124,7 +1134,7 @@ export const CliMenuManager: React.FC = () => {
|
|
|
1124
1134
|
const sliced = entries.slice(start, start + visible);
|
|
1125
1135
|
return (
|
|
1126
1136
|
<Box width={VIEW_W} height={MID_H} flexDirection="column">
|
|
1127
|
-
<ScrollBar total={entries.length} offset={start} height={visible} />
|
|
1137
|
+
<ScrollBar total={entries.length} offset={start} height={visible - 1} />
|
|
1128
1138
|
{sliced.map(([k, v]) => <Text key={k}>{k}: {typeof v === 'object' ? JSON.stringify(v) : String(v)}</Text>)}
|
|
1129
1139
|
<Hint>
|
|
1130
1140
|
↑/k and ↓/j scroll · {start+1}-{Math.min(start+visible, entries.length)}/{entries.length}
|