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 +1 -1
- package/src/cli/ProviderPanel.tsx +10 -5
- package/src/manager/CliMenuManager.tsx +40 -39
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
|
|
|
@@ -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
|
|
|
@@ -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
|
|
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
|
}
|
|
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
|
-
|
|
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 =
|
|
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}
|
|
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}
|
|
1024
|
-
{loadingMsgs && <
|
|
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}
|
|
1032
|
-
<
|
|
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 &&
|
|
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}
|
|
1042
|
-
<Text color="
|
|
1043
|
-
<Box marginTop={
|
|
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
|
}
|